Introduction To Ansible - Automation | Configuration | Deployment
Posted on May 29, 2023 (Last modified on March 8, 2024) • 9 min read • 1,897 wordsIntroduction to using Ansible to help you configure, deploy, and manage your infrastructure and applications why using Ansible as a configuration management tool is useful.
In today’s fast-paced world of software development, automation is key to ensuring that teams can deliver high-quality software quickly and efficiently. One of the most popular tools for IT automation is Ansible, an open-source platform that simplifies tasks such as configuration management, software deployment, and task automation.
Ansible is an IT automation tool that helps you configure, deploy, and manage your infrastructure and applications. Ansible is fairly simple use, flexibile, and uses an agentless architecture which means that it doesn’t require any additional software to be installed on the target machines, making it lightweight and easy to use. It uses a declarative language and tasks are defined in YAML (Yet Another Markup Language), so all you have to do is define the desired state of your system and Ansible takes care of making it happen.
I followed the Official Docs to install Ansible using Python pip. Make sure you have pip
installed (curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
) and for a control node which is a system on which Ansible is installed where you run Ansible commands such as ansible
or ansible-inventory
on a control node you will need Python 3.9 - 3.11 for Ansible version 2.14 at the time of writing. From the official docs you can install ansible by: python3 -m pip install --user ansible
to install Ansible for the current user. The docs also have a basic getting started guide and the first two inventory examples below are from the official docs.
Once installed, you’ll need to create an inventory and define your hosts. This can be an INI file which might look like:
mail.example.com
[webservers]
foo.example.com
bar.example.com
[dbservers]
one.example.com
two.example.com
three.example.com
Or a YAML file, which I use, looks like:
all:
hosts:
mail.example.com:
children:
webservers:
hosts:
foo.example.com:
bar.example.com:
dbservers:
hosts:
one.example.com:
two.example.com:
three.example.com:
Here is what my current YAML inventory looks like where I have defined variables for my SSH key and sudo
password and have used IP addresses for my machines.
all:
vars:
# These are global variables, they can be overwritten at the host level
ansible_user: liam
# We can use variables by putting them in double curly brackets
# these variables are assigned in a vault file and read here
#ansible_ssh_pass: '{{ host_default_pass }}'
# The "become_pass" is used from sudo commands
ansible_become_pass: '{{ host_default_pass }}'
ansible_ssh_private_key_file: '{{ host_ssh_key }}'
ansible_python_interpreter: auto_silent
# This little trick is to use short hostnames like myhost
# instead of myhost.mynetwork.local, the domain can be overwritten
# at the host level as well
#ansible_host: '{{ inventory_hostname }}.{{ host_domain }}'
#host_domain: 'mynetwork.local'
hosts:
# These are hosts on the global level, we can also group hosts as
# seen below under "children"
children:
# These are host groups, we can use groups later to target
# hosts as a group instead of individually
# hosts without ips are defined in .ssh/config
containers:
vars:
ansible_user: root
hosts:
pihole:
ansible_host: 192.168.1.124
mysql-server:
ansible_host: 192.168.1.134
postgres-server:
ansible_host: 192.168.1.232
dev-ct:
ansible_host:
virtual_machines:
hosts:
media-server:
ansible_host: 192.168.1.218
docker-vm:
ansible_host: 192.168.1.184
dev-vm:
ansible_host: 192.168.1.214
As demonstrated in the examples above, you can reference individual machines or groups of machines in Ansible. This feature is particularly useful when writing playbooks that need to execute tasks against a specific set of machines. For instance, you may have a task that updates a group of webservers or machines located in a particular region. Moving forward, it is recommended to switch to using hostnames instead of IPs. Additionally, you can leverage Terraform to dynamically create and populate your inventory, which can then trigger a playbook run. This approach ensures that your inventory is always up-to-date and eliminates the need for manual updates.
The basic example below is slightly adapted from one of playbooks that I apply to my virtual machines. In this example the docker-vm
host is the target. The playbook:
apt update
and upgrade
qemu-guest-agent
for Proxmoxp7zip
, unzip
and rsync
pip
and then docker
and docker-compose
Python packages using pip
fail2ban
- hosts: docker-vm
tasks:
- name: update and upgrade apt packages
become: yes
apt:
upgrade: yes
update-cache: yes
- name: install qemu guest agent
become: yes
apt:
name: qemu-guest-agent
- name: install some useful packages
become: yes
apt:
pkg:
- p7zip-full
- unzip
- rsync
- name: install pip
become: yes
apt:
name: python3-pip
- name: install ansible dependency pip docker
pip:
name:
- docker
- docker-compose
- name: install fail2ban
become: yes
apt:
name: fail2ban
Below is an example of a larger playbook that I have used to configure my media server. This uses some roles;
base
: Does an apt
update and upgrade and are the tasks defined above for the “Basic Playbook Example”user
: Sets up my user and configures /etc/sudoers
nfs_shares
: Add my NFS sharesffmpeg
: Installs and configures ffmpegdocker
: Install docker engine and docker composedeploy_watchtower
: Deploys a watchtower container that monitors and notifies of Docker Image updates for my running containersrclone
: Installs and configures rclone for backupsThe deployment and configuration of my media server have become much easier with the use of Ansible. The tasks involved in this process include creating the necessary directories, copying over docker-compose files for media services, and any environment files. Once these tasks are completed, the containers are run, and their status is confirmed. This streamlined process saves a significant amount of time compared to manually copying over files and running commands on the terminal, which I have done for years. By combining Ansible with Terraform, I can now provision a virtual machine and configure my media server with ease by simply running the playbook against the machine. That way I can sit back and drink more ☕.
- hosts: media-server
roles:
- base
- user
- nfs_shares
- ffmpeg
- docker
- deploy_watchtower
- rclone
tasks:
- name: creating required directories
file:
path: "/home/{{ ansible_user }}/docker/{{ item }}"
state: directory
loop:
- jellyfin
- media-stack
- name: copy docker compose files
copy:
src: "templates/docker-compose/{{ item.file }}"
dest: "/home/{{ ansible_user }}/docker/{{ item.name }}/docker-compose.yaml"
loop:
- { name: "jellyfin", file: "jellyfin.yaml" }
- { name: "media-stack", file: "media-stack.yaml" }
- name: copy env files
copy:
src: "templates/env/{{ item.file }}"
dest: "/home/{{ ansible_user }}/docker/{{ item.name }}/.env"
loop:
- { name: "jellyfin", file: "jellyfin.env" }
- { name: "media-stack", file: "media-stack.env" }
- name: run media containers
community.docker.docker_compose:
project_src: "/home/{{ ansible_user }}/docker/{{ item }}/"
loop:
- jellyfin
- media-stack
register: output
- ansible.builtin.debug:
var: output
- ansible.builtin.assert:
that:
- "{{ item }}.state.running"
loop:
- sonarr
- radarr
- nzbget
- jellyseer
- bazarr
- prowlarr
- jellyfin
- readarr
I have used variables in my inventory, playbooks and roles to either set default values or to access secrets. I won’t cover variables here but will refer you to the Variables Docs on using variables in your files, where to store variables for hosts or groups and how Ansible defines variable precendence. You can find more examples of the Ansible playbooks and roles I’ve created for my homelab (and also Terraform examples) in my homelab GitHub repo.
Similar to Terraform you can define secrets in a file, use an external secrets manager or even your CI/CD platform. To get stared I have opted to use Ansible Vault to store and encrypt any credentials and secret information. To do this I have stored and encrypted the relevant secrets for a machine in variables files. To create an encypted file you can run ansible-vault create --vault-id test@multi_password_file file.yml
where test@multi_password_file
is the password for the test
vault from the multi_password_file
. Your default text editor will open and you can input your secrets using YAML
variable_name: secret_value
Alternatively, you can create a file with your secrets in and encrypt this existing file using: ansible-vault encrypt file.yml
. You can view and edit encrypted files using ansible-vault view file.yml
and ansible-vault edit file.yml
.
Ansible is a powerful and versatile IT automation tool that can help streamline your software engineering projects and improve overall efficiency. By leveraging its key features, such as playbooks, roles, and modules, you can simplify complex tasks, enhance collaboration, and ensure consistent, high-quality deployments. If you’re looking to automate your infrastructure and application management, Ansible is an excellent choice to consider. You can already see from even the basic examples I’ve given here, and I’ll cover how I have automated the deployment of my homelab in a future post, that utilising tools such as Ansible greatly increases efficiency and reduces errors.