The immutable repository
immutable represents my best implementation of an immutable, opinionated and DRY-driven repository. You can get more information about here. In few words, the idea behind immutable is to provide one-command deployable repository that contains everything needed to deploy itself.
The idea behind immutable is to provide a repository as much as possible - 100% - immutable and without repetitions. This repository contains the code to deploy the infrastructure, a CI/CD agent and a sample application that actually is the website hosted by the infrastructure described in the repo.
The project is divided into several folder:
root: contains a Dockerfile to build a single
builderbased on alpine:3.8 docker image. This docker container includes everything is needed to let you deploy everything inside this repository.
applications: ideally, this folder contains
ndifferent applications to deploy in your infrastructure. In this repo, it only contain a website to show the infrastructure behind the application.
infrastructure: this folder contains the infrastructure resources definition to deploy the entire stack.
infrastructure folder contains two folder inside,
modules. To build the infrastructure, I mainly made use of terraform, terragrunt,ansible and packer. terragrunt defines the folder structure at infrastructure level (see the official doc for more information).
From an high level perspective, the
live folder contains the configurations patterns and values for two staging enviroment, called
prod You can modify the project to match your requirements using some simple rules defined by terragrunt.
modules folder my *.tf files to setup the AWS infrastructure. The folders are both configured as much as possible to follow the DRY approach: this was possible mostly thanks to terraform and terragrunt: unfortunately, there are still some things I would like to change/remove as soon as I understand how :)
The live folder
live folder represent the environments. I defined two environments at the moment (20/10/18): both of them contains the variables init required by the modules - without the effective
.tf file module definition, that describe respective resources of the module. The terraform files are defined in respective
modules folder. Every enviroment contains some variables stored in two different places:
- the shared and common variables defined in the
common.tfvarsfile in the root level of every stage-folder
- the modules specific variables and respective values are defined in the
terraform.tfvarsfile in the
Actually, what the environments define are the values of the variables and remote states - computed in a single terraform.tfvars but persisted, for each modules in a separate object using an s3 backend bucket (see more here). In each environment, every module has a terragrunt statement to reach the respective terraform source files (that is unique and not related to the environment) defined in the
The modules folder
modules folder contains the terraform sources of the infrastructure. Every module is contained in a subfolder with at least one
output.tf and a
variables.tf terraform file. These modules can be written in the same way a terraform module is usually written: the only difference is that anything in your code that should be different between environments should be exposed as an input variable - commonly declared in a
In each module, the main file contains the provider definition with the reference to the aws-profile defined in your credentials plus the
backend configuration as an empty reference, that will be filled in by terragrunt - more precisely, with the values specified in the global
terraform.tfvars file shared across each environment in the
The states of all the modules are mantained preserved across different environment thanks to interpolated keys, using the built-in
path_relative_to_include() function that, exposed by terragrunt, that let you get the relative path between the current
terraform.tfvars file and the path specified in its include block - thus, the modules name.
Enjoy and extend!
NOTE: the only side effect of this approach is that the first you extend is tricky to handle cross-module resource dependencies. In order, to accomplish this you have to:
- Output the values required by the resource that has dependencies;
- Define in the environment folder the remote state key of the module(s) that contains the resource(s) you have some dependencies with - this is required by the way terragrunt work;
- Reach the remote state for each module(s) / resource(s) and get the value you need from them using the same output name you define in the first state. To have an example, look at the VPC id output example and how it is propagated to the security group module. The same happens also for the Bastion EC2 instance.
Inside the modules
Inside the modules there are many folder:
vpccontains the VPC definition, that is nothing more than a standard VPC with n subnets, private or public and shared across multiple availabily zone
- Change the
terraform.tfvarsfile in the
- Go to one of the stage folder. To plan, run
- Enjoy watching your immutable project start
- Iter subnets and all involved VPC resources across both the number of avaiability zone and the number
Many thanks to