Ansible Infrastructure as Code
Introduction
Infrastructure as Code (IaC) is a modern approach to infrastructure management where infrastructure configurations are defined using code instead of manual processes. Ansible, a powerful automation tool, excels at implementing IaC principles by allowing you to describe your infrastructure using simple, human-readable YAML files.
This guide will introduce you to using Ansible as an Infrastructure as Code tool, explaining the core concepts and providing practical examples to get you started on your automation journey.
What is Infrastructure as Code?
Infrastructure as Code treats infrastructure configuration as software code that can be:
- Version controlled: Track changes over time
- Tested: Validate before deployment
- Reused: Apply the same configuration to multiple environments
- Automated: Eliminate manual, error-prone processes
IaC ensures consistent, reproducible environments and reduces the risk of configuration drift—when manually configured systems gradually become inconsistent due to ad-hoc changes.
Ansible as an IaC Tool
Ansible stands out among IaC tools for several key reasons:
- Agentless architecture: No need to install software on managed nodes
- YAML-based syntax: Easy to read and write, even for beginners
- Idempotent operations: Running the same playbook multiple times produces the same result
- Extensive module library: Built-in support for most infrastructure components
Core Components of Ansible IaC
Inventory Files
Inventory files define the hosts and groups that Ansible manages. These can be static files or dynamic scripts that pull host information from external sources.
# inventory.yml
webservers:
hosts:
web1.example.com:
http_port: 80
web2.example.com:
http_port: 8080
vars:
ansible_user: deploy
databases:
hosts:
db1.example.com:
db2.example.com:
Variables
Variables allow you to customize your infrastructure configuration for different environments or use cases.
# group_vars/webservers.yml
http_port: 80
deploy_path: /var/www/html
app_version: 1.2.3
# host_vars/web1.example.com.yml
http_port: 8080 # Override group variable
Playbooks
Playbooks are the heart of Ansible's IaC approach. They define a series of tasks to configure systems to the desired state.
# webserver-setup.yml
---
- name: Configure webservers
hosts: webservers
become: true
vars:
doc_root: /var/www/html
tasks:
- name: Install Apache
apt:
name: apache2
state: present
update_cache: yes
- name: Start and enable Apache
service:
name: apache2
state: started
enabled: yes
- name: Deploy application
copy:
src: files/index.html
dest: "{{ doc_root }}/index.html"
owner: www-data
group: www-data
mode: '0644'
Roles
Roles provide a framework for fully independent or interdependent collections of variables, tasks, files, templates, and modules.
roles/
webserver/
tasks/
main.yml
handlers/
main.yml
files/
index.html
templates/
httpd.conf.j2
vars/
main.yml
defaults/
main.yml
meta/
main.yml
Example usage in a playbook:
# site.yml
---
- name: Configure webservers
hosts: webservers
become: true
roles:
- webserver
- { role: monitoring, when: monitoring_enabled }
Practical Example: Complete Infrastructure Deployment
Let's walk through a practical example of using Ansible to define and deploy a complete web application infrastructure.
Step 1: Define Your Inventory
# inventory.yml
---
all:
children:
webservers:
hosts:
web1:
ansible_host: 192.168.1.10
web2:
ansible_host: 192.168.1.11
loadbalancers:
hosts:
lb1:
ansible_host: 192.168.1.5
databases:
hosts:
db1:
ansible_host: 192.168.1.20
vars:
ansible_user: deploy
ansible_ssh_private_key_file: ~/.ssh/id_rsa
Step 2: Create Roles for Each Component
Let's create a role for our web server configuration:
# roles/webserver/tasks/main.yml
---
- name: Install necessary packages
apt:
name:
- nginx
- python3
- python3-pip
state: present
update_cache: yes
- name: Configure Nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/default
owner: root
group: root
mode: '0644'
notify: Restart Nginx
- name: Deploy application code
git:
repo: https://github.com/example/webapp.git
dest: /var/www/html
version: master
notify: Restart Application
And handlers to respond to changes:
# roles/webserver/handlers/main.yml
---
- name: Restart Nginx
service:
name: nginx
state: restarted
- name: Restart Application
command: /var/www/html/scripts/restart.sh
Step 3: Create a Main Playbook
# site.yml
---
- name: Configure databases
hosts: databases
become: true
roles:
- database
- name: Configure web servers
hosts: webservers
become: true
roles:
- webserver
- app_config
- name: Configure load balancers
hosts: loadbalancers
become: true
roles:
- loadbalancer
Step 4: Run Your Infrastructure Deployment
# Command line execution
ansible-playbook -i inventory.yml site.yml
Output example:
PLAY [Configure databases] *************************
TASK [database : Install MySQL] ********************
ok: [db1]
TASK [database : Configure MySQL] ******************
changed: [db1]
PLAY [Configure web servers] **********************
TASK [webserver : Install necessary packages] ******
ok: [web1]
ok: [web2]
TASK [webserver : Configure Nginx] ****************
changed: [web1]
changed: [web2]
TASK [webserver : Deploy application code] *********
changed: [web1]
changed: [web2]
PLAY [Configure load balancers] *******************
TASK [loadbalancer : Install HAProxy] *************
ok: [lb1]
TASK [loadbalancer : Configure HAProxy] ***********
changed: [lb1]
PLAY RECAP ****************************************
db1 : ok=2 changed=1 unreachable=0 failed=0
web1 : ok=3 changed=2 unreachable=0 failed=0
web2 : ok=3 changed=2 unreachable=0 failed=0
lb1 : ok=2 changed=1 unreachable=0 failed=0
Advanced IaC Techniques with Ansible
Infrastructure Visualization
You can create visualizations of your infrastructure using Mermaid diagrams:
Orchestration
Ansible can orchestrate complex deployments with fine-grained control:
# orchestration.yml
---
- name: Database migration
hosts: databases
become: true
tasks:
- name: Backup database
command: /usr/local/bin/backup.sh
- name: Run migrations
command: /usr/local/bin/migrate.sh
- name: Deploy new application version
hosts: webservers
serial: 1 # One server at a time
become: true
tasks:
- name: Pull new code
git:
repo: https://github.com/example/webapp.git
dest: /var/www/html
version: "{{ app_version }}"
- name: Run tests
command: /var/www/html/test.sh
register: test_result
- name: Restart application
service:
name: webapp
state: restarted
when: test_result.rc == 0
- name: Verify deployment
uri:
url: http://localhost/health
return_content: yes
register: health_check
failed_when: "'OK' not in health_check.content"
- name: Rollback if necessary
command: /usr/local/bin/rollback.sh
when: health_check is failed
Environment Templating
Use templates to maintain different configurations for different environments:
# templates/nginx.conf.j2
server {
listen {{ http_port }};
server_name {{ server_name }};
root {{ doc_root }};
{% if env == "production" %}
ssl on;
ssl_certificate {{ ssl_cert_path }};
ssl_certificate_key {{ ssl_key_path }};
{% endif %}
location / {
try_files $uri $uri/ /index.html;
}
}