https://github.com/howdoicomputer/tf-polarstomps-gcp-environment
A Terraform module to deploy an environment for Polarstomps.
https://github.com/howdoicomputer/tf-polarstomps-gcp-environment
Last synced: 5 months ago
JSON representation
A Terraform module to deploy an environment for Polarstomps.
- Host: GitHub
- URL: https://github.com/howdoicomputer/tf-polarstomps-gcp-environment
- Owner: howdoicomputer
- Created: 2024-10-28T03:38:31.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-10-31T16:16:15.000Z (over 1 year ago)
- Last Synced: 2025-09-08T02:22:34.448Z (11 months ago)
- Language: HCL
- Size: 125 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Polarstomps GCP Environment Terraform Module
## About
This Terraform module is used to provision an opinionated GCP environment for the Polarstomps web application. It deploys all resources in **us-west1** by default.
It will deploy:
* A VPC with a public and private subnet
* A VPC route that allows egress to the Internet
* Firewall rules to enable ingress from a "home" IP address
* A [Cloud Router](https://cloud.google.com/network-connectivity/docs/router/concepts/overview) and a [Cloud Nat](https://cloud.google.com/nat/docs/overview) in order to enable routing to a private GKE cluster
* A GKE/Kubernetes autopilot cluster that has private nodes and a public control plane endpoint (that is locked down to that same "home" IP address)
The philosphy of this module is that it enforces a set of hypothetical standards for an organization - i.e, the Polarstomps organization. It assumes that environments would be relatively homogenous and so works to enforce a set of defaults in order to minimize the need for creating wrapper modules to set inputs.
However, it also gives you an escape hatch for customizing the underlying environment. For the average instantiation, the only real inputs are an environment string (dev/stage/prod) and subnet cidrs.
### One Cluster One Environment
This module defines a logical environment as "one GKE cluster in one VPC." This is fairly simplistic but so are the needs of the Polarstomps web application. If you need multiple clusters per environment (say you want a cluster solely dedicated to processing HIPAA or PII data) then this module isn't for you. It would need to be refactored so that the GKE cluster is its own module. For now, centralizing the GKE cluster and the VPC into a single module helps with code maintainability.
### Autopilot vs Standard GKE
Autopilot is pretty neat. It is the recommended way of deploying Kubernetes in GCP now and, honestly, simplifies administration quite a bit. This means there are no node pools managed by this module.
Autopilot knows which worker nodes (and GPUs) are needed by your application by looking at the `nodeSelector` for a manifest and then provisioning the right type of node for your workload.
If you need a standard GKE cluster then this module is not for you.
Some use cases for a standard GKE cluster:
* Your one true passion in life is to manage node pools for a Kubernetes cluster
### Network Layout
By default, this module creates the below diagram:

## Post Cluster Creation
You can populate your `~/.kube/config` with auth details for the provisioned cluster by running: `gcloud container clusters get-credentials $(terragrunt output -raw gke_cluster_name) --zone us-west1`. If you customize the region then you'll need to change the shell command.
After you setup your kubeconfig, you can then bootstrap ArgoCD like so:
``` sh
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
```
And then to login to it you:
``` sh
kubectl config set-context --current --namespace argocd
argocd login --core
argocd admin initial-password -n argocd
```
Then port forward into the ArgoCD UI with `kubectl port-forward svc/argocd-server -n argocd 8080:443` and then use the password generated from the above shell to get to the good stuff.
### Customization and Usage
An example of using this module is in the [polarstomps-infra-gcp]() repository as its used to deploy the dev and prod environments.
The most basic usage looks like this:
``` terraform
module "prod" {
source = "github.com/howdoicomputer/tf-polarstomps-gcp-environment?ref=v1"
env = "prod"
project_id = "REPLACE_ME"
public_subnet_cidr = "10.10.40.0/26"
private_subnet_cidr = "10.10.50.0/26"
control_plane_cidr = "10.10.60.0/28"
# The IP address that is allowed to contact the GKE control plane.
#
# It's also allowed to SSH into the public subnet by default.
#
my_ip_address = "1.1.1.1/32"
}
```
Note, this module makes copious use of the `coalesce` and `coalescelist` functions in order to make the tradeoff of DRY module instantiation with slightly obscured defaults.
Generally, this means that any list of objects has a default defined in the `locals` block of `main.tf` rather than the defaults of `variables.tf`.
#### Customizing Firewall Rules Example
``` terraform
module "environment" {
env = "prod"
public_subnet_cidr = "10.10.40.0/26"
private_subnet_cidr = "10.10.50.0/26"
control_plane_cidr = "10.10.60.0/28"
# Disable ingress on 22 for a home address
#
# If this is set to true and my_ip_address
# is defined then a 22 ingress rule would be
# created for the public_subnet_cidr.
#
vpc_enable_my_ip_ingress_rule = false
# We still need to set an IP address for the public control plane even if
# we're not using that same address to SSH into the public subnet.
#
my_ip_address = "1.1.1.1/32"
# Define your own rules. You rebel you.
#
vpc_firewall_rules = [
{
name = "foobar"
description = "foobar"
direction = "INGRESS"
priority = 0
destination_ranges = ["0.0.0.0/26"]
source_ranges = ["1.1.1.1/32"]
allow = [{
protocol = "tcp"
ports = ["22"]
}]
}
]
}
```
#### Customizing VPC Subnets Example
``` terraform
module "environment" {
env = "prod"
# The node subnet name and the private subnet name need to match
#
# Or, really, the subnet name that you want the GKE worker nodes to be
# created in needs to match the private subnet name. The worker pools
# have to live somewhere and you get to choose where. You caregiver you.
#
gke_node_subnet_name = "private"
vpc_subnets = [
{
subnet_name = "public"
subnet_ip = "10.0.10.0/26"
subnet_region = "us-west1"
description = "foobar"
},
{
subnet_name = "private"
subnet_ip = "10.0.20.0/26"
subnet_region = "us-west1"
description = "foobar"
}
]
}
```
#### Customizing VPC Routes Example
``` terraform
module "environment" {
env = "prod"
vpc_routes = [{
name = "egress"
description = "egress"
destination_range = "0.0.0.0/0"
tags = "egress-inet"
next_hop_internet = "true"
}]
}
```
#### Contact
No Terraform module is perfect. Terraform, as a language, is frought with peril. If you need help setting this up then don't hesitate to ping me.
#### Testing
This module uses [Terratest](https://terratest.gruntwork.io/). The example configurations it tests are in `/examples`.
``` terraform
export TF_VAR_MY_IP_ADDRESS=foobar/32
export TF_VAR_PROJECT_ID=pid
cd ./test
go test
```
In order to automate the above environment variables, feel free to use an `.envrc` file. An example is provided in the root of this repo.
# Terraform-docs
The below is generated by [terraform-docs](https://terraform-docs.io/). The defaults column of the inputs should be taken with a grain of salt after reading the above.
## Requirements
| Name | Version |
|------|---------|
| [terraform](#requirement\_terraform) | >=1.3 |
| [google](#requirement\_google) | >= 5.40.0, != 5.44.0, != 6.2.0, != 6.3.0, < 7 |
## Providers
| Name | Version |
|------|---------|
| [google](#provider\_google) | >= 5.40.0, != 5.44.0, != 6.2.0, != 6.3.0, < 7 |
## Modules
| Name | Source | Version |
|------|--------|---------|
| [firewall\_rules](#module\_firewall\_rules) | terraform-google-modules/network/google//modules/firewall-rules | ~> 9.0.0 |
| [gke\_cluster](#module\_gke\_cluster) | terraform-google-modules/kubernetes-engine/google//modules/beta-autopilot-private-cluster | ~> 33.0 |
| [routes](#module\_routes) | terraform-google-modules/network/google//modules/routes | ~> 9.0.0 |
| [subnets](#module\_subnets) | terraform-google-modules/network/google//modules/subnets | ~> 9.0.0 |
| [vpc](#module\_vpc) | terraform-google-modules/network/google//modules/vpc | ~> 9.0.0 |
## Resources
| Name | Type |
|------|------|
| [google_compute_router.router](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_router) | resource |
| [google_compute_router_nat.nat](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_router_nat) | resource |
| [google_client_config.default](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/client_config) | data source |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| [env](#input\_env) | What to name a logical environment | `string` | n/a | yes |
| [gke\_control\_plane\_cidr](#input\_gke\_control\_plane\_cidr) | The cidr block for the GKE control plane. | `string` | `"10.10.30.0/28"` | no |
| [gke\_deletion\_protection](#input\_gke\_deletion\_protection) | Deletino protection. I'm not made of money so this is false by default. | `string` | `false` | no |
| [gke\_enable\_private\_endpoint](#input\_gke\_enable\_private\_endpoint) | Whether or not to make the control plane endpoint private to a subnet | `string` | `false` | no |
| [gke\_enable\_private\_nodes](#input\_gke\_enable\_private\_nodes) | Hide them nodes. | `string` | `true` | no |
| [gke\_enable\_vertical\_pod\_autoscaling](#input\_gke\_enable\_vertical\_pod\_autoscaling) | Enabling GKE vertical pod autoscaling. | `bool` | `true` | no |
| [gke\_horizontal\_pod\_autoscaling](#input\_gke\_horizontal\_pod\_autoscaling) | Enabling GKE horizontal pod autoscaling. | `bool` | `true` | no |
| [gke\_kubernetes\_version](#input\_gke\_kubernetes\_version) | Which Kubernetes version to use for the cluster. | `string` | `"latest"` | no |
| [gke\_master\_authorized\_networks](#input\_gke\_master\_authorized\_networks) | Use to override the default ingress rule that is constructed from var.my\_ip\_address |
list(object({
cidr_block = string
display_name = string
})) | `[]` | no |
| [gke\_network\_tags](#input\_gke\_network\_tags) | GKE network tags. | `list(string)` | `[]` | no |
| [gke\_node\_subnet\_name](#input\_gke\_node\_subnet\_name) | The subnet name for nodes created by GKE autopilot | `string` | n/a | yes |
| [gke\_pods\_range\_cidr](#input\_gke\_pods\_range\_cidr) | The cidr block for the GKE pods | `string` | `"192.168.0.0/18"` | no |
| [gke\_release\_channel](#input\_gke\_release\_channel) | The release channel for GKE versions | `string` | `"REGULAR"` | no |
| [gke\_svc\_range\_cidr](#input\_gke\_svc\_range\_cidr) | The cidr block for GKE services | `string` | `"192.168.64.0/18"` | no |
| [my\_ip\_address](#input\_my\_ip\_address) | A source IP used to connect to the k8s control plane | `string` | n/a | yes |
| [nat\_ip\_allocate\_option](#input\_nat\_ip\_allocate\_option) | https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_router_nat#nat_ip_allocate_option | `string` | `"AUTO_ONLY"` | no |
| [nat\_source\_subnetwork\_ip\_ranges\_to\_nat](#input\_nat\_source\_subnetwork\_ip\_ranges\_to\_nat) | https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_router_nat#source_subnetwork_ip_ranges_to_nat | `string` | `"ALL_SUBNETWORKS_ALL_IP_RANGES"` | no |
| [project\_id](#input\_project\_id) | n/a | `string` | n/a | yes |
| [region](#input\_region) | n/a | `string` | `"us-west1"` | no |
| [vpc\_auto\_create\_subnets](#input\_vpc\_auto\_create\_subnets) | Whether or not to automatically create VPC subnets. This should almost always be off. | `bool` | `false` | no |
| [vpc\_enable\_my\_ip\_ingress\_rule](#input\_vpc\_enable\_my\_ip\_ingress\_rule) | Whether or not to create an ingress rule for a home IP address. Is added to var.vpc\_firewall\_rules. If set then you need to set var.vpc\_public\_subnet\_cidr to your public subnet cidr block. | `bool` | `true` | no |
| [vpc\_firewall\_rules](#input\_vpc\_firewall\_rules) | Use to define firewall rules | list(object({
name = string
description = string
direction = string
priority = number
destination_ranges = list(string)
source_ranges = list(string)
allow = list(object({
protocol = string
ports = list(string)
}))
})) | `[]` | no |
| [vpc\_private\_subnet\_cidr](#input\_vpc\_private\_subnet\_cidr) | The cidr block for the private subnet | `string` | `"10.10.20.0/26"` | no |
| [vpc\_private\_subnet\_name](#input\_vpc\_private\_subnet\_name) | The name of the VPC private subnet. Default: infra-$(env)-private-01 | `string` | `""` | no |
| [vpc\_private\_subnet\_secondary\_ranges](#input\_vpc\_private\_subnet\_secondary\_ranges) | Use to override the creation of secondary subnet ranges for GKE worker node allocations. | list(object({
range_name = string
ip_cidr_range = string
})) | `[]` | no |
| [vpc\_public\_subnet\_cidr](#input\_vpc\_public\_subnet\_cidr) | The cidr block for the public subnet | `string` | `"10.10.10.0/26"` | no |
| [vpc\_public\_subnet\_name](#input\_vpc\_public\_subnet\_name) | The name of the VPC public subnet. Default: infra-$(env)-public-01 | `string` | `""` | no |
| [vpc\_routes](#input\_vpc\_routes) | Use to override the default VPC routes. | list(object({
name = string
description = string
destination_range = string
tags = string
next_hop_internet = string
})) | `[]` | no |
| [vpc\_routing\_mode](#input\_vpc\_routing\_mode) | https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_network#routing_mode | `string` | `"GLOBAL"` | no |
| [vpc\_shared\_vpc\_host](#input\_vpc\_shared\_vpc\_host) | Whether or not to setup a VPC as a 'shared VPC.' | `bool` | `false` | no |
| [vpc\_subnets](#input\_vpc\_subnets) | Use to override the creation of the default subnets. There be dragons here. | list(object({
subnet_name = string
subnet_ip = string
subnet_region = string
description = string
})) | `[]` | no |
## Outputs
| Name | Description |
|------|-------------|
| [gke\_cluster\_endpoint](#output\_gke\_cluster\_endpoint) | n/a |
| [gke\_cluster\_name](#output\_gke\_cluster\_name) | n/a |
| [vpc\_network\_id](#output\_vpc\_network\_id) | n/a |
| [vpc\_network\_name](#output\_vpc\_network\_name) | n/a |
| [vpc\_network\_subnets](#output\_vpc\_network\_subnets) | n/a |