Vagrant - Provisioning 2/2

Vagrant provides an easy and powerful way to deploy local machines for development and testing. You can use Ansible or Podman and Docker to provision complete use cases on the machines, too.

Vagrant - Provisioning 2/2

Vagrant provides an easy and powerful way to deploy local machines for development and testing. You can add provisioners via Shell and file, as discussed in the "Vagrant - Provisioning 1/2" article. Furthermore, you can use Ansible or Podman and Docker to provision complete use cases on the machines.

Provisioning

In addition to the spin-up of VMs, you can define provisioning tasks and scripts in Vagrant. This is especially helpful, if you want to add packages to a machine or configure services. A simple command vagrant up will trigger the provisioners by default. In case you want to run it again, you can use the command vagrant provision.

For Ansible and container development, you can also use the feature to test your code and test it properly. In the below sections, I will explain how you can add Ansible Playbooks or container deployments to your Vagrantfile.

Hint
The guide is tested on Fedora 34 with Vagrant 2.2.16.

Ansible

If you develop Ansible Playbooks, Roles or Collections, you may want to test them properly. Much stuff can be done in containers, but sometimes you want to test virtualization, kernel parameters, certain security and firewall settings and just need some "real" machine.

Vagrant can help to spin up a machine and deploy your code on different Operating Systems and you can do much more, than in Docker or Podman containers.

You can also consider Ansible, if you are using it anyway, to deploy and configure your infrastructure. Same tool for production and develop is my preferred way.

If you don't know Ansible, I strongly recommend reading some articles about it.

Ansible Provisioner

The Ansible Provisioner is very useful, if you are having Ansible installed on your machine anyway. You can use different Ansible Version in a "virtualenv" and run everything as, if the Vagrant Box is just another remote machine (stack).

Let's see how this works. First, we need to create a small project and some files.

# Create project directory
$ mkdir vagrant-ansible
$ cd vagrant-ansible

# Create files
$ touch playbook.yml
$ touch Vagrantfile

Let's write a simple playbook, that will install a basic web server. Edit the "playbook.yml" file and fill in some content like below.

---
- hosts: "all"

  tasks:
  
    - name: "Install httpd Packages"
      ansible.builtin.package:
        name: "httpd"
        state: "present"
      become: true

    - name: "Copy a website"
      ansible.builtin.copy:
        src: "index.html"
        dest: "/var/www/html/index.html"
      become: true
      
    - name: "Start & Enable httpd"
      ansible.builtin.service:
        name: "httpd.service"
        state: "started"
        enabled: true
      become: true
playbook.yml

As you can see, we want to copy a custom index.html, too. Let's create this.

# Create directory
$ mkdir files

# Create file
$ touch files/index.html

The ansible.builtin.copy module will look in the "files" directory on its own, and you don't need to specify the full path. Some content for the website may look like the below example.

<!DOCTYPE html>
<body>
    <h1>Deployed with Ansible</h1>
    <p>Ansible - Automation made simple</p>
</body>
files/index.html

The last part, we need to add content for the Vagrantfile. The below example should be sufficient. Edit the Vagrantfile with the editor of your choice.

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

  # machine
  config.vm.define "fedora" do |fedora|
    fedora.vm.box = "fedora/34-cloud-base"
    fedora.vm.hostname = "fedora"
  end

  # provision
  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "playbook.yml"
  end
end
Vagrantfile

As you can see, we will spin-up a Fedora 34 machine. In addition, you can see a new section "config.vm.provision", which will trigger the Ansible provisioning. Vagrant will create an inventory automatically and run the "ansible-playbook" command, so the new inventory is used.

Just run vagrant up to start the VM and run the provisioner. Afterwards, you can test the deployment or change the playbook and run the provisioner again.

# Start the machine and provision
$ vagrant up

# Check for machine IP
$ vagrant ssh-config

# Check the deployed website
$ curl http://IP_ADDRESS

# Run the provisioner again
$ vagrant provision

Ansible Local Provisioner

If you don't have Ansible on your machine, you can consider using the Ansible Local provisioner. Vagrant will try to install Ansible in the Vagrant machine and apply the provided playbook afterwards.

During this process, Vagrant will sync the working directory in the Vagrant machine. It will be available in the "/vagrant" directory. To get a better understanding of this sync feature, please have a look at the documentation.

We can easily tune the previous example. Just edit the Vagrantfile as described below.

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

  # machine
  config.vm.define "fedora" do |fedora|
    fedora.vm.box = "fedora/34-cloud-base"
    fedora.vm.hostname = "fedora"
  end

  # provision
  config.vm.provision "ansible_local" do |ansible|
    ansible.playbook = "playbook.yml"
  end
end
Vagrantfile

You can spin up a fresh VM or re-apply the playbook with the below commands.

# Start the machine and provision
$ vagrant up

# Check for machine IP
$ vagrant ssh-config

# Check the deployed website
$ curl http://IP_ADDRESS

# Run the provisioner again
$ vagrant provision

Podman / Docker

The development of containers can be a problem, too. If you have an application, that must be shipped as a container, or if you just want to spin-up a third party container, Vagrant can be helpful. This way, you don't need to define Networks, Secrets or download images, which are idling around until you clean them up.

You will also be able to verify if containers can run on different platforms. Since the Podman and Docker provisioner are working very similar and are having basically the same syntax, I will focus on Podman for this section. The examples are easy to apply to Docker, too.

If you don't know anything about Podman and Docker, I recommend reading the articles here.

Hint: Unfortunately, the Podman installation on Fedora fails. Therefore, I am using the CentOS 8 Stream Vagrant box.

Pull/Run Image

You can use Vagrant to run a container from an existing image. I will stick to a simple example again. Let's create the project directory and some needed files first.

# Create project directory
$ mkdir vagrant-podman
$ cd vagrant-podman

# Create files
$ touch Vagrantfile

Edit the Vagrantfile and add the Podman provisioner.

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

  # machine
  config.vm.define "centos" do |centos|
    centos.vm.box = "centos/stream8"
    centos.vm.hostname = "centos"
  end

  # provision
  config.vm.provision "podman" do |container|
    container.run "web01",
      image: "docker.io/library/httpd:latest",
      args: "-d -t -p 80:80"
  end
end
Vagrantfile

After starting the Vagrant machine, we can test if the server is reachable.

# Start vagrant machine
$ vagrant up

# Get IP address
$ vagrant ssh-config
Host centos
  HostName 192.168.122.4
...

# Test if httpd is really running
$ curl 192.168.122.4
<html><body><h1>It works!</h1></body></html>

So, running a prepared image (or multiple images) is easy.

Build Image

Since I want to use the provisioner to test new images and Dockerfiles/Containerfiles, let's have a look at the "build image" feature.

First, we need to create a simple Dockerfile. Unfortunately, Vagrant does not support the general Containerfile and we need to name it "Dockerfile".

# Create Dockerfile
$ touch Dockerfile

Edit the file and fill in the below content.

FROM docker.io/library/fedora:34

RUN dnf install -y httpd && \
    dnf clean all

EXPOSE 80/tcp

CMD ["/usr/sbin/httpd","-DFOREGROUND"]
Dockerfile

This file should create a container image, based on Fedora Linux, with the Apache httpd web server in it.

The Vagrantfile to build the image and run it afterwards will look like the below example.

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

  # machine
  config.vm.define "centos" do |centos|
    centos.vm.box = "centos/stream8"
    centos.vm.hostname = "centos"
  end

  # provision
  config.vm.provision "podman" do |container|
    container.build_image "/vagrant/",
      args: "-t localhost/fedora-web"
    container.run "localhost/fedora-web",
      args: "-d -t -p 80:80"
  end
end
Vagrantfile

Spin up the machine and test if everything works.

# Create the machine and run the provisoner
$ vagrant up

# Check IP address
$ vagrant ssh-config 
Host centos
  HostName 192.168.122.190

# Check the website
$ curl 192.168.122.190
<!doctype html>
<html>
  <head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
    <title>Test Page for the HTTP Server on Fedora</title>
...

Vagrant is doing this by syncing the complete working directory into "/vagrant" on the target machine. To get a better understanding of the Vagrant sync feature, please have a look at the official documentation.

Code

I have uploaded a bunch of examples, which you can use to build your own provisioners. These also include some examples from the "Vagrant - Getting Started" and "Vagrant - Provisioning 1/2" articles, and the repository is under active maintenance.

vagrant-templates
A collection of vagrant templates for different purposes.

Vagrant provides additional documentation for the provisioners. Please have a look at these, too.

Ansible - Short Introduction | Vagrant by HashiCorp
This page includes options and information that is applicable to both VagrantAnsible provisioners.
Ansible Local - Provisioning | Vagrant by HashiCorp
The Vagrant Ansible Local provisioner allows you to provision the guest using Ansible playbooks by executing “ansible-playbook” directly on the guestmachine.
Common Ansible Options - Provisioning | Vagrant by HashiCorp
This page details the common options to the Vagrant Ansible provisioners.
Podman - Provisioning | Vagrant by HashiCorp
The Vagrant Podman provisioner can automatically install Podman, and run it as a drop in replacement for Docker.
Docker - Provisioning | Vagrant by HashiCorp
The Vagrant Docker provisioner can automatically install Docker, pull Dockercontainers, and configure certain containers to run on boot.

Conclusion

Hint: Unfortunately, the Podman installation on Fedora fails. Therefore, I am using the CentOS 8 Stream Vagrant box.