\newpage Découverte de `kubectl` ======================= `kubectl` ([prononcé](https://www.reddit.com/r/kubernetes/comments/5qthoc/how_should_i_pronounce_kubectl/) 'cube C T L', 'cube cuttle', 'kyoob cuddle', 'cube control', ...) est le principal programme que l'on utilise pour interagir avec notre cluster. Étant donné qu'il s'agit d'un programme client, qui ne fait rien de plus que discuter avec une API REST HTTP, on peut le considérer comme un gros wrapper au dessus de `curl`. Obtenir de l'aide ----------------- ```bash kubectl describe type/name kubectl describe type name kubectl explain type ``` `get` ----- ```bash kubectl get node ``` Plus d'infos : ```bash kubectl get nodes -o wide ``` Lisible par une machine : ```bash kubectl get no -o yaml kubectl get no -o json ``` On aimera utiliser `jq(1)` avec la sortie `-o json` : ```bash kubectl get no -o json | \ jq ".items[] | {name:.metadata.name} + .status.capacity" ``` ### Services ```bash kubectl get services kubectl get svc ``` Pour le moment, nous n'avons qu'un seul service, il s'agit de l'API Kubernetes. `ClusterIP` désigne l'IP d'un service accessible en interne, pour le cluster. ### Conteneurs actifs Jetons un œil aux conteneurs actifs : ```bash kubectl get pods ``` Regardons maintenant les `namespaces` : ```bash kubectl get namespaces ``` On l'a vu, les *namespaces* ici désignent des espaces de noms qui n'ont rien à voir avec les *namespaces* de Linux. Regardons par exemple les conteneurs d'un autre espace de noms : ```bash kubectl -n kube-system get pods ``` Eh oui ! De nombreux services de base pour Kubernetes tournent dans des conteneurs, gérés par lui-même... notamment : - `etcd` : notre base de données clef/valeur, - `kube-apiserver` : l'API REST avec qui communique `kubectl`, - `kube-controller-manager` et `kube-scheduler`, deux autres composants indispensables, - `coredns` : un composant additionnel pour gérer la résolution de noms internes (pour pas avoir à s'embêter avec les IP), - `kube-proxy` : 1 par nœud, pour gérer l'ouverture des ports notamment, - `kindnet`, `weave` : 1 par nœud, le plugin réseau. Mon premier conteneur --------------------- Prêt à lancer notre premier conteneur ?! Pas si vite ! En fait ... Kubernetes ne permet pas de lancer de conteneur... Nous devons lancer un *pod* (qui ne contiendra qu'un seul conteneur). ### Mon premier pod ```bash kubectl run pingpong --image alpine ping 1.1.1.1 ``` `kubectl` doit nous indiquer nous qu'un *pod* a été créée. Si l'on affiche la liste des pods, vous devriez avoir quelque chose qui ressemble à cela : ``` $ kubectl get pods NAME READY STATUS RESTARTS AGE pingpong 1/1 Running 0 123s ``` #### Sortie d'un conteneur Allons maintenant regarder si nous recevons bien nos PONG. Pour cela, nous allons utiliser la commande `kubectl logs`. Cette commande s'utilise d'une manière similaire à `docker logs` : ```bash kubectl logs pingpong ``` ou bien : ```bash kubectl logs -f pingpong ``` Notez ici l'option -f qui permet de suivre les logs en direct. Notre premier test ayant réussi, nous pouvons arrêter de DDos Cloudflare : ```bash kubectl delete deploy/pingpong ``` ### Déploiement³ Bien ... maintenant que nous savons nous débrouiller avec `kubectl`, attaquons les choses sérieuses : en temps normal avec Kubernetes, nous ne déploierons pas de *pod* directement, car cela reviendrait à utiliser Docker, mais des tâches de déploiement. Essayons sans plus attendre de lancer nos `ping` à travers une tâche de déploiement : ```bash kubectl create deployment pingpong --image=alpine -- ping 1.1.1.1 ``` Si l'on regarde maintenant la sortie de `kubectl get all`, on obtient : ``` NAME READY STATUS RESTARTS AGE pod/pingpong-98f6d5899-5wsrm 0/1 ContainerCreating 0 123s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 443/TCP 123h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/pingpong 0/1 1 0 123s NAME DESIRED CURRENT READY AGE replicaset.apps/pingpong-98f6d5899 1 1 0 123s ``` Pas de panique, on peut très facilement le décortiquer : Les tâches de déploiements (*deployment.apps*) sont des ressources de haut-niveau et sont là pour s'assurer que les migrations se font en douceur : elles vont permettre de basculer progressivement les pods d'une version X à une version Y (par exemple si l'on change notre ping d'alpine vers debian), mais éventuellement de revenir sur la version X si besoin, en cours de migration. Elles délèguent aux *replicatsets* la gestion des pods. Le *replicatset* est là pour indiquer le nombre de pods que l'on désire, et s'assurer que le nombre de pods actuellement lancé est bien en adéquation avec le nombre de pods attendu. Pour résumer : `kubectl` a créé une tâche de déploiement `deploy/pingpong`. Cette tâche de déploiement a créé elle-même un *replicatset* `rs/pingpong-xxxx`. Ce *replicatset* a créé un *pod* `po/pingpong-yyyy`. ### Passage à l'échelle : facile ? Pour lancer 3 ping en parallèle, modifions la tâche de déploiement comme suit : ```bash kubectl scale deploy/pingpong --replicas 3 ``` À ce stade, comme nous ne modifions que le nombre de replicats, Kubernetes va tout simplement propager ce nombre au *replicatset* existant. Puis, le *replicatset* voyant un décalage entre le nombre de pods attendus et le nombre de pods en cours d'exécution, il va en lancer de nouveaux, afin de répondre à la demande. Et que se passe-t-il alors, si l'on tue un *pod* ? ```bash kubectl delete pod pingpong-yyyy ``` Cela supprime bien un *pod*, mais un autre est relancé instantannément car le *replicatset* constate une différence dans le nombre attendu. Si nous voulons arrêter de DDoS Cloudflare, il ne s'agit pas de tuer chacun des pods un par un, car de nouveaux seraient créés par le *replicatset*. Si l'on supprime le *replicatset*, la tâche de déploiement en rećréera un similaire. Pour arrêter nos conteneurs, il convient donc de supprimer la tâche de déploiement : ```bash kubectl delete deploy/pingpong ``` ### Exposer son conteneur Exposer un conteneur revient à créer un nouveau service (une *resource* service). Un service est une adresse IP que l'on peut considérer comme stable pour un *pod* ou un groupe de *pods*. Il est nécessaire de créer un service si l'on veut pouvoir se connecter à un *pod*. Une fois le service créé, le serveur DNS interne va permettre de résoudre le nom du *pod* depuis les autres conteneurs. #### Types de services Il y a différents types de services : - `ClusterIP` (par défaut) : une adresse IP virtuelle est allouée pour le service, elle n'est accessible que depuis le réseau interne (par les pods et les nœuds). Il n'y a pas de translation de port à effectuer. - `NodePort` : un port est alloué pour le service, sur tous les nœuds le cluster, et n'importe qui peut alors s'y connecter. Le port est choisi aléatoirement. - `LoadBalancer` : lorsque l'infrastructure sous-jacente fourni un load-balancer (typiquement AWS, GCE, Azure, ...), un service `NodePort` est créé pour utiliser ce load-balancer externe. - `ExternalName` : une entrée DNS est créée pour avoir un alias. #### Le retour de `youp0m` ```bash kubectl create deployment youp0m --image=nemunaire/youp0m ``` Commençons par créer un service `ClusterIP` : ```bash kubectl expose deployment youp0m --port 8080 ``` Ce qui donne : ``` $ kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE youp0m ClusterIP 10.102.129.233 8080/TCP 42s ``` Depuis un nœud du cluster, on peut donc venir interroger cette IP. Si l'on essaie avec plusieurs nœuds, on voit alors que les requêtes sont balancées sur différents nœuds. Si vous passez par `kind`, vous pouvez constater le bon fonctionnement grâce à : ```bash docker exec -it kind-control-plane curl 10.96.179.154:8080 ``` Kubernetes dashboard -------------------- L'équipe de Kubernetes propose un tableau de bord assez pratique, qui permet de voir toutes les *resources*, comme nous l'avons fait avec `kubectl`, mais dans une interface web. Ils mettent à disposition un fichier décrivant l'état d'un cluster ayant une telle application. Nous pouvons demander à ce que notre cluster converge vers la configuration nécessaire : ```bash kubectl create -f https://virli.nemunai.re/insecure-dashboard.yaml ``` Notez que le dashboard, avec cette configuration, va s'exécuter sans les prérequis minimum de sécurité : pas de certificat TLS, ni d'authentification. Ceci est juste pour jouer avec l'interface, en production, on n'utilisera pas cette recette. Regardons où nous pouvons contacter notre dashboard : ```bash $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE dashboard NodePort 10.96.78.69 80:31505/TCP 3m10s kubernetes ClusterIP 10.96.0.1 443/TCP 6m51s ``` Regardons si cela répond : ```bash $ docker exec -it kind-control-plane curl 127.0.0.1:31505

You are using an outdated browser. ``` Pas très sympa... il faudrait que l'on puisse le voir dans un navigateur plus ... moderne alors. Étant donné que notre cluster ne se trouve pas directement sur notre machine, mais dans différents conteneurs Docker, nous ne pouvons pas accéder à `127.0.0.1`. Heureusement, au moment de la création de notre cluster, nous avons renseigné plusieurs ports redirigés au sein de notre configuration. Il va donc falloir indiquer à Kubernetes que l'on désire utiliser un port spécifique pour exposer le tableau de bord. Pour ce faire, éditons le fichier `insecure-dashboard.yaml`, pour ajouter, dans la partie `Service` un *node port* plus spécifique : ```yaml - port: 80 protocol: TCP targetPort: 80 nodePort: 30002 ``` Maintenant, nous n'allons pas recréer un nouveau dashboard : nous allons simplement « appliquer » la nouvelle configuration : ```bash kubectl apply -f my-insecure-dashboard.yaml ``` En voyant la divergence entre la réalité et la configuration demandée, Kubernetes va tout mettre en œuvre pour se conformer à nos directives. En l'occurrence, il s'agit de changer le port qui expose le service au sein du cluster. En fait, on pourra faire exactement la même chose lors d'un changement de version. Kubernetes verra la différence et appliquera une politique de migration déterminée. Une fois que c'est fait, nous pouvons fièrement utiliser notre navigateur pour aller sur (vous pouvez *Skip* l'authentification, dans cette configuration d'exemple, elle n'est pas nécessaire).