Contents

Ansible - Configuration Management

Introduction

This is a beginner taster course, I’m going to use Ansible to deploy and configure a basic web server. I’ll cover the core concepts and we should understand how it works and its place in a DevOps toolbox.

Pros:

  • Easy to read
  • Uses SSH rather than an Agent ()
  • quick to deploy consistent configuration across hundreds of linux servers

Cons:

  • Limited Windows support - (can’t install directly but can with WSL2)
  • Being procedural, can cause errors in very large (thousands) or complex infrastructure (dynamic micro servers).

Use-cases

  • Solving your own problems

And this is worth highlighting that Ansible is a great tool to solve a difficult problem in an environment you manage. It ensures servers / virtual machines are in a desired, consistent state. If there is an app that needs a specific version or data needs to be collected in a certain flow. It automates the end-to-end process of deploying applications, including copying files, installing dependencies, and restarting services in a repeatable manner. Install Ansible First, we install Ansible on a machine. To avoid clashing configurations, I will be using a fresh Ubuntu desktop VM. These steps are possible to run on MacOS locally but Windows does not support, so Windows Subsystem for Linux is required. I won’t cover Mac and Windows here.

Ubuntu/Debian: sudo apt update && apt install ansible Then verify it with: ansible –version Seeing a version number means it is ready. This machine now becomes our control node — the place from which we define and push configuration.

Points to Clarify

Before moving further, let’s clarify.

Agentless Deployed agents have their own pros and cons but without needing to deploy an agent, SSH is widely integrated into Linux distributions. SSH needs to be enabled and allowed - firewall rules can lock down SSH protocol or local ports. This approach lowers the friction to achieve the task at hand.

Declarative The Declarative part took me a while to conceptualise, in the YAML we write the desired state to get to and Ansible drives - we don’t bring a map with directions. This lowers the barrier to entry but does raise the barrier to master Ansible.

Versatility It can handle configuration management, application deployment, and infrastructure provisioning across servers, networks, and clouds. Getting network devices to pass compliance checks is a specific use-case but easily handled by Ansible.

For this guide, we’ll use localhost as our target.

Inventory & First Command

Create a workspace: mkdir ansible-start cd ansible-start

Create an inventory file: nano inventory.ini

Set the target as the local host: [local] localhost ansible_connection=local

Test it: ansible all -i inventory.ini -m ping

Which should return: “ping”: “pong”

This confirms Ansible can communicate with our defined host. Local host in this case but it is the fist hurdle.

Now a useful diagnoses command: ansible all -i inventory.ini -m command -a “uptime”

Hurray, this proves we can query the target and can move on to more.

##First Playbook

Create a playbook: nano install_nginx.yml YAML is indentation (spaces) dependent, always indent by factor of two spaces for new lines. Paste or type in:


  • name: Install and start Nginx hosts: local become: yes

    tasks:

    • name: Install Nginx apt: name: nginx state: present update_cache: yes

    • name: Ensure Nginx is running service: name: nginx state: started enabled: yes

To run it (make sure you’re in the correct directory): ansible-playbook -i inventory.ini install_nginx.yml

What happened? Ansible gets Nginx installed, started, and enabled. If we run it again, nothing should change.

If you can’t view the target host default Nginx webpage, we can pull it with: curl http://localhost

Terminal should output the default Nginx page.

Dynamic Variables

To improve the playbook, replace hardcoded values with variables. Create another playbook: nano install_webserver.yml

Paste in:


  • name: Install web server hosts: local become: yes vars: package_name: nginx

    tasks:

    • name: Install package apt: name: “{{ package_name }}” state: present update_cache: yes

By introducing package_name, it becomes reusable. If we want apache2, the same structure installs a different server.

Variables are stated in the vars section and referenced using the double quotes, double curly braces, space name, variable name {{ var-name }}. Which is to align with the same convention from Jinja2 (j2 files).

Templates (the Real value) Let’s customize the homepage.

Create a templates directory: mkdir templates nano templates/index.html.j2

Paste in:

Hello {{ ansible_hostname }}

Deployed using Ansible.

Now update the playbook:


  • name: Install web server hosts: local become: yes vars: package_name: nginx

    tasks:

    • name: Install package apt: name: “{{ package_name }}” state: present update_cache: yes

    • name: Deploy custom index page template: src: templates/index.html.j2 dest: /var/www/html/index.html

    • name: Ensure web server is running service: name: “{{ package_name }}” state: restarted enabled: yes

Run: ansible-playbook -i inventory.ini install_webserver.yml

When we refresh the browser, the page is generated using the template variable. This is touching on the real value.

If needed, pull it with: curl http://localhost

Example Structure

A realistic layout looks like:

ansible-start/ │ ├── inventory.ini ├── site.yml ├── roles/ │ └── web/ │ ├── tasks/main.yml │ ├── templates/ │ └── handlers/

(tree -L 3)

From here, a next step is to target remote VMs over SSH and manage multiple hosts.