Назад | Перейти на главную страницу

AWS CloudFormation - пользовательские переменные в шаблонах

Есть ли способ определить ярлыки для часто используемых значений, полученных из параметров шаблона CloudFormation?

Например, у меня есть скрипт, который создает стек проекта Multi-AZ с именем ELB. project и два экземпляра за ELB называются project-1 и project-2. Я только прохожу ELBHostName параметр в шаблон, а затем использовать его для создания:

"Fn::Join": [
    ".", [
        { "Fn::Join": [ "", [ { "Ref": "ELBHostName" }, "-1" ] ] },
        { "Ref": "EnvironmentVersioned" },
        { "Ref": "HostedZone" }
    ]
]

Эта или очень похожая конструкция повторяется много раз в шаблоне - для создания имени хоста EC2, записей Route53 и т. Д.

Вместо того, чтобы повторять это снова и снова, я хотел бы назначить вывод этого Fn::Join к какой-либо переменной и ссылаться только на нее, так же, как я могу с "Ref": заявление.

В идеале что-то вроде:

Var::HostNameFull = "Fn::Join": [ ... ]
...
{ "Name": { "Ref": "Var::HostNameFull" } }

или что-то подобное простое.

Возможно ли это с Amazon CloudFormation?

У меня нет ответа, но я хочу отметить, что вы можете избавить себя от боли, используя Fn::Sub на месте Fn::Join

{ "Fn::Sub": "${ELBHostName"}-1.${EnvironmentVersioned}.${HostedZone}"}

Заменяет

"Fn::Join": [
    ".", [
        { "Fn::Join": [ "", [ { "Ref": "ELBHostName" }, "-1" ] ] },
        { "Ref": "EnvironmentVersioned" },
        { "Ref": "HostedZone" }
    ]
]

Я искал такую ​​же функциональность. Мне пришло в голову использование вложенного стека, как предлагал SpoonMeiser, но потом я понял, что на самом деле мне нужны специальные функции. К счастью, CloudFormation позволяет использовать AWS :: CloudFormation :: CustomResource что, приложив немного усилий, позволяет сделать именно это. Это кажется излишним только для переменных (что, я бы сказал, должно было быть в CloudFormation в первую очередь), но он выполняет свою работу и, кроме того, обеспечивает всю гибкость (выберите python / node на ваш выбор). /Ява). Следует отметить, что лямбда-функции стоят денег, но мы говорим здесь о копейках, если вы не создаете / не удаляете свои стеки несколько раз в час.

Первый шаг - создать лямбда-функцию на этой странице который ничего не делает, кроме как берет входное значение и копирует его на выход. У нас может быть лямбда-функция, которая будет делать всевозможные сумасшедшие вещи, но когда у нас есть функция идентификации, все остальное становится проще. В качестве альтернативы мы могли бы создать лямбда-функцию в самом стеке. Поскольку я использую много стеков в одной учетной записи, у меня будет целая куча оставшихся лямбда-функций и ролей (и все стеки должны быть созданы с помощью --capabilities=CAPABILITY_IAM, поскольку ему также нужна роль.

Создать лямбда-функцию

  • Перейти к домашняя страница лямбдаи выберите свой любимый регион
  • Выберите "Пустую функцию" в качестве шаблона.
  • Нажмите «Далее» (не настраивайте триггеры)
  • Заполнить:
    • Имя: CloudFormationIdentity
    • Описание: возвращает то, что получает, поддержка переменных в Cloud Formation
    • Время выполнения: python2.7
    • Тип ввода кода: встроенное редактирование кода
    • Код: см. Ниже
    • Обработчик: index.handler
    • Роль: создание настраиваемой роли. На этом этапе открывается всплывающее окно, в котором можно создать новую роль. Примите все на этой странице и нажмите «Разрешить». Он создаст роль с разрешениями на публикацию в журналах Cloudwatch.
    • Память: 128 (это минимум)
    • Тайм-аут: 3 секунды (должно быть достаточно)
    • VPC: без VPC

Затем скопируйте и вставьте приведенный ниже код в поле кода. Вверху функции находится код из Модуль Python cfn-response, который по какой-то странной причине устанавливается автоматически только в том случае, если лямбда-функция создается через CloudFormation. В handler функция довольно понятна.

from __future__ import print_function
import json

try:
    from urllib2 import HTTPError, build_opener, HTTPHandler, Request
except ImportError:
    from urllib.error import HTTPError
    from urllib.request import build_opener, HTTPHandler, Request


SUCCESS = "SUCCESS"
FAILED = "FAILED"


def send(event, context, response_status, reason=None, response_data=None, physical_resource_id=None):
    response_data = response_data or {}
    response_body = json.dumps(
        {
            'Status': response_status,
            'Reason': reason or "See the details in CloudWatch Log Stream: " + context.log_stream_name,
            'PhysicalResourceId': physical_resource_id or context.log_stream_name,
            'StackId': event['StackId'],
            'RequestId': event['RequestId'],
            'LogicalResourceId': event['LogicalResourceId'],
            'Data': response_data
        }
    )
    if event["ResponseURL"] == "http://pre-signed-S3-url-for-response":
        print("Would send back the following values to Cloud Formation:")
        print(response_data)
        return

    opener = build_opener(HTTPHandler)
    request = Request(event['ResponseURL'], data=response_body)
    request.add_header('Content-Type', '')
    request.add_header('Content-Length', len(response_body))
    request.get_method = lambda: 'PUT'
    try:
        response = opener.open(request)
        print("Status code: {}".format(response.getcode()))
        print("Status message: {}".format(response.msg))
        return True
    except HTTPError as exc:
        print("Failed executing HTTP request: {}".format(exc.code))
        return False

def handler(event, context):
    responseData = event['ResourceProperties']
    send(event, context, SUCCESS, None, responseData, "CustomResourcePhysicalID")
  • Нажмите кнопку "Далее"
  • Нажмите "Создать функцию"

Теперь вы можете протестировать лямбда-функцию, нажав кнопку «Тест» и выбрав «Запрос на создание CloudFormation» в качестве образца шаблона. Вы должны увидеть в своем журнале, что переменные, переданные в него, возвращаются.

Используйте переменную в шаблоне CloudFormation

Теперь, когда у нас есть эта лямбда-функция, мы можем использовать ее в шаблонах CloudFormation. Сначала обратите внимание на лямбда-функцию Arn (перейдите к домашняя страница лямбда, щелкните только что созданную функцию, Arn должен быть вверху справа, что-то вроде arn:aws:lambda:region:12345:function:CloudFormationIdentity).

Теперь в вашем шаблоне в разделе ресурсов укажите свои переменные, например:

Identity:
  Type: "Custom::Variable"
  Properties:
    ServiceToken: "arn:aws:lambda:region:12345:function:CloudFormationIdentity"
    Arn: "arn:aws:lambda:region:12345:function:CloudFormationIdentity"

ClientBucketVar:
  Type: "Custom::Variable"
  Properties:
    ServiceToken: !GetAtt [Identity, Arn]
    Name: !Join ["-", [my-client-bucket, !Ref ClientName]]
    Arn: !Join [":", [arn, aws, s3, "", "", !Join ["-", [my-client-bucket, !Ref ClientName]]]]

ClientBackupBucketVar:
  Type: "Custom::Variable"
  Properties:
    ServiceToken: !GetAtt [Identity, Arn]
    Name: !Join ["-", [my-client-bucket, !Ref ClientName, backup]]
    Arn: !Join [":", [arn, aws, s3, "", "", !Join ["-", [my-client-bucket, !Ref ClientName, backup]]]]

Сначала я указываю Identity переменная, содержащая Arn для лямбда-функции. Ввод здесь в переменную означает, что мне нужно указать это только один раз. Я делаю все свои переменные типа Custom::Variable. CloudFormation позволяет использовать любое имя типа, начинающееся с Custom:: для пользовательских ресурсов.

Обратите внимание, что Identity переменная дважды содержит Arn для лямбда-функции. Один раз указать используемую лямбда-функцию. Второй раз как значение переменной.

Теперь, когда у меня есть Identity переменной, я могу определить новые переменные, используя ServiceToken: !GetAtt [Identity, Arn] (Я думаю, что код JSON должен быть чем-то вроде "ServiceToken": {"Fn::GetAtt": ["Identity", "Arn"]}). Я создаю 2 новые переменные, в каждой по 2 поля: Имя и Арн. В остальной части моего шаблона я могу использовать !GetAtt [ClientBucketVar, Name] или !GetAtt [ClientBucketVar, Arn] всякий раз, когда мне это нужно.

Слово предостережения

При работе с пользовательскими ресурсами, если лямбда-функция дает сбой, вы застреваете на 1-2 часа, потому что CloudFormation ожидает ответа от (сбойной) функции в течение часа, прежде чем отказаться. Поэтому было бы неплохо указать короткий тайм-аут для стека при разработке вашей лямбда-функции.

Нет. Я попробовал, но ничего не вышло. Для меня это имело смысл создать запись Mappings под названием «CustomVariables» и разместить в ней все мои переменные. Это работает для простых строк, но вы не может использовать Intrinsics (Refs, Fn :: Joins и т. д.) внутри Mappings.

Работает:

"Mappings" : {
  "CustomVariables" : {
    "Variable1" : { "Value" : "foo" },
    "Variable2" : { "Value" : "bar" }
  }
}

Не получится:

  "Variable3" : { "Value" : { "Ref" : "AWS::Region" } }

Это просто пример. Вы не стали бы помещать отдельную ссылку в переменную.

Вы можете использовать вложенный стек, который разрешает все ваши переменные на своих выходах, а затем использовать Fn::GetAtt читать выходные данные из этого стека

Вы можете использовать вложенные шаблоны, в которых вы «разрешаете» все свои переменные во внешнем шаблоне и передаете их другому шаблону.