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

15 KiB
Raw Blame History

Развёртывание платформы 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 и секреты уже без ручных шагов в кластере.