iac/aero/roles/sarex_stack/tasks/vault-k8s.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

170 lines
8.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.

---
# Интеграция Vault ↔ k3s: метод auth/kubernetes.
#
# Vault контура живёт в docker-compose, ВНЕ кластера, поэтому не может
# воспользоваться ни собственным ServiceAccount-токеном, ни CA из
# /var/run/secrets — всё это ему нужно передать явно:
# kubernetes_host — адрес apiserver, резолвится по compose-DNS;
# kubernetes_ca_cert — CA кластера (из секрета token reviewer'а);
# token_reviewer_jwt — долгоживущий токен SA vault-auth.
#
# После этого под с аннотациями vault.hashicorp.com/* получает от injector'а
# init-контейнер vault-agent, тот логинится в Vault токеном ServiceAccount'а
# пода, Vault валидирует его через TokenReview и отдаёт секреты файлами
# в /vault/secrets/.
#
# Все шаги идемпотентны: повторный прогон на настроенном Vault — no-op.
#
# ВНИМАНИЕ по стилю: команды здесь задаются ЛИТЕРАЛЬНЫМ блоком (|), а не
# складывающим (>-). В складывающем скаляре строка с бОльшим отступом считается
# "more-indented", и YAML СОХРАНЯЕТ перед ней перевод строки — многострочная
# команда разрывается посреди аргументов. Именно на этом падал первый вариант:
# `vault write auth/kubernetes/config` отрывался от своих параметров.
- name: Применить манифесты vault в k3s (DNS-мост + token reviewer)
ansible.builtin.shell:
cmd: |
{{ compose_cmd }} exec -T k3s-server sh -ec '
sed "s|__VAULT_BRIDGE_IP__|{{ vault_bridge_ip }}|g" /output/manifests/vault/bridge-vault.yaml |
kubectl apply -f -
kubectl apply -f /output/manifests/vault/vault-auth.yaml
'
chdir: "{{ deploy_dir }}"
changed_when: true
# Секрет читается ЦЕЛИКОМ в JSON, а поля достаются фильтром from_json.
#
# Так сделано не из вкуса: вариант `-o jsonpath={.data.ca\.crt}` здесь НЕ
# работает. Модуль command разбирает строку через shlex с обработкой escape-
# последовательностей и съедает обратный слэш, поэтому kubectl получает
# {.data.ca.crt} — ищет вложенное поле crt внутри ca, не находит и возвращает
# ПУСТУЮ строку с кодом 0. Отказ тихий: задача чтения проходит успешно, а
# падает уже `vault write` с пустым CA. Ключ token при этом читался верно —
# в нём нет точки, — что делало симптом ещё запутаннее.
#
# token-controller заполняет секрет асинхронно, поэтому ждём оба поля.
- name: Прочитать секрет token reviewer'а (дождавшись заполнения)
ansible.builtin.command:
cmd: >-
{{ compose_cmd }} exec -T k3s-server
kubectl -n vault get secret vault-auth-token -o json
chdir: "{{ deploy_dir }}"
register: vault_auth_secret
until: >-
vault_auth_secret.rc == 0
and '"ca.crt"' in vault_auth_secret.stdout
and '"token"' in vault_auth_secret.stdout
retries: 30
delay: 2
changed_when: false
no_log: true
- name: Выделить CA и JWT из секрета
ansible.builtin.set_fact:
vault_k8s_ca_b64: "{{ (vault_auth_secret.stdout | from_json).data['ca.crt'] }}"
vault_sa_token_b64: "{{ (vault_auth_secret.stdout | from_json).data['token'] }}"
no_log: true
- name: Проверить, что CA и JWT непустые
ansible.builtin.assert:
that:
- vault_k8s_ca_b64 | length > 0
- vault_sa_token_b64 | length > 0
fail_msg: >-
Секрет vault-auth-token прочитан, но CA или JWT пусты — Vault нельзя
настроить на валидацию токенов подов.
success_msg: "CA и JWT получены"
# Повторный вызов на уже включённом пути отдаёт "path is already in use"
# и ненулевой код — это штатно, глушим.
- name: Включить метод auth/kubernetes в Vault
ansible.builtin.shell:
cmd: |
{{ compose_cmd }} exec -T vault-init sh -ec '
export VAULT_TOKEN=$(cat /vault/init/root.token)
vault auth enable kubernetes 2>&1 | grep -v "path is already in use" || true
'
chdir: "{{ deploy_dir }}"
changed_when: true
# CA и JWT передаём внутрь контейнера base64-строкой и раскодируем во временные
# файлы: vault CLI умеет читать значение параметра из файла синтаксисом @путь,
# и так не приходится экранировать многострочный PEM в аргументах.
- name: Настроить auth/kubernetes (адрес apiserver, CA, token reviewer)
ansible.builtin.shell:
cmd: |
{{ compose_cmd }} exec -T vault-init sh -ec '
export VAULT_TOKEN=$(cat /vault/init/root.token)
umask 077
echo "{{ vault_k8s_ca_b64 }}" | base64 -d > /tmp/k8s-ca.crt
echo "{{ vault_sa_token_b64 }}" | base64 -d > /tmp/k8s-jwt
vault write auth/kubernetes/config kubernetes_host="{{ vault_k8s_host }}" kubernetes_ca_cert=@/tmp/k8s-ca.crt token_reviewer_jwt=@/tmp/k8s-jwt
rm -f /tmp/k8s-ca.crt /tmp/k8s-jwt
'
chdir: "{{ deploy_dir }}"
changed_when: true
no_log: true
# Политика доступа к kv-v2 на пути secrets/ — том самом, что включает vault-init
# и на который ссылаются аннотации приложений (secrets/data/apps/<app>/...).
- name: Создать политику чтения секретов
ansible.builtin.shell:
cmd: |
{{ compose_cmd }} exec -T vault-init sh -ec '
export VAULT_TOKEN=$(cat /vault/init/root.token)
vault policy write {{ vault_apps_policy }} - <<EOF
path "secrets/data/*" {
capabilities = ["read"]
}
path "secrets/metadata/*" {
capabilities = ["read", "list"]
}
EOF
'
chdir: "{{ deploy_dir }}"
changed_when: true
# Роль на приложение: связывает ServiceAccount'ы в namespace с политикой.
# Имя роли — то, что приложение указывает в vault.hashicorp.com/role.
- name: Создать роли auth/kubernetes для приложений
ansible.builtin.shell:
cmd: |
{{ compose_cmd }} exec -T vault-init sh -ec '
export VAULT_TOKEN=$(cat /vault/init/root.token)
vault write auth/kubernetes/role/{{ item.name }} bound_service_account_names="{{ item.service_accounts | default(["*"]) | join(",") }}" bound_service_account_namespaces="{{ item.namespaces | join(",") }}" policies="{{ item.policies | default([vault_apps_policy]) | join(",") }}" ttl={{ item.ttl | default(vault_role_ttl) }}
'
chdir: "{{ deploy_dir }}"
loop: "{{ vault_k8s_roles }}"
loop_control:
label: "{{ item.name }}"
changed_when: true
# Значения передаём JSON'ом через stdin, а не парами key=value в аргументах:
# так не приходится экранировать пробелы и спецсимволы внутри вложенных кавычек.
#
# kv-v2 версионирует записи, поэтому повторный прогон создаёт новую версию с тем
# же содержимым. Для демо-секрета это безобидно; боевые секреты сюда не кладутся.
- name: Положить креды сервисов в Vault
ansible.builtin.shell:
cmd: |
{{ compose_cmd }} exec -T vault-init sh -ec '
export VAULT_TOKEN=$(cat /vault/init/root.token)
vault kv put {{ item.path }} - <<EOF
{{ item.data | to_json }}
EOF
'
chdir: "{{ deploy_dir }}"
loop: "{{ vault_app_secrets }}"
loop_control:
label: "{{ item.path }}"
changed_when: true
# Пароли сервисов не должны попасть в лог.
no_log: true
- name: Итог интеграции Vault ↔ k3s
ansible.builtin.debug:
msg:
- "auth/kubernetes настроен на {{ vault_k8s_host }}"
- "Роли: {{ vault_k8s_roles | map(attribute='name') | join(', ') }}"
- "Проверка ролей: docker compose exec vault-init sh -c 'VAULT_TOKEN=$(cat /vault/init/root.token) vault list auth/kubernetes/role'"