Kubernetes ConfigMaps: Nuancen zu wissen

Hinweis : Dies ist kein vollständiger Leitfaden, sondern eine Erinnerung / ein Hinweis für diejenigen, die ConfigMap bereits in Kubernetes verwenden oder gerade ihre Anwendung für die Arbeit darin vorbereiten.



Hintergrund: Von rsync zu ... Kubernetes


Was ist vorher passiert? Im Zeitalter der „klassischen Administration“ wurde in der einfachsten Version die Konfigurationsdatei direkt neben den Anwendungen (oder, wenn Sie möchten, im Repository) platziert. Es ist ganz einfach: Wir machen eine elementare Lieferung (CD) für unseren Code zusammen mit der Konfiguration. Sogar eine bedingte rsync-Implementierung kann als die Grundlagen einer CD bezeichnet werden.

Als die Infrastruktur wuchs, waren unterschiedliche Konfigurationen für unterschiedliche Umgebungen erforderlich (Entwickler / Bühne / Produktion). Die Anwendung wurde geschult, um zu verstehen, welche Konfiguration verwendet werden soll, und sie als Argumente zum Starten oder als Umgebungsvariablen in der Umgebung übergeben. Noch mehr CDs werden durch das Aufkommen solch nützlicher Köche / Puppen / Ansible kompliziert. Rollen werden auf Servern angezeigt, und Umgebungen werden an verschiedenen Stellen nicht mehr beschrieben - wir kommen zu IaC (Infrastruktur als Code).

Was folgte? Wenn es möglich war, Kubernetes entscheidende Vorteile für sich selbst zu erkennen und sich sogar mit der Notwendigkeit abzufinden, Anwendungen so zu ändern, dass sie in dieser Umgebung funktionieren, kam es zu einer Migration. Unterwegs erwartete ich viele Nuancen und Unterschiede in der Konstruktion der Architektur, aber als ich den Hauptteil bewältigen konnte, lief die lang erwartete Anwendung in K8s.

Sobald wir hier sind, können wir weiterhin die im Repository neben der Anwendung vorbereiteten Konfigurationen verwenden oder ENV an den Container übergeben. Zusätzlich zu diesen Methoden sind jedoch auch ConfigMaps verfügbar . Mit diesem K8-Grundelement können Sie Go-Vorlagen in Konfigurationen verwenden, d. H. Rendern Sie sie wie HTML-Seiten und laden Sie die Anwendung neu, wenn Sie die Konfiguration ohne Neustart ändern. Mit ConfigMaps müssen nicht mehr 3+ Konfigurationen für verschiedene Umgebungen gespeichert und die Relevanz der einzelnen Umgebungen verfolgt werden.

Eine allgemeine Einführung in ConfigMaps finden Sie beispielsweise hier . In diesem Artikel werde ich mich auf einige Funktionen der Arbeit mit ihnen konzentrieren.

Einfache ConfigMaps


Wie sahen Konfigurationen in Kubernetes aus? Was haben sie von den Go-Vorlagen bekommen? Hier ist beispielsweise eine normale ConfigMap für eine Anwendung, die aus einem Helmdiagramm bereitgestellt wird:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app
data:
  config.json: |
    {
      "welcome": {{ pluck .Values.global.env .Values.welcome | quote }},
      "name": {{ pluck .Values.global.env .Values.name | quote }}
    }

Hier werden die Werte ersetzt .Values.welcomeund .Values.nameaus der Datei übernommen values.yaml. Warum genau von values.yaml? Wie funktioniert die Go Template Engine? Wir haben bereits über diese Details genauer gesprochen hier .

Der Anruf pluckhilft bei der Auswahl der erforderlichen Linie aus der Karte:

$ cat .helm/values.yaml 
welcome:
  production: "Hello"
  test: "Hey"
name:
  production: "Bob"
  test: "Mike"

Darüber hinaus können Sie sowohl bestimmte Zeilen als auch ganze Fragmente der Konfiguration verwenden.

ConfigMap könnte beispielsweise folgendermaßen aussehen:

data:
  config.json: |
    {{ pluck .Values.global.env .Values.data | first | toJson | indent 4 }}

... und in values.yaml- den folgenden Inhalten:

data:
  production:
    welcome: "Hello"
    name: "Bob"

Hier global.envgeht es um den Namen der Umgebung. Wenn Sie diesen Wert während der Bereitstellung ersetzen, können Sie ConfigMaps mit unterschiedlichen Inhalten rendern. firsthier gebraucht, weil pluckGibt eine Liste zurück, deren erstes Element den gewünschten Wert enthält.

Wenn es viele Konfigurationen gibt


Eine ConfigMap kann mehrere Konfigurationsdateien enthalten:

data:
  config.json: |
    {
      "welcome": {{ pluck .Values.global.env .Values.welcome | first | quote }},
      "name": {{ pluck .Values.global.env .Values.name | first | quote }}
    }
  database.yml: |
    host: 127.0.0.1
    db: app
    user: app
    password: app

Sie können sogar jede Konfiguration separat bereitstellen:

        volumeMounts:
        - name: app-conf
          mountPath: /app/configfiles/config.json
          subPath: config.json
        - name: app-conf
          mountPath: /app/configfiles/database.yml
          subPath: database.yml

... oder alle Konfigurationen auf einmal mit dem Verzeichnis abholen:

        volumeMounts:
        - name: app-conf
          mountPath: /app/configfiles

Wenn Sie die Beschreibung der Bereitstellungsressource während der Bereitstellung ändern, erstellt Kubernetes ein neues ReplicaSet, verringert das alte auf 0 und erhöht das neue auf die angegebene Anzahl von Replikaten. (Dies gilt für die Bereitstellungsstrategie RollingUpdate.)

Solche Aktionen führen zur Neuerstellung des Pods mit einer neuen Beschreibung. Zum Beispiel: Es gab ein Bild image:my-registry.example.com:v1, wurde aber - image:my-registry.example.com:v2. Dabei spielt es keine Rolle, was genau wir in der Beschreibung unserer Bereitstellung geändert haben: Hauptsache, dies hat dazu geführt, dass das replicaSet (und damit der Pod) neu erstellt wurde. In diesem Fall wird die neue Version der Konfigurationsdatei in der neuen Version der Anwendung automatisch bereitgestellt, und es tritt kein Problem auf.

ConfigMap-Änderungsantwort


Bei Änderungen in ConfigMap können vier Ereignisszenarien folgen. Betrachten Sie sie:

  1. : ConfigMap, subPath.
    : .
  2. : ConfigMap, pod.
    : pod .
  3. : ConfigMap Deployment -.
    : , ConfigMap’, Deployment, pod , — .
  4. : ConfigMap, .
    : pod’ / pod’.

Wir werden genauer analysieren.

Szenario 1


Wir haben nur ConfigMap korrigiert? Die Anwendung wird nicht neu gestartet. Bei der Montage durch subPathwerden keine Änderungen vorgenommen, bis der Pod manuell neu gestartet wird.

Alles ist einfach: Kubernetes stellt unsere ConfigMap in einem Pod einer bestimmten Version der Ressource bereit. Da es mit gemountet ist subPath, wird kein zusätzlicher "Einfluss" auf die Konfiguration mehr bereitgestellt.

Szenario 2


Sie können die Datei nicht aktualisieren, ohne den Pod neu zu erstellen? Okay, wir haben 6 Replikate in Deployment, sodass wir abwechselnd alles manuell erledigen können delete pod. Wenn sie dann neue Pods erstellen, werden sie die neue Version von ConfigMap "abholen".

Szenario 3


Sind Sie es leid, solche Vorgänge manuell auszuführen? Eine Lösung für dieses Problem finden Sie in den Tipps und Tricks zu Helm :

kind: Deployment
spec:
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
[...]

Daher wird der spec.templateHash der gerenderten Annotationskonfiguration einfach in die pod ( ) - Vorlage geschrieben .

Anmerkungen sind beliebige Schlüsselwertfelder, in denen Sie Ihre Werte speichern können. Wenn Sie sie in der Vorlage des spec.templatezukünftigen Pods registrieren , fallen diese Felder in das ReplicaSet und den Pod selbst. Kubernetes wird feststellen, dass sich die Pod-Vorlage geändert hat (seit sich die sha256-Konfiguration geändert hat) und startet RollingUpdate, wobei sich außer dieser Anmerkung nichts ändert.

Infolgedessen speichern wir dieselbe Version der Anwendung und Beschreibung der Bereitstellung und lösen im Wesentlichen nur die Neuerstellung des Pods durch den Computer aus - ähnlich wie bei der manuellen Ausführung kubectl delete, jedoch bereits „korrekt“: automatisch und mit RollingUpdate.

Szenario 4


Vielleicht weiß die Anwendung bereits, wie Änderungen in der Konfiguration überwacht und automatisch neu geladen werden? Hier liegt eine wichtige Funktion von ConfigMaps ...

Wenn die Konfiguration in Kubernetes bereitgestellt wird subPath, wird sie erst aktualisiert, wenn der Pod neu gestartet wird (siehe die ersten drei oben beschriebenen Szenarien) . Wenn Sie ConfigMap jedoch als Verzeichnis ohne bereitstellen subPath, befindet sich im Container ein Verzeichnis mit einer aktualisierten Konfiguration, ohne den Pod neu zu starten.

Es gibt andere nützliche Funktionen:

  • Eine solche aktualisierte Konfigurationsdatei im Container wird mit einiger Verzögerung aktualisiert. Dies liegt daran, dass die Datei nicht genau gemountet wird, sondern das Kubernetes-Objekt.
  • Die darin enthaltene Datei ist ein Symlink. Beispiel mit subPath:

    $ kubectl -n production exec go-conf-example-6b4cb86569-22vqv -- ls -lha /app/configfiles 
    total 20K    
    drwxr-xr-x    1 root     root        4.0K Mar  3 19:34 .
    drwxr-xr-x    1 app      app         4.0K Mar  3 19:34 ..
    -rw-r--r--    1 root     root          42 Mar  3 19:34 config.json
    -rw-r--r--    1 root     root          47 Mar  3 19:34 database.yml

    Und was passiert ohne, subPathwenn es vom Verzeichnis gemountet wird?

    $ kubectl -n production exec go-conf-example-67c768c6fc-ccpwl -- ls -lha /app/configfiles 
    total 12K    
    drwxrwxrwx    3 root     root        4.0K Mar  3 19:40 .
    drwxr-xr-x    1 app      app         4.0K Mar  3 19:34 ..
    drwxr-xr-x    2 root     root        4.0K Mar  3 19:40 ..2020_03_03_16_40_36.675612011
    lrwxrwxrwx    1 root     root          31 Mar  3 19:40 ..data -> ..2020_03_03_16_40_36.675612011
    lrwxrwxrwx    1 root     root          18 Mar  3 19:40 config.json -> ..data/config.json
    lrwxrwxrwx    1 root     root          19 Mar  3 19:40 database.yml -> ..data/database.yml

    Aktualisieren Sie die Konfiguration (über kubectl editDeployment oder ), warten Sie 2 Minuten (Apiserver-Caching-Zeit) - und voila:

    $ kubectl -n production exec go-conf-example-67c768c6fc-ccpwl -- ls -lha --color /app/configfiles 
    total 12K    
    drwxrwxrwx    3 root     root        4.0K Mar  3 19:44 .
    drwxr-xr-x    1 app      app         4.0K Mar  3 19:34 ..
    drwxr-xr-x    2 root     root        4.0K Mar  3 19:44 ..2020_03_03_16_44_38.763148336
    lrwxrwxrwx    1 root     root          31 Mar  3 19:44 ..data -> ..2020_03_03_16_44_38.763148336
    lrwxrwxrwx    1 root     root          18 Mar  3 19:40 config.json -> ..data/config.json
    lrwxrwxrwx    1 root     root          19 Mar  3 19:40 database.yml -> ..data/database.yml

    Beachten Sie den geänderten Zeitstempel in dem von Kubernetes erstellten Verzeichnis.

Tracking ändern


Und schließlich - ein einfaches Beispiel dafür, wie Sie Änderungen in der Konfiguration überwachen können.

Wir werden eine solche Go-Anwendung verwenden
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/fsnotify/fsnotify"
)

// Config fo our application
type Config struct {
	Welcome string `json:"welcome"`
	Name    string `json:"name"`
}

var (
	globalConfig *Config
)

// LoadConfig - load our config!
func LoadConfig(path string) (*Config, error) {
	configFile, err := os.Open(path)

	if err != nil {
		return nil, fmt.Errorf("Unable to read configuration file %s", path)
	}

	config := new(Config)

	decoder := json.NewDecoder(configFile)
	err = decoder.Decode(&config)
	if err != nil {
		return nil, fmt.Errorf("Unable to parse configuration file %s", path)
	}

	return config, nil
}

// ConfigWatcher - watches config.json for changes
func ConfigWatcher() {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}
	defer watcher.Close()

	done := make(chan bool)
	go func() {
		for {
			select {
			case event, ok := <-watcher.Events:
				if !ok {
					return
				}
				log.Println("event:", event)
				if event.Op&fsnotify.Write == fsnotify.Write {
					log.Println("modified file:", event.Name)
				}
				globalConfig, _ = LoadConfig("./configfiles/config.json")
				log.Println("config:", globalConfig)
			case err, ok := <-watcher.Errors:
				if !ok {
					return
				}
				log.Println("error:", err)
			}
		}
	}()

	err = watcher.Add("./configfiles/config.json")
	if err != nil {
		log.Fatal(err)
	}
	<-done
}

func main() {
	log.Println("Start")
	globalConfig, _ = LoadConfig("./configfiles/config.json")
	go ConfigWatcher()
	for {
		log.Println("config:", globalConfig)
		time.Sleep(30 * time.Second)
	}
}

... mit einer solchen Konfiguration hinzufügen:

$ cat configfiles/config.json 
{
  "welcome": "Hello",
  "name": "Alice"
}

Wenn ausgeführt, lautet das Protokoll:

2020/03/03 22:18:22 config: &{Hello Alice}
2020/03/03 22:18:52 config: &{Hello Alice}

Und jetzt werden wir diese Anwendung in Kubernetes installieren, nachdem wir die ConfigMap-Konfiguration im Pod anstelle der Datei aus dem Image bereitgestellt haben. Auf GitHub wurde ein Beispiel für ein Helm-Diagramm erstellt :

helm install -n habr-configmap --namespace habr-configmap ./habr-configmap --set 'name.production=Alice' --set 'global.env=production'

Und ändern Sie nur ConfigMap:

-  production: "Alice"
+  production: "Bob"

Aktualisieren Sie das Helm-Diagramm im Cluster beispielsweise wie folgt:

helm upgrade habr-configmap ./habr-configmap --set 'name.production=Bob' --set 'global.env=production'

Was wird passieren?

  • Die Anwendungen v1 und v2 werden nicht neu gestartet, da Für sie hat sich in der Bereitstellung nichts geändert - sie heißen Alice immer noch willkommen.
  • Die v3-Anwendung wurde neu gestartet, die Konfiguration erneut gelesen und Bob begrüßt.
  • Die v4-Anwendung wurde nicht neu gestartet. Da ConfigMap als Verzeichnis bereitgestellt wird, wurden Änderungen in der Konfiguration festgestellt und die Konfiguration im laufenden Betrieb geändert, ohne den Pod neu zu starten. Ja, die Anwendung hat Änderungen in unserem einfachen Beispiel festgestellt - siehe die Ereignismeldung von fsnotify :

    2020/03/03 22:19:15 event: "configfiles/config.json": CHMOD
    2020/03/03 22:19:15 config: &{Hello Bob}
    2020/03/03 22:19:22 config: &{Hello Bob}

Sie können prüfen , wie eine ähnliche Situation - ConfigMap die Änderungen Tracking - in einem erwachsenen gelöst wird (und nur „real“) Projekt hier .

Wichtig! Es ist auch nützlich daran zu erinnern, dass all das oben Genannte im Artikel auch für Secret'ov in Kubernetes ( kind: Secret) gilt: Nicht umsonst sind sie ConfigMap so ähnlich ...

Bonus! Lösungen von Drittanbietern


Wenn Sie sich für das Thema Nachverfolgung von Änderungen in Konfigurationen interessieren, gibt es dafür bereits vorgefertigte Dienstprogramme:

  • jimmidyson / configmap-reload - Sendet eine HTTP-Anfrage, wenn sich die Datei geändert hat. Der Entwickler plant auch, SIGHUP das Senden beizubringen, aber das Fehlen von Commits ab Oktober 2019 lässt diese Pläne in Frage.
  • stakater / Reloader - überwacht ConfigMap / Secrets und führt ein fortlaufendes Upgrade (wie der Autor es nennt) für die ihnen zugeordneten Ressourcen durch.

Es ist zweckmäßig, solche Anwendungen mit einem Beiwagencontainer für vorhandene Anwendungen zu starten. Wenn Sie jedoch die Funktionen von Kubernetes / ConfigMap kennen und konfigurieren, um nicht "live" (durch edit), sondern nur im Rahmen der Bereitstellung zu bearbeiten , erscheinen die Funktionen solcher Dienstprogramme möglicherweise überflüssig, d. H. Grundfunktionen duplizieren.

Fazit


Mit dem Aufkommen von ConfigMap in Kubernetes gingen die Konfigurationen in die nächste Entwicklungsrunde über: Die Verwendung einer Vorlagen-Engine brachte ihnen Flexibilität, die mit dem Rendern von HTML-Seiten vergleichbar war. Glücklicherweise ersetzten solche Komplikationen nicht die bestehenden Lösungen, sondern wurden zu ihrer Ergänzung . Daher sind für Administratoren (oder besser gesagt sogar Entwickler), die neue Funktionen für überflüssig halten, weiterhin gute alte Dateien verfügbar.

Für diejenigen, die ConfigMap'ami bereits verwenden oder sich nur ansehen, bietet der Artikel einen kurzen Überblick über ihre Essenz und Verwendungsnuancen. Wenn Sie eigene Tipps und Tricks zum Thema haben, freue ich mich über die Kommentare.

PS


Lesen Sie auch in unserem Blog:


All Articles