virli/tutorial/k8s/discover.md
2019-11-27 13:11:20 +01:00

8.8 KiB

\newpage

Découverte de kubectl

kubectl (prononcé '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

kubectl describe type/name
kubectl describe type name
kubectl explain type

get

kubectl get node

Plus d'infos :

kubectl get nodes -o wide

Lisible par une machine :

kubectl get no -o yaml
kubectl get no -o json

On aimera utiliser jq(1) avec la sortie -o json :

kubectl get no -o json | \
  jq ".items[] | {name:.metadata.name} + .status.capacity"

Services

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 :

kubectl get pods

Regardons maintenant les namespaces :

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 :

kubectl -n kube-system get pods

Eh oui ! De nombreux services de base pour Kubernetes tournent dans des conteneurs, géré 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 interne (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

kubectl run pingpong --image alpine ping 1.1.1.1

Outre un avertissement, kubectl doit indiquer nous qu'une tâche de déploiement 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-7d49d9bc9-k8fpg   1/1     Running   0          123s

Déploiement³

Si l'on affiche davantage d'informations, on obtient :

$ kubectl get all
NAME                           READY   STATUS    RESTARTS   AGE
pod/pingpong-7d49d9bc9-k8fpg   1/1     Running   0          123s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   2m3s

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/pingpong   1/1     1            1           123s

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/pingpong-7d49d9bc9   1         1         1       123s

Les tâches de déploiements (deployment.apps) sont des ressources de haut-niveau et sont là pour 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'assure que le nombre de pods actuellement lancé est bien en adéquation avec le nombre de pods attendus.

Pour résumer : kubectl run 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.

Sortie d'un conteneur

Bref ... 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 :

kubectl logs deploy/pingpong

Cette ligne est la plus simple, et nous affichera la sortie du premier pod de la tâche de déploiement.

Pour afficher un pod en particulier, il faut indiquer son nom en entier, en remplaçant yyyy par son identifiant :

kubectl logs -f pingpong-yyyy

Notez ici l'option -f qui permet de suivre les logs en direct.

Mise à l'échelle : facile ?

Bien ... maintenant que nous savons nous débrouiller avec kubectl, attaquons les choses sérieuses.

Pour lancer 8 ping en parallèle, modifions la tâche de déploiement comme suit :

kubectl scale deploy/pingpong --replicas 8

À 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 ?

kubectl delete pod pingpong-yyyy

Autres usages de run

Si l'on veut des tâche qui ne redémarrent pas systématiquement, on peut utiliser : kubectl run --restart=OnFailure ou kubectl run --restart=Never, ... au lieu de créer des tâches de déploiement, cela va créer des jobs ou des pods. On peut même créer l'équivalent de tâches cron avec : kubectl run --schedule=....

Comme nous venons de le voir, actuellement kubectl run sert un peu à tout et n'importe quoi, la ressource créée n'est pas évidente, c'est pour cela que l'avertissement nous recommande d'utiliser kubectl create :

  • kubectl create deployment pour créer une tâche de déploiement,
  • kubectl create job pour créer un job.

Dans le futur, kubectl run servira à lancer un pod à usage unique, sans tâche de déploiement ni réplicat.

En fait, kubectl run génère une nouvelle spécification, qu'il envoie à l'API de Kubernetes. On peut voir le fichier généré avec la ligne de commande suivante :

kubectl run --dry-run -o yaml pingpong --image alpine ping 1.1.1.1

Le fichier YAML récupéré peut s'utiliser comme suit :

kubectl apply -f my-specs.yml

Arrêter de flooder 1.1.1.1

Ok, on s'est bien amusé à ping Cloudflare, pour ne pas être trop méchants, nous pouvons maintenant arrêter nos pods.

Comme nous l'avons vu juste avant, 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 :

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 a 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 utiiser ce load-balancer externe.
  • ExternalName : une entrée DNS est créée pour avoir un alias.

Le retour de youp0m

kubectl create deployment youp0m --image=nemunaire/youp0m

Commençons par créer un service ClusterIP :

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   <none>        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.