304 lines
11 KiB
Markdown
304 lines
11 KiB
Markdown
# Deployment
|
||
|
||
## 1. Назначение документа
|
||
|
||
Этот документ фиксирует platform-agnostic модель деплоя проекта.
|
||
|
||
Он отвечает на вопросы:
|
||
|
||
- какие deployment artifacts должны быть в репозитории;
|
||
- как проект должен запускаться в production-like окружении;
|
||
- как routing должен работать через reverse proxy;
|
||
- что должен делать CD pipeline.
|
||
|
||
Документ не привязан к конкретному hypervisor, cloud provider, типу VM или домашней инфраструктуре.
|
||
|
||
Практический post-deploy smoke checklist вынесен отдельно в [deploy-and-staging-smoke.md](/home/github-ops/crank/docs/deploy-and-staging-smoke.md).
|
||
|
||
Отдельный release checklist именно для открытой редакции вынесен в
|
||
[community-release-checklist.md](/home/github-ops/crank/docs/community-release-checklist.md).
|
||
|
||
## 2. Базовая модель деплоя
|
||
|
||
Для MVP принимается контейнерная модель:
|
||
|
||
- `admin-api`, `mcp-server` и `ui` упаковываются в контейнеры;
|
||
- `PostgreSQL` запускается как отдельный контейнер;
|
||
- сервисы запускаются через `docker-compose`;
|
||
- перед приложением находится reverse proxy;
|
||
- CI проверяет качество, CD доставляет и перезапускает сервисы.
|
||
|
||
## 3. Что должно быть в репозитории
|
||
|
||
В проекте должны существовать:
|
||
|
||
- `Dockerfile` для `apps/admin-api`;
|
||
- `Dockerfile` для `apps/mcp-server`;
|
||
- `Dockerfile` для `apps/ui` или отдельный production build flow для UI;
|
||
- `deploy/community/docker-compose.yml` как canonical Community deployment manifest;
|
||
- `deploy/community/.env.example` как canonical Community runtime env template;
|
||
- `docker-compose.yml` и root `.env.example` только как local development convenience layers, а не как future private delivery boundary;
|
||
- `.github/workflows/ci.yml`;
|
||
- `.github/workflows/deploy.yml`;
|
||
- примеры reverse proxy конфигураций.
|
||
|
||
Рекомендуемое стартовое распределение портов:
|
||
|
||
- `ui` -> `3000`
|
||
- `admin-api` -> `3001`
|
||
- `mcp-server` -> `3002`
|
||
|
||
## 4. Production-like runtime состав
|
||
|
||
Минимальный набор сервисов:
|
||
|
||
- `ui`
|
||
- `admin-api`
|
||
- `mcp-server`
|
||
- `postgres`
|
||
- optional `valkey` через compose profile `cache`
|
||
|
||
Persistent data:
|
||
|
||
- volume для `postgres`;
|
||
- volume или bind mount для `artifact storage`.
|
||
|
||
## 5. Рекомендуемая routing model
|
||
|
||
Под одним доменом:
|
||
|
||
- `/` -> `ui`
|
||
- `/api/admin/` -> `admin-api`
|
||
- `/mcp/` -> `mcp-server`
|
||
|
||
Это позволяет:
|
||
|
||
- централизовать TLS;
|
||
- не плодить поддомены без необходимости;
|
||
- держать demo URL простым;
|
||
- использовать один reverse proxy для UI, API и MCP.
|
||
|
||
## 6. Требования к reverse proxy
|
||
|
||
Reverse proxy должен обеспечивать:
|
||
|
||
- TLS termination;
|
||
- редирект `HTTP -> HTTPS`;
|
||
- `X-Forwarded-*` headers;
|
||
- отключение buffering для MCP endpoint;
|
||
- увеличенные таймауты для долгих runtime-запросов;
|
||
- корректную маршрутизацию UI, admin API и MCP.
|
||
|
||
Особенно для `mcp-server` важно:
|
||
|
||
- `proxy_buffering off`;
|
||
- `proxy_request_buffering off`;
|
||
- длинный `proxy_read_timeout`.
|
||
|
||
## 7. Пример `nginx`
|
||
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name example.com;
|
||
|
||
return 301 https://$host$request_uri;
|
||
}
|
||
|
||
server {
|
||
listen 443 ssl http2;
|
||
server_name example.com;
|
||
|
||
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
|
||
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
|
||
|
||
client_max_body_size 25m;
|
||
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_http_version 1.1;
|
||
|
||
location / {
|
||
proxy_pass http://ui:3000;
|
||
}
|
||
|
||
location /api/admin/ {
|
||
proxy_pass http://admin-api:3001/;
|
||
}
|
||
|
||
location /mcp/ {
|
||
proxy_pass http://mcp-server:3002/;
|
||
proxy_buffering off;
|
||
proxy_request_buffering off;
|
||
proxy_read_timeout 300s;
|
||
proxy_send_timeout 300s;
|
||
}
|
||
}
|
||
```
|
||
|
||
## 8. Пример `Traefik`
|
||
|
||
```yaml
|
||
services:
|
||
ui:
|
||
labels:
|
||
- traefik.enable=true
|
||
- traefik.http.routers.ui.rule=Host(`example.com`) && PathPrefix(`/`)
|
||
- traefik.http.services.ui.loadbalancer.server.port=3000
|
||
|
||
admin-api:
|
||
labels:
|
||
- traefik.enable=true
|
||
- traefik.http.routers.admin.rule=Host(`example.com`) && PathPrefix(`/api/admin`)
|
||
- traefik.http.services.admin.loadbalancer.server.port=3001
|
||
|
||
mcp-server:
|
||
labels:
|
||
- traefik.enable=true
|
||
- traefik.http.routers.mcp.rule=Host(`example.com`) && PathPrefix(`/mcp`)
|
||
- traefik.http.services.mcp.loadbalancer.server.port=3002
|
||
```
|
||
|
||
Для production нужно отдельно проверить timeout behavior на MCP маршруте.
|
||
|
||
## 9. Требования к `docker-compose`
|
||
|
||
`deploy/community/docker-compose.yml` должен:
|
||
|
||
- поднимать `postgres`, `admin-api`, `mcp-server`, `ui`;
|
||
- использовать env vars вместо hardcoded config;
|
||
- иметь persistent storage для БД и artifacts;
|
||
- публиковать `ui`, `admin-api` и `mcp-server` на loopback по умолчанию;
|
||
- позволять переопределить publish bind одним env для случаев, когда reverse proxy вынесен на отдельный хост;
|
||
- не публиковать внутренние сервисы наружу без необходимости;
|
||
- содержать healthchecks;
|
||
- поддерживать optional cache profile без превращения `Valkey` в обязательную runtime dependency.
|
||
|
||
Для Community optional cache layer включается отдельно:
|
||
|
||
- profile: `cache`
|
||
- service: `valkey`
|
||
- базовый запуск без profile должен оставаться полностью рабочим
|
||
|
||
Пример:
|
||
|
||
```bash
|
||
docker compose \
|
||
-f deploy/community/docker-compose.yml \
|
||
--env-file deploy/community/.env.example \
|
||
--profile cache up -d
|
||
```
|
||
|
||
## 10. Healthchecks
|
||
|
||
Нужны как минимум:
|
||
|
||
- `GET /health` для `admin-api`
|
||
- `GET /health` для `mcp-server`
|
||
|
||
CD не должен считать deployment успешным, пока эти endpoints не начали возвращать `200 OK`.
|
||
|
||
## 11. CI и CD
|
||
|
||
### 11.1. CI
|
||
|
||
CI должен выполнять:
|
||
|
||
- `cargo fmt --all --check`
|
||
- `cargo check --workspace`
|
||
- `cargo clippy --workspace --all-targets --all-features -- -D warnings`
|
||
- `cargo test --workspace --all-targets`
|
||
|
||
### 11.2. CD
|
||
|
||
CD для MVP должен:
|
||
|
||
- запускаться только после успешного `CI` на `main` или вручную;
|
||
- собирать versioned container images;
|
||
- пушить их в container registry с cache;
|
||
- доставлять deployment files на целевой Linux host;
|
||
- выполнять controlled restart через `pull`, без сборки на сервере;
|
||
- проверять health endpoints после запуска.
|
||
|
||
Минимальный flow:
|
||
|
||
1. push в `main`
|
||
2. успешный `CI`
|
||
3. build and push images в registry
|
||
4. upload runtime env и deployment files
|
||
5. `docker login` на registry на целевом хосте
|
||
6. `docker compose config -q`
|
||
7. `docker compose pull`
|
||
8. `docker compose up -d`
|
||
9. healthcheck verification для `ui`, `admin-api`, `mcp-server`
|
||
|
||
Для текущего проекта базовая рекомендуемая схема:
|
||
|
||
- `GHCR` как registry;
|
||
- versioned tags по `git sha`;
|
||
- `docker/buildx` cache в GitHub Actions;
|
||
- на сервере только `pull + restart`, без `docker compose up --build`.
|
||
|
||
Для private registry на сервере нужны отдельные deployment secrets:
|
||
|
||
- `DEPLOY_REGISTRY_USER`
|
||
- `DEPLOY_REGISTRY_TOKEN`
|
||
|
||
Они используются только для `docker login` на целевом Linux host перед `docker compose pull`.
|
||
|
||
Монолитный `DEPLOY_ENV_FILE` больше не используется.
|
||
|
||
Workflow доставляет именно `deploy/community/docker-compose.yml` как public Community deployment manifest
|
||
и собирает `.env` на сервере из отдельных GitHub secrets. Для runtime-переменных нужно
|
||
создавать secrets с теми же именами, что и у приложений:
|
||
|
||
- `POSTGRES_HOST`
|
||
- `POSTGRES_PORT`
|
||
- `POSTGRES_DB`
|
||
- `POSTGRES_USER`
|
||
- `POSTGRES_PASSWORD`
|
||
- `CRANK_STORAGE_ROOT`
|
||
- `CRANK_PUBLISH_BIND`
|
||
- `CRANK_ADMIN_BIND`
|
||
- `CRANK_MCP_BIND`
|
||
- `CRANK_MCP_REFRESH_MS`
|
||
- `CRANK_LOG_LEVEL`
|
||
- `CRANK_MASTER_KEY`
|
||
- `CRANK_BASE_URL`
|
||
- `CRANK_CACHE_BACKEND`
|
||
- `CRANK_CACHE_URL`
|
||
- `CRANK_CACHE_DEFAULT_TTL_MS`
|
||
- `CRANK_SESSION_SECRET`
|
||
- `CRANK_PASSWORD_PEPPER`
|
||
- `CRANK_SESSION_TTL_HOURS`
|
||
- `CRANK_BOOTSTRAP_ADMIN_EMAIL`
|
||
- `CRANK_BOOTSTRAP_ADMIN_PASSWORD`
|
||
- `CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME`
|
||
- `CRANK_DEMO_SEED`
|
||
|
||
Практически это означает:
|
||
|
||
- одна runtime-переменная = один GitHub secret;
|
||
- ротация одного секрета не требует переписывать весь набор env;
|
||
- `.env` на сервере каждый deploy собирается заново из актуальных secrets.
|
||
|
||
## 11. Граница Community против future commercial delivery
|
||
|
||
Community delivery path должен оставаться отдельным и воспроизводимым сам по себе.
|
||
|
||
Это означает:
|
||
|
||
- `deploy/community/*` — public source of truth для открытой поставки;
|
||
- future `Enterprise` и `Cloud` не должны появляться как еще один `.env` на том же compose-файле;
|
||
- commercial manifests и pipelines должны жить отдельно после создания private repositories.
|
||
|
||
## 12. Что нельзя забыть
|
||
|
||
- TLS и renewal сертификатов;
|
||
- backup для `postgres` и artifact storage;
|
||
- секреты вне репозитория;
|
||
- app-level auth для `admin-api` и bootstrap admin credentials в env;
|
||
- rollback на предыдущий image tag или предыдущую compose revision.
|