angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Dois-je migrer mon Docker Swarm vers Kubernetes ?

J'ai migré un projet Docker-Compose vers Kubernetes et j'ai décidé d'opter pour Kubernetes.

15 septembre 2023
post main image
https://www.pexels.com/nl-nl/@fotios-photos

Quand on lit des articles sur internet disant que Docker Swarm est mort, on a peur. J'ai un Docker Swarm qui fonctionne et je l'aime bien, c'est facile quand on utilise déjà Docker.

Quelles sont les alternatives ? Nous avons toujours lu qu'il n'y avait qu'une seule chose à faire, c'est de migrer vers Kubernetes et d'oublier tout le reste.

J'étais à mi-chemin de la migration de Docker à Docker Swarm, et je voulais savoir si je devais continuer ou me concentrer sur Kubernetes.

L'étape suivante la plus logique serait peut-être d'utiliser Docker Desktop parce qu'elle inclut Kubernetes. Cependant, je n'utilise pas Docker Desktop, j'utilise aussi Docker Swarm sur ma machine de développement et c'est parfait.

Revenons à Kubernetes. Toutes mes applications tournent sur Ubuntu et Debian et après quelques lectures supplémentaires, Microk8s semble être un bon choix, vous pouvez l'utiliser pour le développement et la production. Supposons que nous passions à Microk8s, que faut-il faire pour migrer ? J'ai utilisé l'un de mes projets Docker-Compose actuels pour le découvrir.

Je ne montre pas toutes les detail du projet Docker-Compose.

Comme toujours, je suis sur Ubuntu 22.04.

Mon application

Mon application se compose de plusieurs blocs. Chaque bloc comprend un ou plusieurs projets Docker-Compose. Pour l'instant, seuls les projets Task Runners sont gérés par Docker Swarm. Ils se connectent au Backend en utilisant un réseau superposé Ingress .

   +----------+   +---------+         +-------------+
 --| Frontend |---| Backend |----+----| Task Runner |   
   +----------+   +---------+    |    +-------------+
                                 |
                                 |    +-------------+
                   Ingress ->    +----| Task Runner |   
                   overlay       |    +-------------+
                   network       //
                                 |    +-------------+
                                 +----| Task Runner |   
                                      +-------------+

          manage with              manage with
   |<--- Docker-Compose --->|<---- Docker Swarm ---->|
                                        v
                                      migrate
                                        v
                                   manage with
                            |<----- Kubernetes ---->|

Le Task Runner est un projet Docker-Compose avec cinq services :

rabbitmq
task
local-web
unbound-cloudflare
unbound-quad9

local-web' est une image Nginx légèrement modifiée, qui renvoie une page web personnalisée.

                          +-------------+
                          |  local-web  |   
                          +-------------+
                                 ^
                                 |
     +----------+       +-----------------+
     |          |       |                 |-+
     |          |------>|                 | |-+ 
 <-->| rabbitmq |       |      task       | | |
     |          |<------|                 | | |
     |          |       |                 | | |
     +----------+       +-----------------+ | |
                              |     | ------+ |
                              |     | --------+
     +--------------------+   |     |
     | unbound-cloudflare |<--+     |
     +--------------------+         |
                                    |
     +--------------------+         |
     | unbound-quad9      |<--------+
     +--------------------+

Le rabbitmq-service se connecte au Backend. Avec Docker Swarm , je crée des répliques de task-service.

Voici une version dépouillée du fichier de projet Docker-Compose :

# docker-compose.yml 

services:
  rabbitmq:
    ...
    networks:
      task-runner-network
  task:
    ...
  local-web:
    ...
  unboud-cloudflare:
    ...

networks:
  task-runner-network:
    external: true

Seul rabbitmq est connecté à un réseau externe. Dans ce billet, je vais exécuter un Task Runner sur un Kubernetes, avec le service 'task' répliqué.

Questions et réponses

Avant de commencer le travail, j'ai dressé une liste de questions et j'ai essayé d'obtenir des réponses. Les voici :

Un Pod est-il la même chose qu'un conteneur Docker ?

Non, ce n'est pas le cas. D'après la documentation : Un Pod est la plus petite unité de calcul déployable que vous pouvez créer et gérer dans Kubernetes. Un Pod peut contenir plusieurs conteneurs. Un Pod fournit un environnement permettant aux conteneurs de fonctionner et de partager les ressources, le stockage, le réseau et la communication interprocessus. Ils partagent le même espace de noms réseau et peuvent communiquer entre eux à l'aide de localhost.

En résumé, sauf exception, vous devez utiliser un Pod par conteneur.

Pods communiquer à l'aide de services

Un conteneur dans un Pod communique avec un autre Pod en utilisant son IP address. Mais ce n'est pas la meilleure façon de procéder. En général, nous créons un service pour chaque Pod, et nous accédons au Pod via son service. Nous nous référons à un service en utilisant son nom, nous pouvons également mapper des ports ici.

Si nous avons un Pod avec des conteneurs répliqués, le Service dist distribuera les requêtes à travers ces conteneurs. Dans ce cas, le service agit également comme un équilibreur de charge. Cela ressemble beaucoup à Docker Swarm.

Quand utiliser Deployments ?

Un Deployment est utilisé pour gérer un Pod. Avec un Deployment , vous pouvez spécifier les répliques de Pod , l'emplacement de Pod sur les nœuds, la façon dont les nouvelles mises à jour sont publiées. Cela signifie que vous avez (presque) toujours besoin d'un Deployment pour un Pod. Comme nous pouvons spécifier un Pod à l'intérieur d'un Deployment, nous n'avons pas besoin de fichiers YAML distincts pour le Pods !

Comment effectuer la journalisation Pods ?

Les Pods ne font pas de journalisation. Ce sont les conteneurs qui génèrent les journaux. Cela n'est pas différent de la journalisation des Docker . Sur mon système, les fichiers de logs se trouvent dans le répertoire de l'hôte :

/var/log/pods

Peut-on assigner un répertoire sur l'hôte à un Pod comme Docker Volumes ?

Oui, nous pouvons utiliser la directive 'hostPath'.

La directive Kubernetes est-elle uniquement destinée à la production ou peut-elle également être utilisée pour le développement ?

La directive Microk8s peut être utilisée pour le développement et la production. Mais pour la production, nous pouvons également utiliser quelque chose d'autre, comme une variante Kubernetes d'un fournisseur de services en nuage. Sur ma machine de développement, j'utilise Microk8s avec Docker sans problème.

Dans Docker Swarm nous avons Task.Slot, quel est l'équivalent dans Kubernetes ?

Le Kubernetes 'StatefulSet' n'est pas exactement le même, mais il est comparable. Je ne l'ai pas encore essayé.

Peut-on connecter un réseau Docker Swarm à un Kubernetes Pod (conteneur) ?

La raison pour laquelle je voudrais cela est la migration. Bien sûr, nous pouvons toujours construire notre propre équilibreur de charge proxy, mais existe-t-il un moyen simple / standard de le faire ? Pour le moment, je crée un service 'nodePort'. Cela permet de faire correspondre le port d'un service à l'intérieur de Kubernetes à un port sur l'hôte.

Avant la conversion, vérifiez et corrigez les noms d'objets

J'avais tous ces jolis noms dans mes fichiers Docker-Compose et ils utilisaient des underscores ('_'). Dans Kubernetes , les noms d'objets doivent être conformes à RFC 1123, ce qui signifie :

  • 63 caractères au maximum.
  • Ils doivent commencer et se terminer par une lettre minuscule ou un chiffre.
  • Ils doivent contenir des lettres minuscules, des chiffres et des traits d'union.

Avant de faire quoi que ce soit, j'ai modifié cela dans mon projet Docker-Compose.

Kompose : Conversion du fichier du projet Docker-Compose

Essayons maintenant de convertir les fichiers Task Runner Docker-Compose en quelque chose de Kubernetes, en utilisant Kompose. Kompose est un outil de conversion de Docker Compose en Kubernetes. Et nous rencontrons immédiatement des problèmes. Kompose semble ne pas pouvoir gérer le fichier '.env' qui est pourtant essentiel. En outre (par conséquent ?), aucun fichier ConfigMap n'a été généré. Un problème a été signalé à ce sujet.

Je vérifierai si une nouvelle version est disponible dans les semaines à venir, mais pour l'instant, nous remplaçons les variables d'environnement en utilisant Docker-Compose 'config' :

> docker-compose -f docker_compose_shared_file.yml -f docker_compose_deployment_file.yml config > docker-compose-config.yml

puis en exécutant Kompose. Nous utilisons l'option hostPath ici parce que j'utilise des volumes utilisant des répertoires système locaux :

> kompose -v convert -f docker-compose-config.yml --volumes hostPath

Kompose a créé les fichiers suivants :

local-web-pod.yaml
rabbitmq-pod.yaml
rabbitmq-service.yaml
task-pod.yaml
unbound-cloudflare-pod.yaml
unbound-quad9-pod.yaml

Pour chaque service Docker-Compose, un fichier '-pod.yaml' est créé, et pour un seul, un fichier '-service.yaml'. J'ai également vu des exemples où Kompose générait Deployments au lieu de Pods. Pourquoi ? Pour moi, les résultats de Kompose ne sont qu'un point de départ, je dois créer les fichiers Deployment YAML à partir des fichiers Pod YAML et ajouter les fichiers YAML de Service. Vous pouvez probablement obtenir de meilleurs résultats avec Kompose (en ajoutant des instructions au fichier 'docker-compose.yml') mais je n'ai pas le temps d'expérimenter pour le moment.

Installation de Microk8s

Maintenant, mettons les mains dans le cambouis. Je suis sur Ubuntu 22.04 donc c'est facile, nous utilisons Microk8s pour obtenir la dernière version stable :

> sudo snap install microk8s --classic

Voyons ce qui est activé :

> microk8s status

Résultat :

microk8s is running
high-availability: no
  datastore master nodes: 127.0.0.1:19001
  datastore standby nodes: none
addons:
  enabled:
    dns                  # (core) CoreDNS
    ha-cluster           # (core) Configure high availability on the current node
    helm                 # (core) Helm - the package manager for Kubernetes
    helm3                # (core) Helm 3 - the package manager for Kubernetes
  disabled:
    cert-manager         # (core) Cloud native certificate management
    community            # (core) The community addons repository
    dashboard            # (core) The Kubernetes dashboard
    gpu                  # (core) Automatic enablement of Nvidia CUDA
    host-access          # (core) Allow Pods connecting to Host services smoothly
    hostpath-storage     # (core) Storage class; allocates storage from host directory
    ingress              # (core) Ingress controller for external access
    kube-ovn             # (core) An advanced network fabric for Kubernetes
    mayastor             # (core) OpenEBS MayaStor
    metallb              # (core) Loadbalancer for your Kubernetes cluster
    metrics-server       # (core) K8s Metrics Server for API access to service metrics
    minio                # (core) MinIO object storage
    observability        # (core) A lightweight observability stack for logs, traces and metrics
    prometheus           # (core) Prometheus operator for monitoring and logging
    rbac                 # (core) Role-Based Access Control for authorisation
    registry             # (core) Private image registry exposed on localhost:32000
    storage              # (core) Alias to hostpath-storage add-on, deprecated

Activer le stockage par chemin d'accès

Certains services dans mon fichier docker-compose.yml utilisent des données persistantes dans des répertoires sur localhost. Activer les services de stockage dans Microk8s :

> microk8s.enable hostpath-storage

Tableau de bord Kubernetes

Microk8s inclut le tableau de bord Kubernetes . Commencez par l'activer :

> microk8s enable dashboard

Il existe plusieurs options pour le démarrer, mais celle-ci est facile :

> microk8s dashboard-proxy

Ensuite, dans votre navigateur, naviguez vers :

https://127.0.0.1:10443

Acceptez le certificat auto-signé, copiez-collez le jeton du terminal et c'est parti.

Arrêter et démarrer

Pour arrêter et démarrer Microk8s :

> microk8s stop
> microk8s start

Utilisation de kubectl

Kubectl est un outil de ligne de commande utilisé pour exécuter des commandes afin de gérer les clusters Kubernetes .

Pour utiliser 'kubectl' au lieu de 'microk8s kubectl' :

> alias kubectl='microk8s kubectl'

Pour rendre cela permanent, ajoutez ceci à votre fichier 'bashrc' :

> echo "alias kubectl='microk8s kubectl'" >> ~/.bashrc

Créez beaucoup d'alias, ou vos doigts se fatigueront !

Obtenir de l'aide, par exemple sur les ports d'un conteneur dans un Pod :

> kubectl explain pod.spec.containers.ports

Pour créer et mettre à jour les objets Kubernetes , nous pouvons utiliser une syntaxe déclarative ('apply'), ou impérative ('create').

Voici quelques commandes :

> kubectl get nodes
> kubectl apply -f <manifest file>
> kubectl get pods -o wide
> kubectl get deployments
> kubectl get services
> kubectl delete pods local-web
> kubectl describe pods local-web
> kubectl logs local-web

Pour obtenir les derniers événements :

> kubectl get events

Et, pour tout obtenir :

> kubectl get all -A

Rappelez-vous qu'un objet Deployment contient une spécification ReplicaSet et une spécification Pod , ce qui signifie que si vous créez un objet Deployment, vous obtiendrez un ou plusieurs objets Pods en fonction des réplicas que vous avez spécifiés.

Déployer un Pod

Peut-on déployer un des Pods créés par Kompose ? Kompose a créé le fichier suivant pour nous :

local-web-pod.yaml

Sans éditer ce fichier, nous essayons de déployer ce Pod :

> kubectl apply -f local-web-pod.yaml

Résultat :

pod/local-web created

Le Pod est-il présent ?

> kubectl get pods

Résultat :

NAME        READY   STATUS    RESTARTS   AGE
local-web   0/1     Pending   0          119s

Il est là mais le statut est 'Pending'. Vérifions les journaux :

> kubectl logs --timestamps local-web

Aucun journal, rien n'est renvoyé. Vérifions les événements :

> kubectl get events

Résultat :

LAST SEEN   TYPE      REASON                OBJECT          MESSAGE
9m31s       Warning   FailedScheduling      pod/local-web   0/1 nodes are available: persistentvolumeclaim "local-web-claim0" not found. preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod..
101s        Warning   FailedScheduling      pod/local-web   0/1 nodes are available: 1 node(s) didn't match Pod's node affinity/selector. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling..

Il est apparu que 'nodeAffinity' avait la valeur du fichier Docker-Compose 'deploy - node.hostname'. J'ai dû changer cette valeur pour le nom de l'hôte que j'utilise en ce moment, bien sûr ... :-(

Ajouter un registre privé (image)

Supprimer le Pod, faire une nouvelle demande et obtenir le statut :

> kubectl delete pod local-web
> kubectl apply -f local-web-pod.yaml
> kubectl get pods

Résultat :

NAME        READY   STATUS             RESTARTS   AGE
local-web   0/1     ErrImagePull       0          76s

J'utilise déjà un registre privé pour Docker . Pour l'utiliser dans Kubernetes, nous devons ajouter l'authentification pour ce registre.

Vérifier si un élément registry-credential est présent :

> kubectl get secret registry-credential --output=yaml

Résultat :

Error from server (NotFound): secrets "registry-credential" not found

Ici, j'utilise les informations déjà présentes dans Docker. Créer le registry-credential :

> kubectl create secret generic registry-credential \
    --from-file=.dockerconfigjson=/home/peter/.docker/config.json \
    --type=kubernetes.io/dockerconfigjson

Résultat :

secret/registry-credential created

Pour le vérifier :

> kubectl get secret registry-credential --output=yaml

Nous commençons par pousser l'image dans le registre. Ensuite, nous modifions les fichiers Pod et ajoutons la référence aux informations d'identification du registre :

  imagePullSecrets:
    - name: registry-credential

Nous supprimons ensuite le fichier Pod et l'appliquons à nouveau :

> kubectl get pods

Résultat :

NAME        READY   STATUS    RESTARTS   AGE
local-web   1/1     Running   0          9s

Enfin, le système fonctionne !

Entrez dans le conteneur Pod

Entrez dans le conteneur 'local-web' :

> kubectl exec -it local-web -- /bin/bash

Nous pouvons maintenant vérifier les variables d'environnement, les montages, etc.

Vous pouvez également entrer dans le conteneur en tant que root. Mon Microk8s utilise 'runc' (Open Container Initiative runtime) pour accéder aux conteneurs. Obtenez l'ID du conteneur :

> kubectl describe pod <your pod> | grep containerd

Cela donnera quelque chose comme :

Container ID: containerd://6a060ba8436f575b86b4f3fe10a373125aaf7c125af835180d792f5382836355

Ensuite, exécutez en tant que root dans le conteneur :

> sudo runc --root /run/containerd/runc/k8s.io/ exec -t -u 0 6a060ba8436f575b86b4f3fe10a373125aaf7c125af835180d792f5382836355 sh

Accès au Pod par l'intermédiaire d'un service

Après avoir arrêté et démarré un Pod, un nouveau IP address peut être assigné à un Pod. C'est pourquoi nous utilisons un service pour accéder à un Pod, et utilisons le nom du service.

En créant un service pour un Pod , nous pouvons accéder au Pod en utilisant son nom au lieu de IP address. Pour cela, nous avons besoin du DNS du cluster Kubernetes , qui doit être activé :

> kubectl get services kube-dns --namespace=kube-system

Résultat :

NAME       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.152.183.10   <none>        53/UDP,53/TCP,9153/TCP   3d5h

Créer un service pour le local-web Pod

Le fichier local-web-pod.yaml n'avait pas d'entrée ports, j'ai donc ajouté un containerPort :

      ports:
      - containerPort: 80

Comme le fichier local-web-service.yaml n'a pas été créé par Kompose, je l'ai créé moi-même. Important : j'ajoute ici un port qui donne accès au Pod. Dans Docker-Compose nous n'avions pas besoin de ce port car le service est sur le réseau interne.

apiVersion: v1
kind: Service
metadata:
  name: local-web
  namespace: default
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  selector:
    io.kompose.service: local-web

Le 'selector' doit correspondre au 'label' du Pod.

Ensuite, nous démarrons le service :

> kubectl apply -f local-web-service.yaml

Nous devrions maintenant pouvoir accéder au service local-web à partir d'un autre conteneur Pod .

Démarrons un conteneur Busybox . Il y a des problèmes avec les récents Busybox images et Kubernetes, donc nous utilisons la version 1.28.

> kubectl run -i --tty --image busybox:128 test --restart=Never --rm /bin/sh

Puis à l'intérieur du conteneur :

# wget local-web-service:80

Resultat :

Connecting to local-web-service:80 (10.152.183.30:80)
saving to 'index.html'
index.html           100% |**********************************************************************************************************************************|   400  0:00:00 ETA
'index.html' saved

Très bien.

Création de Deployments et de services

Comme indiqué ci-dessus, nous ne créons pas les fichiers Pod YAML mais les fichiers Deployment YAML . Nous ajoutons également un service pour chaque fichier Deployment comportant des ports.

Dans Kubernetes , les noms des services sont globaux, alors que dans Docker-Compose, les services sur un réseau interne sont locaux au projet Docker-Compose. Cela signifie que dans Kubernetes , nous devons attribuer un espace de noms à nos services.

Nous pouvons le faire de deux manières :

  • Ajouter un espace de noms pour ce projet Docker-Compose
  • Ajouter un prefix au nom du service.

Pour le moment, je pense que l'ajout d'un prefix est la meilleure option :

task-runner-

Cela nous donne les noms Deployment et les noms de service :

task-runner-rabbitmq-deployment
task-runner-local-web-deployment
task-runner-task-deployment
task-runner-unbound-cloudflare-deployment
task-runner-unbound-quad9-deployment

task-runner-rabbitmq-service
task-runner-local-web-service
task-runner-task-service
task-runner-unbound-cloudflare-service
task-runner-unbound-quad9-service

Et les noms de fichiers :

task-runner-rabbitmq-deployment.yaml
task-runner-local-web-deployment.yaml
task-runner-task-deployment.yaml
task-runner-unbound-cloudflare-deployment.yaml
task-runner-unbound-quad9-deployment.yaml

task-runner-rabbitmq-service.yaml
task-runner-local-web-service.yaml
task-runner-task-service.yaml
task-runner-unbound-cloudflare-service.yaml
task-runner-unbound-quad9-service.yaml

Dans le Deployment , je commence avec 1 réplique. Voici la première partie d'un fichier Deployment :

# task-runner-local-web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  # metadata name must not contain dots
  name: task-runner-local-web-deployment
  namespace: default
spec:
  # number of copies of each pod we want
  replicas: 1

  strategy:
    type: Recreate

  # pods managed by this deployment
  selector:
    # match the labels we set on the pod, see below
    matchLabels:
      task-runner.local-web.deployment: task-runner-local-web-deployment

  # template field is a regular pod configuration nested inside the deployment spec
  template:
    metadata:
      # set labels on the pod, used in the deployment selector, see above
      labels:
        task-runner.local-web.deployment: task-runner-local-web-deployment
    spec:
      ...

Rendre le fichier '.env' disponible au démarrage des conteneurs.

J'ai beaucoup de paires clé-valeur dans le fichier '.env' utilisé par Docker-Compose. Dans Kubernetes , nous pouvons utiliser un fichier ConfigMap pour les importer dans nos fichiers Deployment YAML .

Nous commençons par créer un fichier ConfigMap. Le fichier ConfigMap est créé dans l'espace de noms "par défaut". Vous pouvez le modifier en ajoutant le drapeau et la valeur de l'espace de noms :

> kubectl create configmap task-runner-env-configmap --from-env-file=.env

Lister le ConfigMaps :

> kubectl get configmaps

Résultat :

NAME                      DATA   AGE
...
task-runner-env-configmap   51     19m

Nous pouvons afficher le résultat sous forme de fichier yaml :

> kubectl get configmaps task-runner-env-configmap -o yaml

Ensuite, dans les fichiers Deployment YAML , nous supprimons la section 'env' et ajoutons la section 'envFrom'.

De :

    spec:
      containers:
      - env:
        - name: ENV_VAR1
          value: "1"
        ...
        image: ...

A :

    spec:
      containers:
      - envFrom:
        - configMapRef:
            name: task-runner-env-configmap
        env:
          - name: VAR_C = $(VAR_A)/$(VAR_B)
        image: ...

Nous créons également une nouvelle variable d'environnement VAR_C à partir de deux variables d'environnement présentes dans le fichier ConfigMap.

Nous pouvons mettre à jour la ConfigMap comme suit :

> kubectl create configmap task-runner-env-configmap --from-env-file=.env -o yaml --dry-run | kubectl apply -f -

Nous avons créé un ConfigMap pour transmettre les paires clé-valeur du fichier '.env' aux conteneurs. Mais il y a un gros problème. Nous ne pouvons pas utiliser les variables d'environnement dans d'autres parties de nos manifestes. Par exemple, supposons que vous vouliez pouvoir changer de référentiel avec une variable d'environnement IMAGE_REGISTRY :

    spec:
      containers:
      - envFrom:
        - configMapRef:
            name: task-runner-env-configmap
        env:
          - name: VAR_C = $(VAR_A)/$(VAR_B)
        image: $(IMAGE_REGISTRY)...

Cela ne fonctionnera pas ! Nous sommes en 2023 et parce que cette fonctionnalité (triviale) ne fonctionne pas, les gens du monde entier créent leurs propres scripts pour la faire fonctionner. Merveilleux. Quoi qu'il en soit, que doit faire le script ?

  1. Démarrer un nouveau shell (Bash).
  2. Exporter les paires clé-valeur du fichier '.env'.
  3. Utiliser 'envsubst' pour remplacer les variables dans notre manifeste.

Tout d'abord, nous créons un nouveau shell pour éviter que dist ne perturbe l'environnement actuel. Dans le nouvel environnement, nous créons les variables d'environnement en utilisant le fichier '.env'. Enfin, nous remplaçons les variables :

> envsubst < mydeploy.yaml | kubectl apply -f -

PersistentVolumes et VolumeClaims

Kompose a créé pour moi une section VolumeMounts et une section Volumes dans les fichiers Pods YAML en utilisant hostPath. Cela signifie que les répertoires actuels de localhost sont codés en dur dans les fichiers Deployment . Ce n'est évidemment pas ce que nous voulons.

J'ai donc créé des manifestes PersistentVolume et PersistentVolumeClaim et je les ai utilisés dans les fichiers Deployment .

Créer des services pour le fichier Deployments

Je ne montre ici que le service local-web .

# task-runner-local-web-service.yaml
apiVersion: v1
kind: Service
metadata:
  # metadata name must not contain dots
  name: task-runner-local-web-service
  namespace: default
spec:
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
  selector:
    task-runner.local-web.deployment: task-runner-local-web-deployment

Démarrer le service :

> kubectl apply -f task-runner-local-web-service.yaml

Modifications du code de l'application

Je n'ai eu besoin que de petites modifications du code de mon application. Comme mentionné plus haut, j'ai ajouté 'task-runner-' aux noms initiaux des services prefix. Pour rester compatible avec Docker-Compose, j'utilise maintenant des variables d'environnement pour les noms de service et les ports, spécifiés sous forme de paires clé-valeur dans le fichier '.env'.

Bien que les ports soient maintenant spécifiés dans tous les services, il n'est pas nécessaire de changer quoi que ce soit car les ports sont liés aux services, et chaque service a son propre IP address.

Pour vérifier où tourne une instance de Pod , je stocke la variable d'environnement HOSTNAME avec le résultat d'une tâche. Pour Docker Swarm , j'ai utilisé .Node.Hostname. . Dans les journaux, nous pouvons vérifier ce champ.

Utilisation de nodePort pour exposer le service rabbitmq au Back End

Le service Task Runner est opérationnel, mais il n'est pas connecté au monde extérieur. Il y a plusieurs façons d'exposer le service rabbitmq et ici j'utilise nodePort. Il suffit de créer un service supplémentaire de type nodePort et de spécifier le numéro de port :

# task-runner-rabbitmq-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  # metadata name must not contain dots
  name: task-runner-rabbitmq-nodeport
  namespace: default
spec:
  type: NodePort
  # The range of valid ports is 30000-32767
  ports:
    - name: amqp
      port: 10763
      targetPort: 5672
      nodePort: 31763
    - name: http
      port: 20763
      targetPort: 15672
      nodePort: 32763
  selector:
    task-runner.rabbitmq.deployment: task-runner-rabbitmq-deployment

Nous pouvons ensuite faire référence à ce service Kubernetes sur l'hôte comme suit : localhost:<port>.

Pour connecter le conteneur Backend Docker à ce port, nous ajoutons quelques lignes au fichier 'docker-compose.yaml' :

    extra_hosts:
      - "host.docker.internal:host-gateway"

Puis nous nous référons au service en tant que :

host.docker.internal:<port>

Ajout d'un nœud de travail, le DNS ne fonctionne pas

Bien sûr, je voulais ajouter un nouveau noeud et y exécuter Pods . Avec VitualBox sur ma machine de développement, j'ai créé un VM avec Ubuntu server 22.04. J'ai ensuite ajouté Microk8s et joint le nouveau noeud.

Problèmes ... Kubernetes Le DNS ne fonctionnait pas sur le nouveau noeud. Il y a beaucoup de problèmes DNS mentionnés sur la page Microk8s Github . Cela a probablement à voir avec iptables ...

J'ai pu avoir un cluster Kubernetes fonctionnant correctement, composé de deux machines VirtualBox . Je suis actuellement en train d'enquêter sur ce problème.

Développement et dépannage sur Kubernetes

L'une des machines Pods ne fonctionnait pas. Je pouvais voir dans le journal ce qui n'allait pas, mais nous ne voulons pas entrer dans une boucle :

--+-> make changes ---> build image ---> test --+--->
  ^                                             |
  |                                             v
  +--------------------<------------------------+ 

J'ai donc remonté le code du projet externe dans le conteneur, ce qui me ramène à l'environnement de développement, et j'ai pu résoudre le problème très rapidement.

Outils

Je n'ai utilisé qu'un seul outil supplémentaire pendant la migration : yamllint pour valider les manifestes (fichiers YAML ).

> sudo apt-get install yamllint

Et ensuite exécuter pour l'exemple :

> yamllint task-runner-local-web-deployment.yaml

Résumé

Pour commencer, je n'ai passé que deux semaines avec Kubernetes (ne tirez pas sur le pianiste). J'ai peut-être fait de mauvais choix, mais une partie de mon application est maintenant gérée par Kubernetes.

Docker Swarm est un choix facile lorsqu'il s'agit d'orchestrer des projets Docker-Compose. Il fonctionne simplement, sans limitations sérieuses. Pour mon application, qui consiste en plusieurs projets Docker-Compose, je n'ai pas besoin de milliers de répliques. Avec Docker Swarm, vous démarrez un nouveau nœud, ajoutez quelques directives de déploiement à vos fichiers docker-compose.yml, et le tour est joué. Le réseau Docker est l'une des meilleures fonctionnalités de Docker. Avec Docker Swarm, il suffit de transformer un réseau en un réseau superposé chiffré Ingress pour communiquer entre les nœuds.

Le passage à Kubernetes offre une plus grande souplesse de déploiement, mais la principale raison pour laquelle Kubernetes est devenu le premier choix est qu'il est très répandu et que de nombreux outils y sont intégrés. C'est là que Docker Swarm est à la traîne. Au moment où j'écris ce billet, le développement de Docker Swarm semble plus ou moins au point mort (considéré comme pleinement fonctionnel ?). C'est dommage, car il s'agit d'une excellente solution à un problème très complexe.

Dans un monde idéal, l'environnement de développement est identique à l'environnement de production. S'il fonctionne en développement, il fonctionne aussi en production. Docker Swarm s'en rapproche beaucoup, sans grande complexité. L'utilisation de Kubernetes, en revanche, crée un fossé. C'est comme si l'on disait : "Ok, développeur, tu as fait ton travail, maintenant laisse le DevOps le mettre en production". Si le développeur apporte des modifications aux projets Docker-Compose, les DevOps doivent également y travailler.

Si vous utilisez Kubernetes pour la production, je pense qu'il est inévitable que l'environnement de développement utilise également Kubernetes. Et dans de nombreux cas, Docker Swarm également. Cela rend-il le développement et la production plus faciles ou plus difficiles par rapport à Docker Swarm ? Microk8s est très facile à utiliser dans un environnement de développement, mais ce n'est que le début. Créer des fichiers de projet Docker-Compose est très facile. Vous avez généralement deux fichiers yaml et un fichier '.env'. Après la conversion, mon projet Kubernetes avait déjà 14 fichiers yaml Kubernetes et plus.

Ce serait une très bonne idée si Kubernetes ajoutait des extensions pour les fichiers YAML qui permettent d'importer / ajouter d'autres fichiers (comme Nginx) et des données d'environnement. Pour l'instant, nous devons écrire nos propres scripts ou utiliser quelque chose comme Helm ... d'autres choses à apprendre.

Quoi qu'il en soit, dans ce billet, j'ai tenté d'étudier ce qu'il faudrait pour déplacer une partie de mon application de Docker Swarm à Kubernetes. Sur la base des résultats, j'ai décidé de passer à Kubernetes, je ne continuerai pas à utiliser Docker Swarm pour la production. Je vais également utiliser Kubernetes pour le développement.

Commencer avec Kubernetes n'est pas très difficile, mais parfois très déroutant. Après quelques jours, vous comprenez la plupart des concepts et vous avez quelque chose qui fonctionne. À partir de là, vous pouvez vous améliorer et vous développer. Il peut y avoir des problèmes (très) sérieux et chronophages, comme un DNS qui ne fonctionne pas. Je ne veux pas devenir un spécialiste d'iptables, mais il n'y a pas d'autre solution.

Le nombre de modifications que j'ai dû apporter à mon application est très limité :

  • Changement des noms en RFC1123.
  • Préfixation des noms de services, en utilisant des variables d'environnement.
  • Stockage d'autres variables d'environnement dans les résultats.

Retour au codage ...

Liens / crédits

Configure a Pod to Use a ConfigMap
https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap

Connecting Kubernetes and Docker
https://developers.redhat.com/blog/2017/09/22/connecting-kubernetes-docker

Helm - The package manager for Kubernetes
https://helm.sh

How to go from Docker to Kubernetes the right way
https://www.opensourcerers.org/2021/02/01/how-to-go-from-docker-to-kubernetes-the-right-way

How To Migrate a Docker Compose Workflow to Kubernetes
https://www.digitalocean.com/community/tutorials/how-to-migrate-a-docker-compose-workflow-to-kubernetes

Kompose
https://github.com/kubernetes/kompose

Kubernetes - Documentation - Concepts
https://kubernetes.io/docs/concepts

Kubernetes - Documentation - kubectl Cheat Sheet
https://kubernetes.io/docs/reference/kubectl/cheatsheet

Kustomize - Kubernetes native configuration management
https://kustomize.io

MicroK8s documentation - home
https://microk8s.io/docs

Running Kubernetes locally on Linux with Microk8s
https://kubernetes.io/blog/2019/11/26/running-kubernetes-locally-on-linux-with-microk8s

Using private docker registry inside kubernetes
https://sam-thomas.medium.com/using-private-docker-registry-inside-kubernetes-46a3cede7cb1

When should I use envFrom for configmaps?
https://stackoverflow.com/questions/66352023/when-should-i-use-envfrom-for-configmaps

Laissez un commentaire

Commentez anonymement ou connectez-vous pour commenter.

Commentaires

Laissez une réponse

Répondez de manière anonyme ou connectez-vous pour répondre.