Tutoriel : Comment installer Kubernetes avec kubeadm ?

Publié le

Dans un précédant article, nous avions vu que kubeadm est une solutions de choix pour installer un cluster Kubernetes.

Nous allons mettre en œuvre cet outil pour installer un cluster en local dans des machines virtuelles. Il est tout à fait possible de faire la même chose chez un provider cloud.

Création du labo

Comme dans le tutoriel pour installer Kubernetes avec Rke, nous allons utiliser Vagrant pour créer notre environnement de test en local. Les instructions sont exactement les mêmes sur cette partie, mise à part le Vagrantfile qui est légèrement différent.

Article connexe : Comment créer un labo d’admin DevOps en local avec Vagrant ?

On commence par créer le fichier Vagrantfile avec le contenu suivant :

NODE_COUNT = 2
IMAGE = "ubuntu/focal64"

Vagrant.configure("2") do |config|
  config.vm.define "control-plane" do |server|
    server.vm.box = IMAGE
    server.vm.hostname = "control-plane"

    server.vm.provider "virtualbox" do |node|
      node.gui = false
      node.memory = "2048"
    end
    server.vm.network :private_network, ip: "10.0.0.10"
  end

  (1..NODE_COUNT).each do |server_index|
    config.vm.define "worker-#{server_index}" do |node|
      node.vm.box = IMAGE
      node.vm.hostname = "worker-#{server_index}"
      node.vm.network :private_network, ip: "10.0.0.#{server_index + 10}"

      node.vm.provider "virtualbox" do |node|
        node.gui = false
        node.memory = "2048"
      end
    end
  end
end

Grâce à ce fichier, nous définissons 3 machines virtuelles qui vont être déployées dans VirtualBox (le provider par défaut de Vagrant) :

  • 1 machine de control-plane (qui va héberger etcd, et l'api-server de Kubernetes). Cette machine s'appelle « control-plane » et a l'adresse IP 10.0.0.10.
  • 2 machines destinées à exécuter les tâches via Docker, elles s'appellent « worker-1 » et « worker-2 » et ont respectivement les adresse IP 10.0.0.11 et 10.0.0.12. On peut changer le nombre de workers en modifiant la variable NODE_COUNT à la première ligne.

Toutes ces machines virtuelles fonctionnent sous ubuntu focal (20.04). On définit 2Go de mémoire vive pour chacunes, kubeadm ne fonctionnera pas sans ce minimum.

Une fois le Vagrantfile créé, il ne reste plus qu'à créer les machines virtuelles en lançant la commande vagrant up.

Choix et installation du CRI

Contrairement à l'installation de Kubernetes via rke, avec kubeadm on a le choix du runtime qui va gérer les containers. Ce runtime s'appelle une CRI (Container Runtime Interface). Historiquement, Kubernetes s'appuyait sur Docker pour exécuter les containers. Le soucis avec Docker, c'est que c'est un outil générique qui fait bien plus que de simplement executer des containers. Actuellement on a le choix de runtimes plus légers. Les principales alternatives à Docker sont  :

  • containerd, qui est en réalité un composant utilisé par Docker
  • CRI-O, qui est un projet soutenu par Redhat.

Dans ce tutoriel j'ai fait le choix d'installer CRI-O.

Installation des paquets pour Kubernetes et kubeadm

On a quelques paquets à installer avant de pouvoir procéder à la mise en place du cluster par kubeadm.

On se connecte au serveur de control-plane vagrant ssh control-plane, puis on execute les commandes suivantes :

Mise à jour du système et installation de prérequis :

# On commence par mettre à jour le système
sudo apt-get update
sudo apt-get upgrade -y

# On installe quelques paquets nécessaire pour ajouter des dépôts APT.
sudo apt-get install -y dialog apt-utils curl gnupg2 software-properties-common apt-transport-https ca-certificates

Installation de CRI-O:

# Ajustement des paramètres pour iptables et CRI-O
# à voir :
# - https://kubernetes.io/docs/setup/production-environment/container-runtimes/#cri-o
# - https://kubernetes.io/fr/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#permettre-à-iptables-de-voir-le-trafic-ponté

cat <<EOF | sudo tee /etc/modules-load.d/cri-o.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-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF

sudo sysctl --system

# On ajoute les dépôts pour CRI-O
curl -s https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_20.04/Release.key | sudo apt-key add -
curl -s https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/1.20/xUbuntu_20.04/Release.key | sudo apt-key add -
sudo apt-add-repository "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_20.04 /"
sudo apt-add-repository "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/1.20/xUbuntu_20.04/ /"

# On installe CRI-O
sudo apt-get install -y cri-o cri-o-runc cri-tools cri-o-runc runc

# On supprime la configuration "bridge" installée avec CRI-O,
# Celle-ci rentre en conflit avec les paramètres mis en place par la 
# CNI qu'on installera plus tard.
sudo rm /etc/cni/net.d/100-crio-bridge.conf

# Finalement on démarre le service CRI-O
sudo systemctl start crio
sudo systemctl enable crio

Installation des composants de Kubernetes et de kubeadm :

# On ajoute les dépôts pour Kubernetes et kubeadm
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
sudo apt-add-repository "deb https://apt.kubernetes.io/ kubernetes-xenial main"

# On installe les composants de Kubernetes et Kubeadm
sudo apt-get install -y kubectl=1.20.1-00 kubeadm=1.20.1-00 kubelet=1.20.1-00

# On met en place la configuration nécessaire pour le kubelet :
# - le cgroup-drive (systemd)
# - la socket pour accéder à la CRI
#
# Attention: il faut bien sûr adapter la valeur de node-ip:
# - 10.0.0.10 sur control-plane
# - 10.0.0.11 sur worker-1
# - 10.0.0.12 sur worker-2
cat <<EOF | sudo tee /etc/default/kubelet
KUBELET_EXTRA_ARGS=--node-ip=10.0.0.1x --cgroup-driver=systemd --container-runtime=remote --container-runtime-endpoint="unix:///var/run/crio/crio.sock"
EOF

On répète l'opération sur les workers avec vagrant ssh worker-1 et vagrant ssh worker-2. Dans la dernière commande, il faut absolument spécifier l'adresse IP de communication des nœuds. En effet dans chaque VM comme chez beaucoup de cloud-providers, il y a plusieurs interfaces réseau : une publique, et une privée. La communication doit se faire par le réseau privé, tel qu'il est définit dans le Vagrantfile. Sans cette option, le kubelet tente d'utiliser l'interface par défaut.

Les prérequis sont maintenant installés. Il n'y a plus qu'a mettre en place le cluster avec kubeadm.

Mise en place du cluster avec kubeadm : le control-plane

On se connecte au serveur de control-plane vagrant ssh control-plane, et on execute les commandes suivantes :

# On met en place la configuration pour kubeadm. 
# Celle-ci permet de définir :
# - L'adresse IP sur laquelle on "bind" l'API server.
# - La partie "ClusterConfiguration" nous permet de définir les
# sous-réseaux pour dans lesquels seront attribuées des adresses IP
# pour les Pods et les Services.
# - Le cgroup driver (systemd)
#
# Il existe beaucoup d'autres options mais nous n'en avons pas l'utilité
# ici.
#
cat <<EOF | sudo tee /etc/kubernetes/kubeadmcfg.yaml
apiVersion: kubeadm.k8s.io/v1beta2
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: 10.0.0.10
  bindPort: 6443
---
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: 1.20.1
networking:
  podSubnet: "10.244.0.0/16"
  serviceSubnet: "10.245.0.0/16"
  dnsDomain: "cluster.internal"
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF

# On initialise le control-plane
sudo kubeadm init --config /etc/kubernetes/kubeadmcfg.yaml --upload-certs

Rapidement, on obtient ce type résultat :

...

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

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

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 10.0.0.10:6443 --token zby90x.e9k3m62hb1trecxn \
    --discovery-token-ca-cert-hash sha256:ffa095299c01cd12f24d89e9e7d705511690d695a44a2240285adfd2729645c7

La commande qui commence par "kubeadm join" est importante (les dernières lignes). C'est la commande a executer sur les nœuds du cluster pour rejoindre le control-plane. Dans mon cas c'est :

kubeadm join 10.0.0.10:6443 --token zby90x.e9k3m62hb1trecxn \
    --discovery-token-ca-cert-hash sha256:ffa095299c01cd12f24d89e9e7d705511690d695a44a2240285adfd2729645c7

On peut executer les commandes suggérées 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

Testons :

vagrant@control-plane:~$ kubectl get nodes
NAME            STATUS     ROLES                  AGE     VERSION
control-plane   NotReady   control-plane,master   12s   v1.20.1

La configuration est correcte dans notre cas. On remarque que le control-plane est en status "NotReady". C'est dû au fait que la CNI n'est pas installée.

Installation des nœuds de travail

Sur chacun des workers (accès par vagrant ssh worker-1 et vagrant ssh worker-2), on execute la commande que nous avons mis de côté lors de l'initialisation du control-plane :

kubeadm join 10.0.0.10:6443 --token zby90x.e9k3m62hb1trecxn \
    --discovery-token-ca-cert-hash sha256:ffa095299c01cd12f24d89e9e7d705511690d695a44a2240285adfd2729645c7

Très rapidement, la mise en place des workers est effective :

[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:

  • Certificate signing request was sent to apiserver and a response was received.
  • The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

Si on exécute kubectl get nodes sur le control-plane, on obtient ce résultat :

vagrant@control-plane:~$ kubectl get nodes -o wide
NAME            STATUS     ROLES                  AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
control-plane   NotReady   control-plane,master   61s   v1.20.1   10.0.2.15     <none>        Ubuntu 20.04.1 LTS   5.4.0-59-generic   cri-o://1.20.0
worker-1        NotReady   <none>                 9s    v1.20.1   10.0.2.15     <none>        Ubuntu 20.04.1 LTS   5.4.0-59-generic   cri-o://1.20.0
worker-2        NotReady   <none>                 8s    v1.20.1   10.0.2.15     <none>        Ubuntu 20.04.1 LTS   5.4.0-59-generic   cri-o://1.20.0

Le cluster est presque prêt. Les nœuds sont tous en status "NotReady". Pour finaliser le déploiement du cluster, il reste à installer la CNI.

Installation de la CNI : Calico

On ne va pas rentrer dans les détails dans cet article, mais pour faire simple, un cluster Kubernetes a besoin d'un réseau interne spécifique pour permettre la communication entre les Pods et les Services du cluster. Ici je fais le choix d'utiliser Calico. C'est une des solutions les plus utilisées. Il faut savoir qu'il en existe bien d'autres. J'ai l'habitude d'installer la CNI avant de mettre en place les workers (directement après avoir finit l'installation du control-plane).

L'installation de Calico avec les paramètres par défaut est assez simple :

# On récupère les manifests de déploiement :
curl https://docs.projectcalico.org/manifests/calico.yaml -o calico.yaml

# On exécute les déploiements :
kubectl apply -f calico.yaml

kubectl nous répond normalement avec ceci :

vagrant@control-plane:~$ kubectl apply -f calico.yaml
configmap/calico-config created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/kubecontrollersconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networksets.crd.projectcalico.org created
clusterrole.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrolebinding.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrole.rbac.authorization.k8s.io/calico-node created
clusterrolebinding.rbac.authorization.k8s.io/calico-node created
daemonset.apps/calico-node created
serviceaccount/calico-node created
deployment.apps/calico-kube-controllers created
serviceaccount/calico-kube-controllers created
poddisruptionbudget.policy/calico-kube-controllers created

On ne remarque pas d'erreur dans cette sortie.

Vérification de l'installation

Rapidement les nœuds de notre cluster passent à l'état "Ready", kubectl get nodes -o wide :

vagrant@control-plane:~$ kubectl get nodes -o wide
NAME            STATUS   ROLES                  AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
control-plane   Ready    control-plane,master   12m   v1.20.1   10.0.2.15     <none>        Ubuntu 20.04.1 LTS   5.4.0-59-generic   cri-o://1.20.0
worker-1        Ready    <none>                 11m   v1.20.1   10.0.2.15     <none>        Ubuntu 20.04.1 LTS   5.4.0-59-generic   cri-o://1.20.0
worker-2        Ready    <none>                 11m   v1.20.1   10.0.2.15     <none>        Ubuntu 20.04.1 LTS   5.4.0-59-generic   cri-o://1.20.0

On vérifie les déploiements. kubectl get pods -o wide --all-namespaces : tous les Pods sont en statut Running, il n'y a pas d'erreurs. Notre cluster est prêt.

vagrant@control-plane:~$ kubectl get pods -o wide --all-namespaces
NAMESPACE     NAME                                       READY   STATUS    RESTARTS   AGE    IP               NODE            NOMINATED NODE   READINESS GATES
kube-system   calico-kube-controllers-744cfdf676-r49g5   1/1     Running   0          54s    10.244.226.65    worker-1        <none>           <none>
kube-system   calico-node-655w9                          1/1     Running   0          54s    10.0.2.15        worker-2        <none>           <none>
kube-system   calico-node-7h92p                          1/1     Running   0          54s    10.0.2.15        worker-1        <none>           <none>
kube-system   calico-node-nlt82                          1/1     Running   0          54s    10.0.2.15        control-plane   <none>           <none>
kube-system   coredns-74ff55c5b-cmxrs                    1/1     Running   0          105s   10.244.235.130   control-plane   <none>           <none>
kube-system   coredns-74ff55c5b-vg6kg                    1/1     Running   0          104s   10.244.235.129   control-plane   <none>           <none>
kube-system   etcd-control-plane                         1/1     Running   0          114s   10.0.2.15        control-plane   <none>           <none>
kube-system   kube-apiserver-control-plane               1/1     Running   0          114s   10.0.2.15        control-plane   <none>           <none>
kube-system   kube-controller-manager-control-plane      1/1     Running   0          114s   10.0.2.15        control-plane   <none>           <none>
kube-system   kube-proxy-2pskc                           1/1     Running   0          94s    10.0.2.15        worker-2        <none>           <none>
kube-system   kube-proxy-tkn8d                           1/1     Running   0          94s    10.0.2.15        worker-1        <none>           <none>
kube-system   kube-proxy-wjqkn                           1/1     Running   0          105s   10.0.2.15        control-plane   <none>           <none>
kube-system   kube-scheduler-control-plane               1/1     Running   0          114s   10.0.2.15        control-plane   <none>           <none>

Destruction du labo

On peut détruire complètement le labo avec la commande vagrant destroy.

Conclusion

Installer Kubernetes via kubeadm nécessite de lancer beaucoup plus de commande qu'avec rke de Rancher. Ici nous nous sommes concentré sur une installation assez basique. Nous n'avons pas abordé les nombreux détails de paramètrage, mais il faut savoir que kubeadm permet beaucoup plus de customisation dans l'installation d'un cluster.

Comme dans le tutoriel sur rke, la prochaine étape dans notre process d'installation de Kubernetes serait d'automatiser le processus en utilisant un outil d'IaC (Infrastructure as Code). Cela fera aussi l'objet d'un prochain article montrant comment faire,  avec Terraform d'Hashicorp.

PHILIPPE CHEPY

Administrateur Système et Développeur