Развертывание приложения Django на AWS с помощью Terraform. GitLab CI/CD

В предыдущих частях мы развернули веб-приложение 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
Enter fullscreen mode Выйти из полноэкранного режима

Пояснение:

Зафиксируйте свои изменения и проверьте конвейер 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.

Оцените статью
devanswers.ru
Добавить комментарий