Skip to content

Address plan

Every internal IP range we allocate, what owns it, and where it is declared.

Ranges must not overlap. VPC peering rejects an overlap outright. The tailnet silently routes the wrong way when two environments claim the same space.

Take a new range from here and record it here in the same change (INFRA-6).

Written after the fact

These were chosen ad hoc, one environment at a time. This page documents what exists, not a scheme designed up front. The patterns below are real but unplanned, so check "next free" before using it.

Main subnets

One per environment, europe-west3. Declared in infra/terraform/envs/common.env as TF_VAR_gcp_main_subnet_ip_cidr_range_<env>, and again in each <env>.env unsuffixed.

Environment Range Notes
production 10.0.0.0/20
staging 10.0.16.0/20 Reserved, not allocated. The environment is not built
unassigned 10.0.32.0/20 Free
devel 10.0.48.0/20

/20 on 16-block boundaries. Next free: 10.0.32.0/20, then 10.0.64.0/20.

Private service access

Where Google's managed services (Cloud SQL, Memorystore) are peered in. Declared as TF_VAR_gcp_private_service_access_ip_cidr_range_<env>.

Environment Range
devel 10.220.124.0/22
unassigned 10.221.124.0/22 (free)
staging 10.222.124.0/22 (reserved)
production 10.223.124.0/22

Second octet increments from 220. Next free: 10.221.124.0/22, then 10.224.124.0/22.

Private Service Connect endpoints

One address per environment, not a range. Declared in infra/inventory/<env>/config.yml as LYC_GCP_PRIVATE_SERVICE_CONNECT_IP_ADDRESS, mirrored per environment in infra/inventory/common/config.yml.

Environment Address
production 10.3.0.5
unassigned 10.3.0.6 (free)
devel 10.3.0.7
staging 10.3.0.8 (reserved)

All in 10.3.0.0/24, reserved for this.

Inbound DNS resolver

The GCP inbound forwarding address the tailnet resolves internal names through. It comes out of the environment's own main subnet, so it needs no separate reservation. Declared as LYC_GCP_INBOUND_DNS_RESOLVE_ADDR, and for staging as TF_VAR_gcp_inbound_dns_resolve_addr_staging in common.env.

Environment Address
production 10.0.0.23
devel 10.0.48.92
staging 10.0.16.2

k3s load-balancer VIP pool

The pool the k3s server hands to LoadBalancer services. Declared in infra/inventory/<env>/config.yml as LYC_K3S_SERVER_LB_IPPOOL_CIDR.

Environment Range
production 10.41.0.0/24
unassigned 10.43.0.0/24 (free)
devel 10.45.0.0/24

k3s cluster and service CIDRs

The pod and service ranges the k3s server uses. Declared in infra/inventory/<env>/config.yml as LYC_K3S_SERVER_CLUSTER_CIDR and LYC_K3S_SERVER_SERVICE_CIDR.

Environment Cluster Service
production 10.42.0.0/16 10.43.0.0/16
unassigned 10.44.0.0/16 (free) 10.45.0.0/16 (free)
devel 10.46.0.0/16 10.47.0.0/16

These interleave with the VIP pools above, so pools sit inside a /16 recorded here. The free 10.43.0.0/24 is inside production's service CIDR, and devel's 10.45.0.0/24 is inside the free 10.45.0.0/16. Both are cluster-internal and unrouted, so nothing conflicts today, but an environment that takes the free pair would put its service CIDR around devel's VIP pool.

Next free pair: 10.48.0.0/16 and 10.49.0.0/16.

Cluster-internal, never routed onto the tailnet.

Tailscale

The tailnet uses Tailscale's CGNAT range, 100.64.0.0/10. A route for it is created in infra/terraform/_modules/gcp/tailscale/routing.tf. We allocate nothing inside it.

Subnet routers advertise the environment's main subnet, its private-service-access range and its PSC address as a /32. See infra/ansible/playbooks/tailscale-subnet-router.yml.

Every environment's routes go into one tailnet. Two environments with overlapping ranges therefore break each other's routing, not just their own. That is why the no-overlap rule is not negotiable.

Adding an environment

  1. Take the next free main subnet, private-service-access range and PSC address from above.
  2. Add them as TF_VAR_gcp_*_<env> entries in common.env and unsuffixed in <env>.env. Consumers reading the _${lyceum_env} form get an empty value until the common.env entries exist, which surfaces as unrelated DNS and PSC errors.
  3. Record them here, in the same pull request.