cloud:2019:tp_kube

Kubernetes

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.

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.

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.

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.

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

  • 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

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

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 :

  1. le nombre de répliquas demandés;
  2. le matchLabel c'est à dire le moyen pour le contrôleur de retrouver les pods créés.
  3. 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.

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 pod busybox. 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 fichier page.html qui contient le contenu HTML de cette page. Vous pouvez utiliser wget 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 :

  1. par défaut les pods n'ont pas accès à internet en dehors de l'université (a cause des configuration de proxy).
  2. 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 :
    1. un username de valeur nomsecret
    2. un password de valeur t0t0
  • Ajoutez au pod busybox
    1. une variable d'environnement de nom SECRET_USERNAME contenant la valeur du champs username du secret
    2. un fichier /secret/password contenant la valeur du champs password

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

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 :
    1. un conteneur d'image php:apache
    2. un volume fichiers monté comme répertoire /var/www/html du serveur apache
    3. 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

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,

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 menu DNS puis Zones cliquez sur cc.os.univ-lyon1.fr et create reccord set ce qui vous permet d'ajouter un champs A 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.
  • cloud/2019/tp_kube.txt
  • Dernière modification : 2021/12/10 11:30
  • de romain.chanu