iac/aero/roles/sarex_stack/tasks/platform.yml
emelinda e1a81d3dd8 Processing: секрет yc-s3 для подов задач workflows
Задача падала ещё до обращения к S3, на разборе конфигурации:
  pydantic ValidationError for S3AccountModel
  Value error, Expecting value: line 1 column 1 (char 0)
  ValueError: SERVICE_S3 is not valid
"Expecting value ... char 0" — это разбор пустоты: файла по пути не было.

Секрет secrets/minio/apps/processing из Vault при этом настроен верно, и
движок его видит (S3_SERVICE_ACCOUNT=/vault/secrets/processing-s3). Но в
поды задач движок его НЕ передаёт — для них у него отдельный механизм,
зашитый в код:

  services[S3Storage] = Service{
      CredentialsName: "yc-s3",
      CredentialsPath: "/etc/sarex/yc-s3",
      Envs: {"SERVICE_S3": `{"service_account":
             "/etc/sarex/yc-s3/yc-s3-service-account.json"}`},
  }
  (pkg/kube_services/services.go)

То есть он монтирует в Job обычный Secret с именем yc-s3 из namespace
задач (JOBS_NAMESPACE=processing). Ни имя секрета, ни путь, ни имя файла
не настраиваются.

vault-agent сюда не дотягивается по построению: поды задач движок создаёт
сам во время работы, аннотаций инжекции у них нет. Поэтому секрет заводит
ansible — tasks/app-secrets.yml, шаблон app-secrets.yaml.j2 и описание в
k8s_file_secrets. Формат содержимого повторяет
engine/yc-s3-service-account.json из compose-стека и совпадает с тем, что
vault-agent рендерит самому движку.

Манифест рендерится, применяется и сразу удаляется — в нём креды открытым
текстом, ровно как у секретов реестра. Namespace объявлен в том же
манифесте: на холодном старте Flux до слоя apps ещё не дошёл, а apply в
несуществующий namespace упал бы. Повторное создание безвредно, так же
заведён namespace vault.
2026-08-09 23:30:53 +03:00

131 lines
7.4 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
# Секреты-файлы для подов, которые создаёт сам движок workflows. Идут сразу за
# секретами реестра: намеренно ДО Flux, потому что namespace задач манифест
# создаёт сам — ждать слой apps незачем.
- name: Создать секреты-файлы для подов задач
ansible.builtin.include_tasks: app-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)"