====== 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 {{https://github.com/rancher/rke/|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 {{https://rancher.com/an-introduction-to-rke/|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=== UNIQUEMENT SI RKE EST EXÉCUTÉ DEPUIS UNE VM 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 : {{https://github.com/rancher/rke/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. Correction 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 === UNIQUEMENT SI RKE EST EXÉCUTÉ DEPUIS UNE VM 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é {{https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl|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 {{http://www.projectatomic.io/docs/gettingstarted/|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 # pour voir les logs d'un pods kubectl logs # pour exécuter une commande dans le pod kubectl exec -it # pour supprimer un pod kubectl delete pods * 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 [[https://kubernetes.io/docs/concepts/workloads/controllers/deployment/|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.[[https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/|Voir ici la méthode]] pour ajouter des variables aux //pods//. Attention, 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 [[https://kubernetes.io/fr/docs/concepts/storage/volumes/#emptydir|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. [[https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-initialization/|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 : - 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. [[https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/|La documentation est ici]]. * Créez un secret contenant 2 champs : - un ''username'' de valeur ''nomsecret'' - un ''password'' de valeur ''t0t0'' * Ajoutez au pod ''busybox'' - une variable d'environnement de nom ''SECRET_USERNAME'' contenant la valeur du champs ''username'' du secret - un fichier ''/secret/password'' contenant la valeur du champs ''password'' [[https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/|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 [[https://kubernetes.io/docs/concepts/configuration/secret/#projection-of-secret-keys-to-specific-paths|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
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
\n
' > /var/www/html/index.php"]
Pour créer la connexion, nous allons utiliser un nouvel objet de kubernetes, le //service//. [[https://kubernetes.io/fr/docs/concepts/services-networking/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
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] => 
Apache/2.4.38 (Debian) Server at serv-php Port 80
[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 )
- 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//, [[https://kubernetes.io/fr/docs/concepts/services-networking/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 sur ''service1:4200'' * les requêtes ''http://foo.bar.com/bar'' seront dispatchées sur ''service2.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 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.