iac/docs/closed-contour-deployment.md
2026-08-11 12:23:55 +03:00

59 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Развёртывание платформы SAREX в закрытом контуре
Документ описывает полный цикл разворачивания платформы в изолированном контуре: от поднятия кластера kubespray до раскатки сущностей terraform. Порядок шагов фиксированный — каждый следующий этап зависит от предыдущего, менять местами нельзя.
## Кластер: kubespray
Кластер поднимается через kubespray (`/Users/eh/SAREX/SAREX-NEW-GITLAB/kubespray`, ветка `v2.29`). Запуск идёт не с голого хоста, а из docker-образа, который собирается по `Dockerfile` в корне репозитория — образ на базе Ubuntu 22.04 с ansible, kubectl и зависимостями из `requirements.txt`. Собранный образ монтирует директорию инвентаря и гоняет `ansible-playbook` внутри контейнера, поэтому на машине оператора не нужно ничего кроме docker.
Инвентарь описывается в `inventory/<имя-контура>/inventory.ini` по образцу `inventory/sample` или уже существующих `inventory/nn`, `inventory/docker-test`. В секции `[all:vars]` задаётся `ansible_user`, а способ входа на хосты — либо `ansible_ssh_private_key_file` (путь к приватному ключу), либо `ansible_ssh_pass` вместе с `ansible_become_pass`, если на хостах пароль, а не ключ. Control-plane и worker-узлы перечисляются в секциях `[kube_control_plane]` и `[kube_node]` с `ansible_host` и `ip`.
Отдельно нужно прописать SAN будущего API-сервера — параметр `supplementary_addresses_in_ssl_keys` в `inventory/<контур>/group_vars/k8s_cluster/k8s-cluster.yml` (по умолчанию закомментирован). Туда добавляется тот IP мастера, по которому вы потом будете подключаться kubeconfig'ом — обычно это внешний/публичный адрес, а не внутренний `ip` из инвентаря. Если это не сделать, TLS-сертификат апи-сервера будет выписан только на внутренний адрес, и коннект по внешнему IP будет падать на верификации сертификата — придётся пересобирать сертификаты вручную задним числом.
После того как инвентарь готов, играется `cluster.yml`:
```
docker run --rm -it --mount type=bind,source="$(pwd)"/inventory/<контур>,dst=/inventory \
--mount type=bind,source=${HOME}/.ssh/id_rsa,dst=/root/.ssh/id_rsa \
<образ kubespray> \
ansible-playbook -i /inventory/inventory.ini --private-key /root/.ssh/id_rsa cluster.yml
```
Если вход по паролю, ключ не монтируется, а `ansible-playbook` дополнительно вызывается с `--ask-pass --ask-become-pass` либо соответствующие переменные уже прописаны в инвентаре.
Играется он долго, минут 30-60 в зависимости от числа узлов. По завершении с control-plane забирается `/etc/kubernetes/admin.conf` — это и есть kubeconfig с полным доступом. Кладём его локально, проверяем коннект (`kubectl --kubeconfig admin.conf get nodes`) — на этом задача kubespray закрыта, дальше кластер существует сам по себе, kubespray больше не трогаем, пока не понадобится добавить узел или обновить версию.
## Gitea
Дальше в кластер ставится gitea — она нужна как локальная точка, через которую flux и terraform будут читать репозитории, не имея прямого выхода в интернет к `gitlab.sarex.io`. Исходник — `/Users/eh/SAREX/SAREX-NEW-GITLAB/gitea`, ветка `master`. Чарт лежит в `.helm`, состоит из `universal-chart` (сама gitea + act_runner) и `postgresql-preprod` как зависимость с `condition: postgresql.enabled` — если под БД есть managed postgres, поднимать embedded не нужно.
Внутри `universal-chart.services` описан `gitea-ci-worker` — это и есть act_runner, тот самый "воркер". Ему нужно прописать `GITEA_INSTANCE_URL` (внутренний адрес gitea), `GITEA_RUNNER_NAME`, `GITEA_RUNNER_LABELS` (метки раннера, под них потом ориентируются workflow) и, если раннер должен уметь применять что-то в самом кластере — `KUBECONFIG`/`KUBE_CONTEXT`, смонтированный из PVC. Готовый референс — раскатка в кластере `yc-infra-prod` (`.helm/values.yc-cps-prod.yaml` как пример по структуре, сама раскатка в `yc-infra-prod` — рабочий пример gitea+worker в проде).
Если для gitea выделен отдельный хост рядом с кластером (не разворачиваем внутри k8s) — тот же чарт руками разворачивается через docker-compose: контейнер gitea, контейнер act_runner, том с данными. В этом случае раннеру дополнительно монтируется kubeconfig того кластера, куда он должен катить деплои — без этого он видит только себя.
## Зеркалирование gitlab.sarex.io в gitea
Основной код (iac, terraform) живёт в `gitlab.sarex.io`, а не в контуре — контур не должен иметь туда прямого доступа на чтение снаружи каждый раз, вместо этого gitea внутри контура сама периодически подтягивает изменения. Для этого в gitlab.sarex.io заводится пользователь `gitlab-pusher`, ему создаётся токен на чтение (repository read, никаких прав на запись туда не нужно). В gitea на этот токен настраивается pull mirror — для репозитория `infra/iac` и `infra/terraform`, интервал синхронизации минимальный, который позволяет gitea — 10 минут. Меньше gitea не даёт, это не настраиваемый лимит, а особенность её пул-миррора.
После первого успешного синка в gitea появляются полные копии обоих репозиториев с той же историей веток. Дальше вся работа идёт как обычно — пушим в `gitlab.sarex.io`, gitea сама подтягивает изменения по своему расписанию.
## Flux bootstrap
Flux разворачивается через `flux bootstrap`, но не против `gitlab.sarex.io`, а против уже замирроренной репы `infra/iac` в локальной gitea — именно она физически доступна из кластера. Перед бутстрапом в кластере должен уже существовать секрет `yc-cr-auth` (dockerconfigjson с доступом к `cr.yandex/crp3ccidau046kdj8g9q`) — все helm-репозитории в `iac` заведены как OCI-репозиторий с этим registry, и без секрета flux не сможет вытащить ни один чарт с самого начала, включая свои собственные компоненты, если они тоже идут оттуда. Секрет создаётся руками до бутстрапа, flux его не создаёт и не обновляет — это на разработчике/операторе.
Дальше поток такой: пушим в `iac` на `gitlab.sarex.io`, gitea подтягивает изменение с задержкой до 10 минут, flux в кластере видит новый коммит в своей `GitRepository` (она указывает на адрес локальной gitea, не на gitlab) и применяет `Kustomization` по пути `./clusters/<имя-контура>`. Референс структуры — уже закрытые контуры `d8-ugmk-prod` и `brusnika-prod` в `iac`, там `flux-system/gotk-sync.yaml` указывает прямо на внутренний адрес gitea контура, а не наружу.
## Vault
Vault раскатывается тем же flux'ом из `iac`, манифест лежит в `infrastructure/vault` с патчем под конкретный кластер в `infrastructure/vault/<кластер>/vault.yaml`. В патче обязательно нужно поправить три вещи под конкретное железо: `storageClass` в `server.dataStorage` (в контуре обычно нет managed-дисков, часто `local-path` или локальный `nfs`, смотря что провижн заведён на нодах), и `nodeSelector`/`tolerations` — если в кластере выделены таргетные ноды под инфраструктурные сервисы (как `node.deckhouse.io/group: generic` в примере для `d8-ugmk-prod`), vault нужно на них же и посадить, иначе поды зависнут в Pending. Дефолтные `tolerations`, если под них нет отдельной группы нод, обнуляются пустым списком.
После того как HelmRelease встал и поды vault поднялись, vault ещё запечатан (sealed) — это его нормальное стартовое состояние, писать/читать в него нельзя. Инициализация делается вручную: `vault operator init` на первом поде, из вывода забираются unseal keys и root token. Каждый под vault нужно распечатать (`vault operator unseal`) отдельно — если реплик несколько (HA-режим на raft), печатать нужно все, иначе часть кластера останется недоступна и raft не соберётся в кворум. Весь вывод `vault operator init` (ключи + root token) сохраняется в k8s secret `vault-secret` в ns `vault` — это единственное место, где эти данные остаются после инициализации, вывод команды больше нигде не логируется и не хранится. Если секрет потерять, а vault при этом запечатается (рестарт пода, например) — распечатать его без этих ключей уже не выйдет, придётся поднимать заново с потерей данных.
## Terraform: ветка contour
После того как в кластере есть кластер, gitea, flux и vault, разворачиваются собственно сущности приложений — через terraform, репозиторий `infra/terraform`, ветка `contour`. Она собрана так, чтобы одним и тем же кодом обслуживать любой закрытый контур: конкретное окружение выбирается через `INFRA_ENV`, а не через отдельную ветку на контур.
Ветка `contour` при пуше в `gitlab.sarex.io` автоматически зеркалится в отдельный репозиторий `https://gitlab.sarex.io/infra/terraform-contour-mirror`а его уже мирроит к себе gitea контура тем же pull-mirror механизмом, что и `iac`. CI (`.gitea/workflows/terraform.yml`) в самой gitea читает набор переменных и секретов, специфичных для контура — они прописываются в настройках репозитория в gitea (Settings → Actions → Variables/Secrets), не в коде. Из переменных (Variables) обязательны `INFRA_ENV` (имя блока в `infrastructure.yaml`, под которым описан этот контур) и `RUNNER_LABEL` (метка раннера, который будет исполнять джобу — должна совпадать с тем, что реально зарегистрировано в gitea act_runner). Из секретов (Secrets) нужны реквизиты S3-бэкенда для tfstate (`TF_STATE_S3_ENDPOINT`, `TF_STATE_S3_BUCKET`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`), доступ к vault контура (`VAULT_ADDR`, `VAULT_TOKEN`) и приватный age-ключ для sops (`SOPS_AGE_KEY`) — им шифруется `infrastructure-secrets.yaml`. Если раннер не in-cluster, а внешний, дополнительно нужен `KUBECONFIG_B64` — kubeconfig в base64 для доступа к кластеру контура.
Хосты инфраструктурных сервисов конкретного контура (postgres, kafka, rabbitmq, minio, если есть) прописываются не в CI-переменных, а прямо в `infrastructure.yaml`, в отдельном блоке `environments.<имя-контура>` — том самом, на который указывает `INFRA_ENV`. Секретная часть (пароли, ключи) идёт туда же по смыслу, но в `infrastructure-secrets.yaml`, зашифрованном sops на общий age-ключ. Как только оба файла содержат блок под контур и правильные CI-переменные/секреты выставлены в gitea, можно катить план и применять — terraform дальше заводит namespace'ы, базы, топики, vhosts и секреты уже без ручных шагов в кластере.