Ansible is one of the most popular tools for managing cloud and on-premises infrastructure. GitHub Actions allows users to automate the software development and deployment tasks defined in a GitHub repository. Combining them will enable you to automate and streamline your Ansible deployments, leveraging continuous integration and continuous delivery (CI/CD) principles.
What we will cover:
Ansible is an open-source, battle-tested automation tool known for its simplicity and powerful capabilities. It is a flexible, powerful tool for automating infrastructure management and configuration tasks, making it an excellent choice for configuration management, infrastructure provisioning, and application deployment use cases.
Ansible is targeted chiefly at IT operators, administrators, and decision-makers, helping them achieve operational excellence across their entire infrastructure ecosystem. Backed by RedHat and a loyal open-source community, Ansible can operate across hybrid clouds, on-prem infrastructure, and IoT. It’s an engine that can greatly improve the efficiency and consistency of your IT environments.
GitHub Actions is a robust CI/CD platform provided by GitHub. It allows you to automate your software workflows directly from your GitHub repository, enabling tasks such as building, testing, and deploying code. GitHub Actions uses YAML files called “Workflows” to define the sequence of tasks to be executed, triggered by events like code pushes, pull requests, or scheduled times.
Workflows
Workflows are configurable sets of processes that run jobs. They are written in YAML in the .github/workflows
directory and define when jobs should be triggered. GitHub repositories can have multiple workflows, each with their specific configuration. For more information about workflows, see Using workflows.
Events
Events are activities that could trigger a workflow run. Examples include creating a pull request, pushing new code, and opening an issue. For a complete list of events that can trigger workflows, see Events that trigger workflows in the documentation.
Jobs
Jobs define the different steps and processes that should be executed to complete a workflow. Each step in a job executes a specific script or triggers a predefined action. Steps in a job are executed in order. By default, different jobs have no dependencies and run in parallel. You can customize this behavior and define dependencies between jobs. For more information about jobs, see the section on using jobs in a workflow in the documentation.
Actions
Actions are custom applications or scripts that perform a frequently used task and need to be packaged for reuse. You can write down your own GitHub action, but you can also reuse actions from others in the GitHub Marketplace. For more information, see Creating actions in the documentation.
Runners
Runners are virtual machines that effectively execute the code and actions defined in your workflows when triggered.
Learn more about GitHub Actions with this GitHub Actions tutorial.
Download the Build vs. Buy Guide to Scaling Infrastructure as Code
Automating Ansible deployments with GitHub Actions or any other tool provides several benefits that enhance the efficiency and reliability of software development workflows. Let’s look at some of the key advantages:
- Consistent and reproducible deployments: By automating Ansible Playbook execution through GitHub Actions, you can ensure consistent and reproducible deployments across your infrastructure. Ansible’s idempotent nature ensures that tasks are executed consistently and predictably, reducing the risk of operations. GitHub Actions enables automated testing, linting, and deployment of your Ansible Playbooks whenever changes are pushed to your repository.
- Audit trail and traceability: GitHub Actions provides a detailed log of deployments, making it easier to track changes and troubleshoot issues. Thus, it effectively creates an audit trail for your infrastructure changes.
- Scalability and parallelization: GitHub Actions allows you to run Ansible Playbooks in a parallel and automated fashion across multiple runner instances, enabling you to manage large-scale infrastructures efficiently.
In this section, we examine the different parts of developing a solution to manage and deploy Ansible Playbooks with CI/CD and GitHub Actions.
1. Create and organize Ansible Playbooks in a repository
Start by organizing your Ansible playbooks in a structured manner within a GitHub repository. Check out Quickstart for repositories to create a new repository.
You can organize your Ansible code repositories in multiple ways, so look for the one that suits your needs. This is an example of a well-organized Ansible directory structure that you can modify:
inventory/
production # inventory file for production servers
staging # inventory file for staging environment
testing # inventory file for testing environment
group_vars/
group1.yml # variables for particular groups
group2.yml
host_vars/
host1.yml # variables for particular systems
host2.yml
library/ # Store here any custom modules (optional)
module_utils/ # Store here any custom module_utils to support modules (optional)
filter_plugins/ # Store here any filter plugins (optional)
master.yml # master playbook
webservers.yml # playbook for webserver tier
dbservers.yml # playbook for dbserver tier
roles/
example_role/ # this hierarchy represents a "role"
tasks/ #
main.yml # <-- tasks file can include smaller files if warranted
handlers/ #
main.yml # <-- handlers file
templates/ # <-- files for use with the template resource
ntp.conf.j2 # <------- templates end in jinja2
files/ #
bar.txt # <-- files for use with the copy resource
foo.sh # <-- script files for use with the script resource
vars/ #
main.yml # <-- variables associated with this role
defaults/ #
main.yml # <-- default lower priority variables for this role
meta/ #
main.yml # <-- role dependencies
library/ # roles can also include custom modules
module_utils/ # roles can also include custom module_utils
lookup_plugins/ # or other types of plugins, like lookup in this case
monitoring/ # same kind of structure as "common" was above, done for the monitoring role
2. Create a GitHub Actions Workflow
To trigger and manage GitHub Actions workflows, define a YAML file in your repository’s .github/workflows
directory. A GitHub Actions Workflow is a configurable automated process with one or more jobs that need to be executed.
Create a file named lint_ansible.yml
and add the workflow below:
lint_ansible.yml
name: Ansible files & Deployment
on:
push:
paths:
- 'playbooks/**'
pull_request:
paths:
- 'playbooks/**'
jobs:
ansible-lint:
uses: ansible/ansible-content-actions/.github/workflows/ansible_lint.yaml@main
with:
args: '-p playbooks'
The on
parameter defines which events can trigger the workflow. For a list of available events, see Events that trigger workflows.
For this example, the workflow will be triggered when changes are made in files under the playbooks
directory via a code push to any branch on this repository or when someone creates a pull request.
The jobs
parameter defines the various jobs that run in runner environments to complete the workflow. For our example, we are running the ansible-lint
job that checks playbooks for best practices, syntax issues, and behavior that could be improved. In this example, we opt to lint ansible files only on the playbooks
directory by using the flag -p
.
Now, with the workflow for linting our Ansible playbooks in place, let’s push a commit with an example playbook deploy_web_server.yml
under the playbooks
directory and see the GitHub Action workflow get triggered.
deploy_web_server.yml
- name: Install and Configure Nginx
hosts: all
become: true
pre_tasks:
- name: Set SSH user for Ubuntu
ansible.builtin.set_fact:
ansible_user: ubuntu
when: ansible_os_family == "Debian"
tasks:
- name: Install Nginx Web Server
ansible.builtin.apt:
update_cache: true
name: nginx
state: present
- name: Create index.html
ansible.builtin.copy:
dest: "/var/www/html/index.html"
content: |
<!DOCTYPE html>
<html>
<head><title>Server Details</title></head>
<body>
<h1>Served from {{ ansible_hostname }}</h1>
</body>
</html>
mode: '0644'
- name: Ensure Nginx is running and enabled
ansible.builtin.service:
name: nginx
state: started
enabled: true
We see that the workflow has been executed successfully, which means our file syntax follows best practices:
3. Handle sensitive data in GitHub Actions
You can leverage Environment and Repository GitHub Secrets to avoid storing sensitive information in configuration and code files. In this example, let’s create two repository secrets.
Go to Repository → Settings → Secrets and variables → Actions. Here, you have to select the New repository secret option and configure the secret ANSIBLE_USER
storing the value ubuntu
:
Similarly, create a secret SSH_PRIVATE_KEY
and store the content of the private SSH key to connect to the remote target server.
4. Deploy the Ansible Playbook with GitHub Actions
Next, define another workflow file to automate running an Ansible Playbook against remote targets. Create a new file, deploy_playbook.yml
, under the .github/workflows
directory.
deploy_playbook.yml
name: Deploy with Ansible
on:
pull_request:
branches:
- main
types: [closed]
jobs:
deploy:
name: Deploy Ansible Playbook
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up SSH
run: |
echo "${{ secrets.SSH_PRIVATE_KEY }}" > private_key.pem
chmod 600 private_key.pem
- name: Install Ansible
shell: bash
run: |
sudo apt update
sudo apt install -y ansible
- name: Run Ansible Playbook
env:
ANSIBLE_USER: ${{ secrets.ANSIBLE_USER }}
ANSIBLE_HOST_KEY_CHECKING: False
run: |
ansible-playbook -i inventory/hosts.ini playbooks/deploy_web_server.yml --private-key private_key.pem -u ${{ secrets.ANSIBLE_USER }}
In this workflow, set up the SSH key, install Ansible on the runner, and finally run the Ansible Playbook against the remote targets.
We only trigger this workflow when pull requests are closed on the main
branch and only if the GitHub event is a pull request merge.
Let’s also create a GitHub Environment named production
to enable manual approvals for deployments. On your repository, go to Settings → Environments and select New Environment:
On the next screen, opt for a required manual approval on this environment:
We are ready to deploy. Push code to a new branch and create a pull request against the main
branch to trigger this workflow. For the needs of this demo, let’s update the deploy_web_server.yml
playbook to kick-start the workflow:
After the linting finishes, we can merge the pull request and proceed. Go to Actions, and you should see the deployment workflow waiting for approval to continue:
Select Approve and deploy:
Finally, we have configured automatic linting for all our Ansible Playbooks, and we can automatically execute them against our server targets via GitHub Actions. See the action completed successfully:
Compared to building a custom and production-grade infrastructure management pipeline with a CI/CD tool like GitHub Actions, adopting a collaborative infrastructure delivery tool like Spacelift feels a bit like cheating.
Spacelift’s vibrant ecosystem and excellent GitOps flow can greatly assist you in managing and orchestrating Ansible. By introducing Spacelift on top of Ansible, you can easily create custom workflows based on pull requests and apply any necessary compliance checks for your organization.
With Spacelift, you get:
- Better playbook automation – Manage the execution of Ansible playbooks from one central location.
- Inventory observability – View all Ansible-managed hosts and related playbooks, with clear visual indicators showing the success or failure of recent runs.
- Playbook run insights – Audit Ansible playbook run results with detailed insights to pinpoint problems and simplify troubleshooting.
- Policies – Control what kind of resources engineers can create, what parameters they can have, how many approvals you need for a run, what kind of task you execute, what happens when a pull request is open, and where to send your notifications
- Stack dependencies – Build multi-infrastructure automation workflows with dependencies, having the ability to build a workflow that, for example, generates your EC2 instances using Terraform and combines it with Ansible to configure them
- Self-service infrastructure via Blueprints, or Spacelift’s Kubernetes operator – Enable your developers to do what matters – developing application code while not sacrificing control
- Creature comforts such as contexts (reusable containers for your environment variables, files, and hooks), and the ability to run arbitrary code
- Drift detection and optional remediation
If you want to learn more about using Spacelift with Ansible, check our documentation, read our Ansible guide, or book a demo with one of our engineers.
Would you like to see this in action – or just want a tl;dr? Check out this video I put together showing you Spacelift’s new Ansible functionality:
This blog post combined GitHub Actions with Ansible to run playbooks via CI/CD. We explored the benefits of running Ansible in an automated fashion, and we walked through a complete example of configuring a code repository with GitHub Actions to lint Ansible files and execute playbooks against remote hosts.
Thanks for reading, and I hope you enjoyed this as much as I did.
Manage Ansible Better with Spacelift
Managing large-scale playbook execution is hard. Spacelift enables you to automate Ansible playbook execution with visibility and control over resources, and seamlessly link provisioning and configuration workflows.