AWS CloudFormation-模板中的自定义变量


18

有什么方法可以为从CloudFormation模板参数派生的常用值定义快捷方式吗?

例如-我有一个脚本,该脚本使用ELB名称创建一个多可用区项目堆栈,project并在ELB后面的两个实例称为project-1project-2。我只将ELBHostName参数传递给模板,以后再使用它来构造:

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

在整个模板中,多次重复这种构造或非常类似的操作-创建EC2主机名,Route53记录等。

我不想一遍又一遍地重复该操作,我想将其输出分配Fn::Join给某种变量,并且仅引用该变量,就像我可以使用"Ref":statement一样。

理想情况是:

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

或类似的简单内容。

Amazon CloudFormation有可能吗?


ELBHostName是否充满了您明确传递给Cloudformation的参数?如果是这样,为什么要使用Ref?可能可以使用Mustache在模板中包含变量,然后将其转换为JSON,然后再将其交付给Cloudformation。取决于您要配置的过程。
Canuteson

Answers:


5

我一直在寻找相同的功能。我想到了使用SpoonMeiser建议的嵌套堆栈,但是后来我意识到我真正需要的是自定义函数。幸运的是,CloudFormation允许使用AWS :: CloudFormation :: CustomResource,并且只需做一些工作,就可以做到这一点。这似乎只是变量的过大杀伤力(我认为这本来应该放在CloudFormation中),但是它可以完成工作,此外,还提供了所有的灵活性(请选择python / node) / java)。应该注意的是,lambda函数要花钱,但除非您每小时创建/删除堆栈数多次,否则我们在这里只说几分钱。

第一步是在此页面上创建一个lambda函数,该函数除了获取输入值并将其复制到输出外什么也不做。我们可以让lambda函数做各种疯狂的事情,但是一旦有了identity函数,其他任何事情都很容易。或者,我们可以在堆栈本身中创建lambda函数。由于我在1个帐户中使用了许多堆栈,因此我将有一堆剩余的lambda函数和角色(并且所有堆栈都需要使用创建--capabilities=CAPABILITY_IAM,因为它也需要一个角色。

创建lambda函数

  • 转到lambda主页,然后选择您喜欢的区域
  • 选择“空白功能”作为模板
  • 单击“下一步”(不配置任何触发器)
  • 填写:
    • 名称:CloudFormationIdentity
    • 描述:返回得到的信息,Cloud Form中的可变支持
    • 运行时:python2.7
    • 代码输入类型:内联编辑代码
    • 代码:见下文
    • 处理程序: index.handler
    • 角色:创建自定义角色。此时,将打开一个弹出窗口,允许您创建一个新角色。接受此页面上的所有内容,然后单击“允许”。它将创建一个具有发布到Cloudwatch日志的权限的角色。
    • 内存:128(最小值)
    • 超时:3秒(应该足够)
    • VPC:无VPC

然后在代码字段中复制粘贴下面的代码。该函数的顶部是cfn-response python模块中的代码,仅出于某些奇怪的原因,如果通过CloudFormation创建了lambda函数,该模块才自动安装。该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")
  • 点击下一步”
  • 点击“创建功能”

现在,您可以通过选择“测试”按钮来测试lambda函数,然后选择“ CloudFormation Create Request”作为示例模板。您应该在日志中看到返回给它的变量。

在CloudFormation模板中使用变量

现在我们有了此lambda函数,我们可以在CloudFormation模板中使用它了。首先记下lambda函数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包含lambda函数的Arn 的变量。将其放在此处的变量中,意味着我只需指定一次即可。我将所有变量设置为type Custom::Variable。CloudFormation允许您将任何类型名称以开头Custom::用于自定义资源。

请注意,该Identity变量两次包含了lambda函数的Arn。一次指定要使用的lambda函数。第二次作为变量的值。

现在有了Identity变量,我可以使用定义新变量了ServiceToken: !GetAtt [Identity, Arn](我认为JSON代码应该类似于"ServiceToken": {"Fn::GetAtt": ["Identity", "Arn"]})。我创建了2个新变量,每个变量都有2个字段:Name和Arn。在我的模板的其余部分,我可以使用!GetAtt [ClientBucketVar, Name]!GetAtt [ClientBucketVar, Arn]每当我需要它。

注意事项

使用自定义资源时,如果lambda函数崩溃,则您将停留1到2个小时,因为CloudFormation在放弃之前会等待(崩溃)函数的答复一个小时。因此,在开发lambda函数时,为堆栈指定较短的超时时间可能会很好。


很棒的答案!我阅读并在堆栈中运行了它,尽管对于我来说,我不必担心lambda函数在帐户中的泛滥,而且我喜欢独立的模板(我使用cloudformation-toolgem进行模块化),所以我将lambda创建打包到模板,然后可以直接使用它,而不用创建Identity自定义资源。看到这里我的代码:gist.github.com/guss77/2471e8789a644cac96992c4102936fb3
Guss

当您因为“ lambda”崩溃而没有以cfn-response进行回复而“ ...被困1到2个小时...”时,可以通过使用curl / wget on手动重新设置模板签名的URL。只需确保始终在lambda的开头打印事件/ 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

不,我尝试过,但是空了出来。对我而言,有意义的方法是创建一个名为“ CustomVariables”的Mapping条目,并保存所有变量。它适用于简单的String,但是您不能在Mappings中使用Intrinsics(Refs,Fn :: Joins等)

作品:

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

无法运作:

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

这只是一个例子。您不会在变量中放置独立的Ref。


1
文档说映射值必须是文字字符串。
伊万·阿尼奇丘克

3

您可以使用嵌套堆栈来解析其输出中的所有变量,然后用于Fn::GetAtt从该堆栈读取输出


2

您可以使用嵌套模板,在其中您可以“解析”外部模板中的所有变量,并将它们传递给另一个模板。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.