Deploying Ansible on CentOS Stream 9: Automate Linux Server Management Without Installing Agents

CentOS tutorial - IT technology blog
CentOS tutorial - IT technology blog

Get Started in 5 Minutes

If you’re managing 3+ Linux servers and every update means SSH-ing into each one to run commands — I know that feeling. Ansible solves this in the simplest way possible: SSH into each machine, run the commands, done. No agent installation, no background daemons, no extra ports to open.

Install Ansible on the Control Node (CentOS Stream 9)

# Enable EPEL repository
sudo dnf install epel-release -y

# Install Ansible
sudo dnf install ansible -y

# Check version
ansible --version

Create Your First Inventory File

The inventory file is a list of servers you want to manage. Create a hosts.ini file in your project directory:

[webservers]
web1 ansible_host=192.168.1.10
web2 ansible_host=192.168.1.11

[dbservers]
db1 ansible_host=192.168.1.20

[all:vars]
ansible_user=admin
ansible_ssh_private_key_file=~/.ssh/id_rsa

Run Your First Command

# Ping all servers to check connectivity
ansible all -i hosts.ini -m ping

# Check uptime of the webservers group
ansible webservers -i hosts.ini -m command -a "uptime"

# Check disk usage on all machines
ansible all -i hosts.ini -m command -a "df -h /"

If you see "ping": "pong" returned from the machines — you’ve connected successfully and are ready to automate.

How Does Ansible Work?

When most people hear “automation tool,” they immediately think of installing additional services on every server. Ansible is different: it uses SSH — the same thing you use every day to connect to servers — as its only communication channel.

The architecture has 2 parts:

  • Control Node: The machine where you install Ansible (your laptop or a jump server). You only need this one machine.
  • Managed Nodes: The servers you want to manage. They only need SSH and Python 3 — available on most modern Linux distros.

When you run an Ansible command, it follows this sequence:

  1. Reads the inventory file to determine which machines to connect to
  2. SSH into each managed node (in parallel if using -f)
  3. Uploads a temporary Python module to /tmp
  4. Executes the module, collects results, and immediately removes the temp files

The entire process happens over SSH — nothing remains on the managed node after execution. This is why Ansible is called agentless.

Writing Real-World Playbooks

Ad-hoc commands are great for quick one-off tasks. When you need to execute multiple steps in sequence, you need a Playbook — a YAML file that describes “what to do, on which machines, and in what order.”

Example: Playbook to Install Nginx on Web Servers

---
- name: Install and configure Nginx
  hosts: webservers
  become: yes  # run with sudo

  vars:
    nginx_port: 80

  tasks:
    - name: Install nginx
      dnf:
        name: nginx
        state: present

    - name: Start nginx and enable on boot
      systemd:
        name: nginx
        state: started
        enabled: yes

    - name: Open port 80 on firewall
      firewalld:
        port: "{{ nginx_port }}/tcp"
        permanent: yes
        state: enabled
        immediate: yes

    - name: Create index.html page
      copy:
        content: "<h1>Server {{ inventory_hostname }} — Deployed by Ansible</h1>"
        dest: /var/www/html/index.html
        owner: nginx
        group: nginx
        mode: '0644'

  handlers:
    - name: Reload nginx
      systemd:
        name: nginx
        state: reloaded
# Run the playbook
ansible-playbook -i hosts.ini deploy_nginx.yml

# Dry-run: check changes without applying them
ansible-playbook -i hosts.ini deploy_nginx.yml --check --diff

The --check --diff flags are extremely useful when you’re not sure what a playbook will change — they show you exactly which files and lines would be modified without actually touching the system.

Real-World Story: Migrating 5 Servers in One Week

When CentOS 8 reached EOL, I had to urgently migrate 5 servers to Rocky Linux in one week. My initial plan was to do it manually — SSH into each machine, follow an Excel checklist. By the third server, mistakes were already creeping in: forgot to re-enable SELinux after testing on one, installed the wrong PHP version on another, missed the JST timezone configuration step on a third…

That’s when I stopped and wrote an Ansible playbook for the entire migration process. It took about 2 hours to write and test — but the remaining 2 servers ran the playbook in 8 minutes each, perfectly consistent, zero mistakes.

That playbook was then reused many times when provisioning new servers — no need to remember each step, no checklist needed, just ansible-playbook provision.yml.

Lesson learned: Write a playbook even if you only have 2–3 servers. The initial writing cost is far less than the debugging cost when you make a mistake on step 4 doing it manually.

Organizing Code with Roles

When a playbook exceeds 50 tasks, it’s time to split it into Roles — a standard directory structure that enables reuse and sharing across projects.

# Initialize a new role
ansible-galaxy init roles/nginx

Directory structure after creation:

roles/nginx/
├── tasks/
│   └── main.yml      # Main tasks
├── handlers/
│   └── main.yml      # Handlers (restart/reload service)
├── templates/
│   └── nginx.conf.j2 # Jinja2 template with dynamic variables
├── vars/
│   └── main.yml      # Fixed variables
└── defaults/
    └── main.yml      # Default variables (can be overridden)

Using roles in a playbook is much cleaner:

---
- name: Setup web server stack
  hosts: webservers
  become: yes
  roles:
    - common      # timezone, hostname, sysctl
    - nginx
    - php-fpm
    - firewall

Practical Tips

1. Protect Sensitive Information with Ansible Vault

# Encrypt files containing passwords/API keys
ansible-vault encrypt secrets.yml

# View the contents of an encrypted file
ansible-vault view secrets.yml

# Run playbook with vault
ansible-playbook site.yml --ask-vault-pass

# Or use a password file (suitable for CI/CD)
ansible-playbook site.yml --vault-password-file ~/.vault_pass

2. Speed Up Execution with Many Servers

# By default Ansible runs 5 hosts in parallel
# Increase to 20 if your infrastructure is large enough
ansible-playbook -i hosts.ini site.yml -f 20

# Or set it in ansible.cfg
[defaults]
forks = 20

3. Check Playbook Quality Before Using in Production

# Install ansible-lint
pip install ansible-lint

# Lint the entire project
ansible-lint

# Quick syntax check
ansible-playbook deploy.yml --syntax-check

4. Create an ansible.cfg File to Avoid Specifying -i Every Time

[defaults]
inventory = hosts.ini
remote_user = admin
private_key_file = ~/.ssh/id_rsa
host_key_checking = False
forks = 10

[privilege_escalation]
become = True
become_method = sudo

With this ansible.cfg file, the command becomes simply:

ansible-playbook deploy_nginx.yml

Ansible doesn’t require you to be a senior DevOps engineer to get started. Begin with a simple inventory file and a few ad-hoc commands to get comfortable — within a few days you’ll find yourself never wanting to manually SSH into individual machines again. That’s a good sign.

Share: