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.
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 ?
- Démarrer un nouveau shell (Bash).
- Exporter les paires clé-valeur du fichier '.env'.
- 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
En savoir plus...
Docker Swarm Kubernetes
Récent
- Un commutateur de base de données avec HAProxy et HAProxy Runtime API
- Docker Swarm rolling updates
- Masquer les clés primaires de la base de données UUID de votre application web
- Don't Repeat Yourself (DRY) avec Jinja2
- SQLAlchemy, PostgreSQL, nombre maximal de lignes par user
- Afficher les valeurs des filtres dynamiques SQLAlchemy
Les plus consultés
- Utilisation des Python's pyOpenSSL pour vérifier les certificats SSL téléchargés d'un hôte
- Utiliser PyInstaller et Cython pour créer un exécutable Python
- Réduire les temps de réponse d'un Flask SQLAlchemy site web
- Connexion à un service sur un hôte Docker à partir d'un conteneur Docker
- Utiliser UUIDs au lieu de Integer Autoincrement Primary Keys avec SQLAlchemy et MariaDb
- SQLAlchemy : Utilisation de Cascade Deletes pour supprimer des objets connexes