
Recent, o companie cunoscută a anunțat că își va muta linia de laptopuri pe arhitectura ARM. Când am auzit această veste, mi-am amintit că, uitându-mă pentru a nu știu câta oară la prețurile de pe EC2 în AWS, am observat Graviton-urile cu un preț foarte atrăgător. Desigur, capcana era că acestea sunt ARM. Atunci nu mi-a trecut prin cap că ARM este ceva serios...
Pentru mine, această arhitectură a fost întotdeauna specifică dispozitivelor mobile și altor gadget-uri IoT. Serverele „adevărate” pe ARM mi se păreau oarecum neobișnuite, chiar stranii în anumite privințe... Totuși, o idee nouă s-a așezat în minte, așa că într-o zi de weekend am decis să verific ce se poate rula astăzi pe ARM. Pentru asta, am decis să încep cu ceva familiar - un cluster Kubernetes. Și nu un simplu „cluster” oarecare, ci unul „serios”, pentru a-l face cât mai apropiat de ceea ce sunt obișnuit să văd în producție.
Conform planului meu, clusterul ar trebui să fie accesibil de pe internet, să ruleze o anumită aplicație web și să aibă cel puțin monitorizare. Pentru a implementa această idee, va fi nevoie de câteva (sau mai multe) Raspberry Pi, model 3B sau superior. Locul pentru experimente ar putea fi AWS, dar m-au interesat exact „malițele” (care oricum stăteau degeaba). Așadar, vom desfășura pe ele un cluster Kubernetes cu Ingress, Prometheus și Grafana.
Pregătirea „malițelor”
Instalarea sistemului de operare și SSH
În alegerea sistemului de operare pentru instalare, nu m-am complicat foarte mult: am luat pur și simplu cea mai recentă versiune a Raspberry Pi OS Lite de pe . Acolo este disponibilă , toate acțiunile din care trebuie îndeplinite pe toate nodurile viitorului cluster. Apoi, va fi necesar să efectuez următoarele manevre (de asemenea, pe toate nodurile).
Conectând monitorul și tastatura, trebuie să pregătesc rețeaua și SSH:
- Pentru ca clusterul să funcționeze, masterul trebuie să aibă neapărat o adresă IP statică, iar pe nodurile de lucru - la discreția utilizatorului. Eu am preferat adrese statice peste tot din rațiuni de confort în configurare.
- Adresa statică poate fi configurată în sistemul de operare (în fișierul
/etc/dhcpcd.confeste un exemplu potrivit) sau prin fixarea lease-ului pe serverul DHCP al routerului folosit (în cazul meu - routerul de acasă). - ssh-server se activează pur și simplu în raspi-config (opțiuni interfacing → ssh).
După aceasta, te poți conecta prin SSH (în mod implicit, utilizatorul este pi, iar parola este raspberry or the one that was changed) and continue with the settings.
Other settings
- Let's set the hostname. In my example, I will use
pi-controlșipi-worker. - Let's check that the file system is expanded to the entire disk (
df -h /). If necessary, it can be expanded using raspi-config. - Let's change the default user password in raspi-config.
- We will disable the swap file (this is a requirement for Kubernetes; if you're interested in details on this topic, see ):
dphys-swapfile swapoff systemctl disable dphys-swapfile - We will update the packages to the latest versions:
apt-get update && apt-get dist-upgrade -y - We will install Docker and additional packages:
apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistentDuring the installation
iptables-persistentyou will need to save the iptables settings for ipv4, and in the file/etc/iptables/rules.v4— add rules to theFORWARDchain, like this:# 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 - All that's left is to reboot.
Now everything is ready for the installation of the Kubernetes cluster.
Kubernetes Installation
At this stage, I intentionally postponed all my and our corporate developments on automating the installation and configuration of the K8s cluster. Instead, we will use the official documentation from (slightly supplemented with comments and abbreviations).
Let's add the Kubernetes repository:
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 updateNext, the documentation suggests installing CRI (container runtime interface). Since Docker is already installed, we will move forward and install the core components:
sudo apt-get install -y kubelet kubeadm kubectl kubernetes-cni At the stage of installing core components, I immediately added kubernetes-cni, which is essential for the functioning of the cluster. And there is an important point: the package kubernetes-cni for some reason does not create the default directory for CNI settings, so I had to create it manually:
mkdir -p /etc/cni/net.dTo operate the network backend, which will be discussed below, it is necessary to install the plugin for CNI. I chose the familiar and understandable portmap plugin (for a complete list, see ):
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/ ./portmapKubernetes Setup
Node with control plane
Setting up the cluster itself is quite simple. To speed up this process and verify that the Kubernetes images are accessible, you can execute:
kubeadm config images pullAcum efectuăm instalarea efectivă — inițializăm control plane-ul cluster-ului:
kubeadm init --pod-network-cidr=10.1.0.0/16 --service-cidr=10.2.0.0/16 --upload-certsAveți grijă ca subrețelele pentru servicii și poduri să nu se suprapună între ele și cu rețelele existente.
La final, ni se va afișa un mesaj că totul este în regulă și ne va indica cum să alăturăm nodurile de lucru la control plane:
Control-plane-ul Kubernetes a fost inițializat cu succes!
Pentru a începe să folosiți cluster-ul, trebuie să rulați următoarele comenzi ca utilizator obișnuit:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Acum ar trebui să desfășurați o rețea pod pentru cluster.
Rulați "kubectl apply -f [podnetwork].yaml" cu una dintre opțiunile listate la:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Puteți acum să alăturați oricâte noduri de control-plane rulând următoarea comandă pe fiecare dintre ele ca utilizator root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050
--control-plane --certificate-key 72a3c0a14c627d6d7fdade1f4c8d7a41b0fac31b1faf0d8fdf9678d74d7d2403
Vă rugăm să rețineți că cheia de certificat oferă acces la date sensibile ale cluster-ului, păstrați-o secretă!
Ca măsură de siguranță, certificatele încărcate vor fi șterse în două ore; Dacă este necesar, puteți folosi
"kubeadm init phase upload-certs --upload-certs" pentru a reîncărca certificatele ulterior.
Apoi puteți alătura oricâte noduri de lucru rulând următoarea comandă pe fiecare dintre ele ca utilizator root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050Vom urma recomandările pentru adăugarea configurației pentru utilizator. De asemenea, vă recomand să adăugați completarea automată pentru kubectl:
kubectl completion bash > ~$HOME/.kube/completion.bash.inc
printf "
# Completarea shell-ului Kubectl
source '$HOME/.kube/completion.bash.inc'
" >> $HOME/.bash_profile
source $HOME/.bash_profileÎn această etapă, deja putem vedea primul nod în cluster (deși nu este gata încă):
root@pi-control:~# kubectl get no
NAME STATUS ROLES AGE VERSION
pi-control NotReady master 29s v1.18.6Configurarea rețelei
În continuare, așa cum s-a menționat în mesajul de după instalare, va trebui să instalăm rețeaua în cluster. În documentație se oferă alegerea dintre Calico, Cilium, contiv-vpp, Kube-router și Weave Net… Eu am ales o opțiune mai familiară și mai ușor de înțeles: în modul host-gw (mai multe detalii despre backend-urile disponibile găsiți în ).
Instalarea sa în cluster este destul de simplă. Mai întâi — descărcăm manifestele:
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml Apoi, schimbăm în setări tipul din vxlan pe host-gw:
sed -i 's/vxlan/host-gw/' kube-flannel.yml… și subrețeaua pod-urilor — din valoarea implicită în cea specificată la inițializarea cluster-ului:
sed -i 's#10.244.0.0/16#10.1.0.0/16#' kube-flannel.ymlDupă aceasta, creăm resursele:
kubectl create -f kube-flannel.yml Gata! În scurt timp, primul nod K8s va trece în statutul Gata:
NUME STATUT ROLURI VÂRSTĂ VERSIUNE
pi-control Gata master 2m v1.18.6Adăugarea unui nod de lucru
Acum putem adăuga un worker. Pentru aceasta, pe acesta — după ce a fost instalat Kubernetes conform scenariului descris mai sus — trebuie pur și simplu să executăm comanda obținută anterior:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050Pe aceasta putem considera că clusterul este pregătit:
root@pi-control:~# kubectl get no
NUME STATUT ROLURI VÂRSTĂ VERSIUNE
pi-control Gata master 28m v1.18.6
pi-worker Gata 2m8s v1.18.6Am avut doar două Raspberry Pi la dispoziție, așa că nu am vrut să aloc una pentru control plane doar pentru că nu voiam să o dedic control plane-ului. Așa că am eliminat taint-ul instalat automat de pe nodul pi-control, executând:
root@pi-control:~# kubectl edit node pi-control… și am șters liniile:
- effect: NoSchedule
key: node-role.kubernetes.io/masterUmplerea clusterului cu minimum necesar
În primul rând, avem nevoie de Helm. Desigur, se poate face totul și fără el, dar Helm permite, practic, să configurăm unele componente așa cum dorim fără a modifica fișierele. Și, de fapt, este pur și simplu un fișier binar care "nu cere multe".
Așadar, accesați în secțiunea docs/installation și executați comanda de acolo:
curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bashDupă aceasta, adăugăm repositoriu de chart-uri:
helm repo add stable https://kubernetes-charts.storage.googleapis.com/Acum să instalăm componentele infrastructurii conform planului:
- Ingress controller;
- Prometheus;
- Grafana;
- cert-manager.
Ingress controller
Primul component — Ingress controller — se instalează destul de simplu și este gata de utilizare "din cutie". Pentru aceasta, este suficient să accesați și să executați comanda de instalare de acolo:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yamlCu toate acestea, în acel moment „zmeura” a început să se agite și să se confrunte cu IOPS-ul pe disc. Motivul este că, împreună cu Ingress controller-ul, se instalează un număr mare de resurse, se efectuează multe interogări către API și, în consecință, se scrie o cantitate mare de date în etcd. În general, fie cardul de memorie de clasa 10 nu este foarte performant, fie pur și simplu nu avem suficient pentru o astfel de sarcină. Cu toate acestea, după aproximativ 5 minute, totul a pornit.
A fost creat un namespace și în acesta a apărut controller-ul și tot ce îi era necesar:
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
Următoarele două componente sunt destul de ușor de instalat prin Helm din chart repo.
Găsim Prometheus, creăm un namespace și îl instalăm în:
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"}În mod implicit, Prometheus solicită 2 discuri: unul pentru datele proprii ale Prometheus și unul pentru datele AlertManager. Deoarece în cluster nu a fost creat un storage class, discurile nu vor fi solicitate, iar pod-urile nu se vor lansa. Pentru instalările pe bare metal Kubernetes, de obicei folosim Ceph rbd, însă în cazul Raspberry Pi, aceasta este o exagerare.
Așadar, să creăm un storage local simplu pe hostpath. Manifestele PV (persistent volume) pentru prometheus-server și prometheus-alertmanager sunt combinate într-un fișier prometheus-pv.yaml în . Directorul pentru PV trebuie să fie creat anterior pe discul acelui nod la care dorim să atașăm Prometheus: în exemplu este specificat nodeAffinity după hostname pi-worker și pe el au fost create directoare /data/localstorage/prometheus-server și /data/localstorage/prometheus-alertmanager.
Descărcăm (clonăm) manifestul și îl adăugăm în Kubernetes:
kubectl create -f prometheus-pv.yamlÎn acest stadiu, m-am confruntat pentru prima dată cu problema arhitecturii ARM. Kube-state-metrics, care este instalat în mod implicit în chartul Prometheus, a refuzat să pornească. A generat o eroare:
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"Problema este că pentru kube-state-metrics este folosit un imagine din proiectul CoreOS, care nu este construit pentru 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.7A trebuit să caut puțin pe Google și am găsit, de exemplu, . Pentru a o folosi, vom actualiza release-ul specificând ce imagine să folosim pentru 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.6Verificăm că totul s-a lansat:
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 și cert-manager
Pentru grafice și dashboard-uri instalăm Grafana:
helm install grafana --namespace monitoring stable/grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}La finalul ieșirii ne va arăta cum să obținem parola de acces:
kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echoPentru a comanda certificate, vom instala cert-manager. Pentru instalarea acestuia ne vom adresa la , care oferă comenzile corespunzătoare pentru 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=truePentru certificatele autofirmate în utilizarea personală, acest lucru este suficient. Dacă dorim să obținem același Let’s Encrypt, va trebui să configurăm și un cluster issuer. Detalii despre acest lucru pot fi găsite în articolul nostru „».
Eu am optat pentru varianta din , hotărând că varianta staging a LE va fi suficientă. Schimbăm emailul în exemplu, salvăm în fișier și adăugăm în cluster ():
kubectl create -f cert-manager-cluster-issuer.yamlAcum putem comanda un certificat, de exemplu, pentru Grafana. Pentru aceasta, va fi necesar un domeniu și acces la cluster din exterior. Eu am domeniu, iar traficul l-am configurat prin redirecționarea porturilor 80 și 443 pe routerul meu de acasă conform serviciului ingress-controller creat:
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 23dPortul 80 este tradus în acest caz în 31303, iar 443 — în 30498. (Porturile sunt generate aleator, așa că la voi vor fi altele.)
Iată un exemplu 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-stagingAdăugăm în cluster:
kubectl create -f cert-manager-grafana-certificate.yamlDupă aceasta, va apărea un resurs Ingress, prin care va avea loc validarea de către 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 După finalizarea validării, vom vedea că resursa certificat este pregătit, iar în secretul menționat mai sus grafana-tls – certificat și cheie. Putem verifica imediat cine a emis certificatul:
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 X1Să revenim la Grafana. Va trebui să modificăm puțin release-ul său Helm, ajustând setările pentru TLS conform certificatului creat.
Pentru asta, descărcăm chart-ul, facem modificările și actualizăm din directorul local:
helm pull --untar stable/grafana Edităm în fișierul grafana/values.yaml setările TLS:
tls:
- secretName: grafana-tls
hosts:
- grafana.home.pi Aici putem configura imediat Prometheus instalat ca datasource:
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus-server:80
access: proxy
isDefault: trueAcum din directorul local, actualizăm chart-ul Grafana:
helm upgrade grafana --namespace monitoring ./grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"} Verificăm că în Ingress grafana s-a adăugat portul 443 și există acces prin 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; includeSubDomainsPentru a demonstra Grafana în acțiune, puteți descărca și adăuga . Așa arată:

De asemenea, recomand să adăugați un dashboard pentru node exporter: va arăta în detaliu ce se întâmplă cu „malițile” (încărcarea CPU, utilizarea memoriei, rețelei, discului etc.).
După aceasta, consider că clusterul este pregătit să primească și să ruleze aplicații!
Notă despre construcție
Pentru a construi aplicații pentru arhitectura ARM, există cel puțin două opțiuni. În primul rând, puteți construi pe un dispozitiv ARM. Totuși, după ce am observat utilizarea curentă a două Raspberry Pi, am realizat că nici măcar construcția nu ar rezista. Așa că mi-am comandat o nouă Raspberry Pi 4 (care este mai puternică și are nu mai puțin de 4 GB de memorie) — intenționez să construiesc pe ea.
A doua opțiune este construirea unei imagini Docker multi-arhitectură pe un sistem mai puternic. Pentru asta, există . Dacă aplicația este într-un limbaj compilat, va fi necesară o cross-compilare pentru ARM. Nu voi descrie toate setările pentru această abordare, deoarece ar necesita un articol separat. Implementând această metodă, se pot obține imagini „universale”: Docker, care rulează pe o mașină ARM, va descărca automat imaginea corespunzătoare arhitecturii.
Concluzie
Experimentul efectuat a depășit toate așteptările mele: [cel puțin] „vanilla” Kubernetes cu baza necesară se descurcă bine pe ARM, iar în configurarea sa au apărut doar câteva mici nuanțe.
Raspberry Pi 3B+ suportă sarcinile de CPU, însă cardurile SD sunt un punct de bottleneck evident. Colegii mi-au sugerat că în unele versiuni există posibilitatea de a porni de pe USB, unde se poate conecta un SSD: atunci, cel mai probabil, situația se va îmbunătăți.
Iată un exemplu de utilizare a CPU în timpul instalării Grafana:

Pentru experimente și „să încercăm”, din punctul meu de vedere, un cluster Kubernetes pe „mici” oferă o experiență mult mai autentică de utilizare decât același Minikube, deoarece toate componentele cluster-ului sunt instalate și funcționează „serios”.
În perspectiva viitoare, am ideea de a adăuga întregul ciclu CI/CD la cluster, implementat complet pe Raspberry Pi. De asemenea, aș fi mulțumit dacă cineva ar împărtăși experiența sa în configurarea K8s pe AWS Graviton.
P.S. Da, „production” ar putea fi mai aproape decât credeam:

P.P.S.
Citiți și în blogul nostru:
- «».
Sursa: habr.com
