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

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

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

Pour moi, cette architecture a toujours été le domaine du mobile et d’autres objets IoT. Les « vrais » serveurs sur ARM sont en quelque sorte inhabituels, voire fous à certains égards... Cependant, une nouvelle pensée m'est venue à l'esprit, alors un week-end, j'ai décidé de vérifier ce qui pouvait être exécuté sur ARM aujourd'hui. Et pour cela, j'ai décidé de commencer par quelque chose qui me tient à cœur : le cluster Kubernetes. Et pas seulement une sorte de « cluster » conventionnel, mais tout « de manière adulte », pour que ce soit autant que possible le même que celui que j'ai l'habitude de voir en production.

Selon mon idée, le cluster devrait être accessible depuis Internet, une application Web devrait y être exécutée 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 au moins modèle 3B+. AWS aurait pu devenir une plateforme d'expérimentation, mais j'étais intéressé par les « framboises » (qui restaient encore inactives). Nous allons donc déployer un cluster Kubernetes avec Ingress, Prometheus et Grafana dessus.

Préparer les "framboises"

Installation du système d'exploitation et de SSH

Je ne me suis pas trop soucié de choisir un OS à installer : j'ai juste pris le dernier Raspberry Pi OS Lite avec site officiel. Disponible là-bas documentation d'installation, dont toutes les actions doivent être effectuées sur tous les nœuds du futur cluster. Ensuite, vous devrez 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é les adresses statiques pour faciliter la configuration.
  2. L'adresse statique peut être configurée dans l'OS (dans le fichier /etc/dhcpcd.conf il existe un exemple approprié) ou en fixant un bail sur le serveur DHCP du routeur utilisé (dans mon cas, domestique).
  3. ssh-server est simplement inclus dans raspi-config (options d'interface → ssh).

Après cela, vous pouvez vous connecter via SSH (la connexion par défaut est piet le mot de passe est raspberry ou celui que vous avez modifié) et poursuivez les réglages.

Autres réglages

  1. Définissons le nom d'hôte. Dans mon exemple, ils utiliseront pi-control и pi-worker.
  2. Vérifions que le système de fichiers est étendu pour couvrir l'intégralité du disque (df -h /). Si nécessaire, il peut être étendu à l'aide de raspi-config.
  3. Modifions le mot de passe utilisateur par défaut dans raspi-config.
  4. Désactivons le fichier d'échange (c'est une exigence de Kubernetes ; si vous êtes intéressé par des détails sur ce sujet, voir 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. Installons Docker et les packages supplémentaires :
    apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistent

    При установке iptables-persistent vous devrez enregistrer les paramètres iptables pour ipv4, et dans le fichier /etc/iptables/rules.v4 - ajouter des règles à la chaîne 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

A ce stade, j'ai délibérément mis de côté tous mes développements et ceux de notre entreprise pour automatiser l'installation et la configuration du cluster K8s. Utilisons plutôt la documentation officielle avec kubernetes.io (légèrement complété 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 update

Plus loin dans la documentation, il est suggéré d'installer CRI (container runtime interface). Puisque Docker est déjà installé, passons à autre chose et installons les principaux composants :

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

A l'étape d'installation des principaux composants, j'ai immédiatement ajouté kubernetes-cni, ce qui est nécessaire au fonctionnement du cluster. Et là il y a un point important : le colis kubernetes-cni pour une raison quelconque, il 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 compréhensible (pour une liste complète, voir documentation):

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

Configuration de Kubernetes

Nœud avec plan de contrôle

L'installation 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

Nous effectuons maintenant l'installation elle-même - 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 des services et des pods ne doivent pas se chevaucher les uns avec les autres ou avec les réseaux existants.

À la fin, un message nous indiquera que tout va bien, et en même temps, ils nous diront comment attacher les 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. Dans le même temps, je recommande d'ajouter immédiatement l'auto-complétion 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

A ce stade, vous pouvez déjà voir le premier nœud du cluster (même s'il n'est 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

Ensuite, comme indiqué dans le message après l'installation, vous devrez installer le réseau dans le cluster. La documentation offre un choix parmi Calico, Cilium, contiv-vpp, Kube-router et Weave Net... Ici, je me suis écarté des instructions officielles et j'ai choisi une option plus familière et plus compréhensible pour moi : flanelle en mode host-gw (pour plus d'informations sur les backends disponibles, voir documentation du 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 avec dans les paramètres 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 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

Prêt! Après un certain temps, le premier nœud K8s passera à 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 dessus - 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

À ce stade, 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, alors j'en ai offert un seulement Je ne le voulais pas sous le plan de contrôle. J'ai donc supprimé la contamination automatiquement 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 Casque. Bien sûr, vous pouvez tout faire sans cela, mais Helm vous permet de personnaliser littéralement certains composants à votre discrétion sans modifier de fichiers. Et en fait, c’est juste un fichier binaire qui « ne demande pas de pain ».

Alors allons à barre.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/

Installons maintenant les composants de l'infrastructure comme prévu :

  • Contrôleur d'entrée ;
  • Prométhée;
  • Grafana;
  • gestionnaire de certificats.

Contrôleur d'entrée

Le premier composant est Contrôleur d'entrée - L'installation est assez simple et prête à l'emploi. Pour ce faire, il suffit d'aller sur section nue sur le 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-là, la « framboise » a commencé à se fatiguer et à se heurter à des IOPS de disque. Le fait est qu'avec le contrôleur Ingress, un grand nombre de ressources sont installées, de nombreuses requêtes sont adressées à l'API et, par conséquent, de nombreuses données sont écrites dans 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. Cependant, au bout de 5 minutes environ, tout a démarré.

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

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 dépôt de graphiques.

Nous trouvons Prométhée, créez un espace de noms et définissez-le sur :

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 elles-mêmes et pour les données AlertManager. Puisqu'aucune classe de stockage n'a été créée dans le cluster, les disques ne seront pas commandés et les pods ne démarreront pas. Pour les installations Kubernetes nues, nous utilisons généralement Ceph rbd, mais dans le cas du Raspberry Pi, c'est clairement excessif.

Par conséquent, créons un simple stockage local sur le chemin hôte. Les manifestes PV (volume persistant) pour prometheus-server et prometheus-alertmanager sont combinés dans un fichier prometheus-pv.yaml в Dépôts Git avec des exemples pour l'article. L'annuaire pour PV est obligatoire à l'avance créer sur le disque du nœud auquel on veut lier Prometheus : dans l'exemple il est écrit nodeAffinity par nom d'hôte pi-worker et des répertoires sont créés dessus /data/localstorage/prometheus-server и /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 de l'architecture ARM. Kube-state-metrics, installé par défaut dans le graphique Prometheus, a refusé de démarrer. Cela a donné 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 kube-state-metrics utilise une image du projet CoreOS, 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û faire une petite recherche sur Google et trouver, par exemple, cette image. Pour l'utiliser, mettons à jour la version pour indiquer 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.6

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 que nous définissons 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, nous installerons gestionnaire de cert. Pour l'installer, veuillez vous référer à documentation, 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=true

Pour les certificats auto-signés destinés à un usage domestique, cela est tout à fait suffisant. Si vous avez besoin d'obtenir la même chose Chiffrons, vous devez également configurer l'émetteur du cluster. Vous trouverez des détails à ce sujet dans notre article «Certificats SSL de Let's Encrypt avec cert-manager dans Kubernetes».

J'ai moi-même opté pour l'option de exemple dans la documentation, décidant que l’option de mise en scène de LE serait suffisante. Nous modifions l'e-mail dans l'exemple, l'enregistrons dans un fichier et l'ajoutons au cluster (cert-manager-cluster-issuer.yaml):

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

Vous pouvez désormais commander un certificat, par exemple pour Grafana. Cela nécessitera un domaine et un accès au cluster depuis l’extérieur. J'ai un domaine et j'ai configuré le trafic en redirigeant 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 le port 443 en 30498. (Les ports sont générés aléatoirement, le vôtre sera donc différent.)

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 passée, nous verrons que la ressource certificate prêt, et dans le secret ci-dessus grafana-tls - certificat et clé. Vous pouvez immédiatement vérifier qui a délivré 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 devrons légèrement corriger sa version Helm en modifiant les paramètres de TLS pour qu'ils correspondent au certificat généré.

Pour ce faire, téléchargez le graphique, modifiez-le et mettez-le à jour depuis le répertoire local :

helm pull --untar stable/grafana

Modification dans un fichier grafana/values.yaml Paramètres TLS :

  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

Nous mettons maintenant à jour la carte Grafana à partir du répertoire local :

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

Vérifier le contenu du contenu Ingress grafana le port 443 a été ajouté et 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 tableau de bord pour les métriques Kube-State. Voici à quoi cela ressemble :

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

Je recommande également d'ajouter un tableau de bord pour l'exportateur de nœuds : il montrera en détail ce qui se passe avec les « framboises » (charge CPU, mémoire, réseau, utilisation du disque, etc.).

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

Remarque sur l'assemblage

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 de deux Raspberry Pi, j'ai réalisé qu'ils ne survivraient pas non plus à l'assemblage. Par conséquent, je me suis commandé un nouveau Raspberry Pi 4 (il est plus puissant et possède jusqu'à 4 Go de mémoire) - je prévois de m'appuyer dessus.

La deuxième option consiste à créer une image Docker multi-arch sur une machine plus puissante. Pour cela il y a extension Docker buildx. Si l'application est dans un langage compilé, une compilation croisée pour ARM sera requise. Je ne décrirai pas tous les paramètres de ce chemin, car... Cela nécessitera un article séparé. En mettant en œuvre cette approche, il est possible de réaliser des images « universelles » : Docker fonctionnant sur une machine ARM téléchargera lui-même automatiquement l'image correspondant à l'architecture.

Conclusion

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

Les Raspberry Pi 3B+ eux-mêmes maintiennent la charge sur 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 d'un port USB, où vous pouvez connecter un SSD : la situation s'améliorera très probablement.

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

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

Pour expérimenter et « essayer », à mon avis, un cluster Kubernetes sur « framboises » transmet bien mieux la sensation de fonctionnement que le même Minikube, car tous les composants du cluster sont installés et fonctionnent « comme un adulte ».

À l'avenir, il y a une idée pour ajouter au cluster l'ensemble du cycle CI/CD, entièrement implémenté sur le Raspberry Pi. Je serai également heureux si quelqu'un partage son expérience dans la configuration des K8 sur AWS Gravitons.

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

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

PPS

A lire aussi sur notre blog :

Source: habr.com