Découvrir Kubernetes

Objectifs

  • Découvrir Kubernetes et l’orchestration de conteneurs

Note pour la mise en place

Pour que minikube fonctionne correctement pour ce TD, il faut être une seule personne par compte utilisateur sur les Xeon à l’INSA-CVL.

Si vous partagez un Xeon avec d’autres élévèes, alors vous devez créer des comptes utilisateurs distincts pour chacun d’entre vous, les ajouter dans le groupe libvirt et vous y connecter avant de commencer ce TD :

root@xeon $ useradd --create-home --user-group --groups wheel,libvirt userX
root@xeon $ passwd userX
user@localhost $ ssh userX@172.x.y.z

Obtenir un cluster Kubernetes avec minikube

Nous allons utiliser minikube pour obtenir un cluster en local pour réaliser ce TD.

Récupérer le binaire minikube :

# "Ces commandes ne sont nécessaires qu'une seule fois par machine physique / Xeon"
$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
$ sudo install -o root -g root -m 0755 minikube-linux-amd64 /usr/local/bin/minikube

Configurer l’utilisation du driver kvm2 avec libvirt et l’usage de cri-o :

$ minikube config set driver kvm2
$ minikube config set container-runtime cri-o

Récupérer l’image minikube mise à disposition en TD :

$ cp /mnt/nfs/DATASTORE/A_COURS/RAVIER/minikube-v1.34.0-amd64.iso* ./
$ sha256sum --check minikube-v1.34.0-amd64.iso.sha256sum

Démarer la machine virtuelle avec l’image mise à jour, en précisant votre nom-prénom comme nom de profil :

$ minikube start --profile nomprenom --iso-url=file://$(pwd)/minikube-v1.34.0-amd64.iso
Commandes à utiliser uniquement si vous rencontrez une erreur parce que le réseau "default" n'existe pas.

Utilisez les commandes suivantes pour re-créer le réseau “default” :

# "Ces commandes ne sont nécessaires qu'une seule fois par machine physique / Xeon"
$ curl -o default.xml https://raw.githubusercontent.com/libvirt/libvirt/master/src/network/default.xml
$ sed -i 's/virbr0/virbr99/' default.xml
$ virsh net-define default.xml
$ virsh net-start default
$ virsh net-autostart default

Définir ensuite votre profil comme celui par défaut pour éviter d’avoir à le préciser pour chaque commande :

$ minikube profile nomprenom

Installer kubectl :

# "Ces commandes ne sont nécessaires qu'une seule fois par machine physique / Xeon"
$ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
$ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl.sha256"
$ echo "$(cat kubectl.sha256)  kubectl" | sha256sum --check
$ sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

Activer la completion (avec <TAB>) des commandes kubectl :

$ echo 'source <(kubectl completion bash)' >>~/.bashrc
$ source ~/.bashrc

Vérifier que le noeud minikube est bien fonctionel :

$ kubectl get nodes

Intéragir avec le cluster

Pour intéragir avec l’API exposée par le cluster Kubernetes, nous allons utiliser kubectl.

Le commandes kubectl suivent le format suivant :

$ kubectl <action> <ressource>/(<nom-de-la-ressource)
# ou
$ kubectl <action> <ressource> (<nom-de-la-ressource)

Par exemple pour obtenir la liste des noeuds disponibles dans notre cluster Kubernetes :

$ kubectl get nodes

Pour obtenir plus d’informations, vous pouvez rajouter l’option -o wide :

$ kubectl get nodes -o wide

Pour vérifier la version de kubectl et du cluster :

$ kubectl version --output=yaml

Vous pouvez vous connecter au noeud de notre cluster (notre machine virtuelle) :

$ minikube ssh

Et constater que nous avons les services minimum nécessaires au fonctionnement de Kubernetes installés et lancés :

$ systemctl -t service | grep -E 'crio|kubelet'

Listez les conteneurs qui forment le control plane de Kubernetes :

$ sudo crictl ps

Déployer une application

Pour déployer une application sur un cluster Kubernetes, nous utilisons un déploiement (deployment).

Le déploiement indique à Kubernetes comment créer et mettre à jour des instances d’une application. Nous ne gérons pas les coteneurs directement mais c’est Kubernetes qui se charge de garantir que ce qui tourne sur le cluster correspond à ce que nous lui demandons.

Pour créer un déploiement avec l’interface de ligne de commande kubectl, nous devons préciser un nom et l’image de conteneur qui sera utilisée :

$ kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1

Kubernetes va faire les choses suivantes :

  • Ecrire dans la base de donnée etcd les informations pour faire tourner notre deploiement
  • Le Scheduler va chercher un noeud disponible pour faire tourner notre deploiement et ses pods (nous n’en avons ici qu’un seul)
  • Il va alors écrire le ou les noeuds choisis dans la configuration du/des noeuds dans etcd pour programmer le lancement des pods sur ces noeuds
  • Les kubelets sur les noeuds vont réaliser qu’il a des nouveaux pods à lancer qui n’existent pas sur le noeud actuellement

Pour lister les déploiements :

$ kubectl get deployments

Pods

Lorsque que nous créons un déploiement, Kubernetes crée un Pod pour regrouper les conteneurs qui forment notre application. Un pod est une abstraction Kubernetes qui représente un groupe de conteneurs qui partagent certains namespaces :

  • le stockage partagé (volumes)
  • le namespace réseau, avec une adresse IP unique sur tout le cluster

Pour lister les pods déployés :

$ kubectl get pods

Pour obtenir plus d’informations sur les pods et les conteneurs en cours d’exécution :

$ kubectl describe pods

Logs

Pour obtenir les logs des conteneurs qui constituent notre pod, nous pouvons utiliser :

$ kubectl logs kubernetes-bootcamp-f95c5b745-7td7b

Exécuter des commandes dans les conteneurs

Nous pouvons aussi exécuter directement des commandes à l’intérieur des conteneurs :

$ kubectl exec kubernetes-bootcamp-f95c5b745-7td7b -- env

Ou obtenir un shell :

$ kubectl exec -ti kubernetes-bootcamp-f95c5b745-7td7b -- bash

Exposer une application publiquement

Les conteneurs lancés dans un cluster Kubernetes ne sont pas accessibles par défaut depuis l’extérieur. Seules les autres applications du cluster peuvent communiquer avec eux.

De plus, les pods sont ephemères et peuvent être déplacés entre les noeuds du cluster en cas de crash, mise à jour ou redémarrage. Pour y accéder depuis l’extérieur du cluster, nous allons utiliser le concept de Service. Pour lister les Services sur le cluster :

$ kubectl get services

Nous allons ensuite exposer notre application publiquement à l’aide d’un service de type “NodePort”, qui va mettre à disposition notre application sur un port d’un noeud du cluster :

$ kubectl expose deployment/kubernetes-bootcamp --type="NodePort" --port 8080

Nous pouvons obtenir le port sur lequel notre application a été rendu disponible :

$ kubectl get services
$ kubectl describe services/kubernetes-bootcamp
...
NodePort:                 <unset>  30542/TCP
...

Et ensuite contacter notre application :

# Obtenir l'IP de la machine minikube
$ minikube ip
192.168.39.80
$ curl http://192.168.39.80:30542

Passage à l’échelle

Si notre application gère beaucoup de requettes, il peut devenir nécessaire d’en déployer plusieurs copies pour répartir la charge sur les noeuds du cluster. Pour cela, nous allons utiliser les ReplicaSet. Lorsque nous avons créé notre déploiement, un ReplicaSet a aussi été automatiquement créé :

$ kubectl get replicasets

Par défaut, un seul réplicat est demandé et un seul est en cours d’exécution.

Nous allons désormais en demander 4 :

$ kubectl scale deployments/kubernetes-bootcamp --replicas=4

Constater que le changement a été effectué avec :

$ kubectl get deployments

et constater la création des nouveaux pods avec :

$ kubectl get pods -o wide

Noter que chaque pod a sa propre adresse IP.

Ce changement a aussi été enregistré dans le log des évènements :

$ kubectl describe deployments/kubernetes-bootcamp

Lorsque nous contactons notre application via notre service, nous sommes désormais redirigé vers différentes instances de notre application :

# Répétez la requête jusqu'à obtenir une reponse d'un pod différent
$ curl http://192.168.39.80:30542

Nous pouvons ensuite réduire le nombre de conteneurs déployés :

$ kubectl scale deployments/kubernetes-bootcamp --replicas=2
$ kubectl get deployments
$ kubectl get pods

Déploiment progressif d’une mise à jour

Pour pouvoir déployer une mise à jour de notre application sans créer d’interruption de service, nous allons réaliser une mise à jour progressive (rolling update).

Nous allons indiquer à Kubernetes que nous voulons désormais utiliser une nouvelle version de notre application et celui-ci va se charger de déployer de nouveaux pods avec cette nouvelles version pour progressivement remplacer les anciens pods.

Listons d’abord les pods en cours d’exécution :

$ kubectl get pods

Indiquons que nous voulons désormais utiliser la version 2 de notre application :

$ kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=jocatalin/kubernetes-bootcamp:v2

Constater le remplacement progressif des pods :

$ kubectl get pods

Constater aussi que notre application reste toujours disponible au cours de la mise à jour :

$ curl http://192.168.39.80:31436

Les réponses arrivent progressivement depuis les nouveaux pods.

Nous pouvons aussi valider la progression du déploiment avec :

$ kubectl rollout status deployments/kubernetes-bootcamp

Essayons désormais de réaliser une autre mise à jour avec la version 10 de notre application :

$ kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=gcr.io/google-samples/kubernetes-bootcamp:v10

Observer la (non) progression :

$ kubectl get pods

Pour obtenir plus d’informations sur l’erreur :

$ kubectl describe pods

Et pour annuler le déploiement de la mise à jour :

$ kubectl rollout undo deployments/kubernetes-bootcamp

Références