Run Terraform in Automation with Atlantis
Introduction
Automation of Terraform is a way to ensure consistency and implement features such as integration with version control hooks, and it's useful for development and production servers. Following this guide, you'll set up an example CI/CD pipeline with Atlantis, GitHub, and Terraform.
Prerequisites
This guide uses a Vultr Ubuntu 20.04 VPS with 1 vCPU and 1 GB of RAM. This is an advance guide, requiring a basic understanding of Terraform and CI/CD concepts.
- Deploy a new Vultr Ubuntu 20.04 (x64) cloud server
- Update the server according to the Ubuntu best practices guide
This guide uses variables with uppercase values in angle brackets. Replace these with your values.
1. Install Terraform CLI
Atlantis uses Terraform to plan, create, and destroy cloud resources.
Download the 64-bit Linux version of Terraform, found on the Terraform download page. The example below is for version 0.14.5.
# wget https://releases.hashicorp.com/terraform/0.14.5/terraform_0.14.5_linux_amd64.zip
Extract the Terraform .zip file.
# apt install -y unzip # unzip terraform_*_linux_amd64.zip && rm terraform_*_linux_amd64.zip
Move terraform to
/usr/bin
.# mv terraform /usr/bin/terraform
Check the terraform installation.
# terraform --version # which terraform
2. Install Atlantis
Download the 64-bit Linux version of Atlantis, found on the Atlantis release page. The example below is for version 0.16.0.
# wget https://github.com/runatlantis/atlantis/releases/download/v0.16.0/atlantis_linux_amd64.zip
Extract) the Atlantis .zip file.
# unzip atlantis_linux_amd64.zip && rm atlantis_linux_amd64.zip
Create an Atlantis user with a home directory.
# adduser -m atlantis
Move atlantis to its home directory /home/atlantis
# mv atlantis /home/atlantis/atlantis
Change the ownership of the file to Atlantis user.
# chown atlantis:atlantis /home/atlantis/atlantis
Check the Atlantis installation.
# /home/atlantis/atlantis --help
3. Setup a GitHub Repository
Create a private GitHub repository. The recommended organization for the Github repository is shown below. Replace vultr-vc
and instance1
(flagged with double asterisks below) with your corresponding module and service name.
├── modules
│ └── vultr-vc **
│ ├── provider.tf
│ ├── variable.tf
│ ├── output.tf
│ └── main.tf
│
├── services
│ └── instance1 **
│ ├── terraform.tfvars.example
│ ├── variable.tf
│ ├── output.tf
│ └── main.tf
├── .gitignore
├── README.md
└── atlantis.yaml
On GitHub:
- Go to Settings then Webhook.
- Click Add webhook.
- Set Payload URL to
http://<INSTANCE_IP_OR_URL>/events
- Set Content type to
application/json
- Enter a random string in Secret. More than eight characters is recommended.
- Pick the events to send.
- Pushes
- Pull request review comments
- Pull requests
- Issue comments
- Leave Active ticked.
- Click Add webhook.
Create a Github token:
- Go to Profile > Developer Settings > Personal access token
- Click Generate new token
- Select scopes.
- repo
- Give the token a name, For example: Atlantis Token
- Click Generate Token and copy the token.
Create an atlantis.yaml
file in the GitHub repository, which tells Atlantis which directories and workflows to apply. It also creates a tfvars
file on /home/atlantis/tfvars/service.tfvars
to execute terraform apply -var-file <FILENAME>
.
version: 3
projects:
- name: instance1
dir: services/instance1
workflow: service
autoplan:
when_modified: ["modules/**/*.tf","*.tf"]
enabled: true
workflows:
service:
plan:
steps:
- init
- plan:
extra_args: ["-var-file", "/home/atlantis/tfvars/service.tfvars"]
4. Create a Terraform Module
A Terraform module is a universal template that speeds up the creation of similar services.
Create a Vultr
provider.tf
file.terraform { required_providers { vultr = { source = "vultr/vultr" version = "2.1.2" } } } provider "vultr" { api_key = var.vultr_token rate_limit = 700 retry_limit = 3 }
Create a
main.tf
file.This example creates a Vultr Cloud Compute instance.
resource "vultr_instance" "instance" { plan = var.server_type region = var.region os_id = var.server_os label = var.server_label tag = "terraform" }
Create a
variable.tf
file, which defines the variables passed to the Terraform.variable "vultr_token" { description = "Vultr api token" type = string } variable "server_os" { description = "Vultr OS" type = string } variable "server_type" { description = "Vultr Server Type Ex. vc2-1c-1gb" type = string } variable "server_label" { description = "Vultr Server Name Labeling" type = string } variable "region" { description = "Vultr Region" type = string }
5. Create a Terraform Service
Create a
main.tf
file.Terraform stores state about your infrastructure and configuration in a
.tfstate
file, which is declared in the terraform backend block. The module block sets the variables for the terraform module.terraform { backend "local" { path = "/home/atlantis/tfstate/service-instance1.tfstate" } } module "server1" { source = "../../modules/vultr-vc" vultr_token = var.vultr_token server_type = "vc2-1c-1gb" server_os = "387" server_label = "Server1-Terraform" region = "SGP" }
Use the Vultr API to find valid
server_os
values.$ curl https://api.vultr.com/v2/os
For valid
region
values:$ curl https://api.vultr.com/v2/regions
Create a
variable.tf
file to pass the API token as a secret variable to Terraform.variable "vultr_token" { description = "Vultr api token" type = string }
6. Get an API Key
- Navigate to the Vultr Customer Portal.
- Go to Account > API.
- Click Enable API.
- Copy the Vultr API key.
- Go to Access Control.
- Add the Server IP to access control.
7. Set up the Atlantis Service
Set up the tfvars file.
# mkdir -p /home/atlantis/tfvars # nano /home/atlantis/tfvars/service.tfvars
Add your Vultr API key as shown.
vultr_token = "<VULTR_API_KEY>"
Grant ownership of the
tfvars
folder to the atlantis user.# chown -R atlantis:atlantis /home/atlantis/tfvars
Set up the
tfstate
local backend directory.# mkdir -p /home/atlantis/tfstate # chown -R atlantis:atlantis /home/atlantis/tfstate
Set up the
repos.yaml
file in the atlantis user home directory.# nano /home/atlantis/repos.yaml
The
repos.yaml
file sets theatlantis.yaml
file's abilities in the GitHub repo. By setting it to mergeable, the action can only occur if the Pull Request is mergeable.repos: - id: /.*/ apply_requirements: [mergeable] allowed_overrides: [workflow] allow_custom_workflows: true
Grant ownership of
repos.yaml
to the atlantis user.# chown atlantis:atlantis /home/atlantis/repos.yaml
Create a new systemd service.
# cd /etc/systemd/system # nano atlantis.service or vi atlantis.service
Paste the following:
[Unit] Description=Atlantis [Service] User=atlantis WorkingDirectory=/home/atlantis ExecStart=/home/atlantis/atlantis server --allow-fork-prs --gh-user=<Github Username> --gh-token=<Github Token> --repo-whitelist=github.com/<GITHUB_USERNAME>/<REPOSITORY_NAME> --atlantis-url=http://<INSTANCE_IP_OR_URL> --gh-webhook-secret=GITHUB_SECRET> --repo-config=/home/atlantis/repos.yaml --log-level debug --port 80 Restart=always [Install] WantedBy=multi-user.target # systemctl daemon-reload # systemctl start atlantis.service
Check the installation.
# systemctl status atlantis.service # journalctl -xe
8. Create a New Server / Service
On your local computer or GitHub:
Checkout to a new branch.
$ git checkout -b SERVICE-2
Create a new service folder. It's recommended to use the service name.
Add an
atlantis.yaml
file in that folder. Paste the following:projects: - name: instance1 dir: services/instance1 workflow: service autoplan: when_modified: ["modules/**/*.tf","*.tf"] enabled: true - name: instance2 dir: services/instance2 workflow: service autoplan: when_modified: ["modules/**/*.tf","*.tf"] enabled: true
Create a pull request on GitHub to the main branch.
Check the automated Terraform Plan. For example, you may see:
An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # module.server1.vultr_instance.instance will be created + resource "vultr_instance" "instance" { + activation_email = true + allowed_bandwidth = (known after apply) + app_id = (known after apply) + backups = "disabled" + date_created = (known after apply) + ddos_protection = false + default_password = (sensitive value) + disk = (known after apply) + enable_ipv6 = false + enable_private_network = false + features = (known after apply) + firewall_group_id = (known after apply) + gateway_v4 = (known after apply) + hostname = (known after apply) + id = (known after apply) + internal_ip = (known after apply) + kvm = (known after apply) + label = "Server1-Terraform" + main_ip = (known after apply) + netmask_v4 = (known after apply) + os = (known after apply) + os_id = 387 + plan = "vc2-1c-1gb" + power_status = (known after apply) + ram = (known after apply) + region = "SGP" + reserved_ip_id = (known after apply) + script_id = (known after apply) + server_status = (known after apply) + snapshot_id = (known after apply) + status = (known after apply) + tag = "terraform" + user_data = (known after apply) + v6_main_ip = (known after apply) + v6_network = (known after apply) + v6_network_size = (known after apply) + vcpu_count = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy.
To re-plan, you could use
atlantis plan
.Execute the terraform plan using
atlantis apply
The output of apply should look like this:
module.server1.vultr_instance.instance: Creating... module.server1.vultr_instance.instance: Still creating... [10s elapsed] module.server1.vultr_instance.instance: Still creating... [20s elapsed] module.server1.vultr_instance.instance: Still creating... [30s elapsed] module.server1.vultr_instance.instance: Still creating... [40s elapsed] module.server1.vultr_instance.instance: Still creating... [50s elapsed] module.server1.vultr_instance.instance: Still creating... [1m0s elapsed] module.server1.vultr_instance.instance: Still creating... [1m10s elapsed] module.server1.vultr_instance.instance: Still creating... [1m20s elapsed] module.server1.vultr_instance.instance: Still creating... [1m30s elapsed] module.server1.vultr_instance.instance: Still creating... [1m40s elapsed] module.server1.vultr_instance.instance: Still creating... [1m50s elapsed] module.server1.vultr_instance.instance: Creation complete after 1m54s [id=1ef9bc13-cb27-48b2-b776-30559caffc3f] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. The state of your infrastructure has been saved to the path below. This state is required to modify and destroy your infrastructure, so keep it safe. To inspect the complete state use the `terraform show` command. State path: /home/atlantis/tfstate/service-instance1.tfstate