ANSIBLE TUTORIALS #1
In this series of tutorials, we would be creating our own Ubuntu and CentOS VMs (virtual machines) using Vagrant and VirtualBox, and with these VMs, we would learn how to execute various Ansible tasks by writing playbooks to:
- create users with sudo privileges on remote servers,
- update and upgrade servers,
- install Apache, NGINX, Terraform, Docker, and Jenkins,
- deploy a simple web page,
- demonstrate the use of ansible roles, handlers, host_vars, and so on.
This is going to be so much fun! At the end of ANSIBLE TUTORIAL #1, I will leave the link to the next tutorial below. Please make sure you follow the series to the end to get the full package.
Prerequisites
This is a hands-on tutorial, and as such, you need
- a Personal Computer with a stable internet connection,
- VirtualBox and Vagrant installed,
- background knowledge of Linux CLI, Vagrant, and VirtualBox.
In this particular tutorial, we would focus on the tasks outlined below:
- Create two virtual machines: Ubuntu and CentOS.
- Create a new user with sudo permissions on the target virtual machines.
- Write an Ansible playbook to update and upgrade the server depending on the OS.
- Install Apache on both Ubuntu and CentOS using a single task.
- Also, write a task to make sure Apache is started.
- Copy a default HTML page from the Ansible control VM and deploy it on both target servers.
Instruction: Every task must be executed with the user created in task-2.
# 1: Create Two Virtual Machines: Ubuntu and CentOS.
To carry out this tutorial, the first thing we must do is create our Ubuntu and CentOS virtual machines. We would spin up one Ubuntu control server, and four target servers consisting of one CentOS/7 and three Ubuntu/focal64. To do this, please follow the instructions in my tutorial titled, “Building Virtual Machines for Home Lab”. Over there you would find the resources you need to create the required VMs and instructions on how to connect to your target VMs from your control VM via SSH. Without a successful SSH connection established between the control and target servers, we will be unable to configure our target VMs with Ansible.
Installing Ansible on Control VM
Once our home lab is set up, we log into our Ubuntu control VM, update it, and install Ansible. Again, I would not be going through the procedures for installing Ansible in this article because I already have another tutorial titled, “Installing Ansible on Ubuntu and CentOS” to guide you.
Now we can begin with writing our Ansible playbook to configure our virtual machines according to the pre-outlined set of tasks for this tutorial.
# 2: Create a New User with Sudo Privileges on the Target Virtual Machines
First, we create a new directory for our work and switch into it using the command below:
vagrant@ubuntu-control:~$ mkdir ansible_tasks1; cd ansible_tasks1
Then next we start writing our Ansible playbook. The Ansible playbook is a list of tasks written in YAML format. Each task executes a specific command or set of commands on the target host(s). Paying good attention to indentation is paramount to writing an error-free playbook. Do not use the “Tab” button when writing this file, instead use the space bar and keep your indentation consistent throughout your playbook.
Using the Vim editor, we run:
vagrant@ubuntu-control:~/ansible_tasks1$ vi playbook.yml
and enter the following commands:
---
- hosts: all
tasks:
- name: create user - cloudlord
tags: always
user:
name: cloudlord
groups: "{{ sudo_group }}"
- name: add ssh_key to the user - cloudlord
tags: always
authorized_key:
user: cloudlord
key: "{{ lookup('ansible.builtin.file', '~/.ssh/id_ed25519.pub') }}"
Since we want to have this new user in all our VMs, we have the “hosts” parameter set to all. We would see more of the inventory file later in this tutorial. Then we have our list of tasks. With the first task, “create user — cloudlord”, we create a new user named cloudlord and add it to the “sudo group” which we have represented with the variable name “sudo_group”. We used a variable name here because the sudo group on Ubuntu and CentOS servers have different names: sudo and wheel, respectively. Therefore, we must specify the value of this variable name for each of our hosts in our inventory file as shown below:
vagrant@ubuntu-control:~/ansible_tasks1$ cat > hosts
ubuntu-node-1 ansible_host=192.168.53.21 sudo_group=sudo
centos-node-1 ansible_host=192.168.53.61 sudo_group=wheel
then Enter CTRL+D to exit the input field.
By default, this inventory file is located at /etc/ansible/hosts, but we would save ours in /home/vagrant/ansible_tasks1. Since we are doing without the default, we must specify the file location in our Ansible configuration file which by convention is named ansible.cfg. We would see more of this file later.
The next task, add ssh_key to the user — cloudlord, is used to give a user the authentication keys necessary for us to have access to our VMs as that user.
Let us go ahead and create our ansible.cfg file:
vagrant@ubuntu-control:~/ansible_tasks1$ cat > ansible.cfg
[defaults]
inventory=./hosts
host_key_checking=false
private_key_file=/home/vagrant/.ssh/id_ed25519
remote_user=vagrant
ansible_connection=ssh
[privilege_escalation]
become=true
become_method=sudo
become_user=root
become_ask_pass=false
With the Ansible configuration file, we can
- indicate the location of our inventory file,
- prevent strict checking of keys by each host,
- point our hosts to our SSH authentication key,
- indicate which user in our target host we would log in as (at this, stage we would connect as the default user, vagrant until we create our new user, cloudlord, then we can connect as cloudlord),
- state our mode of connection and
- grant our user permission to execute privileged commands on our target host.
# 3: Write an Ansible playbook to update and upgrade the server depending on the OS
Back to our playbook, we add a new set of tasks to update and upgrade our Ubuntu and CentOS servers:
- name: upgrade & update cache (Ubuntu)
tags: update,ubuntu
apt:
upgrade: dist
update_cache: yes
when: ansible_distribution == "Ubuntu"
- name: install package updates and upgrade (centos)
tags: update,upgrade,centos
yum:
name: "*"
state: latest
update_cache: yes
exclude: kernel*
when: ansible_distribution == "CentOS"
- The Ansible apt module is used when working with most Debian (Ubuntu belongs to the Debian family) packages. This is because apt is the primary package manager for this Linux distro. What apt is to Ubuntu, yum is to CentOS (the Community Enterprise OS of the Red Hat family).
- So we introduced the Ansible “when” conditional to instruct Ansible to execute a specific task on a specific type of Operating System.
- We update our servers with the update_cache parameter set to yes.
- The upgrade parameter under the apt module makes it quite easy for us to upgrade our Ubuntu server. In the yum module, we do not find a direct upgrade parameter, but the Ansible documentation gives us an example of how we can do this by using the wild card “*” to select all packages, and the exclude argument to exclude kernel-related packages from the upgrade to avoid getting stuck on this task for too long.
# 4: Install Apache on both Ubuntu and CentOS using a single task
To install Apache on both Ubuntu and CentOS hosts in a single Ansible task, we must first consider the fact that Apache has different names on these two OS types — apache2 on Ubuntu and httpd on CentOS. Therefore, we are constrained to use Ansible variables just like we did when we added our new user to the two differently named sudo groups on our Ubuntu (sudo) and CentOS (wheel) servers. So we head back to our inventory file
vagrant@ubuntu-control:~/ansible_tasks1$ vi hosts
and make a new entry as shown below:
ubuntu-node-1 ansible_host=192.168.53.21 sudo_group=sudo apache=apache2
centos-node-1 ansible_host=192.168.53.61 sudo_group=wheel apache=httpd
Then we return to our playbook and include the following task:
- name: install Apache
tags: install_apache
package:
name: "{{ apache }}"
state: latest
We use the package module here for the convenience of not having to specify the right package manager for each OS type, while the state parameter ensures we install only the latest version.
Lest I forget to mention, the tags parameter serves as a label that enables us to run our playbook on the command line using the -t flag to specify the tags of only the tasks we want to execute. The tasks tagged always will be executed anytime we run our playbook whether we specify them or not with the -t flag; and you can execute multiple tasks by specifying multiple tags separated with a comma only. For example, say we want to only update and install Apache on our Ubuntu host, we write our command as thus:
$ ansible-playbook playbook.yml -t ubuntu,install_apache
# 5: Write a Task to Start Apache
For our proxy server, Apache to be live, we must start it as a service using the task below:
- name: start Apache
tags: start_apache
service:
name: "{{ apache }}"
state: started
enabled: yes
Remember our “apache” variable from our previous task? Yes! It comes in handy here again.
# 6: Copy a Default HTML Page from the Control VM and Deploy it on both Target Servers
At this point, when we visit our IP address on our web browser, we should find the default Apache web page live.
The default Apache index.html file located at the /var/www/html directory makes this possible. Therefore, to have our default web page instead, we can create one and copy it to this same directory. Our default HTML page must also be named index.html so that it can replace Apache’s default, and also because of the configuration we have at the sites-available configuration file at the /etc/apache2 directory.
Now let us go to our terminal and create a simple HTML file:
vagrant@ubuntu-control:~/ansible_tasks1$ mkdir files; cd files
vagrant@ubuntu-control:~/ansible_tasks1/files$ cat > default_site.html
<h1>It works!!!</h1>
<h2>Welcome to our default HTML page</h2>
That will do for now. Remember to enter CTRL+D to exit the input field. Next, we write a task to copy this file from our control machine to our hosts.
- name: copy default web file
tags: copy_web_file
copy:
src: default_site.html
dest: /var/www/html/index.html
owner: root
group: root
mode: 0644
Then, we have to either place this task before the start_apache task or write another task to restart Apache. If you choose to restart Apache, add this task to your playbook:
- name: restart Apache
tags: restart_apache
service:
name: "{{ apache }}"
state: restarted
enabled: yes
Then we visit our host IP (http://192.168.53.23) on our web browser
If you made it this far, you are awesome! Join me in our next set of Ansible tasks in my next article titled, “ANSIBLE TUTORIALS #2”. You can also access the files we created for this tutorial at our GitHub repository. For any questions or observations, please chat with me on WhatsApp.
Thank you!