AWS ElasticBeanstalk — PaaS на базе инфраструктуры AWS. На мой взгляд значительное преимущество этого сервиса — возможность прямого доступа к элементам инфраструктуры (балансировщики, инстансы, очереди и т.п.) В этой статье решил собрать некоторые трюки, позволяющие решить типичные проблемы при использовании ElasticBeanstalk. Буду дополнять по мере нахождения новых. Вопросы и предложения в комментариях приветствуются.



Варианты добавления конфигурации приложения


На мой взгляд очевидный недостаток платформы — невнятный механизм хранения конфигурации. Поэтому я применяю следующие методы добавления конфигурации.

Самый очевидный и нативный для ElasticBeanstalk — установка через переменные окружения. Внутри инстанса эти переменные окружения доступны не как обычно, а исключительно в environment приложения. Для установки этих параметров наиболее удобно использовать команду eb setenv из пакета awsebcli, который используется для развертывания приложения (подходит для небольших проектов), либо API AWS.

eb setenv RDS_PORT=5432    PYTHONPATH=/opt/python/current/app/myapp:$PYTHONPATH    RDS_PASSWORD=12345    DJANGO_SETTINGS_MODULE=myapp.settings    RDS_USERNAME=dbuser    RDS_DB_NAME=appdb    RDS_HOSTNAME=dbcluster.us-east-1.rds.amazonaws.com

Второй вариант — когда конфиг инжектится внутрь созданной версии приложения. Для этого надо пояснить, как происходить процесс развертывания. Вручную или скриптом создается zip архив, содержащий код приложения, выкладывается на специальный S3 bucket, уникальный для каждого региона ( вида elasticbeanstalk-<region_name>-<my_account_id> — не пытайтесь использовать другой, работать не будет — проверено). Можно создавать данный пакет вручную, либо редактировать программно. Я предпочитаю использовать альтернативный вариант развертывания, когда вместо awsebcli используется собственный код создания пакета версии.



Третий вариант — подгрузка конфигурации удаленно на этапе развертывания из внешней базы конфигурации. ИМХО наиболее правильный подход, однако выходит за рамки данной статьи. Я использую схему с хранением конфигов на S3 и проксированием запросов к S3 через API Gateway — это позволяет наиболее гибко управлять конфигами. S3 также поддерживает версионность.

Включаем задания в crontab


ElasticBeanstalk поддерживает создание заданий для планировщика, используя файл cron.yaml. Однако этот конфиг работает только для worker environment — конфигурации, используемой для обработки очереди задач / периодических задач. Для решения этой задачи в окружении WebServer добавляем в каталоге проекта .ebextensions файл со следующим содержимым:

files:
  "/etc/cron.d/cron_job":
    mode: "000644"
    owner: root
    group: root
    content: |
       #Add comands below
       15 10 * * * root curl  www.google.com >/dev/null 2>&1<code>

  "/usr/local/bin/cron_job.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/bin/bash
      /usr/local/bin/test_cron.sh || exit
      echo "Cron running at " `date` >> /tmp/cron_job.log
      # Now do tasks that should only run on 1 instance ...

  "/usr/local/bin/test_cron.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/bin/bash

      METADATA=/opt/aws/bin/ec2-metadata
      INSTANCE_ID=`$METADATA -i | awk '{print $2}'`
      REGION=`$METADATA -z | awk '{print substr($2, 0, length($2)-1)}'`

      # Find our Auto Scaling Group name.
      ASG=`aws ec2 describe-tags --filters "Name=resource-id,Values=$INSTANCE_ID"         --region $REGION --output text | awk '/aws:autoscaling:groupName/ {print $5}'`

      # Find the first instance in the Group
      FIRST=`aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names $ASG         --region $REGION --output text | awk '/InService$/ {print $4}' | sort | head -1`

      # Test if they're the same.
      [ "$FIRST" = "$INSTANCE_ID" ]

commands:
  rm_old_cron:
    command: "rm *.bak"
    cwd: "/etc/cron.d"
    ignoreErrors: true

Автоматическое применение миграций Django и сборка статики при развертывании


Добавляем в файл конфига в .ebextensions:

container_commands:
  01_migrate:
    command: "python manage.py migrate --noinput"
    leader_only: true
  02_collectstatic:
      command: "./manage.py collectstatic --noinput"

Аналогичным образом применяются миграции alembic; для того, чтобы избежать применения миграций на каждом инстансе autoscaling группы, указывается параметр leader_only

Использование hooks при развертывании приложений


Создавая скрипты в каталоге /opt/elasticbeanstalk/hooks/, можно добавлять различные управляющие скрипты, в частности, модифицировать процесс развертывания приложения. Скрипты, исполняющиеся перед развертыванием, лежат в каталоге /opt/elasticbeanstalk/hooks/appdeploy/pre/*, во время — в /opt/elasticbeanstalk/hooks/appdeploy/enact/*, и после — в /opt/elasticbeanstalk/hooks/appdeploy/post/*. Скрипты исполняются в алфавитном порядке, благодаря этому можно выстроить правильную последовательность развертывания приложений.

Добавление демона Celery в уже имеющийся конфиг supervisor


files:
  "/opt/elasticbeanstalk/hooks/appdeploy/post/run_supervised_celeryd.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/usr/bin/env bash

      # Get django environment variables
      celeryenv=`cat /opt/python/current/env | tr '\n' ',' | sed 's/export //g' | sed 's/$PATH/%(ENV_PATH)s/g' | sed 's/$PYTHONPATH//g' | sed 's/$LD_LIBRARY_PATH//g'`
      celeryenv=${celeryenv%?}

      # Create celery configuraiton script
      celeryconf="[program:celeryd]
      ; Set full path to celery program if using virtualenv
      command=/opt/python/run/venv/bin/celery worker -A yourapp -B --loglevel=INFO -s /tmp/celerybeat-schedule

      directory=/opt/python/current/app
      user=nobody
      numprocs=1
      stdout_logfile=/var/log/celery-worker.log
      stderr_logfile=/var/log/celery-worker.log
      autostart=true
      autorestart=true
      startsecs=10

      ; Need to wait for currently executing tasks to finish at shutdown.
      ; Increase this if you have very long running tasks.
      stopwaitsecs = 600

      ; When resorting to send SIGKILL to the program to terminate it
      ; send SIGKILL to its whole process group instead,
      ; taking care of its children as well.
      killasgroup=true

      ; if rabbitmq is supervised, set its priority higher
      ; so it starts first
      priority=998

      environment=$celeryenv"

      # Create the celery supervisord conf script
      echo "$celeryconf" | tee /opt/python/etc/celery.conf

      # Add configuration script to supervisord conf (if not there already)
      if ! grep -Fxq "[include]" /opt/python/etc/supervisord.conf
          then
          echo "[include]" | tee -a /opt/python/etc/supervisord.conf
          echo "files: celery.conf" | tee -a /opt/python/etc/supervisord.conf
      fi

      # Reread the supervisord config
      supervisorctl -c /opt/python/etc/supervisord.conf reread

      # Update supervisord in cache without restarting all services
      supervisorctl -c /opt/python/etc/supervisord.conf update

      # Start/Restart celeryd through supervisord
      supervisorctl -c /opt/python/etc/supervisord.conf restart celeryd

Кстати, я использовал экспериментальную возможность взять в качестве брокера для Celery SQS и это вполне себя оправдало; правда, flower еще не имеет поддержки такой схемы.

Автоматическая переадресация HTTP на HTTPS


Используется такая вот добавка к конфигу Apache внутри ElasticBeanstalk

files:
    "/etc/httpd/conf.d/ssl_rewrite.conf":
        mode: "000644"
        owner: root
        group: root
        content: |
            RewriteEngine On
            <If "-n '%{HTTP:X-Forwarded-Proto}' && %{HTTP:X-Forwarded-Proto} != 'https'">
            RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
            </If>

Использование нескольких SSL доменов


Amazon предоставляет возможность владельцам доменов бесплатно использовать SSL сертификаты, в том числе и wildcard, однако только внутри самого AWS. Для использования нескольких доменов с SSL на одном environment получаем сертификат через AWS Certificate Manager, добавляем еще ELB балансировщик и настраиваем на нем SSL. Можно использовать и полученные у другого поставщика сертификаты.



UPDATE Ниже в комментарии уважаемый darken99 привел еще пару полезных фишек, позволю добавить их здесь с некоторыми пояснениями

Выключаем environment по расписанию

В данном случае в зависимости от указанного временного диапазона количество инстансов в autoscaling группе уменьшается с 1 до 0.


option_settings:
  - namespace: aws:autoscaling:scheduledaction
    resource_name: Start
    option_name: MinSize 
    value: 1
  - namespace: aws:autoscaling:scheduledaction
    resource_name: Start
    option_name: MaxSize
    value: 1
  - namespace: aws:autoscaling:scheduledaction
    resource_name: Start
    option_name: DesiredCapacity
    value: 1
  - namespace: aws:autoscaling:scheduledaction
    resource_name: Start
    option_name: Recurrence
    value: "0 9 * * 1-5"
  - namespace: aws:autoscaling:scheduledaction
    resource_name: Stop
    option_name: MinSize
    value: 0
  - namespace: aws:autoscaling:scheduledaction
    resource_name: Stop
    option_name: MaxSize
    value: 0
  - namespace: aws:autoscaling:scheduledaction
    resource_name: Stop
    option_name: DesiredCapacity
    value: 0
  - namespace: aws:autoscaling:scheduledaction
    resource_name: Stop
    option_name: Recurrence
    value: "0 18 * * 1-5"


Замена Apache на Nginx
option_settings:
  aws:elasticbeanstalk:environment:proxy:
    ProxyServer: nginx
Поделиться с друзьями
-->

Комментарии (11)


  1. darken99
    06.09.2016 16:18

    1. random1st
      06.09.2016 16:23

      Я же не ссылку на доку привел, а те вещи, которые там не описаны, или не так очевидны, с упором на сам сервис и его возможности в плане архитектуры.


      1. darken99
        06.09.2016 16:42
        -1

        В статье описано только применительно к Python и в общем довольно очевидные вещи.
        Например при использовании Tomcat либо Docker solution никакого supervisor не будет.
        Certificate manager притянут за уши.
        Бакеты можно использовать с любым названием, главное чтобы в том же регионе был.


        1. random1st
          06.09.2016 17:17

          А я, собственно говоря, и не скрывал, что пишу применительно к развертыванию Python приложений. Буду рад почитать, если напишите что-то менее очевидное, информации в целом не так уж и много, исключая официальный форум и документацию.


          1. darken99
            06.09.2016 17:26
            +1

            В заголовке и названии статьи это не упомянуто.
            Из полезного я бы добавил:


            Scheduled actions
            option_settings:
              - namespace: aws:autoscaling:scheduledaction
                resource_name: Start
                option_name: MinSize
                value: 1
              - namespace: aws:autoscaling:scheduledaction
                resource_name: Start
                option_name: MaxSize
                value: 1
              - namespace: aws:autoscaling:scheduledaction
                resource_name: Start
                option_name: DesiredCapacity
                value: 1
              - namespace: aws:autoscaling:scheduledaction
                resource_name: Start
                option_name: Recurrence
                value: "0 9 * * 1-5"
              - namespace: aws:autoscaling:scheduledaction
                resource_name: Stop
                option_name: MinSize
                value: 0
              - namespace: aws:autoscaling:scheduledaction
                resource_name: Stop
                option_name: MaxSize
                value: 0
              - namespace: aws:autoscaling:scheduledaction
                resource_name: Stop
                option_name: DesiredCapacity
                value: 0
              - namespace: aws:autoscaling:scheduledaction
                resource_name: Stop
                option_name: Recurrence
                value: "0 18 * * 1-5"


            1. random1st
              06.09.2016 17:38

              С Вашего разрешения добавил в статью. Что касается открытия портов — я так понимаю, Вы добавляете открытый порт на самом инстансе, а не на балансировщике?


              1. darken99
                06.09.2016 20:18
                +1

                Тут зависит от того, какой solution используется и какой тип — load balanсed либо standalone instance.
                Если точно известно, что приложению нужен какой-то порт, который Elastc Beanstalk не открывает автоматом, можно через ebextensions, его открыть автоматически при деплое. Это избавит от необходимости вручную искать нужный Security Group и его туда добавлять.


                1. darken99
                  06.09.2016 20:22

                  А по сути Elastic Beanstalk в зависимости от solution использует разные Cloudformation templates, с четко определенными параметрами, их можно даже посмотреть в соответствующем разделе AWS Console.


  1. yurtaev
    12.09.2016 11:16

    Рекомендую https://github.com/briandilley/ebs-deploy Упрощает деплой + легче поддерживать сразу несколько окружений (dev/beta/prod)


    1. random1st
      12.09.2016 11:18

      Чем он лучше чем штатный awsebcli? Я существенной разницы не увидел.


      1. yurtaev
        12.09.2016 11:30
        +1

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