В предыдущих частях мы развернули веб-приложение Django на ECS и подключили к нему PostgreSQL. Но теперь нам нужно развернуть изменения в приложении вручную. В этой части мы собираемся автоматизировать этот процесс с помощью следующих шагов:
- Создайте группу GitLab и проекты для бэкенда и инфраструктурных репозиториев.
- Добавьте этап CI/CD
test
для запуска тестов. - Добавьте этап
build
CI/CD для создания образа docker и отправки его на ECR. - Добавьте этап
deploy
CI/CD для обновления приложения на AWS.
Создание проектов
Начнем с создания группы GitLab и проектов. Перейдите на сайт gitlab.com и зарегистрируйте учетную запись.
Затем создайте группу GitLab. Группа — это как папка для проектов. Вы можете настроить для них общие параметры, такие как политика доступа и переменные CI.
Создайте в этой группе проекты для Django и Terraform. Обязательно удалите опцию «Initialize repository with a README», чтобы создать чистый репозиторий.
Также добавьте свой ssh-ключ в раздел SSH Keys. Это позволит вам получить доступ к вашим проектам через Git.
Теперь давайте запустим оба наших репозитория на GitLab.
# Push backend
cd ../django-aws-backend
git remote add origin git@gitlab.com:django-aws/django-aws-backend
git push --set-upstream origin main
# Push infrastructure
cd ../django-aws-infrastructure
git remote add origin git@gitlab.com:django-aws/django-aws-infrastructure
git push --set-upstream origin main
Проверьте свои проекты GitLab в браузере и убедитесь, что push прошел успешно.
Этап: Тестирование
Теперь давайте добавим проверку модульных тестов в проект Django. Юнит-тесты Django используют модуль unittest стандартной библиотеки Python. Более подробную информацию о тестировании приложения Django можно найти здесь.
Перейдите в проект Django, активируйте venv
и запустите Docker-контейнер PostgreSQL:
$ cd ../django-aws-backend
$ . ./venv/bin/activate
$ docker-compose up -d
Давайте создадим django_aws/tests.py
и добавим простой тест для проверки соединения с БД:
from django.test import TestCase
from django.db import connection
class DbConnectionTestCase(TestCase):
def test_db_connection(self):
self.assertTrue(connection.is_usable())
Запустите python manage.py test
локально:
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.010s
OK
Destroying test database for alias 'default'...
Теперь давайте добавим эту проверку на стороне GitLab. Посмотрите здесь, если вы не знаете, что такое GitLab CI. В общем, GitLab будет запускать код, указанный в .gitlab-ci.yml
при каждом push в репозитории.
Создайте файл .gitlab-ci.yml
со следующим содержимым:
image: python:3.10
stages:
- test
variables:
POSTGRES_PASSWORD: password
POSTGRES_DB: django_aws
DATABASE_URL: postgres://postgres:password@postgres:5432/django_aws
test:
services:
- postgres:14.2
cache:
key:
files:
- requirements.txt
prefix: ${CI_JOB_NAME}
paths:
- venv
- .cache/pip
stage: test
script:
- python -m venv venv
- . venv/bin/activate
- pip install --upgrade pip
- pip install -r requirements.txt
- python manage.py test
Пояснение:
Теперь внесите изменения и посмотрите на вкладку CI/CD вашего проекта GitLab Django.
$ git add .
$ git commit -m "add gitlab-ci; add test"
$ git push
Этап: Сборка
Тесты пройдены, поэтому мы переходим к этапу build
. На этом этапе нам нужно связать нашу учетную запись GitLab с учетной записью AWS, чтобы предоставить GitLab доступ к репозиторию ECR. Для этого мы создадим отдельного пользователя AWS gitlab
с ограниченными правами. Давайте перейдем в консоль AWS IAM и создадим нового пользователя.
Инструкции по созданию пользователя AWS вы можете найти здесь
Добавьте к этому пользователю политику AmazonEC2ContainerRegistryPowerUser
, чтобы включить разрешение на чтение и запись в любой ECR-репозиторий на этой учетной записи. Перейдите к последнему шагу создания пользователя и сохраните ACCESS_KEY_ID
и SECRET_ACCESS_KEY
.
Теперь перейдите в настройки группы GitLab и добавьте переменные AWS_ACCOUNT_ID
, AWS_SECRET_ACCESS_KEY
, AWS_ACCESS_KEY_ID
, и AWS_DEFAULT_REGION
. GitLab runner будет использовать эти учетные данные для вызовов AWS CLI.
Обязательно маскируйте чувствительные переменные AWS_ACCOUNT_ID
, AWS_SECRET_ACCESS_KEY
и AWS_ACCESS_KEY_ID
, чтобы скрыть их значения в логах GitLab. Посмотрите страницу документации по развертыванию AWS для получения дополнительной информации.
Затем добавьте в блок сборки .gitlab-ci.yml
:
stages:
- test
- build
variables:
...
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
AWS_REGISTRY_URL: "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${CI_PROJECT_NAME}:latest"
test:
...
build:
stage: build
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
services:
- docker:20.10-dind
before_script:
- aws ecr get-login-password | docker login --username AWS --password-stdin $AWS_REGISTRY_URL
script:
- docker pull $AWS_REGISTRY_URL || true
- docker build --cache-from $AWS_REGISTRY_URL -t $AWS_REGISTRY_URL .
- docker push $AWS_REGISTRY_URL
only:
- main
Пояснение:
Зафиксируйте свои изменения и проверьте конвейер CI/CD:
$ git add .
$ git commit -m "add build stage"
$ git push
Этап: Развертывание
Этап сборки пройден, теперь мы развернем контейнер из ECR в ECS.
Но давайте начнем с запуска миграций. Мы хотим запустить миграции как отдельную задачу ECS, чтобы избежать побочных эффектов для веб-приложения. Перейдите в проект infrastructure
и внесите следующие изменения в ecs.tf
и примените изменения:
# Production cluster
...
locals {
container_vars = {
region = var.region
image = aws_ecr_repository.backend.repository_url
log_group = aws_cloudwatch_log_group.prod_backend.name
rds_db_name = var.prod_rds_db_name
rds_username = var.prod_rds_username
rds_password = var.prod_rds_password
rds_hostname = aws_db_instance.prod.address
}
}
# Backend web task definition and service
resource "aws_ecs_task_definition" "prod_backend_web" {
...
container_definitions = templatefile(
"templates/backend_container.json.tpl",
merge(
local.container_vars,
{
name = "prod-backend-web"
command = ["gunicorn", "-w", "3", "-b", ":8000", "django_aws.wsgi:application"]
log_stream = aws_cloudwatch_log_stream.prod_backend_web.name
},
)
)
...
}
...
# Cloudwatch Logs
...
resource "aws_cloudwatch_log_stream" "prod_backend_migrations" {
name = "prod-backend-migrations"
log_group_name = aws_cloudwatch_log_group.prod_backend.name
}
# Migrations
resource "aws_ecs_task_definition" "prod_backend_migration" {
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = 256
memory = 512
family = "backend-migration"
container_definitions = templatefile(
"templates/backend_container.json.tpl",
merge(
local.container_vars,
{
name = "prod-backend-migration"
command = ["python", "manage.py", "migrate"]
log_stream = aws_cloudwatch_log_stream.prod_backend_migrations.name
},
)
)
depends_on = [aws_db_instance.prod]
execution_role_arn = aws_iam_role.ecs_task_execution.arn
task_role_arn = aws_iam_role.prod_backend_task.arn
}
Здесь мы перенесли те же переменные для контейнеров migration
и web
в container_vars
. Затем мы создали отдельную постановку задачи и поток журнала для контейнера миграции. Теперь мы можем запустить задачу с указанным определением задачи для применения миграций для каждого выпуска.
Теперь давайте создадим сценарий развертывания. Вернитесь в проект Django и создайте файл scripts/deploy.sh
.
cd ../django-aws-backend
mkdir scripts
touch scripts/deploy.sh
chmod 777 scripts/deploy.sh
со следующим содержанием:
#!/bin/bash
set -e
# Collect ECS_GROUP_ID and PRIVATE_SUBNET_ID for running migrations
echo "Collecting data..."
ECS_GROUP_ID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=prod-ecs-backend --query "SecurityGroups[*][GroupId]" --output text)
PRIVATE_SUBNET_ID=$(aws ec2 describe-subnets --filters "Name=tag:Name,Values=prod-private-1" --query "Subnets[*][SubnetId]" --output text)
echo "Running migration task..."
# Construct NETWORK_CONFIGURATON to run migtaion task
NETWORK_CONFIGURATON="{"awsvpcConfiguration": {"subnets": ["${PRIVATE_SUBNET_ID}"], "securityGroups": ["${ECS_GROUP_ID}"],"assignPublicIp": "DISABLED"}}"
# Start migration task
MIGRATION_TASK_ARN=$(aws ecs run-task --cluster prod --task-definition backend-migration --count 1 --launch-type FARGATE --network-configuration "${NETWORK_CONFIGURATON}" --query 'tasks[*][taskArn]' --output text)
echo "Task ${MIGRATION_TASK_ARN} running..."
# Wait migration task to complete
aws ecs wait tasks-stopped --cluster prod --tasks "${MIGRATION_TASK_ARN}"
echo "Updating web..."
# Updating web service
aws ecs update-service --cluster prod --service prod-backend-web --force-new-deployment --query "service.serviceName" --output json
echo "Done!"
Вы можете проверить в документации команды run-task, wait tasks-stopped и update-service.
Запустите этот скрипт локально, чтобы проверить его:
$ ./scripts/deploy.sh
Collecting data...
Running migration task...
Task arn:aws:ecs:us-east-2:947134793474:task/prod/dcc06d7ec1ac4bb69bba445565eddf8b running...
Updating web...
"prod-backend-web"
Done!
Мы успешно запустили этот сценарий под пользователем admin
. GitLab CI/CD будет использовать пользователя gitlab
, поэтому нам нужно предоставить все необходимые разрешения.
Давайте создадим новую политику gitlab-deploy-ecs
и добавим ее к пользователю gitlab
. Перейдите в консоль IAM, выберите вкладку «Пользователи» и нажмите на пользователя gitlab
.
Далее нажмите на «Добавить встроенную политику» и добавьте определение политики JSON. Вам необходимо использовать ваш AWS_ACCOUNT_ID
вместо номера 947134793474
.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups",
"ecs:UpdateService",
"ecs:DescribeTasks"
],
"Resource": ["*"]
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": [
"arn:aws:iam::947134793474:role/ecs-task-execution",
"arn:aws:iam::947134793474:role/prod-backend-task"
]
},
{
"Effect": "Allow",
"Action": "ecs:RunTask",
"Resource": "arn:aws:ecs:us-east-2:947134793474:task-definition/backend-migration*"
}
]
}
Объяснение политик:
Нажмите «Review Policy», назовите политику gitlab-ecs-deploy
и нажмите «Create Policy». Теперь пользователь gitlab
сможет выполнить сценарий deploy.sh
.
Добавьте этап развертывания в .gitlab-ci.yml
.
...
stages:
- test
- build
- deploy
...
deploy:
stage: deploy
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
script:
- ./scripts/deploy.sh
only:
- main
На этапе развертывания мы просто запускаем скрипт deploy.sh
. Мы используем образ aws-base
, чтобы иметь доступ к командам AWS CLI.
Наконец, давайте добавим некоторые изменения в Django, чтобы убедиться, что наше приложение будет обновляться автоматически. Давайте изменим текст заголовка Django Admin. Добавьте эту строку в django_aws/urls.py
:
admin.site.site_header = "Django AWS Admin Panel"
Теперь зафиксируйте изменения и посмотрите, как работает ваш конвейер.
$ git add .
$ git commit -m "add deploy stage"
$ git push
Проверьте свою страницу администратора prod-57218461274.us-east-2.elb.amazonaws.com/admin
и увидите новый заголовок. Обновление службы ECS может занять некоторое время.
Также не забудьте выложить код инфраструктуры на GitLab.
Поздравляем! Мы успешно настроили CI/CD с помощью GitLab для нашего веб-приложения Django. Теперь мы можем зафиксировать наш код в ветке main
, и GitLab CI/CD будет автоматически тестировать, собирать и развертывать его на AWS.
Но домен prod-57218461274.us-east-2.elb.amazonaws.com
выглядит не очень удобным для пользователя 🙂 Также нам нужно защитить соединение между пользователем и приложением Django с помощью SSL сертификата. В следующей части мы подключим домен Namecheap к AWS и настроим для него SSL-сертификат.
Вы можете найти исходный код проектов бэкенда и инфраструктуры здесь и здесь.
Если вам нужен технический консалтинг по вашему проекту, загляните на наш сайт или свяжитесь со мной напрямую на LinkedIn.