Kubernetes
Introduction
Ce TP aura une note de type bonus pour les TPs sur Docker/Swarm/k8s. Il est donc important que vous ayez un cluster kubernetes fonctionnel et que la ressource Ingress utilise un nom DNS déclaré sur l'Openstack afin qu'on puisse vérifier votre travail:
- Sur tomuss mettre le nom DNS de votre service si vous avez fini le TP sinon mettre l'adresse ip d'un worker
Kubernetes est un orchestrateur de conteneur souvent utilisé avec docker. Vous devez aujourd'hui créer un cluster kubernetes en utilisant l’installeur RKE (Rancher Kubernetes Engine). RKE est un installeur qui utilise docker pour contenir les différents composant de Kubernetes. Il est donc nécessaire de disposer de machines où docker est installé et fonctionnel.
Préparation
Nous allons suivre ce tutoriel. L'une des machines utilisée sera le master assumant les rôles de control-plane et etcd et les 2 autres seront des workers. Mais pour que cela fonctionne, il faut au départ vérifier quelques points.
Avoir des machines pour le cluster
Dans le projet openstack CloudComputing, créez vos 2 machines :
- basées sur l'image
Ubuntu 20.04.3 LTS - Docker Ready
- avec le flavor
m1.xsmall
(1Gb mémoire 1 CPU et 10Go de disque) - avec le nom suivant:
p1234567-worker
Puis vous allez créer le control plane (master) qui requiert plus de puissance:
- basées sur l'image
Ubuntu 20.04.3 LTS - Docker Ready
- avec le flavor
c2.small
(2Gb mémoire 2 CPUs et 10Go de disque) - avec le nom suivant:
p1234567-controlplane
Si nécessaire, modifiez ces machines pour que l'utilisateur ubuntu
soit utilisateur de docker (c'est à dire dans le groupe docker
).
Vérifiez bien le fonctionnement et la possibilité de télécharger quelque chose avec la commande docker search
.
Avoir un utilisateur capable de se connecter automatiquement
<hi #ed1c24>UNIQUEMENT SI RKE EST EXÉCUTÉ DEPUIS UNE VM</hi>
Chaque machine doit avoir un moyen de se connecter par ssh sur les autres. Grâce à votre clef vous pouvez le faire, mais il faut maintenant une seconde clef pour permettre à la machine de faire comme vous. Pour cela :
- Créez une paire de clefs ssh sans passphrase sur le master (commande
ssh-keygen
) et placez les à la position par défaut dans le fichier.ssh/id_rsa
et.ssh/id_rsa.pub
. Ne mettez pas de mot de passe. - Copiez la clef secrète sur les autres machines dans le même répertoire
- Ajoutez la clef au fichier des clefs autorisées (
.ssh/authorized_keys
). Attention de conserver les clefs déjà présentes (celles qui vous permettent de vous connecter à ces serveurs).
Vérifier le fonctionnement en testant la connexion depuis l'une des machines du cluster sur les autres.
Installation de RKE
Installez RKE depuis le dépôt : releases. Attention de bien choisir une version stable. Ce site propose les versions de la plus récente à la plus ancienne, les premières version proposées sont souvent des Release Candidate RC
, ne les utilisez pas.
Le fichier qui est fourni est un simple fichier exécutable, vous pouvez changer ses droits et l'utilisez le avec la commande :
./rke config
Cette commande est un script interactif qui pose des questions pour créer le fichier de configuration du cluster. Créer un cluster sur vos 3 machines, la première devant assurer le rôle de controle-plane
et celui de etcd
, les deux autres seront worker
. Il est possible de modifier le fichier après sa création. Ne recommencez pas tout à chaque erreur.
Attention, dans la plupart des cas, il faut utiliser les réponses par défaut.
Cette commande va créer le fichier de configuration du cluster cluster.yml
qu'il est possible d'éditer à la main si vous avez fait une petite erreur lors du script.
Installation de kubernetes
L'installation se lance simplement avec la commande
./rke up
depuis le répertoire où se trouve le fichier cluster.yml
.
Si tout se passe bien, la commande se termine par
... INFO[0327] [addons] no user addons defined INFO[0327] Finished building Kubernetes cluster successfully
Le cluster est maintenant en place. Il faut un logiciel client pour le piloter.
Erreurs courantes
Problème de connexion en ssh
ubuntu@prepakube-1:~$ ./rke up INFO[0000] Running RKE version: v1.0.0 INFO[0000] Initiating Kubernetes cluster INFO[0000] [dialer] Setup tunnel for host [192.168.76.124] INFO[0000] [dialer] Setup tunnel for host [192.168.76.125] INFO[0000] [dialer] Setup tunnel for host [192.168.76.120] WARN[0000] Failed to set up SSH tunneling for host [192.168.76.124]: Can't retrieve Docker Info: error during connect: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.24/info: Unable to access node with address [192.168.76.124:22] using SSH. Please check if you are able to SSH to the node using the specified SSH Private Key and if you have configured the correct SSH username. Error: ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain WARN[0000] Removing host [192.168.76.124] from node lists WARN[0000] [state] can't fetch legacy cluster state from Kubernetes: Cluster must have at least one etcd plane host: failed to connect to the following etcd host(s) [192.168.76.124] INFO[0000] [certificates] Generating CA kubernetes certificates INFO[0000] [certificates] Generating Kubernetes API server aggregation layer requestheader client CA certificates INFO[0000] [certificates] Generating Kubernetes API server certificates INFO[0001] [certificates] Generating Service account token key INFO[0001] [certificates] Generating Kube Controller certificates INFO[0001] [certificates] Generating Kube Scheduler certificates INFO[0001] [certificates] Generating Kube Proxy certificates INFO[0001] [certificates] Generating Node certificate INFO[0001] [certificates] Generating admin certificates and kubeconfig INFO[0001] [certificates] Generating Kubernetes API server proxy client certificates INFO[0002] Successfully Deployed state file at [./cluster.rkestate] INFO[0002] Building Kubernetes cluster INFO[0002] [dialer] Setup tunnel for host [192.168.76.125] INFO[0002] [dialer] Setup tunnel for host [192.168.76.120] FATA[0002] Cluster must have at least one etcd plane host: please specify one or more etcd in cluster config
Pour comprendre l'erreur, il faut lire un peu plus haut dans les logs :
WARN[0000] Failed to set up SSH tunneling for host [192.168.76.124]: Can't retrieve Docker Info: error during connect: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.24/info: Unable to access node with address [192.168.76.124:22] using SSH. Please check if you are able to SSH to the node using the specified SSH Private Key and if you have configured the correct SSH username. Error: ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain WARN[0000] Removing host [192.168.76.124] from node lists
rke
n'a pas été capable de se connecter à l'une des machine, elle a donc été supprimée. Mais il se trouve que c'est la seule dont le rôle est controle pane
, le cluster ne peut donc pas être mis en place.
<hi #22b14c>Correction</hi> fait en sorte que la machine depuis laquelle se lance rke
soit capable de se connecter sur la machine en question en ssh. rke
utilise ssh même si c'est la même machine.
Problème dans le fichier de configuration
ubuntu@prepakube-1:~$ ./rke up INFO[0000] Running RKE version: v1.0.0 INFO[0000] Initiating Kubernetes cluster FATA[0000] Failed to validate cluster: Cluster can't have duplicate node: 192.168.76.124
Dans le fichier cluster.yml
, il y a plusieurs fois la même IP pour deux hosts différents. Corrigez cela.
Problème avec le proxy
<hi #ed1c24>UNIQUEMENT SI RKE EST EXÉCUTÉ DEPUIS UNE VM</hi>
Comme d'habitude, la configuration du proxy à l'université pose problème. Par exemple :
ubuntu@prepakube-1:~$ ./rke up INFO[0000] Running RKE version: v1.0.0 INFO[0000] Initiating Kubernetes cluster INFO[0000] [certificates] Generating Kubernetes API server certificates INFO[0000] [certificates] Generating admin certificates and kubeconfig INFO[0000] [certificates] Generating kube-etcd-192-168-76-124 certificate and key ... INFO[0126] [controlplane] Successfully started Controller Plane.. INFO[0126] Using proxy environment variable HTTP_PROXY with value [http://proxy.univ-lyon1.fr:3128] INFO[0126] Using proxy environment variable HTTPS_PROXY with value [http://proxy.univ-lyon1.fr:3128] INFO[0126] Using proxy environment variable NO_PROXY with value [univ-lyon1.fr,127.0.0.1,localhost] INFO[0126] [authz] Creating rke-job-deployer ServiceAccount FATA[0152] Failed to apply the ServiceAccount needed for job execution: Post https://192.168.76.124:6443/apis/rbac.authorization.k8s.io/v1/clusterrolebindings?timeout=30s: Forbidden ubuntu@prepakube-1:~$
Les machines étant configurées pour utiliser le proxy de l'université afin d'aller sur internet et comme rke utilise des requêtes HTTP, il est possible que la configuration du proxy empêche l'une des machines du cluster d'en contacter une autre. En effet, la requête passe par le proxy qui refuse ce type de connexion. Vous devez donc vérifier votre configuration des VMs. Cela peut aussi venir du fait que vous n'ayez pas modifié les variables d'environnement. Par exemple si vous n'avez pas pensé à vous déconnecter puis vous reconnecter à la machine (Les variables sont chargées à la connexion).
Installation de kubectl
- Installez le package
kubectl
comme indiqué ici.
- Installer aussi les complétions kubectl pour votre SHELL (bash, zsh…)
La génération du cluster à créé un fichier de description kube_config_cluster.yml
. C'est un fichier qui permet à kubectl
de contacter le cluster. Vous pouvez soit copier ce fichier sous le nom ~/.kube/config
soit ajouter --kubeconfig nomdufichier
à chaque commande. Il est aussi possible de spécifier le chemin du fichier avec export KUBECONFIG=/chemin/vers/mon/fichier/kube_config_cluster.yml
.
Pour vérifier la configuration, vous pouvez lister les nœuds du cluster avec la commande kubectl get nodes
qui doit afficher 3 noeuds.
kubectl get nodes NAME STATUS ROLES AGE VERSION master Ready controlplane,etcd 3m57s v1.17.14 worker-1 Ready worker 3m50s v1.17.14 worker-2 Ready worker 3m49s v1.17.14
Un premier pod
L'unité de base du cluster est un pod. C'est un ensemble de 1 ou plusieurs conteneurs, en général un seul. Les conteneurs d'un même pod sont forcement instanciés sur le même nœud et se retrouverons dans les mêmes espace de nom, c'est à dire par exemple qu'ils pourront se contacter directement via l'adresse localhost
.
Vous pouvez tester le cluster avec un premier pod (l'exemple est pris ici).
Les objets kubernetes peuvent être crées par des commandes kubectl
, mais le plus simple est de créer des fichiers yml
et de les appliquer via la commande kubectl apply -f nomfichier.yml
Créez tout d'abord un fichier de description test_www.yml :
apiVersion: v1 kind: Pod metadata: name: www spec: containers: - name: nginx image: nginx ports: - containerPort: 80 hostPort: 8080
Et utilisez les commandes pour creer le pod:
# pour créer le pod kubectl apply -f test_www.yml # ou kubectl create -f test_www.yml # pour voir les pods existants kubectl get pods # pour voir les détails d'un pod kubectl describe pod <nomdupod> # pour voir les logs d'un pods kubectl logs <nomdupod> # pour exécuter une commande dans le pod kubectl exec -it <nomdupod> <commande> # pour supprimer un pod kubectl delete pods <nomdupod>
- Sur quel noeud a été instancié le pod ?
- Avec votre navigateur testez la réponse de nginx sur le port
8080
du noeud qui instancie le pod - Vérifiez dans les logs que c'est bien ce dernier qui est contacté
- Testez la réponse à partir du port
8080
d'un autre noeud. Cela fonctionne-t-il ? Est-ce différent de ce qui se passe avec swarm ? - Allez sur le noeud qui instancie le pod, retrouver le docker crée et supprimez le. Est-il automatiquement recréé ? Est-ce différent de ce qui se passe avec swarm ?
- Lisez l'aide de la commande
kubectl scale
(avec l'option--help
). Pouvez-vous augmenter le nombre de pods qui fournisse le service ? Est-ce différent de ce qui se passe avec swarm ? - Supprimez le pod
Un premier contrôleur
Normalement vous ne passez jamais directement par une création de pods mais on charge un contrôleur de le faire. Ce dernier va par exemple s'occuper de créer des répliquas, de maintenir l'existence du nombre demandé de répliquas fonctionnels … Nous ne verrons ici que la ressource deployment qui est le moyen plus direct de créer des pods et de les modifier.
Une documentation plus complète des déploiements est ici
Créer un fichier busy.yml
contenant ceci :
apiVersion: apps/v1 kind: Deployment metadata: name: dep-busy labels: app: dep-busybox tp: kubernetes spec: replicas: 2 selector: matchLabels: app: busybox tp: kubernetes template: metadata: labels: app: busybox tp: kubernetes spec: containers: - name: busy image: busybox command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']
Le type d'objet est Deployment
, Le label
servira à faire des requêtes pour retrouver l'objet. Les spécifications contiennent :
- le nombre de répliquas demandés;
- le
matchLabel
c'est à dire le moyen pour le contrôleur de retrouver les pods créés. - le template des pods à créer.
La partie template prend donc les options que peuvent prendre les spécifications de pods. Ici : le pods crée contiendra un conteneur de nom busy
et se base sur l'image docker busybox
. Il est important que les labels des pods (champs de spec.template.metadata.labels
) contiennent les champs recherchés par le contrôleur (spec.selector.matchLabels
).
- Créez votre déploiement grâce à
kubectl apply …
. - Vérifiez les créations du deployment et du replicaset (objet effectivement chargé de la réplication) des pods grâce à
kubectl get …
. - Regardez les logs de vos pods (
kubectl logs NOMDUPOD
)
Busybox est une image avec des commandes simples qui vont permettre de faire des tests.
- Grâce à
kubectl describe pod NOMDUPOD
retrouver sur quel nœud est implémenté l'un des pods créés, et son adresse IP. - Grâce à
kubectl exec -it NOMDUPOD – sh
ouvrez un shell dans le pod, essayer de pinger l'autre pod.
Configuration des conteneurs
Variables d'environnement
Comme avec docker, beaucoup de configurations utilisent des variables d'environnements qui peuvent être positionnées dans le fichier de description.Voir ici la méthode pour ajouter des variables aux pods. <hi #ff7f27>Attention</hi>, la documentation ici présente le fichier de description d'un pods, la partie spec
correspond à la partie template.spec
du deployment.
- Dans votre conteneur busybox, faites en sorte d'avoir une variable
MESSAGE
et faite en sorte que le conteneur affiche le message au départ.
Volumes
Les volumes existent dans kubernetes, mais leur sens est différent. En fait, un volume de base est seulement attaché aux pods et leur durée de vie n'est que celle du pod qui l'utilise. Il existe d'autre type de volumes dont la durée de vie est persistante et qui peuvent être partagés entre les noeuds du cluster. Mais nous n'aurons pas le temps de traiter cela dans ce TP
- En vous basant sur la documentation ici, créez un volume de type
emptyDir
dans le podbusybox
. Vous monterez ce volume sur/data
Initialisation des conteneurs
Les volumes permettent de créer une phase d'initialisation pour les conteneurs. En effet, dans un pod, il existe des initContainers
. Ce sont des conteneurs qui sont lancés avant les conteneurs normaux du pod, qui ne sont lancés qu'à la création du pod et qui peuvent par exemple télécharger du code ou initialiser une base de données. La documentation est ici.
- utilisez un conteneur d'initialisation pour remplir le volume
/data
avec un fichierpage.html
qui contient le contenu HTML de cette page. Vous pouvez utiliserwget
avec l'option-O
pour définir où écrire le fichier. Et il faut bien monter le volume dans les containers
Attention, il est conseillé de tester la commande lancée par le conteneur d'initialisation dans un busybox avant de l'utiliser réellement. Par exemple, il y a 2 problèmes courant :
- par défaut les pods n'ont pas accès à internet en dehors de l'université (a cause des configuration de proxy).
- les busybox gèrent assez mal les connexions
https
.
Gestion des secrets
Pour les données sensibles, il est possible de faire des secrets, c'est à dire des données qui sont injectées dans le cluster et utilisées par les dockers sans apparaitre dans les fichiers de configurations. La documentation est ici.
- Créez un secret contenant 2 champs :
- un
username
de valeurnomsecret
- un
password
de valeurt0t0
- Ajoutez au pod
busybox
- une variable d'environnement de nom
SECRET_USERNAME
contenant la valeur du champsusername
du secret - un fichier
/secret/password
contenant la valeur du champspassword
Toujours en utilisant cette documentation vous devez donc créer un seul secret mais vous passez le username avec une variable env et le password avec un volume. (Il y a des exemples pour les 2 méthodes dans la documentation)
Bien faire attention à distinguer le nom du secret et les clés qui contiennent les valeurs dans votre secret. Pour le volume cette page va vous aider
Connecter les pods
Pour le moment, il n'est pas possible de connecter les pods entre eux. En effet, ces derniers peuvent communiquer par leur adresse IP, mais il manque un moyen pour un pod de nommer un autre pod.
- Commencez par créer un déploiement avec 2 réplicas de pods. Ces pods doivent être labellisés par
{app: php, tp: kubernetes}
. De plus chacun doit contenir :- un conteneur d'image
php:apache
- un volume
fichiers
monté comme répertoire/var/www/html
du serveur apache - un fichier
index.php
dans ce volume contenant le code :
je suis le serveur php<br/> <pre><?php print_r($_SERVER);?></pre>
Un bon moyen pour cela est d'exécuter un conteneurs d'initialisation dont la commande est :
["sh", "-c", "echo 'je suis le serveur php<br/>\n<pre><?php print_r($_SERVER);?></pre>' > /var/www/html/index.php"]
Pour créer la connexion, nous allons utiliser un nouvel objet de kubernetes, le service. La documentation est ici.
Dans l'exemple suivant :
apiVersion: v1 kind: Service metadata: name: my-service spec: selector: app: MyApp ports: - protocol: TCP port: 80 targetPort: 9376
Le service dont le nom est my-service
transfert le nom my-service:80
vers le port 9376
de tous les pods dont les labels contiendrons {app: MyApp}
.
- Créez un service de nom
serv-php
qui transférera le port 80 vers le port 80 des pods php créés précédemment.
- Testez le bon fonctionnement du service en exécutant une requête http depuis le pod
busybox
$ kubectl exec -it NOMDUPOD -- sh / # wget http://serv-php/ -O - Connecting to serv-php (10.43.79.61:80) writing to stdout je suis le serveur php<br/> <pre>Array ( [HTTP_HOST] => serv-php [HTTP_USER_AGENT] => Wget [HTTP_CONNECTION] => close [PATH] => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin [SERVER_SIGNATURE] => <address>Apache/2.4.38 (Debian) Server at serv-php Port 80</address> [SERVER_SOFTWARE] => Apache/2.4.38 (Debian) [SERVER_NAME] => serv-php [SERVER_ADDR] => 10.42.1.33 [SERVER_PORT] => 80 [REMOTE_ADDR] => 10.42.2.17 [DOCUMENT_ROOT] => /var/www/html [REQUEST_SCHEME] => http [CONTEXT_PREFIX] => [CONTEXT_DOCUMENT_ROOT] => /var/www/html [SERVER_ADMIN] => webmaster@localhost [SCRIPT_FILENAME] => /var/www/html/index.php [REMOTE_PORT] => 48724 [GATEWAY_INTERFACE] => CGI/1.1 [SERVER_PROTOCOL] => HTTP/1.1 [REQUEST_METHOD] => GET [QUERY_STRING] => [REQUEST_URI] => / [SCRIPT_NAME] => /index.php [PHP_SELF] => /index.php [REQUEST_TIME_FLOAT] => 1578645615.917 [REQUEST_TIME] => 1578645615 [argv] => Array ( ) [argc] => 0 ) </pre> - 100% |************************************************************************************************************************| 1067 0:00:00 ETA written to stdout
Rendre accessible les services depuis l'extérieur
Jusqu'à présent, les accès se font via les Pods dans le cluster. Pour finir il faut être capable d'ouvrir le service sur l'extérieur. Pour cela, il faut mettre en place un Ingress, La documentation est ici.
Un peu comme les services nginx
que vous avez déjà utilisé, l'ingress va recevoir les requêtes de l'extérieur et les redispatcher sur les services internes en fonction de ce qui est demandé. Pour cela, il se base sur le nom DNS utilisé pour accéder au service et sur le contenu des requêtes HTTP.
Vous pouvez créer une ressource ingress en vous basant sur cette exemple de la documentation :
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: simple-fanout-example annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: foo.bar.com http: paths: - path: /foo backend: serviceName: service1 servicePort: 4200 - path: /bar backend: serviceName: service2 servicePort: 8080
Ici,
- les requêtes
http://foo.bar.com/foo
seront dispatchées surservice1:4200
- les requêtes
http://foo.bar.com/bar
seront dispatchées surservice2.8080
Pour mettre en place cela, il faut que vous ajoutiez un nom DNS à votre cluster. Pour cela, la plateforme openstack est maintenant munie du service Designate
qui vous permet de gérer un nom valable sur le réseau de l'université.
- Dans la plateforme openstack, dans l'onglet
compute
ouvrez le menuDNS
puisZones
cliquez surcc.os.univ-lyon1.fr
etcreate reccord set
ce qui vous permet d'ajouter un champsA
c'est à dire une correspondance entre un nom DNS et une ou plusieurs adresse IP. - Pour le nom
VOTRENOM.cc.os.univ-lyon1.fr
ajouter les 2 adresses IP de vos worker - Bien vérifier que votre enregistrement est avec le statut Active. Faites attention la fonction refresh de votre navigateur ne va pas fonctionner avec le module angular de Designate (il faut donc passer par un autre menu puis revenir dans la zone DNS )
- Depuis la réseau de l'université vous pouvez essayer de faire un
ping
en utilisant le nom DNS
Le service designate
est nouveau et pas très bien rodé. Si vous ne parvenez pas à créer le nom, abandonnez et utiliser le nom dns qui a été automatiquement créé pour vos machine: NOMDELAMACHINE.cc.os.univ-lyon1.fr
Il faut maintenant faire la correspondance entre ce nom et l'accès au service php
- Créez une ressource ingress qui envoie les requêtes http://VOTRENOM.cc.os.univ-lyon1.fr/ sur le service php que vous avez crée.
- Vérifiez que cela fonctionne avec votre navigateur.