CICD Infrastructure Automation
Introduction
Infrastructure automation is a critical component of modern CI/CD (Continuous Integration/Continuous Deployment) pipelines. While traditional CI/CD focuses on automating code testing and deployment, infrastructure automation extends these benefits to the underlying computing resources that run your applications.
In this guide, we'll explore how to automate infrastructure provisioning, configuration, and management as part of your CI/CD workflow. This approach, often called "Infrastructure as Code" (IaC), allows you to treat your infrastructure with the same practices you apply to application code.
What is Infrastructure Automation?
Infrastructure automation refers to the practice of using scripts or declarative definitions to set up, configure, and manage computing resources automatically. When combined with CI/CD, it enables:
- Consistent environments: Eliminate "it works on my machine" problems
- Rapid provisioning: Create new environments in minutes instead of days
- Version control: Track changes to infrastructure just like application code
- Self-documenting systems: Your code becomes the documentation
- Disaster recovery: Quickly rebuild infrastructure if necessary
Infrastructure as Code Tools
Let's examine the most popular tools for infrastructure automation:
Terraform
Terraform by HashiCorp is a declarative IaC tool that supports multiple cloud providers.
# Example Terraform configuration for AWS
provider "aws" {
region = "us-west-2"
}
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "WebServer"
Environment = "Development"
}
}
Ansible
Ansible is a configuration management tool that uses YAML to define infrastructure state.
# Example Ansible playbook to install Nginx
- name: Install and configure Nginx
hosts: web_servers
become: yes
tasks:
- name: Install Nginx package
apt:
name: nginx
state: present
- name: Start Nginx service
service:
name: nginx
state: started
enabled: yes
AWS CloudFormation/Azure Resource Manager
Cloud-specific IaC tools for managing resources in their respective platforms.
// Example AWS CloudFormation template
{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"S3Bucket": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": "my-application-logs",
"AccessControl": "Private"
}
}
}
}
Integrating Infrastructure Automation in CI/CD Pipelines
Now, let's explore how to incorporate infrastructure automation into your CI/CD workflow:
CI/CD Infrastructure Automation Workflow
Example: GitLab CI Pipeline for Terraform
Here's a practical example of a GitLab CI pipeline that incorporates Terraform for infrastructure automation:
stages:
- validate
- plan
- apply
- deploy
variables:
TF_ROOT: ${CI_PROJECT_DIR}/terraform
TF_STATE_NAME: ${CI_PROJECT_NAME}-${CI_ENVIRONMENT_NAME}
validate:
stage: validate
script:
- cd ${TF_ROOT}
- terraform init
- terraform validate
plan:
stage: plan
script:
- cd ${TF_ROOT}
- terraform init
- terraform plan -out=tfplan
artifacts:
paths:
- ${TF_ROOT}/tfplan
apply:
stage: apply
script:
- cd ${TF_ROOT}
- terraform init
- terraform apply -auto-approve tfplan
dependencies:
- plan
when: manual
environment:
name: staging
deploy_app:
stage: deploy
script:
- echo "Deploying application to infrastructure..."
- ./deploy_script.sh
dependencies:
- apply
Running Terraform in GitHub Actions
GitHub Actions is another popular CI/CD platform. Here's how you can automate infrastructure using Terraform within a GitHub workflow:
name: 'Terraform CI/CD'
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
- name: Terraform Init
run: terraform init
- name: Terraform Format
run: terraform fmt -check
- name: Terraform Plan
run: terraform plan -out=tfplan
- name: Terraform Apply
if: github.event_name == 'push'
run: terraform apply -auto-approve tfplan
Infrastructure Testing
An important aspect of infrastructure automation is testing. Here are some approaches:
Example: Testing Terraform with Terratest
Terratest is a Go library that makes it easier to test Terraform code:
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestTerraformAwsExample(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../examples/aws",
VarFiles: []string{"varfile.tfvars"},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
output := terraform.Output(t, terraformOptions, "instance_ip")
assert.NotEmpty(t, output)
}
Best Practices for Infrastructure Automation
-
Immutable Infrastructure: Rather than modifying existing resources, replace them with new ones.
-
Environment Parity: Use the same infrastructure code across development, staging, and production, with only configuration differences:
# Parameterize environments
variable "environment" {
description = "Environment (dev, staging, prod)"
type = string
}
resource "aws_instance" "app_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = var.environment == "prod" ? "t2.medium" : "t2.micro"
tags = {
Environment = var.environment
}
}
- State Management: Store infrastructure state securely and enable collaboration:
# Remote state configuration
terraform {
backend "s3" {
bucket = "company-terraform-states"
key = "myapp/terraform.tfstate"
region = "us-west-2"
encrypt = true
}
}
- Modularization: Break infrastructure into reusable modules:
module "web_app" {
source = "./modules/web-app"
app_name = "my-application"
environment = "staging"
instance_count = 2
}
Practical Example: Complete CI/CD Infrastructure Pipeline
Let's put it all together with a complete example of a CI/CD pipeline that includes infrastructure automation for a web application:
Directory Structure
project/
├── .github/
│ └── workflows/
│ └── main.yml
├── terraform/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── src/
│ └── [application code]
└── tests/
└── [test code]
GitHub Workflow (main.yml)
name: 'Application CI/CD with Infrastructure'
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
name: 'Test Application'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
infrastructure:
name: 'Deploy Infrastructure'
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-west-2
- name: Terraform Init
run: |
cd terraform
terraform init
- name: Terraform Apply
run: |
cd terraform
terraform apply -auto-approve
- name: Save infrastructure outputs
id: tf-outputs
run: |
cd terraform
echo "::set-output name=app_url::$(terraform output -raw app_url)"
deploy:
name: 'Deploy Application'
needs: infrastructure
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Deploy to infrastructure
env:
APP_URL: ${{ needs.infrastructure.outputs.app_url }}
run: |
# Deploy application to the newly provisioned infrastructure
./deploy.sh ${APP_URL}
- name: Run smoke tests
run: |
# Verify deployment was successful
curl -f ${APP_URL}/health
Terraform Infrastructure (main.tf)
provider "aws" {
region = "us-west-2"
}
resource "aws_s3_bucket" "app_bucket" {
bucket = "my-app-${var.environment}-static"
acl = "public-read"
website {
index_document = "index.html"
error_document = "error.html"
}
}
resource "aws_cloudfront_distribution" "app_distribution" {
origin {
domain_name = aws_s3_bucket.app_bucket.website_endpoint
origin_id = "S3-${aws_s3_bucket.app_bucket.bucket}"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-${aws_s3_bucket.app_bucket.bucket}"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
tags = {
Environment = var.environment
}
}
output "app_url" {
value = "https://${aws_cloudfront_distribution.app_distribution.domain_name}"
}
Benefits and Challenges
Benefits
- Consistency: Every environment is created from the same code.
- Speed: Provisioning infrastructure takes minutes, not days or weeks.
- Security: Infrastructure vulnerabilities can be patched across all environments quickly.
- Cost Management: Resources can be created when needed and destroyed when not in use.
- Auditability: All changes to infrastructure are tracked in version control.
Challenges
- Learning Curve: Teams need to learn IaC tools and practices.
- State Management: Managing infrastructure state requires careful consideration.
- Testing Complexity: Testing infrastructure changes can be more complicated than application code.
Summary
Infrastructure automation within CI/CD pipelines brings the benefits of DevOps practices to your entire technology stack. By treating infrastructure as code, you gain consistency, speed, and reliability in how you provision and manage computing resources.
The key steps to implementing infrastructure automation in your CI/CD workflow are:
- Choose appropriate IaC tools for your environment (Terraform, Ansible, CloudFormation, etc.)
- Define your infrastructure in code and store it in version control
- Integrate infrastructure provisioning into your CI/CD pipeline
- Test your infrastructure code just like you test application code
- Apply best practices like immutability, modularity, and proper state management
As you grow more confident with infrastructure automation, you'll find opportunities to optimize costs, improve security, and accelerate delivery of your applications.
Additional Resources
- Learn more about Infrastructure as Code concepts
- Explore advanced Terraform techniques
- Study CI/CD pipeline optimization strategies
- Practice creating and managing cloud resources through code
Exercises
- Create a basic Terraform configuration to provision a web server in your preferred cloud platform.
- Set up a GitHub Actions workflow that applies Terraform changes when code is pushed.
- Modify your infrastructure code to support multiple environments (dev, staging, prod).
- Implement a module structure for your infrastructure code to promote reuse.
- Add automated tests for your infrastructure using a tool like Terratest.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)