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

MLu*_*MLu 26 amazon-web-services amazon-cloudformation

有没有办法为从 CloudFormation 模板参数派生的常用值定义快捷方式?

例如-我有一个创建了ELB名字多AZ项目组的脚本project和ELB后面两个实例叫project-1project-2。我只将ELBHostName参数传递给模板,然后使用它来构造:

"Fn::Join": [
    ".", [
        { "Fn::Join": [ "", [ { "Ref": "ELBHostName" }, "-1" ] ] },
        { "Ref": "EnvironmentVersioned" },
        { "Ref": "HostedZone" }
    ]
]
Run Code Online (Sandbox Code Playgroud)

这种结构或非常相似的结构在整个模板中重复多次 - 以创建 EC2 主机名、Route53 记录等。

而不是一遍又一遍地重复,我想将它的输出分配Fn::Join给某种变量并且只引用它,就像我可以用"Ref":语句一样。

理想情况下是这样的:

Var::HostNameFull = "Fn::Join": [ ... ]
...
{ "Name": { "Ref": "Var::HostNameFull" } }
Run Code Online (Sandbox Code Playgroud)

或类似简单的东西。

使用 Amazon CloudFormation 可以吗?

小智 15

我没有答案,但确实想指出,使用Fn::Sub代替可以为自己省去很多痛苦Fn::Join

{ "Fn::Sub": "${ELBHostName"}-1.${EnvironmentVersioned}.${HostedZone}"}
Run Code Online (Sandbox Code Playgroud)

替换

"Fn::Join": [
    ".", [
        { "Fn::Join": [ "", [ { "Ref": "ELBHostName" }, "-1" ] ] },
        { "Ref": "EnvironmentVersioned" },
        { "Ref": "HostedZone" }
    ]
]
Run Code Online (Sandbox Code Playgroud)


小智 6

不,我试过了,但结果是空的。对我来说有意义的方法是创建一个名为“CustomVariables”的映射条目,并让该条目包含我的所有变量。它适用于简单的字符串,但您不能在 Mappings 中使用内部函数(Refs、Fn::Joins 等)

作品:

"Mappings" : {
  "CustomVariables" : {
    "Variable1" : { "Value" : "foo" },
    "Variable2" : { "Value" : "bar" }
  }
}
Run Code Online (Sandbox Code Playgroud)

不会工作:

  "Variable3" : { "Value" : { "Ref" : "AWS::Region" } }
Run Code Online (Sandbox Code Playgroud)

那只是一个例子。您不会将独立的 Ref 放入变量中。

  • 文档说映射值必须是文字字符串。 (2认同)

小智 5

我正在寻找相同的功能。我想到了使用 SpoonMeiser 建议的嵌套堆栈,但后来我意识到我真正需要的是自定义函数。幸运的是,CloudFormation 允许使用AWS::CloudFormation::CustomResource,只需做一些工作,就可以做到这一点。这对于变量来说感觉有点矫枉过正(我认为应该首先放在 CloudFormation 中),但它完成了工作,此外,还允许(选择 python/node /java)。应该注意的是,lambda 函数要花钱,但我们在这里谈论的是一分钱,除非您每小时多次创建/删除堆栈。

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

创建 lambda 函数

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

然后将下面的代码复制粘贴到代码字段中。该函数的顶部是来自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")
Run Code Online (Sandbox Code Playgroud)
  • 点击下一步”
  • 点击“创建函数”

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

在 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]]]]
Run Code Online (Sandbox Code Playgroud)

首先,我指定一个Identity包含 lambda 函数的 Arn的变量。把它放在一个变量中,意味着我只需要指定一次。我制作了所有类型的变量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 函数时为堆栈指定一个较短的超时可能会很好。