Топология 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 сливает с дефолтами, возвращая их целиком.
125 lines
6.9 KiB
YAML
125 lines
6.9 KiB
YAML
---
|
||
# Подъём 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)"
|