Easy way to Install Ansible on Ubuntu 24.04

Install and Setup Ansible on Ubuntu

In this tutorial, we are going to learn how to install Ansible on Ubuntu 24.04. Ansible is a simple agentless IT automation tool. It handles configuration management, application deployment, cloud provisioning, ad-hoc task execution, network automation, and multi-node orchestration over the SSH protocol. Ansible makes easy to automate repetitive tasks.

Basic Ansible Concepts/Architecture

Ansible automation tool is made up various components. So, what are these components that makes up Ansible?

  • Control Node: As you already know, Ansible is used to automate various tasks on your infrastructure nodes. As such, Ansible has to be installed on one node that is then used to connect to (via SSH) and orchestrate tasks on the remote nodes. The node on which you install Ansible, becomes the control node.
  • Managed Node: Ansible managed node refers to nodes that Ansible will connect to and execute various tasks or commands on them. They can be virtual machines, physical servers, or any other systems that you can be connected to via SSH.
  • Inventory: Ansible inventory is file that lists managed hosts, either individually or as grouped hosts depending on a common role. As such, inventory defines hostnames or IP addresses of the managed nodes, along with any variables or groupings.
  • Playbooks: Ansible playbooks are YAML files that define a set of tasks, here in called plays, that are meant for execution on the managed hosts. They are execute one by one, and in the order they are defined, against all or specific hosts in the inventory. “playbooks are made up of plays; plays are made up of tasks“.
  • Tasks: Ansible tasks define specific actions or commands to be executed against all or specific subset of hosts defined in the inventory. Tasks can include actions to install packages, manage files, start or stop services, run scripts, …
  • Modules: Ansible modules are the building blocks of Ansible. They represent small Python codes that does the actual execution of the tasks defined in the Ansible playbooks on managed hosts. They contain metadata that defines when and where a specific automation task is executed and which users can execute it. By default, Ansible provides a large collection of built-in modules, that facilitates execution of a wide range of tasks.
  • Plugins: Ansible plugins are used together with Ansible modules extend the functionality of Ansible.
  • Handlers: Ansible handlers are used to define specific actions to be taken whenever Ansible tasks meet specific condition. For example, you can define an handler to restart application service whenever a change is made to that application configuration.
  • Roles: You can think of Ansible roles define a group related tasks, variables, templates, and other Ansible resources in a standard directory structure.
  • Ansible Vault: It is possible that some of your Ansible tasks requires sensitive information such as credentials to perform specific action. Such sensitive information can be securely stored in an Ansible vault in an encrypted format.
  • Variables: Ansible uses variables to store values that can be re-used across Ansible tasks/playbooks.

Installing Ansible on Ubuntu 24.04

Run System Update

Before you can proceed, ensure that your system package cache is up-to-date;

sudo apt update

Install Ansible on Ubuntu 24.04

There different methods you can use to install Ansible on Ubuntu 24.04 systems;

  1. Install Ansbile using PIP on Ubuntu
  2. Install Ansible using APT Package Manager

Install Ansbile using PIP on Ubuntu

To install Ansbile using Python PIP command, install PIP before you can proceed;

sudo apt install python3-pip python3-venv

If PIP is already installed, you can upgrade it;

However, recent versions of Python PIP now discourages glabol Python package installation or removal for the purposes of ensuring system stability. As a result, it encourages the installation of Python packages on user virtual environment. Read more on PEP 668.

As a result, create your Python virtual environment where you can have control of what Python packages you can ins without introducing any conflicts with other system-wide Python installations.

python3 -m venv ansible

This will create a self contained Ansible Python Virtual environment in the current working directory.

ls -1 ansible
bin
include
lib
lib64
pyvenv.cfg

Next, activate your Python virtual environment and upgrade PIP (if it needs upgrade);

source ansible/bin/activate
python3 -m pip install --upgrade pip

Then install Ansbile for the current user only by running the command below;

python3 -m pip install ansible

All the Ansible binaries are installed in ‘$HOME/ansible/bin‘. As a result, you can only access Ansible when you login and activate the users Python virtual environment created above.

Check Ansible version;

ansible --version
ansible [core 2.16.6]
  config file = None
  configured module search path = ['/home/kifarunix/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/kifarunix/ansible/lib/python3.12/site-packages/ansible
  ansible collection location = /home/kifarunix/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/kifarunix/ansible/bin/ansible
  python version = 3.12.3 (main, Apr 10 2024, 05:33:47) [GCC 13.2.0] (/home/kifarunix/ansible/bin/python3)
  jinja version = 3.1.3
  libyaml = True

Install Ansible using APT Package Manager

Ubuntu default repos provides Ansible packages. However, such packages may not be up-to-date.

To check available versions of the Ansible package, run the command below;

sudo apt-cache policy ansible

Sample output from Ubuntu 24.04;

ansible:
  Installed: (none)
  Candidate: 9.2.0+dfsg-0ubuntu5
  Version table:
     9.2.0+dfsg-0ubuntu5 500
        500 http://de.archive.ubuntu.com/ubuntu noble/universe amd64 Packages

Read more on Ansible changelogs.

The most recent version of Ansible provided by the default Ubuntu 24.04 repositories as of this writing is v9.4.0.

You can install the latest version from the Ansible PPA repositories. However, Ubuntu 24.04, Noble Numbat PPA repos are currently unavailable.

As such, install Ansible from the default Ubuntu 24.04 repositories.

You can then install Ansible by executing the command below;

sudo apt install ansible

Once the installation is done, you can check the version of install Ansible as follows;

ansible --version

When the PPA repos for Ubuntu 24.04 are available, you can install latest Ansible release versions by running the commands;

sudo apt -y install software-properties-common
sudo apt-add-repository ppa:ansible/ansible --yes
sudo apt update
sudo apt install ansible
Installing Ansible on Ubuntu 24.04 from PPA repos

Setup Ansible on Control Node

Now that Ansible is installed on an Ubuntu 24.04 control node, it is time to set it up to enable you automate deployment of your tasks on the managed remote hosts via SSH protocol.

Running Ansible Installed System-Wide and Python Virtualenv

If you have installed Ansible system-wide, then you can run normally because all required binaries must already be on the path.

If you are running Ansible on a user’s Python virtual environment, then you must activate that virtual environment for you to access Ansible binaries.

For example, our user’s Ansible virtual env is, ~/ansible. To activate this environment;

source ~/ansible/bin/activate

After that, you should be able to now use Ansible normally.

Configure Ansible Inventory

If you have installed /etc/ansible/hosts is the default Ansible inventory file. It is also possible to define a custom file as your Ansible inventory file. When you use a non-default inventory file, you have to specify the path to it with -i <path> option while running ansible.

In the inventory file:

  • you can specify the details of the hosts using resolvable hostnames or IP addresses,
  • A group of hosts are delimited by [header] elements
  • A hostname/IP address can be a member of multiple groups

See examples below to create a hosts file with ungrouped hosts, grouped hosts and a range of hosts.

In this tutorial, I will create my inventory in my home directory.

mkdir -p $HOME/etc/ansible
vim $HOME/etc/ansible/hosts

NOTE: Any individual host, (Ungrouped hosts),should be specified before any group headers.


# Ungrouped list of hosts
server01.kifarunix-demo.com
192.168.122.92

# Range of hosts
192.168.122.[111:112]

[webapps]
app[01:04].kifarunix-demo.com

# Grouped Hosts
[database]
db[01:03].kifarunix-demo.com
db05.kifarunix-demo.com
db07.kifarunix-demo.com

Save and exit the file.

You can list the hosts in your inventory using the ansible-inventory command. For example, to display the contents of our hosts list above in YAML format;

ansible-inventory -i $HOME/etc/ansible/hosts --list -y
all:
  children:
    database:
      hosts:
        db01.kifarunix-demo.com: {}
        db02.kifarunix-demo.com: {}
        db03.kifarunix-demo.com: {}
        db05.kifarunix-demo.com: {}
        db07.kifarunix-demo.com: {}
    ungrouped:
      hosts:
        192.168.122.111: {}
        192.168.122.112: {}
        192.168.122.92: {}
        server01.kifarunix-demo.com: {}
    webapps:
      hosts:
        app01.kifarunix-demo.com: {}
        app02.kifarunix-demo.com: {}
        app03.kifarunix-demo.com: {}
        app04.kifarunix-demo.com: {}

Read more on how to build ansible inventory.

Setup Ansible SSH key authentication

Generate SSH Keys

Ansible uses SSH to connect to managed hosts. Use of SSH key authentication (usually passwordless) is one of the common methods that by default Ansible uses for managing remote hosts. It is also possible to have Ansible use username/password authentication to connect to managed hosts,however not generally recommended.

Therefore, you need to generate passwordless SSH key and copy the keys to the remote hosts your want to manage with Ansible.

ssh-keygen

For passwordless SSH key generation, simply press ENTER for empty password.

Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/kifarunix/.ssh/id_ed25519): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/kifarunix/.ssh/id_ed25519
Your public key has been saved in /home/kifarunix/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:7qHPByiZZuog9Z3IRgTBhd0aKQ8wuXgBoh0EZZ98tVg kifarunix@noble
The key's randomart image is:
+--[ED25519 256]--+
|*OB=.o  E        |
|++*=+..+ .       |
|o..==oo .        |
|o ..o.           |
| .. .o .S        |
| . +*o.o.        |
|o  +=.o o.       |
|....   + ..      |
| ..   ..+.       |
+----[SHA256]-----+

Read more about other connection methods on Ansible connection plugins page.

Copy SSH Public keys to Managed Hosts

Next, copy the keys to the remote hosts. Use a remote user with sudo rights.

for i in 192.168.122.11 192.168.122.92; do ssh-copy-id -o StrictHostKeyChecking=no johndoe@$i; done

In this demo, we will be using a user with passwordless sudo rights. To enable a user to run commands with sudo without being prompted for password, run the command below on the remote hosts being managed.

sudo EDITOR=vim visudo /etc/sudoers.d/johndoe

Paste the following, replacing the usernames accordingly;

johndoe ALL=(ALL:ALL) NOPASSWD: ALL

Save and exit the file.

Read more on Ansible privileges escalation page.

Test Ansible Connection to Remote Hosts

Ansible uses ping module to verify the reachability to the remote hosts defined on the inventory file. Below is our test inventory;

ansible-inventory -i $HOME/etc/ansible/hosts --list -y
all:
  children:
    centos:
      hosts:
        192.168.122.11: {}
    ubuntu:
      hosts:
        192.168.122.92: {}

To test the connection, as user johndoe, whose keys we copied above;

ansible -i $HOME/etc/ansible/hosts -m ping -u johndoe all
192.168.122.11 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
192.168.122.92 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

Running Ansible ad-hoc commands

If you have tasks that you would rarely repeat, ad-hoc commands come in so handy. With ad-hoc commands, you can manage user accounts, services, packages, run ad-hoc system commands like check free RAM, gather system facts.

To see a few examples, check if the remote systems have internet connectivity using ping command;

 ansible -i $HOME/etc/ansible/hosts -a "ping 8.8.8.8 -c 3" all -u johndoe
192.168.122.11 | CHANGED | rc=0 >>
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=59 time=5.06 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=59 time=5.71 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=59 time=5.35 ms

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 5.061/5.370/5.706/0.263 ms
192.168.122.92 | CHANGED | rc=0 >>
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=59 time=5.12 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=59 time=5.22 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=59 time=5.26 ms

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 5.121/5.199/5.258/0.057 ms

To check free memory;

ansible -i $HOME/etc/ansible/hosts -a "free -h" all -u johndoe
192.168.122.92 | CHANGED | rc=0 >>
               total        used        free      shared  buff/cache   available
Mem:           3.8Gi       268Mi       1.4Gi       1.0Mi       2.1Gi       3.3Gi
Swap:          2.1Gi          0B       2.1Gi
192.168.122.11 | CHANGED | rc=0 >>
               total        used        free      shared  buff/cache   available
Mem:           1.2Gi       316Mi       827Mi       6.0Mi       278Mi       955Mi
Swap:             0B          0B          0B

To install a package, for example a vim editor, as user johndoe with sudo rights (--become);

ansible -i $HOME/etc/ansible/hosts -m apt -a "name=vim state=present" all -u johndoe --become
ansible -i $HOME/etc/ansible/hosts -m yum -a "name=vim state=present" centos -u johndoe --become
192.168.122.11 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "msg": "",
    "rc": 0,
    "results": [
        "Installed: vim-filesystem-2:8.2.2637-20.el9.noarch",
        "Installed: vim-common-2:8.2.2637-20.el9.x86_64",
        "Installed: gpm-libs-1.20.7-29.el9.x86_64",
        "Installed: vim-enhanced-2:8.2.2637-20.el9.x86_64"
    ]
}

Read more about ad-hoc commands on Introduction to ad-hoc commands.

If you want to auto-detect the package manager, simply use package module against all hosts;

ansible -i $HOME/etc/ansible/hosts -m package -a "name=vim state=present" all -u johndoe --become
192.168.122.11 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "msg": "Nothing to do",
    "rc": 0,
    "results": []
}
192.168.122.92 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "cache_update_time": 1713503759,
    "cache_updated": false,
    "changed": false
}

Using Ansible Playbooks

Playbooks are Ansible’s configuration, deployment, and orchestration language. They define roles and tasks (a set of steps to take to set up a specific process) to be executed on the remote hosts.

In this demo, we will cover two simple examples of playbooks;

So before we can proceed, create Ansible roles directory. Ansible Roles defines a collections of variables, tasks, files, templates and any other ansible artefacts.

For our example, we will have two roles, useraccount and apache, for defining what user account to create and the package to be installed.

Below is our simple sample Roles directory structure;

tree myplaybooks
myplaybooks/
├── main.yml
└── roles
    ├── useraccounts
    │   └── tasks
    │       └── main.yml
    └── webserver
        └── tasks
            └── main.yml

6 directories, 3 files

To create the relevant directories as per above structure;

mkdir -p myplaybooks/roles/{useraccounts,webserver}/tasks

Create master playbook, main.yml to define values that will be applicable to all other tasks;

vim myplaybooks/main.yml
---
#
# A common Playbook to Create a user accounts.
#
- hosts: all 
  remote_user: johndoe
  become: yes
  roles:
  - useraccounts
  - webserver

Ansible Playbook to Create User Account

The playbook for creating user account is as per my roles directory structure above. So create a file in which you will define particulars of a user to be created.

vim myplaybooks/roles/useraccounts/tasks/main.yml
---
#
# Create a user account
#
- name: Add User Jane Doe to the system
  user:
          name: janedoe
          password: "{{ 'strongpassword' | password_hash('sha512_crypt', 65534) }}"
          groups: "{{ 'sudo' if ansible_distribution == 'Ubuntu' else 'wheel' }}"
          shell: /bin/bash
          comment: Jane Doe
          expires: 1584993090

Ensure passlib for hashing the password is installed.

pip install passlib

Ansible Playbook to Install Apache Web server

Similarly, create task file for installing Apache web server on systems;

vim myplaybooks/roles/webserver/tasks/main.yml
---
#
# Install Apache Web server on CentOS 9
#
- name: Install Apache HTTP Server on CentOS
  ansible.builtin.package:
    name: httpd
    state: latest
  when: ansible_distribution == 'CentOS'

- name: Install Apache2 on Ubuntu/Debian
  ansible.builtin.package:
    name: apache2
    state: latest
  when: ansible_distribution in ['Ubuntu', 'Debian']

To list master Ansible Playbook tasks;

ansible-playbook myplaybooks/main.yml --list-tasks -i etc/ansible/hosts

playbook: myplaybooks/main.yml

  play #1 (all): all	TAGS: []
    tasks:
      useraccounts : Add User Jane Doe to the system	TAGS: []
      webserver : Install Apache HTTP Server on CentOS	TAGS: []
      webserver : Install Apache2 on Ubuntu/Debian	TAGS: []

Running Ansible Playbook Dry Run checks

To check your playbook without making any changes on the remote host, use the --check option.

ansible-playbook myplaybooks/main.yml --check -i etc/ansible/hosts

PLAY [all] ************************************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************
ok: [192.168.122.11]
ok: [192.168.122.92]

TASK [useraccounts : Add User Jane Doe to the system] *****************************************************************************************************************************
changed: [192.168.122.92]
changed: [192.168.122.11]

TASK [webserver : Install Apache HTTP Server on CentOS] ***************************************************************************************************************************
skipping: [192.168.122.92]
changed: [192.168.122.11]

TASK [webserver : Install Apache2 on Ubuntu/Debian] *******************************************************************************************************************************
skipping: [192.168.122.11]
changed: [192.168.122.92]

PLAY RECAP ************************************************************************************************************************************************************************
192.168.122.11             : ok=3    changed=2    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
192.168.122.92             : ok=3    changed=2    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0  

Executing Ansible Playbook

Once you have verified that there is not error with the checks above, proceed to execute the playbook;

ansible-playbook myplaybooks/main.yml -i etc/ansible/hosts
PLAY [all] ************************************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************
ok: [192.168.122.11]
ok: [192.168.122.92]

TASK [useraccounts : Add User Jane Doe to the system] *****************************************************************************************************************************
changed: [192.168.122.92]
changed: [192.168.122.11]

TASK [webserver : Install Apache HTTP Server on CentOS] ***************************************************************************************************************************
skipping: [192.168.122.92]
changed: [192.168.122.11]

TASK [webserver : Install Apache2 on Ubuntu/Debian] *******************************************************************************************************************************
skipping: [192.168.122.11]
changed: [192.168.122.92]

PLAY RECAP ************************************************************************************************************************************************************************
192.168.122.11             : ok=3    changed=2    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
192.168.122.92             : ok=3    changed=2    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0 

Verify User Account Creation and Package installed using Ansible

You can create yet another master playbook to verify if user account for janedoe is created as well as checking is Apache is installed by checking its service if available. Both of these can be checked using Ansible getent module.

vim myplaybooks/check.yml
---
- hosts: all
  remote_user: johndoe
  become: yes
  gather_facts: yes
  tasks:
          - name: Gather service facts
            ansible.builtin.service_facts:

          - name: Check if user Jane Doe (janedoe) is created.
            getent:
                  database: passwd
                  key: janedoe
          - name: Check if Apache service on CentOS 9.
            getent:
                  database: services
                  key: http
            when: ansible_distribution == 'CentOS'

          - name: Check if Apache service on Ubuntu.
            ansible.builtin.debug:
              msg: "Apache service is {{ 'present' if 'apache2' in ansible_facts.services.keys() else 'not present' }}"
            when: ansible_distribution in ['Ubuntu', 'Debian']

Execute the playbook;

ansible-playbook myplaybooks/check.yml -i etc/ansible/hosts

PLAY [all] ************************************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************
ok: [192.168.122.11]
ok: [192.168.122.92]

TASK [Gather service facts] *******************************************************************************************************************************************************
ok: [192.168.122.11]
ok: [192.168.122.92]

TASK [Check if user Jane Doe (janedoe) is created.] *******************************************************************************************************************************
ok: [192.168.122.92]
ok: [192.168.122.11]

TASK [Check if Apache service on CentOS 9.] ***************************************************************************************************************************************
skipping: [192.168.122.92]
ok: [192.168.122.11]

TASK [Check if Apache service on Ubuntu.] *****************************************************************************************************************************************
skipping: [192.168.122.11]
ok: [192.168.122.92] => {
    "msg": "Apache service is present"
}

PLAY RECAP ************************************************************************************************************************************************************************
192.168.122.11             : ok=4    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
192.168.122.92             : ok=4    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0 

And there you go.

That marks the end of our tutorial on how to install and setup Ansible on Ubuntu 20.04. We hope this was informative. Enjoy.

Further Reading

Getting Started with Ansible

Ansible tutorials

SUPPORT US VIA A VIRTUAL CUP OF COFFEE

We're passionate about sharing our knowledge and experiences with you through our blog. If you appreciate our efforts, consider buying us a virtual coffee. Your support keeps us motivated and enables us to continually improve, ensuring that we can provide you with the best content possible. Thank you for being a coffee-fueled champion of our work!

Photo of author
Kifarunix
Linux Certified Engineer, with a passion for open-source technology and a strong understanding of Linux systems. With experience in system administration, troubleshooting, and automation, I am skilled in maintaining and optimizing Linux infrastructure.

Leave a Comment