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/

Плейбук:

  1. Отключает swap и убирает его из /etc/fstab. k3s и kubelet имеют неопределённое поведение при включённом swap — это жёсткое требование.
  2. Устанавливает параметры ядра: 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.
  3. Загружает модули ядра: br_netfilter (правила bridge-трафика), overlay (оверлейная файловая система containerd), nf_conntrack (отслеживание соединений для NetworkPolicy). Проверка модуля overlay проверяет, загружен ли он уже перед попыткой загрузки — modprobe возвращает ошибку на некоторых ядрах, если модуль уже встроен.
  4. Загружает и запускает скрипт установки k3s с --disable traefik --flannel-backend=none — Cilium заменяет CNI по умолчанию, APISIX заменяет Traefik.
  5. Записывает 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 во время обновления во избежание перебоев сети.