In Part 1, we looked at how to spin up a Centos 7 box using Vagrant, Hyper-V and PowerShell. However, to make this virtual machine test-worthy as soon as it is booted, we need to configure our VM, i.e. update the system, install necessary packages, and so on and so forth. In short, we want to automate as much of what would have otherwise been a completely manual installation of the new environment we intend to work on.
There are multiple ways to achieve this with tools like Ansible, Chef, Docker or Puppet, but in the following steps I will be keeping it simple and just use the Shell Provisioner.
There are 2 main ways we can leverage the shell provisioner from within our Vagrantfile:
inline
– Specifies a shell command inline to execute on the remote machinepath
– Path to a shell script to upload and execute
I will be using both methods simultaneously, as some are better suited than others depending on the task at hand.
If you followed along from Part 1, we created a Centos 7 box under D:\Vagrant\centos7-vm.
Let’s see if we can provision this same VM with a few modifications of our own. I would like to:
- Update the OS and install a few packages
- Change the VM’s hostname
- Disable Selinux
- Edit the
/etc/host
s file - Set up authentication using my own pubkey
- Add my user to the sudoers file
- Install Docker
With the exception of the VM’s hostname, all of the above will be configured using a simple shell script, leveraging the path
option
The VM hostname will be set up with the inline
option, just so you can get an example of what this looks like in the Vagrantfile.
Picking up from the Part 1, out Vagrantfile file should look like this:
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box = "generic/centos7"
config.vm.network "public_network"
config.vm.synced_folder ".", "/vagrant_data", disabled: true
config.vm.provider "hyperv" do |h|
h.enable_virtualization_extensions = true
h.linked_clone = true
h.memory = 2048
h.cpus = 2
h.vmname = "centos7-vm"
end
end
This whole block relates to the VM creation, and it is good practice to create a separate block for VM-specific configurations.
💡 It is important to note that any methods applied to config.vm
will apply to ALL your machines (even if you put it in a specific machine block) so its better to put all your config.vm
properties outside of any machine-specific block.
During my tests with Vagrant, I found that mixing config
and the machine-specific variables causes Vagrant to make multiple passes of the VM configuration. This is a problem, especially when dealing with multi-machine scenarios, where a single Vagrantfile can be used to spin up several VMs.
VM Hostname change with the inline option
Let’s create a new block and leverage the shell provisioner’s inline
option in order to set the VM’s hostname first:
config.vm.define "centos7vm" do |centos7vm|
centos7vm.vm.provision "shell", inline: "hostnamectl set-hostname centos7-vm"
end
💡 Notice how I just provided a generic variable “centos7vm” – You can use any name, provided it does not contain any special characters within the |xxx|
block.
(i.e using |centos7-vm|
will generate an error)
Our Vagrantfile should now look like this:
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box = "generic/centos7"
config.vm.network "public_network"
config.vm.synced_folder ".", "/vagrant_data", disabled: true
config.vm.provider "hyperv" do |h|
h.enable_virtualization_extensions = true
h.linked_clone = true
h.memory = 2048
h.cpus = 2
h.vmname = "centos7-vm"
end
config.vm.define "centos7vm" do |centos7vm|
centos7vm.vm.provision "shell", inline: "hostnamectl set-hostname centos7-vm"
end
end
Notice how we are “defining” the machine-specific configuration and how the inline option just contains the hostnamectl
command, directly in the Vagrantfile. You can leverage the inline option to run any command on the guest VM.
Let’s test this out by spinning up our VM and then SSHing into it:
PS D:\Vagrant\centos7-vm> vagrant up
Bringing machine 'centos7vm' up with 'hyperv' provider...
==> centos7vm: Verifying Hyper-V is enabled...
==> centos7vm: Verifying Hyper-V is accessible...
==> centos7vm: Importing a Hyper-V instance
centos7vm: Creating and registering the VM...
centos7vm: Successfully imported VM
centos7vm: Please choose a switch to attach to your Hyper-V instance.
centos7vm: If none of these are appropriate, please open the Hyper-V manager
centos7vm: to create a new virtual switch.
centos7vm:
centos7vm: 1) External
centos7vm: 2) Default Switch
centos7vm: 3) WSL
centos7vm:
centos7vm: What switch would you like to use? 1
centos7vm: Configuring the VM...
centos7vm: Setting VM Enhanced session transport type to disabled/default (VMBus)
==> centos7vm: Starting the machine...
==> centos7vm: Waiting for the machine to report its IP address...
centos7vm: Timeout: 120 seconds
centos7vm: IP: 10.10.5.211
==> centos7vm: Waiting for machine to boot. This may take a few minutes...
centos7vm: SSH address: 192.168.1.27:22
centos7vm: SSH username: vagrant
centos7vm: SSH auth method: private key
centos7vm:
centos7vm: Vagrant insecure key detected. Vagrant will automatically replace
centos7vm: this with a newly generated keypair for better security.
centos7vm:
centos7vm: Inserting generated public key within guest...
centos7vm: Removing insecure key from the guest if it's present...
centos7vm: Key inserted! Disconnecting and reconnecting using new SSH key...
==> centos7vm: Machine booted and ready!
==> centos7vm: Running provisioner: shell...
centos7vm: Running: inline script
Notice the last 3 lines: After the machine booted and was ready, the shell provisioner kicked in and ran our inline “script”.
Also notice how every line is prefixed with “centos7vm”. This is particularly useful in multi-machine scenarios.
Let’s verify the hostname change by SSHing into our guest VM:
PS D:\Vagrant\centos7-vm> vagrant ssh
[vagrant@centos7-vm ~]$
🎉 The machine’s hostname has been successfully changed to centos7-vm
, as defined by the hostnamectl command
Additional configuration from script using the path option
Now let’s configure the rest. As stated at the beginning of this article, I want to update the system, install a bunch of apps, disable selinux, edit my hosts file, create a user and copy my pubkey so I can authenticate directly to my new virtual machine & install Docker.
To achieve this, I will create a simple bash script named bootstrap.sh (who can call it anything you want) containing all the required commands and then I’ll use the shell provisioner’s path
option to call it from the Vagrantfile.
#!/bin/bash
DEFAULT_APPS="curl wget git vim htop net-tools yum-utils zsh tmux netcat"
#* Disable Selinux
sed -i 's/enforcing/disabled/g' /etc/selinux/config
#* Add hosts
echo "192.168.1.20 sqldb" >> /etc/hosts
echo "192.168.1.150 ubuntu01" >> /etc/hosts
echo "192.168.1.100 ansible" >> /etc/hosts
#* Update resolv.conf
echo "nameserver 192.168.1.200" >> /etc/resolv.conf
#* Update system
yum update -y
yum install -y epel-release
yum install -y $DEFAULT_APPS
#* Pubkey authentication for dodz
groupadd dodz -g 1002
adduser dodz -u 1002 -g 1002
install -o dodz -g dodz -m 700 -d /home/dodz/.ssh
echo "ssh-ed25519 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx me@mylaptop" > /home/dodz/.ssh/authorized_keys
chmod 600 /home/dodz/.ssh/authorized_keys
chown dodz:dodz /home/dodz/.ssh/authorized_keys
echo "dodz ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
echo "PubkeyAuthentication yes" >> /etc/ssh/sshd_config
systemctl restart sshd
#* Install Docker
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install -y docker-ce docker-ce-cli containerd.io
systemctl enable docker.service
systemctl enable containerd.service
systemctl start docker.service
systemctl start containerd.service
usermod -aG docker dodz
You can place this script anywhere you want but you will need to provide the path inside the Vagrantfile. For ease of use, I will place this script inside D:\Vagrant\centos7-vm, same folder as the Vagrantfile just so that I won’t need to define the path. So my machine-specific block now looks like this:
config.vm.define "centos7vm" do |centos7vm|
centos7vm.vm.provision "shell", inline: "hostnamectl set-hostname centos7-vm"
centos7vm.vm.provision "shell", path: "bootstrap.sh"
end
Let’s test this out. To properly test, I like destroying the VM completely (vagrant destroy
), just so I can make sure everything works from start to finish, however, you could use vagrant reload
to reload the config. This will shutdown the VM and re-run the Vagrantfile.
I edited the output below, so as not to make this too big to read:
PS D:\Vagrant\centos7-vm> vagrant up
Bringing machine 'centos7vm' up with 'hyperv' provider...
==> centos7vm: Verifying Hyper-V is enabled...
==> centos7vm: Verifying Hyper-V is accessible...
==> centos7vm: Importing a Hyper-V instance
centos7vm: Creating and registering the VM...
centos7vm: Successfully imported VM
centos7vm: Please choose a switch to attach to your Hyper-V instance.
centos7vm: If none of these are appropriate, please open the Hyper-V manager
centos7vm: to create a new virtual switch.
centos7vm:
centos7vm: 1) External
centos7vm: 2) Default Switch
centos7vm: 3) WSL
centos7vm:
centos7vm: What switch would you like to use? 1
centos7vm: Configuring the VM...
centos7vm: Setting VM Enhanced session transport type to disabled/default (VMBus)
==> centos7vm: Starting the machine...
==> centos7vm: Waiting for the machine to report its IP address...
centos7vm: Timeout: 120 seconds
==> centos7vm: Waiting for machine to boot. This may take a few minutes...
centos7vm: SSH address: 192.168.1.27:22
centos7vm: SSH username: vagrant
centos7vm: SSH auth method: private key
centos7vm:
centos7vm: Vagrant insecure key detected. Vagrant will automatically replace
centos7vm: this with a newly generated keypair for better security.
centos7vm:
centos7vm: Inserting generated public key within guest...
centos7vm: Removing insecure key from the guest if it's present...
centos7vm: Key inserted! Disconnecting and reconnecting using new SSH key...
==> centos7vm: Machine booted and ready!
==> centos7vm: Running provisioner: shell...
centos7vm: Running: inline script
==> centos7vm: Running provisioner: shell...
centos7vm: Running: C:/Users/nonda/AppData/Local/Temp/vagrant-shell20210823-12332-1s4wg0t.sh
centos7vm: Loaded plugins: fastestmirror
centos7vm: Determining fastest mirrors
centos7vm: Package epel-release-7-13.noarch already installed and latest version
centos7vm: Nothing to do
centos7vm: Loaded plugins: fastestmirror
centos7vm: Loading mirror speeds from cached hostfile
centos7vm: Package curl-7.29.0-59.el7_9.1.x86_64 already installed and latest version
centos7vm: Package wget-1.14-18.el7_6.1.x86_64 already installed and latest version
centos7vm: Package 2:vim-enhanced-7.4.629-8.el7_9.x86_64 already installed and latest version
centos7vm: Package net-tools-2.0-0.25.20131004git.el7.x86_64 already installed and latest version
centos7vm: Package yum-utils-1.1.31-54.el7_8.noarch already installed and latest version
centos7vm: Resolving Dependencies
centos7vm: --> Finished Dependency Resolution
centos7vm:
centos7vm: Dependencies Resolved
centos7vm:
centos7vm: ================================================================================
centos7vm: Package Arch Version Repository Size
centos7vm: ================================================================================
centos7vm: Installing:
centos7vm: git x86_64 1.8.3.1-23.el7_8 base 4.4 M
centos7vm: htop x86_64 2.2.0-3.el7 epel 103 k
centos7vm: netcat x86_64 1.217-3.el7 epel 29 k
centos7vm: tmux x86_64 1.8-4.el7 base 243 k
centos7vm: zsh x86_64 5.0.2-34.el7_8.2 base 2.4 M
centos7vm: Installing for dependencies:
centos7vm: libbsd x86_64 0.8.3-1.el7 epel 85 k
centos7vm: libevent x86_64 2.0.21-4.el7 base 214 k
centos7vm: libretls x86_64 3.3.3p1-1.el7 epel 38 k
centos7vm: openssl11-libs x86_64 1:1.1.1g-3.el7 epel 1.5 M
centos7vm: perl-Error noarch 1:0.17020-2.el7 base 32 k
centos7vm: perl-Git noarch 1.8.3.1-23.el7_8 base 56 k
centos7vm: perl-TermReadKey x86_64 2.30-20.el7 base 31 k
centos7vm: rsync x86_64 3.1.2-10.el7 base 404 k
centos7vm:
centos7vm: Transaction Summary
centos7vm: ================================================================================
centos7vm: Install 5 Packages (+8 Dependent packages)
centos7vm:
centos7vm: Installed:
centos7vm: git.x86_64 0:1.8.3.1-23.el7_8 htop.x86_64 0:2.2.0-3.el7
centos7vm: netcat.x86_64 0:1.217-3.el7 tmux.x86_64 0:1.8-4.el7
centos7vm: zsh.x86_64 0:5.0.2-34.el7_8.2
centos7vm:
centos7vm: Dependency Installed:
centos7vm: libbsd.x86_64 0:0.8.3-1.el7 libevent.x86_64 0:2.0.21-4.el7
centos7vm: libretls.x86_64 0:3.3.3p1-1.el7 openssl11-libs.x86_64 1:1.1.1g-3.el7
centos7vm: perl-Error.noarch 1:0.17020-2.el7 perl-Git.noarch 0:1.8.3.1-23.el7_8
centos7vm: perl-TermReadKey.x86_64 0:2.30-20.el7 rsync.x86_64 0:3.1.2-10.el7
centos7vm:
centos7vm: Complete!
centos7vm: Loaded plugins: fastestmirror
centos7vm: adding repo from: https://download.docker.com/linux/centos/docker-ce.repo
centos7vm: grabbing file https://download.docker.com/linux/centos/docker-ce.repo to /etc/yum.repos.d/docker-ce.repo
centos7vm: repo saved to /etc/yum.repos.d/docker-ce.repo
centos7vm: --> Finished Dependency Resolution
centos7vm:
centos7vm: Dependencies Resolved
centos7vm:
centos7vm: ================================================================================
centos7vm: Package Arch Version Repository Size
centos7vm: ================================================================================
centos7vm: Installing:
centos7vm: containerd.io x86_64 1.4.9-3.1.el7 docker-ce-stable 30 M
centos7vm: docker-ce x86_64 3:20.10.8-3.el7 docker-ce-stable 23 M
centos7vm: docker-ce-cli x86_64 1:20.10.8-3.el7 docker-ce-stable 29 M
centos7vm: Installing for dependencies:
centos7vm: audit-libs-python x86_64 2.8.5-4.el7 base 76 k
centos7vm: checkpolicy x86_64 2.5-8.el7 base 295 k
centos7vm: container-selinux noarch 2:2.119.2-1.911c772.el7_8 extras 40 k
centos7vm: docker-ce-rootless-extras
centos7vm: x86_64 20.10.8-3.el7 docker-ce-stable 8.0 M
centos7vm: docker-scan-plugin x86_64 0.8.0-3.el7 docker-ce-stable 4.2 M
centos7vm: fuse-overlayfs x86_64 0.7.2-6.el7_8 extras 54 k
centos7vm: fuse3-libs x86_64 3.6.1-4.el7 extras 82 k
centos7vm: libcgroup x86_64 0.41-21.el7 base 66 k
centos7vm: libsemanage-python x86_64 2.5-14.el7 base 113 k
centos7vm: policycoreutils-python x86_64 2.5-34.el7 base 457 k
centos7vm: python-IPy noarch 0.75-6.el7 base 32 k
centos7vm: setools-libs x86_64 3.3.8-4.el7 base 620 k
centos7vm: slirp4netns x86_64 0.4.3-4.el7_8 extras 81 k
centos7vm:
centos7vm: Complete!
centos7vm: Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
centos7vm: Created symlink from /etc/systemd/system/multi-user.target.wants/containerd.service to /usr/lib/systemd/system/containerd.service.
🎉. Success! Things seem like they ran correctly: my VM is renamed, my apps are there, I can directly SSH to this machine, I can sudo, Docker is installed as well and all other configs applied! Just note that commands that do not usually provide an output will not be shown above.
In Part 3 we will have a look at a multi-machine scenario.
If you found this helpful, please feel free to…