Déployer une application sur Kubernetes

Déployer une application sur Kubernetes

Table of Contents

Introduction

Aujourd'hui, je vous embarque à bord de mon voyage, ce voyage consiste à progressivement remplacer mes conteneurs Docker par des services Kubernetes dans mon homelab.

Pourquoi ce changement ?
Car étant encore étudiant et en plein dans le sujet, quoi de mieux que de mettre les mains dans le camboui ?


Préparation

La cible

Parmi la liste des applications que je dois migrer, j'ai dû en choisir une, et Mealie a été l'heureuse gagnante.
C'est quoi Mealie ? Mealie est un gestionnaire de recettes de cuisine accessible via navigateur, entièrement self hosté.

L'existant

---
services:  
  mealie:
    image: ghcr.io/mealie-recipes/mealie:latest 
    container_name: mealie-front-01
    restart: always
    networks:
      - mealie-network
      - reverse-proxy
    labels:
      - traefik.enable=true
      - traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https
      - traefik.http.routers.mealie-https.entrypoints=https
      - traefik.http.routers.mealie-https.rule=Host(`mealie.homelab.domain.fr`)

    volumes:
      - mealie-vol:/app/data/
    env_file:
      - /opt/docker/apps/mealie/stack.env

networks:
  reverse-proxy:
    external: true
  mealie-network: {}

volumes:
  mealie-vol: {}

Ci-haut, vous pouvez voir le yaml docker-compose ayant servi à créer mon conteneur Mealie.

Nous pouvons y observer:

  • Une image en tag latest (on ne juge pas !).
  • 2 Réseaux alloués
    • mealie-network étant le réseau propre de cette stack.
    • reverse-proxy permettant de relier le conteneur au conteneur Reverse Proxy traefik, permettant d'exposer Mealie et de lui attribuer un Certificat Let's Encrypt.
  • Des labels:
    • Des middlewares classiques pour sécuriser l'accès au service Mealie.
    • Deux routeurs, un https pour forcer le port d'entrée sur le 443 et un routeur pour attribuer un hostname au conteneur.
  • Un volume Docker monté sur le chemin /app/data du conteneur
  • Un fichier d'environnement contenant toutes les clés-valeurs requises pour que Mealie fonctionne.
  • Le port d'exposition du Conteneur dans le pod (ici, 9000)

La migration

Maintenant que nous avons décortiqué les éléments présents, il va falloir les traduire en manifests Kubernetes.
Les manifests Kubernetes sont des fichiers yaml déclaratif qui vont dire à Kubernetes comment on veut que notre application soit déployée.

Il est important de noter que mes volumes sont provisonnés sur mon TrueNAS Scale grâce à une StorageClass via le protocole NFS, contrairement à Docker, où les volumes étaient stockés en local.
Mealie étant une application tournant sur du LiteSQL, le site de l'application ne recommande pas de faire du LiteSQL avec du NFS, j'ai donc dû opter pour une PostgreSQL dédiée.

(Petite information, il existe un binaire nommé kompose qui permet de faire une traduction d'un fichier docker-compose en manifests yaml, mais ça s'appelle enlever le fun de la découverte.)

Reprenons notre liste à puce de la partie précédente.
Il nous faut:

  1. Il nous faut un objet de type Deployment, il va servir à déployer les Pods avec des configuration que j'aurais définie.
    Ce manifest yaml contiendra:

    • L'image Docker de Mealie (On enlève le tag latest, c'est risqué de pointer sur un tag d'image en mouvement perpétuel).
    • Le nombre de Replicas du pod (Un replica est une copie d'un pod, si l'un tombe l'autre prend la relève, sert aussi de loadbalancing en cas de workload important).
    • Un appel à des clés-valeurs d'une Configmap, qui est un objet contenant ici mes variables d'environnement.
    • Un appel a des clés-valeurs d'un Secret, qui est un objet qui contient ici les mots de passe de la base de donnée PostgreSQL.
    • Un appel à un PersistentVolumeClaim, qui lui même est lié à un PersistentVolume qui sera monté sur app/data nous reviendrons plus tard dessus, mais retenez que c'est le volume.
  2. Un objet de type StatefulSet pour déployer la base de donnée.
    Le StatefulSet fonctionne presque exactement comme un Deployment, mais il est fait pour les services type stateful, les pods sont identifiés et non-interchangeable.

  3. Ensuite, on va avoir besoin de créer les objets Configmap et Secret.
    Ces objets permettent de stocker sous forme de clé-valeur des informations, que ce soit des variables d'environnement, des arguments de commande, etc.., ils peuvent ensuite être consommés par des pods.
    La différence entre un Secret et une Configmap réside dans le fait que les Secrets sont encodés en base64, et sont fait pour contenir des données sensibles.
    Exemple d'une configmap:

    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: mealie-configmap
      namespace: mealie
    data:
      ALLOW_SIGNUP: "false"
      TZ: "Europe/Paris"
      DB_ENGINE: "postgres"
      POSTGRES_USER: admin
  4. Pour que les pods puissent communiquer entre eux ET que le pod du front web soit disponible dehors, il nous faut des objets de type Services.
    Les Services permettent d'exposer un ou plusieurs pods, car les pods de nature sont amenés à être détruits et recréés, et leur IP changent également.
    La logique d'un service fonctionne donc comme ça:
    Les Pods ont des labels sous forme de clé valeur --> Les Services qui ont une IP statique visent ces labels, et sont donc attribués à ces pods, le service peut même faire du LoadBalancing entre les pods qu'il expose.
    Un service au minimum demande:

    • Le protocole du port qu'on va exposer (TCP ou UDP).
    • Le port que le Service expose (donc son propre port).
    • Le port du conteneur que le Service va viser.

Pour nos besoins, il faudra créer un service pour exposer la base de donnée et un service pour exposer le front.

  1. Pour les volumes, il faut faire une introduction aux PersistentVolumes (PV) et aux PersistentVolumeClaims (PVC).
    Un PV est un volume de stockage défini provisionné manuellement ou dynamiquement (via une StorageClass).
    Le PVC quant à lui est une requête pour consommer un PV.
    Dans mon cas à moi, j'utilise une StorageClass qui est issue du CSI (Container Storage Interface) DemocraticCSI, ça me permet de créer un CSIDriver spécifiquement fait pour interagir avec l'API de TrueNAS, afin de provisionner des PV stockés sur un Dataset ZFS via le protocole NFS.
    Il nous faut donc 2 volumes:
  • Le premier sera monté sur le app/data du front de Mealie, il stockera les Metadata du front, et fera 1Gi.
  • Le deuxième sera le volume de la base de donnée PostgreSQL, ce dernier ne sera pas défini dans son propre manifest yaml mais directement dans le manifest du StatefulSet.
  1. Nous en avons terminé avec les objets qui constituent l'application en elle-même, pour finir il nous faut un objet de type Ingress.
    Un Ingress c'est un objet créé par un IngressController, vous connaissez surement quelques noms connus d'IngressController: Nginx, Traefik, HAProxy, par exemple, pour ma part, j'utilise Traefik car je m'en servais déjà sur Docker.
    Un Ingress est un Reverse Proxy et LoadBalancer de couche 7, il agit comme un point d'entrée pour des routes HTTP et HTTPS et redirige les requêtes selon les règles qu'il contient, par exemple, il peut rediriger une requête vers un objet de type service précis, lui même redirigeant à un pod , dépendant de l'URL (machin.fr/app qui redirige au service app).
    Il peut également gérer plusieurs noms de domaine, utiliser des headers de sécurité et implémenter un certificat SSL/TLS.
    Normalement, il faudrait créer un objet de type certificat contenu dans un Secret Kubernetes et appeler ce secret dans le manifest de l'Ingress, mais comme j'utilise Cert-Manager qui est relié à mon domaine dans Cloudflare, je n'ai qu'à rajouter une annotation dans l'objet pour faire une requête de Certificat Let's Encrypt.
    Je ne rentrerais pas en détail dans le sujet de Cert-Manager, cependant, voici la documentation officielle du projet.

Construction

.
└── base
    ├── kustomization.yaml
    ├── mealie-configmap.yaml
    ├── mealie-db-service.yaml
    ├── mealie-deployment.yaml
    ├── mealie-pvc.yaml
    ├── mealie-secret.yaml
    ├── mealie-statefulset.yaml
    ├── mealie-ingressroute.yaml
    └── mealie-web-service.yaml

Bon, appliquer tous ces fichiers yaml à la main, ça serait VRAIMENT pénible.
Comment faire pour tout faire d'un coup ?
Avec la commande CLI builtin et les manifests Kustomize.

Voici un exemple d'un manifest kustomize:

---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - mealie-deployment.yaml
  - mealie-statefulset.yaml
  - mealie-secret.yaml
  - mealie-web-service.yaml
  - mealie-db-service.yaml
  - mealie-ingressroute.yaml
  - mealie-configmap.yaml
  - mealie-pvc.yaml

Pour appliquer ce fichier il faut lancer la commande kubectl apply -k ./base, le -k spécifie qu'on vise un manifest kustomize.
Kustomize va donc appeler chaque manifest yaml de la liste définie dans resources et les appliquer un à un dans Kubernetes.
On peut faire des choses très utiles avec Kustomize, mais ce n'est pas le but de l'article. Néamoins, Voici un article en parlant.

Conclusion

Et voilà ! À table !


Au final si jamais vous vous posez la question, est-ce que vous devez utiliser Kubernetes ou Docker dans votre homelab, sachez une chose.

Utiliser Docker est vraiment vraiment plus simple dans un petit environnement, quel intérêt d'utiliser Kubernetes ?
Apprendre et évoluer ! Bien sûr c'est suffisant d'utiliser Docker, mais Kubernetes apporte son lot d'avantage, de satisfaction, (de soirée perdues).

Matheo Rodriguez

Les commentaires sont fermés.