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 сливает с дефолтами, возвращая их целиком.
This commit is contained in:
parent
da8e40c044
commit
565faa0e17
@ -2,6 +2,25 @@
|
|||||||
# Каталог развёртывания на хосте (домашка root)
|
# Каталог развёртывания на хосте (домашка root)
|
||||||
deploy_dir: /root/sarex
|
deploy_dir: /root/sarex
|
||||||
|
|
||||||
|
# --- Модули ядра для istio-init --------------------------------------------
|
||||||
|
# Ноды k3s — это docker-контейнеры, своего ядра у них нет: любые операции с
|
||||||
|
# iptables внутри подов идут через ядро ХОСТА. istio-init настраивает перехват
|
||||||
|
# трафика и создаёт legacy-таблицу nat, поэтому на хосте должен быть загружен
|
||||||
|
# iptable_nat.
|
||||||
|
#
|
||||||
|
# На RED OS 8 (ядро 6.6, iptables в режиме nf_tables) он по умолчанию НЕ
|
||||||
|
# загружен: присутствуют только nft-варианты (nft_chain_nat, xt_nat). Без этих
|
||||||
|
# модулей istio-init падает с
|
||||||
|
# xtables parameter problem: iptables-restore: unable to initialize table 'nat'
|
||||||
|
# и КАЖДЫЙ под в namespace с istio-injection=enabled навсегда остаётся в
|
||||||
|
# Init:Error — именно так встал kubernetes-dashboard.
|
||||||
|
aero_kernel_modules:
|
||||||
|
- iptable_nat
|
||||||
|
- iptable_mangle
|
||||||
|
- iptable_raw
|
||||||
|
- xt_REDIRECT
|
||||||
|
- xt_owner
|
||||||
|
|
||||||
# Источник файлов — корень репозитория iac (playbook лежит в aero/)
|
# Источник файлов — корень репозитория iac (playbook лежит в aero/)
|
||||||
deploy_src_root: "{{ playbook_dir }}/.."
|
deploy_src_root: "{{ playbook_dir }}/.."
|
||||||
|
|
||||||
@ -58,6 +77,11 @@ registry_secrets:
|
|||||||
- {name: regcred, namespace: cert-manager}
|
- {name: regcred, namespace: cert-manager}
|
||||||
- {name: regcred, namespace: istio-system}
|
- {name: regcred, namespace: istio-system}
|
||||||
- {name: regcred, namespace: kubernetes-dashboard}
|
- {name: regcred, namespace: kubernetes-dashboard}
|
||||||
|
# Образы vault-k8s и vault-agent для injector'а тоже лежат в cr.yandex.
|
||||||
|
- {name: regcred, namespace: vault}
|
||||||
|
- {name: regcred, namespace: local-path-provisioner}
|
||||||
|
- {name: regcred, namespace: rabbitmq}
|
||||||
|
- {name: regcred, namespace: minio}
|
||||||
|
|
||||||
# Поднимать стек (docker compose up -d). Требует запущенной службы docker и
|
# Поднимать стек (docker compose up -d). Требует запущенной службы docker и
|
||||||
# docker login в cr.yandex — по умолчанию выключено.
|
# docker login в cr.yandex — по умолчанию выключено.
|
||||||
@ -75,6 +99,50 @@ gitea_repo: iac
|
|||||||
aero_git_branch: master
|
aero_git_branch: master
|
||||||
aero_flux_path: clusters/aero
|
aero_flux_path: clusters/aero
|
||||||
|
|
||||||
|
# --- Синхронизация репозитория в gitea --------------------------------------
|
||||||
|
# Содержимое едет с control-node на хост архивом и коммитится в gitea оттуда:
|
||||||
|
# наружу gitea не публикуется дальше самого хоста, а git на control-node может
|
||||||
|
# быть не настроен на работу с контуром.
|
||||||
|
#
|
||||||
|
# Список — это ЗАМЫКАНИЕ ссылок clusters/aero, а не весь репозиторий:
|
||||||
|
# clusters/aero/controllers ссылается на ../../../infrastructure/<компонент>/aero,
|
||||||
|
# каждый такой overlay тянет только ../base. Каталоги ansible-роли, snapshots и
|
||||||
|
# docs кластеру не нужны и не синхронизируются.
|
||||||
|
#
|
||||||
|
# ВАЖНО: добавил компонент в clusters/aero/controllers — добавь его сюда, иначе
|
||||||
|
# kustomize в кластере упадёт на отсутствующем каталоге.
|
||||||
|
gitea_sync_paths:
|
||||||
|
- clusters/aero
|
||||||
|
- infrastructure/cert-manager
|
||||||
|
- infrastructure/dashboard
|
||||||
|
- infrastructure/istio-base
|
||||||
|
- infrastructure/istio-config
|
||||||
|
- infrastructure/istio-gateway
|
||||||
|
- infrastructure/istio-pilot
|
||||||
|
- infrastructure/vault-injector
|
||||||
|
- infrastructure/local-path-provisioner
|
||||||
|
- infrastructure/rabbitmq
|
||||||
|
- infrastructure/minio
|
||||||
|
|
||||||
|
# Рабочая копия репозитория gitea на хосте (клон, живёт между прогонами).
|
||||||
|
gitea_sync_workdir: "{{ deploy_dir }}/gitea-sync"
|
||||||
|
gitea_sync_archive_remote: "{{ deploy_dir }}/gitea-sync.tar.gz"
|
||||||
|
|
||||||
|
# Адрес gitea с точки зрения ХОСТА: контейнер публикует 3000 наружу.
|
||||||
|
gitea_http_addr: "http://127.0.0.1:3000"
|
||||||
|
|
||||||
|
# URL с кредами для push'а. sarex_secrets заполняется в tasks/main.yml, поэтому
|
||||||
|
# выражение раскрывается лениво — к моменту использования пароль уже есть.
|
||||||
|
gitea_sync_url: >-
|
||||||
|
http://{{ gitea_admin_user }}:{{ sarex_secrets.GITEA_ADMIN_PASSWORD | urlencode }}@127.0.0.1:3000/{{ gitea_org }}/{{ gitea_repo }}.git
|
||||||
|
|
||||||
|
# Временный архив на control-node (удаляется после синхронизации).
|
||||||
|
gitea_sync_archive_local: /tmp/aero-gitea-sync.tar.gz
|
||||||
|
|
||||||
|
# Автор коммитов синхронизации.
|
||||||
|
gitea_commit_name: aero-sync
|
||||||
|
gitea_commit_email: "aero-sync@{{ platform_domain }}"
|
||||||
|
|
||||||
# Поднимается отдельно от прикладного стека: образы публичные, docker login в
|
# Поднимается отдельно от прикладного стека: образы публичные, docker login в
|
||||||
# cr.yandex не нужен. По умолчанию выключено, включает `uv run poe platform`.
|
# cr.yandex не нужен. По умолчанию выключено, включает `uv run poe platform`.
|
||||||
aero_platform_up: false
|
aero_platform_up: false
|
||||||
@ -85,17 +153,23 @@ aero_platform_services_wave1:
|
|||||||
- k3s-server
|
- k3s-server
|
||||||
- k3s-worker-1
|
- k3s-worker-1
|
||||||
- k3s-worker-2
|
- k3s-worker-2
|
||||||
- k3s-worker-3
|
|
||||||
- gitea
|
- gitea
|
||||||
|
|
||||||
# Волна 2: провижининг и bootstrap. gitea-init, flux-k8s-init и flux-bootstrap
|
# Волна 2: провижининг. gitea-init и flux-k8s-init одноразовые (restart: "no"),
|
||||||
# одноразовые (restart: "no"), vault-init живёт постоянно и распечатывает Vault
|
# vault-init живёт постоянно и распечатывает Vault после ребута хоста.
|
||||||
# после ребута хоста.
|
#
|
||||||
|
# flux-bootstrap ВЫНЕСЕН в волну 3 намеренно: между волнами Vault получает
|
||||||
|
# auth/kubernetes, а репозиторий контура синхронизируется в gitea. Запусти
|
||||||
|
# bootstrap раньше — Flux привязался бы к репозиторию, в котором ещё нет ни
|
||||||
|
# clusters/aero, ни infrastructure, и первая же реконсиляция упала бы.
|
||||||
aero_platform_services_wave2:
|
aero_platform_services_wave2:
|
||||||
- gitea-init
|
- gitea-init
|
||||||
- flux-k8s-init
|
- flux-k8s-init
|
||||||
- vault
|
- vault
|
||||||
- vault-init
|
- vault-init
|
||||||
|
|
||||||
|
# Волна 3: bootstrap FluxCD. Запускается после наполнения gitea.
|
||||||
|
aero_platform_services_wave3:
|
||||||
- flux-bootstrap
|
- flux-bootstrap
|
||||||
|
|
||||||
# Ноды k3s. Каждой нужен свой каталог PVC-хранилища на хосте: провижинер
|
# Ноды k3s. Каждой нужен свой каталог PVC-хранилища на хосте: провижинер
|
||||||
@ -104,11 +178,57 @@ aero_k3s_nodes:
|
|||||||
- server
|
- server
|
||||||
- worker-1
|
- worker-1
|
||||||
- worker-2
|
- worker-2
|
||||||
- worker-3
|
|
||||||
|
|
||||||
# Сколько ждать появления kubeconfig от k3s-server, секунд.
|
# Сколько ждать появления kubeconfig от k3s-server, секунд.
|
||||||
aero_kubeconfig_timeout: 180
|
aero_kubeconfig_timeout: 180
|
||||||
|
|
||||||
|
# --- Интеграция Vault ↔ k3s (метод auth/kubernetes) -------------------------
|
||||||
|
# Статический IP контейнера vault в compose-сети. ДОЛЖЕН совпадать с
|
||||||
|
# ipv4_address сервиса vault в docker-compose.yaml: отсюда значение
|
||||||
|
# подставляется в Endpoints DNS-моста (k3s/manifests/vault/bridge-vault.yaml).
|
||||||
|
vault_bridge_ip: 172.28.0.13
|
||||||
|
|
||||||
|
# Адрес apiserver, по которому Vault валидирует токены подов. Vault живёт в
|
||||||
|
# compose, поэтому имя резолвится compose-DNS, а не CoreDNS кластера.
|
||||||
|
vault_k8s_host: "https://k3s-server:6443"
|
||||||
|
|
||||||
|
# Политика чтения kv-v2 на пути secrets/ — том же, что включает vault-init.
|
||||||
|
vault_apps_policy: apps-read
|
||||||
|
|
||||||
|
# TTL выдаваемого агенту токена по умолчанию.
|
||||||
|
vault_role_ttl: 24h
|
||||||
|
|
||||||
|
# Роли auth/kubernetes. Имя роли — то, что приложение указывает в аннотации
|
||||||
|
# vault.hashicorp.com/role, namespace — где живут его поды. Пополняется по мере
|
||||||
|
# переезда приложений из compose в k3s.
|
||||||
|
vault_k8s_roles:
|
||||||
|
- {name: rabbitmq, namespaces: [rabbitmq]}
|
||||||
|
- {name: minio, namespaces: [minio]}
|
||||||
|
- {name: django, namespaces: [django]}
|
||||||
|
- {name: processing, namespaces: [processing]}
|
||||||
|
|
||||||
|
# Боевые креды инфраструктурных сервисов. Пути и имена ключей заданы чартами
|
||||||
|
# (см. vaultRoot у minio и auth.vault у rabbitmq в infrastructure/*/aero) —
|
||||||
|
# менять их в отрыве от values нельзя.
|
||||||
|
#
|
||||||
|
# Пароли не хранятся в репозитории: берутся из sarex_secrets, который
|
||||||
|
# генерирует и персистит в aero/.secrets задача "Сгенерировать/загрузить пароли".
|
||||||
|
vault_app_secrets:
|
||||||
|
- path: secrets/rabbitmq/auth
|
||||||
|
data:
|
||||||
|
username: "{{ sarex_rabbitmq_k8s_user }}"
|
||||||
|
password: "{{ sarex_secrets.SAREX_RABBITMQ_PASSWORD }}"
|
||||||
|
- path: secrets/minio/admin
|
||||||
|
data:
|
||||||
|
rootUser: "{{ sarex_minio_k8s_user }}"
|
||||||
|
rootPassword: "{{ sarex_secrets.SAREX_MINIO_ROOT_PASSWORD }}"
|
||||||
|
|
||||||
|
# Логины сервисов в k3s. Совпадают со значениями в .env.example для
|
||||||
|
# одноимённых сервисов compose; пароли к ним — в sarex_secrets (те же
|
||||||
|
# переменные: контур переезжает в k3s, плодить вторую пару кредов незачем).
|
||||||
|
sarex_rabbitmq_k8s_user: rabbit
|
||||||
|
sarex_minio_k8s_user: minioadmin
|
||||||
|
|
||||||
# Сервисы приложения, поднимаемые при sarex_compose_up (depends_on тянет
|
# Сервисы приложения, поднимаемые при sarex_compose_up (depends_on тянет
|
||||||
# зависимости и порядок). k3s/gitea намеренно не поднимаем.
|
# зависимости и порядок). k3s/gitea намеренно не поднимаем.
|
||||||
sarex_services:
|
sarex_services:
|
||||||
|
|||||||
140
aero/roles/sarex_stack/tasks/gitea-sync.yml
Normal file
140
aero/roles/sarex_stack/tasks/gitea-sync.yml
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
---
|
||||||
|
# Синхронизация репозитория контура в gitea.
|
||||||
|
#
|
||||||
|
# Flux читает манифесты ИЗ gitea, а не из этого репозитория, поэтому содержимое
|
||||||
|
# нужно туда доставить. Схема: control-node пакует нужное подмножество путей в
|
||||||
|
# архив → ansible кладёт его на хост → на хосте архив распаковывается в рабочую
|
||||||
|
# копию репозитория gitea и коммитится.
|
||||||
|
#
|
||||||
|
# Почему коммит делается на хосте, а не push'ем с control-node: gitea публикует
|
||||||
|
# порт только на самом хосте, и контур остаётся замкнутым — наружу за git никто
|
||||||
|
# не ходит.
|
||||||
|
#
|
||||||
|
# Синхронизируется ЗАМЫКАНИЕ ссылок clusters/aero (gitea_sync_paths), а не весь
|
||||||
|
# репозиторий: ansible-роль, snapshots и docs кластеру не нужны.
|
||||||
|
#
|
||||||
|
# Идемпотентно: если содержимое не изменилось, коммита не будет.
|
||||||
|
|
||||||
|
- name: Установить git на хосте (нужен для коммита в gitea)
|
||||||
|
ansible.builtin.package:
|
||||||
|
name: git
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Проверить, что синхронизируемые пути существуют
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ deploy_src_root }}/{{ item }}"
|
||||||
|
loop: "{{ gitea_sync_paths }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item }}"
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
register: gitea_sync_stat
|
||||||
|
|
||||||
|
- name: Прервать деплой, если путь из gitea_sync_paths отсутствует
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: >-
|
||||||
|
Путь {{ item.item }} указан в gitea_sync_paths, но отсутствует в
|
||||||
|
репозитории. Список должен совпадать с тем, на что ссылается
|
||||||
|
clusters/aero — иначе kustomize в кластере упадёт.
|
||||||
|
loop: "{{ gitea_sync_stat.results }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.item }}"
|
||||||
|
when: not item.stat.exists
|
||||||
|
|
||||||
|
# Архив собирается из РАБОЧЕГО ДЕРЕВА, а не из HEAD: синхронизировать нужно то,
|
||||||
|
# что лежит на диске, включая ещё не закоммиченные локально правки.
|
||||||
|
- name: Собрать архив синхронизируемых путей на control-node
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: "tar -czf {{ gitea_sync_archive_local }} {{ gitea_sync_paths | join(' ') }}"
|
||||||
|
chdir: "{{ deploy_src_root }}"
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Запомнить исходную ревизию (для сообщения коммита)
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: git rev-parse --short HEAD
|
||||||
|
chdir: "{{ deploy_src_root }}"
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
register: gitea_sync_rev
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Скопировать архив на хост
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ gitea_sync_archive_local }}"
|
||||||
|
dest: "{{ gitea_sync_archive_remote }}"
|
||||||
|
mode: "0600"
|
||||||
|
|
||||||
|
- name: Синхронизировать и закоммитить содержимое в gitea
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: |
|
||||||
|
set -eu
|
||||||
|
WORKDIR={{ gitea_sync_workdir | quote }}
|
||||||
|
BRANCH={{ aero_git_branch | quote }}
|
||||||
|
URL={{ gitea_sync_url | quote }}
|
||||||
|
|
||||||
|
mkdir -p "$WORKDIR"
|
||||||
|
cd "$WORKDIR"
|
||||||
|
|
||||||
|
# Клон может не существовать, а репозиторий в gitea — быть пустым (до
|
||||||
|
# первого коммита у него нет ни одной ветки). init+fetch переживает оба
|
||||||
|
# случая, в отличие от `git clone`, который на пустом репозитории
|
||||||
|
# оставляет рабочую копию без ветки.
|
||||||
|
[ -d .git ] || git init -q
|
||||||
|
git config user.name {{ gitea_commit_name | quote }}
|
||||||
|
git config user.email {{ gitea_commit_email | quote }}
|
||||||
|
git remote remove origin 2>/dev/null || true
|
||||||
|
git remote add origin "$URL"
|
||||||
|
|
||||||
|
if git fetch -q origin "$BRANCH" 2>/dev/null; then
|
||||||
|
git checkout -q -B "$BRANCH" FETCH_HEAD
|
||||||
|
else
|
||||||
|
git checkout -q -B "$BRANCH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Управляемые пути удаляются перед распаковкой, иначе файлы, удалённые в
|
||||||
|
# исходном репозитории, остались бы в gitea навсегда: `git add -A` увидит
|
||||||
|
# удаление только если файла действительно нет на диске.
|
||||||
|
for p in {{ gitea_sync_paths | join(' ') }}; do
|
||||||
|
rm -rf "$p"
|
||||||
|
done
|
||||||
|
|
||||||
|
tar -xzf {{ gitea_sync_archive_remote | quote }}
|
||||||
|
|
||||||
|
git add -A
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "SYNC_RESULT=nochange"
|
||||||
|
else
|
||||||
|
git commit -q -m "sync from control-node ({{ gitea_sync_rev.stdout | default('unknown') }})"
|
||||||
|
echo "SYNC_RESULT=committed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git push -q origin "$BRANCH"
|
||||||
|
echo "SYNC_DONE"
|
||||||
|
chdir: "{{ deploy_dir }}"
|
||||||
|
register: gitea_sync_result
|
||||||
|
changed_when: "'SYNC_RESULT=committed' in gitea_sync_result.stdout"
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: Удалить архив с хоста
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ gitea_sync_archive_remote }}"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Удалить архив с control-node
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ gitea_sync_archive_local }}"
|
||||||
|
state: absent
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
|
||||||
|
- name: Итог синхронизации
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
gitea {{ gitea_org }}/{{ gitea_repo }}@{{ aero_git_branch }}:
|
||||||
|
{{ 'закоммичены изменения' if 'SYNC_RESULT=committed' in gitea_sync_result.stdout
|
||||||
|
else 'изменений нет' }}
|
||||||
|
(источник {{ gitea_sync_rev.stdout | default('unknown') }},
|
||||||
|
путей: {{ gitea_sync_paths | length }})
|
||||||
@ -1,4 +1,17 @@
|
|||||||
---
|
---
|
||||||
|
# --- Предпосылки на хосте -------------------------------------------------
|
||||||
|
# Загружаем ДО подъёма k3s: istio-init внутри подов правит iptables через ядро
|
||||||
|
# хоста, и без этих модулей поды в namespace с istio-injection не стартуют.
|
||||||
|
# persistent: present закрепляет загрузку в /etc/modules-load.d, иначе после
|
||||||
|
# перезагрузки хоста контур снова развалился бы.
|
||||||
|
- name: Загрузить модули ядра, нужные istio-init
|
||||||
|
community.general.modprobe:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: present
|
||||||
|
persistent: present
|
||||||
|
loop: "{{ aero_kernel_modules }}"
|
||||||
|
tags: [kernel]
|
||||||
|
|
||||||
# --- Каталоги и файлы ----------------------------------------------------
|
# --- Каталоги и файлы ----------------------------------------------------
|
||||||
- name: Создать каталог развёртывания
|
- name: Создать каталог развёртывания
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@ -33,12 +33,44 @@
|
|||||||
- name: Создать секреты доступа к реестру в кластере
|
- name: Создать секреты доступа к реестру в кластере
|
||||||
ansible.builtin.include_tasks: registry-secrets.yml
|
ansible.builtin.include_tasks: registry-secrets.yml
|
||||||
|
|
||||||
- name: Волна 2 — gitea-init, vault(+init) и bootstrap Flux
|
- name: Волна 2 — gitea-init, flux-k8s-init и vault(+init)
|
||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
cmd: "{{ compose_cmd }} up -d {{ aero_platform_services_wave2 | join(' ') }}"
|
cmd: "{{ compose_cmd }} up -d {{ aero_platform_services_wave2 | join(' ') }}"
|
||||||
chdir: "{{ deploy_dir }}"
|
chdir: "{{ deploy_dir }}"
|
||||||
changed_when: true
|
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. Забираем его явно, иначе ошибка
|
# flux-bootstrap одноразовый: успех = код 0. Забираем его явно, иначе ошибка
|
||||||
# bootstrap'а останется незамеченной — `compose up -d` о ней не сообщает.
|
# bootstrap'а останется незамеченной — `compose up -d` о ней не сообщает.
|
||||||
#
|
#
|
||||||
@ -85,4 +117,8 @@
|
|||||||
msg:
|
msg:
|
||||||
- "gitea: http://{{ ansible_host }}:3000 (пользователь {{ gitea_admin_user }}, пароль в aero/.secrets/{{ inventory_hostname }}/GITEA_ADMIN_PASSWORD)"
|
- "gitea: http://{{ ansible_host }}:3000 (пользователь {{ gitea_admin_user }}, пароль в aero/.secrets/{{ inventory_hostname }}/GITEA_ADMIN_PASSWORD)"
|
||||||
- "Flux реконсилирует {{ gitea_org }}/{{ gitea_repo }} → {{ aero_flux_path }}"
|
- "Flux реконсилирует {{ gitea_org }}/{{ gitea_repo }} → {{ aero_flux_path }}"
|
||||||
- "Проверка: docker compose exec k3s-server kubectl -n flux-system get kustomizations"
|
- "Слои: 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)"
|
||||||
|
|||||||
169
aero/roles/sarex_stack/tasks/vault-k8s.yml
Normal file
169
aero/roles/sarex_stack/tasks/vault-k8s.yml
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
---
|
||||||
|
# Интеграция 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'"
|
||||||
24
clusters/aero/apps.yaml
Normal file
24
clusters/aero/apps.yaml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Слой приложений. Отделён от инфраструктуры по той же причине, что controllers
|
||||||
|
# от configs (см. infrastructure.yaml): приложениям нужны уже готовые CRD и
|
||||||
|
# работающие вебхуки.
|
||||||
|
#
|
||||||
|
# dependsOn: infra-controllers ставит istiod и vault-agent-injector, а под
|
||||||
|
# тестового приложения без них не поднимется — MutatingWebhook инжектора
|
||||||
|
# перехватывает создание пода, и пока вебхук не готов, под не создастся.
|
||||||
|
---
|
||||||
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: apps
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 10m0s
|
||||||
|
path: ./clusters/aero/apps
|
||||||
|
prune: true
|
||||||
|
wait: true
|
||||||
|
timeout: 10m0s
|
||||||
|
dependsOn:
|
||||||
|
- name: infra-controllers
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: flux-system
|
||||||
15
clusters/aero/apps/kustomization.yaml
Normal file
15
clusters/aero/apps/kustomization.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Слой 3: прикладные сервисы контура.
|
||||||
|
#
|
||||||
|
# Сейчас пуст. Тестовое приложение (apps/hello) удалено за ненадобностью: оно
|
||||||
|
# существовало, чтобы доказать работоспособность подложки — доставку Flux'ом,
|
||||||
|
# внедрение sidecar istio и получение секрета из Vault. Теперь это же
|
||||||
|
# подтверждают боевые сервисы: rabbitmq и minio поднимаются с кредами,
|
||||||
|
# приезжающими из Vault через vault-agent-injector.
|
||||||
|
#
|
||||||
|
# Слой намеренно оставлен пустым, а не удалён: следующий этап — перенос
|
||||||
|
# прикладных сервисов из compose в k3s, и они приедут именно сюда. Пустой
|
||||||
|
# resources корректен для kustomize; Flux при этом удалит из кластера то, что
|
||||||
|
# раньше применял этот слой (prune: true).
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources: []
|
||||||
@ -4,15 +4,25 @@
|
|||||||
# Порядок внутри слоя обеспечивают dependsOn в самих HelmRelease:
|
# Порядок внутри слоя обеспечивают dependsOn в самих HelmRelease:
|
||||||
# istiod и ingressgateway ждут istio-base, istio-config ждёт всех троих.
|
# istiod и ingressgateway ждут istio-base, istio-config ждёт всех троих.
|
||||||
#
|
#
|
||||||
# local-path-provisioner намеренно НЕ подключён: в k3s он встроен и уже
|
# local-path-provisioner подключён ЯВНО, а встроенный в k3s — отключён флагом
|
||||||
# обслуживает StorageClass local-path (по умолчанию). Чарт из репозитория
|
# --disable=local-storage (см. docker-compose.yaml). Два провижинера с одним
|
||||||
# создал бы второй провижинер с тем же именем класса.
|
# именем StorageClass дрались бы за одни и те же PVC, поэтому ровно один из них
|
||||||
|
# должен быть активен. Каталог данных не изменился: чарту задан тот же путь
|
||||||
|
# /var/lib/rancher/k3s/storage, что смонтирован с хоста.
|
||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
resources:
|
resources:
|
||||||
|
# Хранилище идёт первым: PVC rabbitmq и minio без StorageClass не создадутся.
|
||||||
|
- ../../../infrastructure/local-path-provisioner/aero
|
||||||
- ../../../infrastructure/cert-manager/aero
|
- ../../../infrastructure/cert-manager/aero
|
||||||
- ../../../infrastructure/istio-base/aero
|
- ../../../infrastructure/istio-base/aero
|
||||||
- ../../../infrastructure/istio-pilot/aero
|
- ../../../infrastructure/istio-pilot/aero
|
||||||
- ../../../infrastructure/istio-gateway/aero
|
- ../../../infrastructure/istio-gateway/aero
|
||||||
- ../../../infrastructure/istio-config/aero
|
- ../../../infrastructure/istio-config/aero
|
||||||
- ../../../infrastructure/dashboard/aero
|
- ../../../infrastructure/dashboard/aero
|
||||||
|
# Только Vault Agent Injector: сам Vault живёт в compose, а не в кластере.
|
||||||
|
- ../../../infrastructure/vault-injector/aero
|
||||||
|
# Брокер и объектное хранилище. Креды берут из Vault через injector, поэтому
|
||||||
|
# идут после него. Админки выставлены наружу через istio-config/aero.
|
||||||
|
- ../../../infrastructure/rabbitmq/aero
|
||||||
|
- ../../../infrastructure/minio/aero
|
||||||
|
|||||||
@ -7,8 +7,29 @@
|
|||||||
# и не применяется НИЧЕГО — включая сам cert-manager. Дедлок: CRD никогда не
|
# и не применяется НИЧЕГО — включая сам cert-manager. Дедлок: CRD никогда не
|
||||||
# появятся, потому что слой не может пройти проверку.
|
# появятся, потому что слой не может пройти проверку.
|
||||||
#
|
#
|
||||||
# Поэтому: controllers ставят чарты (и CRD), configs применяются после них
|
# Поэтому: controllers ставят чарты (и CRD), configs содержат только
|
||||||
# через dependsOn и содержат только пользовательские ресурсы.
|
# пользовательские ресурсы.
|
||||||
|
#
|
||||||
|
# ПОЧЕМУ У infra-configs НЕТ dependsOn (было — и создавало дедлок).
|
||||||
|
#
|
||||||
|
# В configs лежат ClusterIssuer'ы. Чарты из слоя controllers создают
|
||||||
|
# Certificate, а Flux после установки релиза ждёт готовности его ресурсов.
|
||||||
|
# С dependsOn получался круг:
|
||||||
|
# чарт с Certificate ждёт выпуска
|
||||||
|
# → выпуск ждёт ClusterIssuer
|
||||||
|
# → ClusterIssuer в configs
|
||||||
|
# → configs ждёт готовности controllers
|
||||||
|
# → а там висит тот самый чарт
|
||||||
|
# Наступали на это дважды: сначала istio-config, следом rabbitmq (тот провисел
|
||||||
|
# в "Running 'install' action" полчаса).
|
||||||
|
#
|
||||||
|
# Жёсткий порядок заменён сходимостью через повторы: пока cert-manager не
|
||||||
|
# поставил CRD, configs будет падать на dry-run и переприменяться каждые
|
||||||
|
# 10 минут, а как только CRD появятся — применится сам. Издатели при этом
|
||||||
|
# больше не зависят от здоровья чартов, которые в них нуждаются.
|
||||||
|
#
|
||||||
|
# apps по-прежнему ждёт controllers, и это правильно: без istiod и
|
||||||
|
# vault-agent-injector его поды физически не поднимутся.
|
||||||
---
|
---
|
||||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
@ -34,8 +55,9 @@ spec:
|
|||||||
interval: 10m0s
|
interval: 10m0s
|
||||||
path: ./clusters/aero/configs
|
path: ./clusters/aero/configs
|
||||||
prune: true
|
prune: true
|
||||||
dependsOn:
|
# dependsOn намеренно отсутствует — см. комментарий в начале файла.
|
||||||
- name: infra-controllers
|
# Повторные попытки до появления CRD дешевле, чем дедлок.
|
||||||
|
retryInterval: 1m0s
|
||||||
sourceRef:
|
sourceRef:
|
||||||
kind: GitRepository
|
kind: GitRepository
|
||||||
name: flux-system
|
name: flux-system
|
||||||
|
|||||||
@ -5,3 +5,5 @@ resources:
|
|||||||
- ./helm-repositories.yaml
|
- ./helm-repositories.yaml
|
||||||
# Два слоя инфраструктуры с dependsOn — см. комментарий внутри файла.
|
# Два слоя инфраструктуры с dependsOn — см. комментарий внутри файла.
|
||||||
- ./infrastructure.yaml
|
- ./infrastructure.yaml
|
||||||
|
# Слой приложений. Пока пуст — сюда приедут сервисы при переносе из compose.
|
||||||
|
- ./apps.yaml
|
||||||
|
|||||||
@ -37,6 +37,12 @@ services:
|
|||||||
- --tls-san=k3s-server
|
- --tls-san=k3s-server
|
||||||
- --tls-san=${K3S_API_HOST:-127.0.0.1} # внешний IP/домен для доступа к apiserver извне
|
- --tls-san=${K3S_API_HOST:-127.0.0.1} # внешний IP/домен для доступа к apiserver извне
|
||||||
- --disable=traefik
|
- --disable=traefik
|
||||||
|
# Встроенный в k3s local-path-provisioner отключён: его заменяет чарт
|
||||||
|
# local-path-provisioner из репозитория (clusters/aero/controllers).
|
||||||
|
# Два провижинера с одним именем StorageClass дрались бы за одни и те же
|
||||||
|
# PVC. Каталог данных при этом НЕ меняется — чарту задан тот же путь
|
||||||
|
# /var/lib/rancher/k3s/storage, который смонтирован с хоста ниже.
|
||||||
|
- --disable=local-storage
|
||||||
privileged: true
|
privileged: true
|
||||||
cgroup: host # kubelet в cgroup v2 (Ubuntu 22.04+/RedOS)
|
cgroup: host # kubelet в cgroup v2 (Ubuntu 22.04+/RedOS)
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@ -44,10 +50,19 @@ services:
|
|||||||
K3S_TOKEN: ${K3S_TOKEN:-supersecrettoken}
|
K3S_TOKEN: ${K3S_TOKEN:-supersecrettoken}
|
||||||
K3S_KUBECONFIG_OUTPUT: /output/kubeconfig.yaml
|
K3S_KUBECONFIG_OUTPUT: /output/kubeconfig.yaml
|
||||||
K3S_KUBECONFIG_MODE: "644"
|
K3S_KUBECONFIG_MODE: "644"
|
||||||
#ports:
|
ports:
|
||||||
# - "6443:6443" # kube-apiserver — доступен извне; ОБЯЗАТЕЛЬНО ограничь фаерволом до доверенных IP
|
# Под istio ingressgateway занимает hostPort 80/443 ВНУТРИ этого
|
||||||
# - "80:80" # ingress http — задействуются после установки ingress-контроллера
|
# контейнера и через nodeAffinity живёт только на control-plane, то есть
|
||||||
# - "443:443" # ingress https
|
# именно здесь. Публикация этих портов наружу — единственное, что отделяет
|
||||||
|
# сервисы контура от внешнего мира; маршрутизация дальше по домену задаётся
|
||||||
|
# в infrastructure/istio-config/aero (Gateway + VirtualService).
|
||||||
|
- "80:80" # ingress http
|
||||||
|
- "443:443" # ingress https
|
||||||
|
# kube-apiserver наружу НЕ публикуем: доступ к нему — только изнутри
|
||||||
|
# compose-сети (engine ходит по KUBE_ADDR=https://k3s-server:6443).
|
||||||
|
# Если понадобится внешний kubectl — раскомментируй и ОБЯЗАТЕЛЬНО
|
||||||
|
# ограничь фаерволом до доверенных адресов.
|
||||||
|
# - "6443:6443"
|
||||||
volumes:
|
volumes:
|
||||||
- k3s-server-data:/var/lib/rancher/k3s # etcd/state, образы, манифесты — только named volume
|
- k3s-server-data:/var/lib/rancher/k3s # etcd/state, образы, манифесты — только named volume
|
||||||
- ./k3s:/output:z # отсюда забираем контекст k8s (:z — SELinux label для RedOS)
|
- ./k3s:/output:z # отсюда забираем контекст k8s (:z — SELinux label для RedOS)
|
||||||
@ -97,15 +112,6 @@ services:
|
|||||||
- ./k3s-storage/worker-2:/var/lib/rancher/k3s/storage:z
|
- ./k3s-storage/worker-2:/var/lib/rancher/k3s/storage:z
|
||||||
- ./k3s/registries.yaml:/etc/rancher/k3s/registries.yaml:ro,z
|
- ./k3s/registries.yaml:/etc/rancher/k3s/registries.yaml:ro,z
|
||||||
|
|
||||||
k3s-worker-3:
|
|
||||||
<<: *k3s-worker
|
|
||||||
container_name: k3s-worker-3
|
|
||||||
hostname: k3s-worker-3
|
|
||||||
volumes:
|
|
||||||
- k3s-worker-3-data:/var/lib/rancher/k3s
|
|
||||||
- ./k3s-storage/worker-3:/var/lib/rancher/k3s/storage:z
|
|
||||||
- ./k3s/registries.yaml:/etc/rancher/k3s/registries.yaml:ro,z
|
|
||||||
|
|
||||||
# Одноразовый провижининг k3s: namespace processing + DNS-мост postgres/minio.
|
# Одноразовый провижининг k3s: namespace processing + DNS-мост postgres/minio.
|
||||||
# Образ k3s содержит kubectl; server/tls перекрываем на k3s-server:6443
|
# Образ k3s содержит kubectl; server/tls перекрываем на k3s-server:6443
|
||||||
# (kubeconfig из /output даёт клиентские креды, адрес/TLS переопределяем флагами).
|
# (kubeconfig из /output даёт клиентские креды, адрес/TLS переопределяем флагами).
|
||||||
@ -242,6 +248,12 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 30
|
retries: 30
|
||||||
start_period: 10s
|
start_period: 10s
|
||||||
|
# Статический IP: на него ссылается k8s-Endpoints `vault` (DNS-мост в k3s,
|
||||||
|
# namespace vault). Через этот мост vault-agent-injector и внедрённые в поды
|
||||||
|
# агенты ходят к Vault, который живёт здесь, в compose, а не в кластере.
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
ipv4_address: 172.28.0.13
|
||||||
|
|
||||||
# Инициализация и авто-распечатывание Vault. Живёт постоянно: после рестарта
|
# Инициализация и авто-распечатывание Vault. Живёт постоянно: после рестарта
|
||||||
# хоста Vault поднимается запечатанным, и цикл распечатывает его сам.
|
# хоста Vault поднимается запечатанным, и цикл распечатывает его сам.
|
||||||
@ -868,7 +880,6 @@ volumes:
|
|||||||
k3s-server-data:
|
k3s-server-data:
|
||||||
k3s-worker-1-data:
|
k3s-worker-1-data:
|
||||||
k3s-worker-2-data:
|
k3s-worker-2-data:
|
||||||
k3s-worker-3-data:
|
|
||||||
gitea-data:
|
gitea-data:
|
||||||
sarex-vault-data: # file-хранилище Vault
|
sarex-vault-data: # file-хранилище Vault
|
||||||
sarex-vault-init: # unseal-ключ и root-токен (только для vault-init)
|
sarex-vault-init: # unseal-ключ и root-токен (только для vault-init)
|
||||||
|
|||||||
@ -0,0 +1,47 @@
|
|||||||
|
# Боевой издатель контура: Let's Encrypt через ACME.
|
||||||
|
#
|
||||||
|
# Раньше здесь был только внутренний CA (clusterissuer-ca.yaml) — исходили из
|
||||||
|
# того, что контур закрытый и до ACME-сервера не достучаться. Это оказалось не
|
||||||
|
# так: домены контура резолвятся в публичном DNS
|
||||||
|
# sarex.local.lonsdaleites.ru → 158.160.38.249
|
||||||
|
# *.sarex.local.lonsdaleites.ru → 158.160.38.249
|
||||||
|
# а порты 80/443 хоста опубликованы наружу (см. docker-compose.yaml, k3s-server).
|
||||||
|
# Значит http01-проверка проходит, и сертификаты можно брать настоящие.
|
||||||
|
#
|
||||||
|
# ВАЖНО ПРО WILDCARD: решатель http01 принципиально НЕ выдаёт wildcard-
|
||||||
|
# сертификаты — Let's Encrypt требует для них dns01. Поэтому сертификат контура
|
||||||
|
# перечисляет имена явно (см. infrastructure/istio-config/aero). Wildcard
|
||||||
|
# остался только в hosts самого Gateway, где сертификат не нужен.
|
||||||
|
#
|
||||||
|
# Внутренний CA (aero-ca-issuer) НЕ удалён: он остаётся запасным вариантом на
|
||||||
|
# случай, когда ACME недоступен, и для внутренних имён, которых нет в публичном
|
||||||
|
# DNS.
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt-issuer-istio
|
||||||
|
spec:
|
||||||
|
acme:
|
||||||
|
# На этот адрес Let's Encrypt шлёт предупреждения об истечении.
|
||||||
|
email: "emelinda1990@gmail.com"
|
||||||
|
# ОТДЕЛЬНОЕ имя секрета, НЕ общепринятое в репозитории letsencrypt-secret-key.
|
||||||
|
#
|
||||||
|
# Тот секрет создаёт сам чарт cert-manager-infra (метки managed-by=Helm,
|
||||||
|
# аннотация helm.sh/resource-policy: keep) и кладёт в поле tls.key
|
||||||
|
# ЗАГЛУШКУ — невалидный PEM. cert-manager читает её как ключ ACME-аккаунта
|
||||||
|
# и падает:
|
||||||
|
# Account private key is invalid: error decoding private key PEM block
|
||||||
|
# Reason: ErrVerifyACMEAccount
|
||||||
|
# Удалить заглушку нельзя: чарт пересоздаст её на следующем апгрейде и
|
||||||
|
# затрёт настоящий ключ аккаунта. Поэтому издателю выдан собственный
|
||||||
|
# секрет — его создаёт и владеет им cert-manager, Helm его не трогает.
|
||||||
|
privateKeySecretRef:
|
||||||
|
name: letsencrypt-issuer-istio-account-key
|
||||||
|
server: "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
|
solvers:
|
||||||
|
# class istio — challenge-Ingress обслуживает istio-ingressgateway,
|
||||||
|
# тот самый, что занимает hostPort 80/443 на k3s-server.
|
||||||
|
- http01:
|
||||||
|
ingress:
|
||||||
|
class: istio
|
||||||
@ -4,4 +4,7 @@
|
|||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
resources:
|
resources:
|
||||||
|
# Внутренний CA — запасной издатель для имён, которых нет в публичном DNS.
|
||||||
- clusterissuer-ca.yaml
|
- clusterissuer-ca.yaml
|
||||||
|
# Боевой издатель: Let's Encrypt, http01 через istio-ingressgateway.
|
||||||
|
- clusterissuer-letsencrypt.yaml
|
||||||
|
|||||||
38
infrastructure/dashboard/aero/dashboard.yaml
Normal file
38
infrastructure/dashboard/aero/dashboard.yaml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: dashboard
|
||||||
|
namespace: kubernetes-dashboard
|
||||||
|
spec:
|
||||||
|
interval: 5m
|
||||||
|
timeout: 10m
|
||||||
|
values:
|
||||||
|
# Kong слушает 8000 открытым HTTP и ничего не знает про istio. Без этого
|
||||||
|
# DestinationRule sidecar инициирует к нему mTLS, Kong видит сырое TLS-
|
||||||
|
# рукопожатие на плейнтекстовом порту, отвечает 400 — а наружу istio отдаёт
|
||||||
|
# 503. В логах Kong это выглядит как "\x16\x03\x01..." с SNI
|
||||||
|
# outbound_.80_._.dashboard-kong-proxy...
|
||||||
|
# tlsMode: DISABLE переводит обращение к нему в обычный HTTP.
|
||||||
|
destinationRule:
|
||||||
|
enabled: true
|
||||||
|
host: "dashboard-kong-proxy"
|
||||||
|
tlsMode: "DISABLE"
|
||||||
|
# Собственные VirtualService и Gateway чарта выключены: маршрутизация контура
|
||||||
|
# описана централизованно в infrastructure/istio-config/aero. Плюс родной VS
|
||||||
|
# чарта всё равно нерабочий — он ссылается на сервис
|
||||||
|
# kubernetes-dashboard-kong-proxy, которого не существует (реальное имя —
|
||||||
|
# dashboard-kong-proxy), и на домен dashboard.preprod.sarex.io, к контуру
|
||||||
|
# отношения не имеющий.
|
||||||
|
virtualService:
|
||||||
|
enabled: false
|
||||||
|
gateway:
|
||||||
|
enabled: false
|
||||||
|
# Образы лежат в приватном cr.yandex.
|
||||||
|
app:
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- regcred
|
||||||
|
kong:
|
||||||
|
image:
|
||||||
|
pullSecrets:
|
||||||
|
- regcred
|
||||||
@ -3,3 +3,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1
|
|||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
resources:
|
resources:
|
||||||
- ../base
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: dashboard.yaml
|
||||||
|
target:
|
||||||
|
kind: HelmRelease
|
||||||
|
name: dashboard
|
||||||
|
|||||||
@ -6,6 +6,25 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
interval: 5m
|
interval: 5m
|
||||||
timeout: 10m
|
timeout: 10m
|
||||||
|
# disableWait РАЗРЫВАЕТ ДЕДЛОК, не украшательство.
|
||||||
|
#
|
||||||
|
# Чарт создаёт Certificate, а Flux после установки релиза ждёт готовности его
|
||||||
|
# ресурсов — в том числе этого сертификата. Выпустить его нельзя, пока нет
|
||||||
|
# ClusterIssuer, а издатели живут в слое infra-configs, который по dependsOn
|
||||||
|
# ждёт готовности infra-controllers, где и находится этот самый релиз. Круг:
|
||||||
|
# istio-config → Certificate → ClusterIssuer → infra-configs
|
||||||
|
# → infra-controllers → istio-config
|
||||||
|
# На практике это выглядело как вечное "Running 'upgrade' action" при
|
||||||
|
# Released: True и Certificate в состоянии
|
||||||
|
# "Issuing certificate as Secret does not exist".
|
||||||
|
#
|
||||||
|
# Ждать здесь всё равно нечего: чарт разворачивает только конфигурационные
|
||||||
|
# объекты (Gateway, VirtualService, Certificate), а не рабочие нагрузки.
|
||||||
|
# Сертификат выпустится асинхронно, как только появится издатель.
|
||||||
|
install:
|
||||||
|
disableWait: true
|
||||||
|
upgrade:
|
||||||
|
disableWait: true
|
||||||
# Список продублирован из base целиком: для CRD kustomize заменяет списки,
|
# Список продублирован из base целиком: для CRD kustomize заменяет списки,
|
||||||
# а не сливает, поэтому частичный патч потерял бы зависимости от istio.
|
# а не сливает, поэтому частичный патч потерял бы зависимости от istio.
|
||||||
# cert-manager добавлен сверх base — чарт istio-config рендерит Certificate,
|
# cert-manager добавлен сверх base — чарт istio-config рендерит Certificate,
|
||||||
@ -28,10 +47,82 @@ spec:
|
|||||||
# Блок обязателен, даже пустой: шаблоны gateway.yaml и virtualservice.yaml
|
# Блок обязателен, даже пустой: шаблоны gateway.yaml и virtualservice.yaml
|
||||||
# разыменовывают $env.istio.gateways / .virtualServices без проверки на
|
# разыменовывают $env.istio.gateways / .virtualServices без проверки на
|
||||||
# nil и падают с "nil pointer evaluating interface {}".
|
# nil и падают с "nil pointer evaluating interface {}".
|
||||||
# Публикацию сервисов наружу добавим отдельным шагом.
|
|
||||||
istio:
|
istio:
|
||||||
gateways: {}
|
# Один общий Gateway на весь контур вместо отдельного на каждый сервис.
|
||||||
virtualServices: {}
|
# Так сделано потому, что у контура есть wildcard-домен: сертификат и
|
||||||
|
# Gateway покрывают *.sarex.local.lonsdaleites.ru целиком, а новый
|
||||||
|
# сервис публикуется добавлением ОДНОГО VirtualService — без правки
|
||||||
|
# сертификатов и Gateway. В прод-кластерах домены выписаны поштучно,
|
||||||
|
# там wildcard'а нет.
|
||||||
|
gateways:
|
||||||
|
contour:
|
||||||
|
name: contour-gateway
|
||||||
|
namespace: gateway
|
||||||
|
servers:
|
||||||
|
# ПОРЯДОК ЗНАЧИМ: первым обязан идти хост без звёздочки.
|
||||||
|
# Чарт собирает из hosts[0] имя порта
|
||||||
|
# name: {{ printf "%s-https-443" (index $server.hosts 0 ...) }}
|
||||||
|
# и НЕ заключает результат в кавычки. Хост "*.domain" дал бы
|
||||||
|
# токен "*-domain-https-443", который YAML читает как ссылку на
|
||||||
|
# якорь, и Helm падает с
|
||||||
|
# MalformedYAMLError: yaml: unknown anchor ... referenced
|
||||||
|
# Сами hosts чарт квотирует, поэтому wildcard ниже безопасен.
|
||||||
|
- hosts:
|
||||||
|
- sarex.local.lonsdaleites.ru
|
||||||
|
- "*.sarex.local.lonsdaleites.ru"
|
||||||
|
tls:
|
||||||
|
credentialName: contour-tls
|
||||||
|
# ВНИМАНИЕ: имя ресурса VirtualService чарт берёт из КЛЮЧА map
|
||||||
|
# ({{ $vsName }}), а поле name внутри — игнорирует, в отличие от
|
||||||
|
# gateways, где .name учитывается. Поэтому здесь его нет: ключ и есть
|
||||||
|
# имя. В остальных кластерах репозитория name у virtualServices
|
||||||
|
# присутствует, но ни на что не влияет.
|
||||||
|
virtualServices:
|
||||||
|
# Kubernetes Dashboard. Свой VirtualService чарт дашборда создаёт на
|
||||||
|
# домен dashboard.preprod.sarex.io — он к контуру aero отношения не
|
||||||
|
# имеет и никуда не резолвится. Этот выводит дашборд на домен
|
||||||
|
# контура; точка входа — kong-proxy, как во всех остальных кластерах.
|
||||||
|
dashboard:
|
||||||
|
namespace: gateway
|
||||||
|
hosts:
|
||||||
|
- dashboard.sarex.local.lonsdaleites.ru
|
||||||
|
gateways:
|
||||||
|
- gateway/contour-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: dashboard-kong-proxy.kubernetes-dashboard.svc.cluster.local
|
||||||
|
port: 80
|
||||||
|
# Management UI RabbitMQ.
|
||||||
|
rabbitmq:
|
||||||
|
namespace: gateway
|
||||||
|
hosts:
|
||||||
|
- rabbitmq.sarex.local.lonsdaleites.ru
|
||||||
|
gateways:
|
||||||
|
- gateway/contour-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: rabbitmq.rabbitmq.svc.cluster.local
|
||||||
|
port: 15672
|
||||||
|
# MinIO: консоль на /console/, S3 API в корне. Порядок маршрутов
|
||||||
|
# значим — более специфичный префикс должен идти первым, иначе его
|
||||||
|
# перехватит правило для /.
|
||||||
|
minio:
|
||||||
|
namespace: gateway
|
||||||
|
hosts:
|
||||||
|
- minio.sarex.local.lonsdaleites.ru
|
||||||
|
gateways:
|
||||||
|
- gateway/contour-gateway
|
||||||
|
routes:
|
||||||
|
- path:
|
||||||
|
prefix: /console/
|
||||||
|
service: minio-console.minio.svc.cluster.local
|
||||||
|
port: 9001
|
||||||
|
- path:
|
||||||
|
prefix: /
|
||||||
|
service: minio.minio.svc.cluster.local
|
||||||
|
port: 9000
|
||||||
requestAuthentications: {}
|
requestAuthentications: {}
|
||||||
authorizationPolicies: {}
|
authorizationPolicies: {}
|
||||||
certManager:
|
certManager:
|
||||||
@ -43,9 +134,23 @@ spec:
|
|||||||
# Домены продублированы из aero/roles/sarex_stack/defaults/main.yml
|
# Домены продублированы из aero/roles/sarex_stack/defaults/main.yml
|
||||||
# (platform_domain и производные). Единого источника нет — так же
|
# (platform_domain и производные). Единого источника нет — так же
|
||||||
# захардкожено во всех остальных кластерах репозитория.
|
# захардкожено во всех остальных кластерах репозитория.
|
||||||
dashboard-tls:
|
#
|
||||||
|
# Один сертификат на весь контур, общий для всех его сервисов.
|
||||||
|
contour-tls:
|
||||||
|
# Имена перечислены ЯВНО, а не wildcard'ом: решатель http01
|
||||||
|
# принципиально не выдаёт wildcard-сертификаты, для них Let's
|
||||||
|
# Encrypt требует dns01, а webhook DNS-провайдера в контуре не
|
||||||
|
# настроен. Wildcard остался в hosts Gateway — там он про
|
||||||
|
# маршрутизацию, а не про TLS.
|
||||||
|
#
|
||||||
|
# Публикуешь новый сервис — добавь его имя сюда, иначе браузер
|
||||||
|
# получит сертификат, в котором этого имени нет.
|
||||||
dnsNames:
|
dnsNames:
|
||||||
|
- sarex.local.lonsdaleites.ru
|
||||||
- dashboard.sarex.local.lonsdaleites.ru
|
- dashboard.sarex.local.lonsdaleites.ru
|
||||||
|
- rabbitmq.sarex.local.lonsdaleites.ru
|
||||||
|
- minio.sarex.local.lonsdaleites.ru
|
||||||
issuerRef:
|
issuerRef:
|
||||||
name: aero-ca-issuer
|
# Боевой издатель, см. infrastructure/cert-manager/aero/configs.
|
||||||
|
name: letsencrypt-issuer-istio
|
||||||
kind: ClusterIssuer
|
kind: ClusterIssuer
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: local-path-provisioner.yaml
|
||||||
|
target:
|
||||||
|
kind: HelmRelease
|
||||||
|
name: local-path-provisioner
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: local-path-provisioner
|
||||||
|
namespace: local-path-provisioner
|
||||||
|
spec:
|
||||||
|
interval: 5m
|
||||||
|
timeout: 10m
|
||||||
|
values:
|
||||||
|
replicaCount: 1
|
||||||
|
image:
|
||||||
|
repository: cr.yandex/crp3ccidau046kdj8g9q/contour/local-path-provisioner-nn/local-path-provisioner
|
||||||
|
tag: v0.0.24
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
helperImage:
|
||||||
|
repository: cr.yandex/crp3ccidau046kdj8g9q/contour/local-path-provisioner-nn/busybox
|
||||||
|
tag: latest
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
defaultSettings:
|
||||||
|
registrySecret: null
|
||||||
|
privateRegistry:
|
||||||
|
registryUrl: null
|
||||||
|
registryUser: null
|
||||||
|
registryPasswd: null
|
||||||
|
storageClass:
|
||||||
|
create: true
|
||||||
|
# Класс по умолчанию: встроенный в k3s провижинер отключён
|
||||||
|
# (--disable=local-storage в docker-compose.yaml), и default-класса в
|
||||||
|
# кластере иначе не остаётся — PVC без явного storageClassName висели бы
|
||||||
|
# в Pending.
|
||||||
|
defaultClass: true
|
||||||
|
name: local-path
|
||||||
|
reclaimPolicy: Delete
|
||||||
|
# Путь ВНУТРИ контейнера ноды. Взят тот же, что использовал встроенный
|
||||||
|
# провижинер, потому что он уже смонтирован с хоста:
|
||||||
|
# ./k3s-storage/<нода> -> /var/lib/rancher/k3s/storage
|
||||||
|
# Благодаря этому данные PVC переживают пересоздание контейнеров нод и
|
||||||
|
# доступны для бэкапа обычными средствами хоста. Дефолт чарта
|
||||||
|
# (/opt/local-path-provisioner) никуда не смонтирован — данные исчезли бы
|
||||||
|
# вместе с контейнером ноды.
|
||||||
|
nodePathMap:
|
||||||
|
- node: DEFAULT_PATH_FOR_NON_LISTED_NODES
|
||||||
|
paths:
|
||||||
|
- /var/lib/rancher/k3s/storage
|
||||||
|
rbac:
|
||||||
|
create: true
|
||||||
|
serviceAccount:
|
||||||
|
create: true
|
||||||
|
name: ""
|
||||||
|
resources: {}
|
||||||
|
nodeSelector: {}
|
||||||
|
tolerations: []
|
||||||
|
affinity: {}
|
||||||
|
configmap:
|
||||||
|
name: local-path-config
|
||||||
|
setup: |-
|
||||||
|
set -eu
|
||||||
|
mkdir -m 0777 -p "$VOL_DIR"
|
||||||
|
teardown: |-
|
||||||
|
set -eu
|
||||||
|
rm -rf "$VOL_DIR"
|
||||||
10
infrastructure/minio/aero/kustomization.yaml
Normal file
10
infrastructure/minio/aero/kustomization.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: minio.yaml
|
||||||
|
target:
|
||||||
|
kind: HelmRelease
|
||||||
|
name: minio
|
||||||
40
infrastructure/minio/aero/minio.yaml
Normal file
40
infrastructure/minio/aero/minio.yaml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: minio
|
||||||
|
namespace: minio
|
||||||
|
spec:
|
||||||
|
interval: 5m
|
||||||
|
timeout: 10m
|
||||||
|
values:
|
||||||
|
nameOverride: "minio"
|
||||||
|
mode: standalone
|
||||||
|
replicas: 1
|
||||||
|
drivesPerNode: 1
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
environment:
|
||||||
|
# Внешние адреса: консоль отдаётся с /console/ того же домена,
|
||||||
|
# маршрутизацию задаёт infrastructure/istio-config/aero.
|
||||||
|
MINIO_SERVER_URL: "https://minio.sarex.local.lonsdaleites.ru"
|
||||||
|
MINIO_BROWSER_REDIRECT_URL: "https://minio.sarex.local.lonsdaleites.ru/console/"
|
||||||
|
MINIO_API_CORS_ALLOW_ORIGIN: "https://minio.sarex.local.lonsdaleites.ru"
|
||||||
|
# Root-креды приезжают из Vault через vault-agent-injector: секрет кладёт
|
||||||
|
# ansible (роль sarex_stack, vault_app_secrets), роль minio в auth/kubernetes
|
||||||
|
# создаётся там же. В манифестах и в git паролей нет.
|
||||||
|
vaultRoot:
|
||||||
|
enabled: true
|
||||||
|
role: minio
|
||||||
|
authPath: auth/kubernetes
|
||||||
|
secretPath: secrets/data/minio/admin
|
||||||
|
rootUserKey: rootUser
|
||||||
|
rootPasswordKey: rootPassword
|
||||||
|
# В yc-k8s-test minio прибит к выделенным нодам (nodeSelector/tolerations
|
||||||
|
# dedicated=s3). В контуре aero таких нод нет — всего три, и планировщику
|
||||||
|
# ничего ограничивать не нужно.
|
||||||
|
persistence:
|
||||||
|
storageClass: local-path
|
||||||
|
size: 20Gi
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 512Mi
|
||||||
14
infrastructure/rabbitmq/aero/kustomization.yaml
Normal file
14
infrastructure/rabbitmq/aero/kustomization.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patches:
|
||||||
|
- path: rabbitmq.yaml
|
||||||
|
target:
|
||||||
|
kind: HelmRelease
|
||||||
|
name: rabbitmq
|
||||||
|
# JSON6902-патча с null здесь БОЛЬШЕ НЕТ — он не работал.
|
||||||
|
# kustomize нормализовал null обратно в {}, а пустую карту Helm сливает с
|
||||||
|
# дефолтами чарта, возвращая их целиком. Отключение перенесено в postRenderers
|
||||||
|
# внутри rabbitmq.yaml, где ресурсы вырезаются уже из готового манифеста.
|
||||||
105
infrastructure/rabbitmq/aero/rabbitmq.yaml
Normal file
105
infrastructure/rabbitmq/aero/rabbitmq.yaml
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: rabbitmq
|
||||||
|
namespace: rabbitmq
|
||||||
|
spec:
|
||||||
|
interval: 5m
|
||||||
|
timeout: 10m
|
||||||
|
# Вырезаем ресурсы ПОСЛЕ рендера чарта. Через values это недостижимо:
|
||||||
|
# * `null` до Helm не доезжает — kustomize нормализует его в {};
|
||||||
|
# * `{}` Helm сливает с дефолтом (coalesceMaps), и дефолт возвращается.
|
||||||
|
# postRenderers работает уже с готовым манифестом, где коалесинг позади.
|
||||||
|
#
|
||||||
|
# Что вырезаем: собственные Gateway/VirtualService/Certificate чарта на
|
||||||
|
# посторонний домен rabbitmq.infra.sarex.io. Маршрутизация контура описана
|
||||||
|
# централизованно в infrastructure/istio-config/aero.
|
||||||
|
#
|
||||||
|
# Особенно важен rmq-tls: домен не наш, ACME отдаёт 503 и проверка не пройдёт
|
||||||
|
# никогда. Пока он был в релизе, Helm ждал его выпуска, install падал по
|
||||||
|
# таймауту, срабатывал откат — и весь слой infra-controllers стоял.
|
||||||
|
postRenderers:
|
||||||
|
- kustomize:
|
||||||
|
patches:
|
||||||
|
- target:
|
||||||
|
kind: Gateway
|
||||||
|
name: rmq-gateway
|
||||||
|
patch: |
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: networking.istio.io/v1beta1
|
||||||
|
kind: Gateway
|
||||||
|
metadata:
|
||||||
|
name: rmq-gateway
|
||||||
|
namespace: gateway
|
||||||
|
- target:
|
||||||
|
kind: VirtualService
|
||||||
|
name: rmq-virt-service
|
||||||
|
patch: |
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: networking.istio.io/v1
|
||||||
|
kind: VirtualService
|
||||||
|
metadata:
|
||||||
|
name: rmq-virt-service
|
||||||
|
namespace: rabbitmq
|
||||||
|
- target:
|
||||||
|
kind: Certificate
|
||||||
|
name: rmq-tls
|
||||||
|
patch: |
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: rmq-tls
|
||||||
|
namespace: gateway
|
||||||
|
values:
|
||||||
|
global:
|
||||||
|
security:
|
||||||
|
allowInsecureImages: true
|
||||||
|
# Собственные VirtualService/Gateway/Certificate чарта отключаются НЕ здесь,
|
||||||
|
# а JSON6902-патчем в kustomization.yaml рядом. Причина — в противоположной
|
||||||
|
# семантике null у двух инструментов:
|
||||||
|
#
|
||||||
|
# Helm : удалить дефолт можно ТОЛЬКО значением null;
|
||||||
|
# {} он глубоко сливает с дефолтом, и дефолт остаётся;
|
||||||
|
# kustomize : в strategic-merge-патче null означает УДАЛИТЬ КЛЮЧ,
|
||||||
|
# то есть до Helm он просто не доедет.
|
||||||
|
#
|
||||||
|
# Значит ни `virtualService: null`, ни `virtualService: {}` здесь не
|
||||||
|
# работают: первое стирается kustomize, второе игнорируется Helm. Нужен
|
||||||
|
# JSON6902, где null — это именно значение, а не команда удаления.
|
||||||
|
#
|
||||||
|
# Цена ошибки не косметическая: чарт создавал rmq-gateway, rmq-virt-service
|
||||||
|
# и сертификат rmq-tls на посторонний домен rabbitmq.infra.sarex.io.
|
||||||
|
# Последний не проходит проверку ACME никогда, впустую жжёт лимиты
|
||||||
|
# Let's Encrypt, а релиз висит в install, ожидая его выпуска, и блокирует
|
||||||
|
# весь слой infra-controllers.
|
||||||
|
metrics:
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: false
|
||||||
|
default:
|
||||||
|
enabled: false
|
||||||
|
perObject:
|
||||||
|
enabled: false
|
||||||
|
detailed:
|
||||||
|
enabled: false
|
||||||
|
extraServiceMonitors: []
|
||||||
|
replicaCount: 1
|
||||||
|
persistence:
|
||||||
|
storageClass: local-path
|
||||||
|
size: 8Gi
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 512Mi
|
||||||
|
# Логин/пароль приезжают из Vault через vault-agent-injector: секрет кладёт
|
||||||
|
# ansible (роль sarex_stack, vault_app_secrets), роль rabbitmq в
|
||||||
|
# auth/kubernetes создаётся там же.
|
||||||
|
auth:
|
||||||
|
securePassword: true
|
||||||
|
existingPasswordSecret: ""
|
||||||
|
vault:
|
||||||
|
enabled: true
|
||||||
|
role: rabbitmq
|
||||||
|
authPath: auth/kubernetes
|
||||||
|
secretPath: secrets/data/rabbitmq/auth
|
||||||
|
usernameKey: username
|
||||||
|
passwordKey: password
|
||||||
5
infrastructure/vault-injector/aero/kustomization.yaml
Normal file
5
infrastructure/vault-injector/aero/kustomization.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
49
infrastructure/vault-injector/base/helmrelease.yaml
Normal file
49
infrastructure/vault-injector/base/helmrelease.yaml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# Vault Agent Injector контура aero.
|
||||||
|
#
|
||||||
|
# Тот же чарт vault-contour, что и в остальных кластерах (это официальный
|
||||||
|
# hashicorp/vault-helm, перепубликованный в cr.yandex), но в режиме «внешний
|
||||||
|
# Vault»: сервер Vault здесь живёт в docker-compose, а в кластер ставится
|
||||||
|
# ТОЛЬКО injector.
|
||||||
|
#
|
||||||
|
# global.externalVaultAddr сам по себе отключает деплой vault-сервера — это
|
||||||
|
# документированное поведение чарта, отдельный server.enabled=false не нужен.
|
||||||
|
# Адрес указывает на DNS-мост (Service vault в namespace vault), который
|
||||||
|
# заводит ansible: k3s/manifests/vault/bridge-vault.yaml.
|
||||||
|
#
|
||||||
|
# Имя релиза — vault-injector, а не vault (как в прод-кластерах): чарт
|
||||||
|
# составляет имена ресурсов из имени релиза, и релиз `vault` рисковал бы
|
||||||
|
# перекрыть Service `vault` моста, оставив Vault недоступным.
|
||||||
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||||
|
kind: HelmRelease
|
||||||
|
metadata:
|
||||||
|
name: vault-injector
|
||||||
|
namespace: vault
|
||||||
|
spec:
|
||||||
|
interval: 10m
|
||||||
|
chart:
|
||||||
|
spec:
|
||||||
|
chart: vault-contour
|
||||||
|
version: "0.2.3"
|
||||||
|
sourceRef:
|
||||||
|
kind: HelmRepository
|
||||||
|
name: yc-oci-charts
|
||||||
|
namespace: flux-system
|
||||||
|
interval: 10m
|
||||||
|
install:
|
||||||
|
createNamespace: true
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
upgrade:
|
||||||
|
remediation:
|
||||||
|
retries: 3
|
||||||
|
values:
|
||||||
|
global:
|
||||||
|
# Отключает vault-сервер и задаёт адрес внешнего Vault для агентов.
|
||||||
|
externalVaultAddr: http://vault.vault.svc.cluster.local:8200
|
||||||
|
# Образы чарта лежат в приватном cr.yandex; секрет создаёт роль
|
||||||
|
# sarex_stack (registry_secrets), см. registry-secret.yaml.j2.
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: regcred
|
||||||
|
tlsDisable: true
|
||||||
|
injector:
|
||||||
|
enabled: true
|
||||||
11
infrastructure/vault-injector/base/kustomization.yaml
Normal file
11
infrastructure/vault-injector/base/kustomization.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- helmrelease.yaml
|
||||||
|
|
||||||
|
# namespace vault здесь намеренно НЕ объявлен: его создаёт ansible вместе с
|
||||||
|
# DNS-мостом и token reviewer'ом (k3s/manifests/vault/), потому что Service
|
||||||
|
# моста нужно куда-то положить ещё до того, как Flux дойдёт до этого слоя.
|
||||||
|
# Дублировать объект в двух источниках правды — значит спорить за владение,
|
||||||
|
# поэтому чарту достаточно install.createNamespace на случай чистого кластера.
|
||||||
41
k3s/manifests/vault/bridge-vault.yaml
Normal file
41
k3s/manifests/vault/bridge-vault.yaml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# DNS-мост: имя `vault` в namespace vault → compose-контейнер vault.
|
||||||
|
# Service без селектора + ручной Endpoints, по образцу bridge-gitea/bridge-postgres.
|
||||||
|
#
|
||||||
|
# Зачем: Vault контура живёт в docker-compose, а не в кластере (см. README).
|
||||||
|
# vault-agent-injector и внедрённые им в поды агенты работают ВНУТРИ k3s, где
|
||||||
|
# резолвит CoreDNS, и имя compose-сети `vault` там не существует. Поды ходят до
|
||||||
|
# 172.28.0.0/16 через SNAT на ноде, поэтому мост указывает на статический IP.
|
||||||
|
#
|
||||||
|
# __VAULT_BRIDGE_IP__ подставляет ansible (роль sarex_stack, tasks/vault-k8s.yml)
|
||||||
|
# из переменной VAULT_BRIDGE_IP — она же задаёт ipv4_address сервиса vault
|
||||||
|
# в docker-compose.yaml.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: vault
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: vault
|
||||||
|
namespace: vault
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 8200
|
||||||
|
targetPort: 8200
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Endpoints
|
||||||
|
metadata:
|
||||||
|
name: vault
|
||||||
|
namespace: vault
|
||||||
|
subsets:
|
||||||
|
- addresses:
|
||||||
|
- ip: __VAULT_BRIDGE_IP__
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 8200
|
||||||
|
protocol: TCP
|
||||||
44
k3s/manifests/vault/vault-auth.yaml
Normal file
44
k3s/manifests/vault/vault-auth.yaml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Token reviewer для метода auth/kubernetes.
|
||||||
|
#
|
||||||
|
# Vault живёт ВНЕ кластера, поэтому не может воспользоваться ни своим
|
||||||
|
# ServiceAccount-токеном, ни CA из /var/run/secrets. Чтобы он мог проверять
|
||||||
|
# токены подов, ему выдаётся отдельный ServiceAccount с правом обращаться к
|
||||||
|
# TokenReview API — ClusterRole `system:auth-delegator` (встроенная).
|
||||||
|
#
|
||||||
|
# Схема целиком: под с аннотациями vault.hashicorp.com/* → injector добавляет
|
||||||
|
# init-контейнер vault-agent → агент логинится в Vault токеном ServiceAccount'а
|
||||||
|
# пода → Vault валидирует этот токен через TokenReview от имени vault-auth →
|
||||||
|
# при успехе отдаёт секреты, агент кладёт их файлами в /vault/secrets/.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: vault-auth
|
||||||
|
namespace: vault
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: vault-auth-delegator
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: system:auth-delegator
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: vault-auth
|
||||||
|
namespace: vault
|
||||||
|
---
|
||||||
|
# Долгоживущий токен ServiceAccount'а. С k8s 1.24 секрет-токен больше не
|
||||||
|
# создаётся автоматически при создании SA, а `kubectl create token` выдаёт
|
||||||
|
# токен с TTL — он протух бы, и Vault перестал бы валидировать логины.
|
||||||
|
# Секрет типа service-account-token с этой аннотацией заполняет
|
||||||
|
# token-controller: в него попадают и `token`, и `ca.crt` кластера.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: vault-auth-token
|
||||||
|
namespace: vault
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/service-account.name: vault-auth
|
||||||
|
type: kubernetes.io/service-account-token
|
||||||
Loading…
Reference in New Issue
Block a user