Repository navigation
Doc Infrastructure
Dev-среда сайта работает на одном сервере в Docker Compose. Код попадает туда автоматически: каждый push в main прогоняет тесты, собирает образы и обновляет контейнеры.
- Изменение смержено в
main. - GitHub Actions запускает workflow «Deploy to dev»: тесты .NET, затем сборка 7 образов с тегом, равным sha коммита.
- Workflow по SSH заходит на сервер, обновляет репозиторий, скачивает образы этого sha и перезапускает контейнеры.
- Проверка здоровья ждёт, пока сайт ответит, до 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 и сертификат работают на новом адресе. Если где-то в коде остался старый адрес, это ошибка.
- Купить домен и добавить A-запись
dev.<домен>(и<домен>для продакшна) на IP сервера. - Поменять
SITE_DOMAINв.envна сервере. - Запустить
deploy/scripts/issue-cert.sh: он пересоздаёт frontend и gateway с текущими образами (без сборки), выпускает сертификат черезcertbot certonly --webrootи ставит хук перезагрузки nginx после продления. - При желании оставить старый домен с редиректом на новый, потом удалить его A-запись.
Ожидаемое время: около часа, без релиза кода.
nginx-entrypoint.sh при старте выбирает режим:
-
redirect: сертификат для
SITE_DOMAINуже выпущен. Заход по IP получает 301 на домен. - fallback: сертификата ещё нет. Сайт работает по IP с самоподписанным сертификатом, чтобы dev не пропал, пока домен не делегирован.
Сейчас dev работает в режиме redirect. HSTS не включён специально, пока домен dev может меняться.
| 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 с обновлениями зависимостей |
-
Тесты:
dotnet restore,dotnet build,dotnet testвсего решения. Упали, дальше не идёт. -
Образы: 7 образов (gateway, identity, catalog, booking, chat, media, frontend) публикуются в GHCR с тегами
latestи полным sha:ghcr.io/plutogr/travelagency-<имя>:<sha>. -
Деплой по SSH: на сервере
git fetch originиgit reset --keep <IMAGE_TAG>, затемdocker compose pull,docker compose up -dсIMAGE_TAG=<sha>, удаление старых образов. Код, compose и конфиги берутся из того же коммита, что и образы, а не из вершиныmain: при очереди ранов каждый ран ставит свою версию целиком. Если коммит не найден, шаг падает до изменений; если образы не скачались, код возвращается на прежний коммит, контейнеры не трогаются. Временный токен реестра удаляется с сервера и при ошибке. -
Проверка здоровья (до 5 минут): HTTP по IP отвечает 301 на домен; HTTPS без пароля отвечает 401;
/healthzотвечаетHealthy;GET /api/v1/catalog/toursчерез Gateway отвечает 200; сертификат домена валиден. -
Смоук 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: что осталось.
См. также: Архитектура · Локальный запуск
Система
API
Сервисы
Как реализовано
Справочное
- Видение продукта
- Дорожная карта
- Этап 1: что осталось
- Этап 2: расширение
- Этап 3: масштабирование
- Концепция сайта и интерфейса
- Шаблон ТЗ