
Récemment, une entreprise bien connue a annoncé qu'elle allait migrer sa gamme de portables vers l'architecture ARM. En entendant cette nouvelle, je me suis souvenu : en consultant à nouveau les prix d'EC2 dans AWS, j'ai remarqué les Graviton avec un prix très attractif. L'astuce, bien sûr, était que c'était de l'ARM. À l'époque, je ne réalisais pas que l'ARM était quelque chose de sérieux…
Pour moi, cette architecture a toujours été réservée aux mobiles et autres gadgets IoT. Des « vrais » serveurs sous ARM, c'est un peu étrange, presque sauvage… Cependant, une nouvelle idée m'est venue à l'esprit, et donc un week-end, j'ai décidé de vérifier ce qu'il est possible de faire aujourd'hui sur ARM. Pour cela, j'ai décidé de commencer par quelque chose de familier — un cluster Kubernetes. Et pas juste un « cluster » quelconque, mais un véritable, comme je suis habitué à voir en production.
Selon ma vision, le cluster doit être accessible depuis Internet, exécuter une application Web, et il doit également y avoir au moins une surveillance. Pour réaliser cette idée, il me faudrait quelques Raspberry Pi, au minimum le modèle 3B+. Bien qu'AWS aurait pu servir de plateforme pour les expériences, j'étais plus intéressé par les « framboises » (qui étaient de toute façon inutilisées). Ainsi, nous allons déployer un cluster Kubernetes avec Ingress, Prometheus et Grafana sur ceux-ci.
Préparation des « framboises »
Installation du système d'exploitation et de SSH
Pour le choix du système d'exploitation, je ne me suis pas trop compliqué : j'ai simplement pris la version la plus récente de Raspberry Pi OS Lite avec . Une documentation sur l'installation est également disponible Après avoir connecté un moniteur et un clavier, il est nécessaire de configurer au préalable le réseau et SSH :
Pour que le cluster fonctionne, le maître doit avoir une adresse IP statique, tandis que sur les nœuds de travail, c'est à la discrétion de l'utilisateur. J'ai préféré des adresses statiques partout pour des raisons de commodité de configuration.
- Une adresse statique peut être configurée dans le système d'exploitation (dans le fichier
- , un exemple approprié est disponible) ou en réservant le lease sur le serveur DHCP de votre routeur (dans mon cas, celui de la maison).
/etc/dhcpcd.confLe serveur SSH se configure simplement dans raspi-config ( - options d'interface → sshAprès cela, il est possible de se connecter via SSH (par défaut, le login est).
pi , et le mot de passe estraspberry ou celui que vous avez modifié) et de continuer la configuration. Autres configurations
Autres paramètres
- Nous allons définir le nom d'hôte. Dans mon exemple, nous allons utiliser
pi-controletpi-worker. - Vérifions que le système de fichiers est étendu sur tout le disque (
df -h /). Si nécessaire, il peut être étendu à l'aide de raspi-config. - Changer le mot de passe de l'utilisateur par défaut dans raspi-config.
- Désactivons le fichier d'échange (c'est une exigence de Kubernetes ; si vous êtes intéressé par les détails à ce sujet, consultez ):
dphys-swapfile swapoff systemctl disable dphys-swapfile - Mettons à jour les paquets vers les dernières versions :
apt-get update && apt-get dist-upgrade -y - Installons Docker et des paquets supplémentaires :
apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistentLors de l'installation
iptables-persistent, il sera nécessaire de sauvegarder les paramètres d'iptables pour ipv4, et dans le fichier/etc/iptables/rules.v4— ajouter des règles dans la chaîneFORWARD, 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 - Il ne reste plus qu'à redémarrer.
Tout est maintenant prêt pour l'installation du cluster Kubernetes.
Installation de Kubernetes
À ce stade, j'ai délibérément mis de côté tous mes développements d'automatisation d'installation et de configuration du cluster K8s. À la place, nous allons utiliser la documentation officielle sur (légèrement complétée par des commentaires et des abréviations).
Ajoutons le dépôt 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 updateEnsuite, la documentation suggère d'installer le CRI (interface d'exécution de conteneurs). Étant donné que Docker est déjà installé, nous continuons et installez les composants principaux :
sudo apt-get install -y kubelet kubeadm kubectl kubernetes-cni Au moment de l'installation des composants principaux, j'ai immédiatement ajouté kubernetes-cni, qui est nécessaire au fonctionnement du cluster. Et il y a un point important : le paquet kubernetes-cni ne crée pour une raison quelconque pas de répertoire par défaut pour les configurations des interfaces CNI, donc j'ai dû le créer manuellement :
mkdir -p /etc/cni/net.dPour le backend de réseau, dont nous parlerons ci-dessous, il est nécessaire d'installer le plugin pour CNI. J'ai choisi le plugin portmap que je connais bien (voir la liste complète dans ):
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/ ./portmapConfiguration de Kubernetes
Nœud avec le plan de contrôle
L'installation du cluster lui-même est assez simple. Pour accélérer ce processus et vérifier que les images Kubernetes sont accessibles, vous pouvez préalablement exécuter :
kubeadm config images pullNous allons maintenant procéder à l'installation proprement dite — 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-certsVeuillez noter que les sous-réseaux pour les services et les pods ne doivent pas se chevaucher entre eux ni avec les réseaux existants.
À la fin, un message nous montrera que tout va bien et nous expliquera comment connecter les nœuds de travail au plane de contrôle :
Votre plane de contrôle Kubernetes a été initialisé avec succès !
Pour commencer à utiliser votre cluster, vous devez exécuter ce qui suit en tant qu'utilisateur ordinaire :
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Vous devez maintenant déployer un réseau de pods dans le cluster.
Exécutez "kubectl apply -f [podnetwork].yaml" avec l'une des options répertoriées à :
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Vous pouvez maintenant rejoindre n'importe quel nombre de nœuds du plane de contrôle en exécutant la commande suivante sur chacun d'eux en tant que root :
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050
--control-plane --certificate-key 72a3c0a14c627d6d7fdade1f4c8d7a41b0fac31b1faf0d8fdf9678d74d7d2403
Veuillez noter que la clé de certificat donne accès à des données sensibles du cluster, gardez-la secrète !
En guise de précaution, les certificats téléchargés seront supprimés dans deux heures ; si nécessaire, vous pouvez utiliser
"kubeadm init phase upload-certs --upload-certs" pour recharger les certificats par la suite.
Ensuite, vous pouvez rejoindre n'importe quel nombre de nœuds de travail en exécutant ce qui suit sur chacun d'eux en tant que root :
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050Exécutons les recommandations pour l'ajout de la configuration pour l'utilisateur. Et je recommande également d'ajouter immédiatement l'autocomplétion pour kubectl :
kubectl completion bash > ~$HOME/.kube/completion.bash.inc
printf "
# Complétion de shell Kubectl
source '$HOME/.kube/completion.bash.inc'
" >> $HOME/.bash_profile
source $HOME/.bash_profileÀ ce stade, vous pouvez déjà voir le premier nœud dans le cluster (bien qu'il ne soit pas encore prêt) :
root@pi-control:~# kubectl get no
NOM STATUT RÔLES ÂGE VERSION
pi-control NotReady maitre 29s v1.18.6Configuration du réseau
Ensuite, comme mentionné dans le message après l'installation, il sera nécessaire d'installer un réseau dans le cluster. La documentation propose le choix entre Calico, Cilium, contiv-vpp, Kube-router et Weave Net… Ici, j'ai dévié des instructions officielles et choisi une option qui m'est plus familière et compréhensible : en mode host-gw (plus de détails sur les backends disponibles dans la ).
L'installer dans le cluster est assez simple. Pour commencer - téléchargeons les manifestes :
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml Ensuite, nous changeons dans les paramètres le type de vxlan sur host-gw:
sed -i 's/vxlan/host-gw/' kube-flannel.yml… et le sous-réseau des pods - de la valeur par défaut à celle indiquée lors de l'initialisation du cluster :
sed -i 's#10.244.0.0/16#10.1.0.0/16#' kube-flannel.ymlAprès cela, nous créons les ressources :
kubectl create -f kube-flannel.yml C'est fait ! Après un certain temps, le premier nœud K8s passera au statut Prêt:
NOM STATUT RÔLES ÂGE VERSION
pi-control Prêt maitre 2m v1.18.6Ajout d'un nœud de travail
Vous pouvez maintenant ajouter un worker. Pour ce faire, sur celui-ci — après avoir installé Kubernetes selon le scénario décrit ci-dessus — il suffit simplement d'exécuter la commande précédemment obtenue :
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050À ce stade, nous pouvons considérer 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 2m8s v1.18.6Je n'avais que deux Raspberry Pi à portée de main, donc je ne voulais pas en donner une pour le control plane. uniquement Ainsi, j'ai supprimé le taint automatiquement attribué 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/masterRemplissage du cluster avec le minimum nécessaire
Tout d'abord, nous aurons besoin de Helm. Bien sûr, il est possible de tout faire sans lui, mais Helm permet de configurer littéralement certains composants à votre goût sans modifier de fichiers. Et en fait, c'est juste un fichier binaire qui « ne demande rien ».
Alors, rendez-vous sur dans la section docs/installation et exécutez la commande depuis là :
curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bashEnsuite, ajoutons le dépôt de charts :
helm repo add stable https://kubernetes-charts.storage.googleapis.com/Maintenant, installons les composants d'infrastructure selon le plan :
- Ingress controller ;
- Prometheus ;
- Grafana ;
- cert-manager.
Ingress controller
Le premier composant — Ingress controller — s'installe assez facilement et est prêt à l'emploi « dès la sortie de la boîte ». Pour ce faire, il suffit d'aller dans et d'exécuter la commande d'installation depuis là :
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yamlCependant, à ce moment-là, la « framboise » a commencé à se mettre à ramer et à s'opposer aux IOPS disques. Le problème est que l'Ingress controller installe un grand nombre de ressources, effectue beaucoup de requêtes à l'API et, par conséquent, beaucoup de données sont écrites dans etcd. En général, soit la carte mémoire de classe 10 n'est pas très performante, soit les cartes SD ne sont pas suffisantes pour une telle charge. Néanmoins, après environ 5 minutes, tout s'est lancé.
Un namespace a été créé et un contrôleur avec tout ce dont il avait besoin est apparu en son sein :
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 48sPrometheus
Les deux composants suivants sont assez simples à installer via Helm à partir du dépôt chart.
Trouvons Prometheus, créons un espace de noms et installons-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 demande 2 disques : un pour les données de Prometheus et un pour les données d'AlertManager. Comme aucune classe de stockage n'a été créée dans le cluster, les disques ne seront pas demandés et les pods ne démarreront pas. Pour les installations bare metal de Kubernetes, nous utilisons généralement Ceph rbd, mais dans le cas de Raspberry Pi, c'est clairement excessif.
Nous allons donc créer un stockage local simple sur hostpath. Les manifestes PV (volume permanent) pour prometheus-server et prometheus-alertmanager sont regroupés dans le fichier prometheus-pv.yaml dans . Le répertoire pour le PV doit être préparé à l'avance sur le disque de l'hôte auquel nous voulons lier Prometheus : dans l'exemple, il est précisé nodeAffinity par le hostname pi-worker et des répertoires y ont été créés /data/localstorage/prometheus-server et /data/localstorage/prometheus-alertmanager.
Téléchargeons (clonons) le manifeste et ajoutons-le à Kubernetes :
kubectl create -f prometheus-pv.yamlÀ ce stade, j'ai rencontré pour la première fois un problème d'architecture ARM. Kube-state-metrics, qui est installé par défaut dans le chart Prometheus, a refusé de démarrer. Il a renvoyé l'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"En fait, l'image utilisée pour kube-state-metrics provient du projet CoreOS, qui n'est pas compatible avec 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.7J'ai dû faire quelques recherches et trouver, par exemple, . Pour l'utiliser, nous mettrons à jour le release en spécifiant quelle 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.6Vérifions que tout a démarré :
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 4m52sGrafana et cert-manager
Pour les graphiques et les tableaux de bord, nous installons Grafana:
helm install grafana --namespace monitoring stable/grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}À la fin de la sortie, ils nous montreront comment obtenir le mot de passe d'accès :
kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echoPour commander des certificats, nous installerons cert-manager. Pour son installation, nous nous référerons à , qui propose les commandes correspondantes 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=truePour des certificats auto-signés à usage domestique, cela suffit. Cependant, si vous devez obtenir le même Let's Encrypt, il est nécessaire de configurer un cluster issuer supplémentaire. Les détails peuvent être trouvés dans notre article «».
J'ai moi-même opté pour l'option du , pensant que l'option staging de LE serait suffisante. Modifions l'email dans l'exemple, enregistrons-le dans un fichier et ajoutons-le au cluster ():
kubectl create -f cert-manager-cluster-issuer.yamlNous pouvons maintenant commander un certificat, par exemple, pour Grafana. Pour cela, un domaine et un accès au cluster de l'extérieur sont nécessaires. J'ai un domaine, et j'ai configuré le transfert de trafic des ports 80 et 443 sur mon routeur domestique conformément au service ingress-controller créé :
kubectl -n ingress-nginx get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller NodePort 10.2.206.61 80:31303/TCP,443:30498/TCP 23dLe port 80 est ici traduit en 31303, et le 443 en 30498. (Les ports sont générés de manière aléatoire, donc vous en aurez d'autres.)
Voici un exemple de certificat ():
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-stagingAjoutons-le au cluster :
kubectl create -f cert-manager-grafana-certificate.yamlAprès cela, une ressource Ingress apparaîtra, à travers laquelle la validation avec Let’s Encrypt se fera :
root@pi-control:~# kubectl -n monitoring get ing
NAME CLASS HOSTS ADDRESS PORTS AGE
cm-acme-http-solver-rkf8l grafana.home.pi 192.168.88.31 80 72s
grafana grafana.home.pi 192.168.88.31 80 6d17h
prometheus-server prometheus.home.pi 192.168.88.31 80 8d Après la validation, nous verrons que la ressource certificate est prête, et dans le secret mentionné ci-dessus grafana-tls se trouvent le certificat et la clé. Nous pouvons immédiatement vérifier 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 X1Revenons à Grafana. Nous devrons légèrement modifier son lancement Helm, en ajustant les paramètres TLS conformément au certificat créé.
Pour cela, téléchargeons le chart, modifions-le et le mettons à jour depuis le répertoire local :
helm pull --untar stable/grafana Modifions le fichier grafana/values.yaml les paramètres TLS :
tls:
- secretName: grafana-tls
hosts:
- grafana.home.pi Ici, nous pouvons également configurer Prometheus installé comme datasource:
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus-server:80
access: proxy
isDefault: trueNous mettons maintenant à jour le chart Grafana depuis le répertoire local :
helm upgrade grafana --namespace monitoring ./grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"} Vérifions que dans l'Ingress grafana le port 443 a été ajouté 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 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=; Path=/; HttpOnly; SameSite=Lax
x-frame-options: deny
strict-transport-security: max-age=15724800; includeSubDomainsPour démontrer Grafana en action, vous pouvez télécharger et ajouter . Voici à quoi cela ressemble :

Je recommande également d'ajouter un tableau de bord pour le node exporter : il montrera en détail ce qui se passe avec les « framboises » (charge CPU, utilisation de la mémoire, réseau, disque, etc.).
Après cela, je considère que le cluster est prêt à accepter et à exécuter des applications !
Remarque sur la construction
Pour assembler des applications pour l'architecture ARM, il existe au moins deux options. Tout d'abord, on peut assembler sur un appareil ARM. Cependant, après avoir examiné l'utilisation actuelle de deux Raspberry Pi, j'ai réalisé qu'ils ne peuvent même pas supporter l'assemblage. J'ai donc commandé un nouveau Raspberry Pi 4 (qui est plus puissant et dispose de 4 Go de mémoire) — je prévois de l'utiliser pour l'assemblage.
La deuxième option consiste à assembler une image Docker multi-architecture sur une machine plus puissante. Pour cela, il y a . Si l'application est dans un langage compilé, une cross-compilation pour ARM sera nécessaire. Je ne vais pas décrire tous les réglages nécessaires pour cette approche, car cela nécessiterait un article à part. En mettant en œuvre cette méthode, on peut obtenir des images « universelles » : Docker, exécuté sur un appareil ARM, téléchargera automatiquement l'image correspondant à l'architecture.
Conclusion
L'expérience menée a dépassé toutes mes attentes : [au moins] un Kubernetes « vanille » avec la base nécessaire se comporte plutôt bien sur ARM, et lors de sa configuration, seuls quelques détails ont posé problème.
Les Raspberry Pi 3B+ supportent la charge du CPU, cependant leurs cartes SD constituent un réel goulet d'étranglement. Des collègues m'ont suggéré que dans certaines versions, il est possible de démarrer à partir d'USB, où l'on peut connecter un SSD : dans ce cas, la situation devrait s'améliorer.
Voici un exemple de charge CPU lors de l'installation de Grafana :

Pour des expériences et « à essayer », à mon avis, un cluster Kubernetes sur des « framboises » transmet bien mieux les sensations d'exploitation que Minikube, car tous les composants du cluster sont installés et fonctionnent de manière « professionnelle ».
À terme, j'ai l'idée d'ajouter à ce cluster tout le cycle CI/CD, entièrement réalisé sur Raspberry Pi. Je serais également heureux si quelqu'un pouvait partager son expérience de configuration de K8s sur AWS Graviton.
P.S. Oui, « production » peut être plus proche que je ne le pensais :

P.P.S.
Lisez aussi dans notre blog :
- «».
Source : habr.com
