
Niedawno jedna znana firma ogłosiła, że przenosi swoją linię laptopów na architekturę ARM. Kiedy usłyszałem tę wiadomość, przypomniałem sobie, przeglądając po raz kolejny ceny EC2 w AWS, zwróciłem uwagę na Graviton’y w bardzo atrakcyjnej cenie. Zgadza się, problemem była architektura ARM. Wtedy nawet nie podejrzewałem, jak poważna może być ARM...
Dla mnie ta architektura zawsze była domeną urządzeń mobilnych i różnych gadżetów IoT. "Prawdziwe" serwery na ARM — to jakoś dziwne, a miejscami nawet szalone... Jednak nowa myśl zafiksowała się w mojej głowie, dlatego w jeden z weekendów postanowiłem sprawdzić, co w ogóle można dzisiaj uruchomić na ARM. I dla tego zdecydowałem się zacząć od czegoś bliskiego i znajomego — klastra Kubernetes. I to nie jakiegoś wirtualnego „klastra”, ale całkiem „na poważnie”, aby był maksymalnie podobny do tego, co znam z produkcji.
Zamysł był taki, aby klaster był dostępny z internetu, w nim miała działać jakaś aplikacja internetowa i przynajmniej powinien być monitoring. Do realizacji tego pomysłu potrzebne będą dwa (lub więcej) Raspberry Pi w co najmniej modelu 3B+. Miejscem do eksperymentów mogłaby być również AWS, ale mnie interesowały głównie „maliny” (które i tak leżały bezczynnie). Zatem zbudujemy na nich klaster Kubernetes z Ingress, Prometheus i Grafana.
Przygotowanie „malin”
Instalacja systemu operacyjnego i SSH
Nie przesadzałem z wyborem systemu operacyjnego do instalacji: po prostu wziąłem najnowszą wersję Raspberry Pi OS Lite z . Tam też dostępna jest , wszystkie działania należy wykonać na wszystkich węzłach przyszłego klastra. Następnie należy wykonać następujące manipulacje (też na wszystkich węzłach).
Po podłączeniu monitora i klawiatury, musisz najpierw skonfigurować sieć i SSH:
- Aby klaster działał, master musi mieć statyczny adres IP, a węzły robocze — według uznania. Preferowałem statyczne adresy wszędzie dla wygody konfiguracji.
- Statyczny adres można skonfigurować w OS (w pliku
/etc/dhcpcd.confjest odpowiedni przykład) lub poprzez zablokowanie lease w DHCP serwerze używanego (w moim przypadku — domowego) routera. - serwer ssh można po prostu włączyć w raspi-config (opciones interfejsu → ssh).
Po tym możesz już zalogować się przez SSH (domyślnie login to pi, a hasło to raspberry lub to, na które zostało zmienione) i kontynuować konfigurację.
Inne ustawienia
- Ustaw nazwę hosta. W moim przykładzie użyjemy
pi-controlipi-worker. - Sprawdźmy, czy system plików został rozszerzony na cały dysk (
df -h /). W razie potrzeby można go rozszerzyć za pomocą raspi-config. - Zmienimy domyślne hasło użytkownika w raspi-config.
- Wyłączymy plik swap (takie są wymagania Kubernetes; jeśli chcesz poznać szczegóły na ten temat, zob. ):
dphys-swapfile swapoff systemctl disable dphys-swapfile - Zaktualizujemy pakiety do najnowszych wersji:
apt-get update && apt-get dist-upgrade -y - Zainstalujemy Dockera oraz dodatkowe pakiety:
apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistentPodczas instalacji
iptables-persistentbędzie wymagane zapisanie ustawień iptables dla ipv4, a w pliku/etc/iptables/rules.v4— dodaj reguły do łańcuchaFORWARD, w ten sposób:# 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 - Teraz wystarczy tylko się zrestartować.
Teraz wszystko jest gotowe do instalacji klastra Kubernetes.
Instalacja Kubernetes
Na tym etapie celowo odłożyłem wszystkie nasze korporacyjne rozwiązania dotyczące automatyzacji instalacji i konfiguracji klastra K8s. Zamiast tego, skorzystamy z oficjalnej dokumentacji z (lekko uzupełnione komentarzami i skrótami).
Dodajemy repozytorium 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 updateNastępnie dokumentacja sugeruje zainstalowanie CRI (interfejs czasu uruchomienia kontenera). Ponieważ Docker jest już zainstalowany, przechodzimy dalej i instalujemy podstawowe komponenty:
sudo apt-get install -y kubelet kubeadm kubectl kubernetes-cni Na etapie instalacji podstawowych komponentów od razu dodałem kubernetes-cni, który jest niezbędny do działania klastra. Jest tu ważny aspekt: pakiet kubernetes-cni z jakiegoś powodu nie tworzy domyślnego katalogu dla konfiguracji interfejsów CNI, więc musiałem utworzyć go ręcznie:
mkdir -p /etc/cni/net.dAby uruchomić backend sieciowy, o którym mowa poniżej, należy doinstalować wtyczkę dla CNI. Wybrałem dobrze znajomą mi wtyczkę portmap (pełna lista dostępna w ):
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/ ./portmapKonfiguracja Kubernetes
Węzeł z control plane
Instalacja samego klastra przebiega dosyć prosto. Aby przyspieszyć ten proces oraz sprawdzić, że obrazy Kubernetes są dostępne, można wstępnie wykonać:
kubeadm config images pullTeraz przeprowadzamy samą instalację — inicjujemy control plane klastra:
kubeadm init --pod-network-cidr=10.1.0.0/16 --service-cidr=10.2.0.0/16 --upload-certsZwróć uwagę, że podsieci dla usług i pod’ów nie powinny się ze sobą krzyżować ani z istniejącymi sieciami.
Na koniec zobaczymy komunikat informujący, że wszystko jest w porządku, a także wskazówki, jak dołączyć węzły robocze do control plane:
Twój kontroler Kubernetes został pomyślnie zainicjowany!
Aby zacząć korzystać ze swojego klastra, musisz uruchomić następujące polecenia jako zwykły użytkownik:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Teraz powinieneś wdrożyć sieć podów do klastra.
Uruchom "kubectl apply -f [podnetwork].yaml" z jedną z opcji wymienionych na:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Możesz teraz dołączyć dowolną liczbę węzłów control-plane, uruchamiając polecenie na każdym z nich jako root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050
--contrl-plane --certificate-key 72a3c0a14c627d6d7fdade1f4c8d7a41b0fac31b1faf0d8fdf9678d74d7d2403
Pamiętaj, że klucz certyfikatu daje dostęp do wrażliwych danych klastra, trzymaj go w tajemnicy!
Dla bezpieczeństwa, przesłane certyfikaty zostaną usunięte w ciągu dwóch godzin; W razie potrzeby możesz użyć
"kubeadm init phase upload-certs --upload-certs", aby załadować certyfikaty ponownie.
Następnie możesz dołączyć dowolną liczbę węzłów roboczych, uruchamiając następujące polecenie na każdym z nich jako root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050Wykonaj zalecenia dotyczące dodania konfiguracji dla użytkownika. A przy okazji zalecam od razu dodać autouzupełnianie dla kubectl:
kubectl completion bash > $HOME/.kube/completion.bash.inc
printf "
# Autouzupełnianie powłoki Kubectl
source '$HOME/.kube/completion.bash.inc'
" >> $HOME/.bash_profile
source $HOME/.bash_profileNa tym etapie można już zobaczyć pierwszy węzeł w klastrze (choć jeszcze nie jest gotowy):
root@pi-control:~# kubectl get no
NAME STATUS ROLES AGE VERSION
pi-control NotReady master 29s v1.18.6Konfiguracja sieci
Jak zostało powiedziane w komunikacie po instalacji, konieczne będzie zainstalowanie sieci w klastrze. W dokumentacji oferują wybór z Calico, Cilium, contiv-vpp, Kube-router i Weave Net… Tutaj odstąpiłem od oficjalnej instrukcji i wybrałem bardziej znajomą i zrozumiałą dla mnie opcję: w trybie host-gw (więcej o dostępnych backendach patrz w ).
Zainstalowanie go w klastrze jest dość proste. Na początek — pobieramy manifesty:
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml Następnie zmieniamy w ustawieniach typ z vxlan na host-gw:
sed -i 's/vxlan/host-gw/' kube-flannel.yml… oraz podsieć podów — z wartości domyślnej na tę, która została podana podczas inicjalizacji klastra:
sed -i 's#10.244.0.0/16#10.1.0.0/16#' kube-flannel.ymlPo tym tworzymy zasoby:
kubectl create -f kube-flannel.yml Gotowe! Po chwili pierwszy węzeł K8s przejdzie w status Gotowy:
NAME STATUS ROLES AGE VERSION
pi-control Ready master 2m v1.18.6Dodawanie węzła roboczego
Można teraz dodać workera. W tym celu na nim — po zainstalowaniu samego Kubernetes zgodnie ze scenariuszem opisanym powyżej — wystarczy po prostu wykonać wcześniej otrzymane polecenie:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050Można uznać, że klaster jest gotowy:
root@pi-control:~# kubectl get no
NAME STATUS ROLES AGE VERSION
pi-control Gotowy master 28m v1.18.6
pi-worker Gotowy 2m8s v1.18.6Miałem tylko dwie Raspberry Pi, więc nie chciałem oddawać jednej z nich tylko pod control plane. Dlatego usunąłem automatycznie ustawiony taint z węzła pi-control, uruchamiając:
root@pi-control:~# kubectl edit node pi-control… i usuwając linie:
- efekt: NoSchedule
klucz: node-role.kubernetes.io/masterWypełnienie klastra niezbędnym minimum
Najpierw będziemy potrzebować Helm. Oczywiście można wszystko robić i bez niego, ale Helm pozwala dosłownie bez edytowania plików konfigurować niektóre komponenty według własnych potrzeb. A w rzeczywistości jest to po prostu plik binarny, który "nie prosi o pożywienie."
Zatem wchodzimy na na sekcję docs/installation i wykonujemy polecenie stamtąd:
curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bashPo tym dodajemy repozytorium chartów:
helm repo add stable https://kubernetes-charts.storage.googleapis.com/Teraz zainstalujemy komponenty infrastrukturalne zgodnie z zamysłem:
- Ingress controller;
- Prometheus;
- Grafana;
- cert-manager.
Ingress controller
Pierwszy komponent — Ingress controller — instalowany jest dość prosto i gotowy do użycia "z pudełka." W tym celu wystarczy wejść do i wykonać polecenie instalacji stamtąd:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yamlJednak w tym momencie "malina" zaczęła się dusić i napotykać na problemy z IOPS dysku. Problem polega na tym, że razem z Ingress-controllerem instalowanych jest wiele zasobów, wykonuje się wiele zapytań do API i w związku z tym do etcd zapisywana jest duża ilość danych. Ogólnie rzecz biorąc, albo karta pamięci klasy 10 nie jest zbyt wydajna, albo po prostu brakuje SD-kart do takiego obciążenia. Niemniej jednak, po około 5 minutach wszystko ruszyło.
Utworzono namespace, a w nim pojawił się kontroler oraz wszystko, co jest mu potrzebne:
root@pi-control:~# kubectl -n ingress-nginx get pod
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-2hwdx 0/1 Zakończono 0 31s
ingress-nginx-admission-patch-cp55c 0/1 Zakończono 0 31s
ingress-nginx-controller-7fd7d8df56-68qp5 1/1 Działa 0 48sPrometheus
Następne dwa komponenty są dość łatwe do zainstalowania za pomocą Helm z repozytorium chart.
Znajdujemy Prometheus, tworzymy namespace i instalujemy go w nim:
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"}Domyślnie Prometheus zamawia 2 dyski: na dane samego Prometheusa i na dane AlertManagera. Ponieważ w klastrze nie stworzono klasy storage, dyski nie zostaną zamówione i pod'y nie zostaną uruchomione. W przypadku instalacji na bare metal Kubernetes zazwyczaj używamy Ceph rbd, jednak w przypadku Raspberry Pi to jest oczywiste przesycenie.
Dlatego stworzymy prostą lokalną pamięć na hostpath. Manifesty PV (persistent volume) dla prometheus-server i prometheus-alertmanager są połączone w pliku prometheus-pv.yaml do . Katalog dla PV należy wcześniej utworzyć na dysku tego węzła, do którego chcemy przypisać Prometheusa: w przykładzie wymieniono nodeAffinity według hostname pi-worker i na nim utworzono katalogi /data/localstorage/prometheus-server i /data/localstorage/prometheus-alertmanager.
Pobieramy (klonujemy) manifest i dodajemy go do Kubernetes:
kubectl create -f prometheus-pv.yamlNa tym etapie pierwszy raz napotkałem problem z architekturą ARM. Kube-state-metrics, który domyślnie jest instalowany w charcie Prometheusa, odmówił uruchomienia. Zgłosił błąd:
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"Chodzi o to, że dla kube-state-metrics używany jest obraz projektu CoreOS, który nie jest budowany pod 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.7Musiałem lekko poguglać i znaleźć na przykład . Aby go wykorzystać, zaktualizujemy wydanie, podając, jaki obraz ma być użyty dla 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.6Sprawdzamy, czy wszystko się uruchomiło:
root@pi-control:~# kubectl -n monitoring get po
NAZWA GOTOWY STATUS RESTARTY WIEK
prometheus-alertmanager-df65d99d4-6d27g 2/2 Działa 0 5m56s
prometheus-kube-state-metrics-5dc5fd89c6-ztmqr 1/1 Działa 0 5m56s
prometheus-node-exporter-49zll 1/1 Działa 0 5m51s
prometheus-node-exporter-vwl44 1/1 Działa 0 4m20s
prometheus-pushgateway-c547cfc87-k28qx 1/1 Działa 0 5m56s
prometheus-server-85666fd794-z9qnc 2/2 Działa 0 4m52sGrafana i cert-manager
Dla wykresów i dashboardów instalujemy Grafana:
helm install grafana --namespace monitoring stable/grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}Na końcu wyjścia pokażą nam, jak uzyskać hasło do dostępu:
kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echoAby zamówić certyfikaty, zainstalujemy cert-manager. W tym celu zwrócimy się do , która oferuje odpowiednie komendy dla 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=trueDla certyfikatów self-signed do użytku domowego tego wystarczy. Jeśli jednak potrzebujemy uzyskać ten sam Let’s Encrypt, musimy jeszcze skonfigurować cluster issuer. Szczegóły na ten temat można znaleźć w naszym artykule „».
Osobiście zatrzymałem się na wariancie z , uznając, że wariant stagingowy LE będzie wystarczający. Zmieniamy w przykładzie e-mail, zapisujemy do pliku i dodajemy do klastra ():
kubectl create -f cert-manager-cluster-issuer.yamlTeraz możemy zamówić certyfikat, na przykład dla Grafany. W tym celu potrzebna jest domena i zewnętrzny dostęp do klastra. Mam domenę, a ruch skonfigurowałem przekierowując porty 80 i 443 na domowym routerze zgodnie z utworzonym serwisem ingress-controller’a:
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 23dPort 80 w tym przypadku jest przekierowywany na 31303, a 443 — na 30498. (Porty są generowane losowo, więc u Ciebie będą inne.)
Oto przykład certyfikatu ():
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-stagingDodajemy go do klastra:
kubectl create -f cert-manager-grafana-certificate.yamlPo tym pojawi się zasób Ingress, przez który będzie przebiegać walidacja 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 Po zakończeniu walidacji zobaczymy, że zasób certificate jest gotowy, a w powyżej wskazanym sekrecie grafana-tls — certyfikat i klucz. Można od razu sprawdzić, kto wydał certyfikat:
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 X1Wracamy do Grafany. Musimy nieco zmodyfikować jej wyzwolenie Helm, zmieniając ustawienia TLS zgodnie z utworzonym certyfikatem.
Aby to zrobić, pobieramy chart, edytujemy i aktualizujemy z lokalnego katalogu:
helm pull --untar stable/grafana Edycja w pliku grafana/values.yaml parametry TLS:
tls:
- secretName: grafana-tls
hosts:
- grafana.home.pi Możemy tutaj również od razu skonfigurować zainstalowany Prometheus jako źródło danych:
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus-server:80
access: proxy
isDefault: trueTeraz aktualizujemy chart Grafana z lokalnego katalogu:
helm upgrade grafana --namespace monitoring ./grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"} Sprawdzamy, czy w Ingress grafana dodano port 443 i dostęp przez HTTPS:
root@pi-control:~# kubectl -n monitoring get ing grafana
NAZWA KLASA HOSY ADRES PORTY WIEK
grafana <brak> grafana.home.pi 192.168.88.31 80, 443 63m
root@pi-control:~# curl -kI https://grafana.home.pi
HTTP/2 302
serwer: nginx/1.19.1
data: Wt, 28 lip 2020 19:01:31 GMT
typ-treści: text/html; charset=utf-8
cache-control: no-cache
expires: -1
lokalizacja: /login
pragma: no-cache
set-cookie: redirect_to=; Path=/; HttpOnly; SameSite=Lax
x-frame-options: deny
strict-transport-security: max-age=15724800; includeSubDomainsAby zademonstrować Grafanę w działaniu, można pobrać i dodać Oto jak to wygląda:

Zalecam również dodanie dashboardu dla node exporter: szczegółowo pokaże, co dzieje się z „malinami” (obciążenie CPU, wykorzystanie pamięci, sieci, dysku itp.).
Po tym uważam, że klaster jest gotowy do przyjmowania i uruchamiania aplikacji!
Uwagi dotyczące budowy
Do budowy aplikacji pod architekturę ARM istnieją co najmniej dwie możliwości. Po pierwsze, można budować na urządzeniu ARM. Jednak patrząc na obecne wykorzystanie dwóch Raspberry Pi, zrozumiałem, że nie poradzą sobie nawet z budową. Dlatego zamówiłem nową Raspberry Pi 4 (jest mocniejsza i ma aż 4 GB pamięci) — planuję budować na niej.
Drugą opcją jest zbudowanie wieloarchitektonicznego obrazu Docker na mocniejszej maszynie. Do tego służy . Jeśli aplikacja jest napisana w języku kompilowanym, będzie wymagana cross-kompilacja dla ARM. Nie będę opisywał wszystkich konfiguracji dla takiej drogi, ponieważ to zajmie osobny artykuł. Przy wdrożeniu takiego podejścia można osiągnąć „uniwersalne” obrazy: Docker uruchomiony na maszynie ARM samodzielnie automatycznie pobierze odpowiedni obraz architektury.
Podsumowanie
Przeprowadzony eksperyment przerósł moje oczekiwania: [przynajmniej] „waniliowy” Kubernetes z niezbędną bazą radzi sobie całkiem dobrze na ARM, a podczas jego konfiguracji pojawiło się tylko kilka niuansów.
Raspberry Pi 3B+ radzą sobie z obciążeniem CPU, jednak ich karty SD są wyraźnym wąskim gardłem. Koledzy podpowiedzieli, że w niektórych wersjach jest możliwość uruchamiania z USB, gdzie można podłączyć SSD: prawdopodobnie wówczas sytuacja się poprawi.
Oto przykład obciążenia CPU podczas instalacji Grafana:

Moim zdaniem, dla eksperymentów i „żeby spróbować”, klaster Kubernetes na „malinach” dużo lepiej oddaje wrażenia z eksploatacji niż ten sam Minikube, ponieważ wszystkie komponenty klastra są zarówno instalowane, jak i działają „jak należy”.
W perspektywie istnieje pomysł, aby dodać do klastra cały cykl CI/CD, zrealizowany całkowicie na Raspberry Pi. Będę również wdzięczny, jeśli ktoś podzieli się swoim doświadczeniem w konfiguracji K8s na AWS Gravitonach.
P.S. Tak, „produkcja” może być bliżej, niż myślałem:

P.P.S.
Przeczytaj także na naszym blogu:
- «».
Źródło: habr.com
