
Recentemente, una nota azienda ha annunciato che sta trasferendo la sua linea di laptop sull'architettura ARM. Sentendo questa notizia, ho ricordato che, mentre controllavo nuovamente i prezzi su EC2 in AWS, ho notato i Graviton con un prezzo molto interessante. Il trucco, ovviamente, era che si trattava di ARM. Allora non mi era nemmeno venuto in mente che ARM fosse qualcosa di piuttosto serio...
Per me, quest'architettura è stata sempre associata a mobile e altre cosette IoT. Server "veri" su ARM… è un po' insolito, persino strano... Tuttavia, una nuova idea si è installata nella mia mente, quindi uno dei fine settimana ho deciso di controllare cosa si può eseguire oggi su ARM. E per questo ho pensato di partire da qualcosa di familiare e vicino: un cluster Kubernetes. Non un qualsiasi "cluster", ma uno "serio", per farlo sembrare il più simile possibile a quello che sono abituato a vedere in produzione.
Secondo il mio piano, il cluster deve essere accessibile da internet, deve eseguire un'app web e avere almeno un sistema di monitoraggio. Per realizzare questa idea, saranno necessari un paio (o più) di Raspberry Pi non inferiori al modello 3B+. AWS potrebbe essere una base per gli esperimenti, ma ero particolarmente interessato ai „raspberry“ (che erano comunque fermi senza fare nulla). Quindi, installeremo un cluster Kubernetes su di essi con Ingress, Prometheus e Grafana.
Preparazione dei "raspberry"
Installazione del sistema operativo e SSH
Non mi sono troppo complicato nella scelta del sistema operativo da installare: ho semplicemente scelto l'ultima versione di Raspberry Pi OS Lite con . Qui è disponibile , tutte le operazioni devono essere eseguite su tutti i nodi del futuro cluster. Successivamente, dovranno essere eseguite le seguenti operazioni (anch'esse su tutti i nodi).
Collegando un monitor e una tastiera, è necessario configurare in anticipo la rete e SSH:
- Per il funzionamento del cluster, è necessario che il master abbia un indirizzo IP statico, mentre per i nodi di lavoro è a discrezione. Ho preferito indirizzi statici ovunque per facilità di configurazione.
- L'indirizzo statico può essere configurato nel sistema operativo (nel file
/etc/dhcpcd.confc'è un esempio appropriato) o fissando il lease nel server DHCP del router utilizzato (nel mio caso, il router domestico). - Il server SSH viene semplicemente attivato in raspi-config (interfacing options → ssh).
Dopo di che, è possibile accedere tramite SSH (di default l'username è pi, e la password è raspberry o quella che è stata cambiata) e proseguire con le configurazioni.
Altre configurazioni
- Imposteremo il nome dell'host. Nel mio esempio useremo
pi-controlepi-worker. - Verifichiamo che il filesystem sia esteso su tutto il disco (
df -h /). Se necessario, può essere esteso usando raspi-config. - Cambiamo la password dell'utente predefinito in raspi-config.
- Disabilitiamo il file di swap (è una richiesta di Kubernetes; se ti interessano i dettagli su questo argomento, vedi ):
dphys-swapfile swapoff systemctl disable dphys-swapfile - Aggiorniamo i pacchetti alle versioni più recenti:
apt-get update && apt-get dist-upgrade -y - Installiamo Docker e pacchetti aggiuntivi:
apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistentDurante l'installazione
iptables-persistentsarà necessario salvare le impostazioni di iptables per ipv4, e nel file/etc/iptables/rules.v4aggiungere regole nella catenaFORWARD, in questo modo:# 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 - Rimane solo da riavviarsi.
Ora è tutto pronto per l'installazione del cluster Kubernetes.
Installazione di Kubernetes
In questa fase ho volutamente rinviato tutte le mie e le nostre risorse aziendali per l'automazione dell'installazione e della configurazione del cluster K8s. Invece, utilizziamo la documentazione ufficiale con (leggermente arricchita da commenti e abbreviazioni).
Aggiungiamo il repository 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 updateSuccessivamente, nella documentazione viene proposto di installare CRI (container runtime interface). Poiché Docker è già installato, procediamo e installiamo i componenti principali:
sudo apt-get install -y kubelet kubeadm kubectl kubernetes-cni Nella fase di installazione dei componenti principali ho subito aggiunto kubernetes-cni, che è necessario per il funzionamento del cluster. C'è un aspetto importante: il pacchetto kubernetes-cni per qualche motivo non crea la directory predefinita per le impostazioni degli interfacce CNI, quindi ho dovuto crearla manualmente:
mkdir -p /etc/cni/net.dPer il funzionamento del backend di rete, di cui parleremo più avanti, è necessario installare un plugin per CNI. Ho scelto il plugin portmap che mi è familiare e comprensibile (puoi vedere l'elenco completo in ):
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/ ./portmapConfigurazione Kubernetes
Nodo con control plane
L'installazione stessa del cluster è abbastanza semplice. E per accelerare questo processo e verificare che le immagini Kubernetes siano disponibili, puoi eseguire in anticipo:
kubeadm config images pullOra eseguiamo l'installazione stessa: inizializziamo il control plane del cluster:
kubeadm init --pod-network-cidr=10.1.0.0/16 --service-cidr=10.2.0.0/16 --upload-certsSi prega di notare che le sottoreti per i servizi e i pod non devono sovrapporsi tra loro e con le reti esistenti.
Alla fine ci verrà mostrato un messaggio dicendo che tutto è a posto e ci verrà anche indicato come collegare i nodi di lavoro al control plane:
Il tuo control-plane Kubernetes è stato inizializzato con successo! Per iniziare a utilizzare il tuo cluster, devi eseguire quanto segue come utente normale:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Ora dovresti distribuire una rete pod nel cluster.
Esegui "kubectl apply -f [podnetwork].yaml" con una delle opzioni elencate su:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Ora puoi unire un numero qualsiasi di nodi del control-plane eseguendo il seguente comando su ciascuno come root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050
--contrl-plane --certificate-key 72a3c0a14c627d6d7fdade1f4c8d7a41b0fac31b1faf0d8fdf9678d74d7d2403
Si prega di notare che la certificate-key fornisce accesso a dati sensibili del cluster, mantienila segreta!
Come misura di sicurezza, gli uploaded-certs verranno eliminati in due ore; Se necessario, puoi utilizzare
"kubeadm init phase upload-certs --upload-certs" per ricaricare i certificati successivamente.
Poi puoi unire un numero qualsiasi di nodi di lavoro eseguendo quanto segue su ciascuno come root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050Procediamo con le raccomandazioni per l'aggiunta del config per l'utente. E consiglio anche di aggiungere subito il completamento automatico per kubectl:
kubectl completion bash > ~/.kube/completion.bash.inc
printf "
# Completamento della shell Kubectl
source '$HOME/.kube/completion.bash.inc'
" >> $HOME/.bash_profile
source $HOME/.bash_profileA questo punto è già possibile vedere il primo nodo nel cluster (anche se non è ancora pronto):
root@pi-control:~# kubectl get no
NAME STATUS ROLES AGE VERSION
pi-control NotReady master 29s v1.18.6Configurazione della rete
Successivamente, come detto nel messaggio dopo l'installazione, sarà necessario installare una rete nel cluster. La documentazione offre una scelta tra Calico, Cilium, contiv-vpp, Kube-router e Weave Net… Qui mi sono discostato dalle istruzioni ufficiali e ho scelto un'opzione a me più familiare: in modalità host-gw (maggiore dettaglio sui backend disponibili è disponibile in ).
Installarlo nel cluster è piuttosto semplice. Prima di tutto — scarichiamo i manifesti:
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml Poi cambiamo nelle impostazioni il tipo da vxlan in host-gw:
sed -i 's/vxlan/host-gw/' kube-flannel.yml… e la sottorete dei pod — dal valore predefinito a quello specificato durante l'inizializzazione del cluster:
sed -i 's#10.244.0.0/16#10.1.0.0/16#' kube-flannel.ymlDopo di che creiamo le risorse:
kubectl create -f kube-flannel.yml Fatto! Dopo un po' il primo nodo K8s passerà allo stato Pronto:
NAME STATUS ROLES AGE VERSION
pi-control Ready master 2m v1.18.6Aggiunta del nodo di lavoro
Ora è possibile aggiungere un worker. Per fare ciò, dopo aver installato Kubernetes seguendo lo scenario descritto sopra, è sufficiente eseguire il comando ricevuto in precedenza:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050A questo punto possiamo considerare il cluster pronto:
root@pi-control:~# kubectl get no
NOME STATO RUOLO ETÀ VERSIONE
pi-control Pronto master 28m v1.18.6
pi-worker Pronto 2m8s v1.18.6Avevo solo due Raspberry Pi a disposizione, quindi non volevo dare uno di essi solo al control plane. Pertanto, ho rimosso il taint installato automaticamente dal nodo pi-control eseguendo:
root@pi-control:~# kubectl edit node pi-control… e ho rimosso le righe:
- effect: NoSchedule
key: node-role.kubernetes.io/masterRiempimento del cluster con il minimo necessario
Innanzitutto avremo bisogno di Helm. Certo, è possibile fare tutto anche senza di esso, ma Helm consente di configurare alcuni componenti secondo le proprie esigenze senza modificare i file. E in effetti è semplicemente un file binario, che "non richiede nulla".
Quindi, accediamo a nella sezione docs/installation ed eseguiamo il comando da lì:
curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bashDopo di che aggiungiamo il repository dei chart:
helm repo add stable https://kubernetes-charts.storage.googleapis.com/Ora installeremo i componenti infrastrutturali secondo il progetto:
- Ingress controller;
- Prometheus;
- Grafana;
- cert-manager.
Ingress controller
Il primo componente — Ingress controller — si installa piuttosto facilmente ed è pronto per l'uso "out of the box". È sufficiente andare nella ed eseguire il comando di installazione da lì:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yamlTuttavia, a questo punto il "raspberry" ha cominciato a faticare e a confrontarsi con l'IOPS del disco. Il problema è che insieme all'Ingress controller vengono installate molte risorse, vengono effettuate molte richieste all'API e, di conseguenza, molti dati vengono scritti in etcd. Insomma, o la scheda di memoria di classe 10 non è molto performante, o le schede SD non sono sufficienti per un carico del genere. Tuttavia, dopo circa 5 minuti tutto si è avviato.
È stato creato un namespace e in esso è apparso il controller e tutto ciò di cui ha bisogno:
root@pi-control:~# kubectl -n ingress-nginx get pod
NOME PRONTO STATO RIAVVII ETÀ
ingress-nginx-admission-create-2hwdx 0/1 Completato 0 31s
ingress-nginx-admission-patch-cp55c 0/1 Completato 0 31s
ingress-nginx-controller-7fd7d8df56-68qp5 1/1 In esecuzione 0 48sPrometheus
I seguenti due componenti sono piuttosto semplici da installare tramite Helm dal chart repo.
Troviamo Prometheus, creiamo un namespace e installiamolo in esso:
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"}Per impostazione predefinita, Prometheus richiede 2 dischi: uno per i dati di Prometheus stesso e uno per i dati di AlertManager. Poiché non è stata creata una storage class nel cluster, i dischi non verranno richiesti e i pod non si avvieranno. Per le installazioni bare metal di Kubernetes di solito utilizziamo Ceph rbd, ma nel caso di Raspberry Pi è chiaramente eccessivo.
Pertanto, creiamo un semplice storage locale su hostpath. I manifesti PV (persistent volume) per prometheus-server e prometheus-alertmanager sono combinati nel file prometheus-pv.yaml in . La directory per i PV deve essere creata previamente sul disco del nodo a cui vogliamo collegare Prometheus: nell'esempio è stato specificato nodeAffinity per hostname pi-worker e in esso sono state create le directory /data/localstorage/prometheus-server e /data/localstorage/prometheus-alertmanager.
Scarichiamo (cloniamo) il manifesto e lo aggiungiamo a Kubernetes:
kubectl create -f prometheus-pv.yamlA questo punto ho affrontato per la prima volta un problema con l'architettura ARM. Kube-state-metrics, che viene installato per impostazione predefinita nel chart di Prometheus, si è rifiutato di avviarsi. Ha restituito un errore:
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"Il fatto è che per kube-state-metrics viene utilizzato un'immagine del progetto CoreOS, che non viene compilata per 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.7Ho dovuto cercare un po' e trovare, ad esempio, . Per utilizzarla, aggiorniamo il rilascio specificando quale immagine utilizzare per 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.6Controlliamo che tutto sia avviato:
root@pi-control:~# kubectl -n monitoring get po
NOME PRONTO STATO RIAVVÍ ETÀ
prometheus-alertmanager-df65d99d4-6d27g 2/2 In esecuzione 0 5m56s
prometheus-kube-state-metrics-5dc5fd89c6-ztmqr 1/1 In esecuzione 0 5m56s
prometheus-node-exporter-49zll 1/1 In esecuzione 0 5m51s
prometheus-node-exporter-vwl44 1/1 In esecuzione 0 4m20s
prometheus-pushgateway-c547cfc87-k28qx 1/1 In esecuzione 0 5m56s
prometheus-server-85666fd794-z9qnc 2/2 In esecuzione 0 4m52sGrafana e cert-manager
Per i grafici e i dashboard installiamo Grafana:
helm install grafana --namespace monitoring stable/grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}Alla fine dell'output verrà mostrato come ottenere la password di accesso:
kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echoPer ordinare i certificati installeremo cert-manager. Per la sua installazione ci rivolgeremo a , che offre i comandi pertinenti per 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=truePer certificati self-signed per uso domestico, questo è più che sufficiente. Se invece è necessario ottenere lo stesso Let’s Encrypt, è necessario configurare anche il cluster issuer. I dettagli su questo possono essere trovati nel nostro articolo «».
Io ho scelto l'opzione da , decidendo che la variante di staging di LE sarebbe stata sufficiente. Modifichiamo nell'esempio l'e-mail, salviamo nel file e aggiungiamo al cluster ():
kubectl create -f cert-manager-cluster-issuer.yamlOra possiamo richiedere un certificato, ad esempio, per Grafana. Per questo sarà necessario un dominio e l'accesso al cluster dall'esterno. Ho un dominio e ho configurato il traffico inoltrando le porte 80 e 443 sul mio router domestico in base al servizio creato dell'ingress-controller:
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 23dLa porta 80 in questo caso è tradotta in 31303, e la 443 in 30498. (Le porte vengono generate casualmente, quindi le vostre saranno diverse.)
Ecco un esempio di certificato ():
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-stagingAggiungiamolo al cluster:
kubectl create -f cert-manager-grafana-certificate.yamlDopo questo apparirà la risorsa Ingress, attraverso la quale avverrà la validazione da parte di Let’s Encrypt:
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 Dopo che la validazione sarà completata, vedremo che la risorsa certificate è pronta, e nel segreto sopra indicato grafana-tls si trovano il certificato e la chiave. Possiamo subito controllare chi ha emesso il certificato:
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 X1Torniamo a Grafana. Dobbiamo apportare alcune modifiche al suo rilascio Helm, modificando le impostazioni per TLS in base al certificato creato.
Per questo, scarichiamo il chart, lo modifichiamo e aggiorniamo dalla directory locale:
helm pull --untar stable/grafana Modifichiamo il file grafana/values.yaml parametri TLS:
tls:
- secretName: grafana-tls
hosts:
- grafana.home.pi Inoltre, possiamo subito configurare Prometheus installato come datasource:
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus-server:80
access: proxy
isDefault: trueOra aggiorniamo il chart di Grafana dalla directory locale:
helm upgrade grafana --namespace monitoring ./grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"} Controlliamo che in Ingress grafana sia stato aggiunto il porto 443 e ci sia accesso tramite 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; includeSubDomainsPer dimostrare Grafana in azione, puoi scaricare e aggiungere . Ecco come appare:

Ti consiglio anche di aggiungere un dashboard per node exporter: mostrerà in dettaglio cosa succede con le "raspberries" (carico CPU, utilizzo della memoria, rete, disco, ecc.).
Dopo questo, considero che il cluster è pronto per accogliere e avviare applicazioni!
Nota sulla compilazione
Per la compilazione di applicazioni per architettura ARM ci sono almeno due opzioni. In primo luogo, è possibile compilare su un dispositivo ARM. Tuttavia, guardando l'utilizzo attuale di due Raspberry Pi, ho capito che non reggeranno nemmeno la compilazione. Quindi ho ordinato una nuova Raspberry Pi 4 (è più potente e ha 4 GB di RAM) — ho intenzione di compilare su di essa.
Il secondo opzione è la compilazione di un'immagine Docker multiarchitettura su una macchina più potente. Per questo c'è . Se l'applicazione è in un linguaggio compilato, sarà necessaria una cross-compilazione per ARM. Non descriverò tutte le impostazioni per questo percorso, poiché richiederebbe un articolo a parte. Utilizzando questo approccio, è possibile ottenere immagini "universali": Docker, eseguito su una macchina ARM, caricherà automaticamente l'immagine corrispondente all'architettura.
Conclusione
L'esperimento condotto ha superato tutte le mie aspettative: [almeno] un Kubernetes "vaniglia" con la base necessaria si comporta bene su ARM, e durante la sua configurazione sono emersi solo un paio di dettagli.
I Raspberry Pi 3B+ gestiscono carichi di lavoro sulla CPU, ma le loro schede SD sono un chiaro collo di bottiglia. I miei colleghi hanno suggerito che in alcune versioni esiste la possibilità di avviarsi da USB, dove è possibile collegare un SSD: allora la situazione probabilmente migliorerà.
Ecco un esempio di carico della CPU durante l'installazione di Grafana:

Per esperimenti e "per provare", a mio avviso, un cluster Kubernetes sui "Raspberry" trasmette molto meglio le sensazioni di utilizzo rispetto a Minikube, perché tutti i componenti del cluster si installano e funzionano "in modo professionale".
C'è l'idea di aggiungere al cluster l'intero ciclo CI/CD, realizzato interamente su Raspberry Pi. Sarei anche felice se qualcuno condividesse la propria esperienza nella configurazione di K8s su AWS Graviton.
P.S. Sì, il "production" potrebbe essere più vicino di quanto pensassi:

P.P.S.
Leggi anche nel nostro blog:
- «».
Fonte: habr.com
