Rindrics Mumbles

Create Ubuntu development environment on macOSX using Vagrant and Ansible

To learn about compilers hands-on, I built a Linux development environment with configuration management.

Problems discussed in this article

  • how to install software on guest VM
  • share directory between host and guest

The GitHub repository that manages the configurations explained in this article: https://github.com/Rindrics/envs

Prerequisite

Environment of running examples described in this article:

  • macOS 12.3.1
  • Vagrant 2.2.19
  • Ansible 2.13.2
  • Python 3.10.6

Need for Linux development environment

Recently I’ve been planning to learn about low-level technology, especially compilers. I already found an excellent guide for learning how to develop compilers, but the problem is that my development environment—macOSX is incompatible with the examples in that guide. So I need a Linux virtual development environment on my machine.

I wanted the provisioning procedure simple, but I also wanted to manage the VM configuration so that it is reproducible. Thus I decided to provision a Vagrant VM configured using Ansible.

Provision Ubuntu VM with minimum configuration

It’s easy to provision VM using Vagrant. Here’s the minimum Vagrantfile to provision Ubuntu22.04:

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu/jammy64"
end

To provision the VM, we just need to execute vagrant up:

❯ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'ubuntu/jammy64'...

...(Omitted)

Restarting VM to apply changes...
==> default: Attempting graceful shutdown of VM...
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Mounting shared folders...
    default: /vagrant => /Users/rindrics/envs/ubuntu22.04

How easy it is!

Configure VM using Ansible

Minimum configuration

Before starting to install specific software on Vagrant VM, I learned how to configure Vagrant VM using Ansible by reading this page.

To start managing the VM using Ansible, I fixed the IP address of the VM:

 VAGRANTFILE_API_VERSION = "2"

 Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
   config.vm.box = "ubuntu/jammy64"

+  config.vm.network "private_network", ip: "192.168.33.10"
 end

I need to specify that fixed IP in Ansible “inventory file” under a directory with an arbitrary name. In this example, I created provisioning/hosts as follows:

[devenv]
192.168.33.10

Then I told Ansible the location of the inventory file:

 VAGRANTFILE_API_VERSION = "2"

 Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
   config.vm.box = "ubuntu/jammy64"

   config.vm.network "private_network", ip: "192.168.33.10"
+
+  config.vm.provision "ansible" do |ansible|
+    ansible.inventory_path = "provisioning/hosts"
+  end
 end

Next, I wrote a minimum Ansible “playbook” that does nothing on the guest VM:

---
- name: Setup development environment
  hosts: devenv
  become: yes
  user: vagrant

I saved the configuration above as provisioning/playbook.yml and told Ansible the location:

 VAGRANTFILE_API_VERSION = "2"

 Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
   config.vm.box = "ubuntu/jammy64"

   config.vm.network "private_network", ip: "192.168.33.10"

   config.vm.provision "ansible" do |ansible|
+    ansible.playbook = "provisioning/playbook.yml"
     ansible.inventory_path = "provisioning/hosts"
   end
 end

All set, I updated the VM configuration as follows:

❯ vagrant provision
==> default: Running provisioner: ansible...
    default: Running ansible-playbook...
 ______________________________________
< PLAY [Setup development environment] >
 --------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

 ________________________
< TASK [Gathering Facts] >
 ------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

ok: [192.168.33.10]
 ____________
< PLAY RECAP >
 ------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

192.168.33.10              : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Cows in Vagrant output told me I managed to call Ansible via Vagrant!

Install a software on VM

To learn how to configure VM using Ansible, I looked for software that is small and easy to install. I chose Starship, my favorite software that enables easy customization of shell prompt.

As explained in “Installation”, we just need to run the following two commands to use Starship on bash:

curl -sS https://starship.rs/install.sh | sh
echo 'eval "$(starship init bash)"' >> ~/.bashrc

I did this via Ansible by updating playbook as follows:

 ---
 - name: Setup development environment
   hosts: devenv
   become: yes
   user: vagrant
+
+  tasks:
+    - name: Install Starship
+      ansible.builtin.shell:
+        cmd: curl -sSf https://starship.rs/install.sh | sh -s -- -y

Then applied the configuration:

❯ vagrant provision
==> default: Running provisioner: ansible...
    default: Running ansible-playbook...
 ______________________________________
< PLAY [Setup development environment] >
 --------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

 ________________________
< TASK [Gathering Facts] >
 ------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

ok: [192.168.33.10]
 _________________________
< TASK [Install Starship] >
 -------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

changed: [192.168.33.10]
 ____________
< PLAY RECAP >
 ------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

192.168.33.10              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Starship seems to be installed on the VM.

Running Ansible commands in verbose mode is useful to know what is happening in the VM:

 VAGRANTFILE_API_VERSION = "2"

 Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
   config.vm.box = "ubuntu/jammy64"

   config.vm.network "private_network", ip: "192.168.33.10"

   config.vm.provision "ansible" do |ansible|
     ansible.playbook = "provisioning/playbook.yml"
     ansible.inventory_path = "provisioning/hosts"
     ansible.limit = 'devenv'
+    ansible.verbose = true
   end
 end

To use Starship, we have to update ~/.bashrc on VM. I added a task to the playbook:

 ---
 - name: Setup development environment
   hosts: devenv
   become: yes
   user: vagrant

   tasks:
     - name: Install Starship
       ansible.builtin.shell:
         cmd: curl -sSf https://starship.rs/install.sh | sh -s -- -y
+
+    - name: Add eval statement to .bashrc
+      lineinfile:
+        dest: /home/vagrant/.bashrc
+        line: "eval \"$(starship init bash)\""
+        owner: vagrant

and applied it:

 ______________________________________
< TASK [Add eval statement to .bashrc] >
 --------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

changed: [192.168.33.10]

Done.

Finally, I logged in to the VM by vagrant ssh:

❯ vagrant ssh
Welcome to Ubuntu 22.04 LTS (GNU/Linux 5.15.0-41-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sun Nov  6 04:58:58 UTC 2022

  System load:  0.0               Processes:               107
  Usage of /:   4.8% of 38.70GB   Users logged in:         0
  Memory usage: 21%               IPv4 address for enp0s3: 10.0.2.15
  Swap usage:   0%                IPv4 address for enp0s8: 192.168.33.10


108 updates can be applied immediately.
57 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable


Last login: Sun Nov  6 04:59:03 2022 from 192.168.33.1

vagrant in 🌐 ubuntu-jammy in ~

A pretty prompt tells the installation of Starship succeeded.

Share directory between host and guest

During development, it is useful to have a host–guest shared folder.

So I also configured synced folder as follows:

 VAGRANTFILE_API_VERSION = "2"

 Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
   config.vm.box = "ubuntu/jammy64"

   config.vm.network "private_network", ip: "192.168.33.10"
+  config.vm.synced_folder "./shared", "/home/vagrant/shared", :mount_options => [ "dmode=777", "fmode=777"]

   config.vm.provision "ansible" do |ansible|
     ansible.playbook = "provisioning/playbook.yml"
     ansible.inventory_path = "provisioning/hosts"
     ansible.limit = 'devenv'
   end
 end

On host macOS, I created the synced directory and blank file under it:

❯ mkdir shared
❯ touch shared/foo

To see whether the shared/foo exists on the VM, I applied the configuration and logged in to the VM:

❯ vagrant reload
...(Omitted)

❯ vagrant ssh
Welcome to Ubuntu 22.04 LTS (GNU/Linux 5.15.0-41-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sun Nov  6 05:08:41 UTC 2022

  System load:  0.01025390625     Processes:               111
  Usage of /:   4.8% of 38.70GB   Users logged in:         0
  Memory usage: 21%               IPv4 address for enp0s3: 10.0.2.15
  Swap usage:   0%                IPv4 address for enp0s8: 192.168.33.10


108 updates can be applied immediately.
57 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable


Last login: Sun Nov  6 05:06:41 2022 from 10.0.2.2

vagrant in 🌐 ubuntu-jammy in ~
❯ ls shared
foo

Yes, there it is!

I’ve successfully obtained a virtual development environment to start learning about compilers.


comments powered by Disqus