Skip to content

Checklist

[[TOC]]

Dieser Leitfaden soll dabei unterstützen, einen eigenen Service mithilfe von ohMyHelm bereitzustellen.

Welche Services kann ich mit diesem Leitfaden bereitstellen?

  • Pullen eigener Container aus einer Privaten Container Registry
  • Einfache webservices mit/ohne persistenz mit/ohne Ingress
  • Hinzufügen von Konfigurationen (Als Secrets / Configs)
  • Bereitstellen der Secrets / Configs als Datei innerhalb des Pods
  • Migrationen von Datenbanken mithilfe von Jobs/initContainer
  • "Verstecken" der Anwendung in ein "Sidecar" mit vorgeschaltetem nginx. TODO

Bevor es losgeht

Erstelle einen Ordner checklist

Erstelle folgende dateien im Ordner checklist:

  • Chart.yaml
  • values.yaml

Füge folgenden Inhalt in die Chart.yaml

apiVersion: v2
name: checklist
description: My first checklist
type: application
version: 0.1.0
appVersion: "1.16.0"

dependencies:
  - name: ohmyhelm
    alias: myservice
    repository: https://gitlab.com/api/v4/projects/28993678/packages/helm/stable
    version: 1.17.0
    condition: myservice.enabled

Basics

enable the chart function

Füge folgenden Codeabschnitt in die values.yaml damit die chart funktion für unseren erster Service myservice aktiviert wird.

myservice:
  enabled: true
  chart:
    enabled: true

In den nächsten Schritten werden wir weitere Codeabschnitte zu unserer values.yaml hinzufügen und unseren Service Stück für Stück aufbauen.

Welche art von Service möchtest du installieren (Deployment, Statefulset or Daemonset)?

Um die Entscheidung für den Anfang etwas zu erleichtern, hier ein paar Hinweise. HINWEIS: Deployments können auch mit Persistenzen ausgestattet werden. Wir möchten aber hier nicht zu tief ins Detail gehen, da es sonst den Rahmen dieses Leitfadens sprengen würde. Diese Liste dient lediglich als Orientierung.

  • Deployment (Keine Persistenz, kann auf 1 bis N nodes laufen)
  • Statefulset (Mit Persistenz, läuft auf einer node)
  • Daemonset (Keine Persistenz, läuft einmal auf allen nodes)
myservice:
  chart:
    # Folgende auswahl ist möglich, wobei nur ein Eintrag benötigt wird. Siehe nächsten Code Abschnitt.
    deployment: true
    statefulset: false
    daemonset: false
myservice:
  chart:
    # ...
    statefulset: true

Wie soll dein Service heißen?

myservice:
  chart:
    # ...
    fullnameOverride: "nodered"

Benötigt dein Service einen Dynamischen Port Range?

myservice:
  chart:
    # Das ist der Standardwert. Diesen Eintrag benötigen wir nur wenn wir ihn auf `true`setzen.
    hostNetwork: false

Image Pullen

Um aus einer privaten Container Registry zu pullen, müssen in Kubernetes spezielle Secrets angelegt werden. Hierfür können wir den imageCredentials helper verwenden.

# `imageCredentials` kann mehrere Container Registry Secrets anlegen und wird daher nur einmal in der values.yaml benötigt.
# Wenn ein Secret in einem anderen Namespace benötigt wird, muss man dies explizit angeben, Ansonsten werden die Secrets in dem Namepsace installiert, den man mit `helm upgrade --install -n "mychecklist"` angibt.
imageCredentials:
  - name: my-container-registry
    registry: https://mydockerregistry.example.com
    username: "myname"
    accessToken: "mytoken"
  - name: my-second-container-registry
    # Folgendes Secret wird im `dev` Namespace angelegt. Wenn es diesen Namespace nicht gibt, schlägt das Deployment fehl. Siehe `HELPER` namespaces.
    namespace: dev
    registry: https://my2dockerregistry.example.com
    username: "myname"
    accessToken: "mytoken"

Die image Secrets können nun im Service hinterlegt werden

myservice:
  chart:
    imagePullSecrets: 
      - name: my-container-registry

Aktiviere Prometheus Metriken für diesen Service

myservice:
  chart:
    # this will add an applabel with the servicename for prometheus
    applabel: true
    # the name in `chart.fullnameOverride` and `monitoring[0].name must match`
    monitoring: 
      - enabled: true
        name: nodered
        release: prometheus
        endpoints:
          - port: http
            interval: 15s
            path: merics
            scheme: http

container

Dies ist die Standardkonfiguration eines containers in ohMyHelm

myservice:
  chart:
    # ...
    container:
      # Add your container image here
      image: mydockerregistry.example.com/mycusomservice:latest
      # ohMyHelm default values. Remove unnecessary parts if you doesn't need them.
      # When disabling ports: you should also diable the service. (See "## Service")
      # When changing or adding new entries to ports (name, protocoll), remember to add them to your service too. (See "## Service")
      ports:
        - name: http
          containerPort: 80
          protocol: TCP
      imageConfig:
        pullPolicy: IfNotPresent
      securityContext: {}
      command: []
      args: []
      env: []
      extraEnv: []
      envFrom: []

Im nächsten Beispiel deaktivieren wir die Ports, da unser container nur ausgehend kommuniziert. Wir benötigen aber Environments, welche in configMaps und Secrets hinterlegt sind.

myservice:
  chart:
    # ...
    container:
      # Add your container image here
      image: mydockerregistry.example.com/mycusomservice:latest
      # Disable ports
      ports: []
      env:
        - name: foo
          value: "bar"
          # Load a single environment from an config map. See next code block.
        - name: DEBUGLEVEL
          valueFrom: 
            configMapKeyRef:
              name: anotherconfig
              key: debuglevel
          # Load a single secret to your environment. See next code block.
        - name: SECRET
          valueFrom:
            secretKeyRef:
              name: allsecrets
              key: myservicesecret
      # Load multiple environments from an config map. See next code block.
      envFrom:
        - configMapRef:
          name: specialconfig

ConfigMaps und Secrets anlegen

Im vorherigen Beispiel referenzieren wir in den Environments auf ein Secret und eine ConfigMap. Diese können wir wie folgt anlegen.

HINWEIS: Wir können Secret und ConfigMaps auch als Dateien innerhalb eines Containers bereitstellen. Dieser Teil wird später im Leitfaden erwähnt.

myservice:
  chart:
    # Config CHART helper
    configs:
      - name: specialconfig
        values:
          BAR: foo
          TEST: "1"
      - name: anotherconfig
        values:
          debuglevel: WARN
          config.yaml: |
            number: "1"
            foo:
              bar:
                - 1
                - 2
                - 3
    # Secret CHART helper
    secrets:
      - name: allsecrets
        values:
          myservicesecret: "admin;-)"
          mytoken: "******"
          secret.file: |
            somesensitive content.
            FOO:BAR 1,2,3

liveness and readiness probe

Standardmäßig sind livenessProbe und readinessProbe nicht gesetzt. Die konfiguration erfolgt wie in den Kubernetes docs beschrieben wird.

Beispiele:

myservice:
  chart:
    # ...
    container:
      # ...
      livenessProbe:
        initialDelaySeconds: 120
        failureThreshold: 3
        periodSeconds: 10
        timeoutSeconds: 3
        httpGet:
          path: /healthz
          port: http
myservice:
  chart:
    # ...
    container:
      # ...
      readinessProbe:
        initialDelaySeconds: 120
        failureThreshold: 3
        periodSeconds: 10
        timeoutSeconds: 3
        httpGet:
          path: /healthz
          port: http

Service

Dies ist das Kubernetes Service Objekt. Wenn Anpassungen an chart.container.port vorgenommen wurden, muss targetPort entsprechend angepasst werden.

myservice:
  chart:
    # ...
    service:
      # This is the default configuration. You don't need to add them.
      type: ClusterIP
      #clusterIP: 1.2.3.4
      #selector:
      #  app.kubernetes.io/name: MyApp
      ports:
        - port: 80
          targetPort: http
          protocol: TCP
          name: http

Die Erstellung des Service kann wie folgt deaktiviert werden

myservice:
  chart:
    # ...
      service: []

initContainer

Dies ist die Standard Konfiguration des initContainer.

Der initContainer ist Standardmäßig deaktiviert und nutzt eine wait-for-job image ohmyhelm-job-helper.

Anwendungsbereich für diesen initContainer Datenbankmigration

Wird ein job verwendet um eine Datenbankmigration durchzuführen, wartet der initContainer solange bis der job einen exitstatus 0 ausgibt. Anschließend beendet sich der initContainer und der eigentliche container im Pod startet.

Um dieses Feature zu nutzen, muss deer Wert yourjobnamehere mit dem Namen in chart.fullnameOverride ersetzt werden und job und rbac aktiviert werden. rbac sorgt dafür, dass der initContainer auf die Informationen des job zugreifen darf.

Einschränkungen initContainer:

  • Man kann keinen Port hinzufügen
  • Man kann keine Volumes mounten
myservice:
  chart:
    # ...
    initContainer:
      enabled: false
      # Add your container image here
      image: registry.gitlab.com/ayedocloudsolutions/ohmyhelm-job-helper:1.0.0
      imageConfig:
        pullPolicy: IfNotPresent
      securityContext: {}
      command: []
      args:
        - "job"
        - "yourjobnamehere"
      env: []
      extraEnv: []
      envFrom: []

job (container)

Dies ist die Standard Konfiguration des job.

Der job ist Standardmäßig deaktiviert. Wenn keine Image gesetzt wurde, wird die image aus chart.container.image geladen. Mit args können wir dem Container Parameter mitgeben. Bspw.: args: ['miration'].

rbac muss hinzugefügt werden, wenn der ohmyhelm-job-helper im initContainer verwendet wird.

Jobs werden normalerweise nicht gelöscht un bleiben bestehen. Dieses verhalten kann durch setzen von true bei chart.job.removejob.enabled angepasst werden.

myservice:
  chart:
    # ...
    job:
      enabled: false
      # Add your container image here. If no image is set `chart.container.image` will be used.
      #image:
      imageConfig:
        pullPolicy: IfNotPresent
      securityContext: {}
      command: []
      args: []
      env: []
      extraEnv: []
      envFrom: []
      restartPolicy: Never
      removejob:
        enabled: false
        ttlSecondsAfterFinished: 60
      activeDeadlineSeconds: 1200
      backoffLimit: 20

Role based access control

Dies ist die Standard Konfiguration des rbac.

rbac ist Standardmäßig deaktiviert.

myservice:
  chart:
    # ...
    rbac:
      enabled: false
      roleRules:
        - apiGroups: ["", "batch"]
          resources: ["*"]
          verbs: ["*"]

Create an ingress

Es gibt zwei arten von ingress in ohMyHelm

  • ingress Konfiguration des ingress ab spec: see Kubernetes docs ingress
  • ingressSimple Kompakte Konfigurtion mit einem host und einem tls eintrag.

ingress

myservice:
  chart:
    # ...
    ingress:
      enabled: false
      annotations: {}
      tls:
        - hosts:
            - my.example.com
          secretName: example-tls
      # You can add multiple hosts / paths
      hosts:
        - host: my.example.com
          http:
            paths:
            - path: /
              # pathType Supported Kubernetes >= v1.19 [stable]
              pathType: Prefix
              backend:
                serviceName: your-service-name-here
                servicePort: http

simpleIngress

myservice:
  chart:
    # ...
    ingressSimple:
      enabled: false
      annotations: {}
      host: my.example.com
      tlsSecretName: example-tls
      pathType: Prefix
      path: /

Beispiel mit annotations:

myservice:
  chart:
    # ...
    ingressSimple:
      enabled: false
      annotations:
        kubernetes.io/ingress.class: nginx
        kubernetes.io/tls-acme: "true"
        cert-manager.io/cluster-issuer: "letsencrypt-staging"
        nginx.ingress.kubernetes.io/rewrite-target: /$2
        nginx.ingress.kubernetes.io/configuration-snippet: |
          rewrite ^(/myservice)$ $1/ redirect;
        nginx.ingress.kubernetes.io/x-forwarded-prefix: "/myservice"
      host: my.example.com
      tlsSecretName: example-tls
      pathType: Prefix
      path: /myservice(/|$)(.*)

Persistenz einrichten

In einem Zukunftigen release wird dieser Part optimiert. Es wird lediglich eine persistance geben.

Je nach Auswahl des Typs unter chart.deployment, chart.statefulset oder chart.daemonset muss das entsprechende *Volume ausgewählt werden.

  • statefulsetVolume:
  • deploymentVolume: (PVC (=PersistentVolumeClaim) muss seperat angelegt werden)
  • daemonsetVolume: (PVC muss seperat angelegt werden)
  • (Noch nicht implementiert. Fasst *Volume zusammen) persistance:

Die Konfiguration erfolgt unter:

statefulsetVolume

myservice:
  chart:
    # ...
    statefulsetVolume:
      volumeClaimTemplates: []
      volumeMounts: []
      volumes: []

Eine PersistentVolumeClaim hinzufügen:

myservice:
  chart:
    # ...
    statefulsetVolume:
      volumeClaimTemplates:
        - metadata:
            name: "database-data"
          spec:
            accessModes: [ReadWriteOnce]
            resources:
              requests:
                storage: 5Gi
      volumeMounts:
        - name: database-data
          mountPath: /app/database
          subPath:

deploymentVolume und daemonsetVolume

Bei deploymentVolume und daemonsetVolume legt Kubernetes Standardmäßig keine PVC an. Hier muss mit dem manifests helper diese Claim manuell erzeugt werden.

myservice:
  chart:
    # ...
    daemonsetVolume:
      volumeMounts: []
      volumes: []
# Use mainfests helper to create the pvc
manifests: []

PVC für ein Deployment anlegen

myservice:
  chart:
    # ...
    deploymentVolume:
      volumeMounts:
        - name: filesystem-data
          mountPath: /app/filesystem
          subPath:
      volumes: []
        - name: filesystem-data
          persistentVolumeClaim:
            claimName: filesystem-data-deployment
# Use mainfests helper to create the pvc
manifests:
  - kind: PersistentVolumeClaim
    apiVersion: v1
    content:
      metadata:
        name: filesystem-data-deployment
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 100Gi
        storageClassName: filesystem-data-deployment

persistance (Nich implementiert)

Stadardmäßig wird bei einem statefulset eine PVC angelegt. Ein deployment und ein daemonset haben diese Funktion nicht.

Mit persistance: kann man eine PVC zu einem deployment, daemonset oder statefulset hinzufügen.

Die Konfiguration erfolgt unter:

Standard Konfiguration

myservice:
  chart:
    # ...
    persistance:
      volumeClaimTemplates: []
      volumeMounts: []
      volumes: []

Beispiel: daemon.json im container mit einer Configmap überschreiben

myservice:
  chart:
    # ...
    persistance:
      volumeClaimTemplates: []
      volumeMounts:
        - name: daemon-json
          mountPath: /etc/docker/daemon.json
          subPath: daemon.json
      volumes:
        - name: daemon-json
          configMap:
            name: daemon-json
    configs:
      - name: daemon-json
        values:
          daemon.json: |
            {
              "mtu": 1350
            }

Beispiel: PVC hnzufügen

myservice:
  chart:
    # ...
    persistance:
      volumeClaimTemplates:
        - metadata:
            name: "database-data"
          spec:
            accessModes: [ReadWriteOnce]
            resources:
              requests:
                storage: 5Gi
      volumeMounts:
        - name: database-data
          mountPath: /app/database
          subPath:
      volumes: []

TODO replica, serviceaccount, etc

Dieser Abschnitt ist nich in Arbeit.

myservice:
  chart:
    # ...
    replicaCount: 1
    podAnnotations: {}
    podSecurityContext: {}
    serviceAccount:
      create: true
      annotations: {}
      name: ""
    resources: {}
    autoscaling:
      enabled: false
      minReplicas: 1
      maxReplicas: 100
      targetCPUUtilizationPercentage: 80
      targetMemoryUtilizationPercentage: 80
    nodeSelector: {}
    tolerations: []
    affinity: {}

Das erstellte Chart installieren

helm dep update
helm upgrade --install -n "mychecklist" --create-namespace my-checklist .

Beispiele

Ein einfacher Service

  • Pod und Service lauschen auf Port 80 (ohMyHelm Default)
  • Wir benötigen ein Service Objekt (ohMyHelm Default)
  • Registry ist öffentlich. (ohMyHelm Default)

Chart.yaml

apiVersion: v2
name: my-application
description: my simple apllication
type: application
version: 1.2.2
appVersion: "1.3.5"

dependencies:
  - name: ohmyhelm
    alias: myservice
    repository: https://gitlab.com/api/v4/projects/28993678/packages/helm/stable
    version: 1.15.2
    condition: myservice.enabled

values.yaml

myservice:
  enabled: true
  chart:
    enabled: true
    deployment: true 
    fullnameOverride: "myservice"
    container:
      image: my-public-registry/my-image:1.3.5

Ein Komplexer Service

Unser Deployment benötigt ein volume mit "ReadWriteMany". Ein vorhandener CSI unterstütz dies aber nicht. Daher vewenden wir als CSI nfs-subdir-external-provisioner und installieren einen nfs-server ins Cluster.

WARNUNG: Dieses Szenario ist nicht für den Produktivbetrieb geeiget und dient lediglich als demonstration. Ein NFS Server sollte nicht im Cluster betrieben werden und die NFS Mounts sollten manuell und nicht durch nfs-subdir-external-provisioner erfolgen.

myservice:

  • Pod und Service lauschen auf Port 80 (ohMyHelm Default)
  • Registry ist NICHT öffentlich. imageCredentails müssen ngelegt werden( Siehe dependency secretsconfigs)
  • Wir benötigen eine Zentrale configMap for unsere environments (Siehe dependency secretsconfigs)
  • Wir benötigen volumes und eine PVC für unser Deployment. Volumes müssen in der lage sein readWriteMany operationen durchführen zu können, da wir einen replicaCount von 10 benötgien (Siehe dependency myservice)

nfs:

  • we need multiple ports
  • securityContext set to privileged: true
  • We need volumes and PVC for our statefulset

Chart.yaml

apiVersion: v2
name: my-application
description: myapplication needs ReadWriteMany
type: application
version: 1.2.3
appVersion: "1.3.5"

dependencies:
  - name: secretsconfigs
    alias: myservice
    repository: https://gitlab.com/api/v4/projects/28993678/packages/helm/stable
    version: 1.15.2
    condition: secretsconfigs.enabled
  - name: ohmyhelm
    alias: myservice
    repository: https://gitlab.com/api/v4/projects/28993678/packages/helm/stable
    version: 1.15.2
    condition: myservice.enabled
  - name: ohmyhelm
    alias: nfs
    repository: https://gitlab.com/api/v4/projects/28993678/packages/helm/stable
    version: 1.15.2
    condition: nfs.enabled
  - name: nfs-subdir-external-provisioner
    repository: https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner
    version: 4.0.16

values.yaml

secretsconfigs:
  imageCredentials:
    - name: my-container-registry
      registry: https://mydockerregistry.example.com
      username: "myname"
      accessToken: "mytoken"
  # # ohMyHelm config Helper for a single config
  # config:
  #   enabled: true
  #   name: myspecialconfig
  #     values:
  #       ENV: prod
  #       DEBUG: info
  #       START_WITH: "2022"
  # # ohMyHelm config Helper for multiple configs
  configs:
    - name: myspecialconfig
      values:
        ENV: prod
        DEBUG: info
        START_WITH: "2022"

myservice:
  enabled: true
  chart:
    enabled: true
    deployment: true 
    replicaCount: 10
    fullnameOverride: "myservice"
    imagePullSecrets:
      - name: my-registry
    container:
      image: my-registry/my-image:1.3.5
      envFrom:
        - configMapRef:
            name: myspecialconfig
    deploymentVolume:
      volumeMounts:
        - name: nfs-volume
          mountPath: /data
      volumes:
        - name: nfs-volume
          persistentVolumeClaim:
            claimName: data-service-nfs
    # # ohMyHelm Chart config helper for multiple configs
    # configs:
    #   - name: myspecialconfig
    #     values:
    #       ENV: prod
    #       DEBUG: info
    #       START_WITH: "2022"
  # We need to add an PVC for the Deployment
  manifests:
    - kind: PersistentVolumeClaim
      apiVersion: v1
      content:
        metadata:
          name: data-service-nfs
        spec:
          accessModes:
            - ReadWriteMany
          resources:
            requests:
              storage: 100Gi
          storageClassName: nfs-client

nfs:
  enabled: true
  chart:
    enabled: true
    statefulset: true 
    fullnameOverride: "nfs"
    container:
      image: k8s.gcr.io/volume-nfs:0.8
      securityContext:
        privileged: true
      args:
        - /app/data
      ports:
        - name: nfs
          containerPort: 2049
        - name: mountd
          containerPort: 20048
        - name: rpcbind
          containerPort: 111
    service:
      type: ClusterIP
      clusterIP: 10.43.249.55
      ports:
        - port: 2049
          name: nfs
          targetPort: nfs
        - port: 20048
          targetPort: mountd
          name: mountd
        - port: 111
          targetPort: rpcbind
          name: rpcbind
    statefulsetVolume:
      volumeMounts:
        - name: data-nfs
          mountPath: /app/data
      volumeClaimTemplates:
        - metadata:
            name: "data-nfs"
          spec:
            accessModes: [ReadWriteOnce]
            resources:
              requests:
                storage: 10Gi

nfs-subdir-external-provisioner:
  storageClass:
    name: nfs-client
  nfs:
    server: 10.43.249.55
    path: "/"
    mountOptions:
      - nolock
      - nfsvers=4.2