Bootstrap k3s-кластеров с Ansible и Flux
Published: 2026-02-12
Создание нового Kubernetes-кластера должно быть воспроизводимым. Ansible отвечает за уровень ОС; FluxCD берёт управление с этого момента. Вот как работает bootstrap-пайплайн, шаг за шагом, включая нюансы, которые важны при автоматизации реального железа.
Инструменты
bashbrew install kubectl helm fluxcd/tap/flux kubeseal ansible
ansible-galaxy install -r ansible/requirements.yml
Используется коллекция Ansible ansible.posix плюс стандартная kubernetes.core. k3s устанавливается через официальный скрипт установки, зафиксированный на версии в group_vars/all.yaml. Фиксация важна: обновления k3s иногда требуют процедур drain узла, которые небезопасно выполнять без присмотра.
Структура инвентаря кластера
ansible/
├── ansible.cfg
├── group_vars/
│ ├── all.yaml ← версия k3s, общий конфиг
│ ├── infra.yaml ← переменные для infra (SANS, token)
│ ├── dev.yaml
│ └── test.yaml
└── inventory/
├── infra/hosts.yaml
├── dev/hosts.yaml
└── test/hosts.yaml
sre, loadgds и demo — Managed Kubernetes в Yandex Cloud, без Ansible-инвентаря. Kubeconfig получается через yc managed-kubernetes cluster get-credentials.
Пример group_vars/all.yaml
yamlk3s_version: "v1.31.4+k3s1"
k3s_token: "{{ vault_k3s_token }}" # из Ansible Vault
k3s_extra_args: "--disable traefik --flannel-backend=none"
Токен — общий секрет, который все узлы k3s используют для вступления в кластер. Его стоит ротировать после bootstrap — он нужен только для присоединения узла, а не для текущей работы.
Шаг 1: установка k3s
bashansible-playbook ansible/install-k3s.yaml -i ansible/inventory/dev/
Плейбук:
- Отключает swap и убирает его из
/etc/fstab. k3s и kubelet имеют неопределённое поведение при включённом swap — это жёсткое требование. - Устанавливает параметры ядра:
net.ipv4.ip_forward=1,net.bridge.bridge-nf-call-iptables=1,fs.inotify.max_user_watches=524288,fs.inotify.max_user_instances=512. Лимиты inotify предотвращают ошибкиtoo many open filesв кластерах, где много подов наблюдает за ConfigMap. - Загружает модули ядра:
br_netfilter(правила bridge-трафика),overlay(оверлейная файловая система containerd),nf_conntrack(отслеживание соединений для NetworkPolicy). Проверка модуляoverlayпроверяет, загружен ли он уже перед попыткой загрузки —modprobeвозвращает ошибку на некоторых ядрах, если модуль уже встроен. - Загружает и запускает скрипт установки k3s с
--disable traefik --flannel-backend=none— Cilium заменяет CNI по умолчанию, APISIX заменяет Traefik. - Записывает kubeconfig на узел control-plane.
k3s устанавливается без Traefik и без Flannel, потому что APISIX обрабатывает ingress, а Cilium — сеть. Запуск k3s без CNI означает, что поды не будут планироваться до установки Cilium на следующем шаге — это ожидаемо.
Почему Cilium раньше Flux
CNI должен работать до запуска Flux, так как Flux сам работает как поды. Если выполнить bootstrap Flux до Cilium, поды Flux застрянут в состоянии Pending (нет сети). Порядок bootstrap: k3s → Cilium → контроллер Sealed Secrets → Flux.
Sealed Secrets должен быть установлен перед Flux: Flux сразу начнёт реконсиляцию репозитория, в котором есть SealedSecret, требующие наличия контроллера.
Шаг 2: сбор kubeconfig
bashansible-playbook ansible/get-kubeconfig.yaml -i ansible/inventory/dev/
Копирует kubeconfig с узла control-plane в .kubeconfigs/dev.yaml локально с переименованием контекста в dev-k8s.
У kubeconfig 127.0.0.1 как адрес сервера (k3s по умолчанию привязывается к localhost). Плейбук заменяет его на внешний IP узла для работы извне:
yaml- name: Replace localhost with external IP
ansible.builtin.replace:
path: ".kubeconfigs/dev.yaml"
regexp: "https://127.0.0.1"
replace: "https://{{ ansible_host }}"
Шаг 3: установка Cilium
bashansible-playbook ansible/upgrade-cilium.yaml -i ansible/inventory/dev/
Несмотря на название, этот плейбук обрабатывает и первоначальную установку Cilium. Cilium деплоится через Helm напрямую (не через Flux), так как является предварительным условием для сети подов — сам Flux нуждается в работающем CNI.
Для on-prem кластеров плейбук включает:
kubeProxyReplacement: true(полная замена kube-proxy на базе eBPF)- L2-анонсы (
l2announcements.enabled: true— позволяют LoadBalancer-сервисам работать на bare metal) - Hubble с UI (
hubble.enabled: true,hubble.relay.enabled: true,hubble.ui.enabled: true)
Дождитесь, пока Cilium сообщит о готовности всех агентов:
bashcilium status --wait
После этого поды начнут планироваться и кластер станет функциональным.
Шаг 4: bootstrap Flux на hub
bashansible-playbook ansible/bootstrap-flux.yaml -i ansible/inventory/infra/
Выполняет flux bootstrap gitlab для infra-кластера, указывая на infra git-репозиторий. Создаёт namespace flux-system, устанавливает CRD и контроллеры Flux, создаёт объект GitRepository, следящий за веткой main репозитория.
С этого момента Flux непрерывно выполняет реконсиляцию. Файлы gotk-components.yaml и gotk-sync.yaml в fluxcd/flux-system/ фиксируют точную версию Flux.
Команда bootstrap идемпотентна — повторный запуск на уже инициализированном кластере обновляет Flux, если в репозитории изменилась зафиксированная версия.
GitLab token для Flux
Flux нужен GitLab personal access token (или deploy token) со scope read_repository для клонирования репозитория:
bashflux bootstrap gitlab \
--owner=my-group \
--repository=infra \
--branch=main \
--path=fluxcd \
--token-auth \
--personal # использовать personal access token
Для production используйте deploy token со scope read_repository только для репозитория.
Шаг 5: запечатать kubeconfig spoke-кластеров
Spoke-кластеры не запускают Flux. Hub-Flux нужны их kubeconfig для деплоя в них. Эти kubeconfig хранятся как SealedSecret в git.
bashkubectl create secret generic dev-kubeconfig \
--from-file=value=.kubeconfigs/dev.yaml \
--namespace flux-system \
--dry-run=client -o yaml | \
kubeseal \
--format yaml \
--controller-name sealed-secrets-controller \
--controller-namespace kube-system \
--kubeconfig .kubeconfigs/infra.yaml \
> fluxcd/dev-kubeconfig-sealed.yaml
Namespace — flux-system, что должно совпадать с namespace, где работает Kustomization, ссылающаяся на kubeconfig. Если здесь указать --namespace dev, а Kustomization в flux-system, Flux не найдёт секрет.
Шаг 6: коммит и пуш
bashgit add fluxcd/
git commit -m "feat: bootstrap dev cluster"
git push
GitLab CI запускает flux reconcile source git flux-system при пуше в main. Через несколько секунд Flux создаёт namespace dev на hub, расшифровывает kubeconfig и начинает реконсиляцию HelmRelease для dev-кластера.
Мониторинг реконсиляции:
bashflux get kustomizations -w
flux get helmreleases -A
Первая реконсиляция загружает все Helm-чарты — на холодном кластере это занимает несколько минут. Последующие проходят быстро (секунды).
Настройка логирования узла
bashansible-playbook ansible/tune-node-logging.yaml -i ansible/inventory/dev/
Ограничивает использование диска journald (SystemMaxUse=500M) и устанавливает RateLimitBurst=10000, чтобы лавина логов не заполнила диск. Стандартная практика для долго работающих узлов кластера. Без этого интенсивно логирующий под может заполнить /var/log и уронить узел.
Обновление k3s
Обновления k3s выполняются через тот же плейбук установки с новой версией в group_vars/all.yaml. Для кластеров из одного узла обновление выполняется на месте:
bash# Обновить k3s_version в group_vars/all.yaml, затем:
ansible-playbook ansible/install-k3s.yaml -i ansible/inventory/dev/
В кластерах из нескольких узлов выполняйте drain каждого узла перед обновлением:
bashkubectl drain <node> --ignore-daemonsets --delete-emptydir-data
ansible-playbook ansible/install-k3s.yaml -i ansible/inventory/dev/ --limit <node>
kubectl uncordon <node>
k3s 1.28+ поддерживает автоматические обновления через system-upgrade-controller, который обрабатывает последовательность drain/uncordon автоматически.
Обновление Cilium
bashansible-playbook ansible/upgrade-cilium.yaml -i ansible/inventory/dev/
Плейбук выполняет rolling-обновление Cilium через Helm с последовательностью drain/uncordon при наличии нескольких узлов. Безопасно запускать на живом кластере.
При мажорных обновлениях Cilium (например, 1.14 → 1.15) проверьте руководство по обновлению Cilium — некоторые версии требуют временного отключения kube-proxy replacement во время обновления во избежание перебоев сети.