Топология 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 сливает с дефолтами, возвращая их целиком.
170 lines
8.9 KiB
YAML
170 lines
8.9 KiB
YAML
---
|
||
# Интеграция 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'"
|