Русский
Русский
English
Статистика
Реклама

Полноценный Kubernetes с нуля на Raspberry Pi



Совсем недавно одна известная компания объявила, что переводит линейку своих ноутбуков на 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; если вам интересны подробности по этой теме, см. issue #53533):

    dphys-swapfile swapoffsystemctl 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 ACCEPTCOMMIT
    
  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.listdeb https://apt.kubernetes.io/ kubernetes-xenial mainEOFsudo 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 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


Узел с 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/configYou should now deploy a pod network to the cluster.Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/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 72a3c0a14c627d6d7fdade1f4c8d7a41b0fac31b1faf0d8fdf9678d74d7d2403Please 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 noNAME         STATUS     ROLES    AGE   VERSIONpi-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

и подсеть 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   VERSIONpi-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 noNAME         STATUS   ROLES    AGE    VERSIONpi-control   Ready    master   28m    v1.18.6pi-worker    Ready    <none>   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 позволяет буквально без правки файлов настраивать некоторые компоненты по своему усмотрению. И по факту это просто бинарный файл, который хлеба не просит.

Итак, заходим на 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 podNAME                                        READY   STATUS      RESTARTS   AGEingress-nginx-admission-create-2hwdx        0/1     Completed   0          31singress-nginx-admission-patch-cp55c         0/1     Completed   0          31singress-nginx-controller-7fd7d8df56-68qp5   1/1     Running     0          48s

Prometheus


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

Находим Prometheus, создаем namespace и устанавливаем в него:

helm search repo stable | grep prometheuskubectl create ns monitoringhelm 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, который по умолчанию устанавливается в чарте Prometheus, отказался запускаться. Он выдавал ошибку:

root@pi-control:~# kubectl -n monitoring logs prometheus-kube-state-metrics-c65b87574-l66d8standard_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 poNAME                                             READY   STATUS              RESTARTS   AGEprometheus-alertmanager-df65d99d4-6d27g          2/2     Running             0          5m56sprometheus-kube-state-metrics-5dc5fd89c6-ztmqr   1/1     Running             0          5m56sprometheus-node-exporter-49zll                   1/1     Running             0          5m51sprometheus-node-exporter-vwl44                   1/1     Running             0          4m20sprometheus-pushgateway-c547cfc87-k28qx           1/1     Running             0          5m56sprometheus-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.iohelm 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 будет достаточно. Изменяем в примере e-mail, сохраняем в файл и добавляем в кластер (cert-manager-cluster-issuer.yaml):

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

Теперь можно заказать сертификат, например, для Grafana. Для этого потребуется домен и доступ в кластер извне. Домен у меня есть, а трафик я настроил пробросом портов 80 и 443 на домашнем маршрутизаторе в соответствии с созданным сервисом ingress-controller'a:

kubectl -n ingress-nginx get svcNAME                                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGEingress-nginx-controller             NodePort    10.2.206.61    <none>        80:31303/TCP,443:30498/TCP   23d

80-й порт в данном случае транслируется в 31303, а 443 в 30498. (Порты генерируются случайным образом, поэтому у вас они будут другие.)

Вот пример сертификата (cert-manager-grafana-certificate.yaml):

apiVersion: cert-manager.io/v1alpha2kind: Certificatemetadata:  name: grafana  namespace: monitoringspec:  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 ingNAME                        CLASS    HOSTS                        ADDRESS         PORTS   AGEcm-acme-http-solver-rkf8l   <none>   grafana.home.pi      192.168.88.31   80      72sgrafana                     <none>   grafana.home.pi      192.168.88.31   80      6d17hprometheus-server           <none>   prometheus.home.pi   192.168.88.31   80      8d

После того, как валидация пройдет, мы увидим, что ресурс certificate готов, а в указанном выше секрете grafana-tls сертификат и ключ. Можно сразу проверить, кто выпустил сертификат:

root@pi-control:~# kubectl -n monitoring get certificateNAME      READY   SECRET        AGEgrafana   True    grafana-tls   13mroot@pi-control:~# kubectl -n monitoring get secrets grafana-tls -ojsonpath="{.data['tls\.crt']}" | base64 -d | openssl x509 -issuer -nooutissuer=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 grafanaNAME      CLASS    HOSTS                     ADDRESS         PORTS     AGEgrafana   <none>   grafana.home.pi           192.168.88.31   80, 443   63mroot@pi-control:~# curl -kI https://grafana.home.piHTTP/2 302server: nginx/1.19.1date: Tue, 28 Jul 2020 19:01:31 GMTcontent-type: text/html; charset=utf-8cache-control: no-cacheexpires: -1location: /loginpragma: no-cacheset-cookie: redirect_to=%2F; Path=/; HttpOnly; SameSite=Laxx-frame-options: denystrict-transport-security: max-age=15724800; includeSubDomains

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



Еще рекомендую добавить 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-кластер на малинах гораздо лучше передаёт ощущения от эксплуатации, чем тот же самый Minikube, потому что все компоненты кластера и устанавливаются, и работают по-взрослому.

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

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



P.P.S.


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

Источник: habr.com
К списку статей
Опубликовано: 13.08.2020 10:12:29
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании флант

Системное администрирование

Devops

Kubernetes

Raspberry pi

Arm

Just for fun

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru