Organisatie van deployment naar meerdere k8s-omgevingen met helmfile

Helmfile β€” een wrapper voor helm, die het mogelijk maakt om op één plek meerdere helm-releases te beschrijven, hun charts te parameteriseren voor verschillende omgevingen en ook de volgorde van hun deployment vast te stellen.

Over helmfile zelf en voorbeelden van het gebruik ervan kun je lezen in de readme en best practices guide.

We zullen kennismaken met minder voor de hand liggende manieren om releases in helmfile te beschrijven.

Stel dat we een reeks helm-charts hebben (bijvoorbeeld postgres en een backend-applicatie) en verschillende omgevingen (meerdere Kubernetes-clusters, meerdere namespaces of een combinatie van beiden). We pakken helmfile, lezen de documentatie en beginnen onze omgevingen en releases te beschrijven:

    .
    β”œβ”€β”€ envs
    β”‚Β Β  β”œβ”€β”€ devel
    β”‚Β Β  β”‚Β Β  └── values
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ backend.yaml
    β”‚Β Β  β”‚Β Β      └── postgres.yaml
    β”‚Β Β  └── production
    β”‚Β Β      └── values
    β”‚Β Β          β”œβ”€β”€ backend.yaml
    β”‚Β Β          └── postgres.yaml
    └── helmfile.yaml

helmfile.yaml

omgevingen:
  devel:
  production:

releases:
  - naam: postgres
    labels:
      app: postgres
    wait: true
    chart: stable/postgresql
    versie: 8.4.0
    waarden:
      - envs/{{ .Environment.Name }}/values/postgres.yaml
  - naam: backend
    labels:
      app: backend
    wait: true
    chart: private-helm-repo/backend
    versie: 1.0.5
    behoeftes:
      - postgres
    waarden:
      - envs/{{ .Environment.Name }}/values/backend.yaml

We hebben 2 omgevingen gekregen: devel, production β€” in elke omgeving bevinden zich eigen waarden voor de helm-charts van de releases. We zullen ze als volgt deployen:

helmfile -n  -e  apply

Verschillende versies van helm-charts in verschillende omgevingen

Wat te doen als we verschillende versies van de backend in verschillende omgevingen moeten uitrollen? Hoe de versie van de release parameteriseren? De omgevingswaarden, toegankelijk via {{ .Values }}

helmfile.yaml

omgevingen:
  devel:
+   waarden:
+   - charts:
+       versies:
+         backend: 1.1.0
  production:
+   waarden:
+   - charts:
+       versies:
+         backend: 1.0.5
...
  - naam: backend
    labels:
      app: backend
    wait: true
    chart: private-helm-repo/backend
-   versie: 1.0.5
+   versie: {{ .Values.charts.versies.backend }}
...

Verschillende sets applicaties in verschillende omgevingen

Prima, maar wat als we geen production postgres hoeven uit te rollen, omdat we weten dat we geen database in k8s moeten stoppen en we voor de productie een geweldige aparte postgres-cluster hebben? Voor dit probleem hebben we labels (labels).

helmfile -n  -e devel apply
helmfile -n  -e production -l app=backend apply

Dat is geweldig, maar persoonlijk geef ik er de voorkeur aan om te beschrijven welke applicaties in een omgeving moeten worden uitgerold, niet met startup-argumenten, maar in de beschrijvingen van de omgevingen zelf. Wat te doen? We kunnen de releasebeschrijvingen in een aparte map plaatsen, in de omschrijving van de omgeving een lijst maken met de benodigde releases en alleen de benodigde releases 'aanhaken', waarbij de anderen worden genegeerd.

    .
    β”œβ”€β”€ envs
    β”‚   β”œβ”€β”€ devel
    β”‚   β”‚   └── values
    β”‚   β”‚       β”œβ”€β”€ backend.yaml
    β”‚   β”‚       └── postgres.yaml
    β”‚   └── production
    β”‚       └── values
    β”‚           β”œβ”€β”€ backend.yaml
    β”‚           └── postgres.yaml
+   β”œβ”€β”€ releases
+   β”‚   β”œβ”€β”€ backend.yaml
+   β”‚   └── postgres.yaml
    └── helmfile.yaml

helmfile.yaml


  omgevingen:
    devel:
      waarden:
      - charts:
          versies:
            backend: 1.1.0
      - apps:
        - postgres
        - backend

    productie:
      waarden:
      - charts:
          versies:
            backend: 1.0.5
      - apps:
        - backend

- releases:
-    - naam: postgres
-      labels:
-        app: postgres
-      wacht: true
-      chart: stable/postgresql
-      versie: 8.4.0
-      waarden:
-        - envs/{{ .Environment.Name }}/values/postgres.yaml
-    - naam: backend
-      labels:
-        app: backend
-      wacht: true
-      chart: private-helm-repo/backend
-      versie: {{ .Values.charts.versions.backend }}
-      needs:
-        - postgres
-      waarden:
-        - envs/{{ .Environment.Name }}/values/backend.yaml
+ ---
+ bases:
+ {{- range .Values.apps }}
+   - releases/{{ . }}.yaml
+ {{- end }}

releases/postgres.yaml

releases:
  - naam: postgres
    labels:
      app: postgres
    wacht: true
    chart: stable/postgresql
    versie: 8.4.0
    waarden:
      - envs/{{ .Environment.Name }}/values/postgres.yaml

releases/backend.yaml

releases:
  - naam: backend
    labels:
      app: backend
    wacht: true
    chart: private-helm-repo/backend
    versie: {{ .Values.charts.versions.backend }}
    needs:
      - postgres
    waarden:
      - envs/{{ .Environment.Name }}/values/backend.yaml

Notitie

Bij het gebruik van bases: het is noodzakelijk om de yaml-scheidingsteken te gebruiken ---, zodat we releases (en andere delen, zoals helmDefaults) kunnen sjabloniseren met waarden uit omgevingen

In dat geval zal de release postgres zelfs niet in de beschrijving voor productie verschijnen. Erg handig!

Globale waarden voor releases zijn overschrijfbaar

Het is natuurlijk geweldig dat we voor elke omgeving waarden voor helm-charts kunnen opgeven, maar wat als we meerdere omgevingen hebben beschreven en we bijvoorbeeld hetzelfde voor allemaal willen instellen affinity, maar het niet als standaard in de charts zelf willen configureren, die in de repositories worden opgeslagen.

In dat geval zouden we voor elke release 2 bestanden met waarden kunnen opgeven: de eerste met standaardwaarden die de waarden van de chart bepalen, en de tweede met omgevingswaarden die op hun beurt de standaardwaarden zullen overschrijven.

    .
    β”œβ”€β”€ envs
+   β”‚Β Β  β”œβ”€β”€ default
+   β”‚Β Β  β”‚Β Β  └── values
+   β”‚Β Β  β”‚Β Β      β”œβ”€β”€ backend.yaml
+   β”‚Β Β  β”‚Β Β      └── postgres.yaml
    β”‚Β Β  β”œβ”€β”€ devel
    β”‚Β Β  β”‚Β Β  └── values
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ backend.yaml
    β”‚Β Β  β”‚Β Β      └── postgres.yaml
    β”‚Β Β  └── production
    β”‚Β Β      └── values
    β”‚Β Β          β”œβ”€β”€ backend.yaml
    β”‚Β Β          └── postgres.yaml
    β”œβ”€β”€ releases
    β”‚Β Β  β”œβ”€β”€ backend.yaml
    β”‚Β Β  └── postgres.yaml
    └── helmfile.yaml

releases/backend.yaml

releases:
  - name: backend
    labels:
      app: backend
    wait: true
    chart: private-helm-repo/backend
    version: {{ .Values.charts.versions.backend }}
    needs:
      - postgres
    values:
+     - envs/default/values/backend.yaml
      - envs/{{ .Environment.Name }}/values/backend.yaml

envs/default/values/backend.yaml

affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 1
      podAffinityTerm:
        labelSelector:
          matchExpressions:
          - key: app.kubernetes.io/name
            operator: In
            values:
            - backend
        topologyKey: "kubernetes.io/hostname"

Globale waarden definiΓ«ren voor helm charts van alle releases op omgevingsniveau

Stel dat we in verschillende releases meerdere ingress maken β€” we zouden handmatig voor elke chart kunnen definiΓ«ren hosts:, maar in ons geval is het domein hetzelfde, waarom zouden we dat niet in een globale variabele kunnen onderbrengen en gewoon de waarde ervan in de charts kunnen gebruiken? De bestanden met waarden die we willen parameteriseren, moeten de extensie hebben .gotmpl, zodat helmfile weet dat ze door de sjabloondel moeten.

    .
    β”œβ”€β”€ envs
    β”‚Β Β  β”œβ”€β”€ default
    β”‚Β Β  β”‚Β Β  └── values
-   β”‚Β Β  β”‚Β Β      β”œβ”€β”€ backend.yaml
-   β”‚Β Β  β”‚Β Β      β”œβ”€β”€ postgres.yaml
+   β”‚Β Β  β”‚Β Β      β”œβ”€β”€ backend.yaml.gotmpl
+   β”‚Β Β  β”‚Β Β      └── postgres.yaml.gotmpl
    β”‚Β Β  β”œβ”€β”€ devel
    β”‚Β Β  β”‚Β Β  └── values
    β”‚Β Β  β”‚Β Β      β”œβ”€β”€ backend.yaml
    β”‚Β Β  β”‚Β Β      └── postgres.yaml
    β”‚Β Β  └── production
    β”‚Β Β      └── values
    β”‚Β Β          β”œβ”€β”€ backend.yaml
    β”‚Β Β          └── postgres.yaml
    β”œβ”€β”€ releases
    β”‚Β Β  β”œβ”€β”€ backend.yaml
    β”‚Β Β  └── postgres.yaml
    └── helmfile.yaml

helmfile.yaml

  omgevingen:
    devel:
      values:
      - charts:
          versions:
            backend: 1.1.0
      - apps:
        - postgres
        - backend
+     - global:
+         ingressDomain: k8s.devel.domain

    production:
      values:
      - charts:
          versions:
            backend: 1.0.5
      - apps:
        - backend
+     - global:
+         ingressDomain: production.domain
  ---
  bases:
  {{- range .Values.apps }}
    - releases/{{ . }}.yaml
  {{- end }}

envs/default/values/backend.yaml.gotmpl

ingress:
  enabled: true
  paths:
    - /api
  hosts:
    - {{ .Values.global.ingressDomain }}

envs/default/values/postgres.yaml.gotmpl

ingress:
  enabled: true
  paths:
    - /
  hosts:
    - postgres.{{ .Values.global.ingressDomain }}

Notitie

Het is duidelijk dat ingress in de Postgres-chart iets zeer twijfelachtig is, daarom wordt dit in het artikel simpelweg gegeven als een sferisch voorbeeld in een vacuΓΌm en om te voorkomen dat er een nieuwe release in het artikel wordt opgenomen alleen voor de beschrijving van ingress.

Vervanging van geheimen (secrets) vanuit omgevingswaarden.

Op dezelfde manier als het bovenstaande voorbeeld kunnen ook versleutelde waarden worden ingevoegd met behulp van. helm secrets waarden. In plaats van voor elke release een apart secrets-bestand aan te maken, waarin de versleutelde waarden voor de chart worden gedefinieerd, kunnen we eenvoudig de waarden definiΓ«ren in de release default.yaml.gotmpl, die worden gehaald uit de variabelen die op omgevingsniveau zijn opgegeven. En de waarden die we niet verborgen hoeven te houden, kunnen we rustig overschrijven in de release-waarden in de specifieke omgeving.

    .
    β”œβ”€β”€ envs
    β”‚   β”œβ”€β”€ default
    β”‚   β”‚   └── values
    β”‚   β”‚       β”œβ”€β”€ backend.yaml
    β”‚   β”‚       └── postgres.yaml
    β”‚   β”œβ”€β”€ devel
    β”‚   β”‚   β”œβ”€β”€ values
    β”‚   β”‚   β”‚   β”œβ”€β”€ backend.yaml
    β”‚   β”‚   β”‚   └── postgres.yaml
+   β”‚   β”‚   └── secrets.yaml
    β”‚   └── production
    β”‚       β”œβ”€β”€ values
    β”‚       β”‚   β”œβ”€β”€ backend.yaml
    β”‚       β”‚   └── postgres.yaml
+   β”‚       └── secrets.yaml
    β”œβ”€β”€ releases
    β”‚   β”œβ”€β”€ backend.yaml
    β”‚   └── postgres.yaml
    └── helmfile.yaml

helmfile.yaml

  omgevingen:
    devel:
      waarden:
      - charts:
          versies:
            backend: 1.1.0
      - apps:
        - postgres
        - backend
      - global:
          ingressDomain: k8s.devel.domain
+     secrets:
+       - envs/devel/secrets.yaml

    productie:
      waarden:
      - charts:
          versies:
            backend: 1.0.5
      - apps:
        - backend
      - global:
          ingressDomain: production.domain
+     secrets:
+       - envs/production/secrets.yaml
  ---
  bases:
  {{- range .Values.apps }}
    - releases/{{ . }}.yaml
  {{- end }}

envs/devel/secrets.yaml

secrets:
    elastic:
        password: ENC[AES256_GCM,data:hjCB,iv:Z1P6/6xBJgJoKLJ0UUVfqZ80o4L84jvZfM+uH9gBelc=,tag:dGqQlCZnLdRAGoJSj63rBQ==,type:int]
...

envs/production/secrets.yaml

secrets:
    elastic:
        password: ENC[AES256_GCM,data:ZB/VpTFk8f0=,iv:EA//oT1Cb5wNFigTDOz3nA80qD9UwTjK5cpUwLnEXjs=,tag:hMdIUaqLRA8zuFBd82bz6A==,type:str]
...

envs/default/values/backend.yaml.gotmpl

elasticsearch:
  host: elasticsearch
  port: 9200
  password: {{ .Values | getOrNil "secrets.elastic.password" | default "password" }}

envs/devel/values/backend.yaml

elasticsearch:
  host: elastic-0.devel.domain

envs/production/values/backend.yaml

elasticsearch:
  host: elastic-0.production.domain

Notitie

Overigens, getOrNil β€” een speciale functie voor Go-templates in helmfile, die, zelfs als .Values.secrets niet bestaat, geen fout zal geven, maar in staat zal stellen om uiteindelijk met behulp van de functie default een standaardwaarde in te voegen.

Conclusie

De beschreven zaken lijken vrij voor de hand liggend, maar informatie over een handige beschrijving van een deployment in meerdere omgevingen met behulp van helmfile is beperkt, en ik hou van IaC (Infrastructure-as-Code) en wil een duidelijke beschrijving van de staat van de deployment hebben.

Tot slot wil ik toevoegen dat de variabelen voor de default omgeving kunnen worden geparameteriseerd met de omgevingsvariabelen van een bepaalde runner, van waaruit de deployment zal worden uitgevoerd, en zo dynamische omgevingen kunnen worden verkregen.

helmfile.yaml

omgevingen:
  standaard:
    waarden:
    - globaal:
        clusterDomein: {{ env "CLUSTER_DOMAIN" | default "cluster.local" }}
        ingressDomein: {{ env "INGRESS_DOMAIN" }}

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers πŸ”₯ Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster