Deployment of Educational Django Application project
Проект, демонстрирующий использование GitLab CI/CD для развертывания простого Django веб-приложения.
1. Назначение проекта
Репозиторий содержит Django-приложение и CI/CD-обвязку для: - сборки и публикации Docker-образа в GitLab Container Registry; - развёртывания через Terraform + Ansible; - базовой проверки доступности после деплоя; - попытки отката при неуспешной проверке.
Разделение ответственности: - GitLab CI: оркестрация - Terraform: создание инфраструктуры - Ansible: конфигурация и доставка - Docker Compose: запуск сервисов на App VM и Monitoring VM
2. Архитектура проекта
Используемые технологии:
Django 5.2- приложение.Gunicorn- запуск WSGI-приложения.PostgreSQL- основная БД.Redis- кэш/бэкенд дляdjango-redis.Docker- упаковка и запуск сервисов.Docker Compose- описание runtime-сервисов.GitLab CI- оркестрация CI/CD.GitLab Container Registry- хранение образов.Pytest+pytest-django- тестирование.Terraform- инфраструктура (VPC, subnet, VM, SG).Ansible- конфигурация и деплой на VM.
Cостав репозитория:
Djangoприложение (config/,django_educational_demo_application/).Dockerfileдля контейнера приложения.GitLab CIпайплайн (.gitlab-ci.yml).Terraform(директорияinfra/) для создания инфраструктуры в Yandex Cloud.Ansible(директорияansible/) для конфигурации хостов и запуска сервисов.- Шаблоны Docker Compose в Ansible-ролях для запуска сервисов на VM.
3. Архитектура CI/CD
Общая схема пайплайна:
flowchart LR
A[test: run_linters] --> C[build]
B[test: run_pytest] --> C
C --> D[publish]
C --> E[terraform_apply]
E -. manual .-> K[terraform_destroy]
D --> F[ansible_deploy]
E --> F
F --> G[health_check]
G -->|success| L[notify_telegram_success]
G -->|failure| I[notify_telegram_failure]
I --> M[rollback]
style L fill:#4ade80
style M fill:#4ade80
style I fill:#fbbf24
Примечание: rollback дополнительно использует артефакты из publish_latest и terraform_apply (needs в .gitlab-ci.yml).
Назначение этапов (stage) пайплайна:
test- параллельный запускrun_linters(pre-commit-hooks, django-upgrade, ruff, djLint) иrun_pytest(pytest)build- сборка Docker-образа приложения и push в Container Registry с тегом commit SHApublish- публикация тегированного образа в GitLab Container Registryterraform- создание инфраструктуры для деплоя (Terraform apply) и ручное удаление (Terraform destroy)deploy- запуск Ansible-плейбука для деплоя приложения, БД и мониторингаhealth_check- проверка/healthи главной страницы у задеплоенного приложенияrollback- в случае провалаhealth_checkосуществляет откат к предыдущему образу из Container Registrynotify- отправка уведомления в Telegram по итогам pipeline (success/failure)
Что происходит на каждом из этапов pipeline
| Stage | Job | Когда запускается | Что выполняется | Результат |
|---|---|---|---|---|
test |
run_linters |
При каждом push в репозиторий, параллельно с run_pytest |
Образ ghcr.io/astral-sh/uv:python3.13-bookworm; установка git; запуск uv tool run pre-commit==4.5.1 run --show-diff-on-failure --color=always --all-files |
Проверка качества кода, при ошибке пайплайн останавливается |
test |
run_pytest |
При каждом push в репозиторий, параллельно с run_linters |
Образ ghcr.io/astral-sh/uv:python3.13-bookworm; сервис postgres:15; настройка DATABASE_URL; установка build-essential, libpq-dev; uv sync --locked; uv run pytest |
Проверка корректности тестами, при падении тестов дальнейшие стадии не запускаются |
build |
build_image |
После успешного завершения run_linters и run_pytest (needs) |
Сборка через Kaniko: создание /kaniko/.docker/config.json; запуск /kaniko/executor с --context "$CI_PROJECT_DIR", --dockerfile "$CI_PROJECT_DIR/Dockerfile", --destination "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" (IMAGE_TAG), --cache=true |
Docker-образ собирается и сразу пушится в GitLab Container Registry с тегом коммита |
publish |
publish_latest |
Для main (prod) и dev/develop (dev), после build_image (needs) |
Образ gcr.io/go-containerregistry/crane:debug; crane auth login в $CI_REGISTRY; если latest-$DEPLOY_ENV существует — crane tag ... previous-$DEPLOY_ENV, затем crane tag $IMAGE_TAG latest-$DEPLOY_ENV |
Образ с тегом коммита получает latest-dev/latest-prod, предыдущий хранится как previous-dev/previous-prod |
terraform |
terraform_destroy |
Для main (prod) и dev/develop (dev), вручную (when: manual) |
Образ hashicorp/terraform:1.6; переход в infra; terraform init с backend key ${DEPLOY_ENV}/terraform.tfstate; terraform destroy -auto-approve |
Полное удаление инфраструктуры по выбранному окружению |
terraform |
terraform_apply |
Для main (prod) и dev/develop (dev), после build_image (needs), параллельно с publish_latest |
Образ hashicorp/terraform:1.6; переход в infra; terraform init с backend key ${DEPLOY_ENV}/terraform.tfstate; terraform validate; terraform plan -out=tfplan; terraform apply -auto-approve tfplan; сохранение артефактов |
Для каждого окружения создаётся собственная сеть (VPC/subnets/NAT), VM/SG и (опционально) DNS |
deploy |
ansible_deploy |
Для main (prod) и dev/develop (dev), после publish_latest и terraform_apply (needs) |
Образ python:3.12-slim; pip install ansible; переход в ansible; ansible-playbook site.yml с --extra-vars: image=$IMAGE_TAG, registry_url=$CI_REGISTRY, registry_user=$CI_REGISTRY_USER, registry_password=$CI_REGISTRY_PASSWORD |
Попытка развернуть приложение и сопутствующие сервисы на подготовленной инфраструктуре |
health_check |
health_check |
Для main (prod) и dev/develop (dev), после ansible_deploy (needs) |
Образ curlimages/curl:latest; проверки curl -f https://$APP_DOMAIN/health и curl -f https://$APP_DOMAIN/ (с fallback на IP) |
Если домен не резолвится (curl rc=6) стадия падает; при временных TLS-проблемах возможен non-blocking проход по HTTP/IP fallback |
rollback |
rollback |
Для main (prod) и dev/develop (dev), when: on_failure |
Образ python:3.12-slim; установка ansible; переход в ansible; запуск ansible-playbook site.yml с image=$PREVIOUS_IMAGE и параметрами registry_url, registry_user, registry_password |
Попытка отката на previous-dev/previous-prod (если тег существует) |
notify |
notify_telegram_success / notify_telegram_failure |
Для main (prod) и dev/develop (dev), when: on_success / when: on_failure |
Образ curlimages/curl:latest; POST в Telegram Bot API sendMessage с данными pipeline; использует TELEGRAM_BOT_TOKEN и TELEGRAM_CHAT_ID |
Отправка уведомления об успешном/неуспешном деплое в Telegram |
4. Что реально работает и что частично
Реализовано
test: параллельный запускrun_linters(pre-commit) иrun_pytest(pytest + PostgreSQL service).build: сборка и push образа через Kaniko.publish: выставление теговlatest-dev/latest-prodи поддержкаprevious-dev/previous-prod.terraform: запускterraform init/validate/plan/applyдляdev/prodс отдельным backend key (dev/terraform.tfstate,prod/terraform.tfstate); у каждого окружения собственная VPC/subnets/NAT.terraform_destroy: ручное удаление инфраструктуры черезterraform destroy.deploy: Ansible-роль рендерит.env,docker-compose.yml,Caddyfile, подтягивает образ и поднимает стек.health_check: проверяются endpoint/healthи главная страница через HTTPS по домену (с fallback на IP).rollback: сохраняется предыдущийlatest-$DEPLOY_ENVв тегеprevious-$DEPLOY_ENV, выполняется откат при падении.notify: отправляется уведомление в Telegram о результате pipeline (success/failure).
Частично реализовано / с критическими ограничениями
rollback:- хранится только один предыдущий тег на окружение (
previous-<env>), поэтому первый деплой откатывать некуда.
Отсутствует
- История стабильных тегов глубже одного шага.
5. Соответствие целям ВКР
Цель ВКР: реализовать автоматизированный, надёжный и воспроизводимый GitLab CI pipeline для деплоя Django-приложения.
Оценка соответствия по фактическому состоянию: 9/10 (Готово к защите)
Декомпозиция:
- Автоматизация CI (run_linters + run_pytest + build/publish): ✅ Реализовано
- Автоматизация IaC (terraform) и деплоя (ansible): ✅ Реализовано
- Надёжность (health-check, rollback): ✅ Реализовано
- Воспроизводимость production-деплоя: ✅ Реализовано
- Production-архитектура (VPC, security groups, private subnet): ✅ Реализовано
- Мониторинг (Grafana + Prometheus): ✅ Развёрнуто
Статус проекта: Production-Ready, Готово к защите ВКР.
6. Текущие ограничения
- Для
MANAGE_DNS=trueтребуется делегирование NS у регистратора на DNS-зону из Yandex Cloud, иначе ACME challenge для TLS не пройдет. health_checkвыполняет fallback на HTTP/IP при части HTTPS-сбоев; строгий fail включен для DNS-ошибок резолва домена (curl rc=6).- Откат ограничен одним тегом
previous-<env>; на первом деплое окружения откатывать некуда. - Полное удаление инфраструктуры не выполняется автоматически; для этого предусмотрен ручной job
terraform_destroy. - Для production обязательны секреты в CI/CD (
DJANGO_SECRET_KEY,DB_PASSWORD, registry credentials). - Чувствительные значения не должны храниться в git: используйте GitLab CI/CD Variables (masked/protected) и локальные секреты вне репозитория.
- Если секреты ранее попадали в историю git, их нужно ротировать (ключи/пароли/токены) и считать скомпрометированными.
- Telegram-уведомления требуют настройки CI/CD Variables:
TELEGRAM_BOT_TOKEN,TELEGRAM_CHAT_ID(рекомендуетсяmasked/protected, environment-scoped). - Для
dev/prodнужно настроить environment-scoped переменные в GitLab (APP_DOMAIN,DJANGO_SECRET_KEY,DB_PASSWORDи т.д.). APP_DOMAINобязателен: при отсутствии или невалидном форматеterraform_applyзавершится ошибкой.
7. Статус проекта
Production-Ready: ✅ Да, при корректно настроенных DNS-записях и выпуске TLS-сертификата.
Текущая версия: 1.2.3
Статус для ВКР: Готово к защите (9/10)
Проект полностью реализует заявленные цели:
- Автоматизированный CI/CD pipeline (test/build/publish_latest || terraform_apply/deploy/health_check/rollback/notify)
- Infrastructure as Code (Terraform)
- Configuration Management (Ansible)
- Health-check и автоматический rollback
- Production-архитектура (VPC, security groups, private subnet, NAT)
- Мониторинг (Grafana + Prometheus)
8. Тестирование
Фактически присутствуют:
- 8 test-файлов (users + tests/test_merge_production_dotenvs_in_dotenv.py + tests/test_health_endpoint.py + tests/test_home_page.py).
Особенности:
- Миграция contrib/sites использует PostgreSQL sequence (django_site_id_seq), из-за чего тесты не совместимы с SQLite.
- CI настроен на PostgreSQL service, что соответствует этим ограничениям.
9. Минимальные шаги для локального запуска (текущее состояние)
uv venv
uv sync --locked
export DATABASE_URL=postgres://<user>:<pass>@<host>:5432/<db>
uv run python manage.py migrate
uv run python manage.py runserver
Для production-настроек обязательно задать env-переменные (DJANGO_SECRET_KEY, DJANGO_ADMIN_URL, DJANGO_ALLOWED_HOSTS и др.).
10. Автоматизированное развертывание с DNS и TLS (актуально)
В проект добавлен полностью автоматизированный контур Terraform -> Ansible -> HTTPS health-check -> Telegram notify:
- Terraform создаёт/обновляет инфраструктуру и (опционально) DNS в Yandex Cloud.
- terraform_apply формирует ansible/inventory/hosts.generated.ini из terraform output.
- Ansible разворачивает приложение через Docker Compose и reverse-proxy Caddy.
- Caddy автоматически получает/обновляет TLS-сертификат Let's Encrypt для домена.
- health_check проверяет https://<APP_DOMAIN>/health.
- notify отправляет результат pipeline в Telegram.
- Полное удаление инфраструктуры выполняется вручную через job terraform_destroy.
Архитектурная модель dev/prod (separate network)
prodиdevиспользуют полностью независимые сети (отдельные VPC/subnets/NAT).- Каждое окружение имеет собственный Terraform state (
prod/terraform.tfstateиdev/terraform.tfstate) и не зависит от remote state другого окружения. - Такой подход повышает изоляцию окружений и убирает связность по порядку деплоя (
mainиdev/developможно разворачивать независимо).
11. Текущий статус проекта (Production-Ready)
Реализовано полностью ✅
- CI Pipeline: test (
run_linters+run_pytest, параллельно) → build → (publish_latest||terraform_apply) → deploy (после обоих) → health_check → rollback/notify (полностью автоматизировано) - Terraform IaC: VPC, subnets (public/private), NAT Gateway, security groups, VM (app/db/monitoring), опциональная DNS-зона
- Ansible деплой: idempotent-роли для app, db, monitoring; авто-определение docker-compose команды
- Image Tagging: commit SHA +
latest-dev/latest-prod+previous-dev/previous-prod(для rollback) - Health-check: HTTPS проверка /health и главной страницы с fallback на IP
- Rollback: автоматический откат к
previous-<env>при провале health_check - Terraform destroy: ручное удаление инфраструктуры
- S3 Backend: состояние Terraform в Yandex Object Storage с разделением state по ключам
dev/terraform.tfstateиprod/terraform.tfstate - Public IP strategy: статический публичный IP назначается только
app(по одному наdevиprod),monitoringвсегда использует динамический публичный IP - Security Groups: минимальные ingress правила (HTTP/HTTPS/SSH/Postgres)
- Сеть: у
prodиdevотдельные VPC/subnets/NAT (изоляция окружений) - Тестирование: pytest (8 test-файлов), PostgreSQL service в CI
Позиционирование для ВКР
Оценка соответствия целям ВКР: 9/10
| Требование | Статус |
|---|---|
| Контейнеризация (Docker) | ✅ Реализовано |
| CI-пайплайн (GitLab CI) | ✅ Реализовано |
| Публикация в Registry | ✅ Реализовано |
| Автотестирование (pytest) | ✅ Реализовано |
| Rollback (on_failure) | ✅ Реализовано |
| Infrastructure as Code (Terraform) | ✅ Реализовано |
| Configuration Management (Ansible) | ✅ Реализовано |
| Health-check перед переключением | ✅ Реализовано |
| Разделение сред (dev/prod) | ✅ Реализовано через branch-based rules |
| Monitoring (Grafana/Prometheus) | ✅ Развёрнуто на Monitoring VM |
| Security (Security Groups, private subnet) | ✅ Реализовано |
| Terraform state в S3 | ✅ Реализовано |
Как DevOps-учебный проект: 9/10 Как ВКР уровня ITMO (магистратура DevOps): 9/10
Архитектурная схема
┌─────────────────────────────────────────────────────────────────┐
│ Yandex Cloud │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ App VM │ │ DB VM │ │ Monitoring VM│ │
│ │ (Django + │ │ (PostgreSQL) │ │ (Grafana + │ │
│ │ Caddy) │ │ │ │ Prometheus) │ │
│ │ :443, :80 │ │ :5432 │ │ :3000, :9090│ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ │ │
│ ┌────────────┴────────────┐ │
│ │ Security Groups │ │
│ │ (app_sg, db_sg, mon_sg)│ │
│ └────────────┬────────────┘ │
│ │ │
│ ┌─────────────────┴─────────────────┐ │
│ │ VPC Network (<env>) │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │ public │ │ private │ │ │
│ │ │ subnet │ │ subnet │ │ │
│ │ │ │ │ │ │ │
│ │ │ App VM │ │ DB VM │ │ │
│ │ │ Mon VM │ │ │ │ │
│ │ └──────────┘ └──────────┘ │ │
│ └─────────────────┬─────────────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ NAT Gateway │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
▲
│ HTTPS
│
┌───────┴───────┐
│ GitLab CI │
│ Pipeline │
│ │
│ test→ │
│ build→(publish│
│ ||terraform)→ │
│ deploy→check │
└───────────────┘
Что было улучшено (2026)
- ✅ Исправлена ошибка Ansible с
docker_compose_pkg(Ubuntu 24.04 compatibility) - ✅ HTTPS-доступ по домену работает при корректных DNS-записях и доступности ACME challenge
- ✅ Полностью автоматический TLS через Caddy + Let's Encrypt
- ✅ Rollback через previous tag реализован и протестирован
- ✅ Добавлено разделение
dev/prod(branch rules + отдельные terraform state + env-specific image tags) - ✅
devиprodпереведены на отдельные VPC/subnets/NAT (изоляция окружений) - ✅ В
terraform_applyдобавлена валидацияAPP_DOMAIN(обязательная переменная, проверка формата) - ✅ В
health_checkдобавлен hard-fail приcurl rc=6(домен не резолвится)
Новые переменные Terraform
app_domain- FQDN приложения, напримерapp.example.commanage_dns-true/false, управлять ли DNS-зоной и A-записью через Terraformdns_zone- базовая зона, напримерexample.comdns_zone_resource_name- имя ресурса DNS-зоны в Yandex Cloudenvironment- имя окружения (dev/prod) для префиксов ресурсов
Важные CI/CD переменные GitLab
APP_DOMAIN- домен приложения (обязательная environment-scoped переменная)MANAGE_DNS-true/falseDNS_ZONE- зона для DNSDNS_ZONE_RESOURCE_NAME- имя зоны в YC DNSDJANGO_SECRET_KEY- секрет DjangoDJANGO_ADMIN_URL- URL админки (напримерadmin/)DB_USER,DB_PASSWORD,DB_NAME- параметры БДTLS_ACME_EMAIL- email для Let's Encrypt
Переменная DEPLOY_ENV вычисляется из ветки:
- main -> prod
- dev/develop -> dev
DNS делегирование
Если MANAGE_DNS=true, Terraform создаёт публичную DNS-зону.
Terraform отдаёт в output:
- dns_zone_id
- dns_zone_name
- dns_delegation_name_servers (по умолчанию ns1.yandexcloud.net. и ns2.yandexcloud.net.)
Без делегирования Let's Encrypt не сможет пройти HTTP-01 challenge.