Immutable Infrastructure Automation with Packer, Ansible, Terraform, and GitHub Actions

Immutable Infrastructure Automation with Packer, Ansible, Terraform, and GitHub Actions

·

11 min read

The field of DevOps is dynamic and in constant evolution. New tools and techniques being developed to help organizations streamline their processes and improve their infrastructure management. Ansible and Terraform are two tools that have gained widespread popularity for their ability to automate and manage infrastructure. Packer is used to create identical machine images for multiple cloud platforms, and GitHub actions is a CI/CD platform that lets you run workflows when a push, pull requests, and release events happen in your repository. In this post, we’ll briefly explore what these tools are, how they differ, and how to integrate them to automate the creation of immutable infrastructure, a concept where servers are never modified after they’re deployed. If changes are required, new servers are built from a common image with the required changes, rather than updating the existing servers.

What is Packer?

Packer is an open-source tool for creating identical machine images for multiple platforms from a single source configuration. It’s lightweight, portable, and command-line driven, making it ideal for continuous delivery pipelines. A common use case is creating golden images for organizations to use in cloud infrastructure. Packer does not replace configuration management like Chef or Puppet. In fact, when building images, Packer is able to use tools like Chef, Puppet or Ansible to install software onto the image. Packer is instrumental in the immutable infrastructure because it allows for the creation of fixed server images that can be quickly deployed, ensuring consistency across environments.

What is Ansible?

Ansible is an open-source automation tool that simplifies configuration management, application deployment, and task automation. It uses a simple, human-readable data serialization format called YAML to define automation tasks as playbooks. The Ansible automation platform is agentless, meaning it doesn’t require any additional software to be installed on the target servers, and it communicates with them over SSH or WinRM.

What is Terraform?

Terraform is an open-source infrastructure-as-code (IaC) tool that lets you build, change, and version your infrastructure safely and efficiently using a declarative language called HCL (HashiCorp Configuration Language).

Terraform vs. Ansible: Key Differences and Use Cases

Both Ansible and Terraform serve as powerful tools in the realm of orchestration and configuration management, each with its unique strengths. Terraform, often misconstrued as a configuration management tool, is more aptly an orchestration tool. It excels at creating, managing, and decommissioning new cloud infrastructure and resources such as virtual machines, networks, and storage. Its plugin-based architecture allows it to manage infrastructure across a wide range of cloud providers, making it particularly versatile.

Conversely, Ansible shines in managing the configuration of individual systems and deploying applications. It can automate tasks like package installation, service management, and network settings configuration. So, while Terraform lays the groundwork by provisioning the infrastructure, Ansible steps in to ensure that network devices and systems are properly configured, and applications are correctly deployed.

GitHub Actions

GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate, customize, and execute your software development workflows directly in your repository. GitHub Actions can trigger workflows based on various events such as a push to a branch, a pull request being opened, or a release being published in your repository. This makes it an integral part of many CI/CD pipelines, helping to automate build, test, and deployment tasks.

What is Immutable Infrastructure?

It is a concept where servers are never modified (no ssh access) after they’re deployed. If any configuration change or code change is required, new servers are built from a common image with the required changes, rather than updating the existing servers. Immutable infrastructure model is the underlining principle in autoscaling groups, replicasets, containers, and Kubernetes. Once you do a new deployment it rolls out new pods and containers and simply destroy the existing ones.

Server Images (machine image)

A machine image is a single static unit that contains a pre-configured operating system and installed software which is used to quickly create new running machines. Machine image formats change for each platform. Some examples include AMIs for EC2, VMDK/VMX files for VMware, OVF exports for VirtualBox, etc.

When setting up a new virtual server with a cloud provider, the initial step is to select a base image, such as Ubuntu, RHEL or Windows. Once you’ve specified your preferences and have a blank machine, you install all necessary dependencies.

With the immutable infrastructure approach, you create custom server images with your dependencies and code already deployed. You start with a base like Ubuntu, add your server software, and your application code, and then convert it into an image. This ensures that you’re deploying the exact same image, regardless of when you deploy it, leading to consistency and reliability in your deployments.

In this sample project, we integrate a set of tools to automate the creation of immutable infrastructure. Packer is used to create a server image, with Ansible configuring the image during the baking process. Terraform manages cloud resources, such as spinning up a server using the pre-configured image. GitHub Actions automates and streamlines the entire process, enhancing efficiency. When we need to spin up our application and create new servers, we specify the server image to use in Terraform. This approach allows us to leverage Ansible, Packer, Terraform, and GitHub Actions effectively to manage our infrastructure in an immutable way.

Sample Project

In this sample project, we’re going to implement the immutable infrastructure model, a modern approach in cloud application deployment that involves recreating the entire infrastructure for each deployment, rather than updating existing servers and configurations.

Consider a sample project: a web server with NGINX installed and some minimal HTML code to demonstrate that the server is configured. The goal is to deploy this to AWS. The process involves creating the image and then deploying the infrastructure as a server to our Amazon cloud environment. This approach ensures consistency and reliability in our deployments.

Project Prerequisites

1.Local machine set up: Install and configure Git, Terraform, and vscode. You can setup Packer, Ansible and Terraform in your GitHub actions workflows.

2.IAM Role: Start by creating a new role in AWS IAM, ensuring it has the necessary permissions for Packer. It’s important that this IAM role is configured with the appropriate trust relationships, allowing AWS STS (Security Token Service) to assume the role. Finally, in your Packer template, utilize the assume_role block to specify the ARN of the IAM role.

3.For the GitHub repository secrets, you need to configure valid credentials. These should include the authentication access key ID, secret access key, SSH private key, as well as the S3 bucket name and key for the S3 backend.

4.Regarding the project structure, please arrange your project directories and files as illustrated below.

Packer Template and Ansible playbooks

The ami-builder directory contains the various files (ami.pkr.hcl) Packer will use to bake the machine image. Ansible installs and configures Nginx and the HTML code on the image during baking. It is good to assign tags to the baked image. This tag enables Terraform to identify the latest image available and use it to spin up your application and server (EC2 instance).

Terraform Configuration files

Terraform provisions various cloud resources, including a virtual private cloud (VPC), subnets, an internet gateway, and security groups, and spins up our application and server using the pre-configured image.

GitHub Actions CI/CD Pipeline

Create a file “.github/workflows/packer-ansible-terraform.yml” in your GitHub repository. The GitHub workflow illustrated below automates the provisioning of immutable infrastructure. It consists of two jobs: packer-ansible and Terraform-commands. The packer-ansible job sets up Packer and Ansible, and builds an Amazon Machine Image (AMI). The Terraform-commands job, which depends on packer-ansible, sets up Terraform and applies a Terraform plan to provision infrastructure. The workflow is triggered on push events to the main branch in your repository.

name: Automate provisioning of Immutable Infrastructure

on:
  push:
    branches:
      - main

jobs:
  packer-ansible:
    runs-on: ubuntu-latest
    defaults:
      run:
        shell: bash
        working-directory: ./ami-builder/

    steps:
      - name: Configure AWS credentials from AWS Account
        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: ${{ secrets.AWS_REGION }}

      - name: Checkout code
        uses: actions/checkout@v2

      - name: Setup Ansible
        run: |
          sudo apt update
          sudo apt install -y ansible

      - name: Set up Packer
        env:
          PACKER_LOG: 1
        id: setup
        uses: hashicorp/setup-packer@main

      - name: Packer Init
        id: init
        run: packer init 

      - name: Packer Format
        id: fmt
        run: packer fmt 

      - name: Packer Validate
        id: validate
        run: packer validate 

      - name: Build Image
        id: build
        run: packer build 
        if: failure()
        run: echo "Build Image step failed"

  Terraform-commands:
    runs-on: ubuntu-latest
    needs: packer-ansible
    defaults:
      run:
        shell: bash
        working-directory: ./terraform
    env:
      TF_LOG: INFO
      TF_VAR_web-key: ${{ secrets.SSH_PRIVATE_KEY }}

    steps:
      - name: Configure AWS credentials from AWS Account
        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: ${{ secrets.AWS_REGION }}

      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up Terraform
        uses: hashicorp/setup-terraform@v2
        id: terraform
        with:
           terraform_version: 1.5.5

      - name: Terraform Init
        id: init
        env:
          AWS_BUCKET_NAME: ${{ secrets.AWS_BUCKET_NAME }}
          AWS_BUCKET_KEY_NAME: ${{ secrets.AWS_BUCKET_KEY_NAME }}
        run: |
           rm -rf .terraform  
           terraform init -backend-config="bucket=${AWS_BUCKET_NAME}" \
                          -backend-config="key=${AWS_BUCKET_KEY_NAME}" \
                          -backend-config="region=${AWS_REGION}"

      - name: Terraform Format
        id: format
        run: terraform fmt 

      - name: Terraform Validate
        id: validate
        run: terraform validate 

      - name: Terraform Plan
        id: plan
        run: terraform plan -input=false

      - name: Terraform Apply
        id: apply
        run: terraform apply -auto-approve -input=false
        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
  • GitHub actions showing runs of Packer Image Build and Terraform

  • AWS Account showing pre-configured AMI. Check the image tag

    • AWS Account showing pre-configured AMI snapshot. Check image tag

      • AWS Account with application and server running

          • Enter the IP address on the browser to confirm that your application and server is running

            Challenges Encountered

            1. Packer Build Failures: Packer was unable to locate the target due to a missing configuration.

            2. Ansible Provisioner Issues: Ansible couldn’t find the path to the playbook because an incorrect path was specified in the Packer template.

To catch these errors early in the development process, I implemented extensive logging. Both Packer and Terraform provide built-in logging that can be enabled for debugging. By setting PACKER_LOG=1 and TF_LOG=INFO, I was able to generate detailed logs that helped identify these issues. Through careful debugging, patience, and persistence, I ensured that the Packer template was correctly configured, all files were in the correct locations, and the correct path to the playbook was specified in the Packer template.

Immutable Infrastructure Approach Strategy

To explore the immutable infrastructure model, use this strategy:

  1. Create a base image: Use Packer to create a base image with the core software and necessary dependencies. For instance, you create an Amazon Machine Image (AMI) with your base operating system, core packages, and other common software for all instances.

  2. During the baking process, configure the AMI with Ansible: Use Ansible to apply server-specific configurations on your base image.

  3. Infrastructure as Code: Use Terraform to create and manage your infrastructure resources like VPCs, subnets, security groups, and instances. Instead of modifying existing instances, create new instances with the updated configuration when needed.

  4. Automation: Use a Continuous Integration/Continuous Deployment (CI/CD) pipeline to automate the deployment process. When changes are made to your infrastructure or server configurations, your pipeline can automatically build new images with Packer, configure the image with Ansible and, deploy new resources with Terraform

  5. Use version control: Track your Terraform and Ansible configurations in a version control system like Git. This allows you to maintain a history of your infrastructure changes and easily roll back to previous configurations if needed.

  6. Decommission: As you deploy new instances with updated configurations, don’t forget to clean up the old instances that are no longer needed. You can use Terraform to destroy old resources or create scripts to terminate instances that are no longer in use automatically.

The benefits of immutable infrastructure approach

  1. Pre-configured Images: Immutable infrastructure involves creating server images with dependencies and application code already deployed. This allows for new servers to be spun up quickly and efficiently.

  2. Consistency: By deploying the exact same image across servers, immutable infrastructure ensures that the same versions of all software are running on all machines, providing a consistent environment.

  3. Scalability: Immutable infrastructure simplifies the process of scaling up. If there’s a need for more resources, new servers can be spun up from the pre-configured image.

  4. Change Management: Changes are made by replacing components rather than modifying them in place. This means changes are made as code changes, with the server being rebuilt with all its dependencies and then deployed. This mitigates configuration drift*.*

  5. Reliability: Immutable infrastructure improves reliability because every instance is identical and changes are made in a controlled manner. This gives confidence in what is deployed.

Conclusion: Immutable Infrastructure tools

  • Packer is key to creating fixed server images for quick deployment, ensuring environment consistency.

  • Terraform maintains the infrastructure-as-code, a crucial aspect of immutable infrastructure.

  • Ansible, with its push-based system, is effective for setting up and maintaining immutable servers.

  • GitHub Actions automates various stages of the delivery pipeline, essential for testing and deploying immutable infrastructures.

  • Git tracks infrastructure changes, providing a history of modifications.

  • Other tools like Jenkins, Kubernetes, and Docker can also be integrated at various levels to further enhance the immutable infrastructure model.

Each tool plays a unique role, and their integration provides a robust and efficient approach to implementing the immutable infrastructure model.

References

To further learn about and explore immutable infrastructure, you can refer to these valuable resources:

HashiCorp Packer

HashiCorp Terraform

Ansible documentation

devopscube

GitHub Actions documentation

Reminder

Thank you for following up to this point. I hope you found the article helpful, consider showing your support with a few claps! 👏👏👏