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


18

Есть ли способ определить ярлыки для часто используемых значений, полученных из параметров шаблона 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?


Является ли ELBHostName полным параметром, который вы явно передаете в Cloudformation? Если так, зачем использовать Ref? Могли бы использовать Mustache, чтобы включить переменные в шаблон и преобразовать их в JSON перед отправкой в ​​Cloudformation. Зависит от того, как выглядит процесс инициализации.
Canuteson

Ответы:


5

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

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

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

  • Перейдите на домашнюю страницу lambda и выберите свой любимый регион
  • Выберите «Пустую функцию» в качестве шаблона
  • Нажмите «Далее» (не настраивайте триггеры)
  • Заполните:
    • Имя: 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 Create Request» в качестве примера шаблона. Вы должны увидеть в своем журнале, что переменные, переданные ему, возвращаются.

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

Теперь, когда у нас есть эта лямбда-функция, мы можем использовать ее в шаблонах CloudFormation. Сначала запишите лямбда-функцию Arn (перейдите на домашнюю страницу lambda , нажмите на только что созданную функцию, 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 ждет ответа от (сбойной) функции в течение часа, прежде чем сдаться. Поэтому, возможно, было бы неплохо указать короткое время ожидания для стека при разработке вашей лямбда-функции.


Отличный ответ! Я прочитал его и запустил в своих стеках, хотя для себя я не беспокоюсь о распространении лямбда-функций в моей учетной записи, и мне нравятся автономные шаблоны (я модулирую с помощью cloudformation-toolгема), поэтому я упаковываю создание лямбды в шаблон, а затем может использовать его напрямую вместо создания Identityпользовательского ресурса. Смотрите здесь для моего кода: gist.github.com/guss77/2471e8789a644cac96992c4102936fb3
Guss

Когда вы «... застряли на 1–2 часа ...», потому что лямбда потерпела крах и не ответила cfn-response, вы можете заставить шаблон двигаться снова, вручную используя curl / wget on подписанный URL. Просто убедитесь, что вы всегда распечатываете событие / URL в начале лямбды, чтобы вы могли зайти в CloudWatch и получить URL, если он зависает.
Тейлор

12

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

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

Заменяет

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

3

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

Работает:

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

Не будет работать:

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

Это всего лишь пример. Вы бы не поместили автономный Ref в переменную.


1
Документация говорит, что значения отображения должны быть литеральными строками.
Иван Анищук

3

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


2

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.