Kubernetes ConfigMaps: matices para saber

Nota : este no es un artículo de guía completo, sino más bien un recordatorio / sugerencia para aquellos que ya usan ConfigMap en Kubernetes o simplemente están preparando su aplicación para trabajar en él.



Antecedentes: de rsync a ... Kubernetes


Que paso antes En la era de la "administración clásica", en la versión más simple, el archivo de configuración se colocaba justo al lado de las aplicaciones (o en el repositorio, si lo desea). Es simple: hacemos entrega elemental (CD) para nuestro código junto con la configuración. Incluso una implementación rsync condicional se puede llamar los rudimentos de un CD.

Cuando la infraestructura creció, se requirieron diferentes configuraciones para diferentes entornos (desarrollo / etapa / producción). La aplicación fue entrenada para comprender qué configuración usar, pasándolas como argumentos para iniciar o variables de entorno establecidas en el entorno. Aún más CD es complicado con la llegada de tan útil Chef / Puppet / Ansible. Los roles aparecen en los servidores y los entornos dejan de describirse en diferentes lugares: llegamos a IaC (Infraestructura como código).

¿Qué siguió? Si fue posible ver las ventajas críticas de Kubernetes por sí mismo e incluso aceptar la necesidad de modificar las aplicaciones para que funcionen en este entorno, hubo una migración. En el camino, esperaba muchos matices y diferencias en la construcción de la arquitectura, pero cuando me las arreglé para hacer frente a la parte principal, obtuve la tan esperada aplicación ejecutándose en K8.

Una vez aquí, aún podemos usar las configuraciones preparadas en el repositorio al lado de la aplicación o pasar ENV al contenedor. Sin embargo, además de estos métodos, ConfigMaps también están disponibles . Esta primitiva K8s le permite usar plantillas Go en configuraciones, es decir renderícelos como páginas HTML y vuelva a cargar la aplicación cuando cambie la configuración sin reiniciar. Con ConfigMaps, ya no es necesario mantener más de 3 configuraciones para diferentes entornos y realizar un seguimiento de la relevancia de cada una.

Se puede encontrar una introducción general a ConfigMaps, por ejemplo, aquí . Y en este artículo me centraré en algunas características de trabajar con ellos.

ConfigMaps simples


¿Cómo se veían las configuraciones en Kubernetes? ¿Qué obtuvieron de las plantillas go? Por ejemplo, aquí hay un ConfigMap ordinario para una aplicación implementada desde un gráfico Helm:

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 }}
    }

Aquí los valores sustituidos en .Values.welcomey .Values.nameserán tomados del archivo values.yaml. ¿Por qué exactamente de values.yaml? ¿Cómo funciona el motor de plantillas Go? Ya hemos hablado sobre estos detalles con más detalle aquí .

La llamada pluckayuda a seleccionar la línea necesaria del mapa:

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

Además, puede tomar tanto líneas específicas como fragmentos enteros de la configuración.

Por ejemplo, ConfigMap podría ser así:

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

... y en values.yaml- los siguientes contenidos:

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

El involucrado aquí global.enves el nombre del medio ambiente. Sustituyendo este valor durante la implementación, puede representar ConfigMaps con contenido diferente. firstnecesitado aquí porque pluckdevuelve una lista, cuyo primer elemento contiene el valor deseado.

Cuando hay muchas configuraciones


Un ConfigMap puede contener varios archivos de configuración:

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

Incluso puede montar cada configuración por separado:

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

... o recoge todas las configuraciones a la vez con el directorio:

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

Si cambia la descripción del recurso de implementación durante la implementación, Kubernetes creará un nuevo ReplicaSet, disminuyendo el antiguo a 0 y aumentando el nuevo al número especificado de réplicas. (Esto es cierto para la estrategia de implementación RollingUpdate).

Tales acciones conducirán a la recreación del pod con una nueva descripción. Por ejemplo: había una imagen image:my-registry.example.com:v1, pero se convirtió en - image:my-registry.example.com:v2. Y no importa en absoluto lo que cambiamos exactamente en la descripción de nuestra implementación: lo principal es que esto hizo que se recreara el replicaSet (y, como resultado, el pod). En este caso, la nueva versión del archivo de configuración en la nueva versión de la aplicación se monta automáticamente y no habrá ningún problema.

Respuesta de cambio de ConfigMap


En caso de cambios en ConfigMap pueden seguir cuatro escenarios de eventos. Considerarlos:

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

Analizaremos con más detalle.

escenario 1


¿ Solo corregimos ConfigMap? La aplicación no se reiniciará. En el caso de montaje por, subPathno habrá cambios hasta que el pod se reinicie manualmente.

Todo es simple: Kubernetes monta nuestro ConfigMap en un pod de una versión específica del recurso. Como está montado con subPath, ya no se proporciona "influencia" adicional en la configuración.

Escenario 2


¿No puede actualizar el archivo sin volver a crear el pod? De acuerdo, tenemos 6 réplicas en Implementación, por lo que podemos turnarnos manualmente para hacer todo delete pod. Luego, al crear nuevos pods, "recogerán" la nueva versión de ConfigMap.

Escenario 3


¿Cansado de realizar tales operaciones manualmente? En los consejos y trucos de Helm se describe una solución a este problema :

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

Por lo tanto, el spec.templatehash de la configuración de anotación representada simplemente se escribe en la plantilla pod ( ).

Las anotaciones son campos de valores clave arbitrarios en los que puede almacenar sus valores. Si los registra en la plantilla del spec.templatepod futuro, estos campos caerán en el ReplicaSet y el pod en sí. Kubernetes notará que la plantilla del pod ha cambiado (ya que la configuración de sha256 ha cambiado) y comenzará RollingUpdate, en el que nada cambia excepto esta anotación.

Como resultado, guardamos la misma versión de la aplicación y la descripción del Despliegue y esencialmente desencadenamos la recreación del pod por la máquina, de forma similar a cómo lo haríamos manualmente kubectl delete, pero ya "correctamente": automáticamente y con RollingUpdate.

Escenario 4


¿Quizás la aplicación ya sabe cómo monitorear los cambios en la configuración y recargar automáticamente? Aquí radica una característica importante de ConfigMaps ...

En Kubernetes, si la configuración está montada subPath, no se actualizará hasta que se reinicie el pod (ver los primeros tres escenarios discutidos anteriormente) . Pero si monta ConfigMap como un directorio, sin subPath, dentro del contenedor habrá un directorio con una configuración actualizada sin reiniciar el pod.

Hay otras características que son útiles para recordar:

  • Tal archivo de configuración actualizado dentro del contenedor se actualiza con cierto retraso. Esto se debe al hecho de que el archivo no está montado exactamente, sino el objeto Kubernetes.
  • El archivo dentro es un enlace simbólico. Ejemplo con 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

    ¿Y qué pasará sin subPathcuando esté montado por el directorio?

    $ 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

    Actualice la configuración (mediante implementación o kubectl edit), espere 2 minutos (tiempo de almacenamiento en caché del servidor) y listo:

    $ 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

    Tenga en cuenta la marca de tiempo modificada en el directorio creado por Kubernetes.

Seguimiento de cambios


Y, por último, un ejemplo simple de cómo puede monitorear los cambios en la configuración.

Utilizaremos dicha aplicación Go
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)
	}
}

... agregándolo con tal configuración:

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

Si se ejecuta, el registro será:

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

Y ahora instalaremos esta aplicación en Kubernetes, después de haber montado la configuración de ConfigMap en el pod en lugar del archivo de la imagen. Se ha preparado un ejemplo de un gráfico Helm en GitHub :

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

Y cambie solo ConfigMap:

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

Actualice el gráfico Helm en el clúster, por ejemplo, así:

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

¿Lo que sucederá?

  • Las aplicaciones v1 y v2 no se reinician porque Para ellos, no se ha producido ningún cambio en la implementación, todavía dan la bienvenida a Alice.
  • La aplicación v3 se reinició, volvió a leer la configuración y saludó a Bob.
  • La aplicación v4 no se reinició. Como ConfigMap se monta como un directorio, se notaron cambios en la configuración y la configuración cambió sobre la marcha, sin reiniciar el pod. Sí, la aplicación notó cambios en nuestro ejemplo simple: vea el mensaje de evento de 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}

Puede ver cómo una situación similar, el seguimiento de los cambios de ConfigMap, se está resolviendo en un proyecto más adulto (y simplemente "real"), aquí .

¡Importante! También es útil recordar que todo lo anterior en el artículo también es cierto para Secret'ov en Kubernetes ( kind: Secret): no es por nada que son tan similares a ConfigMap ...

¡Prima! Soluciones de terceros


Si está interesado en el tema del seguimiento de cambios en las configuraciones, ya hay utilidades listas para esto:

  • jimmidyson / configmap-reload : envía una solicitud HTTP si el archivo ha cambiado. El desarrollador también planea enseñarle a SIGHUP a enviar, pero la falta de compromisos a partir de octubre de 2019 deja estos planes en cuestión;
  • stakater / Reloader : supervisa ConfigMap / Secrets y realiza una actualización continua (como lo llama su autor) en los recursos asociados con ellos.

Será conveniente lanzar dichas aplicaciones con un contenedor de sidecar a las aplicaciones existentes. Sin embargo, si conoce las características de Kubernetes / ConfigMap y las configuraciones para editar no "en vivo" (a través de edit), sino solo como parte de la implementación ... entonces las capacidades de tales utilidades pueden parecer superfluas, es decir Duplicar funciones básicas.

Conclusión


Con el advenimiento de ConfigMap en Kubernetes, las configuraciones pasaron a la siguiente ronda de desarrollo: el uso de un motor de plantillas les proporcionó una flexibilidad comparable a la representación de páginas HTML. Afortunadamente, tales complicaciones no reemplazaron las soluciones existentes, sino que se convirtieron en su complemento . Por lo tanto, para los administradores (o más bien, incluso los desarrolladores) que consideran redundantes las nuevas funciones, todavía hay disponibles archivos antiguos buenos.

Para aquellos que ya usan ConfigMap'ami o simplemente los miran, el artículo ofrece una breve descripción de su esencia y matices de uso. Si tiene sus propios consejos y trucos sobre el tema, estaré encantado de verlos en los comentarios.

PD


Lea también en nuestro blog:


All Articles