The pods from the last post are perfectly isolated. That’s the requirement, and straight away the problem. Every one of them needs Windows updates, a package repo, DNS, maybe a domain controller. Do I really run a WSUS (Windows Server Update Services) per pod?
No. There’s a third subnet access mode for exactly this. The design it enables is hub-and-spoke, with a very specific property: spokes reach the hub; the hub cannot reach the spokes; spokes never reach each other.
The three access modes
Everything in this series comes down to one field on a VPC subnet, which is either elegant or slightly deflating, depending on the day:
accessMode | Advertised to | Use |
|---|---|---|
Private | nobody outside the VPC | workloads — isolation and overlapping CIDRs |
PrivateTGW | every VPC attached to the org’s transit gateway | shared services |
Public | the external network | internet/corp-facing endpoints |
PrivateTGW (private transit gateway) subnets draw their addresses from a
separate transit block (here 172.31.0.0/…), not from the VPC’s own
privateIPs. That’s the key. The shared range can’t collide with the pods'
172.30.0.0/16, because it comes from a different pool that all VPCs agree
on.
The build
One more VPC, shared-svc, follows the same ordering rules as any other:
VPC, then VPCAttachment, then LoadBalancer, then namespace. Then a single
subnet:
apiVersion: crd.nsx.vmware.com/v1alpha1
kind: Subnet
metadata: {name: sn-services}
spec: {accessMode: PrivateTGW, ipv4SubnetSize: 32}
It realized as 172.31.0.0/27. A VM on it, svc-repo01 at 172.31.0.2,
serving HTTP, became the shared repo. Not the most glamorous job in the lab,
but somebody has to do it.
One way by construction: the pods reach the hub, and nothing reaches a pod.
The test that matters is directional
Reachability to the service is the easy claim. From esx01 in each of the
three pods (ESXi ships python3, so urllib is the test client):
pod-a esx01 -> http://172.31.0.2/ 200 shared-svc repo01
pod-b esx01 -> http://172.31.0.2/ 200 shared-svc repo01
pod-c esx01 -> http://172.31.0.2/ 200 shared-svc repo01
default-vpc -> http://172.31.0.2/ 200 shared-svc repo01
Three pods with identical source addresses (172.30.0.40) all hit one
service, and all get answers. So how does each reply find its way back to the
right pod, when three of them claim .40?
Because pod traffic crosses the transit gateway SNAT’d to a per-VPC
address from the external IP block, by the
default outbound NAT
in each VPC’s connectivity profile. The service never sees 172.30.0.40; it
sees three distinct SNAT addresses. Ambiguity never arises.
Now the other direction, from svc-repo01 back towards a pod:
svc-repo01 -> 172.30.0.40 unreachable
svc-repo01 -> 172.30.0.41 unreachable
Not “blocked”: unroutable. Pod subnets are Private, so they were never
advertised to the transit gateway. Even if they had been, 172.30.0.40
would be ambiguous across three VPCs. The hub literally cannot start a
conversation with a spoke.
For a shared service that will one day be compromised, that’s the property you want. Gloomy, I know, but gloom is a perfectly good security posture.
What goes in the hub
Anything that pods consume and that is stateless about which pod is asking: WSUS and patch mirrors, OS and package repos, container registries, NTP, DNS forwarders, licence servers. Domain controllers work too, with the usual caveat: identical hostnames across pods need per-pod domains or a naming scheme.
What does not go in the hub: anything that needs to reach into a pod,
such as monitoring pollers, backup agents that pull, and jump hosts. Those
either live in the pod, or the pod publishes a LoadBalancer VIP for them:
the deliberate door from part 0.
Tightening further
The transit gateway gives you reachability; policy gives you precision. A
VPCGatewayFirewallPolicy on shared-svc can restrict inbound traffic to
tcp/80,443 from the transit range and nothing else. Then the repo is a
repo, and not a foothold.
I left it open for the test. You shouldn’t: do as I say, not as I did.
Why this matters outside the lab
This is the pattern that makes isolated tenants affordable. Without it, every isolated environment needs its own patch server, repository, DNS and directory. That’s cost and drift, and they quietly kill the idea.
With it, a customer runs one set of shared services for dozens of tenants, and keeps them patched in one place. They can still show a security reviewer that the shared service has no path back into any tenant.
The same hub serves well beyond patching: central logging and monitoring collectors, licence servers, artifact registries, build agents. Anything tenants consume, but shouldn’t be reachable by.
Rules learned
PrivateTGWis the shared-services mode: addresses from the transit block, advertised to every attached VPC, no collision with pod space.- Access is one-way by construction: pods to service works (SNAT’d per VPC), and service to pod has no route. Test both directions and write down both results.
- Identical pod addressing and shared services coexist because of the SNAT. The hub sees per-VPC SNAT addresses, never the overlapping private ones.
- The same ordering rules apply to the hub VPC: VPC, attachment, load balancer, namespace, subnets, wait for image sync, then workloads.
- Add a gateway firewall policy on the hub. Reachability is not authorisation.
Broadcom documentation
- Add a Subnet to a VPC: the Private Transit Gateway mode: addresses from the private transit gateway blocks, reachable from the project’s VPCs.
- Add a VPC Connectivity Profile: private transit gateway IP blocks, and the SNAT rule default outbound NAT gives each VPC.
- Transit Gateways: traffic between a tenant’s VPCs, and out to the external network.
- Virtual Private Clouds Overview: Private, Private - Transit Gateway and Public subnets side by side.
- Add GFW Rules for VPCs: north-south gateway firewall rules for a VPC’s ingress and egress traffic.
Previously: three datacenters, one IP plan. Next: the whole pod as a single catalog item.
Lab environment; opinions my own. Output captured live, trimmed for length, never edited for outcome.