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 are really helpful for 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.
Another advantage of using Spacelift is that you can manage different infrastructure tools like Ansible, OpenTofu, Terraform, Pulumi, AWS CloudFormation, and even Kubernetes from the same place and combine their stacks with building workflows across tools. Spacelift greatly simplifies and elevates your workflow for all of these tools, and the ability to create dependencies between stacks and passing outputs enables you to make end-to-end deployments with a small change.
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.
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
Spacelift helps you manage the complexities and compliance challenges of using Ansible. It brings with it a GitOps flow, so your infrastructure repository is synced with your Ansible Stacks, and pull requests show you a preview of what they’re planning to change. It also has an extensive selection of policies, which lets you automate compliance checks and build complex multi-stack workflows.