318 lines
9.2 KiB
Markdown
318 lines
9.2 KiB
Markdown
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
|
||
|
||
Commençons par apprivoiser `kubectl` en prenant quelques
|
||
renseignements et surtout en apprenant comment obtenir de l'aide :
|
||
|
||
<div lang="en-US">
|
||
```bash
|
||
kubectl describe type name
|
||
kubectl explain type
|
||
```
|
||
</div>
|
||
|
||
Les `type`s que vous pouvez découvrir sont ceux que l'on a vu à la
|
||
section précédente : `node`, `pod`, ...
|
||
|
||
La commande `describe` permet d'afficher l'état tel qu'il est attendu
|
||
et tel qu'il est actuellement (cela permet de se rendre lorsque les
|
||
deux divergent).
|
||
|
||
|
||
### `get`
|
||
|
||
Une autre manière, moins verbeuse, de récupérer des informations est
|
||
d'utiliser `get` :
|
||
|
||
```bash
|
||
kubectl get node
|
||
```
|
||
|
||
On peut ajouter des options pour avoir plus d'infos :
|
||
|
||
```bash
|
||
kubectl get nodes -o wide
|
||
```
|
||
|
||
... ou rendre la sortie 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 ne 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éé.
|
||
|
||
Si l'on affiche la liste des *pod*s, 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 pods 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. Nous allons
|
||
plutôt créer 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 8.8.8.8
|
||
```
|
||
|
||
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 <none> 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
|
||
```
|
||
|
||
Oula, on a vraiment lancé tout ça ?!
|
||
|
||
Pas de panique, on peut très facilement le décortiquer :
|
||
|
||
Les tâches de déploiement (*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 *pod*s d'une version X à une
|
||
version Y (par exemple si l'on change notre ping d'alpine 3.14 vers alpine
|
||
edge), mais éventuellement de revenir sur la version X si besoin, en cours de
|
||
migration. Elles délèguent ensuite aux *replicatsets* la gestion des *pod*s.
|
||
|
||
Le *replicatset* est là pour indiquer le nombre de *pod*s que l'on désire et
|
||
s'assurer que le nombre de *pod*s actuellement lancé est bien en adéquation avec
|
||
le nombre de *pod*s 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`.
|
||
|
||
|
||
#### Pasage à l'échelle : facile ?
|
||
|
||
Pour lancer 3 `ping`s 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 *pod*s attendus et le nombre
|
||
de *pod*s 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é instantanément car le
|
||
*replicatset* constate une différence dans le nombre attendu.
|
||
|
||
Si nous voulons arrêter de DDoS Google/Cloudflare, il ne s'agit pas de tuer
|
||
chacun des *pod*s 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
|
||
recréera un similaire (avec de nouveaux *pod*s).
|
||
|
||
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 ressource
|
||
*service*). Un service est une adresse IP que l'on peut considérer comme stable
|
||
pour un *pod* ou un groupe de *pod*s.
|
||
|
||
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 *pod*s 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 fournit 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`\
|
||
|
||
Déployons maintenant l'image `youp0m` pour voir comment utiliser les *service*s :
|
||
|
||
```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 <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.
|
||
|
||
Si vous passez par `kind`, vous pouvez constater le bon fonctionnement grâce à :
|
||
|
||
```bash
|
||
docker exec -it kind-control-plane curl 10.102.129.233: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 :
|