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:
- 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.
- Une adresse statique peut être configurée dans le système d'exploitation (
/etc/dhcpcd.conf
il y a un exemple approprié dans le fichier ) ou en fixant le bail dans le serveur DHCP du routeur utilisé (dans mon cas, domestique). - 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 raspberry
celui que vous avez changé) et continuer les paramètres.
Autres réglages
- Définissons le nom d'hôte. Dans mon exemple,
pi-control
et sera utilisépi-worker
. - 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. - Modifiez le mot de passe utilisateur par défaut dans raspi-config.
- 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
- Mettons à jour les packages vers les dernières versions:
apt-get update && apt-get dist-upgrade -y
- 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, vousiptables-persistent
devrez enregistrer les paramètres iptables pour ipv4 et/etc/iptables/rules.v4
ajouter les règles à la chaîne dans le fichierFORWARD
, 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.
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-cni
ce qui est nécessaire pour que le cluster fonctionne. Et ici, il y a un point important: kubernetes-cni
pour 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.yaml
du 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-worker
et les répertoires /data/localstorage/prometheus-server
et 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
certificate
prête et que le secret ci-dessus contient le grafana-tls
certificat 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
grafana
port 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: