Configuration Management with Ansible: A Comprehensive Guide
Learn how to master configuration management with Ansible, including playbooks, roles, best practices, and advanced automation techniques
Learn how to effectively manage infrastructure configuration using Ansible. This comprehensive guide covers key concepts, playbook development, role management, and best practices for automation.
Ansible Architecture Overview
Understanding Ansible
Ansible is an agentless automation tool that uses:
- Declarative Language: YAML-based playbooks
- Agentless Architecture: SSH/WinRM for communication
- Idempotency: Consistent state management
- Extensibility: Modules and plugins
Implementation Guide
1. Inventory Configuration
Define your infrastructure inventory:
# inventory/production.ini [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
2. Ansible Configuration
Configure Ansible behavior:
# ansible.cfg [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
3. Role Structure
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
4. Playbook Development
Create comprehensive playbooks:
# site.yml --- - name: Configure Common Settings hosts: all roles: - role: common tags: ['common', 'security'] - name: Configure Web Servers 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 - name: Configure Database Servers 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: "%" # roles/web_server/tasks/main.yml --- - name: Include installation tasks ansible.builtin.include_tasks: file: install.yml apply: tags: ['install'] tags: ['install'] - name: Include configuration tasks ansible.builtin.include_tasks: file: configure.yml apply: tags: ['configure'] tags: ['configure'] - name: Include security tasks ansible.builtin.include_tasks: file: security.yml apply: tags: ['security'] tags: ['security'] # roles/web_server/tasks/install.yml --- - name: Install NGINX ansible.builtin.apt: name: nginx state: present update_cache: yes notify: restart nginx - name: Install PHP and extensions 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 # roles/web_server/tasks/configure.yml --- - name: Configure NGINX ansible.builtin.template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf owner: root group: root mode: '0644' notify: reload nginx - name: Configure virtual hosts 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 - name: Enable virtual hosts 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 # roles/web_server/tasks/security.yml --- - name: Configure SSL 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 - name: Configure security headers 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 - name: Configure firewall ansible.builtin.ufw: rule: allow port: "{{ item }}" proto: tcp loop: - 80 - 443 when: firewall_enabled | bool
5. Working with Variables
Organize variables by environment:
# group_vars/all.yml --- ntp_servers: - 0.pool.ntp.org - 1.pool.ntp.org - 2.pool.ntp.org backup_retention_days: 7 monitoring_interval: 60 # group_vars/web_servers.yml --- 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 # group_vars/db_servers.yml --- 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: "%" # group_vars/production/vault.yml (encrypted) --- vault_mysql_root_password: supersecret vault_app_db_password: appsecret
6. Templates
Create dynamic configuration files:
# templates/nginx.conf.j2 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/*; } # templates/vhost.conf.j2 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; } }
7. Task Organization
Break down complex tasks:
# roles/common/tasks/main.yml --- - name: Include package management tasks ansible.builtin.include_tasks: file: packages.yml apply: tags: ['packages'] tags: ['packages'] - name: Include user management tasks ansible.builtin.include_tasks: file: users.yml apply: tags: ['users'] tags: ['users'] - name: Include security tasks ansible.builtin.include_tasks: file: security.yml apply: tags: ['security'] tags: ['security'] # roles/common/tasks/packages.yml --- - name: Update package cache ansible.builtin.apt: update_cache: yes cache_valid_time: 3600 - name: Install common packages ansible.builtin.apt: name: - vim - curl - wget - git - htop - ntp - ufw - fail2ban state: present # roles/common/tasks/users.yml --- - name: Create system users ansible.builtin.user: name: "{{ item.name }}" shell: "{{ item.shell | default('/bin/bash') }}" groups: "{{ item.groups | default([]) }}" append: yes loop: "{{ system_users }}" - name: Add SSH keys ansible.builtin.authorized_key: user: "{{ item.name }}" key: "{{ item.ssh_key }}" state: present loop: "{{ system_users }}" when: item.ssh_key is defined # roles/common/tasks/security.yml --- - name: Configure SSH ansible.builtin.template: src: sshd_config.j2 dest: /etc/ssh/sshd_config owner: root group: root mode: '0600' notify: restart ssh - name: Configure firewall ansible.builtin.ufw: state: enabled policy: deny - name: Allow SSH ansible.builtin.ufw: rule: allow port: ssh proto: tcp
8. Performance Optimization
Improve playbook execution:
# ansible.cfg [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 # site.yml --- - name: High Performance Configuration 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"
9. Testing and Validation
Implement testing:
# molecule/default/molecule.yml --- 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 # molecule/default/verify.yml --- - name: Verify 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"
Best Practices
-
Repository Structure
- Clear organization
- Role-based design
- Environment separation
- Version control
-
Security
- Vault integration
- Key rotation
- Least privilege
- Audit logging
-
Performance
- Fact caching
- Parallel execution
- Task optimization
- Connection pooling
-
Testing
- Molecule testing
- Syntax validation
- Integration tests
- CI/CD integration
Conclusion
Effective Ansible implementation requires:
- Clear structure
- Security focus
- Performance optimization
- Comprehensive testing
- Documentation
Remember to:
- Follow best practices
- Test thoroughly
- Monitor execution
- Document changes
- Review regularly