doc_memo/raspberry-and-orange-pi/k3s.md

464 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Развертывание 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 <none> <none>
kube-system helm-install-traefik-crd-bdbgd 0/1 Completed 0 2h <none> opi5plus-2 <none> <none>
kube-system helm-install-traefik-mlztm 0/1 Completed 1 2h <none> opi5plus-2 <none> <none>
kube-system local-path-provisioner-5cf85fd84d-jwz5n 1/1 Running 0 2h 10.42.0.3 opi5plus-2 <none> <none>
kube-system metrics-server-5985cbc9d7-n9dwz 1/1 Running 0 2h 10.42.0.2 opi5plus-2 <none> <none>
kube-system svclb-traefik-4f8c2580-jddgz 2/2 Running 0 2h 10.42.0.7 opi5plus-2 <none> <none>
kube-system svclb-traefik-4f8c2580-xzt5d 2/2 Running 0 2m35s 10.42.1.2 opi5plus-3 <none> <none>
kube-system traefik-5d45fc8cc9-t5d58 1/1 Running 0 2h 10.42.0.8 opi5plus-2 <none> <none>
```
Как видим, у нас появился еще один `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 <none> 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 <none> <none>
helm-install-traefik-crd-bdbgd 0/1 Completed 0 3h <none> opi5plus-2 <none> <none>
helm-install-traefik-mlztm 0/1 Completed 1 3h <none> opi5plus-2 <none> <none>
local-path-provisioner-5cf85fd84d-jwz5n 1/1 Running 0 3h 10.42.0.3 opi5plus-2 <none> <none>
metrics-server-5985cbc9d7-n9dwz 1/1 Running 0 3h 10.42.0.2 opi5plus-2 <none> <none>
svclb-traefik-4f8c2580-4q7dj 3/3 Running 0 92s 10.42.2.2 opi5plus-1 <none> <none>
svclb-traefik-4f8c2580-h7b9c 3/3 Running 0 2h 10.42.0.9 opi5plus-2 <none> <none>
svclb-traefik-4f8c2580-qmzf6 3/3 Running 0 2h 10.42.1.5 opi5plus-3 <none> <none>
traefik-6c979cd89d-98fk8 1/1 Running 0 1h 10.42.1.6 opi5plus-3 <none> <none>
```
Посмотрим состояние сервисов в кластере:
```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 <none> 53/UDP,53/TCP,9153/TCP 3d
metrics-server ClusterIP 10.43.248.208 <none> 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 <none> 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` с ролью `<none>` — это остатки бывшего воркера. Если запустить на том же хосте масте, узел
может "ожить" и перерегистрироваться с новыми ролями. Но это не обязательно удалит старый объект 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 <none> <none>
helm-install-traefik-crd-bdbgd 0/1 Completed 0 4d19h <none> opi5plus-2 <none> <none>
helm-install-traefik-mlztm 0/1 Completed 1 4d19h <none> opi5plus-2 <none> <none>
local-path-provisioner-5cf85fd84d-jwz5n 1/1 Running 0 4d19h 10.42.0.3 opi5plus-2 <none> <none>
metrics-server-5985cbc9d7-n9dwz 1/1 Running 0 4d19h 10.42.0.2 opi5plus-2 <none> <none>
svclb-traefik-4f8c2580-h7b9c 3/3 Running 0 2d18h 10.42.0.9 opi5plus-2 <none> <none>
svclb-traefik-4f8c2580-nhz65 3/3 Running 0 38h 10.42.2.2 opi5plus-1 <none> <none>
svclb-traefik-4f8c2580-qmzf6 3/3 Running 0 2d18h 10.42.1.5 opi5plus-3 <none> <none>
traefik-6c979cd89d-98fk8 1/1 Terminating 0 2d15h 10.42.1.6 opi5plus-3 <none> <none>
traefik-6c979cd89d-t4rhw 1/1 Running 0 38h 10.42.2.3 opi5plus-1 <none> <none>
```
Если бы у нас были рабочие поды на удаленном узле, то они бы перезапустились на других нодах. Но, у нас там был только
`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 <none> <none>
local-path-provisioner-5cf85fd84d-s9frj 1/1 Running 0 101m 10.42.1.9 opi5plus-3 <none> <none>
metrics-server-5985cbc9d7-q525g 1/1 Terminating 0 101m 10.42.2.4 opi5plus-1 <none> <none>
metrics-server-5985cbc9d7-v8vlt 1/1 Running 0 29m 10.42.0.12 opi5plus-2 <none> <none>
svclb-traefik-4f8c2580-h7b9c 3/3 Running 3 (35m ago) 3d2h 10.42.0.10 opi5plus-2 <none> <none>
svclb-traefik-4f8c2580-nhz65 3/3 Running 0 47h 10.42.2.2 opi5plus-1 <none> <none>
svclb-traefik-4f8c2580-qmzf6 3/3 Running 3 (133m ago) 3d2h 10.42.1.7 opi5plus-3 <none> <none>
traefik-6c979cd89d-t4rhw 1/1 Terminating 0 46h 10.42.2.3 opi5plus-1 <none> <none>
traefik-6c979cd89d-z6wwm 1/1 Running 0 29m 10.42.0.11 opi5plus-2 <none> <none>
```
Хотя, в целом, кластер остается рабочим, и сам чинится при отключении и восстановлении узлов, но если отключается нода
на которой исполняется под с `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 <none> opi5plus-1 <none> <none>
coredns-ccb96694c-wzh96 1/1 Running 0 3h10m 10.42.1.8 opi5plus-3 <none> <none>
...
```
А затем:
```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 <none> <none>
coredns-ccb96694c-wzh96 1/1 Running 0 3h10m 10.42.1.8 opi5plus-3 <none> <none>
...
```
**Как это будет работать?** Обе реплики `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`.
### Разные архитектуры на узлах кластера (гетерогенность)