На днях столкнулся с интересным поведением применения опций окружения в Beanstalk. Beanstalk - это PaaS(Platform as a Service), один из способов предоставления клиенту готовой программной среды. Одновременно предоставляются инструменты для тонкой настройки такой среды. Элементами PaaS является аппаратное обеспечение, операционная система, СУБД, промежуточное ПО, инструменты тестирования и разработки. AWS Elastic Beanstalk – это простой в использовании сервис для развертывания и масштабирования веб-приложений и сервисов, разработанных с помощью Java, .NET, PHP, Node.js, Python, Ruby, Go и Docker, на серверах Apache, Nginx, Passenger и IIS.
Т.е. по сути AWS предоставляет вам уже настроенную платформу для определенного вида окружений, например, Java/PHP/Python и т.п. И соответственно следит за ее обновлениями и совместимостью. Конечно не все так гладко, как хотелось бы, но в целом очень удобный сервис. Другими словами Beanstalk представляет набор сервисов AWS таких как EC2, ELB, Autoscaling, RDS, S3 и т.д. и выступает в неком роде оркестратором.
Для автоматизации деплоя мы используем Jenkins и одним из параметров сборки является тип EC2 инстанса, на котором необходимо запускать приложение. Одним из условий данной сборки является возможность динамически менять тип EC2 при каждой сборке и деплое.
Мы используем Multicontainer docker environment, внутри которого запускаем java приложение, но в контексте данной статьи это не столь принципиально. Для описания того, как запускать docker образ и с какими параметрами используется специальный файл - Dockerrun.aws.json. Ниже привожу самый простой пример
{ "AWSEBDockerrunVersion": 2, "containerDefinitions": [{ "memory": 256, "cpu": 1, "name": "nginx", "image": "nginx", "portMappings": [{ "hostPort": 80, "containerPort": 80 }] }] }
Это обычный json файл с довольно таки простым синтаксисом. Вышеприведенный файл запустит контейнер с nginx, выделит ему 256 Mb памяти и пробросит 80й порт с docker хоста внутрь контейнера. Для того, чтобы создать окружения на базе нашего Dockerrun.aws.json необходимо воспользоваться утилитой командной строки Beanstalk - aws eb cli. Поскольку написана она на python, то устанавливается она довольно таки просто - через pip.
Непосредственно перед созданием самого окружения, нам необходимо произвести инициализацию приложения.
# eb init TEST --profile personal --region us-west-1 Application TEST has been created. It appears you are using Multi-container Docker. Is this correct? (Y/n): y Select a platform version. 1) Multi-container Docker 17.03.2-ce (Generic) 2) Multi-container Docker 1.11.2 (Generic) (default is 1): 1 Cannot setup CodeCommit because there is no Source Control setup, continuing with initialization Do you want to set up SSH for your instances? (Y/n): y Select a keypair. 1) test 2) [ Create new KeyPair ] (default is 1): 1
По сути, все что делает эта команда - создает служебную папку .elasticbeanstalk и конфигурационный файл config.yml внутри нее.
# tree -a . ├── Dockerrun.aws.json ├── .elasticbeanstalk │ └── config.yml └── .gitignore 1 directory, 3 files # cat .elasticbeanstalk/config.yml branch-defaults: default: environment: null group_suffix: null global: application_name: TEST branch: null default_ec2_keyname: test default_platform: Multi-container Docker 17.03.2-ce (Generic) default_region: us-west-1 include_git_submodules: true instance_profile: null platform_name: null platform_version: null profile: personal repository: null sc: null workspace_type: Application
Если посмотреть в веб консоли самого Beanstalk, то там появится созданное нами приложение TEST
Отлично, теперь у нас все готово для создания самого окружения.
# eb create ebns-options-precedence --cname ebns-options-precedence \ --region us-west-1 --profile personal \ --vpc --vpc.id vpc-44bbbb21 --vpc.securitygroups sg-4b8fe72d \ --vpc.ec2subnets subnet-140fc770,subnet-1344584a \ --vpc.elbsubnets subnet-140fc770,subnet-1344584a \ --vpc.elbpublic --vpc.public \ --keyname test \ --instance_profile aws-elasticbeanstalk-ec2-role \ --instance_type t2.nano Creating application version archive "app-171009_183531". Uploading TEST/app-171009_183531.zip to S3. This may take a while. Upload Complete. WARNING: Uploaded SSH public key for "tkap" into EC2 for region us-west-1. Environment details for: ebns-options-precedence Application name: TEST Region: us-west-1 Deployed Version: app-171009_183531 Environment ID: e-mxej3cvxn4 Platform: arn:aws:elasticbeanstalk:us-west-1::platform/Multi-container Docker running on 64bit Amazon Linux/2.7.5 Tier: WebServer-Standard CNAME: ebns-options-precedence.us-west-1.elasticbeanstalk.com Updated: 2017-10-09 15:35:38.704000+00:00 Printing Status: INFO: createEnvironment is starting. INFO: Using elasticbeanstalk-us-west-1-012345678910 as Amazon S3 storage bucket for environment data. INFO: Created security group named: sg-568fe730 INFO: Created load balancer named: awseb-e-m-AWSEBLoa-QZ2VZZD361DW INFO: Created security group named: sg-2380e845 INFO: Created Auto Scaling launch configuration named: awseb-e-mxej3cvxn4-stack-AWSEBAutoScalingLaunchConfiguration-73VVARDND7Z9 INFO: Environment health has transitioned to Pending. Initialization in progress (running for 8 seconds). There are no instances. INFO: Added instance [i-0353e83bb20fbbf2d] to your environment. INFO: Created Auto Scaling group named: awseb-e-mxej3cvxn4-stack-AWSEBAutoScalingGroup-CK1YXIHAR1LC INFO: Waiting for EC2 instances to launch. This may take a few minutes. INFO: Created Auto Scaling group policy named: arn:aws:autoscaling:us-west-1:012345678910:scalingPolicy:d6b10534-9844-4256-b66a-68f344979553:autoScalingGroupName/awseb-e-mxej3cvxn4-stack-AWSEBAutoScalingGroup-CK1YXIHAR1LC:policyName/awseb-e-mxej3cvxn4-stack-AWSEBAutoScalingScaleDownPolicy-RZ43ZCFUSMO4 INFO: Created Auto Scaling group policy named: arn:aws:autoscaling:us-west-1:012345678910:scalingPolicy:bc8d32d4-5a90-4110-92cc-85a1874aacaf:autoScalingGroupName/awseb-e-mxej3cvxn4-stack-AWSEBAutoScalingGroup-CK1YXIHAR1LC:policyName/awseb-e-mxej3cvxn4-stack-AWSEBAutoScalingScaleUpPolicy-18QZ4TJ2YUWGU INFO: Created CloudWatch alarm named: awseb-e-mxej3cvxn4-stack-AWSEBCloudwatchAlarmHigh-116GPNS213CY4 INFO: Created CloudWatch alarm named: awseb-e-mxej3cvxn4-stack-AWSEBCloudwatchAlarmLow-111DBMFL47DTV INFO: Starting new ECS task with awseb-ebns-options-precedence-mxej3cvxn4:1. INFO: ECS task: arn:aws:ecs:us-west-1:012345678910:task/c562d6ef-e9de-42e8-9335-b1da8fd2aa79 is RUNNING. INFO: Successfully launched environment: ebns-options-precedence
Как мы видем из вывода - окружение было успешно создано и запущено, так же в этом можно убедиться с помощью следующей команды
# eb status -v Environment details for: ebns-options-precedence Application name: TEST Region: us-west-1 Deployed Version: app-171009_183531 Environment ID: e-mxej3cvxn4 Platform: arn:aws:elasticbeanstalk:us-west-1::platform/Multi-container Docker running on 64bit Amazon Linux/2.7.5 Tier: WebServer-Standard CNAME: ebns-options-precedence.us-west-1.elasticbeanstalk.com Updated: 2017-10-09 15:39:11.239000+00:00 Status: Ready Health: Green Running instances: 1 i-0353e83bb20fbbf2d: InService # curl -i http://ebns-options-precedence.us-west-1.elasticbeanstalk.com/ HTTP/1.1 200 OK Accept-Ranges: bytes Content-Type: text/html Date: Mon, 09 Oct 2017 15:56:00 GMT ETag: "5989d7cc-264" Last-Modified: Tue, 08 Aug 2017 15:25:00 GMT Server: nginx/1.13.5 Content-Length: 612 Connection: keep-alive ... ... ...
Если вы обратили внимание, то при создании окружения мы использовали ключ –instance_type t2.nano, где явно задали какой тип инстанса необходимо создать в нашем окружении. И все бы хорошо, но теперь нам необходимо сделать возможность менять это значение. Beanstalk поддерживает несколько путей модификации окружений
Последние два способа нам не подходят, так как практически не поддаются автоматизации. Использование чистого api тоже довольно таки сложно автоматизировать, хотя и можно. А самый простой способ - использование .ebextensions. Все что нам нужно, создать папку .ebextensions и внутри нее добавить файлы с расширением config. И вот тут начинается самое интересное.
Мы, законопослушные граждане, читаем документацию и находим, что для изменения типа инстанса достаточно использовать т.н. namespace - aws:autoscaling:launchconfiguration. Создаем соответствующую структуру папок и сам файл
# tree -a . ├── Dockerrun.aws.json ├── .ebextensions │ └── ec2-settings.config ├── .elasticbeanstalk │ └── config.yml └── .gitignore 2 directories, 4 files # cat .ebextensions/ec2-settings.config option_settings: aws:autoscaling:launchconfiguration: InstanceType: t2.micro
Напоминаю, что изначально мы создали окружение с t2.nano, а сейчас хотим перейти на более мощный инстанс t2.micro. После этого обновляем наше окружение.
# eb deploy Creating application version archive "app-171009_200401". Uploading TEST/app-171009_200401.zip to S3. This may take a while. Upload Complete. INFO: Environment update is starting. INFO: Deploying new version to instance(s). INFO: Stopping ECS task arn:aws:ecs:us-west-1:012345678910:task/c562d6ef-e9de-42e8-9335-b1da8fd2aa79. INFO: ECS task: arn:aws:ecs:us-west-1:012345678910:task/c562d6ef-e9de-42e8-9335-b1da8fd2aa79 is STOPPED. INFO: Starting new ECS task with awseb-ebns-options-precedence-mxej3cvxn4:3. INFO: ECS task: arn:aws:ecs:us-west-1:012345678910:task/4d0ea10e-68d1-4990-872b-7fb0e85685b5 is RUNNING. INFO: New application version was deployed to running EC2 instances. INFO: Environment update completed successfully.
Проверяем тип инстанса
# aws ec2 describe-instances --profile cardpool-dev --region us-west-1 \ --instance-ids i-0353e83bb20fbbf2d --query "Reservations[].Instances[].InstanceType" [ "t2.nano" ]
Странно, но как мы видим, тип не изменился. Пробуем изменить напрямую через aws api
# aws elasticbeanstalk update-environment --environment-name ebns-options-precedence \ --profile personal --region us-west-1 \ --option-settings Namespace=aws:autoscaling:launchconfiguration,OptionName=InstanceType,Value=t2.small # eb status -v | tail -2 Running instances: 1 i-049d31ef7196304a1: InService
Как мы видим по статусу id у нашего инстанса изменился, а значит и тип скорее всего. Проверяем
# aws ec2 describe-instances --instance-ids i-049d31ef7196304a1 \ --profile personal --region us-west-1 --query "Reservations[].Instances[].InstanceType" [ "t2.small" ]
Да, так и есть. Тип инстанса поменялся. Но каждый раз вызывать из Jenkins aws elasticbeanstalk update-environment не очень удобно, к тому же нам надо будет отслеживать поменялся ли тип инстанса по сравнению с предыдущим деплоем, что лишь усложнит сам процесс деплоя. Через Beanstalk Webconsole тип инстанса так же меняется без проблем.
Снова обращаемся к документации и находим там раздел Precedence
Итак, во время создания и/или обновления окружения опции применяется в следующем приоритете. Список приведен от наиболее приоритетных к менее приоритетным.
Теперь становится понятно, почему значение InstanceType из нашего файла .ebextensions/ec2-settings.config не имеет эффекта. Но что же тогда делать, оказывается выход есть. После создания окружения нам надо лишь удалить соответствующий параметр на уровне API и тогда начнет действовать значение параметров из .ebextensions
# aws elasticbeanstalk update-environment --profile personal --region us-west-1 \ --environment-name ebns-options-precedence \ --options-to-remove Namespace=aws:autoscaling:launchconfiguration,OptionName=InstanceType
Данный вызов необходимо выполнить только один раз, после первоначального создания окружения. Как по мне - то такое поведение является очень не удобным и не очевидным. Получается, что с помощью eb cli вы можете задать тип инстанса, но сменить его в дальнейшем вы уже не можете.
Так же стоит учитывать, что если после удаления данной опции, кто либо поменяет ее через Web Console и/или через aws api cli, то настройки из .ebextensions опять перестанут применяться из-за приоритета и нужно будет заново выполнить options-to-remove