Cloud Native Apps

 View Only

Use Vagrant to create a CentOS environment for Ansible development

By Stephan Bester posted Fri July 15, 2022 04:12 AM

  
Ansible + Vagrant

I recently wanted to test Ansible playbooks locally, but since I am on a Windows environment, this proved a little difficult. It is possible to install Ansible in WSL, but I found that this did not quite fit my need, since I wanted a control node where I can tweak and run the Ansible scripts from, but also needed some target machines to apply the changes to. This gave me the idea to use another tool I am very fond of, Vagrant, to create several Linux machines to create my Ansible development environment.

Ansible is a popular IT automation framework, allowing the creation of scripts to automate the installation of software and packages, lifecycle management such as patching and upgrades and other complex tasks. 
Vagrant is a tool for creation and management of virtual machines, also using scripts to automate the provisioning process.

Prerequisites

  • Virtual Box (or similar Hypervisor).
  • Vagrant.
  • Visual Studio Code (or another text editor).

Getting Started

  • Download and install Visual Studio Code
  • Download and install Oracle Virtual Box (or your preferred Hypervisor). Please note, if you are already using Hyper-V, you can run Virtual Box side-by-side, as long as you have enabled the Windows Hypervisor Platform feature.
  • Download and install Vagrant from the official Downloads page, or you can use your favourite package manager.
  • Make sure Vagrant was successfully installed by running the following from the command-line.
vagrant --version

Next we will look at the creation of a Vagrantfile to start our Virtual Machine automation.

Vagrantfile

  • In your working directory, create a new file and name it Vagrantfile. Open this file in your text editor of choice, I will be using Visual Studio Code.
  • Each time we provision a new Virtual Machine, we do not want to perform a fresh installation of the operating system, as this is too time consuming. Vagrant has the concept of a box, which is a base image to start provisioning from. You can create your own boxes using a tool like Packer, but Vagrant also provides a catalogue of publicly available boxes.
  • Firstly, we need to tell Vagrant which version of configuration to use:
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

# Content goes here

end
  • We can add the instruction on which box to use. I have selected the Centos Stream 8 image available on the Vagrant Box catalogue.
config.vm.box = "centos/stream8"
  • Configure the provider, in this case Virtual Box, with the required settings for our virtual machine(s). 
config.vm.provider :virtualbox do |v|
v.memory = 1024
v.cpus = 1
v.linked_clone = true
end
  • It is possible to instruct Vagrant to repeat the instruction multiple times, to create more than one virtual machine.
# Define three VMs with static private IP addresses.
boxes = [
{ :name => "ansible-1", :ip => "192.168.33.71" },
{ :name => "node1", :ip => "192.168.33.72" },
{ :name => "node2", :ip => "192.168.33.73" }
]

# Provision each of the VMs.
boxes.each do |bx|
config.vm.define bx[:name] do |config|
config.vm.hostname = bx[:name]
config.vm.network :private_network, ip: bx[:ip]
end
end
  • Vagrant generates a unique SSH key on the host for communication with each virtual machine. You can instruct Vagrant to use the same SSH key for each VM.
 config.ssh.insert_key = false
  • We can use the provision instruction to copy files and run scripts.
  • We add a condition for the machine named ansible-1, which will function as the Ansible control node, to allow SSH communication to the other nodes by reusing the SSH key generated by Vagrant on the host machine.   
  • Create a script to perform dnf update and install the Ansible components, requiring sudo permissions.
  • Use pip to install ansible-navigator.
$script = <<-'SCRIPT'
dnf -y update
dnf -y install python3 python3-pip podman ansible-core
SCRIPT
$script2 = <<-'SCRIPT'
python3 -m pip install ansible-navigator --user
SCRIPT
if bx[:name] == "ansible-1"
config.vm.provision "file", source: "~/.vagrant.d/insecure_private_key", destination: "~/.ssh/id_rsa"
config.vm.provision :shell, inline: "chmod 600 ~/.ssh/id_rsa", privileged: false
config.vm.provision :shell, inline: $script, privileged: true
config.vm.provision :shell, inline: $script2, privileged: false
end
  • For configuration of ansible-navigator, we create a .ansible-navigator.yml settings file in the working directory which contains the Vagrantfile. The settings file must be added to the home directory of the Ansible control node. This identifies an inventory file, with some required Ansible configuration.
  • Finally we create a hosts inventory file, also in the working directory, with basic variables matching our Vagrantfile, and default SSH configuration.
  • Putting it all together, the working directory should contain:
.ansible-navigator.yml
---
ansible-navigator:
ansible:
inventories:
- /home/vagrant/hosts
hosts
[all:vars]
ansible_user=vagrant
ansible_port=22

[control]
ansible-1 ansible_host=192.168.33.71

[web]
node1 ansible_host=192.168.33.72
node2 ansible_host=192.168.33.73
Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

config.vm.box = "centos/stream8"

# Do not generate a new SSH key for each VM
config.ssh.insert_key = false

config.vm.provider :virtualbox do |v|
v.memory = 1024
v.cpus = 1
v.linked_clone = true
end

# Define three VMs with static private IP addresses.
boxes = [
{ :name => "ansible-1", :ip => "192.168.33.71" },
{ :name => "node1", :ip => "192.168.33.72" },
{ :name => "node2", :ip => "192.168.33.73" }
]

# Install ansible & dependencies
$script = <<-'SCRIPT'
dnf -y update
dnf -y install python3 python3-pip podman ansible-core
SCRIPT

# Install ansible-navigator
$script2 = <<-'SCRIPT'
python3 -m pip install ansible-navigator --user
SCRIPT

# Provision each of the VMs.
boxes.each do |bx|
config.vm.define bx[:name] do |config|
config.vm.hostname = bx[:name]
config.vm.network :private_network, ip: bx[:ip]

# Copy SSH key to primary node and run scripts
if bx[:name] == "ansible-1"
config.vm.provision "file", source: "~/.vagrant.d/insecure_private_key", destination: "~/.ssh/id_rsa"
config.vm.provision :shell, inline: "chmod 600 ~/.ssh/id_rsa", privileged: false
config.vm.provision :shell, inline: $script, privileged: true
config.vm.provision "file", source: "./.ansible-navigator.yml", destination: "~/.ansible-navigator.yml"
config.vm.provision :shell, inline: $script2, privileged: false
config.vm.provision "file", source: "./hosts", destination: "~/hosts"
end
end
end
end
  • These three files are also available on GitHub.
  • From an Administrative command-line, we can now instruct Vagrant to create and provision the three CentOS virtual machines:
vagrant up
  • Once completed, you can verify access connecting to the virtual machines using SSH.
vagrant ssh ansible-1
Vagrant SSH Ansible-1

Remote SSH (Optional - VS Code)

If you are using Visual Studio Code, install the Remote - SSH extension. This will allow you to develop against the remote Linux environment from the host machine. Add a new SSH target in the Remote Explorer for ansible-1 with the IP address specified in the Vagrantfile, which is 192.168.33.71. Also specify the identity_file, named insecure_private_key, which is located at C:\Users\<your username>\.vagrant.d\. The default user for Vagrant boxes is (almost) always vagrant.

ssh -i c:\Users\<your username>\.vagrant.d\insecure_private_key vagrant@192.168.33.71

If the SSH connection is successful, VS Code Server will be downloaded and installed on the target machine, allowing you to use your local VS Code to work against the remote instance. Click on File > Open Folder and select /home/vagrant. You will see the hosts file and .ansible-navigator.yml file we copied during the Vagrant provisioning in the directory.

.ansible-navigator.yml

Ansible

  • After connecting to the ansible-1 via SSH, create a new folder learn-ansible.
  • Create a file and call it install-nginx.yml, this will be our Ansible playbook. A playbook is nothing more than a YML file consisting of repeatable plays, each with a set of tasks that is run from top to bottom.
mkdir learn-ansible
cd learn-ansible/
touch install-nginx.yml
  • In the new file we'll create our first play.
  • We give the play a name, specify which hosts it will apply to (specifying node1 which we defined in the hosts file) and use the become instruction to tell Ansible that this will require administrative privileges.
---
- name: nginx is installed
hosts: node1
become: yes
  • A play will contain one or more tasks to execute.
  • First, let's add the task to install the NGINX web server. We name the task, and instruct ansible to use the dnf module to install the nginx package.
---
- name: nginx is installed
hosts: node1
become: yes

tasks:
- name: latest nginx version is installed
dnf:
name: nginx
state: latest
  • We also want to make sure the NGINX service is started after installation.
  • We add a second task, using the service module to make sure that NGINX is enabled and started.
---
- name: nginx is installed
hosts: node1
become: yes

tasks:
- name: latest nginx version is installed
dnf:
name: nginx
state: latest
- name: nginx is started
service:
name: nginx
enabled: true
state: started
  • We can now use the command-line tool, ansible-navigator, to apply the playbook. Ansible Navigator is a text-based user interface to help with the creation and management of Ansible content.
  • Ansible Navigator will use the .ansible-navigator.yml file to detect settings, in which we have defined the hosts file with our SSH configuration and virtual machine inventory.
  • Apply the playbook.
ansible-navigator run install-nginx.yml
ansible-navigator run install-nginx.yml
  • You can interact with Ansible Navigator to drill down into tasks. Notice that shows that 2 changes where made.
  • Once the playbook has finished running, you can access node1 on 192.168.33.72 in your browser, which will show the default NGINX web server landing page.
  • Because we specified node1 in hosts, NGINX was not installed on node2. Change the value from node1 to web in the playbook, to apply it to all servers specified in the hosts file under the web group.
  • Run the playbook again to apply the changes to node2. You can use the --mode parameter to use stdout instead of the default interactive output.
ansible-navigator run install-nginx.yml --mode stdout
ansible-navigator run install-nginx.yml --mode stdout
  • The output is printed to the console instead. Note that node1 was not changed since it already had NGINX installed.

Cleanup

You can destroy the Vagrant machines with a simple command, which will stop and delete all the virtual machines defined in the Vagrantfile.

vagrant destroy -f

Conclusion

In this blog post we created three Linux virtual machines by using the virtual machine automation tool Vagrant. We also installed ansible and ansible-navigator. We created a simple Ansible playbook using the Visual Studio Code Remote SSH extension. We used ansible-navigator to apply the playbook, installing the NGINX web server on our target nodes. This is only a small taste of what you can do with Ansible, start learning more today!

Also check out how you can use Ansible on IBM Cloud.

If you are using Vagrant on Linux, there is also an Ansible Provisioner available to trigger your playbooks directly from vagrant. Read more in the Vagrant Guide from Ansible.

References

0 comments
128 views

Permalink