Первая часть по 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", ...
Готово: