Parmi les bonnes pratiques enoncées dans les 12 factors se trouve la notion de configuration externalisée.

En effet, la configuration d'une application étant dépendante de la plateforme où celle-ci sera hébergée, autant la stocker sur les plateformes en question. Cela évite de multiplier les fichiers de configuration dans le code du projet, tout en apportant de la sécurité (les mots de passe et autres valeurs secrètes n'apparaissent pas dans le dépôt de gestion de version).

Là où certaines plateformes d'hébergement cloud proposent simplement la possibilité de définir des variables d'environnements, Kubernetes offre une solution puissante à travers deux types d'objets : les ConfigMaps et les Secrets.

ConfigMaps

Les ConfigMaps sont des ressources dédiées au stockage des éléments de configuration non secrets. Cela inclus par exemple les adresses et les ports d'accès aux services externes (bdd, api REST), les niveaux de logs etc. Cela n'inclus en revanche pas les mots de passes, les clés privées ou tout autre élément sensibles du point de vue de la sécurité.

Il existe différents moyens de créer et de consommer des ConfigMaps, nous allons les explorer un à un.

Variables d'environnement

Création avec un manifeste

Pour commencer, nous allons créer un ConfigMap contenant quelques variables d'environnement, puis un Pod qui les affichera.

  • Créer un manifeste env-configmap.yaml avec le contenu suivant
apiVersion: v1
kind: ConfigMap
metadata:
  name: env-configmap
data:
  dbHost: "mydb.example.com"
  appVersion: "12"
  • Vérifier la création
kubectl get configmap
NAME            DATA      AGE
env-configmap   2         5s
  • Afficher les détails
kubectl get configmap env-configmap -o yaml

Pour l'instant nous n'avons à aucun moment spécifié que les données du ConfigMap correspondaient à des variables d'environnement. C'est parceque la façon de consommer les valeurs définies dans le ConfigMap est propre au pod, et pas au ConfigMap en lui-même.

Nous allons créer un Pod pour consommer notre ConfigMap sous forme de variables d'environnement.

  • Utiliser le modèle ci-dessous pour créer le Pod.
apiVersion: v1
kind: Pod
metadata:
 name: env-config-pod
spec:
 restartPolicy: Never
 containers:
   - name: env-config-container
     image: busybox
     command: [ "/bin/sh", "-c", "while [ true ] ; do env; printf \"\n\"; sleep 1 ; done;" ]
     env:

       - name: DB_HOST
         valueFrom:
           configMapKeyRef:
             name: ...
             key: ...

       - name: APP_VERSION
         ...
  • Appliquer le manifeste pour démarrer le Pod, celui-ci va afficher ses variables d'environnement toutes les secondes.
kubectl logs env-config-pod
...
DB_HOST=mydb.example.com
APP_VERSION=12

Si les attributs de spec.containers.env sont bien configurés, on retrouve les valeurs du ConfigMap dans les variables d'environnement du Pod.

  • Modifier le ConfigMap, changer par exemple la valeur de appVersion
  • Afficher de nouveau les logs du Pod
  • La valeur a-t-elle changé dans les variables d'environnement du Pod ?

La mise à jour des Pods lors du changement d'un ConfigMap ne se fait pas automatiquement. Nous devons donc supprimer et recréer le Pod pour que la nouvelle valeur prenne effet.

Dans le cas où un ConfigMap contient l'intégralité des variables d'environnement d'un Pod, il existe une forme plus simple pour les importer.

  • Remplacer toute la section spec.containers.env du Pod par les attributs ci-dessous, puis appliquer les changements.
envFrom:
  - configMapRef:
      name: env-configmap

En affichant les logs, du Pod, on voit effectivement les même valeurs.

Création depuis kubectl

Kubectl nous offre quelques possibilités supplémentaires dans la création des ConfigMaps. Il est notamment possible de créer un ConfigMap depuis un ou plusieurs fichiers.

  • Créer un fichier pod.env avec le contenu suivant.
DB_HOST=mydb.example.com
APP_VERSION=12
  • Créer un ConfigMap avec kubectl, l'option --from-env-file permets de spécifier le fichier à utiliser.
kubectl create configmap from-env-file-configmap --from-env-file=pod.env
  • Afficher le ConfigMap créé, on doit retrouver les valeurs du fichier .env.
kubectl get configmap from-env-file-configmap -o yaml
apiVersion: v1
kind: ConfigMap
data:
  APP_VERSION: "12"
  DB_HOST: mydb.example.com

Fichiers

Kubernetes ne s'arrête pas aux variables d'environnements, il est en effet possible de stocker un ou plusieurs fichiers dans un ConfigMap.

kubectl create configmap letsencrypt-ca-configmap --from-file ca.crt
  • Afficher le résultat
kubectl get configmap letsencrypt-ca-configmap -o yaml
apiVersion: v1
kind: ConfigMap
data:
  ca.crt: |
    -----BEGIN CERTIFICATE-----
    MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
    MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
    DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
    SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
    GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
    AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
    q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
    SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
    Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
    a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
    /PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
    AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
    CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
    bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
    c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
    VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
    ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
    MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
    Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
    AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
    uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
    wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
    X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
    PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
    KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
    -----END CERTIFICATE-----

On retrouve effectivement le contenu du fichier. Nous pourrions monter cette valeur à l'intérieur d'un Pod dans une variable d'environnement, mais cela aurait peu de sens. Au lieu de ça, nous allons demander à Kubernetes de créer un fichier dans le Pod avec le contenu du ConfigMap.

Pour ce faire, nous allons déclarer un volume avec notre ConfigMap et le monter à l'intérieur du Pod (dans le répertoire /etc/certs)

  • Utiliser le modèle suivant pour créer le Pod.
apiVersion: v1
kind: Pod
metadata:
 name: volume-config-pod
spec:
 restartPolicy: Never
 containers:
   - name: volume-config-container
     image: busybox
     command: [ "/bin/sh", "-c", "while [ true ] ; do ls /etc/certs; printf \"\n\"; sleep 1 ; done;" ]
     volumeMounts:
     - name: config-volume
       mountPath: ...
 volumes:
   - name: config-volume
     configMap:
       name: ...

Au démarrage, le Pod se mets à afficher le contenu du répertoire /etc/certs, dans lequel nous retrouvons le fichier du ConfigMap.

Il est possible de spécifier un nom de fichier différent à l'intérieur du Pod.

volumes:
- name: config-volume
  configMap:
    name: ...
    items:
    - key: ca.crt
      path: autre-nom.crt

Enfin, il est aussi possible de créer un ConfigMap depuis un répertoire.

mkdir config
echo hello > config/test1.txt
echo world > config/test2.txt
kubectl create configmap folder-config --from-file=config/
kubectl get configmap folder-config -o yaml
apiVersion: v1
kind: ConfigMap
binaryData:
test1.txt: //5oAGUAbABsAG8ADQAKAA==
test2.txt: //53AG8AcgBsAGQADQAKAA==

Les données sont encodées en base64 pour éviter les problèmes d'encodage.

Secrets

Il y a peu de choses à savoir sur les Secrets car ils fonctionnent de manière quasiment identique aux ConfigMaps. La différence majeure étant que les données à l'intérieur d'un Secret peuvent être binaires et sont donc encodées en base64.

La manipulation des Secrets implique donc l'étape supplémentaire d'encodage/décodage en base64 pour l'ajout de valeur litérales.

  • Encoder une chaine arbitraire en base64
printf 'abcde12345' | base64
YWJjZGUxMjM0NQ==
  • Créer un Secret avec cette valeur
apiVersion: v1
data:
  password: YWJjZGUxMjM0NQ==
kind: Secret
metadata:
  name: example-secret

De la même manière qu'avec un ConfigMap, nous pouvons monter notre Secret dans un fichier.

Une fonctionnalité intéressante pour les Secrets (disponible aussi pour les ConfigMaps) est la possibilité de définir les droits d'accès du fichier.

apiVersion: v1
kind: Pod
metadata:
 name: secret-pod
spec:
 ...
 volumes:
   - name: config-volume
     secret:
       secretName: ...
       defaultMode: 0700