Compléter Kubernetes à partir de zéro sur Raspberry Pi





Plus récemment, une entreprise bien connue a annoncé qu'elle transférait sa gamme d'ordinateurs portables vers l'architecture ARM. En entendant cette nouvelle, je me suis souvenu: en regardant à nouveau les prix de EC2 dans AWS, j'ai remarqué des Gravitons avec un prix très savoureux. Le hic, bien sûr, c'est que c'est ARM. Il ne m'est même pas venu à l'esprit que ARM était assez sérieux ...



Pour moi, cette architecture a toujours été le lot du mobile et d'autres choses IoT. Les "vrais" serveurs sur ARM sont en quelque sorte inhabituels, à certains égards même sauvages ... Cependant, une nouvelle pensée m'est restée en tête, alors un week-end j'ai décidé de vérifier ce qui pouvait être lancé sur ARM aujourd'hui. Et pour cela, j'ai décidé de commencer par un cluster proche et cher - un cluster Kubernetes. Et pas seulement un "cluster" conditionnel, mais tout "à la manière d'un adulte" pour que ce soit autant que possible le même que j'ai l'habitude de le voir en production.



Selon mon idée, le cluster devrait être accessible depuis Internet, certaines applications Web devraient y fonctionner et il devrait y avoir au moins une surveillance. Pour mettre en œuvre cette idée, vous aurez besoin d'une paire (ou plus) de Raspberry Pi modèle 3B + ou supérieur. AWS pourrait également devenir une plate-forme d'expérimentation, mais ce sont les «framboises» qui m'intéressaient (qui restaient inactives). Nous allons donc déployer un cluster Kubernetes avec Ingress, Prometheus et Grafana sur eux.



Préparation de "framboises"



Installation du système d'exploitation et SSH



Je ne me suis pas beaucoup soucié du choix du système d'exploitation pour l'installation: je viens de prendre le dernier Raspberry Pi OS Lite sur le site officiel . La documentation d'installation y est également disponible , toutes les étapes à partir desquelles doivent être effectuées sur tous les nœuds du futur cluster. Ensuite, vous devez effectuer les manipulations suivantes (également sur tous les nœuds).



Après avoir connecté le moniteur et le clavier, vous devez d'abord configurer le réseau et SSH:



  1. Pour que le cluster fonctionne, le maître doit avoir une adresse IP statique et les nœuds de travail doivent avoir une adresse IP statique. J'ai préféré des adresses statiques partout pour faciliter la configuration.
  2. Une adresse statique peut être configurée dans le système d'exploitation ( /etc/dhcpcd.confil y a un exemple approprié dans le fichier ) ou en fixant le bail dans le serveur DHCP du routeur utilisé (dans mon cas, domestique).
  3. ssh-server est juste inclus dans raspi-config ( options d'interfaçage -> ssh ).


Après cela, vous pouvez déjà vous connecter via SSH (par défaut, la connexion est pi, et le mot de passe est raspberrycelui que vous avez changé) et continuer les paramètres.



Autres réglages



  1. Définissons le nom d'hôte. Dans mon exemple, pi-controlet sera utilisé pi-worker.
  2. Vérifions que le système de fichiers est étendu à l'ensemble du disque ( df -h /). Il peut être étendu si nécessaire en utilisant raspi-config.
  3. Modifiez le mot de passe utilisateur par défaut dans raspi-config.
  4. Désactivez le fichier d'échange (c'est l'exigence de Kubernetes; si vous êtes intéressé par les détails sur ce sujet, consultez le numéro 53533 ):



    dphys-swapfile swapoff
    systemctl disable dphys-swapfile
  5. Mettons à jour les packages vers les dernières versions:



    apt-get update && apt-get dist-upgrade -y
  6. Installez Docker et des packages supplémentaires:



    apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistent


    Lors de l'installation, vous iptables-persistentdevrez enregistrer les paramètres iptables pour ipv4 et /etc/iptables/rules.v4ajouter les règles à la chaîne dans le fichier FORWARD, comme ceci:



    # Generated by xtables-save v1.8.2 on Sun Jul 19 00:27:43 2020
    *filter
    :INPUT ACCEPT [0:0]
    :FORWARD ACCEPT [0:0]
    :OUTPUT ACCEPT [0:0]
    -A FORWARD -s 10.1.0.0/16  -j ACCEPT
    -A FORWARD -d 10.1.0.0/16  -j ACCEPT
    COMMIT
  7. Il ne reste plus qu'à redémarrer.


Vous êtes maintenant prêt à installer votre cluster Kubernetes.



Installer Kubernetes



À ce stade, j'ai délibérément reporté tous mes développements et ceux de notre entreprise sur l'automatisation de l'installation et de la configuration du cluster K8s. À la place, nous utiliserons la documentation officielle de kubernetes.io (légèrement augmentée avec des commentaires et des abréviations).



Ajoutez le référentiel Kubernetes:



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


Plus loin dans la documentation, il est proposé d'installer CRI (interface d'exécution du conteneur). Puisque Docker est déjà installé, passons à autre chose et installons les principaux composants:



sudo apt-get install -y kubelet kubeadm kubectl kubernetes-cni


Lors de l'installation des principaux composants, j'ai immédiatement ajouté kubernetes-cnice qui est nécessaire pour que le cluster fonctionne. Et ici, il y a un point important: kubernetes-cnipour une raison quelconque, le package ne crée pas de répertoire par défaut pour les paramètres de l'interface CNI, j'ai donc dû le créer manuellement:



mkdir -p /etc/cni/net.d


Pour que le backend réseau fonctionne, ce qui sera discuté ci-dessous, vous devez installer le plugin pour CNI. J'ai choisi le plugin portmap, qui m'est familier et clair (voir la documentation pour une liste complète ):



curl -sL https://github.com/containernetworking/plugins/releases/download/v0.7.5/cni-plugins-arm-v0.7.5.tgz | tar zxvf - -C /opt/cni/bin/ ./portmap


Configurer Kubernetes



Noeud du plan de contrôle



La configuration du cluster lui-même est assez simple. Et pour accélérer ce processus et vérifier que les images Kubernetes sont disponibles, vous pouvez d'abord exécuter:



kubeadm config images pull


Maintenant, nous effectuons l'installation elle-même - nous initialisons le plan de contrôle du cluster:



kubeadm init --pod-network-cidr=10.1.0.0/16 --service-cidr=10.2.0.0/16 --upload-certs


Veuillez noter que les sous-réseaux pour les services et les pods ne doivent pas se chevaucher les uns avec les autres ou avec les réseaux existants.



À la fin, on nous montrera un message indiquant que tout va bien, et en même temps, ils vous diront comment attacher des nœuds de travail au plan de contrôle:



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
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/
You can now join any number of the control-plane node running the following command on each as root:
 kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4 \
   --discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050 \
   --contrl-plane --certificate-key 72a3c0a14c627d6d7fdade1f4c8d7a41b0fac31b1faf0d8fdf9678d74d7d2403
Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4 \
   --discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050


Suivons les recommandations pour ajouter une configuration pour l'utilisateur. Et en même temps, je recommande d'ajouter immédiatement la complétion automatique pour kubectl:



 kubectl completion bash > ~/.kube/completion.bash.inc
 printf "
 # Kubectl shell completion
 source '$HOME/.kube/completion.bash.inc'
 " >> $HOME/.bash_profile
 source $HOME/.bash_profile


À ce stade, vous pouvez déjà voir le premier nœud du cluster (bien qu'il ne soit pas encore prêt):



root@pi-control:~# kubectl get no
NAME         STATUS     ROLES    AGE   VERSION
pi-control   NotReady   master   29s   v1.18.6


Configuration du réseau



De plus, comme indiqué dans le message après l'installation, vous devrez installer le réseau dans le cluster. La documentation propose un choix de Calico, Cilium, contiv-vpp, Kube-router et Weave Net ... Ici j'ai dévié des instructions officielles et choisi une option plus familière et compréhensible pour moi: la flanelle en mode host-gw (pour plus d'informations sur les backends disponibles, voir la documentation projet ).



L'installer dans un cluster est assez simple. Tout d'abord, téléchargez les manifestes:



wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml


Puis changez le type de vxlanà dans les paramètres host-gw:



sed -i 's/vxlan/host-gw/' kube-flannel.yml


... et le sous-réseau du pod - de la valeur par défaut à celle spécifiée lors de l'initialisation du cluster:



sed -i 's#10.244.0.0/16#10.1.0.0/16#' kube-flannel.yml


Après cela, nous créons des ressources:



kubectl create -f kube-flannel.yml


Terminé! Après un certain temps, le premier nœud K8s passera dans l'état Ready:



NAME         STATUS   ROLES    AGE   VERSION
pi-control   Ready    master   2m    v1.18.6


Ajout d'un nœud de travail



Vous pouvez maintenant ajouter un travailleur. Pour ce faire, après avoir installé Kubernetes lui-même selon le scénario décrit ci-dessus, il vous suffit d'exécuter la commande précédemment reçue:



kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4 \
    --discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050


Sur ce, nous pouvons supposer que le cluster est prêt:



root@pi-control:~# kubectl get no
NAME         STATUS   ROLES    AGE    VERSION
pi-control   Ready    master   28m    v1.18.6
pi-worker    Ready    <none>   2m8s   v1.18.6


Je n'avais que deux Raspberry Pi sous la main, donc je ne voulais pas en donner un uniquement sous le plan de contrôle. J'ai donc supprimé la tache auto-installée du nœud pi-control en exécutant:



root@pi-control:~# kubectl edit node pi-control


... et en supprimant les lignes:



 - effect: NoSchedule
   key: node-role.kubernetes.io/master


Remplir le cluster avec le minimum requis



Tout d'abord, nous avons besoin de Helm . Bien sûr, vous pouvez tout faire sans cela, mais Helm vous permet de personnaliser certains composants à votre discrétion littéralement sans modifier les fichiers. Et en fait c'est juste un fichier binaire qui "ne demande pas de pain".



Alors, allez sur helm.sh dans la section docs / installation et exécutez la commande à partir de là:



curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash


Après cela, ajoutez le référentiel de graphiques:



helm repo add stable https://kubernetes-charts.storage.googleapis.com/


Maintenant, installons les composants de l'infrastructure conformément à l'idée:



  • Contrôleur d'entrée;
  • Prométhée;
  • Grafana;
  • cert-manager.


Contrôleur d'entrée



Le premier composant, le contrôleur Ingress , est assez facile à installer et prêt à l'emploi prêt à l'emploi. Pour ce faire, accédez simplement à la section bare-metal du site et exécutez la commande d'installation à partir de là:



kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yaml


Cependant, à ce moment, la «framboise» a commencé à se forcer et à se heurter aux IOPS du disque. Le fait est qu'avec le contrôleur Ingress, un grand nombre de ressources sont installées, de nombreuses demandes d'API sont effectuées et, par conséquent, beaucoup de données sont écrites sur etcd. En général, soit une carte mémoire de classe 10 n'est pas très productive, soit une carte SD n'est fondamentalement pas suffisante pour une telle charge. Néanmoins, après 5 minutes, tout a commencé.



Un espace de noms a été créé et un contrôleur y est apparu et tout ce dont il a besoin:



root@pi-control:~# kubectl -n ingress-nginx get pod
NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-2hwdx        0/1     Completed   0          31s
ingress-nginx-admission-patch-cp55c         0/1     Completed   0          31s
ingress-nginx-controller-7fd7d8df56-68qp5   1/1     Running     0          48s


Prométhée



Les deux composants suivants sont assez faciles à installer via Helm à partir du repo de graphiques.



Trouvez Prometheus , créez un espace de noms et installez-y:



helm search repo stable | grep prometheus
kubectl create ns monitoring
helm install prometheus --namespace monitoring stable/prometheus --set server.ingress.enabled=True --set server.ingress.hosts={"prometheus.home.pi"}


Par défaut, Prometheus commande 2 disques: pour les données Prometheus et pour les données AlertManager. Puisqu'aucune classe de stockage n'a été créée dans le cluster, les disques ne seront pas classés et les pods ne démarreront pas. Pour les installations Kubernetes bare metal, nous utilisons généralement Ceph rbd, mais dans le cas du Raspberry Pi, c'est exagéré.



Créons donc un stockage local simple sur le chemin de l'hôte. Les manifestes PV (volume persistant) pour prometheus-server et prometheus-alertmanager sont fusionnés dans un fichier prometheus-pv.yamldu référentiel Git avec des exemples pour l'article . Le répertoire pour PV doit être créé au préalable sur le disque du nœud auquel nous voulons lier Prometheus: dans l'exemple nodeAffinity, le nom d'hôte est spécifié pi-workeret les répertoires /data/localstorage/prometheus-serveret sont créés sur celui-ci /data/localstorage/prometheus-alertmanager.



Téléchargez (clonez) le manifeste et ajoutez-le à Kubernetes:



kubectl create -f prometheus-pv.yaml


A ce stade, j'ai d'abord rencontré le problème d'architecture ARM. Kube-state-metrics, qui est défini par défaut dans le graphique Prometheus, a refusé de démarrer. C'était une erreur:



root@pi-control:~# kubectl -n monitoring logs prometheus-kube-state-metrics-c65b87574-l66d8
standard_init_linux.go:207: exec user process caused "exec format error"


Le fait est que pour kube-state-metrics, l'image du projet CoreOS est utilisée, qui n'est pas compilée pour ARM:



kubectl -n monitoring get deployments.apps prometheus-kube-state-metrics -o=jsonpath={.spec.template.spec.containers[].image}
quay.io/coreos/kube-state-metrics:v1.9.7


J'ai dû un peu google et trouver, par exemple, cette image . Pour en profiter, mettons à jour la version, en spécifiant l'image à utiliser pour kube-state-metrics:



helm upgrade prometheus --namespace monitoring stable/prometheus --set server.ingress.enabled=True --set server.ingress.hosts={"prometheus.home.pi"} --set kube-state-metrics.image.repository=carlosedp/kube-state-metrics --set kube-state-metrics.image.tag=v1.9.6


Nous vérifions que tout a commencé:



root@pi-control:~# kubectl -n monitoring get po
NAME                                             READY   STATUS              RESTARTS   AGE
prometheus-alertmanager-df65d99d4-6d27g          2/2     Running             0          5m56s
prometheus-kube-state-metrics-5dc5fd89c6-ztmqr   1/1     Running             0          5m56s
prometheus-node-exporter-49zll                   1/1     Running             0          5m51s
prometheus-node-exporter-vwl44                   1/1     Running             0          4m20s
prometheus-pushgateway-c547cfc87-k28qx           1/1     Running             0          5m56s
prometheus-server-85666fd794-z9qnc               2/2     Running             0          4m52s


Grafana et cert-manager



Pour les graphiques et les tableaux de bord, installez Grafana :



helm install grafana --namespace monitoring stable/grafana  --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}


À la fin de la sortie, nous verrons comment obtenir le mot de passe d'accès:



kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo


Pour commander des certificats, installez cert-manager . Pour l'installer, reportez-vous à la documentation , qui propose les commandes appropriées pour Helm:



helm repo add jetstack https://charts.jetstack.io

helm install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --version v0.16.0 \
  --set installCRDs=true


Pour les certificats auto-signés à usage domestique, cela suffit. Si vous devez recevoir le même Let's Encrypt , vous devez configurer un autre émetteur de cluster. Plus de détails peuvent être trouvés dans notre article " Certificats SSL de Let's Encrypt avec cert-manager sur Kubernetes ".



J'ai moi-même choisi la version de l' exemple dans la documentation , décidant que la version intermédiaire de LE suffirait. Modifiez l'e-mail dans l'exemple, enregistrez-le dans un fichier et ajoutez-le au cluster ( cert-manager-cluster-issuer.yaml ):



kubectl create -f cert-manager-cluster-issuer.yaml


Vous pouvez maintenant commander un certificat, par exemple, pour Grafana. Cela nécessitera un domaine et un accès externe au cluster. J'ai un domaine et j'ai configuré le trafic en transférant les ports 80 et 443 sur mon routeur domestique conformément au service de contrôleur d'entrée créé:



kubectl -n ingress-nginx get svc
NAME                                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.2.206.61    <none>        80:31303/TCP,443:30498/TCP   23d


Dans ce cas, le port 80 est traduit en 31303 et 443 en 30498. (Les ports sont générés aléatoirement, vous en aurez donc différents.)



Voici un exemple de certificat ( cert-manager-grafana-certificate.yaml ):



apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: grafana
  namespace: monitoring
spec:
  dnsNames:
    - grafana.home.pi
  secretName: grafana-tls
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt-staging


Ajoutez-le au cluster:



kubectl create -f cert-manager-grafana-certificate.yaml


Après cela, la ressource Ingress apparaîtra, à travers laquelle la validation Let's Encrypt aura lieu:



root@pi-control:~# kubectl -n monitoring get ing
NAME                        CLASS    HOSTS                        ADDRESS         PORTS   AGE
cm-acme-http-solver-rkf8l   <none>   grafana.home.pi      192.168.88.31   80      72s
grafana                     <none>   grafana.home.pi      192.168.88.31   80      6d17h
prometheus-server           <none>   prometheus.home.pi   192.168.88.31   80      8d


Une fois la validation réussie, nous verrons que la ressource est certificateprête et que le secret ci-dessus contient le grafana-tlscertificat et la clé. Vous pouvez vérifier immédiatement qui a émis le certificat:



root@pi-control:~# kubectl -n monitoring get certificate
NAME      READY   SECRET        AGE
grafana   True    grafana-tls   13m

root@pi-control:~# kubectl -n monitoring get secrets grafana-tls -ojsonpath="{.data['tls\.crt']}" | base64 -d | openssl x509 -issuer -noout
issuer=CN = Fake LE Intermediate X1


Revenons à Grafana. Nous devons corriger un peu sa version Helm, en modifiant les paramètres de TLS conformément au certificat généré.



Pour ce faire, téléchargez le graphique, éditez et mettez à jour à partir du répertoire local:



helm pull --untar stable/grafana


Modifiez grafana/values.yaml les paramètres TLS dans le fichier :



  tls:
    - secretName: grafana-tls
      hosts:
        - grafana.home.pi


Ici, vous pouvez immédiatement configurer le Prometheus installé comme datasource:



datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Prometheus
      type: prometheus
      url: http://prometheus-server:80
      access: proxy
      isDefault: true


Maintenant, mettez à jour le graphique Grafana à partir du répertoire local:



helm upgrade grafana --namespace monitoring ./grafana  --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}


Nous vérifions que le grafanaport 443 a été ajouté à Ingress et qu'il y a un accès via HTTPS:



root@pi-control:~# kubectl -n monitoring get ing grafana
NAME      CLASS    HOSTS                     ADDRESS         PORTS     AGE
grafana   <none>   grafana.home.pi           192.168.88.31   80, 443   63m

root@pi-control:~# curl -kI https://grafana.home.pi
HTTP/2 302
server: nginx/1.19.1
date: Tue, 28 Jul 2020 19:01:31 GMT
content-type: text/html; charset=utf-8
cache-control: no-cache
expires: -1
location: /login
pragma: no-cache
set-cookie: redirect_to=%2F; Path=/; HttpOnly; SameSite=Lax
x-frame-options: deny
strict-transport-security: max-age=15724800; includeSubDomains


Pour démontrer Grafana en action, vous pouvez télécharger et ajouter un tableau de bord pour kube-state-metrics . Voici à quoi ça ressemble: je







recommande également d'ajouter un tableau de bord pour l'exportateur de nœuds: il montrera en détail ce qui arrive aux "framboises" (charge CPU, mémoire, réseau, utilisation du disque, etc.).



Après cela, je pense que le cluster est prêt à recevoir et exécuter des applications!



Note de montage



Il existe au moins deux options pour créer des applications pour l'architecture ARM. Tout d'abord, vous pouvez construire sur un appareil ARM. Cependant, après avoir examiné la disposition actuelle des deux Raspberry Pi, j'ai réalisé qu'ils ne survivraient pas non plus à l'assemblage. Par conséquent, j'ai commandé un nouveau Raspberry Pi 4 (il est plus puissant et contient 4 Go de mémoire) - je prévois de le construire dessus.



La deuxième option consiste à créer une image Docker multi-architecture sur une machine plus puissante. Il existe une extension docker buildx pour cela . Si l'application est dans un langage compilé, une compilation croisée pour ARM est requise. Je ne décrirai pas tous les paramètres de ce chemin. cela mènera à un article séparé. Lors de la mise en œuvre de cette approche, vous pouvez réaliser des images "universelles": Docker fonctionnant sur une machine ARM chargera automatiquement l'image correspondant à l'architecture.



Conclusion



L'expérience réalisée a dépassé toutes mes attentes: [au moins] Kubernetes "vanille" avec la base nécessaire se sent bien sur ARM, et avec sa configuration, seules quelques nuances sont apparues.



Le Raspberry Pi 3B + eux-mêmes occupent le processeur, mais leurs cartes SD constituent un goulot d'étranglement évident. Des collègues ont suggéré que dans certaines versions, il est possible de démarrer à partir de l'USB, où vous pouvez connecter un SSD: alors la situation s'améliorera probablement.



Voici un exemple de charge CPU lors de l'installation de Grafana:







Pour les expériences et "essayer", à mon avis, le cluster Kubernetes sur "framboises" transmet bien mieux les sensations de fonctionnement que le même Minikube, car tous les composants du cluster sont installés et fonctionnent "D'une manière adulte."



Dans le futur, il y a une idée d'ajouter au cluster tout le cycle CI / CD, entièrement implémenté sur le Raspberry Pi. Et je serai également heureux si quelqu'un partage son expérience sur la configuration de K8 sur AWS Gravitons.



PS Oui, la "production" est peut-être plus proche que je ne le pensais:







PPS



Lisez aussi sur notre blog:






All Articles