Projects · Mini Hostpapa

Lab architecture and resource budget

The design of a simulated datacenter that fits in 15 GiB of RAM and 44 GiB of disk. Production to lab mapping, the resource ledger that proves it fits, tool selection, and the deviation register.

Updated Aug 4, 2026 · 33 min read

Lab architecture and resource budget

This is the only document in Volume 2 with no commands in it, and it is the one that decides whether the other three work.

I want to be honest about why it exists. My first instinct was to skip straight to installing things. Open a terminal, apt install qemu-kvm, start clicking. I have built labs that way before and I know exactly how it ends: three hours in, the management server will not start, and I cannot tell whether the problem is a bad config, a wrong IP, or the machine simply running out of memory, because I never wrote down how much memory I was allowed to use.

The failure mode I am specifically designing against

On a machine this size, the most likely outcome is not a configuration error. It is the OOM killer quietly terminating MySQL at 2am, leaving a CloudStack management server that starts, logs nothing useful, and answers no API calls.

That failure looks exactly like a software problem and is entirely a capacity planning problem. Which is a lesson worth learning on a laptop rather than in production, and the reason this document leads with arithmetic instead of commands.


1. What Volume 1 asked for

Before deciding what to simulate, I need the thing being simulated written down. This is the rack from Volume 1, compressed to the parts that have network or storage identity.

Production elementVolume 1 specificationWhy the lab cannot ignore it
fw-edge-01Edge firewall, default deny inbound, NAT for the public rangeThe Secondary Storage VM must reach the internet to download templates. No NAT, no templates, no CloudStack
sw-tor-01Top of rack switch, access port on VLAN 20, trunk carrying 30, 40, 100 to 999, SVIs as gatewaysThis is the piece CloudStack manipulates most. It has to do genuine 802.1Q tagging
cs-mgmt-01Management server, 10.50.0.201, behind VIP 10.50.0.200The control plane. Java plus MySQL, and the largest fixed RAM commitment in the lab
kvm-host-01Hypervisor, cloudbr0 at 10.50.0.101, cloudbr1 with no IP, storage on bond1.30 at 10.50.1.101 MTU 9000Must run its own VMs, so it needs working nested virtualization
nfs-store-01Primary storage export to 10.50.1.0/24, secondary to 10.50.0.0/24, never bothTwo exports, two client networks, two MTUs. The most commonly botched part of a CloudStack build
Public range203.0.113.0/24, gateway .1, CloudStack pool from .32System VMs and Virtual Routers draw from this. It must actually route out
Pod reserved system range10.50.0.20 to 10.50.0.79CloudStack assigns system VM management addresses from here
Here is the rule I gave myself for the whole lab

Every IP address, VLAN ID, bridge name, and export path in that table gets used verbatim in the lab.

Not "something similar". Not "renumbered for convenience". The literal same values.

The reason is that the point of this lab is to test the Volume 1 design, and a test that changes the inputs is not a test. It also means that when I eventually build this on real hardware, the configuration files are not a fresh start. They are these files with the fake parts removed.


2. The three floors, and what each one costs

The overview introduced the nesting model. Here I need it as an accounting structure rather than a picture, because every floor multiplies the cost of the floor above it.

Preparing diagram
The consequence people miss on the first attempt

Memory at L2 is not free memory. It is memory subtracted from kvm-host-01, which was already subtracted from the laptop.

So when CloudStack decides to start a Secondary Storage VM with the default 1024 MiB offering, it is not asking the laptop for 1 GiB. It is asking kvm-host-01 for 1 GiB out of the 7 GiB I gave it, and kvm-host-01 cannot borrow from the laptop.

This is why tuning the CloudStack system offerings down matters far more here than it would on real hardware. I will come back to it with numbers in section 3.3.


3. The RAM ledger

This is the arithmetic that the whole lab stands on. I am writing it as two tables because there are two independent budgets: the laptop's, and the nested hypervisor's.

3.1 The laptop's budget

Total usable is roughly 15.4 GiB, which I will work in as 15,800 MiB to keep the numbers honest rather than optimistic.

ConsumerMiBNotes
Host kernel, systemd, minimal desktop session1,800Measured with the IDE and browser closed, not with them open
QEMU process overhead, 2 VMs400Roughly 200 MiB per VM outside the guest's own allocation. Easy to forget, and it is real
Open vSwitch, NFS server, nftables150All three are cheap. OVS userspace is the bulk of it
cs-mgmt-01 guest RAM4,096MySQL plus a Java 17 heap capped at 2048 MiB
kvm-host-01 guest RAM7,168Everything at L2 comes out of this number
Committed total13,614
Headroom~2,190Page cache, NFS buffers, and the margin that stops the OOM killer from getting involved
Read the first row again, because it is the row that decides your evening

That 1,800 MiB is the host with my IDE and browser closed. With them open the measured figure was over 13,000 MiB, which leaves 1.5 GiB available and makes this entire lab impossible.

I am not going to pretend I can engineer around that. The lab and the IDE do not coexist on this machine. Section 5 turns that into an actual procedure instead of a hope.

3.2 The nested hypervisor's budget

Inside kvm-host-01, the 7,168 MiB gets spent like this. Every figure below assumes I have tuned the CloudStack system offerings down, which section 3.3 explains.

ConsumerMiBFixed or variable
Ubuntu 24.04 minimal server400Fixed
cloudstack-agent, a Java 17 process700Fixed, and larger than people expect
QEMU overhead for nested guests300Grows with instance count
Virtual Router, one per guest network256Variable. A second guest network means a second router
Secondary Storage VM, tuned from 1024512Fixed, exactly one per zone
Console Proxy VM, tuned from 1024512Fixed at this scale
Committed before any tenant instance2,680
Available for tenant instances~4,480At 192 MiB per Alpine guest that is over 20 instances. I plan for 4
This table is the single strongest argument for Alpine Linux

Look at the last row. With Alpine at 192 MiB I have room for twenty instances and will comfortably run four.

Swap Alpine for Ubuntu at 1024 MiB and that number becomes four instances maximum, with no margin. Swap it for a Windows guest and the answer is zero.

Choosing a 190 MB guest OS is what converts this lab from theoretically possible into comfortable. It is the highest leverage decision in the whole volume, and it costs nothing, because a tenant instance in this lab exists to prove networking works, not to run a workload.

3.3 The system offering tax nobody warns you about

CloudStack ships default system service offerings. On a fresh 4.22 zone the Secondary Storage VM and Console Proxy VM each get 1024 MiB. That is 2,048 MiB of my 7,168 gone before a single tenant instance exists, on two VMs that in this lab are almost entirely idle.

Halving both to 512 MiB recovers 1,024 MiB, which is more than five Alpine instances.

To be precise about how badly I need this: at the stock 1024 MiB the committed total inside kvm-host-01 is about 3,704 MiB, leaving roughly 3,460 MiB for instances. That is still enough for the four Alpine guests I plan to run, so the lab does boot without this tuning. Halving the offerings roughly doubles the instance headroom rather than making the difference between working and not. I do it because headroom is what lets me experiment without doing arithmetic first.

Why I am comfortable halving them, and where I would not be

These two VMs are sized in the defaults for a production zone. The Console Proxy multiplexes VNC sessions for potentially hundreds of concurrent users, and the Secondary Storage VM streams template and snapshot data at whatever rate the storage network allows.

In my lab there is one user, four instances, and one template download. The work simply is not there.

Where I would not do this: anywhere real. This is a deviation, it goes in the register in section 8, and the production value is the default. A lab tuning that leaks into production becomes a mysterious performance incident six months later.

3.4 Two things that give memory back for free

Kernel Samepage Merging scans memory for identical pages and collapses duplicates into one physical page, copying on write if anything changes.

The reason it works unusually well here: both of my L1 VMs run the same Ubuntu 24.04 cloud image. Same kernel, same libc, same systemd, largely the same page cache. That is a large amount of genuinely identical memory.

In a two-identical-guest setup like this, reclaiming 10 to 25 percent is typical. On 11,264 MiB of guest RAM, the low end of that range is over 1 GiB back.

The cost is CPU spent scanning, which on a mostly idle lab is a trade I will happily take.


4. The disk ledger

Disk is the constraint I nearly got wrong, in the opposite direction from RAM. My root filesystem is 86 percent full with 64 GiB free, and my ceiling is 50 GiB. Add up the virtual disk sizes I want and the total looks impossible.

It is not, and the reason is the single most useful fact about qcow2.

Here is the trick that makes the disk budget work

A qcow2 image has a virtual size and an actual size, and they are wildly different.

Create a 12 GiB qcow2 and install Ubuntu into it: the guest believes it has a 12 GiB disk, and the file on my laptop is about 2 GiB. It grows only as blocks are genuinely written.

Better still, a qcow2 can use a backing file. Both of my servers derive from one Ubuntu cloud image, so each overlay stores only the blocks where that server differs from the shared base.

So I budget the ceiling and spend the reality. The ceiling is what protects me from filling the disk. The reality is what I actually pay on day one.

ItemCeiling (virtual)Actual at first bootWhat makes it grow
Ubuntu 24.04 cloud image, shared backing file3.5 GiB0.6 GiBNothing. It is read only and shared by both servers
Alpine 3.24 cloud image0.2 GiB0.2 GiBNothing
cs-mgmt-01 overlay12 GiB~4.5 GiBMySQL database growth and CloudStack logs
kvm-host-01 overlay10 GiB~3.0 GiBAgent logs only. Instance disks live on NFS, not here
cloud-init seed ISOs, 2 of them2 MiB2 MiBNothing
NFS secondary storage8 GiB~3.5 GiBTemplates and snapshots. The system VM template alone is ~3 GiB
NFS primary storage10 GiB~1.5 GiBInstance root volumes, thin provisioned
Total43.7 GiB~13.3 GiBUnder the 50 GiB ceiling with 6 GiB of margin
The one operation that would blow this budget instantly

virsh managedsave suspends a VM by writing its entire RAM to disk, so the lab resumes exactly where it was. It is a lovely feature and I am not going to use it.

Saving both servers means writing 11,264 MiB of RAM to disk. That is 11 GiB, on top of a 43.7 GiB ceiling, on a filesystem with 64 GiB free.

Suspending the lab costs more disk than the entire rest of the lab's growth budget. So I shut down cleanly instead. It is slower to resume and it costs nothing, and on a machine this full "costs nothing" wins.

Because everything lives under one directory, checking the budget is a single command rather than an audit:

Watching the disk budget
adam@laptop:~$
Read those first two commands as a pair, because that is the whole point

Command one uses --apparent-size and reports 44 GiB. That is the ceiling: what the lab could grow into if every image filled up.

Command two reports 13 GiB. That is what I am actually paying today.

Watching only the first number means panicking over space I have not spent. Watching only the second means being blindsided the day the images grow into their allocation. The gap between the two numbers is my remaining runway, and it is the number worth tracking.

CPU, briefly

Eight threads, and I hand out 2 vCPU to cs-mgmt-01 and 4 vCPU to kvm-host-01. Six of eight, which leaves two for the host.

CPU is the one resource I am relaxed about, because vCPUs are timeshared while RAM is not. An idle vCPU costs nothing, and this lab is idle almost all of the time. Overcommitting CPU makes things slow; overcommitting RAM makes things die. Only one of those is recoverable.

The place it does hurt is nested virtualization. An L2 guest's instructions are trapped and emulated through two hypervisor layers, so nested guests are genuinely slower, in the rough neighbourhood of two to five times for CPU-bound work. Alpine booting in a few seconds instead of one is fine. This is also, incidentally, the second reason not to run Ubuntu guests at L2.


5. Lab mode, as a procedure rather than a hope

Section 3.1 established that the lab and my normal desktop workload cannot share this machine. Leaving that as advice would guarantee I ignore it, so it becomes a checklist with a verification step.

  1. Save and close the IDE. This is the largest single consumer and there is no way around it.
  2. Close the browser, or leave one window with the CloudStack UI and nothing else. Modern browsers will happily eat 2 GiB across a normal set of tabs.
  3. Stop any container runtime. docker and podman keep daemons and volumes resident.
  4. Drop the page cache so the starting figure is honest rather than flattering: sync && echo 3 | sudo tee /proc/sys/vm/drop_caches
  5. Verify before starting anything. free -m should show at least 13,600 MiB available. Not free, available.
  6. Start the lab.

If step five fails, do not start the lab. Find the consumer with ps -eo rss,comm --sort=-rss | head -15 first. Starting anyway does not produce a slow lab, it produces a broken one with confusing symptoms.


6. Choosing the software, including what I rejected

The overview listed the choices in one line each. Here is the reasoning, because a decision I cannot defend is a decision I will second guess at the first error message.

6.1 Hypervisor: KVM, and why not the alternatives

CandidateVerdictReasoning
KVM / QEMU / libvirtChosenIn the kernel already, so no extra memory for a hypervisor layer. Solid nested virtualization. And critically, it is the same hypervisor CloudStack will use at L1, so what I learn at L0 transfers directly
VirtualBoxRejectedNested virtualization support is limited and historically fragile. The entire lab depends on it working
VMware WorkstationRejectedNested virt works well, but it is proprietary, it duplicates a hypervisor the kernel already provides, and it does not integrate with libvirt tooling or Terraform providers
LXC or Docker containersRejected, and worth explainingContainers share the host kernel, so a container cannot be a KVM hypervisor with its own kernel modules. This lab is about the hypervisor layer, which is exactly the layer containers remove

6.2 Network fabric: Open vSwitch, and the option I nearly took instead

This was the closest decision in the volume and I changed my mind once.

OVS is a real software switch. It performs genuine 802.1Q tagging, it has real access ports and real trunk ports, and it exposes them in a model that maps one to one onto the physical switch config from Volume 1.

The decisive feature is libvirt integration. libvirt understands OVS natively through <virtualport type='openvswitch'/>, which means I can define access and trunk ports declaratively in network XML rather than scripting ovs-vsctl. That single fact moved the fabric from "a shell script I will forget" into "a versioned file".

Cost is roughly 100 MiB of userspace. For a genuine switch, that is nothing.

6.3 Terraform and Ansible, and where the line sits

I use both, and the boundary is not arbitrary.

TerraformAnsible
OwnsWhether a thing existsThe state inside a thing that exists
In this labVM definitions, disks, cloud-init seeds, libvirt networks and poolsHost packages, nested virt, OVS bridges, NFS exports, CloudStack install
Natural verbdestroy and recreateConverge in place
Keeps state?Yes, in a state file, which is why it can destroy safelyNo, it reads reality each run
Why the boundary is worth defending

The tempting shortcut is to have Terraform run provisioners that configure the guest, or to have Ansible create VMs with the libvirt module. Both work. Both produce the same eventual mess, where nobody can answer what happens if I run this twice.

Keeping the line clean buys one concrete capability I care about a lot: I can destroy the entire rack and rebuild it from zero with two commands, because Terraform knows precisely what it created and Ansible does not care whether the server is new.

That is what makes the lab disposable, and a lab you are afraid to destroy stops being a lab and becomes production with no backups.

6.4 Guest operating systems

Ubuntu 24.04 LTS for the two servers. It is on the CloudStack 4.22 support matrix, the repository has a noble component, official cloud images with cloud-init exist, and it is what I would run on real hardware.

There is a bonus I did not plan for. Volume 1 flagged Ubuntu's ufw DEFAULT_FORWARD_POLICY as a CloudStack pitfall: forwarding defaults to drop, which silently breaks guest traffic through the bridges. Running Ubuntu means I will actually meet that pitfall instead of having read about it.

Alpine Linux 3.24 for tenant instances. The cloud image is about 190 MB and boots in 192 MiB of RAM. Section 3.2 already made this argument with numbers.

One Alpine detail that will matter later

Alpine uses musl libc and busybox instead of glibc and GNU coreutils. For a lab instance whose job is to hold an IP address and answer a ping, this is irrelevant.

It stops being irrelevant if you try to run something that assumes glibc. I am flagging it now so that when a binary mysteriously fails to execute inside an Alpine instance in a later volume, the cause is already written down. The tiny image is not free, it is just cheap in the dimension I care about here.


7. The network fabric

Now the part that took the longest to get right on paper, and would have taken far longer to get right by trial and error.

There is exactly one OVS bridge, lab-tor, and it is sw-tor-01 from Volume 1. Everything else attaches to it.

Preparing diagram

7.1 The VLANs, and who lives on each

VLANPurposeSubnetGatewayMTUWho has an address on it
20Management10.50.0.0/2410.50.0.1 (laptop SVI)1500cs-mgmt-01 .201, kvm-host-01 .101, secondary storage .210, system VMs from .20 to .79
30Primary storage10.50.1.0/24None, deliberately9000NFS array .20, hypervisor .101
40Public203.0.113.0/24203.0.113.1 (laptop SVI, NAT)1500CloudStack pool .32 to .60: SSVM, Console Proxy, Virtual Routers
100 to 199Guest, one per tenant networkAllocated by CloudStack from 10.0.0.0/8The Virtual Router for that network1500Nothing on the laptop. Entirely CloudStack's
Two rows in that table are load bearing, and both are easy to get wrong

VLAN 30 has no gateway, on purpose. This is straight from Volume 1 and it is a security property, not an oversight. A subnet with no gateway is a subnet whose traffic physically cannot leave. Storage traffic between a hypervisor and an array has no legitimate reason to be routable, so removing the possibility is better than writing a firewall rule that forbids it.

VLANs 100 to 199 have nothing on the laptop. No SVI, no address, no route. Tenant traffic is routed by its Virtual Router, and if the laptop had an address in a tenant network it would be a host sitting inside a network that customer traffic traverses, which is precisely what Volume 1's segmentation design exists to prevent.

An empty column in a table is hard to notice. Both of these are correct configurations that look like omissions.

7.2 Access ports and trunk ports, declared not scripted

The reason OVS won section 6.2 is that libvirt can express the port types directly. Two portgroups cover the whole rack:

PortgroupTypeWhat the guest seesAttached to
mgmt-accessAccess, single tag 20Untagged frames. The guest needs no VLAN configuration at allcs-mgmt-01 enp1s0, kvm-host-01 enp1s0
rack-trunkTrunk: 30, 40, 100 to 199Tagged frames. The guest creates sub-interfaces per VLANkvm-host-01 enp2s0

The first one is why cs-mgmt-01 has no VLAN configuration anywhere in its cloud-init. From inside that VM, enp1s0 is an ordinary NIC on an ordinary network. The VLAN exists entirely in the switch, which is exactly how an access port behaves on real hardware, and exactly the misunderstanding that leads people to configure VLANs on servers that do not need them.

7.3 The kernel detail that makes the hypervisor's interfaces work

This is the part of the design I was least sure about, and I want to record both the doubt and the resolution, because the resolution turned out to explain something about CloudStack I had accepted without understanding.

Volume 1 specifies that on the hypervisor:

  • cloudbr1 is a bridge whose member is the trunk interface (bond1 in production, enp2s0 here)
  • the storage IP lives on enp2s0.30, a VLAN sub-interface of that same trunk interface

Reading that back while designing the lab, it looked wrong to me. If enp2s0 is enslaved to a bridge, surely every frame arriving on it goes to the bridge? In which case enp2s0.30 never receives anything and primary storage silently fails.

Here is the answer, and it is in the kernel receive path

Frames are processed in __netif_receive_skb_core, and the order of two checks decides the entire question:

vlan_do_receive() runs before the bridge's rx_handler. So a tagged frame for VLAN 30 is claimed by enp2s0.30 and never reaches cloudbr1 at all. Untagged frames fall through to the bridge as normal.

Volume 1 is correct. My doubt was wrong.

And this is the bit that made something click about CloudStack

That kernel ordering is not a quirk I am exploiting. It is the mechanism CloudStack's entire KVM networking model is built on.

When CloudStack needs public VLAN 40, the agent creates enp2s0.40 plus a dedicated bridge for it. When it needs guest VLAN 101, it creates enp2s0.101 plus another bridge. Each of those sub-interfaces claims its own tagged frames before the parent bridge ever sees them.

So cloudbr1 is not really "the bridge that carries public and guest traffic". It is a declaration of which physical interface the agent should hang VLAN sub-interfaces off. That is why Volume 1 found the traffic label to be just a string that CloudStack writes down and uses later, and why cloudbr1 correctly holds no IP address.

I had those two facts memorised from reading the docs. Now I know why they are true, and that is the difference this lab is supposed to make.

7.4 Following a packet from a tenant instance to the internet

If I can trace this path, I understand the fabric. Every hop is a place I have to have configured something correctly.

Preparing diagram
The thing that surprised me about that path

Count the NATs. There are two.

The Virtual Router source NATs the tenant's private address to its public address, which is real CloudStack behaviour and exactly what Volume 1 designed. Then nftables masquerades 203.0.113.0/24 to my laptop's actual address, because 203.0.113.0/24 is documentation space and no real router will carry it.

Only the second NAT is fake. It is standing in for the fact that a real deployment owns routable address space, so the edge firewall would forward rather than translate.

The practical consequence is that inbound connections from the real internet will not work in this lab, because the outer NAT has no port forwards. Testing inbound means testing from the laptop, which sits on the correct side of it. Knowing that in advance is worth a lot, because "why can I not reach my instance from my phone" is otherwise an hour of debugging a firewall that is behaving perfectly.


8. The deviation register

This is the most important table in the volume, and the reason is simple: a lab that quietly differs from production is worse than no lab, because it teaches you confident wrong things.

Every simplification lives here with its production equivalent next to it.

#Lab doesProduction doesRisk if I forget
1One laptop plays the ToR switch, the edge firewall, and the storage arrayThree separate devices with separate failure domainsLow. The interfaces and addresses are identical, only the chassis count differs
2Single NIC per role, no bonding. enp1s0 and enp2s0bond0 and bond1, LACP to an MLAG pairMedium. Bond misconfiguration is a real production failure the lab cannot rehearse. Bridge names stay identical so CloudStack config is unaffected
3One management server at 10.50.0.201, no VIPTwo servers behind VIP 10.50.0.200Medium. The host global setting must point at the VIP in production. Pointing it at a real server address is a documented pitfall
4System offerings halved to 512 MiBDefaults, 1024 MiBLow in the lab, high if it leaks. Undersized system VMs in production become a mysterious performance incident
5Guest VLANs 100 to 199100 to 999Low. Purely to keep the trunk declaration readable. Capacity, not behaviour
6Public pool 203.0.113.32 to .60.32 to .190Low. Same subnet, same gateway, fewer addresses
7DNS is 1.1.1.1 for both internal and externalInternal resolvers at 10.50.0.11 and .12Medium. Real internal DNS is how hosts resolve each other. The lab uses /etc/hosts, which does not scale and hides resolver problems
8An outer NAT hides the public range behind the laptop's IPRoutable address space, forwarded not translatedHigh for expectations. Inbound from the real internet cannot work. See section 7.4
9No IPv6Public /64 and a guest /48 per Volume 1Medium. Volume 1's IPv6 plan stays untested. Deferred deliberately, because it needs routable space the laptop does not have
10Nested virtualization, so L2 guests are 2 to 5 times slowerBare metal hypervisorsLow for correctness, high for timing. Never draw a performance conclusion from this lab
11One hypervisor hostThree, in a clusterMedium. No live migration, no HA, no capacity rebalancing to test. These are Volume 4 topics
Number 8 and number 11 are the two I expect to actually bite me

The others are size and count differences. These two remove capabilities.

Number 8 means every "I cannot reach it from outside" symptom has a boring explanation, and I will still spend time on it at least once.

Number 11 means a whole category of CloudStack behaviour, migration, host failure, and capacity allocation across a cluster, cannot be exercised here at all. A one-host cluster is not a small cluster, it is a different thing. I would rather write that down than come away believing I have tested clustering.


9. What I expect to go wrong

Predicting failures is worth more than hoping for none, because a predicted failure is a diagnostic step instead of a mystery.

Nested virtualization not actually enabled. kvm_intel needs nested=1, and kvm-host-01 needs host-passthrough CPU mode to see VMX at all. Miss either and the CloudStack agent installs fine, registers fine, and then every system VM fails to start.

Diagnostic: /dev/kvm must exist inside kvm-host-01. If it does not, nothing else in Volume 2 works, so this is the first thing document 2 verifies.

Ubuntu's forward policy dropping guest traffic. ufw defaults DEFAULT_FORWARD_POLICY to DROP, which breaks bridged guest traffic while leaving the host perfectly reachable. Volume 1 flagged it; I expect to meet it anyway.

Diagnostic: the host pings fine, instances cannot reach their gateway.

Running out of RAM despite the ledger. Something on the host will be larger than I measured.

Diagnostic: dmesg | grep -i "killed process". If the OOM killer is in the logs, stop debugging the application.


10. Success criteria for this document

No commands were run, so the test is whether the design holds up to questioning:

  • The RAM ledger adds up with headroom, and I know which single row is most likely to be wrong (host baseline, section 3.1)
  • The disk ceiling is 43.7 GiB against a 50 GiB budget, and I know why the ceiling and the actual figure differ by 30 GiB
  • I can name what each of the three floors costs, and why L2 memory is subtracted twice
  • Every Volume 1 IP address, VLAN, and bridge name is reused verbatim, so the lab tests the design rather than a variation of it
  • Every simplification is in the deviation register with its production equivalent
  • I can trace a packet from an Alpine instance to the internet and name what is configured at each hop
  • I know which two deviations remove capability rather than just scale, and what that means for conclusions I am allowed to draw
One outcome from writing this document that I did not expect

Section 7.3 was going to be a bug report. I was fairly sure Volume 1 had specified something that could not work, and I was ready to correct it.

It was correct, and chasing it taught me why cloudbr1 holds no IP address and why a traffic label is "just a string CloudStack writes down". Facts I had already written down in Volume 1 and believed without understanding.

That is the real argument for building the lab. Not that the design was wrong, but that I could not tell the difference between knowing something and having read it. Now I can, for that one detail. The next three documents are for finding the rest.


Next

Host preparation and network fabric builds the L0 layer: nested virtualization on, the OVS ToR carrying real VLANs, SVIs answering, NAT working, and NFS exporting. It is where this design meets its first machine.