iac/aero/roles/sarex_stack/tasks/platform.yml
emelinda 565faa0e17 feat(aero): базовый слой контура — Vault↔k3s, GitOps-синхронизация, выход наружу
Топология k3s: 1 мастер + 2 воркера (убран k3s-worker-3 с томом).

Vault ↔ k3s. Vault живёт в compose, вне кластера, поэтому получает
статический IP 172.28.0.13, DNS-мост в namespace vault и token reviewer
(SA vault-auth + system:auth-delegator). Ansible включает auth/kubernetes,
передавая адрес apiserver, CA и JWT ревьюера явно, и заводит политику с
ролями. Секрет token reviewer'а читается целиком в JSON: вариант
-o jsonpath={.data.ca\.crt} НЕ работает — модуль command разбирает строку
через shlex, съедает обратный слэш, kubectl возвращает пустую строку с
кодом 0, и отказ остаётся незамеченным до падения vault write.

Синхронизация репозитория в gitea. Нужное подмножество путей (замыкание
ссылок clusters/aero) едет архивом на хост и коммитится там: gitea не
публикуется дальше самого хоста, контур остаётся замкнутым. flux-bootstrap
вынесен в отдельную волну — до неё Vault получает auth/kubernetes, а
репозиторий наполняется, иначе первая реконсиляция падает на пустом репо.

Инфраструктура: vault-agent-injector (чарт vault-contour в режиме внешнего
Vault), local-path-provisioner взамен встроенного в k3s (--disable=
local-storage; путь данных прежний), rabbitmq и minio с кредами из Vault.

Выход наружу: порты 80/443 k3s-server опубликованы (istio ingressgateway
занимает эти hostPort), общий contour-gateway на wildcard-хост, сертификат
Let's Encrypt через http01. Имена в сертификате перечислены явно —
http01 не выдаёт wildcard, для них нужен dns01.

Разорваны два дедлока Flux: infra-configs больше не зависит от
infra-controllers (издатели не должны зависеть от здоровья чартов, которые
их используют), istio-config получил disableWait.

Модули ядра iptable_nat и смежные грузятся на хосте: ноды k3s —
контейнеры, istio-init правит iptables через ядро хоста, а на RED OS 8
(nf_tables) legacy-модули не загружены, из-за чего любой под с
istio-injection навсегда вставал в Init:Error.

Собственные Gateway/VirtualService/Certificate чартов rabbitmq и dashboard
отключены — маршрутизация описана централизованно. У rabbitmq это сделано
postRenderers, а не values: kustomize нормализует null в {}, а пустую карту
Helm сливает с дефолтами, возвращая их целиком.
2026-08-08 09:36:08 +03:00

125 lines
6.9 KiB
YAML
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.

---
# Подъём GitOps-подложки контура: k3s + gitea + vault + bootstrap FluxCD.
# Отделено от прикладного стека (sarex_services) намеренно: здесь только
# публичные образы, docker login в приватный реестр не требуется.
#
# Порядок важен и поэтому разбит на две волны: flux-bootstrap монтирует
# ./k3s/kubeconfig.yaml, который k3s-server создаёт только при первом старте.
# Если поднять всё одной командой, docker может создать на месте отсутствующего
# файла директорию, и bootstrap упадёт с невнятной ошибкой.
- name: Волна 1 — поднять k3s-server и gitea
ansible.builtin.command:
cmd: "{{ compose_cmd }} up -d {{ aero_platform_services_wave1 | join(' ') }}"
chdir: "{{ deploy_dir }}"
changed_when: true
- name: Дождаться kubeconfig от k3s-server
ansible.builtin.wait_for:
path: "{{ deploy_dir }}/k3s/kubeconfig.yaml"
state: present
timeout: "{{ aero_kubeconfig_timeout }}"
# Пустой файл означает, что k3s-server ещё дописывает kubeconfig. Без этой
# проверки flux-bootstrap может стартовать с нулевым конфигом.
- name: Убедиться, что kubeconfig не пустой
ansible.builtin.wait_for:
path: "{{ deploy_dir }}/k3s/kubeconfig.yaml"
search_regex: "clusters:"
timeout: 60
# Секреты реестра — до bootstrap: без yc-cr-auth в flux-system source-controller
# не сможет скачать ни один OCI-чарт, и вся первая волна инфраструктуры встанет.
- name: Создать секреты доступа к реестру в кластере
ansible.builtin.include_tasks: registry-secrets.yml
- name: Волна 2 — gitea-init, flux-k8s-init и vault(+init)
ansible.builtin.command:
cmd: "{{ compose_cmd }} up -d {{ aero_platform_services_wave2 | join(' ') }}"
chdir: "{{ deploy_dir }}"
changed_when: true
# vault-init инициализирует и распечатывает Vault в цикле с шагом 10 секунд,
# поэтому сразу после `up -d` root-токена ещё нет. Он нужен следующему шагу,
# который настраивает auth/kubernetes.
- name: Дождаться инициализации Vault (появления root-токена)
ansible.builtin.command:
cmd: "{{ compose_cmd }} exec -T vault-init test -s /vault/init/root.token"
chdir: "{{ deploy_dir }}"
register: aero_vault_ready
until: aero_vault_ready.rc == 0
retries: 30
delay: 5
changed_when: false
# Метод auth/kubernetes: Vault учится валидировать токены подов k3s, а поды
# получают секреты файлами через vault-agent-injector (ставится Flux'ом).
- name: Настроить интеграцию Vault с k3s
ansible.builtin.include_tasks: vault-k8s.yml
tags: [vault]
# Наполнение gitea — ДО bootstrap: Flux начинает реконсиляцию сразу после
# привязки, и репозиторий к этому моменту должен содержать clusters/aero
# вместе со всем, на что тот ссылается.
- name: Синхронизировать репозиторий контура в gitea
ansible.builtin.include_tasks: gitea-sync.yml
tags: [sync]
- name: Волна 3 — bootstrap Flux
ansible.builtin.command:
cmd: "{{ compose_cmd }} up -d {{ aero_platform_services_wave3 | join(' ') }}"
chdir: "{{ deploy_dir }}"
changed_when: true
# flux-bootstrap одноразовый: успех = код 0. Забираем его явно, иначе ошибка
# bootstrap'а останется незамеченной — `compose up -d` о ней не сообщает.
#
# `docker compose wait` здесь не годится: для уже завершившегося контейнера он
# возвращает rc=1 и "no containers for project". Так как `up -d` не дожидается
# одноразового сервиса, состояние гоночное — поэтому опрашиваем `ps -a`,
# который корректно отвечает и для running, и для exited.
#
# Формат обёрнут в {% raw %}: это Go-шаблон docker'а, и без raw Jinja попыталась
# бы раскрыть {{.State.Status}} как свою переменную. Аргументы передаём списком —
# у `command` нет шелла, и строка со скобками разбилась бы по пробелу.
- name: Дождаться завершения flux-bootstrap
ansible.builtin.command:
argv:
- "{{ compose_cmd.split()[0] }}"
- inspect
- "-f"
- "{% raw %}{{.State.Status}} {{.State.ExitCode}}{% endraw %}"
- flux-bootstrap
register: aero_flux_bootstrap
changed_when: false
retries: 30
delay: 10
until: aero_flux_bootstrap.stdout | trim is not match('^running')
- name: Показать логи flux-bootstrap при ошибке
ansible.builtin.command:
cmd: "{{ compose_cmd }} logs --no-color --tail 50 flux-bootstrap"
chdir: "{{ deploy_dir }}"
register: aero_flux_logs
changed_when: false
when: aero_flux_bootstrap.stdout.split() | last != "0"
- name: Прервать деплой, если bootstrap не удался
ansible.builtin.fail:
msg: |
flux bootstrap завершился с кодом {{ aero_flux_bootstrap.stdout.split() | last }}.
Логи контейнера:
{{ aero_flux_logs.stdout | default('(недоступны)') }}
when: aero_flux_bootstrap.stdout.split() | last != "0"
- name: Итог
ansible.builtin.debug:
msg:
- "gitea: http://{{ ansible_host }}:3000 (пользователь {{ gitea_admin_user }}, пароль в aero/.secrets/{{ inventory_hostname }}/GITEA_ADMIN_PASSWORD)"
- "Flux реконсилирует {{ gitea_org }}/{{ gitea_repo }} → {{ aero_flux_path }}"
- "Слои: infra-controllers (istio, cert-manager, vault-injector, local-path, rabbitmq, minio) → infra-configs → apps"
- "Состояние Flux: docker compose exec k3s-server kubectl -n flux-system get kustomizations,helmreleases -A"
- "Dashboard: https://dashboard.{{ platform_domain }}"
- "RabbitMQ: https://rabbitmq.{{ platform_domain }} (пользователь {{ sarex_rabbitmq_k8s_user }}, пароль в aero/.secrets/{{ inventory_hostname }}/SAREX_RABBITMQ_PASSWORD)"
- "MinIO UI: https://minio.{{ platform_domain }}/console/ (пользователь {{ sarex_minio_k8s_user }}, пароль в aero/.secrets/{{ inventory_hostname }}/SAREX_MINIO_ROOT_PASSWORD)"