When configuring Elastic Load Balancers in CloudFormation, we often need to reference pre-existing subnets from different Availability Zones. The common approach using Fn::FindInMap
works perfectly for single values, but becomes problematic when we need to return a list of subnet IDs for multi-AZ deployment.
Here's what typically fails:
"Subnets" : [
{
"Fn::FindInMap": [
"AWSEnv2PublicSubnets",
{"Ref": "Env"},
"subList"
]
}
]
This structure creates a nested list (list containing another list) rather than the flat list of strings that ELB expects.
Solution 1: Using Fn::Split with Delimiter
"Subnets": {
"Fn::Split": [
",",
{"Fn::FindInMap": [
"AWSEnv2PublicSubnets",
{"Ref": "Env"},
"subList"
]}
]
}
With corresponding mapping:
"Mappings": {
"AWSEnv2PublicSubnets": {
"DEV": {
"subList": "subnet-1111,subnet-2222,subnet-3333"
},
"TEST": {
"subList": "subnet-4444"
}
}
}
Solution 2: Using Fn::Select with List
"Subnets": {
"Fn::Select": [
"0",
{"Fn::FindInMap": [
"AWSEnv2PublicSubnets",
{"Ref": "Env"},
"subList"
]}
]
}
This approach requires your mapping to be structured as:
"Mappings": {
"AWSEnv2PublicSubnets": {
"DEV": {
"subList": [
["subnet-1111", "subnet-2222", "subnet-3333"]
]
}
}
}
For cleaner templates, consider using Fn::Join
with Fn::GetAZs
when creating new subnets:
"Subnets": [
{"Fn::Select": ["0", {"Fn::GetAZs": ""}]},
{"Fn::Select": ["1", {"Fn::GetAZs": ""}]}
]
This automatically selects subnets from different AZs without hardcoding values.
For complex scenarios, you might create a Lambda-backed custom resource that returns the subnet list:
"Resources": {
"SubnetResolver": {
"Type": "Custom::SubnetList",
"Properties": {
"ServiceToken": "arn:aws:lambda:...",
"Env": {"Ref": "Env"}
}
},
"ELB": {
"Properties": {
"Subnets": {"Fn::GetAtt": ["SubnetResolver", "SubnetList"]}
}
}
}
When troubleshooting, use Fn::Sub
to inspect values:
"Outputs": {
"SubnetDebug": {
"Value": {"Fn::Sub": "${AWSEnv2PublicSubnets.${Env}.subList}"}
}
}
This helps verify your mapping structure before implementing the full solution.
When working with AWS CloudFormation's Fn::FindInMap
intrinsic function, many developers encounter difficulties when trying to return a list of values. The common error Value of property Subnets must be of type List of String
appears because CloudFormation expects a direct list but receives a nested structure.
For Elastic Load Balancers in multi-AZ environments, you typically need to specify multiple subnets. The natural approach would be to store these subnet IDs in a Mapping and retrieve them, but the standard implementation fails:
"Subnets" : [
{
"Fn::FindInMap": [
"AWSEnv2PublicSubnets",
{"Ref": "Env"},
"subList"
]
}
]
To properly return a list from FindInMap, you need to use a combination of Fn::Join
and Fn::Split
functions:
"Subnets" : {
"Fn::Split": [",",
{"Fn::FindInMap": [
"AWSEnv2PublicSubnets",
{"Ref": "Env"},
"subList"
]}
]
}
Your mapping should then be modified to use comma-separated values:
"Mappings": {
"AWSEnv2PublicSubnets": {
"DEV": {
"subList": "subnet-1111,subnet-2222,subnet-3333"
},
"TEST": {
"subList": "subnet-4444"
}
}
}
For more complex scenarios, you might consider creating a custom Lambda function that returns the subnet list:
"Subnets": {
"Fn::GetAtt": ["SubnetResolver", "Subnets"]
},
"SubnetResolver": {
"Type": "Custom::SubnetList",
"Properties": {
"ServiceToken": "arn:aws:lambda...",
"Environment": {"Ref": "Env"}
}
}
- Always validate subnet IDs before template deployment
- Consider using AWS Systems Manager Parameter Store for dynamic values
- Test your template with different environment configurations
- Use CloudFormation linting tools to catch syntax issues early
Remember that FindInMap doesn't natively support list returns. Other mistakes include:
- Forgetting to update all environment mappings when adding new subnets
- Using different AZs for subnets in the same ELB
- Not accounting for region differences in subnet IDs