有没有办法定义从 CloudFormation 模板参数派生的常用值的快捷方式?
例如 - 我有一个脚本,它创建一个多可用区项目堆栈,其名称为 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 可以实现这个吗?
答案1
我正在寻找相同的功能。我想到使用 SpoonMeiser 建议的嵌套堆栈,但后来我意识到我真正需要的是自定义函数。幸运的是,CloudFormation 允许使用AWS::CloudFormation::自定义资源只需一点工作,就可以做到这一点。这感觉对于变量来说有点过度(我认为它应该首先存在于 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 秒(应该足够了)
- 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 创建请求”作为示例模板。您应该在日志中看到输入的变量已返回。
在 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 的变量。将其放入此处的变量中意味着我只需指定一次。我将所有变量都设为 类型Custom::Variable
。CloudFormation 允许您使用以 开头的任何类型名称Custom::
作为自定义资源。
请注意,Identity
变量包含两次 lambda 函数的 Arn。第一次用于指定要使用的 lambda 函数。第二次作为变量的值。
现在我有了Identity
变量,我可以使用 定义新变量ServiceToken: !GetAtt [Identity, Arn]
(我认为 JSON 代码应该是这样的)。我创建了 2 个新变量,每个变量都有 2 个字段:Name 和 Arn。在其余模板中,我可以在需要时"ServiceToken": {"Fn::GetAtt": ["Identity", "Arn"]}
使用!GetAtt [ClientBucketVar, Name]
或。!GetAtt [ClientBucketVar, Arn]
警告
使用自定义资源时,如果 lambda 函数崩溃,您将被困 1 到 2 个小时,因为 CloudFormation 会等待(崩溃的)函数的回复一小时后才会放弃。因此,在开发 lambda 函数时,为堆栈指定一个较短的超时时间可能是个不错的选择。
答案2
我没有答案,但我想指出的是,你可以Fn::Sub
用Fn::Join
{ "Fn::Sub": "${ELBHostName"}-1.${EnvironmentVersioned}.${HostedZone}"}
替换
"Fn::Join": [
".", [
{ "Fn::Join": [ "", [ { "Ref": "ELBHostName" }, "-1" ] ] },
{ "Ref": "EnvironmentVersioned" },
{ "Ref": "HostedZone" }
]
]
答案3
没有。我试过了,但一无所获。对我来说,最合理的方法是创建一个名为“CustomVariables”的 Mappings 条目,并让其容纳我的所有变量。它适用于简单的字符串,但你不能在 Mappings 中使用 Intrinsics(Refs、Fn::Joins 等)。
作品:
"Mappings" : {
"CustomVariables" : {
"Variable1" : { "Value" : "foo" },
"Variable2" : { "Value" : "bar" }
}
}
不起作用:
"Variable3" : { "Value" : { "Ref" : "AWS::Region" } }
这只是一个例子。你不会将独立的 Ref 放在变量中。
答案4
您可以使用嵌套模板,在其中“解析”外部模板中的所有变量并将它们传递给另一个模板。