Terraform Module Components for AWS Landing Zone Solution
The core innovation in terraform module for AWS Landing Zone solution is the immutable nature of components. Let’s dive deeper into what a component is and how does it work.
To make sure that everybody has the same understanding about Terraform Module for AWS Landing Zone solution, here below is how this module looks like at the time of writing:
And if we take a look at terraform.tfvars, we should see something like this:
landing_zone_providers = {
default = {
account_id = "123456789012"
region = "us-east-1"
}
[...]
}landing_zone_components = {
landing_zone_vpc = "s3://terraform-aws-landing-zone/mycompany/landing_zone_vpc/default.tfvars"
[...]
}landing_zone_backend = {
backend = "local"
path = "/tmp/.terrahub/landing_zone"
}
Immutable Nature of Landing Zone Components
AWS Landing Zone solution addresses the challenge of managing multiple accounts in a faster and more secure manner, following AWS best practices. AWS empowers customers to use immutable patterns for resource allocation, but the complexity of managing terraform or cloudformation scripts that manages those resources is still pretty big.
That is why terraform module for AWS Landing Zone is designed to be dynamic, therefore reducing management complexity while still keeping high level of security. Each element of landing_zone_components
variable is a pair where the key is component’s name (immutable and static) and the value is path to .tfvars
file (mutable and dynamic). This setup allows customers to focus on dynamic aspects of their AWS environments, while terraform codebase almost never changes.
NOTE: In case some terraform config would need to change, instead of updating existing component, create a new one and update
landing_zone_components
list.
The Structure of Landing Zone Components
When looking at each component defined in landing_zone_components
map, the first issue that jumps into our sight is YAML format instead of HCL (Why? More on this later…) But what’s more important at this point is the emerging repeatable pattern. For example, landing_zone_vpc looks something like this:
component:
name: landing_zone_vpc
template:
locals:
elements_landing_zone_vpc_map: var.${tfvar.terrahub["landing_zone_providers"]["0"]}_provider["landing_zone_vpc_resource"]
[...]
resource:
aws_vpc:
landing_zone_vpc:
provider: 'aws.${tfvar.terrahub["landing_zone_providers"]["0"]}'
count: length(var.${tfvar.terrahub["landing_zone_providers"]["0"]}_provider["landing_zone_vpc_resource"])
cidr_block: local.elements_landing_zone_vpc_map["config_${count.index}"]["cidr_block"]
[...]
output:
[...]
And the corresponding default.tfvars (from s3://terraform-aws-landing-zone/mycompany/landing_zone_vpc/default.tfvars) looks like this:
landing_zone_providers = [
"default"
]default_provider = {
landing_zone_vpc_tags_element = {
config_0 = {
Name = "VPC for Landing Zone"
Description = "Managed by TerraHub"
ThubCode = "1234abcd"
ThubEnv = "prod"
}
},
landing_zone_vpc_resource = {
config_0 = {
cidr_block = "172.16.0.0/16"
instance_tenancy = "default"
assign_generated_ipv6_cidr_block = "false"
enable_classiclink = "false"
enable_classiclink_dns_support = "false"
enable_dns_support = "true"
enable_dns_hostnames = "false"
}
}
}
Let’s connect these two pieces from above:
- Line 10: this component will create terraform resource aws_vpc
- Line 12: this component will create separate terraform provider aws for each value from
landing_zone_providers
variable (which in practice how AWS accounts and AWS regions are separated in terraform) - Line 14: this component will iterate through
landing_zone_providers
, expecting specific variable for each provider (e.g.default_provider
); that is why it’s required to define all[provider_name]_provider
variables (e.g. iflanding_zone_providers
has valuesdefault
,alpha
andbeta
, it’s expected.tfvars
file(s) to define variablesdefault_provider
,alpha_provider
andbeta_provider
) - Line 16: this component uses
count
to iterate through resources defined by variablelanding_zone_vpc_resource
; to use native terraform capability, define[component_name]_resource
values as iterate-able list of elementsconfig_[iterator]
(e.g.config_0
,config_1
, and so on)
Ideal proposed structure for .tfvars
file(s) should be the following:
landing_zone_providers = [
"default"
"{{alpha_provider}}"
"{{beta_provider}}"
[...]
]default_provider = {
{{component_name}}_resource = {
config_0 = {
[...]
},
config_1 = {
[...]
},
[...]
}
}{{alpha_provider}}_provider = {
{{component_name}}_resource = {
[...]
}
}{{beta_provider}}_provider = {
[...]
}[...]
Landing Zone Components Using YAML Instead of HCL
Consider the following: Our goal for this terraform module is to empower users to do less by using native terraform capabilities, but we couldn’t do that primarily because HCL doesn’t allow usage of variables inside .tfvars
files and doesn’t support iterations through providers. For these reasons (and a couple of more) we opted into using terrahub cli — terraform automation and orchestration tool.
When executing terraform init
and terraform apply
onlanding_zone
module, the underlying code triggers terrahub run
for entire list of landing_zone_components
. Internally, landing zone components in YAML format are converted into HCL. This terrahub feature is called JIT (aka Just In Time) and, as the name suggests, YAML configs are converted into HCL in real-time during terraform workflow execution.
For example, above mentioned component landing_zone_vpc defined as .yml
file will be converted into the following set of .tf
files:
$ ls ~/.terrahub/cache/hcl/landing_zone_vpc_eef16dcf/
README.md default.tfvars locals.tf main.tf output.tf provider.tf terraform.tf variable.tf
NOTE: In order to debug JIT converted files from YAML format into HCL go to ~/.terrahub/cache/hcl/ folder and explore corresponding component(s). If specific component is missing, execute
terrahub run -i [component_name]
in order to generate corresponding[component_name]_[hash]
folder and.tf
files.
Putting Everything Together
After putting everything together, we get a very powerful terraform module:
$ terraform init
Initializing modules...
- landing_zone in modules/landing_zone
[...]
and
$ terraform applyAn execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create[...]Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
And that’s it. We hope it helps.
Share your thoughts and your experience on LinkedIn, Twitter, Facebook or in the comments section below.