TL;DR
Learn how to master configuration management with Ansible, including playbooks, roles, best practices, and advanced automation techniques
import { MermaidDiagram } from '@/components/mermaid-diagram'
Learn how to effectively manage infrastructure configuration using Ansible. This comprehensive guide covers key concepts, playbook development, role management, and best practices for automation.
graph TB
subgraph "Control Node"
Inventory["Inventory"]
Playbooks["Playbooks"]
Config["ansible.cfg"]
end
subgraph "Managed Nodes"
Linux["Linux Hosts"]
Windows["Windows Hosts"]
Network["Network Devices"]
end
Inventory --> Linux
Inventory --> Windows
Inventory --> Network
Playbooks --> Linux
Playbooks --> Windows
Playbooks --> Network
Config --> Inventory
Config --> Playbooks
style Inventory fill:#3b82f6,stroke:#2563eb,color:white
style Playbooks fill:#3b82f6,stroke:#2563eb,color:white
style Config fill:#3b82f6,stroke:#2563eb,color:white
style Linux fill:#f1f5f9,stroke:#64748b
style Windows fill:#f1f5f9,stroke:#64748b
style Network fill:#f1f5f9,stroke:#64748b
/>
}
$1
Ansible is an agentless automation tool that uses:
1. Declarative Language: YAML-based playbooks
2. Agentless Architecture: SSH/WinRM for communication
3. Idempotency: Consistent state management
4. Extensibility: Modules and plugins
$1
$1
Define your infrastructure inventory:
`` [web_servers]
web1.example.com ansible_host=10.0.1.10
web2.example.com ansible_host=10.0.1.11 [db_servers]
db1.example.com ansible_host=10.0.2.10
db2.example.com ansible_host=10.0.2.11 [load_balancers]
lb1.example.com ansible_host=10.0.3.10
lb2.example.com ansible_host=10.0.3.11 [production:children]
web_servers
db_servers
load_balancers [production:vars]
ansible_user=devops
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible_become=yes
ansible_become_method=sudo
environment=production
monitoring_enabled=true
backup_enabled=true
ini
`inventory/production.ini
$1
Configure Ansible behavior:
` [defaults]
inventory = ./inventory/production.ini
remote_user = devops
private_key_file = ~/.ssh/id_rsa
host_key_checking = False
forks = 20
timeout = 30
log_path = ./logs/ansible.log
roles_path = ./roles
vault_password_file = ./.vault_pass
retry_files_enabled = True
retry_files_save_path = ./retry/ [privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False [ssh_connection]
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r
ssh_args = -o ControlMaster=auto -o ControlPersist=60s [colors]
highlight = white
verbose = blue
warn = bright purple
error = red
debug = dark gray
deprecate = purple
skip = cyan
unreachable = red
ok = green
changed = yellow
diff_add = green
diff_remove = red
diff_lines = cyan
ini
`ansible.cfg
$1
Create a comprehensive role structure:
` roles/
├── common/
│ ├── defaults/
│ │ └── main.yml
│ ├── files/
│ │ ├── motd
│ │ └── sshd_config
│ ├── handlers/
│ │ └── main.yml
│ ├── meta/
│ │ └── main.yml
│ ├── tasks/
│ │ ├── main.yml
│ │ ├── packages.yml
│ │ ├── users.yml
│ │ └── security.yml
│ ├── templates/
│ │ ├── ntp.conf.j2
│ │ └── sysctl.conf.j2
│ └── vars/
│ └── main.yml
├── web_server/
│ ├── defaults/
│ │ └── main.yml
│ ├── files/
│ │ ├── nginx.conf
│ │ └── ssl/
│ ├── handlers/
│ │ └── main.yml
│ ├── meta/
│ │ └── main.yml
│ ├── tasks/
│ │ ├── main.yml
│ │ ├── install.yml
│ │ ├── configure.yml
│ │ └── security.yml
│ ├── templates/
│ │ ├── vhost.conf.j2
│ │ └── php-fpm.conf.j2
│ └── vars/
│ └── main.yml
└── db_server/
├── defaults/
│ └── main.yml
├── files/
│ └── my.cnf
├── handlers/
│ └── main.yml
├── meta/
│ └── main.yml
├── tasks/
│ ├── main.yml
│ ├── install.yml
│ ├── configure.yml
│ └── backup.yml
├── templates/
│ └── backup.sh.j2
└── vars/
└── main.yml
bash
`
$1
Create comprehensive playbooks:
` ---
hosts: all
roles:
- role: common
tags: ['common', 'security'] hosts: web_servers
roles:
- role: web_server
tags: ['web', 'nginx']
vars:
nginx_worker_processes: auto
nginx_worker_connections: 1024
php_version: "8.2"
ssl_enabled: true
monitoring_enabled: true hosts: db_servers
roles:
- role: db_server
tags: ['database', 'mysql']
vars:
mysql_version: "8.0"
mysql_root_password: "{{ vault_mysql_root_password }}"
mysql_databases:
- name: app_db
encoding: utf8mb4
collation: utf8mb4_unicode_ci
mysql_users:
- name: app_user
password: "{{ vault_app_db_password }}"
priv: "app_db.*:ALL"
host: "%" ---
ansible.builtin.include_tasks:
file: install.yml
apply:
tags: ['install']
tags: ['install'] ansible.builtin.include_tasks:
file: configure.yml
apply:
tags: ['configure']
tags: ['configure'] ansible.builtin.include_tasks:
file: security.yml
apply:
tags: ['security']
tags: ['security'] ---
ansible.builtin.apt:
name: nginx
state: present
update_cache: yes
notify: restart nginx ansible.builtin.apt:
name:
- "php{{ php_version }}-fpm"
- "php{{ php_version }}-mysql"
- "php{{ php_version }}-curl"
- "php{{ php_version }}-gd"
- "php{{ php_version }}-mbstring"
- "php{{ php_version }}-xml"
- "php{{ php_version }}-zip"
state: present
notify: restart php-fpm ---
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
notify: reload nginx ansible.builtin.template:
src: vhost.conf.j2
dest: "/etc/nginx/sites-available/{{ item.name }}"
owner: root
group: root
mode: '0644'
loop: "{{ nginx_vhosts }}"
notify: reload nginx ansible.builtin.file:
src: "/etc/nginx/sites-available/{{ item.name }}"
dest: "/etc/nginx/sites-enabled/{{ item.name }}"
state: link
loop: "{{ nginx_vhosts }}"
notify: reload nginx ---
ansible.builtin.template:
src: ssl.conf.j2
dest: /etc/nginx/conf.d/ssl.conf
owner: root
group: root
mode: '0644'
when: ssl_enabled | bool
notify: reload nginx ansible.builtin.template:
src: security-headers.conf.j2
dest: /etc/nginx/conf.d/security-headers.conf
owner: root
group: root
mode: '0644'
notify: reload nginx ansible.builtin.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- 80
- 443
when: firewall_enabled | bool
yaml
`site.yml
roles/web_server/tasks/main.yml
roles/web_server/tasks/install.yml
roles/web_server/tasks/configure.yml
roles/web_server/tasks/security.yml
$1
Organize variables by environment:
` ---
ntp_servers:
- 0.pool.ntp.org
- 1.pool.ntp.org
- 2.pool.ntp.org backup_retention_days: 7
monitoring_interval: 60 ---
nginx_worker_processes: auto
nginx_worker_connections: 1024
php_version: "8.2" nginx_vhosts:
- name: example.com
server_name: example.com www.example.com
root: /var/www/example.com
ssl: true
ssl_certificate: /etc/letsencrypt/live/example.com/fullchain.pem
ssl_certificate_key: /etc/letsencrypt/live/example.com/privkey.pem ---
mysql_version: "8.0"
mysql_root_password: "{{ vault_mysql_root_password }}" mysql_databases:
- name: app_db
encoding: utf8mb4
collation: utf8mb4_unicode_ci mysql_users:
- name: app_user
password: "{{ vault_app_db_password }}"
priv: "app_db.*:ALL"
host: "%" ---
vault_mysql_root_password: supersecret
vault_app_db_password: appsecret
yaml
`group_vars/all.yml
group_vars/web_servers.yml
group_vars/db_servers.yml
group_vars/production/vault.yml (encrypted)
$1
Create dynamic configuration files:
` user www-data;
worker_processes {{ nginx_worker_processes }};
pid /run/nginx.pid; events {
worker_connections {{ nginx_worker_connections }};
multi_accept on;
} http {
include /etc/nginx/mime.types;
default_type application/octet-stream; access_log /var/log/nginx/access.log combined buffer=512k flush=1m;
error_log /var/log/nginx/error.log warn; sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048; ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
} server {
listen 80;
listen [::]:80;
server_name {{ item.server_name }}; {% if item.ssl | default(false) %}
return 301 https://$server_name$request_uri;
} server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name {{ item.server_name }}; ssl_certificate {{ item.ssl_certificate }};
ssl_certificate_key {{ item.ssl_certificate_key }};
{% endif %} root {{ item.root }};
index index.php index.html index.htm; location / {
try_files $uri $uri/ /index.php?$args;
} location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php{{ php_version }}-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
} location ~ /\.ht {
deny all;
}
}
jinja
`templates/nginx.conf.j2
templates/vhost.conf.j2
$1
Break down complex tasks:
` ---
ansible.builtin.include_tasks:
file: packages.yml
apply:
tags: ['packages']
tags: ['packages'] ansible.builtin.include_tasks:
file: users.yml
apply:
tags: ['users']
tags: ['users'] ansible.builtin.include_tasks:
file: security.yml
apply:
tags: ['security']
tags: ['security'] ---
ansible.builtin.apt:
update_cache: yes
cache_valid_time: 3600 ansible.builtin.apt:
name:
- vim
- curl
- wget
- git
- htop
- ntp
- ufw
- fail2ban
state: present ---
ansible.builtin.user:
name: "{{ item.name }}"
shell: "{{ item.shell | default('/bin/bash') }}"
groups: "{{ item.groups | default([]) }}"
append: yes
loop: "{{ system_users }}" ansible.builtin.authorized_key:
user: "{{ item.name }}"
key: "{{ item.ssh_key }}"
state: present
loop: "{{ system_users }}"
when: item.ssh_key is defined ---
ansible.builtin.template:
src: sshd_config.j2
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: '0600'
notify: restart ssh ansible.builtin.ufw:
state: enabled
policy: deny ansible.builtin.ufw:
rule: allow
port: ssh
proto: tcp
yaml
`roles/common/tasks/main.yml
roles/common/tasks/packages.yml
roles/common/tasks/users.yml
roles/common/tasks/security.yml
$1
Improve playbook execution:
` [defaults]
forks = 20
pipelining = True
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 7200
gathering = smart
host_key_checking = False [ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s ---
hosts: all
gather_facts: yes
strategy: free
serial: "25%"
vars:
ansible_forks: 20
pre_tasks:
- name: Gather service facts
ansible.builtin.service_facts:
tags: ['always']
roles:
- role: common
- role: web_server
when: "'web_servers' in group_names"
- role: db_server
when: "'db_servers' in group_names"
yaml
`ansible.cfg
site.yml
$1
Implement testing:
` ---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: instance
image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2004}-ansible
command: ${MOLECULE_DOCKER_COMMAND:-""}
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
privileged: true
pre_build_image: true
provisioner:
name: ansible
playbooks:
converge: ${MOLECULE_PLAYBOOK:-converge.yml}
verifier:
name: ansible ---
hosts: all
gather_facts: false
tasks:
- name: Check if NGINX is running
ansible.builtin.service_facts:
register: services_state - name: Assert NGINX is running
ansible.builtin.assert:
that:
- "'nginx' in services_state.ansible_facts.services"
- "services_state.ansible_facts.services['nginx.service'].state == 'running'"
fail_msg: "NGINX is not running"
success_msg: "NGINX is running" - name: Check NGINX configuration
ansible.builtin.command: nginx -t
changed_when: false
register: nginx_conf_test - name: Assert NGINX configuration is valid
ansible.builtin.assert:
that:
- nginx_conf_test.rc == 0
fail_msg: "NGINX configuration is invalid"
success_msg: "NGINX configuration is valid"
yaml
``molecule/default/molecule.yml
molecule/default/verify.yml
$1
1. Repository Structure
- Clear organization
- Role-based design
- Environment separation
- Version control
2. Security
- Vault integration
- Key rotation
- Least privilege
- Audit logging
3. Performance
- Fact caching
- Parallel execution
- Task optimization
- Connection pooling
4. Testing
- Molecule testing
- Syntax validation
- Integration tests
- CI/CD integration
$1
Effective Ansible implementation requires:
1. Clear structure
2. Security focus
3. Performance optimization
4. Comprehensive testing
5. Documentation
Remember to:
$1
1. [Ansible Documentation](https://docs.ansible.com/)
2. [Ansible Galaxy](https://galaxy.ansible.com/)
3. [Ansible Best Practices](https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html)
4. [Molecule Documentation](https://molecule.readthedocs.io/)
5. [Ansible Security Guide](https://docs.ansible.com/ansible/latest/user_guide/playbooks_vault.html)
Why This Matters
Understanding the business and technical context helps you make informed decisions rather than blindly following patterns.
Trade-offs to Consider
Every architectural decision involves trade-offs. Consider your specific requirements, team expertise, and scale when evaluating options.
When NOT to Use This
Knowing when a solution doesn't apply is as valuable as knowing when it does. Consider alternatives for your specific situation.
Decision Framework
Use this framework to evaluate whether this approach is right for your use case based on your specific constraints and requirements.