De Docker Compose à Helm#

Dans cet exercice, nous allons migrer une application web complète, définie avec Docker Compose, vers Kubernetes en créant notre propre chart Helm. (Si cela fait beaucoup pour vous, un exemple de solution est donné en fin d’exercice et vous permettra de vous “débloquer”)

L’application de départ#

L’application est un simple compteur de visites qui utilise une application backend en Flask, une base de données Redis pour la persistance, et un HAProxy comme reverse proxy. L’objectif est de reproduire cette architecture sur Kubernetes.

Voici l’arborescence du projet de départ :

projet-flask/
├── backend/
│   ├── app.py
│   ├── Dockerfile
│   └── requirements.txt
├── haproxy/
│   └── haproxy.cfg
└── docker-compose.yml

Fichiers de l’application#

  • docker-compose.yml: Orchestre les services backend, database (Redis), et proxy (HAProxy).

    version: '3.8'
    services:
      backend:
        build: ./backend
        ports:
          - "5000"
        environment:
          - REDIS_HOST=database
      database:
        image: "redis:alpine"
      proxy:
        image: "haproxy:2.5"
        volumes:
          - ./haproxy:/usr/local/etc/haproxy:ro
        ports:
          - "80:80"
        depends_on:
          - backend
    
  • backend/app.py: L’application Flask qui se connecte à Redis.

    from flask import Flask
    from redis import Redis
    import os
    
    app = Flask(__name__)
    redis = Redis(host=os.environ.get('REDIS_HOST', 'localhost'), port=6379)
    
    @app.route('/')
    def hello():
        count = redis.incr('hits')
        return f'Hello! Cette page a été vue {count} fois.\n'
    
    if __name__ == "__main__":
        app.run(host="0.0.0.0", port=5000, debug=True)
    
  • backend/requirements.txt: Les dépendances Python.

    flask
    redis
    
  • backend/Dockerfile: Pour construire l’image du backend.

    FROM python:3.9-slim
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install -r requirements.txt
    COPY . .
    CMD ["flask", "run", "--host=0.0.0.0"]
    
  • haproxy/haproxy.cfg: La configuration pour HAProxy qui redirige le trafic vers le backend.

    global
        daemon
    
    defaults
        mode http
    
    frontend http_front
        bind *:80
        default_backend http_back
    
    backend http_back
        server backend1 backend:5000
    

Votre mission#

Votre tâche est de créer un chart Helm qui déploie cette application sur Kubernetes.

1. Créez l’arborescence du chart#

Dans un nouveau dossier, créez la structure de base d’un chart Helm :

mon-chart/
├── Chart.yaml
├── values.yaml
└── templates/

2. Rédigez le Chart.yaml#

Ce fichier contient les métadonnées de votre chart. Inspirez-vous des exercices précédents pour le remplir.

apiVersion: v2
name: mon-application-flask
description: Un chart pour déployer une application Flask avec Redis.
type: application
version: 0.1.0
appVersion: "1.0.0"

3. Définissez les valeurs par défaut dans values.yaml#

C’est ici que nous allons rendre notre chart configurable. Définissez les valeurs pour nos deux services, api et frontend.

backend:
  image:
    repository: <votre-registre>/mon-app-flask # À remplacer par l'image que vous aurez buildée
    tag: "latest"

redis:
  image:
    repository: redis
    tag: "alpine"

ingress:
  enabled: true
  host: flask.192.168.49.2.nip.io # Remplacez par l'IP de votre noeud minikube

4. Créez les templates Kubernetes#

Dans le dossier templates/, créez les manifestes pour les objets Kubernetes.

  • Déploiement et Service pour Redis (deployment-redis.yaml et service-redis.yaml) Créez un Deployment pour Redis et un Service de type ClusterIP nommé redis-service pour que le backend puisse s’y connecter.

  • Déploiement et Service pour le Backend (deployment-backend.yaml et service-backend.yaml) Créez un Deployment pour l’application Flask. Assurez-vous de passer la variable d’environnement REDIS_HOST avec le nom du service Redis (redis-service). Créez également un Service pour exposer le backend.

  • Ingress (ingress.yaml) Créez un Ingress qui expose le service du backend. Cet objet remplacera notre HAProxy.

Exemple de solution — Attention, ne dérouler qu'après avoir essayé

Votre dépôt a la structure suivante :

mon-chart/
├── Chart.yaml
├── values.yaml
└── templates/
    ├── deployment-backend.yaml
    ├── deployment-redis.yaml
    ├── service-backend.yaml
    ├── service-redis.yaml
    └── ingress.yaml

templates/deployment-redis.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
        - name: redis
          image: "{{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }}"
          ports:
            - containerPort: 6379

templates/service-redis.yaml

Service interne (ClusterIP) pour que le backend puisse parler à Redis.

apiVersion: v1
kind: Service
metadata:
  name: redis-service
spec:
  type: ClusterIP
  selector:
    app: redis
  ports:
    - port: 6379
      targetPort: 6379

templates/deployment-backend.yaml

Deployment pour l’appli Flask.
On lui passe REDIS_HOST=redis-service pour qu’elle trouve Redis via le Service.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
        - name: backend
          image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}"
          ports:
            - containerPort: 5000
          env:
            - name: REDIS_HOST
              value: "redis-service"

templates/service-backend.yaml

Service interne sur le port 5000 (celui exposé par Flask).

apiVersion: v1
kind: Service
metadata:
  name: backend
spec:
  type: ClusterIP
  selector:
    app: backend
  ports:
    - port: 5000
      targetPort: 5000

templates/ingress.yaml

Ingress qui remplace le HAProxy du docker‑compose : il expose le Service backend vers l’extérieur.

{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: flask-ingress
spec:
  rules:
    - host: {{ .Values.ingress.host }}
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: backend
                port:
                  number: 5000
{{- end }}

Avec cette structure, on a :

  • un fichier par objet K8s,

  • les images et le host d’Ingress configurables via values.yaml,

  • un Ingress qui remplace le rôle de HAProxy dans le docker-compose.yml.

5. Testez et déployez#

  1. Construisez et poussez l’image du backend sur un registre de conteneurs (comme celui de GitLab ou Docker Hub).

  2. Mettez à jour votre values.yaml avec le nom correct de l’image.

  3. Vérifiez le rendu de votre chart avec helm template ..

  4. Déployez votre chart sur le cluster.

Expérimentations#

  1. Accédez à votre application via l’URL de l’Ingress et rafraîchissez la page plusieurs fois. Le compteur s’incrémente-t-il ?

  2. Supprimez le pod du backend. Que se passe-t-il ? Le compteur conserve-t-il sa valeur ?

  3. Modifiez le tag de l’image Redis dans values.yaml et redéployez. Observez le déploiement.