Lorsque l'on exécute une application sur Kubernetes on peut vouloir passer une configuration dynamique à notre pod (mot de passe BDD, token API etc...). Pour gérer celà il existe différents moyens de faire.
ConfigMaps peut stocker des données sous forme de clé/valeur. Les ConfigMaps peuvent être passées au container.
apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
data:
key1: Hello, world!
key2: |
Test
multiple lines
more lines
key3:
subkey:
morekey: data
encoreplus: plus de data
Les Secrets sont similaires aux ConfigMaps mais les Secrets ont été conçu pour recevoir des données sensibles (mot de passe, clé API...). Les Secrets sont créés et utilisés de la même façon qu'une ConfigMap.
apiVersion: v1
kind: Secret
metadata:
name: my-secret
type: Opaque # Il existe différents types de Secret : service-account-token, dockercfg, ssh-auth...
data:
username: <base64> # echo -n 'user' | base64
password: <base64> # echo -n 'password' | base64
Plus d'informations sur les types de secrets ici.
On peut passer les ConfigMaps ainsi que les Secrets comme des variables d'environnement. Les variables seront alors visibles lors de l'exécution du container.
spec:
containers:
- ...
env:
- name: ENVVAR
valueFrom:
configMapKeyRef:
name: my-configmap
key: mykey
Notre container aura ici une variable $ENVVAR contenant la valeur de la clé mykey.
On peut aussi passer notre configuration depuis un volume. La configuration sera donc disponible dans un fichier monté sur le container.
...
volumes:
- name: secret-vol
secret:
secretName: my-secret
En pratique :
configmap.yml :
apiVersion: v1
kind: ConfigMap
metadata:
name: my-configmap
data:
key1: Hello, world!
key2: |
Test
multiple lines
more lines
Si on décrit cette configmap (une fois appliquée) :
$ kubectl describe configmaps my-configmap
Name: my-configmap
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
key1:
----
Hello, world!
key2:
----
Test
multiple lines
more lines
Events: <none>
$ echo -n 'utilisateur' | base64
dXRpbGlzYXRldXI=
$ echo -n 'motdepasse' | base64
bW90ZGVwYXNzZQ==
secret.yml :
apiVersion: v1
kind: Secret
metadata:
name: my-secret
type: Opaque
data:
secretkey1: dXRpbGlzYXRldXI=
secretkey2: bW90ZGVwYXNzZQ==
Si on décrit le Secret :
$ kubectl describe secrets my-secret
Name: my-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
secretkey1: 11 bytes
secretkey2: 10 bytes
apiVersion: v1
kind: Pod
metadata:
name: env-pod
spec:
containers:
- name: busybox
image: busybox
command: ['sh', '-c', 'echo "configmap: $CONFIGMAPVAR secret: $SECRETVAR"']
env:
- name: CONFIGMAPVAR
valueFrom:
configMapKeyRef:
name: my-configmap
key: key1
- name: SECRETVAR
valueFrom:
secretKeyRef:
name: my-secret
key: secretkey1
Si on regarde les logs, on devrait donc afficher key1 et secretkey1 :
$ kubectl logs env-pod
configmap: Hello, world! secret: utilisateur
apiVersion: v1
kind: Pod
metadata:
name: volume-pod
spec:
containers:
- name: busybox
image: busybox
command: ['sh', '-c', 'while true; do sleep 3600; done'] # Ne fait rien pendant 1h (3600s)
volumeMounts:
- name: configmap-volume
mountPath: /etc/config/configmap
- name: secret-volume
mountPath: /etc/config/secret
volumes:
- name: configmap-volume
configMap:
name: my-configmap
- name: secret-volume
secret:
secretName: my-secret
Ici, chaque clé sera un fichier différent :
$ kubectl exec volume-pod -- ls /etc/config/configmap
key1
key2
$ kubectl exec volume-pod -- cat /etc/config/configmap/key1
Hello, world!
$ kubectl exec volume-pod -- cat /etc/config/configmap/key2
Test
multiple lines
more lines
$ kubectl exec volume-pod -- ls /etc/config/secret
secretkey1
secretkey2
$ kubectl exec volume-pod -- cat /etc/config/secret/secretkey1
utilisateur
$ kubectl exec volume-pod -- cat /etc/config/secret/secretkey2
motdepasse
Nous pouvons limiter l'utilisation du CPU/RAM... De chaque container. Il est recommandé de mettre une limite par container afin d'éviter qu'un container prenne 90% des ressources, et ainsi, provoquant des OOM (Out Of Memory) sur les autres.
Il existe deux types de RessourceRequest :
Voici un pod avec une ResourceRequest :
apiVersion: v1
kind: Pod
metadata:
name: big-request-pod
spec:
containers:
- name: busybox
image: busybox
command: ['sh', '-c', 'while true; do sleep 3600; done']
resources:
requests:
cpu: "100m" # milliCPU soit 0,1 CPU
memory: "128Mi"
limits:
cpu: "250m" # milliCPU soit 0,25 CPU
memory: "256Mi"
Dans le cas on l'on demanderait plus de CPU/RAM que ce qu'il y a de disponible le pod resterait en pending le temps d'avoir un noeud avec la ressource nécessaire.
K8s permet de redémarrer les containers "unhealthy". Pour déterminer si un container est healthy/unhealthy il faut mettre en place une probe. Il existe trois types de probe :
apiVersion: v1
kind: Pod
metadata:
name: liveness-pod
spec:
containers:
- name: busybox
image: busybox
command: ['sh', '-c', 'while true; do sleep 3600; done']
livenessProbe:
exec:
command: ["echo", "Hello, world!"] # Si la commande retourne 0 c'est que le container tourne bien
initialDelaySeconds: 5 # Commence à checker 5s après le lancement
periodSeconds: 5 # Effectue le commande toutes les 5s
# De base, il réessaiera 3 fois avant de passer le pod en unhealthy
On peut aussi envoyer une requête HTTP :
apiVersion: v1
kind: Pod
metadata:
name: liveness-pod-http
spec:
containers:
- name: nginx
image: nginx:1.19.1
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
apiVersion: v1
kind: Pod
metadata:
name: startup-pod
spec:
containers:
- name: nginx
image: nginx:1.19.1
startupProbe:
httpGet:
path: /
port: 80
failureThreshold: 30
periodSeconds: 10
apiVersion: v1
kind: Pod
metadata:
name: readiness-pod
spec:
containers:
- name: nginx
image: nginx:1.19.1
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
Kubernetes peut redémarrer des containers quand ils se stoppent. Les politiques de redémarrages nous permettent de configurer quand est-ce que k8s doit redémarrer le container ou non.
Politique par défaut. Avec cette politique, les containers redémarreront s'ils se stoppent même s'il n'y a pas d'erreur. Utiliser cette politique si on veut qu'une app soit tout le temps UP.
apiVersion: v1
kind: Pod
metadata:
name: always-pod
spec:
restartPolicy: Always
containers:
- name: busybox
image: busybox
command: ['sh', '-c', 'sleep 10']
Toutes les 10s le container sera redémarré.
OnFailure comme son nom l'indique, redémarre si et uniquement si le container se stoppe avec un code erreur ou si la liveness probe indique qu'il est unhealthy. Utiliser cette politique pour des applications éphémères (start --> effectue une action --> stop).
apiVersion: v1
kind: Pod
metadata:
name: onfailure-pod
spec:
restartPolicy: OnFailure
containers:
- name: busybox
image: busybox
command: ['sh', '-c', 'sleep 10']
Ici, le container ne sera pas redémarré car le container stoppe avec un code 0. Il sera en statut "Completed".
apiVersion: v1
kind: Pod
metadata:
name: onfailure-pod
spec:
restartPolicy: OnFailure
containers:
- name: busybox
image: busybox
command: ['sh', '-c', 'sleep 10; Commande avec une erreur!']
La commande sortira une erreur (exit 1), le container sera donc redémarré.
Comme son nom l'indique, Never ne redémarre jamais le pod. Utiliser cette politique pour les applications qui doivent tourner qu'une seule fois.
apiVersion: v1
kind: Pod
metadata:
name: never-pod
spec:
restartPolicy: Never
containers:
- name: busybox
image: busybox
command: ['sh', '-c', 'sleep 10; Commande avec une erreur!']
Le container ne sera pas redémarré, il sera en statut "Error".
Pour vérifier les événements d'un pod (liveness, restart...) on peut décrire le pod :
$ kubectl describe pod my-busybox
...
Liveness: http-get http://:8080/ delay=5s timeout=1s period=5s #success=1 #failure=3
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Unhealthy 39s (x3 over 49s) kubelet Liveness probe failed: HTTP probe failed with statuscode: 500
Normal Killing 39s kubelet Container shipping-data failed liveness probe, will be restarted
Normal Pulled 9s (x2 over 93s) kubelet Container image "linuxacademycontent/random-crashing-web-server:1" already present on machine
Comme on le sait, un pod peut contenir un ou plusieurs container. Les pod multi-container partagent le réseau et le stockage.
Best Practice : éviter autant que possible de faire des pods multi containers.
Les containers partageant le même pod peuvent intéragir entre eux en utilisant les ressources partagées :
Voici une définition d'un pod multi containers :
apiVersion: v1
kind: Pod
metadata:
name: multi-container-pod
spec:
containers:
- name: nginx
image: nginx
- name: redis
image: redis
- name: couchbase
image: couchbase
On voit donc que le pod possèdera 3 containers (nginx, redis, couchbase). Aucun intérêt mais c'est pour l'exemple.
On va maintenant créer un pod avec 2 containers. Un qui écrit dans un fichier de log, l'autre qui écrit ces logs directement dans la console :
apiVersion: v1
kind: Pod
metadata:
name: sidecar-pod
spec:
containers:
- name: busybox1
image: busybox
command: ['sh', '-c', 'while true; do echo logs data > /output/output.log; sleep 5; done']
volumeMounts:
- name: sharedvol
mountPath: /output
- name: sidecar
image: busybox
command: ['sh', '-c', 'tail -f /input/output.log']
volumeMounts:
- name: sharedvol
mountPath: /input
volumes:
- name: sharedvol
emptyDir: {}
On va ensuite afficher les logs du container sidecar :
$ kubectl logs sidecar-pod -c sidecar
logs data
Init containers sont des containers qui s'exécutent qu'une seule fois au démarrage d'un pod. Un pod peut avoir plusieurs init containers, ils tourneront chacun leur tour (dans l'ordre).
On peut utiliser les init containers afin de faire différentes tâches au démarrage du pod. Ils peuvent contenir des outils qui ne sont pas utilisés par l'application principale.
C'est une manière d'alléger l'application et être plus sécurisé.
Exemple d'utilisation :
apiVersion: v1
kind: Pod
metadata:
name: init-pod
spec:
containers:
- name: nginx
image: nginx:1.19.1
initContainers:
- name: delay
image: busybox
command: ['sleep', '30']
L'application principale (nginx) attendra donc 30s avant d'être démarrée.
Une fois lancé, on peut lister le pod :
$ kubectl get pod init-pod
NAME READY STATUS RESTARTS AGE
init-pod 0/1 Init:0/1 0 7s
On voit que le statut indique bien qu'on est actuellement en "Init" qui contient 1 container.
Voici un exemple de pod qui va attendre que le service "my-service" soit disponible :
apiVersion: v1
kind: Pod
metadata:
name: shipping-web
spec:
containers:
- name: nginx
image: nginx:1.19.1
initContainers:
- name: my-init
image: busybox
command: ['sh', '-c', 'until nslookup my-service.default.svc.cluster.local; do echo waiting for my-service.default.svc.cluster.local; sleep 10; done;']
Le DNS Kubernetes fonctionne de la manière suivante <nom-service>.<nom-namespace>.svc.cluster.local