dotlinux guide

Using Ansible for Linux System Automation and Configuration

In the modern era of DevOps and cloud computing, managing Linux systems manually has become increasingly unsustainable. Tasks like configuring servers, deploying software, managing users, and ensuring consistency across environments are time-consuming, error-prone, and难以扩展 at scale. This is where Ansible—an open-source automation tool—shines. Ansible simplifies Linux system automation with its agentless architecture, human-readable YAML playbooks, and extensive module library. Whether you’re managing a handful of servers or a large enterprise infrastructure, Ansible enables you to automate repetitive tasks, enforce configurations, and maintain system state consistently. This blog will guide you through the fundamentals of Ansible, from core concepts to practical implementation, common use cases, and best practices. By the end, you’ll have the knowledge to start automating Linux systems efficiently with Ansible.

Table of Contents

What is Ansible?

Ansible is an open-source automation platform developed by Red Hat. It is designed to automate provisioning, configuration management, application deployment, and orchestration of IT infrastructure. Unlike tools like Puppet or Chef, Ansible is agentless—it uses SSH (or WinRM for Windows) to communicate with managed nodes, eliminating the need to install software agents on target systems. This makes Ansible lightweight, easy to set up, and low-maintenance.

Key features of Ansible include:

  • Human-readable playbooks: Written in YAML, making them easy to write, read, and version-control.
  • Extensive module library: Thousands of built-in modules for tasks like package management, file operations, and cloud provisioning.
  • Idempotent operations: Ensures running the same playbook multiple times produces the same result (no unintended side effects).
  • Scalability: Automate hundreds or thousands of nodes from a single control node.

Core Concepts

To use Ansible effectively, you need to understand its core components:

Control Node

The machine where Ansible is installed and from which automation is executed. It can be any Linux or macOS system with Python 3.8+ and an SSH client. Windows is not supported as a control node.

Managed Nodes

The target systems (Linux servers, network devices, etc.) that Ansible automates. Managed nodes require:

  • Python 2.7 or 3.5+ (for most modules).
  • SSH server (enabled and accessible from the control node).
  • Proper network connectivity and authentication (SSH keys recommended).

Inventory

A file (or directory) that lists managed nodes, organized into groups for easier targeting. Inventory files can be in INI or YAML format and may include variables specific to nodes/groups.

Playbooks

YAML files that define a set of “plays” and “tasks” to execute on managed nodes. Playbooks are the primary way to automate workflows in Ansible.

Tasks

The smallest unit of work in Ansible. A task calls a module with specific arguments to perform an action (e.g., install a package, create a user).

Modules

Reusable, standalone scripts that Ansible executes on managed nodes to perform tasks. Ansible provides over 4,500 built-in modules (e.g., apt, user, copy, service), and you can write custom modules in Python.

Roles

A way to organize playbooks, variables, tasks, handlers, and templates into a reusable directory structure. Roles simplify sharing and reusing automation logic across projects.

Getting Started with Ansible

Let’s walk through setting up Ansible and running your first automation tasks.

Installation

Install Ansible on the control node using one of these methods:

On Ubuntu/Debian:

sudo apt update && sudo apt install ansible -y

On CentOS/RHEL:

sudo dnf install ansible -y  # For RHEL 8+/CentOS 8+
# Or for older versions: sudo yum install ansible -y

Using pip (Python package manager):

pip3 install ansible

Verify installation:

ansible --version

Setting Up Inventory

The inventory file tells Ansible which nodes to manage. By default, Ansible uses /etc/ansible/hosts, but you can specify a custom inventory with the -i flag.

Example Inventory (INI format):
Create a file named inventory.ini:

# inventory.ini
[webservers]
server1.example.com  # IP or hostname
server2.example.com  ansible_user=ubuntu  # Override SSH user

[databases]
db1.example.com ansible_port=2222  # Override SSH port

[all:vars]
ansible_user=centos  # Default SSH user for all nodes

YAML Format (Alternative):
inventory.yml:

all:
  vars:
    ansible_user: centos
  children:
    webservers:
      hosts:
        server1.example.com:
        server2.example.com:
          ansible_user: ubuntu
    databases:
      hosts:
        db1.example.com:
          ansible_port: 2222

Test inventory connectivity with the ping module (checks if nodes are reachable):

ansible all -i inventory.ini -m ping

Output should look like:

server1.example.com | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Ad-Hoc Commands

For one-off tasks, use ad-hoc commands instead of playbooks. Syntax:

ansible <target> -i <inventory> -m <module> -a <module_arguments> [--become]

Examples:

  • Check disk space on all nodes:
    ansible all -i inventory.ini -m command -a "df -h"
  • Install nginx on web servers (with sudo):
    ansible webservers -i inventory.ini -m apt -a "name=nginx state=present" --become
    (Use yum instead of apt for RHEL/CentOS.)
  • Restart nginx on web servers:
    ansible webservers -i inventory.ini -m service -a "name=nginx state=restarted" --become

--become (or -b) elevates privileges (equivalent to sudo).

Writing Playbooks: The Heart of Ansible

Playbooks are Ansible’s configuration, deployment, and orchestration language. They define a series of tasks to run on target nodes and are written in YAML.

Basic Playbook Structure

A playbook consists of one or more “plays,” each targeting a group of hosts and defining tasks to execute. Here’s a simple playbook to install and start nginx:

# install_nginx.yml
---
- name: Install and start Nginx on web servers
  hosts: webservers  # Target group from inventory
  become: yes        # Run tasks with sudo
  tasks:
    - name: Install Nginx package
      apt:            # Module for Debian/Ubuntu
        name: nginx
        state: present  # Ensure package is installed

    - name: Start and enable Nginx service
      service:
        name: nginx
        state: started
        enabled: yes  # Start on boot

Run the playbook with:

ansible-playbook -i inventory.ini install_nginx.yml

Variables

Variables make playbooks reusable by allowing dynamic values. Define variables in:

  • Playbooks (using vars keyword).
  • Inventory files (per node/group).
  • Separate vars_files (e.g., vars/webservers.yml).
  • Command line (with -e "var=value").

Example with Variables:

# install_nginx_with_vars.yml
---
- name: Install Nginx with variables
  hosts: webservers
  become: yes
  vars:
    web_package: nginx  # Variable defined in playbook
    service_state: started
  tasks:
    - name: Install {{ web_package }}
      apt:
        name: "{{ web_package }}"
        state: present

    - name: Ensure {{ web_package }} is {{ service_state }}
      service:
        name: "{{ web_package }}"
        state: "{{ service_state }}"
        enabled: yes

Conditionals

Use when clauses to run tasks only if a condition is met (e.g., based on OS, node hostname, or variable values).

Example: Install nginx only on Debian/Ubuntu:

tasks:
  - name: Install Nginx on Debian/Ubuntu
    apt:
      name: nginx
      state: present
    when: ansible_os_family == "Debian"  # Built-in fact: ansible_os_family

  - name: Install Nginx on RHEL/CentOS
    yum:
      name: nginx
      state: present
    when: ansible_os_family == "RedHat"

Ansible gathers “facts” (system info) by default, including ansible_os_family, ansible_distribution, and ansible_hostname. View all facts with:
ansible <node> -m setup

Loops

Use loops to repeat tasks (e.g., install multiple packages). Ansible supports loop (recommended) and with_items (legacy).

Example: Install Multiple Packages:

tasks:
  - name: Install web server dependencies
    apt:
      name: "{{ item }}"
      state: present
    loop:
      - nginx
      - git
      - curl
    when: ansible_os_family == "Debian"

Handlers

Handlers are tasks that run only when triggered (e.g., restart a service after a config file changes). Use notify in a task to trigger a handler.

Example: Update Nginx Config and Restart:

tasks:
  - name: Copy Nginx config file
    copy:
      src: ./nginx.conf  # Local file on control node
      dest: /etc/nginx/nginx.conf
      mode: '0644'
    notify: Restart Nginx  # Trigger handler if file changes

handlers:  # Define handlers at the play level
  - name: Restart Nginx
    service:
      name: nginx
      state: restarted

Common Use Cases

Ansible excels at automating routine Linux system administration tasks. Below are common scenarios with playbook examples.

Package Management

Install, remove, or update packages across systems.

Example: Install/Remove Packages:

# package_management.yml
---
- name: Manage packages on web and db servers
  hosts: webservers:databases
  become: yes
  tasks:
    - name: Install required packages
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - nginx
        - mysql-client
      when: ansible_os_family == "Debian"

    - name: Remove unwanted package
      yum:
        name: httpd
        state: absent  # Uninstall package
      when: ansible_os_family == "RedHat"

User Management

Create users, set passwords, and manage groups.

Example: Create a System User:

# create_user.yml
---
- name: Create devops user with SSH access
  hosts: all
  become: yes
  tasks:
    - name: Create devops group
      group:
        name: devops
        state: present
        gid: 1001

    - name: Create devops user
      user:
        name: devops
        uid: 1001
        group: devops
        groups: sudo  # Add to sudo group
        shell: /bin/bash
        home: /home/devops
        create_home: yes
        state: present

    - name: Add SSH public key for devops user
      authorized_key:
        user: devops
        key: "{{ lookup('file', '/home/control-node-user/.ssh/id_rsa.pub') }}"  # Local pub key
        state: present

File Configuration

Copy files, templates, or set permissions. Use template (Jinja2) for dynamic configs.

Example: Deploy a Custom Nginx Config with Template:

# deploy_nginx_config.yml
---
- name: Deploy Nginx config from template
  hosts: webservers
  become: yes
  vars:
    nginx_port: 8080  # Variable used in template
  tasks:
    - name: Copy Nginx template
      template:
        src: templates/nginx.conf.j2  # Jinja2 template on control node
        dest: /etc/nginx/nginx.conf
        mode: '0644'
      notify: Restart Nginx

  handlers:
    - name: Restart Nginx
      service:
        name: nginx
        state: restarted

Template File (templates/nginx.conf.j2):

server {
    listen {{ nginx_port }};  # Dynamic port from variable
    server_name {{ ansible_fqdn }};  # Ansible fact for hostname

    root /var/www/html;
    index index.html;
}

Service Management

Start, stop, restart, or enable services.

Example: Manage Docker Service:

# manage_docker.yml
---
- name: Ensure Docker is running and enabled
  hosts: webservers
  become: yes
  tasks:
    - name: Start Docker service
      service:
        name: docker
        state: started
        enabled: yes

System Updates

Update system packages (use cautiously in production!).

Example: Update Debian/Ubuntu Packages:

# update_packages.yml
---
- name: Update apt packages
  hosts: all
  become: yes
  when: ansible_os_family == "Debian"
  tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600  # Cache valid for 1 hour

    - name: Upgrade all packages
      apt:
        upgrade: dist  # Safe upgrade (avoid kernel updates)
        state: present

Best Practices

To write maintainable, efficient Ansible automation, follow these best practices:

Idempotency

Ensure playbooks are idempotent—running them multiple times has the same effect as running once. Most Ansible modules are idempotent by design (e.g., apt with state: present won’t reinstall if already installed). Avoid command/shell modules for idempotent tasks (use specialized modules instead).

Use Roles for Organization

Roles organize playbooks, variables, tasks, and templates into a standard directory structure, making them reusable and shareable. Use ansible-galaxy init to create a role skeleton:

ansible-galaxy init roles/nginx  # Creates roles/nginx/...

Role directory structure:

roles/nginx/
├── tasks/          # Main tasks (main.yml)
├── handlers/       # Handlers (main.yml)
├── vars/           # Variables (main.yml)
├── defaults/       # Default variables (low precedence)
├── templates/      # Jinja2 templates
├── files/          # Static files to copy
└── meta/           # Role metadata (dependencies, etc.)

Example: Use a Role in a Playbook:

# site.yml
---
- name: Deploy web servers
  hosts: webservers
  become: yes
  roles:
    - role: nginx  # Path to role directory

Variables Management

  • Store sensitive variables (passwords, API keys) in ansible-vault (encrypted files).
    Example: ansible-vault create vars/secrets.yml, then reference with vars_files: [vars/secrets.yml].
  • Use group_vars and host_vars directories for inventory-specific variables:
    inventory/group_vars/webservers.yml (vars for webservers group).
  • Avoid hardcoding variables in playbooks.

Security

  • Use SSH keys instead of passwords for authentication.
  • Restrict become privileges (e.g., limit sudo access for Ansible tasks).
  • Encrypt sensitive data with `ans