Первая часть по AWS: CloudFormation- в посте AWS: CloudFormation.
В этом посте будет использоваться шаблон WordPress от AWS с некоторыми изменениями. Будет добавлена возможность выбора различных окружений (PROD или DEV), в зависимости от которых будет выбираться тип RDS и EC2 инстансов и другие параметры. Операционная система – Ubuntu, вместо Amazon Linux.
В панели управления создадим ключ CloudFormationWPsetupCustom.pem, как описано в первой части.
Клонируем шаблон:
$ wget https://s3.amazonaws.com/cloudformation-templates-us-east-1/WordPress_Single_Instance_With_RDS.template
Копируем:
$ cp WordPress_Single_Instance_With_RDS.template WordPress_Custom_Instance_With_RDS.template
Примечание: очень понравился плагин Amazon AWS CloudFormation language support для PyCharm – подставляет имена “переменных”, проверяет синтаксис и так далее.
Содержание
Блок Parameters
В блоке Parameters
добавляем свой параметр – EnvironmentType:
... "Parameters" : { "EnvironmentType": { "Type": "String", "Default": "DEV", "AllowedValues": ["DEV", "PROD"], "Description": "Enter environment type. Default is DEV" }, ...
Сюда же добавляем параметры для пользователя базы данных и его пароль.
Для DBPassword указываем "NoEcho": "true"
, что бы пароль не отображался во время создания стека:
... "DBUsername" : { "Default": "admin", "NoEcho": "false", "Description" : "The WordPress database admin account username", "Type": "String", "MinLength": "1", "MaxLength": "16", "AllowedPattern" : "[a-zA-Z][a-zA-Z0-9]*", "ConstraintDescription" : "must begin with a letter and contain only alphanumeric characters." }, "DBPassword" : { "Default": "password", "NoEcho": "true", "Description" : "The WordPress database admin account password", "Type": "String", "MinLength": "8", "MaxLength": "41", "AllowedPattern" : "[a-zA-Z0-9]*", "ConstraintDescription" : "must contain only alphanumeric characters." } ...
Блок Mappings
Т.к. требуется использовать свой AMI – то указываем его (тут для примера используется ami-a609b6d5 – образ Ubuntu hvm:ebs
отсюда>>>), добавив его в блок RegionMap
:
"Mappings":{ "RegionMap" : { "eu-west-1" : { "AMI" : "ami-a609b6d5" } ...
Далее это AMI ID будет использоваться в Properties
ресурса при вызове функции Fn::FindInMap
, например:
... "Properties" : { "ImageId": { "Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "AMI" ] }, ...
Кроме того, для каждого типа среды мы будем использовать различные имена создаваемых баз данных и различные типы инстансов.
Добавляем их в Mappings
:
... "DBNameMap" : { "DEV" : { "dbname" : "wordpress_dev" }, "PROD" : { "dbname" : "wordpress_prod" } }, "InstanceTypeMap" : { "DEV" : { "instancetype" : "t2.nano" }, "PROD" : { "instancetype" : "t2.micro" } } }, ...
Аналогично – добавляем типы инстансов для MySQL в RDS и их максимальный размер:
В результате блок Mappings
будет выглядеть так:
... "Mappings":{ "RegionMap" : { "eu-west-1" : { "AMI" : "ami-a609b6d5" } }, "DBNameMap" : { "DEV" : { "dbname" : "wordpress_dev" }, "PROD" : { "dbname" : "wordpress_prod" } }, "InstanceTypeMap" : { "DEV" : { "instancetype" : "t2.nano" }, "PROD" : { "instancetype" : "t2.micro" } }, "DBInstanceTypeMap" : { "DEV" : { "dbinstancetype" : "db.t1.micro" }, "PROD" : { "dbinstancetype" : "db.m1.small" } }, "DBAllocatedStorageMap" : { "DEV" : { "dbstorage" : "5" }, "PROD" : { "dbstorage" : "10" } } }, ...
Блок Resources и cfn-скрипты
Описываем EC2 инстансы.
Сначала рассмотрим все дерево, потом – наиболее важные его части:
Resources
- WebServerSecurityGroup – правила доступа к инстансу веб-сервера
Type
–AWS::EC2::SecurityGroup
Properties
GroupDescription
– описаниеSecurityGroupIngress
– правила для входящих соединения
- DBSecurityGroup – правила доступа к инстансу баз данных
Type
–AWS::RDS::DBSecurityGroup
Properties
DBSecurityGroupIngress
– правила для входящих соединенияGroupDescription
– описание
- WebNodeWaitHandle – дескриптор обработчика для
WaitCondition
(см. ниже) - WebNodeWaitCondition – ресурс, содержащий условия для продолжения создания стека (см. ниже)
Type
–AWS::CloudFormation::WaitCondition
DependsOn
– ресурс, от которого зависит этотWaitCondition
Properties
Handle
– URL для получения статусаTimeout
– таймаут для получения статуса
- WebNode – имя ресурса, в данном случае – описывается создаваемый инстанс EC2
Type
–AWS::EC2::Instance
– тип описываемого далее ресурса, напримерAWS::EC2::VP
C илиAWS::S3::Bucket
Metadata
– атрибут, описывающий свойства этого ресурса в JSON-форматеAWS::CloudFormation::Init
” – “точка входа” скриптаcfn-init
(см. ниже)
Properties
” – параметры для этого ресурсаImageId
– AMI ID, который будет использоваться для создания ресурсаInstanceType
– тип инстанса, напримерm1.small
SecurityGroups
– список групп правил доступаKeyName
– имя ключа доступаUserData
– пользовательские данныедля инстанса, отсюда будет вызыватьсяcnf-init
- DBInstance
-
Type
–AWS::RDS::DBInstance
Properties
DBName
Engine
MasterUsername
DBInstanceClass
DBSecurityGroups
AllocatedStorage
MasterUserPassword
-
- WebServerSecurityGroup – правила доступа к инстансу веб-сервера
cfn-helpers
AWS CloudFormation предоставляет набор cfn
-скриптов, которые могут использоваться для установки и запуска служб на Amazon EC2 в создаваемом стеке. Скрипты используют метаданные (блок “Metadata
“), указанные для ресурса в этом шаблоне и запускаются на инстансе как часть процесса создания стека.
Этот набор скриптов устанавливается по умолчанию на Amazon Linux AMI, но его требуется установить вручную при использовании образов других ОС:
cfn-init
: используется для чтения метаданных ресурса, установки пакетов, создания файлов и запуск служб;cfn-signal
: используется для передачи AWS CloudFormation-уCreationPolicy
илиWaitCondition
, позволяя синхронизировать другие ресурсы стека;cfn-get-metadata
: позволяет получить все метаданные ресурса, или указать путь к ключу или вложенному поддереву этого ресурса;cfn-hup
: демон для проверки обновления метаданных и выполения определенных действий (hooks), если обнаружены изменения.
Сам cfn-init
вызывается из UserData
и ищет ключ AWS::CloudFormation::Init
, после чего выполняет следующие действия:
- загружает и считывает метаданные из CloudFormation;
- устанавливает пакеты;
- записывает файлы на диск;
- включает/выключает и запускает/останавливает службы.
Если при вызове cfn-init
ему не указан configset
– то он ищет ключ config
в описании AWS::CloudFormation::Init
, и запускает ключи, описанные в нем.
В примере выше – это будут:
cfn-init
обарабатывает секцию config
(configset
мы тут не рассматриваем) в следующем порядке:
packages
groups
users
sources
files
commands
services
Если вам требуется переопределить порядок их выполнения – используйте наборы конфигураций (configset
).
Пример вызова cfn-init
, который будет использоваться в этом шаблоне:
"/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackId" }, " -r WebServer ", " --region ", { "Ref" : "AWS::Region" }, "\n",
-s, --stack
: имя стека, врозвращаемое функцийRef
из параметраAWS::StackId
-r, --resource
: ID (имя) ресурса для загрузки метаданных--region
: регион, возвращаемый из параметраAWS::Region
Далее – вызывается скрипт cfn-signal
:
... "/opt/aws/bin/cfn-signal -e $? '", { "Ref" : "WebNodeWaitHandle" }, "'\n", ...
cfn-signal
сообщает CloudFormation, что EC2 инстанс был создан или обновлен. Т.е., после установки и настройки служб на инстансе вы отправляете уведомление CloudFormation, что приложение готово.
-e, --exit-code
: отправляет код выполнения предыдущей команды, через “$?
“, ресурсу{ "Ref" : "WebNodeWaitHandle" }
Примечание: на самом деле /usr/local/bin/cfn-signal
, который создается при установке из aws-cfn-bootstrap-latest.tar.gz через easy_install
импортирует модуль pkg_resources
, из которого потом вызывает сам скрипт /usr/local/lib/python2.7/dist-packages/aws_cfn_bootstrap-1.4-py2.7.egg/EGG-INFO/scripts/cfn-signal
.
Функция send()
в нем выполняет запрос
requests.put()
на URL, переданный первым аргументом (т.е. полученный из { "Ref" : "WebNodeWaitHandle" }
), передавая туда список:
data = { ‘Status’: get_status(signal_success),
‘Reason’ : options.reason,
‘Data’ : options.data,
‘UniqueId’ : options.id }
в виде JSON-объекта:
def send(url, data):
requests.put(url,
data=json.dumps(data),
…
WaitConditionHandle
и WaitCondition
Используя AWS::CloudFormation::WaitCondition
и AWS::CloudFormation::WaitConditionHandle
вы можете указать CloudFormation о необходимости приостановить дальнейшее создание стека, пока не будут выполнены условия, указанные для ресурса WaitCondition
. Например – вам может понадобится загрузить и установить какое-то приложение на EC2 инстансе, прежде чем его создание будет считаться завершенным.
Ресурс WaitConditionHandle
выглядит так:
... "WebNodeWaitHandle" : { "Type" : "AWS::CloudFormation::WaitConditionHandle" }, ...
А WaitCondition
– так:
... "WebNodeWaitCondition" : { "Type" : "AWS::CloudFormation::WaitCondition", "DependsOn" : "WebNode", "Properties" : { "Handle" : { "Ref" : "WebNodeWaitHandle" }, "Timeout" : "1600" } }, ...
Ресурс WaitConditionHandle
используется для передачи отчета о состоянии ресурса между скриптом cfn-signal
в UserData
ресурса “WebNode” самому CloudFormation-у:
... "/opt/aws/bin/cfn-signal -e $? '", { "Ref": "WebNodeWaitHandle" }, ...
Далее, CloudFormation использует AWS::CloudFormation::WaitCondition
, в котором опредены атрибуты DependsOn
, который описывает ресурс, от которого зависит текущий ресурс и Properties
– который содержит значения:
Handle
– обработчик, возвращающий специально сформирвоанный URL, содержащий данные, полученные отcfn-signal
Timeout
-таймаут, после которого ожидание будет прервано, если не полученное кол-во сообщений, указанное вCount
Count
– количество SUCCESS-сигналов, которые будет ожидатьCloudFormation
для продолжения создания или обновления стека. Если не указан – то по умолчанию используется значение 1.
URL обработчика имеет примерно такой вид:
https://cloudformation-waitcondition-eu-west-1.s3-eu-west-1.amazonaws.com/arn%3Aaws%3Acloudformation%3Aeu-west-1%3A264418146286%3Astack/WPCustom/6fd7a580-ec5f-11e5-88f9-50a686326636/WebNodeWaitHandle?AWSAccessKeyId=AKIAJRBFOG6RPGASDWGA&Expires=1458319439&Signature=GEGN6nIwqJCC91AnzmceV3xqgzY%3D
Суммируя описанные выше ресурсы – получается следующая схема:
- CloudFormation встречает ресурс
AWS::CloudFormation::WaitCondition
, в котором указана зависимость от ресурса “WebNode“; AWS::CloudFormation::WaitCondition
(в данном примере – с именем WebNodeWaitCondition) обращается к ресурсуAWS::CloudFormation::WaitConditionHandle
с именем WebNodeWaitHandle, от которого получает URL с результатом создания ресурса “WebNode“;- в процессе создания ресурса “WebNode” – CloudFormation выполняет скрипт
cfn-signal
из секцииUserData
, которому передаетAWS::CloudFormation::WaitConditionHandle
с именем WebNodeWaitHandle, который возвращает специально сформированный для этого ресурса URL, в которыйcfn-signal
передает код выполнения предыдущего скриптаcfn-init
, который выполняет действия из блокаAWS::CloudFormation::Init
; - когда
AWS::CloudFormation::WaitCondition
(в данном примере – с именемWebNodeWaitCondition
) получает заданное вCount
(по умолчанию 1) количество SUCCSESS – он сигнализирует CloudFormation-у об успешном завершении создания или обновления ресурса “WebNode“, после чего CloudFormation продолжает создание стека.
Итак, мы уже определили Parameters
, Mappings
и Resourses.
Полностью шаблон можно посмотреть тут>>> или тут>>>.
При создании стека – отключите Roll back:
В случае ошибок при создании инстанса – из панели управления AWS (или зайдя на сам сервер по SSH) можно посмотреть в чем именно проблема:
Запускаем создание стека через CLI с помощью aws:cloudformation – create-stack:
$ aws cloudformation create-stack --stack-name WPCusttonStackTest --template-url https://s3-eu-west-1.amazonaws.com/rtfmuploads/WordPress_Custom_Instance_With_RDS.template --disable-rollback --parameters ParameterKey=KeyName,ParameterValue=CloudFormationWPsetupCustom
Просмотреть события можно с помощью describe-stack-events:
$ aws cloudformation describe-stack-events --stack-name WPCusttonStackTest { "StackEvents": [ { "ResourceStatusReason": "Resource creation Initiated", "LogicalResourceId": "DBInstance", "PhysicalResourceId": "wd12ks8gkffh9mj", "ResourceStatus": "CREATE_IN_PROGRESS", "StackName": "WPCusttonStackTest", "ResourceProperties": "{\"MasterUsername\":\"admin\",\"DBSecurityGroups\":[\"wpcusttonstacktest-dbsecuritygroup-j8aq7ru58wu7\"],\"DBInstanceClass\":\"db.t1.micro\",\"AllocatedStorage\":\"5\",\"DBName\":\"wordpress_dev\",\"Engin e\":\"MySQL\",\"MasterUserPassword\":\"****\"}", "EventId": "DBInstance-CREATE_IN_PROGRESS-2016-03-17T17:25:14.646Z", "StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/WPCusttonStackTest/77fc9eb0-ec63-11e5-8b16-50faeb53b42a", "Timestamp": "2016-03-17T17:25:14.646Z", "ResourceType": "AWS::RDS::DBInstance" }, { "LogicalResourceId": "DBInstance", "PhysicalResourceId": null, "ResourceStatus": "CREATE_IN_PROGRESS", ...
Готово: