Files
2026-06-03 18:37:53 +03:00

27 KiB
Raw Permalink Blame History

Test-контур Sova на k3s: что сделано и как перенести на удалённый сервер

Статья описывает изолированный test-контур в каталоге k3s-test/: архитектуру, порядок локального развёртывания и план переноса на реальный сервер.


Зачем это нужно

Монорепозиторий Sova (apps/backend, apps/cabinet, local/ и т.д.) не трогали. Вместо этого собран отдельный контур — песочница, где можно:

  • поднять backend, admin panel, БД и моки в Kubernetes;
  • проверить GitOps (ArgoCD), CI (Gitea Actions), мониторинг (Prometheus/Grafana);
  • накатывать изолированные test-БД (не *_local, а *_test) из тех же SQL, что и в local/;
  • отрабатывать сценарии до выкладки на stage/production.

Домены локально: *.sova.local. На удалённом сервере их заменят на реальные, например *.test.sova.dev.


Архитектура

flowchart TB
  subgraph dev["Mac (разработчик)"]
    docker["Docker build"]
    helm["Helm / kubectl"]
    scripts["scripts/*.sh"]
  end

  subgraph vm["VM sova-test (Multipass) / удалённый сервер"]
    k3s["k3s single-node"]
    ingress["ingress-nginx"]
  end

  subgraph platform["Platform layer"]
    argocd["ArgoCD"]
    gitea["Gitea + Actions"]
    prom["Prometheus"]
    graf["Grafana"]
  end

  subgraph apps["Application layer"]
    be["backend nginx+php-fpm"]
    ap["adminpanel nginx"]
    mocks["WireMock + Mailpit"]
  end

  subgraph data["Data layer (sova-data-test)"]
    pg["PostgreSQL Bitnami"]
    mysql["MySQL 8.0"]
    redis["Redis"]
    dbinit["Job db-init: schema → seed"]
  end

  scripts --> helm
  docker --> k3s
  helm --> k3s
  k3s --> ingress
  ingress --> be & ap & argocd & gitea & graf & prom
  argocd --> apps & data
  gitea --> argocd
  dbinit --> pg & mysql
  be --> pg & redis & mysql
  be --> mocks

Структура репозитория k3s-test/

Каталог Назначение
sova-backend/ Копия Symfony API + Dockerfile + workflow Gitea
sova-adminpanel/ Копия React admin + runtime env.js
sova-cabinet/ Копия Symfony ЛК + Dockerfile + workflow Gitea
sova-docs/ VitePress-документация (из docs/ монорепо) + Dockerfile + workflow Gitea
sova-deploy/ Helm charts, ArgoCD manifests, SQL для test-БД
sova-mocks/ WireMock (MIS, Calltouch, Captcha) + Mailpit
sova-platform/ Terraform-модуль установки k3s на VM
scripts/ Bootstrap, сборка образов, деплой, smoke

Что сделано в коде

Backend (sova-backend)

  • Вынесены URL интеграций в env: WIDGET_API_URL, API_PUBLIC_URL.
  • Stub-режим внешних сервисов: SMS, Calltouch, SmartCaptcha (INTEGRATIONS_STUB_MODE=true).
  • Production Dockerfile: PHP 8.4-FPM, multistage.
  • JWT-ключи монтируются из Helm Secret (sova-deploy/apps/backend/jwt/).

Admin Panel (sova-adminpanel)

  • Runtime-конфиг public/env.js (API URL без пересборки).
  • Dockerfile: Node build → nginx.
  • ConfigMap + entrypoint для подстановки URL в k8s.

Базы данных

SQL из монорепо (local/postgres/init/, local/mysql-bitrix/init/) разделён на два этапа:

  1. Schemasova-deploy/data/test/sql/*/schema/
  2. Seedsova-deploy/data/test/sql/*/seed/

Скрипт scripts/prepare-db-init.py:

  • переименовывает *_local*_test;
  • добавляет \connect в seed-файлы;
  • заменяет md5('...') на литералы (PostgreSQL 18 не поддерживает md5() в INSERT).

Job db-init (Helm chart sova-deploy/data/db-init/) выполняет: schema PostgreSQL → schema MySQL → seed PostgreSQL → seed MySQL.

База Имя Назначение
PostgreSQL sova_backend_test Backend API, admin auth
PostgreSQL sova_cabinet_test Личный кабинет (если подключается)
MySQL sova_bitrix_test Views/синк Bitrix
Redis Кэш, messenger

MySQL поднят через простой manifest mysql:8.0 (Bitnami chart давал ImagePullBackOff). Auth plugin: mysql_native_password для совместимости с клиентом в init Job.

GitOps (sova-deploy)

  • Helm: apps/backend, apps/adminpanel, data/db-init, data/test.
  • ArgoCD Applications: backend, adminpanel, mocks, data-test.
  • Репозитории в Gitea: sova/backend, sova/adminpanel, sova/sova-deploy, sova/sova-mocks.

Backend: stateless console в ArgoCD

Помимо Deployment (nginx + php-fpm), chart apps/backend создаёт CronJob-ы с контейнером console — Symfony-команды по расписанию. В ArgoCD они видны в дереве приложения backend-test как отдельные ресурсы с label app.kubernetes.io/component: console:

CronJob Расписание (MSK) Команда
backend-clear-schedule-cache 0 */6 * * * app:schedule:clear-cache
backend-sync-doctors 0 3 * * * upload:doctors
backend-sync-reviews 30 3 * * * bitrix-update-reviews

Конфигурация — список cronjobs в values.yaml. Для test-контура Job миграций (backend-migrate) отключён (migrate.enabled: false), т.к. схема накатывается через db-init, а не doctrine:migrations:migrate. На stage/prod можно включить PreSync-migrate.

Deployment использует initContainer warmup-cache и emptyDir для /app/var/cache и /app/var/log — иначе Symfony не может писать кэш в prod (HTTP 500).

Platform layer

Компонент Namespace Ingress
ingress-nginx ingress LoadBalancer на IP VM
cert-manager cert-manager
sealed-secrets kube-system
ArgoCD argocd argocd.sova.local
Gitea gitea git.sova.local
kube-prometheus-stack monitoring grafana.sova.local, prometheus.sova.local
Loki + Promtail monitoring только внутри кластера (логи в Grafana)

Метрики vs логи: Prometheus собирает метрики (CPU, память, kube-state). Логи подов — через Loki + Promtail (Explore → Loki в Grafana). Без Loki в Grafana «логов нет» — это ожидаемо до ./scripts/deploy-monitoring-logs.sh.

# После deploy-platform или отдельно:
./scripts/deploy-monitoring-logs.sh

# В Grafana Explore:
#   datasource: Loki
#   query: {namespace="sova-test", app="backend"}

Важно для ArgoCD: UI работает по HTTP только при:

  • server.insecure=true в argocd-cmd-params-cm (ключ server.insecure, не вложенный YAML);
  • url: http://argocd.sova.local в argocd-cm;
  • ingress без принудительного HTTPS.

Терминал в pod (exec): по умолчанию в Argo CD выключен. В test-контуре включён через sova-deploy/platform/argocd/values-test.yaml (exec.enabled: true + RBAC). В UI: Application → backend-test → Pod → Terminal → контейнер php-fpm.

Пример миграций из терминала ArgoCD или kubectl:

php bin/console doctrine:migrations:migrate --no-interaction
php bin/console doctrine:migrations:status

Через kubectl (если UI недоступен):

kubectl exec -n sova-test deploy/backend -c php-fpm -it -- sh
# внутри pod:
php bin/console doctrine:migrations:migrate --no-interaction

ArgoCD внутри кластера ходит в Gitea по внутреннему URL:
http://gitea-http.gitea.svc.cluster.local:3000/sova/...
(снаружи браузер использует http://git.sova.local).


Локальное развёртывание (Multipass)

Требования: Docker, Multipass, Helm ≥ 3.14, kubectl, Terraform (опционально).

Полный цикл

cd k3s-test

# 1. Синхронизация кода из монорепо (если нужно обновить)
./scripts/sync-from-monorepo.sh

# 2. VM + k3s (4 CPU, 8 GB RAM, 40 GB disk)
./scripts/bootstrap-multipass.sh local-test

# 3. Platform: ingress, ArgoCD, Gitea, Prometheus/Grafana
./scripts/deploy-platform.sh

# 4. Приложения, БД, моки
USE_MULTIPASS=1 ./scripts/deploy-test-stack.sh local-test

# 5. Репозитории в Gitea + CI user
./scripts/bootstrap-gitea.sh

# 6. Gitea Actions runner
./scripts/bootstrap-gitea-runner.sh

# 7. ArgoCD applications + HTTP UI
./scripts/bootstrap-argocd.sh

# 8. Логины и URL
./scripts/print-test-users.sh
./scripts/print-urls.sh

/etc/hosts на Mac

Подставьте IP из multipass info sova-test:

192.168.x.x api.test.sova.local admin.test.sova.local
192.168.x.x argocd.sova.local git.sova.local grafana.sova.local prometheus.sova.local

Kubeconfig: ~/.kube/sova-test-config.

Альтернатива без Multipass (k3d)

Для быстрой проверки на Mac без VM:

./scripts/k3d-bootstrap.sh local-test
echo "127.0.0.1 api.test.sova.local admin.test.sova.local" | sudo tee -a /etc/hosts
./scripts/smoke-test.sh

Platform layer (Gitea, ArgoCD) в k3d-варианте поднимается по необходимости через deploy-platform.sh.


URL и тестовые пользователи

Сервис URL
Backend API http://api.test.sova.local
Admin Panel http://admin.test.sova.local
Gitea http://git.sova.local/sova/
ArgoCD http://argocd.sova.local
Grafana http://grafana.sova.local
Prometheus http://prometheus.sova.local

Пользователи приложения (seed в БД)

Сценарий Логин Пароль
Admin Panel + Backend JWT local.backend@example.test local-password
Cabinet (отдельная БД) local.cabinet@example.test local-password
Auth по pcode uid 100001, birthDate 19900101

Проверка логина:

curl -X POST http://api.test.sova.local/user/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"local.backend@example.test","password":"local-password"}'

Platform credentials

После bootstrap-gitea.sh — файл .generated/platform-credentials.env:

  • Gitea admin: gitea_admin
  • CI user: sova-ci
  • ArgoCD: admin + пароль из argocd-initial-admin-secret

Актуальные значения: ./scripts/print-test-users.sh.


CI/CD (Gitea Actions)

В репозиториях лежат workflow:

  • sova-backend/.gitea/workflows/build.yml
  • sova-adminpanel/.gitea/workflows/build.yml

Цепочка по тегу backend-v1.0.1-test / adminpanel-v1.0.1-test:

  1. test — composer / npm, lint, build
  2. build-and-push — Docker-образ в Gitea Container Registry
  3. deploy-gitops — commit в sova/sova-deploy (repository, tag, pullPolicy)

Registry и секреты CI

Секрет Назначение
REGISTRY_USER / REGISTRY_PASSWORD Push образов (gitea_adminу sova-ci нет прав на packages)
DEPLOY_TOKEN HTTPS push в sova-deploy
HOST_IP IP VM для /etc/hosts (имена с префиксом GITEA_ Gitea не принимает)

Push из runner идёт на внутренний URL gitea-http.gitea.svc.cluster.local:3000 (DinD не резолвит git.sova.local). В values-test.yaml для pull на ноде k3s используется git.sova.local/sova/backend — после ./scripts/configure-k3s-registry.sh.

./scripts/bootstrap-gitea-ci-secrets.sh   # секреты в Gitea Actions
./scripts/configure-k3s-registry.sh       # k3s registries.yaml + pull secret
./scripts/release-test-tag.sh backend backend-v1.0.1-test

Runner (Gitea Actions)

Развёрнут через официальный chart gitea-charts/actions + DinD (Docker-in-Docker):

./scripts/bootstrap-gitea-runner.sh

Конфиг: sova-deploy/platform/gitea-actions/values-test.yaml

Параметр Значение
Release gitea-actions
Namespace gitea
Имя runner sova-test-k8s
Labels ubuntu-latest, ubuntu-22.04
Gitea URL (внутри кластера) http://gitea-http.gitea.svc.cluster.local:3000

Проверка: http://git.sova.local/admin/actions/runners — должен быть Online.

Логи: kubectl logs -n gitea -l app.kubernetes.io/name=actions-runner -c runner -f

Workflow запускается по push тега. Job test не требует registry; build-and-push — нужен Container Registry (packages.ENABLED=true в Gitea).


Проверка штатного режима (чеклист)

После полного bootstrap или CI-релиза:

export KUBECONFIG=~/.kube/sova-test-config

# 1. Поды
kubectl get pods -n sova-test
kubectl get cronjobs -n sova-test   # backend-sync-doctors, backend-sync-reviews, ...

# 2. ArgoCD
kubectl get applications -n argocd
# UI: http://argocd.sova.local → backend-test → дерево с CronJob

# 3. API
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: api.test.sova.local" \
  "http://$(multipass info sova-test | awk '/IPv4/{print $2; exit}')/news/list?page=1"
# Ожидается: 200

curl -X POST http://api.test.sova.local/user/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"local.backend@example.test","password":"local-password"}'

# 4. CI (если настроен runner)
# http://git.sova.local/sova/backend/actions — последний run: success

# 5. Логи в Grafana (не Prometheus!)
# http://grafana.sova.local → Explore → Loki → {namespace="sova-test"}

# 6. Admin panel
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: admin.test.sova.local" \
  "http://$(multipass info sova-test | awk '/IPv4/{print $2; exit}')/"

Перенос на удалённый сервер

Ниже — пошаговый план миграции с Multipass на bare metal / VPS / облако.

1. Требования к серверу

Ресурс Минимум (test) Рекомендуется
CPU 4 vCPU 8 vCPU
RAM 8 GB 16 GB
Disk 40 GB SSD 80+ GB SSD
ОС Ubuntu 22.04 LTS Ubuntu 22.04 / 24.04
Сеть Публичный IP или VPN + DNS на ваш домен

Открытые порты: 80, 443 (ingress), опционально 22 (SSH).

2. Подготовка сервера

# На сервере (Ubuntu)
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git

# Установка k3s (аналог install-k3s-multipass.sh, но на bare metal)
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable traefik" sh -

# Kubeconfig на вашу машину
scp user@SERVER:/etc/rancher/k3s/k3s.yaml ~/.kube/sova-remote-config
# Заменить 127.0.0.1 на IP сервера в server:
export KUBECONFIG=~/.kube/sova-remote-config

Terraform-модуль sova-platform/terraform/modules/k3s-single-node можно использовать и для удалённого сервера: задать server_ip, ssh_user, ssh_private_key_path в terraform.tfvars.

3. DNS вместо /etc/hosts

Замените *.sova.local на реальный домен, например:

Было (local) Станет (remote test)
api.test.sova.local api.test.sova.dev
admin.test.sova.local admin.test.sova.dev
git.sova.local git.test.sova.dev
argocd.sova.local argocd.test.sova.dev
grafana.sova.local grafana.test.sova.dev

A-записи всех имён → публичный IP сервера.

Файлы для правки:

  • sova-deploy/apps/backend/values-test.yaml — ingress host, API_PUBLIC_URL, CORS
  • sova-deploy/apps/adminpanel/values-test.yaml — ingress, env.js API URL
  • scripts/deploy-platform.sh — Gitea DOMAIN, ROOT_URL
  • scripts/deploy-platform-ingress.sh — host rules
  • sova-adminpanel/public/env.js (или ConfigMap) — API_BASE_URL

4. TLS (рекомендуется на сервере)

  1. cert-manager уже ставится через deploy-platform.sh.
  2. Создайте ClusterIssuer Let's Encrypt (HTTP-01 или DNS-01).
  3. В Ingress добавьте tls: секции и annotation cert-manager.io/cluster-issuer.
  4. Для ArgoCD при TLS на ingress: оставьте server.insecure=true или настройте TLS passthrough — см. документацию ArgoCD.

5. Образы: с локального import на registry

Сейчас (Multipass): образы собираются на Mac, импортируются через multipass transfer + k3s ctr images import, image.pullPolicy: Never.

На сервере:

# Вариант A: registry в Gitea
# После bootstrap-gitea — включить Container Registry в Gitea,
# push: git.test.sova.dev/sova/backend:backend-v1.0.0-test

# Вариант B: внешний registry (GitHub CR, GitLab, Yandex CR)
docker build -t registry.example.com/sova/backend:1.0.0-test ./sova-backend
docker push registry.example.com/sova/backend:1.0.0-test

В values-test.yaml:

image:
  repository: git.test.sova.dev/sova/backend   # или ваш registry
  tag: backend-v1.0.0-test
  pullPolicy: IfNotPresent

Добавьте imagePullSecrets если registry приватный.

6. Секреты

Сейчас: plain Secrets в Helm values (sova-deploy/apps/backend/values.yaml).

На сервере:

  1. Sealed Secrets — уже ставится в platform layer.
  2. Зашифруйте секреты локально, commit sealed-манifests в sova-deploy.
  3. Уберите пароли из plain values.yaml.

Обязательно смените:

  • APP_SECRET, пароли БД, JWT keys (новая пара для test/stage/prod)
  • Gitea admin password
  • Grafana admin password

7. Базы данных

Test-контур использует in-cluster PostgreSQL/Redis/MySQL. Для удалённого test это допустимо; для stage/prod рассмотрите:

  • managed PostgreSQL (Yandex Cloud, RDS, etc.);
  • отдельный MySQL для Bitrix views;
  • backup PVC или external storage class.

Порядок инициализации тот же:

./scripts/prepare-db-init.sh
helm upgrade --install db-init ./sova-deploy/data/db-init -n sova-data-test

При смене PostgreSQL major version перегенерируйте SQL (prepare-db-init.py уже учитывает отличия PG 18).

8. Деплой на сервер — чеклист

# С вашей машины (KUBECONFIG → удалённый кластер)
export KUBECONFIG=~/.kube/sova-remote-config

# 1. Platform
BOOTSTRAP_PLATFORM=1 ./scripts/deploy-platform.sh

# 2. Обновить values-test.yaml под новый домен (см. п.3)
# 3. Apps + data
./scripts/deploy-test-stack.sh test   # или ваш tag

# 4. Gitea repos (push с CI-машины или с сервера)
GITEA_HOST=git.test.sova.dev ./scripts/bootstrap-gitea.sh

# 5. ArgoCD
GITEA_REPO_URL=http://gitea-http.gitea.svc.cluster.local:3000 \
  ./scripts/bootstrap-argocd.sh

# 6. Gitea Actions runner
./scripts/bootstrap-gitea-runner.sh

# 7. Smoke
curl https://api.test.sova.dev/news/list?page=1
./scripts/print-test-users.sh

9. GitOps как единственный источник правды

После первичного bootstrap:

  1. Все изменения infra/apps — через commit в sova/sova-deploy.
  2. ArgoCD sync автоматически (у Applications включён automated.selfHeal).
  3. CI по тегу обновляет values-test.yaml → ArgoCD выкатывает новый образ.

10. Мониторинг, логи и алерты

  • Prometheus — метрики k8s и приложений: http://prometheus.sova.local
  • Grafana — дашборды + логи через Loki: http://grafana.sova.local
  • Установка логов: ./scripts/deploy-monitoring-logs.sh (Loki + Promtail + datasource)
  • На сервере: смените пароль Grafana, TLS на Ingress

11. Registry на сервере

./scripts/configure-k3s-registry.sh   # HTTP mirror git.sova.local + imagePullSecret
./scripts/bootstrap-gitea-ci-secrets.sh

В values-test.yaml после CI:

image:
  repository: git.test.sova.dev/sova/backend
  tag: backend-v1.0.1-test
  pullPolicy: IfNotPresent
imagePullSecrets:
  - name: gitea-registry

12. Отличия local vs remote (сводка)

Аспект Multipass (сейчас) Удалённый сервер
Домен *.sova.local + /etc/hosts Реальный DNS
TLS HTTP Let's Encrypt / свой сертификат
Образы local import, pullPolicy: Never Gitea/registry, IfNotPresent
Секреты plain values SealedSecrets / External Secrets
k3s VM Multipass bare metal / VPS / Terraform
Gitea URL для ArgoCD internal service URL тот же паттерн (internal)
CI runner gitea-actions StatefulSet (DinD) масштабировать replicas / отдельная VM
State Terraform local S3 + DynamoDB / GitLab state

Troubleshooting

Симптом Причина Решение
ArgoCD редирект на HTTPS неверный server.insecure bootstrap-argocd.sh, patch argocd-cmd-params-cm
Gitea 503 неверный service в Ingress gitea-http:3000 в deploy-platform-ingress.sh
/user/login 500 нет JWT keys Helm chart backend-jwt secret, remount
/news/list 500 Symfony cache not writable initContainer warmup-cache, emptyDir /app/var/cache
ImagePullBackOff после CI k3s не резолвит .svc.cluster.local repository: git.sova.local/... + configure-k3s-registry.sh
Grafana без логов Loki не установлен ./scripts/deploy-monitoring-logs.sh, Explore → Loki
ArgoCD нет кнопки Terminal в pod exec.enabled: false ./scripts/bootstrap-argocd.sh или values platform/argocd/values-test.yaml
db-init Failed MySQL TLS / auth plugin --skip-ssl, mysql_native_password
db-init Failed на seed нет \connect / md5() перезапустить prepare-db-init.sh
ArgoCD Unknown sync Gitea по внешнему DNS из pod internal URL gitea-http.gitea.svc.cluster.local:3000
Bitnami chart 403 закрытый HTTP repo OCI: oci://registry-1.docker.io/bitnamicharts/...

Redmine (30.05.2026)

В test-контуре развёрнут Redmine — issue tracker для задач разработки и связи с Git-flow (Refs #N / Fixes #N в коммитах).

URL Namespace Назначение
http://redmine.sova.local redmine Задачи, проект sova-platform, пример #27

Sentry был удалён из test-контура (≈10 GiB RAM). Ошибки приложений смотрите в Grafana → Loki.

Что сделано

  • Redmine — Bitnami chart, образ bitnamilegacy/redmine:6.0.5-debian-12-r0, БД redmine_test в sova-data-test.
  • ArgoCD app redmine-test в platform-tools.yaml; namespace redmine в GitOps sova-projects / sova-project.yaml.
  • Ingress redmine.sova.local в deploy-platform-ingress.sh.
  • SMTP через Mailpit (письма в UI мока, не реальная почта).
  • Скрипты: deploy-redmine.sh, bootstrap-redmine.sh, setup-gitea-redmine-integration.sh.
  • Bootstrap: проект Sova Platform, задача #27 (CRUD сущностей в API).
  • Custom image sova-redmine/ с plugin redmine_github_hook — опционально, для webhook из Gitea.

Деплой

./scripts/deploy-redmine.sh
./scripts/deploy-redmine.sh --bootstrap   # проект + задача #27
./scripts/deploy-platform-ingress.sh
kubectl get application redmine-test -n argocd
kubectl get pods -n redmine

Подробный гайд со скриншотами: Redmine.

Следующие шаги

  • Gitea external tracker + webhooks в Redmine (инструкции: setup-gitea-redmine-integration.sh)
  • При необходимости — custom image sova-redmine с redmine_github_hook

Полезные команды

# Статус
kubectl get pods -A
kubectl get applications -n argocd

# Логи backend
kubectl logs -n sova-test deploy/backend -c php-fpm --tail=50

# Пересоздать БД
kubectl delete job db-init -n sova-data-test
USE_MULTIPASS=1 ./scripts/deploy-test-stack.sh local-test

# Helm dry-run
helm template backend-test ./sova-deploy/apps/backend \
  -f ./sova-deploy/apps/backend/values-test.yaml

Итог

В k3s-test/ собран полноценный test-контур: приложения, изолированные БД (schema → seed), моки внешних сервисов, GitOps, Redmine и заготовка CI. Локально он крутится на Multipass + k3s и имитирует production-подобный стек без изменений монорепо.

Перенос на удалённый сервер — это в основном замена домена, TLS, registry образов и управления секретами; скрипты и Helm charts из k3s-test/ переиспользуются с минимальными правками values-test.yaml.

Дальнейшие шаги для production-ready test-сервера:

  1. DNS + TLS
  2. Gitea Container Registry (для docker push в CI)
  3. SealedSecrets
  4. Backup БД
  5. Отдельный stage-контур с values-stage.yaml