# Развертывание k3s на Orange Pi K3s — это облегчённая версия Kubernetes, созданная для слабых или малых серверов (Raspberry Pi, Orange Pi, IoT-устройства, edge-серверы и т.п.). Для кластера из нескольких Orange Pi он предпочтительнее, так как: * K3S менее требователен к ресурсам (Полный k8s на ARM может сожрать 1-2 ГБ только на управление кластером, а k3s занимает ~500 МБ. * K3s проще устанавливать и обновлять. Shell-скрипт с [https://get.k3s.io](get.k3s.io) все сделает сам, и не нужно погружаться сложные настройки kubeadm. Обычный Kubernetes состоит из множества компонентов: kube-apiserver, kube-controller-manager, kube-scheduler, kubelet на каждой ноде, kube-proxy, etcd и т.д. В K3s всё это упаковано в один бинарник. * Всё работает "из коробки" благодаря встроенному Flannel (CNI) и не надо вручную настраивать Calico, Weave, Cilium. * В отличие от "классического" Kubernetes (например, kubeadm), где мастер-узлы по умолчанию изолированы от рабочих нагрузок с помощью taint'ов (например, NoSchedule), k3s не добавляет такие ограничения автоматически. Это значит: * Для моего проекта особо важно, что из коробки мастер-узел(ы)) в k3s является "гибридным" и выполняет одновременно функции управления (control-plane) и может запускать обычные поды, как воркер. Компоненты управления (API-сервер, контроллеры, etcd) работают как системные сервисы, а для пользовательских подов используется тот же kubelet, что и на воркерах. Но, есть у k3s и минус для конкретно моего случая -- распределенная база **etcd**, в которой хранится состояния кластера, нод и подов, в нем заменена SQLite. Это круто для маленьких компьютеров: экономно по памяти и другим ресурсам, и, что главное, никак не сказывается на производительности (пока узлов меньше 50-80), но означает, что в кластере k3s может быть только одна мастер-нода. Если мастер-нода упадет, её некому будет заменить и весь кластер умрет. Мне же надо, чтобы как миниум две (а лучше все) ноды могли быть мастерами, так что я буду делать k3s-кластер с использованием *etcd*. ### Важное предупреждение k3s -- это не упрощенная мини-версия Kubernetes, здесь все компоненты упакованы в один бинарник, а значит намного проще не только добавлять узлы, но и удалять их. Так что если что-то пойдет не так с настройкой узла, просто удалите и начните заново. Удаление k3s с узла: ```bash sudo /usr/local/bin/k3s-uninstall.sh # На мастерах sudo /usr/local/bin/k3s-agent-uninstall.sh # На воркере ``` ## Установка k3s на первом узле (мастер) Некоторые требования к узлам: * На всех Orange Pi установлена одинаковая версия Ubuntu (например, 22.04 или 24.04). * Статические IP-адреса узлов (или зрезервированные под MAC-адреса IP в DHCP). * На уздах открыты порты 6443 (для API), 2379-2380 (для etcd) и 10250 (для kubelet). Установливаем первый мастер: ```bash curl -sfL https://get.k3s.io | sh -s - server --cluster-init --tls-san=192.168.1.27 ``` Здесь: * `server` -- значение по умолчанию, устанавливает узел k3s в режиме *мастер* (control-plane). В этом режиме узел будет запускать все компоненты управления Kubernetes: API-сервер, контроллер-менеджер, планировщик (scheduler). Такой узел отвечает за управление кластером и может также выполнять рабочие нагрузки (workloads), если не настроены ограничения (taints). Если бы мы указали `agent` -- был бы установлен узел k3s в режиме *воркер*-узла. * `--cluster-init` -- добавляет поддержку высокой доступности (HA -- High Availability) через встроенный `etcd`. Это значит, что узел инициализирует новый кластер и готов к тому, чтобы другие мастер-узлы могли к нему подключиться (для создания HA-конфигурации). * `--tls-san=192.168.1.27` -- добавляет IP 192.168.1.27 в сертификаты API-сервера, чтобы другие узлы и клиенты могли обращаться к нему по этому адресу. Проверим, что все k3s запущен: ```bash sudo service k3s status ``` Увидим что-то типа: ```text ● k3s.service - Lightweight Kubernetes Loaded: loaded (/etc/systemd/system/k3s.service; enabled; vendor preset: enabled) Active: active (running) since ... ... ... ``` Посмотрим сколько нод в кластере: ```bash sudo kubectl get nodes ``` И, та-да! Увидим одну ноду: ```text NAME STATUS ROLES AGE VERSION opi5plus-2 Ready control-plane,etcd,master 31m v1.31.5+k3s1 ``` Как видим, узел `opi5plus-2` готов к работе и выполняет роли *control-plane*, *etcd* и *master*. А что там внутри? Посмотрим на поды: ```bash sudo kubectl get pods -A ``` Целых семь подов (минималистичная установка k3s): ```text NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-ccb96694c-tfjwj 1/1 Running 0 13m kube-system helm-install-traefik-crd-bdbgd 0/1 Completed 0 13m kube-system helm-install-traefik-mlztm 0/1 Completed 1 13m kube-system local-path-provisioner-5cf85fd84d-jwz5n 1/1 Running 0 13m kube-system metrics-server-5985cbc9d7-n9dwz 1/1 Running 0 13m kube-system svclb-traefik-4f8c2580-jddgz 2/2 Running 0 12m kube-system traefik-5d45fc8cc9-t5d58 1/1 Running 0 12m ``` Тут статус X/Y в выводе kubectl get pods показывает: * Y — сколько контейнеров должно быть в поде (по спецификации). * X — сколько из них сейчас работает (running). Представлены следующие поды: 1. `coredns` — это DNS-сервер для кластера. Он отвечает за разрешение имен внутри Kubernetes (например, чтобы поды могли обращаться друг к другу по именам сервисов вроде my-service.default.svc.cluster.local). 2. `helm-install-traefik-crd` -- это временный под (Job), который устанавливает Custom Resource Definitions (CRD) для *Traefik* — ingress-контроллера, встроенного в k3s. CRD нужны для управления ingress-ресурсами (маршрутизацией HTTP/HTTPS). Этот под — одноразовая задача (Job), а не постоянный сервис. Он запустился, выполнил работу (установил CRD) и завершился. Статус "*Completed*" значит, что он больше не работает. 3. `helm-install-traefik` -- ещё один Job, который устанавливает сам Traefik через Helm-чарт. Этот под развернул основной Traefik-под и завершился. 4. `local-path-provisioner` -- компонент для автоматического создания локальных Persistent Volumes (PV) на узлах. Он позволяет подам запрашивать хранилище (например, через PersistentVolumeClaim) без сложной настройки NFS или внешних хранилищ. В k3s это встроено для простоты. 5. `metrics-server` -- собирает данные об использовании ресурсов (CPU, память) подов и узлов. Это нужно для команд вроде `kubectl top` или для Horizontal Pod Autoscaler (HPA). Установку метрик можно отключить при запуске k3s флагом `--disable=metrics-server`. 6. `svclb-traefik` - это под для балансировки нагрузки (Service Load Balancer) для Traefik. В k3s нет встроенного облачного балансировщика (как в AWS/GCP), поэтому *svclb* эмулирует его на уровне узла, перенаправляя трафик к сервисам типа LoadBalancer. У нас два таких контейнера: * один для самой логики балансировки; * другой для мониторинга или дополнительной функциональности (например, *keepalived* или аналога) и это зависит от реализации в k3s. 7. `traefik` -- сам Traefik, ingress-контроллер, который обрабатывает HTTP/HTTPS трафик кластера и маршрутизирует его к соответствующим подам (с динамической конфигурацией нашим) и сервисам по правилам Ingress. Traefik в k3s установлен по умолчанию, но его можно отключить при запуске k3s флагом `--disable=traefik` (не будет ни *traefik*, ни *svclb*, ни связанных *Helm Jobs*). Обратите внимание, что, например, под `coredns` получил имя `coredns-ccb96694c-tfjwj`. Имена подов (Pod Names) в Kubernetes генерируются автоматически на основе правил, чтобы каждый под в кластере имел уникальное имя. Структура имени -- `<имя-приложения>-<хеш-ревизии>-<случайный-суффикс>`. Впрочем, `<хеш-ревизии>` может отсутствовать, если под не имеет контроллера репликации (например, Job или CronJob). Можно проверить, что API нашего узла (кластера) отвечает: ```bash curl -k https://192.168.1.27 ``` Здесь ключ `-k` означает, что мы не проверяем сертификаты (нам важно только, что сервер отвечает). Должны получить Unauthorized JSON-ответ от API. Что-то вроде: ```json { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "Unauthorized", "reason": "Unauthorized", "code": 401 } ``` ## Подключение второго узла (мастер) Для начала, на первой ноде получим токен для подключения нового узла к кластеру: ```bash sudo cat /var/lib/rancher/k3s/server/node-token ``` Вывод будет что-то вроде `K10...::server:longrandomstring`. Это и есть токен, который нужно будет использовать. Теперь на втором Orange Pi (например, с IP 192.168.1.28) можно запустить второй мастер-узел (вставим токен из предыдущего шага): ```bash curl -sfL https://get.k3s.io | sh -s - server --server https://192.168.1.27:6443 --token <ТОКЕН> --tls-san=192.168.1.28 ``` Здесь ключи: * `--server https://192.168.1.27:6443` -- указывает на API мастер-узла, чтобы наш новый узел мог подключиться к кластеру. * `--token` — токен аутентификации из предыдущего шага. * `--tls-san=192.168.1.28` -- добавляет IP нашего второго мастера в сертификаты (для будущих подключений). Проверим какие теперь ноды в кластере: ```bash sudo k3s kubectl get nodes ``` Теперь увидим две ноды: ```text NAME STATUS ROLES AGE VERSION opi5plus-2 Ready control-plane,etcd,master 2h v1.31.5+k3s1 opi5plus-3 Ready control-plane,etcd,master 110s v1.31.5+k3s1 ``` Проверим поды кластера и посмотрим на каких нодах они запущены: ```bash sudo k3s kubectl get pods -A -o wide ``` И увидим, что на второй ноде запустились те же поды, что и на первой: ```text NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system coredns-ccb96694c-tfjwj 1/1 Running 0 2h 10.42.0.4 opi5plus-2 kube-system helm-install-traefik-crd-bdbgd 0/1 Completed 0 2h opi5plus-2 kube-system helm-install-traefik-mlztm 0/1 Completed 1 2h opi5plus-2 kube-system local-path-provisioner-5cf85fd84d-jwz5n 1/1 Running 0 2h 10.42.0.3 opi5plus-2 kube-system metrics-server-5985cbc9d7-n9dwz 1/1 Running 0 2h 10.42.0.2 opi5plus-2 kube-system svclb-traefik-4f8c2580-jddgz 2/2 Running 0 2h 10.42.0.7 opi5plus-2 kube-system svclb-traefik-4f8c2580-xzt5d 2/2 Running 0 2m35s 10.42.1.2 opi5plus-3 kube-system traefik-5d45fc8cc9-t5d58 1/1 Running 0 2h 10.42.0.8 opi5plus-2 ``` Как видим, у нас появился еще один `svclb-traefik` на второй ноде. Это под -- Service Load Balancer (SLB) для Traefik. Он эмулирует облачный балансировщик нагрузки (типа AWS ELB), которого нет в локальном окружении вроде Orange Pi. SLB перенаправляет внешний трафик (например, на порты 80/443) к сервисам типа LoadBalancer внутри кластера. ## Подключение третьего узла (воркера) Добавление третьего узда в качестве воркера (рабочего узла) мы сделаем временно. Во-первых, чтобы показать как это делается, а во-вторых, чтобы показать как удалять узел и с какими особенностями это связано. И наконец, в-третьих, объяснить что такое кворум и почему важно, чтобы в кластере было нечетное количество мастер-узлов. И так, подключение рабочего узла даже проще, чем мастера. Выполним на нашем новом узле: ```bash curl -sfL https://get.k3s.io | sh -s - agent --server https://192.168.1.10:6443 --token <ТОКЕН> ``` Здесь ключ: * `agent` -- устанавливает узел в режиме воркера (worker). Это значит, что узел будет выполнять рабочие нагрузки (поды), но не будет управлять кластером (без *control-plane*, *master* и на нем нет реплики *etcd*). Посмотрим на ноды (команда выполняется на одном из мастер-узлов): ```bash sudo k3s kubectl get nodes ``` Теперь у нас три ноды, и все они имеют статус *Ready*: ```text NAME STATUS ROLES AGE VERSION opi5plus-1 Ready 96s v1.31.5+k3s1 opi5plus-2 Ready control-plane,etcd,master 3h v1.31.5+k3s1 opi5plus-3 Ready control-plane,etcd,master 2h v1.31.5+k3s1 ``` Новая нода `opi5plus-1` готова к работе и не имеет ролей, а только выполняет рабочие нагрузки (поды). Посмотрим на поды: ```bash sudo k3s kubectl get pods -n kube-system -o wide ``` И увидим, что на новом воркере (opi5plus-1) запустился под балансировщика `svclb-traefik`: ```text NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES coredns-ccb96694c-tfjwj 1/1 Running 0 3h 10.42.0.4 opi5plus-2 helm-install-traefik-crd-bdbgd 0/1 Completed 0 3h opi5plus-2 helm-install-traefik-mlztm 0/1 Completed 1 3h opi5plus-2 local-path-provisioner-5cf85fd84d-jwz5n 1/1 Running 0 3h 10.42.0.3 opi5plus-2 metrics-server-5985cbc9d7-n9dwz 1/1 Running 0 3h 10.42.0.2 opi5plus-2 svclb-traefik-4f8c2580-4q7dj 3/3 Running 0 92s 10.42.2.2 opi5plus-1 svclb-traefik-4f8c2580-h7b9c 3/3 Running 0 2h 10.42.0.9 opi5plus-2 svclb-traefik-4f8c2580-qmzf6 3/3 Running 0 2h 10.42.1.5 opi5plus-3 traefik-6c979cd89d-98fk8 1/1 Running 0 1h 10.42.1.6 opi5plus-3 ``` Посмотрим состояние сервисов в кластере: ```bash sudo k3s kubectl get service -n kube-system ``` Увидим, что сервис *traefik* доступен на всех нодах: ```text NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kube-dns ClusterIP 10.43.0.10 53/UDP,53/TCP,9153/TCP 3d metrics-server ClusterIP 10.43.248.208 443/TCP 3d traefik LoadBalancer 10.43.164.48 192.168.1.26,192.168.1.27,192.168.1.28 80:31941/TCP,443:30329/TCP,9000:32185/TCP 3d ``` Можем проверить доступность панели `Traefik` через браузер через IP-адрес нового узла и (в нашем случае `http://192.168.1.26:9000/dashboard/#/`) и увидим, что балаансировщик работает и перенаправляет трафик и с ноды воркера. Что ж, теперь у нас есть кластер k3s с тремя нодами: двумя мастерами и одним воркером. Но, как я уже говорил, это не идеальная конфигурация, так как у нас четное количество мастер-узлов. Попробует отключить один из мастеров (не обязательно выключать питание, достаточно отсоединить сетевой кабель ethernet) и посмотрим что произойдет. Само-собой доступ к панели Traefik на "погашенном узле" пропадет, но с обоих работающих узлов (живого мастера и воркера) сохранится. И еще будет потеряна возможность работать с кластером через `kubectl`. Почему kubectl не работает на втором мастере? Ошибка на втором мастере после отключения первого говорит о том, что кластер потерял полную функциональность API-сервера. Как говорилось ранее, k3s с настройкой HA (высокая доступность) используется встроенный etcd для хранения состояния. Для работы etcd в HA-режиме требуется кворум. Кворум в etcd — это минимальное количество узлов, которые должны быть доступны для согласования данных и принятия решений в кластере. Это основа отказоустойчивости распределённой системы. При двух мастерах: **Кворум = N/2 + 1**, где N — количество мастер-узлов. Для 2 узлов: *кворум = 2/2 + 1 = 2*. Это значит, что оба мастера должны быть живы, чтобы etcd работал. Если один мастер падает, второй не может достичь кворума (1 < 2) и останавливает работу etcd. Без etcd API-сервер на втором мастере не может отвечать на запросы kubectl, хотя поды продолжают работать, так как им не нужен доступ к etcd в реальном времени. В чем может быть смысл иметь два мастера? Это обеспечивает репликацию данных (второй хранит копию etcd), но не даёт отказоустойчивости -- когда один мастер упал, кластер становится неуправляемым (нет управления через kubectl), рабочие нагрузки (поды) могут продолжать работать, пока жив хотя бы один узел, но новые изменения (развертывание подов и обновления) невозможны. Таким образом, два мастера это не идеальная HA (High Availability), а скорее "полу-HA". Полная HA начинается с трёх узлов! Три мастера — это стандарт для настоящей отказоустойчивости в Kubernetes (и k3s). При трёх мастерах: **Кворум = 3/2 + 1 = 2**. Это значит, что кластер остаётся рабочим, если один мастер уме, но живы минимум 2 из 3. Два оставшихся поддерживают кворум (2 >= 2), и кластер полностью управляем (kubectl работает и можно деплоить поды). ### Удаление узла из кластера Чтобы снова получить возможность управлять кластером включим погашенный мастер-узел, подождем пока кворум восстановится и удалим с k3s воркер-узел (opi5plus-1): ```bash sudo /usr/local/bin/k3s-agent-uninstall.sh ``` Теперь состояние узлов в кластере: ```text NAME STATUS ROLES AGE VERSION opi5plus-1 NotReady 147m v1.31.5+k3s1 opi5plus-2 Ready control-plane,etcd,master 3d2h v1.31.5+k3s1 opi5plus-3 Ready control-plane,etcd,master 2d v1.31.5+k3s1 ``` Нода со статусом `NotReady` с ролью `` — это остатки бывшего воркера. Если запустить на том же хосте масте, узел может "ожить" и перерегистрироваться с новыми ролями. Но это не обязательно удалит старый объект Node — он может либо обновиться (если имя совпадает), либо создать дубликат, что приведёт к путанице. Надежнее удалить старый узел из кластера: ```bash sudo k3s kubectl delete node opi5plus-1 ``` Теперь состояние узлов: ```text NAME STATUS ROLES AGE VERSION opi5plus-2 Ready control-plane,etcd,master 3d2h v1.31.5+k3s1 opi5plus-3 Ready control-plane,etcd,master 2d v1.31.5+k3s1 ``` После удаления узла, проверим состояние подов кластера (правильнее, конечно, было бы проверить поды до удаления узла, но, допустим, мы имитировали ситуацию "смерти" узла): ```bash sudo k3s kubectl get pods -n kube-system -o wide ``` Увидим: ```text NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES coredns-ccb96694c-tfjwj 1/1 Running 0 4d19h 10.42.0.4 opi5plus-2 helm-install-traefik-crd-bdbgd 0/1 Completed 0 4d19h opi5plus-2 helm-install-traefik-mlztm 0/1 Completed 1 4d19h opi5plus-2 local-path-provisioner-5cf85fd84d-jwz5n 1/1 Running 0 4d19h 10.42.0.3 opi5plus-2 metrics-server-5985cbc9d7-n9dwz 1/1 Running 0 4d19h 10.42.0.2 opi5plus-2 svclb-traefik-4f8c2580-h7b9c 3/3 Running 0 2d18h 10.42.0.9 opi5plus-2 svclb-traefik-4f8c2580-nhz65 3/3 Running 0 38h 10.42.2.2 opi5plus-1 svclb-traefik-4f8c2580-qmzf6 3/3 Running 0 2d18h 10.42.1.5 opi5plus-3 traefik-6c979cd89d-98fk8 1/1 Terminating 0 2d15h 10.42.1.6 opi5plus-3 traefik-6c979cd89d-t4rhw 1/1 Running 0 38h 10.42.2.3 opi5plus-1 ``` Если бы у нас были рабочие поды на удаленном узле, то они бы перезапустились на других нодах. Но, у нас там был только `svclb-traefik`, который теперь стал в статусе `Terminating`. Это процесс удаления пода. Kubernetes не сразу удаляет поды, особенно если они находятся в состоянии "зависания" (например, `Terminating` или `Running`, но стали недоступны). Так как агент удалён вместе с узлом, то некому сообщить кластеру, что под завершил работу, и он остается "призраком" в списке. Удалим под `svclb-traefik` вручную (не забудьте заменить `xxxxxxxxx-xxxxx` на реальные значения `<хеш-ревизии>`и `<суффикс>`): ```bash sudo k3s kubectl delete pod svclb-traefik-xxxxxxxxx-xxxxx -n kube-system --force --grace-period=0 ``` Здесь `--force` и `--grace-period=0` говорят Kubernetes удалить под "форсированно" и "немедленно". Даже если узел недоступен. Так как это DaemonSet, он не перезапустится на opi5plus-1, потому что узел уже NotReady. ## Добавление третьего мастера Теперь у нас осталось две мастер-ноды и можно добавить третий мастер. Как это сделать, см выше. Но теперь при добавлении можно в флаге `--server` указать IP как первого, так и второго мастера. И не забудьте в `--tls-san` указать IP хоста нового (третьего) мастера. ### Тюнинг kube-dns После установки можно попробовать отключить один из мастеров и убедиться, что кластер остаётся работоспособным, а спустя некоторое время (иногда 10-15 минут) поды с погашенного мастера перезапустятся на других нодах. Например: ```text NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES coredns-ccb96694c-wzh96 1/1 Running 0 101m 10.42.1.8 opi5plus-3 local-path-provisioner-5cf85fd84d-s9frj 1/1 Running 0 101m 10.42.1.9 opi5plus-3 metrics-server-5985cbc9d7-q525g 1/1 Terminating 0 101m 10.42.2.4 opi5plus-1 metrics-server-5985cbc9d7-v8vlt 1/1 Running 0 29m 10.42.0.12 opi5plus-2 svclb-traefik-4f8c2580-h7b9c 3/3 Running 3 (35m ago) 3d2h 10.42.0.10 opi5plus-2 svclb-traefik-4f8c2580-nhz65 3/3 Running 0 47h 10.42.2.2 opi5plus-1 svclb-traefik-4f8c2580-qmzf6 3/3 Running 3 (133m ago) 3d2h 10.42.1.7 opi5plus-3 traefik-6c979cd89d-t4rhw 1/1 Terminating 0 46h 10.42.2.3 opi5plus-1 traefik-6c979cd89d-z6wwm 1/1 Running 0 29m 10.42.0.11 opi5plus-2 ``` Хотя, в целом, кластер остается рабочим, и сам чинится при отключении и восстановлении узлов, но если отключается нода на которой исполняется под с `coredns` -- то временно будет затруднен перезапуска и создание новых подов, а значит и "переезд" подов с погашенного узла, до восстановления `coredns` тоже будет замедлен. Кроме того, если сценарий приложения(ий) развернутых внутри k3s предполагает переподключение с использованием имен подов или обнаружение подов, то это тоже перестанет работать. Решением может быть использование двух реплик `coredns` (вместо одной). Откроем файл конфигурации k3s на редактирование: ```bash sudo k3s kubectl edit deployment coredns -n kube-system ``` Здесь: * `kubectl edit` -- Открывает редактор (по умолчанию *vim*) для изменения ресурса Kubernetes напрямую в кластере. Вместо создания локального YAML-файла и применения его через `kubectl apply`, мы сразу редактируем "живой" конфиг. * `deployment coredns` -- Указывает, что редактируем объект типа *deployment* с именем `coredns`. Deployment — это контроллер, который управляет набором подов (в данном случае coredns), обеспечивая их количество (реплики), перезапуск и обновления. * `-n kube-system` -- Указывает пространство имён (namespace), где находится *coredns8. В k3s системные компоненты, к которым относится *coredns(, обычно живут в kube-system. В открывшемся окне найдем строку `replicas: 1` и заменим её на `replicas: 2`. ```yaml spec: progressDeadlineSeconds: 600 replicas: 2 revisionHistoryLimit: 0 ``` Сохраним изменения и выйдем из редактора. Изменения сразу применятся, и k3s создаст вторую реплику `coredns`: ```text NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES coredns-ccb96694c-n4qsp 0/1 ContainerCreating 0 5s opi5plus-1 coredns-ccb96694c-wzh96 1/1 Running 0 3h10m 10.42.1.8 opi5plus-3 ... ``` А затем: ```text NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES coredns-ccb96694c-n4qsp 1/1 Running 0 15s 10.42.2.6 opi5plus-1 coredns-ccb96694c-wzh96 1/1 Running 0 3h10m 10.42.1.8 opi5plus-3 ... ``` **Как это будет работать?** Обе реплики `coredns` привязаны к сервису `kube-dns` в пространстве имён `kube-system`. Он имеет фиксированный *Cluster IP* (внутренний IP-адрес кластера) и балансирует запросы между всеми зарегистрированными подами `coredns` (у нас теперь две реплики). Каждый под `coredns` регистрируется как endpoint в `kube-dns` при старте. Посмотеть endpoint'ы сервиса `kube-dns` можно командой: ```bash sudo k3s kubectl get endpoints kube-dns -n kube-system ``` И увидим, что у `kube-dns` несколько endpoint'ов (IP-адресов подов `coredns`) включая оба новых и старые, которые гасили при экспериментах с устойчивостью кластера: ```text NAME ENDPOINTS AGE kube-dns 10.42.1.8:53,10.42.2.6:53,10.42.1.8:53 + 3 more... 5d23h ``` Каждый под `coredns` -- самостоятельный DNS-сервер. Они не взаимодействуют друг с другом и не обмениваются данными. Это просто экземпляры одного и того же сервиса, работающие параллельно. Они независимы, получают данные из API Kubernetes и отвечают на запросы параллельно. В каждом поде кластера в качестве DNS настроен `kube-dns` (задаётся в файле `/etc/resolv.conf` внутри пода). Когда под отправляет DNS-запрос, его получит `kube-dns` и перенаправит запрос к одному из доступных `coredns`. Балансировка происходит по случайного выбора (Round-Robin). Если один из `coredns` недоступен (например, узел выключен), `kube-dns` не получит ответа, и направит запросы к живому `coredns`. ### Разные архитектуры на узлах кластера (гетерогенность) Когда мы подключили узлы (мастеры и воркеры) к кластеру, мы использовали одинаковые Orange Pi 5 Plus. Но, в реальности, кластеры Kubernetes часто состоят из узлов с разными архитектурами и характеристиками. Например, если подключить к к кластеру Raspberry Pi 3B увидим примерно такую картину: ```text NAME STATUS ROLES AGE VERSION opi5plus-1 Ready control-plane,etcd,master 3d3h v1.31.5+k3s1 opi5plus-2 Ready control-plane,etcd,master 6d3h v1.31.5+k3s1 opi5plus-3 Ready control-plane,etcd,master 5d1h v1.31.5+k3s1 rpi3b Ready 27s v1.31.6+k3s1 ``` Но надо помнить, что разные архитектуры могут быть оказаться несовместимы с некоторыми приложениями и образами. Например, Raspberry Pi 3B — это 32-битный ARMv7 (armv7l), а Orange Pi 5 Plus — 64-битный ARMv8 (aarch64). Если в подах используются бинарные файлы, скомпилированные под определённую архитектуру, то они могут не работать на узлах с другой архитектурой. Также, некоторые образы Docker могут быть доступны только для определённых архитектур. В ограниченном объеме можно подключать узлы на других платформах. Например, Windows, может иметь только воркер-узлы на k8s (начиная с версии 1.14), а в k3s экспериментальная поддержка Windows-воркеров (начиная с с версии 1.24). На macOS нет официальной поддержки Kubernetes/k3s для узлов на macOS (можно использовать обходные пути с использованием виртуальныех машин). ### Добавление узлов во "внешнем" интернете В моем проекте (специализированном поисковике) будет нужно парсить и интернет сайты, включая заблокированные сайты. К сожалению современный интернет имеет взаимные региональные блокировки и просто использовать VPN интернет-соединения не сработает. Выходом может стать использование воркер-узлов во внешнем интернете. Идея в том, что если какой-нибудь URL не получится обработать на поде одного узла, то можно попробовать обработать его на другом узле, в другой локации. Так как узлы k3s взаимодействуют через API на 6443-порте, то для доступа к кластеру из внешнего интернета нужно будет обеспечить проброс трафика через роутер сети на один из мастер-узлов. НО у нас три мастер-узла, а значит если упадет узел на который происходит проброс, то удаленный воркер-узел "отвелится" и потеряет доступ к кластеру. Объединить IP всеx мастер-узлов в один можно с помощью балансировщика нагрузки **Keepalived**. Он создает виртуальный IP-адрес (VIP), c которого перенапрвляет трафик на один из мастер-узлов, и если этот узел упадет, то трафик перенаправится на другой и так далее. Установи `Keepalived` на все мастер-ноды: ```bash sudo apt update sudo apt install keepalived ``` Настроим `Keepalived` последовательно на каждом мастере. Для этого отредактируем (создадим) файл конфигурации `/etc/keepalived/keepalived.conf`: ```bash sudo nano /etc/keepalived/keepalived.conf ``` На первом мастер-узле (хост -- `opi5plus-1`, IP -- `192.168.1.26`): ```text vrrp_instance VI_1 { state MASTER # ЭТО ГЛАВНЫЙ ХОСТ. ПО УМОЛЧАНИЮ ТРАФИК С VIP БУДЕТ ПЕРЕНАПРАВЛЯТЬСЯ НА ЭТОТ ХОСТ interface enP4p65s0 # У Orange Pi 5 plus два интерфейса, и хост подключен по интерфейсу enP4p65s0 virtual_router_id 51 priority 100 # Самый высокий приоритет advert_int 1 unicast_src_ip 192.168.1.26 # IP текущего хоста (opi5plus-1) unicast_peer { 192.168.1.27 # IP второго хоста (opi5plus-2) 192.168.1.28 # IP третьего хоста (opi5plus-3) } virtual_ipaddress { 192.168.1.200 # Виртуальный IP (VIP), он должен быть исключен из диапазона DHCP } } ``` На втором мастер-узле (хост -- `opi5plus-2`, IP -- `192.168.1.27`): ```text vrrp_instance VI_1 { state BACKUP # ЭТО ВТОРОЙ ХОСТ. ОН БУДЕТ ПОЛУЧАТЬ ТРАФИК С VIP, ЕСЛИ ГЛАВНЫЙ ХОСТ УПАДЕТ interface enP4p65s0 # У Orange Pi 5 plus два интерфейса, и хост подключен по интерфейсу enP4p65s0 virtual_router_id 51 priority 90 # Меньший приоритет advert_int 1 unicast_src_ip 192.168.1.27 # IP текущего хоста (opi5plus-2) unicast_peer { 192.168.1.26 # IP первого хоста (opi5plus-1) 192.168.1.28 # IP третьего хоста (opi5plus-3) } virtual_ipaddress { 192.168.1.200 # Виртуальный IP } } ``` И, наконец, на третьем мастер-узле (хост -- `opi5plus-3`, IP -- `192.168.1.28`): ```text vrrp_instance VI_1 { state BACKUP # ЭТО ТРЕТИЙ ХОСТ. ОН БУДЕТ ПОЛУЧАТЬ ТРАФИК С VIP, ЕСЛИ ГЛАВНЫЙ- И БЭКАП-ХОСТ УПАДЕТ interface enP4p65s0 # У Orange Pi 5 plus два интерфейса, и этот узел подключен по enP4p65s0 virtual_router_id 51 priority 80 # Еще меньший приоритет advert_int 1 unicast_src_ip 192.168.1.28 # IP текущего хоста (opi5plus-3) unicast_peer { 192.168.1.27 # IP первого хоста (opi5plus-1) 192.168.1.28 # IP второго хоста (opi5plus-2) } virtual_ipaddress { 192.168.1.200 # Виртуальный IP } } ``` Добавим `Keepalived` в автозагрузку на всех мастер-узлах и запустим его: ```bash sudo systemctl enable keepalived sudo systemctl start keepalived ``` Теперь, если вы на первом мастер-узле (opi5plus-1) проверить доступные IP-адреса: ```bash ip addr show ``` то увидим: ```text ... ... 2: enP4p65s0: mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether c0:74:2b:fd:42:3c brd ff:ff:ff:ff:ff:ff inet 192.168.1.26/24 brd 192.168.1.255 scope global dynamic noprefixroute enP4p65s0 valid_lft 68779sec preferred_lft 68779sec inet 192.168.1.200/32 scope global enP4p65s0 valid_lft forever preferred_lft forever ... ``` Обратите внимание на виртуальный IP-адрес `192.168.1.200` находится в другой подсети (CIDR) и имеет маску `/32` (то есть с маской подсети `255.255.255.255`). Это "точечная" подсеть, содержащая только один адрес, не привязан к основной подсети интерфейса (/24) и это позволяет VIP "плавать" между узлами, не вызывая конфликтов с основными IP-адресами и не требуя изменения подсети на каждом узле. VIP рассматривается как уникальный адрес, не требующий маршрутизации, он просто "привязан" к интерфейсу. Теперь панель `Traefik` доступна по VIP-адресу `http://192.168.1.200:9000/dashboard/#`, т.к. трафик с этого адреса будет перенаправлен на один из мастер-узлов. API Kubernetes тоже теперь доступен по VIP-адресу. Все воркер-узлы, подключенные к кластеру, лучше подключать к кластеру через VIP-адрес. Сами мастер узлы знают свои IP и взаимодействую через `etcd`, но воркеры подключаясь через VIP будут более устойчивы к сбоям мастер-узлов. Подсоединить удаленный воркер-узел к кластеру лучше через VIP. Для этого нужно на роутере сети настроить проброс порта `6443` с внешнего IP роутера, на виртуальный IP-адрес внутри сети (тоже на `6443` порт). После проверить, что с внешнего хоста API Kubernetes доступно: ```bash curl -k https://:6443 ``` Если отклик есть (например, `Unauthorized`), то можно подключить удаленый воркер-узел к кластеру: ```bash curl -sfL https://get.k3s.io | sh -s - agent --server https://:6443 --token ``` Когда процесс завершится, на любом мастер-узле можно проверить, что воркер-узел подключился: ```bash sudo k3s kubectl get nodes ``` Получим, например: ```text NAME STATUS ROLES AGE VERSION opi5plus-1 Ready control-plane,etcd,master 5d4h v1.31.5+k3s1 opi5plus-2 Ready control-plane,etcd,master 8d v1.31.5+k3s1 opi5plus-3 Ready control-plane,etcd,master 7d2h v1.31.5+k3s1 rpi3b Ready 25h v1.31.6+k3s1 vps-sw-eye Ready 7h35m v1.31.6+k3s1 ```