HimeraSearchDB
Carding_EbayThief
triada
CrackerTuch
d-shop
HimeraSearchDB

НОВОСТИ Полноценный Kubernetes с нуля на Raspberry Pi

NewsBot
Оффлайн

NewsBot

.
.
Регистрация
21.07.20
Сообщения
40.408
Реакции
1
Репутация
0
zfxxqxpx87oqbinmn4pwsrubg-e.png


Совсем недавно одна известная компания объявила, что переводит линейку своих ноутбуков на ARM-архитектуру. Услышав эту новость, я вспомнил: просматривая в очередной раз цены на EC2 в AWS, обратил внимание на Graviton'ы с очень вкусной ценой. Подвох, конечно же, был в том, что это ARM. Тогда мне и в голову не приходило, что ARM — это довольно серьезно…

Для меня эта архитектура всегда была уделом мобильных и прочих IoT-штучек. «Настоящие» серверы на ARM — как-то необычно, в чем-то даже дико… Однако новая мысль засела в голову, поэтому в один из выходных решил проверить, что вообще можно сегодня запустить на ARM. И для этого решил начать с близкого и родного — кластера Kubernetes. Причем не просто какого-то условного «кластера», а всё «по-взрослому», чтобы он был максимально таким же, каким я привык его видеть в production.

По моей задумке, кластер должен быть доступным из интернета, в нём должно выполняться некоторое веб-приложение и еще должен быть как минимум мониторинг. Для реализации этой идеи понадобится пара (или больше) 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; если вам интересны подробности по этой теме, см. ):


    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:


curl -s | sudo apt-key add -
cat etc/apt/sources.list.d/kubernetes.list
deb 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

Для работы network-бэкенда, речь о котором пойдет ниже, необходимо доустановить плагин для CNI. Я выбрал привычный и понятный мне плагин portmap (полный их список см. в ):


curl -sL | tar zxvf - -C /opt/cni/bin/ ./portmap

Настройка Kubernetes


Узел с control plane


Установка самого кластера делается довольно просто. А для ускорения этого процесса и проверки того, что образы Kubernetes доступны, можно предварительно выполнить:


kubeadm config images pull

Теперь проводим саму установку — инициализируем control plane кластера:


kubeadm init --pod-network-cidr=10.1.0.0/16 --service-cidr=10.2.0.0/16 --upload-certs

Обратите внимание, что подсети для сервисов и pod'ов не должны пересекаться между собой и с существующими сетями.

В конце нам покажут сообщение о том, что все хорошо, и заодно подскажут, как присоединить рабочие узлы к control plane:


Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:

You can now join any number of the control-plane node running the following command on each as root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4 \
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050 \
--contrl-plane --certificate-key 72a3c0a14c627d6d7fdade1f4c8d7a41b0fac31b1faf0d8fdf9678d74d7d2403
Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4 \
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050

Выполним рекомендации по добавлению конфига для пользователя. А заодно рекомендую сразу добавить автодополнение для kubectl:


kubectl completion bash > ~/.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
NAME STATUS ROLES AGE VERSION
pi-control NotReady master 29s v1.18.6

Конфигурация сети


Далее, как было сказано в сообщении после установки, потребуется установить сеть в кластер. В документации предлагают выбор из Calico, Cilium, contiv-vpp, Kube-router и Weave Net… Здесь я отступил от официальной инструкции и выбрал более привычный и понятный мне вариант: в режиме host-gw (подробнее о доступных бэкендах см. в ).

Установить его в кластер довольно просто. Для начала — скачиваем манифесты:


wget

Затем меняем в настройках тип с vxlan на host-gw:


sed -i 's/vxlan/host-gw/' kube-flannel.yml

… и подсеть pod'ов — со значения по умолчанию на ту, которая указана при инициализации кластера:


sed -i 's#10.244.0.0/16#10.1.0.0/16#' kube-flannel.yml

После этого создаем ресурсы:


kubectl create -f kube-flannel.yml

Готово! Через некоторое время первый узел K8s перейдет в статус Ready:


NAME STATUS ROLES AGE VERSION
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
NAME STATUS ROLES AGE VERSION
pi-control Ready master 28m v1.18.6
pi-worker Ready 2m8s 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 | bash

После этого добавляем репозиторий чартов:


helm repo add stable

Теперь установим инфраструктурные компоненты в соответствии с задумкой:

  • Ingress controller;
  • Prometheus;
  • Grafana;
  • cert-manager.

Ingress controller


Первый компонент — Ingress controller — устанавливается довольно просто и готов к использованию «из коробки». Для этого достаточно зайти в и выполнить команду установки оттуда:


kubectl apply -f

Однако в этот момент «малина» начала напрягаться и упираться в дисковый 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 Completed 0 31s
ingress-nginx-admission-patch-cp55c 0/1 Completed 0 31s
ingress-nginx-controller-7fd7d8df56-68qp5 1/1 Running 0 48s

Prometheus


Следующие два компонента довольно просто установить через Helm из chart repo.

Находим 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, который по умолчанию устанавливается в чарте 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

helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--version v0.16.0 \
--set installCRDs=true

Для самоподписанных сертификатов в домашнем использовании этого вполне достаточно. Если же нужно получать тот же Let's Encrypt, то необходимо настроить еще cluster issuer. Подробности об этом можно найти в нашей статье « ».

Сам я остановился на варианте из , решив, что staging-варианта LE будет достаточно. Изменяем в примере e-mail, сохраняем в файл и добавляем в кластер ( ):


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. (Порты генерируются случайным образом, поэтому у вас они будут другие.)

Вот пример сертификата ( ):


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:
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
NAME CLASS HOSTS ADDRESS PORTS AGE
grafana grafana.home.pi 192.168.88.31 80, 443 63m

root@pi-control:~# curl -kI
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=%2F; Path=/; HttpOnly; SameSite=Lax
x-frame-options: deny
strict-transport-security: max-age=15724800; includeSubDomains

Для демонстрации Grafana в действии можно скачать и добавить . Вот как это выглядит:

kbvxpmpmcviyijk6w_sblobotmg.png


Еще рекомендую добавить 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:

m6nxhrqrz6ae_wj1hhrywfo_z7a.png


Для экспериментов и «на попробовать», на мой взгляд, Kubernetes-кластер на «малинах» гораздо лучше передаёт ощущения от эксплуатации, чем тот же самый Minikube, потому что все компоненты кластера и устанавливаются, и работают «по-взрослому».

В перспективе есть идея добавить к кластеру весь цикл CI/CD, реализованный полностью на Raspberry Pi. А также я буду рад, если кто-то поделится своим опытом по настройке K8s на AWS Graviton'ах.

P.S. Да, «production» может быть ближе, чем я думал:

hojgfhu2-egnp-hkyijkydcydfw.jpeg


P.P.S.


Читайте также в нашем блоге:

  • « ».
 
Сверху Снизу