feat(ansible): add GitOps foundation (k3s, gitea, vault, and FluxCD) with multi-wave deployment support
This commit is contained in:
parent
e39404cb4c
commit
8a8cd66f59
18
.env.example
18
.env.example
@ -1,4 +1,4 @@
|
|||||||
K3S_TOKEN=gVadzB0OtWGDAM9rmlZvHiwfITsP8bxo
|
K3S_TOKEN=changeme
|
||||||
|
|
||||||
# Внешний адрес (IP или домен), по которому к kube-apiserver обращаются извне.
|
# Внешний адрес (IP или домен), по которому к kube-apiserver обращаются извне.
|
||||||
# Попадёт в TLS-сертификат (--tls-san). Без него внешний kubectl упадёт на x509.
|
# Попадёт в TLS-сертификат (--tls-san). Без него внешний kubectl упадёт на x509.
|
||||||
@ -48,9 +48,25 @@ PLATFORM_DOMAIN=sarex.local # основной домен платфо
|
|||||||
ADMIN_DOMAIN=admin.sarex.local # RabbitMQ management UI
|
ADMIN_DOMAIN=admin.sarex.local # RabbitMQ management UI
|
||||||
MINIO_DOMAIN=minio.sarex.local # MinIO: S3 API (/) + консоль (/console/)
|
MINIO_DOMAIN=minio.sarex.local # MinIO: S3 API (/) + консоль (/console/)
|
||||||
|
|
||||||
|
# --- GitOps-подложка: gitea (источник правды) + Vault + FluxCD в k3s ---
|
||||||
|
# Админ gitea заводится контейнером gitea-init; этими же кредами flux-bootstrap
|
||||||
|
# авторизуется при push'е манифестов. Пароль генерирует роль sarex_stack.
|
||||||
|
GITEA_ADMIN_USER=sarex
|
||||||
|
GITEA_ADMIN_PASSWORD=changeme
|
||||||
|
GITEA_ADMIN_EMAIL=sarex@sarex.local
|
||||||
|
# Организация и репозиторий, которые gitea-init создаёт, а Flux реконсилирует.
|
||||||
|
GITEA_ORG=infra
|
||||||
|
GITEA_REPO=iac
|
||||||
|
# Ветка и путь внутри репозитория, за которыми следит Flux.
|
||||||
|
AERO_GIT_BRANCH=master
|
||||||
|
AERO_FLUX_PATH=clusters/aero
|
||||||
|
|
||||||
# --- Docker-образы инфраструктуры (тег/реестр можно переопределить) ---
|
# --- Docker-образы инфраструктуры (тег/реестр можно переопределить) ---
|
||||||
K3S_IMAGE=rancher/k3s:v1.36.2-k3s1
|
K3S_IMAGE=rancher/k3s:v1.36.2-k3s1
|
||||||
GITEA_IMAGE=gitea/gitea:1.22
|
GITEA_IMAGE=gitea/gitea:1.22
|
||||||
|
VAULT_IMAGE=hashicorp/vault:1.18
|
||||||
|
# Версия CLI должна совпадать с версией контроллеров Flux в остальных кластерах.
|
||||||
|
FLUX_CLI_IMAGE=ghcr.io/fluxcd/flux-cli:v2.8.5
|
||||||
POSTGRES_IMAGE=postgres:12-alpine
|
POSTGRES_IMAGE=postgres:12-alpine
|
||||||
NGINX_IMAGE=nginx:1.27-alpine
|
NGINX_IMAGE=nginx:1.27-alpine
|
||||||
SAREX_REDIS_IMAGE=cr.yandex/crp3ccidau046kdj8g9q/redis:7.0.10
|
SAREX_REDIS_IMAGE=cr.yandex/crp3ccidau046kdj8g9q/redis:7.0.10
|
||||||
|
|||||||
15
.gitignore
vendored
15
.gitignore
vendored
@ -2,7 +2,22 @@
|
|||||||
.claude
|
.claude
|
||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
.env
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
tmp
|
tmp
|
||||||
.tmp/
|
.tmp/
|
||||||
snapshots/*
|
snapshots/*
|
||||||
|
|
||||||
|
# --- Рантайм-артефакты контура aero (создаются docker-compose на хосте) ---
|
||||||
|
gitea-data/ # данные gitea: репозитории, sqlite
|
||||||
|
k3s-storage/ # PVC-данные local-path-provisioner
|
||||||
|
k3s/kubeconfig.yaml # kubeconfig, который пишет k3s-server
|
||||||
|
nginx/certs/ # самоподписанные TLS-сертификаты
|
||||||
|
|
||||||
|
# --- Секреты и ключи: не коммитить никогда ---
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.p12
|
||||||
|
kubeconfig*
|
||||||
|
*service-account*.json
|
||||||
|
|||||||
@ -63,10 +63,13 @@ uv run poe install
|
|||||||
| `uv run poe ping` | проверка SSH + sudo |
|
| `uv run poe ping` | проверка SSH + sudo |
|
||||||
| `uv run poe provision` | базовые пакеты + docker/containerd + запуск службы |
|
| `uv run poe provision` | базовые пакеты + docker/containerd + запуск службы |
|
||||||
| `uv run poe deploy` | деплой конфигурации: файлы, `.env`, сертификаты, `docker login` |
|
| `uv run poe deploy` | деплой конфигурации: файлы, `.env`, сертификаты, `docker login` |
|
||||||
|
| `uv run poe platform` | деплой + поднять GitOps-подложку (k3s + gitea + vault + Flux) |
|
||||||
| `uv run poe stack` | деплой + поднять стек (`docker compose up -d`) |
|
| `uv run poe stack` | деплой + поднять стек (`docker compose up -d`) |
|
||||||
| `uv run poe gen-env` | только сгенерировать пароли и `.env` |
|
| `uv run poe gen-env` | только сгенерировать пароли и `.env` |
|
||||||
| `uv run poe login` | только `docker login` в реестр по ключу |
|
| `uv run poe login` | только `docker login` в реестр по ключу |
|
||||||
| `uv run poe superuser` | сгенерировать логин+пароль и создать Django-суперпользователя |
|
| `uv run poe superuser` | сгенерировать логин+пароль и создать Django-суперпользователя |
|
||||||
|
| `uv run poe flux-status` | состояние FluxCD в k3s |
|
||||||
|
| `uv run poe vault-status` | состояние Vault (инициализирован / распечатан) |
|
||||||
| `uv run poe check` / `deploy-check` | dry-run (`--check --diff`) |
|
| `uv run poe check` / `deploy-check` | dry-run (`--check --diff`) |
|
||||||
|
|
||||||
Поднимаемые сервисы задаются в `sarex_services` (роль `sarex_stack`) —
|
Поднимаемые сервисы задаются в `sarex_services` (роль `sarex_stack`) —
|
||||||
@ -77,6 +80,100 @@ uv run poe install
|
|||||||
> (через `/mnt/c`) права `0777`, и ansible игнорирует `ansible.cfg` в
|
> (через `/mnt/c`) права `0777`, и ansible игнорирует `ansible.cfg` в
|
||||||
> world-writable каталоге, поэтому путь к конфигу передаётся явно.
|
> world-writable каталоге, поэтому путь к конфигу передаётся явно.
|
||||||
|
|
||||||
|
### GitOps-подложка контура (k3s + gitea + vault + FluxCD)
|
||||||
|
|
||||||
|
Целевая схема контура — не «всё в compose», а **compose как подложка, k3s как
|
||||||
|
среда выполнения**. Источником правды становится gitea внутри контура, а
|
||||||
|
раскаткой занимается FluxCD.
|
||||||
|
|
||||||
|
```
|
||||||
|
docker compose k3s (4 ноды в контейнерах)
|
||||||
|
├── k3s-server ──► нода ┐
|
||||||
|
├── k3s-worker-1..3 ──► ноды ├─► flux-system (controllers)
|
||||||
|
├── gitea (172.28.0.12) ◄────── ┘ и всё, что опишем в clusters/aero
|
||||||
|
│ └── infra/iac.git
|
||||||
|
├── vault (+init)
|
||||||
|
├── flux-k8s-init ──► namespace flux-system + Service-мост до gitea
|
||||||
|
└── flux-bootstrap ──► ставит Flux в k3s и привязывает к gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
**Мост до gitea.** Контроллеры Flux работают внутри k3s, где резолвит CoreDNS,
|
||||||
|
а имён compose-сети там нет. Поэтому `flux-k8s-init` заводит в namespace
|
||||||
|
`flux-system` Service без селектора + Endpoints на статический IP gitea — тем же
|
||||||
|
приёмом, что `bridge-postgres`/`bridge-minio` для processing. Источник в
|
||||||
|
`GitRepository` указан именем `gitea.flux-system.svc.cluster.local`, а адрес
|
||||||
|
задан ровно в одном месте — переменной `GITEA_BRIDGE_IP` (она же `ipv4_address`
|
||||||
|
сервиса `gitea`). Контейнер `flux-bootstrap` резолвит то же имя через
|
||||||
|
`extra_hosts` — иначе flux CLI не смог бы запушить манифесты.
|
||||||
|
|
||||||
|
**Имена нод закреплены** через `hostname:` в compose. Это не косметика: k3s
|
||||||
|
берёт имя ноды из hostname, иначе им становится ID контейнера. Local-path
|
||||||
|
привязывает PV к ноде через `nodeAffinity` по имени, и после пересоздания
|
||||||
|
контейнера тома «повисли» бы.
|
||||||
|
|
||||||
|
### Хранилище PVC
|
||||||
|
|
||||||
|
`StorageClass local-path` (встроенный в k3s, он же default). Дефолтный путь
|
||||||
|
провижинера `/var/lib/rancher/k3s/storage` перекрыт bind-mount'ом на хост —
|
||||||
|
поэтому настраивать ConfigMap `local-path-config` не нужно:
|
||||||
|
|
||||||
|
```
|
||||||
|
{{ deploy_dir }}/k3s-storage/
|
||||||
|
├── server/ ← PVC, севшие на k3s-server
|
||||||
|
├── worker-1/
|
||||||
|
├── worker-2/
|
||||||
|
└── worker-3/
|
||||||
|
```
|
||||||
|
|
||||||
|
Файлы лежат на хосте обычными каталогами: переживают пересоздание контейнера
|
||||||
|
k3s и `docker compose down -v`, бэкапятся штатными средствами.
|
||||||
|
|
||||||
|
> **Следствие для stateful-сервисов.** Хранилище **node-local**: PVC привязан к
|
||||||
|
> той ноде, где под запустился первым. При переезде postgres/redis/rabbitmq/minio
|
||||||
|
> в k3s их нужно явно прибивать к конкретной ноде (`nodeSelector`), иначе данные
|
||||||
|
> размажутся по четырём каталогам, а потеря ноды сделает том недоступным.
|
||||||
|
> Бэкап должен покрывать все каталоги `k3s-storage/*`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run poe platform # поднять подложку
|
||||||
|
uv run poe flux-status # убедиться, что Flux реконсилирует
|
||||||
|
```
|
||||||
|
|
||||||
|
Что происходит по шагам:
|
||||||
|
|
||||||
|
1. **Волна 1** — стартуют `k3s-server`, три воркера и `gitea`. Роль ждёт, пока
|
||||||
|
k3s запишет `./k3s/kubeconfig.yaml` (и что файл непустой) — иначе
|
||||||
|
`flux-bootstrap` смонтировал бы каталог вместо файла.
|
||||||
|
2. **`gitea-init`** — одноразовый: заводит администратора (`gitea admin user
|
||||||
|
create`), организацию и пустой репозиторий через API. Идемпотентен.
|
||||||
|
3. **`flux-k8s-init`** — одноразовый: namespace `flux-system` и Service-мост до
|
||||||
|
gitea (см. выше). Обязан отработать до bootstrap.
|
||||||
|
4. **`vault` + `vault-init`** — Vault на file-хранилище. `vault-init` живёт
|
||||||
|
постоянно: инициализирует Vault одним ключом, распечатывает его и включает
|
||||||
|
`kv-v2` на пути `secrets/` (тот же путь, что в k8s-контурах). После ребута
|
||||||
|
хоста Vault поднимается запечатанным — цикл распечатывает его сам.
|
||||||
|
5. **`flux-bootstrap`** — одноразовый: `flux bootstrap git` ставит контроллеры в
|
||||||
|
k3s и пушит их манифесты в gitea в `clusters/aero/flux-system`.
|
||||||
|
|
||||||
|
> **FluxCD не работает в docker-compose** — это контроллеры Kubernetes. В compose
|
||||||
|
> живёт только установщик; после его успеха Flux работает внутри k3s.
|
||||||
|
|
||||||
|
Секреты подложки (`GITEA_ADMIN_PASSWORD`, `K3S_TOKEN`) генерируются так же, как
|
||||||
|
остальные — в `aero/.secrets/<host>/`. Unseal-ключ и root-токен Vault лежат на
|
||||||
|
отдельном docker-томе `sarex-vault-init` с правами `600` и **в git не попадают**.
|
||||||
|
|
||||||
|
#### Границы текущего этапа
|
||||||
|
|
||||||
|
Сделана только подложка. Осознанно **не** сделано:
|
||||||
|
|
||||||
|
- Репозиторий в gitea содержит лишь `clusters/aero/flux-system` — то, что запушил
|
||||||
|
bootstrap. Наполнение (`apps/`, `infrastructure/`) и перенос прикладных сервисов
|
||||||
|
из compose в k3s — следующий этап.
|
||||||
|
- Vault поднят и распечатан, но **kubernetes auth не настроен**: для него нужен
|
||||||
|
Vault Agent Injector внутри k3s, а он приедет уже через Flux. Пока в Vault
|
||||||
|
можно только класть секреты, поды их ещё не читают.
|
||||||
|
- Прикладной стек (`sarex_services`) не тронут и продолжает работать в compose.
|
||||||
|
|
||||||
### Развёртывание sarex-стека (роль `sarex_stack`)
|
### Развёртывание sarex-стека (роль `sarex_stack`)
|
||||||
|
|
||||||
Копирует `docker-compose.yaml`, `nginx/templates` и `backend/uwsgi.ini` в
|
Копирует `docker-compose.yaml`, `nginx/templates` и `backend/uwsgi.ini` в
|
||||||
|
|||||||
@ -36,6 +36,10 @@ deploy = "ansible-playbook deploy.yml"
|
|||||||
deploy-check = "ansible-playbook deploy.yml --check --diff"
|
deploy-check = "ansible-playbook deploy.yml --check --diff"
|
||||||
# Деплой + поднять стек приложения (docker compose up -d)
|
# Деплой + поднять стек приложения (docker compose up -d)
|
||||||
stack = "ansible-playbook deploy.yml -e sarex_compose_up=true"
|
stack = "ansible-playbook deploy.yml -e sarex_compose_up=true"
|
||||||
|
# Деплой + поднять GitOps-подложку: k3s + gitea + vault + bootstrap FluxCD.
|
||||||
|
# Без --tags намеренно: подложке нужны .env, docker-compose.yaml и vault/config,
|
||||||
|
# которые кладут нетегированные задачи роли (при --tags они были бы пропущены).
|
||||||
|
platform = "ansible-playbook deploy.yml -e aero_platform_up=true"
|
||||||
# Только генерация паролей и рендер .env на хосте (без копирования/сертификатов)
|
# Только генерация паролей и рендер .env на хосте (без копирования/сертификатов)
|
||||||
gen-env = "ansible-playbook deploy.yml --tags env"
|
gen-env = "ansible-playbook deploy.yml --tags env"
|
||||||
# docker login в приватный реестр по ключу json_key
|
# docker login в приватный реестр по ключу json_key
|
||||||
@ -53,6 +57,14 @@ up = "ansible-playbook deploy.yml -e sarex_compose_up=true --tags up"
|
|||||||
cmd = "ansible docker_hosts -m ansible.builtin.shell -a 'docker compose ps chdir=/root/sarex'"
|
cmd = "ansible docker_hosts -m ansible.builtin.shell -a 'docker compose ps chdir=/root/sarex'"
|
||||||
help = "Статус стека на хосте (docker compose ps)"
|
help = "Статус стека на хосте (docker compose ps)"
|
||||||
|
|
||||||
|
[tool.poe.tasks.flux-status]
|
||||||
|
cmd = "ansible docker_hosts -m ansible.builtin.shell -a 'docker compose exec -T k3s-server kubectl -n flux-system get kustomizations,gitrepositories,pods chdir=/root/sarex'"
|
||||||
|
help = "Состояние FluxCD в k3s (kustomizations, источники, поды)"
|
||||||
|
|
||||||
|
[tool.poe.tasks.vault-status]
|
||||||
|
cmd = "ansible docker_hosts -m ansible.builtin.shell -a 'docker compose exec -T vault-init vault status chdir=/root/sarex'"
|
||||||
|
help = "Состояние Vault (инициализирован / распечатан)"
|
||||||
|
|
||||||
[tool.poe.tasks.pull]
|
[tool.poe.tasks.pull]
|
||||||
cmd = "ansible docker_hosts -m ansible.builtin.shell -a 'docker compose pull ${service} chdir=/root/sarex'"
|
cmd = "ansible docker_hosts -m ansible.builtin.shell -a 'docker compose pull ${service} chdir=/root/sarex'"
|
||||||
help = "Стянуть образы: uv run poe pull [service]"
|
help = "Стянуть образы: uv run poe pull [service]"
|
||||||
|
|||||||
@ -17,6 +17,7 @@ cert_dir: "{{ deploy_dir }}/nginx/certs"
|
|||||||
# Переменные-пароли: им генерируются стойкие значения взамен слабых дефолтов.
|
# Переменные-пароли: им генерируются стойкие значения взамен слабых дефолтов.
|
||||||
sarex_secret_vars:
|
sarex_secret_vars:
|
||||||
- K3S_TOKEN
|
- K3S_TOKEN
|
||||||
|
- GITEA_ADMIN_PASSWORD
|
||||||
- SAREX_POSTGRES_PASSWORD
|
- SAREX_POSTGRES_PASSWORD
|
||||||
- SAREX_DJANGO_DB_PASSWORD
|
- SAREX_DJANGO_DB_PASSWORD
|
||||||
- SAREX_PROCESSING_DB_PASSWORD
|
- SAREX_PROCESSING_DB_PASSWORD
|
||||||
@ -46,6 +47,51 @@ registry_key_src: /mnt/c/Users/user/.ssh/local/authorized_key.json
|
|||||||
sarex_compose_up: false
|
sarex_compose_up: false
|
||||||
compose_cmd: "docker compose"
|
compose_cmd: "docker compose"
|
||||||
|
|
||||||
|
# --- GitOps-подложка контура (k3s + gitea + vault + flux) -------------------
|
||||||
|
# Параметры gitea и Flux. Попадают в .env (их читает docker-compose) — менять
|
||||||
|
# нужно здесь, а не в .env.example, иначе перезапишется при следующем деплое.
|
||||||
|
gitea_admin_user: sarex
|
||||||
|
gitea_admin_email: "{{ gitea_admin_user }}@{{ platform_domain }}"
|
||||||
|
gitea_org: infra
|
||||||
|
gitea_repo: iac
|
||||||
|
# Ветка и путь, за которыми следит Flux внутри репозитория в gitea.
|
||||||
|
aero_git_branch: master
|
||||||
|
aero_flux_path: clusters/aero
|
||||||
|
|
||||||
|
# Поднимается отдельно от прикладного стека: образы публичные, docker login в
|
||||||
|
# cr.yandex не нужен. По умолчанию выключено, включает `uv run poe platform`.
|
||||||
|
aero_platform_up: false
|
||||||
|
|
||||||
|
# Волна 1: долгоживущие сервисы, которым нужно успеть стать healthy.
|
||||||
|
# k3s-server при первом старте создаёт ./k3s/kubeconfig.yaml — его ждёт волна 2.
|
||||||
|
aero_platform_services_wave1:
|
||||||
|
- k3s-server
|
||||||
|
- k3s-worker-1
|
||||||
|
- k3s-worker-2
|
||||||
|
- k3s-worker-3
|
||||||
|
- gitea
|
||||||
|
|
||||||
|
# Волна 2: провижининг и bootstrap. gitea-init, flux-k8s-init и flux-bootstrap
|
||||||
|
# одноразовые (restart: "no"), vault-init живёт постоянно и распечатывает Vault
|
||||||
|
# после ребута хоста.
|
||||||
|
aero_platform_services_wave2:
|
||||||
|
- gitea-init
|
||||||
|
- flux-k8s-init
|
||||||
|
- vault
|
||||||
|
- vault-init
|
||||||
|
- flux-bootstrap
|
||||||
|
|
||||||
|
# Ноды k3s. Каждой нужен свой каталог PVC-хранилища на хосте: провижинер
|
||||||
|
# local-path node-local, и PV навсегда привязывается к ноде через nodeAffinity.
|
||||||
|
aero_k3s_nodes:
|
||||||
|
- server
|
||||||
|
- worker-1
|
||||||
|
- worker-2
|
||||||
|
- worker-3
|
||||||
|
|
||||||
|
# Сколько ждать появления kubeconfig от k3s-server, секунд.
|
||||||
|
aero_kubeconfig_timeout: 180
|
||||||
|
|
||||||
# Сервисы приложения, поднимаемые при sarex_compose_up (depends_on тянет
|
# Сервисы приложения, поднимаемые при sarex_compose_up (depends_on тянет
|
||||||
# зависимости и порядок). k3s/gitea намеренно не поднимаем.
|
# зависимости и порядок). k3s/gitea намеренно не поднимаем.
|
||||||
sarex_services:
|
sarex_services:
|
||||||
|
|||||||
@ -50,6 +50,22 @@
|
|||||||
dest: "{{ deploy_dir }}/engine/"
|
dest: "{{ deploy_dir }}/engine/"
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
|
|
||||||
|
# vault монтирует ./vault/config как :ro — без конфига сервер не стартует.
|
||||||
|
- name: Скопировать конфигурацию Vault
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ deploy_src_root }}/vault/config/"
|
||||||
|
dest: "{{ deploy_dir }}/vault/config/"
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
# Каталоги PVC-данных k3s — по одному на ноду. Создаём заранее: иначе docker
|
||||||
|
# создаст их от root с umask демона, а local-path пишет туда от имени подов.
|
||||||
|
- name: Создать каталоги PVC-хранилища k3s (по ноде)
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ deploy_dir }}/k3s-storage/{{ item }}"
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
loop: "{{ aero_k3s_nodes }}"
|
||||||
|
|
||||||
# --- Секреты: генерация с персистом на control-node (идемпотентно) --------
|
# --- Секреты: генерация с персистом на control-node (идемпотентно) --------
|
||||||
- name: Сгенерировать/загрузить пароли (persist в secrets_store, gitignored)
|
- name: Сгенерировать/загрузить пароли (persist в secrets_store, gitignored)
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
@ -88,6 +104,12 @@
|
|||||||
'ADMIN_DOMAIN': admin_domain,
|
'ADMIN_DOMAIN': admin_domain,
|
||||||
'MINIO_DOMAIN': minio_domain,
|
'MINIO_DOMAIN': minio_domain,
|
||||||
'SAREX_MINIO_CONSOLE_URL': 'https://' ~ minio_domain,
|
'SAREX_MINIO_CONSOLE_URL': 'https://' ~ minio_domain,
|
||||||
|
'GITEA_ADMIN_USER': gitea_admin_user,
|
||||||
|
'GITEA_ADMIN_EMAIL': gitea_admin_email,
|
||||||
|
'GITEA_ORG': gitea_org,
|
||||||
|
'GITEA_REPO': gitea_repo,
|
||||||
|
'AERO_GIT_BRANCH': aero_git_branch,
|
||||||
|
'AERO_FLUX_PATH': aero_flux_path,
|
||||||
'SAREX_JWT_PRIVATE_KEY': "'" ~ (lookup('file', jwt_private_key_path) | replace('\n', '\\n')) ~ "'",
|
'SAREX_JWT_PRIVATE_KEY': "'" ~ (lookup('file', jwt_private_key_path) | replace('\n', '\\n')) ~ "'",
|
||||||
'SAREX_JWT_PUBLIC_KEY': "'" ~ (lookup('file', jwt_public_key_path) | replace('\n', '\\n')) ~ "'",
|
'SAREX_JWT_PUBLIC_KEY': "'" ~ (lookup('file', jwt_public_key_path) | replace('\n', '\\n')) ~ "'",
|
||||||
}) }}
|
}) }}
|
||||||
@ -157,6 +179,14 @@
|
|||||||
state: absent
|
state: absent
|
||||||
tags: [login]
|
tags: [login]
|
||||||
|
|
||||||
|
# --- GitOps-подложка: k3s + gitea + vault + Flux --------------------------
|
||||||
|
# Отдельный тег и отдельный флаг: подложка поднимается публичными образами и
|
||||||
|
# не зависит от docker login, поэтому её можно накатывать независимо от стека.
|
||||||
|
- name: Поднять GitOps-подложку контура
|
||||||
|
ansible.builtin.include_tasks: platform.yml
|
||||||
|
when: aero_platform_up | bool
|
||||||
|
tags: [platform]
|
||||||
|
|
||||||
# --- Поднять сервисы приложения ------------------------------------------
|
# --- Поднять сервисы приложения ------------------------------------------
|
||||||
- name: Поднять сервисы приложения (docker compose up -d)
|
- name: Поднять сервисы приложения (docker compose up -d)
|
||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
|
|||||||
83
aero/roles/sarex_stack/tasks/platform.yml
Normal file
83
aero/roles/sarex_stack/tasks/platform.yml
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
# Подъём GitOps-подложки контура: k3s + gitea + vault + bootstrap FluxCD.
|
||||||
|
# Отделено от прикладного стека (sarex_services) намеренно: здесь только
|
||||||
|
# публичные образы, docker login в приватный реестр не требуется.
|
||||||
|
#
|
||||||
|
# Порядок важен и поэтому разбит на две волны: flux-bootstrap монтирует
|
||||||
|
# ./k3s/kubeconfig.yaml, который k3s-server создаёт только при первом старте.
|
||||||
|
# Если поднять всё одной командой, docker может создать на месте отсутствующего
|
||||||
|
# файла директорию, и bootstrap упадёт с невнятной ошибкой.
|
||||||
|
|
||||||
|
- name: Волна 1 — поднять k3s-server и gitea
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: "{{ compose_cmd }} up -d {{ aero_platform_services_wave1 | join(' ') }}"
|
||||||
|
chdir: "{{ deploy_dir }}"
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Дождаться kubeconfig от k3s-server
|
||||||
|
ansible.builtin.wait_for:
|
||||||
|
path: "{{ deploy_dir }}/k3s/kubeconfig.yaml"
|
||||||
|
state: present
|
||||||
|
timeout: "{{ aero_kubeconfig_timeout }}"
|
||||||
|
|
||||||
|
# Пустой файл означает, что k3s-server ещё дописывает kubeconfig. Без этой
|
||||||
|
# проверки flux-bootstrap может стартовать с нулевым конфигом.
|
||||||
|
- name: Убедиться, что kubeconfig не пустой
|
||||||
|
ansible.builtin.wait_for:
|
||||||
|
path: "{{ deploy_dir }}/k3s/kubeconfig.yaml"
|
||||||
|
search_regex: "clusters:"
|
||||||
|
timeout: 60
|
||||||
|
|
||||||
|
- name: Волна 2 — gitea-init, vault(+init) и bootstrap Flux
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: "{{ compose_cmd }} up -d {{ aero_platform_services_wave2 | join(' ') }}"
|
||||||
|
chdir: "{{ deploy_dir }}"
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
# flux-bootstrap одноразовый: успех = код 0. Забираем его явно, иначе ошибка
|
||||||
|
# bootstrap'а останется незамеченной — `compose up -d` о ней не сообщает.
|
||||||
|
#
|
||||||
|
# `docker compose wait` здесь не годится: для уже завершившегося контейнера он
|
||||||
|
# возвращает rc=1 и "no containers for project". Так как `up -d` не дожидается
|
||||||
|
# одноразового сервиса, состояние гоночное — поэтому опрашиваем `ps -a`,
|
||||||
|
# который корректно отвечает и для running, и для exited.
|
||||||
|
#
|
||||||
|
# Формат обёрнут в {% raw %}: это Go-шаблон docker'а, и без raw Jinja попыталась
|
||||||
|
# бы раскрыть {{.State.Status}} как свою переменную. Аргументы передаём списком —
|
||||||
|
# у `command` нет шелла, и строка со скобками разбилась бы по пробелу.
|
||||||
|
- name: Дождаться завершения flux-bootstrap
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- "{{ compose_cmd.split()[0] }}"
|
||||||
|
- inspect
|
||||||
|
- "-f"
|
||||||
|
- "{% raw %}{{.State.Status}} {{.State.ExitCode}}{% endraw %}"
|
||||||
|
- flux-bootstrap
|
||||||
|
register: aero_flux_bootstrap
|
||||||
|
changed_when: false
|
||||||
|
retries: 30
|
||||||
|
delay: 10
|
||||||
|
until: aero_flux_bootstrap.stdout | trim is not match('^running')
|
||||||
|
|
||||||
|
- name: Показать логи flux-bootstrap при ошибке
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: "{{ compose_cmd }} logs --no-color --tail 50 flux-bootstrap"
|
||||||
|
chdir: "{{ deploy_dir }}"
|
||||||
|
register: aero_flux_logs
|
||||||
|
changed_when: false
|
||||||
|
when: aero_flux_bootstrap.stdout.split() | last != "0"
|
||||||
|
|
||||||
|
- name: Прервать деплой, если bootstrap не удался
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: |
|
||||||
|
flux bootstrap завершился с кодом {{ aero_flux_bootstrap.stdout.split() | last }}.
|
||||||
|
Логи контейнера:
|
||||||
|
{{ aero_flux_logs.stdout | default('(недоступны)') }}
|
||||||
|
when: aero_flux_bootstrap.stdout.split() | last != "0"
|
||||||
|
|
||||||
|
- name: Итог
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg:
|
||||||
|
- "gitea: http://{{ ansible_host }}:3000 (пользователь {{ gitea_admin_user }}, пароль в aero/.secrets/{{ inventory_hostname }}/GITEA_ADMIN_PASSWORD)"
|
||||||
|
- "Flux реконсилирует {{ gitea_org }}/{{ gitea_repo }} → {{ aero_flux_path }}"
|
||||||
|
- "Проверка: docker compose exec k3s-server kubectl -n flux-system get kustomizations"
|
||||||
@ -1,7 +1,33 @@
|
|||||||
|
# Общая конфигурация воркер-нод k3s — см. сервисы k3s-worker-1..3.
|
||||||
|
# У каждой ноды СВОЙ том состояния и СВОЙ каталог PVC-хранилища: провижинер
|
||||||
|
# local-path кладёт данные на ту ноду, где под запустился первым, и через
|
||||||
|
# nodeAffinity навсегда привязывает к ней PV.
|
||||||
|
x-k3s-worker: &k3s-worker
|
||||||
|
image: ${K3S_IMAGE:-rancher/k3s:v1.36.2-k3s1}
|
||||||
|
command: ["agent"]
|
||||||
|
privileged: true
|
||||||
|
cgroup: host # kubelet в cgroup v2 (Ubuntu 22.04+/RedOS)
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
k3s-server:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
K3S_URL: https://k3s-server:6443
|
||||||
|
K3S_TOKEN: ${K3S_TOKEN:-supersecrettoken}
|
||||||
|
tmpfs:
|
||||||
|
- /run
|
||||||
|
- /var/run
|
||||||
|
ulimits:
|
||||||
|
nproc: 65535
|
||||||
|
nofile:
|
||||||
|
soft: 65535
|
||||||
|
hard: 65535
|
||||||
|
|
||||||
services:
|
services:
|
||||||
k3s-server:
|
k3s-server:
|
||||||
image: ${K3S_IMAGE:-rancher/k3s:v1.36.2-k3s1}
|
image: ${K3S_IMAGE:-rancher/k3s:v1.36.2-k3s1}
|
||||||
container_name: k3s-server
|
container_name: k3s-server
|
||||||
|
hostname: k3s-server # имя ноды в кластере — см. комментарий у воркеров
|
||||||
command:
|
command:
|
||||||
- server
|
- server
|
||||||
- --tls-san=127.0.0.1
|
- --tls-san=127.0.0.1
|
||||||
@ -22,6 +48,15 @@ services:
|
|||||||
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)
|
||||||
|
# PVC-данные local-path-provisioner (StorageClass local-path, он же default
|
||||||
|
# в k3s). Перекрываем ровно дефолтный путь провижинера, поэтому настраивать
|
||||||
|
# ConfigMap local-path-config не нужно. Bind-mount на хост означает, что
|
||||||
|
# файлы БД/очередей/объектов переживают пересоздание контейнера k3s и
|
||||||
|
# доступны для бэкапа обычными средствами хоста.
|
||||||
|
# Каталог намеренно не вложен в ./k3s: тот смонтирован ещё и как /output,
|
||||||
|
# и данные PVC засветились бы там же. У каждой ноды свой подкаталог, иначе
|
||||||
|
# сервер видел бы каталоги воркеров как посторонние PV в своём storage.
|
||||||
|
- ./k3s-storage/server:/var/lib/rancher/k3s/storage:z
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /run
|
- /run
|
||||||
- /var/run
|
- /var/run
|
||||||
@ -37,30 +72,32 @@ services:
|
|||||||
retries: 30
|
retries: 30
|
||||||
start_period: 30s
|
start_period: 30s
|
||||||
|
|
||||||
k3s-worker:
|
# hostname задаёт ИМЯ НОДЫ в кластере (k3s берёт его из hostname). Без него
|
||||||
image: ${K3S_IMAGE:-rancher/k3s:v1.36.2-k3s1}
|
# нодой становится ID контейнера, а он меняется при пересоздании — и тогда
|
||||||
container_name: k3s-worker
|
# local-path PV, привязанные к старому имени через nodeAffinity, «повисают».
|
||||||
command:
|
k3s-worker-1:
|
||||||
- agent
|
<<: *k3s-worker
|
||||||
privileged: true
|
container_name: k3s-worker-1
|
||||||
cgroup: host # kubelet в cgroup v2 (Ubuntu 22.04+/RedOS)
|
hostname: k3s-worker-1
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
|
||||||
k3s-server:
|
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
|
||||||
K3S_URL: https://k3s-server:6443
|
|
||||||
K3S_TOKEN: ${K3S_TOKEN:-supersecrettoken}
|
|
||||||
volumes:
|
volumes:
|
||||||
- k3s-worker-data:/var/lib/rancher/k3s
|
- k3s-worker-1-data:/var/lib/rancher/k3s
|
||||||
tmpfs:
|
- ./k3s-storage/worker-1:/var/lib/rancher/k3s/storage:z
|
||||||
- /run
|
|
||||||
- /var/run
|
k3s-worker-2:
|
||||||
ulimits:
|
<<: *k3s-worker
|
||||||
nproc: 65535
|
container_name: k3s-worker-2
|
||||||
nofile:
|
hostname: k3s-worker-2
|
||||||
soft: 65535
|
volumes:
|
||||||
hard: 65535
|
- k3s-worker-2-data:/var/lib/rancher/k3s
|
||||||
|
- ./k3s-storage/worker-2:/var/lib/rancher/k3s/storage: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: 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
|
||||||
@ -82,6 +119,11 @@ services:
|
|||||||
- ./k3s/kubeconfig.yaml:/kube/config:ro,z
|
- ./k3s/kubeconfig.yaml:/kube/config:ro,z
|
||||||
- ./k3s/manifests/processing:/manifests:ro,z
|
- ./k3s/manifests/processing:/manifests:ro,z
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# GitOps-подложка контура: gitea (источник) + vault (секреты) + flux (в k3s).
|
||||||
|
# Порядок: gitea → gitea-init (админ/орг/репо) → flux-bootstrap (ставит Flux
|
||||||
|
# в k3s и привязывает его к репозиторию в gitea).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
gitea:
|
gitea:
|
||||||
image: ${GITEA_IMAGE:-gitea/gitea:1.22}
|
image: ${GITEA_IMAGE:-gitea/gitea:1.22}
|
||||||
container_name: gitea
|
container_name: gitea
|
||||||
@ -91,6 +133,11 @@ services:
|
|||||||
USER_GID: 1000
|
USER_GID: 1000
|
||||||
GITEA__server__ROOT_URL: ${GITEA_ROOT_URL:-http://localhost:3000/}
|
GITEA__server__ROOT_URL: ${GITEA_ROOT_URL:-http://localhost:3000/}
|
||||||
GITEA__server__SSH_PORT: ${GITEA_SSH_PORT:-2222}
|
GITEA__server__SSH_PORT: ${GITEA_SSH_PORT:-2222}
|
||||||
|
# Пропускаем web-визард установки: конфигурация задаётся здесь, админа
|
||||||
|
# заводит gitea-init. Без INSTALL_LOCK первый запрос уводит на /install.
|
||||||
|
GITEA__security__INSTALL_LOCK: "true"
|
||||||
|
GITEA__database__DB_TYPE: sqlite3
|
||||||
|
GITEA__service__DISABLE_REGISTRATION: "true"
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000" # web UI (80/443 заняты k3s)
|
- "3000:3000" # web UI (80/443 заняты k3s)
|
||||||
- "${GITEA_SSH_PORT:-2222}:22" # git over ssh
|
- "${GITEA_SSH_PORT:-2222}:22" # git over ssh
|
||||||
@ -102,6 +149,225 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 12
|
retries: 12
|
||||||
start_period: 20s
|
start_period: 20s
|
||||||
|
# Статический IP по образцу postgres/minio. Он обязателен: source-controller
|
||||||
|
# Flux работает ВНУТРИ k3s, где своя DNS (CoreDNS), и имя compose-сети
|
||||||
|
# `gitea` там не резолвится. Поды k3s дотягиваются до 172.28.0.0/16 через
|
||||||
|
# SNAT на ноде, поэтому в URL источника используется именно этот адрес.
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
ipv4_address: 172.28.0.12
|
||||||
|
|
||||||
|
# Одноразовый провижининг gitea: администратор + организация + пустой репозиторий.
|
||||||
|
# Делит /data с gitea (CLI пишет в ту же sqlite), поэтому запускается тем же uid.
|
||||||
|
# Все шаги идемпотентны: повторный прогон на готовой инсталляции — no-op.
|
||||||
|
gitea-init:
|
||||||
|
image: ${GITEA_IMAGE:-gitea/gitea:1.22}
|
||||||
|
container_name: gitea-init
|
||||||
|
restart: "no"
|
||||||
|
user: "1000:1000"
|
||||||
|
depends_on:
|
||||||
|
gitea:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
GITEA_ADMIN_USER: ${GITEA_ADMIN_USER:-sarex}
|
||||||
|
GITEA_ADMIN_PASSWORD: ${GITEA_ADMIN_PASSWORD:?GITEA_ADMIN_PASSWORD is required}
|
||||||
|
GITEA_ADMIN_EMAIL: ${GITEA_ADMIN_EMAIL:-sarex@sarex.local}
|
||||||
|
GITEA_ORG: ${GITEA_ORG:-infra}
|
||||||
|
GITEA_REPO: ${GITEA_REPO:-iac}
|
||||||
|
volumes:
|
||||||
|
- ./gitea-data:/data:z
|
||||||
|
entrypoint: ["/bin/sh", "-ec"]
|
||||||
|
command:
|
||||||
|
- |
|
||||||
|
export GITEA_WORK_DIR=/data/gitea
|
||||||
|
CONF=/data/gitea/conf/app.ini
|
||||||
|
|
||||||
|
# 1. Администратор. Существующий пользователь → ненулевой код, это норма.
|
||||||
|
gitea --config "$$CONF" admin user create \
|
||||||
|
--admin --username "$$GITEA_ADMIN_USER" --password "$$GITEA_ADMIN_PASSWORD" \
|
||||||
|
--email "$$GITEA_ADMIN_EMAIL" --must-change-password=false 2>&1 \
|
||||||
|
| grep -v 'already exists' || true
|
||||||
|
|
||||||
|
API=http://gitea:3000/api/v1
|
||||||
|
AUTH="$$GITEA_ADMIN_USER:$$GITEA_ADMIN_PASSWORD"
|
||||||
|
|
||||||
|
# 2. Организация. 422 = уже существует.
|
||||||
|
curl -fsS -u "$$AUTH" -X POST "$$API/orgs" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d "{\"username\":\"$$GITEA_ORG\"}" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# 3. Репозиторий. auto_init=false — первый коммит сделает flux bootstrap.
|
||||||
|
curl -fsS -u "$$AUTH" -X POST "$$API/orgs/$$GITEA_ORG/repos" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d "{\"name\":\"$$GITEA_REPO\",\"private\":true,\"auto_init\":false}" \
|
||||||
|
>/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# Проверяем, что репозиторий действительно доступен — иначе flux bootstrap
|
||||||
|
# упадёт позже и менее внятно.
|
||||||
|
curl -fsS -u "$$AUTH" "$$API/repos/$$GITEA_ORG/$$GITEA_REPO" >/dev/null
|
||||||
|
echo "gitea-init: $$GITEA_ORG/$$GITEA_REPO готов"
|
||||||
|
|
||||||
|
vault:
|
||||||
|
image: ${VAULT_IMAGE:-hashicorp/vault:1.18}
|
||||||
|
container_name: vault
|
||||||
|
restart: unless-stopped
|
||||||
|
# Только "server", без -config: entrypoint образа сам добавляет
|
||||||
|
# -config=/vault/config. Явный путь к файлу внутри этого же каталога
|
||||||
|
# приводит к двойной загрузке конфига и падению на
|
||||||
|
# "listen tcp4 0.0.0.0:8200: bind: address already in use".
|
||||||
|
command: ["server"]
|
||||||
|
cap_add:
|
||||||
|
- IPC_LOCK
|
||||||
|
environment:
|
||||||
|
VAULT_ADDR: http://127.0.0.1:8200
|
||||||
|
volumes:
|
||||||
|
# :ro осознанно — entrypoint пишет в лог безвредные "chown: Read-only
|
||||||
|
# file system", но конфиг остаётся неизменяемым изнутри контейнера.
|
||||||
|
- ./vault/config:/vault/config:ro,z
|
||||||
|
- sarex-vault-data:/vault/file
|
||||||
|
- sarex-vault-init:/vault/init
|
||||||
|
healthcheck:
|
||||||
|
# /v1/sys/health отдаёт 200 только когда Vault инициализирован и распечатан
|
||||||
|
# (501 — не инициализирован, 503 — запечатан). Значит healthy здесь
|
||||||
|
# означает «готов принимать секреты», и на это можно вешать depends_on.
|
||||||
|
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8200/v1/sys/health"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 30
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
# Инициализация и авто-распечатывание Vault. Живёт постоянно: после рестарта
|
||||||
|
# хоста Vault поднимается запечатанным, и цикл распечатывает его сам.
|
||||||
|
# Ключ и root-токен лежат на отдельном volume (sarex-vault-init), не в git.
|
||||||
|
vault-init:
|
||||||
|
image: ${VAULT_IMAGE:-hashicorp/vault:1.18}
|
||||||
|
container_name: vault-init
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
vault:
|
||||||
|
condition: service_started
|
||||||
|
environment:
|
||||||
|
VAULT_ADDR: http://vault:8200
|
||||||
|
volumes:
|
||||||
|
- sarex-vault-init:/vault/init
|
||||||
|
entrypoint: ["/bin/sh", "-ec"]
|
||||||
|
command:
|
||||||
|
- |
|
||||||
|
UNSEAL=/vault/init/unseal.key
|
||||||
|
ROOT=/vault/init/root.token
|
||||||
|
|
||||||
|
while :; do
|
||||||
|
# `vault operator init -status`: 0 — инициализирован, 2 — нет,
|
||||||
|
# прочее — Vault ещё не отвечает. Под `set -e` ненулевой код нужно
|
||||||
|
# перехватить явно, иначе скрипт завершится.
|
||||||
|
rc=0
|
||||||
|
vault operator init -status >/dev/null 2>&1 || rc=$$?
|
||||||
|
|
||||||
|
if [ "$$rc" -eq 0 ]; then
|
||||||
|
# Инициализирован. `vault status`: 0 — распечатан, 2 — запечатан.
|
||||||
|
if ! vault status >/dev/null 2>&1; then
|
||||||
|
if [ -s "$$UNSEAL" ]; then
|
||||||
|
vault operator unseal "$$(cat $$UNSEAL)" >/dev/null \
|
||||||
|
&& echo "vault-init: распечатан"
|
||||||
|
else
|
||||||
|
echo "vault-init: Vault запечатан, но $$UNSEAL отсутствует —" \
|
||||||
|
"распечатать нечем (том sarex-vault-init потерян?)" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif [ "$$rc" -eq 2 ]; then
|
||||||
|
# Отвечает, но не инициализирован — инициализируем одним ключом.
|
||||||
|
# Текстовый вывод парсим awk'ом: jq в образе нет.
|
||||||
|
if vault operator init -key-shares=1 -key-threshold=1 > /vault/init/init.txt 2>/dev/null; then
|
||||||
|
awk '/^Unseal Key 1:/{print $$4}' /vault/init/init.txt > "$$UNSEAL"
|
||||||
|
awk '/^Initial Root Token:/{print $$4}' /vault/init/init.txt > "$$ROOT"
|
||||||
|
chmod 600 "$$UNSEAL" "$$ROOT" /vault/init/init.txt
|
||||||
|
vault operator unseal "$$(cat $$UNSEAL)" >/dev/null
|
||||||
|
echo "vault-init: инициализирован и распечатан"
|
||||||
|
|
||||||
|
# kv-v2 на пути secrets/ — так же, как в k8s-контурах
|
||||||
|
# (манифесты ссылаются на secrets/data/...).
|
||||||
|
VAULT_TOKEN="$$(cat $$ROOT)" vault secrets enable -path=secrets kv-v2 \
|
||||||
|
>/dev/null 2>&1 && echo "vault-init: включён kv-v2 на secrets/"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
|
||||||
|
# Одноразовый провижининг namespace flux-system и DNS-моста до gitea.
|
||||||
|
# Должен отработать ДО flux-bootstrap: тот дожидается готовности GitRepository,
|
||||||
|
# а она невозможна, пока source-controller не может отрезолвить источник.
|
||||||
|
flux-k8s-init:
|
||||||
|
image: ${K3S_IMAGE:-rancher/k3s:v1.36.2-k3s1}
|
||||||
|
container_name: flux-k8s-init
|
||||||
|
restart: "no"
|
||||||
|
depends_on:
|
||||||
|
k3s-server:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
GITEA_BRIDGE_IP: ${GITEA_BRIDGE_IP:-172.28.0.12}
|
||||||
|
volumes:
|
||||||
|
- ./k3s/kubeconfig.yaml:/kube/config:ro,z
|
||||||
|
- ./k3s/manifests/flux:/manifests:ro,z
|
||||||
|
entrypoint: ["/bin/sh", "-ec"]
|
||||||
|
command:
|
||||||
|
- |
|
||||||
|
sed "s|__GITEA_BRIDGE_IP__|$$GITEA_BRIDGE_IP|g" /manifests/bridge-gitea.yaml \
|
||||||
|
| kubectl --kubeconfig /kube/config \
|
||||||
|
--server https://k3s-server:6443 --insecure-skip-tls-verify \
|
||||||
|
apply -f -
|
||||||
|
|
||||||
|
# Одноразовый bootstrap FluxCD внутрь k3s с привязкой к репозиторию в gitea.
|
||||||
|
# Flux — это контроллеры Kubernetes, в compose живёт только этот установщик.
|
||||||
|
# После успешного прогона источником правды становится gitea, а не compose.
|
||||||
|
flux-bootstrap:
|
||||||
|
image: ${FLUX_CLI_IMAGE:-ghcr.io/fluxcd/flux-cli:v2.8.5}
|
||||||
|
container_name: flux-bootstrap
|
||||||
|
restart: "no"
|
||||||
|
depends_on:
|
||||||
|
k3s-server:
|
||||||
|
condition: service_healthy
|
||||||
|
gitea-init:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
flux-k8s-init:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
# Один и тот же URL резолвится в двух разных контекстах: внутри k3s — через
|
||||||
|
# Service флакс-моста, здесь, в compose-сети, — через эту запись в /etc/hosts.
|
||||||
|
# Без неё flux CLI не смог бы запушить манифесты в репозиторий.
|
||||||
|
extra_hosts:
|
||||||
|
- "gitea.flux-system.svc.cluster.local:${GITEA_BRIDGE_IP:-172.28.0.12}"
|
||||||
|
environment:
|
||||||
|
GITEA_ADMIN_USER: ${GITEA_ADMIN_USER:-sarex}
|
||||||
|
GITEA_ADMIN_PASSWORD: ${GITEA_ADMIN_PASSWORD:?GITEA_ADMIN_PASSWORD is required}
|
||||||
|
GITEA_ORG: ${GITEA_ORG:-infra}
|
||||||
|
GITEA_REPO: ${GITEA_REPO:-iac}
|
||||||
|
AERO_GIT_BRANCH: ${AERO_GIT_BRANCH:-master}
|
||||||
|
AERO_FLUX_PATH: ${AERO_FLUX_PATH:-clusters/aero}
|
||||||
|
# Должен совпадать с ipv4_address сервиса gitea.
|
||||||
|
GITEA_BRIDGE_IP: ${GITEA_BRIDGE_IP:-172.28.0.12}
|
||||||
|
volumes:
|
||||||
|
- ./k3s/kubeconfig.yaml:/kube/config:ro,z
|
||||||
|
entrypoint: ["/bin/sh", "-ec"]
|
||||||
|
command:
|
||||||
|
- |
|
||||||
|
# k3s пишет kubeconfig с server: https://127.0.0.1:6443 — изнутри другого
|
||||||
|
# контейнера это неверный адрес. Подменяем на имя сервиса; сертификат
|
||||||
|
# apiserver уже содержит --tls-san=k3s-server, поэтому TLS проверяется
|
||||||
|
# штатно, без --insecure.
|
||||||
|
sed 's#https://127.0.0.1:6443#https://k3s-server:6443#' /kube/config > /tmp/kubeconfig
|
||||||
|
export KUBECONFIG=/tmp/kubeconfig
|
||||||
|
|
||||||
|
# Адрес источника — имя Service'а моста (flux-k8s-init), а не IP: так
|
||||||
|
# адрес gitea задан ровно в одном месте — переменной GITEA_BRIDGE_IP.
|
||||||
|
flux bootstrap git \
|
||||||
|
--url="http://gitea.flux-system.svc.cluster.local:3000/$$GITEA_ORG/$$GITEA_REPO.git" \
|
||||||
|
--branch="$$AERO_GIT_BRANCH" \
|
||||||
|
--path="$$AERO_FLUX_PATH" \
|
||||||
|
--username="$$GITEA_ADMIN_USER" \
|
||||||
|
--password="$$GITEA_ADMIN_PASSWORD" \
|
||||||
|
--token-auth \
|
||||||
|
--allow-insecure-http
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Инфраструктурные зависимости apps/django (sarex-backend).
|
# Инфраструктурные зависимости apps/django (sarex-backend).
|
||||||
@ -593,8 +859,12 @@ services:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
k3s-server-data:
|
k3s-server-data:
|
||||||
k3s-worker-data:
|
k3s-worker-1-data:
|
||||||
|
k3s-worker-2-data:
|
||||||
|
k3s-worker-3-data:
|
||||||
gitea-data:
|
gitea-data:
|
||||||
|
sarex-vault-data: # file-хранилище Vault
|
||||||
|
sarex-vault-init: # unseal-ключ и root-токен (только для vault-init)
|
||||||
sarex-postgres-data:
|
sarex-postgres-data:
|
||||||
sarex-redis-data:
|
sarex-redis-data:
|
||||||
sarex-rabbitmq-data:
|
sarex-rabbitmq-data:
|
||||||
|
|||||||
39
k3s/manifests/flux/bridge-gitea.yaml
Normal file
39
k3s/manifests/flux/bridge-gitea.yaml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# DNS-мост: имя `gitea` в namespace flux-system → compose-контейнер gitea.
|
||||||
|
# Service без селектора + ручной Endpoints, по образцу bridge-postgres/bridge-minio.
|
||||||
|
#
|
||||||
|
# Зачем: source-controller Flux работает ВНУТРИ k3s, где резолвит CoreDNS, а имя
|
||||||
|
# сервиса compose-сети там не существует. Без моста GitRepository падает с
|
||||||
|
# "lookup gitea on 10.43.0.10:53: no such host".
|
||||||
|
#
|
||||||
|
# __GITEA_BRIDGE_IP__ подставляет контейнер flux-k8s-init из переменной
|
||||||
|
# GITEA_BRIDGE_IP — она же задаёт ipv4_address сервиса gitea в docker-compose.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: flux-system
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: gitea
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 3000
|
||||||
|
targetPort: 3000
|
||||||
|
protocol: TCP
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Endpoints
|
||||||
|
metadata:
|
||||||
|
name: gitea
|
||||||
|
namespace: flux-system
|
||||||
|
subsets:
|
||||||
|
- addresses:
|
||||||
|
- ip: __GITEA_BRIDGE_IP__
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 3000
|
||||||
|
protocol: TCP
|
||||||
22
vault/config/vault.hcl
Normal file
22
vault/config/vault.hcl
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Vault контура aero: file-хранилище на named volume, TLS терминируется выше
|
||||||
|
// (верхний nginx). Внутри compose-сети слушаем открытым HTTP.
|
||||||
|
// Инициализацию/распечатывание делает сервис vault-init (см. docker-compose.yaml).
|
||||||
|
|
||||||
|
ui = true
|
||||||
|
|
||||||
|
storage "file" {
|
||||||
|
path = "/vault/file"
|
||||||
|
}
|
||||||
|
|
||||||
|
listener "tcp" {
|
||||||
|
address = "0.0.0.0:8200"
|
||||||
|
tls_disable = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Адрес, который Vault сообщает клиентам (и себе при редиректах внутри сети).
|
||||||
|
api_addr = "http://vault:8200"
|
||||||
|
|
||||||
|
// mlock требует IPC_LOCK; в compose capability выдана, но в некоторых окружениях
|
||||||
|
// (RedOS в контейнере без CAP_IPC_LOCK) Vault упадёт на старте. Отключаем —
|
||||||
|
// swap на хосте контура и так выключен.
|
||||||
|
disable_mlock = true
|
||||||
Loading…
Reference in New Issue
Block a user