
Съвсем скоро известна компания обяви, че прехвърля серията си ноутбуци на ARM архитектура. Чувайки тази новина, си спомних: преглеждайки отново цените на EC2 в AWS, ми направи впечатление Graviton с много атрактивна цена. Подводният камък, разбира се, беше, че това е ARM. Тогава изобщо не ми беше хрумнало, че ARM е доста сериозно...
За мен тази архитектура винаги е била свързана с мобилни устройства и различни IoT неща. „Истинските“ сървъри на ARM — изглеждат по-необичайно, дори донякъде ексцентрично... Обаче новата идея ми зае ума, затова в един от уикендите реших да проверя какво всъщност може да се стартира на ARM. И за това реших да започна с близкото и познато — кластер Kubernetes. И не просто някакъв условен „кластер“, а всичко да е „по-възрастно“, за да бъде максимално като това, което съм свикнал да виждам в продукция.
Според моята идея, кластерът трябва да е достъпен от интернет, да има изпълняващо се уеб приложение и да има поне мониторинг. За реализацията на тази идея ще са необходими два (или повече) Raspberry Pi не по-ниски от модел 3B+. Площадката за експериментите може да е и AWS, но мен именно „малините“ ме интересуваха (които така или иначе стояха без работа). И така, ще разположим на тях кластер Kubernetes с Ingress, Prometheus и Grafana.
Подготовка на „малини“
Инсталиране на ОС и SSH
С избора на ОС за инсталиране не се мъчих много: просто взех най-новото Raspberry Pi OS Lite с . Там също е налична , всички действия от която трябва да се изпълнят на всеки възел от бъдещия кластер. След това ще е необходимо да се извършат следните манипулации (също на всички възли).
Свързвайки монитор и клавиатура, е необходимо предварително да се настрои мрежата и SSH:
- За работата на кластера на мастера е задължително да има статичен IP адрес, а на работните възли — по усмотрение. Аз предпочетох статични адреси навсякъде от удобство за настройка.
- Статичният адрес може да се конфигурира в ОС (в файла
/etc/dhcpcd.confима подходящ пример) или чрез фиксиране на lease в DHCP сървъра на използвания (в моя случай — домашен) рутер. - ssh-server просто се включва в raspi-config (interfacing options → ssh).
След това вече можете да влезете по SSH (по подразбиране потребителят е pi, а паролата е raspberry или тази, на която я сменихте) и да продължите настройките.
Други настройки
- Ще зададем име на хоста. В моя пример ще използвам
pi-controlиpi-worker. - Нека проверим дали файловата система е разширена на целия диск (
df -h /). При необходимост може да бъде разширена с raspi-config. - Ще променим паролата на потребителя по подразбиране в raspi-config.
- Ще деактивираме swap файла (това е изискването на Kubernetes; ако ви интересуват подробности по тази тема, вижте ):
dphys-swapfile swapoff systemctl disable dphys-swapfile - Ще обновим пакетите до последните версии:
apt-get update && apt-get dist-upgrade -y - Ще инсталираме Docker и допълнителни пакети:
apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistentПри инсталиране
iptables-persistentще е необходимо да запазите настройките на iptables за ipv4, а във файла/etc/iptables/rules.v4— да добавите правила в веригатаFORWARD, по следния начин:# 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 - Остава само да се рестартираме.
Сега всичко е готово за инсталация на кластера Kubernetes.
Инсталация на Kubernetes
На този етап умишлено отложих всичките си и нашите корпоративни разработки за автоматизация на инсталацията и конфигурацията на кластера K8s. Вместо това, ще използваме официалната документация с (леко допълнена с коментари и съкращения).
Ще добавим репозитория на 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 updateСледва в документацията да се инсталира CRI (container runtime interface). Тъй като Docker вече е инсталиран, преминаваме напред и инсталираме основните компоненти:
sudo apt-get install -y kubelet kubeadm kubectl kubernetes-cni На стъпката за инсталиране на основните компоненти веднага добавих kubernetes-cni, който е необходим за работата на клъстера. И тук има важен момент: пакетът kubernetes-cni по някаква причина не създава директория по подразбиране за настройките на CNI интерфейсите, затова ми се наложи да я създам ръчно:
mkdir -p /etc/cni/net.dЗа работата на мрежовия бекенд, за който ще говорим по-долу, е необходимо да инсталирате плъгина за CNI. Избрах разбираемия и познат ми плъгин portmap (пълният им списък вижте в ):
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
Узел с управлението на плана
Инсталацията на самия клъстер става доста лесно. А за ускоряване на този процес и проверка дали образите на Kubernetes са достъпни, можете предварително да изпълните:
kubeadm config images pullСега извършваме самата инсталация — инициализиране на управляващия план на клъстера:
kubeadm init --pod-network-cidr=10.1.0.0/16 --service-cidr=10.2.0.0/16 --upload-certsОбърнете внимание, че подсетите за услугите и подовете не трябва да се пресичат помежду си и с вече съществуващите мрежи.
В края ще видим съобщение, че всичко е наред, и също така ще ни посочат как да свържем работните възли към контролния план:
Вашият Kubernetes контролен план е инициализиран успешно! За да започнете да използвате клъстера си, трябва да изпълните следното като обикновен потребител:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Сега трябва да разгръщате подова мрежа в клъстера.
Изпълнете "kubectl apply -f [podnetwork].yaml" с една от опциите, изброени на:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Сега можете да добавите произволен брой контролни възли, като изпълните следната команда на всеки от тях като root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050
--contrl-plane --certificate-key 72a3c0a14c627d6d7fdade1f4c8d7a41b0fac31b1faf0d8fdf9678d74d7d2403
Обърнете внимание, че certificate-key дава достъп до чувствителни данни на клъстера, пазете го в тайна!
Като мярка за безопасност, качените сертификати ще бъдат изтрити след два часа; При необходимост можете да използвате
"kubeadm init phase upload-certs --upload-certs" за повторно зареждане на сертификатите след това.
След това можете да добавите произволен брой работни възли, като изпълните следното на всеки от тях като root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050Ще изпълним препоръките за добавяне на конфигурацията за потребителя. А също така препоръчвам веднага да добавите автоматично допълване за kubectl:
kubectl completion bash > $HOME/.kube/completion.bash.inc
printf "
# Kubectl shell completion
source '$HOME/.kube/completion.bash.inc'
" >> $HOME/.bash_profile
source $HOME/.bash_profileНа този етап вече можем да видим първия възел в клъстера (въпреки че той все още не е готов):
root@pi-control:~# kubectl get no
ИМЕ СТАТУС РОЛИ ВЪЗРАСТ ВЕРСИЯ
pi-control NotReady master 29s v1.18.6Конфигурация на мрежата
След това, както беше посочено в съобщението след инсталацията, ще е необходимо да се инсталира мрежа в клъстера. В документацията предлагат избор от Calico, Cilium, contiv-vpp, Kube-router и Weave Net… Тук се отклоних от официалната инструкция и избрах по-познат и разбираем за мен вариант: в режим host-gw (повече за наличните бекенди вижте в ).
Инсталирането му в клъстера е доста просто. Първо — сваляме манифестите:
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml След това променяме в настройките типа от vxlan на host-gw:
sed -i 's/vxlan/host-gw/' kube-flannel.yml… и подсетите на подовете — от стойността по подразбиране на тази, която е посочена при инициализацията на клъстера:
sed -i 's#10.244.0.0/16#10.1.0.0/16#' kube-flannel.ymlСлед това създаваме ресурсите:
kubectl create -f kube-flannel.yml Готово! След известно време първият възел K8s ще премине в статус Готово:
ИМЕ СТАТУС РОЛИ ВЪЗРАСТ ВЕРСИЯ
pi-control Ready master 2m v1.18.6Добавяне на работен възел
Сега можете да добавите worker. За целта на него — след инсталирането на самия Kubernetes по описания по-горе сценарий — просто трябва да изпълните предишно получената команда:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050С това можем да считаме, че клъстерът е готов:
root@pi-control:~# kubectl get no
ИМЕ СТАТУС РОЛИ ВЪЗРАСТ ВЕРСИЯ
pi-control Готов майстор 28м v1.18.6
pi-worker Готов 2м8с v1.18.6Имах под ръка само два Raspberry Pi, така че не ми се искаше да давам един от тях само под control plane. Затова премахнах автоматично инсталирания taint от узла pi-control, като изпълних:
root@pi-control:~# kubectl edit node pi-control… и премахнах редовете:
- effect: NoSchedule
key: node-role.kubernetes.io/masterЗапълване на клъстера с необходимия минимум
Първо, ще ни е нужна Helm. Разбира се, всичко може да се направи и без него, но Helm позволява буквално без редакция на файлове да конфигурирате някои компоненти по ваше желание. И всъщност това е просто бинарен файл, който „не иска хляб“.
И така, влизаме в в секция docs/installation и изпълняваме командата оттам:
curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bashСлед това добавяме хранилището на чартове:
helm repo add stable https://kubernetes-charts.storage.googleapis.com/Сега ще инсталираме инфраструктурните компоненти в съответствие с замисъла:
- Ingress controller;
- Prometheus;
- Grafana;
- cert-manager.
Ingress controller
Първият компонент — Ingress controller — се инсталира доста лесно и е готов за употреба „от кутията“. За целта е достатъчно да влезете в и да изпълните командата за инсталация оттам:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yamlОбаче в този момент „малина“ започна да се напряга и да удря в дисковия IOPS. Работата е там, че заедно с Ingress контролера се инсталира голямо количество ресурси, извършват се много запитвания към API и, съответно, много данни се записват в etcd. В крайна сметка, или картата с памет клас 10 не е много производителна, или SD картите принципно не стигат за такава натовареност. Въпреки това, след около 5 минути всичко се стартира.
Създаден е namespace и в него се появи контролерът и всичко необходимо:
root@pi-control:~# kubectl -n ingress-nginx get pod
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-2hwdx 0/1 Завършено 0 31s
ingress-nginx-admission-patch-cp55c 0/1 Завършено 0 31s
ingress-nginx-controller-7fd7d8df56-68qp5 1/1 Работи 0 48sPrometheus
Следните два компонента се инсталират доста лесно чрез Helm от хранилището на чарт.
Намираме Prometheus, създаваме namespace и го инсталираме в него:
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"}По подразбиране Prometheus изисква 2 диска: за данни от самия Prometheus и за данни от AlertManager. Тъй като в кластерът не е създаден storage class, дисковете няма да бъдат заявени и pod-овете няма да стартират. За bare metal инсталации на Kubernetes обикновено използваме Ceph rbd, но в случая с Raspberry Pi, това е очевидно прехвърляне на границите.
Затова ще създадем прост local storage на hostpath. Манифестите PV (persistent volume) за prometheus-server и prometheus-alertmanager са обединени в файла prometheus-pv.yaml в . Директорията за PV трябва да бъде създадена предварително на диска на узела, към който искаме да привържем Prometheus: в примера е указано nodeAffinity по hostname pi-worker и на него са създадени директории /data/localstorage/prometheus-server и /data/localstorage/prometheus-alertmanager.
Сваляме (клонираме) манифеста и добавяме в Kubernetes:
kubectl create -f prometheus-pv.yamlНа този етап за първи път се сблъсках с проблема с ARM архитектурата. Kube-state-metrics, който по подразбиране се инсталира в чартa Prometheus, отказа да стартира. Той даваше грешка:
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"Става въпрос за това, че за kube-state-metrics се използва образ от проекта CoreOS, който не се компилира за 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Трябваше да потърся малко и да намеря, например, . За да го използваме, ще актуализираме релиза, указвайки кой образ да използваме за 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Проверяваме дали всичко е стартирало:
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 и cert-manager
За графиците и dashboard-овете инсталираме Grafana:
helm install grafana --namespace monitoring stable/grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}Накрая на изхода ще ни покажат как да получим паролата за достъп:
kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echoЗа да поръчаме сертификати, ще инсталираме cert-manager. За неговата инсталация ще се обърнем към , която предлага съответните команди за 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За само-подписаните сертификати за домашна употреба това е напълно достатъчно. Ако обаче е необходимо да получим същия Let’s Encrypt, трябва да настроим и cluster issuer. Подробности за това може да намерите в нашата статия „».
Аз самият избрах варианта от , решавайки, че staging варианта на LE ще е достатъчен. Променяме в примера имейла, запазваме в файл и го добавяме в кластера ():
kubectl create -f cert-manager-cluster-issuer.yamlСега можем да поръчаме сертификат, например, за Grafana. За това ще ни е нужен домейн и достъп до кластера от вън. Имам домейн, а трафикът съм настроил с пренасочване на портове 80 и 443 на домашния рутер в съответствие с създадения сервис на 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 23d80-ти порт се предава в 31303, а 443 — в 30498. (Портовете се генерират случайно, следователно вашите ще бъдат различни.)
Ето пример за сертификат ():
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Добавяме го в кластера:
kubectl create -f cert-manager-grafana-certificate.yamlСлед това ще се появи ресурс Ingress, чрез който ще се извърши валидацията от 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 След като валидацията мине, ще видим, че ресурсът certificate е готов, а в указанния по-горе секрет grafana-tls е сертификатът и ключът. Можем веднага да проверим, кой е издал сертификата:
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Нека се върнем към Grafana. Ще трябва да направим малко корекции в нейния Helm релиз, променяйки настройките за TLS в съответствие със създадения сертификат.
За това изтегляме чарта, редактираме и обновяваме от локалната директория:
helm pull --untar stable/grafana Редактираме в файла grafana/values.yaml настройките за TLS:
tls:
- secretName: grafana-tls
hosts:
- grafana.home.pi Тук можем веднага да настроим инсталирания Prometheus като datasource:
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus-server:80
access: proxy
isDefault: trueСега от локалната директория обновяваме чарт Grafana:
helm upgrade grafana --namespace monitoring ./grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"} Проверяваме, че в Ingress grafana е добавен 443 порт и има достъп по HTTPS:
root@pi-control:~# kubectl -n monitoring get ing grafana
ИМЕ КЛАС ХОСТОВЕ AДРЕС ПОРТ ВЪЗРАСТ
grafana grafana.home.pi 192.168.88.31 80, 443 63м
root@pi-control:~# curl -kI https://grafana.home.pi
HTTP/2 302
сервер: nginx/1.19.1
data: Вт, 28 юли 2020 19:01:31 GMT
съдържание-тип: текст/html; charset=utf-8
контрол-каша: няма кеш
изтича: -1
локация: /login
pragma: няма кеш
set-cookie: redirect_to=; Path=/; HttpOnly; SameSite=Lax
x-frame-options: забрани
строга-транспортна-защитеност: макс-възраст=15724800; включиSubDomainsЗа демонстрация на Grafana в действие можете да изтеглите и добавите . Ето как изглежда:

Също така препоръчвам да добавите dashboard за node exporter: той подробно ще покаже какво се случва с "малина" (натоварване на CPU, използване на памет, мрежа, диск и т.н.).
След това считам, че клъстера е готов да приема и стартира приложения!
Забележка относно сборката
За сборка на приложения за ARM архитектура има поне два варианта. Първо, можете да сглобявате на ARM устройство. Обаче, виждайки текущата натовареност на двата Raspberry Pi, осъзнах, че дори и за сборката те не са достатъчно силни. Затова поръчах нов Raspberry Pi 4 (този е по-мощен и има цели 4 GB памет) — планирам да сглобявам на него.
Вторият вариант е - сборка на многоархитектурен Docker образ на по-мощна машина. За това има . Ако приложението е на компилиран език, ще е необходима крос-компилация за ARM. Няма да описвам всички настройки за този подход, тъй като това ще изисква отделна статия. При реализиране на този подход могат да се постигнат "универсални" образи: Docker, работещ на ARM машина, автоматично ще изтегля съответния образ за архитектурата.
Заключение
Проведени експерименти надминаха всички мои очаквания: [най-малко] "ванилов" Kubernetes с необходимата база работи доста добре на ARM, а само при конфигурацията му възникнаха някои нюанси.
Сами Raspberry Pi 3B+ издържат натоварването на CPU, но техните SD карти са очевидно тесен бутилен гърло. Колеги споменаха, че в някои версии има възможност за зареждане от USB, където може да се свърже SSD: тогава ситуацията вероятно ще се подобри.
Ето пример за натоварване на CPU при инсталиране на Grafana:

За експерименти и "за проба", според мен, Kubernetes кластер на "малини" много по-добре предава усещанията от експлоатацията, отколкото същият Minikube, защото всички компоненти на кластера са както инсталирани, така и работят "по-възрастен".
В перспектива имам идея да добавя към кластера целия цикъл CI/CD, реализиран изцяло на Raspberry Pi. Ще бъда щастлив, ако някой сподели своя опит по настройка на K8s на AWS Graviton-и.
P.S. Да, "продукцията" може да е по-близо, отколкото си мислех:

P.P.S.
Прочетете също в нашия блог:
- «».
Източник: habr.com
