Author: Ivan Ezechial Suratno (@ezeutno on GitHub)
Last Updated: Wed, Feb 3, 2021Automation 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.
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.
This guide uses variables with uppercase values in angle brackets. Replace these with your values.
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
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
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.
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"]
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
}
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
}
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.
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 the atlantis.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
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