Files
docs/infrastructure/test-contour/test-contour-article.md
T

635 lines
25 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.
# 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`.
---
## Архитектура
```mermaid
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. **Schema**`sova-deploy/data/test/sql/*/schema/`
2. **Seed**`sova-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`.
```bash
# После 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:
```bash
php bin/console doctrine:migrations:migrate --no-interaction
php bin/console doctrine:migrations:status
```
Через kubectl (если UI недоступен):
```bash
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 (опционально).
### Полный цикл
```bash
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:
```bash
./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` | — |
Проверка логина:
```bash
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`.
```bash
./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):
```bash
./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-релиза:
```bash
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. Подготовка сервера
```bash
# На сервере (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](https://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/).
### 5. Образы: с локального import на registry
**Сейчас (Multipass):** образы собираются на Mac, импортируются через `multipass transfer` + `k3s ctr images import`, `image.pullPolicy: Never`.
**На сервере:**
```bash
# Вариант 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`:
```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](https://github.com/bitnami-labs/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.
Порядок инициализации **тот же**:
```bash
./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. Деплой на сервер — чеклист
```bash
# С вашей машины (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 на сервере
```bash
./scripts/configure-k3s-registry.sh # HTTP mirror git.sova.local + imagePullSecret
./scripts/bootstrap-gitea-ci-secrets.sh
```
В `values-test.yaml` после CI:
```yaml
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/...` |
---
## Полезные команды
```bash
# Статус
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 и заготовка 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`