🐳

Ansible

9 notes  •  Containers & Orchestration

Automate WordPress Installation on Ubuntu with Ansible

Use Ansible to automate a full WordPress installation on Ubuntu, including Apache, MySQL, PHP, and WordPress configuration.

Prerequisites

  • Ansible installed on the control node
  • Target Ubuntu server accessible via SSH
  • Python installed on the target host

Step 1 — Define the Inventory

# /etc/ansible/hosts
[wordpress]
192.168.1.100 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa

Step 2 — Create the Playbook

# wordpress.yml
---
- hosts: wordpress
  become: yes
  vars:
    db_name: wordpress
    db_user: wp_user
    db_pass: wp_pass
    wp_dir: /var/www/html/wordpress

  tasks:
    - name: Install LAMP packages
      apt:
        name: [apache2, mysql-server, php, php-mysql, libapache2-mod-php, python3-pymysql]
        state: latest
        update_cache: yes

    - name: Create MySQL database
      mysql_db:
        name: "{{ db_name }}"
        state: present
        login_unix_socket: /var/run/mysqld/mysqld.sock

    - name: Create MySQL user
      mysql_user:
        name: "{{ db_user }}"
        password: "{{ db_pass }}"
        priv: "{{ db_name }}.*:ALL"
        state: present
        login_unix_socket: /var/run/mysqld/mysqld.sock

    - name: Download WordPress
      get_url:
        url: https://wordpress.org/latest.tar.gz
        dest: /tmp/wordpress.tar.gz

    - name: Extract WordPress
      unarchive:
        src: /tmp/wordpress.tar.gz
        dest: /var/www/html/
        remote_src: yes

    - name: Configure wp-config.php
      template:
        src: wp-config.j2
        dest: "{{ wp_dir }}/wp-config.php"

    - name: Set ownership
      file:
        path: "{{ wp_dir }}"
        owner: www-data
        group: www-data
        recurse: yes

    - name: Restart Apache
      service:
        name: apache2
        state: restarted

Step 3 — Run the Playbook

ansible-playbook wordpress.yml

Using Ansible Vault to Store Secrets (Apache Install Example)

Ansible Vault encrypts sensitive data (passwords, keys) in playbooks. This guide shows how to encrypt a variable file and use it to install Apache.

Step 1 — Create an Encrypted Variable File

ansible-vault create secrets.yml

Enter a vault password when prompted, then add your secrets:

---
ansible_sudo_password: mysudopassword
db_password: s3cr3t

Step 2 — Edit an Existing Vault File

ansible-vault edit secrets.yml

Step 3 — Write the Playbook

# install-apache.yml
---
- hosts: webservers
  become: yes
  vars_files:
    - secrets.yml
  tasks:
    - name: Install Apache
      apt:
        name: apache2
        state: latest
        update_cache: yes

    - name: Start and enable Apache
      service:
        name: apache2
        state: started
        enabled: yes

Step 4 — Run with Vault Password

ansible-playbook install-apache.yml --ask-vault-pass

Step 4 (Alternative) — Use a Password File

echo "myvaultpassword" > ~/.vault_pass.txt
chmod 600 ~/.vault_pass.txt

ansible-playbook install-apache.yml --vault-password-file ~/.vault_pass.txt

Encrypt a Single String Value

ansible-vault encrypt_string 'mysecret' --name 'db_password'

Ansible Command Reference

Essential Ansible commands and configuration options for managing remote hosts.

Ad-hoc Commands

# Ping all hosts
ansible all -m ping

# Run a shell command
ansible webservers -m shell -a "uptime"

# Copy a file
ansible all -m copy -a "src=/tmp/file.conf dest=/etc/file.conf"

# Install a package
ansible all -m apt -a "name=nginx state=latest" --become

# Restart a service
ansible all -m service -a "name=nginx state=restarted" --become

# Check disk space
ansible all -m shell -a "df -h"

Playbook Commands

# Run a playbook
ansible-playbook site.yml

# Dry run (check mode)
ansible-playbook site.yml --check

# Run on specific hosts
ansible-playbook site.yml --limit webservers

# Run specific tags
ansible-playbook site.yml --tags "install,configure"

# Skip specific tags
ansible-playbook site.yml --skip-tags "debug"

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

Key Options

  • --become — escalate privileges (sudo)
  • --become-user <user> — run as a specific user
  • --ask-become-pass — prompt for sudo password
  • -i <inventory> — specify inventory file
  • -u <user> — remote username
  • --private-key <key> — SSH private key

Inventory Commands

# List all hosts
ansible all --list-hosts

# List hosts in a group
ansible webservers --list-hosts

# Show gathered facts for a host
ansible <host> -m setup

Common Ansible Errors and Fixes

Troubleshooting common Ansible errors encountered when running playbooks or ad-hoc commands.

Error: Python Not Found on Managed Host

MODULE FAILURE: /bin/sh: 1: /usr/bin/python: not found

Fix: Python 2 or 3 must be installed on the managed host. Install it first:

# Option 1: Use the raw module to install Python
ansible <host> -m raw -a "apt-get install -y python3" --become

# Option 2: Specify python3 interpreter in inventory or ansible.cfg
ansible_python_interpreter=/usr/bin/python3

Error: SSH Host Key Verification Failed

UNREACHABLE! Failed to connect to the host via ssh: Host key verification failed.

Fix: Disable strict host key checking in ansible.cfg or inventory:

[defaults]
host_key_checking = False

# Or in inventory:
ansible_ssh_common_args='-o StrictHostKeyChecking=no'

Error: Permission Denied (sudo)

Failed to set permissions on the temporary files Ansible needs to create...

Fix: Ensure the remote user has NOPASSWD sudo or pass the sudo password:

ansible-playbook site.yml --ask-become-pass

# Or add to /etc/sudoers on managed host:
ubuntu ALL=(ALL) NOPASSWD:ALL

Error: Module Not Found

ERROR! no action detected in task

Fix: Install required collections:

ansible-galaxy collection install community.mysql
ansible-galaxy collection install community.general

Error: Unreachable Host

UNREACHABLE! Failed to connect to the host via ssh

Fix: Test SSH connectivity manually first, then check inventory settings:

ssh -i ~/.ssh/id_rsa ubuntu@<host-ip>

# Check the inventory
ansible <host> -m ping -vvv

Ansible Configuration File (ansible.cfg) Reference

The ansible.cfg file controls Ansible's default behavior. Settings are applied in the following priority order:

  1. ANSIBLE_CONFIG environment variable
  2. ./ansible.cfg (current directory)
  3. ~/.ansible.cfg (home directory)
  4. /etc/ansible/ansible.cfg (global)

Common ansible.cfg Settings

[defaults]
# Default inventory file
inventory = /etc/ansible/hosts

# Default remote user
remote_user = ubuntu

# SSH private key
private_key_file = ~/.ssh/id_rsa

# Disable host key checking
host_key_checking = False

# Set python interpreter
interpreter_python = /usr/bin/python3

# Number of parallel processes
forks = 10

# Log output to file
log_path = /var/log/ansible.log

# Default roles path
roles_path = ~/.ansible/roles:/etc/ansible/roles

# Retry failed hosts
retry_files_enabled = False

[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

[ssh_connection]
# Persist SSH connections
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
pipelining = True

Verify Active Configuration

ansible --version
# Shows which config file is being used

ansible-config dump --only-changed

Ansible Best Practices

Recommended patterns and module choices for writing clean, idiomatic Ansible playbooks.

Use Idiomatic Modules Instead of Shell Commands

Ansible warns when you use command or shell where a dedicated module exists. Use the appropriate module:

# Avoid:
- name: Download file
  command: wget -O /tmp/app.tar.gz https://example.com/app.tar.gz

# Prefer:
- name: Download file
  get_url:
    url: https://example.com/app.tar.gz
    dest: /tmp/app.tar.gz

# Avoid:
- name: Hit an API
  command: curl https://api.example.com/status

# Prefer:
- name: Hit an API
  uri:
    url: https://api.example.com/status
    return_content: yes
  register: api_response

Use Variables and Templates

# vars/main.yml
app_port: 8080
app_user: deploy

# tasks/main.yml
- name: Configure app
  template:
    src: app.conf.j2
    dest: /etc/myapp/app.conf
  notify: restart app

Use Handlers for Service Restarts

handlers:
  - name: restart app
    service:
      name: myapp
      state: restarted

Use Tags for Selective Runs

tasks:
  - name: Install packages
    apt:
      name: nginx
      state: latest
    tags: [install, nginx]

Suppress Specific Warnings

# If you must use command/shell, suppress the warning:
- name: Custom command
  command: myspecialcmd --flag
  args:
    warn: false

Directory Structure (Roles)

roles/
  myrole/
    tasks/main.yml
    handlers/main.yml
    templates/
    files/
    vars/main.yml
    defaults/main.yml
    meta/main.yml

Ansible Inventory (hosts) File Guide

The Ansible inventory file defines the hosts and groups that Ansible manages. By default it is located at /etc/ansible/hosts.

Basic Inventory Format

# /etc/ansible/hosts

# Ungrouped hosts
192.168.1.10
server01.example.com

# Group of hosts
[webservers]
web01 ansible_host=192.168.1.11
web02 ansible_host=192.168.1.12

[databases]
db01 ansible_host=192.168.1.20
db02 ansible_host=192.168.1.21

# Group of groups
[production:children]
webservers
databases

Host Variables

[webservers]
web01 ansible_host=192.168.1.11 ansible_user=ubuntu ansible_port=22
web02 ansible_host=192.168.1.12 ansible_user=deploy ansible_ssh_private_key_file=~/.ssh/deploy_key

Group Variables

[webservers:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3

YAML Inventory Format

# inventory.yml
all:
  children:
    webservers:
      hosts:
        web01:
          ansible_host: 192.168.1.11
        web02:
          ansible_host: 192.168.1.12
      vars:
        ansible_user: ubuntu
    databases:
      hosts:
        db01:
          ansible_host: 192.168.1.20

Verify Inventory

# List all hosts
ansible all --list-hosts -i inventory.yml

# Ping a specific group
ansible webservers -m ping -i inventory.yml

Install MySQL 5.7 on Ubuntu 18.04 with Ansible

Installing MySQL 5.7 on Ubuntu 18.04 with Ansible requires extra steps to handle the default auth plugin and the root password. This guide covers a working approach.

Playbook

# mysql57.yml
---
- hosts: dbservers
  become: yes
  vars:
    mysql_root_password: "yourStrongPassword"

  tasks:
    - name: Install MySQL 5.7
      apt:
        name: [mysql-server, python3-pymysql]
        state: present
        update_cache: yes

    - name: Start MySQL
      service:
        name: mysql
        state: started
        enabled: yes

    - name: Set root password
      mysql_user:
        name: root
        password: "{{ mysql_root_password }}"
        login_unix_socket: /var/run/mysqld/mysqld.sock
        plugin: mysql_native_password
        state: present

    - name: Remove anonymous users
      mysql_user:
        name: ''
        host_all: yes
        state: absent
        login_user: root
        login_password: "{{ mysql_root_password }}"

    - name: Remove test database
      mysql_db:
        name: test
        state: absent
        login_user: root
        login_password: "{{ mysql_root_password }}"

    - name: Flush privileges
      command: mysql -u root -p{{ mysql_root_password }} -e "FLUSH PRIVILEGES;"

Key Points

  • On Ubuntu 18.04, MySQL root uses auth_socket by default — you must set plugin: mysql_native_password to use password auth.
  • Use login_unix_socket for the initial root password set before password auth is configured.
  • Install python3-pymysql — Ansible's MySQL modules require it.

Run the Playbook

ansible-playbook mysql57.yml --ask-vault-pass

Connect Ansible to Hosts on Non-Standard SSH Ports

When managed hosts use non-standard SSH ports (common with port-forwarded VMs or containerized environments), you need to specify the port in the inventory.

Inventory Configuration

# /etc/ansible/hosts
[ixioo]
ixioo4 ansible_host=82.127.150.51 ansible_port=22004
ixioo6 ansible_host=82.127.150.51 ansible_port=22006
ixioo7 ansible_host=82.127.150.51 ansible_port=22007

[ixioo:vars]
ansible_user=ubuntu
ansible_ssh_common_args='-o StrictHostKeyChecking=no'

Test Connectivity

ansible ixioo -m ping -u ubuntu --ask-pass

Using Different SSH Keys per Host

[servers]
server01 ansible_host=10.0.0.1 ansible_user=admin ansible_ssh_private_key_file=~/.ssh/admin_key
server02 ansible_host=10.0.0.2 ansible_user=deploy ansible_ssh_private_key_file=~/.ssh/deploy_key

Using a Jump Host (Bastion)

[internal:vars]
ansible_ssh_common_args='-o ProxyJump=ubuntu@bastion.example.com'

SSH Config Alternative

Define host aliases in ~/.ssh/config and reference them in inventory:

# ~/.ssh/config
Host ixioo4
  HostName 82.127.150.51
  Port 22004
  User ubuntu
  IdentityFile ~/.ssh/id_rsa
  StrictHostKeyChecking no
# inventory
[ixioo]
ixioo4
ixioo6