--- title: "K3s HA для homelab: Ставим K3s HA кластер" date: 2025-11-02 draft: false description: "Устанавливаем K3s HA кластер: 3 master и 2 worker ноды за 15 минут. Один curl-скрипт на ноду — и кластер готов к работе." tags: ["kubernetes", "k3s", "homelab", "installation", "ha", "etcd", "devops"] categories: ["Kubernetes", "Homelab", "DevOps практики"] series: ["K3s HA кластер для homelab"] series_order: 3 --- Инфраструктура готова: 5 VM работают, ОС настроена, порты открыты. Пора устанавливать K3s. Один curl-скрипт на каждую ноду - и через 15 минут у вас работающий HA-кластер. Звучит слишком просто? Потому что сложная часть уже позади - в предыдущих статьях. Теперь осталось не перепутать флаги и порядок установки. **Результат:** 5 нод в статусе Ready, etcd кластер 3/3 healthy, kubectl работает с локальной машины. --- ## Для кого это **Подходит:** - Прошёл статьи 1 и 2 (или имеешь готовые VM с настроенной ОС) - Все 5 нод доступны по SSH - Понимаешь разницу между master и worker **Не подходит:** - VM ещё не созданы → статья 2 - Не понимаешь зачем 3 master ноды → статья 1 --- ## Что понадобится | Компонент | Значение | |-----------|----------| | K3s версия | v1.31.4+k3s1 (или актуальная stable) | | Token | Сгенерируем на первом шаге | | SSH доступ | Ко всем 5 нодам | | Время | ~15-20 минут | --- ## Шаг 1: Сгенерировать token Token - общий секрет для всех нод кластера. Без правильного token нода не присоединится. **На локальной машине:** ```bash # Сгенерировать случайный token openssl rand -base64 32 ``` **Пример вывода:** ``` K10f8c9a7b6e5d4c3b2a1f0e9d8c7b6a5e4d3c2b1a0f9e8d7c6b5a4== ``` **Сохрани этот token** - он понадобится для каждой ноды. Положи в менеджер паролей или временный файл. ```bash # Для удобства - сохранить в переменную (на время сессии) export K3S_TOKEN="твой_сгенерированный_token" echo $K3S_TOKEN ``` --- ## Шаг 2: Установить K3s на первую master ноду Первая нода инициализирует etcd кластер. Она особенная - использует флаг `--cluster-init`. **SSH на k3s-master-1:** ```bash ssh k3s@192.168.11.201 ``` **Установка:** ```bash # Задать переменные export K3S_TOKEN="твой_сгенерированный_token" export INSTALL_K3S_VERSION="v1.31.4+k3s1" # Установить K3s curl -sfL https://get.k3s.io | sh -s - server \ --cluster-init \ --tls-san=192.168.11.201 \ --disable=traefik \ --disable=servicelb \ --write-kubeconfig-mode=644 ``` **Разбор флагов:** | Флаг | Зачем | |------|-------| | `server` | Режим control plane (не agent) | | `--cluster-init` | **Ключевой!** Инициализирует etcd. Только на первой ноде | | `--tls-san=192.168.11.201` | Добавить IP в сертификат API server | | `--disable=traefik` | Отключить встроенный Traefik (установим свой через Helm) | | `--disable=servicelb` | Отключить встроенный LB (установим MetalLB) | | `--write-kubeconfig-mode=644` | Разрешить чтение kubeconfig без sudo | **Установка займёт 1-2 минуты.** K3s скачает бинарник (~50MB) и запустит все компоненты. ### Проверка ```bash # 1. Статус сервиса sudo systemctl status k3s ``` **Ожидаемый результат:** ``` ● k3s.service - Lightweight Kubernetes Loaded: loaded Active: active (running) since ... ``` ```bash # 2. Статус ноды sudo k3s kubectl get nodes ``` **Ожидаемый результат:** ``` NAME STATUS ROLES AGE VERSION k3s-master-1 Ready control-plane,etcd,master 45s v1.31.4+k3s1 ``` ### Checkpoint: Первая master работает ```bash # Быстрая проверка sudo systemctl is-active k3s && \ sudo k3s kubectl get nodes | grep -q "Ready" && \ echo "✓ Master-1 готов" || echo "✗ Проблема" ``` **Если статус NotReady или сервис не запустился:** ```bash # Смотреть логи sudo journalctl -u k3s -f --no-pager | tail -50 ``` | Ошибка в логах | Причина | Решение | |----------------|---------|---------| | `cgroup v1 is not supported` | Нужен cgroup v2 | Вернись к статье 2, шаг 6.5 | | `port 6443 already in use` | Что-то занимает порт | `sudo ss -tlnp \| grep 6443` | | `etcd failed to start` | Мало места на диске | `df -h`, увеличь диск | --- ## Шаг 3: Добавить вторую master ноду Теперь присоединяем вторую master. Она подключается к существующему кластеру - **без** `--cluster-init`. **SSH на k3s-master-2:** ```bash ssh k3s@192.168.11.202 ``` **Установка:** ```bash export K3S_TOKEN="твой_сгенерированный_token" export INSTALL_K3S_VERSION="v1.31.4+k3s1" curl -sfL https://get.k3s.io | sh -s - server \ --server=https://192.168.11.201:6443 \ --tls-san=192.168.11.202 \ --disable=traefik \ --disable=servicelb \ --write-kubeconfig-mode=644 ``` **Ключевое отличие:** - ❌ Нет `--cluster-init` - кластер уже инициализирован - ✅ Есть `--server=https://192.168.11.201:6443` - адрес существующего кластера ### Проверка ```bash # На master-2 sudo k3s kubectl get nodes ``` **Ожидаемый результат:** ``` NAME STATUS ROLES AGE VERSION k3s-master-1 Ready control-plane,etcd,master 3m v1.31.4+k3s1 k3s-master-2 Ready control-plane,etcd,master 30s v1.31.4+k3s1 ``` Две ноды - но кворума ещё нет. etcd требует большинство, а 2 из 3 - это ещё не "большинство от трёх". --- ## Шаг 4: Добавить третью master ноду **SSH на k3s-master-3:** ```bash ssh k3s@192.168.11.203 ``` **Установка (аналогично master-2):** ```bash export K3S_TOKEN="твой_сгенерированный_token" export INSTALL_K3S_VERSION="v1.31.4+k3s1" curl -sfL https://get.k3s.io | sh -s - server \ --server=https://192.168.11.201:6443 \ --tls-san=192.168.11.203 \ --disable=traefik \ --disable=servicelb \ --write-kubeconfig-mode=644 ``` ### Checkpoint: Control Plane HA готов **На любой master ноде:** ```bash # 1. Все 3 master ноды Ready sudo k3s kubectl get nodes ``` **Ожидаемый результат:** ``` NAME STATUS ROLES AGE VERSION k3s-master-1 Ready control-plane,etcd,master 5m v1.31.4+k3s1 k3s-master-2 Ready control-plane,etcd,master 3m v1.31.4+k3s1 k3s-master-3 Ready control-plane,etcd,master 1m v1.31.4+k3s1 ``` ```bash # 2. etcd кластер здоров sudo k3s kubectl exec -n kube-system \ $(sudo k3s kubectl get pods -n kube-system -l component=etcd -o name | head -1) \ -- etcdctl endpoint health --cluster ``` **Ожидаемый результат:** ``` https://192.168.11.201:2379 is healthy: successfully committed proposal: took = 2.1ms https://192.168.11.202:2379 is healthy: successfully committed proposal: took = 1.8ms https://192.168.11.203:2379 is healthy: successfully committed proposal: took = 2.3ms ``` **Теперь у вас настоящий HA.** Можете остановить любую master ноду - кластер продолжит работать. ### Тест отказоустойчивости (опционально) Хотите убедиться, что HA работает? Проверьте: ```bash # С локальной машины - остановить master-2 ssh k3s@192.168.11.202 "sudo systemctl stop k3s" # Подождать 30-40 секунд, затем на master-1: ssh k3s@192.168.11.201 "sudo k3s kubectl get nodes" # master-2 станет NotReady, но кластер работает # Проверить etcd - 2/3 кворум есть ssh k3s@192.168.11.201 "sudo k3s kubectl exec -n kube-system \ \$(sudo k3s kubectl get pods -n kube-system -l component=etcd -o name | head -1) \ -- etcdctl endpoint health --cluster" # 2 из 3 healthy - кворум есть # Вернуть master-2 ssh k3s@192.168.11.202 "sudo systemctl start k3s" ``` --- ## Шаг 5: Добавить worker ноды Worker ноды устанавливаются как **agent** - они не участвуют в etcd и не запускают control plane. ### 5.1. Установить K3s agent на worker-1 **SSH на k3s-worker-1:** ```bash ssh k3s@192.168.11.210 ``` **Установка:** ```bash export K3S_TOKEN="твой_сгенерированный_token" export INSTALL_K3S_VERSION="v1.31.4+k3s1" curl -sfL https://get.k3s.io | sh -s - agent \ --server=https://192.168.11.201:6443 ``` **Отличия от master:** - `agent` вместо `server` - режим worker - Нет флагов `--disable` и `--tls-san` - они не нужны для worker - Только `--server` - куда подключаться ### 5.2. Установить K3s agent на worker-2 **SSH на k3s-worker-2:** ```bash ssh k3s@192.168.11.211 ``` **Установка:** ```bash export K3S_TOKEN="твой_сгенерированный_token" export INSTALL_K3S_VERSION="v1.31.4+k3s1" curl -sfL https://get.k3s.io | sh -s - agent \ --server=https://192.168.11.201:6443 ``` ### Checkpoint: Все ноды в кластере **На любой master ноде:** ```bash sudo k3s kubectl get nodes -o wide ``` **Ожидаемый результат:** ``` NAME STATUS ROLES AGE VERSION INTERNAL-IP k3s-master-1 Ready control-plane,etcd,master 10m v1.31.4+k3s1 192.168.11.201 k3s-master-2 Ready control-plane,etcd,master 8m v1.31.4+k3s1 192.168.11.202 k3s-master-3 Ready control-plane,etcd,master 6m v1.31.4+k3s1 192.168.11.203 k3s-worker-1 Ready 2m v1.31.4+k3s1 192.168.11.210 k3s-worker-2 Ready 1m v1.31.4+k3s1 192.168.11.211 ``` **Обрати внимание:** - Master: роли `control-plane,etcd,master` - Worker: роли `` - только выполнение workloads ![K3S HA Cluster](mermaid-diagram.svg) --- ## Шаг 6: Настроить kubectl на локальной машине Сейчас kubectl работает только на master нодах через `sudo k3s kubectl`. Настроим доступ с вашей рабочей машины. ### 6.1. Скопировать kubeconfig **На локальной машине (не на ноде):** ```bash # 1. Создать директорию mkdir -p ~/.kube # 2. Скопировать конфиг с master-1 scp k3s@192.168.11.201:/etc/rancher/k3s/k3s.yaml ~/.kube/config # 3. Заменить localhost на реальный IP sed -i 's/127.0.0.1/192.168.11.201/g' ~/.kube/config # Для macOS: # sed -i '' 's/127.0.0.1/192.168.11.201/g' ~/.kube/config # 4. Права доступа chmod 600 ~/.kube/config ``` ### 6.2. Установить kubectl (если нет) **Linux:** ```bash curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl rm kubectl ``` **macOS:** ```bash brew install kubectl ``` ### 6.3. Проверить подключение ```bash # Версия kubectl version # Ноды kubectl get nodes # Все поды kubectl get pods -A ``` **Ожидаемый результат `kubectl get pods -A`:** ``` NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-xxx 1/1 Running 0 10m kube-system local-path-provisioner-xxx 1/1 Running 0 10m kube-system metrics-server-xxx 1/1 Running 0 10m ``` **Если kubectl не подключается:** | Симптом | Причина | Решение | |---------|---------|---------| | `connection refused` | k3s не запущен | `ssh k3s@192.168.11.201 "sudo systemctl status k3s"` | | `connection timeout` | Firewall блокирует | Проверь UFW на master: `sudo ufw status \| grep 6443` | | `certificate signed by unknown authority` | Неправильный kubeconfig | Скопируй заново с master ноды | ### 6.4. Настроить автодополнение (опционально) ```bash # Bash echo 'source <(kubectl completion bash)' >> ~/.bashrc echo 'alias k=kubectl' >> ~/.bashrc echo 'complete -o default -F __start_kubectl k' >> ~/.bashrc source ~/.bashrc # Zsh echo 'source <(kubectl completion zsh)' >> ~/.zshrc source ~/.zshrc ``` Теперь работает `k get nodes` и Tab-автодополнение. --- ## Финальная проверка Полный чеклист работоспособности кластера: ```bash echo "=== Проверка K3s HA кластера ===" echo -n "1. Все ноды Ready: " [ $(kubectl get nodes --no-headers | grep -c "Ready") -eq 5 ] && echo "✓ (5/5)" || echo "✗" echo -n "2. Master ноды: " kubectl get nodes --no-headers | grep -c "control-plane" | xargs -I {} echo "✓ ({}/3)" echo -n "3. Worker ноды: " kubectl get nodes --no-headers | grep -c "" | xargs -I {} echo "✓ ({}/2)" echo -n "4. etcd healthy: " kubectl exec -n kube-system \ $(kubectl get pods -n kube-system -l component=etcd -o name | head -1) \ -- etcdctl endpoint health --cluster 2>/dev/null | grep -c "is healthy" | xargs -I {} echo "✓ ({}/3)" echo -n "5. CoreDNS Running: " kubectl get pods -n kube-system -l k8s-app=kube-dns --no-headers | grep -q "Running" && echo "✓" || echo "✗" echo -n "6. Metrics Server Running: " kubectl get pods -n kube-system -l k8s-app=metrics-server --no-headers | grep -q "Running" && echo "✓" || echo "✗" echo "" echo "=== Информация о кластере ===" kubectl cluster-info ``` **Ожидаемый результат:** ``` === Проверка K3s HA кластера === 1. Все ноды Ready: ✓ (5/5) 2. Master ноды: ✓ (3/3) 3. Worker ноды: ✓ (2/2) 4. etcd healthy: ✓ (3/3) 5. CoreDNS Running: ✓ 6. Metrics Server Running: ✓ === Информация о кластере === Kubernetes control plane is running at https://192.168.11.201:6443 CoreDNS is running at https://192.168.11.201:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy Metrics-server is running at https://192.168.11.201:6443/api/v1/namespaces/kube-system/services/https:metrics-server:https/proxy ``` --- ## Тестовый деплой Убедимся, что кластер может запускать приложения: ```bash # 1. Создать тестовый под kubectl run nginx-test --image=nginx:alpine --port=80 # 2. Подождать запуска kubectl wait --for=condition=Ready pod/nginx-test --timeout=60s # 3. Проверить на какой ноде запустился kubectl get pod nginx-test -o wide ``` **Ожидаемый результат:** ``` NAME READY STATUS RESTARTS AGE IP NODE nginx-test 1/1 Running 0 30s 10.42.1.5 k3s-worker-1 ``` Под запустился на worker ноде - как и должно быть. ```bash # 4. Проверить доступность изнутри кластера kubectl run -it --rm debug --image=busybox --restart=Never -- wget -qO- nginx-test ``` **Ожидаемый результат:** HTML-страница nginx. ```bash # 5. Удалить тестовые ресурсы kubectl delete pod nginx-test ``` --- ## Troubleshooting ### Нода не присоединяется к кластеру **Симптом:** После установки нода не появляется в `kubectl get nodes`. | Причина | Диагностика | Решение | |---------|-------------|---------| | Неправильный token | Логи: `sudo journalctl -u k3s-agent -f` | Проверь token, переустанови | | Firewall блокирует | `curl -k https://192.168.11.201:6443` | Открой порт 6443 на masters | | DNS не резолвит | `ping k3s-master-1` | Проверь /etc/hosts или DNS | ### etcd не формирует кворум **Симптом:** `etcdctl endpoint health` показывает unhealthy. ```bash # Проверить членов etcd sudo k3s kubectl exec -n kube-system \ $(sudo k3s kubectl get pods -n kube-system -l component=etcd -o name | head -1) \ -- etcdctl member list --write-out=table ``` | Причина | Диагностика | Решение | |---------|-------------|---------| | Порты 2379-2380 закрыты | `sudo ufw status` | `sudo ufw allow 2379:2380/tcp` | | Нода недоступна по сети | `ping 192.168.11.20X` | Проверь сеть, UFW | | etcd ещё стартует | `uptime` на ноде | Подожди 2-3 минуты | ### Поды не запускаются на workers **Симптом:** Все поды на master нодах, workers пустые. ```bash # Проверить taints kubectl describe node k3s-worker-1 | grep Taints ``` Если есть taints - убрать: ```bash kubectl taint nodes k3s-worker-1 node.kubernetes.io/not-ready:NoSchedule- ``` --- ## Откат: удаление K3s Если нужно начать заново: **На master ноде:** ```bash sudo /usr/local/bin/k3s-uninstall.sh ``` **На worker ноде:** ```bash sudo /usr/local/bin/k3s-agent-uninstall.sh ``` **Что удаляется:** - Бинарники K3s - Systemd сервисы - Данные из `/var/lib/rancher/k3s/` - etcd данные (на masters) - Контейнеры и образы **⚠️ Внимание:** etcd снапшоты тоже удаляются. Если нужен бэкап: ```bash # Перед удалением - сохранить снапшоты sudo cp -r /var/lib/rancher/k3s/server/db/snapshots/ ~/k3s-backup/ ``` --- ## Итог **Что сделано:** - ✅ Сгенерирован token для кластера - ✅ Установлен K3s на 3 master ноды с embedded etcd - ✅ Добавлены 2 worker ноды - ✅ Настроен kubectl на локальной машине - ✅ Проверена работоспособность кластера **Что имеем:** - HA Control Plane - выдерживает падение 1 master ноды - 2 worker ноды для приложений - kubectl доступ с локальной машины - Встроенные компоненты: CoreDNS, metrics-server, local-path storage **Что ещё не настроено** (следующие статьи): - LoadBalancer (MetalLB) - для доступа к сервисам извне - Ingress Controller (Traefik) - для HTTP/HTTPS routing - SSL сертификаты (cert-manager) - для автоматического HTTPS - Мониторинг (Prometheus/Grafana) - для наблюдения за кластером --- ## Что дальше Кластер готов, но пока он изолирован от внешнего мира. Чтобы запускать реальные приложения с доступом извне, нужны: 1. **MetalLB** - выдаёт IP-адреса для LoadBalancer сервисов 2. **Traefik** - маршрутизирует HTTP/HTTPS трафик 3. **cert-manager** - автоматически получает SSL сертификаты Это темы для следующей серии статей. **А пока можно:** - Поэкспериментировать с kubectl - Задеплоить тестовые приложения - Изучить как работает scheduling между нодами