Prerequisites
- Basic understanding of programming concepts ๐
- Python installation (3.8+) ๐
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write clean, Pythonic code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on Ansible: Configuration Management! ๐ In this guide, weโll explore how to automate your infrastructure and manage configurations at scale using Ansible with Python.
Have you ever wished you could configure hundreds of servers with just a few lines of code? ๐ค Or ensure that your entire infrastructure stays consistent without manual intervention? Thatโs exactly what Ansible makes possible! Whether youโre managing a handful of servers or orchestrating a massive cloud deployment, Ansible is your trusted automation companion. ๐ค
By the end of this tutorial, youโll feel confident automating infrastructure tasks and creating reusable configuration playbooks! Letโs dive in! ๐โโ๏ธ
๐ Understanding Ansible
๐ค What is Ansible?
Ansible is like having a smart robot assistant ๐ค that can configure and manage all your servers simultaneously. Think of it as a conductor orchestrating a symphony ๐ผ - you write the music (playbooks), and Ansible ensures every instrument (server) plays its part perfectly!
In technical terms, Ansible is an agentless automation tool that uses SSH to configure and manage systems. This means you can:
- โจ Configure servers without installing agents
- ๐ Deploy applications consistently across environments
- ๐ก๏ธ Ensure security compliance automatically
- ๐ Manage complex workflows with simple YAML
๐ก Why Use Ansible?
Hereโs why DevOps engineers love Ansible:
- Agentless Architecture ๐: No need to install software on target systems
- Human-Readable YAML ๐ป: Write infrastructure as code that anyone can understand
- Idempotent Operations ๐: Run playbooks multiple times safely
- Massive Module Library ๐ง: Thousands of pre-built modules for common tasks
- Python Integration ๐: Extend Ansible with custom Python modules
Real-world example: Imagine managing a fleet of web servers ๐. With Ansible, you can update all servers, install security patches, and deploy new code versions with a single command!
๐ง Basic Syntax and Usage
๐ Your First Ansible Playbook
Letโs start with a friendly example:
# ๐ Hello, Ansible!
---
- name: Configure Web Servers ๐
hosts: webservers
become: yes
tasks:
- name: Install Python packages ๐
pip:
name: "{{ item }}"
state: present
loop:
- flask
- gunicorn
- requests
- name: Create application directory ๐
file:
path: /opt/myapp
state: directory
mode: '0755'
- name: Deploy application code ๐
copy:
src: app.py
dest: /opt/myapp/app.py
mode: '0644'
๐ก Explanation: This playbook installs Python packages, creates directories, and deploys code. The YAML format makes it super readable!
๐ฏ Python Integration with Ansible
Hereโs how to use Ansible from Python:
# ๐๏ธ Using Ansible API in Python
import ansible_runner
# ๐จ Run a playbook programmatically
def deploy_application(environment):
"""Deploy our app using Ansible! ๐"""
result = ansible_runner.run(
playbook='deploy.yml',
inventory='inventory.ini',
extravars={
'env': environment,
'version': '2.0',
'emoji': '๐'
}
)
# ๐ Check the results
if result.status == 'successful':
print(f"โ
Deployment to {environment} completed!")
return True
else:
print(f"โ Deployment failed: {result.rc}")
return False
# ๐ Dynamic inventory generation
def generate_inventory():
"""Create inventory from cloud provider ๐ฉ๏ธ"""
inventory = {
'webservers': {
'hosts': ['web1.example.com', 'web2.example.com'],
'vars': {
'ansible_user': 'ubuntu',
'app_port': 8000
}
}
}
return inventory
๐ก Practical Examples
๐ Example 1: E-Commerce Platform Deployment
Letโs build a complete deployment system:
# ๐๏ธ E-commerce deployment automation
import os
import json
from ansible_runner import Runner
from datetime import datetime
class EcommerceDeployer:
"""Deploy our shopping platform! ๐"""
def __init__(self, environment):
self.environment = environment
self.timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
self.deployment_id = f"deploy_{environment}_{self.timestamp}"
def pre_deployment_checks(self):
"""Run safety checks before deployment ๐ก๏ธ"""
print("๐ Running pre-deployment checks...")
# ๐ฏ Check disk space
check_result = ansible_runner.run(
module='shell',
module_args='df -h | grep -E "/$|/var"',
inventory={'all': {'hosts': self.get_hosts()}}
)
# ๐พ Verify database backup
backup_check = ansible_runner.run(
module='stat',
module_args='path=/backup/latest.sql',
inventory={'all': {'hosts': ['db.example.com']}}
)
return check_result.status == 'successful'
def deploy_backend(self):
"""Deploy Python backend services ๐"""
playbook_content = """
---
- name: Deploy E-commerce Backend ๐
hosts: app_servers
serial: 2 # Rolling deployment
vars:
app_name: ecommerce_api
app_version: "{{ version }}"
tasks:
- name: Pull latest code ๐ฆ
git:
repo: https://github.com/company/ecommerce.git
dest: /opt/{{ app_name }}
version: "{{ app_version }}"
- name: Install dependencies ๐
pip:
requirements: /opt/{{ app_name }}/requirements.txt
virtualenv: /opt/{{ app_name }}/venv
- name: Run database migrations ๐๏ธ
shell: |
cd /opt/{{ app_name }}
source venv/bin/activate
python manage.py migrate
run_once: true
- name: Collect static files ๐จ
shell: |
cd /opt/{{ app_name }}
source venv/bin/activate
python manage.py collectstatic --noinput
- name: Restart application โป๏ธ
systemd:
name: "{{ app_name }}"
state: restarted
daemon_reload: yes
- name: Health check ๐ฅ
uri:
url: "http://{{ inventory_hostname }}:8000/health"
status_code: 200
retries: 5
delay: 10
"""
# ๐ฎ Execute the playbook
result = ansible_runner.run(
playbook=playbook_content,
inventory=self.get_inventory(),
extravars={'version': 'v2.1.0'}
)
return result.status == 'successful'
def configure_nginx(self):
"""Update Nginx configuration ๐"""
nginx_config = """
server {
listen 80;
server_name shop.example.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /static {
alias /opt/ecommerce_api/static;
expires 30d;
}
}
upstream backend {
{% for server in app_servers %}
server {{ server }}:8000;
{% endfor %}
}
"""
# ๐ Deploy Nginx config
result = ansible_runner.run(
module='template',
module_args={
'src': nginx_config,
'dest': '/etc/nginx/sites-available/ecommerce'
},
inventory={'webservers': {'hosts': ['nginx.example.com']}}
)
return result.status == 'successful'
# ๐ฎ Let's use it!
deployer = EcommerceDeployer('production')
if deployer.pre_deployment_checks():
print("โ
Pre-checks passed!")
if deployer.deploy_backend():
print("๐ Backend deployed successfully!")
๐ฎ Example 2: Game Server Fleet Management
Letโs manage a fleet of game servers:
# ๐ Game server management system
import ansible_runner
import asyncio
from typing import List, Dict
class GameServerManager:
"""Manage multiplayer game servers! ๐ฎ"""
def __init__(self):
self.regions = ['us-east', 'us-west', 'eu-west', 'asia-pacific']
self.server_configs = {
'minecraft': {'port': 25565, 'memory': '4G', 'emoji': 'โ๏ธ'},
'rust': {'port': 28015, 'memory': '8G', 'emoji': '๐ฆ'},
'valheim': {'port': 2456, 'memory': '6G', 'emoji': 'โ๏ธ'}
}
def provision_game_server(self, game_type: str, region: str):
"""Spin up a new game server ๐"""
config = self.server_configs.get(game_type)
if not config:
print(f"โ Unknown game type: {game_type}")
return False
playbook = f"""
---
- name: Provision {game_type.title()} Server {config['emoji']}
hosts: "{region}_hosts"
vars:
game_type: {game_type}
server_port: {config['port']}
memory_limit: {config['memory']}
tasks:
- name: Install Docker ๐ณ
package:
name: docker.io
state: present
- name: Pull game server image ๐ฆ
docker_image:
name: "gameservers/{{{{ game_type }}}}:latest"
source: pull
- name: Create server data directory ๐
file:
path: "/opt/gameservers/{{{{ game_type }}}}"
state: directory
mode: '0755'
- name: Launch game server ๐ฎ
docker_container:
name: "{{{{ game_type }}}}_server"
image: "gameservers/{{{{ game_type }}}}:latest"
state: started
restart_policy: always
ports:
- "{{{{ server_port }}}}:{{{{ server_port }}}}"
env:
SERVER_NAME: "Awesome {{{{ game_type|title }}}} Server {config['emoji']}"
MAX_PLAYERS: "100"
MEMORY: "{{{{ memory_limit }}}}"
volumes:
- "/opt/gameservers/{{{{ game_type }}}}:/data"
- name: Configure firewall ๐ก๏ธ
ufw:
rule: allow
port: "{{{{ server_port }}}}"
proto: tcp
- name: Setup monitoring ๐
template:
src: prometheus_game_exporter.j2
dest: /etc/prometheus/exporters/game_{{{{ game_type }}}}.yml
"""
# ๐ฏ Run the provisioning
result = ansible_runner.run(
playbook=playbook,
inventory=self.generate_cloud_inventory(region)
)
if result.status == 'successful':
print(f"โ
{game_type.title()} server launched in {region}! {config['emoji']}")
return True
return False
def scale_servers(self, game_type: str, action: str = 'up'):
"""Scale game servers based on demand ๐"""
scaling_playbook = """
---
- name: Auto-scale Game Servers ๐
hosts: localhost
tasks:
- name: Check current player count ๐ฅ
uri:
url: "http://metrics.internal/api/players/{{ game_type }}"
register: player_stats
- name: Calculate required servers ๐งฎ
set_fact:
required_servers: "{{ (player_stats.json.total / 50) | round(0, 'ceil') | int }}"
current_servers: "{{ groups[game_type + '_servers'] | length }}"
- name: Scale up if needed ๐
include_tasks: provision_server.yml
loop: "{{ range(current_servers|int, required_servers|int) | list }}"
when: action == 'up' and current_servers|int < required_servers|int
- name: Scale down if needed ๐
docker_container:
name: "{{ game_type }}_server_{{ item }}"
state: stopped
loop: "{{ range(required_servers|int, current_servers|int) | list }}"
when: action == 'down' and current_servers|int > required_servers|int
"""
result = ansible_runner.run(
playbook=scaling_playbook,
extravars={'game_type': game_type, 'action': action}
)
return result.status == 'successful'
# ๐ฎ Test our game server manager!
manager = GameServerManager()
manager.provision_game_server('minecraft', 'us-east')
manager.scale_servers('minecraft', 'up')
๐ Advanced Concepts
๐งโโ๏ธ Custom Ansible Modules in Python
When youโre ready to level up, create custom modules:
# ๐ฏ Custom Ansible module for advanced operations
#!/usr/bin/python
from ansible.module_utils.basic import AnsibleModule
import requests
import json
def main():
"""Custom module for API deployments ๐"""
# ๐ Define module arguments
module_args = dict(
api_endpoint=dict(type='str', required=True),
deployment_config=dict(type='dict', required=True),
validate_cert=dict(type='bool', default=True),
emoji_mode=dict(type='str', default='๐', choices=['๐', '๐', 'โจ'])
)
# ๐๏ธ Create the module
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
# ๐จ Extract parameters
api_endpoint = module.params['api_endpoint']
config = module.params['deployment_config']
emoji = module.params['emoji_mode']
# ๐ Check mode
if module.check_mode:
module.exit_json(changed=True, msg=f"Would deploy to {api_endpoint} {emoji}")
try:
# ๐ Perform deployment
response = requests.post(
f"{api_endpoint}/deploy",
json=config,
verify=module.params['validate_cert']
)
if response.status_code == 200:
module.exit_json(
changed=True,
msg=f"Deployment successful {emoji}",
deployment_id=response.json().get('id'),
status='completed'
)
else:
module.fail_json(
msg=f"Deployment failed: {response.text}",
status_code=response.status_code
)
except Exception as e:
module.fail_json(msg=f"Error during deployment: {str(e)}")
if __name__ == '__main__':
main()
๐๏ธ Dynamic Inventory with Python
For the brave developers - dynamic cloud inventory:
# ๐ Dynamic inventory provider
import boto3
import json
from typing import Dict, List
class CloudInventory:
"""Generate Ansible inventory from cloud providers ๐ฉ๏ธ"""
def __init__(self):
self.inventory = {
'_meta': {'hostvars': {}},
'all': {'children': ['ungrouped']}
}
def fetch_aws_instances(self) -> Dict:
"""Get EC2 instances from AWS โ๏ธ"""
ec2 = boto3.client('ec2')
# ๐ Find running instances
response = ec2.describe_instances(
Filters=[{'Name': 'instance-state-name', 'Values': ['running']}]
)
for reservation in response['Reservations']:
for instance in reservation['Instances']:
# ๐ท๏ธ Get instance details
instance_id = instance['InstanceId']
private_ip = instance.get('PrivateIpAddress')
public_ip = instance.get('PublicIpAddress')
tags = {tag['Key']: tag['Value'] for tag in instance.get('Tags', [])}
# ๐ฏ Group by role tag
role = tags.get('Role', 'ungrouped')
if role not in self.inventory:
self.inventory[role] = {'hosts': []}
# โจ Add to inventory
hostname = tags.get('Name', instance_id)
self.inventory[role]['hosts'].append(hostname)
# ๐ Set host variables
self.inventory['_meta']['hostvars'][hostname] = {
'ansible_host': public_ip or private_ip,
'ec2_instance_id': instance_id,
'environment': tags.get('Environment', 'unknown'),
'emoji': self._get_role_emoji(role)
}
return self.inventory
def _get_role_emoji(self, role: str) -> str:
"""Assign emojis to server roles ๐จ"""
emoji_map = {
'web': '๐',
'database': '๐๏ธ',
'cache': 'โก',
'queue': '๐ฌ',
'monitoring': '๐',
'api': '๐'
}
return emoji_map.get(role, '๐ฅ๏ธ')
def generate_inventory(self) -> str:
"""Generate final inventory JSON ๐"""
self.fetch_aws_instances()
return json.dumps(self.inventory, indent=2)
# ๐ฎ Use the dynamic inventory
inventory_gen = CloudInventory()
print(inventory_gen.generate_inventory())
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Not Using Check Mode
# โ Wrong way - running destructive tasks without testing!
- name: Delete all data ๐ฐ
file:
path: /important/data
state: absent
# โ
Correct way - test with check mode first!
# Run with: ansible-playbook playbook.yml --check
- name: Remove old logs safely ๐ก๏ธ
file:
path: /var/log/old
state: absent
when: cleanup_confirmed | default(false)
๐คฏ Pitfall 2: Forgetting Idempotency
# โ Dangerous - not idempotent!
def deploy_app_wrong():
"""This will duplicate data every run! ๐ฅ"""
result = ansible_runner.run(
module='lineinfile',
module_args={
'path': '/etc/app.conf',
'line': 'server=prod' # Will add multiple times!
}
)
# โ
Safe - idempotent operations!
def deploy_app_correct():
"""This is safe to run multiple times โ
"""
result = ansible_runner.run(
module='lineinfile',
module_args={
'path': '/etc/app.conf',
'regexp': '^server=', # Replace existing line
'line': 'server=prod'
}
)
๐ ๏ธ Best Practices
- ๐ฏ Use Roles: Organize playbooks into reusable roles
- ๐ Version Control: Keep all playbooks in Git
- ๐ก๏ธ Encrypt Secrets: Use Ansible Vault for sensitive data
- ๐จ Tag Everything: Use tags for selective execution
- โจ Test First: Always use check mode before production
- ๐ Monitor Results: Log and track deployment outcomes
- ๐ Keep It Idempotent: Ensure playbooks can run safely multiple times
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Complete Web App Deployment System
Create an automated deployment system that:
๐ Requirements:
- โ Deploy a Python Flask application
- ๐ท๏ธ Configure Nginx as reverse proxy
- ๐ค Set up PostgreSQL database
- ๐ Schedule automatic backups
- ๐จ Monitor with Prometheus
- ๐ Support blue-green deployments
๐ Bonus Points:
- Add health checks and auto-rollback
- Implement zero-downtime deployments
- Create a deployment dashboard
๐ก Solution
๐ Click to see solution
# ๐ฏ Complete deployment automation system!
import ansible_runner
import time
from datetime import datetime
class WebAppDeployer:
"""Production-ready deployment system ๐"""
def __init__(self, app_name: str):
self.app_name = app_name
self.deployment_id = f"{app_name}_{datetime.now().strftime('%Y%m%d%H%M%S')}"
self.colors = {'blue': '๐ต', 'green': '๐ข'}
self.current_color = 'blue'
def setup_infrastructure(self):
"""Initial infrastructure setup ๐๏ธ"""
setup_playbook = """
---
- name: Infrastructure Setup {{ emoji }}
hosts: all
become: yes
tasks:
- name: Install system packages ๐ฆ
apt:
name:
- python3-pip
- postgresql
- nginx
- prometheus
state: present
update_cache: yes
- name: Create application user ๐ค
user:
name: "{{ app_user }}"
shell: /bin/bash
home: "/home/{{ app_user }}"
- name: Setup PostgreSQL database ๐๏ธ
postgresql_db:
name: "{{ app_name }}"
become_user: postgres
- name: Create database user ๐
postgresql_user:
name: "{{ app_user }}"
password: "{{ db_password }}"
db: "{{ app_name }}"
priv: ALL
become_user: postgres
"""
result = ansible_runner.run(
playbook=setup_playbook,
inventory=self.get_inventory(),
extravars={
'app_name': self.app_name,
'app_user': f"{self.app_name}_user",
'db_password': 'secure_password_here',
'emoji': '๐๏ธ'
}
)
return result.status == 'successful'
def deploy_application(self, version: str, color: str):
"""Deploy app with blue-green strategy ๐"""
deploy_playbook = f"""
---
- name: Deploy {self.app_name} ({color} {self.colors[color]})
hosts: app_servers
vars:
deploy_color: {color}
app_version: {version}
app_port: "{{{{ 8000 if deploy_color == 'blue' else 8001 }}}}"
tasks:
- name: Clone application code ๐ฆ
git:
repo: "https://github.com/company/{{{{ app_name }}}}.git"
dest: "/opt/{{{{ app_name }}}}_{{{{ deploy_color }}}}"
version: "{{{{ app_version }}}}"
- name: Install Python dependencies ๐
pip:
requirements: "/opt/{{{{ app_name }}}}_{{{{ deploy_color }}}}/requirements.txt"
virtualenv: "/opt/{{{{ app_name }}}}_{{{{ deploy_color }}}}/venv"
- name: Run database migrations ๐๏ธ
shell: |
cd /opt/{{{{ app_name }}}}_{{{{ deploy_color }}}}
source venv/bin/activate
flask db upgrade
environment:
FLASK_APP: app.py
DATABASE_URL: "postgresql://{{{{ app_user }}}}:{{{{ db_password }}}}@localhost/{{{{ app_name }}}}"
- name: Create systemd service ๐ง
template:
src: flask_app.service.j2
dest: "/etc/systemd/system/{{{{ app_name }}}}_{{{{ deploy_color }}}}.service"
notify: restart app
- name: Start application ๐
systemd:
name: "{{{{ app_name }}}}_{{{{ deploy_color }}}}"
state: started
enabled: yes
daemon_reload: yes
- name: Health check ๐ฅ
uri:
url: "http://localhost:{{{{ app_port }}}}/health"
status_code: 200
retries: 10
delay: 5
handlers:
- name: restart app
systemd:
name: "{{{{ app_name }}}}_{{{{ deploy_color }}}}"
state: restarted
"""
result = ansible_runner.run(
playbook=deploy_playbook,
inventory=self.get_inventory(),
extravars={
'app_name': self.app_name,
'app_user': f"{self.app_name}_user",
'db_password': 'secure_password_here'
}
)
return result.status == 'successful'
def switch_traffic(self, to_color: str):
"""Switch Nginx to new deployment ๐"""
switch_playbook = """
---
- name: Switch Traffic to {{ to_color }} {{ emoji }}
hosts: nginx_servers
tasks:
- name: Update Nginx upstream ๐
template:
src: nginx_upstream.j2
dest: /etc/nginx/conf.d/{{ app_name }}_upstream.conf
vars:
active_color: "{{ to_color }}"
active_port: "{{ 8000 if to_color == 'blue' else 8001 }}"
- name: Test Nginx configuration โ
command: nginx -t
- name: Reload Nginx ๐
systemd:
name: nginx
state: reloaded
- name: Verify new deployment ๐
uri:
url: "http://{{ inventory_hostname }}/health"
status_code: 200
"""
result = ansible_runner.run(
playbook=switch_playbook,
inventory=self.get_inventory(),
extravars={
'app_name': self.app_name,
'to_color': to_color,
'emoji': self.colors[to_color]
}
)
if result.status == 'successful':
self.current_color = to_color
print(f"โ
Traffic switched to {to_color} {self.colors[to_color]}")
return True
return False
def setup_monitoring(self):
"""Configure Prometheus monitoring ๐"""
monitoring_config = """
global:
scrape_interval: 15s
scrape_configs:
- job_name: '{{ app_name }}'
static_configs:
- targets: ['localhost:8000', 'localhost:8001']
labels:
app: '{{ app_name }}'
emoji: '๐'
"""
result = ansible_runner.run(
module='template',
module_args={
'src': monitoring_config,
'dest': f'/etc/prometheus/conf.d/{self.app_name}.yml'
},
inventory=self.get_inventory(),
extravars={'app_name': self.app_name}
)
return result.status == 'successful'
def perform_deployment(self, version: str):
"""Complete deployment workflow ๐ฏ"""
new_color = 'green' if self.current_color == 'blue' else 'blue'
print(f"๐ Starting deployment of {self.app_name} v{version}")
# Deploy to inactive color
if self.deploy_application(version, new_color):
print(f"โ
Deployed to {new_color} {self.colors[new_color]}")
# Run smoke tests
time.sleep(10) # Wait for app to stabilize
# Switch traffic
if self.switch_traffic(new_color):
print(f"๐ Deployment complete! Now serving from {new_color}")
# Clean up old deployment after 5 minutes
print(f"๐งน Old {self.current_color} deployment will be cleaned in 5 minutes")
return True
print("โ Deployment failed, rolling back...")
return False
# ๐ฎ Test the complete system!
deployer = WebAppDeployer('awesome_shop')
deployer.setup_infrastructure()
deployer.setup_monitoring()
deployer.perform_deployment('v2.0.0')
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Create Ansible playbooks with confidence ๐ช
- โ Automate infrastructure at any scale ๐ก๏ธ
- โ Integrate Python with Ansible for custom solutions ๐ฏ
- โ Implement advanced patterns like blue-green deployments ๐
- โ Build production-ready automation systems! ๐
Remember: Ansible is your automation Swiss Army knife! Itโs here to make your infrastructure management a breeze. ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered Ansible configuration management!
Hereโs what to do next:
- ๐ป Practice with the exercises above
- ๐๏ธ Automate your own infrastructure with Ansible
- ๐ Move on to our next tutorial: Terraform Infrastructure as Code
- ๐ Share your automation success stories!
Remember: Every DevOps expert started by automating one task at a time. Keep automating, keep learning, and most importantly, have fun! ๐
Happy automating! ๐๐โจ