This message is extracted from a ticket originally emailed to support@gruntwork.io. Names and URLs have been removed where appropriate.
I’ve got a random question for you. We’ve started spinning up a bunch of lower environments. These environments are all the same except for their name and cidr block. To spin up a new one I am simply copying an existing directory in our infrastructure-live project and then doing a find a replace on the name:
For example, to create a “qa” environment from our “demo”, I did:
cp -r demo qa
cd qa
find . -name "*.tfvars" -print0 | xargs -0 sed -i '' -e 's/demo/qa/g'
followed by setting a unique cidr block in the vpc module and then running terragrunt apply to each module.
The problem is, now we’re starting to get a bunch of directories which I fear are going to become a nightmare to manage. What I’d really like is to just have one “template” directory and then pass in the name/cidr block when I run terragrunt plan/apply. For example terragrunt apply demo 10.20.0.0/18, and then have those parameters get picked up by the various .tfvars files. Is anything like that possible? If not via the command line then maybe by setting environmental variables?
The reason a single environment consists of many separate folders is for isolation: that is, you want to protect things that you don’t change often and are mission-critical (e.g., your databases and VPCs) from those you change frequently and are less important if they are temporarily down (e.g., a single app). By putting things in separate folders, we significantly reduce the odds that, for example, you’ll break your VPC while deploying some simple change to an app.
The downside to this approach is the issue you’re seeing now: you have more files/folders to manage, which becomes cumbersome if you have many environments.
If you are frequently spinning up & tearing down environments, perhaps you could create a compromise solution as follows:
In your infra-modules repo, create a new module called something like “environment” or “stack”. Put a main.tf file in the folder and have it instantiate all of your modules:
In other words, you are creating an “uber module” that creates your entire tech stack. Note that you’ll have to add a vars.tf that exposes all sorts of input variables for things that vary from environment to environment, such as the name of the VPC, its CIDR block, the name of the DBs, etc.
Note that you may have to make your modules explicitly depend on each other to ensure they are created in the right order (e.g., VPC before DB). You could do probably that, for example, by setting the terraform_state_s3_bucket input parameter of the DB to depend on a parameter of the same name that you add as an output to the VPC.
In non-prod environments, you can deploy this “stack” module with a single .tfvars file for each environment. That should make it pretty easy to maintain, and you can spin the whole thing up with a single terragrunt apply.
In prod environments, you continue to deploy modules individually with separate .tfvars files.
I’m not quite sure how to go from multiple separate module that make use of terraform_remote_state data sources (in prod, staging case) to a single all-in-one stack where all the module share the same remote state (in ephemeral, dev, test case). Is that even possible?
Terraform doesn’t have “native” support for this sort of thing, but it’s possible to do with a little bit of work.
For example, let’s say you have three types of “infrastructure” you want to deploy: a VPC, an ASG, and a DB. The DB depends on the VPC and the ASG depends on both the VPC and the DB. In prod, you want each of those deployed separately, each with its own remote state, for isolation. In dev/stage/qa, you want to deploy those all in a single module so it’s easier to manage. There are a few ways to do this. Here’s one example.
In the infrastructure-modules repo, you create three modules: vpc, asg, db. Each one deploys the corresponding infrastructure and takes in any external dependencies as input variables. E.g., the asg module might look like this:
Note that subnet_ids and db_url are input variables that represent a dependency on something external (in this case, the VPC and DB, respectively).
Next, you create your “uber” module called everything that deploys everything in the environment at once, for use in dev/stage/qa. This module uses the asg, db, and vpc modules under the hood and wires them together accordingly:
Notice how each of the modules now depends on other modules, so everything will be created in the right order.
Finally, you create two more modules that allows you to deploy your infrastructure in a standalone manner for use in prod: db-standalone and asg-standalone. These use the db and asg modules under the hood, but fill in the variables using terraform_remote_state data sources. Here’s the asg-standalone example: