Skip to main content
Back to Category

Server Automation with Ansible: Infrastructure as Code

Ansible installation, inventory file, playbook writing, role structure, variables, encrypted vault, ad-hoc commands, and server configuration automation. Step-by-step guide.

Read time: 20 min DevOps & Automation
ansibleautomationinfrastructure as codedevopsplaybookyamllinux
Author
REXE Teknoloji Network & Security Team
Editor
REXE Teknoloji Technical Editorial
First published
Last updated

Server Automation with Ansible: Infrastructure as Code

Ansible is an open-source tool that lets you manage server configuration, application deployment, and task automation as code. Thanks to its agentless architecture, it only connects to target servers via SSH.

What is Ansible?

Key features of Ansible:

  • Agentless: No additional software installation required on target servers
  • Idempotent: Running the same playbook multiple times produces the same result
  • YAML-based: Easy to read and write
  • Large module library: 3000+ ready-made modules

Ansible connects from the control node to target servers over SSH. WinRM is used for Windows servers.

Ansible Installation

Ubuntu/Debian

hljs bash
# Add PPA repository
sudo apt update
sudo apt install software-properties-common -y
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible -y

# Check version
ansible --version

RHEL/CentOS/AlmaLinux

hljs bash
# Add EPEL repository
sudo dnf install epel-release -y
sudo dnf install ansible -y

# Alternative: install via pip
pip3 install ansible
hljs bash
# Create Python virtual environment
python3 -m venv ansible-env
source ansible-env/bin/activate

# Install Ansible and additional tools
pip install ansible ansible-lint

# Check version
ansible --version

Inventory File

Inventory defines the servers Ansible will manage:

INI Format

hljs ini
# /etc/ansible/hosts or custom inventory file

# Single server
web1.example.com

# Group definition
[webservers]
web1.example.com
web2.example.com ansible_port=2222
192.168.1.10 ansible_user=ubuntu

[databases]
db1.example.com
db2.example.com

# Group variables
[webservers:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3

# Parent group
[production:children]
webservers
databases

YAML Format

hljs yaml
# inventory.yml
all:
  children:
    webservers:
      hosts:
        web1.example.com:
          ansible_port: 22
        web2.example.com:
          ansible_port: 2222
      vars:
        ansible_user: deploy
        http_port: 80

    databases:
      hosts:
        db1.example.com:
        db2.example.com:
      vars:
        ansible_user: dbadmin
        db_port: 5432

Ad-hoc Commands

Run commands without writing a playbook for quick tasks:

hljs bash
# Ping test
ansible all -m ping
ansible webservers -m ping -i inventory.yml

# Run command
ansible all -m command -a "uptime"
ansible webservers -m shell -a "df -h"

# Copy file
ansible webservers -m copy -a "src=/local/file dest=/remote/path"

# Install package
ansible webservers -m apt -a "name=nginx state=present" --become

# Manage service
ansible webservers -m service -a "name=nginx state=started enabled=yes" --become

# Create/delete file
ansible all -m file -a "path=/tmp/test state=touch"
ansible all -m file -a "path=/tmp/test state=absent"

# Gather facts
ansible web1.example.com -m setup
ansible all -m setup -a "filter=ansible_os_family"

Writing Playbooks

Basic Playbook Structure

hljs yaml
# site.yml
---
- name: Configure web servers
  hosts: webservers
  become: yes  # use sudo
  vars:
    http_port: 80
    max_clients: 200

  tasks:
    - name: Install Nginx
      apt:
        name: nginx
        state: present
        update_cache: yes

    - name: Copy Nginx configuration
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
      notify: Restart Nginx

    - name: Start Nginx service
      service:
        name: nginx
        state: started
        enabled: yes

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

Tasks, Handlers and Roles

hljs yaml
# Conditional tasks
- name: Install package on Ubuntu
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - curl
    - git
  when: ansible_os_family == "Debian"

- name: Install package on RHEL
  dnf:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - curl
    - git
  when: ansible_os_family == "RedHat"

# Create users with loop
- name: Create users
  user:
    name: "{{ item.name }}"
    groups: "{{ item.groups }}"
    state: present
  loop:
    - { name: 'alice', groups: 'sudo' }
    - { name: 'bob', groups: 'www-data' }

# Register and condition
- name: Check Nginx version
  command: nginx -v
  register: nginx_version
  ignore_errors: yes

- name: Install Nginx if not installed
  apt:
    name: nginx
    state: present
  when: nginx_version.rc != 0

Variables and Facts

hljs yaml
# group_vars/webservers.yml
http_port: 80
https_port: 443
app_user: www-data
app_dir: /var/www/html

# host_vars/web1.example.com.yml
ansible_host: 192.168.1.10
server_name: web1.example.com
hljs yaml
# Variable usage in playbook
- name: Create application directory
  file:
    path: "{{ app_dir }}"
    state: directory
    owner: "{{ app_user }}"
    mode: '0755'

# Using facts
- name: Show system information
  debug:
    msg: |
      OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
      CPU: {{ ansible_processor_vcpus }} cores
      RAM: {{ ansible_memtotal_mb }} MB
      IP: {{ ansible_default_ipv4.address }}

Ansible Vault

To encrypt sensitive data:

hljs bash
# Create encrypted file
ansible-vault create secrets.yml

# Encrypt existing file
ansible-vault encrypt vars/passwords.yml

# Edit encrypted file
ansible-vault edit secrets.yml

# View encrypted file
ansible-vault view secrets.yml

# Remove encryption
ansible-vault decrypt vars/passwords.yml

# Change password
ansible-vault rekey secrets.yml

# Run playbook with vault
ansible-playbook site.yml --ask-vault-pass
ansible-playbook site.yml --vault-password-file ~/.vault_pass
hljs yaml
# secrets.yml (encrypted content)
db_password: "secret_database_password"
api_key: "secret_api_key"
ssl_cert_key: |
  -----BEGIN PRIVATE KEY-----
  ...
  -----END PRIVATE KEY-----

Role Structure

hljs bash
# Create role
ansible-galaxy init roles/nginx

# Role directory structure
roles/nginx/
├── defaults/
│   └── main.yml      # Default variables
├── files/
│   └── nginx.conf    # Static files
├── handlers/
│   └── main.yml      # Handlers
├── meta/
│   └── main.yml      # Role metadata
├── tasks/
│   └── main.yml      # Main tasks
├── templates/
│   └── vhost.conf.j2 # Jinja2 templates
└── vars/
    └── main.yml      # Variables
hljs yaml
# roles/nginx/tasks/main.yml
---
- name: Install Nginx
  package:
    name: nginx
    state: present

- name: Configure Nginx
  template:
    src: vhost.conf.j2
    dest: /etc/nginx/sites-available/{{ site_name }}
  notify: Reload Nginx

- name: Enable site
  file:
    src: /etc/nginx/sites-available/{{ site_name }}
    dest: /etc/nginx/sites-enabled/{{ site_name }}
    state: link
  notify: Reload Nginx

Example Playbooks

Nginx Installation Playbook

hljs yaml
---
- name: Nginx web server installation
  hosts: webservers
  become: yes
  vars:
    domain: example.com
    web_root: /var/www/{{ domain }}

  tasks:
    - name: Install Nginx
      apt:
        name: nginx
        state: present
        update_cache: yes

    - name: Create web directory
      file:
        path: "{{ web_root }}"
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'

    - name: Create sample index.html
      copy:
        content: "<h1>Hello from {{ inventory_hostname }}</h1>"
        dest: "{{ web_root }}/index.html"
        owner: www-data

    - name: Configure virtual host
      template:
        src: vhost.conf.j2
        dest: /etc/nginx/sites-available/{{ domain }}
      notify: Reload Nginx

    - name: Enable virtual host
      file:
        src: /etc/nginx/sites-available/{{ domain }}
        dest: /etc/nginx/sites-enabled/{{ domain }}
        state: link
      notify: Reload Nginx

    - name: Disable default site
      file:
        path: /etc/nginx/sites-enabled/default
        state: absent
      notify: Reload Nginx

    - name: Allow Nginx through UFW
      ufw:
        rule: allow
        name: 'Nginx Full'

  handlers:
    - name: Reload Nginx
      service:
        name: nginx
        state: reloaded

User Management Playbook

hljs yaml
---
- name: User management
  hosts: all
  become: yes
  vars_files:
    - vars/users.yml

  tasks:
    - name: Create sudo group
      group:
        name: sudo
        state: present

    - name: Create users
      user:
        name: "{{ item.username }}"
        comment: "{{ item.full_name }}"
        groups: "{{ item.groups | join(',') }}"
        shell: /bin/bash
        create_home: yes
        state: present
      loop: "{{ users }}"

    - name: Add SSH public key
      authorized_key:
        user: "{{ item.username }}"
        key: "{{ item.ssh_key }}"
        state: present
      loop: "{{ users }}"
      when: item.ssh_key is defined

    - name: Grant sudo access
      copy:
        content: "{{ item.username }} ALL=(ALL) NOPASSWD:ALL"
        dest: /etc/sudoers.d/{{ item.username }}
        mode: '0440'
      loop: "{{ users }}"
      when: item.sudo | default(false)

Running Playbooks

hljs bash
# Basic run
ansible-playbook site.yml

# With specific inventory
ansible-playbook -i inventory.yml site.yml

# Specific host or group
ansible-playbook site.yml --limit webservers
ansible-playbook site.yml --limit web1.example.com

# Dry-run (check without making changes)
ansible-playbook site.yml --check

# Verbose mode
ansible-playbook site.yml -v
ansible-playbook site.yml -vvv

# Run specific tags
ansible-playbook site.yml --tags "nginx,ssl"
ansible-playbook site.yml --skip-tags "debug"

# Extra variables
ansible-playbook site.yml -e "env=production version=1.5"

Conclusion

Ansible is one of the most accessible ways to manage server automation as code. With its agentless architecture, large module library, and YAML-based structure, you can easily manage both small and large-scale infrastructure. Keep sensitive data secure with Vault while creating reusable configurations with roles.

Frequently Asked Questions

What is the difference between Ansible and Puppet/Chef?

Ansible works agentlessly (only SSH required), while Puppet and Chef require agent installation on target servers. Ansible is YAML-based and easier to learn. Puppet and Chef offer more powerful state management but are more complex to set up and learn. Ansible is generally preferred for small to medium-scale infrastructure.

What is idempotency and why is it important?

Idempotency means running the same operation multiple times always produces the same result. Ansible modules are designed to be idempotent: if nginx is already installed it won't reinstall it, if a file is already correct it won't change it. This allows you to safely re-run playbooks.

What happens if the Ansible Vault password is forgotten?

If the vault password is forgotten, encrypted files cannot be recovered. This is why it's critical to store the vault password in a secure password manager (1Password, Bitwarden, etc.). In production environments, it's recommended to write the vault password to a file (--vault-password-file) and store it securely.

What is Ansible Galaxy?

Ansible Galaxy is the official platform where community-created roles and collections are shared (galaxy.ansible.com). You can download ready-made roles with commands like 'ansible-galaxy install geerlingguy.nginx'. This lets you use tested roles instead of writing from scratch.

How can Ansible performance be improved in large infrastructures?

Increase the 'forks' value in ansible.cfg (default 5, 20-50 recommended). Reduce SSH connection count with 'pipelining = True'. Enable independent hosts to run in parallel with 'strategy: free'. Control batch size with 'serial' in large inventories.

Can I manage Windows servers with Ansible?

Yes, Ansible can manage Windows servers via WinRM (Windows Remote Management) protocol. 'ansible_connection: winrm' and 'ansible_winrm_transport: ntlm' settings are required. Windows modules start with the 'win_' prefix: win_package, win_service, win_file, etc.

Related Articles

Network TrafficInbound GbpsOutbound Gbps