Пълноценен Kubernetes от нулата на Raspberry Pi

Пълноценен Kubernetes от нулата на Raspberry Pi

Съвсем скоро известна компания обяви, че прехвърля серията си ноутбуци на ARM архитектура. Чувайки тази новина, си спомних: преглеждайки отново цените на EC2 в AWS, ми направи впечатление Graviton с много атрактивна цена. Подводният камък, разбира се, беше, че това е ARM. Тогава изобщо не ми беше хрумнало, че ARM е доста сериозно...

За мен тази архитектура винаги е била свързана с мобилни устройства и различни IoT неща. „Истинските“ сървъри на ARM — изглеждат по-необичайно, дори донякъде ексцентрично... Обаче новата идея ми зае ума, затова в един от уикендите реших да проверя какво всъщност може да се стартира на ARM. И за това реших да започна с близкото и познато — кластер Kubernetes. И не просто някакъв условен „кластер“, а всичко да е „по-възрастно“, за да бъде максимално като това, което съм свикнал да виждам в продукция.

Според моята идея, кластерът трябва да е достъпен от интернет, да има изпълняващо се уеб приложение и да има поне мониторинг. За реализацията на тази идея ще са необходими два (или повече) Raspberry Pi не по-ниски от модел 3B+. Площадката за експериментите може да е и AWS, но мен именно „малините“ ме интересуваха (които така или иначе стояха без работа). И така, ще разположим на тях кластер Kubernetes с Ingress, Prometheus и Grafana.

Подготовка на „малини“

Инсталиране на ОС и SSH

С избора на ОС за инсталиране не се мъчих много: просто взех най-новото Raspberry Pi OS Lite с официалния сайт. Там също е налична документация за инсталация, всички действия от която трябва да се изпълнят на всеки възел от бъдещия кластер. След това ще е необходимо да се извършат следните манипулации (също на всички възли).

Свързвайки монитор и клавиатура, е необходимо предварително да се настрои мрежата и SSH:

  1. За работата на кластера на мастера е задължително да има статичен IP адрес, а на работните възли — по усмотрение. Аз предпочетох статични адреси навсякъде от удобство за настройка.
  2. Статичният адрес може да се конфигурира в ОС (в файла /etc/dhcpcd.conf има подходящ пример) или чрез фиксиране на lease в DHCP сървъра на използвания (в моя случай — домашен) рутер.
  3. ssh-server просто се включва в raspi-config (interfacing options → ssh).

След това вече можете да влезете по SSH (по подразбиране потребителят е pi, а паролата е raspberry или тази, на която я сменихте) и да продължите настройките.

Други настройки

  1. Ще зададем име на хоста. В моя пример ще използвам pi-control и pi-worker.
  2. Нека проверим дали файловата система е разширена на целия диск (df -h /). При необходимост може да бъде разширена с raspi-config.
  3. Ще променим паролата на потребителя по подразбиране в raspi-config.
  4. Ще деактивираме swap файла (това е изискването на Kubernetes; ако ви интересуват подробности по тази тема, вижте issue #53533):
    dphys-swapfile swapoff
    systemctl disable dphys-swapfile
  5. Ще обновим пакетите до последните версии:
    apt-get update && apt-get dist-upgrade -y
  6. Ще инсталираме 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
  7. Остава само да се рестартираме.

Сега всичко е готово за инсталация на кластера Kubernetes.

Инсталация на Kubernetes

На този етап умишлено отложих всичките си и нашите корпоративни разработки за автоматизация на инсталацията и конфигурацията на кластера K8s. Вместо това, ще използваме официалната документация с kubernetes.io (леко допълнена с коментари и съкращения).

Ще добавим репозитория на 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… Тук се отклоних от официалната инструкция и избрах по-познат и разбираем за мен вариант: flannel в режим 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 позволява буквално без редакция на файлове да конфигурирате някои компоненти по ваше желание. И всъщност това е просто бинарен файл, който „не иска хляб“.

И така, влизаме в helm.sh в секция 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 — се инсталира доста лесно и е готов за употреба „от кутията“. За целта е достатъчно да влезете в секция bare-metal на сайта и да изпълните командата за инсталация оттам:

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

Prometheus

Следните два компонента се инсталират доста лесно чрез 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 в Git репозиториите с примери за статията. Директорията за 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          4m52s

Grafana и 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. Подробности за това може да намерите в нашата статия „SSL сертификати от Let’s Encrypt с cert-manager в Kubernetes».

Аз самият избрах варианта от пример в документацията, решавайки, че staging варианта на LE ще е достатъчен. Променяме в примера имейла, запазваме в файл и го добавяме в кластера (cert-manager-cluster-issuer.yaml):

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

80-ти порт се предава в 31303, а 443 — в 30498. (Портовете се генерират случайно, следователно вашите ще бъдат различни.)

Ето пример за сертификат (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

Добавяме го в кластера:

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 за kube-state-metrics. Ето как изглежда:

Пълноценен Kubernetes от нулата на Raspberry Pi

Също така препоръчвам да добавите dashboard за node exporter: той подробно ще покаже какво се случва с "малина" (натоварване на CPU, използване на памет, мрежа, диск и т.н.).

След това считам, че клъстера е готов да приема и стартира приложения!

Забележка относно сборката

За сборка на приложения за ARM архитектура има поне два варианта. Първо, можете да сглобявате на ARM устройство. Обаче, виждайки текущата натовареност на двата Raspberry Pi, осъзнах, че дори и за сборката те не са достатъчно силни. Затова поръчах нов Raspberry Pi 4 (този е по-мощен и има цели 4 GB памет) — планирам да сглобявам на него.

Вторият вариант е - сборка на многоархитектурен Docker образ на по-мощна машина. За това има разширение docker buildx. Ако приложението е на компилиран език, ще е необходима крос-компилация за ARM. Няма да описвам всички настройки за този подход, тъй като това ще изисква отделна статия. При реализиране на този подход могат да се постигнат "универсални" образи: Docker, работещ на ARM машина, автоматично ще изтегля съответния образ за архитектурата.

Заключение

Проведени експерименти надминаха всички мои очаквания: [най-малко] "ванилов" Kubernetes с необходимата база работи доста добре на ARM, а само при конфигурацията му възникнаха някои нюанси.

Сами Raspberry Pi 3B+ издържат натоварването на CPU, но техните SD карти са очевидно тесен бутилен гърло. Колеги споменаха, че в някои версии има възможност за зареждане от USB, където може да се свърже SSD: тогава ситуацията вероятно ще се подобри.

Ето пример за натоварване на CPU при инсталиране на Grafana:

Пълноценен Kubernetes от нулата на Raspberry Pi

За експерименти и "за проба", според мен, Kubernetes кластер на "малини" много по-добре предава усещанията от експлоатацията, отколкото същият Minikube, защото всички компоненти на кластера са както инсталирани, така и работят "по-възрастен".

В перспектива имам идея да добавя към кластера целия цикъл CI/CD, реализиран изцяло на Raspberry Pi. Ще бъда щастлив, ако някой сподели своя опит по настройка на K8s на AWS Graviton-и.

P.S. Да, "продукцията" може да е по-близо, отколкото си мислех:

Пълноценен Kubernetes от нулата на Raspberry Pi

P.P.S.

Прочетете също в нашия блог:

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster