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.yamlavec 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.envdu 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.envavec le contenu suivant.
DB_HOST=mydb.example.com
APP_VERSION=12
- Créer un ConfigMap avec kubectl, l'option
--from-env-filepermets 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.
-
Créer un fichier avec un contenu quelconque (par exemple un certificat : https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt)
-
Créer un ConfigMap à partir de ce fichier en l'utilisant l'option
--from-file
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