Compare commits

...

5 Commits

Author SHA1 Message Date
Tom Alexander
b576d44af0 Switch to not including trailing period in root domain. 2025-03-22 21:47:04 -04:00
Tom Alexander
9d0acdac13 Make public IP address quota based on actual use. 2025-03-22 18:34:28 -04:00
Tom Alexander
cbab018652 Re-enable gce ingress. 2025-03-22 18:34:28 -04:00
Tom Alexander
91dd7095da Fix support for the nginx ingress controller. 2025-03-22 16:48:21 -04:00
Tom Alexander
8a0f78032c Fix external dns zone matching. 2025-03-22 15:19:44 -04:00
10 changed files with 41 additions and 25 deletions

View File

@@ -88,8 +88,8 @@ gcloud auth application-default login
Then go into the `terraform` folder and apply the configuration. We need to apply the config in two phases via the `cluster_exists` variable because the kubernetes terraform provider does not have native support for the Gateway API and the `kubernetes_manifest` terraform resource [has a shortcoming that requires the cluster exists at plan time](https://github.com/hashicorp/terraform-provider-kubernetes/issues/1775). Then go into the `terraform` folder and apply the configuration. We need to apply the config in two phases via the `cluster_exists` variable because the kubernetes terraform provider does not have native support for the Gateway API and the `kubernetes_manifest` terraform resource [has a shortcoming that requires the cluster exists at plan time](https://github.com/hashicorp/terraform-provider-kubernetes/issues/1775).
``` ```
terraform apply -var dns_root="k8sdemo.mydomain.example." -var quota_email="MrManager@mydomain.example" -var quota_justification="Explain why you need quotas increased here." -var cluster_exists=false terraform apply -var dns_root="k8sdemo.mydomain.example" -var quota_email="MrManager@mydomain.example" -var quota_justification="Explain why you need quotas increased here." -var cluster_exists=false
terraform apply -var dns_root="k8sdemo.mydomain.example." -var quota_email="MrManager@mydomain.example" -var quota_justification="Explain why you need quotas increased here." -var cluster_exists=true terraform apply -var dns_root="k8sdemo.mydomain.example" -var quota_email="MrManager@mydomain.example" -var quota_justification="Explain why you need quotas increased here." -var cluster_exists=true
``` ```
Please note that this will exceed the default quotas on new Google Cloud projects. The terraform configuration will automatically put in requests for quota increases but they can take multiple days to be approved or denied. You should be able to fit 3 clusters in the default quota until then. Please note that this will exceed the default quotas on new Google Cloud projects. The terraform configuration will automatically put in requests for quota increases but they can take multiple days to be approved or denied. You should be able to fit 3 clusters in the default quota until then.
@@ -279,7 +279,7 @@ But that doesn't mean that we need to use the valuable RFC-1918 IP address space
To demonstrate, we can apply the terraform config again but with the `enable_snat=true` variable set: To demonstrate, we can apply the terraform config again but with the `enable_snat=true` variable set:
``` ```
terraform apply -var dns_root="k8sdemo.mydomain.example." -var quota_email="MrManager@mydomain.example" -var quota_justification="Explain why you need quotas increased here." -var cluster_exists=true -var enable_snat=true terraform apply -var dns_root="k8sdemo.mydomain.example" -var quota_email="MrManager@mydomain.example" -var quota_justification="Explain why you need quotas increased here." -var cluster_exists=true -var enable_snat=true
``` ```
Then in our kubernetes pod, we can run the `curl` again: Then in our kubernetes pod, we can run the `curl` again:
@@ -304,11 +304,13 @@ Question and Answer
[GKE assigns a separate IP address to each `Ingress`](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress#limitations), but we can have a single `Gateway` with an IP address and then any quantity of `HTTPRoute`. This is a design choice for GKE, and not a limitation of kubernetes. [GKE assigns a separate IP address to each `Ingress`](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress#limitations), but we can have a single `Gateway` with an IP address and then any quantity of `HTTPRoute`. This is a design choice for GKE, and not a limitation of kubernetes.
If you need to use `Ingress`, we can achieve the same efficiency for IP addresses by using the nginx ingress controller. This can be enabled by passing `-var ingress_type=nginx`. If you need to use the built-in ingress controller instead of nginx you can set `-var ingress_type=gce` but then each `Ingress` will cost 1 IP address.
Clean Up Clean Up
======== ========
Just like we did a 2-stage apply by toggling the `cluster_exists` variable, we will need to do a 2-stage destroy. First we tear down any kubernetes resources by running *apply* with the `cluster_exists` variable set to `false`. Then we can destroy the entire project. Just like we did a 2-stage apply by toggling the `cluster_exists` variable, we will need to do a 2-stage destroy. First we tear down any kubernetes resources by running *apply* with the `cluster_exists` variable set to `false`. Then we can destroy the entire project.
``` ```
terraform apply -var dns_root="k8sdemo.mydomain.example." -var quota_email="MrManager@mydomain.example" -var quota_justification="Explain why you need quotas increased here." -var cluster_exists=false terraform apply -var dns_root="k8sdemo.mydomain.example" -var quota_email="MrManager@mydomain.example" -var quota_justification="Explain why you need quotas increased here." -var cluster_exists=false
terraform destroy -var dns_root="k8sdemo.mydomain.example." -var quota_email="MrManager@mydomain.example" -var quota_justification="Explain why you need quotas increased here." -var cluster_exists=false terraform destroy -var dns_root="k8sdemo.mydomain.example" -var quota_email="MrManager@mydomain.example" -var quota_justification="Explain why you need quotas increased here." -var cluster_exists=false
``` ```

View File

@@ -1,7 +1,5 @@
# TODO: Switch to not requiring trailing period?
variable "dns_root" { variable "dns_root" {
description = "DNS domain root with trailing period. Example: \"foo.bar.com.\"" description = "DNS domain root. Example: \"k8sdemo.mydomain.example\""
type = string type = string
} }
@@ -9,7 +7,7 @@ variable "dns_root" {
resource "google_dns_managed_zone" "zone" { resource "google_dns_managed_zone" "zone" {
project = google_project.project.project_id project = google_project.project.project_id
name = "dns-zone" name = "dns-zone"
dns_name = var.dns_root dns_name = "${var.dns_root}."
depends_on = [google_project_service.service["dns"], ] depends_on = [google_project_service.service["dns"], ]
} }

View File

@@ -43,7 +43,7 @@ resource "google_project_iam_member" "external_dns" {
role = "roles/dns.reader" role = "roles/dns.reader"
} }
resource "google_dns_managed_zone_iam_member" "member" { resource "google_dns_managed_zone_iam_member" "external_dns" {
project = google_project.project.project_id project = google_project.project.project_id
managed_zone = google_dns_managed_zone.zone.name managed_zone = google_dns_managed_zone.zone.name
role = "roles/dns.admin" role = "roles/dns.admin"

View File

@@ -1,3 +1,4 @@
# TODO: put IP address ranges into variables
terraform { terraform {
backend "gcs" { backend "gcs" {
bucket = "tf-state-4b00" bucket = "tf-state-4b00"
@@ -48,6 +49,11 @@ variable "ingress_type" {
description = "What controller should we use to handle incoming http(s) connections." description = "What controller should we use to handle incoming http(s) connections."
type = string type = string
default = "gateway" default = "gateway"
validation {
condition = contains(["gateway", "nginx", "gce"], var.ingress_type)
error_message = "Must be either \"gateway\", \"nginx\", or \"gce\"."
}
} }
variable "cluster_exists" { variable "cluster_exists" {

View File

@@ -46,12 +46,6 @@ variable "public_ingress" {
variable "ingress_type" { variable "ingress_type" {
description = "What controller should we use to handle incoming http(s) connections." description = "What controller should we use to handle incoming http(s) connections."
type = string type = string
default = "gateway"
validation {
condition = contains(["gateway"], var.ingress_type)
error_message = "Currently only \"gateway\" is supported."
}
} }
variable "main_k8s_namespace" { variable "main_k8s_namespace" {

View File

@@ -12,7 +12,8 @@ variable "external_dns_gcp_service_account_email" {
locals { locals {
external_dns_namespace = length(kubernetes_namespace.external_dns) == 0 ? var.external_dns_k8s_namespace : kubernetes_namespace.external_dns[0].metadata[0].name external_dns_namespace = length(kubernetes_namespace.external_dns) == 0 ? var.external_dns_k8s_namespace : kubernetes_namespace.external_dns[0].metadata[0].name
external_dns_domain_filter = trimsuffix("${var.cluster.name}.${var.dns_managed_zone.dns_name}", ".") external_dns_domain_filter = trimsuffix("${var.dns_managed_zone.dns_name}", ".")
# external_dns_domain_filter needs to match a google_dns_managed_zone so to keep things simple I am only filtering to the dns_root. If we wanted to filter to the cluster subdomain, we could create a separate google_dns_managed_zone for each cluster (and set IAM permissions accordingly).
} }
resource "kubernetes_namespace" "external_dns" { resource "kubernetes_namespace" "external_dns" {

View File

@@ -10,6 +10,7 @@
module "nginx_ingress_controller" { module "nginx_ingress_controller" {
count = var.ingress_type == "nginx" ? 1 : 0 count = var.ingress_type == "nginx" ? 1 : 0
source = "../nginx_ingress_controller" source = "../nginx_ingress_controller"
public_ingress = var.public_ingress
} }
resource "kubernetes_ingress_v1" "ingress_nginx" { resource "kubernetes_ingress_v1" "ingress_nginx" {
@@ -18,7 +19,7 @@ resource "kubernetes_ingress_v1" "ingress_nginx" {
metadata { metadata {
name = "${var.cluster.name}-${each.value.metadata[0].name}" name = "${var.cluster.name}-${each.value.metadata[0].name}"
annotations = { annotations = {
"kubernetes.io/ingress.class" = var.public_ingress ? "gce" : "gce-internal" "kubernetes.io/ingress.class" = "nginx"
} }
} }
@@ -41,5 +42,5 @@ resource "kubernetes_ingress_v1" "ingress_nginx" {
} }
} }
depends_on = [time_sleep.wait_service_cleanup] depends_on = [time_sleep.wait_service_cleanup, module.nginx_ingress_controller]
} }

View File

@@ -514,7 +514,6 @@ resource "kubernetes_manifest" "clusterrolebinding_ingress_nginx_admission" {
resource "kubernetes_manifest" "configmap_ingress_nginx_ingress_nginx_controller" { resource "kubernetes_manifest" "configmap_ingress_nginx_ingress_nginx_controller" {
manifest = { manifest = {
"apiVersion" = "v1" "apiVersion" = "v1"
"data" = null
"kind" = "ConfigMap" "kind" = "ConfigMap"
"metadata" = { "metadata" = {
"labels" = { "labels" = {
@@ -535,6 +534,9 @@ resource "kubernetes_manifest" "service_ingress_nginx_ingress_nginx_controller"
"apiVersion" = "v1" "apiVersion" = "v1"
"kind" = "Service" "kind" = "Service"
"metadata" = { "metadata" = {
"annotations" = {
"networking.gke.io/load-balancer-type" = var.public_ingress ? "External" : "Internal"
}
"labels" = { "labels" = {
"app.kubernetes.io/component" = "controller" "app.kubernetes.io/component" = "controller"
"app.kubernetes.io/instance" = "ingress-nginx" "app.kubernetes.io/instance" = "ingress-nginx"
@@ -612,6 +614,7 @@ resource "kubernetes_manifest" "service_ingress_nginx_ingress_nginx_controller_a
} }
resource "kubernetes_manifest" "deployment_ingress_nginx_ingress_nginx_controller" { resource "kubernetes_manifest" "deployment_ingress_nginx_ingress_nginx_controller" {
computed_fields = ["metadata.annotations", "metadata.labels", "spec.template.metadata.labels"]
manifest = { manifest = {
"apiVersion" = "apps/v1" "apiVersion" = "apps/v1"
"kind" = "Deployment" "kind" = "Deployment"
@@ -627,7 +630,6 @@ resource "kubernetes_manifest" "deployment_ingress_nginx_ingress_nginx_controlle
"namespace" = kubernetes_manifest.namespace_ingress_nginx.manifest.metadata.name "namespace" = kubernetes_manifest.namespace_ingress_nginx.manifest.metadata.name
} }
"spec" = { "spec" = {
"minReadySeconds" = 0
"revisionHistoryLimit" = 10 "revisionHistoryLimit" = 10
"selector" = { "selector" = {
"matchLabels" = { "matchLabels" = {
@@ -795,6 +797,7 @@ resource "kubernetes_manifest" "deployment_ingress_nginx_ingress_nginx_controlle
} }
resource "kubernetes_manifest" "job_ingress_nginx_ingress_nginx_admission_create" { resource "kubernetes_manifest" "job_ingress_nginx_ingress_nginx_admission_create" {
computed_fields = ["metadata.annotations", "metadata.labels", "spec.template.metadata.labels"]
manifest = { manifest = {
"apiVersion" = "batch/v1" "apiVersion" = "batch/v1"
"kind" = "Job" "kind" = "Job"
@@ -872,6 +875,7 @@ resource "kubernetes_manifest" "job_ingress_nginx_ingress_nginx_admission_create
} }
resource "kubernetes_manifest" "job_ingress_nginx_ingress_nginx_admission_patch" { resource "kubernetes_manifest" "job_ingress_nginx_ingress_nginx_admission_patch" {
computed_fields = ["metadata.annotations", "metadata.labels", "spec.template.metadata.labels"]
manifest = { manifest = {
"apiVersion" = "batch/v1" "apiVersion" = "batch/v1"
"kind" = "Job" "kind" = "Job"

View File

@@ -7,6 +7,11 @@ terraform {
} }
} }
variable "public_ingress" {
description = "Set to true to make the kubernetes ingresses exposed to the public internet."
type = bool
}
data "google_client_config" "default" {} data "google_client_config" "default" {}
resource "kubernetes_cluster_role_binding" "cluster_admin_binding" { resource "kubernetes_cluster_role_binding" "cluster_admin_binding" {

View File

@@ -1,5 +1,10 @@
# TODO: Make public IP quota dependent on var.public_ingress and update the amount to match what is expected to be spun up. # TODO: Make public IP quota dependent on var.public_ingress and update the amount to match what is expected to be spun up.
locals {
num_clusters = 14
num_public_ip_addresses = var.ingress_type == "gce" ? 10 * local.num_clusters : local.num_clusters
}
# resource "google_cloud_quotas_quota_preference" "clusters_per_region" { # resource "google_cloud_quotas_quota_preference" "clusters_per_region" {
# count = var.quota_email == null ? 0 : 1 # count = var.quota_email == null ? 0 : 1
# parent = "projects/${google_project.project.project_id}" # parent = "projects/${google_project.project.project_id}"
@@ -16,7 +21,7 @@
# } # }
# resource "google_cloud_quotas_quota_preference" "public_ip_per_project_region" { # resource "google_cloud_quotas_quota_preference" "public_ip_per_project_region" {
# count = var.quota_email == null ? 0 : 1 # count = var.quota_email == null && var.public_ingress == true ? 0 : 1
# parent = "projects/${google_project.project.project_id}" # parent = "projects/${google_project.project.project_id}"
# name = "compute-IN-USE-ADDRESSES-per-project-region" # name = "compute-IN-USE-ADDRESSES-per-project-region"
# dimensions = { region = var.region } # dimensions = { region = var.region }
@@ -24,7 +29,7 @@
# quota_id = "IN-USE-ADDRESSES-per-project-region" # quota_id = "IN-USE-ADDRESSES-per-project-region"
# contact_email = var.quota_email # contact_email = var.quota_email
# quota_config { # quota_config {
# preferred_value = 70 # preferred_value = local.num_public_ip_addresses
# } # }
# justification = var.quota_justification # justification = var.quota_justification
# depends_on = [google_project_service.service["cloudquotas"], ] # depends_on = [google_project_service.service["cloudquotas"], ]