diff --git a/content/posts/create-vms-chromebook-hashicorp-vagrant/index.md b/content/posts/create-vms-chromebook-hashicorp-vagrant/index.md new file mode 100644 index 0000000..44cf0c0 --- /dev/null +++ b/content/posts/create-vms-chromebook-hashicorp-vagrant/index.md @@ -0,0 +1,236 @@ +--- +title: "Create Virtual Machines on a Chromebook with HashiCorp Vagrant" # Title of the blog post. +date: 2023-02-20 # Date of post creation. +# lastmod: 2023-02-18T17:22:02-06:00 # Date when last modified +description: "Pairing the powerful Linux Development Environment on modern Chromebooks with HashiCorp Vagrant to create and manage local virtual machines for development and testing" # Description used for search engine. +featured: false # Sets if post is a featured post, making appear on the home page side bar. +draft: true # Sets whether to render this page. Draft of true will not be rendered. +toc: true # Controls if a table of contents should be generated for first-level links automatically. +usePageBundles: true +# menu: main +# featureImage: "file.png" # Sets featured image on blog post. +# featureImageAlt: 'Description of image' # Alternative text for featured image. +# featureImageCap: 'This is the featured image.' # Caption (optional). +# thumbnail: "thumbnail.png" # Sets thumbnail image appearing inside card on homepage. +# shareImage: "share.png" # Designate a separate image for social media sharing. +codeLineNumbers: false # Override global value for showing of line numbers within code block. +series: Projects +tags: + - linux + - chromeos + - homelab + - infrastructure-as-code +comment: true # Disable comment if false. +--- +I've lately been trying to do more with [Salt](https://saltproject.io/) at work, but I'm still very much a novice with that tool. I thought it would be great to have a nice little portable lab environment where I could deploy a few lightweight VMs and practice managing them with Salt - without impacting any systems that are actually being used for anything. Along the way, I figured I'd leverage [HashiCorp Vagrant](https://www.vagrantup.com/) to create and manage the VMs, which would provide a declarative way to define what the VMs should look like. That will make it easy to build up, destroy, and redeploy a development environment in a simple, repeatable way. + +Also, because I'm a bit of a sadist, I wanted to do this all on my new [Framework Chromebook](https://frame.work/laptop-chromebook-12-gen-intel). I might as well put my 32GB of RAM to good use, right? + +It took a bit of fumbling, but this article describes what it took to get a Vagrant-powered VM up and running in the [Linux Development Environment](https://chromeos.dev/en/linux) on my Chromebook (which is currently running ChromeOS v111 beta). + +### Install the prerequisites +There are are a few packages which need to be installed before we can move on to the Vagrant-specific stuff. It's quite possible that these are already on your system.... but if they *aren't* already present you'll have a bad problem[^problem]. + +```shell +sudo apt update +sudo apt install \ + build-essential \ + gpg \ + lsb-release \ + wget +``` + +[^problem]: and [will not go to space today](https://xkcd.com/1133/). + +I'll be configuring Vagrant to use [`libvirt`](https://libvirt.org/) to interface with the [Kernel Virtual Machine (KVM)](https://www.linux-kvm.org/page/Main_Page) virtualization solution (rather than something like VirtualBox that would bring more overhead) so I'll need to install some packages for that as well: +```shell +sudo apt install virt-manager libvirt-dev +``` + +And to avoid having to `sudo` each time I interact with `libvirt` I'll add myself to that group: +```shell +sudo gpasswd -a $USER libvirt ; newgrp libvirt +``` + +I'm also going to use `rsync` to share a [synced folder](https://developer.hashicorp.com/vagrant/docs/synced-folders/basic_usage) between the host and the VM guest so I'll need to make sure that's installed too: +```shell +sudo apt install rsync +``` + +### Install Vagrant +With that out of the way, I'm ready to move on to the business of installing Vagrant. I'll start by adding the HashiCorp repository: +```shell +wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg +echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list +``` + +I'll then install the Vagrant package: +```shell +sudo apt update +sudo apt install vagrant +``` + +I also need to install the [`vagrant-libvirt` plugin](https://github.com/vagrant-libvirt/vagrant-libvirt) so that Vagrant will know how to interact with `libvirt`: +```shell +vagrant plugin install vagrant-libvirt +``` + +### Create a lightweight VM +Now I can get to the business of creating my first VM with Vagrant! + +Vagrant VMs are distributed as Boxes, and I can browse some published Boxes at [app.vagrantup.com/boxes/search?provider=libvirt](https://app.vagrantup.com/boxes/search?provider=libvirt) (applying the `provider=libvirt` filter so that I only see Boxes which will run on my chosen virtualization provider). For my first VM, I'll go with something light and simple: [`generic/alpine38`](https://app.vagrantup.com/generic/boxes/alpine38). + +So I'll create a new folder to contain the Vagrant configuration: +```shell +mkdir vagrant-alpine +cd vagrant-alpine +``` + +And since I'm referencing a Vagrant Box which is published on Vagrant Cloud, downloading the config is as simple as: +```shell +vagrant init generic/alpine38 +``` + +That lets me know that +```text +A `Vagrantfile` has been placed in this directory. You are now +ready to `vagrant up` your first virtual environment! Please read +the comments in the Vagrantfile as well as documentation on +`vagrantup.com` for more information on using Vagrant. +``` + +Before I `vagrant up` the joint, I do need to make a quick tweak to the default Vagrantfile, which is what tells Vagrant how to configure the VM. By default, Vagrant will try to create a synced folder using NFS and will throw a nasty error when that (inevitably[^inevitable]) fails. So I'll open up the Vagrantfile to review and edit it: +```shell +vim Vagrantfile +``` + +Most of the default Vagrantfile is commented out. Here's the entirey of the configuration *without* the comments: +```ruby +Vagrant.configure("2") do |config| + config.vm.box = "generic/alpine38" +end +``` + +There's not a lot there, is there? Well I'm just going to add these two lines somewhere between the `Vagrant.configure()` and `end` lines: +```ruby + config.nfs.verify_installed = false + config.vm.synced_folder '.', '/vagrant', type: 'rsync' +``` + +The first line tells Vagrant not to bother checking to see if NFS is installed, and will use `rsync` to share the local directory with the VM guest, where it will be mounted at `/vagrant`. + +So here's the full Vagrantfile (sans-comments[^magic], again): +```ruby +Vagrant.configure("2") do |config| + config.vm.box = "generic/alpine38" + config.nfs.verify_installed = false + config.vm.synced_folder '.', '/vagrant', type: 'rsync' +end +``` + +With that, I'm ready to fire up this VM with `vagrant up`! Vagrant will look inside `Vagrantfile` to see the config, pull down the `generic/alpine38` Box from Vagrant Cloud, boot the VM, configure it so I can SSH in to it, and mount the synced folder: +```shell +; vagrant up +Bringing machine 'default' up with 'libvirt' provider... +==> default: Box 'generic/alpine38' could not be found. Attempting to find and install... + default: Box Provider: libvirt + default: Box Version: >= 0 +==> default: Loading metadata for box 'generic/alpine38' + default: URL: https://vagrantcloud.com/generic/alpine38 +==> default: Adding box 'generic/alpine38' (v4.2.12) for provider: libvirt + default: Downloading: https://vagrantcloud.com/generic/boxes/alpine38/versions/4.2.12/providers/libvirt.box + default: Calculating and comparing box checksum... +==> default: Successfully added box 'generic/alpine38' (v4.2.12) for 'libvirt'! +==> default: Uploading base box image as volume into Libvirt storage... +[...] +==> default: Waiting for domain to get an IP address... +==> default: Waiting for machine to boot. This may take a few minutes... + default: SSH address: 192.168.121.41:22 + default: SSH username: vagrant + default: SSH auth method: private key +[...] + default: Key inserted! Disconnecting and reconnecting using new SSH key... +==> default: Machine booted and ready! +==> default: Rsyncing folder: /home/john/projects/vagrant-alpine/ => /vagrant +``` + +And then I can use `vagrant ssh` to log in to the new VM: +```shell +; vagrant ssh +alpine38:~$ cat /etc/os-release +NAME="Alpine Linux" +ID=alpine +VERSION_ID=3.8.5 +PRETTY_NAME="Alpine Linux v3.8" +HOME_URL="http://alpinelinux.org" +BUG_REPORT_URL="http://bugs.alpinelinux.org" +``` + +I can also verify that the synced folder came through as expected: +```shell +alpine38:~$ ls -l /vagrant +total 4 +-rw-r--r-- 1 vagrant vagrant 3117 Feb 20 15:51 Vagrantfile +``` + +Once I'm finished poking at this VM, shutting it down is as easy as: +```shell +vagrant halt +``` + +And if I want to clean up and remove all traces of the VM, that's just: +```shell +vagrant destroy +``` + +[^inevitable]: NFS doesn't work properly from within an LXD container, like the ChromeOS Linux development environment. +[^magic]: Through the magic of `egrep -v "^\s*(#|$)" $file`. + + +### Create a heavy VM, as a treat +Having proven to myself that Vagrant does work on a Chromebook, let's see how it does with a slightly-heavier VM.... like [Windows 11](https://app.vagrantup.com/oopsme/boxes/windows11-22h2). + +Again, I'll create a new folder to hold the Vagrant configuration and do a `vagrant init`: +```shell +mkdir vagrant-win11 +cd vagrant-win11 +vagrant init oopsme/windows11-22h2 +``` + +And, again, I'll edit the Vagrantfile before starting the VM. This time, though, I'm adding a few configuration options to tell `libvirt` that I'd like more compute resources than the default 1 CPU and 512MB RAM: +```ruby +Vagrant.configure("2") do |config| + config.vm.box = "oopsme/windows11-22h2" + config.vm.provider :libvirt do |libvirt| + libvirt.cpus = 4 + libvirt.memory = 4096 + end +end +``` + +Now it's time to bring it up. This one's going to take A While as it syncs the ~6GB Box first. +```shell +vagrant up +``` + +Eventually it should spit out that lovely **Machine booted and ready!** message and I can log in! I *can* do a `vagrant ssh` again to gain a shell in the Windows environment, but I'll probably want to interact with those sweet sweet graphics. That takes a little bit more effort. + +First, I'll use `virsh -c qemu:///system list` to see the running VM(s): +```shell +; virsh -c qemu:///system list + Id Name State +--------------------------------------- + 10 vagrant-win11_default running +``` + +Then I can tell `virt-viewer` that I'd like to attach a session there: +```shell +virt-viewer -c qemu:///system -a vagrant-win11_default +``` + +I log in with the default password `vagrant`, and I'm in Windows 11 land! +![Windows 11 running on a Chromebook!](win-11-vm.png) + +### Next steps +Well that about does it for a proof-of-concept. My next steps will be exploring [multi-machine Vagrant environments](https://developer.hashicorp.com/vagrant/docs/multi-machine) to create a portable lab environment including machines running several different operating systems so that I can learn how to manage them effectively with Salt. It should be fun! + diff --git a/content/posts/create-vms-chromebook-hashicorp-vagrant/win-11-vm.png b/content/posts/create-vms-chromebook-hashicorp-vagrant/win-11-vm.png new file mode 100644 index 0000000..d02b38a Binary files /dev/null and b/content/posts/create-vms-chromebook-hashicorp-vagrant/win-11-vm.png differ