Skip to content

Commit eb43344

Browse files
danielpancakeclaude
andcommitted
Lab 5: QuickNotes in a Vagrant VM (Tasks 1 & 2)
Vagrantfile boots Ubuntu 24.04, installs Go 1.24.5, syncs ./app, and forwards 127.0.0.1:18080 -> guest:8080. submissions/lab5.md documents Task 1 (up + verify) and Task 2 (snapshot save/break/restore). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 221274b commit eb43344

2 files changed

Lines changed: 213 additions & 0 deletions

File tree

β€ŽVagrantfileβ€Ž

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# -*- mode: ruby -*-
2+
# vi: set ft=ruby :
3+
4+
GO_VERSION = "1.24.5".freeze
5+
6+
Vagrant.configure("2") do |config|
7+
config.vm.box = "bento/ubuntu-24.04"
8+
config.vm.hostname = "quicknotes-vm"
9+
config.vm.network "forwarded_port", guest: 8080, host: 18080, host_ip: "127.0.0.1"
10+
config.vm.synced_folder "./app", "/home/vagrant/app"
11+
12+
config.vm.provider "virtualbox" do |vb|
13+
vb.name = "quicknotes-vm"
14+
vb.cpus = 2
15+
vb.memory = 1024
16+
end
17+
18+
config.vm.provision "shell", env: { "GO_VERSION" => GO_VERSION }, inline: <<-'SHELL'
19+
set -euo pipefail
20+
21+
if command -v go >/dev/null 2>&1 && go version | grep -q "go${GO_VERSION} "; then
22+
echo "Go ${GO_VERSION} already installed; skipping."
23+
exit 0
24+
fi
25+
26+
echo "Installing Go ${GO_VERSION}..."
27+
tarball="go${GO_VERSION}.linux-amd64.tar.gz"
28+
curl -fsSLO "https://go.dev/dl/${tarball}"
29+
rm -rf /usr/local/go
30+
tar -C /usr/local -xzf "${tarball}"
31+
rm -f "${tarball}"
32+
33+
echo 'export PATH=$PATH:/usr/local/go/bin' > /etc/profile.d/go.sh
34+
chmod 644 /etc/profile.d/go.sh
35+
36+
# symlink so non-interactive `vagrant ssh -c 'go ...'` (no /etc/profile.d) finds go
37+
ln -sf /usr/local/go/bin/go /usr/local/bin/go
38+
ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt
39+
40+
/usr/local/go/bin/go version
41+
SHELL
42+
end

β€Žsubmissions/lab5.mdβ€Ž

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# Lab 5 Submission
2+
3+
## Task 1: Vagrant Up + Run QuickNotes Inside
4+
5+
### Vagrantfile
6+
7+
The `Vagrantfile` at the repo root:
8+
9+
- uses the `bento/ubuntu-24.04` box (Ubuntu 24.04 LTS)
10+
- sets the hostname to `quicknotes-vm`
11+
- forwards `127.0.0.1:18080` -> guest `:8080`
12+
- syncs host `./app` -> guest `/home/vagrant/app`
13+
- caps the VM at 2 vCPU / 1024 MB RAM
14+
- installs Go 1.24.5 via an idempotent shell provisioner
15+
16+
See [Vagrantfile](../Vagrantfile).
17+
18+
### `vagrant up` β€” first 10 lines
19+
20+
```text
21+
Bringing machine 'default' up with 'virtualbox' provider...
22+
==> default: Box 'bento/ubuntu-24.04' could not be found. Attempting to find and install...
23+
default: Box Provider: virtualbox
24+
default: Box Version: >= 0
25+
==> default: Loading metadata for box 'bento/ubuntu-24.04'
26+
default: URL: https://vagrantcloud.com/api/v2/vagrant/bento/ubuntu-24.04
27+
==> default: Adding box 'bento/ubuntu-24.04' (v202510.26.0) for provider: virtualbox (amd64)
28+
default: Downloading: https://vagrantcloud.com/bento/boxes/ubuntu-24.04/versions/202510.26.0/providers/virtualbox/amd64/vagrant.box
29+
==> default: Successfully added box 'bento/ubuntu-24.04' (v202510.26.0) for 'virtualbox (amd64)'!
30+
==> default: Importing base box 'bento/ubuntu-24.04'...
31+
```
32+
33+
Tail of provisioning confirms the Go install:
34+
35+
```text
36+
==> default: Mounting shared folders...
37+
default: D:/Desktop/DevOps-Intro/app => /home/vagrant/app
38+
==> default: Running provisioner: shell...
39+
default: Installing Go 1.24.5...
40+
default: go version go1.24.5 linux/amd64
41+
```
42+
43+
> **Environment notes:**
44+
>
45+
> - Vagrant 2.4.9 crashes with `Log level must be in 0..8` when fetching a box. Fix: set `VAGRANT_CLOUD_LOG=error` before `vagrant up`.
46+
> - Host had Hyper-V active (WSL2 + Docker). VirtualBox 7.2 ran the VM anyway, so no need to disable it.
47+
48+
### Verify
49+
50+
**Inside the VM:**
51+
52+
```bash
53+
$ vagrant ssh -c 'go version'
54+
go version go1.24.5 linux/amd64
55+
56+
$ vagrant ssh -c 'cd /home/vagrant/app && go build -o /tmp/qn . \
57+
&& (DATA_PATH=/tmp/notes.json setsid /tmp/qn >/tmp/qn.log 2>&1 </dev/null &) \
58+
&& sleep 3 && curl -s http://localhost:8080/health'
59+
{"notes":4,"status":"ok"}
60+
61+
$ vagrant ssh -c 'ss -ltnp | grep 8080'
62+
LISTEN 0 4096 *:8080 *:* users:(("qn",pid=3099,fd=3))
63+
$ vagrant ssh -c 'cat /tmp/qn.log'
64+
2026/06/23 19:51:34 quicknotes listening on :8080 (notes loaded: 4)
65+
```
66+
67+
**From the host (via the `127.0.0.1:18080` port forward):**
68+
69+
```text
70+
$ curl -s http://localhost:18080/health
71+
{"notes":4,"status":"ok"}
72+
73+
$ curl -s http://localhost:18080/notes
74+
[{"id":1,"title":"Welcome to QuickNotes",...},
75+
{"id":2,"title":"Read app/main.go first",...},
76+
{"id":3,"title":"DevOps mantra",...},
77+
{"id":4,"title":"Endpoint cheat-sheet",...}]
78+
```
79+
80+
### 1.2 Design questions
81+
82+
**a) Synced folder type β€” which and why? Trade-off?**
83+
84+
I used the default **VirtualBox shared folder** β€” no extra host tooling, and a
85+
live two-way mount so host edits show up in the guest instantly. Trade-off:
86+
it's slow for heavy small-file I/O, where `rsync` (one-way copy) or `nfs`
87+
(faster, but needs a daemon and a Linux/macOS host) would do better. For one
88+
small Go service, simplicity wins.
89+
90+
**b) NAT vs Bridged vs Host-only; why is loopback-bound forwarding safer?**
91+
92+
I use Vagrant's default **NAT** with a forwarded port. NAT hides the guest from
93+
the LAN unless a port is explicitly forwarded, and I bound the forward to
94+
`127.0.0.1`, so `:18080` is reachable only from the host. A **Bridged**
95+
interface would give the VM its own LAN IP, exposing the unauthenticated
96+
service to everyone on the network β€” needless attack surface for a lab.
97+
98+
**c) Provisioner β€” which and why?**
99+
100+
The **`shell`** provisioner. Installing one Go tarball is a few imperative
101+
steps, and an inline script handles that with no extra dependencies.
102+
`ansible`/`puppet`/`chef` pay off across many hosts; for a single VM with one
103+
tool to install, they're overkill.
104+
105+
**d) Why pin `1.24.5` instead of `1.24`?**
106+
107+
`go.dev/dl/` only serves full point releases (`go1.24.5...tar.gz`), not a bare
108+
`1.24`. Pinning the exact patch keeps every `vagrant up` reproducible β€” same
109+
compiler today or in six months. Floating on `1.24` would silently pull
110+
whatever the latest patch is, reintroducing version drift.
111+
112+
---
113+
114+
## Task 2 β€” Snapshots: Save, Break, Restore
115+
116+
### Commands: save -> break -> verify -> restore -> verify
117+
118+
```bash
119+
$ vagrant snapshot save quicknotes-clean-go124
120+
==> default: Snapshotting the machine as 'quicknotes-clean-go124'...
121+
==> default: Snapshot saved! ...
122+
123+
$ vagrant ssh -c 'sudo rm -rf /usr/local/go /usr/local/bin/go \
124+
/usr/local/bin/gofmt /etc/profile.d/go.sh'
125+
126+
$ vagrant ssh -c 'go version'
127+
bash: line 1: /usr/local/bin/go: No such file or directory (exit 127)
128+
129+
$ time vagrant snapshot restore quicknotes-clean-go124
130+
131+
$ vagrant ssh -c 'go version'
132+
go version go1.24.5 linux/amd64
133+
$ curl -s http://localhost:18080/health
134+
{"notes":4,"status":"ok"}
135+
```
136+
137+
The snapshot captured the **live** machine (RAM included), so the restore
138+
brought the QuickNotes process back with the same PID `3099`, still listening
139+
on `:8080`.
140+
141+
### 6. Restore timing
142+
143+
```text
144+
real 0m21.592s
145+
user 0m0.015s
146+
sys 0m0.000s
147+
```
148+
149+
### 2.2 Design questions
150+
151+
**e) Snapshots are not backups β€” why?**
152+
153+
A snapshot lives on the **same disk and host** as the VM, so it's useless when
154+
that disk dies, the laptop is lost/stolen, or run `vagrant destroy` β€” it
155+
goes with the original. It also can't undo corruption that predates it: if data was already bad (or encrypted by ransomware) when snapshotted, restoring just brings the bad state back. A backup is a separate, off-host, retained copy
156+
157+
**f) Copy-on-write β€” 10 snapshots vs 1?**
158+
159+
Copy-on-write means a snapshot doesn't copy the disk. The current disk is
160+
frozen as a read-only base and new writes go to a delta file, so each snapshot
161+
costs only the blocks that changed since the last one. Ten snapshots β‰ˆ base +
162+
ten small deltas, usually far less than 10Γ— the disk. The cost is on reads,
163+
which must walk the whole delta chain
164+
165+
**g) When is snapshotting an antipattern?**
166+
167+
When the chain gets **long**. Each snapshot adds a delta layer, so long chains
168+
mean slower reads, unbounded disk growth, and fragility β€” losing one delta
169+
breaks everything after it. Snapshots should be short-lived (take β†’ test β†’
170+
restore-or-delete), not long-term history or a backup substitute. Hoarding VM
171+
state as a "pet" instead of rebuilding from code ("cattle") is the antipattern

0 commit comments

Comments
Β (0)