# Cluster Kubernetes sur Raspberry Pi {{INLINETOC}} La conteneurisation démontre son efficacité dans le déploiement d'applications en Cloud Computing. Les conteneurs peuvent encapsuler des programmes complexes avec leurs dépendances dans des environnements isolés, ce qui rend les applications plus portables. Ils sont donc adoptés dans les clusters de **c**alcul **h**aute **p**erformance (**HPC**). Cet article introduit l'orchestrateur de conteneurs (Kubernetes) afin de produire une architecture hybride qui intègre les [[prive:rpi-hpc|clusters HPC]] . **Kubernetes** est une solution de conteneurisation open-source, simplifiant le déploiement, la mise à l’échelle, et la gestion en général, d’applications conteneurisées. ## Préparation des systèmes ### Raspbian Pour le noeud master, il est impératif d’utiliser une image 64b, sans quoi **Kubespray** ne sait pas installer **etcd**. Le problème étant que les mainteneurs du projet etcd ne publient pas de version 32b, il reste possible d’installer une version 32b d’etcd, fournie par Debian, quoi que **Kubespray** ne supporte pas cette combinaison Pour les autres noeuds, on peut utiliser la dernière image 32b. Une fois ces archives téléchargées et extraites, on peut flasher les cartes micro-sd de nos Raspberry Pi : ``` dd if=./2020-08-20-raspios-buster-arm64-lite.img | pv | dd of=/dev/mmcblk0 ``` ``` apt-get update --allow-releaseinfo-change apt-get install python-apt cgroupfs-mount ceph-common rbd-nbd apt-get upgrade apt-get dist-upgrade apt-get install haproxy hatop sudo apt-get install python-pip git ca-certificates ``` ### ArchLinux Installer les packages **python-apt**, **ceph**, **haproxy**, **hatop**, **python-pip**, **git**, **ca-certificates** Pour installer les packages des dépôts officiels, exécuter :\\ \\ `pacman -S foobar` \\ \\ Pour installer les packages de l'Arch User Repository (AUR), télécharger l'archive PKGBUILD, l'extraire, vérifier le contenu et enfin exécuter, dans le même dossier :\\ \\ `makepkg -sri` ### Configurer le système Démarrer le Raspberry, avec écran et clavier, afin d’y configurer un mot de passe root : ``` sudo -i passwd ``` Idéalement, on ne devrait pas être root lors de l'installation ou de l'exécution du cluster comme ce serait le cas dans les environnements de production. On devrait donc créer un utilisateur avec le privilège sudo. Sur Arch linux en tant que root:\\ - installer sudo : `pacman -S sudo`\\ - éditer le fichier des sudoers: `visudo` - décommenter pour permettre aux membres du groupe wheel d'exécuter n'importe quelle commande : ` %wheel ALL=(ALL) ALL`\\ - créer un utilisateur et l'ajouter au groupe wheel Une addresse IP statique : ``` cat </etc/network/interfaces auto eth0 iface eth0 inet static address x.y.z.a netmask 255.255.255.0 gateway x.y.z.b EOF ``` Serveur DNS : ``` cat </etc/resolv.conf nameserver 10.255.255.255 domain friends.intra.example.com search friends.intra.example.com EOF ``` Désactiver le fichier d’échange swap((Lors de l'amorçage du cluster **Kubernetes**, l'un des contrôles en amont effectués par **Kubernetes** consiste à voir si on a un swap actif. S'il trouve une partition de swap active, il échouera et s'arrêtera là. Afin de réussir les contrôles en amont, il faut désactiver le swap sur chaque nœud du cluster Kubernetes.)): ``` sed -i 's|CONF_SWAPSIZE=.*|CONF_SWAPSIZE=0|' /etc/dphys-swapfile systemctl disable dphys-swapfile ``` Configurer le serveur SSH : ``` sed -i -e 's|#PermitRootLog.*|PermitRootLogin yes|' -e 's|#PasswordAuth|PasswordAuthentication yes|' /etc/ssh/sshd_config systemctl enable ssh ``` Désactiver les services réseaux qu'on n’utilisera pas : ``` systemctl disable dhcpcd systemctl disable wpa_supplicant systemctl disable bluetooth ``` Configurer le nom de la machine : ``` cat </etc/hostname EOF ``` ``` cat </etc/hosts x.y.z.a 127.0.0.1 127.0.0.1 localhost ::1 localhost6 localhost6.localdomain ff02::1 ip6-allnodes ff02::2 ip6-allrouters EOF ``` Activer les cgroups((Les groupes de contrôle sont une fonctionnalité fournie par le noyau Linux pour gérer, restreindre et auditer des groupes de processus. Par rapport à d'autres approches les groupes de contrôle sont plus flexibles car ils peuvent opérer sur des sous-ensembles de processus et éventuellement avec différents utilisateurs du système)) mémoire : ``` sed -i 's|rootwait$|rootwait cgroup_enable=cpuset cgroup_enable=memory|' /boot/cmdline.txt ``` ``` cat /boot/cmdline.txt ``` ``` console=serial0,115200 console=tty1 root=PARTUUID=dcca89ee-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait cgroup_enable=cpuset cgroup_enable=memory ``` Redémarrer, et vérifier les mots de passe root, IP, gateway, groups memory: ``` cat /proc/cgroups ``` ``` hostname -s hostname -f ip a ip r ``` La commande `cat /proc/cgroups` doit retourner quelque chose comme cela: ``` subsys_name hierarchy num_cgroups enabled cpuset 9 3 1 cpu 3 60 1 cpuacct 3 60 1 blkio 2 60 1 memory 4 88 1 devices 8 96 1 freezer 6 3 1 net_cls 7 3 1 perf_event 5 3 1 net_prio 7 3 1 pids 10 110 1 ``` Mettre le système à jour. ## Installation de l'écosystème des conteneurs Il faut installer un environnement d'exécution de conteneur dans chaque nœud du cluster afin que les pods puissent s'y exécuter. Cette section décrit ce qui est impliqué et les tâches associées pour la configuration des nœuds. Cette illustration montre comment Docker, Kubernetes, CRI, OCI, containerd et runc s'intègrent dans cet écosystème : Docker, Kubernetes, OCI, CRI-O, containerd et runc: comment ils travaillent ensemble? .------------. .------------. | docker | | Kubernetes | Outils utilisé pour exécuter des conteneurs. .--+---------. .----+-------. | | | v | '---------------------------' | |Container Runtime Interface| CRI est une API Kubernetes. | '------------+-----------+--' Il définit la manière dont Kubernetes | | | interagit avec différents environnements | | | d'exécution de conteneur. | .----------. | | | | Parce qu'il est standardisé dans une | | | spécification, on peut donc choisir | | | l'implémentation CRI qu'on souhaite v v v utiliser. Ou en écrire une .------------. .-------. | containerd | | CRI-0 | On peut choisir parmi quelques .--+---------. .---+---. environnements d'exécution qui | | implémentente la spécification CRI. | | | | containerd vient de Docker. Il est | | rendu compatible CRI grâce à son | | plugin cri. | | | | CRI-0 a été développé en tant | | qu'implémentation CRI, à partir de | | Red Hat/IBM/etc. v v '------------------------------------' |Open Container Initiative (OCI) spec| OCI fournit des spécifications pour '-----------------+------------------' les images de conteneurs et les | conteneurs en cours d'exécution. | | Il fournit également une implémentation | de la spécification runt. C'est un outil | pour créer et exécuter des processus de v conteneur. .------. | RUNC | runc est un outil compatible OCI pour .--+---. générer et exécuter des conteneurs. Il | implémente la spécification OCI et est .-------+-------. responsable de la création et de | | l'exécution des processus de conteneur. v v .----------. .-----------. |conteneur | | conteneur | Enfin, le processus conteneurisé est en .----------. .-----------. cours d'exécution ! ### Containerd Installer le package containerd.io à partir des référentiels Docker officiels. Configurer containerd : ``` sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml ``` Redémarrer containerd : ``` sudo systemctl restart containerd ``` Activer les dépendances d'exécution requises: ``` cat <= 3.3, un LXD construit par source est également pris en charge. Actuellement, LXE nécessite golang 1.13 ou plus récent pour être compilé et utilise des modules Go. **Pour Archlinux**: activer le dépôt de la communauté dans /etc/pacman.conf:\\ \\ `[community]`\\ ` Include = /etc/pacman.d/mirrorlist`\\ \\ Installer le paquet lxd et go:\\ \\ `pacman -Syu go`\\ `pacman -Syu lxd` Cloner le référentiel https://github.com/automaticserver/lxe à l'emplacement souhaité. Construire le projet en utilisant la commande suivante, qui donnera le binaire dans ./bin/ ``` make build ``` On peut également exécuter le programme directement en utilisant :\\ \\ `make run lxe -- --log-level info --network-plugin cni` Mne fois que LXE est exécuté sur lee système, on peut définir le socket LXE comme point de terminaison CRI dans **kubelet**. Il faut définir les options suivantes pour que **kubelet** puisse se connecter au socket LXE: ``` --container-runtime=remote\ --container-runtime-endpoint=unix:///run/lxe.sock ``` ### rktlet **rktlet** est une implémentation de l'interface d'exécution de conteneur Kubernetes utilisant **rkt** comme environnement d'exécution de conteneur principal. **rkt** est un outil CLI écrit en go pour exécuter un conteneur sous Linux. Il utilise une conception multicouche permettant de modifier le ou les temps d'exécution en fonction des souhaits de l'implémenteur. Par défaut, **rkt** utilise **systemd** et **systemd-nspawn** en combinaison pour créer des conteneurs. systemd-nspawn est utilisé pour gérer l'espace de noms dans lequel **systemd** est exécuté pour gérer les groupes de contrôle. Les applications de conteneur sont exécutées en tant qu'unités **systemd**. En utilisant différentes images "étape 1", les outils utilisés pour exécuter l'application peuvent être modifiés des outils **systemd** à presque tout le reste. On peut pouvoir construire rkt sur n'importe quel système Linux moderne avec Go (1.5+) installé. Pour la plupart, la base de code est autonome, mais l'assemblage du stage1 nécessite néessite des dépendances suivantes glibc, libdl, libacl, C compiler. Une fois les dépendances satisfaites, on peut créer rkt. Par défaut, rkt obtient systemd à partir d'une image Linux CoreOS Container pour générer stage1. Il est également possible de construire systemd à partir des sources. Pour ce faire, utilisez les paramètres de configuration suivants après avoir exécuté ./autogen.sh : ``` git clone https://github.com/rkt/rkt.git cd rkt ./autogen.sh && ./configure --with-stage1-flavors=src --with-stage1-systemd-version=v231 --with-stage1-systemd-revision=master --with-stage1-systemd-src= $HOME/src/systemd && make ``` Pour pouvoir exécuter rkt, activer les dépendances d'exécution requises: ``` sudo modprobe overlay ``` Cloner le référentiel https://github.com/kubernetes-retired/rktlet à l'emplacement souhaité. Construire le projet en utilisant la commande suivante: ``` make go build -o bin/rktlet ./cmd/server/main.go ``` Configurer Kubernetes pour utiliser **rktlet**, en supposant que le processus **rktlet** s'exécute avec la configuration par défaut, définir les options suivantes suivantes: ``` --cgroup-driver=systemd \ --container-runtime=remote \ --container-runtime-endpoint=/var/run/rktlet.sock \ --image-service-endpoint=/var/run/rktlet.sock ``` ## Configuration du LoadBalancer Raspberry 3 et 4 conviendront parfaitement au déploiement de **Kubernetes**. En revanche, les modèles plus anciens seront incapables de lancer certaines composantes **Kubernetes**, faute d’images adaptées à leur ARM v6. Déployer le LoadBalancer, qui se trouvera devant le service API des masters **Kubernetes**: ``` cat </etc/haproxy/haproxy.cfg ``` ``` global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners stats timeout 30s user haproxy group haproxy daemon ca-base /etc/ssl/certs crt-base /etc/ssl/private ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS ssl-default-bind-options no-sslv3 defaults log global option dontlognull timeout connect 5000 timeout client 50000 timeout server 50000 errorfile 400 /etc/haproxy/errors/400.http errorfile 403 /etc/haproxy/errors/403.http errorfile 408 /etc/haproxy/errors/408.http errorfile 500 /etc/haproxy/errors/500.http errorfile 502 /etc/haproxy/errors/502.http errorfile 503 /etc/haproxy/errors/503.http errorfile 504 /etc/haproxy/errors/504.http listen kubernetes-apiserver-https bind 0.0.0.0:6443 mode tcp option log-health-checks timeout client 3h timeout server 3h server master1 10.42.253.40:6443 check check-ssl verify none inter 10s server master2 10.42.253.41:6443 check check-ssl verify none inter 10s server master3 10.42.253.42:6443 check check-ssl verify none inter 10s balance roundrobin EOF ``` ``` cat </etc/profile.d/haproxy.sh alias hatop='hatop -s /run/haproxy/admin.sock' EOF ``` ``` systemctl start haproxy systemctl enable haproxy . /etc/profile.d/haproxy.sh hatop ``` ## Déploiement Kubernetes ### Déploiement Manuel (ArchLinux) Cette section présente l'installation rapide sur Arm64 & Arch de kubeadm pour déployer un cluster **Kubernetes** bare metal non HA un maître deux travailleurs. ``` sudo pacman -S ethtool ebtables socat cni-plugins conntrack-tools iproute2 iptables util-linux ``` Installer [[https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=kubelet-bin|aur/kubelet-bin]] et [[https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=kubeadm-binaur/kubeadm-bin]] Sur tous les hôtes : ``` cat > /etc/sysctl.d/bridge.conf < /etc/docker/daemon.json <Les cidrs ne doivent pas se chevaucher car le maître, les noeuds de calcul et chaque élément présent sur le cluster auront le même réseau virtuel superposé aux sous-réseaux physiques réels. Cela impose en quelque sorte une restriction. En cas de succès, on doit voir le message «Your Kubernetes control-plane has initialized successfully! » avec le jeton et la commande pour rejoindre le travailleur. Noter le jeton retourné par kubeadm join. Une fois le cluster initialisé, pour pouvoir joindre les nœuds de travail en exécutant ce qui suit en tant que root : ``` kubeadm join :6443 --token ``` Actuellement `kubectl get nodes` indique NotReady, ce qui est dû au fait qu'aucun réseau n'est configuré sur le cluster. ``` kubectl get nodes NAME STATUS ROLES AGE VERSION archlinux NotReady master 18m v1.20.0 ``` On va nettoyer le nœud maître, ce qui permet de déployer des pods sur le maître, puis de configurer un réseau. ``` kubectl taint nodes --all node-role.kubernetes.io/master- node/archlinux untainted ``` Il faut maintenant déployer un réseau sur les pods du cluster. Exécuter `kubectl apply -f [podnetwork].yaml` avec l'une des options répertoriées dans https://kubernetes.io/docs/concepts/cluster-administration/addons/ Sur un très petit cluster avec une connexion de couche 2 dédiée, il n'y a pas besoin de vxlan, on peut donc appliquer le déploiement multi-arch **Flannel**. ``` curl https://raw.githubusercontent.com/coreos/flannel/62e44c867a2846fefb68bd5f178daf4da3095ccb/Documentation/kube-flannel.yml | sed "s/vxlan/host-gw/" > kube-flannel.yaml ``` Le déploiement de **kube-router** est également assez simple: ``` kubectl apply -f https://raw.githubusercontent.com/cloudnativelabs/kube-router/v1.1.0/daemonset/kubeadm-kuberouter.yaml ``` Maintenant on peut voir l'état des pods: ``` kubectl get pods -A ``` ``` NAMESPACE NAME READY STATUS RESTARTS kube-system coredns-f9fd979d6-2wp2x 1/1 Running 0 kube-system coredns-f9fd979d6-522nz 1/1 Running 0 kube-system etcd-archlinux 1/1 Running 0 kube-system kube-apiserver-archlinux 1/1 Running 0 kube-system kube-controller-manager-archlinux 1/1 Running 0 kube-system kube-proxy-sjxvw 1/1 Running 0 kube-system kube-router-8fq2m 1/1 Running 0 kube-system kube-scheduler-archlinux 1/1 Running 0 # kubectl get nodes NAME STATUS ROLES AGE VERSION archlinux Ready master 20m v1.20.0 ``` **Containerd** fournit **ctr** pour interagir avec les conteneurs, il organise tous les pods liés à kubernetes dans un espace de noms k8s.io, où on peut répertorier les pods. ``` ctr -n k8s.io c list ``` ``` CONTAINER IMAGE RUNTIME 0cf8a9c17 docker.io/cloudnativelabs/kube-router:latest io.containerd.runc.v2 2bda5427a k8s.gcr.io/pause:3.2 io.containerd.runc.v2 4dbdaea0d k8s.gcr.io/pause:3.2 io.containerd.runc.v2 5430728fc k8s.gcr.io/pause:3.2 io.containerd.runc.v2 546633029 k8s.gcr.io/pause:3.2 io.containerd.runc.v2 5593c7f09 k8s.gcr.io/kube-proxy:v1.20.0 io.containerd.runc.v2 7f4dd2ff4 k8s.gcr.io/pause:3.2 io.containerd.runc.v2 96dd370ec k8s.gcr.io/pause:3.2 io.containerd.runc.v2 aab09f1e1 docker.io/cloudnativelabs/kube-router:latest io.containerd.runc.v2 aba203048 k8s.gcr.io/pause:3.2 io.containerd.runc.v2 b1bd5082a sha256:0369cf4303ffdb467dc219990960a9baa8... io.containerd.runc.v2 dc02359d9 k8s.gcr.io/coredns:1.7.0 io.containerd.runc.v2 e42de4f65 k8s.gcr.io/kube-controller-manager:v1.20.0 io.containerd.runc.v2 fc943a990 k8s.gcr.io/kube-apiserver:v1.20.0 io.containerd.runc.v2 fe49fbf3f k8s.gcr.io/kube-scheduler:v1.20.0 io.containerd.runc.v2 ``` Étiqueter les nœuds comme souhaité : ``` kubectl label node myworker2 node-role.kubernetes.io/worker=worker ``` ### Déploiement avec automatisé (Raspbian) **Kubespray** est un outil de déploiement faisant partie de l’écosystème **Kubernetes**, modulaire, fiable, et relativement exhaustif, il n’a rien à envier aux outils de déploiements d’OpenShift que sont openshift-ansible, ou openshift-install **Ansible** est un moteur d'automatisation informatique Open Source qui automatise le provisionnement, la gestion des configurations, le déploiement des applications, l'orchestration et bien d'autres processus informatiques. L’utilisation d’une machine dédié simplifiera les interventions futures sur le cluster, s’assurant que les mêmes versions de **kubespray**, **python**, **ansible**, … soient utilisées, si l’on veut, par exemple, rajouter un noeud. #### Etape 1: installer les playbooks Kubespray, et Ansible ``` git clone https://github.com/kubernetes-sigs/kubespray cd kubespray sudo pip install -r requirements.txt ``` #### Etape 2: Construire les playbooks du cluster Un playbooks **Ansible** est un fichier YAML dans lesquels sont mentionnés toutes les tâches qu'**Ansible** doit exécuter : de la configuration de l'environnement informatique de production jusqu'à l'orchestration de toutes les étapes de déploiement d'une application ou d'une mise à jour sur celui-ci. La syntaxe quasi-naturelle utilisée pour décrire ces tâches est particulièrement simple. Le fichier YAML commence par décrire les hôtes (ou serveurs) ciblés puis les variables du processus avant de dérouler les jobs à accomplir. Créer le playbook inventoriant les machines composant le cluster: ``` mkdir -p inventory/rpi/group_vars/all inventory/rpi/group_vars/all/k8s-cluster cat <inventory/rpi/hosts.yaml all: hosts: pandore.friends.intra.example.com: etcd_member_name: pandore node_labels: my.topology/rack: rpi-rack1 my.topology/row: rpi-row2 hellenes.friends.intra.example.com: etcd_member_name: hellenes node_labels: my.topology/rack: rpi-rack2 my.topology/row: rpi-row2 epimethee.friends.intra.example.com: etcd_member_name: epimethee node_labels: my.topology/rack: rpi-rack3 my.topology/row: rpi-row2 pyrrha.friends.intra.example.com: node_labels: my.topology/rack: rpi-rack1 my.topology/row: rpi-row3 node-role.kubernetes.io/infra: "true" calliope.friends.intra.example.com: node_labels: my.topology/rack: rpi-rack2 my.topology/row: rpi-row3 node-role.kubernetes.io/infra: "true" clio.friends.intra.example.com: node_labels: my.topology/rack: rpi-rack3 my.topology/row: rpi-row3 node-role.kubernetes.io/infra: "true" erato.friends.intra.example.com: node_labels: my.topology/rack: rpi-rack1 my.topology/row: rpi-row4 node-role.kubernetes.io/worker: "true" euterpe.friends.intra.example.com: node_labels: my.topology/rack: rpi-rack2 my.topology/row: rpi-row4 node-role.kubernetes.io/worker: "true" melpomene.friends.intra.example.com: node_labels: my.topology/rack: rpi-rack3 my.topology/row: rpi-row4 node-role.kubernetes.io/worker: "true" polyhymnia.friends.intra.example.com: node_labels: my.topology/rack: rpi-rack3 my.topology/row: rpi-row1 node-role.kubernetes.io/worker: "true" terpsichore.friends.intra.example.com: node_labels: my.topology/rack: rpi-rack2 my.topology/row: rpi-row1 node-role.kubernetes.io/worker: "true" thalia.friends.intra.example.com: node_labels: my.topology/rack: rpi-rack1 my.topology/row: rpi-row1 node-role.kubernetes.io/worker: "true" children: calico-rr: hosts: {} etcd: hosts: pandore.friends.intra.example.com: hellenes.friends.intra.example.com: epimethee.friends.intra.example.com: kube-infra: hosts: pyrrha.friends.intra.example.com: calliope.friends.intra.example.com: clio.friends.intra.example.com: kube-master: hosts: pandore.friends.intra.example.com: hellenes.friends.intra.example.com: epimethee.friends.intra.example.com: kube-workers: hosts: erato.friends.intra.example.com: euterpe.friends.intra.example.com: melpomene.friends.intra.example.com: polyhymnia.friends.intra.example.com: terpsichore.friends.intra.example.com: thalia.friends.intra.example.com: kube-node: children: kube-master: kube-infra: kube-workers: k8s-cluster: children: calico-rr: kube-node: EOF ``` Cet inventaire décrit un cluster composé de 3 noeuds masters+etcd, 3 noeuds “infra”, et 6 noeuds “workers”. Créer un playbook déclarant les variables globales ``` cat <inventory/rpi/group_vars/all/all.yaml ansible_user: root etcd_data_dir: /var/lib/etcd etcd_kubeadm_enabled: false bin_dir: /usr/local/bin apiserver_loadbalancer_domain_name: api-k8s-arm.intra.example.com loadbalancer_apiserver: address: 10.42.253.52 port: 6443 loadbalancer_apiserver_localhost: false loadbalancer_apiserver_port: 6443 upstream_dns_servers: - 10.255.255.255 no_proxy_exclude_workers: false cert_management: script download_container: true deploy_container_engine: true EOF ``` On désigne ici le ou les serveurs DNS existants que Kubernetes devra interroger pour la résolution de noms hors clusters. Ainsi que la VIP d’un LoadBalancer, et le nom DNS correspondant, ceux-ci pointant sur l’**HAProxy** qu'on viens de déployer. Créer un playbook déclarant les variables relatives au cluster etcd ``` cat <inventory/rpi/group_vars/etcd.yaml etcd_compaction_retention: 8 etcd_metrics: basic etcd_memory_limit: 2GB etcd_quota_backend_bytes: "2147483648" etcd_peer_client_auth: true etcd_deployment_type: host EOF ``` On rajoute une limite mémoire et un quota de 2GB, pour etcd, qui doit rentrer sur les masters, disposant de 4GB de mémoire. Si l’on ne veut pas utiliser docker, comme container runtime, alors il faudra changer le type de déploiement etcd. Par défaut conteneurisé, son déploiement dépend de commandes docker. Le déploiement de type host permettant l’utilisation d’un runtime crio, ou containerd. Créer un playbook déclarant les variables spécifiques du cluster: ``` cat <inventory/rpi/group_vars/k8s-cluster/k8s-cluster.yaml kube_config_dir: /etc/kubernetes kube_script_dir: "{{ bin_dir }}/kubernetes-scripts" kube_manifest_dir: "{{ kube_config_dir }}/manifests" kube_cert_dir: "{{ kube_config_dir }}/ssl" kube_token_dir: "{{ kube_config_dir }}/tokens" kube_api_anonymous_auth: true kube_version: v1.19.5 local_release_dir: /tmp/releases retry_stagger: 5 kube_cert_group: kube-cert kube_log_level: 2 credentials_dir: "{{ inventory_dir }}/credentials" kube_oidc_auth: false kube_token_auth: true kube_network_plugin: flannel kube_network_plugin_multus: false kube_service_addresses: 10.233.128.0/18 kube_pods_subnet: 10.233.192.0/18 kube_network_node_prefix: 24 kube_apiserver_ip: "{{ kube_service_addresses|ipaddr('net')|ipaddr(1)|ipaddr('address') }}" kube_apiserver_port: 6443 kube_apiserver_insecure_port: 0 kube_proxy_mode: ipvs authorization_modes: ['Node', 'RBAC'] kube_proxy_strict_arp: false kube_encrypt_secret_data: false cluster_name: cluster.local ndots: 2 dns_mode: coredns enable_nodelocaldns: true nodelocaldns_ip: 169.254.25.10 nodelocaldns_health_port: 9254 enable_coredns_k8s_external: false enable_coredns_k8s_endpoint_pod_names: false resolvconf_mode: none deploy_netchecker: false skydns_server: "{{ kube_service_addresses|ipaddr('net')|ipaddr(3)|ipaddr('address') }}" skydns_server_secondary: "{{ kube_service_addresses|ipaddr('net')|ipaddr(4)|ipaddr('address') }}" dns_domain: "{{ cluster_name }}" container_manager: containerd kata_containers_enabled: false kubeadm_certificate_key: "{{ lookup('password', credentials_dir + '/kubeadm_certificate_key.creds length=64 chars=hexdigits') | lower }}" k8s_image_pull_policy: IfNotPresent kubernetes_audit: false dynamic_kubelet_configuration: false default_kubelet_config_dir: "{{ kube_config_dir }}/dynamic_kubelet_dir" dynamic_kubelet_configuration_dir: "{{ kubelet_config_dir | default(default_kubelet_config_dir) }}" podsecuritypolicy_enabled: true kubeconfig_localhost: true kubectl_localhost: false system_reserved: true system_memory_reserved: 128Mi system_cpu_reserved: 250m system_master_memory_reserved: 256Mi system_master_cpu_reserved: 250m volume_cross_zone_attachment: false persistent_volumes_enabled: false event_ttl_duration: 1h0m0s force_certificate_regeneration: false minimal_node_memory_mb: 896 kube_proxy_nodeport_addresses: >- {%- if kube_proxy_nodeport_addresses_cidr is defined -%} [{{ kube_proxy_nodeport_addresses_cidr }}] {%- else -%} [] {%- endif -%} EOF ``` Beaucoup de variables ci-dessus reprennent des défaults. Entre autre changements, on note: - le plugin réseau, “flannel“, et la définition des “kube_service_addresses” et “kube_pods_subnet” ou le “resolvconf_mode“. - Le runtime conteneur : “containerd“. - L’activation des PodSecurityPolicy. - Les “system_*” vont réserver un peu de CPU et mémoire pour l’OS des noeuds. - Le “minimal\_node\_memory” sera indispensable, lorsque certains noeuds ont moins d’1Gi de mémoire – les RPI 3b+ remontent 924Mi. Il sera possible de configurer le runtime conteneur du cluster, en créant un playbook tel que le suivant : ``` cat <inventory/rpi/group_vars/all/containerd.yaml containerd_config: grpc: max_recv_message_size: 16777216 max_send_message_size: 16777216 debug: level: "" registries: docker.io: "https://registry-1.docker.io" katello: "https://katello.vms.intra.example.com:5000" "registry.registry.svc.cluster.local:5000": "http://registry.registry.svc.cluster.local:5000" max_container_log_line_size: -1 metrics: address: "" grpc_histogram: false EOF ``` Kubespray permet le déploiement de diverses applications optionnelles : ``` cat <inventory/rpi/group_vars/k8s-cluster/addons.yaml helm_enabled: false registry_enabled: false metrics_server_enabled: true metrics_server_kubelet_insecure_tls: true metrics_server_metric_resolution: 60s metrics_server_kubelet_preferred_address_types: "InternalIP" local_path_provisioner_enabled: false local_volume_provisioner_enabled: false cephfs_provisioner_enabled: false rbd_provisioner_enabled: false ingress_nginx_enabled: true ingress_ambassador_enabled: false ingress_alb_enabled: false cert_manager_enabled: false metallb_enabled: false EOF ``` Dans l'exemple, on active **metrics\_server**, et l’**Ingress Controller Nginx**. #### Etape 3: Installer la clé SSH du maître Enfin, générer une clé SSH sur le noeud Ansible, et l’installer sur tous les noeuds composant le cluster : ``` ssh-keygen -t rsa -b 4096 -N '' -f ~/.ssh/id_rsa for i in pandore hellenes .... terpsichore thalia do ssh-copy-id -i ~/.ssh/id_rsa.pub root@$i.friends.intra.example.com; done ... ansible -m ping -i inventory/rpi/hosts.yaml all ``` #### Etape 4: Procéder au déploiement ``` ansible-playbook -i inventory/rpi/hosts.yaml cluster.yml 2>&&1 | tee -a deploy.$(date +%s).log ``` Si le déploiement échoue, on pourra corriger l'inventaire et relancer cette même commande, à moins que l’erreur ne se soit produite lorsqu’ansible lance l’initialisation du cluster Kubernetes (kubeadm init), auquel cas on devrait d’abord lancer le playbook de reset, pour réinitialiser les noeuds : ``` ansible-playbook -i inventory/rpi/hosts.yaml reset.yml ``` Si tout se passe bien, le déploiement ne prendra pas plus d’une trentaine de minutes. ``` ... PLAY RECAP ******************************************* calliope.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1 clio.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1 epimethee.friends.intra.example.com : ok=440 changed=67 unreachable=0 failed=0 skipped=864 rescued=0 ignored=1 erato.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1 euterpe.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1 hellenes.friends.intra.example.com : ok=442 changed=68 unreachable=0 failed=0 skipped=862 rescued=0 ignored=1 localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 melpomene.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1 pandore.friends.intra.example.com : ok=497 changed=86 unreachable=0 failed=0 skipped=924 rescued=0 ignored=1 pyrrha.friends.intra.example.com : ok=308 changed=30 unreachable=0 failed=0 skipped=604 rescued=0 ignored=1 polyhymnia.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1 terpsichore.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1 thalia.friends.intra.example.com : ok=285 changed=29 unreachable=0 failed=0 skipped=514 rescued=0 ignored=1 ``` ``` kubectl get nodes NAME STATUS ROLES AGE VERSION calliope.friends.intra.example.com Ready infra 7m14s v1.19.5 clio.friends.intra.example.com Ready infra 7m14s v1.19.5 epimethee.friends.intra.example.com Ready master 9m26s v1.19.5 erato.friends.intra.example.com Ready worker 7m14s v1.19.5 euterpe.friends.intra.example.com Ready worker 7m1s v1.19.5 hellenes.friends.intra.example.com Ready master 9m24s v1.19.5 melpomene.friends.intra.example.com Ready worker 7m1s v1.19.5 pandore.friends.intra.example.com Ready master 9m59s v1.19.5 pyrrha.friends.intra.example.com Ready infra 7m14s v1.19.5 polyhimnia.friends.intra.example.com Ready worker 7m1s v1.19.5 terpsichore.friends.intra.example.com Ready worker 7m13s v1.19.5 thalia.friends.intra.example.com Ready worker 7m v1.19.5 ``` A ce stade, on peut intégrer le cluster avec une solution de stockage externe – au plus simple, un serveur NFS. On reste limité par les modules kernels fournis avec Raspbian : pour s’interfacer avec avec un cluster Ceph, il faudra recompiler le kernel, ou s’intéresser à rbd-ndb.