Travaillant avec Kubernetes depuis plusieurs années, j’ai l’habitude d’utiliser des clusters en mode managé comme GKE, AKS ou Kapsule. Je me suis souvent posé la question de l’effort à fournir pour créer et surtout gérer au quotidien mon propre cluster Kubernetes.

Appréciant transmettre ma passion autour des conteneurs et de Kubernetes, je vais essayer de partager mon expérience vers cet objectif. Les choix lors de la mise en place sont liés à mon expérience et sont mon point de vue à cet instant. Ma vision peut, et va sûrement, évoluer avec le temps.

Installer son cluster

Rappel : composition d’un cluster

Un cluster Kubernetes est constitué de 2 composants majeurs :

  • les masters, qui composent le plan de contrôle (control-plane)
  • les nodes, où sont exécutes les charges de travail ou conteneurs

Dans un cluster managé, les masters sont entièrement gérés par le fournisseur du service (Google, Azure, AWS, Scaleway, etc.)

Vous pouvez trouver plus de détails sur les composants de Kubernetes dans la documentation officielle

Méthodes d’installation

Il existe plusieurs façons d’installer son cluster, notamment à l’aide d’outils tiers. Avant d’écrire cet article, les méthodes que je connaissais étaient :

Une section de la documentation de Kubernetes en recense un certains nombre selon votre besoin.

Le Landspace de la CNCF repertorie également des installateurs certifiés.

Mon choix

L’outil que je vais utiliser pour gérer mon cluster est kubeadm.

Voici quelques raisons qui m’ont amenés vers ce choix :

  • il est simple d’utilisation
  • il permet de gèrer certains opérations du cycle de vie d’un cluster : notamment la mise à jour des composants du cluster
  • il permet d’obtenir un cluster Kubernetes passant les tests de conformité 1
  • il installe le minimum de fonctionnalitées : cela peut être un inconvénient pour certains, dans mon cas j’ai choisi d’installer et de comprendre les composants nécessaires au bon fonctionnement de Kubernetes sans “extensions” superflues
  • les composants des masters sont exécutés entant que pods

Installation des masters

kubeadm offre la possibilité d’installer un cluster en mode HA (Haute Disponibilité). Dans un premier temps, je vais installer un cluster avec un noeud master unique, par question de simplicité.

Pré-requis

La documentation de kubeadm indique les pré-requis nécessaires à l’installation des masters.

Voici les caractéristiques de mon master :

  • Système d’exploitation : Ubuntu 20.04 LTS
  • CPU : 3
  • Mémoire : 4 Go

Container Runtime

kubeadm installe les composants du master (etcd, api-server, kube-scheduler, kube-controller-manager) sous la forme de conteneurs. Il est donc nécessaire d’installer un container runtime pour exécuter ces derniers.

Docker est le plus souvent utilisé, mais je préfère utiliser containerd. J’expliquerais plus en détail ce choix, dans un autre article.

Pré-requis

Il est nécessaire de configurer certains paramètres du système afin d’utiliser containerd entant que CRI2

Voici les instructions à effectuer :

cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables  = 1
net.ipv4.ip_forward                 = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

sudo sysctl --system

Installation de containerd

Nous allons installer la version CRI3 de containerd. Cette dernière contient d’autres dépendances comme crictl ou runc.

cd /tmp

CTD_VER=1.4.1
wget -q https://github.com/containerd/containerd/releases/download/v${CTD_VER}/cri-containerd-cni-${CTD_VER}-linux-amd64.tar.gz
curl -s -L https://github.com/containerd/containerd/releases/download/v${CTD_VER}/cri-containerd-cni-${CTD_VER}-linux-amd64.tar.gz.sha256sum | sha256sum -c

sudo tar --no-overwrite-dir -C / -xzf cri-containerd-cni-${CTD_VER}-linux-amd64.tar.gz
rm /etc/cni/net.d/10-containerd-net.conflist
rm -r /opt/containerd
rm /tmp/cri-containerd-cni-${CTD_VER}-linux-amd64.tar.gz

sudo systemctl start containerd
sudo systemctl enable containerd

Pour vérifier si containerd est fonctionnel il suffit d’executer la commande crictl version qui retourne un résultat similaire à :

Version:  0.1.0
RuntimeName:  containerd
RuntimeVersion:  v1.4.1
RuntimeApiVersion:  v1alpha2

Kubeadm, kubelet et kubectl

L’étape suivante est d’installer kubeadm, kubelet et kubectl.

Ces 2 derniers seront respectivement utilisés

  • pour gérer les pods des composants formant le control-plane
  • pour communiquer avec votre cluster Kubernetes

Nous allons les installer depuis le dépôt APT de Kubernetes

apt-get update && apt-get install -y apt-transport-https curl

curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF

apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl

Pour le moment le service kubelet ne démarre pas car le fichier de configuration de ce dernier (/var/lib/kubelet/config.yaml) n’est pas encore présent.

Initialisation du cluster

Pour initialiser le cluster il suffit d’exécuter la commande suivante

kubeadm init

Accès au cluster

Il suffit ensuite de copier le fichier de configuration pour accéder au cluster

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

La commande kubectl get nodes vous permet de lister les noeuds du cluster :

NAME      STATUS     ROLES    AGE   VERSION
k8s-m01   NotReady   master   7s    v1.19.4

Comme on peut le voir le status du master est NotReady.

Les logs de kubelet ou un kubectl describe sur le node en question nous permettent d’en connaître la raison :

runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized

En effet, il est nécessaire d’installer un add-on réseau (avec kubeadm il est nécessaire d’utiliser un réseau basé sur un CNI) afin que les pods puissent obtenir des adresses IP et communiquer entre eux.

Ce point est abordé dans l’article Mise en place du plugin CNI.

Points intéressants

Pour finir cet article, revenons un instant sur quelques points intéressants de l’intialisation du control-plane par kubeadm.

En listant les pods du cluster, on remarque plusieurs choses :

  1. des pods en Pending (dans notre exemple il s’agît des instances de coredns)
  2. des pods ayant comme suffixe le nom d’hôte du master (ici k8s-m01)
  3. des pods en Running malgré un status NotReady sur le master
NAMESPACE     NAME                              READY   STATUS    RESTARTS   AGE
kube-system   coredns-f9fd979d6-vjwbq           0/1     Pending   0          2m23s
kube-system   coredns-f9fd979d6-z5tlg           0/1     Pending   0          2m23s
kube-system   etcd-k8s-m01                      1/1     Running   0          2m17s
kube-system   kube-apiserver-k8s-m01            1/1     Running   0          2m17s
kube-system   kube-controller-manager-k8s-m01   1/1     Running   0          2m17s
kube-system   kube-proxy-5qh59                  1/1     Running   0          2m23s
kube-system   kube-scheduler-k8s-m01            1/1     Running   0          2m17s

Pods en Pending

Un describe sur ces derniers nous permet d’en connaître la raison : ils sont dans l’attente d’un noeud avec le statut Ready (et donc du CNI)

FailedScheduling  default-scheduler  0/1 nodes are available: 1 node(s) had taint {node.kubernetes.io/not-ready: }, that the pod didn't tolerate.

Static pods

Les pods ayant comme suffixe -k8s-m01 sont des Static Pods4.

En effet, il est possible de définir des pods locaux à un noeud. Ces derniers sont gérés par le kubelet et non par l’API Kubernetes.

Dans la configuration du kubelet le paramètre staticPodPath indique le dossier contenant les manifestes des Static Pods.

$ grep staticPodPath /var/lib/kubelet/config.yaml
staticPodPath: /etc/kubernetes/manifests

Dans ce dossier on retrouve bien 4 fichiers correspondants aux pods ayant le suffixe -k8s-m01

$ ls -l /etc/kubernetes/manifests/

-rw------- 1 root root 2081 Dec  9 11:58 etcd.yaml
-rw------- 1 root root 3808 Dec  9 11:58 kube-apiserver.yaml
-rw------- 1 root root 3350 Dec  9 11:58 kube-controller-manager.yaml
-rw------- 1 root root 1384 Dec  9 11:58 kube-scheduler.yaml

Les Static Pods sont :

  • listés par kubectl
  • suffixés par le nom d’hôte où ils sont exécutés

Mais il sera impossible d’effectuer une action sur eux via l’API de Kubernetes

  • les Admissions Controllers n’auront donc aucuns effets sur ces derniers, car ils sont initiés avant par le kubelet
  • essayez un kubectl delete sur un Static Pod 😉

Pods en Running

Malgré le fait que le seul noeud du cluster (k8s-m01) soit en NotReady certains pods sont fonctionnels…
Mais pourquoi ? Quel est donc la différence entres les pods coredns et les autres ?

Examinons les pods en affichant plus d’informations avec kubectl get pods -A -o wide

NAMESPACE     NAME                              READY   STATUS    RESTARTS   AGE   IP            NODE      NOMINATED NODE   READINESS GATES
kube-system   coredns-f9fd979d6-6gp6h           0/1     Pending   0          81s   <none>        <none>    <none>           <none>
kube-system   coredns-f9fd979d6-bpfg9           0/1     Pending   0          81s   <none>        <none>    <none>           <none>
kube-system   etcd-k8s-m01                      0/1     Running   0          76s   10.64.246.3   k8s-m01   <none>           <none>
kube-system   kube-apiserver-k8s-m01            1/1     Running   0          76s   10.64.246.3   k8s-m01   <none>           <none>
kube-system   kube-controller-manager-k8s-m01   1/1     Running   0          76s   10.64.246.3   k8s-m01   <none>           <none>
kube-system   kube-proxy-2gjv6                  1/1     Running   0          81s   10.64.246.3   k8s-m01   <none>           <none>
kube-system   kube-scheduler-k8s-m01            1/1     Running   0          75s   10.64.246.3   k8s-m01   <none>           <none>

Les pods en Running ont une adresse IP alors qu’il n’y a pas encore de CNI… 🤔

Examinons alors les noeuds en affichant également plus d’informations avec kubectl get nodes -o wide

NAME      STATUS     ROLES    AGE    VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE           KERNEL-VERSION   CONTAINER-RUNTIME
k8s-m01   NotReady   master   106s   v1.19.4   10.64.246.3   <none>        Ubuntu 20.04 LTS   5.4.0-1018-kvm   containerd://1.4.1

L’adresse IP des pods correspond à celle de k8s-m01.

Il existe un cas où un pod peut exister sans CNI : quand l’option hostNetwork est à true dans la définition de ce dernier.

Vérifions la valeur de cette dernière pour les Static Pods

$ grep hostNetwork /etc/kubernetes/manifests/*
etcd.yaml:  hostNetwork: true
kube-apiserver.yaml:  hostNetwork: true
kube-controller-manager.yaml:  hostNetwork: true
kube-scheduler.yaml:  hostNetwork: true

Pour kube-proxy

$ kubectl get daemonset kube-proxy -n kube-system -oyaml | grep hostNetwork
      hostNetwork: true

Et pour coredns

$ kubetcl get deploy coredns -n kube-system -oyaml | grep hostNetwork

Ressources


  1. Introducing Software Certification for Kubernetes ↩︎

  2. Container runtimes - containerd ↩︎

  3. Install Containerd with Release Tarball ↩︎

  4. Create static Pods ↩︎