TP 3 - Déployer des conteneurs de A à Z

Ce TP va consister à créer des objets Kubernetes pour déployer une stack d’exemple : monster_stack. Elle est composée :

  • d’un front-end en Flask (Python),
  • d’un backend qui génère des images (un avatar de monstre correspondant à une chaîne de caractères),
  • et d’une base de données servant de cache pour ces images, Redis.

Vous pouvez utiliser au choix votre environnement Cloud ou Minikube.

Rappel : Installer Lens

Lens est une interface graphique sympathique pour Kubernetes.

Elle se connecte en utilisant la configuration ~/.kube/config par défaut et nous permettra d’accéder à un dashboard bien plus agréable à utiliser.

Vous pouvez l’installer en lançant ces commandes :

sudo apt-get update; sudo apt-get install -y libxss-dev
curl -fSL https://github.com/lensapp/lens/releases/download/v4.0.6/Lens-4.0.6.AppImage -o ~/Lens.AppImage
chmod +x ~/Lens.AppImage
~/Lens.AppImage &

Déploiement de la stack monsterstack

Les pods sont des ensembles de conteneurs toujours gardés ensembles.

Nous voudrions déployer notre stack monster_app. Nous allons commencer par créer un pod avec seulement notre conteneur monstericon.

  • Créez un projet vide monster_app_k8s.

  • Créez le fichier de déploiement suivant:

monstericon.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: monstericon 
  labels:
    <labels>

Ce fichier exprime un objet déploiement vide.

  • Ajoutez le label app: monsterstack à cet objet Deployment.

  • Pour le moment notre déploiement n’est pas défini car il n’a pas de section spec:.

  • La première étape consiste à proposer un modèle de ReplicaSet pour notre déploiement. Ajoutez à la suite (spec: doit être à la même hauteur que kind: et metadata:) :

spec:
  template:
    spec:

Remplissons la section spec de notre pod monstericon à partir d’un modèle de pod lançant un conteneur Nginx :

        containers:
        - name: nginx
          image: nginx:1.7.9
          ports:
            - containerPort: 80
  • Remplacez le nom du conteneur par monstericon, et l’image de conteneur par tecpi/monster_icon:0.1, cela récupérera l’image préalablement uploadée sur le Docker Hub (à la version 0.1)
  • Complétez le port en mettant le port de production de notre application, 9090

  • Les objets dans Kubernetes sont hautement dynamiques. Pour les associer et les désigner on leur associe des labels c’est-à-dire des étiquettes avec lesquelles on peut les retrouver ou les matcher précisément. C’est grâce à des labels que k8s associe les pods aux ReplicaSets. Ajoutez à la suite au même niveau que la spec du pod :

    metadata:
      labels:
        app: monsterstack
        partie: monstericon

A ce stade nous avons décrit les pods de notre déploiement avec leurs labels (un label commun à tous les objets de l’app, un label plus spécifique à la sous-partie de l’app).

Maintenant il s’agit de rajouter quelques options pour paramétrer notre déploiement (à la hauteur de template:) :

  selector:
    matchLabels:
      app: monsterstack
      partie: monstericon
  strategy:
    type: Recreate

Cette section indique les labels à utiliser pour repérer les pods de ce déploiement parmi les autres.

Puis est précisée la stratégie de mise à jour (rollout) des pods pour le déploiement : Recreate désigne la stratégie la plus brutale de suppression complète des pods puis de redéploiement.

Enfin, juste avant la ligne selector: et à la hauteur du mot-clé strategy:, ajouter replicas: 3. Kubernetes crééra 3 pods identiques lors du déploiement monstericon.

Le fichier monstericon.yaml jusqu’à présent :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: monstericon
  labels:
    app: monsterstack
spec:
  template:
    spec:
      containers:
      - name: monstericon
        image: tecpi/monster_icon:0.1
        ports:
        - containerPort: 9090
    metadata:
      labels:
        app: monsterstack
        partie: monstericon
  selector:
    matchLabels:
      app: monsterstack
      partie: monstericon
  strategy:
    type: Recreate
  replicas: 3

Appliquer notre déploiement

  • Avec la commande apply -f appliquez notre fichier de déploiement.
  • Affichez les déploiements avec kubectl get deploy -o wide.
  • Listez également les pods en lançant kubectl get pods --watch pour vérifier que les conteneurs tournent.
  • Ajoutons un healthcheck de type readinessProbe au conteneur dans le pod avec la syntaxe suivante (le mot-clé readinessProbe doit être à la hauteur du i de image:) :
        readinessProbe:
          failureThreshold: 5 # Reessayer 5 fois
          httpGet:
            path: /
            port: 9090
            scheme: HTTP
          initialDelaySeconds: 30 # Attendre 30s avant de tester
          periodSeconds: 10 # Attendre 10s entre chaque essai
          timeoutSeconds: 5 # Attendre 5s la reponse
          

Ainsi, k8s sera capable de savoir si le conteneur fonctionne bien en appelant la route /. C’est une bonne pratique pour que Kubernetes sache quand redémarrer un pod.

  • Ajoutons aussi des contraintes sur l’usage du CPU et de la RAM, en ajoutant à la même hauteur que image: :
      resources:
        requests:
          cpu: "100m"
          memory: "50Mi"

Nos pods auront alors la garantie de disposer d’un dixième de CPU et de 50 mégaoctets de RAM.

  • Lancer kubectl apply -f monstericon.yaml pour appliquer.
  • Avec kubectl get pods --watch, observons en direct la stratégie de déploiement type: Recreate
  • Avec kubectl describe deployment monstericon, lisons les résultats de notre readinessProbe, ainsi que comment s’est passée la stratégie de déploiement type: Recreate

monstericon.yaml final :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: monstericon
  labels:
    app: monsterstack
spec:
  template:
    spec:
      containers:
      - name: monstericon
        image: tecpi/monster_icon:0.1
        ports:
        - containerPort: 9090
        readinessProbe:
          failureThreshold: 5 # Reessayer 5 fois
          httpGet:
            path: /
            port: 9090
            scheme: HTTP
          initialDelaySeconds: 30 # Attendre 30s avant de tester
          periodSeconds: 10 # Attendre 10s entre chaque essai
          timeoutSeconds: 5 # Attendre 5s la reponse
        resources:
          requests:
            cpu: "100m"
            memory: "50Mi"
    metadata:
      labels:
        app: monsterstack
        partie: monstericon
  selector:
    matchLabels:
      app: monsterstack
      partie: monstericon
  strategy:
    type: Recreate
  replicas: 5

Déploiement semblable pour dnmonster

Maintenant nous allons également créer un déploiement pour dnmonster:

  • créez dnmonster.yaml et collez-y le code suivant :

dnmonster.yaml :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dnmonster 
  labels:
    app: monsterstack
spec:
  selector:
    matchLabels:
      app: monsterstack
      partie: dnmonster
  strategy:
    type: Recreate
  replicas: 5
  template:
    metadata:
      labels:
        app: monsterstack
        partie: dnmonster
    spec:
      containers:
      - image: amouat/dnmonster:1.0
        name: dnmonster
        ports:
        - containerPort: 8080

Enfin, configurons un troisième deployment redis :

redis.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis 
  labels:
    app: monsterstack
spec:
  selector:
    matchLabels:
      app: monsterstack
      partie: redis
  strategy:
    type: Recreate
  replicas: 1
  template:
    metadata:
      labels:
        app: monsterstack
        partie: redis
    spec:
      containers:
      - image: redis:latest
        name: redis
        ports:
        - containerPort: 6379

Exposer notre stack avec des services

Les services K8s sont des endpoints réseaux qui balancent le trafic automatiquement vers un ensemble de pods désignés par certains labels.

Pour créer un objet Service, utilisons le code suivant, à compléter :

apiVersion: v1
kind: Service
metadata:
  name: <nom_service>
  labels:
    app: monsterstack
spec:
  ports:
    - port: <port>
  selector:
    app: <app_selector> 
    partie: <tier_selector>
  type: <type>
---

Ajoutez le code suivant au début de chaque fichier déploiement. Complétez pour chaque partie de notre application : - le nom du service et le nom de la partie par le nom de notre programme (monstericon, dnmonster et redis) - le port par le port du service - les selectors app et partie par ceux du ReplicaSet correspondant.

Le type sera : ClusterIP pour dnmonster et redis, car ce sont des services qui n’ont à être accédés qu’en interne, et LoadBalancer pour monstericon.

Appliquez vos trois fichiers.

  • Listez les services avec kubectl get services.
  • Visitez votre application dans le navigateur avec minikube service <nom-du-service-monstericon>.

Rassemblons les trois objets avec une kustomisation.

Une kustomization permet de résumer un objet contenu dans de multiples fichiers en un seul lieu pour pouvoir le lancer facilement:

  • Créez un dossier monster_stack pour ranger les trois fichiers:

    • monstericon.yaml
    • dnmonster.yaml
    • redis.yaml
  • Créez également un fichier kustomization.yaml avec à l’intérieur:

resources:
    - monstericon.yaml
    - dnmonster.yaml
    - redis.yaml
  • Essayez d’exécuter la kustomization avec kubectl apply -k . depuis le dossier monster_stack.

Ajoutons un loadbalancer ingress pour exposer notre application sur le port standard

Installons le contrôleur Ingress Nginx avec minikube addons enable ingress.

Il s’agit d’une implémentation de loadbalancer dynamique basée sur nginx configurée pour s’interfacer avec un cluster k8s.

Ajoutez également l’objet de configuration du loadbalancer suivant dans le fichier monster-ingress.yaml :

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: monster-ingress 
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
        - path: /monstericon
          backend:
            serviceName: monstericon
            servicePort: 9090
  • Ajoutez ce fichier à notre kustomization.yaml

  • Relancez la kustomization.

Vous pouvez normalement accéder à l’application en faisant minikube service monstericon --url et en ajoutant /monstericon pour y accéder.

Solution

Le dépôt Git des solutions est accessible ici : https://github.com/Uptime-Formation/tp2_k8s_monsterstack_correction