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 (MacBook Pro 2020, Intel Core)
- VirtualBox 6.1
- Vagrant 2.2.19
vagrant-vbguest
- 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.