From 59b3f84f3d32ddf18b2ecf56696f6729f680153b Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Fri, 19 Dec 2025 13:02:23 -0500 Subject: [PATCH 01/23] terraform: add aws-eks --- terraform/aws/aws-eks/README.md | 78 +++++++++++++++ terraform/aws/aws-eks/main.tf | 153 +++++++++++++++++++++++++++++ terraform/aws/aws-eks/outputs.tf | 24 +++++ terraform/aws/aws-eks/variables.tf | 21 ++++ terraform/aws/aws-eks/versions.tf | 42 ++++++++ 5 files changed, 318 insertions(+) create mode 100644 terraform/aws/aws-eks/README.md create mode 100644 terraform/aws/aws-eks/main.tf create mode 100644 terraform/aws/aws-eks/outputs.tf create mode 100644 terraform/aws/aws-eks/variables.tf create mode 100644 terraform/aws/aws-eks/versions.tf diff --git a/terraform/aws/aws-eks/README.md b/terraform/aws/aws-eks/README.md new file mode 100644 index 0000000..4456fcc --- /dev/null +++ b/terraform/aws/aws-eks/README.md @@ -0,0 +1,78 @@ +# aws-eks-tailscale-operator + +This example creates the following: + +- a VPC and related resources including a NAT Gateway +- an EKS cluster with a managed node group +- a Kubernetes namespace for the [Tailscale operator](https://tailscale.com/kb/1236/kubernetes-operator) +- the Tailscale Kubernetes Operator deployed via [Helm](https://tailscale.com/kb/1236/kubernetes-operator#helm) + +## Considerations + +- The EKS cluster is configured with both public and private API server access for flexibility +- The Tailscale operator is deployed in a dedicated `tailscale` namespace +- The operator will create a Tailscale device for API server proxy access +- Any additional Tailscale resources (like ingress controllers) created by the operator will appear in your Tailnet + +## Prerequisites + +- Create a [Tailscale OAuth Client](https://tailscale.com/kb/1215/oauth-clients#setting-up-an-oauth-client) with appropriate scopes +- Ensure you have AWS CLI configured with appropriate permissions for EKS +- Install `kubectl` for cluster access after deployment + +## To use + +Follow the documentation to configure the Terraform providers: + +- [AWS](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) +- [Kubernetes](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs) +- [Helm](https://registry.terraform.io/providers/hashicorp/helm/latest/docs) + +### Configure variables + +Create a `terraform.tfvars` file with your Tailscale OAuth credentials: + +```hcl +tailscale_oauth_client_id = "your-oauth-client-id" +tailscale_oauth_client_secret = "your-oauth-client-secret" +``` + +### Deploy + +```shell +terraform init +terraform apply +``` + +#### Verify deployment + +After deployment, configure kubectl to access your cluster: + +```shell +aws eks update-kubeconfig --region $AWS_REGION --name $(terraform output -raw cluster_name) +``` + +Check that the Tailscale operator is running: + +```shell +kubectl get pods -n tailscale +kubectl logs -n tailscale -l app.kubernetes.io/name=tailscale-operator +``` + +#### Verify connectivity via the [API server proxy](https://tailscale.com/kb/1437/kubernetes-operator-api-server-proxy) + +After deployment, configure kubectl to access your cluster using Tailscale: + +```shell +tailscale configure kubeconfig ${local.operator_name} +``` + +```shell +kubectl get pods -n tailscale +``` + +## To destroy + +```shell +terraform destroy +``` diff --git a/terraform/aws/aws-eks/main.tf b/terraform/aws/aws-eks/main.tf new file mode 100644 index 0000000..136611e --- /dev/null +++ b/terraform/aws/aws-eks/main.tf @@ -0,0 +1,153 @@ +locals { + name = basename(path.cwd) + + aws_tags = { + Name = local.name + } + + // Modify these to use your own VPC + vpc_cidr_block = module.vpc.vpc_cidr_block + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets + + # EKS cluster configuration + cluster_version = "1.34" # TODO: omit this? + node_instance_type = "t3.medium" + node_capacity_type = "ON_DEMAND" + desired_size = 2 + max_size = 2 + min_size = 1 + + # Tailscale Operator configuration + operator_name = "${local.name}-operator" + operator_version = "1.92.4" + tailscale_oauth_client_id = var.tailscale_oauth_client_id + tailscale_oauth_client_secret = var.tailscale_oauth_client_secret # TODO: use https://tailscale.com/kb/1236/kubernetes-operator#installation-with-workload-identity-federation +} + +// Remove this to use your own VPC. +module "vpc" { + source = "../internal-modules/aws-vpc" + + name = local.name + tags = local.aws_tags +} + +module "eks" { + source = "terraform-aws-modules/eks/aws" + version = ">= 21.0, < 22.0" + + name = local.name + kubernetes_version = local.cluster_version + + addons = { + coredns = {} + eks-pod-identity-agent = { + before_compute = true + } + kube-proxy = {} + vpc-cni = { + before_compute = true + } + } + + # Optional + endpoint_public_access = true # TODO: remove? + + # Optional: Adds the current caller identity as an administrator via cluster access entry + enable_cluster_creator_admin_permissions = true + + vpc_id = local.vpc_id + subnet_ids = local.subnet_ids + + eks_managed_node_groups = { + main = { + # Starting on 1.30, AL2023 is the default AMI type for EKS managed node groups + # ami_type = "AL2023_x86_64_STANDARD" + instance_types = [local.node_instance_type] + + desired_size = local.desired_size + max_size = local.max_size + min_size = local.min_size + } + } + + tags = local.aws_tags +} + +# Kubernetes namespace for Tailscale operator +resource "kubernetes_namespace_v1" "tailscale_operator" { + metadata { + name = "tailscale" + labels = { + "pod-security.kubernetes.io/enforce" = "privileged" + } + } +} + +resource "helm_release" "tailscale_operator" { + name = local.operator_name + namespace = kubernetes_namespace_v1.tailscale_operator.metadata[0].name + + repository = "https://pkgs.tailscale.com/helmcharts" + chart = "tailscale-operator" + version = local.operator_version + + values = [ + yamlencode({ + operatorConfig = { + image = { + repo = "tailscale/k8s-operator" + tag = "v${local.operator_version}" + } + hostname = local.operator_name + } + apiServerProxyConfig = { + mode = "true" + tags = "tag:k8s-operator,tag:k8s-api-server" + } + oauth = { + clientId = local.tailscale_oauth_client_id + clientSecret = local.tailscale_oauth_client_secret + } + }) + ] + + set_sensitive = [ + { + name = "oauth.clientId" + value = local.tailscale_oauth_client_id + }, + { + name = "oauth.clientSecret" + value = local.tailscale_oauth_client_secret + }, + ] +} + +# TODO: get working on first apply? +# locals { +# # TODO: inline/simplify? +# yaml_tailscale_operator_ha_proxy = <<-EOT +# apiVersion: tailscale.com/v1alpha1 +# kind: ProxyGroup +# metadata: +# name: ${helm_release.tailscale_operator.name}-ha +# spec: +# type: kube-apiserver +# replicas: 2 +# tags: ["tag:k8s"] +# kubeAPIServer: +# mode: auth +# EOT +# } + +# resource "kubernetes_manifest" "tailscale_operator_ha_proxy" { +# manifest = yamldecode(local.yaml_tailscale_operator_ha_proxy) + +# depends_on = [ +# module.eks.cluster_endpoint, # TODO: remove? +# helm_release.tailscale_operator, +# kubernetes_namespace_v1.tailscale_operator, +# ] +# } diff --git a/terraform/aws/aws-eks/outputs.tf b/terraform/aws/aws-eks/outputs.tf new file mode 100644 index 0000000..44d6116 --- /dev/null +++ b/terraform/aws/aws-eks/outputs.tf @@ -0,0 +1,24 @@ +output "vpc_id" { + description = "VPC ID where the EKS cluster is deployed" + value = module.vpc.vpc_id +} + +output "cluster_name" { + description = "EKS cluster name" + value = module.eks.cluster_name +} + +output "tailscale_operator_namespace" { + description = "Kubernetes namespace where Tailscale operator is deployed" + value = kubernetes_namespace_v1.tailscale_operator.metadata[0].name +} + +output "cmd_kubeconfig_tailscale" { + value = "tailscale configure kubeconfig ${local.operator_name}" +} + +output "cmd_kubeconfig_aws" { + value = "aws eks update-kubeconfig --region ${data.aws_region.current.region} --name ${module.eks.cluster_name}" +} + +data "aws_region" "current" {} # TODO: move? or remove? diff --git a/terraform/aws/aws-eks/variables.tf b/terraform/aws/aws-eks/variables.tf new file mode 100644 index 0000000..e9a505f --- /dev/null +++ b/terraform/aws/aws-eks/variables.tf @@ -0,0 +1,21 @@ +variable "tailscale_oauth_client_id" { + description = "Tailscale OAuth client ID" + type = string + sensitive = true + + validation { + condition = length(var.tailscale_oauth_client_id) > 0 + error_message = "Tailscale OAuth client ID must not be empty." + } +} + +variable "tailscale_oauth_client_secret" { + description = "Tailscale OAuth client secret" + type = string + sensitive = true + + validation { + condition = length(var.tailscale_oauth_client_secret) > 0 + error_message = "Tailscale OAuth client secret must not be empty." + } +} diff --git a/terraform/aws/aws-eks/versions.tf b/terraform/aws/aws-eks/versions.tf new file mode 100644 index 0000000..237f639 --- /dev/null +++ b/terraform/aws/aws-eks/versions.tf @@ -0,0 +1,42 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0, < 7.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 3.0.1, < 4.0" + } + helm = { + source = "hashicorp/helm" + version = ">= 3.1.1, < 4.0" + } + } +} + +provider "kubernetes" { + host = module.eks.cluster_endpoint + cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) + + exec { + api_version = "client.authentication.k8s.io/v1beta1" + command = "aws" + args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] + } +} + +provider "helm" { + kubernetes = { + host = module.eks.cluster_endpoint + cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) + + exec = { + api_version = "client.authentication.k8s.io/v1beta1" + command = "aws" + args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] + } + } +} From 9bb7067b6d8afa29fd67cbbf75eb75e827d162d2 Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Fri, 19 Dec 2025 14:09:07 -0500 Subject: [PATCH 02/23] rename aws-eks to aws-eks-operator --- terraform/aws/{aws-eks => aws-eks-operator}/README.md | 0 terraform/aws/{aws-eks => aws-eks-operator}/main.tf | 0 terraform/aws/{aws-eks => aws-eks-operator}/outputs.tf | 0 terraform/aws/{aws-eks => aws-eks-operator}/variables.tf | 0 terraform/aws/{aws-eks => aws-eks-operator}/versions.tf | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename terraform/aws/{aws-eks => aws-eks-operator}/README.md (100%) rename terraform/aws/{aws-eks => aws-eks-operator}/main.tf (100%) rename terraform/aws/{aws-eks => aws-eks-operator}/outputs.tf (100%) rename terraform/aws/{aws-eks => aws-eks-operator}/variables.tf (100%) rename terraform/aws/{aws-eks => aws-eks-operator}/versions.tf (100%) diff --git a/terraform/aws/aws-eks/README.md b/terraform/aws/aws-eks-operator/README.md similarity index 100% rename from terraform/aws/aws-eks/README.md rename to terraform/aws/aws-eks-operator/README.md diff --git a/terraform/aws/aws-eks/main.tf b/terraform/aws/aws-eks-operator/main.tf similarity index 100% rename from terraform/aws/aws-eks/main.tf rename to terraform/aws/aws-eks-operator/main.tf diff --git a/terraform/aws/aws-eks/outputs.tf b/terraform/aws/aws-eks-operator/outputs.tf similarity index 100% rename from terraform/aws/aws-eks/outputs.tf rename to terraform/aws/aws-eks-operator/outputs.tf diff --git a/terraform/aws/aws-eks/variables.tf b/terraform/aws/aws-eks-operator/variables.tf similarity index 100% rename from terraform/aws/aws-eks/variables.tf rename to terraform/aws/aws-eks-operator/variables.tf diff --git a/terraform/aws/aws-eks/versions.tf b/terraform/aws/aws-eks-operator/versions.tf similarity index 100% rename from terraform/aws/aws-eks/versions.tf rename to terraform/aws/aws-eks-operator/versions.tf From 1b49a5b1e84db1b918964b6038b1b458d4d8d986 Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Fri, 19 Dec 2025 20:35:31 -0500 Subject: [PATCH 03/23] cleanup and tflint fixes --- terraform/aws/aws-eks-operator/main.tf | 11 +++++------ terraform/aws/aws-eks-operator/outputs.tf | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/terraform/aws/aws-eks-operator/main.tf b/terraform/aws/aws-eks-operator/main.tf index 136611e..8636ef9 100644 --- a/terraform/aws/aws-eks-operator/main.tf +++ b/terraform/aws/aws-eks-operator/main.tf @@ -1,19 +1,17 @@ locals { - name = basename(path.cwd) + name = "example-${basename(path.cwd)}" aws_tags = { Name = local.name } // Modify these to use your own VPC - vpc_cidr_block = module.vpc.vpc_cidr_block vpc_id = module.vpc.vpc_id subnet_ids = module.vpc.private_subnets # EKS cluster configuration cluster_version = "1.34" # TODO: omit this? node_instance_type = "t3.medium" - node_capacity_type = "ON_DEMAND" desired_size = 2 max_size = 2 min_size = 1 @@ -22,7 +20,7 @@ locals { operator_name = "${local.name}-operator" operator_version = "1.92.4" tailscale_oauth_client_id = var.tailscale_oauth_client_id - tailscale_oauth_client_secret = var.tailscale_oauth_client_secret # TODO: use https://tailscale.com/kb/1236/kubernetes-operator#installation-with-workload-identity-federation + tailscale_oauth_client_secret = var.tailscale_oauth_client_secret } // Remove this to use your own VPC. @@ -51,8 +49,9 @@ module "eks" { } } - # Optional - endpoint_public_access = true # TODO: remove? + # Once the Tailscale operator is installed, `endpoint_public_access` can be disabled. + # This is left enabled for the sake of easy adoption. + endpoint_public_access = true # Optional: Adds the current caller identity as an administrator via cluster access entry enable_cluster_creator_admin_permissions = true diff --git a/terraform/aws/aws-eks-operator/outputs.tf b/terraform/aws/aws-eks-operator/outputs.tf index 44d6116..058a12b 100644 --- a/terraform/aws/aws-eks-operator/outputs.tf +++ b/terraform/aws/aws-eks-operator/outputs.tf @@ -21,4 +21,4 @@ output "cmd_kubeconfig_aws" { value = "aws eks update-kubeconfig --region ${data.aws_region.current.region} --name ${module.eks.cluster_name}" } -data "aws_region" "current" {} # TODO: move? or remove? +data "aws_region" "current" {} From 386d6c67c160b87eb0db17ba642e5408217c25b1 Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Fri, 19 Dec 2025 20:36:14 -0500 Subject: [PATCH 04/23] terraform fmt --- terraform/aws/aws-eks-operator/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/aws/aws-eks-operator/main.tf b/terraform/aws/aws-eks-operator/main.tf index 8636ef9..635c836 100644 --- a/terraform/aws/aws-eks-operator/main.tf +++ b/terraform/aws/aws-eks-operator/main.tf @@ -6,8 +6,8 @@ locals { } // Modify these to use your own VPC - vpc_id = module.vpc.vpc_id - subnet_ids = module.vpc.private_subnets + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnets # EKS cluster configuration cluster_version = "1.34" # TODO: omit this? From 849beaa46a238468bd50299a9154a5b71186a8e9 Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Sun, 21 Dec 2025 07:40:18 -0500 Subject: [PATCH 05/23] address most copilot feedback --- terraform/aws/aws-eks-operator/README.md | 2 +- terraform/aws/aws-eks-operator/data.tf | 1 + terraform/aws/aws-eks-operator/main.tf | 6 +----- terraform/aws/aws-eks-operator/outputs.tf | 4 ++-- 4 files changed, 5 insertions(+), 8 deletions(-) create mode 100644 terraform/aws/aws-eks-operator/data.tf diff --git a/terraform/aws/aws-eks-operator/README.md b/terraform/aws/aws-eks-operator/README.md index 4456fcc..7331d6a 100644 --- a/terraform/aws/aws-eks-operator/README.md +++ b/terraform/aws/aws-eks-operator/README.md @@ -64,7 +64,7 @@ kubectl logs -n tailscale -l app.kubernetes.io/name=tailscale-operator After deployment, configure kubectl to access your cluster using Tailscale: ```shell -tailscale configure kubeconfig ${local.operator_name} +tailscale configure kubeconfig ${terraform output -raw operator_name} ``` ```shell diff --git a/terraform/aws/aws-eks-operator/data.tf b/terraform/aws/aws-eks-operator/data.tf new file mode 100644 index 0000000..2502393 --- /dev/null +++ b/terraform/aws/aws-eks-operator/data.tf @@ -0,0 +1 @@ +data "aws_region" "current" {} diff --git a/terraform/aws/aws-eks-operator/main.tf b/terraform/aws/aws-eks-operator/main.tf index 635c836..37a13c8 100644 --- a/terraform/aws/aws-eks-operator/main.tf +++ b/terraform/aws/aws-eks-operator/main.tf @@ -102,13 +102,9 @@ resource "helm_release" "tailscale_operator" { hostname = local.operator_name } apiServerProxyConfig = { - mode = "true" + mode = true tags = "tag:k8s-operator,tag:k8s-api-server" } - oauth = { - clientId = local.tailscale_oauth_client_id - clientSecret = local.tailscale_oauth_client_secret - } }) ] diff --git a/terraform/aws/aws-eks-operator/outputs.tf b/terraform/aws/aws-eks-operator/outputs.tf index 058a12b..35b44c7 100644 --- a/terraform/aws/aws-eks-operator/outputs.tf +++ b/terraform/aws/aws-eks-operator/outputs.tf @@ -14,11 +14,11 @@ output "tailscale_operator_namespace" { } output "cmd_kubeconfig_tailscale" { + description = "Command to configure kubeconfig for Tailscale access to the EKS cluster" value = "tailscale configure kubeconfig ${local.operator_name}" } output "cmd_kubeconfig_aws" { + description = "Command to configure kubeconfig for public access to the EKS cluster" value = "aws eks update-kubeconfig --region ${data.aws_region.current.region} --name ${module.eks.cluster_name}" } - -data "aws_region" "current" {} From b4741aca64bc15f4c6f3948301aa41171462ef4d Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Sun, 21 Dec 2025 07:46:43 -0500 Subject: [PATCH 06/23] rename terraform-tflint to terraform-check-tflint --- .github/workflows/terraform-examples.yml | 2 +- Makefile | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/terraform-examples.yml b/.github/workflows/terraform-examples.yml index 25f812e..b9ab353 100644 --- a/.github/workflows/terraform-examples.yml +++ b/.github/workflows/terraform-examples.yml @@ -9,7 +9,7 @@ on: jobs: - terraform-tflint: + terraform-check-tflint: runs-on: ubuntu-latest steps: - name: Check out code diff --git a/Makefile b/Makefile index 5273da4..3a03a04 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ default: help -.PHONY: terraform-tflint -terraform-tflint: ## Run 'terraform-tflint' github actions with https://github.com/nektos/act - act -j terraform-tflint +.PHONY: terraform-check-tflint +terraform-check-tflint: ## Run 'terraform-check-tflint' github actions with https://github.com/nektos/act + act -j terraform-check-tflint .PHONY: check-terraform-examples terraform-check-examples: ## Run specific 'check' github actions with https://github.com/nektos/act From 42dded27a4727a91c9e183151020d4a25e1637db Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Sun, 21 Dec 2025 07:46:54 -0500 Subject: [PATCH 07/23] tflint --- terraform/aws/aws-eks-operator/outputs.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/aws/aws-eks-operator/outputs.tf b/terraform/aws/aws-eks-operator/outputs.tf index 35b44c7..fb4e214 100644 --- a/terraform/aws/aws-eks-operator/outputs.tf +++ b/terraform/aws/aws-eks-operator/outputs.tf @@ -15,10 +15,10 @@ output "tailscale_operator_namespace" { output "cmd_kubeconfig_tailscale" { description = "Command to configure kubeconfig for Tailscale access to the EKS cluster" - value = "tailscale configure kubeconfig ${local.operator_name}" + value = "tailscale configure kubeconfig ${local.operator_name}" } output "cmd_kubeconfig_aws" { description = "Command to configure kubeconfig for public access to the EKS cluster" - value = "aws eks update-kubeconfig --region ${data.aws_region.current.region} --name ${module.eks.cluster_name}" + value = "aws eks update-kubeconfig --region ${data.aws_region.current.region} --name ${module.eks.cluster_name}" } From 110e277be98505885c9117e5f799c8ebd915016f Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Sun, 21 Dec 2025 09:22:56 -0500 Subject: [PATCH 08/23] address more copilot feedback --- terraform/aws/aws-eks-operator/README.md | 2 +- terraform/aws/aws-eks-operator/main.tf | 2 +- terraform/aws/aws-eks-operator/outputs.tf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/terraform/aws/aws-eks-operator/README.md b/terraform/aws/aws-eks-operator/README.md index 7331d6a..c8feed8 100644 --- a/terraform/aws/aws-eks-operator/README.md +++ b/terraform/aws/aws-eks-operator/README.md @@ -1,4 +1,4 @@ -# aws-eks-tailscale-operator +# aws-eks-operator This example creates the following: diff --git a/terraform/aws/aws-eks-operator/main.tf b/terraform/aws/aws-eks-operator/main.tf index 37a13c8..ba6d6dd 100644 --- a/terraform/aws/aws-eks-operator/main.tf +++ b/terraform/aws/aws-eks-operator/main.tf @@ -17,7 +17,7 @@ locals { min_size = 1 # Tailscale Operator configuration - operator_name = "${local.name}-operator" + operator_name = local.name operator_version = "1.92.4" tailscale_oauth_client_id = var.tailscale_oauth_client_id tailscale_oauth_client_secret = var.tailscale_oauth_client_secret diff --git a/terraform/aws/aws-eks-operator/outputs.tf b/terraform/aws/aws-eks-operator/outputs.tf index fb4e214..74d246f 100644 --- a/terraform/aws/aws-eks-operator/outputs.tf +++ b/terraform/aws/aws-eks-operator/outputs.tf @@ -15,7 +15,7 @@ output "tailscale_operator_namespace" { output "cmd_kubeconfig_tailscale" { description = "Command to configure kubeconfig for Tailscale access to the EKS cluster" - value = "tailscale configure kubeconfig ${local.operator_name}" + value = "tailscale configure kubeconfig ${helm_release.tailscale_operator.name}" } output "cmd_kubeconfig_aws" { From 657078d85fc344d1a8381627752379daf748257a Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Sun, 21 Dec 2025 11:04:59 -0500 Subject: [PATCH 09/23] move ha proxy to external kubectl manifest, and various cleanup --- terraform/aws/aws-eks-operator/README.md | 4 +++ terraform/aws/aws-eks-operator/main.tf | 36 +++---------------- terraform/aws/aws-eks-operator/outputs.tf | 10 ++++++ .../tailscale-api-server-ha-proxy.yaml | 10 ++++++ 4 files changed, 29 insertions(+), 31 deletions(-) create mode 100644 terraform/aws/aws-eks-operator/tailscale-api-server-ha-proxy.yaml diff --git a/terraform/aws/aws-eks-operator/README.md b/terraform/aws/aws-eks-operator/README.md index c8feed8..0cbe3fe 100644 --- a/terraform/aws/aws-eks-operator/README.md +++ b/terraform/aws/aws-eks-operator/README.md @@ -42,6 +42,8 @@ tailscale_oauth_client_secret = "your-oauth-client-secret" ```shell terraform init terraform apply + +# execute the output from `terraform output cmd_kubectl_ha_proxy_apply` to deploy the HA proxy ``` #### Verify deployment @@ -74,5 +76,7 @@ kubectl get pods -n tailscale ## To destroy ```shell +# execute the output from `terraform output cmd_kubectl_ha_proxy_delete` to delete the HA proxy + terraform destroy ``` diff --git a/terraform/aws/aws-eks-operator/main.tf b/terraform/aws/aws-eks-operator/main.tf index ba6d6dd..5e35711 100644 --- a/terraform/aws/aws-eks-operator/main.tf +++ b/terraform/aws/aws-eks-operator/main.tf @@ -5,7 +5,7 @@ locals { Name = local.name } - // Modify these to use your own VPC + # Modify these to use your own VPC vpc_id = module.vpc.vpc_id subnet_ids = module.vpc.private_subnets @@ -17,13 +17,14 @@ locals { min_size = 1 # Tailscale Operator configuration + namespace_name = "tailscale" operator_name = local.name operator_version = "1.92.4" tailscale_oauth_client_id = var.tailscale_oauth_client_id tailscale_oauth_client_secret = var.tailscale_oauth_client_secret } -// Remove this to use your own VPC. +# Remove this to use your own VPC. module "vpc" { source = "../internal-modules/aws-vpc" @@ -50,7 +51,7 @@ module "eks" { } # Once the Tailscale operator is installed, `endpoint_public_access` can be disabled. - # This is left enabled for the sake of easy adoption. + # This is left enabled for the sake of easy adoption. endpoint_public_access = true # Optional: Adds the current caller identity as an administrator via cluster access entry @@ -77,7 +78,7 @@ module "eks" { # Kubernetes namespace for Tailscale operator resource "kubernetes_namespace_v1" "tailscale_operator" { metadata { - name = "tailscale" + name = local.namespace_name labels = { "pod-security.kubernetes.io/enforce" = "privileged" } @@ -119,30 +120,3 @@ resource "helm_release" "tailscale_operator" { }, ] } - -# TODO: get working on first apply? -# locals { -# # TODO: inline/simplify? -# yaml_tailscale_operator_ha_proxy = <<-EOT -# apiVersion: tailscale.com/v1alpha1 -# kind: ProxyGroup -# metadata: -# name: ${helm_release.tailscale_operator.name}-ha -# spec: -# type: kube-apiserver -# replicas: 2 -# tags: ["tag:k8s"] -# kubeAPIServer: -# mode: auth -# EOT -# } - -# resource "kubernetes_manifest" "tailscale_operator_ha_proxy" { -# manifest = yamldecode(local.yaml_tailscale_operator_ha_proxy) - -# depends_on = [ -# module.eks.cluster_endpoint, # TODO: remove? -# helm_release.tailscale_operator, -# kubernetes_namespace_v1.tailscale_operator, -# ] -# } diff --git a/terraform/aws/aws-eks-operator/outputs.tf b/terraform/aws/aws-eks-operator/outputs.tf index 74d246f..b767e95 100644 --- a/terraform/aws/aws-eks-operator/outputs.tf +++ b/terraform/aws/aws-eks-operator/outputs.tf @@ -22,3 +22,13 @@ output "cmd_kubeconfig_aws" { description = "Command to configure kubeconfig for public access to the EKS cluster" value = "aws eks update-kubeconfig --region ${data.aws_region.current.region} --name ${module.eks.cluster_name}" } + +output "cmd_kubectl_ha_proxy_apply" { + description = "Command to deploy the Tailscale high availability API server proxy - https://tailscale.com/kb/1437/kubernetes-operator-api-server-proxy#configuring-a-high-availability-api-server-proxy" + value = "OPERATOR_NAME=${helm_release.tailscale_operator.name} envsubst < tailscale-api-server-ha-proxy.yaml | kubectl apply -f -" +} + +output "cmd_kubectl_ha_proxy_delete" { + description = "Command to delete the Tailscale high availability API server proxy - https://tailscale.com/kb/1437/kubernetes-operator-api-server-proxy#configuring-a-high-availability-api-server-proxy" + value = "OPERATOR_NAME=${helm_release.tailscale_operator.name} envsubst < tailscale-api-server-ha-proxy.yaml | kubectl delete -f -" +} diff --git a/terraform/aws/aws-eks-operator/tailscale-api-server-ha-proxy.yaml b/terraform/aws/aws-eks-operator/tailscale-api-server-ha-proxy.yaml new file mode 100644 index 0000000..0e6c824 --- /dev/null +++ b/terraform/aws/aws-eks-operator/tailscale-api-server-ha-proxy.yaml @@ -0,0 +1,10 @@ +apiVersion: tailscale.com/v1alpha1 +kind: ProxyGroup +metadata: + name: ${OPERATOR_NAME}-ha +spec: + type: kube-apiserver + replicas: 2 + tags: ["tag:k8s"] + kubeAPIServer: + mode: auth From 431d846c260228fc60a701e46df5622cf431af80 Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Sun, 21 Dec 2025 14:26:18 -0500 Subject: [PATCH 10/23] add 'terraform-fmt' to Makefile --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 3a03a04..669f282 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,10 @@ terraform-check-examples: ## Run specific 'check' github actions with https://gi act -j terraform-check-fmt act -j terraform-check-variables-tailscale-install-scripts +.PHONY: terraform-fmt +terraform-fmt: ## Run 'terraform-fmt' github actions with https://github.com/nektos/act + terraform fmt -recursive + .PHONY: help help: ## Display this information. Default target. @echo "Valid targets:" From 9c9b5c9286ff4fb1a0e2f03961d11c697d0a7a7c Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Sun, 21 Dec 2025 17:47:04 -0500 Subject: [PATCH 11/23] add operator_name output --- terraform/aws/aws-eks-operator/README.md | 6 ++---- terraform/aws/aws-eks-operator/outputs.tf | 7 ++++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/terraform/aws/aws-eks-operator/README.md b/terraform/aws/aws-eks-operator/README.md index 0cbe3fe..438fc05 100644 --- a/terraform/aws/aws-eks-operator/README.md +++ b/terraform/aws/aws-eks-operator/README.md @@ -42,8 +42,6 @@ tailscale_oauth_client_secret = "your-oauth-client-secret" ```shell terraform init terraform apply - -# execute the output from `terraform output cmd_kubectl_ha_proxy_apply` to deploy the HA proxy ``` #### Verify deployment @@ -76,7 +74,7 @@ kubectl get pods -n tailscale ## To destroy ```shell -# execute the output from `terraform output cmd_kubectl_ha_proxy_delete` to delete the HA proxy - terraform destroy + +# remove leftover Tailscale devices at https://login.tailscale.com/admin/machines ``` diff --git a/terraform/aws/aws-eks-operator/outputs.tf b/terraform/aws/aws-eks-operator/outputs.tf index b767e95..3a58275 100644 --- a/terraform/aws/aws-eks-operator/outputs.tf +++ b/terraform/aws/aws-eks-operator/outputs.tf @@ -8,11 +8,16 @@ output "cluster_name" { value = module.eks.cluster_name } -output "tailscale_operator_namespace" { +output "operator_namespace" { description = "Kubernetes namespace where Tailscale operator is deployed" value = kubernetes_namespace_v1.tailscale_operator.metadata[0].name } +output "operator_name" { + description = "Configured name of the Tailscale operator" + value = helm_release.tailscale_operator.name +} + output "cmd_kubeconfig_tailscale" { description = "Command to configure kubeconfig for Tailscale access to the EKS cluster" value = "tailscale configure kubeconfig ${helm_release.tailscale_operator.name}" From e51384a2be5d8b4cc0d80f24290e9e1a44ece10a Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Sun, 21 Dec 2025 17:47:37 -0500 Subject: [PATCH 12/23] provision ha proxy with null_resource local-exec provisioner --- terraform/aws/aws-eks-operator/main.tf | 47 ++++++++++++++++++++-- terraform/aws/aws-eks-operator/versions.tf | 6 +++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/terraform/aws/aws-eks-operator/main.tf b/terraform/aws/aws-eks-operator/main.tf index 5e35711..05cd9cb 100644 --- a/terraform/aws/aws-eks-operator/main.tf +++ b/terraform/aws/aws-eks-operator/main.tf @@ -62,8 +62,6 @@ module "eks" { eks_managed_node_groups = { main = { - # Starting on 1.30, AL2023 is the default AMI type for EKS managed node groups - # ami_type = "AL2023_x86_64_STANDARD" instance_types = [local.node_instance_type] desired_size = local.desired_size @@ -75,8 +73,9 @@ module "eks" { tags = local.aws_tags } -# Kubernetes namespace for Tailscale operator resource "kubernetes_namespace_v1" "tailscale_operator" { + provider = kubernetes.this + metadata { name = local.namespace_name labels = { @@ -86,6 +85,8 @@ resource "kubernetes_namespace_v1" "tailscale_operator" { } resource "helm_release" "tailscale_operator" { + provider = helm.this + name = local.operator_name namespace = kubernetes_namespace_v1.tailscale_operator.metadata[0].name @@ -119,4 +120,44 @@ resource "helm_release" "tailscale_operator" { value = local.tailscale_oauth_client_secret }, ] + + depends_on = [ + module.eks, + ] +} + +resource "null_resource" "kubectl_ha_proxy" { + count = 1 # Change to 0 to destroy. Commenting or removing the resource will not run the destroy provisioners. + triggers = { + region = data.aws_region.current.region + cluster_arn = module.eks.cluster_arn + cluster_name = module.eks.cluster_name + operator_name = helm_release.tailscale_operator.name + } + + # + # Create provisioners + # + provisioner "local-exec" { + command = "aws eks update-kubeconfig --region ${self.triggers.region} --name ${self.triggers.cluster_name}" + } + provisioner "local-exec" { + command = "OPERATOR_NAME=${self.triggers.operator_name} envsubst < tailscale-api-server-ha-proxy.yaml | kubectl apply --context=${self.triggers.cluster_arn} -f -" + } + + # + # Destroy provisioners + # + provisioner "local-exec" { + when = destroy + command = "aws eks update-kubeconfig --region ${self.triggers.region} --name ${self.triggers.cluster_name}" + } + provisioner "local-exec" { + when = destroy + command = "OPERATOR_NAME=${self.triggers.operator_name} envsubst < tailscale-api-server-ha-proxy.yaml | kubectl delete --context=${self.triggers.cluster_arn} -f -" + } + + depends_on = [ + helm_release.tailscale_operator, + ] } diff --git a/terraform/aws/aws-eks-operator/versions.tf b/terraform/aws/aws-eks-operator/versions.tf index 237f639..482be23 100644 --- a/terraform/aws/aws-eks-operator/versions.tf +++ b/terraform/aws/aws-eks-operator/versions.tf @@ -14,10 +14,15 @@ terraform { source = "hashicorp/helm" version = ">= 3.1.1, < 4.0" } + null = { + source = "hashicorp/null" + version = ">= 3.2.0, < 4.0" + } } } provider "kubernetes" { + alias = "this" host = module.eks.cluster_endpoint cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) @@ -29,6 +34,7 @@ provider "kubernetes" { } provider "helm" { + alias = "this" kubernetes = { host = module.eks.cluster_endpoint cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) From 6ac045228dcda921996e822ebd302e7167568dc7 Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Sun, 21 Dec 2025 22:34:34 -0500 Subject: [PATCH 13/23] cleanup --- terraform/aws/aws-eks-operator/README.md | 4 ++++ terraform/aws/aws-eks-operator/main.tf | 12 ++++++++++-- .../tailscale-api-server-ha-proxy.yaml | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/terraform/aws/aws-eks-operator/README.md b/terraform/aws/aws-eks-operator/README.md index 438fc05..c4480b8 100644 --- a/terraform/aws/aws-eks-operator/README.md +++ b/terraform/aws/aws-eks-operator/README.md @@ -78,3 +78,7 @@ terraform destroy # remove leftover Tailscale devices at https://login.tailscale.com/admin/machines ``` + +## Limitations + +- The [HA API server proxy](https://tailscale.com/kb/1437/kubernetes-operator-api-server-proxy#configuring-a-high-availability-api-server-proxy) is deployed using a [terraform null_resource](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) instead of [kubernetes_manifest](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest.html) due to a Terraform limitation that results in `cannot create REST client: no client config` errors on first run. diff --git a/terraform/aws/aws-eks-operator/main.tf b/terraform/aws/aws-eks-operator/main.tf index 05cd9cb..23af13b 100644 --- a/terraform/aws/aws-eks-operator/main.tf +++ b/terraform/aws/aws-eks-operator/main.tf @@ -62,6 +62,7 @@ module "eks" { eks_managed_node_groups = { main = { + name = local.name instance_types = [local.node_instance_type] desired_size = local.desired_size @@ -84,6 +85,9 @@ resource "kubernetes_namespace_v1" "tailscale_operator" { } } +# +# https://tailscale.com/kb/1236/kubernetes-operator#helm +# resource "helm_release" "tailscale_operator" { provider = helm.this @@ -126,6 +130,9 @@ resource "helm_release" "tailscale_operator" { ] } +# +# https://tailscale.com/kb/1437/kubernetes-operator-api-server-proxy#configuring-a-high-availability-api-server-proxy +# resource "null_resource" "kubectl_ha_proxy" { count = 1 # Change to 0 to destroy. Commenting or removing the resource will not run the destroy provisioners. triggers = { @@ -142,7 +149,7 @@ resource "null_resource" "kubectl_ha_proxy" { command = "aws eks update-kubeconfig --region ${self.triggers.region} --name ${self.triggers.cluster_name}" } provisioner "local-exec" { - command = "OPERATOR_NAME=${self.triggers.operator_name} envsubst < tailscale-api-server-ha-proxy.yaml | kubectl apply --context=${self.triggers.cluster_arn} -f -" + command = "OPERATOR_NAME=${self.triggers.operator_name} envsubst < ${path.module}/tailscale-api-server-ha-proxy.yaml | kubectl apply --context=${self.triggers.cluster_arn} -f -" } # @@ -154,10 +161,11 @@ resource "null_resource" "kubectl_ha_proxy" { } provisioner "local-exec" { when = destroy - command = "OPERATOR_NAME=${self.triggers.operator_name} envsubst < tailscale-api-server-ha-proxy.yaml | kubectl delete --context=${self.triggers.cluster_arn} -f -" + command = "OPERATOR_NAME=${self.triggers.operator_name} envsubst < ${path.module}/tailscale-api-server-ha-proxy.yaml | kubectl delete --context=${self.triggers.cluster_arn} -f -" } depends_on = [ + module.vpc, # prevent network changes before this finishes during a destroy helm_release.tailscale_operator, ] } diff --git a/terraform/aws/aws-eks-operator/tailscale-api-server-ha-proxy.yaml b/terraform/aws/aws-eks-operator/tailscale-api-server-ha-proxy.yaml index 0e6c824..582abe7 100644 --- a/terraform/aws/aws-eks-operator/tailscale-api-server-ha-proxy.yaml +++ b/terraform/aws/aws-eks-operator/tailscale-api-server-ha-proxy.yaml @@ -1,3 +1,4 @@ +# https://tailscale.com/kb/1437/kubernetes-operator-api-server-proxy#configuring-a-high-availability-api-server-proxy apiVersion: tailscale.com/v1alpha1 kind: ProxyGroup metadata: From 0fadf5b1dc3eba261a3b1d5d09b0138f1eed5ec4 Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Mon, 22 Dec 2025 15:55:13 -0800 Subject: [PATCH 14/23] Update Makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 669f282..0066e16 100644 --- a/Makefile +++ b/Makefile @@ -4,14 +4,14 @@ default: help terraform-check-tflint: ## Run 'terraform-check-tflint' github actions with https://github.com/nektos/act act -j terraform-check-tflint -.PHONY: check-terraform-examples +.PHONY: terraform-check-examples terraform-check-examples: ## Run specific 'check' github actions with https://github.com/nektos/act act -j terraform-check-fmt act -j terraform-check-variables-tailscale-install-scripts .PHONY: terraform-fmt terraform-fmt: ## Run 'terraform-fmt' github actions with https://github.com/nektos/act - terraform fmt -recursive + @terraform fmt -recursive .PHONY: help help: ## Display this information. Default target. From ea8dc027ca05d2aecd096286f39c7ca2f552a8ca Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Mon, 22 Dec 2025 16:16:36 -0800 Subject: [PATCH 15/23] use a random suffix for ha proxy name --- terraform/aws/aws-eks-operator/README.md | 2 +- terraform/aws/aws-eks-operator/main.tf | 30 ++++++++++++------- .../tailscale-api-server-ha-proxy.yaml | 2 +- terraform/aws/aws-eks-operator/versions.tf | 12 +++++--- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/terraform/aws/aws-eks-operator/README.md b/terraform/aws/aws-eks-operator/README.md index c4480b8..a627a38 100644 --- a/terraform/aws/aws-eks-operator/README.md +++ b/terraform/aws/aws-eks-operator/README.md @@ -76,7 +76,7 @@ kubectl get pods -n tailscale ```shell terraform destroy -# remove leftover Tailscale devices at https://login.tailscale.com/admin/machines +# remove leftover Tailscale devices at https://login.tailscale.com/admin/machines and services at https://login.tailscale.com/admin/services ``` ## Limitations diff --git a/terraform/aws/aws-eks-operator/main.tf b/terraform/aws/aws-eks-operator/main.tf index 23af13b..2139507 100644 --- a/terraform/aws/aws-eks-operator/main.tf +++ b/terraform/aws/aws-eks-operator/main.tf @@ -18,10 +18,20 @@ locals { # Tailscale Operator configuration namespace_name = "tailscale" - operator_name = local.name + operator_name = "${local.name}-${random_string.operator_name_suffix.result}" operator_version = "1.92.4" tailscale_oauth_client_id = var.tailscale_oauth_client_id tailscale_oauth_client_secret = var.tailscale_oauth_client_secret + + ha_proxy_service_name = "${helm_release.tailscale_operator.name}-ha" +} + +# This isn't required but helps avoid Let's Encrypt throttling to make testing and iterating easier. +resource "random_string" "operator_name_suffix" { + length = 3 + numeric = false + special = false + upper = false } # Remove this to use your own VPC. @@ -39,6 +49,8 @@ module "eks" { name = local.name kubernetes_version = local.cluster_version + tags = local.aws_tags + addons = { coredns = {} eks-pod-identity-agent = { @@ -62,7 +74,7 @@ module "eks" { eks_managed_node_groups = { main = { - name = local.name + name = "${substr(local.name, 0, 20)}" instance_types = [local.node_instance_type] desired_size = local.desired_size @@ -70,8 +82,6 @@ module "eks" { min_size = local.min_size } } - - tags = local.aws_tags } resource "kubernetes_namespace_v1" "tailscale_operator" { @@ -136,10 +146,10 @@ resource "helm_release" "tailscale_operator" { resource "null_resource" "kubectl_ha_proxy" { count = 1 # Change to 0 to destroy. Commenting or removing the resource will not run the destroy provisioners. triggers = { - region = data.aws_region.current.region - cluster_arn = module.eks.cluster_arn - cluster_name = module.eks.cluster_name - operator_name = helm_release.tailscale_operator.name + region = data.aws_region.current.region + cluster_arn = module.eks.cluster_arn + cluster_name = module.eks.cluster_name + ha_proxy_service_name = local.ha_proxy_service_name } # @@ -149,7 +159,7 @@ resource "null_resource" "kubectl_ha_proxy" { command = "aws eks update-kubeconfig --region ${self.triggers.region} --name ${self.triggers.cluster_name}" } provisioner "local-exec" { - command = "OPERATOR_NAME=${self.triggers.operator_name} envsubst < ${path.module}/tailscale-api-server-ha-proxy.yaml | kubectl apply --context=${self.triggers.cluster_arn} -f -" + command = "HA_PROXY_SERVICE_NAME=${self.triggers.ha_proxy_service_name} envsubst < ${path.module}/tailscale-api-server-ha-proxy.yaml | kubectl apply --context=${self.triggers.cluster_arn} -f -" } # @@ -161,7 +171,7 @@ resource "null_resource" "kubectl_ha_proxy" { } provisioner "local-exec" { when = destroy - command = "OPERATOR_NAME=${self.triggers.operator_name} envsubst < ${path.module}/tailscale-api-server-ha-proxy.yaml | kubectl delete --context=${self.triggers.cluster_arn} -f -" + command = "HA_PROXY_SERVICE_NAME=${self.triggers.ha_proxy_service_name} envsubst < ${path.module}/tailscale-api-server-ha-proxy.yaml | kubectl delete --context=${self.triggers.cluster_arn} -f -" } depends_on = [ diff --git a/terraform/aws/aws-eks-operator/tailscale-api-server-ha-proxy.yaml b/terraform/aws/aws-eks-operator/tailscale-api-server-ha-proxy.yaml index 582abe7..b909300 100644 --- a/terraform/aws/aws-eks-operator/tailscale-api-server-ha-proxy.yaml +++ b/terraform/aws/aws-eks-operator/tailscale-api-server-ha-proxy.yaml @@ -2,7 +2,7 @@ apiVersion: tailscale.com/v1alpha1 kind: ProxyGroup metadata: - name: ${OPERATOR_NAME}-ha + name: ${HA_PROXY_SERVICE_NAME} spec: type: kube-apiserver replicas: 2 diff --git a/terraform/aws/aws-eks-operator/versions.tf b/terraform/aws/aws-eks-operator/versions.tf index 482be23..c6d46e1 100644 --- a/terraform/aws/aws-eks-operator/versions.tf +++ b/terraform/aws/aws-eks-operator/versions.tf @@ -6,18 +6,22 @@ terraform { source = "hashicorp/aws" version = ">= 6.0, < 7.0" } - kubernetes = { - source = "hashicorp/kubernetes" - version = ">= 3.0.1, < 4.0" - } helm = { source = "hashicorp/helm" version = ">= 3.1.1, < 4.0" } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 3.0.1, < 4.0" + } null = { source = "hashicorp/null" version = ">= 3.2.0, < 4.0" } + random = { + source = "hashicorp/random" + version = ">= 3.0, < 4.0" + } } } From d0b167260de9d8e53d54e806fa08ccf4f01f5167 Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Mon, 22 Dec 2025 16:19:08 -0800 Subject: [PATCH 16/23] fix tflint issue --- terraform/aws/aws-eks-operator/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/aws/aws-eks-operator/main.tf b/terraform/aws/aws-eks-operator/main.tf index 2139507..e953779 100644 --- a/terraform/aws/aws-eks-operator/main.tf +++ b/terraform/aws/aws-eks-operator/main.tf @@ -74,7 +74,7 @@ module "eks" { eks_managed_node_groups = { main = { - name = "${substr(local.name, 0, 20)}" + name = substr(local.name, 0, 20) instance_types = [local.node_instance_type] desired_size = local.desired_size From d0fdb41ba750e13c014343de1a1c945561d41a9a Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Tue, 23 Dec 2025 06:42:47 -0800 Subject: [PATCH 17/23] use aws_eks_cluster_versions data source for latest version --- terraform/aws/aws-eks-operator/data.tf | 4 ++++ terraform/aws/aws-eks-operator/main.tf | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/terraform/aws/aws-eks-operator/data.tf b/terraform/aws/aws-eks-operator/data.tf index 2502393..6e53d02 100644 --- a/terraform/aws/aws-eks-operator/data.tf +++ b/terraform/aws/aws-eks-operator/data.tf @@ -1 +1,5 @@ data "aws_region" "current" {} + +data "aws_eks_cluster_versions" "latest" { + default_only = true +} diff --git a/terraform/aws/aws-eks-operator/main.tf b/terraform/aws/aws-eks-operator/main.tf index e953779..7937ec3 100644 --- a/terraform/aws/aws-eks-operator/main.tf +++ b/terraform/aws/aws-eks-operator/main.tf @@ -10,7 +10,7 @@ locals { subnet_ids = module.vpc.private_subnets # EKS cluster configuration - cluster_version = "1.34" # TODO: omit this? + cluster_version = data.aws_eks_cluster_versions.latest.cluster_versions[0].cluster_version node_instance_type = "t3.medium" desired_size = 2 max_size = 2 @@ -26,7 +26,7 @@ locals { ha_proxy_service_name = "${helm_release.tailscale_operator.name}-ha" } -# This isn't required but helps avoid Let's Encrypt throttling to make testing and iterating easier. +# This isn't required but helps avoid conflicts and Let's Encrypt throttling to make testing and iterating easier. resource "random_string" "operator_name_suffix" { length = 3 numeric = false From e3a880ca61f5e95fa49ff2a2079ed3a5d529c9b8 Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Tue, 23 Dec 2025 07:10:47 -0800 Subject: [PATCH 18/23] update prerequisites --- terraform/aws/aws-eks-operator/README.md | 3 +++ terraform/aws/aws-eks-operator/main.tf | 3 +++ 2 files changed, 6 insertions(+) diff --git a/terraform/aws/aws-eks-operator/README.md b/terraform/aws/aws-eks-operator/README.md index a627a38..f047ca7 100644 --- a/terraform/aws/aws-eks-operator/README.md +++ b/terraform/aws/aws-eks-operator/README.md @@ -6,6 +6,7 @@ This example creates the following: - an EKS cluster with a managed node group - a Kubernetes namespace for the [Tailscale operator](https://tailscale.com/kb/1236/kubernetes-operator) - the Tailscale Kubernetes Operator deployed via [Helm](https://tailscale.com/kb/1236/kubernetes-operator#helm) +- a [high availability API server proxy](https://tailscale.com/kb/1437/kubernetes-operator-api-server-proxy#configuring-a-high-availability-api-server-proxy) ## Considerations @@ -16,6 +17,8 @@ This example creates the following: ## Prerequisites +- The configuration as-is uses currently only works on macOS or Linux clients. Remove or comment out the `null_resource` provisioners that deploy `tailscale-api-server-ha-proxy.yaml` for the [high availability API server proxy](https://tailscale.com/kb/1437/kubernetes-operator-api-server-proxy#configuring-a-high-availability-api-server-proxy) to run from other platforms. +- Requires the [AWS CLI](https://aws.amazon.com/cli/) for initial authentication to the created AWS EKS cluster. - Create a [Tailscale OAuth Client](https://tailscale.com/kb/1215/oauth-clients#setting-up-an-oauth-client) with appropriate scopes - Ensure you have AWS CLI configured with appropriate permissions for EKS - Install `kubectl` for cluster access after deployment diff --git a/terraform/aws/aws-eks-operator/main.tf b/terraform/aws/aws-eks-operator/main.tf index 7937ec3..ea40fe8 100644 --- a/terraform/aws/aws-eks-operator/main.tf +++ b/terraform/aws/aws-eks-operator/main.tf @@ -143,6 +143,9 @@ resource "helm_release" "tailscale_operator" { # # https://tailscale.com/kb/1437/kubernetes-operator-api-server-proxy#configuring-a-high-availability-api-server-proxy # +# Remove or comment out the `null_resource` provisioners that deploy `tailscale-api-server-ha-proxy.yaml` for the +# high availability API server proxy to run from other platforms. +# resource "null_resource" "kubectl_ha_proxy" { count = 1 # Change to 0 to destroy. Commenting or removing the resource will not run the destroy provisioners. triggers = { From 835bf8f7602e645ba72bb5a66546d10ea6554bcf Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Tue, 23 Dec 2025 07:18:26 -0800 Subject: [PATCH 19/23] add enable_ha_proxy_service local variable --- Makefile | 6 +++--- terraform/aws/aws-eks-operator/main.tf | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 0066e16..3f2ad78 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,16 @@ default: help .PHONY: terraform-check-tflint -terraform-check-tflint: ## Run 'terraform-check-tflint' github actions with https://github.com/nektos/act +terraform-check-tflint: ## Run 'terraform-check-tflint' workflow with https://github.com/nektos/act act -j terraform-check-tflint .PHONY: terraform-check-examples -terraform-check-examples: ## Run specific 'check' github actions with https://github.com/nektos/act +terraform-check-examples: ## Run specific 'check' workflow with https://github.com/nektos/act act -j terraform-check-fmt act -j terraform-check-variables-tailscale-install-scripts .PHONY: terraform-fmt -terraform-fmt: ## Run 'terraform-fmt' github actions with https://github.com/nektos/act +terraform-fmt: ## Run 'terraform-fmt' workflow with https://github.com/nektos/act @terraform fmt -recursive .PHONY: help diff --git a/terraform/aws/aws-eks-operator/main.tf b/terraform/aws/aws-eks-operator/main.tf index ea40fe8..ba2084e 100644 --- a/terraform/aws/aws-eks-operator/main.tf +++ b/terraform/aws/aws-eks-operator/main.tf @@ -23,7 +23,8 @@ locals { tailscale_oauth_client_id = var.tailscale_oauth_client_id tailscale_oauth_client_secret = var.tailscale_oauth_client_secret - ha_proxy_service_name = "${helm_release.tailscale_operator.name}-ha" + enable_ha_proxy_service = true + ha_proxy_service_name = "${helm_release.tailscale_operator.name}-ha" } # This isn't required but helps avoid conflicts and Let's Encrypt throttling to make testing and iterating easier. @@ -147,7 +148,8 @@ resource "helm_release" "tailscale_operator" { # high availability API server proxy to run from other platforms. # resource "null_resource" "kubectl_ha_proxy" { - count = 1 # Change to 0 to destroy. Commenting or removing the resource will not run the destroy provisioners. + count = local.enable_ha_proxy_service ? 1 : 0 + triggers = { region = data.aws_region.current.region cluster_arn = module.eks.cluster_arn From 2982944bb09c719f3c1004253a4afd93338b8af5 Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Tue, 23 Dec 2025 07:35:56 -0800 Subject: [PATCH 20/23] remove beta from api_version for aws auth call --- terraform/aws/aws-eks-operator/versions.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/aws/aws-eks-operator/versions.tf b/terraform/aws/aws-eks-operator/versions.tf index c6d46e1..7e7a690 100644 --- a/terraform/aws/aws-eks-operator/versions.tf +++ b/terraform/aws/aws-eks-operator/versions.tf @@ -31,7 +31,7 @@ provider "kubernetes" { cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) exec { - api_version = "client.authentication.k8s.io/v1beta1" + api_version = "client.authentication.k8s.io/v1" command = "aws" args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] } @@ -44,7 +44,7 @@ provider "helm" { cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) exec = { - api_version = "client.authentication.k8s.io/v1beta1" + api_version = "client.authentication.k8s.io/v1" command = "aws" args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] } From 3ac53c25b39d87837e54a89cd292906c2bbd5c9c Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Tue, 23 Dec 2025 08:51:48 -0800 Subject: [PATCH 21/23] use a data source instead of aws cli --- terraform/aws/aws-eks-operator/README.md | 14 ++++++-------- terraform/aws/aws-eks-operator/data.tf | 4 ++++ terraform/aws/aws-eks-operator/main.tf | 14 ++++++++------ terraform/aws/aws-eks-operator/versions.tf | 18 +++++------------- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/terraform/aws/aws-eks-operator/README.md b/terraform/aws/aws-eks-operator/README.md index f047ca7..3d75554 100644 --- a/terraform/aws/aws-eks-operator/README.md +++ b/terraform/aws/aws-eks-operator/README.md @@ -17,19 +17,17 @@ This example creates the following: ## Prerequisites -- The configuration as-is uses currently only works on macOS or Linux clients. Remove or comment out the `null_resource` provisioners that deploy `tailscale-api-server-ha-proxy.yaml` for the [high availability API server proxy](https://tailscale.com/kb/1437/kubernetes-operator-api-server-proxy#configuring-a-high-availability-api-server-proxy) to run from other platforms. -- Requires the [AWS CLI](https://aws.amazon.com/cli/) for initial authentication to the created AWS EKS cluster. -- Create a [Tailscale OAuth Client](https://tailscale.com/kb/1215/oauth-clients#setting-up-an-oauth-client) with appropriate scopes -- Ensure you have AWS CLI configured with appropriate permissions for EKS -- Install `kubectl` for cluster access after deployment +- Follow the [Kubernetes Operator prerequisites](https://tailscale.com/kb/1236/kubernetes-operator#prerequisites). +- For the [high availability API server proxy](https://tailscale.com/kb/1437/kubernetes-operator-api-server-proxy#configuring-a-high-availability-api-server-proxy): + - The configuration as-is uses currently only works on macOS or Linux clients. Remove or comment out the `null_resource` provisioners that deploy `tailscale-api-server-ha-proxy.yaml` to run from other platforms. + - Requires the [kubectl CLI](https://kubernetes.io/docs/reference/kubectl/) and [AWS CLI](https://aws.amazon.com/cli/). + ## To use Follow the documentation to configure the Terraform providers: - [AWS](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) -- [Kubernetes](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs) -- [Helm](https://registry.terraform.io/providers/hashicorp/helm/latest/docs) ### Configure variables @@ -59,7 +57,7 @@ Check that the Tailscale operator is running: ```shell kubectl get pods -n tailscale -kubectl logs -n tailscale -l app.kubernetes.io/name=tailscale-operator +kubectl logs -n tailscale -l app.kubernetes.io/name=$(terraform output -raw operator_name) ``` #### Verify connectivity via the [API server proxy](https://tailscale.com/kb/1437/kubernetes-operator-api-server-proxy) diff --git a/terraform/aws/aws-eks-operator/data.tf b/terraform/aws/aws-eks-operator/data.tf index 6e53d02..7c1cf38 100644 --- a/terraform/aws/aws-eks-operator/data.tf +++ b/terraform/aws/aws-eks-operator/data.tf @@ -3,3 +3,7 @@ data "aws_region" "current" {} data "aws_eks_cluster_versions" "latest" { default_only = true } + +data "aws_eks_cluster_auth" "this" { + name = module.eks.cluster_name +} diff --git a/terraform/aws/aws-eks-operator/main.tf b/terraform/aws/aws-eks-operator/main.tf index ba2084e..8cd0af8 100644 --- a/terraform/aws/aws-eks-operator/main.tf +++ b/terraform/aws/aws-eks-operator/main.tf @@ -18,7 +18,7 @@ locals { # Tailscale Operator configuration namespace_name = "tailscale" - operator_name = "${local.name}-${random_string.operator_name_suffix.result}" + operator_name = "${local.name}-${random_integer.operator_name_suffix.result}" operator_version = "1.92.4" tailscale_oauth_client_id = var.tailscale_oauth_client_id tailscale_oauth_client_secret = var.tailscale_oauth_client_secret @@ -28,11 +28,9 @@ locals { } # This isn't required but helps avoid conflicts and Let's Encrypt throttling to make testing and iterating easier. -resource "random_string" "operator_name_suffix" { - length = 3 - numeric = false - special = false - upper = false +resource "random_integer" "operator_name_suffix" { + min = 100 + max = 999 } # Remove this to use your own VPC. @@ -94,6 +92,10 @@ resource "kubernetes_namespace_v1" "tailscale_operator" { "pod-security.kubernetes.io/enforce" = "privileged" } } + + depends_on = [ + module.eks, + ] } # diff --git a/terraform/aws/aws-eks-operator/versions.tf b/terraform/aws/aws-eks-operator/versions.tf index 7e7a690..d5859aa 100644 --- a/terraform/aws/aws-eks-operator/versions.tf +++ b/terraform/aws/aws-eks-operator/versions.tf @@ -26,27 +26,19 @@ terraform { } provider "kubernetes" { - alias = "this" + alias = "this" + host = module.eks.cluster_endpoint cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) - - exec { - api_version = "client.authentication.k8s.io/v1" - command = "aws" - args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] - } + token = data.aws_eks_cluster_auth.this.token } provider "helm" { alias = "this" + kubernetes = { host = module.eks.cluster_endpoint cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) - - exec = { - api_version = "client.authentication.k8s.io/v1" - command = "aws" - args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] - } + token = data.aws_eks_cluster_auth.this.token } } From 473ecfcfd36c720780c0aace0d86179bab32db5e Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Tue, 23 Dec 2025 09:45:05 -0800 Subject: [PATCH 22/23] Update terraform/aws/aws-eks-operator/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- terraform/aws/aws-eks-operator/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/aws/aws-eks-operator/README.md b/terraform/aws/aws-eks-operator/README.md index 3d75554..8fb53fc 100644 --- a/terraform/aws/aws-eks-operator/README.md +++ b/terraform/aws/aws-eks-operator/README.md @@ -19,7 +19,7 @@ This example creates the following: - Follow the [Kubernetes Operator prerequisites](https://tailscale.com/kb/1236/kubernetes-operator#prerequisites). - For the [high availability API server proxy](https://tailscale.com/kb/1437/kubernetes-operator-api-server-proxy#configuring-a-high-availability-api-server-proxy): - - The configuration as-is uses currently only works on macOS or Linux clients. Remove or comment out the `null_resource` provisioners that deploy `tailscale-api-server-ha-proxy.yaml` to run from other platforms. + - The configuration as-is uses currently only works on macOS or Linux clients. Remove or comment out the `null_resource` provisioners that deploy `tailscale-api-server-ha-proxy.yaml` to run from other platforms. - Requires the [kubectl CLI](https://kubernetes.io/docs/reference/kubectl/) and [AWS CLI](https://aws.amazon.com/cli/). From c0dbf1c122ce629e393a9759d966f9c767682de8 Mon Sep 17 00:00:00 2001 From: Cameron Stokes Date: Tue, 23 Dec 2025 09:46:21 -0800 Subject: [PATCH 23/23] Update README.md --- terraform/aws/aws-eks-operator/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/aws/aws-eks-operator/README.md b/terraform/aws/aws-eks-operator/README.md index 3d75554..1c92d3e 100644 --- a/terraform/aws/aws-eks-operator/README.md +++ b/terraform/aws/aws-eks-operator/README.md @@ -19,7 +19,7 @@ This example creates the following: - Follow the [Kubernetes Operator prerequisites](https://tailscale.com/kb/1236/kubernetes-operator#prerequisites). - For the [high availability API server proxy](https://tailscale.com/kb/1437/kubernetes-operator-api-server-proxy#configuring-a-high-availability-api-server-proxy): - - The configuration as-is uses currently only works on macOS or Linux clients. Remove or comment out the `null_resource` provisioners that deploy `tailscale-api-server-ha-proxy.yaml` to run from other platforms. + - The configuration as-is currently only works on macOS or Linux clients. Remove or comment out the `null_resource` provisioners that deploy `tailscale-api-server-ha-proxy.yaml` to run from other platforms. - Requires the [kubectl CLI](https://kubernetes.io/docs/reference/kubectl/) and [AWS CLI](https://aws.amazon.com/cli/).