I’ve been working with vagrant boxes for quite awhile now. I usually use them for development environments or creating lab environments for testing various network functions (i.e. BGP EVPN). Often times when I’m using a vagrant box I need to get it in a certain state before it’s ready for me to begin using. In the situation of creating lab environments I may need to install FRRouting on the vagrant box and then apply a configuration. A handy feature that vagrant provides to make this possible is the ability to use provisioners that can execute a script prior to the vagrant box being ready for use. The ones I find myself using most are the shell provisioner and ansible local provisioner. In this post I’ll go over how I use the ansible local provisioner to install FRRouting on a Ubuntu 20.04 vagrant box. If you would like to follow along I have created an example located in github here.

Prerequisites

Before you begin you need to do the following:

Review the Vagrantfile

Whenever you are working with vagrant boxes you are going to have a Vagrantfile defined. Check out the documentation provided in the previous statement to learn more about Vagrantfiles and everything you can do within one. In this post I won’t go into much detail on the Vagrantfile aside from the provisioner section. The basic gist of a Vagrantfile is it essentially defines your vagrant environment - the virtual boxes, their network connectivity, resources, etc.

Looking at the Vagrantfile in our example github repo you’ll see that it’s pretty simple.

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-20.04"
  config.vm.provision "ansible_local" do |ansible|
    ansible.playbook = "playbook.yml"
    ansible.install_mode = "pip"
    ansible.pip_install_cmd = "sudo apt install -y python3-distutils && curl https://bootstrap.pypa.io/get-pip.py | sudo python3"
    ansible.galaxy_command = "sudo ansible-galaxy install -r /vagrant/collections/requirements.yml"
  end
end

The config.vm.box statement defines the vagrant box that we’re going to use (bento/ubuntu-20.04). The next statement goes into our ansible local provisioner. When you perform a vagrant up vagrant will first bring the vagrant box online. Once the vagrant box is online it will run the provisioner. The ansible local provisioner will first install ansible on the vagrant box. There’s a couple methods you can use for the installation. The default method is installing via the operating system package manager. On Ubuntu 20.04 I had issues using that method as focal was not recognized as a valid option. This is the error that I received when attempting that install method:

E: The repository 'http://ppa.launchpad.net/ansible/ansible/ubuntu focal Release' does not have a Release file.

Another approach that vagrant allows is installation via pip. Prior to installing ansible via pip I must first install pip. The vagrant box that I selected comes bare bones and pip is not available. Ubuntu 20.04 does not come with disutils and the pip installation will fail if it’s not present. So I also needed to make sure that is installed prior to the pip installation. Within the Vagrantfile you define how pip should be installed via the pip_install_command statement. In the above example you can see I’m installing python3-disutils first and then installing pip.

Once pip is installed vagrant will proceed with installing ansible. If you prefer a specific version of ansible you can define the version otherwise pip will install the latest available. If you have any ansible roles or collections that need to be installed prior to your playbook executing you can define those in the galaxy_command statement. In my example I have a requirements.yml file defined within my collections folder that needs to be installed prior to my playbook executing.

After ansible is installed vagrant will then run the playbook that is defined. In my example my playbook is called playbook.yml. My playbook basically follows the steps outlined here for installing FRRouting. After the installation I also edit the sysctl.conf file to enable routing on the vagrant box. Here are the contents of my playbook:

---
- name: provision vagrant box with frrouting
  hosts: all
  gather_facts: no

  tasks:

    - name: add frr apt-key
      ansible.builtin.apt_key:
        url: https://deb.frrouting.org/frr/keys.asc
        state: present
      become: yes

    - name: get host distribution
      ansible.builtin.command: lsb_release -sc
      register: release

    - name: add frr repository into source list
      ansible.builtin.apt_repository:
        repo: "deb https://deb.frrouting.org/frr {{ release.stdout }} {{ frr_version }}"
        state: present
        filename: /etc/apt/sources.list.d/frr
      vars:
        frr_version: frr-stable
      become: yes

    - name: install frr
      ansible.builtin.apt:
        name:
          - frr
          - frr-pythontools
        update_cache: yes
      register: apt_status
      retries: 100
      until: apt_status is success or ('Failed to lock apt for exclusive operation' not in apt_status.msg and '/var/lib/dpkg/lock' not in apt_status.msg)
      become: yes

    - name: edit sysctl.conf for ipv4 forward
      ansible.posix.sysctl:
        name: net.ipv4.ip_forward
        value: '1'
        state: present
        reload: yes
      become: yes

    - name: edit sysctl.conf for ipv6 forward
      ansible.posix.sysctl:
        name: net.ipv6.conf.all.forwarding
        value: '1'
        state: present
        reload: yes
      become: yes

Vagrant Up

Go ahead and issue a vagrant up within the example repo. You should see output similar to the following.

Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'bento/ubuntu-20.04'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'bento/ubuntu-20.04' version '202010.24.0' is up to date...
==> default: Setting the name of the VM: vagrant-box-ansible-provisioner-example_default_1612559846779_78606
==> default: Fixed port collision for 22 => 2222. Now on port 2201.
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 22 (guest) => 2201 (host) (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2201
    default: SSH username: vagrant
    default: SSH auth method: private key
    default: Warning: Connection reset. Retrying...
    default:
    default: Vagrant insecure key detected. Vagrant will automatically replace
    default: this with a newly generated keypair for better security.
    default:
    default: Inserting generated public key within guest...
    default: Removing insecure key from the guest if it's present...
    default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
    default: The guest additions on this VM do not match the installed version of
    default: VirtualBox! In most cases this is fine, but in rare cases it can
    default: prevent things such as shared folders from working properly. If you see
    default: shared folder errors, please make sure the guest additions within the
    default: virtual machine match the version of VirtualBox you have installed on
    default: your host and reload your VM.
    default:
    default: Guest Additions Version: 6.1.16
    default: VirtualBox Version: 6.0
==> default: Mounting shared folders...
    default: /vagrant => /Users/username/directory/vagrant-box-ansible-provisioner-example
==> default: Running provisioner: ansible_local...
    default: Installing Ansible...
    default: Installing pip... (for Ansible installation)
    default: Running ansible-playbook...

PLAY [provision vagrant box with frrouting] ************************************

TASK [add frr apt-key] *********************************************************
[DEPRECATION WARNING]: Distribution Ubuntu 20.04 on host default should use
/usr/bin/python3, but is using /usr/bin/python for backward compatibility with
prior Ansible releases. A future Ansible release will default to using the
discovered platform python for this host. See https://docs.ansible.com/ansible/
2.10/reference_appendices/interpreter_discovery.html for more information. This
 feature will be removed in version 2.12. Deprecation warnings can be disabled
by setting deprecation_warnings=False in ansible.cfg.
changed: [default]

TASK [get host distribution] ***************************************************
changed: [default]

TASK [add frr repository into source list] *************************************
changed: [default]

TASK [install frr] *************************************************************
changed: [default]

TASK [edit sysctl.conf for ipv4 forward] ***************************************
changed: [default]

TASK [edit sysctl.conf for ipv6 forward] ***************************************
changed: [default]

PLAY RECAP *********************************************************************
default                    : ok=6    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

You now have a vagrant box that has FRRouting installed!