Cloud Networking
On a developer laptop, BoxOffice lives in one Docker network: every container can reach every other container, and only Nginx publishes a port. In the cloud, the equivalent decision is far bigger and much harder to change later. Which machines get a public address? Can the database reach the Internet — and can the Internet reach the database? How does the payment service call the provider, which only accepts requests from allow-listed IP addresses? How does an engineer debug a pod without opening SSH to the world? This page builds the BoxOffice network in a cloud provider, one component at a time.
The examples use AWS names, the most common in job descriptions; the tabs at the end map them to Google Cloud and Azure. The ideas are the same everywhere1. This page assumes you know what an IP address, a CIDR block, a private address and NAT are — if not, read How the Internet works first: it has the same BoxOffice network as an interactive packet walk.
The VPC: your own private network in the cloud
A VPC (Virtual Private Cloud) is a logically isolated network inside a cloud region. You choose its address range, divide it into subnets, and decide every route in and out. Nothing enters or leaves a VPC unless you add a component and a route that allows it.
| Concept | One-line definition |
|---|---|
| Region | a geographic area with several data centers (e.g. sa-east-1, São Paulo) |
| Availability Zone (AZ) | one or more isolated data centers inside a region, with independent power and network |
| VPC | your private network, spanning all the AZs of one region |
| Subnet | a slice of the VPC address range that lives in one AZ |
| Route table | rules attached to subnets: "traffic to this destination goes to that target" |
CIDR planning
Addresses are written in CIDR notation (Classless Inter-Domain Routing): 10.20.0.0/16 means "the first 16 bits are fixed", leaving 32 − 16 = 16 bits for hosts, i.e. 2¹⁶ = 65 536 addresses. Each extra bit in the prefix halves the block.
The VPC should use a private range (RFC 1918: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)2. Two rules save a lot of pain later:
- Do not overlap with other networks you may ever connect to — the office, another VPC, a partner. Two networks with overlapping ranges cannot be routed to each other (peering is refused).
- Leave room: a VPC range is hard to change. Plan for more AZs and more subnet tiers than you need today.
AWS reserves 5 addresses in every subnet (network address, VPC router, DNS, one reserved for future use, broadcast)1. The table below is computed, not typed:
| /p | 2³²⁻ᵖ | 2³²⁻ᵖ − 5 (AWS) |
|---|---|---|
| /16 | 65 536 | 65 531 |
| /19 | 8 192 | 8 187 |
| /20 | 4 096 | 4 091 |
| /22 | 1 024 | 1 019 |
| /24 | 256 | 251 |
| /26 | 64 | 59 |
| /28 | 16 | 11 |
BoxOffice — the CIDR plan
VPC 10.20.0.0/16 in sa-east-1, three AZs, three tiers per AZ:
| Tier | AZ a | AZ b | AZ c | Why this size |
|---|---|---|---|---|
| Public (load balancer, NAT) | 10.20.0.0/24 | 10.20.1.0/24 | 10.20.2.0/24 | few resources live here |
| Private app (Kubernetes nodes and pods) | 10.20.32.0/19 | 10.20.64.0/19 | 10.20.96.0/19 | pods take one IP each in AWS VPC CNI: 8 187 usable per AZ |
| Isolated data (PostgreSQL, Redis, Kafka) | 10.20.8.0/22 | 10.20.12.0/22 | 10.20.16.0/22 | databases are few but need room for replicas and failover |
About half of the /16 is still free for a fourth AZ or a new tier. Kubernetes pods are the reason the app subnets are big: with the AWS VPC CNI every pod gets a real VPC address.
Check the plan in the address explorer: split 10.20.0.0/16 into 8 subnets. The three app subnets are the /19 blocks 2, 3 and 4; the public /24s and the data /22s all fit in the first /19 (10.20.0.0 to 10.20.31.255); blocks 5 to 8 — half of the VPC — are free. Test 10.20.17.4: which tier would own it?
Think first: how many pods fit in one private app subnet?
A /19 has 2¹³ = 8 192 addresses, minus 5 reserved = 8 187. Nodes also take addresses, and the CNI keeps some warm IPs per node, so the real number of pods is lower — but in the thousands per AZ. A /24 (251 usable) would have run out at the first autoscaling event of a flash sale.
Public, private and isolated subnets
A subnet is not "public" because of a checkbox. It is public because of its route table: it has a route 0.0.0.0/0 → Internet Gateway. Everything else follows from routes.
| Subnet type | Default route (0.0.0.0/0) | Reachable from the Internet? | Can reach the Internet? | BoxOffice uses it for |
|---|---|---|---|---|
| Public | Internet Gateway | yes, if the resource has a public IP and the security group allows | yes | Application Load Balancer, NAT gateways |
| Private | NAT gateway | no | yes, outbound only, through NAT | Kubernetes nodes: gateway, order, payment, … |
| Isolated | none | no | no | PostgreSQL, Redis, Kafka |
Every route table also has an implicit local route (10.20.0.0/16 → local): all subnets of the VPC can reach each other at the routing level. Isolation between tiers is therefore the job of security groups and NACLs, not of routes.
The simulator walks one packet through these three tiers. It starts with payment calling the provider: follow the route-table lookup (0.0.0.0/0 → NAT), the two rewrites of the source address and the reply coming back through the NAT table. Then untick route 0.0.0.0/0 → NAT — the misconfiguration behind many "the payment provider is down" incidents — and run it again; and run PostgreSQL tries to download an update to see what an isolated subnet means.
Internet Gateway and NAT gateway
- Internet Gateway (IGW) — attached to the VPC; lets resources with public IPs send and receive Internet traffic. It is horizontally scaled and highly available by design.
- NAT gateway — lives in a public subnet with a fixed public address (an Elastic IP). Private resources send outbound traffic to it; it rewrites the source address to its own and keeps a translation table, so the replies find their way back. Connections can only be started from inside.
sequenceDiagram
autonumber
actor U as Fan (mobile)
participant IGW as Internet Gateway
participant ALB as Load balancer (public subnet)
participant PAY as payment pod 10.20.40.17 (private subnet)
participant NAT as NAT gateway 15.228.x.x (public subnet)
participant PSP as Payment provider
U->>IGW: HTTPS checkout.boxoffice.com
IGW->>ALB: to the ALB's public address
ALB->>PAY: 10.20.40.17:8080 (via gateway → order)
PAY->>NAT: src 10.20.40.17 → dst PSP:443 (route 0.0.0.0/0 → NAT)
NAT->>PSP: src 15.228.x.x (Elastic IP, allow-listed)
PSP-->>NAT: 200 charge authorized
NAT-->>PAY: translated back to 10.20.40.17
PAY-->>U: order confirmed (through ALB) The provider sees one stable source address — the NAT gateway's Elastic IP — which BoxOffice registers in the provider's allow-list. Pods can be rescheduled to any node; the egress address never changes.
NAT gateways are per AZ
A NAT gateway lives in one AZ. If all private subnets route through the NAT in AZ a and AZ a fails, the pods in AZ b and c lose Internet access — payment cannot reach the provider, and checkout stops. The resilient design is one NAT gateway per AZ, each private subnet routing to the NAT in its own AZ. That costs three times the hourly price and requires registering three Elastic IPs with the provider. It is a classic availability × cost trade-off.
Cost: the surprise line on the bill
NAT gateways charge per hour and per GB processed. Traffic that does not need to go to the Internet — pulling images from the registry, writing to object storage, reading secrets — still goes through NAT if nothing else is configured. The fix is a VPC endpoint:
- Gateway endpoints (S3, DynamoDB on AWS) — a route-table entry; free.
- Interface endpoints (PrivateLink) — a private IP inside your subnet that reaches a cloud service (Secrets Manager, ECR, KMS…) or a partner's service without crossing the Internet; billed per hour and per GB, usually cheaper than NAT for heavy traffic.
BoxOffice — estimating NAT traffic (illustrative numbers)
Assume each Kubernetes node pulls ~2 GB of images per day during deploys, 30 nodes, plus 50 GB/day of logs shipped to object storage. That is ~110 GB/day ≈ 3.3 TB/month through NAT for traffic that never needed the Internet. An S3 gateway endpoint and an ECR interface endpoint remove it from NAT. Always check the current prices of your provider; the structure (per-hour + per-GB) is what matters in the design.
Security groups and NACLs
Routes decide where packets can go; firewalls decide whether they are allowed. AWS has two, at different levels:
| Security group (SG) | Network ACL (NACL) | |
|---|---|---|
| Attached to | a network interface (instance, pod, load balancer, database) | a subnet |
| State | stateful — a reply to an allowed request is allowed automatically | stateless — inbound and outbound must both be allowed, including ephemeral ports |
| Rules | allow only | allow and deny, evaluated in number order |
| Sources | CIDR or another security group | CIDR only |
| Typical use | the main, fine-grained control | coarse guard rails: block a range, separate tiers |
Treat them as layers of the same defense in depth4. The most useful feature is SG references: "allow port 5432 from the security group of the app nodes" instead of "from 10.20.32.0/19". The rule follows the workloads as they scale, move or change IP.
flowchart LR
subgraph sgalb ["sg-alb"]
ALB["ALB :443"]
end
subgraph sgapp ["sg-app"]
N["EKS nodes :8080"]
end
subgraph sgdb ["sg-db"]
DB[("RDS :5432")]
end
I(["Internet 0.0.0.0/0"]) -->|"443"| ALB
ALB -->|"8080 from sg-alb"| N
N -->|"5432 from sg-app"| DB Security groups are not identity
An SG rule says "anything on the app nodes may reach the database". With Kubernetes, every pod on those nodes — including the compromised search pod — matches. Finer control needs per-pod security groups, Kubernetes NetworkPolicy or a service mesh authorization policy, and per-service database credentials (Zero Trust).
The BoxOffice VPC
Putting it together (two of the three AZs drawn):
flowchart TB
NET(["Internet"]) --- IGW["Internet Gateway"]
subgraph vpc ["VPC 10.20.0.0/16 — sa-east-1"]
IGW
subgraph aza ["AZ a"]
subgraph puba ["public 10.20.0.0/24"]
ALBa["ALB node"]
NATa["NAT gw + EIP"]
end
subgraph appa ["private app 10.20.32.0/19"]
Ka["EKS nodes<br/>gateway · order · payment · …"]
end
subgraph dataa ["isolated data 10.20.8.0/22"]
RDSa[("PostgreSQL primary")]
Ra[("Redis")]
end
end
subgraph azb ["AZ b"]
subgraph pubb ["public 10.20.1.0/24"]
ALBb["ALB node"]
NATb["NAT gw + EIP"]
end
subgraph appb ["private app 10.20.64.0/19"]
Kb["EKS nodes"]
end
subgraph datab ["isolated data 10.20.12.0/22"]
RDSb[("PostgreSQL standby")]
Rb[("Redis replica")]
end
end
EP["VPC endpoints<br/>S3 · ECR · Secrets Manager"]
end
IGW --> ALBa & ALBb
ALBa --> Ka
ALBb --> Kb
Ka --> RDSa & Ra
Kb --> RDSa
NATa <-.-|"egress 0.0.0.0/0 → IGW"| Ka
NATb <-.-|"egress 0.0.0.0/0 → IGW"| Kb
Ka & Kb -.-> EP
RDSa -.->|"sync replication"| RDSb As code
Networks are declared with infrastructure as code (IaC) so they can be reviewed, versioned and recreated. A trimmed Terraform/OpenTofu sketch of one AZ (the real module loops over the AZs):
resource "aws_vpc" "boxoffice" {
cidr_block = "10.20.0.0/16"
enable_dns_hostnames = true
tags = { Name = "boxoffice-prod" }
}
resource "aws_subnet" "public_a" {
vpc_id = aws_vpc.boxoffice.id
cidr_block = "10.20.0.0/24"
availability_zone = "sa-east-1a"
}
resource "aws_subnet" "app_a" {
vpc_id = aws_vpc.boxoffice.id
cidr_block = "10.20.32.0/19"
availability_zone = "sa-east-1a"
}
resource "aws_subnet" "data_a" {
vpc_id = aws_vpc.boxoffice.id
cidr_block = "10.20.8.0/22"
availability_zone = "sa-east-1a"
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.boxoffice.id
}
resource "aws_eip" "nat_a" {
domain = "vpc"
}
resource "aws_nat_gateway" "a" {
subnet_id = aws_subnet.public_a.id # NAT lives in the PUBLIC subnet
allocation_id = aws_eip.nat_a.id # fixed egress IP for the PSP allow-list
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.boxoffice.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
}
resource "aws_route_table" "app_a" {
vpc_id = aws_vpc.boxoffice.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.a.id # egress only, same AZ
}
}
# data subnets get a route table with no 0.0.0.0/0 route at all
resource "aws_route_table_association" "public_a" {
subnet_id = aws_subnet.public_a.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "app_a" {
subnet_id = aws_subnet.app_a.id
route_table_id = aws_route_table.app_a.id
}
resource "aws_security_group" "app" {
name = "sg-app"
vpc_id = aws_vpc.boxoffice.id
}
resource "aws_security_group" "db" {
name = "sg-db"
vpc_id = aws_vpc.boxoffice.id
ingress {
description = "PostgreSQL only from the app nodes"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app.id] # reference, not a CIDR
}
}
Read it as a diagram: the only 0.0.0.0/0 → igw route is in the public table; the app table sends the Internet to NAT; the data subnets have no default route; the database accepts 5432 only from members of sg-app.
Try it. Open the payment-system scenario. The payment service calls an external provider. Increase the load and inject chaos on the provider: note that every call crosses the network boundary (in the real deployment, through the NAT gateway). Ask yourself which failures belong to the network (NAT in one AZ, egress IP not allow-listed) and which to the provider. Rebuild the design in the full playground with vpc, subnet and nat components to make the boundaries visible.
Private connectivity: reaching other networks
BoxOffice soon needs to talk to networks it does not own: the office, the data team's VPC, a partner, a second region.
| Option | What it connects | Traffic goes over | Strengths | Limits |
|---|---|---|---|---|
| VPC peering | two VPCs, one-to-one | the provider's backbone | simple, no bandwidth bottleneck, cheap | not transitive; CIDRs must not overlap; becomes a mesh of N² links |
| Transit gateway (hub) | many VPCs and VPNs, hub-and-spoke | the provider's backbone | central routing, scales to hundreds of VPCs | per-attachment and per-GB cost; a central component to operate |
| PrivateLink / interface endpoint | one service exposed into consumer VPCs | the provider's backbone | consumer sees only one IP, no routing between networks, overlapping CIDRs OK | one service at a time, one direction |
| Site-to-site VPN (IPsec) | an on-prem network or another cloud | the public Internet, encrypted | quick to set up, cheap | bandwidth limited per tunnel, Internet latency/jitter |
| Client VPN / WireGuard | individual engineers' laptops | the Internet, encrypted | access for people | a VPN puts the laptop "inside" — classic perimeter trust |
| Dedicated link (Direct Connect / Interconnect / ExpressRoute) | a data center to the cloud | a private physical circuit | stable latency, high bandwidth | weeks to provision, expensive; usually paired with a VPN as backup |
Getting engineers in: bastion vs session manager
The old answer is a bastion host: a small VM in a public subnet with SSH open, from which engineers jump into the private network. It is an attack surface (port 22 on the Internet) and a key-management problem. The modern answer is an agent-based session service (AWS Systems Manager Session Manager, GCP IAP TCP forwarding, Azure Bastion): the engineer authenticates with the company identity provider and MFA, the session is opened by an agent from the inside, no inbound port is open, and every command is logged. For Kubernetes, kubectl through the cluster's authenticated API plays the same role. This is Zero Trust applied to people (Zero Trust).
DNS inside the VPC
Services find each other by name. The VPC provides a resolver (in AWS, at the VPC base address + 2); private hosted zones give internal names like db.boxoffice.internal that resolve only inside the VPC. Inside Kubernetes, CoreDNS resolves payment.boxoffice.svc.cluster.local. Interface endpoints rely on private DNS so that secretsmanager.sa-east-1.amazonaws.com resolves to the private endpoint IP instead of the public one.
The same ideas in other clouds
The vocabulary changes between providers, the building blocks do not3. The biggest real difference: a Google Cloud VPC is global, while AWS and Azure networks are regional.
VPC (regional) · Subnet (per AZ) · Route table · Internet Gateway · NAT Gateway · Security Group + Network ACL · Gateway/Interface Endpoint (PrivateLink) · VPC Peering · Transit Gateway · Site-to-Site VPN · Direct Connect · Systems Manager Session Manager.
VPC network (global; subnets are regional, not per zone) · Routes · Cloud NAT (a managed service, no per-zone instance to place) · VPC firewall rules and hierarchical firewall policies (stateful, can target service accounts or network tags) · Private Service Connect · VPC Network Peering · Network Connectivity Center · Cloud VPN · Cloud Interconnect · Identity-Aware Proxy (IAP).
Virtual Network (VNet, regional) · Subnet · Route table (User-Defined Routes) · NAT Gateway · Network Security Group (NSG) + Application Security Groups · Private Endpoint / Private Link · VNet Peering · Virtual WAN / hub-spoke · VPN Gateway · ExpressRoute · Azure Bastion.
A cluster network (CNI: Calico, Cilium) with pod and service CIDRs · NetworkPolicy as the firewall · an egress gateway (Cilium, Istio) for a stable source IP · WireGuard or IPsec tunnels between sites · MetalLB for load balancer IPs on bare metal.
Trade-offs at a glance
| Decision | Option A | Option B | Choose B when… |
|---|---|---|---|
| NAT topology | one NAT for the VPC (cheap) | one NAT per AZ (resilient) | outbound calls are on the critical path (payment!) |
| Egress to cloud services | through NAT | VPC endpoints | traffic is heavy or must not cross the Internet |
| Pod addressing | overlay network (few VPC IPs) | VPC-native pod IPs | you need SGs/flow logs per pod and have address space |
| Connecting VPCs | peering | transit gateway | more than a handful of VPCs, or VPNs too |
| Admin access | bastion + SSH | session manager / IAP | almost always |
| Firewall granularity | SG per tier | per-pod SG / NetworkPolicy / mesh policy | multi-tenant clusters, sensitive services |
In the interview
When you draw the high-level design, put a box around it labeled VPC and split it into public (load balancer, NAT) and private (services, data) subnets across at least two AZs. Say one sentence about egress: "outbound calls to the payment provider go through a NAT gateway per AZ with a fixed IP the provider allow-lists." If the interviewer asks about cost, mention VPC endpoints to keep object-storage and registry traffic off the NAT. That is enough to signal you have run real systems.
Best practices
- Spread every tier over at least two, preferably three, availability zones, with one subnet per tier per zone — losing a zone then removes a third of the capacity, not the service.
- Reach cloud services through VPC endpoints — gateway endpoints for object storage, interface endpoints for the registry, logs and the secrets manager: the traffic stays private and skips the NAT data-processing charge.
- Write security-group rules that reference other groups, not CIDRs —
db-sgallows 5432 only fromapp-sg, so the rule follows the instances as they scale in and out. - Give data subnets no route to the Internet at all — no
0.0.0.0/0entry in their route table, so a compromised database host cannot exfiltrate directly. - Turn on VPC flow logs (at least rejected traffic) and keep them about 90 days — the only way to answer "what did this host talk to?" after an incident.
- Change the network only through code review — Terraform (or similar) with the plan attached to the pull request; no console changes in production.
Common pitfalls
- SSH or RDP open to
0.0.0.0/0— scanned within minutes; use a session manager instead. - Database in a public subnet "just to connect from my laptop" — one weak password away from a breach.
- A single NAT gateway for every AZ — an AZ outage silently becomes a checkout outage.
- Overlapping CIDRs (
10.0.0.0/16everywhere) — discovered the day you need to peer with the data team or a partner. - Subnets too small for VPC-native pod IPs — the cluster stops scheduling pods during the flash sale.
- Ignoring NAT data-processing charges — registry pulls and log shipping through NAT can cost more than the servers.
How it connects
flowchart LR
VPC["VPC + CIDR plan"] --> SUB["public / private / isolated<br/>subnets, one per AZ"]
SUB --> RT["route tables:<br/>IGW, NAT or none"]
RT --> NAT["NAT gateway per AZ<br/>stable egress IP"]
NAT --> PSP["provider allow-list<br/>→ Compute integrations"]
SUB --> SG["security groups + NACLs"]
SG --> NOID["not an identity: NetworkPolicy,<br/>mesh policy → Service mesh"]
SUB --> AZ["AZ and region outages<br/>→ Reliability"]
VPC --> CON["peering, transit, PrivateLink,<br/>VPN, session manager"]
CON --> ZT["access for people<br/>→ Zero Trust"]
VPC --> IAC["Terraform / OpenTofu<br/>→ Cloud-native"] Palette components: vpc · subnet · nat · vpn · lb (the ALB in the public subnets) · payment (reached through the NAT). Scenarios: payment-system, multi-region, scale-out. Continues in: Service mesh, Zero Trust, Compute — integrations, Reliability — disaster recovery, Cloud-native — delivery (infrastructure as code).
Key takeaways
- A VPC is your private network in a region; subnets live in one AZ; a subnet is public only because its route table points
0.0.0.0/0to an Internet Gateway. - Private subnets reach the Internet outbound only through a NAT gateway, which also gives a stable egress IP for allow-lists; use one per AZ for critical paths.
- Security groups are stateful and can reference each other; NACLs are stateless subnet-level guard rails. Neither is a workload identity.
- Plan CIDRs with room and without overlaps; AWS reserves 5 addresses per subnet; pods may need many IPs.
- Use VPC endpoints for cloud services, peering/transit/PrivateLink between networks, VPN or dedicated links to on-prem, and session managers instead of bastions.
-
AMAZON WEB SERVICES. Amazon VPC User Guide — subnets, route tables, NAT gateways, security groups, network ACLs. ↩↩
-
REKHTER, Y. et al. RFC 1918 — Address Allocation for Private Internets. IETF, 1996. ↩
-
GOOGLE CLOUD. VPC network overview; MICROSOFT. Azure Virtual Network overview. ↩
-
AMAZON WEB SERVICES. AWS Well-Architected Framework — Security Pillar: Infrastructure protection. ↩