我们有一个 CloudFormation 模板,用于创建 EC2 实例和安全组(以及许多其他资源(,但我们需要能够将一些额外的预先存在的安全组添加到同一个 EC2 实例。
我们遇到的问题是,预先存在的安全组的数量并不总是相同的,我们希望有一个模板来处理所有情况。
目前,我们有一个如下所示的输入参数:
"WebTierSgAdditional": {
"Type": "String",
"Default": "",
"Description": ""
}
我们将一个以逗号分隔的预先存在的安全组字符串传递给此参数,例如 'sg-abc123,sg-abc456'
.
EC2 实例的安全组标签如下所示:
"SecurityGroups": [
{
"Ref": "WebSg"
},
{
"Ref": "WebTierSgAdditional"
}
]
使用此代码,创建实例时,我们会在 AWS 控制台中收到此错误:
必须对所有安全组使用组 ID 或组名,而不是同时使用两者
上面的"WebSg"引用是在模板中其他位置创建的安全组之一。如果我们通过输入参数传入组名称列表而不是组 ID 列表,则会出现相同的错误。
当我们将输入参数的"类型"字段更改为"逗号分隔列表"时,我们得到一个错误,指出:
属性的值 安全组 必须为字符串列表类型
它显然不能用字符串连接列表以使其成为新列表。
当参数只包含一个 sg id 时,所有内容都会成功创建,但是,我们需要能够添加多个 sg id。
我们已经尝试了在安全组标签中使用Fn::Join
的许多不同的组合,但似乎没有任何效果。 我们真正需要的是某种"爆炸"函数,从参数字符串中提取单个 id。
有谁知道让它工作的好方法?
在2017年1月推出了Fn::Split,现在可以这样做。它并不漂亮,但您实际上是使用 Fn::Join
将两个列表转换为字符串,然后将字符串转换回带有 Fn::Split
的列表。
Parameters:
WebTierSgAdditional:
Type: CommaDelimitedList
Default: ''
Conditions:
HasWebTierSgAdditional: !Not [ !Equals [ '', !Select [ 0, !Ref WebTierSgAdditional ] ] ]
Resources:
WebSg:
Type: AWS::EC2::SecurityGroup
Properties:
# ...
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
# ...
SecurityGroupIds: !Split
- ','
- !Join
- ','
- - !Ref WebSg
- !If [ HasWebTierSgAdditional, !Join [ ',', !Ref WebTierSgAdditional ], !Ref 'AWS::NoValue' ]
WebTierSgAdditional
以列表开始,但会转换为字符串,每个项目通过 !Join [ ',', !Ref WebTierSgAdditional ]
用逗号分隔。它包含在另一个列表中,该列表包括!Ref WebSg
,该列表也会转换为字符串,每个项目用逗号分隔。 !Split
将采用一个字符串并将项目拆分为一个列表,用逗号分隔。
正如您所发现的,问题是您需要发送字符串列表作为安全组,尽管 CloudFormation 为您提供了一种将字符串列表连接到单个分隔字符串的方法,但它并没有提供将分隔字符串拆分为字符串列表的简单方法。
不幸的是,我知道如何做到这一点的唯一方法是使用嵌套堆栈。您可以使用参数类型"逗号分隔列表"将逗号分隔的字符串拆分为字符串列表。
基本方法是这样的:
- 在云形成模板中创建安全组。
- 使用 Fn::Join 将该安全组 ID 与您的安全组列表合并。
- 将该列表传递给嵌套堆栈(类型为 AWS::CloudFormation::Stack 的资源(。
- 将该参数作为单独模板中的类型"逗号分隔列表"。
- 将参数 ref 传递到 EC2 实例声明中。
我已经使用以下 2 个模板对此进行了测试,它正在工作:
主模板:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters" : {
"SecurityGroups" : {
"Description" : "A comma separated list of security groups to merge with the web security group",
"Type" : "String"
}
},
"Resources" : {
"WebSg" : ... your web security group here,
"Ec2Instance" : {
"Type" : "AWS::CloudFormation::Stack",
"Properties" : {
"TemplateURL" : "s3 link to the other stack template",
"Parameters" : {
"SecurityGroups" : { "Fn::Join" : [ ",", [ { "Ref" : "WebSg" }, { "Ref" : "SecurityGroups" } ] ] },
}
}
}
}
}
嵌套模板(通过上面的"模板URL"链接到(:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters" : {
"SecurityGroups" : {
"Description" : "The Security Groups to launch the instance with",
"Type" : "CommaDelimitedList"
},
}
"Resources" : {
"Ec2Instance" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
... etc
"SecurityGroupIds" : { "Ref" : "SecurityGroups" }
}
}
}
}
我很想知道是否有更好的方法来做到这一点。正如你提到的,它确实需要一个爆炸功能。
我从支持中得到了另一种解决方案,我发现更好。
基本上,它只是从列表中获取每个安全组的索引,然后在末尾添加内部安全组。
"SecurityGroupList": {
"Description": "List of existing security groups",
"Type": "CommaDelimitedList"
},
...
"InternalSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup"
},
...
"SecurityGroupIds": [
{
"Fn::Select": [
"0",
{
"Ref": "SecurityGroupList"
}
]
},
{
"Fn::Select": [
"1",
{
"Ref": "SecurityGroupList"
}
]
},
{
"Fn::Select": [
"2",
{
"Ref": "SecurityGroupList"
}
]
},
{
"Ref": "InternalSecurityGroup"
}
],
希望这对其他人有所帮助。
您实际上可以在 lambda 中编写必要的转换函数,而无需创建子堆栈,以获取必要的列表字符串。
例如:http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/walkthrough-custom-resources-lambda-lookup-amiids.html
事实上,这个函数除了接收列表并将其返回之外,实际上不需要做任何事情。我有一个类似的场景,我有一个List-AWS::EC2::Subnet::Id-,需要将其传递给需要List-String的资源。
过程:
定义 LambdaExecutionRole。
定义 AWS::Lambda::Function 类型的 ListToStringList函数,添加在 cfnresponse 数据 {'Result':event['ResourceProperties']['List']} 中返回的代码。
定义自定义资源 MyList 类型 Custom::MyList,为 GetAtt 的 ServiceToken 提供 ListToStringListFunction Arn。同时将 List 作为引用原始列表的属性传递。
引用 MyList with Fn::GetAtt(MyList,Result(
按照@alanthing的方法,我想出了这个 JSON 格式的解决方案。只需确保参数不为空,因为在我的情况下我没有检查它。另请注意,模板不完整,仅显示相关部分。
将其发布在这里,以防对任何人都有用。
"Parameters": {
"SecurityGroups": {
"Type": "List<AWS::EC2::SecurityGroup::Id>",
"Description": "List of security groups"
}
},
"Resources": {
"EC2Instance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"SecurityGroupIds": {
"Fn::Split": [
",",
{
"Fn::Sub": [
"${SGIdsByParam},${SGByLogicalId}",
{
"SGIdsByParam": {
"Fn::Join": [",", {
"Ref": "SecurityGroups"
}]
},
"SGByLogicalId": {
"Fn::GetAtt": ["InstanceSecurityGroup", "GroupId"]
}
}
]
}
]
}
}
一个更简单的 2 行解决方案如下:
前提是您在映射中具有如下通用AppSecurityGroups
,或者可以作为参数:
"AppSecurityGroups" : "sg-xxx,sg-yyyy,sg-zzz"
第二个安全组(特定于服务(ServiceSecurityGroup
,sg-aaa
值作为 CFT 中的参数。
在服务安全组上编写条件,以检查它是否为"无"。
"Conditions": {
"IsEmptySSG": {
"Fn::Equals": [
{"Ref": "ServiceSecurityGroup"},
"None"
]
}
}
然后根据ServiceSecurityGroup
不为空的条件合并安全组。
"SecurityGroups" : {
"Fn::If" : [
"IsEmptySSG",
{"Fn::Split" : [ "," , {"Ref" : "AppSecurityGroups"} ]},
{"Fn::Split" : [ "," ,
{ "Fn::Join" : [ ",", [{"Ref" : "AppSecurityGroups"}, { "Ref" : "ServiceSecurityGroup" }]]}
]
}
]
}
这会将安全组的 2 个 CSV 列表动态追加到一个列表中,以供您的 AWS 资源使用。
我已经测试并使用它来解决类似的问题,效果非常好。