Skip to content

Doc Infrastructure

PlutoGr edited this page Oct 17, 2026 · 5 revisions

Где и как работает

Dev-среда сайта работает на одном сервере в Docker Compose. Код попадает туда автоматически: каждый push в main прогоняет тесты, собирает образы и обновляет контейнеры.

Простой пример: от коммита до сайта

  1. Изменение смержено в main.
  2. GitHub Actions запускает workflow «Deploy to dev»: тесты .NET, затем сборка 7 образов с тегом, равным sha коммита.
  3. Workflow по SSH заходит на сервер, обновляет репозиторий, скачивает образы этого sha и перезапускает контейнеры.
  4. Проверка здоровья ждёт, пока сайт ответит, до 5 минут. Если не дождалась, пайплайн красный.
  5. Через несколько минут после мержа изменение видно на https://dev.travelagencyonline.ru.

Сервер и адреса

Что Значение
Хостинг Selectel, Ubuntu 24.04
IP 185.75.189.253
Адрес сайта https://dev.travelagencyonline.ru
Сертификат Let's Encrypt, продлевается автоматически (certbot.timer)
Доступ basic auth: сайт закрыт паролем, пароль выдаётся команде и в вики не публикуется
Без пароля открыты /robots.txt, /healthz, /.well-known/acme-challenge/ (проверка Let's Encrypt)
Индексация запрещена: robots.txt и заголовок X-Robots-Tag: noindex, nofollow
Окружение .NET ASPNETCORE_ENVIRONMENT=Staging: Swagger выключен, в ответах 500 нет стека

Что открыто наружу

  • Снаружи открыты только порты 22 (SSH), 80 и 443 (файрвол ufw).
  • HTTP на 80 порту перенаправляется на HTTPS.
  • Заход по IP или чужому имени перенаправляется (301) на https://dev.travelagencyonline.ru.
  • Gateway (127.0.0.1:5001) и консоль MinIO (127.0.0.1:9001) слушают только локальный адрес сервера, к ним можно попасть через SSH-туннель.
  • SSH: вход только по ключу, без пароля и без root; перебор банит fail2ban.
  • Обновления безопасности ОС ставит unattended-upgrades.

Кто принимает запрос

Контейнер frontend это nginx, он же раздаёт SPA:

Путь Куда
/api/* в Gateway (gateway:8080), лимит тела запроса 12 МБ
/healthz в GET /health/ready Gateway; ответ Healthy, если живы все сервисы
остальное файлы SPA

Домен из одной настройки

Домен не зашит в код. Он задаётся одной переменной SITE_DOMAIN в .env на сервере, и из неё берутся:

  • server_name и редиректы nginx (шаблон nginx-domain.conf.template, его рендерит nginx-entrypoint.sh при старте контейнера);
  • разрешённый CORS origin Gateway https://<SITE_DOMAIN>;
  • сертификат Let's Encrypt.

Фронтенд ходит в API только по относительным путям (/api/v1/...), cookie авторизации без Domain, а JWT issuer и audience от домена не зависят. Поэтому смена домена не требует изменений кода.

Пример. Было SITE_DOMAIN=dev.old-name.ru, стало SITE_DOMAIN=dev.new-name.ru. После запуска deploy/scripts/issue-cert.sh сайт, API и сертификат работают на новом адресе. Если где-то в коде остался старый адрес, это ошибка.

Чек-лист смены домена

  1. Купить домен и добавить A-запись dev.<домен> (и <домен> для продакшна) на IP сервера.
  2. Поменять SITE_DOMAIN в .env на сервере.
  3. Запустить deploy/scripts/issue-cert.sh: он пересоздаёт frontend и gateway с текущими образами (без сборки), выпускает сертификат через certbot certonly --webroot и ставит хук перезагрузки nginx после продления.
  4. При желании оставить старый домен с редиректом на новый, потом удалить его A-запись.

Ожидаемое время: около часа, без релиза кода.

Два режима nginx

nginx-entrypoint.sh при старте выбирает режим:

  • redirect: сертификат для SITE_DOMAIN уже выпущен. Заход по IP получает 301 на домен.
  • fallback: сертификата ещё нет. Сайт работает по IP с самоподписанным сертификатом, чтобы dev не пропал, пока домен не делегирован.

Сейчас dev работает в режиме redirect. HSTS не включён специально, пока домен dev может меняться.

CI/CD

Workflow Когда Что делает
ci.yml («CI») каждый pull request сборка и тесты .NET; npm ci, сборка и тесты vitest фронтенда. Ничего не публикует
deploy.yml («Deploy to dev») push в main, ручной запуск тесты, сборка образов, деплой, проверка здоровья, смоук gRPC
gitleaks.yml («Secret scan») push в main, pull request поиск секретов по всей истории git: стандартные правила gitleaks и своё правило для паролей в строках подключения (Password=, Pwd=), настройки в .gitleaks.toml
Dependabot раз в неделю PR с обновлениями зависимостей

Шаги деплоя

  1. Тесты: dotnet restore, dotnet build, dotnet test всего решения. Упали, дальше не идёт.
  2. Образы: 7 образов (gateway, identity, catalog, booking, chat, media, frontend) публикуются в GHCR с тегами latest и полным sha: ghcr.io/plutogr/travelagency-<имя>:<sha>.
  3. Деплой по SSH: на сервере git fetch origin и git reset --keep <IMAGE_TAG>, затем docker compose pull, docker compose up -d с IMAGE_TAG=<sha>, удаление старых образов. Код, compose и конфиги берутся из того же коммита, что и образы, а не из вершины main: при очереди ранов каждый ран ставит свою версию целиком. Если коммит не найден, шаг падает до изменений; если образы не скачались, код возвращается на прежний коммит, контейнеры не трогаются. Временный токен реестра удаляется с сервера и при ошибке.
  4. Проверка здоровья (до 5 минут): HTTP по IP отвечает 301 на домен; HTTPS без пароля отвечает 401; /healthz отвечает Healthy; GET /api/v1/catalog/tours через Gateway отвечает 200; сертификат домена валиден.
  5. Смоук gRPC: из сети compose на порт 8081 каждого gRPC-сервиса отправляется настоящий вызов по HTTP/2 без токена. Успех: сервер ответил по HTTP/2 со статусом 16 Unauthenticated. Это значит, что протокол, метод и проверка токена на месте.

Два деплоя одновременно не идут: следующий ждёт окончания предыдущего.

Откат

Образы каждого коммита хранятся в GHCR с тегом sha, поэтому откат не требует пересборки.

  • Основной способ: Actions → Deploy to dev → Run workflow, в поле rollback_sha указать полный sha (40 символов) нужного коммита. Тесты и сборка пропускаются, на сервер ставятся код и образы этого коммита (IMAGE_TAG = rollback_sha), проверка здоровья та же. Если коммита нет среди веток, сервер догружает его по sha (git fetch origin <sha>).
  • Следующий обычный деплой вернёт свежую версию. Если откат нужен надолго, плохой коммит отменяется через git revert и push в main.

Логи

  • Логи контейнеров: docker compose logs <сервис> на сервере. Docker ротирует их (10 МБ, 3 файла), диск они не заполнят.
  • Access-лог nginx (docker compose logs frontend): одна строка на запрос в формате ta_masked. Значение access_token в адресе и в Referer заменяется на ***, запросы к /healthz не пишутся. Пара строк 301 и 200 с одним адресом это два разных запроса: редирект (с HTTP или с IP) и сам запрос на домен.
  • Логи сборки и деплоя: вкладка Actions репозитория.

Данные, которые переживают деплой

Деплой пересоздаёт контейнеры, поэтому всё, что должно сохраниться, лежит в именованных volume Docker:

Volume Что внутри
postgres-data базы PostgreSQL всех сервисов
minio-data файлы MinIO (фото туров и превью)
gateway-dp-keys ключи DataProtection Gateway (/keys): ими шифруются cookie и antiforgery-токены

Пример: после деплоя Gateway берёт тот же ключ из gateway-dp-keys, и данные, зашифрованные до деплоя, расшифровываются. docker compose down -v удаляет volume вместе с данными.

Ключи DataProtection хранятся на диске без шифрования; защита ключей сертификатом пока не настроена.

Секреты на сервере

Значения секретов (пароли PostgreSQL и MinIO, ключ подписи JWT, сервисный токен gRPC, пароль basic auth) хранятся только на сервере и в GitHub Secrets окружения dev. В репозитории и вики их нет. Правила: Секреты и чувствительные данные.

Резервные копии

Бэкапы PostgreSQL вне сервера ещё не настроены (задача #18). См. Этап 1: что осталось.

См. также: Архитектура · Локальный запуск

Clone this wiki locally