
Di recente, una nota azienda ha annunciato di passare alla architettura ARM per la sua gamma di laptop. Sentendo questa notizia, mi sono ricordato: mentre rivedevo i prezzi di EC2 su AWS, ho notato i Graviton a un prezzo molto allettante. Il trucco, ovviamente, era che si trattava di ARM. All'epoca non pensavo che ARM fosse così serio...
Per me, quest'architettura è sempre stata associata a dispositivi mobili e altre diavolerie IoT. I "veri" server su ARM sembrano un po' insoliti, in qualche modo addirittura strani... Tuttavia, una nuova idea mi è venuta in mente, così in un weekend ho deciso di verificare cosa si può lanciare oggi su ARM. E per farlo, ho deciso di iniziare con qualcosa di vicino e famigliare: un cluster Kubernetes. Non un semplice "cluster" qualsiasi, ma tutto "in grande" per essere il più simile possibile a quello che sono abituato a vedere in produzione.
Secondo la mia concezione, il cluster deve essere accessibile da Internet, deve eseguire qualche applicazione web e deve avere almeno un sistema di monitoraggio. Per realizzare questa idea sarà necessaria una coppia (o più) di Raspberry Pi di almeno modello 3B+. La piattaforma per esperimenti potrebbe essere anche AWS, ma ero particolarmente interessato alle «lamanine» (che comunque erano rimaste inutilizzate). Quindi, installeremo su di esse un cluster Kubernetes con Ingress, Prometheus e Grafana.
Preparazione delle «lamanine»
Installazione dell'OS e SSH
Non mi sono soffermato molto sulla scelta dell'OS da installare: ho semplicemente preso l'ultima versione di Raspberry Pi OS Lite da . Qui è disponibile , tutte le operazioni devono essere eseguite su tutti i nodi del futuro cluster. Successivamente, sarà necessario eseguire le seguenti operazioni (anche su tutti i nodi).
Collegando il monitor e la tastiera, è necessario configurare preliminarmente la rete e SSH:
- Per il funzionamento del cluster, il master deve avere necessariamente un indirizzo IP statico, mentre sui nodi di lavoro è a discrezione. Ho preferito indirizzi statici ovunque per comodità di configurazione.
- L'indirizzo statico può essere configurato nel sistema operativo (nel file
/etc/dhcpcd.confesiste un esempio adeguato) oppure registrando il lease nel server DHCP utilizzato (nel mio caso — router domestico). - il server ssh si attiva semplicemente in raspi-config (opzioni di interfaccia → ssh).
Dopo di che si può effettuare il login tramite SSH (di default il login è pi, e la password è raspberry o quella che è stata cambiata) e continuare con le impostazioni.
Altre impostazioni
- Imposteremo il nome host. Nel mio esempio utilizzeremo
pi-controlepi-worker. - Controlliamo che il file system sia esteso all'intero disco (
df -h /). Se necessario, può essere esteso tramite raspi-config. - Cambiamos la password dell'utente predefinito in raspi-config.
- Disattiviamo il file di swap (è un requisito di Kubernetes; se sei interessato a maggiori dettagli su questo argomento, consulta ):
dphys-swapfile swapoff systemctl disable dphys-swapfile - Aggiorniamo i pacchetti all'ultima versione:
apt-get update && apt-get dist-upgrade -y - Installa Docker e pacchetti aggiuntivi:
apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistentDurante l'installazione di
iptables-persistentsarà necessario salvare le impostazioni di iptables per ipv4, e nel file/etc/iptables/rules.v4— aggiungere le 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 - Ora tutto è pronto per l'installazione del cluster Kubernetes.
Installazione di Kubernetes
Installazione di Kubernetes
A questo punto ho messo da parte le mie e le nostre risorse aziendali relative all'automazione dell'installazione e configurazione del cluster K8s. Invece, utilizziamo la documentazione ufficiale di (leggermente integrata con commenti e abbreviazioni).
Aggiungiamo il repository di 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 updateNella documentazione viene poi proposto di installare il CRI (container runtime interface). Poiché Docker è già installato, procediamo oltre 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. E qui c'è un punto importante: il pacchetto kubernetes-cni per qualche motivo non crea per impostazione predefinita la directory per le impostazioni degli interfaccia CNI, quindi ho dovuto crearla manualmente:
mkdir -p /etc/cni/net.dPer il funzionamento del backend di rete, di cui parleremo in seguito, è necessario installare un plugin per CNI. Ho scelto il plugin portmap a me familiare (per l'elenco completo vedere 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 di Kubernetes
Nodo con control plane
L'installazione del cluster è piuttosto semplice. Per accelerare questo processo e verificare che le immagini di Kubernetes siano disponibili, è possibile eseguire preliminarmente:
kubeadm config images pullAdesso procediamo all'installazione vera e propria: 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 noti che le sottoreti per i servizi e i pod non devono sovrapporsi tra loro né con le reti esistenti.
Alla fine verrà mostrato un messaggio che informa che tutto è andato bene e verrà anche indicato come unire i nodi di lavoro al control plane:
Il tuo piano di controllo 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 qualsiasi numero di nodi del piano di controllo 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
--control-plane --certificate-key 72a3c0a14c627d6d7fdade1f4c8d7a41b0fac31b1faf0d8fdf9678d74d7d2403
Si prega di notare che la chiave di certificato fornisce accesso a dati sensibili del cluster, tienila segreta!
Come misura di sicurezza, i certificati caricati verranno eliminati tra due ore; se necessario, puoi utilizzare
"kubeadm init phase upload-certs --upload-certs" per ricaricare i certificati successivamente.
Puoi quindi unire qualsiasi numero 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:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050Seguiamo le raccomandazioni per aggiungere la configurazione per l'utente. Consiglio inoltre 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
NOME STATO RUOLI ETÀ VERSIONE
pi-control NonPronto master 29s v1.18.6Configurazione di rete
Successivamente, come indicato nel messaggio dopo l'installazione, sarà necessario impostare la rete nel cluster. La documentazione offre la possibilità di scegliere tra Calico, Cilium, contiv-vpp, Kube-router e Weave Net… Qui mi sono discostato dalle istruzioni ufficiali e ho scelto l'opzione che mi è più familiare e comprensibile: in modalità host-gw (per ulteriori dettagli sugli backend disponibili, vedere ).
Installarlo nel cluster è piuttosto semplice. Per iniziare, scarichiamo i manifesti:
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml Poi cambiamo nelle impostazioni il tipo da vxlan con 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:
NOME STATO RUOLI ETÀ VERSIONE
pi-control Pronto master 2m v1.18.6Aggiunta di un nodo di lavoro
Ora possiamo aggiungere un worker. Per farlo, 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 RUOLI ETA 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 via una di esse solo per il control plane. Così ho rimosso la taint installata automaticamente dal nodo pi-control, eseguendo:
root@pi-control:~# kubectl edit node pi-control… e rimuovendo le righe:
- effect: NoSchedule
key: node-role.kubernetes.io/masterRiempimento del cluster con il minimo necessario
Prima di tutto, avremo bisogno di Helm. Certo, è possibile fare tutto anche senza di esso, ma Helm consente di configurare alcuni componenti a proprio piacimento senza dover modificare i file. E, di fatto, è solo un file binario che "non chiede nulla".
Quindi, andiamo su nella sezione docs/installation e 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 installiamo i componenti infrastrutturali secondo il progetto:
- Ingress controller;
- Prometheus;
- Grafana;
- cert-manager.
Ingress controller
Il primo componente — Ingress controller — si installa abbastanza facilmente ed è pronto all'uso "out of the box". È sufficiente accedere al e 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, in quel momento il "raspberry" ha iniziato a risentire e a lottare con IOPS del disco. Infatti, insieme all'Ingress controller vengono installate molte risorse, si effettuano 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, oppure le schede SD non sono sufficienti per tale carico. Tuttavia, dopo circa 5 minuti tutto è partito.
È stato creato un namespace e in esso è apparso il controller e tutto ciò che gli serve:
root@pi-control:~# kubectl -n ingress-nginx get pod
NOME PRONTO STATO RIAVVI 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
Gli altri due componenti si installano abbastanza facilmente tramite Helm dal chart repo.
Troviamo Prometheus, creiamo un namespace e installiamo 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 alcuna classe di storage 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 è decisamente eccessivo.
Pertanto, creeremo 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 il PV deve essere creata in anticipo sul disco del nodo al quale vogliamo collegare Prometheus: nell'esempio è specificato nodeAffinity per hostname pi-worker e su di 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 mi sono imbattuto per la prima volta nel problema dell'architettura ARM. Kube-state-metrics, che viene installato per default nel chart di Prometheus, ha rifiutato di avviarsi. Ha restituito l'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 utilizzata un'immagine del progetto CoreOS, che non è disponibile 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 fare un po' di ricerche e trovare, per 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 partito:
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 e cert-manager
Per grafici e dashboard installiamo Grafana:
helm install grafana --namespace monitoring stable/grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}Alla fine dell'output ci mostreranno come ottenere la password di accesso:
kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echoPer ordinare i certificati installiamo cert-manager. Per la sua installazione ci rivolgiamo a , che offre i comandi appropriati 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 autofirmati per uso domestico, questo è più che sufficiente. Se invece è necessario ottenere lo stesso Let’s Encrypt, è necessario configurare anche un issuer di cluster. Maggiori dettagli sono disponibili nel nostro articolo «».
Personalmente, ho optato per l'opzione presente , decidendo che l'opzione di staging di LE fosse sufficiente. Modifico l'e-mail nell'esempio, salvo in un file e aggiungo al cluster ():
kubectl create -f cert-manager-cluster-issuer.yamlOra posso ordinare un certificato, ad esempio per Grafana. Per questo sarà necessario un dominio e l'accesso al cluster dall'esterno. Ho già un dominio e ho configurato il traffico inoltrando le porte 80 e 443 sul mio router domestico secondo il servizio ingress-controller creato:
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 23dIn questo caso, la porta 80 è mappata su 31303 e la 443 su 30498. (Le porte vengono generate casualmente, quindi potrebbero essere diverse per voi.)
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, verrà creato una risorsa Ingress, attraverso la quale verrà effettuata la validazione con 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 è completata, vedremo che la risorsa certificate è pronta, e nel segreto sopra menzionato grafana-tls si trova 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, aggiornando le impostazioni per TLS in base al certificato creato.
Per fare ciò, scarichiamo il chart, apportiamo le modifiche e lo aggiorniamo dalla directory locale:
helm pull --untar stable/grafana Modifichiamo il file grafana/values.yaml impostazioni TLS:
tls:
- secretName: grafana-tls
hosts:
- grafana.home.pi Qui possiamo anche 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 Grafana dalla directory locale:
helm upgrade grafana --namespace monitoring ./grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"} Verifichiamo 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 <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; 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 sta succedendo con le "mali" (carico CPU, utilizzo della memoria, rete, disco, ecc.).
Dopo di che, considero che il cluster è pronto per ricevere e avviare applicazioni!
Nota sulla compilazione
Per compilare applicazioni per architettura ARM, ci sono almeno due opzioni. Prima di tutto, puoi compilare su un dispositivo ARM. Tuttavia, guardando l'attuale utilizzo di due Raspberry Pi, ho capito che non ce la faranno nemmeno a farlo. Quindi ho ordinato una nuova Raspberry Pi 4 (è più potente e ha ben 4 GB di RAM) — ho intenzione di compilare su di essa.
La seconda opzione è la creazione di un'immagine Docker multiarchitetturale su una macchina più potente. Per fare ciò, c'è . Se l'applicazione è scritta in un linguaggio compilabile, sarà necessaria una cross-compilation per ARM. Non descriverò tutte le impostazioni per questo percorso, poiché richiederebbe un articolo a parte. Adottando questo approccio, si possono ottenere immagini "universali": Docker, eseguito su una macchina ARM, caricherà automaticamente l'immagine corrispondente all'architettura.
Conclusione
L'esperimento effettuato ha superato tutte le mie aspettative: [almeno] un Kubernetes "vanilla" con il suo database si comporta dignitosamente su ARM, e durante la sua configurazione sono emersi solo alcune piccole questioni.
I Raspberry Pi 3B+ reggono il carico della CPU, ma le loro schede SD rappresentano un chiaro collo di bottiglia. I colleghi mi hanno consigliato che in alcune versioni c'è la possibilità di avviarsi da USB, dove è possibile collegare un SSD: allora la situazione dovrebbe migliorare.
Ecco un esempio del carico della CPU durante l'installazione di Grafana:

Per esperimenti e per "provare", ritengo che un cluster Kubernetes sui "Raspberry" trasmetta di gran lunga meglio le sensazioni di utilizzo rispetto a Minikube, poiché tutti i componenti del cluster si installano e funzionano "professionalmente".
C'è un'idea di aggiungere all'cluster l'intero ciclo CI/CD, completamente implementato su Raspberry Pi. Inoltre, sarei felice se qualcuno potesse condividere la propria esperienza riguardo la configurazione di K8s su AWS Graviton.
P.S. Sì, la "produzione" potrebbe essere più vicina di quanto pensassi:

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