OpenStack Individual academic project 2026
A Private Cloud Inside One Google Cloud VM
OpenStack on a single Google Cloud VM, and the Linux networking that let its instances reach the outside and survive a restart
- Period
- June 2026
- Platform
- DevStack on Ubuntu 24.04, Google Cloud n2-standard-4
- Context
- Harokopio University of Athens: Cloud Computing Management
One VM, two layers of virtualisation
The assignment was to run OpenStack and administer one tenant end to end: its project and user, quotas, network and instances, and an application on them. I ran DevStack on a single Google Cloud VM.
That puts two hypervisors in one stack. The VM is the first layer; the instances Nova starts are virtual machines inside it. I created it with nested virtualisation enabled.
One tenant, as an administrator would hand it over
DevStack installed OpenStack on that one host, its services running as 24 systemd units. Inside it I built a single tenant:
- Keystone
- a project, a user with the member role and an openrc file for that user
- Glance
- an Ubuntu 22.04 Minimal image for both instances
- Nova
- custom flavors and project quotas
- Neutron
- a private network and subnet, a router to the public network, a security group and floating IPs
Keeping the tenant apart from admin caused the most trouble. Of the 18 problems I logged, 8 came from creating or looking up a resource with the admin credentials: a security group “not found”, an “invalid” key pair, an instance with no private address. A resource belongs to the project that created it, so the credentials in use decide what exists.
Two networks that did not know about each other
An instance’s traffic crosses two networks. Inside OpenStack, the Neutron router gives it an address from the floating range and sends it out through br-ex, the host’s Open vSwitch bridge for the external network. Outside, Google Cloud knows one address for the whole machine: the VM’s internal address on ens4, its only network interface.
Nothing joined the two: the lab instructions, written for VirtualBox, did not cover it, so the host had to route and translate the traffic itself.
Bridging ens4 into br-ex was not an option. I had run that one ovs-vsctl command in an early lab: it took the VM’s only interface out of the host’s network stack, SSH dropped, the serial console could not recover it, and I rebuilt the VM from scratch. From then on, ens4 stayed the host’s, and traffic was routed to it, not bridged.
Google Cloud network
Forwards traffic from the VM only when its source is the VM’s own address
Google Cloud VM
Nested virtualisation with KVM
ens4
Its only network interface
Added on the host: iptables
MASQUERADE on ens4, FORWARD between br-ex and ens4, saved with iptables-persistent
br-ex
Open vSwitch bridge for OpenStack’s external network
Added on the host: brex-setup.service
A oneshot unit that restores the bridge’s address and the floating-range route at boot
OpenStack, installed with DevStack
Neutron router
Gateway on the public network, one floating IP per instance
Private tenant network
One security group: TCP 22, 80 and 5432, and ICMP
vm-app
NGINX on port 80, Gunicorn, Flask
vm-db
PostgreSQL 14 on port 5432
vm-appvm-dbby private address
Outbound
- Instance
- Neutron router
- br-ex
- ens4
- Google Cloud
Test
SSH tunnel through the host to vm-app, port 80
Following a packet to where it disappeared
- Symptom
- In the first instance, ping got no reply and
apt updatehung at “Connecting to archive.ubuntu.com”. No error, anywhere. - Trace
tcpdumpon each interface along the path: the packets showed up on the instance’s tap interface and onbr-ex, never onens4.- Cause
- The host was not forwarding between
br-exandens4, and anything it did forward would leave with a floating-range source address, which Google Cloud drops without a reply. - Fix
- IP forwarding on; FORWARD rules out from
br-exand back for established connections only; MASQUERADE onens4; all saved with iptables-persistent.
That held until the VM was stopped and started again. The iptables rules came back, but br-ex came up without its address and route, and the floating IPs were unreachable again. I kept a checklist of commands to rerun, then replaced it with brex-setup.service, a oneshot systemd unit ordered after the network and DevStack that restores the bridge at boot.
The lessons I kept: the fault lies one step past the last interface where the packet is still visible, and a fix that a restart undoes is not finished until it is part of the boot.
An API and its database on the tenant network
To test the tenant end to end, I deployed a small two-tier application. The lab exercises put PostgreSQL and Flask on one VM; I split them across two instances, so that the API had to reach its database over OpenStack’s private network.
vm-db runs PostgreSQL 14, set to accept remote connections. vm-app runs a Flask API under Gunicorn as a systemd service, behind NGINX on port 80, and reaches vm-db by its private address. The final check went through an SSH tunnel via the host: /api/products returned four products as JSON, read from the other instance.
What I would change
It worked, and it was built for a lab. Both instances share one security group, so PostgreSQL’s port is open on the application VM and port 80 on the database; I would give each tier its own group, with 5432 open only to vm-app. The database password sits in the application code, and PostgreSQL listens on every interface. And the host’s networking belongs in how the VM is provisioned, not in fixes added along the way.