Ansible Automation
Ansible is an agentless IT automation tool that connects to remote hosts via SSH to perform configuration management, application deployment, and task orchestration. It uses YAML-formatted Playbooks with a gentle learning curve, making it one of the most popular automation solutions in Enterprise Linux environments.
Installing Ansible
Section titled “Installing Ansible”Installing on the Control Node
Section titled “Installing on the Control Node”Ansible only needs to be installed on the control node (the machine from which you run commands). Managed remote hosts do not require any additional software — just SSH and Python.
# Method 1: Install via DNF (recommended)sudo dnf install -y epel-releasesudo dnf install -y ansible-core
# Method 2: Install via pip (for the latest version)sudo dnf install -y python3 python3-pippip3 install --user ansible
# Verify installationansible --versionInstalling Additional Collections
Section titled “Installing Additional Collections”Ansible modules are distributed as Collections:
# Install commonly used collectionsansible-galaxy collection install ansible.posixansible-galaxy collection install community.generalansible-galaxy collection install community.mysql
# List installed collectionsansible-galaxy collection listInventory
Section titled “Inventory”The Inventory file defines the hosts and groups that Ansible will manage.
INI Format
Section titled “INI Format”Create /etc/ansible/hosts or inventory.ini in your project directory:
# Individual hosts10.0.0.100
# Define groups[webservers]web01.example.comweb02.example.comweb03.example.com
[dbservers]db01.example.com ansible_port=2222db02.example.com
[appservers]app[01:05].example.com # app01 through app05
# Group variables[webservers:vars]ansible_user=opsansible_become=truehttp_port=80
[dbservers:vars]ansible_user=dbaansible_become=true
# Parent group (containing multiple child groups)[production:children]webserversdbserversappserversYAML Format
Section titled “YAML Format”all: children: webservers: hosts: web01.example.com: web02.example.com: vars: ansible_user: ops http_port: 80
dbservers: hosts: db01.example.com: ansible_port: 2222 db02.example.com: vars: ansible_user: dba
production: children: webservers: dbservers:Verifying Inventory
Section titled “Verifying Inventory”# List all hostsansible-inventory -i inventory.yml --list
# Display as a graphansible-inventory -i inventory.yml --graph
# View variables for a specific hostansible-inventory -i inventory.yml --host web01.example.comAd-Hoc Commands
Section titled “Ad-Hoc Commands”Ad-Hoc commands are suitable for one-off quick operations.
# Test connectivity to all hostsansible all -i inventory.yml -m ping
# View remote host informationansible webservers -i inventory.yml -m setup
# Execute shell commandsansible all -i inventory.yml -m shell -a "uptime"ansible all -i inventory.yml -m shell -a "free -h"ansible all -i inventory.yml -m shell -a "df -h /"
# Manage packagesansible webservers -i inventory.yml -m dnf -a "name=nginx state=present" --becomeansible webservers -i inventory.yml -m dnf -a "name=* state=latest" --become
# Manage servicesansible webservers -i inventory.yml -m service -a "name=nginx state=started enabled=yes" --become
# Copy filesansible webservers -i inventory.yml -m copy -a "src=/tmp/app.conf dest=/etc/app.conf owner=root mode=0644" --become
# Manage usersansible all -i inventory.yml -m user -a "name=deploy state=present groups=wheel" --become
# Reboot hostsansible dbservers -i inventory.yml -m reboot --become
# Limit execution scopeansible webservers -i inventory.yml -m shell -a "hostname" --limit web01.example.com
# Parallelism control (default is 5 parallel)ansible all -i inventory.yml -m ping -f 20Writing Playbooks
Section titled “Writing Playbooks”Playbooks are Ansible’s core configuration files, using YAML format to define an ordered series of tasks.
Basic Playbook
Section titled “Basic Playbook”# site.yml - Basic web server configuration---- name: Configure web servers hosts: webservers become: true vars: http_port: 80 doc_root: /var/www/myapp
tasks: - name: Install required packages ansible.builtin.dnf: name: - nginx - firewalld - vim-enhanced state: present
- name: Create website directory ansible.builtin.file: path: "{{ doc_root }}" state: directory owner: nginx group: nginx mode: '0755'
- name: Deploy homepage ansible.builtin.copy: content: | <!DOCTYPE html> <html> <head><title>Welcome</title></head> <body><h1>Server: {{ ansible_hostname }}</h1></body> </html> dest: "{{ doc_root }}/index.html" owner: nginx group: nginx mode: '0644'
- name: Start and enable Nginx ansible.builtin.service: name: nginx state: started enabled: true
- name: Start and enable Firewalld ansible.builtin.service: name: firewalld state: started enabled: true
- name: Open HTTP port ansible.posix.firewalld: service: http permanent: true state: enabled immediate: trueRunning Playbooks
Section titled “Running Playbooks”# Execute a Playbookansible-playbook -i inventory.yml site.yml
# Dry run (no actual changes)ansible-playbook -i inventory.yml site.yml --check
# Verbose outputansible-playbook -i inventory.yml site.yml -vansible-playbook -i inventory.yml site.yml -vvv
# Limit to specific hostsansible-playbook -i inventory.yml site.yml --limit web01.example.com
# Start from a specific taskansible-playbook -i inventory.yml site.yml --start-at-task="Start and enable Nginx"
# Step-by-step confirmationansible-playbook -i inventory.yml site.yml --step
# Pass extra variablesansible-playbook -i inventory.yml site.yml -e "http_port=8080"Common Module Details
Section titled “Common Module Details”dnf Module
Section titled “dnf Module”tasks: # Install a single package - name: Install Nginx ansible.builtin.dnf: name: nginx state: present
# Install multiple packages - name: Install development tools ansible.builtin.dnf: name: - gcc - make - python3-devel state: present
# Install a specific version - name: Install specific version ansible.builtin.dnf: name: nginx-1.20.1 state: present
# Update all packages - name: System update ansible.builtin.dnf: name: '*' state: latest
# Install a package group - name: Install Development Tools group ansible.builtin.dnf: name: '@Development Tools' state: present
# Remove a package - name: Remove old software ansible.builtin.dnf: name: httpd state: absentservice Module
Section titled “service Module”tasks: - name: Start and enable service ansible.builtin.service: name: nginx state: started enabled: true
- name: Restart service ansible.builtin.service: name: nginx state: restarted
- name: Reload service configuration ansible.builtin.service: name: nginx state: reloaded
- name: Stop and disable service ansible.builtin.service: name: postfix state: stopped enabled: falsecopy Module
Section titled “copy Module”tasks: # Copy a file from the control node - name: Copy configuration file ansible.builtin.copy: src: files/nginx.conf dest: /etc/nginx/nginx.conf owner: root group: root mode: '0644' backup: true notify: Reload Nginx
# Write content directly - name: Write configuration ansible.builtin.copy: content: | server { listen 80; server_name {{ ansible_fqdn }}; root /var/www/html; } dest: /etc/nginx/conf.d/default.conf owner: root group: root mode: '0644'template Module
Section titled “template Module”Jinja2 templates are one of Ansible’s most powerful features, enabling dynamic configuration file generation based on variables.
Create a template file templates/nginx.conf.j2:
# Ansible managed - Do not edit manuallyworker_processes {{ ansible_processor_vcpus }};
events { worker_connections {{ nginx_worker_connections | default(1024) }};}
http { include /etc/nginx/mime.types; default_type application/octet-stream;
sendfile on; keepalive_timeout 65;
{% for vhost in virtual_hosts %} server { listen {{ vhost.port | default(80) }}; server_name {{ vhost.server_name }}; root {{ vhost.doc_root }};
{% if vhost.ssl | default(false) %} listen 443 ssl; ssl_certificate {{ vhost.ssl_cert }}; ssl_certificate_key {{ vhost.ssl_key }};{% endif %} }{% endfor %}}Use the template in a Playbook:
- name: Configure web servers hosts: webservers become: true vars: nginx_worker_connections: 2048 virtual_hosts: - server_name: app.example.com doc_root: /var/www/app port: 80 - server_name: api.example.com doc_root: /var/www/api port: 8080
tasks: - name: Deploy Nginx configuration ansible.builtin.template: src: templates/nginx.conf.j2 dest: /etc/nginx/nginx.conf owner: root group: root mode: '0644' validate: nginx -t -c %s notify: Reload Nginx
handlers: - name: Reload Nginx ansible.builtin.service: name: nginx state: reloadedOther Common Modules
Section titled “Other Common Modules”tasks: # File and directory management - name: Create a directory ansible.builtin.file: path: /data/app state: directory owner: app group: app mode: '0755'
- name: Create a symbolic link ansible.builtin.file: src: /data/app/current dest: /var/www/app state: link
# Manage scheduled tasks - name: Add a backup cron job ansible.builtin.cron: name: "Database backup" minute: "0" hour: "2" job: "/usr/local/bin/backup.sh >> /var/log/backup.log 2>&1" user: root
# Manage SELinux booleans - name: Allow Nginx network connections ansible.posix.seboolean: name: httpd_can_network_connect state: true persistent: true
# Manage sysctl parameters - name: Set kernel parameters ansible.posix.sysctl: name: net.ipv4.ip_forward value: '1' sysctl_set: true reload: true
# Download files from a URL - name: Download application package ansible.builtin.get_url: url: https://example.com/releases/app-1.0.tar.gz dest: /tmp/app-1.0.tar.gz checksum: sha256:abc123...
# Extract archives - name: Extract application ansible.builtin.unarchive: src: /tmp/app-1.0.tar.gz dest: /opt/ remote_src: trueConditionals and Loops
Section titled “Conditionals and Loops”Conditionals
Section titled “Conditionals”tasks: - name: Run only on AlmaLinux ansible.builtin.dnf: name: almalinux-release state: latest when: ansible_distribution == "AlmaLinux"
- name: Run only on EL9 ansible.builtin.debug: msg: "Running on EL9" when: ansible_distribution_major_version == "9"
- name: Restart if service exists ansible.builtin.service: name: nginx state: restarted when: "'nginx' in ansible_facts.packages"tasks: - name: Create multiple users ansible.builtin.user: name: "{{ item.name }}" groups: "{{ item.groups }}" state: present loop: - { name: 'alice', groups: 'wheel' } - { name: 'bob', groups: 'developers' } - { name: 'charlie', groups: 'developers' }
- name: Start multiple services ansible.builtin.service: name: "{{ item }}" state: started enabled: true loop: - nginx - firewalld - fail2banHandlers
Section titled “Handlers”Handlers are special tasks that only run when triggered by notify, and they execute after all tasks have completed:
- name: Configure servers hosts: webservers become: true
tasks: - name: Update Nginx configuration ansible.builtin.template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf notify: - Validate Nginx configuration - Reload Nginx
- name: Update sysctl configuration ansible.builtin.copy: src: sysctl.conf dest: /etc/sysctl.d/99-custom.conf notify: Apply sysctl
handlers: - name: Validate Nginx configuration ansible.builtin.command: nginx -t
- name: Reload Nginx ansible.builtin.service: name: nginx state: reloaded
- name: Apply sysctl ansible.builtin.command: sysctl --systemRoles are the best practice for organizing Playbooks. They categorize related tasks, variables, templates, and files into a standardized directory structure.
Creating a Role
Section titled “Creating a Role”# Use ansible-galaxy to create a role skeletonansible-galaxy role init roles/webserver
# Generated directory structuretree roles/webserver/# roles/webserver/# ├── defaults/# │ └── main.yml # Default variables (lowest priority)# ├── files/ # Static files# ├── handlers/# │ └── main.yml # Handlers# ├── meta/# │ └── main.yml # Role metadata and dependencies# ├── tasks/# │ └── main.yml # Main tasks# ├── templates/ # Jinja2 templates# └── vars/# └── main.yml # Role variables (higher priority)Writing a Role
Section titled “Writing a Role”---nginx_port: 80nginx_worker_connections: 1024doc_root: /var/www/html---- name: Install Nginx ansible.builtin.dnf: name: nginx state: present
- name: Deploy Nginx configuration ansible.builtin.template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf notify: Reload Nginx
- name: Create website directory ansible.builtin.file: path: "{{ doc_root }}" state: directory owner: nginx group: nginx mode: '0755'
- name: Start Nginx ansible.builtin.service: name: nginx state: started enabled: true---- name: Reload Nginx ansible.builtin.service: name: nginx state: reloadedUsing Roles
Section titled “Using Roles”---- name: Configure web servers hosts: webservers become: true roles: - webserver
- name: Configure database servers hosts: dbservers become: true roles: - role: database vars: db_port: 3306Installing Roles from Ansible Galaxy
Section titled “Installing Roles from Ansible Galaxy”# Search for a roleansible-galaxy search nginx --platforms EL
# Install a roleansible-galaxy role install geerlingguy.nginx
# Batch install from a requirements filecat > requirements.yml << 'EOF'---roles: - name: geerlingguy.nginx - name: geerlingguy.mysql
collections: - name: community.general - name: ansible.posixEOF
ansible-galaxy install -r requirements.ymlRecommended Project Directory Structure
Section titled “Recommended Project Directory Structure”ansible-project/├── ansible.cfg # Ansible configuration├── inventory/│ ├── production.yml # Production environment hosts│ └── staging.yml # Staging environment hosts├── group_vars/│ ├── all.yml # Variables for all hosts│ ├── webservers.yml # Web server group variables│ └── dbservers.yml # Database group variables├── host_vars/│ └── web01.example.com.yml├── roles/│ ├── common/│ ├── webserver/│ └── database/├── site.yml # Main Playbook├── webservers.yml└── dbservers.yml# ansible.cfg[defaults]inventory = inventory/production.ymlremote_user = opsroles_path = roleshost_key_checking = Falseretry_files_enabled = Falsestdout_callback = yaml
[privilege_escalation]become = Truebecome_method = sudobecome_ask_pass = FalseAnsible Vault for Encrypting Sensitive Data
Section titled “Ansible Vault for Encrypting Sensitive Data”# Encrypt a fileansible-vault encrypt group_vars/dbservers.yml
# Edit an encrypted fileansible-vault edit group_vars/dbservers.yml
# Decrypt a fileansible-vault decrypt group_vars/dbservers.yml
# Run a Playbook with encrypted dataansible-playbook site.yml --ask-vault-pass
# Use a password fileansible-playbook site.yml --vault-password-file ~/.vault_passAnsible is a powerful tool for Enterprise Linux operations automation. From simple Ad-Hoc commands to complex multi-role Playbooks, it efficiently manages large-scale server clusters. Combining Cloud-Init for initial configuration with Ansible for subsequent deployment is a classic and proven workflow.