Kubernetes complet de la zero pe Raspberry Pi

Kubernetes complet de la zero pe Raspberry Pi

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 site-ul oficial. Acolo este disponibilă documentația de instalare, 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:

  1. 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.
  2. Adresa statică poate fi configurată în sistemul de operare (în fișierul /etc/dhcpcd.conf este un exemplu potrivit) sau prin fixarea lease-ului pe serverul DHCP al routerului folosit (în cazul meu - routerul de acasă).
  3. 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

  1. Let's set the hostname. In my example, I will use pi-control și pi-worker.
  2. Let's check that the file system is expanded to the entire disk (df -h /). If necessary, it can be expanded using raspi-config.
  3. Let's change the default user password in raspi-config.
  4. We will disable the swap file (this is a requirement for Kubernetes; if you're interested in details on this topic, see issue #53533):
    dphys-swapfile swapoff
    systemctl disable dphys-swapfile
  5. We will update the packages to the latest versions:
    apt-get update && apt-get dist-upgrade -y
  6. We will install Docker and additional packages:
    apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistent

    During the installation iptables-persistent you will need to save the iptables settings for ipv4, and in the file /etc/iptables/rules.v4 — add rules to the FORWARDchain, 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
  7. 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 kubernetes.io (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 update

Next, 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.d

To 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 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

Kubernetes 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 pull

Acum 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-certs

Aveț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:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050

Vom 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.6

Configurarea 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: flannel în modul host-gw (mai multe detalii despre backend-urile disponibile găsiți în documentația proiectului).

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.yml

După 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.6

Adă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:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050

Pe 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.6

Am 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/master

Umplerea 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 helm.sh în secțiunea docs/installation și executați comanda de acolo:

curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash

După 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 secțiunea bare-metal de pe site-ul ș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.yaml

Cu 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          48s

Prometheus

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 Repozițiile Git cu exemple pentru articol. 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.7

A trebuit să caut puțin pe Google și am găsit, de exemplu, această imagine. 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.6

Verifică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          4m52s

Grafana ș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 ; echo

Pentru a comanda certificate, vom instala cert-manager. Pentru instalarea acestuia ne vom adresa la documentation, 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=true

Pentru 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 „Certificate SSL de la Let’s Encrypt cu cert-manager în Kubernetes».

Eu am optat pentru varianta din exemplul din documentație, 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 (cert-manager-cluster-issuer.yaml):

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

Acum 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   23d

Portul 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 (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

Adăugăm în cluster:

kubectl create -f cert-manager-grafana-certificate.yaml

După 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 X1

Să 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: true

Acum 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; includeSubDomains

Pentru a demonstra Grafana în acțiune, puteți descărca și adăuga dashboard pentru kube-state-metrics. Așa arată:

Kubernetes complet de la zero pe Raspberry Pi

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ă extensia docker buildx. 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:

Kubernetes complet de la zero pe Raspberry Pi

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:

Kubernetes complet de la zero pe Raspberry Pi

P.P.S.

Citiți și în blogul nostru:

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster