Перейти к содержанию

Микросервисы — разработка и деплой

Как добавлять, редактировать и деплоить независимые сервисы. 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 сервисов в продакшене и стабильны:

  1. Все handle /api/... в Caddyfile должны явно указывать на сервис, фолбэк удаляется
  2. hotel_api_v2 стопится: docker compose stop hotel_api_v2 && docker rm hotel_api_v2
  3. Удаляется build из docker-compose.yml
  4. 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 не менялись)