Kubernetes : comment écrire un Deployment ?

Kubernetes : comment écrire un Deployment ?

Dans cet article nous allons discuter d’un objet central dans Kubernetes : le Deployment. L’objectif est d’en comprendre le comportement et d’éviter quelques pièges dans sa configuration.

C’est quoi un “Deployment” ?

Un Deployment est un des objets permettant de lancer des Pods. Dans les bonnes pratiques de Kubernetes il est encouragé d’utiliser des Deployments pour les applications stateless.

Lors de la création d’un Deployment le controller associé va créer un ReplicaSet à partir de votre configuration. Le controller associé au ReplicaSet va créer une série de Pod à partir de la configuration du ReplicaSet.

Les avantages d’utiliser un Deployment au lieu de créer directement un ReplicaSet sont :

  • Historisation de l’objet : chaque changement dans l’objet (via un “apply” ou un “edit”) va créer une sauvegarde de la version précédente.

  • Gestion du rollout et du rollback : en lien avec le point précédent, vous pourrez revenir en arrière sur une configuration.

Le Deployment est un objet stable depuis la version 1.9 de Kubernetes, disponible sur l’api “apps/v1”.

apiVersion: apps/v1
kind: Deployment 
metadata: # ici les metadata lié au Deployment 
  # Nom de l’objet, il doit être unique dans le namespace
  name: deployment-example
  namespace: default
spec: # Ici les spécifications du Deployment
  # Lance 3 Pods 
  replicas: 3
 selector:
    matchLabels:
      app: nginx
  template:
    metadata: # ici les metadata lié aux Pods (ne pas confondre avec les précédents) 
      labels:
        app: nginx
    spec: # Ici les spécifications des Pods
      containers: # un Pod peut contenir plusieurs conteneurs
      - name: web
        image: nginx:1.10

Dans ce cas, Kubernetes va lancer dans mon cluster 3 Pods contenant chacun un seul nginx.

Il faut faire attention à ce point, le Pod est l’unité de scalabilité dans Kubernetes il faut donc réfléchir au pattern qu’on veut utiliser si l’on met plusieurs conteneurs dans un même Pod : sidecar, ambassador, init container…

Ce Deployment est conforme, si vous le lancez vous pourrez l’utiliser mais il ne respecte pas l’ensemble des bonnes pratiques que nous verrons dans la suite de cet article.

Comment permettre l’orchestration ?

Kubernetes ce n’est pas de la magie ! Un orchestrateur ne fait qu’appliquer un algorithme qui exécute des actions en fonction de règles et de métriques. Il faut donc associer des contraintes à vos objets pour permettre la meilleure orchestration “possible” mais surtout “voulue”.

La gestion des ressources

Il existe trois catégories de Quality of Service (QoS) dans les Pods de Kubernetes :

  • BestEffort : ce seront les premiers “supprimés” par l’orchestrateur en cas de surcapacité
  • Burstable : ils ont une quantité de ressources minimum garantie par l’orchestrateur et peuvent monter en puissance
  • Guaranted : ils ont une quantité de ressources figées et ne doivent pas être supprimés par un réajustement du cluster

Ces niveaux de QoS sont spécifiés grâce au bloc suivant, ici un Pod avec une QoS “Guaranted” :

 - name: web
    image: nginx:1.10
    resources:
      limits:
        memory: "200Mi"
        cpu: "700m"
      requests:
        memory: "200Mi"
        cpu: "700m"

En fonction des quantités minimum “requests” et maximum “limits” mises en place, Kubernetes conclu le type de QoS à appliquer. Vous trouverez l’information dans la structure “status” après la création de l’objet.

Si plusieurs conteneurs sont inclus dans le Pod, Kubernetes choisira la classe de QoS la plus restrictive.

Les “probes”

Il existe deux types de sondes dans un Pod :

  • readinessProbe : savoir quand votre application a fini de démarrer
  • livenessProbe : savoir si votre application est fonctionnelle

Ces deux sondes sont essentielles. La première permet de ne pas diriger le trafic réseau vers un Pod encore en cours de démarrage, ce qui vous évitera des erreurs 500 lors de la scalabilité. La deuxième sonde permet à Kubernetes de redémarrer votre Pod en cas d’erreur applicative. En effet, votre application peut être en erreur sans que le conteneur soit arrêté.

Il y a plusieurs moyens de configurer vos sondes, via des appels HTTP ou TCP ou même en exécutant une commande régulièrement dans votre conteneur. Je vous invite à parcourir la documentation Kubernetes ici.

La stratégie de déploiement

Par défaut la stratégie de déploiement est le “rolling-update” mais vous pouvez également utiliser “recreate” qui supprime tous les anciens Pod pour en créer de nouveaux - ce qui induit forcément un downtime.

Si vous optez pour “rolling-update” vous pouvez configurer le comportement de celui-ci par rapport au nombre de réplicas voulu.

  • maxSurge permet d’indiquer (en pourcentage ou en absolu) combien de Pod il peut créer en plus du nombre de replica actuellement configurer
  • maxUnavailable permet d’indiquer (en pourcentage ou en absolu) combien de Pod peuvent être “non disponible” pendant la mise à jour, toujours en fonction du nombre de replica configuré

En fonction de votre application et de votre autoscaler (voir plus bas) ces configurations vous permettront d’assurer une QoS ou d'accélérer vos déploiements.

Configurer le “selector”

C’est une recommandation forte depuis la version “apps/v1” du Deployment.

Dans les versions suivante de Kubernetes, les selectors utilisés sur les objets se rapportant au Deployment ou à ses objets associés (ReplicaSet et Pod) utiliseront cette structure à la place des labels définis dans la structure metadata.

spec: # Ici les spécifications du Deployment
  # Lance 3 Pods 
  replicas: 3
 selector:
    matchLabels:
      app: nginx
  template:

Comment configurer mes conteneurs ?

Le Deployment étant l’objet qui crée à terme les Pods, la configuration de l’ensemble de mes applications est faite à cet endroit là.

Les variables d’environnement

Ces variables peuvent être issues de plusieurs objets ou directement indiquées dans le Yaml.

En général les variables utilisées dans de nombreuses applications (nom d’un bucket, nom d’une file de message, etc.) sont stockées dans un objet de type ConfigMap.

Les mots de passe, token ou les clefs sont injectés en utilisant l’objet Secret.

     - name: web
        image:nginx:1.10
        ports:
        - containerPort: 3000
        env:
        - name: PORT
          value: 3000
        - name: LANGUAGE
          valueFrom:
            configMapKeyRef:
              name: language
              key: LANGUAGE
        - name: API_KEY
          valueFrom:
            secretKeyRef:
              name: apikey
              key: API_KEY

Les volumes

Il est également possible d’utiliser des volumes pour la configuration ou pour stocker vos données s’il sont associés à un PersistentVolumeClaim.

     containers:
     - name: prometheus
       image: quay.io/prometheus/prometheus:v2.3.2
       args:
         - '--config.file=/etc/prometheus/prometheus.conf'
       volumeMounts:
       - name: config-volume
         mountPath: /etc/prometheus
     volumes:
     - name: config-volume
       configMap:
         name: prometheus-conf

Que l’on associe à la ConfigMap suivante :

apiVersion: v1
kind: ConfigMap
metadata:
 name: prometheus-conf
data:
 prometheus.conf: |-
   # la conf de prometheus...

A chaque volume est associé un driver (la liste est disponible ici) ainsi que des caractéristiques propres à celui-ci.

Comment gérer la scalabilité ?

Le Deployment défini aujourd’hui un nombre fixe de replica. Heureusement, il existe des objets permettant de mettre en place un auto-scaling.

HorizontalPodAutoscaler

La scalabilité horizontale est la possibilité de faire varier le nombre de Pod en fonction de métriques d’utilisation.

Dans les premières version du HPA, il n’était possible de scaler qu’en fonction des métriques CPU et RAM.
Depuis la version stable du HPA, il est possible d’utiliser des “custom metrics”, ce qui ouvre la possibilité de piloter le nombre de Pod en fonction de métriques métier comme la latence ou le nombre de connexions ouvertes sur un Pod.

Il est également possible de combiner plusieurs critères pour la scalabilité. Dans ce cas là règle logique appliquée est “ET”. Chacune des conditions est donc nécessaire pour enclencher le changement de replica.

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: web-hpa
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      targetAverageUtilization: 5

VerticalPodAutoscaler

Beaucoup moins connu que le précédent, il fait néanmoins parti des autoscaler référencés sur le GitHub de Kubernetes.

La scalabilité verticale consiste à faire varier les ressources disponibles pour un Pod en fonction de l’utilisation. Pour cela le VPA modifie les valeurs de ressource (cf ci-dessus).

Attention, ce projet est en version “alpha” d’après la documentation.

Conclusion

Le Deployment est un objet indispensable lors d’un déploiement sous Kubernetes. Comme un grand pouvoir implique une grande responsabilité, il faut faire attention lors de sa configuration sous peine d’avoir des comportements inattendus.
Certaines des explications de cet article s’appliquent également à d’autres objets Kubernetes.

Je vous encourage à remplacer vos anciens objets ReplicationController et vos ReplicaSet par un Deployment.

Pour aller plus loin avec les configurations du Deployment, je vous renvoie sur la documentation de Kubernetes.