Микросервисы — разработка и деплой¶
Как добавлять, редактировать и деплоить независимые сервисы. URL для пользователей не меняются — Caddy/Nginx роутят
/api/v1/<domain>/*на нужный сервис, остальное — фолбэк в монолитhotel_api_v2.
0. Карта портов¶
| Сервис | Внутренний порт | Внешний порт (debug) | Префикс URL |
|---|---|---|---|
resort-svc |
8000 | 9104 | /api/v1/resort/* |
auth-svc |
8000 | 9101 | /api/v1/auth/* |
booking-svc |
8000 | 9102 | /api/v1/bookings/* |
catalog-svc |
8000 | 9103 | /api/v1/hotels/* и др. |
chat-svc |
8000 | 9105 | /api/v1/chat/* |
rtc-svc |
8000 | 9106 | /api/v1/calls/* /ws/* |
analytics-svc |
8000 | 9107 | /api/v1/analytics/* |
platform-svc |
8000 | 9108 | /api/v1/files/* и др. |
hotel_api_v2 |
8000 | 9002 | фолбэк всё остальное |
Все сервисы в общей docker-сети arkhyz_supa_net → доступны друг другу
по короткому имени: http://resort-svc:8000, http://auth-svc:8000 и т.д.
1. Структура одного сервиса¶
services/resort-svc/
├── Dockerfile
├── requirements.txt
└── app/
├── __init__.py
├── main.py # FastAPI factory + include_router
└── routers/
├── __init__.py
└── resort_status.py
Базовое app/main.py:
from arkhyz_shared.app import make_app
from app.routers import resort_status
app = make_app(service_name="resort-svc", version="0.1.0")
app.include_router(resort_status.router, prefix="/api/v1/resort", tags=["resort"])
make_app из arkhyz_shared даёт CORS, /health, structured logs,
request-timing — единообразно для всех сервисов.
2. Локальная разработка¶
Поднять только один сервис (без всего стека)¶
cd D:\www\Архыз.CLUB\arkhyz-admin-main
# Сборка + запуск конкретного сервиса
docker compose -f docker-compose.yml -f infra/compose/docker-compose.services.yml up -d --build resort-svc
# Проверить
curl http://localhost:9104/health
curl http://localhost:9104/api/v1/resort/status
Hot-reload в dev-режиме (без Docker)¶
cd services\resort-svc
pip install -e ..\..\packages\arkhyz-shared
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
Запустить весь стек локально¶
docker compose -f docker-compose.yml `
-f docker-compose.supabase.yml `
-f infra/compose/docker-compose.services.yml `
up -d
3. Внести изменения в один сервис¶
# 1. Правишь код в services/<svc>/app/...
# 2. Пересборка + рестарт ТОЛЬКО этого сервиса (10–30 сек)
docker compose -f docker-compose.yml -f infra/compose/docker-compose.services.yml `
up -d --build resort-svc
# 3. Логи
docker logs -f arkhyz-resort-svc
# 4. Health
curl http://localhost:9104/health
Никакой другой контейнер не пересоздаётся. Монолит, Supabase, фронты остаются на месте.
4. Добавить новый сервис¶
Допустим, выносим auth-svc:
# 1. Скелет
mkdir -p services/auth-svc/app/routers
cp services/resort-svc/Dockerfile services/auth-svc/
cp services/resort-svc/requirements.txt services/auth-svc/
touch services/auth-svc/app/__init__.py
touch services/auth-svc/app/routers/__init__.py
# 2. Перенести роутеры из api-admin
cp api-admin/app/routers/auth*.py services/auth-svc/app/routers/
cp api-admin/app/routers/sms*.py services/auth-svc/app/routers/
cp api-admin/app/routers/users*.py services/auth-svc/app/routers/
cp api-admin/app/routers/roles*.py services/auth-svc/app/routers/
# 3. main.py
cat > services/auth-svc/app/main.py <<'EOF'
from arkhyz_shared.app import make_app
from app.routers import auth, sms, users, roles
app = make_app(service_name="auth-svc")
app.include_router(auth.router, prefix="/api/v1/auth", tags=["auth"])
app.include_router(sms.router, prefix="/api/v1/auth", tags=["sms"])
app.include_router(users.router, prefix="/api/v1/users", tags=["users"])
app.include_router(roles.router, prefix="/api/v1/roles", tags=["roles"])
EOF
Затем добавить в infra/compose/docker-compose.services.yml:
auth-svc:
build:
context: ../..
dockerfile: services/auth-svc/Dockerfile
image: arkhyz-auth-svc:latest
container_name: arkhyz-auth-svc
restart: unless-stopped
networks: [supa-net]
ports: ["9101:8000"]
env_file: [../../.env]
depends_on:
kong: { condition: service_healthy }
И раскомментировать строку в Caddyfile/Nginx:
handle /api/v1/auth/* { reverse_proxy auth-svc:8000 }
После деплоя auth-svc обслуживает /api/v1/auth/*, монолит — всё остальное.
5. Прод-деплой одного сервиса¶
URL для пользователей не меняется (https://api.arkhyz-club.ru/api/v1/resort/...),
меняется только то, какой контейнер за этим URL.
5.1. Вариант A — деплой по SSH (текущая инфраструктура)¶
ssh root@prod.arkhyz-club.ru
cd /root/arkhyz-admin-main
# Подтянуть код
git pull origin main
# Пересобрать и запустить ТОЛЬКО изменённый сервис
docker compose \
-f docker-compose.yml \
-f docker-compose.prod.yml \
-f infra/compose/docker-compose.services.yml \
up -d --no-deps --build resort-svc
# Перечитать Caddy (если поменялись маршруты)
docker exec arkhyz-caddy caddy reload --config /etc/caddy/Caddyfile
# Проверить
curl https://api.arkhyz-club.ru/api/v1/resort/status | jq .stale
--no-deps гарантирует, что Caddy/монолит/БД не перезапускаются.
Даунтайм для /api/v1/resort/* — около 2 секунд (graceful restart контейнера).
5.2. Вариант B — через CI/CD (рекомендуется)¶
.github/workflows/services.yml детектит изменённый сервис по path-фильтру
и собирает/публикует только его образ. Деплой на прод триггерится
push'ом образа в registry или через docker compose pull && up -d --no-deps.
Шаблон workflow:
name: build-services
on:
push:
paths:
- 'services/**'
- 'packages/arkhyz-shared/**'
jobs:
detect:
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.filter.outputs.services }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 2 }
- id: filter
run: |
# выдаёт JSON-массив изменённых сервисов
echo "services=$(git diff --name-only HEAD~ HEAD | \
grep -oP '^services/\K[^/]+' | sort -u | jq -R -s -c 'split("\n")[:-1]')" \
>> $GITHUB_OUTPUT
build:
needs: detect
if: needs.detect.outputs.changed != '[]'
strategy:
matrix:
svc: ${{ fromJSON(needs.detect.outputs.changed) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/build-push-action@v5
with:
context: .
file: services/${{ matrix.svc }}/Dockerfile
push: true
tags: ghcr.io/arkhyz-club/${{ matrix.svc }}:${{ github.sha }}
- name: Deploy via SSH
run: |
ssh root@prod "cd /root/arkhyz-admin-main && \
docker pull ghcr.io/arkhyz-club/${{ matrix.svc }}:${{ github.sha }} && \
docker compose -f infra/compose/docker-compose.services.yml \
up -d --no-deps ${{ matrix.svc }}"
6. Откат (rollback)¶
# Узнать предыдущий образ
docker images arkhyz-resort-svc
# Запустить старый
docker tag arkhyz-resort-svc:<old-sha> arkhyz-resort-svc:latest
docker compose -f infra/compose/docker-compose.services.yml up -d --no-deps resort-svc
Или (если используется CI с тегами по SHA) — указать в image: старый SHA
и up -d --no-deps.
7. Inter-service вызовы¶
Внутри docker-сети сервисы видят друг друга по имени:
import httpx
async with httpx.AsyncClient() as c:
r = await c.get("http://catalog-svc:8000/api/v1/hotels/123/internal")
Ауть-токен пробрасывается через Authorization заголовок —
auth-svc его выписал, остальные сервисы декодируют локально через
arkhyz_shared.auth (без сетевых хопов).
8. Мониторинг¶
Каждый сервис отдаёт:
- GET /health — {service, version, status}
- Заголовки x-service и x-response-time-ms на каждом ответе
- Structured logs в stdout (JSON-friendly)
# Health всех микросервисов
for p in 9101 9102 9103 9104 9105 9106 9107 9108; do
echo -n "Port $p: "
curl -s http://localhost:$p/health | jq -c '.service,.status' 2>/dev/null || echo DOWN
done
9. Когда удаляется монолит¶
После того как все 8 сервисов в продакшене и стабильны:
- Все
handle /api/...в Caddyfile должны явно указывать на сервис, фолбэк удаляется hotel_api_v2стопится:docker compose stop hotel_api_v2 && docker rm hotel_api_v2- Удаляется build из
docker-compose.yml api-admin/архивируется вlegacy/api-admin-monolith.tar.gzи удаляется
10. Чеклист для нового сервиса¶
- [ ] Создан
services/<svc>/Dockerfile - [ ] Создан
services/<svc>/requirements.txt(с-e /app/packages/arkhyz-shared) - [ ] Создан
services/<svc>/app/main.pyчерезmake_app() - [ ] Перенесены роутеры из
api-admin/app/routers/ - [ ] Добавлена секция в
infra/compose/docker-compose.services.yml - [ ] Открыт порт
91XX:8000 - [ ] Подключён к сети
arkhyz_supa_net - [ ] Добавлен
handle /api/v1/<domain>/* { reverse_proxy <svc>:8000 }в Caddyfile.prod - [ ] Локальный smoke-тест:
curl http://localhost:91XX/health - [ ] Удалены роутеры из монолита (после успешного деплоя)
- [ ] Frontend продолжает работать (URL не менялись)