Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions docs/backend-react-dev-autodeploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,27 @@ Script находит уже работающий `web` container по labels:
- `com.docker.compose.project=api-react`;
- `com.docker.compose.service=web`.

Фактические Compose services `web`, `celerys` и `redis` подтверждены по labels
React-dev сервера, включая `com.docker.compose.service=celerys`.

Из labels читаются Compose project, working directory и config files. Все пути
проверяются через `realpath`: working directory должен совпадать с
`/root/api-react-dev`, каждый config file должен находиться внутри этого каталога,
а legacy `docker-compose.yml` отклоняется.

Compose-команда собирается как Bash array без `eval`. До любых изменений
containers проверяется наличие точных сервисов `web`, `celery` и `redis`. Иное
имя celery не угадывается: deploy завершается с явной ошибкой.
containers проверяется наличие точных сервисов `web`, `celerys` и `redis`. Иное
имя Celery service не угадывается: deploy завершается с явной ошибкой.

## Порядок deploy

1. Получение deployment lock через `flock`.
2. Проверка repository, origin, git state и stale deploy.
3. Сохранение предыдущих SHA, container IDs, image IDs и image references.
4. Сборка новых `web` и `celery` images без остановки текущего backend.
4. Сборка новых `web` и `celerys` images без остановки текущего backend.
5. `python manage.py check` во временном container нового `web` image.
6. `python manage.py migrate --noinput` с существующим React-dev `.env`.
7. Пересоздание только `web` и `celery` через `up -d --no-deps --force-recreate`.
7. Пересоздание только `web` и `celerys` через `up -d --no-deps --force-recreate`.
8. Ожидание running state с ограниченным timeout.
9. Публичный HTTPS health-check.

Expand All @@ -109,7 +112,7 @@ https://api-react-dev.procollab.ru/programs/?limit=1
- непустой body;
- отсутствие HTML/SPA fallback;
- корректный JSON;
- running state `web` и `celery`.
- running state `web` и `celerys`.

Используются ограниченные connect/total timeout и восемь попыток с паузой.
Полный API response в лог не выводится.
Expand All @@ -125,7 +128,7 @@ script:

1. возвращает предыдущий code SHA;
2. возвращает предыдущие image IDs на сохраненные image references;
3. пересоздает только `web` и `celery`;
3. пересоздает только `web` и `celerys`;
4. повторяет ограниченный React-dev health-check;
5. сообщает результат rollback;
6. завершает deploy с ошибкой даже при успешном rollback.
Expand All @@ -152,7 +155,7 @@ GitHub Actions использует одну общую concurrency group с
- repository имеет tracked изменения или неверный origin;
- workflow устарел относительно текущего `origin/master`;
- Compose labels отсутствуют или указывают вне React-dev;
- сервисы называются не `web`, `celery`, `redis`;
- сервисы называются не `web`, `celerys`, `redis`;
- build, Django check или migration завершились ошибкой;
- containers не перешли в running state;
- HTTPS endpoint вернул redirect, HTML, не-JSON или статус не `200`;
Expand Down
21 changes: 11 additions & 10 deletions scripts/deploy_react_dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ readonly DEPLOY_DIR="/root/api-react-dev"
readonly FORBIDDEN_DIR="/root/api"
readonly EXPECTED_REPOSITORY="PROCOLLAB-github/api"
readonly EXPECTED_COMPOSE_PROJECT="api-react"
readonly CELERY_SERVICE="celerys"
readonly HEALTH_URL="https://api-react-dev.procollab.ru/programs/?limit=1"
readonly LOCK_FILE="${DEPLOY_DIR}/.react-dev-deploy.lock"
readonly LOCK_TIMEOUT_SECONDS=10
Expand Down Expand Up @@ -193,12 +194,12 @@ rollback_deployment() {
--detach \
--no-deps \
--force-recreate \
web celery ||
web "$CELERY_SERVICE" ||
rollback_failed=1

if ((rollback_failed == 0)); then
wait_for_service_running web || rollback_failed=1
wait_for_service_running celery || rollback_failed=1
wait_for_service_running "$CELERY_SERVICE" || rollback_failed=1
fi

if ((rollback_failed == 0)); then
Expand Down Expand Up @@ -397,7 +398,7 @@ validate_compose_services() {

services_output="$("${COMPOSE_CMD[@]}" config --services)"
mapfile -t services <<< "$services_output"
for expected_service in web celery redis; do
for expected_service in web "$CELERY_SERVICE" redis; do
found=false
for service in "${services[@]}"; do
if [[ "$service" == "$expected_service" ]]; then
Expand All @@ -419,7 +420,7 @@ mapfile -t current_celery_containers < <(
docker ps \
--quiet \
--filter "label=com.docker.compose.project=${COMPOSE_PROJECT}" \
--filter "label=com.docker.compose.service=celery"
--filter "label=com.docker.compose.service=${CELERY_SERVICE}"
)
if ((${#current_celery_containers[@]} != 1)); then
fail "Не найден ровно один running celery container проекта api-react."
Expand Down Expand Up @@ -468,8 +469,8 @@ fi
validate_compose_services

BUILD_ATTEMPTED=true
log "Сборка новых web и celery images без остановки текущих containers."
"${COMPOSE_CMD[@]}" build web celery
log "Сборка новых web и ${CELERY_SERVICE} images без остановки текущих containers."
"${COMPOSE_CMD[@]}" build web "$CELERY_SERVICE"

new_web_image_id="$(
docker image inspect --format '{{.Id}}' "$PREVIOUS_WEB_IMAGE_REF"
Expand All @@ -494,23 +495,23 @@ log "Применение миграций React-dev на новом web image."
MIGRATION_COMPLETED=true

CONTAINERS_MAY_HAVE_CHANGED=true
log "Пересоздание только React-dev web и celery."
log "Пересоздание только React-dev web и ${CELERY_SERVICE}."
"${COMPOSE_CMD[@]}" up \
--detach \
--no-deps \
--force-recreate \
web celery
web "$CELERY_SERVICE"

wait_for_service_running web
wait_for_service_running celery
wait_for_service_running "$CELERY_SERVICE"
if ! health_check "Deploy"; then
fail "Публичный React-dev HTTPS health-check не пройден."
fi

web_container_id=""
celery_container_id=""
compose_service_container_id web web_container_id
compose_service_container_id celery celery_container_id
compose_service_container_id "$CELERY_SERVICE" celery_container_id
web_status="$(docker inspect --format '{{.State.Status}}' "$web_container_id")"
celery_status="$(docker inspect --format '{{.State.Status}}' "$celery_container_id")"

Expand Down
Loading