Serverless приложение с реализацией CI/CD на базе AWS и Bitbucket Pipelines

    В статье рассказывается о развертывании Django приложения в облаке AWS с помощью Bitbucket Pipelines. Тем, кому интересна эта тема, добро пожаловать под кат.

    image

    Вперед, на мины!

    Создание каркаса приложения


    Проект представляет собой типичное Django приложение. Единственное отличие — настройки приложения будут подтягиваться через переменные environment. Репозиторий проекта находится на битбакете. Чтобы создать аналогичное, устанавливаем requirements из списка:

    zappa==0.45.1
    django-rest-swagger==2.1.2
    djangorestframework==3.7.3
    django-filter==1.1.0
    Django==2.0
    psycopg2==2.7.3.2
    django-storages==1.6.5
    

    Как видим, типовой набор зависимостей для построения REST API и подключения PostgreSQL. Далее проходим по шагам создания типичного Django приложения. Добавляем в настройки проекта настройки подключения к базе и размещения статики на S3.

    STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
    DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
    
    #####################################
    # ENV VARIABLES
    #####################################
    RDS_DB_NAME = os.environ.get('RDS_DB_NAME')
    RDS_USERNAME = os.environ.get('RDS_USERNAME')
    RDS_PASSWORD = os.environ.get('RDS_PASSWORD')
    RDS_HOSTNAME = os.environ.get('RDS_HOSTNAME')
    RDS_PORT = os.environ.get('RDS_PORT')
    S3_BUCKET = os.environ.get('S3_BUCKET')
    #####################################
    
    
    #####################################
    # THIS SETTINGS CAN'T BE OVERRIDED  #
    #####################################
    # Database
    # https://docs.djangoproject.com/en/2.0/ref/settings/#databases
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql_psycopg2',
            'NAME': RDS_DB_NAME,
            'USER': RDS_USERNAME,
            'PASSWORD': RDS_PASSWORD,
            'HOST': RDS_HOSTNAME,
            'PORT': RDS_PORT,
        }
    }
    AWS_STORAGE_BUCKET_NAME = S3_BUCKET
    AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
    
    STATIC_ROOT = 'static'
    STATIC_URL = "https://%s/" % AWS_S3_CUSTOM_DOMAIN
    

    Zappa — фреймворк, упрощающий развертывание wsgi-приложения на базе API Gateway и Lambda. Под капотом имеет генератор Cloudformation темплейта и адаптер event'а Lambda в wsgi-запрос, что позволяет использовать классическую схему работы приложения. Последним штрихом добавляем зависимости для тестов

    pytest==3.3.1
    pylint==1.8.1
    tox==2.9.1
    pytest-django==3.1.2
    docstringtest==0.3.0
    

    и добавляем файлы конфигурации для tox, pylint и pytest

    Конфигурация Zappa


    Представляет собой JSON или YAML файл с набором переменных. В моем варианте хранится на скрытом в настройках Pipelines S3 бакете и копируется каждый раз при создании артефакта.
    Приведу пример:

    {
      "dev1": {
        "environment_variables": {
          "RDS_DB_NAME": "dbname",
          "RDS_USERNAME": "user",
          "RDS_PASSWORD": "pass",
          "RDS_HOSTNAME": "host",
          "RDS_PORT": "5432",
          "S3_BUCKET": "s3-bucket"
        },
        "aws_region": "us-east-1",
        "django_settings": "sample.settings",
        "project_name": "serverless",
        "runtime": "python3.6",
        "s3_bucket": "app-bucket",
        "domain":"example.com",
        "certificate_arn":"<ACM certificate arn>"
      }
    }
    

    Все, что относится к настройкам проекта, прописано в environment_variables. За подробностями всего остального обратитесь к документации zappa

    Конфигурация Bitbucket Pipelines


    Тех, кто не знает, что это такое, отсылаю к другим моим статьям. Здесь же постараюсь подробно рассмотреть конфигурацию пайплайна. Для CI/CD я использую следующий shell-скрипт:

    #!/bin/bash
    
    
    setup () {
        echo  ------- SETUP -------
        apt-get update # required to install zip
        apt-get install -y zip
        pip install virtualenv
        virtualenv --python=python3 env
        source env/bin/activate
        pip install -r requirements.txt
        return $?
    }
    
    
    tests() {
        echo ------- TESTS -------
        pip install -r requirements-test.txt # for tests
        tox
        return $?
    }
    
    deploy() {
        echo ------- DEPLOY -------
        echo $1
        pip install awscli
        aws s3 cp s3://$CMDB/zappa_settings.json .
        zappa update $1 || zappa deploy $1
        zappa certify $1 --yes
        zappa manage $1 "migrate --noinput"
        zappa manage $1 "collectstatic --noinput"
        return $?
    }
    
    setup && test && deploy $1
    

    Скрипт работает в типовом для bitbucket pipeline контейнере (image: python:3.6.1) на Debian. Pipeline позволяет использовать любой контейнер с DockerHub, но адаптация скрипта останется на Вашей совести.

    Собственно конфигурация пайплайна выглядит следующим образом:

    image: python:3.6.1
    
    pipelines:
      tags:
        release-*:
          - step:
              caches:
                - pip
              script:
                - ./ci.sh prod1
      branches:
        master:
          - step:
              caches:
                - pip
              script:
                - ./ci.sh dev1
    

    image указывает на контейнер, деплой продакшена производится по тегам, мастер бранч деплоится в dev1 окружение. Желающие могут самостоятельно добавить прогон тестов на остальные бранчи. Все просто.

    После добавления файлов конфигурации осталось только настроить сам битбакет. Включаем pipelines и прописываем переменные окружения:

    image

    Все, теперь можно пушить в мастер, срезать теги и… в общем, отдайте репозиторий разработчикам, они знают, что с ним делать.

    За кадром осталась тема шифрования секретов и использования Cloudfront, а также настройки RDS, ACM, IAM, Route53, но это уже выходит за рамки статьи. Желающие могут все это найти в документации AWS.

    Еще раз ссылка на репозиторий

    Комментарии 9

      0
      В «Конфигурация Bitbucket Pipelines» секции вы устанавливаете zip с помощью apt-get. Может все же pip?
        0
        не может
          0
          Да тогда было бы python-pip.
        0
        Чтобы создать аналогичное, устанавливаем requirements из списка:

        А как вы работаете со второстепенными зависимостями?
        Сталивались ли регрессией старых багов в них?
        Например в модулях boto3,botocore (zappa), urllib3 (requests),… еще на стейдже устанавливался botocore==1.9.3, а через час на прод уехал botocore==1.9.4 c каким-то старым багом ?

          0
          Лично я придерживаюсь принципа все явно использующиеся зависимости прописывать явно. Ну и по сути как вы себе это представляете? Мейнтейнер пакета взял и поменял в уже опубликованной версии зависимости?
            0

            Вот смотрим zappa-0.45.1 requirements.txt:


            botocore>=1.7.19
            boto3>=1.4.7

            п.с. zappa меня не интересует. Важен сам принцип, когда второстепенные зависимости могу повлиять на поведение продукта. Это может случиться, когда основные зависимости указаны не явно.

              0
              На самом деле тут есть резон, поскольку в AWS SDK максимум прорастают новые вызовы API, код там меняется крайне редко. Поэтому данный конкретный случай вряд ли на что-то повлияет. Во всяком случае мне не нравится подход с фиксацией всего env через использование pip freeze, ибо потом уже не понятно, что с какой целью было добавлено и что от чего зависит. Ну и опять же, каждый деплой прогоняются тесты — если что-то поломается, сами себе враги. Вряд ли меня при этом будет волновать вероятность попасть в тот самый момент, когда обновится пакет.
              P.S. Вы сами себе противоречите — основные зависимости как раз таки явно указаны
                +1
                Во всяком случае мне не нравится подход с фиксацией всего env через использование pip freeze,

                Есть такой подход:
                pip-compile requirements.in --output-file requirements.txt
                И проект уезжает именно сгенерированный файл, на базе основных зависимостей.
                Он так же хранится в репозитории, и позволяет четко видеть, как обновляются второстепенные зависимости.


                Почему я задал вопрос.
                Иногда есть кейсы, когда необходимо зафиксировать версию одного из этих второстепенных модулей, но не хочется вносить его в основные зависимости. Как решить этот вопрос, я не знаю.

                  0
                  Да, возможно этот подход имеет право на жизнь, я проверю на практике. С другой стороны, если версию пакета надо фиксировать, то его надо явно указывать, других вариантов нет. С другой стороны, вот как раз таки он не даст мне указать версию boto3 ==1.4.4 при такой версии zappa. По большому счету приведенный в статье подход не годится для больших проектов, там уже проще формировать артифакт для лямбды и уже работать с ним. Или с механизмом алиасов и версий. Однако acceptance criteria в любом случае это задача не повторяемости venv

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое