Ubuntu 22 : Kubernetes master et nodes

Edit 06/10/23 : ajout de la metrics api

Bonjour,

Aujourd'hui, un billet portant sur la mise en place d'un cluster kubernetes avec 1 master et 2 nodes worker sous Ubuntu 22.04.
Ces VMs tournent sur un cluster proxmox hébergé sur des Lenovo m910q.

Partie commune au master et aux nodes

Common packages

On a besoin de quelques packages :

apt update
apt install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent

Renseignement du fichier hosts

Il faut renseigner le fichier avec les IPs des différents noeuds :

192.168.0.23 k8s-master.adm.securmail.fr
192.168.0.24 k8s-node1.adm.securmail.fr
192.168.0.25 k8s-node2.adm.securmail.fr

Mise en place des différents repositories

Clés gpg docker et kubernetes :

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /etc/apt/keyrings/kubernetes.gpg

Ajout des fichiers de repo :

echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/kubernetes.gpg] http://apt.kubernetes.io/ kubernetes-xenial main" | \
tee /etc/apt/sources.list.d/kubernetes.list > /dev/null

Paramètres spécifiques

Kernel 5.15 :
Ce kernel apporte une modification côté nf_conntrack, il faut donc en augmenter le nombre maxi (issue github : https://github.com/kubernetes-sigs/kind/issues/2240 ) sinon les pods nécessaires à kubernetes n'arrivent pas à se déployer correctement et ils restart en boucle.

Créer ce fichier /etc/sysctl.d/99-kube.conf :

net.netfilter.nf_conntrack_max = 524288

Désactivation du swap :

swapoff -a

Editer le fichier fstab pour désactiver le swap.

Vérifier que les modules br_netfilter et overlay sont bien chargés :

root@k8s-master:~# lsmod | grep -E 'br_netfilter|overlay'
br_netfilter           32768  0
bridge                307200  1 br_netfilter
overlay               151552  16

Si le module n'est pas chargé :

modprobe br_netfilter
sysctl net.bridge.bridge-nf-call-iptables=1

Docker installation

Installation de docker engine :

apt install docker-ce docker-ce-cli containerd.io

On supprime le fichier de conf de containerd livré par défaut et on le regénère :

rm /etc/containerd/config.toml
containerd config default > /etc/containerd/config.toml

Editer le fichier config.toml de containerd pour utiliser systemd:

version = 2
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
  SystemdCgroup = true

L'option est à false de base, puis restart :

systemctl restart containerd

Création du fichier de configuration des logs docker :

mkdir /etc/docker
cat <<EOF | tee /etc/docker/daemon.json
{ "exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts":
{ "max-size": "100m" },
"storage-driver": "overlay2"
}
EOF

On le restart :

systemctl restart docker

On vérifie le status de docker :

root@k8s-master:~# systemctl status docker
● docker.service - Docker Application Container Engine
     Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2023-08-31 15:18:04 CEST; 6s ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 5628 (dockerd)
      Tasks: 9
     Memory: 27.1M
        CPU: 293ms
     CGroup: /system.slice/docker.service
             └─5628 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

Aug 31 15:18:03 k8s-master.adm.securmail.fr systemd[1]: Starting Docker Application Container Engine...
Aug 31 15:18:03 k8s-master.adm.securmail.fr dockerd[5628]: time="2023-08-31T15:18:03.676910806+02:00" level=info msg="Starting up"
Aug 31 15:18:03 k8s-master.adm.securmail.fr dockerd[5628]: time="2023-08-31T15:18:03.677771093+02:00" level=info msg="detected 127.0.0.53 nameserver, assuming systemd-resolved, so using res>
Aug 31 15:18:03 k8s-master.adm.securmail.fr dockerd[5628]: time="2023-08-31T15:18:03.772179479+02:00" level=info msg="Loading containers: start."
Aug 31 15:18:04 k8s-master.adm.securmail.fr dockerd[5628]: time="2023-08-31T15:18:04.078420260+02:00" level=info msg="Loading containers: done."
Aug 31 15:18:04 k8s-master.adm.securmail.fr dockerd[5628]: time="2023-08-31T15:18:04.105389402+02:00" level=info msg="Docker daemon" commit=a61e2b4 graphdriver=overlay2 version=24.0.5
Aug 31 15:18:04 k8s-master.adm.securmail.fr dockerd[5628]: time="2023-08-31T15:18:04.105701369+02:00" level=info msg="Daemon has completed initialization"
Aug 31 15:18:04 k8s-master.adm.securmail.fr dockerd[5628]: time="2023-08-31T15:18:04.166018271+02:00" level=info msg="API listen on /run/docker.sock"
Aug 31 15:18:04 k8s-master.adm.securmail.fr systemd[1]: Started Docker Application Container Engine.

Kubernetes installation

Installer les composants kubernetes :

VERSION='1.27.0-00'
apt install -y kubelet=$VERSION kubeadm=$VERSION kubectl=$VERSION kubernetes-cni

Il est possible de figer la version des packages en utilisant apt :

root@k8s-master:~# apt-mark hold kubelet kubeadm kubectl kubernetes-cni
kubelet set on hold.
kubeadm set on hold.
kubectl set on hold.
kubernetes-cni set on hold.

Tour rapide des différentes cli :

  • kubeadm: permet de créer le cluster et d'ajouter des nodes.
  • kubelet: agent qui tourne sur chaques noeuds, qui permet notamment l'ajout et le démarrage des pods.
  • kubectl: permet d'intéragir avec le cluster.

Changement à chaud du nombre de nf_conntrack max :

root@k8s-node2:~# sysctl net.netfilter.nf_conntrack_max=524288
net.netfilter.nf_conntrack_max = 524288

On disable apparmor et on s'assure que docker est bien enable :

systemctl enable docker
systemctl enable kubelet
systemctl daemon-reload
systemctl stop apparmor
systemctl disable apparmor

UNIQUEMENT SUR LE MASTER

Init kubernetes conf sur le master :

kubeadm init --v=5 \
--upload-certs \
--control-plane-endpoint k8s-master.adm.securmail.fr:6443 \
--pod-network-cidr=10.244.0.0/16 \
--ignore-preflight-errors=NumCPU

Configuration de kubectl :

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

C'est valable également si vous voulez utilisez kubectl depuis un autre ordinateur, il suffit de créer le dossier .kube et d'y copier le fichier config.

On vérifie si tout est OK :

root@k8s-master:~# kubectl get nodes -o wide
NAME                          STATUS     ROLES           AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
k8s-master.adm.securmail.fr   NotReady   control-plane   46s   v1.27.0   192.168.0.23   <none>        Ubuntu 22.04.3 LTS   5.15.0-82-generic   containerd://1.6.2

Le status NotReady est normal car il manque un composant réseau.

Installation de Flannel qui est un network fabric basic :

kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
root@k8s-master:~# kubectl get pods -A -o wide
NAMESPACE      NAME                                                  READY   STATUS    RESTARTS   AGE   IP             NODE                          NOMINATED NODE   READINESS GATES
kube-flannel   kube-flannel-ds-gmmn4                                 1/1     Running   0          38s   192.168.0.23   k8s-master.adm.securmail.fr   <none>           <none>
kube-system    coredns-5d78c9869d-dfg4j                              1/1     Running   0          74s   10.244.0.35    k8s-master.adm.securmail.fr   <none>           <none>
kube-system    coredns-5d78c9869d-q6qjd                              1/1     Running   0          74s   10.244.0.34    k8s-master.adm.securmail.fr   <none>           <none>
kube-system    etcd-k8s-master.adm.securmail.fr                      1/1     Running   0          88s   192.168.0.23   k8s-master.adm.securmail.fr   <none>           <none>
kube-system    kube-apiserver-k8s-master.adm.securmail.fr            1/1     Running   0          88s   192.168.0.23   k8s-master.adm.securmail.fr   <none>           <none>
kube-system    kube-controller-manager-k8s-master.adm.securmail.fr   1/1     Running   0          88s   192.168.0.23   k8s-master.adm.securmail.fr   <none>           <none>
kube-system    kube-proxy-h2bn9                                      1/1     Running   0          74s   192.168.0.23   k8s-master.adm.securmail.fr   <none>           <none>
kube-system    kube-scheduler-k8s-master.adm.securmail.fr            1/1     Running   0          88s   192.168.0.23   k8s-master.adm.securmail.fr   <none>           <none>
root@k8s-master:~# kubectl get componentstatus
Warning: v1 ComponentStatus is deprecated in v1.19+
NAME                 STATUS    MESSAGE   ERROR
controller-manager   Healthy   ok        
scheduler            Healthy   ok        
etcd-0               Healthy   ok 

Installation de la metrics API afin de pouvoir avoir une visualisation de la consommation des ressources. Il faut pour ça déjà télécharger le yaml :

wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

Puis le modifier en ajoutant --kubelet-insecure-tls au bloc arg pour avoir ce résultat :

    spec:
      containers:
      - args:
        - --cert-dir=/tmp
        - --secure-port=4443
        - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
        - --kubelet-use-node-status-port
        - --metric-resolution=15s
        - --kubelet-insecure-tls

Ensuite, on peut faire l'installation :

root@k8s-master:~# kubectl apply -f components.yaml
serviceaccount/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
service/metrics-server created
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created

Le pod metrics est bien créé (il met quelques secondes à démarrer) :

root@k8s-master:~# kubectl get pods -n kube-system
NAME                                                  READY   STATUS    RESTARTS        AGE
coredns-5d78c9869d-dfg4j                              1/1     Running   2 (3d23h ago)   31d
coredns-5d78c9869d-q6qjd                              1/1     Running   2 (3d23h ago)   31d
etcd-k8s-master.adm.securmail.fr                      1/1     Running   2 (3d23h ago)   31d
kube-apiserver-k8s-master.adm.securmail.fr            1/1     Running   2 (3d23h ago)   31d
kube-controller-manager-k8s-master.adm.securmail.fr   1/1     Running   2 (3d23h ago)   31d
kube-proxy-bh789                                      1/1     Running   2 (3d23h ago)   31d
kube-proxy-h2bn9                                      1/1     Running   2 (3d23h ago)   31d
kube-proxy-srdbf                                      1/1     Running   2 (3d23h ago)   31d
kube-scheduler-k8s-master.adm.securmail.fr            1/1     Running   2 (3d23h ago)   31d
metrics-server-75f45b4dd4-qfxjr                       1/1     Running   0               116s
openebs-lvm-controller-0                              5/5     Running   2 (3d23h ago)   3d23h

On peut aller voir par exemple les ressources des différents nodes :

root@k8s-master:~# kubectl top nodes
NAME                          CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%   
k8s-master.adm.securmail.fr   82m          4%     2475Mi          64%       
k8s-node1.adm.securmail.fr    28m          1%     1769Mi          46%       
k8s-node2.adm.securmail.fr    29m          1%     1867Mi          48%  

AJOUTER UN NODE

Sur le master pour récupérer la commande d'ajout d'un node :

root@k8s-master:~# kubeadm token create --print-join-command
kubeadm join k8s-master.adm.securmail.fr:6443 --token boXXXXXX.8v16XXXXXXXXXXXX --discovery-token-ca-cert-hash sha256:7d06424XXXXXXXXXXXXXXXXXXXX

Sur le node à ajouter:

root@k8s-node1:~# kubeadm join k8s-master.adm.securmail.fr:6443 --token boXXXXXX.8v16XXXXXXXXXXXX --discovery-token-ca-cert-hash sha256:7d06424XXXXXXXXXXXXXXXXXXX

Pour vérifier, on peut vérifier la liste des nodes :

root@k8s-master:~# kubectl get nodes
NAME                          STATUS   ROLES           AGE   VERSION
k8s-master.adm.securmail.fr   Ready    control-plane   24h   v1.28.1
k8s-node1.adm.securmail.fr    Ready    <none>          23h   v1.28.1
k8s-node2.adm.securmail.fr    Ready    <none>          23h   v1.28.1

Le ready peut mettre quelques secondes à arriver.

Ajout d'un nginx juste pour le test :

root@k8s-master:~# kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
root@k8s-master:~# kubectl create service nodeport nginx --tcp=80:80
service/nginx created

Listing des services pour faire apparaitre celui de nginx :

root@k8s-master:~# kubectl get service
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP        24h
nginx        NodePort    10.101.35.44   <none>        80:32443/TCP   20s

Listing des pods :

root@k8s-master:~# kubectl get pods --all-namespaces -o wide
NAMESPACE      NAME                                                  READY   STATUS    RESTARTS       AGE     IP             NODE                          NOMINATED NODE   READINESS GATES
default        nginx-7854ff8877-kj8hp                                1/1     Running   0              3m14s   10.244.1.2     k8s-node1.adm.securmail.fr    <none>           <none>
kube-flannel   kube-flannel-ds-2l7j4                                 1/1     Running   17 (23h ago)   24h     192.168.0.23   k8s-master.adm.securmail.fr   <none>           <none>
kube-flannel   kube-flannel-ds-5mvbk                                 1/1     Running   0              23h     192.168.0.25   k8s-node2.adm.securmail.fr    <none>           <none>
kube-flannel   kube-flannel-ds-lmk69                                 1/1     Running   0              23h     192.168.0.24   k8s-node1.adm.securmail.fr    <none>           <none>
kube-system    coredns-5dd5756b68-tj8hw                              1/1     Running   15 (23h ago)   24h     10.244.0.32    k8s-master.adm.securmail.fr   <none>           <none>
kube-system    coredns-5dd5756b68-zzkxr                              1/1     Running   14 (23h ago)   24h     10.244.0.33    k8s-master.adm.securmail.fr   <none>           <none>
kube-system    etcd-k8s-master.adm.securmail.fr                      1/1     Running   22 (23h ago)   24h     192.168.0.23   k8s-master.adm.securmail.fr   <none>           <none>
kube-system    kube-apiserver-k8s-master.adm.securmail.fr            1/1     Running   22 (23h ago)   24h     192.168.0.23   k8s-master.adm.securmail.fr   <none>           <none>
kube-system    kube-controller-manager-k8s-master.adm.securmail.fr   1/1     Running   28 (23h ago)   24h     192.168.0.23   k8s-master.adm.securmail.fr   <none>           <none>
kube-system    kube-proxy-6v7tf                                      1/1     Running   18 (23h ago)   24h     192.168.0.23   k8s-master.adm.securmail.fr   <none>           <none>
kube-system    kube-proxy-bswgg                                      1/1     Running   0              23h     192.168.0.24   k8s-node1.adm.securmail.fr    <none>           <none>
kube-system    kube-proxy-hnwmv                                      1/1     Running   0              23h     192.168.0.25   k8s-node2.adm.securmail.fr    <none>           <none>
kube-system    kube-scheduler-k8s-master.adm.securmail.fr            1/1     Running   25 (23h ago)   24h     192.168.0.23   k8s-master.adm.securmail.fr   <none>           <none>

Le pod nginx apparait bien et le node sur lequel il est exécuté.

On vérifie l'accès (valable aussi depuis un navigateur) :

root@k8s-master:~# curl 192.168.0.23:32443
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Différentes erreurs

Erreur de CRI au join/init :

rm /etc/containerd/config.toml
containerd config default > /etc/containerd/config.toml
systemctl restart containerd

Si connection refused après avoir créé un service port :

kubectl patch node kubsworker -p '{"spec":{"podCIDR":"192.168.200.0/24"}}'

Si besoin de réinitialiser le cluster :

kubeadm reset

Normalement, vous avez un cluster kubernetes basic fonctionnel.

Have fun.