This tutorial is intended for network architects who are finding it difficult to allocate RFC 1918 IPv4 addresses for GKE Pods, Nodes, and Services due to private address space exhaustion or fragmentation. The tutorial walks you through a solution that allows for a full-scale GKE installation by leveraging NAT and the strategic reuse of RFC 1918 CIDR blocks already assigned within your organization. You then use Terraform to automate the infrastructure build, and you use the Google Cloud CLI to inspect the individual components shown in the following diagram.
At a high level, you set up a host project, a service project, a shared VPC, and a Compute Engine VM called an isolated-VPC gateway. You also set up the relevant static routes, firewall rules, and APIs. For a more in-depth discussion of the overall solution and its individual components, see NAT for all GKE CIDR blocks.
This tutorial assumes you are familiar with:
- Linux sysadmin commands
- GKE
- Compute Engine
- Shared VPCs
- NAT
- Terraform
Objectives
- Configure a host project with a shared VPC called the routed-domain VPC.
- Configure a service project with a VPC called the isolated VPC.
- Configure the isolated-VPC gateway VM.
- Configure routing across VPCs through the isolated-VPC gateway.
- Configure NAT on the isolated-VPC gateway.
Costs
This tutorial uses the following billable components of Google Cloud:
To generate a cost estimate based on your projected usage,
use the pricing calculator.
When you finish this tutorial, you can avoid continued billing by deleting the resources you created. For more information, see Clean up.
Before you begin
In this section, you prepare Cloud Shell, set up your environment variables, and deploy the supporting infrastructure.
Prepare Cloud Shell
In the Google Cloud console, open Cloud Shell.
You complete most of this tutorial from the Cloud Shell terminal using HashiCorp's Terraform and the Google Cloud CLI.
In Cloud Shell, clone the GitHub repository and change to the local directory:
git clone https://github.com/GoogleCloudPlatform/terraform-gke-nat-connectivity.git kam cd kam/allnat
The repository contains all the files you need to complete this tutorial. For a complete description of each file, see the
README.md
file in the repository.Make all shell scripts executable:
sudo chmod 755 *.sh
Initialize Terraform:
terraform init
The output is similar to the following:
... Initializing provider plugins... The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking changes, it is recommended to add version = "..." constraints to the corresponding provider blocks in configuration, with the constraint strings suggested below. ∗ provider.google: version = "~> 2.5" Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary. ...
Set environment variables
In Cloud Shell, set and verify the
TF_VAR_org_id
variable, replacingyour-organization-name
with the Google Cloud organization name you want to use in this tutorial:export TF_VAR_org_id=$(gcloud organizations list | \ awk '/your-organization-name/ {print $2}')
Verify that the environment variable is set correctly:
echo $TF_VAR_org_id
The output lists your numeric organization ID and looks similar to the following:
... 123123123123 ...
Set the remaining environment variables by running the following shell command from the repo:
source set_variables.sh
Verify that the environment variables are set correctly:
env | grep TF_
The output is similar to the following:
... TF_VAR_isolated_net_name=isolated-vpc TF_VAR_isolated_pname=isolated-vpc-project TF_VAR_isolated_pid=isolated-vpc-project-id TF_VAR_shared_net_name=routed-domain-vpc TF_VAR_shared_pname=routed-domain-project TF_VAR_shared_pid=routed-domain-project-id TF_VAR_isolated_vpc_gw_host_nic_ip=10.97.0.2 TF_VAR_isolated_vpc_gw_service_nic_ip=10.32.0.2 TF_VAR_isolated_vpc_gw_service_dgw_ip=10.32.0.1 TF_VAR_org_id=123123123123 TF_VAR_region=us-west1 TF_VAR_zone=us-west1-b TF_VAR_user_account=user@example.com TF_VAR_node_cidr=10.32.0.0/19 TF_VAR_pod_cidr=10.0.0.0/11 TF_VAR_service_cidr=10.224.0.0/20 TF_VAR_shared_cidr=10.97.0.0/24 TF_VAR_test1_cidr=10.0.0.0/11 TF_VAR_test2_cidr=10.160.0.0/11 TF_VAR_test3_cidr=10.64.0.0/19 TF_VAR_ilb_cidr=10.32.31.0/24 TF_VAR_masquerade=true ...
Update the
TF_VAR_masquerade
variable if it is not set.This document defaults to setting up the iptables masquerade option, as described in the associated concept document. If you want to set up the iptables SNAT option, you must change the
TF_VAR_masquerade
variable tofalse
:export TF_VAR_masquerade=false
Create an environment variable file:
env | grep TF_ | sed 's/^/export /' > TF_ENV_VARS
This command chain redirects the environment variables that you created into a file called
TF_ENV_VARS
. Each variable is prepended with theexport
command. You can use this file to reset the environment variables in case your Cloud Shell session is terminated. These variables are used by the Terraform scripts, shell scripts, and the SDK commands in this tutorial.If you need to reinitialize the variables, you can run the following command from the directory where the file resides:
source TF_ENV_VARS
Deploy the supporting infrastructure
In Cloud Shell, deploy the Terraform supporting infrastructure:
terraform apply
Terraform prompts for confirmation before making any changes. Answer
yes
to apply either configuration.The
terraform apply
command instructs Terraform to deploy all the solution's components. To better understand how the infrastructure is declaratively defined, you can read through the Terraform manifests—that is, the files with the.tf
extension.
Inspecting the supporting infrastructure
You now use the Google Cloud CLI to view and verify the infrastructure that Terraform created. Verification involves running a command to see if the resource responds and was created correctly.
Verify the projects
In Cloud Shell, list the two projects:
gcloud projects list | grep pid
The output is similar to the following:
... isolated-vpc-pid isolated-vpc-pname 777999333962 routed-domain-pid routed-domain-pname 777852333528 ...
List the API status:
For the isolated-VPC project:
gcloud services list --project=$TF_VAR_isolated_vpc_pid | grep -E "compute|container"
The output is similar to the following:
... compute.googleapis.com Compute Engine API container.googleapis.com Google Kubernetes Engine API ...
For the routed-domain project:
gcloud services list --project=$TF_VAR_shared_vpc_pid | grep -E "compute"
The output is similar to the following:
... compute.googleapis.com Compute Engine API ...
List the shared VPC status:
gcloud compute shared-vpc organizations list-host-projects $TF_VAR_org_id gcloud compute shared-vpc get-host-project $TF_VAR_isolated_vpc_pid gcloud compute shared-vpc list-associated-resources $TF_VAR_shared_vpc_pid
The output is similar to the following:
... NAME CREATION_TIMESTAMP XPN_PROJECT_STATUS svpc-pid--210756488 ... kind: compute#project name: svpc-pid--210756488 ... RESOURCE_ID RESOURCE_TYPE ivpc-pid--695116665 PROJECT ...
Verify the networks and subnetworks
In Cloud Shell, verify the networks and subnetworks for the isolated VPC:
gcloud compute networks describe $TF_VAR_isolated_net_name \ --project=$TF_VAR_isolated_vpc_pid gcloud compute networks subnets describe node-cidr \ --project=$TF_VAR_isolated_vpc_pid \ --region=$TF_VAR_region
The output is similar to the following:
... kind: compute#network name: isolated-vpc-net routingConfig: routingMode: GLOBAL ... subnetworks: ‐ https://www.googleapis.com/compute/v1/projects/ivpc-pid--695116665/regions/us-west1/subnetworks/node-cidr x_gcloud_bgp_routing_mode: GLOBAL ... gatewayAddress: 10.32.0.1 ipCidrRange: 10.32.0.0/19 kind: compute#subnetwork name: node-cidr ... secondaryIpRanges: ‐ ipCidrRange: 10.0.0.0/11 rangeName: pod-cidr ...
Verify the networks and subnetworks for the routed-domain VPC:
gcloud compute networks describe $TF_VAR_shared_net_name \ --project=$TF_VAR_shared_vpc_pid gcloud compute networks subnets describe shared-cidr \ --project=$TF_VAR_shared_vpc_pid \ --region=$TF_VAR_region gcloud compute networks subnets list $TF_VAR_shared_net_name \ --project=$TF_VAR_shared_vpc_pid
The output is similar to the following:
... kind: compute#network name: routed-domain-vpc-net routingConfig: routingMode: GLOBAL ... subnetworks: ‐ https://www.googleapis.com/compute/v1/projects/svpc-pid--210756488/regions/us-west1/subnetworks/shared-cidr x_gcloud_bgp_routing_mode: GLOBAL ... gatewayAddress: 10.97.0.1 ... ipCidrRange: 10.97.0.0/24 kind: compute#subnetwork name: shared-cidr ... region: https://www.googleapis.com/compute/v1/projects/svpc-pid--210756488/regions/us-west1 ... NAME REGION NETWORK RANGE ... test-10-11 us-west1 routed-domain-vpc-net 10.0.0.0/11 test-10-160-11 us-west1 routed-domain-vpc-net 10.160.0.0/11 test-10-64-19 us-west1 routed-domain-vpc-net 10.64.0.0/19 ...
Verify firewall rules
In Cloud Shell, verify the firewall rules in both VPCs:
gcloud compute firewall-rules list --project=$TF_VAR_isolated_vpc_pid gcloud compute firewall-rules list --project=$TF_VAR_shared_vpc_pid
The output is similar to the following:
... NAME NETWORK DIRECTION PRIORITY ALLOW DENY DISABLED allow-rfc1918-in-fwr isolated-vpc-net INGRESS 1000 all False allow-ssh-in-fwr isolated-vpc-net INGRESS 1000 22 False ... NAME NETWORK DIRECTION PRIORITY ALLOW DENY DISABLED allow-rfc1918-in-fwr routed-domain-vpc-net INGRESS 1000 all False allow-ssh-in-fwr routed-domain-vpc-net INGRESS 1000 22 False ...
Verify test machines
In Cloud Shell, verify the test machines:
gcloud compute instances list --project=$TF_VAR_shared_vpc_pid
The output is similar to the following:
... NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS test-10-11-vm us-west1-b n1-standard-1 10.0.0.2 192.0.2.1 RUNNING test-10-160-11-vm us-west1-b n1-standard-1 10.160.0.2 192.0.2.2 RUNNING test-10-64-19-vm us-west1-b n1-standard-1 10.64.0.2 192.0.2.3 RUNNING ...
Verify the isolated-VPC gateway
In Cloud Shell, verify the instance:
gcloud compute instances describe isolated-vpc-gw \ --project=$TF_VAR_isolated_vpc_pid \ --zone $TF_VAR_zone
The output is similar to the following:
... metadata: fingerprint: e_P00DAAAAA= items: ‐ key: startup-script value: |+ #! /bin/bash #Enable packet forwarding sudo sysctl -w net.ipv4.ip_forward=1 ... name: isolated-vpc-gw networkInterfaces: ‐ accessConfigs: ‐ kind: compute#accessConfig name: external-nat natIP: 192.0.2.76 ... networkIP: 10.97.0.12 ... networkIP: 10.32.0.15 ... tags: fingerprint: FOrvKltVVVV= items: ‐ allow-rfc1918-in-fwr ‐ allow-ssh-in-fwr ‐ isolated-vpc-allocated-ilb-route ...
Connect to the isolated-VPC gateway by using SSH:
gcloud compute ssh isolated-vpc-gw \ --project=$TF_VAR_isolated_vpc_pid \ --zone $TF_VAR_zone
In the instance, check for the existence of the following:
- The routed-domain route table
- The policy rules that direct traffic bound for the routed domain to the routed-domain route table
- The static routes in the routed-domain route table that direct traffic to the routed domain
sudo sysctl net.ipv4.ip_forward cat /etc/iproute2/rt_tables | grep routed-domain ip rule show ip route show table routed-domain
The output is similar to the following:
.... net.ipv4.ip_forward = 1 .... routed-domain 100 routed-domain .... 0: from all lookup local 32763: from all to 192.168.0.0/16 lookup routed-domain 32764: from all to 172.16.0.0/12 lookup routed-domain 32765: from all to 10.0.0.0/8 lookup routed-domain 32766: from all lookup main 32767: from all lookup default .... 10.0.0.0/8 via 10.97.0.1 dev eth0 10.32.31.0/24 via 10.32.0.1 dev eth1 172.16.0.0/12 via 10.97.0.1 dev eth0 192.168.0.0/16 via 10.97.0.1 dev eth0 ....
Verify the
iptables
NAT configuration:sudo iptables -t nat -L -v
For the iptables masquerade configuration, the output is similar to the following:
... Chain PREROUTING (policy ACCEPT 1 packets, 60 bytes) pkts bytes target prot opt in out source destination Chain INPUT (policy ACCEPT 1 packets, 60 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 24 packets, 1525 bytes) pkts bytes target prot opt in out source destination Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 24 1525 MASQUERADE all -- any eth0 anywhere anywhere 0 0 MASQUERADE all -- any eth1 anywhere anywhere ...
For the iptables SNAT option, the output is similar to the following:
... Chain PREROUTING (policy ACCEPT 1 packets, 60 bytes) pkts bytes target prot opt in out source destination Chain INPUT (policy ACCEPT 1 packets, 60 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 21 packets, 1345 bytes) pkts bytes target prot opt in out source destination Chain POSTROUTING (policy ACCEPT 21 packets, 1345 bytes) pkts bytes target prot opt in out source destination 0 0 SNAT tcp -- any eth1 10.160.0.0/11 anywhere to:10.160.0.0-10.191.255.255:1024-33279 0 0 SNAT tcp -- any eth1 10.0.0.0/11 anywhere to:10.160.0.0-10.191.255.255:33280-65535 0 0 SNAT tcp -- any eth1 10.64.0.0/19 anywhere to:10.64.0.0-10.64.31.255:1024-33279 0 0 SNAT tcp -- any eth1 10.32.0.0/19 anywhere to:10.64.0.0-10.64.31.255:33280-65535 0 0 SNAT udp -- any eth1 10.160.0.0/11 anywhere to:10.160.0.0-10.191.255.255:1024-33279 0 0 SNAT udp -- any eth1 10.0.0.0/11 anywhere to:10.160.0.0-10.191.255.255:33280-65535 0 0 SNAT udp -- any eth1 10.64.0.0/19 anywhere to:10.64.0.0-10.64.31.255:1024-33279 0 0 SNAT udp -- any eth1 10.32.0.0/19 anywhere to:10.64.0.0-10.64.31.255:33280-65535 0 0 SNAT icmp -- any eth1 10.160.0.0/11 anywhere to:10.160.0.0-10.191.255.255 0 0 SNAT icmp -- any eth1 10.0.0.0/11 anywhere to:10.160.0.0-10.191.255.255 0 0 SNAT icmp -- any eth1 10.64.0.0/19 anywhere to:10.64.0.0-10.64.31.255 0 0 SNAT icmp -- any eth1 10.32.0.0/19 anywhere to:10.64.0.0-10.64.31.255 ...
Verify the GKE cluster
In Cloud Shell, verify the GKE cluster:
gcloud container clusters list \ --project=$TF_VAR_isolated_vpc_pid \ --zone=$TF_VAR_zone
The output is similar to the following:
... NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS cluster1 us-west1-b 1.11.8-gke.6 192.0.2.58 n1-standard-1 1.11.8-gke.6 3 RUNNING ...
Verify inter-VPC routing
In Cloud Shell, verify inter-VPC routing for the shared VPC:
gcloud compute routes list \ --filter=name="('ivpc-allocated-ilb-route')" \ --project=$TF_VAR_shared_vpc_pid
The output is similar to the following:
NAME NETWORK DEST_RANGE NEXT_HOP PRIORITY ivpc-allocated-ilb-route routed-domain-vpc-net 10.32.31.0/24 10.97.0.2 1000
Verify inter-VPC routing for the isolated VPC:
gcloud compute routes list --project=$TF_VAR_isolated_vpc_pid
The output is similar to the following:
... NAME NETWORK DEST_RANGE NEXT_HOP PRIORITY ivpc-to-10net isolated-vpc-net 10.0.0.0/8 10.32.0.2 1000 ivpc-to-172-16net isolated-vpc-net 172.16.0.0/12 10.32.0.2 1000 ivpc-to-192-168net isolated-vpc-net 192.168.0.0/16 10.32.0.2 1000 ...
Verifying the hello-world app
In this section, you get cluster credentials and verify the hello-server
service.
In Cloud Shell, get the cluster credentials:
gcloud container clusters get-credentials cluster1 \ --project=$TF_VAR_isolated_vpc_pid \ --zone $TF_VAR_zone
The output is similar to the following:
... Fetching cluster endpoint and auth data. kubeconfig entry generated for cluster1. ...
Verify the
hello-server
service:kubectl get service hello-server
The output is similar to the following:
... NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello-server LoadBalancer 10.224.7.87 <pending> 8080:30635/TCP 3m ...
Verifying the solution
In this section, you set up monitoring on the isolated-VPC gateway and test the app.
Set up monitoring on the isolated-VPC gateway
In Cloud Shell, use SSH to connect to the isolated-VPC gateway:
gcloud compute ssh isolated-vpc-gw \ --project=$TF_VAR_isolated_vpc_pid \ --zone=$TF_VAR_zone
In the instance, run the
tcpdump
utility:sudo tcpdump -n -i eth1 host 10.32.31.1
The output is similar to the following:
... tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes ...
This command starts
tcpdump
to capture the packets that transverse the isolated-VPC gateway. The utility only captures packets that contain the IP address of the internal load balancer that the GKE service created.
Test the app
Open a new instance of Cloud Shell.
Use
ssh
to connect to thetest-10-11-vm
Compute Engine instance. Then, in the new instance, change to the Git repository directory:source TF_ENV_VARS gcloud compute ssh test-10-11-vm \ --project=$TF_VAR_shared_vpc_pid \ --zone=$TF_VAR_zone
In the new instance, use
curl
to connect to the app:curl http://10.32.31.1:8080/
The preceding command retrieves the webpage from the app running on the GKE cluster. The output is similar to the following:
... Hello, world! Version: 1.0.0 Hostname: my-app-6597cdc789-d6phf ...
In the original Cloud Shell terminal, when you verify the isolated-VPC gateway, the output is similar to the following:
... 17:45:32.674129 IP 10.32.0.2.56698 > 10.32.31.1.8080: Flags [S], seq 2205959832, win 28400, options [mss 1420,sackOK,TS val 296564 ecr 0,nop,wscale 7], length 0 17:45:32.678253 IP 10.32.31.1.8080 > 10.32.0.2.56698: Flags [S.], seq 4243326926, ack 2205959833, win 28160, options [mss 1420,sackOK,TS val 632036963 ecr 296564,nop,wscale 7], length 0 17:45:32.678982 IP 10.32.0.2.56698 > 10.32.31.1.8080: Flags [.], ack 1, win 222, options [nop,nop,TS val 296565 ecr 632036963], length 0 17:45:32.678995 IP 10.32.0.2.56698 > 10.32.31.1.8080: Flags [P.], seq 1:80, ack 1, win 222, options [nop,nop,TS val 296565 ecr 632036963], length 79: HTTP: GET / HTTP/1.1 17:45:32.679411 IP 10.32.31.1.8080 > 10.32.0.2.56698: Flags [.], ack 80, win 220, options [nop,nop,TS val 632036965 ecr 296565], length 0 17:45:32.679904 IP 10.32.31.1.8080 > 10.32.0.2.56698: Flags [P.], seq 1:181, ack 80, win 220, options [nop,nop,TS val 632036965 ecr 296565], length 180: HTTP: HTTP/1.1 200 OK 17:45:32.680032 IP 10.32.0.2.56698 > 10.32.31.1.8080: Flags [.], ack 181, win 231, options [nop,nop,TS val 296565 ecr 632036965], length 0 ...
This output is a packet capture of the
curl
command that you just ran.In the first Cloud Shell instance, press
Control+C
to stop thetcpdump
listing.In that same instance, inspect the connection table on the isolated-VPC gateway:
sudo conntrack -L | grep 10.32.31.49
For the iptables masquerade option, the output is similar to the following:
... tcp 6 118 TIME_WAIT src=10.0.0.2 dst=10.32.31.1 sport=56760 dport=8080 src=10.32.31.1 dst=10.32.0.2 sport=8080 dport=56760 [ASSURED] mark=0 use=1 ...
This command displays source NAT entries created in the connection table from packet capture in the previous step;
src=10.0.0.2
has been translated todst=10.32.0.2
. Output for the iptables SNAT option will be similar.Rerun this entire testing section, substituting
test-10-11-vm
fortest-10-160-11-vm
andtest-10-64-19-vm
.
Clean up
Destroy the infrastructure
From the first Cloud Shell terminal, close the SSH session to the isolated-VPC gateway:
exit
Destroy the Terraform configuration:
terraform destroy
Terraform prompts for confirmation before making the change. Answer
yes
to destroy the configuration and all of the tutorial's components.You might see the following Terraform error:
... ∗ google_compute_shared_vpc_host_project.host (destroy): 1 error(s) occurred: ∗ google_compute_shared_vpc_host_project.host: Error disabling Shared VPC Host "svpc-pid--23156887": googleapi: Error 400: Cannot disable project as a shared VPC host because it has active service projects., badRequest ...
This error occurs when you attempt to destroy the shared VPC before you destroy the isolated-VPC gateway.
Continue the clean up:
gcloud compute shared-vpc associated-projects remove $TF_VAR_isolated_vpc_pid \ --host-project=$TF_VAR_shared_vpc_pid gcloud compute shared-vpc disable $TF_VAR_shared_vpc_pid
You might see the following Terraform error:
... ∗ google_compute_network.ivpc (destroy): 1 error(s) occurred: ∗ google_compute_network.ivpc: Error waiting for Deleting Network: The network resource 'projects/ivpc-pid--1058675427/global/networks/isolated-vpc-net' is already being used by 'projects/ivpc-pid--1058675427/global/firewalls/k8s-05693142c93de80e-node-hc' ...
This error occurs when you try to destroy the isolated-VPC network before the GKE firewall rules.
Complete the clean up by removing the non-default firewall rules from the isolated VPC:
./k8-fwr.sh
The output shows you which firewall rules will be removed.
Review the rules and, when prompted, type
yes
.From the first Cloud Shell terminal, reissue the following command:
terraform destroy
Terraform prompts for confirmation before making the change. Answer
yes
to destroy the configuration.Remove the files created during this tutorial:
rm -rf .git rm -rf .terr* rm* cd .. rm -rf kam
What's next
- Read the associated conceptual document.
- Explore reference architectures, diagrams, and best practices about Google Cloud. Take a look at our Cloud Architecture Center.