AWS: CloudFormation — custom template

Автор: | 03/18/2016
 

aws-logo-square-02Первая часть по 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::VPC или AWS::S3::Bucket
      • Metadata — атрибут, описывающий свойства этого ресурса в JSON-формате
      • Properties» — параметры для этого ресурса
        • ImageIdAMI ID, который будет использоваться для создания ресурса
        • InstanceType — тип инстанса, например m1.small
        • SecurityGroups — список групп правил доступа
        • KeyName — имя ключа доступа
        • UserData — пользовательские данныедля инстанса, отсюда будет вызываться cnf-init
    • DBInstance
        • TypeAWS::RDS::DBInstance
        • Properties
          • DBName
          • Engine
          • MasterUsername
          • DBInstanceClass
          • DBSecurityGroups
          • AllocatedStorage
          • MasterUserPassword

cfn-helpers

AWS CloudFormation предоставляет набор cfn-скриптов, которые могут использоваться для установки и запуска служб на Amazon EC2 в создаваемом стеке. Скрипты используют метаданные (блок «Metadata«), указанные для ресурса в этом шаблоне и запускаются на инстансе как часть процесса создания стека.

Этот набор скриптов устанавливается по умолчанию на Amazon Linux AMI, но его требуется установить вручную при использовании образов других ОС:

  • cfn-init: используется для чтения метаданных ресурса, установки пакетов, создания файлов и запуск служб;
  • cfn-signal: используется для передачи AWS CloudFormationCreationPolicy или WaitCondition, позволяя синхронизировать другие ресурсы стека;
  • cfn-get-metadata: позволяет получить все метаданные ресурса, или указать путь к ключу или вложенному поддереву этого ресурса;
  • cfn-hup: демон для проверки обновления метаданных и выполения определенных действий (hooks), если обнаружены изменения.

Сам cfn-init вызывается из UserData и ищет ключ AWS::CloudFormation::Init, после чего выполняет следующие действия:

  1. загружает и считывает метаданные из CloudFormation;
  2. устанавливает пакеты;
  3. записывает файлы на диск;
  4. включает/выключает и запускает/останавливает службы.

Если при вызове cfn-init ему не указан configset — то он ищет ключ config в описании AWS::CloudFormation::Init, и запускает ключи, описанные в нем.

В примере выше — это будут:

cfn-init  обарабатывает секцию config (configset мы тут не рассматриваем) в следующем порядке:

  1. packages
  2. groups
  3. users
  4. sources
  5. files
  6. commands
  7. 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

Суммируя описанные выше ресурсы — получается следующая схема:

  1. CloudFormation встречает ресурс AWS::CloudFormation::WaitCondition, в котором указана зависимость от ресурса «WebNode«;
  2. AWS::CloudFormation::WaitCondition (в данном примере — с именем WebNodeWaitCondition) обращается к ресурсу AWS::CloudFormation::WaitConditionHandle с именем WebNodeWaitHandle, от которого получает URL с результатом создания ресурса «WebNode«;
  3. в процессе создания ресурса «WebNode» — CloudFormation выполняет скрипт cfn-signal из секции UserData, которому передает AWS::CloudFormation::WaitConditionHandle с именем WebNodeWaitHandle, который возвращает специально сформированный для этого ресурса URL, в который cfn-signal передает код выполнения предыдущего скрипта cfn-init, который выполняет действия из блока AWS::CloudFormation::Init;
  4. когда AWS::CloudFormation::WaitCondition (в данном примере — с именем WebNodeWaitCondition) получает заданное в Count (по умолчанию 1) количество SUCCSESS — он сигнализирует CloudFormation-у об успешном завершении создания или обновления ресурса «WebNode«, после чего CloudFormation продолжает создание стека.

Итак, мы уже определили Parameters, Mappings и Resourses.

Полностью шаблон можно посмотреть тут>>> или тут>>>.

При создании стека — отключите Roll back:

wp_custom_2

В случае ошибок при создании инстанса — из панели управления AWS (или зайдя на сам сервер по SSH) можно посмотреть в чем именно проблема:

wp_custom_1

Запускаем создание стека через 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",
...

Готово:

wp_custom_4