Первая часть по 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::SecurityGroupPropertiesGroupDescription— описаниеSecurityGroupIngress— правила для входящих соединения
- DBSecurityGroup — правила доступа к инстансу баз данных
Type—AWS::RDS::DBSecurityGroupPropertiesDBSecurityGroupIngress— правила для входящих соединенияGroupDescription— описание
- WebNodeWaitHandle — дескриптор обработчика для
WaitCondition(см. ниже) - WebNodeWaitCondition — ресурс, содержащий условия для продолжения создания стека (см. ниже)
Type—AWS::CloudFormation::WaitConditionDependsOn— ресурс, от которого зависит этотWaitConditionPropertiesHandle— URL для получения статусаTimeout— таймаут для получения статуса
- WebNode — имя ресурса, в данном случае — описывается создаваемый инстанс EC2
Type—AWS::EC2::Instance— тип описываемого далее ресурса, напримерAWS::EC2::VPC илиAWS::S3::BucketMetadata— атрибут, описывающий свойства этого ресурса в JSON-форматеAWS::CloudFormation::Init» — «точка входа» скриптаcfn-init(см. ниже)
Properties» — параметры для этого ресурсаImageId— AMI ID, который будет использоваться для создания ресурсаInstanceType— тип инстанса, напримерm1.smallSecurityGroups— список групп правил доступаKeyName— имя ключа доступаUserData— пользовательские данныедля инстанса, отсюда будет вызыватьсяcnf-init
- DBInstance
-
Type—AWS::RDS::DBInstancePropertiesDBNameEngineMasterUsernameDBInstanceClassDBSecurityGroupsAllocatedStorageMasterUserPassword
-
- 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 мы тут не рассматриваем) в следующем порядке:
packagesgroupsuserssourcesfilescommandsservices
Если вам требуется переопределить порядок их выполнения — используйте наборы конфигураций (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-signalTimeout-таймаут, после которого ожидание будет прервано, если не полученное кол-во сообщений, указанное вCountCount— количество 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",
...
Готово:


