Writing Terraform code
Substrate generates a directory structure for your Terraform code, remote state file configurations, provider configurations, and
moduleblocks so you can get started right away writing Terraform code that matters to your business.
tl;dr: Write code in the
modulessubdirectories Substrate creates for each domain. Run Terraform from the
root-modulessubdirectories matching the domain, environment, quality, and AWS region you want or just use
Suppose you're responsible for a service called example that you run in staging as alpha-quality and in production as beta- and gamma-quality. You will create those environments and qualities interactively with
substrate setupand will then run the following commands to create all your service accounts:
substrate create-account -domain example -environment staging -quality alpha
substrate create-account -domain example -environment production -quality beta
substrate create-account -domain example -environment production -quality gamma
These will create the following directory trees, which you will should commit to version control:
What do you do next? And where do you do it?
The vast majority of your work should happen in your domain's Terraform modules. In this example domain those are
modules/example/regional. Put global resources like IAM roles and Route 53 records in
modules/example/global. Put regional resources like autoscaling groups and EKS clusters in
modules/example/regional. (Substrate has generated all the
moduleblocks necessary to instantiate these modules with the right Terraform providers.)
You selected some number of AWS regions when you configured your network but you may not want to run all your infrastructure in all those regions all the time (if for no other reason than cost control). You may edit
root-modules/*/*/*/*/main.tfto customize which of your selected regions are actually in use. By default, all your selected regions are in use. If you don't want to provision your infrastructure in any of them, simply comment out the resources in
root-modules/*/*/*/*/main.tf(substituting your domains, environments, qualities, and regions as desired).
Every domain module automatically instantiates a common module, too. Here,
modules/common/regional. These common modules, following the now-familiar pattern of global and regional Terraform modules, are for resources that should exist in every service account. Note that the common modules are not instantiated in the Substrate, audit, deploy, management, or network accounts.
It's no accident that
modules/example/regionalare referenced by the root Terraform modules for every environment and quality in the domain. These afford you multiple opportunities to implement changes in pre-production and partial-production in order to catch more bugs and failures before they impact all your capacity and all your customers. Continuing from the example above, here is the complete lifecycle of a Terraform change in the example domain:
- 1.Change e.g. an EC2 launch template in
- 2.Commit, push, get a code review, and merge into the main branch
substrate create-account -domain example -environment staging -quality alphaand verify your changes
substrate create-account -domain example -environment production -quality betaand verify your changes, either at the end or at each pause between regions
substrate create-account -domain example -environment production -quality gammaand verify your changes, either at the end or at each pause between regions
As you write your own Terraform modules, you're certainly going to want to parameterize them in the same ways Substrate helps you parameterize your AWS accounts. Plus, you're also going to need a network, and Substrate's already created some and shared the right one with every service account to make it easy, secure, and cost-effective to build new things.
substrate create-accountautomatically creates global and regional Terraform modules for your domain in
>. Those modules include a reference to
modules/substratewhich provides the following helpful context:
module.substrate.tags.domain: The domain of this AWS account, from the tags on the account itself.
module.substrate.tags.environment: The environment of this AWS account, from the tags on the account itself.
module.substrate.tags.quality: The quality of this AWS account, from the tags on the account itself.
module.substrate.cidr_prefix: The CIDR prefix of this environment/quality's shared VPC.
module.substrate.private_subnet_ids: A set of three private subnet IDs in this environment/quality's shared VPC.
module.substrate.public_subnet_ids: A set of three public subnet IDs in this environment/quality's shared VPC. (This set is empty in your Substrate account, which only has public networks because bastion/jump hosts need public IP addresses.)
module.substrate.vpc_id: The ID of this environment/quality's shared VPC.