diff --git a/.gitignore b/.gitignore
index 839afa9..31988be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,17 +1,22 @@
+# Other
+*.zip
+
# Terraform
-.terraform/
-.terraform.lock.hcl
+.terraform**
terraform.tfstate*
**.tfvars**
tf.plan
+# Terragrunt
+backend.tf
+provider.tf
+
# Helm + Kubernetes
infra/aws/us-east-2/apps/coder-ws/experiment/prometheus.yaml
infra/aws/us-east-2/apps/coder-devel/build-and-push
infra/aws/us-east-2/apps/cert-manager
coder-observability/
observability/
-charts/
secrets/
# Python
diff --git a/coder/agent-providers.tf b/coder/agent-providers.tf
new file mode 100644
index 0000000..2fcaae7
--- /dev/null
+++ b/coder/agent-providers.tf
@@ -0,0 +1,466 @@
+##
+# Agents Provider Configuration
+##
+
+locals {
+ credential_age = 30
+}
+
+resource "time_rotating" "bedrock" {
+ rotation_days = local.credential_age
+}
+
+resource "aws_iam_user" "agent" {
+ name = "coder-bedrock-provider"
+ path = "/${var.region}/"
+}
+
+resource "aws_iam_user_policy_attachment" "test-attach" {
+ user = aws_iam_user.agent.name
+ policy_arn = "arn:aws:iam::aws:policy/AmazonBedrockLimitedAccess"
+}
+
+resource "aws_iam_access_key" "agent" {
+ user = aws_iam_user.agent.name
+ lifecycle {
+ replace_triggered_by = [time_rotating.bedrock]
+ }
+}
+
+resource "aws_iam_service_specific_credential" "bedrock" {
+ service_name = "bedrock.amazonaws.com"
+ user_name = aws_iam_user.agent.name
+ credential_age_days = local.credential_age
+ lifecycle {
+ replace_triggered_by = [time_rotating.bedrock]
+ }
+}
+
+resource "coderd_ai_provider" "openai-gpt-oss" {
+ type = "openai"
+ name = "aws-bedrock-openai-gpt-oss"
+ display_name = "AWS Bedrock - OpenAI GPT OSS"
+ enabled = true
+ base_url = "https://bedrock-mantle.${var.region}.api.aws/v1"
+
+ api_key_wo = aws_iam_service_specific_credential.bedrock.service_credential_secret
+ api_key_wo_version = time_rotating.bedrock.unix
+}
+
+resource "coderd_ai_provider" "openai-gpt" {
+ type = "openai"
+ name = "aws-bedrock-openai-gpt"
+ display_name = "AWS Bedrock - OpenAI GPT"
+ enabled = true
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-openai-gpt-55.html#model-card-openai-gpt-55-programmatic-access
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-openai-gpt-54.html#model-card-openai-gpt-54-programmatic-access
+ base_url = "https://bedrock-mantle.${var.region}.api.aws/openai/v1"
+
+ api_key_wo = aws_iam_service_specific_credential.bedrock.service_credential_secret
+ api_key_wo_version = time_rotating.bedrock.unix
+}
+
+resource "coderd_ai_provider" "openai-compatible" {
+ type = "openai-compat"
+ name = "aws-bedrock-openai-compatible"
+ display_name = "AWS Bedrock - OpenAI Compatible"
+ enabled = true
+ base_url = "https://bedrock-mantle.${var.region}.api.aws"
+
+ api_key_wo = aws_iam_service_specific_credential.bedrock.service_credential_secret
+ api_key_wo_version = time_rotating.bedrock.unix
+}
+
+resource "coderd_ai_provider" "openai-compatible-mantle" {
+ type = "openai-compat"
+ name = "aws-bedrock-openai-mantle-compatible"
+ display_name = "AWS Bedrock Mantle - OpenAI Compatible"
+ enabled = true
+ base_url = "https://bedrock-mantle.${var.region}.api.aws/v1"
+
+ api_key_wo = aws_iam_service_specific_credential.bedrock.service_credential_secret
+ api_key_wo_version = time_rotating.bedrock.unix
+}
+
+resource "coderd_ai_provider" "openai-compatible-runtime" {
+ type = "openai-compat"
+ name = "aws-bedrock-openai-runtime-compatible"
+ display_name = "AWS Bedrock Runtime - OpenAI Compatible"
+ enabled = true
+ base_url = "https://bedrock-runtime.${var.region}.amazonaws.com/openai/v1"
+
+ api_key_wo = aws_iam_service_specific_credential.bedrock.service_credential_secret
+ api_key_wo_version = time_rotating.bedrock.unix
+}
+
+resource "coderd_ai_provider" "bedrock" {
+ type = "bedrock"
+ name = "aws-bedrock"
+ display_name = "AWS Bedrock"
+ enabled = true
+ base_url = "https://bedrock-runtime.${var.region}.amazonaws.com"
+
+ settings = {
+ bedrock = {
+ model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
+ small_fast_model = "global.anthropic.claude-haiku-4-5-20251001-v1:0"
+ access_key_wo = aws_iam_access_key.agent.id
+ access_key_secret_wo = aws_iam_access_key.agent.secret
+ credentials_wo_version = time_rotating.bedrock.unix
+ }
+ }
+}
+
+locals {
+ anthropic = {
+ "global.anthropic.claude-opus-4-8" = {
+ name = "aws-anthropic-opus-4-8"
+ display_name = "Claude Opus 4.8"
+ enabled = false
+ model_config = {
+ max_output_tokens = 8192
+ temperature = 1
+ cost = {
+ input_price_per_million_tokens = "3"
+ output_price_per_million_tokens = "15"
+ }
+ provider_options = {
+ anthropic = {}
+ }
+ }
+ }
+ "global.anthropic.claude-opus-4-7" = {
+ name = "aws-anthropic-opus-4-7"
+ display_name = "Claude Opus 4.7"
+ enabled = false
+ model_config = {
+ max_output_tokens = 8192
+ temperature = 1
+ cost = {
+ input_price_per_million_tokens = "3"
+ output_price_per_million_tokens = "15"
+ }
+ provider_options = {
+ anthropic = {}
+ }
+ }
+ }
+ "global.anthropic.claude-opus-4-6-v1" = {
+ name = "aws-anthropic-opus-4-6"
+ display_name = "Claude Opus 4.6"
+ enabled = false
+ model_config = {
+ max_output_tokens = 8192
+ temperature = 1
+ cost = {
+ input_price_per_million_tokens = "3"
+ output_price_per_million_tokens = "15"
+ }
+ provider_options = {
+ anthropic = {}
+ }
+ }
+ }
+ "global.anthropic.claude-sonnet-4-6" = {
+ name = "aws-anthropic-sonnet-4-6"
+ display_name = "Claude Sonnet 4.6"
+ model_config = {
+ max_output_tokens = 8192
+ temperature = 1
+ cost = {
+ input_price_per_million_tokens = "3"
+ output_price_per_million_tokens = "15"
+ }
+ provider_options = {
+ anthropic = {}
+ }
+ }
+ }
+ # "global.anthropic.claude-fable-5" = {
+ # name = "aws-anthropic-fable-5"
+ # display_name = "Claude Fable 5"
+ # model_config = {
+ # max_output_tokens = 8192
+ # temperature = 1
+ # cost = {
+ # input_price_per_million_tokens = "3"
+ # output_price_per_million_tokens = "15"
+ # }
+ # provider_options = {
+ # anthropic = {}
+ # }
+ # }
+ # }
+ "global.anthropic.claude-sonnet-5" = {
+ name = "aws-anthropic-sonnet-5"
+ display_name = "Claude Sonnet 5"
+ model_config = {
+ max_output_tokens = 8192
+ temperature = 1
+ cost = {
+ input_price_per_million_tokens = "3"
+ output_price_per_million_tokens = "15"
+ }
+ provider_options = {
+ anthropic = {}
+ }
+ }
+ }
+ }
+
+}
+
+resource "coderd_ai_provider" "anthropic" {
+
+ for_each = local.anthropic
+
+ type = "bedrock"
+ name = each.value.name
+ display_name = "AWS Bedrock - ${each.value.display_name}"
+ enabled = try(each.value.enabled, true)
+ base_url = "https://bedrock-runtime.${var.region}.amazonaws.com"
+
+ settings = {
+ bedrock = {
+ model = each.key
+ small_fast_model = "global.anthropic.claude-haiku-4-5-20251001-v1:0"
+ access_key_wo = aws_iam_access_key.agent.id
+ access_key_secret_wo = aws_iam_access_key.agent.secret
+ credentials_wo_version = time_rotating.bedrock.unix
+ }
+ }
+}
+
+##
+# Agent Models
+##
+
+resource "coderd_agents_model" "sonnet-4-5" {
+ ai_provider_id = coderd_ai_provider.bedrock.id
+ model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
+ display_name = "Claude Sonnet 4.5"
+ enabled = true
+ context_limit = 200000
+
+ model_config = jsonencode({
+ max_output_tokens = 8192
+ temperature = 1
+ # POST "https://bedrock-runtime.${var.region}.amazonaws.com/v1/messages": 400 Bad Request
+ # {
+ # "message":"`temperature` may only be set to 1 when thinking is enabled. Please consult our documentation at
+ # https://docs.claude.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking"
+ # }
+ cost = {
+ input_price_per_million_tokens = "3"
+ output_price_per_million_tokens = "15"
+ }
+ provider_options = {
+ anthropic = {
+ effort = "low"
+ thinking = { budget_tokens = 4096 }
+ }
+ }
+ })
+}
+
+# Mark the Sonnet model as the deployment-wide default for Coder Agents.
+# Setting a new default automatically demotes the previous one, so only a single
+# coderd_default_agents_model resource should exist per deployment.
+resource "coderd_default_agents_model" "default" {
+ model_id = coderd_agents_model.sonnet-4-5.id
+}
+
+resource "coderd_agents_model" "haiku-4-5" {
+ ai_provider_id = coderd_ai_provider.bedrock.id
+ model = "global.anthropic.claude-haiku-4-5-20251001-v1:0"
+ display_name = "Claude Haiku 4.5"
+ enabled = true
+ context_limit = 200000
+
+ model_config = jsonencode({
+ max_output_tokens = 8192
+ temperature = 1
+ cost = {
+ input_price_per_million_tokens = "3"
+ output_price_per_million_tokens = "15"
+ }
+ provider_options = {
+ anthropic = {
+ effort = "low"
+ thinking = { budget_tokens = 4096 }
+ }
+ }
+ })
+}
+
+resource "coderd_agents_model" "anthropic-models" {
+
+ for_each = local.anthropic
+
+ ai_provider_id = coderd_ai_provider.anthropic[each.key].id
+ model = each.key
+ display_name = each.value.display_name
+ enabled = true
+ context_limit = 200000
+
+ model_config = jsonencode(each.value.model_config)
+}
+
+resource "coderd_agents_model" "gpt-oss-120b" {
+ ai_provider_id = coderd_ai_provider.openai-compatible-runtime.id
+ model = "openai.gpt-oss-120b-1:0"
+ display_name = "GPT OSS 120b"
+ enabled = true
+ context_limit = 200000
+
+ model_config = jsonencode({
+ max_output_tokens = 8192
+ temperature = 0.7
+ cost = {
+ input_price_per_million_tokens = "3"
+ output_price_per_million_tokens = "15"
+ }
+ provider_options = {
+ anthropic = {
+ effort = "low"
+ thinking = { budget_tokens = 4096 }
+ }
+ }
+ })
+}
+
+resource "coderd_agents_model" "gpt-oss-20b" {
+ ai_provider_id = coderd_ai_provider.openai-compatible-runtime.id
+ model = "openai.gpt-oss-20b-1:0"
+ display_name = "GPT OSS 20b"
+ enabled = true
+ context_limit = 200000
+
+ model_config = jsonencode({
+ max_output_tokens = 8192
+ temperature = 0.7
+ cost = {
+ input_price_per_million_tokens = "3"
+ output_price_per_million_tokens = "15"
+ }
+ provider_options = {
+ anthropic = {
+ effort = "low"
+ thinking = { budget_tokens = 4096 }
+ }
+ }
+ })
+}
+
+resource "coderd_agents_model" "gpt-oss-safeguard-120b" {
+ ai_provider_id = coderd_ai_provider.openai-gpt-oss.id
+ model = "openai.gpt-oss-safeguard-120b"
+ display_name = "GPT OSS Safeguard 120b"
+ enabled = true
+ context_limit = 200000
+
+ model_config = jsonencode({
+ max_output_tokens = 8192
+ temperature = 0.7
+ cost = {
+ input_price_per_million_tokens = "3"
+ output_price_per_million_tokens = "15"
+ }
+ provider_options = {
+ anthropic = {
+ effort = "low"
+ thinking = { budget_tokens = 4096 }
+ }
+ }
+ })
+}
+
+resource "coderd_agents_model" "gpt-oss-safeguard-20b" {
+ ai_provider_id = coderd_ai_provider.openai-gpt-oss.id
+ model = "openai.gpt-oss-safeguard-20b"
+ display_name = "GPT OSS Safeguard 20b"
+ enabled = true
+ context_limit = 200000
+
+ model_config = jsonencode({
+ max_output_tokens = 8192
+ temperature = 0.7
+ cost = {
+ input_price_per_million_tokens = "3"
+ output_price_per_million_tokens = "15"
+ }
+ provider_options = {
+ anthropic = {
+ effort = "low"
+ thinking = { budget_tokens = 4096 }
+ }
+ }
+ })
+}
+
+resource "coderd_agents_model" "gpt-5-5" {
+ ai_provider_id = coderd_ai_provider.openai-gpt.id
+ model = "openai.gpt-5.5"
+ display_name = "GPT 5.5"
+ enabled = false
+ context_limit = 200000
+
+ model_config = jsonencode({
+ max_output_tokens = 8192
+ temperature = 0.7
+ cost = {
+ input_price_per_million_tokens = "3"
+ output_price_per_million_tokens = "15"
+ }
+ provider_options = {
+ openai = {
+ reasoning_effort = "low"
+ max_completion_tokens = 8192
+ parallel_tool_calls = true
+ }
+ }
+ })
+}
+
+resource "coderd_agents_model" "gpt-5-4" {
+ ai_provider_id = coderd_ai_provider.openai-gpt.id
+ model = "openai.gpt-5.4"
+ display_name = "GPT 5.4"
+ enabled = false
+ context_limit = 200000
+
+ model_config = jsonencode({
+ max_output_tokens = 8192
+ temperature = 0.7
+ cost = {
+ input_price_per_million_tokens = "3"
+ output_price_per_million_tokens = "15"
+ }
+ provider_options = {
+ openai = {
+ reasoning_effort = "low"
+ max_completion_tokens = 8192
+ parallel_tool_calls = true
+ }
+ }
+ })
+}
+
+resource "coderd_agents_model" "nova-2-lite" {
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-amazon-nova-2-lite.html#model-card-amazon-nova-2-lite-programmatic-access
+ ai_provider_id = coderd_ai_provider.openai-compatible.id
+ model = "amazon.nova-2-lite-v1:0"
+ display_name = "Nova 2 Lite"
+ enabled = false
+ context_limit = 200000
+
+ model_config = jsonencode({
+ max_output_tokens = 8192
+ temperature = 0.7
+ cost = {
+ input_price_per_million_tokens = "3"
+ output_price_per_million_tokens = "15"
+ }
+ provider_options = {}
+ })
+}
\ No newline at end of file
diff --git a/coder/main.tf b/coder/main.tf
index f9e94e2..9b6bcea 100644
--- a/coder/main.tf
+++ b/coder/main.tf
@@ -3,8 +3,11 @@ terraform {
coderd = {
source = "coder/coderd"
}
- random = {
- source = "hashicorp/random"
+ aws = {
+ source = "hashicorp/aws"
+ }
+ time = {
+ source = "hashicorp/time"
}
}
backend "s3" {}
@@ -24,11 +27,26 @@ variable "coder_username" {
sensitive = true
}
+variable "region" {
+ type = string
+ default = "us-east-2"
+}
+
+variable "profile" {
+ type = string
+ default = "demo-coder"
+}
+
provider "coderd" {
url = var.coder_primary_url
token = var.coder_token
}
+provider "aws" {
+ region = var.region
+ profile = var.profile
+}
+
module "experiment-org" {
source = "../modules/coder/org"
provisioner_key_name = "default"
diff --git a/coder/providers.tf b/coder/providers.tf
new file mode 100644
index 0000000..e69de29
diff --git a/infra/1-click/0-infra/0-vpc.tf b/infra/1-click/0-infra/0-vpc.tf
new file mode 100644
index 0000000..056f2aa
--- /dev/null
+++ b/infra/1-click/0-infra/0-vpc.tf
@@ -0,0 +1,49 @@
+##
+# Networking Infrastructure
+##
+
+locals {
+ # Subnet Discovery - https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/deploy/subnet_discovery/#subnet-filtering
+ tags_lb_subnet_discovery = {
+ "kubernetes.io/cluster/${local.formatted_name}" = "owned"
+ }
+ # Internet-Facing LB - https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/deploy/subnet_discovery/#subnet-role-tag
+ tags_public_lb = {
+ "kubernetes.io/role/elb" = 1
+ }
+ tags_public_subnet = merge(
+ local.tags_lb_subnet_discovery,
+ local.tags_public_lb
+ )
+ tags_private_subnet = {
+ "karpenter.sh/discovery" = "${local.formatted_name}"
+ }
+ # Use variable to configure AZ just in case 1 AZ isn't accessible
+ # https://repost.aws/questions/QUgdQev4KETKG_Bwev9tMtRQ/is-it-possible-to-enable-3rd-availability-zone-in-us-west-1#AN9eAH55FwTC-NSSp-FjIfTQ
+ availability_zones = [for az in var.azs : "${var.region}${az}"]
+ public_subnet_cidrs = ["10.0.8.0/21", "10.0.4.0/22", "10.0.0.0/22"]
+ private_subnet_cidrs = ["10.0.64.0/20", "10.0.80.0/20", "10.0.96.0/20"]
+}
+
+module "vpc" {
+ source = "terraform-aws-modules/vpc/aws"
+ version = "~> 6.6.0"
+
+ name = local.formatted_name
+
+ enable_nat_gateway = true
+ enable_dns_hostnames = true
+
+ cidr = "10.0.0.0/16"
+ azs = local.availability_zones
+
+ public_subnet_suffix = "public"
+ public_subnets = [for index, _ in var.azs : local.public_subnet_cidrs[index]]
+ public_subnet_tags = local.tags_public_subnet
+
+ private_subnet_suffix = "private"
+ private_subnets = [for index, _ in var.azs : local.private_subnet_cidrs[index]]
+ private_subnet_tags = local.tags_private_subnet
+
+ tags = {}
+}
\ No newline at end of file
diff --git a/infra/1-click/0-infra/1-db.tf b/infra/1-click/0-infra/1-db.tf
new file mode 100644
index 0000000..fafff67
--- /dev/null
+++ b/infra/1-click/0-infra/1-db.tf
@@ -0,0 +1,99 @@
+##
+# Database Infrastructure
+##
+
+resource "aws_security_group" "postgres" {
+ vpc_id = module.vpc.vpc_id
+ name = "${local.formatted_name}-pgsql"
+ description = "security group for postgres all egress traffic"
+ tags = {
+ Name = "PostgreSQL"
+ }
+}
+
+resource "aws_vpc_security_group_ingress_rule" "postgres" {
+ security_group_id = aws_security_group.postgres.id
+ cidr_ipv4 = module.vpc.vpc_cidr_block
+ ip_protocol = "tcp"
+ from_port = 5432
+ to_port = 5432
+}
+
+resource "aws_vpc_security_group_egress_rule" "postgres" {
+ security_group_id = aws_security_group.postgres.id
+ cidr_ipv4 = "0.0.0.0/0"
+ ip_protocol = -1
+}
+
+resource "aws_db_subnet_group" "coder" {
+ name = "${local.formatted_name}-coder"
+ subnet_ids = module.vpc.private_subnets
+
+ tags = {
+ Name = "${local.formatted_name}-coder"
+ }
+}
+
+resource "time_static" "coder_snapshot" {
+ triggers = {
+ run_on_ip_change = "${local.formatted_name}-coder"
+ }
+}
+
+resource "aws_db_instance" "coder" {
+ identifier = "${local.formatted_name}-coder"
+ instance_class = "db.t4g.medium"
+ allocated_storage = 50
+ engine = "postgres"
+ engine_version = "15.12"
+ username = var.coder_username
+ password = var.coder_password
+ db_name = "coder"
+ db_subnet_group_name = aws_db_subnet_group.coder.name
+ vpc_security_group_ids = [aws_security_group.postgres.id]
+ publicly_accessible = false
+ snapshot_identifier = null
+ skip_final_snapshot = false
+ final_snapshot_identifier = "coder-${replace(time_static.coder_snapshot.rfc3339, ":", "-")}"
+
+ tags = {
+ Name = "${local.formatted_name}-coder"
+ }
+ lifecycle {
+ ignore_changes = [
+ snapshot_identifier
+ ]
+ }
+}
+
+resource "time_static" "grafana_snapshot" {
+ triggers = {
+ run_on_ip_change = "${local.formatted_name}-grafana"
+ }
+}
+
+resource "aws_db_instance" "grafana" {
+ identifier = "${local.formatted_name}-grafana"
+ instance_class = "db.t4g.medium"
+ allocated_storage = 50
+ engine = "postgres"
+ engine_version = "15.12"
+ username = var.grafana_username
+ password = var.grafana_password
+ db_name = "grafana"
+ db_subnet_group_name = aws_db_subnet_group.coder.name
+ vpc_security_group_ids = [aws_security_group.postgres.id]
+ publicly_accessible = false
+ snapshot_identifier = null
+ skip_final_snapshot = false
+ final_snapshot_identifier = "grafana-${replace(time_static.coder_snapshot.rfc3339, ":", "-")}"
+
+ tags = {
+ Name = "${local.formatted_name}-grafana"
+ }
+ lifecycle {
+ ignore_changes = [
+ snapshot_identifier
+ ]
+ }
+}
\ No newline at end of file
diff --git a/infra/1-click/0-infra/2-s3.tf b/infra/1-click/0-infra/2-s3.tf
new file mode 100644
index 0000000..67b73c6
--- /dev/null
+++ b/infra/1-click/0-infra/2-s3.tf
@@ -0,0 +1,28 @@
+##
+# Bucket Infrastructure
+##
+
+##
+# Loki Inputs
+##
+
+resource "aws_s3_bucket" "loki" {
+ bucket = "${local.formatted_name}-grafana"
+ tags = var.loki_s3_bucket_tags
+}
+
+resource "aws_s3_bucket_lifecycle_configuration" "loki" {
+ bucket = aws_s3_bucket.loki.id
+
+ rule {
+ id = "logs"
+
+ filter {} # Target all objects in bucket.
+
+ expiration {
+ days = 365
+ }
+
+ status = "Enabled"
+ }
+}
\ No newline at end of file
diff --git a/infra/1-click/0-infra/3-eks.tf b/infra/1-click/0-infra/3-eks.tf
new file mode 100644
index 0000000..e3cd839
--- /dev/null
+++ b/infra/1-click/0-infra/3-eks.tf
@@ -0,0 +1,110 @@
+##
+# Kubernetes Infrastructure
+##
+
+locals {
+ # Karpenter Security Group Discovery - https://karpenter.sh/v1.0/concepts/nodeclasses/#specsecuritygroupselectorterms
+ tags_kptr_sg_discovery = {
+ "karpenter.sh/discovery" = "${local.formatted_name}-karpenter"
+ }
+ labels_system_node = {
+ "scheduling.coder.com/pool" = "system"
+ }
+ taints_system = {
+ key = "CriticalAddonsOnly"
+ effect = "NO_SCHEDULE"
+ }
+ tolerations_system = [{
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }]
+}
+
+module "eks" {
+ source = "terraform-aws-modules/eks/aws"
+ version = "~> 21.15.1"
+
+ vpc_id = module.vpc.vpc_id
+
+ # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_cluster#vpc_config-1
+ # https://docs.aws.amazon.com/eks/latest/userguide/network-reqs.html#network-requirements-subnets
+ subnet_ids = toset(concat(
+ module.vpc.private_subnets
+ ))
+
+ name = local.formatted_name
+
+ kubernetes_version = "1.34"
+ endpoint_public_access = true
+ endpoint_private_access = true
+
+ create_security_group = true
+ create_node_security_group = true
+ create_iam_role = true
+ node_security_group_tags = local.tags_kptr_sg_discovery
+ create_node_iam_role = true
+ node_iam_role_use_name_prefix = true
+
+ compute_config = {
+ enabled = true
+ node_pools = ["system"]
+ }
+
+ attach_encryption_policy = false
+ create_kms_key = false # Enable unless needed
+ encryption_config = null
+ enable_cluster_creator_admin_permissions = true
+ enable_irsa = true
+
+ addons = {
+ coredns = {
+ before_compute = true
+ }
+ kube-proxy = {
+ before_compute = true
+ }
+ vpc-cni = {
+ before_compute = true
+ configuration_values = jsonencode({
+ enableNetworkPolicy = "true"
+ nodeAgent = {
+ enablePolicyEventLogs = "true"
+ }
+ env = {
+ ENABLE_PREFIX_DELEGATION = "true"
+ WARM_PREFIX_TARGET = "1"
+ WARM_IP_TARGET = "0"
+ AWS_VPC_K8S_CNI_LOGLEVEL = "DEBUG"
+ }
+ })
+ }
+ }
+
+ tags = {}
+}
+
+module "karpenter" {
+
+ source = "../../../modules/k8s/bootstrap/karpenter"
+
+ cluster_name = module.eks.cluster_name
+ cluster_oidc_provider_arn = module.eks.oidc_provider_arn
+ cluster_oidc_provider = module.eks.oidc_provider
+
+ namespace = "karpenter"
+ chart_version = "1.9.0"
+
+ node_selector = {
+ "beta.kubernetes.io/os" = "linux"
+ "kubernetes.io/os" = null
+ }
+ tolerations = local.tolerations_system
+ topology_spread = []
+
+ iam_role_use_name_prefix = true
+ node_iam_role_use_name_prefix = true
+ replicas = 2
+ karpenter_controller_role_policies = {
+ "AmazonEFSCSIDriverPolicy" = "arn:aws:iam::aws:policy/service-role/AmazonEFSCSIDriverPolicy"
+ }
+}
\ No newline at end of file
diff --git a/infra/1-click/0-infra/4-bootstrap.tf b/infra/1-click/0-infra/4-bootstrap.tf
new file mode 100644
index 0000000..077fa95
--- /dev/null
+++ b/infra/1-click/0-infra/4-bootstrap.tf
@@ -0,0 +1,37 @@
+##
+# System-Level Addons
+##
+
+module "metrics-server" {
+
+ depends_on = [module.eks]
+ source = "../../../modules/k8s/bootstrap/metrics-server"
+
+ namespace = "metrics-server"
+ chart_version = "3.13.0"
+ tolerations = local.tolerations_system
+}
+
+module "lb-ctrl" {
+
+ source = "../../../modules/k8s/bootstrap/lb-controller"
+ cluster_name = module.eks.cluster_name
+ cluster_oidc_provider_arn = module.eks.oidc_provider_arn
+
+ namespace = "lb-ctrl"
+ chart_version = "3.0.0"
+ vpc_id = module.vpc.vpc_id
+ tolerations = local.tolerations_system
+}
+
+module "ebs-csi" {
+
+ source = "../../../modules/k8s/bootstrap/ebs-csi"
+ cluster_name = module.eks.cluster_name
+ cluster_oidc_provider_arn = module.eks.oidc_provider_arn
+
+ namespace = "ebs-csi"
+ chart_version = "2.55.0"
+ tolerations = local.tolerations_system
+ replace = true
+}
\ No newline at end of file
diff --git a/infra/1-click/0-infra/main.tf b/infra/1-click/0-infra/main.tf
new file mode 100644
index 0000000..e2b5a0d
--- /dev/null
+++ b/infra/1-click/0-infra/main.tf
@@ -0,0 +1,49 @@
+##
+# Global Inputs + Providers
+##
+
+locals {
+ normalized_domain_name = split(".", var.domain_name)[0]
+ formatted_name = "${var.name}-${local.normalized_domain_name}"
+}
+
+data "aws_eks_cluster_auth" "coder" {
+ name = module.eks.cluster_name
+}
+
+provider "aws" {
+ region = var.region
+ profile = var.profile
+}
+
+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,
+ "--profile", var.profile,
+ "--region", var.region
+ ]
+ }
+ }
+}
+
+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,
+ "--profile", var.profile,
+ "--region", var.region
+ ]
+ }
+}
\ No newline at end of file
diff --git a/infra/1-click/0-infra/terragrunt.hcl b/infra/1-click/0-infra/terragrunt.hcl
new file mode 100644
index 0000000..6f1ca3b
--- /dev/null
+++ b/infra/1-click/0-infra/terragrunt.hcl
@@ -0,0 +1,19 @@
+##
+# Base Module
+##
+
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+inputs = {
+ profile = include.root.locals.CODER_AWS_PROFILE
+ region = include.root.locals.CODER_AWS_REGION
+ domain_name = include.root.locals.CODER_DOMAIN_NAME
+ azs = jsondecode(include.root.locals.CODER_AWS_AZS)
+ coder_username = include.root.locals.CODER_DB_USERNAME
+ coder_password = include.root.locals.CODER_DB_PASSWORD
+ grafana_username = include.root.locals.GRAFANA_DB_USERNAME
+ grafana_password = include.root.locals.GRAFANA_DB_PASSWORD
+}
\ No newline at end of file
diff --git a/infra/1-click/0-infra/variables.tf b/infra/1-click/0-infra/variables.tf
new file mode 100644
index 0000000..be3922c
--- /dev/null
+++ b/infra/1-click/0-infra/variables.tf
@@ -0,0 +1,58 @@
+variable "region" {
+ description = "The AWS region to deploy AWS-resources to."
+ type = string
+ default = "us-east-2"
+}
+
+variable "name" {
+ description = "Name for created resources and it's tag prefix"
+ type = string
+ default = "coder"
+}
+
+variable "profile" {
+ description = "The local AWS profile to use."
+ type = string
+ default = "default"
+}
+
+variable "domain_name" {
+ description = "Your domain name (i.e. coder-example.com)."
+ type = string
+}
+
+variable "azs" {
+ type = list(string)
+ default = ["a", "b", "c"]
+}
+
+variable "coder_username" {
+ description = "Coder DB's username."
+ type = string
+ default = "coder"
+}
+
+variable "coder_password" {
+ description = "Coder DB's password."
+ type = string
+ sensitive = true
+ default = "th1s1sn0tas3cur3pass0wrd"
+}
+
+variable "grafana_username" {
+ description = "Grafana DB's username."
+ type = string
+ default = "grafana"
+}
+
+variable "grafana_password" {
+ description = "Grafana DB's password."
+ type = string
+ sensitive = true
+ default = "th1s1sn0tas3cur3pass0wrd"
+}
+
+variable "loki_s3_bucket_tags" {
+ type = map(string)
+ default = {}
+}
\ No newline at end of file
diff --git a/infra/1-click/0-init.sh b/infra/1-click/0-init.sh
new file mode 100755
index 0000000..83cb72f
--- /dev/null
+++ b/infra/1-click/0-init.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -ae -o pipefail
+
+source coder.env
+
+terragrunt run --all --non-interactive --config root.hcl init -- -migrate-state -upgrade
\ No newline at end of file
diff --git a/infra/1-click/1-apply.sh b/infra/1-click/1-apply.sh
new file mode 100755
index 0000000..a36c589
--- /dev/null
+++ b/infra/1-click/1-apply.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -ae -o pipefail
+
+source coder.env
+
+terragrunt run --all --non-interactive --config root.hcl apply
\ No newline at end of file
diff --git a/infra/1-click/1-setup/5-manifests.tf b/infra/1-click/1-setup/5-manifests.tf
new file mode 100644
index 0000000..0561540
--- /dev/null
+++ b/infra/1-click/1-setup/5-manifests.tf
@@ -0,0 +1,314 @@
+##
+# Manifest Setup Post Addon-Deployment
+# Includes auxiliary resources depending on CRDs
+##
+
+##
+# EBS CSI StorageClasses
+##
+
+resource "kubernetes_manifest" "default-sc" {
+ manifest = {
+ apiVersion = "storage.k8s.io/v1"
+ kind = "StorageClass"
+ metadata = {
+ name = "default"
+ annotations = {
+ "storageclass.kubernetes.io/is-default-class" = "true"
+ }
+ }
+ provisioner = "ebs.csi.aws.com"
+ volumeBindingMode = "WaitForFirstConsumer"
+ allowedTopologies = [{
+ matchLabelExpressions = [{
+ key = "topology.ebs.csi.aws.com/zone"
+ values = [for az in var.azs : "${data.aws_region.this.region}${az}"]
+ }]
+ }]
+ parameters = {
+ type = "gp3"
+ encrypted = "true"
+ }
+ }
+}
+
+resource "kubernetes_manifest" "automode-sc" {
+ manifest = {
+ apiVersion = "storage.k8s.io/v1"
+ kind = "StorageClass"
+ metadata = {
+ name = "automode"
+ }
+ provisioner = "ebs.csi.eks.amazonaws.com"
+ volumeBindingMode = "WaitForFirstConsumer"
+ allowedTopologies = [{
+ matchLabelExpressions = [{
+ key = "eks.amazonaws.com/compute-type"
+ values = ["auto"]
+ }]
+ }]
+ parameters = {
+ type = "gp3"
+ encrypted = "true"
+ }
+ }
+}
+
+##
+# NodeClass(es) for Coder (Server, Provisioner, & Workspaces)
+##
+
+data "kubernetes_service_account_v1" "kptr" {
+ metadata {
+ name = "node-role"
+ namespace = "karpenter"
+ }
+}
+
+locals {
+ nodeclass_configs = {
+ "coder" = {
+ api_version = "karpenter.k8s.aws/v1"
+ kind = "EC2NodeClass"
+ user_data = <<-EOT
+ MIME-Version: 1.0
+ Content-Type: multipart/mixed; boundary="//"
+
+ --//
+ Content-Type: application/node.eks.aws
+
+ apiVersion: node.eks.aws/v1alpha1
+ kind: NodeConfig
+ spec:
+ kubelet:
+ config:
+ registryPullQPS: 30
+
+ --//--
+ EOT
+ subnet_selector = [{
+ tags = {
+ "karpenter.sh/discovery" = "${local.formatted_name}"
+ }
+ }]
+ sg_selector = [{
+ # Use for EKS AutoMode. AWS manages this, not TF: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_cluster#cluster_security_group_id-1
+ id = data.aws_eks_cluster.coder.vpc_config[0].cluster_security_group_id
+ # tags = {
+ # # If AutoMode is enabled, THIS BREAKS. Communication to CoreDNS locked behind system nodes. Kptr SG cant talk to AutoMode's SGs (only trusts itself and another AWS-managed SG)
+ # "karpenter.sh/discovery" = "${local.formatted_name}-karpenter"
+ # }
+ }]
+ block_device_mappings = [{
+ deviceName = "/dev/xvda"
+ ebs = {
+ volumeSize = "500Gi"
+ volumeType = "gp3"
+ encrypted = false
+ deleteOnTermination = true
+ }
+ }]
+ }
+ }
+}
+
+resource "kubernetes_manifest" "nodeclass" {
+
+ for_each = local.nodeclass_configs
+
+ manifest = {
+ apiVersion = each.value.api_version
+ kind = each.value.kind
+ metadata = {
+ name = each.key
+ }
+ spec = {
+ role = data.kubernetes_service_account_v1.kptr.metadata[0].annotations["eks.amazonaws.com/role-arn"]
+ amiSelectorTerms = [{
+ alias = "al2023@latest"
+ }]
+ subnetSelectorTerms = each.value.subnet_selector
+ securityGroupSelectorTerms = each.value.sg_selector
+ blockDeviceMappings = each.value.block_device_mappings
+ userData = each.value.user_data
+ }
+ }
+}
+
+##
+# NodePool(s) for Coder (Server, Provisioner, & Workspaces)
+##
+
+locals {
+ nodepool_configs = {
+ # "automode" = {
+ # node_expires_after = "24h"
+ # disruption_consolidation_policy = "WhenEmpty"
+ # disruption_consolidate_after = "1m"
+ # instance_type = "t3a.large"
+ # node_class_ref = {
+ # group = "eks.amazonaws.com"
+ # kind = "NodeClass"
+ # name = "default"
+ # }
+ # taints = []
+ # }
+ "karpenter" = {
+ node_expires_after = "24h"
+ disruption_consolidation_policy = "WhenEmpty"
+ disruption_consolidate_after = "1m"
+ instance_type = "t3a.large"
+ node_class_ref = {
+ group = "karpenter.k8s.aws"
+ kind = "EC2NodeClass"
+ name = "coder"
+ }
+ taints = []
+ }
+ }
+}
+
+resource "kubernetes_manifest" "nodepool" {
+
+ depends_on = [kubernetes_manifest.nodeclass]
+ for_each = local.nodepool_configs
+
+ field_manager {
+ force_conflicts = true
+ }
+
+ wait {
+ condition {
+ type = "Ready"
+ status = "True"
+ }
+ }
+
+ timeouts {
+ create = "10m"
+ update = "10m"
+ delete = "30s"
+ }
+
+ manifest = {
+ apiVersion = "karpenter.sh/v1"
+ kind = "NodePool"
+ metadata = {
+ name = each.key
+ }
+ spec = {
+ template = {
+ metadata = {
+ labels = {
+ "node.coder.io/instance" = "coder-v2"
+ "node.coder.io/managed-by" = "karpenter"
+ "node.coder.io/name" = "coder"
+ "node.coder.io/part-of" = "coder"
+ # "eks.amazonaws.com/compute-type" = "auto"
+ "node.coder.io/used-for" = each.key
+ }
+ }
+ spec = {
+ taints = each.value.taints == null ? [{
+ key = "dedicated"
+ value = each.key
+ effect = "NoSchedule"
+ }] : each.value.taints
+ requirements = [{
+ key = "kubernetes.io/arch"
+ operator = "In"
+ values = ["amd64"]
+ }, {
+ key = "kubernetes.io/os"
+ operator = "In"
+ values = ["linux"]
+ }, {
+ key = "kubernetes.sh/capacity-type"
+ operator = "In"
+ values = ["spot", "on-demand"]
+ }, {
+ key = "node.kubernetes.io/instance-type"
+ operator = "In"
+ values = [each.value.instance_type]
+ }]
+ nodeClassRef = each.value.node_class_ref
+ expireAfter = each.value.node_expires_after
+ }
+ }
+ disruption = {
+ consolidationPolicy = each.value.disruption_consolidation_policy
+ consolidateAfter = each.value.disruption_consolidate_after
+ }
+ }
+ }
+}
+
+##
+# Image Prefetch DaemonSet. Add images to warm new Coder nodes with workspace image.
+##
+
+resource "kubernetes_daemon_set_v1" "img-fetch" {
+
+ for_each = local.nodepool_configs
+
+ metadata {
+ name = "imgs-for-${each.key}"
+ namespace = "kube-system"
+ labels = {
+ "app.kubernetes.io/name" = "img-fetch"
+ "app.kubernetes.io/part-of" = "coder-workspaces"
+ }
+ }
+
+ spec {
+ selector {
+ match_labels = {
+ "app.kubernetes.io/name" = "img-fetch"
+ }
+ }
+
+ template {
+ metadata {
+ labels = {
+ "app.kubernetes.io/name" = "img-fetch"
+ }
+ }
+
+ spec {
+
+ # Select's Coder-specific nodes. Do not pull on system nodes.
+ node_selector = kubernetes_manifest.nodepool[each.key].manifest.spec.template.metadata.labels
+
+ toleration {
+ key = "dedicated"
+ value = each.key
+ effect = "NoSchedule"
+ }
+
+ termination_grace_period_seconds = 5
+
+ init_container {
+ name = "enterprise-base"
+ image = "docker.io/codercom/enterprise-base:ubuntu"
+ command = []
+ }
+
+ container {
+ name = "pause"
+ image = "registry.k8s.io/pause:3.9"
+
+ resources {
+ requests = {
+ cpu = "1m"
+ memory = "1Mi"
+ }
+ limits = {
+ cpu = "10m"
+ memory = "10Mi"
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/infra/1-click/1-setup/6-coder-server.tf b/infra/1-click/1-setup/6-coder-server.tf
new file mode 100644
index 0000000..b5fcef8
--- /dev/null
+++ b/infra/1-click/1-setup/6-coder-server.tf
@@ -0,0 +1,265 @@
+##
+# Fetch DNS Zone
+##
+
+data "aws_route53_zone" "coder" {
+ name = local.apex_domain
+}
+
+##
+# SSL Certificate
+##
+
+resource "aws_acm_certificate" "coder" {
+ domain_name = var.domain_name
+ subject_alternative_names = ["*.${var.domain_name}"]
+ validation_method = "DNS"
+}
+
+resource "aws_route53_record" "coder_validation" {
+
+ for_each = {
+ for rec in aws_acm_certificate.coder.domain_validation_options : rec.domain_name => {
+ name = rec.resource_record_name
+ record = rec.resource_record_value
+ type = rec.resource_record_type
+ }
+ }
+
+ allow_overwrite = true
+ name = each.value.name
+ records = [each.value.record]
+ ttl = 60
+ type = each.value.type
+ zone_id = data.aws_route53_zone.coder.zone_id
+}
+
+resource "aws_acm_certificate_validation" "coder" {
+ certificate_arn = aws_acm_certificate.coder.arn
+ validation_record_fqdns = [for record in aws_route53_record.coder_validation : record.fqdn]
+
+ timeouts {
+ create = "30m"
+ }
+}
+
+resource "aws_acm_certificate" "grafana" {
+ domain_name = "grafana.${var.domain_name}"
+ subject_alternative_names = ["*.grafana.${var.domain_name}"]
+ validation_method = "DNS"
+}
+
+resource "aws_route53_record" "grafana_validation" {
+
+ for_each = {
+ for rec in aws_acm_certificate.grafana.domain_validation_options : rec.domain_name => {
+ name = rec.resource_record_name
+ record = rec.resource_record_value
+ type = rec.resource_record_type
+ }
+ }
+
+ allow_overwrite = true
+ name = each.value.name
+ records = [each.value.record]
+ ttl = 60
+ type = each.value.type
+ zone_id = data.aws_route53_zone.coder.zone_id
+}
+
+resource "aws_acm_certificate_validation" "grafana" {
+ certificate_arn = aws_acm_certificate.grafana.arn
+ validation_record_fqdns = [for record in aws_route53_record.grafana_validation : record.fqdn]
+
+ timeouts {
+ create = "30m"
+ }
+}
+
+##
+# ------
+##
+
+resource "aws_iam_user" "bedrock" {
+ name = "bedrock-access"
+ path = "/${local.normalized_domain_name}/${data.aws_region.this.region}/"
+}
+
+resource "aws_iam_access_key" "bedrock" {
+ user = aws_iam_user.bedrock.name
+}
+
+resource "aws_iam_user_policy_attachment" "bedrock" {
+ user = aws_iam_user.bedrock.name
+ # https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonBedrockLimitedAccess.html
+ policy_arn = "arn:aws:iam::aws:policy/AmazonBedrockLimitedAccess"
+}
+
+locals {
+ pub_subs = [for az in var.azs : "${local.formatted_name}-public-${data.aws_region.this.region}${az}"]
+}
+
+##
+# EIP Mapping
+##
+
+resource "aws_eip" "coder" {
+ count = length(var.azs)
+ domain = "vpc"
+ public_ipv4_pool = "amazon"
+ tags = {
+ Name = "${local.formatted_name}-coder-${count.index}"
+ }
+}
+
+resource "aws_eip" "grafana" {
+ count = 1
+ domain = "vpc"
+ public_ipv4_pool = "amazon"
+ tags = {
+ Name = "${local.formatted_name}-grafana-${count.index}"
+ }
+}
+
+resource "aws_route53_record" "coder-primary" {
+ name = var.domain_name
+ records = aws_eip.coder.*.public_ip
+ ttl = 60
+ type = "A"
+ zone_id = data.aws_route53_zone.coder.zone_id
+}
+
+resource "aws_route53_record" "coder-wildcard" {
+ name = "*.${var.domain_name}"
+ records = aws_eip.coder.*.public_ip
+ ttl = 60
+ type = "A"
+ zone_id = data.aws_route53_zone.coder.zone_id
+}
+
+resource "aws_route53_record" "grafana-primary" {
+ name = "grafana.${var.domain_name}"
+ records = aws_eip.grafana.*.public_ip
+ ttl = 60
+ type = "A"
+ zone_id = data.aws_route53_zone.coder.zone_id
+}
+
+resource "aws_route53_record" "grafana-wildcard" {
+ name = "*.grafana.${var.domain_name}"
+ records = aws_eip.grafana.*.public_ip
+ ttl = 60
+ type = "A"
+ zone_id = data.aws_route53_zone.coder.zone_id
+}
+
+##
+# Helm Installs
+##
+
+locals {
+ coder_release_name = "coder"
+ coder_ns = "coder"
+}
+
+module "coder-server" {
+
+ source = "../../../modules/k8s/bootstrap/coder-server"
+
+ release_name = local.coder_release_name
+ chart_version = var.coder_version
+ cluster_name = data.aws_eks_cluster.coder.id
+ cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.coder.arn
+
+ namespace = local.coder_ns
+ # lb_class = "eks.amazonaws.com/nlb"
+ lb_class = "service.k8s.aws/nlb"
+
+ coder = {
+ access_url = "https://${var.domain_name}"
+ wildcard_url = "*.${var.domain_name}"
+ image_repo = "ghcr.io/coder/coder"
+ image_tag = "v${var.coder_version}"
+ rep_cnt = 1
+ # External Provisioners will be used
+ prov_rep_cnt = var.coder_license != "" ? 0 : 5
+ }
+
+ prometheus = {
+ enable = true
+ }
+
+ db = {
+ url = data.aws_db_instance.coder.endpoint
+ username = var.coder_username
+ password = var.coder_password
+ }
+
+ aibridge = {
+ enabled = var.coder_license != ""
+ bedrock = var.coder_license != "" ? {
+ region = data.aws_region.this.region
+ model = "global.anthropic.claude-opus-4-5-20251101-v1:0"
+ access_id = aws_iam_access_key.bedrock.id
+ secret_id = aws_iam_access_key.bedrock.secret
+ } : null
+ }
+
+ resource_request = {
+ cpu = "1000m"
+ memory = "2Gi"
+ }
+ resource_limit = {
+ cpu = "1000m"
+ memory = "2Gi"
+ }
+
+ tags = {}
+
+ svc_annot = {
+ "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "instance"
+ "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
+ "service.beta.kubernetes.io/aws-load-balancer-attributes" = "deletion_protection.enabled=false"
+ "service.beta.kubernetes.io/aws-load-balancer-eip-allocations" = join(",", aws_eip.coder.*.allocation_id)
+ "service.beta.kubernetes.io/aws-load-balancer-subnets" = join(",", local.pub_subs)
+ "service.beta.kubernetes.io/aws-load-balancer-ssl-cert" = aws_acm_certificate.coder.arn
+ "service.beta.kubernetes.io/aws-load-balancer-ssl-ports" = 443
+ }
+
+ node_selector = kubernetes_manifest.nodepool["karpenter"].manifest.spec.template.metadata.labels
+ tolerations = [for toleration in kubernetes_manifest.nodepool["karpenter"].manifest.spec.template.spec.taints : {
+ key = toleration.key
+ operator = "Equal"
+ value = toleration.value
+ effect = toleration.effect
+ }]
+
+ topology_spread = [{
+ max_skew = 1
+ topology_key = "kubernetes.io/hostname"
+ when_unsatisfiable = "ScheduleAnyway"
+ label_selector = {
+ match_labels = {
+ "app.kubernetes.io/name" = local.coder_release_name
+ "app.kubernetes.io/part-of" = "coder"
+ }
+ }
+ match_label_keys = [
+ "app.kubernetes.io/instance"
+ ]
+ }]
+
+ pod_aaf_pref_sched_ie = [{
+ weight = 100
+ pod_affinity_term = {
+ label_selector = {
+ match_labels = {
+ "app.kubernetes.io/instance" = "coder"
+ "app.kubernetes.io/name" = local.coder_release_name
+ "app.kubernetes.io/part-of" = "coder"
+ }
+ }
+ topology_key = "kubernetes.io/hostname"
+ }
+ }]
+}
\ No newline at end of file
diff --git a/infra/1-click/1-setup/7-coder-init.tf b/infra/1-click/1-setup/7-coder-init.tf
new file mode 100644
index 0000000..03be15e
--- /dev/null
+++ b/infra/1-click/1-setup/7-coder-init.tf
@@ -0,0 +1,111 @@
+# Wait for DNS propagation. May require multiple requests
+
+data "http" "first-user" {
+
+ depends_on = [module.coder-server]
+
+ url = "http://${aws_eip.coder.0.public_ip}/api/v2/users/first"
+ method = "POST"
+ request_headers = {
+ Host = var.domain_name
+ Accept = "application/json"
+ }
+ request_body = jsonencode({
+ email = var.coder_admin_email
+ username = var.coder_admin_username
+ password = var.coder_admin_password
+ trial = false
+ })
+ retry {
+ attempts = 60 + 1
+ max_delay_ms = 5000
+ min_delay_ms = 5000
+ }
+}
+
+locals {
+ coder_svc_url = "http://${local.coder_release_name}.${local.coder_ns}.svc.cluster.local"
+}
+
+module "coder-kube-logstream" {
+
+ depends_on = [module.coder-server]
+ source = "../../../modules/k8s/bootstrap/coder-logstream"
+
+ coder = {
+ access_url = local.coder_svc_url
+ ws_ns = [local.coder_ns]
+ }
+}
+
+module "monitoring" {
+
+ source = "../../../modules/k8s/bootstrap/monitoring"
+
+ chart_version = "0.7.0-rc.1"
+ chart_timeout = 360
+ cluster_name = "${var.name}-${local.normalized_domain_name}"
+ cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.coder.arn
+
+ lb_class = "service.k8s.aws/nlb"
+ storage_class = kubernetes_manifest.automode-sc.manifest.metadata.name
+
+ domain_name = "grafana.${var.domain_name}"
+ tolerations = concat([{
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }], [for toleration in kubernetes_manifest.nodepool["karpenter"].manifest.spec.template.spec.taints : {
+ key = toleration.key
+ operator = "Equal"
+ value = toleration.value
+ effect = toleration.effect
+ }])
+
+ coder = {
+ db = {
+ host = data.aws_db_instance.coder.address
+ password = var.coder_password
+ username = var.coder_username
+ database = data.aws_db_instance.coder.db_name
+ }
+ selector = {
+ coderd = "pod=~`coder.*`, pod!~`.*provisioner.*`, namespace=~`(coder)`"
+ provisionerd = "pod=~`coder-provisioner.*`, namespace=~`(coder)`"
+ workspaces = "pod!~`coder.*`, namespace=~`(coder)`"
+ ctrl_plane_ns = "coder"
+ ext_prov_ns = "coder"
+ }
+ }
+ grafana = {
+ admin = {
+ username = var.grafana_admin_username
+ password = var.grafana_admin_password
+ }
+ db = {
+ host = data.aws_db_instance.grafana.address
+ password = var.grafana_password
+ username = var.grafana_username
+ database = data.aws_db_instance.grafana.db_name
+ }
+ svc = {
+ annots = {
+ "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "instance"
+ "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
+ "service.beta.kubernetes.io/aws-load-balancer-attributes" = "deletion_protection.enabled=false"
+ "service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol" = "tcp"
+ "service.beta.kubernetes.io/aws-load-balancer-healthcheck-path" = "/api/health"
+ "service.beta.kubernetes.io/aws-load-balancer-ssl-cert" = aws_acm_certificate.grafana.arn
+ "service.beta.kubernetes.io/aws-load-balancer-ssl-ports" = 443
+ "service.beta.kubernetes.io/aws-load-balancer-eip-allocations" = join(",", aws_eip.grafana.*.allocation_id)
+ "service.beta.kubernetes.io/aws-load-balancer-subnets" = join(",", [local.pub_subs[0]])
+ }
+ }
+ }
+ loki = {
+ s3 = {
+ chunks_bucket = data.aws_s3_bucket.loki.id
+ ruler_bucket = data.aws_s3_bucket.loki.id
+ region = data.aws_s3_bucket.loki.bucket_region
+ }
+ }
+}
\ No newline at end of file
diff --git a/infra/1-click/1-setup/main.tf b/infra/1-click/1-setup/main.tf
new file mode 100644
index 0000000..3e960e9
--- /dev/null
+++ b/infra/1-click/1-setup/main.tf
@@ -0,0 +1,67 @@
+##
+# Remote State Resources
+##
+
+provider "aws" {
+ region = var.region
+ profile = var.profile
+}
+
+provider "helm" {
+ kubernetes = {
+ host = data.aws_eks_cluster.coder.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.coder.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.coder.token
+ }
+}
+
+provider "kubernetes" {
+ host = data.aws_eks_cluster.coder.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.coder.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.coder.token
+}
+
+data "aws_region" "this" {}
+
+locals {
+ apex_domain = join(".", slice(split(".", var.domain_name), length(split(".", var.domain_name)) - 2, length(split(".", var.domain_name))))
+ normalized_domain_name = split(".", var.domain_name)[0]
+ formatted_name = "${var.name}-${local.normalized_domain_name}"
+}
+
+data "aws_vpc" "this" {
+ tags = {
+ "Name" = local.formatted_name
+ }
+}
+
+data "aws_s3_bucket" "loki" {
+ bucket = "${local.formatted_name}-grafana"
+}
+
+data "aws_db_instance" "coder" {
+ db_instance_identifier = "${local.formatted_name}-coder"
+}
+
+data "aws_db_instance" "grafana" {
+ db_instance_identifier = "${local.formatted_name}-grafana"
+}
+
+data "aws_security_group" "coder" {
+ name = "${local.formatted_name}-pgsql"
+ vpc_id = data.aws_vpc.this.id
+}
+
+data "aws_eks_cluster" "coder" {
+ name = local.formatted_name
+ region = var.region
+}
+
+data "aws_eks_cluster_auth" "coder" {
+ name = local.formatted_name
+ region = var.region
+}
+
+data "aws_iam_openid_connect_provider" "coder" {
+ url = data.aws_eks_cluster.coder.identity[0].oidc[0].issuer
+}
\ No newline at end of file
diff --git a/infra/1-click/1-setup/terragrunt.hcl b/infra/1-click/1-setup/terragrunt.hcl
new file mode 100644
index 0000000..e633e43
--- /dev/null
+++ b/infra/1-click/1-setup/terragrunt.hcl
@@ -0,0 +1,23 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = ["../0-infra"]
+}
+
+inputs = {
+ # Optional. Set if using Terragrunt.
+ profile = include.root.locals.CODER_AWS_PROFILE
+ region = include.root.locals.CODER_AWS_REGION
+ domain_name = include.root.locals.CODER_DOMAIN_NAME
+ azs = jsondecode(include.root.locals.CODER_AWS_AZS)
+ coder_version = include.root.locals.CODER_VERSION
+ coder_username = include.root.locals.CODER_DB_USERNAME
+ coder_password = include.root.locals.CODER_DB_PASSWORD
+ coder_license = include.root.locals.CODER_LICENSE
+ coder_admin_email = include.root.locals.CODER_EMAIL
+ coder_admin_username = include.root.locals.CODER_USERNAME
+ coder_admin_password = include.root.locals.CODER_PASSWORD
+}
\ No newline at end of file
diff --git a/infra/1-click/1-setup/variables.tf b/infra/1-click/1-setup/variables.tf
new file mode 100644
index 0000000..0cac0e0
--- /dev/null
+++ b/infra/1-click/1-setup/variables.tf
@@ -0,0 +1,98 @@
+
+##
+# Global Inputs + Providers
+##
+
+variable "region" {
+ description = "The AWS region of the deployment."
+ type = string
+ default = "us-east-2"
+}
+
+variable "name" {
+ description = "Name for created resources and tag prefix."
+ type = string
+ default = "coder"
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "domain_name" {
+ type = string
+}
+
+##
+# Coder K8s Inputs
+##
+
+variable "coder_username" {
+ description = "Coder DB's username."
+ type = string
+ default = "coder"
+}
+
+variable "coder_password" {
+ description = "Coder DB's password."
+ type = string
+ default = "th1s1sn0tas3cur3pass0wrd"
+ sensitive = true
+}
+
+variable "coder_version" {
+ type = string
+ default = "2.30.0"
+}
+
+variable "coder_license" {
+ type = string
+ default = ""
+ sensitive = true
+}
+
+variable "coder_admin_email" {
+ type = string
+ default = "admin@coder.com"
+}
+
+variable "coder_admin_username" {
+ type = string
+ default = "admin"
+}
+
+variable "coder_admin_password" {
+ type = string
+ default = "Th1s1sN0TS3CuR3!!"
+ sensitive = true
+}
+
+variable "grafana_username" {
+ description = "Grafana DB's username."
+ type = string
+ default = "grafana"
+}
+
+variable "grafana_password" {
+ description = "Grafana DB's password."
+ type = string
+ default = "th1s1sn0tas3cur3pass0wrd"
+ sensitive = true
+}
+
+variable "grafana_admin_username" {
+ type = string
+ default = "admin"
+}
+
+variable "grafana_admin_password" {
+ type = string
+ default = "Th1s1sN0TS3CuR3!!"
+ sensitive = true
+}
+
+variable "azs" {
+ type = list(string)
+ default = ["a", "b", "c"]
+}
\ No newline at end of file
diff --git a/infra/1-click/2-clean.sh b/infra/1-click/2-clean.sh
new file mode 100755
index 0000000..73c1463
--- /dev/null
+++ b/infra/1-click/2-clean.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -ae -o pipefail
+
+source coder.env
+
+terragrunt run --all --non-interactive --config root.hcl destroy
\ No newline at end of file
diff --git a/infra/1-click/2-coder/8-backend.tf b/infra/1-click/2-coder/8-backend.tf
new file mode 100644
index 0000000..a23d278
--- /dev/null
+++ b/infra/1-click/2-coder/8-backend.tf
@@ -0,0 +1,150 @@
+##
+# Setup main provisioner + proxy if license is added.
+##
+
+##
+# External Provisioner Setup
+##
+
+resource "coderd_license" "enterprise" {
+ count = var.coder_license != "" ? 1 : 0
+ license = var.coder_license
+}
+
+locals {
+ coder_ns = "coder"
+ coder_release_name = "coder"
+ coder_svc_url = "http://${local.coder_release_name}.${local.coder_ns}.svc.cluster.local"
+}
+
+module "coder-ext-prov" {
+
+ count = length(coderd_license.enterprise)
+ depends_on = [ coderd_license.enterprise ]
+ source = "../../../modules/k8s/bootstrap/coder-provisioner"
+
+ release_name = "coder-provisioner"
+ namespace = "coder-provisioner"
+ chart_name = "coder-provisioner"
+ chart_version = var.coder_version
+
+ cluster_name = "${local.formatted_name}"
+ cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.coder.arn
+
+ coder = {
+ access_url = local.coder_svc_url
+ ws_ns = [local.coder_ns]
+ image_tag = "v${var.coder_version}"
+ rep_cnt = 5
+ }
+}
+
+##
+# Proxy Setup
+##
+
+locals {
+ apex_domain = join(".", slice(split(".", var.domain_name), length(split(".", var.domain_name)) - 2, length(split(".", var.domain_name))))
+ pub_subs = [for az in var.azs : "${local.formatted_name}-public-${data.aws_region.this.region}${az}"]
+ proxy_access_url = "proxy.${var.domain_name}"
+ proxy_wildcard_url = "*.proxy.${var.domain_name}"
+}
+
+##
+# Fetch AWS Hosted Zone
+##
+
+data "aws_route53_zone" "coder" {
+ name = local.apex_domain
+}
+
+resource "aws_eip" "proxy" {
+ count = 1
+ domain = "vpc"
+ public_ipv4_pool = "amazon"
+ tags = {
+ Name = "${local.formatted_name}-proxy-${count.index}"
+ }
+}
+
+resource "aws_route53_record" "proxy-primary" {
+ name = local.proxy_access_url
+ records = aws_eip.proxy.*.public_ip
+ ttl = 60
+ type = "A"
+ zone_id = data.aws_route53_zone.coder.zone_id
+}
+
+resource "aws_route53_record" "proxy-wildcard" {
+ name = local.proxy_wildcard_url
+ records = aws_eip.proxy.*.public_ip
+ ttl = 60
+ type = "A"
+ zone_id = data.aws_route53_zone.coder.zone_id
+}
+
+resource "aws_acm_certificate" "proxy" {
+ domain_name = local.proxy_access_url
+ subject_alternative_names = [local.proxy_wildcard_url]
+ validation_method = "DNS"
+}
+
+resource "aws_route53_record" "proxy_validation" {
+
+ for_each = {
+ for rec in aws_acm_certificate.proxy.domain_validation_options : rec.domain_name => {
+ name = rec.resource_record_name
+ record = rec.resource_record_value
+ type = rec.resource_record_type
+ }
+ }
+
+ allow_overwrite = true
+ name = each.value.name
+ records = [each.value.record]
+ ttl = 60
+ type = each.value.type
+ zone_id = data.aws_route53_zone.coder.zone_id
+}
+
+resource "aws_acm_certificate_validation" "proxy" {
+ certificate_arn = aws_acm_certificate.proxy.arn
+ validation_record_fqdns = [for record in aws_route53_record.proxy_validation : record.fqdn]
+
+ timeouts {
+ create = "30m"
+ }
+}
+
+# module "coder-proxy" {
+
+# count = length(coderd_license.enterprise)
+# depends_on = [ coderd_license.enterprise, module.coder-ext-prov ]
+# source = "../../../modules/k8s/bootstrap/coder-proxy"
+
+# release_name = "coder-proxy"
+# namespace = "coder-proxy"
+# chart_name = "coder"
+# chart_version = var.coder_version
+
+# proxy = {
+# name = "proxy"
+# display_name = "Coder Proxy"
+# access_url = "https://${local.proxy_access_url}"
+# icon = "/emojis/1f4a1.png" # 💡
+# wildcard_url = local.proxy_wildcard_url
+# coder_access_url = "https://${var.domain_name}"
+# image_tag = "v${var.coder_version}"
+# rep_cnt = 1
+# }
+
+# svc_annot = {
+# "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "instance"
+# "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
+# "service.beta.kubernetes.io/aws-load-balancer-attributes" = "deletion_protection.enabled=false"
+# "service.beta.kubernetes.io/aws-load-balancer-eip-allocations" = join(",", aws_eip.proxy.*.allocation_id)
+# "service.beta.kubernetes.io/aws-load-balancer-subnets" = join(",", [local.pub_subs[0]])
+# "service.beta.kubernetes.io/aws-load-balancer-ssl-cert" = aws_acm_certificate.proxy.arn
+# "service.beta.kubernetes.io/aws-load-balancer-ssl-ports" = 443
+# }
+# }
\ No newline at end of file
diff --git a/infra/1-click/2-coder/9-templates.tf b/infra/1-click/2-coder/9-templates.tf
new file mode 100644
index 0000000..77a81e9
--- /dev/null
+++ b/infra/1-click/2-coder/9-templates.tf
@@ -0,0 +1,222 @@
+##
+# Coder Template Push
+##
+
+data "coderd_organization" "default" {
+ is_default = true
+}
+
+##
+# Standalone Kubernetes Template
+##
+
+data "archive_file" "k8s-default" {
+ type = "zip"
+ excludes = ["${path.module}/templates/kubernetes/.terraform"]
+ source_dir = "${path.module}/templates/kubernetes"
+ output_path = "${path.module}/templates/kubernetes.zip"
+}
+
+resource "time_static" "k8s-default" {
+ triggers = {
+ run_on_ip_changes = data.aws_eip.coder.private_ip
+ run_on_checksum = "${data.archive_file.k8s-default.id}"
+ }
+}
+
+resource "coderd_template" "k8s-default" {
+
+ depends_on = [module.coder-ext-prov]
+
+ name = "kubernetes"
+ organization_id = data.coderd_organization.default.id
+ display_name = "Kubernetes (Deployment)"
+ description = "Provision Kubernetes Deployments as Coder workspaces"
+ icon = "https://${var.domain_name}/icon/k8s.png"
+ versions = [
+ {
+ name = "stable-${formatdate("YYYY-MM-DD_hh-mm-ss", time_static.k8s-default.rfc3339)}"
+ description = "The stable version of the template."
+ directory = "${path.module}/templates/kubernetes"
+ active = true
+ tf_vars = [{
+ name = "namespace"
+ value = "coder"
+ }, {
+ name = "use_kubeconfig"
+ value = tostring(false)
+ }, {
+ name = "host_ip"
+ value = data.aws_eip.coder.private_ip
+ }]
+ }
+ ]
+}
+
+##
+# Claude-Code Kubernetes Template
+##
+
+##
+# NOTICE: This template requires a Premium license as it includes the following:
+# - AI Bridge
+#
+# NOTE: AI Boundary's will not work in EKS Auto Mode.
+##
+
+data "archive_file" "k8s-claude" {
+
+ count = var.coder_license != "" && length(module.coder-ext-prov) > 0 ? 1 : 0
+
+ type = "zip"
+ excludes = ["${path.module}/templates/kubernetes-claude/.terraform"]
+ source_dir = "${path.module}/templates/kubernetes-claude"
+ output_path = "${path.module}/templates/kubernetes-claude.zip"
+}
+
+resource "time_static" "k8s-claude" {
+
+ count = var.coder_license != "" && length(module.coder-ext-prov) > 0 ? 1 : 0
+
+ triggers = {
+ run_on_ip_changes = data.aws_eip.coder.private_ip
+ run_on_checksum = "${data.archive_file.k8s-claude[0].id}"
+ }
+}
+
+resource "coderd_template" "k8s-claude" {
+
+ count = var.coder_license != "" && length(module.coder-ext-prov) > 0 ? 1 : 0
+
+ name = "kubernetes-claude"
+ organization_id = data.coderd_organization.default.id
+ display_name = "Claude Code on Coder"
+ description = "Provision a Kubernetes Deployments with Claude installed."
+ icon = "https://${var.domain_name}/icon/claude.svg"
+ versions = [
+ {
+ name = "stable-${formatdate("YYYY-MM-DD_hh-mm-ss", time_static.k8s-claude[0].rfc3339)}"
+ description = "The stable version of the template."
+ directory = "${path.module}/templates/kubernetes-claude"
+ active = true
+ tf_vars = [{
+ name = "namespace"
+ value = "coder"
+ }, {
+ name = "use_kubeconfig"
+ value = tostring(false)
+ }, {
+ name = "host_ip"
+ value = data.aws_eip.coder.private_ip
+ }]
+ }
+ ]
+}
+
+##
+# AWS EC2 Linux
+##
+
+data "archive_file" "aws-linux" {
+ type = "zip"
+ excludes = ["${path.module}/templates/aws-linux/.terraform"]
+ source_dir = "${path.module}/templates/aws-linux"
+ output_path = "${path.module}/templates/aws-linux.zip"
+}
+
+resource "time_static" "aws-linux" {
+ triggers = {
+ run_on_checksum = "${data.archive_file.aws-linux.id}"
+ }
+}
+
+resource "coderd_template" "aws-linux" {
+
+ depends_on = [module.coder-ext-prov]
+
+ name = "aws-linux"
+ organization_id = data.coderd_organization.default.id
+ display_name = "AWS EC2 (Linux)"
+ description = "Provision an AWS EC2 Linux VM."
+ icon = "https://${var.domain_name}/icon/aws.svg"
+ versions = [
+ {
+ name = "stable-${formatdate("YYYY-MM-DD_hh-mm-ss", time_static.aws-linux.rfc3339)}"
+ description = "The stable version of the template."
+ directory = "${path.module}/templates/aws-linux"
+ active = true
+ }
+ ]
+}
+
+##
+# AWS EC2 Windows
+##
+
+data "archive_file" "aws-windows" {
+ type = "zip"
+ excludes = ["${path.module}/templates/aws-windows/.terraform"]
+ source_dir = "${path.module}/templates/aws-windows"
+ output_path = "${path.module}/templates/aws-windows.zip"
+}
+
+resource "time_static" "aws-windows" {
+ triggers = {
+ run_on_checksum = "${data.archive_file.aws-windows.id}"
+ }
+}
+
+resource "coderd_template" "aws-windows" {
+
+ depends_on = [module.coder-ext-prov]
+
+ name = "aws-windows"
+ organization_id = data.coderd_organization.default.id
+ display_name = "AWS EC2 (Windows)"
+ description = "Provision an AWS EC2 Windows VM."
+ icon = "https://${var.domain_name}/icon/windows.svg"
+ versions = [
+ {
+ name = "stable-${formatdate("YYYY-MM-DD_hh-mm-ss", time_static.aws-windows.rfc3339)}"
+ description = "The stable version of the template."
+ directory = "${path.module}/templates/aws-windows"
+ active = true
+ }
+ ]
+}
+
+##
+# AWS EC2 DevContainer
+##
+
+data "archive_file" "aws-devcontainer" {
+ type = "zip"
+ excludes = ["${path.module}/templates/aws-devcontainer/.terraform"]
+ source_dir = "${path.module}/templates/aws-devcontainer"
+ output_path = "${path.module}/templates/aws-devcontainer.zip"
+}
+
+resource "time_static" "aws-devcontainer" {
+ triggers = {
+ run_on_checksum = "${data.archive_file.aws-devcontainer.id}"
+ }
+}
+
+resource "coderd_template" "aws-devcontainer" {
+
+ depends_on = [module.coder-ext-prov]
+
+ name = "aws-devcontainer"
+ organization_id = data.coderd_organization.default.id
+ display_name = "AWS EC2 (Linux with Devcontainer)"
+ description = "Provision an AWS EC2 Linux VM running DevContainers."
+ icon = "https://${var.domain_name}/icon/aws.svg"
+ versions = [
+ {
+ name = "stable-${formatdate("YYYY-MM-DD_hh-mm-ss", time_static.aws-devcontainer.rfc3339)}"
+ description = "The stable version of the template."
+ directory = "${path.module}/templates/aws-devcontainer"
+ active = true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/infra/1-click/2-coder/main.tf b/infra/1-click/2-coder/main.tf
new file mode 100644
index 0000000..be172b6
--- /dev/null
+++ b/infra/1-click/2-coder/main.tf
@@ -0,0 +1,72 @@
+##
+# Global Inputs + Providers
+##
+
+locals {
+ formatted_name = "${var.name}-${local.normalized_domain_name}"
+ normalized_domain_name = split(".", var.domain_name)[0]
+}
+
+provider "aws" {
+ region = var.region
+ profile = var.profile
+}
+
+data "aws_region" "this" {}
+
+data "aws_eks_cluster" "coder" {
+ name = local.formatted_name
+ region = var.region
+}
+
+data "aws_eks_cluster_auth" "coder" {
+ name = local.formatted_name
+ region = var.region
+}
+
+data "aws_iam_openid_connect_provider" "coder" {
+ url = data.aws_eks_cluster.coder.identity[0].oidc[0].issuer
+}
+
+##
+# Coder MUST be in a reachable state by now
+##
+
+data "aws_eip" "coder" {
+ region = var.region
+ tags = {
+ Name = "${local.formatted_name}-coder-0"
+ }
+}
+
+data "http" "login" {
+ url = "http://${data.aws_eip.coder.public_ip}/api/v2/users/login"
+ method = "POST"
+ request_headers = {
+ Host = var.domain_name
+ Accept = "application/json"
+ }
+ request_body = jsonencode({
+ email = var.coder_admin_email
+ password = var.coder_admin_password
+ })
+}
+
+provider "helm" {
+ kubernetes = {
+ host = data.aws_eks_cluster.coder.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.coder.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.coder.token
+ }
+}
+
+provider "kubernetes" {
+ host = data.aws_eks_cluster.coder.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.coder.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.coder.token
+}
+
+provider "coderd" {
+ url = "http://${data.aws_eip.coder.public_ip}"
+ token = jsondecode(data.http.login.response_body).session_token
+}
\ No newline at end of file
diff --git a/infra/1-click/2-coder/templates/aws-devcontainer/README.md b/infra/1-click/2-coder/templates/aws-devcontainer/README.md
new file mode 100644
index 0000000..3b58d73
--- /dev/null
+++ b/infra/1-click/2-coder/templates/aws-devcontainer/README.md
@@ -0,0 +1,111 @@
+---
+display_name: AWS EC2 (Devcontainer)
+description: Provision AWS EC2 VMs with a devcontainer as Coder workspaces
+icon: ../../../site/static/icon/aws.svg
+maintainer_github: coder
+verified: true
+tags: [vm, linux, aws, persistent, devcontainer]
+---
+
+# Remote Development on AWS EC2 VMs using a Devcontainer
+
+Provision AWS EC2 VMs as [Coder workspaces](https://coder.com/docs) with this example template.
+
+
+
+
+## Prerequisites
+
+### Authentication
+
+By default, this template authenticates to AWS using the provider's default [authentication methods](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration).
+
+The simplest way (without making changes to the template) is via environment variables (e.g. `AWS_ACCESS_KEY_ID`) or a [credentials file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-format). If you are running Coder on a VM, this file must be in `/home/coder/aws/credentials`.
+
+To use another [authentication method](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication), edit the template.
+
+## Required permissions / policy
+
+The following sample policy allows Coder to create EC2 instances and modify
+instances provisioned by Coder:
+
+```json
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Sid": "VisualEditor0",
+ "Effect": "Allow",
+ "Action": [
+ "ec2:GetDefaultCreditSpecification",
+ "ec2:DescribeIamInstanceProfileAssociations",
+ "ec2:DescribeTags",
+ "ec2:DescribeInstances",
+ "ec2:DescribeInstanceTypes",
+ "ec2:DescribeInstanceStatus",
+ "ec2:CreateTags",
+ "ec2:RunInstances",
+ "ec2:DescribeInstanceCreditSpecifications",
+ "ec2:DescribeImages",
+ "ec2:ModifyDefaultCreditSpecification",
+ "ec2:DescribeVolumes"
+ ],
+ "Resource": "*"
+ },
+ {
+ "Sid": "CoderResources",
+ "Effect": "Allow",
+ "Action": [
+ "ec2:DescribeInstanceAttribute",
+ "ec2:UnmonitorInstances",
+ "ec2:TerminateInstances",
+ "ec2:StartInstances",
+ "ec2:StopInstances",
+ "ec2:DeleteTags",
+ "ec2:MonitorInstances",
+ "ec2:CreateTags",
+ "ec2:RunInstances",
+ "ec2:ModifyInstanceAttribute",
+ "ec2:ModifyInstanceCreditSpecification"
+ ],
+ "Resource": "arn:aws:ec2:*:*:instance/*",
+ "Condition": {
+ "StringEquals": {
+ "aws:ResourceTag/Coder_Provisioned": "true"
+ }
+ }
+ }
+ ]
+}
+```
+
+## Architecture
+
+This template provisions the following resources:
+
+- AWS Instance
+
+Coder uses `aws_ec2_instance_state` to start and stop the VM. This example template is fully persistent, meaning the full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.
+
+> **Note**
+> This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.
+
+## Caching
+
+To speed up your builds, you can use a container registry as a cache.
+When creating the template, set the parameter `cache_repo` to a valid Docker repository in the form `host.tld/path/to/repo`.
+
+See the [Envbuilder Terraform Provider Examples](https://github.com/coder/terraform-provider-envbuilder/blob/main/examples/resources/envbuilder_cached_image/envbuilder_cached_image_resource.tf/) for a more complete example of how the provider works.
+
+> [!NOTE]
+> We recommend using a registry cache with authentication enabled.
+> To allow Envbuilder to authenticate with a registry cache hosted on ECR, specify an IAM instance
+> profile that has read and write access to the given registry. For more information, see the
+> [AWS documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html).
+>
+> Alternatively, you can specify the variable `cache_repo_docker_config_path`
+> with the path to a Docker config `.json` on disk containing valid credentials for the registry.
+
+## code-server
+
+`code-server` is installed via the [`code-server`](https://registry.coder.com/modules/code-server) registry module. For a list of all modules and templates pplease check [Coder Registry](https://registry.coder.com).
\ No newline at end of file
diff --git a/infra/1-click/2-coder/templates/aws-devcontainer/architecture.svg b/infra/1-click/2-coder/templates/aws-devcontainer/architecture.svg
new file mode 100644
index 0000000..737db74
--- /dev/null
+++ b/infra/1-click/2-coder/templates/aws-devcontainer/architecture.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/infra/1-click/2-coder/templates/aws-devcontainer/cloud-init/cloud-config.yaml.tftpl b/infra/1-click/2-coder/templates/aws-devcontainer/cloud-init/cloud-config.yaml.tftpl
new file mode 100644
index 0000000..3aaefb4
--- /dev/null
+++ b/infra/1-click/2-coder/templates/aws-devcontainer/cloud-init/cloud-config.yaml.tftpl
@@ -0,0 +1,15 @@
+#cloud-config
+cloud_final_modules:
+ - [scripts-user, always]
+hostname: ${hostname}
+users:
+ - name: ${linux_user}
+ sudo: ALL=(ALL) NOPASSWD:ALL
+ shell: /bin/bash
+ ssh_authorized_keys:
+ - "${ssh_pubkey}"
+# Automatically grow the partition
+growpart:
+ mode: auto
+ devices: ['/']
+ ignore_growroot_disabled: false
\ No newline at end of file
diff --git a/infra/1-click/2-coder/templates/aws-devcontainer/cloud-init/userdata.sh.tftpl b/infra/1-click/2-coder/templates/aws-devcontainer/cloud-init/userdata.sh.tftpl
new file mode 100644
index 0000000..9fb8117
--- /dev/null
+++ b/infra/1-click/2-coder/templates/aws-devcontainer/cloud-init/userdata.sh.tftpl
@@ -0,0 +1,37 @@
+#!/bin/bash
+# Install Docker
+if ! command -v docker &> /dev/null
+then
+ echo "Docker not found, installing..."
+ curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh 2>&1 >/dev/null
+ usermod -aG docker ${linux_user}
+ newgrp docker
+else
+ echo "Docker is already installed."
+fi
+
+# Set up Docker credentials
+mkdir -p "/home/${linux_user}/.docker"
+
+if [ -n "${docker_config_json_base64}" ]; then
+ # Write the Docker config JSON to disk if it is provided.
+ printf "%s" "${docker_config_json_base64}" | base64 -d | tee "/home/${linux_user}/.docker/config.json"
+else
+ # Assume that we're going to use the instance IAM role to pull from the cache repo if we need to.
+ # Set up the ecr credential helper.
+ apt-get update -y && apt-get install -y amazon-ecr-credential-helper
+ mkdir -p .docker
+ printf '{"credsStore": "ecr-login"}' | tee "/home/${linux_user}/.docker/config.json"
+fi
+chown -R ${linux_user}:${linux_user} "/home/${linux_user}/.docker"
+
+# Start envbuilder
+sudo -u coder docker run \
+ --rm \
+ --net=host \
+ -h ${hostname} \
+ -v /home/${linux_user}/envbuilder:/workspaces \
+ %{ for key, value in environment ~}
+ -e ${key}="${value}" \
+ %{ endfor ~}
+ ${builder_image}
\ No newline at end of file
diff --git a/infra/1-click/2-coder/templates/aws-devcontainer/main.tf b/infra/1-click/2-coder/templates/aws-devcontainer/main.tf
new file mode 100644
index 0000000..703c1b3
--- /dev/null
+++ b/infra/1-click/2-coder/templates/aws-devcontainer/main.tf
@@ -0,0 +1,331 @@
+terraform {
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ }
+ aws = {
+ source = "hashicorp/aws"
+ }
+ cloudinit = {
+ source = "hashicorp/cloudinit"
+ }
+ envbuilder = {
+ source = "coder/envbuilder"
+ }
+ }
+}
+
+module "aws_region" {
+ source = "https://registry.coder.com/modules/aws-region"
+ default = "us-east-1"
+}
+
+provider "aws" {
+ region = module.aws_region.value
+}
+
+variable "cache_repo" {
+ default = ""
+ description = "(Optional) Use a container registry as a cache to speed up builds. Example: host.tld/path/to/repo."
+ type = string
+}
+
+variable "cache_repo_docker_config_path" {
+ default = ""
+ description = "(Optional) Path to a docker config.json containing credentials to the provided cache repo, if required. This will depend on your Coder setup. Example: `/home/coder/.docker/config.json`."
+ sensitive = true
+ type = string
+}
+
+variable "iam_instance_profile" {
+ default = ""
+ description = "(Optional) Name of an IAM instance profile to assign to the instance."
+ type = string
+}
+
+data "coder_workspace" "me" {}
+data "coder_workspace_owner" "me" {}
+
+data "aws_ami" "ubuntu" {
+ most_recent = true
+ filter {
+ name = "name"
+ values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
+ }
+ filter {
+ name = "virtualization-type"
+ values = ["hvm"]
+ }
+ owners = ["099720109477"] # Canonical
+}
+
+data "coder_parameter" "instance_type" {
+ name = "instance_type"
+ display_name = "Instance type"
+ description = "What instance type should your workspace use?"
+ default = "t3.micro"
+ mutable = false
+ option {
+ name = "2 vCPU, 1 GiB RAM"
+ value = "t3.micro"
+ }
+ option {
+ name = "2 vCPU, 2 GiB RAM"
+ value = "t3.small"
+ }
+ option {
+ name = "2 vCPU, 4 GiB RAM"
+ value = "t3.medium"
+ }
+ option {
+ name = "2 vCPU, 8 GiB RAM"
+ value = "t3.large"
+ }
+ option {
+ name = "4 vCPU, 16 GiB RAM"
+ value = "t3.xlarge"
+ }
+ option {
+ name = "8 vCPU, 32 GiB RAM"
+ value = "t3.2xlarge"
+ }
+}
+
+data "coder_parameter" "root_volume_size_gb" {
+ name = "root_volume_size_gb"
+ display_name = "Root Volume Size (GB)"
+ description = "How large should the root volume for the instance be?"
+ default = 30
+ type = "number"
+ mutable = true
+ validation {
+ min = 1
+ monotonic = "increasing"
+ }
+}
+
+data "coder_parameter" "fallback_image" {
+ default = "codercom/enterprise-base:ubuntu"
+ description = "This image runs if the devcontainer fails to build."
+ display_name = "Fallback Image"
+ mutable = true
+ name = "fallback_image"
+ order = 3
+}
+
+data "coder_parameter" "devcontainer_builder" {
+ description = <<-EOF
+Image that will build the devcontainer.
+Find the latest version of Envbuilder here: https://ghcr.io/coder/envbuilder
+Be aware that using the `:latest` tag may expose you to breaking changes.
+EOF
+ display_name = "Devcontainer Builder"
+ mutable = true
+ name = "devcontainer_builder"
+ default = "ghcr.io/coder/envbuilder:latest"
+ order = 4
+}
+
+data "coder_parameter" "repo_url" {
+ name = "repo_url"
+ display_name = "Repository URL"
+ default = "https://github.com/coder/envbuilder-starter-devcontainer"
+ description = "Repository URL"
+ mutable = true
+}
+
+data "coder_parameter" "ssh_pubkey" {
+ name = "ssh_pubkey"
+ display_name = "SSH Public Key"
+ default = ""
+ description = "(Optional) Add an SSH public key to the `coder` user's authorized_keys. Useful for troubleshooting. You may need to add a security group to the instance."
+ mutable = false
+}
+
+data "local_sensitive_file" "cache_repo_dockerconfigjson" {
+ count = var.cache_repo_docker_config_path == "" ? 0 : 1
+ filename = var.cache_repo_docker_config_path
+}
+
+data "aws_iam_instance_profile" "vm_instance_profile" {
+ count = var.iam_instance_profile == "" ? 0 : 1
+ name = var.iam_instance_profile
+}
+
+# Be careful when modifying the below locals!
+locals {
+ # TODO: provide a way to pick the availability zone.
+ aws_availability_zone = "${module.aws_region.value}a"
+
+ hostname = lower(data.coder_workspace.me.name)
+ linux_user = "coder"
+
+ # The devcontainer builder image is the image that will build the devcontainer.
+ devcontainer_builder_image = data.coder_parameter.devcontainer_builder.value
+
+ # We may need to authenticate with a registry. If so, the user will provide a path to a docker config.json.
+ docker_config_json_base64 = try(data.local_sensitive_file.cache_repo_dockerconfigjson[0].content_base64, "")
+
+ # The envbuilder provider requires a key-value map of environment variables. Build this here.
+ envbuilder_env = {
+ # ENVBUILDER_GIT_URL and ENVBUILDER_CACHE_REPO will be overridden by the provider
+ # if the cache repo is enabled.
+ "ENVBUILDER_GIT_URL" : data.coder_parameter.repo_url.value,
+ # The agent token is required for the agent to connect to the Coder platform.
+ "CODER_AGENT_TOKEN" : try(coder_agent.dev.0.token, ""),
+ # The agent URL is required for the agent to connect to the Coder platform.
+ "CODER_AGENT_URL" : data.coder_workspace.me.access_url,
+ # The agent init script is required for the agent to start up. We base64 encode it here
+ # to avoid quoting issues.
+ "ENVBUILDER_INIT_SCRIPT" : "echo ${base64encode(try(coder_agent.dev[0].init_script, ""))} | base64 -d | sh",
+ "ENVBUILDER_DOCKER_CONFIG_BASE64" : local.docker_config_json_base64,
+ # The fallback image is the image that will run if the devcontainer fails to build.
+ "ENVBUILDER_FALLBACK_IMAGE" : data.coder_parameter.fallback_image.value,
+ # The following are used to push the image to the cache repo, if defined.
+ "ENVBUILDER_CACHE_REPO" : var.cache_repo,
+ "ENVBUILDER_PUSH_IMAGE" : var.cache_repo == "" ? "" : "true",
+ # You can add other required environment variables here.
+ # See: https://github.com/coder/envbuilder/?tab=readme-ov-file#environment-variables
+ }
+}
+
+# Check for the presence of a prebuilt image in the cache repo
+# that we can use instead.
+resource "envbuilder_cached_image" "cached" {
+ count = var.cache_repo == "" ? 0 : data.coder_workspace.me.start_count
+ builder_image = local.devcontainer_builder_image
+ git_url = data.coder_parameter.repo_url.value
+ cache_repo = var.cache_repo
+ extra_env = local.envbuilder_env
+}
+
+data "cloudinit_config" "user_data" {
+ gzip = false
+ base64_encode = false
+
+ boundary = "//"
+
+ part {
+ filename = "cloud-config.yaml"
+ content_type = "text/cloud-config"
+
+ content = templatefile("${path.module}/cloud-init/cloud-config.yaml.tftpl", {
+ hostname = local.hostname
+ linux_user = local.linux_user
+
+ ssh_pubkey = data.coder_parameter.ssh_pubkey.value
+ })
+ }
+
+ part {
+ filename = "userdata.sh"
+ content_type = "text/x-shellscript"
+
+ content = templatefile("${path.module}/cloud-init/userdata.sh.tftpl", {
+ hostname = local.hostname
+ linux_user = local.linux_user
+
+ # If we have a cached image, use the cached image's environment variables.
+ # Otherwise, just use the environment variables we've defined in locals.
+ environment = try(envbuilder_cached_image.cached[0].env_map, local.envbuilder_env)
+
+ # Builder image will either be the builder image parameter, or the cached image, if cache is provided.
+ builder_image = try(envbuilder_cached_image.cached[0].image, data.coder_parameter.devcontainer_builder.value)
+
+ docker_config_json_base64 = local.docker_config_json_base64
+ })
+ }
+}
+
+# This is useful for debugging the startup script. Left here for reference.
+# resource local_file "startup_script" {
+# content = data.cloudinit_config.user_data.rendered
+# filename = "${path.module}/user_data.txt"
+# }
+
+resource "aws_instance" "vm" {
+ ami = data.aws_ami.ubuntu.id
+ availability_zone = local.aws_availability_zone
+ instance_type = data.coder_parameter.instance_type.value
+ iam_instance_profile = try(data.aws_iam_instance_profile.vm_instance_profile[0].name, null)
+ root_block_device {
+ volume_size = data.coder_parameter.root_volume_size_gb.value
+ }
+
+ user_data = data.cloudinit_config.user_data.rendered
+ tags = {
+ Name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}"
+ # Required if you are using our example policy, see template README
+ Coder_Provisioned = "true"
+ }
+ lifecycle {
+ ignore_changes = [ami]
+ }
+}
+
+resource "aws_ec2_instance_state" "vm" {
+ instance_id = aws_instance.vm.id
+ state = data.coder_workspace.me.transition == "start" ? "running" : "stopped"
+}
+
+resource "coder_agent" "dev" {
+ count = data.coder_workspace.me.start_count
+ arch = "amd64"
+ auth = "token"
+ os = "linux"
+ dir = "/workspaces/${trimsuffix(basename(data.coder_parameter.repo_url.value), ".git")}"
+ connection_timeout = 0
+
+ metadata {
+ key = "cpu"
+ display_name = "CPU Usage"
+ interval = 5
+ timeout = 5
+ script = "coder stat cpu"
+ }
+ metadata {
+ key = "memory"
+ display_name = "Memory Usage"
+ interval = 5
+ timeout = 5
+ script = "coder stat mem"
+ }
+}
+
+resource "coder_metadata" "info" {
+ count = data.coder_workspace.me.start_count
+ resource_id = coder_agent.dev[0].id
+ item {
+ key = "ami"
+ value = aws_instance.vm.ami
+ }
+ item {
+ key = "availability_zone"
+ value = local.aws_availability_zone
+ }
+ item {
+ key = "instance_type"
+ value = data.coder_parameter.instance_type.value
+ }
+ item {
+ key = "ssh_pubkey"
+ value = data.coder_parameter.ssh_pubkey.value
+ }
+ item {
+ key = "repo_url"
+ value = data.coder_parameter.repo_url.value
+ }
+ item {
+ key = "devcontainer_builder"
+ value = data.coder_parameter.devcontainer_builder.value
+ }
+}
+
+# See https://registry.coder.com/modules/coder/code-server
+module "code-server" {
+ count = data.coder_workspace.me.start_count
+ source = "registry.coder.com/coder/code-server/coder"
+ # This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
+ version = "~> 1.0"
+ agent_id = coder_agent.dev[0].id
+}
\ No newline at end of file
diff --git a/infra/1-click/2-coder/templates/aws-linux/README.md b/infra/1-click/2-coder/templates/aws-linux/README.md
new file mode 100644
index 0000000..3575640
--- /dev/null
+++ b/infra/1-click/2-coder/templates/aws-linux/README.md
@@ -0,0 +1,94 @@
+---
+display_name: AWS EC2 (Linux)
+description: Provision AWS EC2 VMs as Coder workspaces
+icon: ../../../site/static/icon/aws.svg
+maintainer_github: coder
+verified: true
+tags: [vm, linux, aws, persistent-vm]
+---
+
+# Remote Development on AWS EC2 VMs (Linux)
+
+Provision AWS EC2 VMs as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.
+
+## Prerequisites
+
+### Authentication
+
+By default, this template authenticates to AWS using the provider's default [authentication methods](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration).
+
+The simplest way (without making changes to the template) is via environment variables (e.g. `AWS_ACCESS_KEY_ID`) or a [credentials file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-format). If you are running Coder on a VM, this file must be in `/home/coder/aws/credentials`.
+
+To use another [authentication method](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication), edit the template.
+
+## Required permissions / policy
+
+The following sample policy allows Coder to create EC2 instances and modify
+instances provisioned by Coder:
+
+```json
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Sid": "VisualEditor0",
+ "Effect": "Allow",
+ "Action": [
+ "ec2:GetDefaultCreditSpecification",
+ "ec2:DescribeIamInstanceProfileAssociations",
+ "ec2:DescribeTags",
+ "ec2:DescribeInstances",
+ "ec2:DescribeInstanceTypes",
+ "ec2:DescribeInstanceStatus",
+ "ec2:CreateTags",
+ "ec2:RunInstances",
+ "ec2:DescribeInstanceCreditSpecifications",
+ "ec2:DescribeImages",
+ "ec2:ModifyDefaultCreditSpecification",
+ "ec2:DescribeVolumes"
+ ],
+ "Resource": "*"
+ },
+ {
+ "Sid": "CoderResources",
+ "Effect": "Allow",
+ "Action": [
+ "ec2:DescribeInstanceAttribute",
+ "ec2:UnmonitorInstances",
+ "ec2:TerminateInstances",
+ "ec2:StartInstances",
+ "ec2:StopInstances",
+ "ec2:DeleteTags",
+ "ec2:MonitorInstances",
+ "ec2:CreateTags",
+ "ec2:RunInstances",
+ "ec2:ModifyInstanceAttribute",
+ "ec2:ModifyInstanceCreditSpecification"
+ ],
+ "Resource": "arn:aws:ec2:*:*:instance/*",
+ "Condition": {
+ "StringEquals": {
+ "aws:ResourceTag/Coder_Provisioned": "true"
+ }
+ }
+ }
+ ]
+}
+```
+
+## Architecture
+
+This template provisions the following resources:
+
+- AWS Instance
+
+Coder uses `aws_ec2_instance_state` to start and stop the VM. This example template is fully persistent, meaning the full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.
+
+> **Note**
+> This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.
+
+## code-server
+
+`code-server` is installed via the `startup_script` argument in the `coder_agent`
+resource block. The `coder_app` resource is defined to access `code-server` through
+the dashboard UI over `localhost:13337`.
\ No newline at end of file
diff --git a/infra/1-click/2-coder/templates/aws-linux/cloud-init/cloud-config.yaml.tftpl b/infra/1-click/2-coder/templates/aws-linux/cloud-init/cloud-config.yaml.tftpl
new file mode 100644
index 0000000..b0ae9b2
--- /dev/null
+++ b/infra/1-click/2-coder/templates/aws-linux/cloud-init/cloud-config.yaml.tftpl
@@ -0,0 +1,8 @@
+#cloud-config
+cloud_final_modules:
+ - [scripts-user, always]
+hostname: ${hostname}
+users:
+ - name: ${linux_user}
+ sudo: ALL=(ALL) NOPASSWD:ALL
+ shell: /bin/bash
\ No newline at end of file
diff --git a/infra/1-click/2-coder/templates/aws-linux/cloud-init/userdata.sh.tftpl b/infra/1-click/2-coder/templates/aws-linux/cloud-init/userdata.sh.tftpl
new file mode 100644
index 0000000..0045120
--- /dev/null
+++ b/infra/1-click/2-coder/templates/aws-linux/cloud-init/userdata.sh.tftpl
@@ -0,0 +1,2 @@
+#!/bin/bash
+sudo -u '${linux_user}' sh -c '${init_script}'
\ No newline at end of file
diff --git a/infra/1-click/2-coder/templates/aws-linux/main.tf b/infra/1-click/2-coder/templates/aws-linux/main.tf
new file mode 100644
index 0000000..ba22558
--- /dev/null
+++ b/infra/1-click/2-coder/templates/aws-linux/main.tf
@@ -0,0 +1,286 @@
+terraform {
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ }
+ cloudinit = {
+ source = "hashicorp/cloudinit"
+ }
+ aws = {
+ source = "hashicorp/aws"
+ }
+ }
+}
+
+# Last updated 2023-03-14
+# aws ec2 describe-regions | jq -r '[.Regions[].RegionName] | sort'
+data "coder_parameter" "region" {
+ name = "region"
+ display_name = "Region"
+ description = "The region to deploy the workspace in."
+ default = "us-east-1"
+ mutable = false
+ option {
+ name = "Asia Pacific (Tokyo)"
+ value = "ap-northeast-1"
+ icon = "/emojis/1f1ef-1f1f5.png"
+ }
+ option {
+ name = "Asia Pacific (Seoul)"
+ value = "ap-northeast-2"
+ icon = "/emojis/1f1f0-1f1f7.png"
+ }
+ option {
+ name = "Asia Pacific (Osaka)"
+ value = "ap-northeast-3"
+ icon = "/emojis/1f1ef-1f1f5.png"
+ }
+ option {
+ name = "Asia Pacific (Mumbai)"
+ value = "ap-south-1"
+ icon = "/emojis/1f1ee-1f1f3.png"
+ }
+ option {
+ name = "Asia Pacific (Singapore)"
+ value = "ap-southeast-1"
+ icon = "/emojis/1f1f8-1f1ec.png"
+ }
+ option {
+ name = "Asia Pacific (Sydney)"
+ value = "ap-southeast-2"
+ icon = "/emojis/1f1e6-1f1fa.png"
+ }
+ option {
+ name = "Canada (Central)"
+ value = "ca-central-1"
+ icon = "/emojis/1f1e8-1f1e6.png"
+ }
+ option {
+ name = "EU (Frankfurt)"
+ value = "eu-central-1"
+ icon = "/emojis/1f1ea-1f1fa.png"
+ }
+ option {
+ name = "EU (Stockholm)"
+ value = "eu-north-1"
+ icon = "/emojis/1f1ea-1f1fa.png"
+ }
+ option {
+ name = "EU (Ireland)"
+ value = "eu-west-1"
+ icon = "/emojis/1f1ea-1f1fa.png"
+ }
+ option {
+ name = "EU (London)"
+ value = "eu-west-2"
+ icon = "/emojis/1f1ea-1f1fa.png"
+ }
+ option {
+ name = "EU (Paris)"
+ value = "eu-west-3"
+ icon = "/emojis/1f1ea-1f1fa.png"
+ }
+ option {
+ name = "South America (São Paulo)"
+ value = "sa-east-1"
+ icon = "/emojis/1f1e7-1f1f7.png"
+ }
+ option {
+ name = "US East (N. Virginia)"
+ value = "us-east-1"
+ icon = "/emojis/1f1fa-1f1f8.png"
+ }
+ option {
+ name = "US East (Ohio)"
+ value = "us-east-2"
+ icon = "/emojis/1f1fa-1f1f8.png"
+ }
+ option {
+ name = "US West (N. California)"
+ value = "us-west-1"
+ icon = "/emojis/1f1fa-1f1f8.png"
+ }
+ option {
+ name = "US West (Oregon)"
+ value = "us-west-2"
+ icon = "/emojis/1f1fa-1f1f8.png"
+ }
+}
+
+data "coder_parameter" "instance_type" {
+ name = "instance_type"
+ display_name = "Instance type"
+ description = "What instance type should your workspace use?"
+ default = "t3.micro"
+ mutable = false
+ option {
+ name = "2 vCPU, 1 GiB RAM"
+ value = "t3.micro"
+ }
+ option {
+ name = "2 vCPU, 2 GiB RAM"
+ value = "t3.small"
+ }
+ option {
+ name = "2 vCPU, 4 GiB RAM"
+ value = "t3.medium"
+ }
+ option {
+ name = "2 vCPU, 8 GiB RAM"
+ value = "t3.large"
+ }
+ option {
+ name = "4 vCPU, 16 GiB RAM"
+ value = "t3.xlarge"
+ }
+ option {
+ name = "8 vCPU, 32 GiB RAM"
+ value = "t3.2xlarge"
+ }
+}
+
+provider "aws" {
+ region = data.coder_parameter.region.value
+}
+
+data "coder_workspace" "me" {}
+data "coder_workspace_owner" "me" {}
+
+data "aws_ami" "ubuntu" {
+ most_recent = true
+ filter {
+ name = "name"
+ values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
+ }
+ filter {
+ name = "virtualization-type"
+ values = ["hvm"]
+ }
+ owners = ["099720109477"] # Canonical
+}
+
+resource "coder_agent" "dev" {
+ count = data.coder_workspace.me.start_count
+ arch = "amd64"
+ auth = "aws-instance-identity"
+ os = "linux"
+ startup_script = <<-EOT
+ set -e
+
+ # Add any commands that should be executed at workspace startup (e.g install requirements, start a program, etc) here
+ EOT
+
+ metadata {
+ key = "cpu"
+ display_name = "CPU Usage"
+ interval = 5
+ timeout = 5
+ script = "coder stat cpu"
+ }
+ metadata {
+ key = "memory"
+ display_name = "Memory Usage"
+ interval = 5
+ timeout = 5
+ script = "coder stat mem"
+ }
+ metadata {
+ key = "disk"
+ display_name = "Disk Usage"
+ interval = 600 # every 10 minutes
+ timeout = 30 # df can take a while on large filesystems
+ script = "coder stat disk --path $HOME"
+ }
+}
+
+# See https://registry.coder.com/modules/coder/code-server
+module "code-server" {
+ count = data.coder_workspace.me.start_count
+ source = "registry.coder.com/modules/code-server/coder"
+
+ # This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
+ version = "~> 1.0"
+
+ agent_id = coder_agent.dev[0].id
+ order = 1
+}
+
+# See https://registry.coder.com/modules/coder/jetbrains
+module "jetbrains" {
+ count = data.coder_workspace.me.start_count
+ source = "registry.coder.com/coder/jetbrains/coder"
+ version = "~> 1.0"
+ agent_id = coder_agent.dev[0].id
+ agent_name = "dev"
+ folder = "/home/coder"
+}
+
+locals {
+ hostname = lower(data.coder_workspace.me.name)
+ linux_user = "coder"
+}
+
+data "cloudinit_config" "user_data" {
+ gzip = false
+ base64_encode = false
+
+ boundary = "//"
+
+ part {
+ filename = "cloud-config.yaml"
+ content_type = "text/cloud-config"
+
+ content = templatefile("${path.module}/cloud-init/cloud-config.yaml.tftpl", {
+ hostname = local.hostname
+ linux_user = local.linux_user
+ })
+ }
+
+ part {
+ filename = "userdata.sh"
+ content_type = "text/x-shellscript"
+
+ content = templatefile("${path.module}/cloud-init/userdata.sh.tftpl", {
+ linux_user = local.linux_user
+
+ init_script = try(coder_agent.dev[0].init_script, "")
+ })
+ }
+}
+
+resource "aws_instance" "dev" {
+ ami = data.aws_ami.ubuntu.id
+ availability_zone = "${data.coder_parameter.region.value}a"
+ instance_type = data.coder_parameter.instance_type.value
+
+ user_data = data.cloudinit_config.user_data.rendered
+ tags = {
+ Name = "coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}"
+ # Required if you are using our example policy, see template README
+ Coder_Provisioned = "true"
+ }
+ lifecycle {
+ ignore_changes = [ami]
+ }
+}
+
+resource "coder_metadata" "workspace_info" {
+ resource_id = aws_instance.dev.id
+ item {
+ key = "region"
+ value = data.coder_parameter.region.value
+ }
+ item {
+ key = "instance type"
+ value = aws_instance.dev.instance_type
+ }
+ item {
+ key = "disk"
+ value = "${aws_instance.dev.root_block_device[0].volume_size} GiB"
+ }
+}
+
+resource "aws_ec2_instance_state" "dev" {
+ instance_id = aws_instance.dev.id
+ state = data.coder_workspace.me.transition == "start" ? "running" : "stopped"
+}
\ No newline at end of file
diff --git a/infra/1-click/2-coder/templates/aws-windows/README.md b/infra/1-click/2-coder/templates/aws-windows/README.md
new file mode 100644
index 0000000..b5af8cb
--- /dev/null
+++ b/infra/1-click/2-coder/templates/aws-windows/README.md
@@ -0,0 +1,96 @@
+---
+display_name: AWS EC2 (Windows)
+description: Provision AWS EC2 VMs as Coder workspaces
+icon: ../../../site/static/icon/aws.svg
+maintainer_github: coder
+verified: true
+tags: [vm, windows, aws]
+---
+
+# Remote Development on AWS EC2 VMs (Windows)
+
+Provision AWS EC2 Windows VMs as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.
+
+
+
+## Prerequisites
+
+### Authentication
+
+By default, this template authenticates to AWS with using the provider's default [authentication methods](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration).
+
+The simplest way (without making changes to the template) is via environment variables (e.g. `AWS_ACCESS_KEY_ID`) or a [credentials file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-format). If you are running Coder on a VM, this file must be in `/home/coder/aws/credentials`.
+
+To use another [authentication method](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication), edit the template.
+
+## Required permissions / policy
+
+The following sample policy allows Coder to create EC2 instances and modify
+instances provisioned by Coder:
+
+```json
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Sid": "VisualEditor0",
+ "Effect": "Allow",
+ "Action": [
+ "ec2:GetDefaultCreditSpecification",
+ "ec2:DescribeIamInstanceProfileAssociations",
+ "ec2:DescribeTags",
+ "ec2:DescribeInstances",
+ "ec2:DescribeInstanceTypes",
+ "ec2:DescribeInstanceStatus",
+ "ec2:CreateTags",
+ "ec2:RunInstances",
+ "ec2:DescribeInstanceCreditSpecifications",
+ "ec2:DescribeImages",
+ "ec2:ModifyDefaultCreditSpecification",
+ "ec2:DescribeVolumes"
+ ],
+ "Resource": "*"
+ },
+ {
+ "Sid": "CoderResources",
+ "Effect": "Allow",
+ "Action": [
+ "ec2:DescribeInstanceAttribute",
+ "ec2:UnmonitorInstances",
+ "ec2:TerminateInstances",
+ "ec2:StartInstances",
+ "ec2:StopInstances",
+ "ec2:DeleteTags",
+ "ec2:MonitorInstances",
+ "ec2:CreateTags",
+ "ec2:RunInstances",
+ "ec2:ModifyInstanceAttribute",
+ "ec2:ModifyInstanceCreditSpecification"
+ ],
+ "Resource": "arn:aws:ec2:*:*:instance/*",
+ "Condition": {
+ "StringEquals": {
+ "aws:ResourceTag/Coder_Provisioned": "true"
+ }
+ }
+ }
+ ]
+}
+```
+
+## Architecture
+
+This template provisions the following resources:
+
+- AWS Instance
+
+Coder uses `aws_ec2_instance_state` to start and stop the VM. This example template is fully persistent, meaning the full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.
+
+> **Note**
+> This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.
+
+## code-server
+
+`code-server` is installed via the `startup_script` argument in the `coder_agent`
+resource block. The `coder_app` resource is defined to access `code-server` through
+the dashboard UI over `localhost:13337`.
\ No newline at end of file
diff --git a/infra/1-click/2-coder/templates/aws-windows/main.tf b/infra/1-click/2-coder/templates/aws-windows/main.tf
new file mode 100644
index 0000000..d157b24
--- /dev/null
+++ b/infra/1-click/2-coder/templates/aws-windows/main.tf
@@ -0,0 +1,214 @@
+terraform {
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ }
+ aws = {
+ source = "hashicorp/aws"
+ }
+ }
+}
+
+# Last updated 2023-03-14
+# aws ec2 describe-regions | jq -r '[.Regions[].RegionName] | sort'
+data "coder_parameter" "region" {
+ name = "region"
+ display_name = "Region"
+ description = "The region to deploy the workspace in."
+ default = "us-east-1"
+ mutable = false
+ option {
+ name = "Asia Pacific (Tokyo)"
+ value = "ap-northeast-1"
+ icon = "/emojis/1f1ef-1f1f5.png"
+ }
+ option {
+ name = "Asia Pacific (Seoul)"
+ value = "ap-northeast-2"
+ icon = "/emojis/1f1f0-1f1f7.png"
+ }
+ option {
+ name = "Asia Pacific (Osaka-Local)"
+ value = "ap-northeast-3"
+ icon = "/emojis/1f1f0-1f1f7.png"
+ }
+ option {
+ name = "Asia Pacific (Mumbai)"
+ value = "ap-south-1"
+ icon = "/emojis/1f1f0-1f1f7.png"
+ }
+ option {
+ name = "Asia Pacific (Singapore)"
+ value = "ap-southeast-1"
+ icon = "/emojis/1f1f0-1f1f7.png"
+ }
+ option {
+ name = "Asia Pacific (Sydney)"
+ value = "ap-southeast-2"
+ icon = "/emojis/1f1f0-1f1f7.png"
+ }
+ option {
+ name = "Canada (Central)"
+ value = "ca-central-1"
+ icon = "/emojis/1f1e8-1f1e6.png"
+ }
+ option {
+ name = "EU (Frankfurt)"
+ value = "eu-central-1"
+ icon = "/emojis/1f1ea-1f1fa.png"
+ }
+ option {
+ name = "EU (Stockholm)"
+ value = "eu-north-1"
+ icon = "/emojis/1f1ea-1f1fa.png"
+ }
+ option {
+ name = "EU (Ireland)"
+ value = "eu-west-1"
+ icon = "/emojis/1f1ea-1f1fa.png"
+ }
+ option {
+ name = "EU (London)"
+ value = "eu-west-2"
+ icon = "/emojis/1f1ea-1f1fa.png"
+ }
+ option {
+ name = "EU (Paris)"
+ value = "eu-west-3"
+ icon = "/emojis/1f1ea-1f1fa.png"
+ }
+ option {
+ name = "South America (São Paulo)"
+ value = "sa-east-1"
+ icon = "/emojis/1f1e7-1f1f7.png"
+ }
+ option {
+ name = "US East (N. Virginia)"
+ value = "us-east-1"
+ icon = "/emojis/1f1fa-1f1f8.png"
+ }
+ option {
+ name = "US East (Ohio)"
+ value = "us-east-2"
+ icon = "/emojis/1f1fa-1f1f8.png"
+ }
+ option {
+ name = "US West (N. California)"
+ value = "us-west-1"
+ icon = "/emojis/1f1fa-1f1f8.png"
+ }
+ option {
+ name = "US West (Oregon)"
+ value = "us-west-2"
+ icon = "/emojis/1f1fa-1f1f8.png"
+ }
+}
+
+data "coder_parameter" "instance_type" {
+ name = "instance_type"
+ display_name = "Instance type"
+ description = "What instance type should your workspace use?"
+ default = "t3.micro"
+ mutable = false
+ option {
+ name = "2 vCPU, 1 GiB RAM"
+ value = "t3.micro"
+ }
+ option {
+ name = "2 vCPU, 2 GiB RAM"
+ value = "t3.small"
+ }
+ option {
+ name = "2 vCPU, 4 GiB RAM"
+ value = "t3.medium"
+ }
+ option {
+ name = "2 vCPU, 8 GiB RAM"
+ value = "t3.large"
+ }
+ option {
+ name = "4 vCPU, 16 GiB RAM"
+ value = "t3.xlarge"
+ }
+ option {
+ name = "8 vCPU, 32 GiB RAM"
+ value = "t3.2xlarge"
+ }
+}
+
+provider "aws" {
+ region = data.coder_parameter.region.value
+}
+
+data "coder_workspace" "me" {
+}
+data "coder_workspace_owner" "me" {}
+
+data "aws_ami" "windows" {
+ most_recent = true
+ owners = ["amazon"]
+
+ filter {
+ name = "name"
+ values = ["Windows_Server-2019-English-Full-Base-*"]
+ }
+}
+
+resource "coder_agent" "main" {
+ arch = "amd64"
+ auth = "aws-instance-identity"
+ os = "windows"
+}
+
+locals {
+
+ # User data is used to stop/start AWS instances. See:
+ # https://github.com/hashicorp/terraform-provider-aws/issues/22
+
+ user_data_start = <
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+${coder_agent.main.init_script}
+
+true
+EOT
+
+ user_data_end = <
+shutdown /s
+
+true
+EOT
+}
+
+resource "aws_instance" "dev" {
+ ami = data.aws_ami.windows.id
+ availability_zone = "${data.coder_parameter.region.value}a"
+ instance_type = data.coder_parameter.instance_type.value
+
+ user_data = data.coder_workspace.me.transition == "start" ? local.user_data_start : local.user_data_end
+ tags = {
+ Name = "coder-${data.coder_workspace_owner.me.name}-${data.coder_workspace.me.name}"
+ # Required if you are using our example policy, see template README
+ Coder_Provisioned = "true"
+ }
+ lifecycle {
+ ignore_changes = [ami]
+ }
+}
+
+resource "coder_metadata" "workspace_info" {
+ resource_id = aws_instance.dev.id
+ item {
+ key = "region"
+ value = data.coder_parameter.region.value
+ }
+ item {
+ key = "instance type"
+ value = aws_instance.dev.instance_type
+ }
+ item {
+ key = "disk"
+ value = "${aws_instance.dev.root_block_device[0].volume_size} GiB"
+ }
+}
\ No newline at end of file
diff --git a/infra/1-click/2-coder/templates/kubernetes-claude/README.md b/infra/1-click/2-coder/templates/kubernetes-claude/README.md
new file mode 100644
index 0000000..e687f92
--- /dev/null
+++ b/infra/1-click/2-coder/templates/kubernetes-claude/README.md
@@ -0,0 +1,105 @@
+---
+display_name: Claude Code on Coder
+description: Provision Kubernetes workspaces with Claude Code and code-server
+icon: ../../../site/static/icon/claude.svg
+maintainer_github: coder
+verified: true
+tags: [kubernetes, container, claude, ai]
+---
+
+# Kubernetes Workspaces with Claude Code
+
+Provision Kubernetes-based [Coder workspaces](https://coder.com/docs/workspaces) with Claude Code and code-server pre-configured.
+
+## Features
+
+- **Claude Code**: AI-powered coding assistant via the [claude-code module](https://registry.coder.com/modules/claude-code)
+- **code-server**: Browser-based VS Code experience
+- **Persistent Storage**: Home directory persists across workspace restarts
+- **Resource Control**: Configurable CPU, memory, and disk allocation
+- **Workspace Metrics**: Real-time CPU, memory, and disk usage displayed in the dashboard
+
+## Prerequisites
+
+### Infrastructure
+
+- **Kubernetes Cluster**: An existing Kubernetes cluster with sufficient resources
+- **Namespace**: A pre-existing namespace for workspace deployments
+- **Storage Class**: A default storage class for persistent volume claims
+
+### Authentication
+
+This template supports two authentication methods:
+
+1. **In-cluster authentication** (default): When Coder runs as a pod in the same cluster, it uses the ServiceAccount for authentication. Set `use_kubeconfig = false`.
+
+2. **Kubeconfig authentication**: When Coder runs outside the cluster, provide a valid `~/.kube/config` on the Coder host. Set `use_kubeconfig = true`.
+
+## Configuration
+
+### Template Variables
+
+| Variable | Type | Default | Description |
+|----------|------|---------|-------------|
+| `use_kubeconfig` | bool | `false` | Use host kubeconfig for authentication |
+| `namespace` | string | required | Kubernetes namespace for workspaces |
+
+### Workspace Parameters
+
+Users can customize these settings when creating a workspace:
+
+| Parameter | Options | Default | Description |
+|-----------|---------|---------|-------------|
+| CPU | 2, 4, 6, 8 cores | 2 | CPU cores allocated to the workspace |
+| Memory | 2, 4, 6, 8 GB | 2 | Memory allocated to the workspace |
+| Home Disk Size | 1-99999 GB | 10 | Persistent storage for `/home/coder` |
+
+## Architecture
+
+This template provisions the following Kubernetes resources:
+
+- **Deployment**: A single-replica deployment running the workspace container
+- **Persistent Volume Claim**: Storage for the `/home/coder` directory
+
+### Container Image
+
+Uses `codercom/enterprise-base:ubuntu` which includes common development tools. To customize:
+
+- Extend the image with additional tools
+- Build your own image based on the [enterprise-images repository](https://github.com/coder/enterprise-images)
+
+### Included Applications
+
+| Application | Access | Description |
+|-------------|--------|-------------|
+| Claude Code | Subdomain | AI coding assistant with AI Bridge enabled |
+| code-server | Subdomain | Browser-based VS Code |
+
+### Workspace Metadata
+
+The dashboard displays real-time metrics:
+
+- CPU Usage (container and host)
+- RAM Usage (container and host)
+- Home Disk Usage
+- Load Average (host)
+
+## Pod Scheduling
+
+Workspaces use pod anti-affinity to distribute pods across nodes, improving cluster utilization and resilience.
+
+## Security
+
+- Containers run as non-root user (UID 1000)
+- File system group set to 1000
+- Resource limits enforced based on user-selected parameters
+
+## Customization
+
+This template is designed as a starting point. Common modifications include:
+
+- Adding environment variables
+- Mounting secrets or configmaps
+- Changing the container image
+- Adding init containers
+- Configuring node selectors or tolerations
diff --git a/infra/1-click/2-coder/templates/kubernetes-claude/main.tf b/infra/1-click/2-coder/templates/kubernetes-claude/main.tf
new file mode 100644
index 0000000..28817a2
--- /dev/null
+++ b/infra/1-click/2-coder/templates/kubernetes-claude/main.tf
@@ -0,0 +1,383 @@
+terraform {
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ }
+ kubernetes = {
+ source = "hashicorp/kubernetes"
+ }
+ }
+}
+
+provider "coder" {}
+
+variable "use_kubeconfig" {
+ type = bool
+ description = <<-EOF
+ Use host kubeconfig? (true/false)
+
+ Set this to false if the Coder host is itself running as a Pod on the same
+ Kubernetes cluster as you are deploying workspaces to.
+
+ Set this to true if the Coder host is running outside the Kubernetes cluster
+ for workspaces. A valid "~/.kube/config" must be present on the Coder host.
+ EOF
+ default = false
+}
+
+variable "namespace" {
+ type = string
+ description = "The Kubernetes namespace to create workspaces in (must exist prior to creating workspaces). If the Coder host is itself running as a Pod on the same Kubernetes cluster as you are deploying workspaces to, set this to the same namespace."
+}
+
+variable "host_ip" {
+ type = string
+ description = "The IP address to use for hostAliases in workspace pods. If empty, no hostAliases will be added."
+ default = ""
+}
+
+data "coder_parameter" "cpu" {
+ name = "cpu"
+ display_name = "CPU"
+ description = "The number of CPU cores"
+ default = "2"
+ icon = "/icon/memory.svg"
+ mutable = true
+ option {
+ name = "2 Cores"
+ value = "2"
+ }
+ option {
+ name = "4 Cores"
+ value = "4"
+ }
+ option {
+ name = "6 Cores"
+ value = "6"
+ }
+ option {
+ name = "8 Cores"
+ value = "8"
+ }
+}
+
+data "coder_parameter" "memory" {
+ name = "memory"
+ display_name = "Memory"
+ description = "The amount of memory in GB"
+ default = "2"
+ icon = "/icon/memory.svg"
+ mutable = true
+ option {
+ name = "2 GB"
+ value = "2"
+ }
+ option {
+ name = "4 GB"
+ value = "4"
+ }
+ option {
+ name = "6 GB"
+ value = "6"
+ }
+ option {
+ name = "8 GB"
+ value = "8"
+ }
+}
+
+data "coder_parameter" "home_disk_size" {
+ name = "home_disk_size"
+ display_name = "Home disk size"
+ description = "The size of the home disk in GB"
+ default = "10"
+ type = "number"
+ icon = "/emojis/1f4be.png"
+ mutable = false
+ validation {
+ min = 1
+ max = 99999
+ }
+}
+
+provider "kubernetes" {
+ # Authenticate via ~/.kube/config or a Coder-specific ServiceAccount, depending on admin preferences
+ config_path = var.use_kubeconfig == true ? "~/.kube/config" : null
+}
+
+data "coder_workspace" "me" {}
+data "coder_workspace_owner" "me" {}
+
+resource "coder_agent" "main" {
+ os = "linux"
+ arch = "amd64"
+
+ # The following metadata blocks are optional. They are used to display
+ # information about your workspace in the dashboard. You can remove them
+ # if you don't want to display any information.
+ # For basic resources, you can use the `coder stat` command.
+ # If you need more control, you can write your own script.
+ metadata {
+ display_name = "CPU Usage"
+ key = "0_cpu_usage"
+ script = "coder stat cpu"
+ interval = 10
+ timeout = 1
+ }
+
+ metadata {
+ display_name = "RAM Usage"
+ key = "1_ram_usage"
+ script = "coder stat mem"
+ interval = 10
+ timeout = 1
+ }
+
+ metadata {
+ display_name = "Home Disk"
+ key = "3_home_disk"
+ script = "coder stat disk --path $${HOME}"
+ interval = 60
+ timeout = 1
+ }
+
+ metadata {
+ display_name = "CPU Usage (Host)"
+ key = "4_cpu_usage_host"
+ script = "coder stat cpu --host"
+ interval = 10
+ timeout = 1
+ }
+
+ metadata {
+ display_name = "Memory Usage (Host)"
+ key = "5_mem_usage_host"
+ script = "coder stat mem --host"
+ interval = 10
+ timeout = 1
+ }
+
+ metadata {
+ display_name = "Load Average (Host)"
+ key = "6_load_host"
+ # get load avg scaled by number of cores
+ script = <`
+- Check pod events: `kubectl describe pod -n coder-`
+- Verify StorageClass is available: `kubectl get sc`
+
+### Cannot connect to workspace
+
+- Ensure the Coder agent is running: check pod logs
+- Verify network policies allow traffic to the workspace
+
+### Persistent volume not binding
+
+- Check PVC status: `kubectl get pvc -n `
+- Verify StorageClass supports dynamic provisioning
diff --git a/infra/1-click/2-coder/templates/kubernetes/main.tf b/infra/1-click/2-coder/templates/kubernetes/main.tf
new file mode 100644
index 0000000..e63cd73
--- /dev/null
+++ b/infra/1-click/2-coder/templates/kubernetes/main.tf
@@ -0,0 +1,365 @@
+terraform {
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ }
+ kubernetes = {
+ source = "hashicorp/kubernetes"
+ }
+ }
+}
+
+provider "coder" {}
+
+variable "use_kubeconfig" {
+ type = bool
+ description = <<-EOF
+ Use host kubeconfig? (true/false)
+
+ Set this to false if the Coder host is itself running as a Pod on the same
+ Kubernetes cluster as you are deploying workspaces to.
+
+ Set this to true if the Coder host is running outside the Kubernetes cluster
+ for workspaces. A valid "~/.kube/config" must be present on the Coder host.
+ EOF
+ default = false
+}
+
+variable "namespace" {
+ type = string
+ description = "The Kubernetes namespace to create workspaces in (must exist prior to creating workspaces). If the Coder host is itself running as a Pod on the same Kubernetes cluster as you are deploying workspaces to, set this to the same namespace."
+}
+
+variable "host_ip" {
+ type = string
+ description = "The IP address to use for hostAliases in workspace pods. If empty, no hostAliases will be added."
+ default = ""
+}
+
+data "coder_parameter" "cpu" {
+ name = "cpu"
+ display_name = "CPU"
+ description = "The number of CPU cores"
+ default = "2"
+ icon = "/icon/memory.svg"
+ mutable = true
+ option {
+ name = "2 Cores"
+ value = "2"
+ }
+ option {
+ name = "4 Cores"
+ value = "4"
+ }
+ option {
+ name = "6 Cores"
+ value = "6"
+ }
+ option {
+ name = "8 Cores"
+ value = "8"
+ }
+}
+
+data "coder_parameter" "memory" {
+ name = "memory"
+ display_name = "Memory"
+ description = "The amount of memory in GB"
+ default = "2"
+ icon = "/icon/memory.svg"
+ mutable = true
+ option {
+ name = "2 GB"
+ value = "2"
+ }
+ option {
+ name = "4 GB"
+ value = "4"
+ }
+ option {
+ name = "6 GB"
+ value = "6"
+ }
+ option {
+ name = "8 GB"
+ value = "8"
+ }
+}
+
+data "coder_parameter" "home_disk_size" {
+ name = "home_disk_size"
+ display_name = "Home disk size"
+ description = "The size of the home disk in GB"
+ default = "10"
+ type = "number"
+ icon = "/emojis/1f4be.png"
+ mutable = false
+ validation {
+ min = 1
+ max = 99999
+ }
+}
+
+provider "kubernetes" {
+ # Authenticate via ~/.kube/config or a Coder-specific ServiceAccount, depending on admin preferences
+ config_path = var.use_kubeconfig == true ? "~/.kube/config" : null
+}
+
+data "coder_workspace" "me" {}
+data "coder_workspace_owner" "me" {}
+
+resource "coder_agent" "main" {
+ os = "linux"
+ arch = "amd64"
+ startup_script = <<-EOT
+ set -e
+
+ # Install the latest code-server.
+ # Append "--version x.x.x" to install a specific version of code-server.
+ curl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server
+
+ # Start code-server in the background.
+ /tmp/code-server/bin/code-server --auth none --port 13337 >/tmp/code-server.log 2>&1 &
+ EOT
+
+ # The following metadata blocks are optional. They are used to display
+ # information about your workspace in the dashboard. You can remove them
+ # if you don't want to display any information.
+ # For basic resources, you can use the `coder stat` command.
+ # If you need more control, you can write your own script.
+ metadata {
+ display_name = "CPU Usage"
+ key = "0_cpu_usage"
+ script = "coder stat cpu"
+ interval = 10
+ timeout = 1
+ }
+
+ metadata {
+ display_name = "RAM Usage"
+ key = "1_ram_usage"
+ script = "coder stat mem"
+ interval = 10
+ timeout = 1
+ }
+
+ metadata {
+ display_name = "Home Disk"
+ key = "3_home_disk"
+ script = "coder stat disk --path $${HOME}"
+ interval = 60
+ timeout = 1
+ }
+
+ metadata {
+ display_name = "CPU Usage (Host)"
+ key = "4_cpu_usage_host"
+ script = "coder stat cpu --host"
+ interval = 10
+ timeout = 1
+ }
+
+ metadata {
+ display_name = "Memory Usage (Host)"
+ key = "5_mem_usage_host"
+ script = "coder stat mem --host"
+ interval = 10
+ timeout = 1
+ }
+
+ metadata {
+ display_name = "Load Average (Host)"
+ key = "6_load_host"
+ # get load avg scaled by number of cores
+ script = <= v1.14.1](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli)
+- [Terragrunt (>= v0.78.0)](https://terragrunt.gruntwork.io/docs/getting-started/install/)
+- [AWS CLI (>= v2.33.23)](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
+- [AWS Account](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-creating.html)
+- [AWS Bedrock/Anthropic Agreement Completed](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html).
+- [AWS Route53 Domain](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-register.html#domain-register-procedure-section)
+
+### Enterprise
+- Same OSS requirements.
+- An [Enterprise Coder License](https://coder.com/docs/admin/licensing).
+
+# Getting Started
+
+1. Create or acquire an AWS account
+2. Login as a user with AdministratorAccess
+3. Setup your local machine to have an [AWS profile](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html).
+ - When running `aws configure`, this will automatically setup a `default` profile for you.
+ - If using `aws configure sso` you must manually set the profile name to `default`. Afterwards, you can login with `aws sso login`
+ - If using `aws login` follow this [thread](https://github.com/hashicorp/terraform-provider-aws/issues/45316) to see the fix.
+
+4. Purchase/Register a domain in Route53 and tie it to a public zone. When purchasing, it'll create a zone in a few minutes, wait for this. If using an existing domain, cleanup any conflicting A/AAAA/CNAME/TXT records.
+
+5. Fill the `coder.env` file with the following environment variables:
+
+```env
+CODER_AWS_PROFILE=default # (Change profile if needed)
+CODER_AWS_REGION=us-east-2 # (Change region if needed)
+CODER_DOMAIN_NAME=put.your.domain.here.com
+CODER_LICENSE=abcde1234..... # (Optional)
+```
+
+> [!IMPORTANT]
+> Upon creating the initial deployment, DO NOT CHANGE THE DOMAIN. Clean up first, then recreate it. The entire infrastructure depends on the name.
+
+6. Initialize the project:
+
+```bash
+./0-init.sh
+```
+
+7. Finally, deploy:
+
+```bash
+./1-apply.sh
+```
+
+## Logging In
+
+1. On your browser, go to "https://put.your.domain.here.com"
+2. Enter the below login details (if you didn't override it):
+
+```
+Email: admin@coder.com
+Password: Th1s1sN0TS3CuR3!!
+```
+
+3. You're done!
+
+## Cleaning Up
+
+1. [Delete all Coder workspaces](https://coder.com/docs/user-guides/workspace-lifecycle#deleting-workspaces).
+
+2. Run this script to tear down the deployment:
+
+```bash
+./2-clean.sh
+```
+
+## Advanced Configuration
+
+### Using a Remote State
+
+You can enable the deployment to store your terraform state remotely if you need to interact with this outside your local desktop.
+
+This is useful if you plan on running this deployment within an ephemeral environment and need to persist it's state.
+
+Otherwise, if you're running this on your laptop or on a machine that persists state, then feel free to keep the state local.
+
+If using a remote state, then follow this to setup the S3 backend: https://spacelift.io/blog/terraform-s3-backend#how-to-create-a-terraform-s3-backend
+
+Follow this link if you need more details on Terraform's S3 backend: https://developer.hashicorp.com/terraform/language/backend/s3
+
+Once the AWS S3 Bucket is setup, set the following environment variables on your `coder.env` file:
+
+```
+CODER_TF_USE_REMOTE_STATE=true
+CODER_TF_BACKEND_AWS_BUCKET_NAME=
+CODER_TF_BACKEND_AWS_REGION=
+```
+
+### Changing the Coder Version
+
+There's no rolling back in Coder, only updating forward. If you need to change the Coder version, change the following environment variable:
+
+```
+CODER_VERSION=
+```
+
+Make sure that the version is ALWAYS greater than the previous.
+
+# Troubleshooting
+
+- When logging in to AWS you might run into issues such as: `An error occurred (InvalidRequestException)`. Make sure that:
+ - Your spelling is correct.
+ - The correct region for your SSO Start URL is set.
+ - You're consuming the correct SSO URL.
+
+- When initializing the project, and if you ran `aws login`, you might run into: `Error: failed to refresh cached credentials, no EC2 IMDS role found`.
+ - View this [thread]((https://github.com/hashicorp/terraform-provider-aws/issues/45316)) to see how to workaround this. Otherwise, try the other options to login (manually setting the profile/credentials, or use AWS SSO).
+
+- To verify the state of the infrastructure, visit the AWS Console and look at the pages of following AWS services that this solution deploys/manages:
+ - EC2
+ - VPC (NAT Gateway, Subnets, Security Groups, EIPs)
+ - RDS (Database, Snapshots, and SubnetGroups)
+ - EKS
+ - Route53
+ - CloudWatch Logs
+ - Bedrock
+
+- To verify the cluster state, run `aws eks update-kubeconfig --name --region --profile ` and execute kubectl commands.
+
+- To verify the cluster addons, inspect the status/logs of the following cluster addons:
+ - vpc-cni
+ - coredns
+ - metrics-server
+ - karpenter
+ - aws-load-balancer-controller (lb-ctrl)
+ - aws-ebs-csi (ebs-ctrl)
+
+- To verify the status of resources tied to CRDs, you can inspect the following:
+ - Service (check the aws-load-balancer-controller deployment for logs)
+ - PersistentVolumeClaim (check the ebs-csi deployment for logs)
+ - NodePool (EKS AutoMode + Karpenter)
+ - EC2NodeClass (Karpenter)
+ - NodeClass (EKS AutoMode)
+
+
+- Be careful with running this deployment multiple times in succession. You may be getting rate-limited by AWS.
+
+- If the deployment fails on "data.external.first-user", then you might be running into:
+ - DNS hasn't propagated yet (you may need to re-run this after waiting a few more minutes)
+ - Domain name records not set properly. Check Route53 for your domain to see if the A record was properly set to the correct IP.
+ - Local DNS may not also be refreshed yet either (i.e. curl may fail, but browser still accessible)
+ - Load Balancer health checks may be failing (i.e. Availability Zone is temporarily unavailable, or Coder failed to start.)
+
+- EKS Addons may fail installation due to "taking too long". Try re-running the deployment script to verify if changes have finished applying
\ No newline at end of file
diff --git a/infra/1-click/coder.env b/infra/1-click/coder.env
new file mode 100644
index 0000000..c0849e2
--- /dev/null
+++ b/infra/1-click/coder.env
@@ -0,0 +1,14 @@
+CODER_TF_USE_REMOTE_STATE=false
+CODER_TF_BACKEND_AWS_BUCKET_NAME=
+CODER_TF_BACKEND_AWS_REGION=
+
+CODER_AWS_REGION=us-east-2
+
+CODER_VERSION=2.30.0
+CODER_LICENSE=
+
+CODER_USERNAME=admin
+CODER_EMAIL=admin@coder.com
+CODER_PASSWORD=Th1s1sN0TS3CuR3!!
+
+CODER_DOMAIN_NAME=
\ No newline at end of file
diff --git a/infra/1-click/root.hcl b/infra/1-click/root.hcl
new file mode 100644
index 0000000..4d1168f
--- /dev/null
+++ b/infra/1-click/root.hcl
@@ -0,0 +1,99 @@
+locals {
+ CODER_TF_BACKEND_AWS_BUCKET_NAME = get_env("CODER_TF_BACKEND_AWS_BUCKET_NAME")
+ CODER_TF_BACKEND_AWS_REGION = get_env("CODER_TF_BACKEND_AWS_REGION", "us-east-2")
+ CODER_TF_BACKEND_AWS_PROFILE = get_env("CODER_TF_BACKEND_AWS_PROFILE", "default")
+ CODER_TF_BACKEND_ENCRYPT = get_env("CODER_TF_BACKEND_ENCRYPT", "false")
+ CODER_TF_USE_REMOTE_STATE = get_env("CODER_TF_USE_REMOTE_STATE", "false") == "true"
+
+ CODER_AWS_PROFILE = get_env("CODER_AWS_PROFILE", "default")
+ CODER_AWS_REGION = get_env("CODER_AWS_REGION", "us-east-2")
+ CODER_AWS_AZS = get_env("CODER_AWS_AZS", jsonencode(["a", "c"]))
+
+ CODER_DB_USERNAME = get_env("CODER_DB_USERNAME", "coder")
+ CODER_DB_PASSWORD = get_env("CODER_DB_PASSWORD", "th1s1sn0tas3cur3pass0wrd")
+ GRAFANA_DB_USERNAME = get_env("GRAFANA_DB_USERNAME", "grafana")
+ GRAFANA_DB_PASSWORD = get_env("GRAFANA_DB_PASSWORD", "th1s1sn0tas3cur3pass0wrd")
+
+ CODER_DOMAIN_NAME = get_env("CODER_DOMAIN_NAME")
+ CODER_LICENSE = get_env("CODER_LICENSE", "")
+ CODER_VERSION = get_env("CODER_VERSION", "2.30.0")
+
+ CODER_USERNAME = get_env("CODER_USERNAME", "admin")
+ CODER_EMAIL = get_env("CODER_EMAIL", "admin@coder.com")
+ CODER_PASSWORD = get_env("CODER_PASSWORD", "Th1s1sN0TS3CuR3!!")
+
+ GRAFANA_USERNAME = get_env("GRAFANA_ADMIN_USERNAME", "admin")
+ GRAFANA_PASSWORD = get_env("GRAFANA_ADMIN_PASSWORD", "Th1s1sN0TS3CuR3!!")
+}
+
+generate "backend" {
+ path = "backend.tf"
+ if_exists = "overwrite"
+ disable = !local.CODER_TF_USE_REMOTE_STATE
+ if_disabled = "remove_terragrunt"
+
+ contents = <<-EOF
+ terraform {
+ backend "s3" {
+ bucket = "${local.CODER_TF_BACKEND_AWS_BUCKET_NAME}"
+ key = "${path_relative_to_include()}/terraform.tfstate"
+ region = "${local.CODER_TF_BACKEND_AWS_REGION}"
+ profile = "${local.CODER_TF_BACKEND_AWS_PROFILE}"
+ encrypt = "${local.CODER_TF_BACKEND_ENCRYPT}"
+ }
+ }
+ EOF
+}
+
+generate "provider" {
+ path = "provider.tf"
+ if_exists = "overwrite"
+
+ contents = <<-EOF
+ terraform {
+ required_version = ">= 1.0"
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 6.32.1"
+ }
+ helm = {
+ source = "hashicorp/helm"
+ version = "~> 3.1.1"
+ }
+ kubernetes = {
+ source = "hashicorp/kubernetes"
+ version = "~> 3.0.1"
+ }
+ external = {
+ source = "hashicorp/external"
+ version = "~> 2.3.5"
+ }
+ time = {
+ source = "hashicorp/time"
+ version = "~> 0.13.1"
+ }
+ http = {
+ source = "hashicorp/http"
+ version = "~> 3.5.0"
+ }
+ dns = {
+ source = "hashicorp/dns"
+ version = "~> 3.5.0"
+ }
+ archive = {
+ source = "hashicorp/archive"
+ version = "~> 2.7.1"
+ }
+ random = {
+ source = "hashicorp/random"
+ version = "~> 3.8.1"
+ }
+ coderd = {
+ source = "coder/coderd"
+ version = "~> 0.0.12"
+ }
+ }
+ }
+ EOF
+}
\ No newline at end of file
diff --git a/infra/aws/0-init.sh b/infra/aws/0-init.sh
new file mode 100755
index 0000000..5b00bc9
--- /dev/null
+++ b/infra/aws/0-init.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -ae -o pipefail
+
+source coder.env
+
+terragrunt run --all --non-interactive --config root.hcl $@ init -- -migrate-state -upgrade
\ No newline at end of file
diff --git a/infra/aws/1-plan.sh b/infra/aws/1-plan.sh
new file mode 100755
index 0000000..82fe3a5
--- /dev/null
+++ b/infra/aws/1-plan.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+set -ae -o pipefail
+
+source coder.env
+
+echo $@
+
+TG_ARGS=()
+TF_ARGS=()
+
+while [[ $# -gt 0 ]]; do
+ if [[ "$1" == "--" ]]; then
+ shift
+ TF_ARGS=("$@")
+ break
+ fi
+
+ TG_ARGS+=("$1")
+ shift
+done
+
+terragrunt run --all \
+ --config root.hcl \
+ "${TG_ARGS[@]}" \
+ -- \
+ plan \
+ "${TF_ARGS[@]}"
\ No newline at end of file
diff --git a/infra/aws/2-apply.sh b/infra/aws/2-apply.sh
new file mode 100755
index 0000000..0322d9e
--- /dev/null
+++ b/infra/aws/2-apply.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+set -ae -o pipefail
+
+source coder.env
+
+echo $@
+
+TG_ARGS=()
+TF_ARGS=()
+
+while [[ $# -gt 0 ]]; do
+ if [[ "$1" == "--" ]]; then
+ shift
+ TF_ARGS=("$@")
+ break
+ fi
+
+ TG_ARGS+=("$1")
+ shift
+done
+
+terragrunt run --all \
+ --config root.hcl \
+ "${TG_ARGS[@]}" \
+ -- \
+ apply \
+ "${TF_ARGS[@]}"
\ No newline at end of file
diff --git a/infra/aws/3-destroy.sh b/infra/aws/3-destroy.sh
new file mode 100755
index 0000000..a9ae45a
--- /dev/null
+++ b/infra/aws/3-destroy.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -ae -o pipefail
+
+source coder.env
+
+terragrunt run --all --non-interactive --config root.hcl $@ destroy
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/eks/main.tf b/infra/aws/eu-west-2/eks/main.tf
index 2bffa33..ae33c20 100644
--- a/infra/aws/eu-west-2/eks/main.tf
+++ b/infra/aws/eu-west-2/eks/main.tf
@@ -1,129 +1,56 @@
-terraform {
- required_version = ">= 1.0"
- required_providers {
- aws = {
- source = "hashicorp/aws"
- version = ">= 5.100.0"
- }
- }
- backend "s3" {}
-}
-
-variable "name" {
- description = "The resource name and tag prefix"
- type = string
-}
-
-variable "profile" {
- type = string
-}
-
-variable "region" {
- description = "The aws region to deploy eks cluster"
- type = string
-}
-
-variable "cluster_version" {
- description = "The eks version"
- type = string
-}
-
-variable "cluster_instance_type" {
- description = "EKS Instance Size/Type"
- default = "t3.xlarge"
- type = string
-}
-
provider "aws" {
region = var.region
profile = var.profile
}
-##
-# Cluster Infrastructure
-##
-
-locals {
- system_subnet_tags = {
- "subnet.amazonaws.io/system/owned-by" = var.name
- }
- provisioner_subnet_tags = {
- "subnet.amazonaws.io/coder-provisioner/owned-by" = var.name
- }
- ws_all_subnet_tags = {
- "subnet.amazonaws.io/coder-ws-all/owned-by" = var.name
- }
- system_sg_tags = {
- "subnet.amazonaws.io/system/owned-by" = var.name
- }
- provisioner_sg_tags = {
- "sg.amazonaws.io/coder-provisioner/owned-by" = var.name
- }
- ws_all_sg_tags = {
- "sg.amazonaws.io/coder-ws-all/owned-by" = var.name
- }
- cluster_asg_node_labels = {
- "node.amazonaws.io/managed-by" = "asg"
+data "aws_vpc" "this" {
+ tags = {
+ Name = var.vpc_name
}
}
-data "aws_region" "this" {}
+data "aws_subnets" "public" {
+ filter {
+ name = "vpc-id"
+ values = [data.aws_vpc.this.id]
+ }
-module "eks-network" {
- source = "../../../../modules/network/eks-vpc"
+ tags = {
+ Name = "*${var.public_subnet_suffix}*"
+ }
+}
- name = var.name
- vpc_cidr_block = "10.0.0.0/16"
- public_subnets = {
- "system0" = {
- cidr_block = "10.0.10.0/24"
- availability_zone = "${data.aws_region.this.name}a"
- map_public_ip_on_launch = true
- private_dns_hostname_type_on_launch = "ip-name"
- }
- "system1" = {
- cidr_block = "10.0.11.0/24"
- availability_zone = "${data.aws_region.this.name}b"
- map_public_ip_on_launch = true
- private_dns_hostname_type_on_launch = "ip-name"
- }
+data "aws_subnets" "private" {
+ filter {
+ name = "vpc-id"
+ values = [data.aws_vpc.this.id]
}
- private_subnets = {
- "system0" = {
- cidr_block = "10.0.20.0/24"
- availability_zone = "${data.aws_region.this.name}a"
- private_dns_hostname_type_on_launch = "ip-name"
- tags = local.system_subnet_tags
- }
- "system1" = {
- cidr_block = "10.0.21.0/24"
- availability_zone = "${data.aws_region.this.name}b"
- private_dns_hostname_type_on_launch = "ip-name"
- tags = local.system_subnet_tags
- }
- "provisioner" = {
- cidr_block = "10.0.22.0/24"
- availability_zone = "${data.aws_region.this.name}a"
- map_public_ip_on_launch = true
- private_dns_hostname_type_on_launch = "ip-name"
- tags = local.provisioner_subnet_tags
- }
- "ws-all" = {
- cidr_block = "10.0.16.0/22"
- availability_zone = "${data.aws_region.this.name}b"
- map_public_ip_on_launch = true
- private_dns_hostname_type_on_launch = "ip-name"
- tags = local.ws_all_subnet_tags
- }
+
+ tags = {
+ Name = "*${var.private_subnet_suffix}*"
}
}
-data "aws_iam_policy_document" "sts" {
- statement {
- effect = "Allow"
- actions = ["sts:*"]
- resources = ["*"]
+locals {
+ tags = {
+ Name = var.name
+ "karpenter.sh/discovery" = var.name
+ }
+ # Karpenter Security Group Discovery - https://karpenter.sh/v1.0/concepts/nodeclasses/#specsecuritygroupselectorterms
+ tags_kptr_sg_discovery = {
+ "karpenter.sh/discovery" = var.name
+ }
+ labels_system_node = {
+ "scheduling.coder.com/pool" = "system"
}
+ taints_system = {
+ key = "CriticalAddonsOnly"
+ effect = "NO_SCHEDULE"
+ }
+ tolerations_system = [{
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }]
}
resource "aws_iam_policy" "sts" {
@@ -131,47 +58,62 @@ resource "aws_iam_policy" "sts" {
path = "/"
description = "Assume Role Policy"
policy = data.aws_iam_policy_document.sts.json
+ tags = local.tags
}
-module "cluster" {
+module "eks" {
source = "terraform-aws-modules/eks/aws"
- version = "~> 20.0"
+ version = "~> 21.15.1"
- vpc_id = module.eks-network.vpc_id
- subnet_ids = toset(concat(concat(
- module.eks-network.public_subnet_ids,
- module.eks-network.private_subnet_ids),
- module.eks-network.intra_subnet_ids
+ vpc_id = data.aws_vpc.this.id
+ subnet_ids = toset(concat(
+ data.aws_subnets.private.ids
))
- cluster_name = var.name
- cluster_version = var.cluster_version
- cluster_endpoint_public_access = true
- cluster_endpoint_private_access = true
+ name = var.name
+ kubernetes_version = var.eks_version
+ endpoint_public_access = true
+ endpoint_private_access = true
- create_cluster_security_group = true
+ create_security_group = true
create_node_security_group = true
- node_security_group_tags = merge(
- local.system_sg_tags,
- merge(local.provisioner_sg_tags, local.ws_all_sg_tags)
- )
- create_iam_role = true
- cluster_addons = {
+ create_iam_role = true
+ node_security_group_tags = local.tags_kptr_sg_discovery
+ create_node_iam_role = true
+ node_iam_role_use_name_prefix = true
+
+ create_auto_mode_iam_resources = true
+ compute_config = {
+ enabled = true
+ }
+
+ addons = {
coredns = {
- most_recent = true
+ before_compute = true
}
kube-proxy = {
- most_recent = true
+ before_compute = true
}
vpc-cni = {
- most_recent = true
+ before_compute = true
+ configuration_values = jsonencode({
+ enableNetworkPolicy = "true"
+ nodeAgent = {
+ enablePolicyEventLogs = "true"
+ }
+ env = {
+ ENABLE_PREFIX_DELEGATION = "true"
+ WARM_PREFIX_TARGET = "1"
+ WARM_IP_TARGET = "0"
+ AWS_VPC_K8S_CNI_LOGLEVEL = "DEBUG"
+ }
+ })
}
}
- # Disable KMS, speed up cluster creation times. Enable if encryption is necessary.
- attach_cluster_encryption_policy = false
- create_kms_key = false
- cluster_encryption_config = {}
+ attach_encryption_policy = false
+ create_kms_key = false # Enable unless needed
+ encryption_config = null
enable_cluster_creator_admin_permissions = true
enable_irsa = true
@@ -180,9 +122,9 @@ module "cluster" {
min_size = 0
max_size = 10
desired_size = 0 # Cant be modified after creation. Override from AWS Console
- labels = local.cluster_asg_node_labels
+ labels = local.labels_system_node
- instance_types = [var.cluster_instance_type]
+ instance_types = [var.instance_type]
capacity_type = "ON_DEMAND"
iam_role_additional_policies = {
AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
@@ -190,7 +132,152 @@ module "cluster" {
}
# System Nodes should not be public
- subnet_ids = module.eks-network.private_subnet_ids
+ subnet_ids = data.aws_subnets.private.ids
}
}
+
+ tags = local.tags
+}
+
+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"
+ args = ["eks", "get-token", "--cluster-name", var.name, "--region", var.region, "--profile", var.profile]
+ command = "aws"
+ }
+}
+
+resource "kubernetes_manifest" "default-class" {
+
+ depends_on = [module.eks]
+
+ manifest = {
+ apiVersion = "eks.amazonaws.com/v1"
+ kind = "NodeClass"
+
+ metadata = {
+ name = "coder-default"
+ labels = {
+ "app.kubernetes.io/managed-by" = "terraform"
+ }
+ }
+
+ spec = {
+ ephemeralStorage = {
+ iops = 3000
+ size = "80Gi"
+ throughput = 125
+ }
+
+ networkPolicy = "DefaultAllow"
+ networkPolicyEventLogs = "Disabled"
+ snatPolicy = "Disabled"
+
+ role = module.eks.node_iam_role_name
+
+ securityGroupSelectorTerms = [
+ {
+ id = module.eks.cluster_primary_security_group_id
+ }
+ ]
+
+ subnetSelectorTerms = [for subnet_id in data.aws_subnets.private.ids : { id = subnet_id }]
+ }
+ }
+}
+
+# Override the System NodePool to restrict number of nodess
+resource "kubernetes_manifest" "system-pool" {
+
+ depends_on = [module.eks]
+
+ manifest = {
+ apiVersion = "karpenter.sh/v1"
+ kind = "NodePool"
+
+ metadata = {
+ name = "coder-system"
+ labels = {
+ "app.kubernetes.io/managed-by" = "terraform"
+ }
+ }
+
+ spec = {
+ disruption = {
+ budgets = [
+ {
+ nodes = "10%"
+ }
+ ]
+ consolidateAfter = "30s"
+ consolidationPolicy = "WhenEmptyOrUnderutilized"
+ }
+
+ limits = {
+ nodes = 4
+ }
+
+ template = {
+ metadata = {}
+
+ spec = {
+ expireAfter = "336h"
+
+ nodeClassRef = {
+ group = split("/", kubernetes_manifest.default-class.manifest.apiVersion)[0]
+ kind = kubernetes_manifest.default-class.manifest.kind
+ name = kubernetes_manifest.default-class.manifest.metadata.name
+ }
+
+ requirements = [
+ {
+ key = "karpenter.sh/capacity-type"
+ operator = "In"
+ values = ["on-demand"]
+ },
+ {
+ key = "eks.amazonaws.com/instance-category"
+ operator = "In"
+ values = ["c", "m", "r"]
+ },
+ {
+ key = "eks.amazonaws.com/instance-generation"
+ operator = "Gt"
+ values = ["4"]
+ },
+ {
+ key = "kubernetes.io/arch"
+ operator = "In"
+ values = ["amd64", "arm64"]
+ },
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = slice([for az in var.azs : "${var.region}${az}"], 0, 1)
+ },
+ {
+ key = "kubernetes.io/os"
+ operator = "In"
+ values = ["linux"]
+ }
+ ]
+
+ taints = [
+ {
+ key = "CriticalAddonsOnly"
+ effect = "NoSchedule"
+ }
+ ]
+
+ terminationGracePeriod = "24h0m0s"
+ }
+ }
+ }
+ }
+}
+
+output "azs" {
+ value = join(", ", slice([for az in var.azs : "${var.region}${az}"], 0, 1))
}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/eks/terragrunt.hcl b/infra/aws/eu-west-2/eks/terragrunt.hcl
new file mode 100644
index 0000000..d77a8c1
--- /dev/null
+++ b/infra/aws/eu-west-2/eks/terragrunt.hcl
@@ -0,0 +1,21 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = ["../vpc"]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region="eu-west-2"
+ azs=jsondecode(include.root.locals.CODER_AWS_AZS)
+
+ name=include.root.locals.CODER_CLUSTER_NAME
+ vpc_name=include.root.locals.CODER_VPC_NAME
+ eks_version=include.root.locals.CODER_CLUSTER_VERSION
+ instance_type=include.root.locals.CODER_CLUSTER_INSTANCE_TYPE
+ public_subnet_suffix=include.root.locals.CODER_PUBLIC_SUBNET_SUFFIX
+ private_subnet_suffix=include.root.locals.CODER_PRIVATE_SUBNET_SUFFIX
+}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/eks/variables.tf b/infra/aws/eu-west-2/eks/variables.tf
new file mode 100644
index 0000000..4e5b465
--- /dev/null
+++ b/infra/aws/eu-west-2/eks/variables.tf
@@ -0,0 +1,43 @@
+variable "profile" {
+ type = string
+}
+
+variable "region" {
+ description = "The aws region to deploy eks cluster"
+ type = string
+}
+
+variable "azs" {
+ description = "The aws availability zones to deploy eks cluster"
+ type = list(string)
+}
+
+variable "name" {
+ description = "The resource name and tag prefix"
+ type = string
+}
+
+variable "eks_version" {
+ description = "The eks version"
+ type = string
+}
+
+variable "instance_type" {
+ description = "EKS Instance Size/Type"
+ default = "t3.xlarge"
+ type = string
+}
+
+variable "vpc_name" {
+ type = string
+}
+
+variable "private_subnet_suffix" {
+ type = string
+ default = "private"
+}
+
+variable "public_subnet_suffix" {
+ type = string
+ default = "public"
+}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/cert-manager/main.tf b/infra/aws/eu-west-2/k8s/cert-manager/main.tf
index c095843..7bc7507 100644
--- a/infra/aws/eu-west-2/k8s/cert-manager/main.tf
+++ b/infra/aws/eu-west-2/k8s/cert-manager/main.tf
@@ -1,59 +1,6 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "addon_namespace" {
- type = string
- default = "cert-manager"
-}
-
-variable "addon_version" {
- type = string
- default = "v1.18.2"
-}
-
-variable "cloudflare_api_token" {
- type = string
- sensitive = true
-}
-
-variable "cloudflare_email" {
- type = string
- sensitive = true
-}
-
provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
+ region = var.region
+ profile = var.profile
}
data "aws_eks_cluster" "this" {
@@ -64,6 +11,10 @@ data "aws_eks_cluster_auth" "this" {
name = var.cluster_name
}
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
+}
+
provider "helm" {
kubernetes = {
host = data.aws_eks_cluster.this.endpoint
@@ -79,12 +30,17 @@ provider "kubernetes" {
}
module "cert-manager" {
- source = "../../../../../modules/k8s/bootstrap/cert-manager"
+
+ source = "../../../../../modules/k8s/bootstrap/cert-manager"
+
cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
+ cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
+
+ namespace = var.addon_namespace
+ helm_version = var.addon_version
- namespace = var.addon_namespace
- helm_version = var.addon_version
- cloudflare_token_secret = var.cloudflare_api_token
- cloudflare_token_secret_email = var.cloudflare_email
+ tolerations = [{
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }]
}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/cert-manager/terragrunt.hcl b/infra/aws/eu-west-2/k8s/cert-manager/terragrunt.hcl
new file mode 100644
index 0000000..dc8cb8e
--- /dev/null
+++ b/infra/aws/eu-west-2/k8s/cert-manager/terragrunt.hcl
@@ -0,0 +1,20 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks"
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region="eu-west-2"
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ addon_namespace=include.root.locals.CRTMGR_ADDON_NAMESPACE
+ addon_version=include.root.locals.CRTMGR_ADDON_VERSION
+}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/cert-manager/variables.tf b/infra/aws/eu-west-2/k8s/cert-manager/variables.tf
new file mode 100644
index 0000000..f93b94c
--- /dev/null
+++ b/infra/aws/eu-west-2/k8s/cert-manager/variables.tf
@@ -0,0 +1,22 @@
+variable "cluster_name" {
+ type = string
+}
+
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "addon_namespace" {
+ type = string
+ default = "cert-manager"
+}
+
+variable "addon_version" {
+ type = string
+ default = "v1.18.2"
+}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/coder-proxy/main.tf b/infra/aws/eu-west-2/k8s/coder-proxy/main.tf
index 3b5cf7b..e6f60af 100644
--- a/infra/aws/eu-west-2/k8s/coder-proxy/main.tf
+++ b/infra/aws/eu-west-2/k8s/coder-proxy/main.tf
@@ -1,114 +1,6 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- coderd = {
- source = "coder/coderd"
- }
- acme = {
- source = "vancluever/acme"
- }
- tls = {
- source = "hashicorp/tls"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "acme_server_url" {
- type = string
- default = "https://acme-v02.api.letsencrypt.org/directory"
-}
-
-variable "acme_registration_email" {
- type = string
-}
-
-variable "addon_version" {
- type = string
- default = "2.25.1"
-}
-
-variable "coder_proxy_name" {
- type = string
-}
-
-variable "coder_proxy_display_name" {
- type = string
-}
-
-variable "coder_proxy_icon" {
- type = string
-}
-
-variable "coder_access_url" {
- type = string
- # sensitive = true
-}
-
-variable "coder_proxy_url" {
- type = string
- # sensitive = true
-}
-
-variable "coder_proxy_wildcard_url" {
- type = string
- # sensitive = true
-}
-
-variable "coder_token" {
- type = string
- sensitive = true
-}
-
-variable "image_repo" {
- type = string
- sensitive = true
-}
-
-variable "image_tag" {
- type = string
- default = "latest"
-}
-
-variable "kubernetes_ssl_secret_name" {
- type = string
-}
-
-variable "kubernetes_create_ssl_secret" {
- type = bool
- default = true
-}
-
-variable "cloudflare_api_token" {
- type = string
- sensitive = true
-}
-
provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
+ region = var.region
+ profile = var.profile
}
data "aws_eks_cluster" "this" {
@@ -119,6 +11,8 @@ data "aws_eks_cluster_auth" "this" {
name = var.cluster_name
}
+data "aws_region" "this" {}
+
provider "helm" {
kubernetes = {
host = data.aws_eks_cluster.this.endpoint
@@ -133,79 +27,176 @@ provider "kubernetes" {
token = data.aws_eks_cluster_auth.this.token
}
+data "http" "login" {
+ url = "${var.coder_access_url}/api/v2/users/login"
+ method = "POST"
+ request_headers = {
+ Accept = "application/json"
+ }
+ request_body = jsonencode({
+ email = var.coder_admin_email
+ password = var.coder_admin_password
+ })
+
+ retry {
+ attempts = 5
+ min_delay_ms = (5 * 1000) # 5 seconds
+ }
+}
+
provider "coderd" {
url = var.coder_access_url
- token = var.coder_token
+ token = jsondecode(data.http.login.response_body).session_token
}
-provider "acme" {
- server_url = var.acme_server_url
+locals {
+ azs = slice(var.azs, 0, 1)
+ pub_subs = [for az in local.azs : "${var.vpc_name}-public-${data.aws_region.this.region}${az}"]
+ release_name = "coder"
+ chart_name = "coder"
+ namespace = "coder"
+
+ common_name = trimprefix(trimprefix(var.coder_proxy_url, "https://"), "http://")
+ wildcard_name = trimprefix(trimprefix(var.coder_proxy_wildcard_url, "https://"), "http://")
+ ssl_vol_friendly_name = replace(local.common_name, ".", "-")
+}
+
+resource "kubernetes_manifest" "certificate" {
+
+ field_manager {
+ force_conflicts = true
+ }
+
+ wait {
+ condition {
+ type = "Ready"
+ status = "True"
+ }
+ }
+
+ timeouts {
+ create = "10m"
+ update = "10m"
+ delete = "30s"
+ }
+
+ manifest = {
+ apiVersion = "cert-manager.io/v1"
+ kind = "Certificate"
+ metadata = {
+ name = local.ssl_vol_friendly_name
+ namespace = module.coder-proxy.namespace
+ }
+ spec = {
+ commonName = local.common_name
+ dnsNames = [
+ local.common_name,
+ local.wildcard_name
+ ]
+ duration = "2160h" # 90 days
+ renewBefore = "360h" # 15 days
+ issuerRef = {
+ kind = "ClusterIssuer"
+ name = "issuer"
+ }
+ secretName = local.ssl_vol_friendly_name
+ privateKey = {
+ rotationPolicy = "Never"
+ algorithm = "RSA"
+ encoding = "PKCS1"
+ size = "2048"
+ }
+ }
+ }
+}
+
+resource "aws_eip" "coder" {
+ count = length(local.pub_subs)
+ domain = "vpc"
+ public_ipv4_pool = "amazon"
+ tags = {
+ Name = "coder-eip-${count.index}"
+ }
}
module "coder-proxy" {
+
source = "../../../../../modules/k8s/bootstrap/coder-proxy"
- namespace = "coder-proxy"
- acme_registration_email = var.acme_registration_email
- acme_days_until_renewal = 90
- replica_count = 2
- helm_version = var.addon_version
- image_repo = var.image_repo
- image_tag = var.image_tag
- primary_access_url = var.coder_access_url
- proxy_access_url = var.coder_proxy_url
- proxy_wildcard_url = var.coder_proxy_wildcard_url
- coder_proxy_name = var.coder_proxy_name
- coder_proxy_display_name = var.coder_proxy_display_name
- coder_proxy_icon = var.coder_proxy_icon
- proxy_token_config = {
- name = "coder-proxy"
+ proxy = {
+ access_url = var.coder_proxy_url
+ wildcard_url = var.coder_proxy_wildcard_url
+ coder_access_url = var.coder_access_url
+ mount_ssl = true
+ mount_ssl_name = kubernetes_manifest.certificate.manifest.spec.secretName
+ name = var.coder_proxy_name
+ display_name = var.coder_proxy_display_name
+ icon = var.coder_proxy_icon
+ rep_cnt = 2
+ image_repo = var.image_repo
+ image_tag = var.image_tag
}
- cloudflare_api_token = var.cloudflare_api_token
- ssl_cert_config = {
- name = var.kubernetes_ssl_secret_name
- create_secret = var.kubernetes_create_ssl_secret
+
+ namespace = local.namespace
+ resource_limit = {
+ cpu = "2"
+ memory = "4Gi"
}
- service_annotations = {
- "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "instance"
- "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
- "service.beta.kubernetes.io/aws-load-balancer-attributes" = "deletion_protection.enabled=true"
+ resource_request = {
+ cpu = "500m"
+ memory = "4Gi"
}
- node_selector = {
- "node.coder.io/managed-by" = "karpenter"
- "node.coder.io/used-for" = "coder-proxy"
+ svc_annot = {
+ "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "ip"
+ "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
+ "service.beta.kubernetes.io/aws-load-balancer-attributes" = "deletion_protection.enabled=false"
+ "service.beta.kubernetes.io/aws-load-balancer-eip-allocations" = join(",", aws_eip.coder.*.allocation_id)
+ "service.beta.kubernetes.io/aws-load-balancer-subnets" = join(",", local.pub_subs)
}
tolerations = [{
- key = "dedicated"
- operator = "Equal"
- value = "coder-proxy"
- effect = "NoSchedule"
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
}]
- topology_spread_constraints = [{
+ topology_spread = [{
max_skew = 1
topology_key = "kubernetes.io/hostname"
when_unsatisfiable = "ScheduleAnyway"
label_selector = {
match_labels = {
- "app.kubernetes.io/name" = "coder"
- "app.kubernetes.io/part-of" = "coder"
+ "app.kubernetes.io/name" = local.chart_name
+ "app.kubernetes.io/part-of" = local.chart_name
}
}
match_label_keys = [
"app.kubernetes.io/instance"
]
}]
- pod_anti_affinity_preferred_during_scheduling_ignored_during_execution = [{
- weight = 100
- pod_affinity_term = {
- label_selector = {
- match_labels = {
- "app.kubernetes.io/instance" = "coder-v2"
- "app.kubernetes.io/name" = "coder"
- "app.kubernetes.io/part-of" = "coder"
- }
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [{
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [for az in local.azs : "${data.aws_region.this.region}${az}"]
+ }]
+ }]
}
- topology_key = "kubernetes.io/hostname"
}
- }]
+ # podAntiAffinity = {
+ # preferredDuringSchedulingIgnoredDuringExecution = [{
+ # weight = 100
+ # podAffinityTerm = {
+ # topologyKey = "kubernetes.io/hostname"
+ # labelSelector = {
+ # matchLabels = {
+ # "app.kubernetes.io/instance" = local.release_name
+ # "app.kubernetes.io/name" = local.chart_name
+ # "app.kubernetes.io/part-of" = local.chart_name
+ # }
+ # }
+ # }
+ # }]
+ # }
+ }
}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/coder-proxy/terragrunt.hcl b/infra/aws/eu-west-2/k8s/coder-proxy/terragrunt.hcl
new file mode 100644
index 0000000..a661dbb
--- /dev/null
+++ b/infra/aws/eu-west-2/k8s/coder-proxy/terragrunt.hcl
@@ -0,0 +1,40 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks",
+ "../lb-controller",
+ "../cert-manager",
+ "../other",
+ "../../../us-east-2/k8s/coder-server"
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region="eu-west-2"
+
+ azs=jsondecode(include.root.locals.CODER_AWS_AZS)
+ vpc_name=include.root.locals.CODER_VPC_NAME
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ coder_access_url=include.root.locals.CODER_DOMAIN_NAME
+ coder_wildcard_access_url=include.root.locals.CODER_WILDCARD_URL
+
+ coder_proxy_url="https://emea-proxy.ai.coder.com"
+ coder_proxy_wildcard_url="*.emea-proxy.ai.coder.com"
+ coder_proxy_name="eu-west-2"
+ coder_proxy_display_name="Europe (London)"
+ coder_proxy_icon="/emojis/1f1ec-1f1e7.png"
+
+ image_repo=include.root.locals.CODER_IMAGE_REPO
+ image_tag=include.root.locals.CODER_IMAGE_TAG
+ addon_version=include.root.locals.CODER_ADDON_VERSION
+
+ coder_admin_email=include.root.locals.CODER_EMAIL
+ coder_admin_password=include.root.locals.CODER_PASSWORD
+}
+
diff --git a/infra/aws/eu-west-2/k8s/coder-proxy/variables.tf b/infra/aws/eu-west-2/k8s/coder-proxy/variables.tf
new file mode 100644
index 0000000..49b092a
--- /dev/null
+++ b/infra/aws/eu-west-2/k8s/coder-proxy/variables.tf
@@ -0,0 +1,69 @@
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "azs" {
+ type = list(string)
+ default = ["a", "b", "c"]
+}
+
+variable "vpc_name" {
+ type = string
+}
+
+variable "cluster_name" {
+ type = string
+}
+
+variable "addon_version" {
+ type = string
+ default = "2.25.1"
+}
+
+variable "coder_proxy_name" {
+ type = string
+}
+
+variable "coder_proxy_display_name" {
+ type = string
+}
+
+variable "coder_proxy_icon" {
+ type = string
+}
+
+variable "coder_access_url" {
+ type = string
+}
+
+variable "coder_proxy_url" {
+ type = string
+}
+
+variable "coder_proxy_wildcard_url" {
+ type = string
+}
+
+variable "image_repo" {
+ type = string
+ sensitive = true
+}
+
+variable "image_tag" {
+ type = string
+ default = "latest"
+}
+
+variable "coder_admin_email" {
+ type = string
+}
+
+variable "coder_admin_password" {
+ type = string
+ sensitive = true
+}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/coder-ws/main.tf b/infra/aws/eu-west-2/k8s/coder-ws/main.tf
deleted file mode 100644
index 31c49ac..0000000
--- a/infra/aws/eu-west-2/k8s/coder-ws/main.tf
+++ /dev/null
@@ -1,209 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- coderd = {
- source = "coder/coderd"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "provisioner_addon_version" {
- type = string
- default = "2.23.0"
-}
-
-variable "logstream_addon_version" {
- type = string
- default = "0.0.11"
-}
-
-variable "coder_access_url" {
- type = string
- sensitive = true
-}
-
-variable "coder_token" {
- type = string
- sensitive = true
-}
-
-variable "image_repo" {
- type = string
- sensitive = true
-}
-
-variable "image_tag" {
- type = string
- default = "latest"
-}
-
-variable "rotate_key_image_repo" {
- type = string
- sensitive = true
-}
-
-variable "rotate_key_image_tag" {
- type = string
- sensitive = true
-}
-
-variable "aws_secret_id" {
- type = string
- sensitive = true
-}
-
-variable "aws_secret_region" {
- type = string
- sensitive = true
-}
-
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
-
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
-
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
-
-provider "helm" {
- kubernetes = {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
- }
-}
-
-provider "kubernetes" {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
-}
-
-provider "coderd" {
- url = var.coder_access_url
- token = var.coder_token
-}
-
-locals {
- service_account_labels = {
- "app.kubernetes.io/instance" = "coder-provisioner"
- "app.kubernetes.io/name" = "coder-provisioner"
- "app.kubernetes.io/part-of" = "coder-provisioner"
- }
- node_selector = {
- "node.coder.io/managed-by" = "karpenter"
- "node.coder.io/used-for" = "coder-provisioner"
- }
- tolerations = [{
- key = "dedicated"
- operator = "Equal"
- value = "coder-provisioner"
- effect = "NoSchedule"
- }]
-}
-
-module "default-ws" {
- source = "../../../../../modules/k8s/bootstrap/coder-provisioner"
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- coder_organization_name = "coder"
-
- namespace = "coder-ws"
- image_repo = var.image_repo
- image_tag = var.image_tag
- ws_service_account_name = "coder-ws"
- ws_service_account_labels = local.service_account_labels
- provisioner_service_account_name = "coder"
- replica_count = 6
- primary_access_url = var.coder_access_url
- env_vars = {
- CODER_PROMETHEUS_ENABLE = "false"
- CODER_PROMETHEUS_COLLECT_AGENT_STATS = "false"
- CODER_PROMETHEUS_COLLECT_DB_METRICS = "false"
- }
- node_selector = local.node_selector
- tolerations = local.tolerations
-}
-
-module "experiment-ws" {
- source = "../../../../../modules/k8s/bootstrap/coder-provisioner"
-
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- coder_organization_name = "experiment"
-
- namespace = "coder-ws-experiment"
- image_repo = var.image_repo
- image_tag = var.image_tag
- ws_service_account_name = "coder-ws-experiment"
- ws_service_account_labels = local.service_account_labels
- provisioner_service_account_name = "coder"
- replica_count = 2
- primary_access_url = var.coder_access_url
- env_vars = {
- CODER_PROMETHEUS_ENABLE = "false"
- CODER_PROMETHEUS_COLLECT_AGENT_STATS = "false"
- CODER_PROMETHEUS_COLLECT_DB_METRICS = "false"
- }
- node_selector = local.node_selector
- tolerations = local.tolerations
-}
-
-module "demo-ws" {
- source = "../../../../../modules/k8s/bootstrap/coder-provisioner"
-
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- coder_organization_name = "demo"
-
- namespace = "coder-ws-demo"
- image_repo = var.image_repo
- image_tag = var.image_tag
- ws_service_account_name = "coder-ws-demo"
- ws_service_account_labels = local.service_account_labels
- provisioner_service_account_name = "coder"
- replica_count = 2
- primary_access_url = var.coder_access_url
- env_vars = {
- CODER_PROMETHEUS_ENABLE = "false"
- CODER_PROMETHEUS_COLLECT_AGENT_STATS = "false"
- CODER_PROMETHEUS_COLLECT_DB_METRICS = "false"
- }
- node_selector = local.node_selector
- tolerations = local.tolerations
-}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/ebs-controller/main.tf b/infra/aws/eu-west-2/k8s/ebs-controller/main.tf
deleted file mode 100644
index e882e1d..0000000
--- a/infra/aws/eu-west-2/k8s/ebs-controller/main.tf
+++ /dev/null
@@ -1,72 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "addon_version" {
- type = string
- default = "2.22.1"
-}
-
-variable "addon_namespace" {
- type = string
- default = "default"
-}
-
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
-
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
-
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
-
-provider "helm" {
- kubernetes = {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
- }
-}
-
-module "ebs-controller" {
- source = "../../../../../modules/k8s/bootstrap/ebs-controller"
- cluster_name = data.aws_eks_cluster.this.name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- namespace = var.addon_namespace
- chart_version = var.addon_version
-}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/karpenter/main.tf b/infra/aws/eu-west-2/k8s/karpenter/main.tf
deleted file mode 100644
index a997bdf..0000000
--- a/infra/aws/eu-west-2/k8s/karpenter/main.tf
+++ /dev/null
@@ -1,203 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "addon_version" {
- type = string
-}
-
-variable "addon_namespace" {
- type = string
- default = "default"
-}
-
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
-
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
-
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
-
-provider "helm" {
- kubernetes = {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
- }
-}
-
-provider "kubernetes" {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
-}
-
-locals {
- global_node_labels = {
- "node.coder.io/instance" = "coder-v2"
- "node.coder.io/managed-by" = "karpenter"
- }
- global_node_reqs = [{
- key = "kubernetes.io/arch"
- operator = "In"
- values = ["amd64"]
- }, {
- key = "kubernetes.io/os"
- operator = "In"
- values = ["linux"]
- }, {
- key = "kubernetes.sh/capacity-type"
- operator = "In"
- values = ["spot", "on-demand"]
- }]
- provisioner_subnet_tags = {
- "subnet.amazonaws.io/coder-provisioner/owned-by" = var.cluster_name
- }
- ws_all_subnet_tags = {
- "subnet.amazonaws.io/coder-ws-all/owned-by" = var.cluster_name
- }
- provisioner_sg_tags = {
- "sg.amazonaws.io/coder-provisioner/owned-by" = var.cluster_name
- }
- ws_all_sg_tags = {
- "sg.amazonaws.io/coder-ws-all/owned-by" = var.cluster_name
- }
-}
-
-locals {
- nodepool_configs = [{
- name = "coder-proxy"
- node_labels = merge(local.global_node_labels, {
- "node.coder.io/name" = "coder"
- "node.coder.io/part-of" = "coder"
- "node.coder.io/used-for" = "coder-proxy"
- })
- node_taints = [{
- key = "dedicated"
- value = "coder-proxy"
- effect = "NoSchedule"
- }]
- node_requirements = concat(local.global_node_reqs, [{
- key = "node.kubernetes.io/instance-type"
- operator = "In"
- values = ["m5a.xlarge", "m6a.xlarge"]
- }])
- node_class_ref_name = "coder-proxy-class"
- }, {
- name = "coder-provisioner"
- node_labels = merge(local.global_node_labels, {
- "node.coder.io/name" = "coder"
- "node.coder.io/part-of" = "coder"
- "node.coder.io/used-for" = "coder-provisioner"
- })
- node_taints = [{
- key = "dedicated"
- value = "coder-provisioner"
- effect = "NoSchedule"
- }]
- node_requirements = concat(local.global_node_reqs, [{
- key = "node.kubernetes.io/instance-type"
- operator = "In"
- values = ["m5a.4xlarge", "m6a.4xlarge"]
- }])
- node_class_ref_name = "coder-provisioner-class"
- }, {
- name = "coder-ws-all"
- node_labels = merge(local.global_node_labels, {
- "node.coder.io/name" = "coder"
- "node.coder.io/part-of" = "coder"
- "node.coder.io/used-for" = "coder-ws-all"
- })
- node_taints = [{
- key = "dedicated"
- value = "coder-ws"
- effect = "NoSchedule"
- }]
- node_requirements = concat(local.global_node_reqs, [{
- key = "node.kubernetes.io/instance-type"
- operator = "In"
- values = ["c6a.32xlarge", "c5a.32xlarge"]
- }])
- node_class_ref_name = "coder-ws-class"
- disruption_consolidate_after = "30m"
- }]
-}
-
-module "karpenter-addon" {
- source = "../../../../../modules/k8s/bootstrap/karpenter"
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- namespace = var.addon_namespace
- chart_version = var.addon_version
- node_selector = {
- "node.amazonaws.io/managed-by" : "asg"
- }
- ec2nodeclass_configs = [{
- name = "coder-proxy-class"
- subnet_selector_tags = local.provisioner_subnet_tags
- sg_selector_tags = local.provisioner_sg_tags
- }, {
- name = "coder-ws-class"
- subnet_selector_tags = local.ws_all_subnet_tags
- # ami_alias = "al2023@latest" # Use /dev/xvda
- ami_alias = "bottlerocket@latest" # Use /dev/xvda + /dev/xvdb
- sg_selector_tags = local.ws_all_sg_tags
- user_data = <<-EOF
- [settings.kubernetes]
- 'registry-qps' = 20
- EOF
- block_device_mappings = [{
- device_name = "/dev/xvda"
- ebs = {
- volume_size = "1000Gi"
- volume_type = "gp3"
- }
- }, {
- device_name = "/dev/xvdb"
- ebs = {
- volume_size = "1000Gi"
- volume_type = "gp3"
- }
- }]
- }, {
- name = "coder-provisioner-class"
- subnet_selector_tags = local.provisioner_subnet_tags
- sg_selector_tags = local.provisioner_sg_tags
- }]
-}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/lb-controller/main.tf b/infra/aws/eu-west-2/k8s/lb-controller/main.tf
index 6bab002..c5721e0 100644
--- a/infra/aws/eu-west-2/k8s/lb-controller/main.tf
+++ b/infra/aws/eu-west-2/k8s/lb-controller/main.tf
@@ -1,54 +1,6 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "addon_namespace" {
- type = string
- default = "default"
-}
-
-variable "addon_version" {
- type = string
- default = "1.13.2"
-}
-
-variable "use_cert_manager" {
- type = bool
- default = false
-}
-
provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
+ region = var.region
+ profile = var.profile
}
data "aws_eks_cluster" "this" {
@@ -59,6 +11,16 @@ data "aws_eks_cluster_auth" "this" {
name = var.cluster_name
}
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
+}
+
+data "aws_vpc" "this" {
+ tags = {
+ Name = var.vpc_name
+ }
+}
+
provider "helm" {
kubernetes = {
host = data.aws_eks_cluster.this.endpoint
@@ -67,18 +29,62 @@ provider "helm" {
}
}
+provider "kubernetes" {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+}
+
+locals {
+ release_name = "aws-load-balancer-controller"
+}
+
module "lb-controller" {
source = "../../../../../modules/k8s/bootstrap/lb-controller"
cluster_name = data.aws_eks_cluster.this.name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
+ cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
+ release_name = local.release_name
namespace = var.addon_namespace
chart_version = var.addon_version
enable_cert_manager = var.use_cert_manager
- service_target_eni_sg_tags = {
- Name = "aidemo-node"
+ vpc_id = data.aws_vpc.this.id
+
+ tolerations = [{
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }]
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [
+ {
+ key = "eks.amazonaws.com/compute-type",
+ operator = "In",
+ values = ["auto"]
+ }
+ ]
+ }]
+ }
+ }
+ podAntiAffinity = {
+ preferredDuringSchedulingIgnoredDuringExecution = [{
+ weight = 100
+ podAffinityTerm = {
+ labelSelector = {
+ matchExpressions = [{
+ key = "app.kubernetes.io/name"
+ operator = "In"
+ values = [local.release_name]
+ }]
+ }
+ topologyKey = "kubernetes.io/hostname"
+ }
+ }]
+ }
}
- cluster_asg_node_labels = { # var.karpenter_node_labels
- "node.amazonaws.io/managed-by" = "asg"
+ service_target_eni_sg_tags = {
+ Name = "aienv-node"
}
}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/lb-controller/terragrunt.hcl b/infra/aws/eu-west-2/k8s/lb-controller/terragrunt.hcl
new file mode 100644
index 0000000..670a7d7
--- /dev/null
+++ b/infra/aws/eu-west-2/k8s/lb-controller/terragrunt.hcl
@@ -0,0 +1,23 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks",
+ "../cert-manager"
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region="eu-west-2"
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ addon_namespace=include.root.locals.LB_ADDON_NAMESPACE
+ addon_version=include.root.locals.LB_ADDON_VERSION
+ vpc_name=include.root.locals.CODER_VPC_NAME
+ use_cert_manager=include.root.locals.LB_ADDON_USE_CERTMGR
+}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/lb-controller/variables.tf b/infra/aws/eu-west-2/k8s/lb-controller/variables.tf
new file mode 100644
index 0000000..1b505c0
--- /dev/null
+++ b/infra/aws/eu-west-2/k8s/lb-controller/variables.tf
@@ -0,0 +1,31 @@
+variable "cluster_name" {
+ type = string
+}
+
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "addon_namespace" {
+ type = string
+ default = "default"
+}
+
+variable "addon_version" {
+ type = string
+ default = "1.13.2"
+}
+
+variable "vpc_name" {
+ type = string
+}
+
+variable "use_cert_manager" {
+ type = bool
+ default = false
+}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/metrics-server/main.tf b/infra/aws/eu-west-2/k8s/metrics-server/main.tf
index 8b50a90..a4a69e2 100644
--- a/infra/aws/eu-west-2/k8s/metrics-server/main.tf
+++ b/infra/aws/eu-west-2/k8s/metrics-server/main.tf
@@ -1,42 +1,6 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "addon_namespace" {
- type = string
- default = "kube-system"
-}
-
-variable "addon_version" {
- type = string
- default = "3.13.0"
-}
-
provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
+ region = var.region
+ profile = var.profile
}
data "aws_eks_cluster" "this" {
@@ -60,7 +24,8 @@ module "metrics-server" {
namespace = var.addon_namespace
chart_version = var.addon_version
- node_selector = {
- "node.amazonaws.io/managed-by" = "asg"
- }
+ tolerations = [{
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }]
}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/metrics-server/terragrunt.hcl b/infra/aws/eu-west-2/k8s/metrics-server/terragrunt.hcl
new file mode 100644
index 0000000..6c0c0d2
--- /dev/null
+++ b/infra/aws/eu-west-2/k8s/metrics-server/terragrunt.hcl
@@ -0,0 +1,20 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks"
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region="eu-west-2"
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ addon_namespace=include.root.locals.METRICS_SRV_ADDON_NAMESPACE
+ addon_version=include.root.locals.METRICS_SRV_ADDON_VERSION
+}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/metrics-server/variables.tf b/infra/aws/eu-west-2/k8s/metrics-server/variables.tf
new file mode 100644
index 0000000..6e7dfc0
--- /dev/null
+++ b/infra/aws/eu-west-2/k8s/metrics-server/variables.tf
@@ -0,0 +1,22 @@
+variable "cluster_name" {
+ type = string
+}
+
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "addon_namespace" {
+ type = string
+ default = "kube-system"
+}
+
+variable "addon_version" {
+ type = string
+ default = "3.13.0"
+}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/other/main.tf b/infra/aws/eu-west-2/k8s/other/main.tf
new file mode 100644
index 0000000..ecb5627
--- /dev/null
+++ b/infra/aws/eu-west-2/k8s/other/main.tf
@@ -0,0 +1,135 @@
+provider "aws" {
+ region = var.region
+ profile = var.profile
+}
+
+data "aws_eks_cluster" "this" {
+ name = var.cluster_name
+}
+
+data "aws_eks_cluster_auth" "this" {
+ name = var.cluster_name
+}
+
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
+}
+
+data "aws_region" "this" {}
+
+provider "helm" {
+ kubernetes = {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+ }
+}
+
+provider "kubernetes" {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+}
+
+##
+# Manifest Setup Post Addon-Deployment
+# Includes auxiliary resources depending on CRDs
+##
+
+##
+# EBS CSI StorageClasses
+##
+
+resource "kubernetes_manifest" "automode-gp3" {
+ manifest = {
+ apiVersion = "storage.k8s.io/v1"
+ kind = "StorageClass"
+ metadata = {
+ name = "gp3-automode"
+ }
+ provisioner = "ebs.csi.eks.amazonaws.com"
+ volumeBindingMode = "WaitForFirstConsumer"
+ allowedTopologies = [{
+ matchLabelExpressions = [{
+ key = "eks.amazonaws.com/compute-type"
+ values = ["auto"]
+ }]
+ }]
+ parameters = {
+ type = "gp3"
+ encrypted = "true"
+ }
+ }
+}
+
+##
+# Setup Cert-Manager ClusterIssuer
+##
+
+locals {
+ cf_secret_key = "key"
+}
+
+resource "kubernetes_secret_v1" "cf" {
+ metadata {
+ name = "cloudflare-token"
+ namespace = var.cloudflare_secret_namespace
+ annotations = {
+ "custom.kubernetes.secret/key" = local.cf_secret_key
+ "custom.kubernetes.secret/email" = var.cloudflare_email
+ }
+ }
+ data = {
+ (local.cf_secret_key) = var.cloudflare_api_token
+ }
+}
+
+resource "kubernetes_manifest" "issuer" {
+
+ field_manager {
+ force_conflicts = true
+ }
+
+ wait {
+ condition {
+ type = "Ready"
+ status = "True"
+ }
+ }
+
+ timeouts {
+ create = "10m"
+ update = "10m"
+ delete = "30s"
+ }
+
+ manifest = {
+ apiVersion = "cert-manager.io/v1"
+ kind = "ClusterIssuer"
+ metadata = {
+ labels = {}
+ name = var.cluster_issuer_name
+ }
+ spec = {
+ acme = {
+ privateKeySecretRef = {
+ name = var.cluster_issuer_priv_key_ref
+ }
+ server = "https://acme-v02.api.letsencrypt.org/directory"
+ solvers = [
+ {
+ dns01 = {
+ cloudflare = {
+ apiTokenSecretRef = {
+ key = kubernetes_secret_v1.cf.metadata[0].annotations["custom.kubernetes.secret/key"]
+ name = kubernetes_secret_v1.cf.metadata[0].name
+ }
+ email = kubernetes_secret_v1.cf.metadata[0].annotations["custom.kubernetes.secret/email"]
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/other/terragrunt.hcl b/infra/aws/eu-west-2/k8s/other/terragrunt.hcl
new file mode 100644
index 0000000..3268609
--- /dev/null
+++ b/infra/aws/eu-west-2/k8s/other/terragrunt.hcl
@@ -0,0 +1,25 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks",
+ "../lb-controller",
+ "../cert-manager",
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region="eu-west-2"
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ azs=include.root.locals.CODER_VPC_AZS
+
+ cloudflare_api_token=include.root.locals.CF_TOKEN
+ cloudflare_secret_namespace=include.root.locals.CRTMGR_ADDON_NAMESPACE
+ cloudflare_email=include.root.locals.CF_EMAIL
+}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/k8s/other/variables.tf b/infra/aws/eu-west-2/k8s/other/variables.tf
new file mode 100644
index 0000000..74f086c
--- /dev/null
+++ b/infra/aws/eu-west-2/k8s/other/variables.tf
@@ -0,0 +1,42 @@
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "region" {
+ type = string
+ default = "us-east-2"
+}
+
+variable "cluster_name" {
+ type = string
+}
+
+variable "cluster_issuer_name" {
+ type = string
+ default = "issuer"
+}
+
+variable "cluster_issuer_priv_key_ref" {
+ type = string
+ default = "issuer-account-key"
+}
+
+variable "azs" {
+ type = list(string)
+ default = ["a", "b", "c"]
+}
+
+variable "cloudflare_api_token" {
+ type = string
+ sensitive = true
+}
+
+variable "cloudflare_secret_namespace" {
+ type = string
+ default = "cert-manager"
+}
+
+variable "cloudflare_email" {
+ type = string
+}
diff --git a/infra/aws/eu-west-2/vpc/main.tf b/infra/aws/eu-west-2/vpc/main.tf
new file mode 100644
index 0000000..4114767
--- /dev/null
+++ b/infra/aws/eu-west-2/vpc/main.tf
@@ -0,0 +1,67 @@
+provider "aws" {
+ region = var.region
+ profile = var.profile
+}
+
+locals {
+ # Subnet Discovery - https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/deploy/subnet_discovery/#subnet-filtering
+ tags_lb_subnet_discovery = {
+ "kubernetes.io/cluster/${var.cluster_name}" = "owned"
+ }
+ # Internet-Facing LB - https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/deploy/subnet_discovery/#subnet-role-tag
+ tags_public_lb = {
+ "kubernetes.io/role/elb" = 1
+ }
+ tags_public_subnet = merge(
+ local.tags_lb_subnet_discovery,
+ local.tags_public_lb
+ )
+ tags_private_subnet = {
+ "karpenter.sh/discovery" = "${var.cluster_name}"
+ }
+ # Use variable to configure AZ just in case 1 AZ isn't accessible
+ # https://repost.aws/questions/QUgdQev4KETKG_Bwev9tMtRQ/is-it-possible-to-enable-3rd-availability-zone-in-us-west-1#AN9eAH55FwTC-NSSp-FjIfTQ
+ availability_zones = [for az in var.azs : "${var.region}${az}"]
+ public_subnet_cidrs = [for index, _ in var.azs : cidrsubnet(var.vpc_cidr, 8, index + 1)]
+ private_subnet_cidrs = [for index, _ in var.azs : cidrsubnet(var.vpc_cidr, 2, index + 1)]
+}
+
+module "vpc" {
+ source = "terraform-aws-modules/vpc/aws"
+ version = "~> 6.6.0"
+
+ name = var.vpc_name
+
+ enable_nat_gateway = false
+ enable_dns_hostnames = true
+
+ cidr = var.vpc_cidr
+ azs = local.availability_zones
+
+ public_subnet_suffix = var.public_subnet_suffix
+ public_subnets = [for index, _ in var.azs : local.public_subnet_cidrs[index]]
+ public_subnet_tags = local.tags_public_subnet
+
+ private_subnet_suffix = var.private_subnet_suffix
+ private_subnets = [for index, _ in var.azs : local.private_subnet_cidrs[index]]
+ private_subnet_tags = local.tags_private_subnet
+
+ tags = {}
+}
+
+module "nat" {
+
+ source = "RaJiska/fck-nat/aws"
+ version = "~> 1.4.0"
+
+ name = "${var.region}-${var.nat_name}"
+ vpc_id = module.vpc.vpc_id
+ subnet_id = module.vpc.public_subnets[0]
+ ha_mode = true # Enables high-availability mode
+ use_cloudwatch_agent = true # Enables Cloudwatch agent and have metrics reported
+
+ update_route_tables = true
+ route_tables_ids = {
+ for index, _ in var.azs : "rtb-${index}" => module.vpc.private_route_table_ids[index]
+ }
+}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/vpc/terragrunt.hcl b/infra/aws/eu-west-2/vpc/terragrunt.hcl
new file mode 100644
index 0000000..7858497
--- /dev/null
+++ b/infra/aws/eu-west-2/vpc/terragrunt.hcl
@@ -0,0 +1,21 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = []
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region="eu-west-2"
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+ vpc_name=include.root.locals.CODER_VPC_NAME
+ vpc_cidr=include.root.locals.CODER_VPC_CIDR
+ azs=include.root.locals.CODER_VPC_AZS
+ nat_name=include.root.locals.CODER_VPC_NAT_NAME
+ public_subnet_suffix=include.root.locals.CODER_PUBLIC_SUBNET_SUFFIX
+ private_subnet_suffix=include.root.locals.CODER_PRIVATE_SUBNET_SUFFIX
+}
\ No newline at end of file
diff --git a/infra/aws/eu-west-2/vpc/variables.tf b/infra/aws/eu-west-2/vpc/variables.tf
new file mode 100644
index 0000000..6ff8d7d
--- /dev/null
+++ b/infra/aws/eu-west-2/vpc/variables.tf
@@ -0,0 +1,48 @@
+variable "profile" {
+ type = string
+}
+
+variable "region" {
+ description = "The aws region for the vpc"
+ type = string
+}
+
+variable "cluster_name" {
+ description = "Cluster name"
+ type = string
+}
+
+variable "azs" {
+ type = list(string)
+ default = ["a", "b", "c"]
+
+ validation {
+ condition = length(var.azs) <= 3
+ error_message = "There should only be at most 3 availability zones specified."
+ }
+}
+
+variable "vpc_name" {
+ description = "Name for created resources and tag prefix"
+ type = string
+}
+
+variable "vpc_cidr" {
+ type = string
+ default = "10.0.0.0/16"
+}
+
+variable "private_subnet_suffix" {
+ type = string
+ default = "private"
+}
+
+variable "public_subnet_suffix" {
+ type = string
+ default = "public"
+}
+
+variable "nat_name" {
+ description = "Name for created resources and tag prefix"
+ type = string
+}
\ No newline at end of file
diff --git a/infra/aws/root.hcl b/infra/aws/root.hcl
new file mode 100644
index 0000000..484b2ab
--- /dev/null
+++ b/infra/aws/root.hcl
@@ -0,0 +1,187 @@
+locals {
+ CODER_TF_BACKEND_AWS_BUCKET_NAME = get_env("CODER_TF_BACKEND_AWS_BUCKET_NAME")
+ CODER_TF_BACKEND_AWS_REGION = get_env("CODER_TF_BACKEND_AWS_REGION", "us-east-2")
+ CODER_TF_BACKEND_AWS_PROFILE = get_env("CODER_TF_BACKEND_AWS_PROFILE", "default")
+ CODER_TF_BACKEND_ENCRYPT = get_env("CODER_TF_BACKEND_ENCRYPT", "false")
+
+ CODER_AWS_PROFILE = get_env("CODER_AWS_PROFILE", "default")
+ CODER_AWS_REGION = get_env("CODER_AWS_REGION", "us-east-2")
+ CODER_AWS_AZS = get_env("CODER_AWS_AZS", jsonencode(["a", "c"]))
+
+ CODER_CLUSTER_NAME = get_env("CODER_CLUSTER_NAME", "coder")
+ CODER_CLUSTER_VERSION = get_env("CODER_CLUSTER_VERSION", "1.35")
+ CODER_CLUSTER_INSTANCE_TYPE = get_env("CODER_CLUSTER_INSTANCE_TYPE", "c6a.large")
+ CODER_VPC_NAME = get_env("CODER_VPC_NAME", "coder")
+ CODER_VPC_CIDR = get_env("CODER_VPC_CIDR", "10.0.0.0/16")
+ CODER_VPC_AZS = get_env("CODER_VPC_AZS", jsonencode(["a", "b", "c"]))
+ CODER_VPC_NAT_NAME = get_env("CODER_VPC_NAT_NAME", "nat-instance")
+ CODER_DB_SUBNET_GROUP_NAME = get_env("CODER_DB_SUBNET_GROUP_NAME", "coder-db-subnet-group")
+ CODER_PUBLIC_SUBNET_SUFFIX = get_env("CODER_PUBLIC_SUBNET_SUFFIX", "public")
+ CODER_PRIVATE_SUBNET_SUFFIX = get_env("CODER_PRIVATE_SUBNET_SUFFIX", "private")
+
+ CODER_DB_RDS_ID = get_env("CODER_DB_RDS_ID", "coder")
+ CODER_DB_USERNAME = get_env("CODER_DB_USERNAME", "coder")
+ CODER_DB_PASSWORD = get_env("CODER_DB_PASSWORD", "th1s1sn0tas3cur3pass0wrd")
+ CODER_DB_NAME = get_env("CODER_DB_NAME", "coder")
+
+ LITELLM_DOMAIN_NAME = get_env("LITELLM_DOMAIN_NAME")
+ LITELLM_ADDON_NAMESPACE = get_env("LITELLM_ADDON_NAMESPACE", "litellm")
+ LITELLM_ADDON_VERSION = get_env("LITELLM_ADDON_VERSION", "0.1.830")
+ LITELLM_DB_RDS_ID = get_env("LITELLM_DB_RDS_ID", "litellm")
+ LITELLM_DB_NAME = get_env("LITELLM_DB_NAME", "litellm")
+ LITELLM_DB_USERNAME = get_env("LITELLM_DB_USERNAME", "litellm")
+ LITELLM_DB_USER_PASSWORD = get_env("LITELLM_DB_USER_PASSWORD", "th1s1sn0tas3cur3pass0wrd")
+ LITELLM_DB_ADMIN_PASSWORD = get_env("LITELLM_DB_ADMIN_PASSWORD", "th1s1sn0tas3cur3pass0wrd")
+ LITELLM_GCLOUD_AUTH = get_env("LITELLM_GCLOUD_AUTH", "th1s1sn0tas3cur3pass0wrd")
+ LITELLM_MASTER_KEY = get_env("LITELLM_MASTER_KEY", "th1s1sn0tas3cur3pass0wrd")
+
+ CODER_IMAGE_REPO = get_env("CODER_IMAGE_REPO", "ghcr.io/coder/coder")
+ CODER_IMAGE_TAG = get_env("CODER_IMAGE_TAG", "v2.28.6")
+ CODER_ADDON_VERSION = get_env("CODER_ADDON_VERSION", "2.25.1")
+ CODER_LOGSTREAM_ADDON_VERSION = get_env("CODER_LOGSTREAM_ADDON_VERSION", "0.0.11")
+
+ KPTR_ADDON_NAMESPACE = get_env("KPTR_ADDON_NAMESPACE", "karpenter")
+ KPTR_ADDON_VERSION = get_env("KPTR_ADDON_VERSION", "1.9.0")
+
+ CRTMGR_ADDON_VERSION = get_env("CRTMGR_ADDON_VERSION", "v1.18.2")
+ CRTMGR_ADDON_NAMESPACE = get_env("CRTMGR_ADDON_NAMESPACE", "cert-manager")
+
+ EBS_ADDON_NAMESPACE = get_env("EBS_ADDON_NAMESPACE", "ebs-controller")
+ EBS_ADDON_VERSION = get_env("EBS_ADDON_VERSION", "2.22.1")
+ EBS_ADDON_REPLACE = get_env("EBS_ADDON_REPLACE", "true")
+
+ LB_ADDON_NAMESPACE = get_env("LB_ADDON_NAMESPACE", "lb-controller")
+ LB_ADDON_VERSION = get_env("LB_ADDON_VERSION", "1.13.2")
+ LB_ADDON_USE_CERTMGR = get_env("LB_ADDON_USE_CERTMGR", "true")
+
+ METRICS_SRV_ADDON_NAMESPACE = get_env("METRICS_SRV_ADDON_NAMESPACE", "kube-system")
+ METRICS_SRV_ADDON_VERSION = get_env("METRICS_SRV_ADDON_VERSION", "3.13.0")
+
+ CODER_OBSRV_CHART_VERSION = get_env("CODER_OBSRV_CHART_VERSION", "0.7.0-rc.1")
+ CODER_OBSRV_CHART_NAMESPACE = get_env("CODER_OBSRV_CHART_NAMESPACE", "observability")
+
+ LOKI_S3_BUCKET_NAME = get_env("LOKI_S3_BUCKET_NAME", "loki")
+ LOKI_S3_BUCKET_REGION = get_env("LOKI_S3_BUCKET_REGION", "us-east-2")
+
+ GRAFANA_DOMAIN_NAME = get_env("GRAFANA_DOMAIN_NAME")
+
+ GRAFANA_DB_RDS_ID = get_env("GRAFANA_DB_RDS_ID", "grafana")
+ GRAFANA_DB_NAME = get_env("GRAFANA_DB_NAME", "grafana")
+ GRAFANA_DB_USERNAME = get_env("GRAFANA_DB_USERNAME", "grafana")
+ GRAFANA_DB_PASSWORD = get_env("GRAFANA_DB_PASSWORD", "Th1s1sN0TS3CuR3!!")
+
+ GRAFANA_USERNAME = get_env("GRAFANA_USERNAME", "admin")
+ GRAFANA_PASSWORD = get_env("GRAFANA_PASSWORD", "Th1s1sN0TS3CuR3!!")
+
+ GRAFANA_ADMIN_USERNAME = get_env("GRAFANA_ADMIN_USERNAME", "admin")
+ GRAFANA_ADMIN_PASSWORD = get_env("GRAFANA_ADMIN_PASSWORD", "Th1s1sN0TS3CuR3!!")
+
+ CODER_DOMAIN_NAME = get_env("CODER_DOMAIN_NAME")
+ CODER_WILDCARD_URL = get_env("CODER_WILDCARD_URL")
+ CODER_LICENSE = get_env("CODER_LICENSE", "")
+ CODER_EXPERIMENTS = get_env("CODER_EXPERIMENTS", jsonencode([]))
+ CODER_BUILT_IN_PROVISIONER_COUNT = get_env("CODER_BUILT_IN_PROVISIONER_COUNT", "0")
+
+ CODER_USERNAME = get_env("CODER_USERNAME", "admin")
+ CODER_EMAIL = get_env("CODER_EMAIL", "admin@coder.com")
+ CODER_PASSWORD = get_env("CODER_PASSWORD", "Th1s1sN0TS3CuR3!!")
+
+ CF_EMAIL = get_env("CF_EMAIL")
+ CF_TOKEN = get_env("CF_TOKEN")
+
+ CODER_OIDC_SIGN_IN_TEXT = get_env("CODER_OIDC_SIGN_IN_TEXT")
+ CODER_OIDC_ICON_URL = get_env("CODER_OIDC_ICON_URL")
+ CODER_OIDC_SCOPES = get_env("CODER_OIDC_SCOPES", "[]")
+ CODER_OIDC_EMAIL_DOMAIN = get_env("CODER_OIDC_EMAIL_DOMAIN")
+ CODER_OIDC_ISSUER_URL = get_env("CODER_OIDC_ISSUER_URL")
+ CODER_OIDC_CLIENT_ID = get_env("CODER_OIDC_CLIENT_ID")
+ CODER_OIDC_CLIENT_SECRET = get_env("CODER_OIDC_CLIENT_SECRET")
+
+ CODER_GITHUB_OAUTH_CLIENT_ID = get_env("CODER_GITHUB_OAUTH_CLIENT_ID")
+ CODER_GITHUB_OAUTH_CLIENT_SECRET = get_env("CODER_GITHUB_OAUTH_CLIENT_SECRET")
+ CODER_GITHUB_EXTERN_AUTH_CLIENT_ID = get_env("CODER_GITHUB_EXTERN_AUTH_CLIENT_ID")
+ CODER_GITHUB_EXTERN_AUTH_CLIENT_SECRET = get_env("CODER_GITHUB_EXTERN_AUTH_CLIENT_SECRET")
+
+ CODER_ANTHROPIC_LLM_ENDPOINT = get_env("CODER_ANTHROPIC_LLM_ENDPOINT")
+ CODER_ANTHROPIC_LLM_KEY = get_env("CODER_ANTHROPIC_LLM_KEY")
+ CODER_OPENAI_LLM_ENDPOINT = get_env("CODER_OPENAI_LLM_ENDPOINT")
+ CODER_OPENAI_LLM_KEY = get_env("CODER_OPENAI_LLM_KEY")
+}
+
+generate "backend" {
+ path = "backend.tf"
+ if_exists = "overwrite"
+
+ contents = <<-EOF
+ terraform {
+ backend "s3" {
+ bucket = "${local.CODER_TF_BACKEND_AWS_BUCKET_NAME}"
+ key = "ai.coder.com/infra/aws/${path_relative_to_include()}/terraform.tfstate"
+ region = "${local.CODER_TF_BACKEND_AWS_REGION}"
+ profile = "${local.CODER_TF_BACKEND_AWS_PROFILE}"
+ encrypt = "${local.CODER_TF_BACKEND_ENCRYPT}"
+ }
+ }
+ EOF
+}
+
+generate "provider" {
+ path = "provider.tf"
+ if_exists = "overwrite"
+
+ contents = <<-EOF
+ terraform {
+ required_version = ">= 1.0"
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 6.52.0"
+ }
+ helm = {
+ source = "hashicorp/helm"
+ version = "~> 3.1.1"
+ }
+ kubernetes = {
+ source = "hashicorp/kubernetes"
+ version = "~> 3.0.1"
+ }
+ grafana = {
+ source = "grafana/grafana"
+ version = "4.28.1"
+ }
+ external = {
+ source = "hashicorp/external"
+ version = "~> 2.3.5"
+ }
+ time = {
+ source = "hashicorp/time"
+ version = "~> 0.13.1"
+ }
+ http = {
+ source = "hashicorp/http"
+ version = "~> 3.5.0"
+ }
+ dns = {
+ source = "hashicorp/dns"
+ version = "~> 3.5.0"
+ }
+ archive = {
+ source = "hashicorp/archive"
+ version = "~> 2.7.1"
+ }
+ random = {
+ source = "hashicorp/random"
+ version = "~> 3.8.1"
+ }
+ coderd = {
+ source = "coder/coderd"
+ version = "~> 0.0.12"
+ }
+ argocd = {
+ source = "argoproj-labs/argocd"
+ version = "~> 7.15.3"
+ }
+ }
+ }
+ EOF
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/eks/data.tf b/infra/aws/us-east-2/eks/data.tf
new file mode 100644
index 0000000..7dccbf0
--- /dev/null
+++ b/infra/aws/us-east-2/eks/data.tf
@@ -0,0 +1,70 @@
+data "aws_caller_identity" "me" {}
+
+data "aws_vpc" "this" {
+ tags = {
+ Name = var.vpc_name
+ }
+}
+
+data "aws_subnets" "public" {
+ filter {
+ name = "vpc-id"
+ values = [data.aws_vpc.this.id]
+ }
+
+ tags = {
+ Name = "*${var.public_subnet_suffix}*"
+ }
+}
+
+data "aws_subnets" "private" {
+ filter {
+ name = "vpc-id"
+ values = [data.aws_vpc.this.id]
+ }
+
+ tags = {
+ Name = "*${var.private_subnet_suffix}*"
+ }
+}
+
+data "aws_ssoadmin_instances" "this" {
+ region = "us-east-1"
+}
+
+data "aws_identitystore_group" "aws_administrator" {
+ identity_store_id = one(data.aws_ssoadmin_instances.this.identity_store_ids)
+ region = "us-east-1"
+
+ alternate_identifier {
+ unique_attribute {
+ attribute_path = "DisplayName"
+ attribute_value = "CoderCSAWSAdmin"
+ }
+ }
+}
+
+data "aws_iam_policy_document" "ecr-mirror" {
+
+ statement {
+ effect = "Allow"
+ actions = ["ecr:CreateRepository"]
+ resources = ["*"]
+ }
+
+ statement {
+ effect = "Allow"
+ actions = ["ecr:GetAuthorizationToken"]
+ resources = ["*"]
+ }
+
+ statement {
+ effect = "Allow"
+ actions = [
+ "ecr:BatchImportUpstreamImage",
+ "ecr:BatchGetImage",
+ "ecr:GetDownloadUrlForLayer"
+ ]
+ resources = [ "arn:aws:ecr:${var.region}:${data.aws_caller_identity.me.account_id}:repository/cache/*" ]
+ }
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/eks/main.tf b/infra/aws/us-east-2/eks/main.tf
index e23a5bf..5269a67 100644
--- a/infra/aws/us-east-2/eks/main.tf
+++ b/infra/aws/us-east-2/eks/main.tf
@@ -1,56 +1,3 @@
-terraform {
- required_version = ">= 1.0"
- required_providers {
- aws = {
- source = "hashicorp/aws"
- version = ">= 5.46"
- }
- }
- backend "s3" {}
-}
-
-variable "name" {
- description = "The resource name and tag prefix"
- type = string
-}
-
-variable "profile" {
- type = string
-}
-
-variable "region" {
- description = "The aws region to deploy eks cluster"
- type = string
-}
-
-variable "cluster_version" {
- description = "The eks version"
- type = string
-}
-
-variable "cluster_instance_type" {
- description = "EKS Instance Size/Type"
- default = "t3.xlarge"
- type = string
-}
-
-variable "vpc_id" {
- type = string
- sensitive = true
-}
-
-variable "private_subnet_ids" {
- type = list(string)
- default = []
- sensitive = true
-}
-
-variable "public_subnet_ids" {
- type = list(string)
- default = []
- sensitive = true
-}
-
provider "aws" {
region = var.region
profile = var.profile
@@ -58,106 +5,330 @@ provider "aws" {
locals {
tags = {
- Environment = "prod"
- Name = "${var.name}-eks-cluster"
+ Name = var.name
"karpenter.sh/discovery" = var.name
}
- system_subnet_tags = {
- "subnet.amazonaws.io/system/owned-by" = var.name
- }
- provisioner_subnet_tags = {
- "subnet.amazonaws.io/coder-provisioner/owned-by" = var.name
- }
- ws_all_subnet_tags = {
- "subnet.amazonaws.io/coder-ws-all/owned-by" = var.name
- }
- system_sg_tags = {
- "subnet.amazonaws.io/system/owned-by" = var.name
- }
- provisioner_sg_tags = {
- "sg.amazonaws.io/coder-provisioner/owned-by" = var.name
+ # Karpenter Security Group Discovery - https://karpenter.sh/v1.0/concepts/nodeclasses/#specsecuritygroupselectorterms
+ tags_kptr_sg_discovery = {
+ "karpenter.sh/discovery" = var.name
}
- ws_all_sg_tags = {
- "sg.amazonaws.io/coder-ws-all/owned-by" = var.name
+ labels_system_node = {
+ "scheduling.coder.com/pool" = "system"
}
- cluster_asg_node_labels = {
- "node.amazonaws.io/managed-by" = "asg"
+ taints_system = {
+ key = "CriticalAddonsOnly"
+ effect = "NO_SCHEDULE"
}
+ tolerations_system = [{
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }]
}
-data "aws_iam_policy_document" "sts" {
- statement {
- effect = "Allow"
- actions = ["sts:*"]
- resources = ["*"]
- }
-}
-
-resource "aws_iam_policy" "sts" {
- name_prefix = "sts"
- path = "/"
- description = "Assume Role Policy"
- policy = data.aws_iam_policy_document.sts.json
- tags = local.tags
+resource "aws_iam_policy" "ecr-mirror" {
+ name_prefix = "ecr-mirror-auto"
+ description = "Allows ECR pull-through cache automation including repository creation"
+ path = "/${var.region}/auto-mode/"
+ policy = data.aws_iam_policy_document.ecr-mirror.json
}
module "eks" {
source = "terraform-aws-modules/eks/aws"
- version = "~> 20.0"
+ version = "~> 21.24.0"
- vpc_id = var.vpc_id
- subnet_ids = toset(concat(var.public_subnet_ids, var.private_subnet_ids))
+ vpc_id = data.aws_vpc.this.id
+ subnet_ids = toset(concat(
+ data.aws_subnets.private.ids
+ ))
- cluster_name = var.name
- cluster_version = var.cluster_version
- cluster_endpoint_public_access = true
- cluster_endpoint_private_access = true
+ name = var.name
+ kubernetes_version = var.eks_version
+ endpoint_public_access = true
+ endpoint_private_access = true
- create_cluster_security_group = true
+ create_security_group = true
create_node_security_group = true
create_iam_role = true
- cluster_addons = {
+ node_security_group_tags = local.tags_kptr_sg_discovery
+ create_node_iam_role = true
+ node_iam_role_use_name_prefix = true
+
+ node_iam_role_additional_policies = {
+ "ECRMirrorPolicy" = aws_iam_policy.ecr-mirror.arn
+ }
+
+ create_auto_mode_iam_resources = true
+ compute_config = {
+ enabled = true
+ }
+
+ addons = {
coredns = {
- most_recent = true
+ before_compute = true
}
kube-proxy = {
- most_recent = true
+ before_compute = true
}
vpc-cni = {
- most_recent = true
+ before_compute = true
configuration_values = jsonencode({
enableNetworkPolicy = "true"
nodeAgent = {
enablePolicyEventLogs = "true"
}
+ env = {
+ ENABLE_PREFIX_DELEGATION = "true"
+ WARM_PREFIX_TARGET = "1"
+ WARM_IP_TARGET = "0"
+ AWS_VPC_K8S_CNI_LOGLEVEL = "DEBUG"
+ }
})
}
}
- attach_cluster_encryption_policy = false
- create_kms_key = false
- cluster_encryption_config = {}
+ attach_encryption_policy = false
+ create_kms_key = false # Enable unless needed
+ encryption_config = null
enable_cluster_creator_admin_permissions = true
enable_irsa = true
- eks_managed_node_groups = {
- system = {
- min_size = 0
- max_size = 10
- desired_size = 0 # Cant be modified after creation. Override from AWS Console
- labels = local.cluster_asg_node_labels
-
- instance_types = [var.cluster_instance_type]
- capacity_type = "ON_DEMAND"
- iam_role_additional_policies = {
- AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
- STSAssumeRole = aws_iam_policy.sts.arn
+ tags = local.tags
+}
+
+# https://aws-controllers-k8s.github.io/docs/intro
+# https://github.com/aws-controllers-k8s
+# https://docs.aws.amazon.com/eks/latest/userguide/ack.html
+module "eks-ack-capability" {
+ source = "terraform-aws-modules/eks/aws//modules/capability"
+ version = "~> 21.24.0"
+
+ name = "eks-ack"
+ cluster_name = module.eks.cluster_name
+ type = "ACK"
+
+ # IAM Role/Policy
+ iam_role_policies = {
+ AdministratorAccess = "arn:aws:iam::aws:policy/AdministratorAccess"
+ }
+
+ tags = local.tags
+}
+
+# https://docs.aws.amazon.com/eks/latest/userguide/argocd.html
+module "eks-argocd-capability" {
+ source = "terraform-aws-modules/eks/aws//modules/capability"
+ version = "~> 21.24.0"
+
+ type = "ARGOCD"
+ cluster_name = module.eks.cluster_name
+
+ configuration = {
+ argo_cd = {
+ aws_idc = {
+ idc_instance_arn = one(data.aws_ssoadmin_instances.this.arns)
+ idc_region = "us-east-1"
}
+ namespace = "argocd"
+ rbac_role_mapping = [{
+ role = "ADMIN"
+ identity = [{
+ id = data.aws_identitystore_group.aws_administrator.group_id
+ type = "SSO_GROUP"
+ }]
+ }]
+ }
+ }
- # System Nodes should not be public
- subnet_ids = var.private_subnet_ids
+ # IAM Role/Policy
+ iam_policy_statements = {
+ ECRRead = {
+ actions = [
+ "ecr:GetAuthorizationToken",
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:BatchGetImage",
+ ]
+ resources = ["*"]
}
}
tags = local.tags
+}
+
+resource "aws_eks_access_entry" "auto-mode" {
+ principal_arn = module.eks.node_iam_role_arn
+ cluster_name = module.eks.cluster_name
+ type = "EC2"
+}
+
+resource "aws_eks_access_policy_association" "attach" {
+
+ cluster_name = module.eks.cluster_name
+ policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSAutoNodePolicy"
+ principal_arn = module.eks.node_iam_role_arn
+
+ access_scope {
+ type = "cluster"
+ }
+}
+
+resource "aws_iam_instance_profile" "auto-mode" {
+ name = module.eks.node_iam_role_name
+ role = module.eks.node_iam_role_name
+}
+
+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"
+ args = ["eks", "get-token", "--cluster-name", var.name, "--region", var.region, "--profile", var.profile]
+ command = "aws"
+ }
+}
+
+resource "kubernetes_service_account_v1" "auto-mode-node-role" {
+
+ metadata {
+ name = "auto-mode-node-role"
+ namespace = "default"
+ annotations = {
+ "eks.amazonaws.com/role-arn" = module.eks.node_iam_role_arn
+ "eks.amazonaws.com/role-name" = module.eks.node_iam_role_name
+ }
+ }
+}
+
+resource "kubernetes_manifest" "sys-default-cls" {
+
+ depends_on = [
+ module.eks,
+ aws_eks_access_entry.auto-mode,
+ aws_eks_access_policy_association.attach
+ ]
+
+ manifest = {
+ apiVersion = "eks.amazonaws.com/v1"
+ kind = "NodeClass"
+
+ metadata = {
+ name = "sys-default"
+ labels = {
+ "app.kubernetes.io/managed-by" = "terraform"
+ }
+ }
+
+ spec = {
+ ephemeralStorage = {
+ iops = 3000
+ size = "80Gi"
+ throughput = 125
+ }
+
+ networkPolicy = "DefaultAllow"
+ networkPolicyEventLogs = "Disabled"
+
+ role = module.eks.node_iam_role_name
+
+ securityGroupSelectorTerms = [
+ {
+ id = module.eks.cluster_primary_security_group_id
+ }
+ ]
+
+ snatPolicy = "Random"
+
+ subnetSelectorTerms = [for subnet_id in data.aws_subnets.private.ids : { id = subnet_id }]
+ }
+ }
+}
+
+# Override the System NodePool to restrict number of nodess
+resource "kubernetes_manifest" "system-pool" {
+
+ depends_on = [
+ module.eks,
+ aws_eks_access_entry.auto-mode,
+ aws_eks_access_policy_association.attach
+ ]
+
+ manifest = {
+ apiVersion = "karpenter.sh/v1"
+ kind = "NodePool"
+
+ metadata = {
+ name = "coder-system"
+ labels = {
+ "app.kubernetes.io/managed-by" = "terraform"
+ }
+ }
+
+ spec = {
+ disruption = {
+ budgets = [
+ {
+ nodes = "10%"
+ }
+ ]
+ consolidateAfter = "30s"
+ consolidationPolicy = "WhenEmptyOrUnderutilized"
+ }
+
+ template = {
+ metadata = {}
+
+ spec = {
+ expireAfter = "336h"
+
+ nodeClassRef = {
+ group = split("/", kubernetes_manifest.sys-default-cls.manifest.apiVersion)[0]
+ kind = kubernetes_manifest.sys-default-cls.manifest.kind
+ name = kubernetes_manifest.sys-default-cls.manifest.metadata.name
+ }
+
+ requirements = [
+ {
+ key = "karpenter.sh/capacity-type"
+ operator = "In"
+ values = ["spot", "on-demand"]
+ },
+ {
+ key = "eks.amazonaws.com/instance-category"
+ operator = "In"
+ values = ["c", "m", "r"]
+ },
+ {
+ key = "eks.amazonaws.com/instance-generation"
+ operator = "Gt"
+ values = ["4"]
+ },
+ {
+ key = "kubernetes.io/arch"
+ operator = "In"
+ values = ["amd64", "arm64"]
+ },
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = slice([for az in var.azs : "${var.region}${az}"], 0, 1)
+ },
+ {
+ key = "kubernetes.io/os"
+ operator = "In"
+ values = ["linux"]
+ }
+ ]
+
+ taints = [
+ {
+ key = "CriticalAddonsOnly"
+ effect = "NoSchedule"
+ }
+ ]
+
+ terminationGracePeriod = "24h0m0s"
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/eks/terragrunt.hcl b/infra/aws/us-east-2/eks/terragrunt.hcl
new file mode 100644
index 0000000..827f4bc
--- /dev/null
+++ b/infra/aws/us-east-2/eks/terragrunt.hcl
@@ -0,0 +1,21 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = ["../vpc"]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+ azs=jsondecode(include.root.locals.CODER_AWS_AZS)
+
+ name=include.root.locals.CODER_CLUSTER_NAME
+ vpc_name=include.root.locals.CODER_VPC_NAME
+ eks_version=include.root.locals.CODER_CLUSTER_VERSION
+ instance_type=include.root.locals.CODER_CLUSTER_INSTANCE_TYPE
+ public_subnet_suffix=include.root.locals.CODER_PUBLIC_SUBNET_SUFFIX
+ private_subnet_suffix=include.root.locals.CODER_PRIVATE_SUBNET_SUFFIX
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/eks/variables.tf b/infra/aws/us-east-2/eks/variables.tf
new file mode 100644
index 0000000..4e5b465
--- /dev/null
+++ b/infra/aws/us-east-2/eks/variables.tf
@@ -0,0 +1,43 @@
+variable "profile" {
+ type = string
+}
+
+variable "region" {
+ description = "The aws region to deploy eks cluster"
+ type = string
+}
+
+variable "azs" {
+ description = "The aws availability zones to deploy eks cluster"
+ type = list(string)
+}
+
+variable "name" {
+ description = "The resource name and tag prefix"
+ type = string
+}
+
+variable "eks_version" {
+ description = "The eks version"
+ type = string
+}
+
+variable "instance_type" {
+ description = "EKS Instance Size/Type"
+ default = "t3.xlarge"
+ type = string
+}
+
+variable "vpc_name" {
+ type = string
+}
+
+variable "private_subnet_suffix" {
+ type = string
+ default = "private"
+}
+
+variable "public_subnet_suffix" {
+ type = string
+ default = "public"
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/acm-controller/main.tf b/infra/aws/us-east-2/k8s/acm-controller/main.tf
new file mode 100644
index 0000000..a7f8d0a
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/acm-controller/main.tf
@@ -0,0 +1,111 @@
+provider "aws" {
+ region = var.region
+ profile = var.profile
+}
+
+data "aws_eks_cluster" "this" {
+ name = var.cluster_name
+}
+
+data "aws_eks_cluster_auth" "this" {
+ name = var.cluster_name
+}
+
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
+}
+
+provider "helm" {
+ kubernetes = {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+ }
+}
+
+provider "kubernetes" {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+}
+
+module "oidc-role" {
+ source = "../../../../../modules/security/role/access-entry"
+ name = "acm-ctrl"
+ path = "/${var.cluster_name}/${var.region}/"
+ cluster_name = var.cluster_name
+ policy_arns = {
+ "AmazonS3ReadOnlyAccess" = "arn:aws:iam::aws:policy/AWSCertificateManagerFullAccess"
+ }
+ cluster_policy_arns = {
+ "AmazonEKSClusterAdminPolicy" = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy",
+ }
+ oidc_principals = {
+ "${data.aws_iam_openid_connect_provider.this.arn}" = ["system:serviceaccount:*:*"]
+ }
+ tags = {}
+}
+
+resource "kubernetes_namespace_v1" "this" {
+
+ count = var.create_namespace ? 1 : 0
+
+ metadata {
+ name = var.namespace
+ }
+}
+
+resource "kubernetes_service_account_v1" "acm-ctrl" {
+ metadata {
+ name = "ack-acm-controller"
+ namespace = try(kubernetes_namespace_v1.this[0].metadata[0].name, var.namespace)
+ annotations = {
+ "eks.amazonaws.com/role-arn" = module.oidc-role.role_arn
+ }
+ }
+ automount_service_account_token = true
+}
+
+resource "helm_release" "acm-ctrl" {
+ name = var.release_name
+ namespace = try(kubernetes_namespace_v1.this[0].metadata[0].name, var.namespace)
+ chart = var.chart_name
+ repository = "oci://public.ecr.aws/aws-controllers-k8s/"
+ create_namespace = false
+ upgrade_install = true
+ skip_crds = false
+ wait = true
+ wait_for_jobs = true
+ version = var.chart_version
+ timeout = 600
+
+ values = [yamlencode({
+ aws = {
+ region = "us-east-1" # Required by CloudFront
+ }
+ serviceAccount = {
+ create = false
+ name = kubernetes_service_account_v1.acm-ctrl.metadata[0].name
+ }
+ deployment = {
+ tolerations = [{
+ effect = "NoSchedule"
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }]
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [{
+ key = "karpenter.sh/nodepool"
+ operator = "In"
+ values = ["system"]
+ }]
+ }]
+ }
+ }
+ }
+ }
+ })]
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/acm-controller/terragrunt.hcl b/infra/aws/us-east-2/k8s/acm-controller/terragrunt.hcl
new file mode 100644
index 0000000..1d7058d
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/acm-controller/terragrunt.hcl
@@ -0,0 +1,20 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks"
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ # addon_namespace=include.root.locals.KYVERNO_ADDON_NAMESPACE
+ # addon_version=include.root.locals.KYVERNO_ADDON_VERSION
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/acm-controller/variables.tf b/infra/aws/us-east-2/k8s/acm-controller/variables.tf
new file mode 100644
index 0000000..189def8
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/acm-controller/variables.tf
@@ -0,0 +1,37 @@
+variable "cluster_name" {
+ type = string
+}
+
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "release_name" {
+ type = string
+ default = "acm-chart"
+}
+
+variable "chart_name" {
+ type = string
+ default = "acm-chart"
+}
+
+variable "chart_version" {
+ type = string
+ default = "1.3.4"
+}
+
+variable "create_namespace" {
+ type = bool
+ default = true
+}
+
+variable "namespace" {
+ type = string
+ default = "acm-ctrl"
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-server/chart/Chart.lock b/infra/aws/us-east-2/k8s/argo/coder-server/chart/Chart.lock
new file mode 100644
index 0000000..e9c61f1
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-server/chart/Chart.lock
@@ -0,0 +1,6 @@
+dependencies:
+- name: coder
+ repository: https://helm.coder.com/v2
+ version: 2.25.1
+digest: sha256:c339760603080970eae8717ddf19b6475a4278a73724036eed0d5a9e4188c406
+generated: "2026-06-26T11:47:33.29437-07:00"
diff --git a/infra/aws/us-east-2/k8s/argo/coder-server/chart/Chart.yaml b/infra/aws/us-east-2/k8s/argo/coder-server/chart/Chart.yaml
new file mode 100644
index 0000000..44ade60
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-server/chart/Chart.yaml
@@ -0,0 +1,10 @@
+apiVersion: v2
+name: coder
+description: Wrapper chart that deploys the upstream Coder Helm chart with opinionated defaults
+type: application
+version: 0.1.0
+appVersion: "2.25.1"
+dependencies:
+ - name: coder
+ version: 2.25.1
+ repository: https://helm.coder.com/v2
diff --git a/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/_helpers.tpl b/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/_helpers.tpl
new file mode 100644
index 0000000..776818c
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/_helpers.tpl
@@ -0,0 +1,51 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "coder.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Fully qualified app name. Truncated to 63 chars (K8s label limit).
+*/}}
+{{- define "coder.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Common labels for wrapper-managed resources.
+*/}}
+{{- define "coder.labels" -}}
+app.kubernetes.io/name: {{ include "coder.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+app.kubernetes.io/version: {{ .Values.coder.coder.image.tag | default .Chart.AppVersion | quote }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Selector labels matching the upstream coder chart's pod labels.
+The upstream chart uses:
+ app.kubernetes.io/name: coder
+ app.kubernetes.io/instance:
+*/}}
+{{- define "coder.upstreamSelectorLabels" -}}
+app.kubernetes.io/name: coder
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Namespace helper.
+*/}}
+{{- define "coder.namespace" -}}
+{{- .Values.namespace | default .Release.Namespace }}
+{{- end }}
diff --git a/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/certificate.yaml b/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/certificate.yaml
new file mode 100644
index 0000000..9c8b425
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/certificate.yaml
@@ -0,0 +1,24 @@
+{{- if .Values.certificate.enabled }}
+apiVersion: cert-manager.io/v1
+kind: Certificate
+metadata:
+ name: {{ .Values.tls.secretName | default (include "coder.fullname" .) }}
+ namespace: {{ include "coder.namespace" . }}
+ labels:
+ {{- include "coder.labels" . | nindent 4 }}
+spec:
+ commonName: {{ .Values.certificate.commonName | quote }}
+ dnsNames:
+ {{- toYaml .Values.certificate.dnsNames | nindent 4 }}
+ duration: {{ .Values.certificate.duration | quote }}
+ renewBefore: {{ .Values.certificate.renewBefore | quote }}
+ issuerRef:
+ kind: {{ .Values.certificate.issuerRef.kind }}
+ name: {{ .Values.certificate.issuerRef.name }}
+ secretName: {{ .Values.tls.secretName | default (include "coder.fullname" .) }}
+ privateKey:
+ rotationPolicy: {{ .Values.certificate.privateKey.rotationPolicy }}
+ algorithm: {{ .Values.certificate.privateKey.algorithm }}
+ encoding: {{ .Values.certificate.privateKey.encoding }}
+ size: {{ .Values.certificate.privateKey.size }}
+{{- end }}
diff --git a/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/env-configmap.yaml b/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/env-configmap.yaml
new file mode 100644
index 0000000..643ba36
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/env-configmap.yaml
@@ -0,0 +1,107 @@
+{{/*
+ConfigMap containing all non-secret CODER_* environment variables.
+Referenced by the upstream coder chart via coder.envFrom.
+*/}}
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ .Release.Name }}-env
+ namespace: {{ include "coder.namespace" . }}
+ labels:
+ {{- include "coder.labels" . | nindent 4 }}
+data:
+ CODER_ACCESS_URL: {{ .Values.accessUrl | quote }}
+ CODER_WILDCARD_ACCESS_URL: {{ .Values.wildcardUrl | quote }}
+ CODER_REDIRECT_TO_ACCESS_URL: {{ .Values.tls.enabled | quote }}
+ CODER_ENABLE_TERRAFORM_DEBUG_MODE: {{ .Values.terraformDebugMode | quote }}
+ CODER_TRACE_LOGS: {{ .Values.traceLogs | quote }}
+ CODER_TRACE_ENABLE: {{ .Values.traceEnable | quote }}
+ CODER_LOG_FILTER: {{ .Values.logFilter | quote }}
+ CODER_SWAGGER_ENABLE: {{ .Values.swaggerEnable | quote }}
+ CODER_UPDATE_CHECK: {{ .Values.updateCheck | quote }}
+ CODER_CLI_UPGRADE_MESSAGE: {{ .Values.cliUpgradeMessage | quote }}
+ CODER_PROVISIONER_DAEMONS: {{ .Values.provisionerDaemons | quote }}
+ CODER_PROVISIONER_FORCE_CANCEL_INTERVAL: {{ .Values.provisionerForceCancelInterval | quote }}
+ CODER_QUIET_HOURS_DEFAULT_SCHEDULE: {{ .Values.quietHoursDefaultSchedule | quote }}
+ CODER_ALLOW_CUSTOM_QUIET_HOURS: {{ .Values.allowCustomQuietHours | quote }}
+ {{- if .Values.experiments }}
+ CODER_EXPERIMENTS: {{ .Values.experiments | quote }}
+ {{- end }}
+
+ # Database
+ CODER_PG_AUTH: {{ .Values.db.pgAuth | quote }}
+
+ # Prometheus
+ CODER_PROMETHEUS_ENABLE: {{ .Values.prometheus.enabled | quote }}
+ CODER_PROMETHEUS_COLLECT_AGENT_STATS: {{ .Values.prometheus.collectAgentStats | quote }}
+ CODER_PROMETHEUS_COLLECT_DB_METRICS: {{ .Values.prometheus.collectDbMetrics | quote }}
+
+ # OIDC
+ {{- if .Values.oidc.enabled }}
+ CODER_OIDC_ISSUER_URL: {{ .Values.oidc.issuerUrl | quote }}
+ CODER_OIDC_CLIENT_ID: {{ .Values.oidc.clientId | quote }}
+ CODER_OIDC_SIGN_IN_TEXT: {{ .Values.oidc.signInText | quote }}
+ CODER_OIDC_ICON_URL: {{ .Values.oidc.iconUrl | quote }}
+ {{- if .Values.oidc.scopes }}
+ CODER_OIDC_SCOPES: {{ join "," .Values.oidc.scopes | quote }}
+ {{- end }}
+ CODER_OIDC_EMAIL_DOMAIN: {{ .Values.oidc.emailDomain | quote }}
+ {{- end }}
+
+ # GitHub OAuth2
+ {{- if .Values.oauth2.enabled }}
+ CODER_OAUTH2_GITHUB_DEFAULT_PROVIDER_ENABLE: {{ .Values.oauth2.defaultProviderEnable | quote }}
+ CODER_OAUTH2_GITHUB_CLIENT_ID: {{ .Values.oauth2.clientId | quote }}
+ CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS: {{ .Values.oauth2.allowSignups | quote }}
+ CODER_OAUTH2_GITHUB_DEVICE_FLOW: {{ .Values.oauth2.deviceFlow | quote }}
+ CODER_OAUTH2_GITHUB_ALLOW_EVERYONE: {{ eq (len .Values.oauth2.allowedOrgs) 0 | quote }}
+ {{- if .Values.oauth2.allowedOrgs }}
+ CODER_OAUTH2_GITHUB_ALLOWED_ORGS: {{ join "," .Values.oauth2.allowedOrgs | quote }}
+ {{- end }}
+ {{- else }}
+ CODER_OAUTH2_GITHUB_DEFAULT_PROVIDER_ENABLE: "false"
+ {{- end }}
+
+ # External auth providers
+ {{- range $i, $auth := .Values.externalAuth }}
+ CODER_EXTERNAL_AUTH_{{ $i }}_ID: {{ $auth.id | quote }}
+ CODER_EXTERNAL_AUTH_{{ $i }}_TYPE: {{ $auth.type | quote }}
+ CODER_EXTERNAL_AUTH_{{ $i }}_CLIENT_ID: {{ $auth.clientId | quote }}
+ {{- if $auth.authUrl }}
+ CODER_EXTERNAL_AUTH_{{ $i }}_AUTH_URL: {{ $auth.authUrl | quote }}
+ {{- end }}
+ {{- if $auth.tokenUrl }}
+ CODER_EXTERNAL_AUTH_{{ $i }}_TOKEN_URL: {{ $auth.tokenUrl | quote }}
+ {{- end }}
+ {{- if $auth.revokeUrl }}
+ CODER_EXTERNAL_AUTH_{{ $i }}_REVOKE_URL: {{ $auth.revokeUrl | quote }}
+ {{- end }}
+ {{- if $auth.validateUrl }}
+ CODER_EXTERNAL_AUTH_{{ $i }}_VALIDATE_URL: {{ $auth.validateUrl | quote }}
+ {{- end }}
+ {{- if $auth.regex }}
+ CODER_EXTERNAL_AUTH_{{ $i }}_REGEX: {{ $auth.regex | quote }}
+ {{- end }}
+ {{- end }}
+
+ # AI Bridge
+ CODER_AIBRIDGE_ENABLED: {{ .Values.aibridge.enabled | quote }}
+ CODER_AIBRIDGE_STRUCTURED_LOGGING: {{ .Values.aibridge.structuredLogging | quote }}
+ {{- if .Values.aibridge.anthropic.enabled }}
+ CODER_AIBRIDGE_ANTHROPIC_BASE_URL: {{ .Values.aibridge.anthropic.url | quote }}
+ {{- end }}
+ {{- if .Values.aibridge.openai.enabled }}
+ CODER_AIBRIDGE_OPENAI_BASE_URL: {{ .Values.aibridge.openai.url | quote }}
+ {{- end }}
+ {{- if .Values.aibridge.bedrock.enabled }}
+ CODER_AIBRIDGE_BEDROCK_BASE_URL: {{ .Values.aibridge.bedrock.url | quote }}
+ CODER_AIBRIDGE_BEDROCK_REGION: {{ .Values.aibridge.bedrock.region | quote }}
+ CODER_AIBRIDGE_BEDROCK_ACCESS_KEY: {{ .Values.aibridge.bedrock.accessKey | quote }}
+ CODER_AIBRIDGE_BEDROCK_MODEL: {{ .Values.aibridge.bedrock.model | quote }}
+ CODER_AIBRIDGE_BEDROCK_SMALL_FAST_MODEL: {{ .Values.aibridge.bedrock.fastModel | quote }}
+ {{- end }}
+
+ # Extra env vars
+ {{- range $k, $v := .Values.extraEnvVars }}
+ {{ $k }}: {{ $v | quote }}
+ {{- end }}
diff --git a/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/namespace.yaml b/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/namespace.yaml
new file mode 100644
index 0000000..f601e25
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/namespace.yaml
@@ -0,0 +1,8 @@
+{{- if .Values.createNamespace }}
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: {{ include "coder.namespace" . }}
+ labels:
+ {{- include "coder.labels" . | nindent 4 }}
+{{- end }}
diff --git a/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/pdb.yaml b/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/pdb.yaml
new file mode 100644
index 0000000..e409fcf
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/pdb.yaml
@@ -0,0 +1,14 @@
+{{- if .Values.pdb.enabled }}
+apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+ name: {{ .Release.Name }}
+ namespace: {{ include "coder.namespace" . }}
+ labels:
+ {{- include "coder.labels" . | nindent 4 }}
+spec:
+ maxUnavailable: {{ .Values.pdb.maxUnavailable }}
+ selector:
+ matchLabels:
+ {{- include "coder.upstreamSelectorLabels" . | nindent 6 }}
+{{- end }}
diff --git a/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/secrets.yaml b/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/secrets.yaml
new file mode 100644
index 0000000..67e856f
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/secrets.yaml
@@ -0,0 +1,42 @@
+{{/*
+Aggregate Secret containing all sensitive CODER_* environment variables.
+Referenced by the upstream coder chart via coder.envFrom.
+Individual per-key secrets are also created (matching the TF module pattern)
+for any external consumers that reference them by name.
+*/}}
+apiVersion: v1
+kind: Secret
+metadata:
+ name: {{ .Release.Name }}-env-secrets
+ namespace: {{ include "coder.namespace" . }}
+ labels:
+ {{- include "coder.labels" . | nindent 4 }}
+type: Opaque
+stringData:
+ {{- if .Values.db.url }}
+ {{- if eq .Values.db.pgAuth "awsiamrds" }}
+ CODER_PG_CONNECTION_URL: {{ printf "postgresql://%s@%s/%s" .Values.db.username .Values.db.url .Values.db.name | quote }}
+ {{- else }}
+ CODER_PG_CONNECTION_URL: {{ printf "postgresql://%s:%s@%s/%s" .Values.db.username .Values.db.password .Values.db.url .Values.db.name | quote }}
+ {{- end }}
+ {{- end }}
+ {{- if and .Values.oidc.enabled .Values.oidc.clientSecret }}
+ CODER_OIDC_CLIENT_SECRET: {{ .Values.oidc.clientSecret | quote }}
+ {{- end }}
+ {{- if and .Values.oauth2.enabled .Values.oauth2.clientSecret }}
+ CODER_OAUTH2_GITHUB_CLIENT_SECRET: {{ .Values.oauth2.clientSecret | quote }}
+ {{- end }}
+ {{- range $i, $auth := .Values.externalAuth }}
+ {{- if $auth.clientSecret }}
+ CODER_EXTERNAL_AUTH_{{ $i }}_CLIENT_SECRET: {{ $auth.clientSecret | quote }}
+ {{- end }}
+ {{- end }}
+ {{- if and .Values.aibridge.anthropic.enabled .Values.aibridge.anthropic.key }}
+ CODER_AIBRIDGE_ANTHROPIC_KEY: {{ .Values.aibridge.anthropic.key | quote }}
+ {{- end }}
+ {{- if and .Values.aibridge.openai.enabled .Values.aibridge.openai.key }}
+ CODER_AIBRIDGE_OPENAI_KEY: {{ .Values.aibridge.openai.key | quote }}
+ {{- end }}
+ {{- if and .Values.aibridge.bedrock.enabled .Values.aibridge.bedrock.secretKey }}
+ CODER_AIBRIDGE_BEDROCK_ACCESS_KEY_SECRET: {{ .Values.aibridge.bedrock.secretKey | quote }}
+ {{- end }}
diff --git a/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/service.yaml b/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/service.yaml
new file mode 100644
index 0000000..0c95d06
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-server/chart/templates/service.yaml
@@ -0,0 +1,47 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ .Release.Name }}
+ namespace: {{ include "coder.namespace" . }}
+ labels:
+ {{- include "coder.labels" . | nindent 4 }}
+ {{- with .Values.service.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+spec:
+ type: {{ .Values.service.type }}
+ {{- if and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerClass }}
+ loadBalancerClass: {{ .Values.service.loadBalancerClass }}
+ {{- end }}
+ ports:
+ - name: http
+ port: 80
+ protocol: TCP
+ targetPort: http
+ - name: https
+ port: 443
+ protocol: TCP
+ targetPort: {{ if .Values.tls.enabled }}https{{ else }}http{{ end }}
+ selector:
+ {{- include "coder.upstreamSelectorLabels" . | nindent 4 }}
+---
+{{- if .Values.prometheus.enabled }}
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ .Release.Name }}-prometheus
+ namespace: {{ include "coder.namespace" . }}
+ labels:
+ {{- include "coder.labels" . | nindent 4 }}
+spec:
+ type: ClusterIP
+ clusterIP: None
+ ports:
+ - name: http
+ protocol: TCP
+ port: 2112
+ targetPort: 2112
+ selector:
+ {{- include "coder.upstreamSelectorLabels" . | nindent 4 }}
+{{- end }}
diff --git a/infra/aws/us-east-2/k8s/argo/coder-server/chart/values.yaml b/infra/aws/us-east-2/k8s/argo/coder-server/chart/values.yaml
new file mode 100644
index 0000000..078b3de
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-server/chart/values.yaml
@@ -0,0 +1,212 @@
+# -- Namespace to deploy into.
+namespace: coder
+createNamespace: true
+
+# ---------------------------------------------------------------------------
+# Wrapper-level configuration (translated into coder.env for the subchart)
+# ---------------------------------------------------------------------------
+
+# -- Coder access URL (e.g. https://ai.coder.com).
+accessUrl: ""
+# -- Coder wildcard access URL (e.g. https://*.ai.coder.com).
+wildcardUrl: ""
+
+# -- Comma-separated list of experiments to enable.
+experiments: ""
+
+provisionerDaemons: 0
+provisionerForceCancelInterval: "10m0s"
+quietHoursDefaultSchedule: "CRON_TZ=America/Los_Angeles 50 23 * * *"
+allowCustomQuietHours: true
+
+terraformDebugMode: true
+traceLogs: true
+traceEnable: true
+logFilter: ".*"
+swaggerEnable: true
+updateCheck: true
+cliUpgradeMessage: true
+
+# -- Arbitrary extra CODER_* env vars. Keys are the env-var name.
+extraEnvVars: {}
+# CODER_EXPERIMENTS: "multi-organization"
+
+# ---------------------------------------------------------------------------
+# TLS / cert-manager certificate
+# ---------------------------------------------------------------------------
+tls:
+ enabled: false
+ # -- Name of the TLS secret. When certificate.enabled=true this is also
+ # used as the Certificate secretName.
+ secretName: ""
+
+certificate:
+ enabled: false
+ commonName: ""
+ dnsNames: []
+ duration: "2160h" # 90 days
+ renewBefore: "360h" # 15 days
+ issuerRef:
+ kind: ClusterIssuer
+ name: issuer
+ privateKey:
+ rotationPolicy: Never
+ algorithm: RSA
+ encoding: PKCS1
+ size: 2048
+
+# ---------------------------------------------------------------------------
+# Database
+# ---------------------------------------------------------------------------
+db:
+ # -- Full host:port endpoint.
+ url: ""
+ username: ""
+ password: ""
+ name: coder
+ # -- Authentication mode: "password" or "awsiamrds".
+ pgAuth: password
+
+# ---------------------------------------------------------------------------
+# OIDC
+# ---------------------------------------------------------------------------
+oidc:
+ enabled: false
+ issuerUrl: ""
+ clientId: ""
+ clientSecret: ""
+ signInText: ""
+ iconUrl: ""
+ scopes: []
+ emailDomain: ""
+
+# ---------------------------------------------------------------------------
+# GitHub OAuth2
+# ---------------------------------------------------------------------------
+oauth2:
+ enabled: false
+ defaultProviderEnable: false
+ allowSignups: true
+ deviceFlow: false
+ allowedOrgs: []
+ clientId: ""
+ clientSecret: ""
+
+# ---------------------------------------------------------------------------
+# External auth providers
+# ---------------------------------------------------------------------------
+externalAuth: []
+# - id: primary-github
+# type: github
+# clientId: ""
+# clientSecret: ""
+
+# ---------------------------------------------------------------------------
+# AI Bridge
+# ---------------------------------------------------------------------------
+aibridge:
+ enabled: false
+ structuredLogging: true
+ anthropic:
+ enabled: false
+ url: ""
+ key: ""
+ openai:
+ enabled: false
+ url: ""
+ key: ""
+ bedrock:
+ enabled: false
+ url: ""
+ region: ""
+ accessKey: ""
+ secretKey: ""
+ model: "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
+ fastModel: "global.anthropic.claude-haiku-4-5-20251001-v1:0"
+
+# ---------------------------------------------------------------------------
+# Prometheus metrics
+# ---------------------------------------------------------------------------
+prometheus:
+ enabled: false
+ collectAgentStats: true
+ collectDbMetrics: true
+
+# ---------------------------------------------------------------------------
+# Service (managed outside the upstream chart, matching the TF module)
+# ---------------------------------------------------------------------------
+service:
+ type: LoadBalancer
+ loadBalancerClass: "service.k8s.aws/nlb"
+ annotations: {}
+
+# ---------------------------------------------------------------------------
+# Pod Disruption Budget
+# ---------------------------------------------------------------------------
+pdb:
+ enabled: false
+ maxUnavailable: 1
+
+# ---------------------------------------------------------------------------
+# Upstream coder subchart overrides
+#
+# The subchart is named "coder" and its values live under ".Values.coder".
+# Inside the subchart the values are nested under ".Values.coder.*", so from
+# the parent chart we address them as "coder.coder.*".
+#
+# Non-secret env vars are loaded from a ConfigMap (-env) via envFrom.
+# Secret env vars are loaded from a Secret (-env-secrets) via envFrom.
+# Both resources are created by this wrapper chart's templates.
+# ---------------------------------------------------------------------------
+coder:
+ coder:
+ image:
+ repo: ghcr.io/coder/coder
+ tag: latest
+ pullPolicy: IfNotPresent
+ pullSecrets: []
+
+ # Additional env entries beyond what envFrom provides.
+ env: []
+
+ # Load all CODER_* variables from the wrapper-managed ConfigMap and Secret.
+ # IMPORTANT: The names below must match the template output names. If you
+ # change the release name from "coder", update these accordingly.
+ envFrom:
+ - configMapRef:
+ name: coder-env
+ - secretRef:
+ name: coder-env-secrets
+ envUseClusterAccessURL: false
+
+ # Disable the upstream chart's Service — we manage our own.
+ service:
+ enable: false
+
+ tls:
+ secretNames: []
+
+ replicaCount: 1
+
+ resources:
+ requests:
+ cpu: "500m"
+ memory: "2Gi"
+ limits:
+ cpu: "2"
+ memory: "2Gi"
+
+ serviceAccount:
+ annotations: {}
+ name: coder
+
+ annotations: {}
+ podAnnotations: {}
+
+ tolerations: []
+ nodeSelector: {}
+ affinity: {}
+ topologySpreadConstraints: []
+
+ # NOTE: terminationGracePeriodSeconds is not configurable in the
+ # upstream coder chart v2.25.1 (hardcoded to 60s).
diff --git a/infra/aws/us-east-2/k8s/argo/coder-server/main.tf b/infra/aws/us-east-2/k8s/argo/coder-server/main.tf
new file mode 100644
index 0000000..b570007
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-server/main.tf
@@ -0,0 +1,343 @@
+provider "aws" {
+ region = var.region
+ profile = var.profile
+}
+
+data "aws_region" "this" {}
+
+data "aws_caller_identity" "this" {}
+
+data "aws_eks_cluster" "this" {
+ name = var.cluster_name
+}
+
+data "aws_eks_cluster_auth" "this" {
+ name = var.cluster_name
+}
+
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
+}
+
+data "aws_db_instance" "coder" {
+ db_instance_identifier = var.coder_db_rds_name
+}
+
+data "aws_vpc" "this" {
+ tags = {
+ Name = var.vpc_name
+ }
+}
+
+provider "kubernetes" {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+}
+
+# ---------------------------------------------------------------------------
+# Locals
+# ---------------------------------------------------------------------------
+
+locals {
+ region = data.aws_region.this.region
+ account_id = data.aws_caller_identity.this.account_id
+ azs = var.azs
+ pub_subs = [for az in local.azs : "${var.vpc_name}-public-${local.region}${az}"]
+ release_name = "coder"
+ namespace = "coder"
+ rds_db_name = split(".", data.aws_db_instance.coder.endpoint)[0]
+
+ common_name = trimprefix(trimprefix(var.coder_access_url, "https://"), "http://")
+ wildcard_name = trimprefix(trimprefix(var.coder_wildcard_access_url, "https://"), "http://")
+ ssl_vol_friendly_name = replace(local.common_name, ".", "-")
+}
+
+# ---------------------------------------------------------------------------
+# Elastic IPs for NLB
+# ---------------------------------------------------------------------------
+
+resource "aws_eip" "coder" {
+ count = length(local.pub_subs)
+ domain = "vpc"
+ public_ipv4_pool = "amazon"
+ tags = {
+ Name = "coder-eip-${count.index}"
+ }
+}
+
+# ---------------------------------------------------------------------------
+# IRSA: Provisioner policy (EC2 permissions for Terraform provisioners)
+# ---------------------------------------------------------------------------
+
+data "aws_iam_policy_document" "provisioner" {
+ statement {
+ effect = "Allow"
+ actions = [
+ "ec2:GetDefaultCreditSpecification",
+ "ec2:DescribeIamInstanceProfileAssociations",
+ "ec2:DescribeTags",
+ "ec2:DescribeInstances",
+ "ec2:DescribeInstanceTypes",
+ "ec2:DescribeInstanceStatus",
+ "ec2:CreateTags",
+ "ec2:RunInstances",
+ "ec2:DescribeInstanceCreditSpecifications",
+ "ec2:DescribeImages",
+ "ec2:ModifyDefaultCreditSpecification",
+ "ec2:DescribeVolumes"
+ ]
+ resources = ["*"]
+ }
+
+ statement {
+ effect = "Allow"
+ actions = [
+ "ec2:DescribeInstanceAttribute",
+ "ec2:UnmonitorInstances",
+ "ec2:TerminateInstances",
+ "ec2:StartInstances",
+ "ec2:StopInstances",
+ "ec2:DeleteTags",
+ "ec2:MonitorInstances",
+ "ec2:CreateTags",
+ "ec2:RunInstances",
+ "ec2:ModifyInstanceAttribute",
+ "ec2:ModifyInstanceCreditSpecification"
+ ]
+ resources = ["arn:aws:ec2:*:*:instance/*"]
+ }
+}
+
+module "provisioner-policy" {
+ count = var.coder_builtin_provisioner_count == 0 ? 0 : 1
+
+ source = "../../../../../../modules/security/policy"
+ name = "coder-srv"
+ path = "/${var.cluster_name}/${local.region}/"
+ description = "Coder Terraform External Provisioner Policy"
+ policy_json = data.aws_iam_policy_document.provisioner.json
+}
+
+# ---------------------------------------------------------------------------
+# IRSA: RDS IAM auth policy
+# ---------------------------------------------------------------------------
+
+data "aws_iam_policy_document" "rds" {
+ statement {
+ effect = "Allow"
+ actions = ["rds-db:connect"]
+ resources = [
+ "arn:aws:rds-db:${local.region}:${local.account_id}:dbuser:${data.aws_db_instance.coder.resource_id}/${var.coder_db_username}"
+ ]
+ }
+}
+
+module "rds-policy" {
+ source = "../../../../../../modules/security/policy"
+ name = "coder-srv-${local.rds_db_name}"
+ path = "/${var.cluster_name}/${local.region}/"
+ description = "Coder DB IAM Access Policy"
+ policy_json = data.aws_iam_policy_document.rds.json
+}
+
+# ---------------------------------------------------------------------------
+# IRSA: OIDC role for the Coder ServiceAccount
+# ---------------------------------------------------------------------------
+
+module "provisioner-oidc-role" {
+ source = "../../../../../../modules/security/role/access-entry"
+ name = "coder-srv"
+ path = "/${var.cluster_name}/${local.region}/"
+ cluster_name = var.cluster_name
+ policy_arns = merge({
+ "AmazonEC2ReadOnlyAccess" = "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
+ "CoderRDSDBPolicy" = module.rds-policy.policy_arn
+ }, var.coder_builtin_provisioner_count == 0 ? {} : {
+ "TFProvisionerPolicy" = module.provisioner-policy[0].policy_arn
+ })
+ cluster_policy_arns = {}
+ oidc_principals = {
+ "${data.aws_iam_openid_connect_provider.this.arn}" = ["system:serviceaccount:*:*"]
+ }
+}
+
+# ---------------------------------------------------------------------------
+# ArgoCD Application — deploys the local wrapper Helm chart
+# ---------------------------------------------------------------------------
+
+resource "argocd_application" "coder" {
+ metadata {
+ name = local.release_name
+ namespace = "argocd"
+ }
+
+ spec {
+ project = "default"
+
+ destination {
+ server = "https://kubernetes.default.svc"
+ namespace = local.namespace
+ }
+
+ source {
+ repo_url = "https://helm.coder.com/v2"
+ chart = "coder"
+ target_revision = var.addon_version
+
+ helm {
+ release_name = local.release_name
+
+ values = yamlencode({
+ # Wrapper-level values
+ namespace = local.namespace
+ createNamespace = true
+
+ accessUrl = var.coder_access_url
+ wildcardUrl = var.coder_wildcard_access_url
+ experiments = join(",", var.coder_experiments)
+
+ provisionerDaemons = var.coder_builtin_provisioner_count
+
+ tls = {
+ enabled = true
+ secretName = local.ssl_vol_friendly_name
+ }
+
+ certificate = {
+ enabled = true
+ commonName = local.common_name
+ dnsNames = [local.common_name, local.wildcard_name]
+ }
+
+ db = {
+ url = data.aws_db_instance.coder.endpoint
+ username = var.coder_db_username
+ password = var.coder_db_password
+ name = var.coder_db_name
+ pgAuth = "awsiamrds"
+ }
+
+ oidc = {
+ enabled = true
+ issuerUrl = var.coder_oidc_secret_issuer_url
+ clientId = var.coder_oidc_secret_client_id
+ clientSecret = var.coder_oidc_secret_client_secret
+ signInText = var.oidc_sign_in_text
+ iconUrl = var.oidc_icon_url
+ scopes = var.oidc_scopes
+ emailDomain = var.oidc_email_domain
+ }
+
+ oauth2 = {
+ enabled = true
+ defaultProviderEnable = false
+ allowSignups = true
+ deviceFlow = false
+ allowedOrgs = var.coder_github_allowed_orgs
+ clientId = var.coder_oauth_secret_client_id
+ clientSecret = var.coder_oauth_secret_client_secret
+ }
+
+ externalAuth = [{
+ id = "primary-github"
+ type = "github"
+ clientId = var.coder_github_external_auth_secret_client_id
+ clientSecret = var.coder_github_external_auth_secret_client_secret
+ }]
+
+ aibridge = {
+ enabled = true
+ }
+
+ prometheus = {
+ enabled = true
+ }
+
+ service = {
+ type = "LoadBalancer"
+ loadBalancerClass = "service.k8s.aws/nlb"
+ annotations = {
+ "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "ip"
+ "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
+ "service.beta.kubernetes.io/aws-load-balancer-attributes" = "deletion_protection.enabled=false,load_balancing.cross_zone.enabled=true"
+ "service.beta.kubernetes.io/aws-load-balancer-eip-allocations" = join(",", aws_eip.coder[*].allocation_id)
+ "service.beta.kubernetes.io/aws-load-balancer-subnets" = join(",", local.pub_subs)
+ }
+ }
+
+ coder = {
+ coder = {
+ image = {
+ repo = var.image_repo
+ tag = var.image_tag
+ }
+ replicaCount = length(local.pub_subs)
+ resources = {
+ requests = { cpu = "500m", memory = "2Gi" }
+ limits = { cpu = "2", memory = "2Gi" }
+ }
+ serviceAccount = {
+ annotations = {
+ "eks.amazonaws.com/role-arn" = module.provisioner-oidc-role.role_arn
+ }
+ name = local.release_name
+ }
+ service = {
+ enable = false
+ }
+ tls = {
+ secretNames = [local.ssl_vol_friendly_name]
+ }
+ tolerations = [{
+ key = "platform"
+ value = "coder-server"
+ effect = "NoSchedule"
+ }]
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [for az in local.azs : "${local.region}${az}"]
+ },
+ {
+ key = "node.coder.io/used-for"
+ operator = "In"
+ values = ["coder-server"]
+ }
+ ]
+ }]
+ }
+ }
+ podAntiAffinity = {
+ preferredDuringSchedulingIgnoredDuringExecution = []
+ requiredDuringSchedulingIgnoredDuringExecution = []
+ }
+ }
+ }
+ }
+ })
+ }
+ }
+
+ sync_policy {
+ # automated {
+ # prune = true
+ # self_heal = true
+ # }
+ # sync_options = ["CreateNamespace=true"]
+ # retry {
+ # limit = "5"
+ # backoff {
+ # duration = "30s"
+ # max_duration = "2m"
+ # factor = "2"
+ # }
+ # }
+ }
+ }
+}
diff --git a/infra/aws/us-east-2/k8s/argo/coder-server/variables.tf b/infra/aws/us-east-2/k8s/argo/coder-server/variables.tf
new file mode 100644
index 0000000..41ca65d
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-server/variables.tf
@@ -0,0 +1,147 @@
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "azs" {
+ type = list(string)
+ default = ["a", "c"]
+}
+
+variable "vpc_name" {
+ type = string
+}
+
+variable "cluster_name" {
+ type = string
+}
+
+variable "addon_version" {
+ type = string
+ default = "2.25.1"
+}
+
+variable "coder_access_url" {
+ type = string
+}
+
+variable "coder_wildcard_access_url" {
+ type = string
+}
+
+variable "coder_experiments" {
+ type = list(string)
+ default = []
+}
+
+variable "coder_github_allowed_orgs" {
+ type = list(string)
+ default = []
+}
+
+variable "coder_builtin_provisioner_count" {
+ type = number
+ default = 0
+}
+
+variable "coder_github_external_auth_secret_client_secret" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_github_external_auth_secret_client_id" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_oauth_secret_client_secret" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_oauth_secret_client_id" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_oidc_secret_client_secret" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_oidc_secret_client_id" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_oidc_secret_issuer_url" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_db_rds_name" {
+ type = string
+}
+
+variable "coder_db_username" {
+ type = string
+}
+
+variable "coder_db_password" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_db_name" {
+ type = string
+}
+
+variable "image_repo" {
+ type = string
+ sensitive = true
+}
+
+variable "image_tag" {
+ type = string
+ default = "latest"
+}
+
+variable "oidc_sign_in_text" {
+ type = string
+}
+
+variable "oidc_icon_url" {
+ type = string
+}
+
+variable "oidc_scopes" {
+ type = list(string)
+}
+
+variable "oidc_email_domain" {
+ type = string
+}
+
+variable "anthropic_llm_endpoint" {
+ type = string
+ sensitive = true
+}
+
+variable "anthropic_llm_key" {
+ type = string
+ sensitive = true
+}
+
+variable "openai_llm_endpoint" {
+ type = string
+ sensitive = true
+}
+
+variable "openai_llm_key" {
+ type = string
+ sensitive = true
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/Chart.lock b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/Chart.lock
new file mode 100644
index 0000000..9b6d067
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/Chart.lock
@@ -0,0 +1,6 @@
+dependencies:
+- name: libcoder
+ repository: file://charts/libcoder
+ version: 0.1.0
+digest: sha256:8788012975e83c0a94502ae0cca1159feb805460431b6dcbbdbd57c4a2d29e1b
+generated: "2026-06-02T06:34:10.952532379Z"
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/Chart.yaml b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/Chart.yaml
new file mode 100644
index 0000000..536bd18
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/Chart.yaml
@@ -0,0 +1,23 @@
+apiVersion: v2
+appVersion: 2.34.5
+dependencies:
+- name: libcoder
+ repository: file://charts/libcoder
+ version: 0.1.0
+description: External provisioner daemon for Coder. This is an Enterprise feature;
+ contact sales@coder.com.
+home: https://github.com/coder/coder
+icon: https://helm.coder.com/coder_logo_black.png
+keywords:
+- coder
+- terraform
+kubeVersion: '>= 1.19.0-0'
+maintainers:
+- email: support@coder.com
+ name: Coder Technologies, Inc.
+ url: https://coder.com/contact
+name: coder-provisioner
+sources:
+- https://github.com/coder/coder/tree/main/helm/provisioner
+type: application
+version: 2.34.5
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/README.md b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/README.md
new file mode 100644
index 0000000..a5d6ffd
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/README.md
@@ -0,0 +1,149 @@
+# Coder Helm Chart
+
+This directory contains the Helm chart used to deploy Coder provisioner daemons onto a Kubernetes
+cluster.
+
+External provisioner daemons are a Premium feature. Contact sales@coder.com.
+
+## Getting Started
+
+> **Warning**: The main branch in this repository does not represent the
+> latest release of Coder. Please reference our installation docs for
+> instructions on a tagged release.
+
+View
+[our docs](https://coder.com/docs/admin/provisioners)
+for detailed installation instructions.
+
+## Values
+
+Please refer to [values.yaml](values.yaml) for available Helm values and their
+defaults.
+
+A good starting point for your values file is:
+
+```yaml
+coder:
+ env:
+ - name: CODER_URL
+ value: "https://coder.example.com"
+ # This env enables the Prometheus metrics endpoint.
+ - name: CODER_PROMETHEUS_ADDRESS
+ value: "0.0.0.0:2112"
+ replicaCount: 10
+provisionerDaemon:
+ keySecretName: "coder-provisionerd-key"
+ keySecretKey: "provisionerd-key"
+```
+
+## Specific Examples
+
+Below are some common specific use-cases when deploying a Coder provisioner.
+
+### Set Labels and Annotations
+
+If you need to set deployment- or pod-level labels and annotations, set `coder.{annotations,labels}` or `coder.{podAnnotations,podLabels}`.
+
+Example:
+
+```yaml
+coder:
+ # ...
+ annotations:
+ com.coder/annotation/foo: bar
+ com.coder/annotation/baz: qux
+ labels:
+ com.coder/label/foo: bar
+ com.coder/label/baz: qux
+ podAnnotations:
+ com.coder/podAnnotation/foo: bar
+ com.coder/podAnnotation/baz: qux
+ podLabels:
+ com.coder/podLabel/foo: bar
+ com.coder/podLabel/baz: qux
+```
+
+### Additional Templates
+
+You can include extra Kubernetes manifests in `extraTemplates`.
+
+The below example will also create a `ConfigMap` along with the Helm release:
+
+```yaml
+coder:
+ # ...
+provisionerDaemon:
+ # ...
+extraTemplates:
+ - |
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: some-config
+ namespace: {{ .Release.Namespace }}
+ data:
+ key: some-value
+```
+
+### Disable Service Account Creation
+
+### Deploying multiple provisioners in the same namespace
+
+To deploy multiple provisioners in the same namespace, set the following values explicitly to avoid conflicts:
+
+- `nameOverride`: controls the name of the provisioner deployment
+- `serviceAccount.name`: controls the name of the service account.
+
+Note that `nameOverride` does not apply to `extraTemplates`, as illustrated below:
+
+```yaml
+coder:
+ # ...
+ serviceAccount:
+ name: other-coder-provisioner
+provisionerDaemon:
+ # ...
+nameOverride: "other-coder-provisioner"
+extraTemplates:
+ - |
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+ name: some-other-config
+ namespace: {{ .Release.Namespace }}
+ data:
+ key: some-other-value
+```
+
+If you wish to deploy a second provisioner that references an existing service account, you can do so as follows:
+
+- Set `coder.serviceAccount.disableCreate=true` to disable service account creation,
+- Set `coder.serviceAccount.workspacePerms=false` to disable creation of a role and role binding,
+- Set `coder.serviceAccount.nameOverride` to the name of an existing service account.
+
+See below for a concrete example:
+
+```yaml
+coder:
+ # ...
+ serviceAccount:
+ name: preexisting-service-account
+ disableCreate: true
+ workspacePerms: false
+provisionerDaemon:
+ # ...
+nameOverride: "other-coder-provisioner"
+```
+
+## Testing
+
+The test suite for this chart lives in `./tests/chart_test.go`.
+
+Each test case runs `helm template` against the corresponding `test_case.yaml`, and compares the output with that of the corresponding `test_case.golden` in `./tests/testdata`.
+If `expectedError` is not empty for that specific test case, no corresponding `.golden` file is required.
+
+To add a new test case:
+
+- Create an appropriately named `.yaml` file in `testdata/` along with a corresponding `.golden` file, if required.
+- Add the test case to the array in `chart_test.go`, setting `name` to the name of the files you added previously (without the extension). If appropriate, set `expectedError`.
+- Run the tests and ensure that no regressions in existing test cases occur: `go test ./tests`.
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/Chart.yaml b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/Chart.yaml
new file mode 100644
index 0000000..9b692ad
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/Chart.yaml
@@ -0,0 +1,11 @@
+apiVersion: v2
+appVersion: 0.1.0
+description: Coder library chart
+home: https://github.com/coder/coder
+maintainers:
+- email: support@coder.com
+ name: Coder Technologies, Inc.
+ url: https://coder.com/contact
+name: libcoder
+type: library
+version: 0.1.0
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/templates/_coder.yaml b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/templates/_coder.yaml
new file mode 100644
index 0000000..71d38f6
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/templates/_coder.yaml
@@ -0,0 +1,201 @@
+{{- define "libcoder.deployment.tpl" -}}
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ include "coder.name" .}}
+ namespace: {{ .Release.Namespace }}
+ labels:
+ {{- include "coder.labels" . | nindent 4 }}
+ {{- with .Values.coder.labels }}
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+ annotations: {{ toYaml .Values.coder.annotations | nindent 4}}
+spec:
+ replicas: {{ .Values.coder.replicaCount }}
+ selector:
+ matchLabels:
+ {{- include "coder.selectorLabels" . | nindent 6 }}
+ template:
+ metadata:
+ labels:
+ {{- include "coder.labels" . | nindent 8 }}
+ {{- with .Values.coder.podLabels }}
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ annotations:
+ {{- include "coder.componentAnnotation" . | nindent 8 }}
+ {{- with .Values.coder.podAnnotations }}
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+
+ spec:
+ serviceAccountName: {{ .Values.coder.serviceAccount.name | quote }}
+ {{- with .Values.coder.priorityClassName }}
+ priorityClassName: {{ . | quote }}
+ {{- end }}
+ {{- with .Values.coder.podSecurityContext }}
+ securityContext:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ restartPolicy: Always
+ {{- with .Values.coder.image.pullSecrets }}
+ imagePullSecrets:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ terminationGracePeriodSeconds: 60
+ {{- with .Values.coder.affinity }}
+ affinity:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.coder.tolerations }}
+ tolerations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.coder.nodeSelector }}
+ nodeSelector:
+ {{ toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.coder.topologySpreadConstraints }}
+ topologySpreadConstraints:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.coder.hostAliases }}
+ hostAliases:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.coder.initContainers }}
+ initContainers:
+ {{ toYaml . | nindent 8 }}
+ {{- end }}
+ containers: []
+ {{- include "coder.volumes" . | nindent 6 }}
+{{- end -}}
+{{- define "libcoder.deployment" -}}
+{{- include "libcoder.util.merge" (append . "libcoder.deployment.tpl") -}}
+{{- end -}}
+
+{{- define "libcoder.statefulset.tpl" -}}
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: {{ include "coder.name" .}}
+ namespace: {{ .Release.Namespace }}
+ labels:
+ {{- include "coder.labels" . | nindent 4 }}
+ {{- with .Values.coder.labels }}
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+ annotations: {{ toYaml .Values.coder.annotations | nindent 4}}
+spec:
+ serviceName: {{ include "coder.name" . }}
+ replicas: {{ .Values.coder.replicaCount }}
+ selector:
+ matchLabels:
+ {{- include "coder.selectorLabels" . | nindent 6 }}
+ updateStrategy:
+ type: RollingUpdate
+ template:
+ metadata:
+ labels:
+ {{- include "coder.labels" . | nindent 8 }}
+ {{- with .Values.coder.podLabels }}
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ annotations:
+ {{- include "coder.componentAnnotation" . | nindent 8 }}
+ {{- with .Values.coder.podAnnotations }}
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+
+ spec:
+ serviceAccountName: {{ .Values.coder.serviceAccount.name | quote }}
+ {{- with .Values.coder.priorityClassName }}
+ priorityClassName: {{ . | quote }}
+ {{- end }}
+ {{- with .Values.coder.podSecurityContext }}
+ securityContext:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ restartPolicy: Always
+ {{- with .Values.coder.image.pullSecrets }}
+ imagePullSecrets:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ terminationGracePeriodSeconds: 60
+ {{- with .Values.coder.affinity }}
+ affinity:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.coder.tolerations }}
+ tolerations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.coder.nodeSelector }}
+ nodeSelector:
+ {{ toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.coder.topologySpreadConstraints }}
+ topologySpreadConstraints:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.coder.hostAliases }}
+ hostAliases:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.coder.initContainers }}
+ initContainers:
+ {{ toYaml . | nindent 8 }}
+ {{- end }}
+ containers: []
+ {{- include "coder.volumes" . | nindent 6 }}
+ {{- with .Values.coder.volumeClaimTemplates }}
+ volumeClaimTemplates:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+{{- end -}}
+{{- define "libcoder.statefulset" -}}
+{{- include "libcoder.util.merge" (append . "libcoder.statefulset.tpl") -}}
+{{- end -}}
+
+{{- define "libcoder.containerspec.tpl" -}}
+name: coder
+image: {{ include "coder.image" . | quote }}
+imagePullPolicy: {{ .Values.coder.image.pullPolicy }}
+command:
+ {{- toYaml .Values.coder.command | nindent 2 }}
+resources:
+ {{- if and (hasKey .Values.coder "resources") (not (empty .Values.coder.resources)) }}
+ {{- toYaml .Values.coder.resources | nindent 2 }}
+ {{- else }}
+ limits:
+ cpu: 2000m
+ memory: 4096Mi
+ requests:
+ cpu: 2000m
+ memory: 4096Mi
+ {{- end }}
+lifecycle:
+ {{- toYaml .Values.coder.lifecycle | nindent 2 }}
+securityContext: {{ toYaml .Values.coder.securityContext | nindent 2 }}
+{{ include "coder.volumeMounts" . }}
+{{- end -}}
+{{- define "libcoder.containerspec" -}}
+{{- include "libcoder.util.merge" (append . "libcoder.containerspec.tpl") -}}
+{{- end -}}
+
+{{- define "libcoder.serviceaccount.tpl" -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{ .Values.coder.serviceAccount.name | quote }}
+ namespace: {{ .Release.Namespace }}
+ annotations: {{ toYaml .Values.coder.serviceAccount.annotations | nindent 4 }}
+ labels:
+ {{- include "coder.labels" . | nindent 4 }}
+ {{- with .Values.coder.serviceAccount.labels }}
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+{{- end -}}
+{{- define "libcoder.serviceaccount" -}}
+{{- include "libcoder.util.merge" (append . "libcoder.serviceaccount.tpl") -}}
+{{- end -}}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/templates/_helpers.tpl b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/templates/_helpers.tpl
new file mode 100644
index 0000000..4999ec4
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/templates/_helpers.tpl
@@ -0,0 +1,248 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "coder.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "coder.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Selector labels
+
+!!!!! DO NOT ADD ANY MORE SELECTORS. IT IS A BREAKING CHANGE !!!!!
+*/}}
+{{- define "coder.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "coder.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "coder.labels" -}}
+helm.sh/chart: {{ include "coder.chart" . }}
+{{ include "coder.selectorLabels" . }}
+app.kubernetes.io/part-of: {{ include "coder.name" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Coder Docker image URI
+*/}}
+{{- define "coder.image" -}}
+{{- if and (eq .Values.coder.image.tag "") (eq .Chart.AppVersion "0.1.0") -}}
+{{ fail "You must specify the coder.image.tag value if you're installing the Helm chart directly from Git." }}
+{{- end -}}
+{{ .Values.coder.image.repo }}:{{ .Values.coder.image.tag | default (printf "v%v" .Chart.AppVersion) }}
+{{- end }}
+
+{{/*
+Coder TLS enabled.
+*/}}
+{{- define "coder.tlsEnabled" -}}
+ {{- if hasKey .Values.coder "tls" -}}
+ {{- if .Values.coder.tls.secretNames -}}
+ true
+ {{- else -}}
+ false
+ {{- end -}}
+ {{- else -}}
+ false
+ {{- end -}}
+{{- end }}
+
+{{/*
+Coder TLS environment variables.
+*/}}
+{{- define "coder.tlsEnv" }}
+{{- if eq (include "coder.tlsEnabled" .) "true" }}
+- name: CODER_TLS_ENABLE
+ value: "true"
+- name: CODER_TLS_ADDRESS
+ value: "0.0.0.0:8443"
+- name: CODER_TLS_CERT_FILE
+ value: "{{ range $idx, $secretName := .Values.coder.tls.secretNames -}}{{ if $idx }},{{ end }}/etc/ssl/certs/coder/{{ $secretName }}/tls.crt{{- end }}"
+- name: CODER_TLS_KEY_FILE
+ value: "{{ range $idx, $secretName := .Values.coder.tls.secretNames -}}{{ if $idx }},{{ end }}/etc/ssl/certs/coder/{{ $secretName }}/tls.key{{- end }}"
+{{- end }}
+{{- end }}
+
+{{/*
+Coder default access URL
+*/}}
+{{- define "coder.defaultAccessURL" }}
+{{- if eq (include "coder.tlsEnabled" .) "true" -}}
+https
+{{- else -}}
+http
+{{- end -}}
+://coder.{{ .Release.Namespace }}.svc.cluster.local
+{{- end }}
+
+{{/*
+Coder volume definitions.
+*/}}
+{{- define "coder.volumeList" }}
+{{- if hasKey .Values.coder "tls" -}}
+{{- range $secretName := .Values.coder.tls.secretNames }}
+- name: "tls-{{ $secretName }}"
+ secret:
+ secretName: {{ $secretName | quote }}
+{{ end -}}
+{{- end }}
+{{ range $secret := .Values.coder.certs.secrets -}}
+- name: "ca-cert-{{ $secret.name }}"
+ secret:
+ secretName: {{ $secret.name | quote }}
+{{ end -}}
+{{ if gt (len .Values.coder.volumes) 0 -}}
+{{ toYaml .Values.coder.volumes }}
+{{ end -}}
+{{- end }}
+
+{{/*
+Coder volumes yaml.
+*/}}
+{{- define "coder.volumes" }}
+{{- if trim (include "coder.volumeList" .) -}}
+volumes:
+{{- include "coder.volumeList" . -}}
+{{- else -}}
+volumes: []
+{{- end -}}
+{{- end }}
+
+{{/*
+Coder volume mounts.
+*/}}
+{{- define "coder.volumeMountList" }}
+{{- if hasKey .Values.coder "tls" }}
+{{ range $secretName := .Values.coder.tls.secretNames -}}
+- name: "tls-{{ $secretName }}"
+ mountPath: "/etc/ssl/certs/coder/{{ $secretName }}"
+ readOnly: true
+{{ end -}}
+{{- end }}
+{{ range $secret := .Values.coder.certs.secrets -}}
+- name: "ca-cert-{{ $secret.name }}"
+ mountPath: "/etc/ssl/certs/{{ $secret.name }}.crt"
+ subPath: {{ $secret.key | quote }}
+ readOnly: true
+{{ end -}}
+{{ if gt (len .Values.coder.volumeMounts) 0 -}}
+{{ toYaml .Values.coder.volumeMounts }}
+{{ end -}}
+{{- end }}
+
+{{/*
+Coder volume mounts yaml.
+*/}}
+{{- define "coder.volumeMounts" }}
+{{- if trim (include "coder.volumeMountList" .) -}}
+volumeMounts:
+{{- include "coder.volumeMountList" . -}}
+{{- else -}}
+volumeMounts: []
+{{- end -}}
+{{- end }}
+
+{{/*
+Coder ingress wildcard hostname with the wildcard suffix stripped.
+*/}}
+{{- define "coder.ingressWildcardHost" -}}
+{{/* This regex replace is required as the original input including the suffix
+ * is not a legal ingress host. We need to remove the suffix and keep the
+ * wildcard '*'.
+ *
+ * - '\\*' Starts with '*'
+ * - '[^.]*' Suffix is 0 or more characters, '-suffix'
+ * - '(' Start domain capture group
+ * - '\\.' The domain should be separated with a '.' from the subdomain
+ * - '.*' Rest of the domain.
+ * - ')' $1 is the ''.example.com'
+ */}}
+{{- regexReplaceAll "\\*[^.]*(\\..*)" .Values.coder.ingress.wildcardHost "*${1}" -}}
+{{- end }}
+
+{{/*
+Fail on fully deprecated values or deprecated value combinations. This is
+included at the top of coder.yaml.
+*/}}
+{{- define "coder.verifyDeprecated" }}
+{{/*
+Deprecated value coder.tls.secretName must not be used.
+*/}}
+{{- if .Values.coder.tls.secretName }}
+{{ fail "coder.tls.secretName is deprecated, use coder.tls.secretNames instead." }}
+{{- end }}
+{{- end }}
+
+{{/*
+Renders a value that contains a template.
+Usage:
+{{ include "coder.renderTemplate" ( dict "value" .Values.path.to.the.Value "context" $) }}
+*/}}
+{{- define "coder.renderTemplate" -}}
+ {{- if typeIs "string" .value }}
+ {{- tpl .value .context }}
+ {{- else }}
+ {{- tpl (.value | toYaml) .context }}
+ {{- end }}
+{{- end -}}
+
+{{- define "libcoder.rbac.rules.basic" -}}
+- apiGroups: [""]
+ resources: ["pods"]
+ verbs:
+ - create
+ - delete
+ - deletecollection
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups: [""]
+ resources: ["persistentvolumeclaims"]
+ verbs:
+ - create
+ - delete
+ - deletecollection
+ - get
+ - list
+ - patch
+ - update
+ - watch
+{{- end }}
+
+{{- define "libcoder.rbac.rules.deployments" -}}
+- apiGroups:
+ - apps
+ resources:
+ - deployments
+ verbs:
+ - create
+ - delete
+ - deletecollection
+ - get
+ - list
+ - patch
+ - update
+ - watch
+{{- end }}
+
+{{/*
+Component annotation for pod metadata.
+This should be overridden in each chart to specify the component type.
+*/}}
+{{- define "coder.componentAnnotation" -}}
+{{- end }}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/templates/_rbac.yaml b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/templates/_rbac.yaml
new file mode 100644
index 0000000..e8e0a98
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/templates/_rbac.yaml
@@ -0,0 +1,91 @@
+{{- define "libcoder.rbac.forNamespace" -}}
+ {{- $nsPerms := ternary .workspacePerms .Top.Values.coder.serviceAccount.workspacePerms (hasKey . "workspacePerms") -}}
+ {{- $nsDeployRaw := ternary .enableDeployments .Top.Values.coder.serviceAccount.enableDeployments (hasKey . "enableDeployments") -}}
+ {{- $nsExtraRaw := ternary .extraRules .Top.Values.coder.serviceAccount.extraRules (hasKey . "extraRules") -}}
+ {{- $nsDeploy := and $nsPerms $nsDeployRaw -}}
+ {{- $nsExtra := ternary $nsExtraRaw (list) $nsPerms -}}
+
+ {{- if or $nsPerms (or $nsDeploy $nsExtra) }}
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: {{ .Top.Values.coder.serviceAccount.name }}-workspace-perms
+ namespace: {{ .NS }}
+rules:
+{{- if $nsPerms }}
+{{ include "libcoder.rbac.rules.basic" .Top | trimPrefix "\n" | indent 2 }}
+{{- end }}
+{{- if $nsDeploy }}
+{{ include "libcoder.rbac.rules.deployments" .Top | trimPrefix "\n" | indent 2 }}
+{{- end }}
+{{- if $nsExtra }}
+ {{- if kindIs "slice" $nsExtra }}
+{{ toYaml $nsExtra | trimPrefix "\n" | indent 2 }}
+ {{- else }}
+{{ toYaml (list $nsExtra) | trimPrefix "\n" | indent 2 }}
+ {{- end }}
+{{- end }}
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: {{ .Top.Values.coder.serviceAccount.name | quote }}
+ namespace: {{ .NS }}
+subjects:
+ - kind: ServiceAccount
+ name: {{ .Top.Values.coder.serviceAccount.name | quote }}
+ {{- if ne .NS .Top.Release.Namespace }}
+ namespace: {{ .Top.Release.Namespace }}
+ {{- end }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: {{ .Top.Values.coder.serviceAccount.name }}-workspace-perms
+ {{- end }}
+{{- end -}}
+
+{{- define "libcoder.rbac.core" -}}
+ {{- $top := . -}}
+ {{- $rootPerms := $top.Values.coder.serviceAccount.workspacePerms | default false -}}
+ {{- $rootDeploy := $top.Values.coder.serviceAccount.enableDeployments | default false -}}
+ {{- $rootExtra := $top.Values.coder.serviceAccount.extraRules | default (list) -}}
+
+ {{- $rootParams := dict
+ "Top" $top
+ "NS" $top.Release.Namespace
+ "workspacePerms" $rootPerms
+ "enableDeployments" $rootDeploy
+ "extraRules" $rootExtra -}}
+ {{ include "libcoder.rbac.forNamespace" $rootParams }}
+
+ {{- $wsnsRaw := get $top.Values.coder.serviceAccount "workspaceNamespaces" -}}
+ {{- $extra := default (list) $wsnsRaw -}}
+
+ {{- range $_, $ns := $extra }}
+ {{- $nsName := ternary $ns.name $ns (kindIs "map" $ns) -}}
+ {{- if $nsName }}
+ {{- $params := dict "Top" $top "NS" $nsName -}}
+ {{- if kindIs "map" $ns }}
+ {{- if hasKey $ns "workspacePerms" }}{{- $_ := set $params "workspacePerms" $ns.workspacePerms }}{{- else }}{{- $_ := set $params "workspacePerms" $rootPerms }}{{- end }}
+ {{- if hasKey $ns "enableDeployments" }}{{- $_ := set $params "enableDeployments" $ns.enableDeployments }}{{- else }}{{- $_ := set $params "enableDeployments" $rootDeploy }}{{- end }}
+ {{- if hasKey $ns "extraRules" }}{{- $_ := set $params "extraRules" $ns.extraRules }}{{- else }}{{- $_ := set $params "extraRules" $rootExtra }}{{- end }}
+ {{- else }}
+ {{- $_ := set $params "workspacePerms" $rootPerms -}}
+ {{- $_ := set $params "enableDeployments" $rootDeploy -}}
+ {{- $_ := set $params "extraRules" $rootExtra -}}
+ {{- end }}
+ {{ include "libcoder.rbac.forNamespace" $params }}
+ {{- end }}
+ {{- end }}
+{{- end -}}
+
+{{- define "libcoder.rbac.tpl" -}}
+ {{- if not .Values.coder.serviceAccount.disableCreate -}}
+ {{ include "libcoder.rbac.core" . }}
+ {{- end }}
+{{- end -}}
+
+{{- define "libcoder.namespace.rbac.tpl" -}}
+ {{ include "libcoder.rbac.core" . }}
+{{- end -}}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/templates/_util.yaml b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/templates/_util.yaml
new file mode 100644
index 0000000..d1b2742
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/charts/libcoder/templates/_util.yaml
@@ -0,0 +1,13 @@
+{{- /*
+ libcoder.util.merge will merge two YAML templates and output the result.
+ This takes an array of three values:
+ - the top context
+ - the template name of the overrides (destination)
+ - the template name of the base (source)
+*/}}
+{{- define "libcoder.util.merge" -}}
+{{- $top := first . -}}
+{{- $overrides := fromYaml (include (index . 1) $top) | default (dict ) -}}
+{{- $tpl := fromYaml (include (index . 2) $top) | default (dict ) -}}
+{{- toYaml (merge $overrides $tpl) -}}
+{{- end -}}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/NOTES.txt b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/NOTES.txt
new file mode 100644
index 0000000..fab4573
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/NOTES.txt
@@ -0,0 +1,12 @@
+{{/*
+Deprecation notices:
+*/}}
+
+{{- if .Values.provisionerDaemon.pskSecretName }}
+* Provisioner Daemon PSKs are no longer recommended for use with external
+ provisioners. Consider migrating to scoped provisioner keys instead. For more
+ information, see: https://coder.com/docs/admin/provisioners#authentication
+{{- end }}
+
+Enjoy Coder! Please create an issue at https://github.com/coder/coder if you run
+into any problems! :)
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/_coder.tpl b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/_coder.tpl
new file mode 100644
index 0000000..d0ba44f
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/_coder.tpl
@@ -0,0 +1,108 @@
+{{/*
+Service account to merge into the libcoder template
+*/}}
+{{- define "coder.serviceaccount" -}}
+{{- end }}
+
+{{/*
+Component annotation for pod metadata.
+*/}}
+{{- define "coder.componentAnnotation" -}}
+app.kubernetes.io/component: provisionerd
+{{- end }}
+
+{{/*
+StatefulSet to merge into the libcoder template
+*/}}
+{{- define "coder.statefulset" -}}
+spec:
+ template:
+ spec:
+ terminationGracePeriodSeconds: {{ .Values.provisionerDaemon.terminationGracePeriodSeconds }}
+ containers:
+ -
+{{ include "libcoder.containerspec" (list . "coder.containerspec") | indent 8}}
+
+{{- end }}
+
+{{/*
+ContainerSpec for the Coder container of the Coder provisioner daemon
+*/}}
+{{- define "coder.containerspec" -}}
+args:
+{{- if .Values.coder.commandArgs }}
+ {{- toYaml .Values.coder.commandArgs | nindent 12 }}
+{{- else }}
+- provisionerd
+- start
+{{- end }}
+env:
+- name: CODER_PROMETHEUS_ADDRESS
+ value: "0.0.0.0:2112"
+{{- if and (empty .Values.provisionerDaemon.pskSecretName) (empty .Values.provisionerDaemon.keySecretName) }}
+{{ fail "Either provisionerDaemon.pskSecretName or provisionerDaemon.keySecretName must be specified." }}
+{{- else if and .Values.provisionerDaemon.keySecretName .Values.provisionerDaemon.keySecretKey }}
+ {{- if and (not (empty .Values.provisionerDaemon.pskSecretName)) (ne .Values.provisionerDaemon.pskSecretName "coder-provisioner-psk") }}
+ {{ fail "Either provisionerDaemon.pskSecretName or provisionerDaemon.keySecretName must be specified, but not both." }}
+ {{- else if .Values.provisionerDaemon.tags }}
+ {{ fail "provisionerDaemon.tags may not be specified with provisionerDaemon.keySecretName." }}
+ {{- end }}
+- name: CODER_PROVISIONER_DAEMON_KEY
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.provisionerDaemon.keySecretName | quote }}
+ key: {{ .Values.provisionerDaemon.keySecretKey | quote }}
+{{- else }}
+- name: CODER_PROVISIONER_DAEMON_PSK
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.provisionerDaemon.pskSecretName | quote }}
+ key: psk
+{{- end }}
+{{- if include "provisioner.tags" . }}
+- name: CODER_PROVISIONERD_TAGS
+ value: {{ include "provisioner.tags" . }}
+{{- end }}
+ # Set the default access URL so a `helm apply` works by default.
+ # See: https://github.com/coder/coder/issues/5024
+{{- $hasAccessURL := false }}
+{{- range .Values.coder.env }}
+{{- if eq .name "CODER_URL" }}
+{{- $hasAccessURL = true }}
+{{- end }}
+{{- end }}
+{{- if not $hasAccessURL }}
+- name: CODER_URL
+ value: {{ include "coder.defaultAccessURL" . | quote }}
+{{- end }}
+{{- with .Values.coder.env }}
+{{ toYaml . }}
+{{- end }}
+ports:
+ {{- range .Values.coder.env }}
+ {{- if eq .name "CODER_PROMETHEUS_ENABLE" }}
+ {{/*
+ This sadly has to be nested to avoid evaluating the second part
+ of the condition too early and potentially getting type errors if
+ the value is not a string (like a `valueFrom`). We do not support
+ `valueFrom` for this env var specifically.
+ */}}
+ {{- if eq .value "true" }}
+- name: "prometheus-http"
+ containerPort: 2112
+ protocol: TCP
+ {{- end }}
+ {{- end }}
+ {{- end }}
+{{- end }}
+
+{{/*
+Convert provisioner tags to the environment variable format
+*/}}
+{{- define "provisioner.tags" -}}
+ {{- $keys := keys .Values.provisionerDaemon.tags | sortAlpha -}}
+ {{- range $i, $key := $keys -}}
+ {{- $val := get $.Values.provisionerDaemon.tags $key -}}
+ {{- if ne $i 0 -}},{{- end -}}{{ $key }}={{ $val }}
+ {{- end -}}
+{{- end -}}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/coder.yaml b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/coder.yaml
new file mode 100644
index 0000000..796950e
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/coder.yaml
@@ -0,0 +1,20 @@
+---
+{{- if not .Values.coder.serviceAccount.disableCreate }}
+{{ include "libcoder.serviceaccount" (list . "coder.serviceaccount") }}
+{{- end }}
+
+---
+{{ include "libcoder.statefulset" (list . "coder.statefulset") }}
+
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ include "coder.name" . }}
+ namespace: {{ .Release.Namespace }}
+ labels:
+ {{- include "coder.labels" . | nindent 4 }}
+spec:
+ clusterIP: None
+ selector:
+ {{- include "coder.selectorLabels" . | nindent 4 }}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/extra-templates.yaml b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/extra-templates.yaml
new file mode 100644
index 0000000..3d31333
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/extra-templates.yaml
@@ -0,0 +1,4 @@
+{{- range .Values.extraTemplates }}
+---
+{{ include "coder.renderTemplate" (dict "value" . "context" $) }}
+{{- end }}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/rbac.yaml b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/rbac.yaml
new file mode 100644
index 0000000..1ddef21
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/templates/rbac.yaml
@@ -0,0 +1 @@
+{{ include "libcoder.rbac.tpl" . }}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/values.yaml b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/values.yaml
new file mode 100644
index 0000000..042e5c9
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/charts/coder-provisioner/values.yaml
@@ -0,0 +1,260 @@
+# coder -- Common configuration options.
+coder:
+ # coder.env -- The environment variables to set. These can be used to
+ # configure all aspects of Coder provisioner daemon. Please see
+ # `coder provisionerd start --help for information about what environment
+ # variables can be set.
+ # Note: The following environment variables are set by default and cannot be
+ # overridden:
+ # - CODER_PROMETHEUS_ADDRESS: set to 0.0.0.0:2112 and cannot be changed.
+ # Prometheus must still be enabled by setting CODER_PROMETHEUS_ENABLE.
+ #
+ # We will additionally set CODER_URL, if unset, to the cluster service
+ # URL.
+ env: []
+ # - name: "CODER_URL"
+ # value: "https://coder.example.com"
+
+ # coder.image -- The image to use for Coder provisioner daemon.
+ image:
+ # coder.image.repo -- The repository of the image.
+ repo: "ghcr.io/coder/coder"
+ # coder.image.tag -- The tag of the image, defaults to {{.Chart.AppVersion}}
+ # if not set. If you're using the chart directly from git, the default
+ # app version will not work and you'll need to set this value. The helm
+ # chart helpfully fails quickly in this case.
+ tag: ""
+ # coder.image.pullPolicy -- The pull policy to use for the image. See:
+ # https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy
+ pullPolicy: IfNotPresent
+ # coder.image.pullSecrets -- The secrets used for pulling the Coder image from
+ # a private registry.
+ pullSecrets: []
+ # - name: "pull-secret"
+
+ # coder.initContainers -- Init containers for the deployment. See:
+ # https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
+ initContainers:
+ []
+ # - name: init-container
+ # image: busybox:1.28
+ # command: ['sh', '-c', "sleep 2"]
+
+ # coder.annotations -- The StatefulSet annotations. See:
+ # https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
+ annotations: {}
+
+ # coder.labels -- The StatefulSet labels. See:
+ # https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
+ labels: {}
+
+ # coder.podAnnotations -- The Coder pod annotations. See:
+ # https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
+ # The annotation `app.kubernetes.io/component` is automatically added to identify
+ # the component type (coderd, wsproxy, or provisionerd).
+ podAnnotations: {}
+
+ # coder.podLabels -- The Coder pod labels. See:
+ # https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
+ podLabels: {}
+
+ # coder.serviceAccount -- Configuration for the automatically created service
+ # account. Creation of the service account cannot be disabled.
+ serviceAccount:
+ # coder.serviceAccount.workspacePerms -- Whether or not to grant the
+ # service account permissions to manage workspaces. This includes
+ # permission to manage pods and persistent volume claims in the deployment
+ # namespace.
+ #
+ # It is recommended to keep this on if you are using Kubernetes templates
+ # within Coder.
+ workspacePerms: true
+ # coder.serviceAccount.enableDeployments -- Provides the service account permission
+ # to manage Kubernetes deployments.
+ enableDeployments: true
+ # coder.serviceAccount.annotations -- The Coder service account annotations.
+ annotations: {}
+ # coder.serviceAccount.name -- The service account name
+ name: coder-provisioner
+ # coder.serviceAccount.disableCreate -- Whether to create the service account or use existing service account.
+ disableCreate: false
+
+ # coder.securityContext -- Fields related to the container's security
+ # context (as opposed to the pod). Some fields are also present in the pod
+ # security context, in which case these values will take precedence.
+ securityContext:
+ # coder.securityContext.runAsNonRoot -- Requires that the coder container
+ # runs as an unprivileged user. If setting runAsUser to 0 (root), this
+ # will need to be set to false.
+ runAsNonRoot: true
+ # coder.securityContext.runAsUser -- Sets the user id of the container.
+ # For security reasons, we recommend using a non-root user.
+ runAsUser: 1000
+ # coder.securityContext.runAsGroup -- Sets the group id of the container.
+ # For security reasons, we recommend using a non-root group.
+ runAsGroup: 1000
+ # coder.securityContext.readOnlyRootFilesystem -- Mounts the container's
+ # root filesystem as read-only.
+ readOnlyRootFilesystem: null
+ # coder.securityContext.seccompProfile -- Sets the seccomp profile for
+ # the coder container.
+ seccompProfile:
+ type: RuntimeDefault
+ # coder.securityContext.allowPrivilegeEscalation -- Controls whether
+ # the container can gain additional privileges, such as escalating to
+ # root. It is recommended to leave this setting disabled in production.
+ allowPrivilegeEscalation: false
+
+ # coder.volumes -- A list of extra volumes to add to the Coder provisioner daemon pod.
+ volumes: []
+ # - name: "my-volume"
+ # emptyDir: {}
+
+ # coder.volumeMounts -- A list of extra volume mounts to add to the Coder provisioner daemon pod.
+ volumeMounts: []
+ # - name: "my-volume"
+ # mountPath: "/mnt/my-volume"
+
+ # coder.replicaCount -- The number of StatefulSet replicas. This
+ # should only be increased if High Availability is enabled.
+ #
+ # This is an Enterprise feature. Contact sales@coder.com.
+ replicaCount: 1
+
+ # coder.volumeClaimTemplates -- Volume claim templates for the StatefulSet.
+ # Each provisioner pod gets its own persistent volume, useful for caching
+ # Terraform providers, modules, etc.
+ volumeClaimTemplates: []
+ # - metadata:
+ # name: provisioner-cache
+ # spec:
+ # accessModes: ["ReadWriteOnce"]
+ # resources:
+ # requests:
+ # storage: 10Gi
+
+ # coder.lifecycle -- container lifecycle handlers for the Coder container, allowing
+ # for lifecycle events such as postStart and preStop events
+ # See: https://kubernetes.io/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/
+ lifecycle:
+ {}
+ # postStart:
+ # exec:
+ # command: ["/bin/sh", "-c", "echo postStart"]
+ # preStop:
+ # exec:
+ # command: ["/bin/sh","-c","echo preStart"]
+
+ # coder.resources -- The resources to request for Coder. These are optional
+ # and are not set by default.
+ resources:
+ {}
+ # limits:
+ # cpu: 2000m
+ # memory: 4096Mi
+ # requests:
+ # cpu: 2000m
+ # memory: 4096Mi
+
+ # coder.certs -- CA bundles to mount inside the Coder pod.
+ certs:
+ # coder.certs.secrets -- A list of CA bundle secrets to mount into the
+ # pod. The secrets should exist in the same namespace as the Helm
+ # deployment.
+ #
+ # The given key in each secret is mounted at
+ # `/etc/ssl/certs/{secret_name}.crt`.
+ secrets:
+ []
+ # - name: "my-ca-bundle"
+ # key: "ca-bundle.crt"
+
+ # coder.affinity -- Allows specifying an affinity rule for the deployment.
+ affinity:
+ {}
+ # podAntiAffinity:
+ # preferredDuringSchedulingIgnoredDuringExecution:
+ # - podAffinityTerm:
+ # labelSelector:
+ # matchExpressions:
+ # - key: app.kubernetes.io/instance
+ # operator: In
+ # values:
+ # - "coder"
+ # topologyKey: kubernetes.io/hostname
+ # weight: 1
+
+ # coder.tolerations -- Tolerations for tainted nodes.
+ # See: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
+ tolerations:
+ []
+ # - key: "key"
+ # operator: "Equal"
+ # value: "value"
+ # effect: "NoSchedule"
+
+ # coder.nodeSelector -- Node labels for constraining coder pods to nodes.
+ # See: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
+ nodeSelector: {}
+ # kubernetes.io/os: linux
+
+ # coder.command -- The command to use when running the container. Used
+ # for customizing the location of the `coder` binary in your image.
+ command:
+ - /opt/coder
+
+ # coder.commandArgs -- Set arguments for the entrypoint command of the Coder pod.
+ commandArgs: []
+
+# provisionerDaemon -- Provisioner Daemon configuration options
+provisionerDaemon:
+ # provisionerDaemon.pskSecretName -- The name of the Kubernetes secret that contains the
+ # Pre-Shared Key (PSK) to use to authenticate with Coder. The secret must be
+ # in the same namespace as the Helm deployment, and contain an item called
+ # "psk" which contains the pre-shared key.
+ # NOTE: We no longer recommend using PSKs. Please consider using provisioner
+ # keys instead. They have a number of benefits, including the ability to
+ # rotate them easily.
+ pskSecretName: "coder-provisioner-psk"
+
+ # provisionerDaemon.keySecretName -- The name of the Kubernetes
+ # secret that contains a provisioner key to use to authenticate with Coder.
+ # See: https://coder.com/docs/admin/provisioners#authentication
+ # NOTE: it is not permitted to specify both provisionerDaemon.keySecretName
+ # and provisionerDaemon.pskSecretName. An exception is made for the purposes
+ # of backwards-compatibility: if provisionerDaemon.pskSecretName is unchanged
+ # from the default value and provisionerDaemon.keySecretName is set, then
+ # provisionerDaemon.keySecretName and provisionerDaemon.keySecretKey will take
+ # precedence over provisionerDaemon.pskSecretName.
+ keySecretName: ""
+ # provisionerDaemon.keySecretKey -- The key of the Kubernetes
+ # secret specified in provisionerDaemon.keySecretName that contains
+ # the provisioner key. Defaults to "key".
+ keySecretKey: "key"
+
+ # provisionerDaemon.tags -- If using a PSK, specify the set of provisioner
+ # job tags for which this provisioner daemon is responsible.
+ # See: https://coder.com/docs/admin/provisioners#provisioner-tags
+ # NOTE: it is not permitted to specify both provisionerDaemon.tags and
+ # provsionerDaemon.keySecretName.
+ tags:
+ {}
+ # location: usa
+ # provider: kubernetes
+
+ # provisionerDaemon.terminationGracePeriodSeconds -- Time in seconds that Kubernetes should wait before forcibly
+ # terminating the provisioner daemon. You should set this to be longer than your longest expected build time so that
+ # redeployments do not interrupt builds in progress.
+ terminationGracePeriodSeconds: 600
+
+# extraTemplates -- Array of extra objects to deploy with the release. Strings
+# are evaluated as a template and can use template expansions and functions. All
+# other objects are used as yaml.
+extraTemplates:
+ #- |
+ # apiVersion: v1
+ # kind: ConfigMap
+ # metadata:
+ # name: my-configmap
+ # data:
+ # key: {{ .Values.myCustomValue | quote }}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/main.tf b/infra/aws/us-east-2/k8s/argo/coder-workspace/main.tf
new file mode 100644
index 0000000..f48c153
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/main.tf
@@ -0,0 +1,541 @@
+provider "aws" {
+ region = var.region
+ profile = var.profile
+}
+
+data "aws_region" "this" {}
+
+data "aws_eks_cluster" "this" {
+ name = var.cluster_name
+}
+
+data "aws_eks_cluster_auth" "this" {
+ name = var.cluster_name
+}
+
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
+}
+
+data "http" "login" {
+ url = "${var.coder_access_url}/api/v2/users/login"
+ method = "POST"
+ request_headers = {
+ Accept = "application/json"
+ }
+ request_body = jsonencode({
+ email = var.coder_admin_email
+ password = var.coder_admin_password
+ })
+
+ retry {
+ attempts = 5
+ min_delay_ms = (5 * 1000) # 5 seconds
+ }
+}
+
+provider "helm" {
+ kubernetes = {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+ }
+}
+
+provider "argocd" {
+ kubernetes {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+ }
+}
+
+provider "kubernetes" {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+}
+
+provider "coderd" {
+ url = var.coder_access_url
+ token = jsondecode(data.http.login.response_body).session_token
+}
+
+locals {
+ release_name = "coder"
+ chart_name = "coder-provisioner"
+ namespace = "coder"
+
+ node_selector = {}
+ topology_spread = []
+ # topology_spread = [{
+ # max_skew = 2
+ # topology_key = "kubernetes.io/hostname"
+ # when_unsatisfiable = "ScheduleAnyway"
+ # label_selector = {
+ # match_labels = {
+ # "app.kubernetes.io/name" = local.chart_name
+ # "app.kubernetes.io/part-of" = local.chart_name
+ # }
+ # }
+ # match_label_keys = [
+ # "app.kubernetes.io/instance"
+ # ]
+ # }]
+ tolerations = [{
+ key = "coder"
+ operator = "Exists"
+ values = ["provisioner"]
+ }]
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [
+ {
+ key = "node.coder.io/used-for",
+ operator = "In",
+ values = ["coder-provisioner"]
+ }
+ ]
+ }]
+ }
+ }
+ podAntiAffinity = {}
+ # podAntiAffinity = {
+ # preferredDuringSchedulingIgnoredDuringExecution = [{
+ # weight = 100
+ # podAffinityTerm = {
+ # labelSelector = {
+ # match_labels = {
+ # "app.kubernetes.io/name" = local.chart_name
+ # "app.kubernetes.io/part-of" = local.chart_name
+ # }
+ # }
+ # topologyKey = "kubernetes.io/hostname"
+ # }
+ # }]
+ # }
+ }
+}
+
+# module "default-ws" {
+
+# source = "../../../../../modules/k8s/bootstrap/coder-provisioner"
+
+# release_name = local.release_name
+# chart_version = var.addon_version
+# chart_name = local.chart_name
+# cluster_name = data.aws_eks_cluster.this.id
+# cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
+
+# namespace = "coder-ws"
+
+# coder = {
+# access_url = var.coder_access_url
+# org_name = "coder"
+# image_repo = var.image_repo
+# image_tag = var.image_tag
+# rep_cnt = 1
+# ws_extra_rules = [{
+# apiGroups = [""]
+# resources = ["configmaps"]
+# verbs = [
+# "create",
+# "delete",
+# "deletecollection",
+# "get",
+# "list",
+# "patch",
+# "update",
+# "watch"
+# ]
+# }]
+# env_vars = {
+# CODER_PROMETHEUS_ENABLE = "true"
+# CODER_PROMETHEUS_COLLECT_AGENT_STATS = "true"
+# CODER_PROMETHEUS_COLLECT_DB_METRICS = "true"
+# # TF_VAR_namespace = "coder-ws"
+# }
+# }
+
+# svc_acc = {
+# create = true
+# name = "coder"
+# }
+
+# node_selector = local.node_selector
+# tolerations = local.tolerations
+# topology_spread = local.topology_spread
+# affinity = local.affinity
+# }
+
+# module "experiment-ws" {
+
+# source = "../../../../../modules/k8s/bootstrap/coder-provisioner"
+
+# release_name = local.release_name
+# chart_version = var.addon_version
+# chart_name = local.chart_name
+# cluster_name = var.cluster_name
+# cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
+
+# namespace = "coder-ws-experiment"
+
+# coder = {
+# access_url = var.coder_access_url
+# org_name = "experiment"
+# image_repo = var.image_repo
+# image_tag = var.image_tag
+# rep_cnt = 1
+# ws_extra_rules = [{
+# apiGroups = [""]
+# resources = ["configmaps"]
+# verbs = [
+# "create",
+# "delete",
+# "deletecollection",
+# "get",
+# "list",
+# "patch",
+# "update",
+# "watch"
+# ]
+# },{
+# apiGroups = [""]
+# resources = ["serviceaccounts"]
+# verbs = [
+# "create",
+# "delete",
+# "deletecollection",
+# "get",
+# "list",
+# "patch",
+# "update",
+# "watch"
+# ]
+# },{
+# apiGroups = ["rbac.authorization.k8s.io"]
+# resources = ["clusterrolebindings"]
+# verbs = [
+# "create",
+# "delete",
+# "deletecollection",
+# "get",
+# "list",
+# "patch",
+# "update",
+# "watch"
+# ]
+# }]
+# env_vars = {
+# CODER_PROMETHEUS_ENABLE = "true"
+# CODER_PROMETHEUS_COLLECT_AGENT_STATS = "true"
+# CODER_PROMETHEUS_COLLECT_DB_METRICS = "true"
+# # TF_VAR_namespace = "coder-ws-experiment"
+# }
+# }
+
+# svc_acc = {
+# create = true
+# name = "coder"
+# }
+
+# node_selector = local.node_selector
+# tolerations = local.tolerations
+# topology_spread = local.topology_spread
+# affinity = local.affinity
+# }
+
+data "aws_iam_policy_document" "eks" {
+ statement {
+ effect = "Allow"
+ actions = [
+ "eks:*",
+ "iam:*"
+ ]
+ resources = ["*"]
+ }
+}
+
+module "eks-admin-policy" {
+ source = "../../../../../../modules/security/policy"
+ name = "demo-ws-eks-policy"
+ path = "/"
+ description = "Coder External Demo-Provisioner Policy (EKS)"
+ policy_json = data.aws_iam_policy_document.eks.json
+}
+
+# module "demo-ws" {
+
+# source = "../../../../../modules/k8s/bootstrap/coder-provisioner"
+
+# release_name = local.release_name
+# chart_version = var.addon_version
+# chart_name = local.chart_name
+# cluster_name = var.cluster_name
+# cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
+
+# namespace = "coder-ws-demo"
+
+# coder = {
+# access_url = var.coder_access_url
+# org_name = "demo"
+# image_repo = var.image_repo
+# image_tag = var.image_tag
+# rep_cnt = 1
+# ws_extra_rules = [{
+# apiGroups = [""]
+# resources = ["configmaps"]
+# verbs = [
+# "create",
+# "delete",
+# "deletecollection",
+# "get",
+# "list",
+# "patch",
+# "update",
+# "watch"
+# ]
+# },{
+# apiGroups = [""]
+# resources = ["serviceaccounts"]
+# verbs = [
+# "create",
+# "delete",
+# "deletecollection",
+# "get",
+# "list",
+# "patch",
+# "update",
+# "watch"
+# ]
+# },{
+# apiGroups = ["rbac.authorization.k8s.io"]
+# resources = ["clusterrolebindings"]
+# verbs = [
+# "create",
+# "delete",
+# "deletecollection",
+# "get",
+# "list",
+# "patch",
+# "update",
+# "watch"
+# ]
+# }]
+# env_vars = {
+# CODER_PROMETHEUS_ENABLE = "true"
+# CODER_PROMETHEUS_COLLECT_AGENT_STATS = "true"
+# CODER_PROMETHEUS_COLLECT_DB_METRICS = "true"
+# # TF_VAR_namespace = "coder-ws-experiment"
+# }
+# }
+
+# svc_acc = {
+# create = true
+# name = "coder"
+# iam_policy_arns = {
+# "EKSAdminPolicy" = module.eks-admin-policy.policy_arn
+# }
+# }
+
+# node_selector = local.node_selector
+# tolerations = local.tolerations
+# topology_spread = local.topology_spread
+# affinity = local.affinity
+# }
+
+locals {
+ # coder-provisioner-values = yamlencode({
+ # coder = {
+ # image = {
+ # repo = var.coder.image_repo
+ # tag = var.coder.image_tag
+ # pullPolicy = var.coder.image_pull_policy
+ # pullSecrets = var.coder.image_pull_secrets
+ # }
+ # serviceAccount = {
+ # workspacePerms = true
+ # enableDeployments = true
+ # name = var.svc_acc.name
+ # disableCreate = !var.svc_acc.create
+ # workspaceNamespaces = [ for v in var.coder.ws_ns : { name = v } ]
+ # extraRules = var.coder.ws_extra_rules
+ # annotations = merge({
+ # "eks.amazonaws.com/role-arn" = module.oidc-role.role_arn
+ # }, var.svc_acc.annots)
+ # }
+ # podAnnotations = {
+ # "prometheus.io/scrape" = "true"
+ # "prometheus.io/port" = "2112"
+ # }
+ # env = [
+ # for k, v in merge({
+ # CODER_URL = var.coder.access_url
+ # }, var.coder.env_vars) : { name = k, value = v }
+ # ]
+ # volumeClaimTemplates = [{
+ # metadata = {
+ # name = "cache"
+ # }
+ # spec = {
+ # accessModes = ["ReadWriteOnce"]
+ # storageClassName = "gp3-automode"
+ # resources = {
+ # requests = {
+ # storage = "10Gi"
+ # }
+ # }
+ # }
+ # }]
+ # # volumes = [{
+ # # name = "cache"
+ # # persistentVolumeClaim = {
+ # # claimName = kubernetes_persistent_volume_claim_v1.cache.metadata[0].name
+ # # readOnly = false
+ # # }
+ # # }]
+ # volumeMounts = [{
+ # mountPath = "/home/coder/.cache/coder"
+ # name = "cache"
+ # readOnly = false
+ # }]
+ # podSecurityContext = {
+ # fsGroup = 1000
+ # }
+ # securityContext = {
+ # runAsNonRoot = true
+ # runAsUser = 1000
+ # runAsGroup = 1000
+ # readOnlyRootFilesystem = null
+ # seccompProfile = {
+ # type = "RuntimeDefault"
+ # }
+ # allowPrivilegeEscalation = false
+ # }
+ # resources = {
+ # requests = var.rsrc_req
+ # limits = var.rsrc_lim
+ # }
+ # nodeSelector = {} # var.node_selector
+ # replicaCount = # var.coder.rep_cnt
+ # tolerations = # var.tolerations
+ # topologySpreadConstraints = local.topology_spread
+ # affinity = var.affinity
+ # }
+ # provisionerDaemon = {
+ # keySecretKey = kubernetes_secret_v1.ext-prov.metadata[0].annotations["custom.kubernetes.secret/key"]
+ # keySecretName = kubernetes_secret_v1.ext-prov.metadata[0].name
+ # terminationGracePeriodSeconds = 600
+ # }
+ # })
+}
+
+resource "argocd_application_set" "coder-workspaces" {
+ metadata {
+ name = "coder-workspaces"
+ namespace = "argocd"
+ labels = {}
+ annotations = {}
+ }
+ spec {
+ generator {
+ list {
+ elements = [{
+ name = "coder-ws"
+ namespace = "coder-ws-test"
+ repoURL = "https://github.com/coder/ai.coder.com.git"
+ path = "infra/aws/us-east-2/k8s/argo/coder-ws/charts/coder-provisioner"
+ chart = "coder-provisioner"
+ revision = "main"
+ values = yamlencode({})
+ template = yamlencode({
+ spec = {
+ sync_policy = {
+ sync_options = ["CreateNamespace=true"]
+ }
+ }
+ })
+ },{
+ name = "coder-ws-experiment"
+ namespace = "coder-ws-experiment-test"
+ repoURL = "https://github.com/coder/ai.coder.com.git"
+ path = "infra/aws/us-east-2/k8s/argo/coder-ws/charts/coder-provisioner"
+ chart = "coder-provisioner"
+ revision = "main"
+ values = yamlencode({})
+ template = yamlencode({
+ spec = {
+ sync_policy = {
+ sync_options = ["CreateNamespace=true"]
+ }
+ }
+ })
+ },{
+ name = "coder-ws-demo"
+ namespace = "coder-ws-demo-test"
+ repoURL = "https://github.com/coder/ai.coder.com.git"
+ path = "infra/aws/us-east-2/k8s/argo/coder-ws/charts/coder-provisioner"
+ chart = "coder-provisioner"
+ revision = "main"
+ values = yamlencode({})
+ template = yamlencode({
+ spec = {
+ sync_policy = {
+ sync_options = ["CreateNamespace=true"]
+ }
+ }
+ })
+ },{
+ name = "coder-logstream-kube"
+ namespace = "coder-logstream-kube-test"
+ repoURL = "https://github.com/coder/ai.coder.com.git"
+ path = "infra/aws/us-east-2/k8s/argo/coder-ws/charts/coder-provisioner"
+ chart = "coder-logstream-kube"
+ revision = "main"
+ values = yamlencode({
+ url = var.coder_access_url
+ namespaces = ["coder-ws", "coder-ws-demo", "coder-ws-experiment"]
+ image = {
+ repo = "ghcr.io/coder/coder-logstream-kube"
+ tag = "v0.0.15"
+ pullPolicy = "IfNotPresent"
+ }
+
+ nodeSelector = {} # var.node_selector
+ affinity = {} # var.affinity
+ tolerations = {} # var.tolerations
+ })
+ template = yamlencode({
+ spec = {
+ sync_policy = {
+ sync_options = ["CreateNamespace=true"]
+ }
+ }
+ })
+ }]
+ }
+ }
+
+ template {
+ metadata {
+ name = "{{name}}"
+ }
+ spec {
+ source {
+ repo_url = "{{repoURL}}"
+ path = "{{path}}"
+ target_revision = "{{version}}"
+ }
+ # repo_url = "{{repoURL}}"
+ # chart = "{{chart}}"
+ # target_revision = "{{version}}"
+ destination {
+ server = "https://kubernetes.default.svc"
+ namespace = "{{namespace}}"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/terragrunt.hcl b/infra/aws/us-east-2/k8s/argo/coder-workspace/terragrunt.hcl
new file mode 100644
index 0000000..2577291
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/terragrunt.hcl
@@ -0,0 +1,29 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../../eks",
+ "../../coder-server",
+ "../../kyverno",
+ "../../other" # Deploy's auxillary manifests
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ coder_access_url=include.root.locals.CODER_DOMAIN_NAME
+ coder_admin_email=include.root.locals.CODER_EMAIL
+ coder_admin_password=include.root.locals.CODER_PASSWORD
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ image_repo=include.root.locals.CODER_IMAGE_REPO
+ image_tag=include.root.locals.CODER_IMAGE_TAG
+ addon_version=include.root.locals.CODER_ADDON_VERSION
+ logstream_addon_version=include.root.locals.CODER_LOGSTREAM_ADDON_VERSION
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/argo/coder-workspace/variables.tf b/infra/aws/us-east-2/k8s/argo/coder-workspace/variables.tf
new file mode 100644
index 0000000..edc2d5d
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/argo/coder-workspace/variables.tf
@@ -0,0 +1,45 @@
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "cluster_name" {
+ type = string
+}
+
+variable "addon_version" {
+ type = string
+ default = "2.23.0"
+}
+
+variable "logstream_addon_version" {
+ type = string
+ default = "0.0.11"
+}
+
+variable "coder_access_url" {
+ type = string
+}
+
+variable "image_repo" {
+ type = string
+ sensitive = true
+}
+
+variable "image_tag" {
+ type = string
+ default = "latest"
+}
+
+variable "coder_admin_email" {
+ type = string
+}
+
+variable "coder_admin_password" {
+ type = string
+ sensitive = true
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/cert-manager/main.tf b/infra/aws/us-east-2/k8s/cert-manager/main.tf
index c095843..226cbec 100644
--- a/infra/aws/us-east-2/k8s/cert-manager/main.tf
+++ b/infra/aws/us-east-2/k8s/cert-manager/main.tf
@@ -1,59 +1,6 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "addon_namespace" {
- type = string
- default = "cert-manager"
-}
-
-variable "addon_version" {
- type = string
- default = "v1.18.2"
-}
-
-variable "cloudflare_api_token" {
- type = string
- sensitive = true
-}
-
-variable "cloudflare_email" {
- type = string
- sensitive = true
-}
-
provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
+ region = var.region
+ profile = var.profile
}
data "aws_eks_cluster" "this" {
@@ -64,6 +11,10 @@ data "aws_eks_cluster_auth" "this" {
name = var.cluster_name
}
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
+}
+
provider "helm" {
kubernetes = {
host = data.aws_eks_cluster.this.endpoint
@@ -79,12 +30,30 @@ provider "kubernetes" {
}
module "cert-manager" {
- source = "../../../../../modules/k8s/bootstrap/cert-manager"
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
- namespace = var.addon_namespace
- helm_version = var.addon_version
- cloudflare_token_secret = var.cloudflare_api_token
- cloudflare_token_secret_email = var.cloudflare_email
+ source = "../../../../../modules/k8s/bootstrap/cert-manager"
+
+ cluster_name = var.cluster_name
+ cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
+
+ namespace = var.addon_namespace
+ helm_version = var.addon_version
+
+ tolerations = [{
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }]
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [{
+ key = "karpenter.sh/nodepool"
+ operator = "In"
+ values = ["system"]
+ }]
+ }]
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/cert-manager/terragrunt.hcl b/infra/aws/us-east-2/k8s/cert-manager/terragrunt.hcl
new file mode 100644
index 0000000..27da9ba
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/cert-manager/terragrunt.hcl
@@ -0,0 +1,20 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks"
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ addon_namespace=include.root.locals.CRTMGR_ADDON_NAMESPACE
+ addon_version=include.root.locals.CRTMGR_ADDON_VERSION
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/cert-manager/variables.tf b/infra/aws/us-east-2/k8s/cert-manager/variables.tf
new file mode 100644
index 0000000..f93b94c
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/cert-manager/variables.tf
@@ -0,0 +1,22 @@
+variable "cluster_name" {
+ type = string
+}
+
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "addon_namespace" {
+ type = string
+ default = "cert-manager"
+}
+
+variable "addon_version" {
+ type = string
+ default = "v1.18.2"
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/coder-server/main.tf b/infra/aws/us-east-2/k8s/coder-server/main.tf
index 10da024..006555a 100644
--- a/infra/aws/us-east-2/k8s/coder-server/main.tf
+++ b/infra/aws/us-east-2/k8s/coder-server/main.tf
@@ -1,305 +1,324 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- coderd = {
- source = "coder/coderd"
- }
- acme = {
- source = "vancluever/acme"
- }
- tls = {
- source = "hashicorp/tls"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "acme_server_url" {
- type = string
- default = "https://acme-v02.api.letsencrypt.org/directory"
-}
-
-variable "acme_registration_email" {
- type = string
-}
-
-variable "addon_version" {
- type = string
- default = "2.25.1"
-}
-
-variable "coder_access_url" {
- type = string
+provider "aws" {
+ region = var.region
+ profile = var.profile
}
-variable "coder_wildcard_access_url" {
- type = string
-}
+data "aws_region" "this" {}
-variable "coder_experiments" {
- type = list(string)
- default = []
-}
-
-variable "coder_github_allowed_orgs" {
- type = list(string)
- default = []
+data "aws_eks_cluster" "this" {
+ name = var.cluster_name
}
-variable "coder_builtin_provisioner_count" {
- type = number
- default = 0
+data "aws_eks_cluster_auth" "this" {
+ name = var.cluster_name
}
-variable "coder_github_external_auth_secret_client_secret" {
- type = string
- sensitive = true
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
}
-variable "coder_github_external_auth_secret_client_id" {
- type = string
- sensitive = true
+data "aws_db_instance" "coder" {
+ db_instance_identifier = var.coder_db_rds_name
}
-variable "coder_oauth_secret_client_secret" {
- type = string
- sensitive = true
+data "aws_vpc" "this" {
+ tags = {
+ Name = var.vpc_name
+ }
}
-variable "coder_oauth_secret_client_id" {
- type = string
- sensitive = true
+provider "helm" {
+ kubernetes = {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+ }
}
-variable "coder_oidc_secret_client_secret" {
- type = string
- sensitive = true
+provider "kubernetes" {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
}
-variable "coder_oidc_secret_client_id" {
- type = string
- sensitive = true
+locals {
+ common_name = trimprefix(trimprefix(var.coder_access_url, "https://"), "http://")
+ wildcard_name = trimprefix(trimprefix(var.coder_wildcard_access_url, "https://"), "http://")
+ ssl_vol_friendly_name = replace(local.common_name, ".", "-")
}
-variable "coder_oidc_secret_issuer_url" {
- type = string
- sensitive = true
-}
+resource "kubernetes_manifest" "certificate" {
-variable "coder_db_secret_url" {
- type = string
- sensitive = true
-}
+ field_manager {
+ force_conflicts = true
+ }
-variable "coder_token" {
- type = string
- sensitive = true
-}
+ wait {
+ condition {
+ type = "Ready"
+ status = "True"
+ }
+ }
-variable "image_repo" {
- type = string
- sensitive = true
-}
+ timeouts {
+ create = "10m"
+ update = "10m"
+ delete = "30s"
+ }
-variable "image_tag" {
- type = string
- default = "latest"
+ manifest = {
+ apiVersion = "cert-manager.io/v1"
+ kind = "Certificate"
+ metadata = {
+ name = local.ssl_vol_friendly_name
+ namespace = module.coder-server.namespace
+ }
+ spec = {
+ commonName = local.common_name
+ dnsNames = [
+ local.common_name,
+ local.wildcard_name
+ ]
+ duration = "2160h" # 90 days
+ renewBefore = "360h" # 15 days
+ issuerRef = {
+ kind = "ClusterIssuer"
+ name = "issuer"
+ }
+ secretName = local.ssl_vol_friendly_name
+ privateKey = {
+ rotationPolicy = "Never"
+ algorithm = "RSA"
+ encoding = "PKCS1"
+ size = "2048"
+ }
+ }
+ }
}
-variable "kubernetes_ssl_secret_name" {
- type = string
+locals {
+ azs = var.azs
+ pub_subs = [for az in local.azs : "${var.vpc_name}-public-${data.aws_region.this.region}${az}"]
+ release_name = "coder"
+ chart_name = "coder"
+ namespace = "coder"
}
-variable "kubernetes_create_ssl_secret" {
- type = bool
- default = true
+resource "aws_eip" "coder" {
+ count = length(local.pub_subs)
+ domain = "vpc"
+ public_ipv4_pool = "amazon"
+ tags = {
+ Name = "coder-eip-${count.index}"
+ }
}
-variable "cloudflare_api_token" {
- type = string
- sensitive = true
+# resource "kubernetes_pod_disruption_budget_v1" "coder" {
+# metadata {
+# name = local.release_name
+# namespace = module.coder-server.namespace
+# }
+# spec {
+# # Avoid disrupting ongoing connections.
+# max_unavailable = 1
+# selector {
+# match_labels = {
+# "app.kubernetes.io/instance" = local.release_name
+# "app.kubernetes.io/name" = local.chart_name
+# "app.kubernetes.io/part-of" = local.chart_name
+# }
+# }
+# }
+# }
+
+# ---
+
+resource "aws_iam_user" "bedrock" {
+ name = "${local.release_name}-bedrock"
+ path = "/${var.cluster_name}/${var.region}/"
+ force_destroy = true
+
+ tags = {
+ Purpose = "coder-aibridge"
+ ManagedBy = "terraform"
+ }
}
-variable "oidc_sign_in_text" {
- type = string
-}
+data "aws_iam_policy_document" "bedrock" {
+ statement {
+ sid = "InvokeBedrockModels"
+ effect = "Allow"
-variable "oidc_icon_url" {
- type = string
-}
+ actions = [
+ "bedrock:InvokeModel",
+ "bedrock:InvokeModelWithResponseStream"
+ ]
-variable "oidc_scopes" {
- type = list(string)
+ resources = [
+ "arn:aws:bedrock:*::foundation-model/*",
+ "arn:aws:bedrock:*:*:inference-profile/*"
+ ]
+ }
}
-variable "oidc_email_domain" {
- type = string
+resource "aws_iam_policy" "bedrock" {
+ name = "${local.release_name}-bedrock"
+ description = "Allow Coder AI Bridge to invoke Amazon Bedrock models."
+ policy = data.aws_iam_policy_document.bedrock.json
}
-variable "anthropic_llm_endpoint" {
- type = string
- sensitive = true
+resource "aws_iam_user_policy_attachment" "bedrock" {
+ user = aws_iam_user.bedrock.name
+ policy_arn = aws_iam_policy.bedrock.arn
}
-variable "anthropic_llm_key" {
- type = string
- sensitive = true
+resource "aws_iam_access_key" "bedrock" {
+ user = aws_iam_user.bedrock.name
}
-variable "openai_llm_endpoint" {
- type = string
- sensitive = true
-}
+# ---
-variable "openai_llm_key" {
- type = string
- sensitive = true
-}
+module "coder-server" {
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
+ source = "../../../../../modules/k8s/bootstrap/coder-server"
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
+ release_name = local.release_name
+ chart_version = var.addon_version
+ chart_name = local.chart_name
+ cluster_name = data.aws_eks_cluster.this.id
+ cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
+
+ coder = {
+ access_url = var.coder_access_url
+ wildcard_url = var.coder_wildcard_access_url
+ mount_ssl = true
+ mount_ssl_name = kubernetes_manifest.certificate.manifest.spec.secretName
+
+ image_repo = var.image_repo
+ image_tag = var.image_tag
+
+ rep_cnt = length(local.pub_subs)
+ # External Provisioners will be used
+ prov_rep_cnt = var.coder_builtin_provisioner_count
+ env_vars = {
+ CODER_EXPERIMENTS = join(",", var.coder_experiments)
+ }
+ }
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
+ db = {
+ url = data.aws_db_instance.coder.endpoint
+ username = var.coder_db_username
+ password = var.coder_db_password
+ db = var.coder_db_name
+ pg_auth = "awsiamrds"
+ }
-provider "helm" {
- kubernetes = {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
+ prometheus = {
+ enable = true
}
-}
-provider "kubernetes" {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
-}
+ oidc = {
+ enable = true
+ sign_in_text = var.oidc_sign_in_text
+ icon_url = var.oidc_icon_url
+ scopes = var.oidc_scopes
+ email_domain = var.oidc_email_domain
+ issuer_url = var.coder_oidc_secret_issuer_url
+ client_id = var.coder_oidc_secret_client_id
+ client_secret = var.coder_oidc_secret_client_secret
+ }
-provider "coderd" {
- url = var.coder_access_url
- token = var.coder_token
-}
+ oauth2 = {
+ enable = true
+ default_provider_enable = false
+ allow_signups = true
+ device_flow = false
+ allowed_orgs = var.coder_github_allowed_orgs
+ client_id = var.coder_oauth_secret_client_id
+ client_secret = var.coder_oauth_secret_client_secret
+ use_extern_auth = false
+ }
-provider "acme" {
- server_url = var.acme_server_url
-}
+ extern_auth = [{
+ id = "primary-github"
+ type = "github"
+ client_id = var.coder_github_external_auth_secret_client_id
+ client_secret = var.coder_github_external_auth_secret_client_secret
+ }]
-module "coder-server" {
- source = "../../../../../modules/k8s/bootstrap/coder-server"
+ aibridge = {
+ enabled = true
+ }
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- namespace = "coder"
- acme_registration_email = var.acme_registration_email
- acme_days_until_renewal = 90
- replica_count = 2
- helm_version = var.addon_version
- image_repo = var.image_repo
- image_tag = var.image_tag
- primary_access_url = var.coder_access_url
- wildcard_access_url = var.coder_wildcard_access_url
- cloudflare_api_token = var.cloudflare_api_token
- coder_experiments = var.coder_experiments
- coder_builtin_provisioner_count = var.coder_builtin_provisioner_count
- coder_github_allowed_orgs = var.coder_github_allowed_orgs
- anthropic_llm_endpoint = var.anthropic_llm_endpoint
- anthropic_llm_key = var.anthropic_llm_key
- openai_llm_endpoint = var.openai_llm_endpoint
- openai_llm_key = var.openai_llm_key
- ssl_cert_config = {
- name = var.kubernetes_ssl_secret_name
- create_secret = var.kubernetes_create_ssl_secret
+ namespace = local.namespace
+ resource_limit = {
+ cpu = "2"
+ memory = "2Gi"
}
- oidc_config = {
- sign_in_text = var.oidc_sign_in_text
- icon_url = var.oidc_icon_url
- scopes = var.oidc_scopes
- email_domain = var.oidc_email_domain
+ resource_request = {
+ cpu = "500m"
+ memory = "2Gi"
}
- db_secret_url = var.coder_db_secret_url
- oidc_secret_issuer_url = var.coder_oidc_secret_issuer_url
- oidc_secret_client_id = var.coder_oidc_secret_client_id
- oidc_secret_client_secret = var.coder_oidc_secret_client_secret
- oauth_secret_client_id = var.coder_oauth_secret_client_id
- oauth_secret_client_secret = var.coder_oauth_secret_client_secret
- github_external_auth_secret_client_id = var.coder_github_external_auth_secret_client_id
- github_external_auth_secret_client_secret = var.coder_github_external_auth_secret_client_secret
- tags = {}
- service_annotations = {
- "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "instance"
+ lb_class = "service.k8s.aws/nlb"
+ svc_annot = {
+ "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "ip"
"service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
- "service.beta.kubernetes.io/aws-load-balancer-attributes" = "deletion_protection.enabled=true"
- }
- node_selector = {
- "node.coder.io/managed-by" = "karpenter"
- "node.coder.io/used-for" = "coder-server"
+ "service.beta.kubernetes.io/aws-load-balancer-attributes" = "deletion_protection.enabled=false,load_balancing.cross_zone.enabled=true"
+ "service.beta.kubernetes.io/aws-load-balancer-eip-allocations" = join(",", aws_eip.coder.*.allocation_id)
+ "service.beta.kubernetes.io/aws-load-balancer-subnets" = join(",", local.pub_subs)
}
tolerations = [{
- key = "dedicated"
- operator = "Equal"
- value = "coder-server"
- effect = "NoSchedule"
+ key = "platform"
+ value = "coder-server"
+ effect = "NoSchedule"
}]
- topology_spread_constraints = [{
- max_skew = 1
- topology_key = "kubernetes.io/hostname"
- when_unsatisfiable = "ScheduleAnyway"
- label_selector = {
- match_labels = {
- "app.kubernetes.io/name" = "coder"
- "app.kubernetes.io/part-of" = "coder"
+ topology_spread = []
+ # topology_spread = [{
+ # max_skew = 1
+ # topology_key = "topology.kubernetes.io/zone"
+ # when_unsatisfiable = "ScheduleAnyway"
+ # label_selector = {
+ # match_labels = {
+ # "app.kubernetes.io/name" = local.chart_name
+ # "app.kubernetes.io/part-of" = local.chart_name
+ # }
+ # }
+ # match_label_keys = [
+ # "app.kubernetes.io/instance"
+ # ]
+ # }]
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [for az in local.azs : "${data.aws_region.this.region}${az}"]
+ },
+ {
+ key = "node.coder.io/used-for",
+ operator = "In",
+ values = ["coder-server"]
+ }
+ ]
+ }]
}
}
- match_label_keys = [
- "app.kubernetes.io/instance"
- ]
- }]
- pod_anti_affinity_preferred_during_scheduling_ignored_during_execution = [{
- weight = 100
- pod_affinity_term = {
- label_selector = {
- match_labels = {
- "app.kubernetes.io/instance" = "coder-v2"
- "app.kubernetes.io/name" = "coder"
- "app.kubernetes.io/part-of" = "coder"
- }
- }
- topology_key = "kubernetes.io/hostname"
+ podAntiAffinity = {
+ preferredDuringSchedulingIgnoredDuringExecution = []
+ requiredDuringSchedulingIgnoredDuringExecution = []
+ # requiredDuringSchedulingIgnoredDuringExecution = [{
+ # labelSelector = {
+ # matchExpressions = [{
+ # key = "app.kubernetes.io/instance"
+ # operator = "In"
+ # values = [local.chart_name]
+ # }]
+ # }
+ # topologyKey = "kubernetes.io/hostname"
+ # }]
}
- }]
+ }
}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/coder-server/terragrunt.hcl b/infra/aws/us-east-2/k8s/coder-server/terragrunt.hcl
new file mode 100644
index 0000000..6790e89
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/coder-server/terragrunt.hcl
@@ -0,0 +1,58 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks",
+ "../../rds",
+ "../litellm",
+ "../karpenter",
+ "../lb-controller",
+ "../cert-manager",
+ "../other" # Deploy's auxillary manifests
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ azs=jsondecode(include.root.locals.CODER_AWS_AZS)
+ vpc_name=include.root.locals.CODER_VPC_NAME
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ coder_access_url=include.root.locals.CODER_DOMAIN_NAME
+ coder_wildcard_access_url=include.root.locals.CODER_WILDCARD_URL
+ coder_experiments=jsondecode(include.root.locals.CODER_EXPERIMENTS)
+ coder_builtin_provisioner_count=include.root.locals.CODER_BUILT_IN_PROVISIONER_COUNT
+
+ coder_db_rds_name=include.root.locals.CODER_DB_RDS_ID
+ coder_db_username=include.root.locals.CODER_DB_USERNAME
+ coder_db_password=include.root.locals.CODER_DB_PASSWORD
+ coder_db_name=include.root.locals.CODER_DB_NAME
+
+ oidc_sign_in_text=include.root.locals.CODER_OIDC_SIGN_IN_TEXT
+ oidc_icon_url=include.root.locals.CODER_OIDC_ICON_URL
+ oidc_scopes=include.root.locals.CODER_OIDC_SCOPES
+ oidc_email_domain=include.root.locals.CODER_OIDC_EMAIL_DOMAIN
+ coder_oidc_secret_issuer_url=include.root.locals.CODER_OIDC_ISSUER_URL
+ coder_oidc_secret_client_id=include.root.locals.CODER_OIDC_CLIENT_ID
+ coder_oidc_secret_client_secret=include.root.locals.CODER_OIDC_CLIENT_SECRET
+
+ coder_oauth_secret_client_id=include.root.locals.CODER_GITHUB_OAUTH_CLIENT_ID
+ coder_oauth_secret_client_secret=include.root.locals.CODER_GITHUB_OAUTH_CLIENT_SECRET
+ coder_github_external_auth_secret_client_id=include.root.locals.CODER_GITHUB_EXTERN_AUTH_CLIENT_ID
+ coder_github_external_auth_secret_client_secret=include.root.locals.CODER_GITHUB_EXTERN_AUTH_CLIENT_SECRET
+
+ image_repo=include.root.locals.CODER_IMAGE_REPO
+ image_tag=include.root.locals.CODER_IMAGE_TAG
+ addon_version=include.root.locals.CODER_ADDON_VERSION
+
+ anthropic_llm_endpoint=include.root.locals.CODER_ANTHROPIC_LLM_ENDPOINT
+ anthropic_llm_key=include.root.locals.CODER_ANTHROPIC_LLM_KEY
+
+ openai_llm_endpoint=include.root.locals.CODER_OPENAI_LLM_ENDPOINT
+ openai_llm_key=include.root.locals.CODER_OPENAI_LLM_KEY
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/coder-server/variables.tf b/infra/aws/us-east-2/k8s/coder-server/variables.tf
new file mode 100644
index 0000000..41ca65d
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/coder-server/variables.tf
@@ -0,0 +1,147 @@
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "azs" {
+ type = list(string)
+ default = ["a", "c"]
+}
+
+variable "vpc_name" {
+ type = string
+}
+
+variable "cluster_name" {
+ type = string
+}
+
+variable "addon_version" {
+ type = string
+ default = "2.25.1"
+}
+
+variable "coder_access_url" {
+ type = string
+}
+
+variable "coder_wildcard_access_url" {
+ type = string
+}
+
+variable "coder_experiments" {
+ type = list(string)
+ default = []
+}
+
+variable "coder_github_allowed_orgs" {
+ type = list(string)
+ default = []
+}
+
+variable "coder_builtin_provisioner_count" {
+ type = number
+ default = 0
+}
+
+variable "coder_github_external_auth_secret_client_secret" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_github_external_auth_secret_client_id" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_oauth_secret_client_secret" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_oauth_secret_client_id" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_oidc_secret_client_secret" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_oidc_secret_client_id" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_oidc_secret_issuer_url" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_db_rds_name" {
+ type = string
+}
+
+variable "coder_db_username" {
+ type = string
+}
+
+variable "coder_db_password" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_db_name" {
+ type = string
+}
+
+variable "image_repo" {
+ type = string
+ sensitive = true
+}
+
+variable "image_tag" {
+ type = string
+ default = "latest"
+}
+
+variable "oidc_sign_in_text" {
+ type = string
+}
+
+variable "oidc_icon_url" {
+ type = string
+}
+
+variable "oidc_scopes" {
+ type = list(string)
+}
+
+variable "oidc_email_domain" {
+ type = string
+}
+
+variable "anthropic_llm_endpoint" {
+ type = string
+ sensitive = true
+}
+
+variable "anthropic_llm_key" {
+ type = string
+ sensitive = true
+}
+
+variable "openai_llm_endpoint" {
+ type = string
+ sensitive = true
+}
+
+variable "openai_llm_key" {
+ type = string
+ sensitive = true
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/coder-ws/main.tf b/infra/aws/us-east-2/k8s/coder-ws/main.tf
index 0dc793f..b77b504 100644
--- a/infra/aws/us-east-2/k8s/coder-ws/main.tf
+++ b/infra/aws/us-east-2/k8s/coder-ws/main.tf
@@ -1,94 +1,10 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- coderd = {
- source = "coder/coderd"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "provisioner_addon_version" {
- type = string
- default = "2.23.0"
-}
-
-variable "logstream_addon_version" {
- type = string
- default = "0.0.11"
-}
-
-variable "coder_access_url" {
- type = string
- sensitive = true
-}
-
-variable "coder_token" {
- type = string
- sensitive = true
-}
-
-variable "image_repo" {
- type = string
- sensitive = true
-}
-
-variable "image_tag" {
- type = string
- default = "latest"
-}
-
-variable "rotate_key_image_repo" {
- type = string
- sensitive = true
-}
-
-variable "rotate_key_image_tag" {
- type = string
- sensitive = true
-}
-
-variable "aws_secret_id" {
- type = string
- sensitive = true
-}
-
-variable "aws_secret_region" {
- type = string
- sensitive = true
-}
-
provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
+ region = var.region
+ profile = var.profile
}
+data "aws_region" "this" {}
+
data "aws_eks_cluster" "this" {
name = var.cluster_name
}
@@ -97,6 +13,27 @@ data "aws_eks_cluster_auth" "this" {
name = var.cluster_name
}
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
+}
+
+data "http" "login" {
+ url = "${var.coder_access_url}/api/v2/users/login"
+ method = "POST"
+ request_headers = {
+ Accept = "application/json"
+ }
+ request_body = jsonencode({
+ email = var.coder_admin_email
+ password = var.coder_admin_password
+ })
+
+ retry {
+ attempts = 5
+ min_delay_ms = (5 * 1000) # 5 seconds
+ }
+}
+
provider "helm" {
kubernetes = {
host = data.aws_eks_cluster.this.endpoint
@@ -113,97 +50,374 @@ provider "kubernetes" {
provider "coderd" {
url = var.coder_access_url
- token = var.coder_token
+ token = jsondecode(data.http.login.response_body).session_token
}
locals {
- service_account_labels = {
- "app.kubernetes.io/instance" = "coder-provisioner"
- "app.kubernetes.io/name" = "coder-provisioner"
- "app.kubernetes.io/part-of" = "coder-provisioner"
- }
- node_selector = {
- "node.coder.io/managed-by" = "karpenter"
- "node.coder.io/used-for" = "coder-provisioner"
- }
+ release_name = "coder"
+ chart_name = "coder-provisioner"
+ namespace = "coder"
+
+ node_selector = {}
+ topology_spread = []
+ # topology_spread = [{
+ # max_skew = 2
+ # topology_key = "kubernetes.io/hostname"
+ # when_unsatisfiable = "ScheduleAnyway"
+ # label_selector = {
+ # match_labels = {
+ # "app.kubernetes.io/name" = local.chart_name
+ # "app.kubernetes.io/part-of" = local.chart_name
+ # }
+ # }
+ # match_label_keys = [
+ # "app.kubernetes.io/instance"
+ # ]
+ # }]
tolerations = [{
- key = "dedicated"
- operator = "Equal"
- value = "coder-provisioner"
- effect = "NoSchedule"
+ key = "coder"
+ operator = "Exists"
+ values = ["provisioner"]
}]
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [
+ {
+ key = "node.coder.io/used-for",
+ operator = "In",
+ values = ["coder-provisioner"]
+ }
+ ]
+ }]
+ }
+ }
+ podAntiAffinity = {}
+ # podAntiAffinity = {
+ # preferredDuringSchedulingIgnoredDuringExecution = [{
+ # weight = 100
+ # podAffinityTerm = {
+ # labelSelector = {
+ # match_labels = {
+ # "app.kubernetes.io/name" = local.chart_name
+ # "app.kubernetes.io/part-of" = local.chart_name
+ # }
+ # }
+ # topologyKey = "kubernetes.io/hostname"
+ # }
+ # }]
+ # }
+ }
}
module "default-ws" {
- source = "../../../../../modules/k8s/bootstrap/coder-provisioner"
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- coder_organization_name = "coder"
-
- namespace = "coder-ws"
- image_repo = var.image_repo
- image_tag = var.image_tag
- ws_service_account_name = "coder-ws"
- ws_service_account_labels = local.service_account_labels
- provisioner_service_account_name = "coder"
- replica_count = 6
- primary_access_url = var.coder_access_url
- env_vars = {
- CODER_PROMETHEUS_ENABLE = "true"
- CODER_PROMETHEUS_COLLECT_AGENT_STATS = "true"
- CODER_PROMETHEUS_COLLECT_DB_METRICS = "true"
+
+ source = "../../../../../modules/k8s/bootstrap/coder-provisioner"
+
+ release_name = local.release_name
+ chart_version = var.addon_version
+ chart_name = local.chart_name
+ cluster_name = data.aws_eks_cluster.this.id
+ cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
+
+ namespace = "coder-ws"
+
+ coder = {
+ access_url = var.coder_access_url
+ org_name = "coder"
+ image_repo = var.image_repo
+ image_tag = var.image_tag
+ rep_cnt = 1
+ ws_extra_rules = [{
+ apiGroups = [""]
+ resources = ["configmaps"]
+ verbs = [
+ "create",
+ "delete",
+ "deletecollection",
+ "get",
+ "list",
+ "patch",
+ "update",
+ "watch"
+ ]
+ }]
+ env_vars = {
+ CODER_PROMETHEUS_ENABLE = "true"
+ CODER_PROMETHEUS_COLLECT_AGENT_STATS = "true"
+ CODER_PROMETHEUS_COLLECT_DB_METRICS = "true"
+ # TF_VAR_namespace = "coder-ws"
+ }
+ }
+
+ svc_acc = {
+ create = true
+ name = "coder"
}
- node_selector = local.node_selector
- tolerations = local.tolerations
+
+ node_selector = local.node_selector
+ tolerations = local.tolerations
+ topology_spread = local.topology_spread
+ affinity = local.affinity
}
module "experiment-ws" {
+
source = "../../../../../modules/k8s/bootstrap/coder-provisioner"
+ release_name = local.release_name
+ chart_version = var.addon_version
+ chart_name = local.chart_name
cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- coder_organization_name = "experiment"
-
- namespace = "coder-ws-experiment"
- image_repo = var.image_repo
- image_tag = var.image_tag
- ws_service_account_name = "coder-ws-experiment"
- ws_service_account_labels = local.service_account_labels
- provisioner_service_account_name = "coder"
- replica_count = 2
- primary_access_url = var.coder_access_url
- env_vars = {
- CODER_PROMETHEUS_ENABLE = "false"
- CODER_PROMETHEUS_COLLECT_AGENT_STATS = "false"
- CODER_PROMETHEUS_COLLECT_DB_METRICS = "false"
+ cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
+
+ namespace = "coder-ws-experiment"
+
+ coder = {
+ access_url = var.coder_access_url
+ org_name = "experiment"
+ image_repo = var.image_repo
+ image_tag = var.image_tag
+ rep_cnt = 1
+ ws_extra_rules = [{
+ apiGroups = [""]
+ resources = ["configmaps"]
+ verbs = [
+ "create",
+ "delete",
+ "deletecollection",
+ "get",
+ "list",
+ "patch",
+ "update",
+ "watch"
+ ]
+ },{
+ apiGroups = [""]
+ resources = ["serviceaccounts"]
+ verbs = [
+ "create",
+ "delete",
+ "deletecollection",
+ "get",
+ "list",
+ "patch",
+ "update",
+ "watch"
+ ]
+ },{
+ apiGroups = ["rbac.authorization.k8s.io"]
+ resources = ["clusterrolebindings"]
+ verbs = [
+ "create",
+ "delete",
+ "deletecollection",
+ "get",
+ "list",
+ "patch",
+ "update",
+ "watch"
+ ]
+ }]
+ env_vars = {
+ CODER_PROMETHEUS_ENABLE = "true"
+ CODER_PROMETHEUS_COLLECT_AGENT_STATS = "true"
+ CODER_PROMETHEUS_COLLECT_DB_METRICS = "true"
+ # TF_VAR_namespace = "coder-ws-experiment"
+ }
+ }
+
+ svc_acc = {
+ create = true
+ name = "coder"
}
- node_selector = local.node_selector
- tolerations = local.tolerations
+
+ node_selector = local.node_selector
+ tolerations = local.tolerations
+ topology_spread = local.topology_spread
+ affinity = local.affinity
+}
+
+data "aws_iam_policy_document" "eks" {
+ statement {
+ effect = "Allow"
+ actions = [
+ "eks:*",
+ "iam:*"
+ ]
+ resources = ["*"]
+ }
+}
+
+module "eks-admin-policy" {
+ source = "../../../../../modules/security/policy"
+ name = "demo-ws-eks-policy"
+ path = "/"
+ description = "Coder External Demo-Provisioner Policy (EKS)"
+ policy_json = data.aws_iam_policy_document.eks.json
}
module "demo-ws" {
+
source = "../../../../../modules/k8s/bootstrap/coder-provisioner"
+ release_name = local.release_name
+ chart_version = var.addon_version
+ chart_name = local.chart_name
cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- coder_organization_name = "demo"
-
- namespace = "coder-ws-demo"
- image_repo = var.image_repo
- image_tag = var.image_tag
- ws_service_account_name = "coder-ws-demo"
- ws_service_account_labels = local.service_account_labels
- provisioner_service_account_name = "coder"
- replica_count = 2
- primary_access_url = var.coder_access_url
- env_vars = {
- CODER_PROMETHEUS_ENABLE = "true"
- CODER_PROMETHEUS_COLLECT_AGENT_STATS = "true"
- CODER_PROMETHEUS_COLLECT_DB_METRICS = "true"
+ cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
+
+ namespace = "coder-ws-demo"
+
+ coder = {
+ access_url = var.coder_access_url
+ org_name = "demo"
+ image_repo = var.image_repo
+ image_tag = var.image_tag
+ rep_cnt = 1
+ ws_extra_rules = [{
+ apiGroups = [""]
+ resources = ["configmaps"]
+ verbs = [
+ "create",
+ "delete",
+ "deletecollection",
+ "get",
+ "list",
+ "patch",
+ "update",
+ "watch"
+ ]
+ },{
+ apiGroups = [""]
+ resources = ["serviceaccounts"]
+ verbs = [
+ "create",
+ "delete",
+ "deletecollection",
+ "get",
+ "list",
+ "patch",
+ "update",
+ "watch"
+ ]
+ },{
+ apiGroups = ["rbac.authorization.k8s.io"]
+ resources = ["clusterrolebindings"]
+ verbs = [
+ "create",
+ "delete",
+ "deletecollection",
+ "get",
+ "list",
+ "patch",
+ "update",
+ "watch"
+ ]
+ }]
+ env_vars = {
+ CODER_PROMETHEUS_ENABLE = "true"
+ CODER_PROMETHEUS_COLLECT_AGENT_STATS = "true"
+ CODER_PROMETHEUS_COLLECT_DB_METRICS = "true"
+ # TF_VAR_namespace = "coder-ws-experiment"
+ }
}
- node_selector = local.node_selector
- tolerations = local.tolerations
+
+ svc_acc = {
+ create = true
+ name = "coder"
+ iam_policy_arns = {
+ "EKSAdminPolicy" = module.eks-admin-policy.policy_arn
+ }
+ }
+
+ node_selector = local.node_selector
+ tolerations = local.tolerations
+ topology_spread = local.topology_spread
+ affinity = local.affinity
+}
+
+# resource "kubernetes_cluster_role_v1" "coder-ws-demo" {
+# metadata {
+# name = "coder-ws-demo"
+# }
+
+# rule {
+# api_groups = ["rbac.authorization.k8s.io"]
+# resources = ["clusterrolebindings"]
+# verbs = [
+# "create",
+# "delete",
+# "deletecollection",
+# "get",
+# "list",
+# "patch",
+# "update",
+# "watch"
+# ]
+# }
+# }
+
+# resource "kubernetes_cluster_role_binding_v1" "coder-cluster-workspace-perms" {
+
+# depends_on = [module.demo-ws]
+
+# metadata {
+# name = "coder-cluster-workspace-perms"
+# }
+
+# subject {
+# kind = "ServiceAccount"
+# name = "coder"
+# namespace = "coder-ws-demo"
+# }
+
+# role_ref {
+# api_group = "rbac.authorization.k8s.io"
+# kind = "ClusterRole"
+# name = kubernetes_cluster_role_v1.coder-ws-demo.metadata[0].name
+# }
+# }
+
+module "coder-logstream-kube" {
+
+ source = "../../../../../modules/k8s/bootstrap/coder-logstream"
+
+ release_name = "coder-logstream-kube"
+ chart_version = "0.0.15"
+ chart_name = "coder-logstream-kube"
+
+ namespace = "coder-logstream-kube"
+ image_tag = "v0.0.15"
+
+ coder = {
+ access_url = var.coder_access_url
+ ws_ns = ["coder-ws", "coder-ws-demo", "coder-ws-experiment"]
+ }
+
+ tolerations = [{
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }, {
+ key = "dedicated"
+ value = "general"
+ effect = "NoSchedule"
+ }]
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [
+ {
+ key = "eks.amazonaws.com/compute-type",
+ operator = "In",
+ values = ["auto"]
+ }
+ ]
+ }]
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/coder-ws/terragrunt.hcl b/infra/aws/us-east-2/k8s/coder-ws/terragrunt.hcl
new file mode 100644
index 0000000..bc4bd7c
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/coder-ws/terragrunt.hcl
@@ -0,0 +1,30 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks",
+ "../litellm",
+ "../coder-server",
+ "../kyverno",
+ "../other" # Deploy's auxillary manifests
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ coder_access_url=include.root.locals.CODER_DOMAIN_NAME
+ coder_admin_email=include.root.locals.CODER_EMAIL
+ coder_admin_password=include.root.locals.CODER_PASSWORD
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ image_repo=include.root.locals.CODER_IMAGE_REPO
+ image_tag=include.root.locals.CODER_IMAGE_TAG
+ addon_version=include.root.locals.CODER_ADDON_VERSION
+ logstream_addon_version=include.root.locals.CODER_LOGSTREAM_ADDON_VERSION
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/coder-ws/variables.tf b/infra/aws/us-east-2/k8s/coder-ws/variables.tf
new file mode 100644
index 0000000..edc2d5d
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/coder-ws/variables.tf
@@ -0,0 +1,45 @@
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "cluster_name" {
+ type = string
+}
+
+variable "addon_version" {
+ type = string
+ default = "2.23.0"
+}
+
+variable "logstream_addon_version" {
+ type = string
+ default = "0.0.11"
+}
+
+variable "coder_access_url" {
+ type = string
+}
+
+variable "image_repo" {
+ type = string
+ sensitive = true
+}
+
+variable "image_tag" {
+ type = string
+ default = "latest"
+}
+
+variable "coder_admin_email" {
+ type = string
+}
+
+variable "coder_admin_password" {
+ type = string
+ sensitive = true
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/ebs-controller/main.tf b/infra/aws/us-east-2/k8s/ebs-controller/main.tf
deleted file mode 100644
index df1252c..0000000
--- a/infra/aws/us-east-2/k8s/ebs-controller/main.tf
+++ /dev/null
@@ -1,78 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "addon_version" {
- type = string
- default = "2.22.1"
-}
-
-variable "addon_namespace" {
- type = string
- default = "default"
-}
-
-variable "addon_replace" {
- type = bool
- default = false
-}
-
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
-
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
-
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
-
-provider "helm" {
- kubernetes = {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
- }
-}
-
-module "ebs-controller" {
- source = "../../../../../modules/k8s/bootstrap/ebs-controller"
- cluster_name = data.aws_eks_cluster.this.name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- namespace = var.addon_namespace
- chart_version = var.addon_version
- replace = var.addon_replace
-}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/ebs-csi/main.tf b/infra/aws/us-east-2/k8s/ebs-csi/main.tf
new file mode 100644
index 0000000..8cc6fbb
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/ebs-csi/main.tf
@@ -0,0 +1,85 @@
+provider "aws" {
+ region = var.region
+ profile = var.profile
+}
+
+data "aws_eks_cluster" "this" {
+ name = var.cluster_name
+}
+
+data "aws_eks_cluster_auth" "this" {
+ name = var.cluster_name
+}
+
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
+}
+
+provider "helm" {
+ kubernetes = {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+ }
+}
+
+module "ebs-controller" {
+ source = "../../../../../modules/k8s/bootstrap/ebs-csi"
+ cluster_name = data.aws_eks_cluster.this.name
+ cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
+
+ namespace = var.addon_namespace
+ chart_version = var.addon_version
+ replace = var.addon_replace
+
+ tolerations = [{
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }, {
+ key = "dedicated"
+ value = "general"
+ effect = "NoSchedule"
+ }]
+ topology_spread = [{
+ topologyKey = "topology.kubernetes.io/zone"
+ maxSkew = 1
+ whenUnsatisfiable = "ScheduleAnyway"
+ }]
+ affinity = {
+ nodeAffinity = {
+ preferredDuringSchedulingIgnoredDuringExecution = []
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [
+ {
+ key = "eks.amazonaws.com/compute-type",
+ operator = "In",
+ values = ["auto"]
+ }
+ ]
+ }]
+ }
+ }
+ podAntiAffinity = {
+ preferredDuringSchedulingIgnoredDuringExecution = [{
+ podAffinityTerm = {
+ topologyKey = "topology.kubernetes.io/zone"
+ labelSelector = {
+ matchLabels = {
+ "app" = "ebs-csi-controller"
+ }
+ }
+ }
+ weight = 100
+ }]
+ requiredDuringSchedulingIgnoredDuringExecution = [{
+ topologyKey = "kubernetes.io/hostname"
+ labelSelector = {
+ matchLabels = {
+ "app" = "ebs-csi-controller"
+ }
+ }
+ }]
+ }
+ }
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/ebs-csi/terragrunt.hcl b/infra/aws/us-east-2/k8s/ebs-csi/terragrunt.hcl
new file mode 100644
index 0000000..2a314ed
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/ebs-csi/terragrunt.hcl
@@ -0,0 +1,21 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks"
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ addon_namespace=include.root.locals.EBS_ADDON_NAMESPACE
+ addon_version=include.root.locals.EBS_ADDON_VERSION
+ addon_replace=include.root.locals.EBS_ADDON_REPLACE
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/ebs-csi/variables.tf b/infra/aws/us-east-2/k8s/ebs-csi/variables.tf
new file mode 100644
index 0000000..dd4d169
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/ebs-csi/variables.tf
@@ -0,0 +1,27 @@
+variable "cluster_name" {
+ type = string
+}
+
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "addon_version" {
+ type = string
+ default = "2.22.1"
+}
+
+variable "addon_namespace" {
+ type = string
+ default = "default"
+}
+
+variable "addon_replace" {
+ type = bool
+ default = false
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/efs-controller/main.tf b/infra/aws/us-east-2/k8s/efs-controller/main.tf
deleted file mode 100644
index a5e2eb2..0000000
--- a/infra/aws/us-east-2/k8s/efs-controller/main.tf
+++ /dev/null
@@ -1,78 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "addon_version" {
- type = string
- default = "3.2.4"
-}
-
-variable "addon_namespace" {
- type = string
- default = "default"
-}
-
-variable "addon_replace" {
- type = bool
- default = false
-}
-
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
-
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
-
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
-
-provider "helm" {
- kubernetes = {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
- }
-}
-
-module "efs-controller" {
- source = "../../../../../modules/k8s/bootstrap/efs-controller"
- cluster_name = data.aws_eks_cluster.this.name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- namespace = var.addon_namespace
- chart_version = var.addon_version
- replace = var.addon_replace
-}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/efs-controller/pv.yaml b/infra/aws/us-east-2/k8s/efs-controller/pv.yaml
deleted file mode 100644
index 9b0a19f..0000000
--- a/infra/aws/us-east-2/k8s/efs-controller/pv.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-apiVersion: v1
-kind: PersistentVolume
-metadata:
- name: efs-pv
-spec:
- capacity:
- storage: 10Gi
- volumeMode: Filesystem
- accessModes:
- - ReadWriteMany
- persistentVolumeReclaimPolicy: Retain
- storageClassName: efs-sc
- csi:
- driver: efs.csi.aws.com
- volumeHandle: "fs-0243b71fd52de9c0d"
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/efs-controller/storageclass.yaml b/infra/aws/us-east-2/k8s/efs-controller/storageclass.yaml
deleted file mode 100644
index 5878fda..0000000
--- a/infra/aws/us-east-2/k8s/efs-controller/storageclass.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-kind: StorageClass
-apiVersion: storage.k8s.io/v1
-metadata:
- name: efs-sc
-provisioner: efs.csi.aws.com
-parameters:
- provisioningMode: efs-ap
- fileSystemId: fs-0243b71fd52de9c0d
- directoryPerms: "777"
- # gidRangeStart: "1000" # optional
- # gidRangeEnd: "2000" # optional
- uid: "0"
- gid: "1000"
- basePath: "/dynamic_provisioning"
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/fetch-and-store/main.tf b/infra/aws/us-east-2/k8s/fetch-and-store/main.tf
deleted file mode 100644
index 49194c0..0000000
--- a/infra/aws/us-east-2/k8s/fetch-and-store/main.tf
+++ /dev/null
@@ -1,78 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
- sensitive = true
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "addon_namespace" {
- type = string
- default = "kube-system"
-}
-
-variable "addon_version" {
- type = string
- default = "3.13.0"
-}
-
-variable "image_repo" {
- type = string
- sensitive = true
-}
-
-variable "image_tag" {
- type = string
- sensitive = true
-}
-
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
-
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
-
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
-
-provider "kubernetes" {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
-}
-
-module "fetch-and-store" {
- source = "../../../../../modules/k8s/bootstrap/fetch-and-store"
-
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
- namespace = var.addon_namespace
- image_repo = var.image_repo
- image_tag = var.image_tag
-}
diff --git a/infra/aws/us-east-2/k8s/karpenter/main.tf b/infra/aws/us-east-2/k8s/karpenter/main.tf
index 5e1a8a7..cbbd788 100644
--- a/infra/aws/us-east-2/k8s/karpenter/main.tf
+++ b/infra/aws/us-east-2/k8s/karpenter/main.tf
@@ -1,48 +1,6 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "addon_version" {
- type = string
-}
-
-variable "addon_namespace" {
- type = string
- default = "default"
-}
-
provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
+ region = var.region
+ profile = var.profile
}
data "aws_eks_cluster" "this" {
@@ -53,6 +11,12 @@ data "aws_eks_cluster_auth" "this" {
name = var.cluster_name
}
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
+}
+
+data "aws_caller_identity" "me" {}
+
provider "helm" {
kubernetes = {
host = data.aws_eks_cluster.this.endpoint
@@ -101,126 +65,92 @@ locals {
}
}
-locals {
- nodepool_configs = [{
- name = "coder-server"
- node_labels = merge(local.global_node_labels, {
- "node.coder.io/name" = "coder"
- "node.coder.io/part-of" = "coder"
- "node.coder.io/used-for" = "coder-server"
- })
- node_taints = [{
- key = "dedicated"
- value = "coder-server"
- effect = "NoSchedule"
- }]
- node_requirements = concat(local.global_node_reqs, [{
- key = "node.kubernetes.io/instance-type"
- operator = "In"
- values = ["m5a.xlarge", "m6a.xlarge"]
- }])
- node_class_ref_name = "coder-server-class"
- }, {
- name = "coder-provisioner"
- node_labels = merge(local.global_node_labels, {
- "node.coder.io/name" = "coder"
- "node.coder.io/part-of" = "coder"
- "node.coder.io/used-for" = "coder-provisioner"
- })
- node_taints = [{
- key = "dedicated"
- value = "coder-provisioner"
- effect = "NoSchedule"
- }]
- node_requirements = concat(local.global_node_reqs, [{
- key = "node.kubernetes.io/instance-type"
- operator = "In"
- values = ["m5a.4xlarge", "m6a.4xlarge"]
- }])
- node_class_ref_name = "coder-provisioner-class"
- }, {
- name = "coder-ws-all"
- node_labels = merge(local.global_node_labels, {
- "node.coder.io/name" = "coder"
- "node.coder.io/part-of" = "coder"
- "node.coder.io/used-for" = "coder-ws-all"
- })
- node_taints = [{
- key = "dedicated"
- value = "coder-ws"
- effect = "NoSchedule"
- }]
- node_requirements = concat(local.global_node_reqs, [{
- key = "node.kubernetes.io/instance-type"
- operator = "In"
- values = ["c6a.32xlarge", "c5a.32xlarge"]
- }])
- node_class_ref_name = "coder-ws-class"
- disruption_consolidate_after = "30m"
- }]
+data "aws_iam_policy_document" "ecr-mirror" {
+
+ statement {
+ effect = "Allow"
+ actions = ["ecr:CreateRepository"]
+ resources = ["*"]
+ }
+
+ statement {
+ effect = "Allow"
+ actions = ["ecr:GetAuthorizationToken"]
+ resources = ["*"]
+ }
+
+ statement {
+ effect = "Allow"
+ actions = [
+ "ecr:BatchImportUpstreamImage",
+ "ecr:BatchGetImage",
+ "ecr:GetDownloadUrlForLayer"
+ ]
+ resources = [ "arn:aws:ecr:${var.region}:${data.aws_caller_identity.me.account_id}:repository/cache/*" ]
+ }
+}
+
+resource "aws_iam_policy" "ecr-mirror" {
+ name_prefix = "ecr-mirror"
+ description = "Allows ECR pull-through cache automation including repository creation"
+ path = "/${var.region}/kptr/"
+ policy = data.aws_iam_policy_document.ecr-mirror.json
}
module "karpenter-addon" {
+
source = "../../../../../modules/k8s/bootstrap/karpenter"
cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
+ cluster_oidc_provider = trimprefix(data.aws_iam_openid_connect_provider.this.url, "https://")
+ cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
namespace = var.addon_namespace
chart_version = var.addon_version
- node_selector = {
- "node.amazonaws.io/managed-by" : "asg"
+
+ node_selector = {}
+ tolerations = [{
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }]
+ topology_spread = []
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [
+ {
+ key = "eks.amazonaws.com/compute-type",
+ operator = "In",
+ values = ["auto"]
+ }
+ ]
+ }]
+ }
+ }
+ podAntiAffinity = {
+ preferredDuringSchedulingIgnoredDuringExecution = [{
+ weight = 100
+ podAffinityTerm = {
+ labelSelector = {
+ matchExpressions = [{
+ key = "app.kubernetes.io/name"
+ operator = "In"
+ values = ["karpenter"]
+ }]
+ }
+ topologyKey = "kubernetes.io/hostname"
+ }
+ }]
+ }
}
+
+ iam_role_use_name_prefix = true
+ node_iam_role_use_name_prefix = true
+ replicas = 2
karpenter_controller_role_policies = {
"AmazonEFSCSIDriverPolicy" = "arn:aws:iam::aws:policy/service-role/AmazonEFSCSIDriverPolicy"
}
- ec2nodeclass_configs = [{
- name = "coder-server-class"
- subnet_selector_tags = local.provisioner_subnet_tags
- sg_selector_tags = local.provisioner_sg_tags
- }, {
- name = "coder-ws-class"
- subnet_selector_tags = local.ws_all_subnet_tags
- sg_selector_tags = local.ws_all_sg_tags
- ami_alias = "al2023@latest" # Use /dev/xvda
- user_data = <<-EOF
- apiVersion: node.eks.aws/v1alpha1
- kind: NodeConfig
- spec:
- kubelet:
- config:
- registryPullQPS: 30
- EOF
- block_device_mappings = [{
- device_name = "/dev/xvda"
- ebs = {
- volume_size = "500Gi"
- volume_type = "gp3"
- }
- }]
- # # Bottlerocket configs.
- # ami_alias = "bottlerocket@latest" # Use /dev/xvda + /dev/xvdb
- # user_data = <<-EOF
- # [settings.kubernetes]
- # 'registry-qps' = 30
- # [settings.kernel.sysctl]
- # 'user.max_user_namespaces' = "16384"
- # EOF
- # block_device_mappings = [{
- # device_name = "/dev/xvda"
- # ebs = {
- # volume_size = "500Gi"
- # volume_type = "gp3"
- # }
- # }, {
- # device_name = "/dev/xvdb"
- # ebs = {
- # volume_size = "500Gi"
- # volume_type = "gp3"
- # }
- # }]
- }, {
- name = "coder-provisioner-class"
- subnet_selector_tags = local.provisioner_subnet_tags
- sg_selector_tags = local.provisioner_sg_tags
- }]
+ karpenter_node_role_policies = {
+ "ECRMirrorPolicy" = aws_iam_policy.ecr-mirror.arn
+ }
}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/karpenter/terragrunt.hcl b/infra/aws/us-east-2/k8s/karpenter/terragrunt.hcl
new file mode 100644
index 0000000..2a69c09
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/karpenter/terragrunt.hcl
@@ -0,0 +1,20 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks"
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ addon_namespace=include.root.locals.KPTR_ADDON_NAMESPACE
+ addon_version=include.root.locals.KPTR_ADDON_VERSION
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/karpenter/variables.tf b/infra/aws/us-east-2/k8s/karpenter/variables.tf
new file mode 100644
index 0000000..442aaa5
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/karpenter/variables.tf
@@ -0,0 +1,21 @@
+variable "cluster_name" {
+ type = string
+}
+
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "addon_version" {
+ type = string
+}
+
+variable "addon_namespace" {
+ type = string
+ default = "default"
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/kyverno/main.tf b/infra/aws/us-east-2/k8s/kyverno/main.tf
new file mode 100644
index 0000000..b686e6f
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/kyverno/main.tf
@@ -0,0 +1,116 @@
+provider "aws" {
+ region = var.region
+ profile = var.profile
+}
+
+data "aws_eks_cluster" "this" {
+ name = var.cluster_name
+}
+
+data "aws_eks_cluster_auth" "this" {
+ name = var.cluster_name
+}
+
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
+}
+
+provider "helm" {
+ kubernetes = {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+ }
+}
+
+provider "kubernetes" {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+}
+
+resource "kubernetes_namespace_v1" "this" {
+
+ count = var.create_namespace ? 1 : 0
+
+ metadata {
+ name = var.namespace
+ }
+}
+
+locals {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [{
+ key = "karpenter.sh/nodepool"
+ operator = "In"
+ values = ["system"]
+ }]
+ }]
+ }
+ }
+}
+
+resource "helm_release" "kyverno" {
+ name = var.release_name
+ namespace = try(kubernetes_namespace_v1.this[0].metadata[0].name, var.namespace)
+ chart = var.chart_name
+ repository = "https://kyverno.github.io/kyverno/"
+ create_namespace = false
+ upgrade_install = true
+ skip_crds = false
+ wait = true
+ wait_for_jobs = true
+ version = var.chart_version
+ timeout = 600
+
+ values = [yamlencode({
+
+ global = {
+ tolerations = [{
+ effect = "NoSchedule"
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }]
+ }
+
+ config = {
+ defaultRegistry = "docker.io"
+ enableDefaultRegistryMutation = true
+ webhooks = {
+ namespacesSelector = {
+ matchExpressions = [{
+ key = "kubernetes.io/metadata.name"
+ operator = "NotIn"
+ values = [
+ "kube-system",
+ ]
+ }]
+ }
+ }
+ }
+
+ crds = {
+ migration = {
+ nodeAffinity = local.nodeAffinity
+ }
+ }
+ admissionController = {
+ replicas = 3
+ nodeAffinity = local.nodeAffinity
+ }
+ backgroundController = {
+ replicas = 2
+ nodeAffinity = local.nodeAffinity
+ }
+ cleanupController = {
+ replicas = 2
+ nodeAffinity = local.nodeAffinity
+ }
+ reportsController = {
+ replicas = 2
+ nodeAffinity = local.nodeAffinity
+ }
+ })]
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/kyverno/terragrunt.hcl b/infra/aws/us-east-2/k8s/kyverno/terragrunt.hcl
new file mode 100644
index 0000000..1d7058d
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/kyverno/terragrunt.hcl
@@ -0,0 +1,20 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks"
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ # addon_namespace=include.root.locals.KYVERNO_ADDON_NAMESPACE
+ # addon_version=include.root.locals.KYVERNO_ADDON_VERSION
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/kyverno/variables.tf b/infra/aws/us-east-2/k8s/kyverno/variables.tf
new file mode 100644
index 0000000..4148cbc
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/kyverno/variables.tf
@@ -0,0 +1,37 @@
+variable "cluster_name" {
+ type = string
+}
+
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "release_name" {
+ type = string
+ default = "kyverno"
+}
+
+variable "chart_name" {
+ type = string
+ default = "kyverno"
+}
+
+variable "chart_version" {
+ type = string
+ default = "3.7.1"
+}
+
+variable "create_namespace" {
+ type = bool
+ default = true
+}
+
+variable "namespace" {
+ type = string
+ default = "kyverno"
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/lb-controller/main.tf b/infra/aws/us-east-2/k8s/lb-controller/main.tf
index b0b7e2d..a51a8b1 100644
--- a/infra/aws/us-east-2/k8s/lb-controller/main.tf
+++ b/infra/aws/us-east-2/k8s/lb-controller/main.tf
@@ -1,54 +1,6 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "addon_namespace" {
- type = string
- default = "default"
-}
-
-variable "addon_version" {
- type = string
- default = "1.13.2"
-}
-
-variable "use_cert_manager" {
- type = bool
- default = false
-}
-
provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
+ region = var.region
+ profile = var.profile
}
data "aws_eks_cluster" "this" {
@@ -59,6 +11,16 @@ data "aws_eks_cluster_auth" "this" {
name = var.cluster_name
}
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
+}
+
+data "aws_vpc" "this" {
+ tags = {
+ Name = var.vpc_name
+ }
+}
+
provider "helm" {
kubernetes = {
host = data.aws_eks_cluster.this.endpoint
@@ -73,18 +35,67 @@ provider "kubernetes" {
token = data.aws_eks_cluster_auth.this.token
}
+locals {
+ release_name = "aws-load-balancer-controller"
+}
+
module "lb-controller" {
source = "../../../../../modules/k8s/bootstrap/lb-controller"
cluster_name = data.aws_eks_cluster.this.name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
+ cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
+ release_name = local.release_name
namespace = var.addon_namespace
chart_version = var.addon_version
enable_cert_manager = var.use_cert_manager
+ vpc_id = data.aws_vpc.this.id
+
+ tolerations = [{
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }]
+ topology_spread = [{
+ topologyKey = "topology.kubernetes.io/zone"
+ maxSkew = 1
+ whenUnsatisfiable = "ScheduleAnyway"
+ }]
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [
+ {
+ key = "eks.amazonaws.com/compute-type",
+ operator = "In",
+ values = ["auto"]
+ }
+ ]
+ }]
+ }
+ }
+ podAntiAffinity = {
+ preferredDuringSchedulingIgnoredDuringExecution = [{
+ weight = 100
+ podAffinityTerm = {
+ topologyKey = "topology.kubernetes.io/zone"
+ labelSelector = {
+ matchLabels = {
+ "app.kubernetes.io/name" = local.release_name
+ }
+ }
+ }
+ }]
+ requiredDuringSchedulingIgnoredDuringExecution = [{
+ topologyKey = "kubernetes.io/hostname"
+ labelSelector = {
+ matchLabels = {
+ "app.kubernetes.io/name" = local.release_name
+ }
+ }
+ }]
+ }
+ }
service_target_eni_sg_tags = {
Name = "aidemo-eks-node"
}
- cluster_asg_node_labels = { # var.karpenter_node_labels
- "node.amazonaws.io/managed-by" = "asg"
- }
}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/lb-controller/terragrunt.hcl b/infra/aws/us-east-2/k8s/lb-controller/terragrunt.hcl
new file mode 100644
index 0000000..ee0e6fb
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/lb-controller/terragrunt.hcl
@@ -0,0 +1,22 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks"
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ addon_namespace=include.root.locals.LB_ADDON_NAMESPACE
+ addon_version=include.root.locals.LB_ADDON_VERSION
+ vpc_name=include.root.locals.CODER_VPC_NAME
+ use_cert_manager=include.root.locals.LB_ADDON_USE_CERTMGR
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/lb-controller/variables.tf b/infra/aws/us-east-2/k8s/lb-controller/variables.tf
new file mode 100644
index 0000000..1b505c0
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/lb-controller/variables.tf
@@ -0,0 +1,31 @@
+variable "cluster_name" {
+ type = string
+}
+
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "addon_namespace" {
+ type = string
+ default = "default"
+}
+
+variable "addon_version" {
+ type = string
+ default = "1.13.2"
+}
+
+variable "vpc_name" {
+ type = string
+}
+
+variable "use_cert_manager" {
+ type = bool
+ default = false
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/litellm/main.tf b/infra/aws/us-east-2/k8s/litellm/main.tf
index a759c7f..43ac7f6 100644
--- a/infra/aws/us-east-2/k8s/litellm/main.tf
+++ b/infra/aws/us-east-2/k8s/litellm/main.tf
@@ -1,151 +1,442 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- }
- backend "s3" {}
+provider "aws" {
+ region = var.region
+ profile = var.profile
}
-variable "cluster_name" {
- type = string
+data "aws_eks_cluster" "this" {
+ name = var.cluster_name
}
-variable "cluster_region" {
- type = string
+data "aws_eks_cluster_auth" "this" {
+ name = var.cluster_name
}
-variable "cluster_oidc_provider_arn" {
- type = string
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
}
-variable "cluster_profile" {
- type = string
- default = "default"
+data "aws_db_instance" "litellm" {
+ db_instance_identifier = var.db_rds_id
}
-variable "addon_namespace" {
- type = string
- default = "kube-system"
-}
+data "aws_region" "this" {}
-variable "aws_ingress_certificate_arn" {
- type = string
- sensitive = true
+provider "kubernetes" {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
}
-variable "db_url" {
- type = string
- sensitive = true
+provider "helm" {
+ kubernetes = {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+ }
}
-variable "litellm_salt_key" {
- type = string
- sensitive = true
+locals {
+ common_name = replace(replace(var.host_name, "https://", ""), "http://", "")
+ ssl_vol_friendly_name = replace(local.common_name, ".", "-")
}
-variable "litellm_master_key" {
- type = string
- sensitive = true
-}
+# resource "kubernetes_manifest" "cert" {
-variable "redis_host" {
- type = string
- sensitive = true
-}
+# field_manager {
+# force_conflicts = true
+# }
+# manifest = {
+# apiVersion = "cert-manager.io/v1"
+# kind = "Certificate"
+# metadata = {
+# labels = {} # var.cert_labels
+# name = local.ssl_vol_friendly_name
+# namespace = module.litellm.namespace
+# }
+# spec = {
+# secretName = local.ssl_vol_friendly_name
+# commonName = local.common_name
+# dnsNames = [local.common_name]
+# duration = "${90 * 24}h"
+# renewBefore = "8h"
+# additionalOutputFormats = [{
+# type = "CombinedPEM"
+# }, {
+# type = "DER"
+# }]
+# issuerRef = {
+# kind = "ClusterIssuer"
+# name = "issuer"
+# }
+# }
+# }
+# }
-variable "redis_password" {
- type = string
- sensitive = true
-}
+# resource "kubernetes_secret_v1" "gcloud" {
+# metadata {
+# name = "gcloud-auth"
+# namespace = module.litellm.namespace
+# labels = {}
+# }
+# data = {
+# "service_account.json" = var.gcloud_auth
+# }
+# }
-variable "gcloud_auth" {
- type = string
- sensitive = true
+locals {
+ azs = var.azs
+ pub_subs = [for az in local.azs : "${var.vpc_name}-public-${data.aws_region.this.region}${az}"]
+ # App port is actually being ignored on the LiteLLM app-level. Statically set to 4000
+ app_port = 4000
+ release_name = "litellm"
+ chart_name = "litellm-helm"
+ namespace = "litellm"
}
-variable "host_name" {
- type = string
- sensitive = true
-}
+# resource "kubernetes_pod_disruption_budget_v1" "litellm" {
+# metadata {
+# name = local.release_name
+# namespace = module.litellm.namespace
+# }
+# spec {
+# # Avoid disrupting ongoing connections.
+# max_unavailable = 1
+# selector {
+# match_labels = {
+# "app.kubernetes.io/instance" = local.release_name
+# "app.kubernetes.io/name" = local.release_name
+# }
+# }
+# }
+# }
-variable "rotate_key_image_repo" {
- type = string
- sensitive = true
-}
+# resource "aws_eip" "litellm" {
+# count = length(local.pub_subs)
+# domain = "vpc"
+# public_ipv4_pool = "amazon"
+# tags = {
+# Name = "litellm-eip-${count.index}"
+# }
+# }
-variable "rotate_key_image_tag" {
- type = string
- sensitive = true
-}
+# module "litellm" {
+# source = "../../../../../modules/k8s/bootstrap/litellm"
-variable "aws_secret_id" {
- type = string
- sensitive = true
-}
+# release_name = local.release_name
+# chart_name = local.chart_name
+
+# image_config = {
+# repository = "ghcr.io/berriai/litellm-database"
+# # tag = "v1.82.0.patch5"
+# tag = "v1.83.7-stable.patch.1"
+# }
-variable "aws_secret_region" {
- type = string
- sensitive = true
-}
+# cluster_name = var.cluster_name
+# cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
+# namespace = var.addon_namespace
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
+# replicas = length(aws_eip.litellm.*.allocation_id)
+# autoscaling_min_replicas = length(aws_eip.litellm.*.allocation_id)
+# autoscaling_max_replicas = 12
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
+# resource_limit = {
+# cpu = "2"
+# memory = "2Gi"
+# }
+# resource_request = {
+# cpu = "500m"
+# memory = "2Gi"
+# }
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
+# litellm_master_key = var.litellm_master_key
-provider "kubernetes" {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
-}
+# service_lb_class = "service.k8s.aws/nlb"
+# svc_port = local.app_port
+# svc_annots = {
+# "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "ip"
+# "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
+# "service.beta.kubernetes.io/aws-load-balancer-attributes" = "deletion_protection.enabled=false,load_balancing.cross_zone.enabled=true"
+# "service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol" = "https"
+# "service.beta.kubernetes.io/aws-load-balancer-healthcheck-port" = "${local.app_port}"
+# "service.beta.kubernetes.io/aws-load-balancer-eip-allocations" = join(",", aws_eip.litellm.*.allocation_id)
+# "service.beta.kubernetes.io/aws-load-balancer-subnets" = join(",", local.pub_subs)
+# }
+# node_selector = {}
+# tolerations = [{
+# key = "platform"
+# value = "litellm"
+# effect = "NoSchedule"
+# }]
+# topology_spread = []
+# # topology_spread = [{
+# # maxSkew = 1
+# # topologyKey = "topology.kubernetes.io/zone"
+# # whenUnsatisfiable = "DoNotSchedule"
+# # labelSelector = {
+# # matchLabels = {
+# # "app.kubernetes.io/name" = local.release_name
+# # }
+# # }
+# # matchLabelKeys = [
+# # "app.kubernetes.io/instance"
+# # ]
+# # }]
+# affinity = {
+# nodeAffinity = {
+# requiredDuringSchedulingIgnoredDuringExecution = {
+# nodeSelectorTerms = [{
+# matchExpressions = [
+# {
+# key = "topology.kubernetes.io/zone"
+# operator = "In"
+# values = [for az in local.azs : "${data.aws_region.this.region}${az}"]
+# },
+# {
+# key = "node.coder.io/used-for",
+# operator = "In",
+# values = ["litellm"]
+# }
+# ]
+# }]
+# }
+# }
+# podAntiAffinity = {
+# preferredDuringSchedulingIgnoredDuringExecution = []
+# requiredDuringSchedulingIgnoredDuringExecution = []
+# # requiredDuringSchedulingIgnoredDuringExecution = [{
+# # labelSelector = {
+# # matchExpressions = [{
+# # key = "app.kubernetes.io/instance"
+# # operator = "In"
+# # values = [local.release_name]
+# # }]
+# # }
+# # topologyKey = "kubernetes.io/hostname"
+# # }]
+# }
+# }
-module "litellm" {
- source = "../../../../../modules/k8s/bootstrap/litellm"
-
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
- namespace = var.addon_namespace
- name = "litellm"
- host_name = var.host_name
- resource_limits = {
- cpu = "2"
- memory = "4Gi"
- }
- replicas = 8
- aws_ingress_certificate_arn = var.aws_ingress_certificate_arn
- db_url = var.db_url
- litellm_salt_key = var.litellm_salt_key
- litellm_master_key = var.litellm_master_key
- redis_host = var.redis_host
- redis_password = var.redis_password
- gcloud_auth = var.gcloud_auth
-}
+# db = {
+# use_existing = true
+# db_name = "litellm"
+# endpoint = split(":", data.aws_db_instance.litellm.endpoint)[0]
+# username = "litellm"
+# admin_password = var.db_admin_password
+# user_password = var.db_user_password
+# auth_type = "awsiamrds"
+# }
+
+# proxy_config = {
+# general_settings = {
+# master_key = "os.environ/PROXY_MASTER_KEY"
+
+# store_model_in_db = true
+# store_prompts_in_spend_logs = true
+# proxy_batch_write_at = 60
+# database_connection_pool_limit = 10
+
+# disable_error_logs = true
+# allow_requests_on_db_unavailable = true
+# }
+
+# litellm_settings = {
+# allowed_fails = 3
+# cooldown_time = 30
+# num_retries = 1
+# request_timeout = 45
+# set_verbose = true
+# json_logs = false
+# cache = false
+# }
+
+# model_list = [
+# {
+# model_name = "anthropic.claude-opus-4-5-20251101-v1:0"
+# litellm_params = {
+# max_parallel_requests = 50
+# model = "vertex_ai/claude-opus-4-5@20251101"
+# rpm = 450
+# tpm = 6000000
+# vertex_location = "us-east5"
+# vertex_project = "coder-vertex-demos"
+# vertex_credentials = "/tmp/gcloud/service_account.json"
+# }
+# },
+# {
+# model_name = "anthropic.claude-opus-4-5-20251101-v1:0"
+# litellm_params = {
+# max_parallel_requests = 50
+# model = "vertex_ai/claude-opus-4-5@20251101"
+# rpm = 450
+# tpm = 6000000
+# vertex_location = "europe-west1"
+# vertex_project = "coder-vertex-demos"
+# vertex_credentials = "/tmp/gcloud/service_account.json"
+# }
+# },
+# {
+# model_name = "anthropic.claude-haiku-4-5-20251001-v1:0"
+# litellm_params = {
+# max_parallel_requests = 50
+# model = "vertex_ai/claude-haiku-4-5@20251001"
+# rpm = 3000
+# tpm = 3000000
+# vertex_location = "us-east5"
+# vertex_project = "coder-vertex-demos"
+# vertex_credentials = "/tmp/gcloud/service_account.json"
+# }
+# },
+# {
+# model_name = "anthropic.claude-haiku-4-5-20251001-v1:0"
+# litellm_params = {
+# max_parallel_requests = 50
+# model = "vertex_ai/claude-haiku-4-5@20251001"
+# rpm = 3600
+# tpm = 3600000
+# vertex_location = "europe-west1"
+# vertex_project = "coder-vertex-demos"
+# vertex_credentials = "/tmp/gcloud/service_account.json"
+# }
+# },
+# {
+# model_name = "claude-opus-4-6"
+# litellm_params = {
+# max_parallel_requests = 50
+# model = "vertex_ai/claude-opus-4-6@default"
+# rpm = 450
+# tpm = 6000000
+# vertex_location = "us-east5"
+# vertex_project = "coder-vertex-demos"
+# vertex_credentials = "/tmp/gcloud/service_account.json"
+# }
+# },
+# {
+# model_name = "claude-opus-4-6"
+# litellm_params = {
+# max_parallel_requests = 50
+# model = "vertex_ai/claude-opus-4-6@default"
+# rpm = 450
+# tpm = 6000000
+# vertex_location = "europe-west1"
+# vertex_project = "coder-vertex-demos"
+# vertex_credentials = "/tmp/gcloud/service_account.json"
+# }
+# },
+# {
+# model_name = "claude-opus-4-5"
+# litellm_params = {
+# max_parallel_requests = 50
+# model = "vertex_ai/claude-opus-4-5@20251101"
+# rpm = 450
+# tpm = 6000000
+# vertex_location = "us-east5"
+# vertex_project = "coder-vertex-demos"
+# vertex_credentials = "/tmp/gcloud/service_account.json"
+# }
+# },
+# {
+# model_name = "claude-opus-4-5"
+# litellm_params = {
+# max_parallel_requests = 50
+# model = "vertex_ai/claude-opus-4-5@20251101"
+# rpm = 450
+# tpm = 6000000
+# vertex_location = "europe-west1"
+# vertex_project = "coder-vertex-demos"
+# vertex_credentials = "/tmp/gcloud/service_account.json"
+# }
+# },
+# {
+# model_name = "claude-sonnet-4-6"
+# litellm_params = {
+# max_parallel_requests = 50
+# model = "vertex_ai/claude-sonnet-4-6@default"
+# rpm = 450
+# tpm = 6000000
+# vertex_location = "us-east5"
+# vertex_project = "coder-vertex-demos"
+# vertex_credentials = "/tmp/gcloud/service_account.json"
+# }
+# },
+# {
+# model_name = "claude-sonnet-4-6"
+# litellm_params = {
+# max_parallel_requests = 50
+# model = "vertex_ai/claude-sonnet-4-6@default"
+# rpm = 450
+# tpm = 6000000
+# vertex_location = "europe-west1"
+# vertex_project = "coder-vertex-demos"
+# vertex_credentials = "/tmp/gcloud/service_account.json"
+# }
+# },
+# {
+# model_name = "claude-sonnet-4-5"
+# litellm_params = {
+# max_parallel_requests = 50
+# model = "vertex_ai/claude-sonnet-4-5@20250929"
+# rpm = 3000
+# tpm = 3000000
+# vertex_location = "us-east5"
+# vertex_project = "coder-vertex-demos"
+# vertex_credentials = "/tmp/gcloud/service_account.json"
+# }
+# },
+# {
+# model_name = "claude-sonnet-4-5"
+# litellm_params = {
+# max_parallel_requests = 50
+# model = "vertex_ai/claude-sonnet-4-5@20250929"
+# rpm = 3600
+# tpm = 3600000
+# vertex_location = "europe-west1"
+# vertex_project = "coder-vertex-demos"
+# vertex_credentials = "/tmp/gcloud/service_account.json"
+# }
+# },
+# {
+# model_name = "claude-haiku-4-5"
+# litellm_params = {
+# max_parallel_requests = 50
+# model = "vertex_ai/claude-haiku-4-5@20251001"
+# rpm = 3000
+# tpm = 3000000
+# vertex_location = "us-east5"
+# vertex_project = "coder-vertex-demos"
+# vertex_credentials = "/tmp/gcloud/service_account.json"
+# }
+# },
+# {
+# model_name = "claude-haiku-4-5"
+# litellm_params = {
+# max_parallel_requests = 50
+# model = "vertex_ai/claude-haiku-4-5@20251001"
+# rpm = 3600
+# tpm = 3600000
+# vertex_location = "europe-west1"
+# vertex_project = "coder-vertex-demos"
+# vertex_credentials = "/tmp/gcloud/service_account.json"
+# }
+# }
+# ]
+
+# router_settings = {
+# # enable_pre_call_checks = true
+# # fallbacks = [{"claude-opus-4-6": ["claude-opus-4-5"]}]
+# num_retries = 2
+# routing_strategy = "usage-based-routing-v2"
+
+# }
+# }
+
+# mount_ssl = {
+# enable = true
+# secret_name = kubernetes_manifest.cert.manifest.metadata.name
+# path = "/tmp/ssl"
+# }
-module "litellm-gen-key" {
- depends_on = [module.litellm]
- source = "../../../../../modules/k8s/bootstrap/litellm-generate-key"
-
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
- namespace = var.addon_namespace
- name = "rotate-key"
- image_repo = var.rotate_key_image_repo
- image_tag = var.rotate_key_image_tag
- litellm_create_secret = false
- litellm_url = "https://${var.host_name}"
- secret_id = var.aws_secret_id
- secret_region = var.aws_secret_region
-}
\ No newline at end of file
+# mounts = [{
+# secret_name = kubernetes_secret_v1.gcloud.metadata[0].name
+# path = "/tmp/gcloud/"
+# }]
+# }
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/litellm/terragrunt.hcl b/infra/aws/us-east-2/k8s/litellm/terragrunt.hcl
new file mode 100644
index 0000000..38f7c15
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/litellm/terragrunt.hcl
@@ -0,0 +1,35 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks",
+ "../../rds",
+ "../lb-controller",
+ "../cert-manager",
+ "../other" # Deploy's auxillary manifests
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ addon_namespace=include.root.locals.LITELLM_ADDON_NAMESPACE
+ addon_version=include.root.locals.LITELLM_ADDON_VERSION
+
+ host_name=include.root.locals.LITELLM_DOMAIN_NAME
+ vpc_name=include.root.locals.CODER_VPC_NAME
+ azs=include.root.locals.CODER_VPC_AZS
+
+ db_rds_id=include.root.locals.LITELLM_DB_RDS_ID
+ db_admin_password=include.root.locals.LITELLM_DB_ADMIN_PASSWORD
+ db_user_password=include.root.locals.LITELLM_DB_USER_PASSWORD
+
+ gcloud_auth=include.root.locals.LITELLM_GCLOUD_AUTH
+ litellm_master_key=include.root.locals.LITELLM_MASTER_KEY
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/litellm/variables.tf b/infra/aws/us-east-2/k8s/litellm/variables.tf
new file mode 100644
index 0000000..192cd55
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/litellm/variables.tf
@@ -0,0 +1,56 @@
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "cluster_name" {
+ type = string
+}
+
+variable "addon_namespace" {
+ type = string
+ default = "kube-system"
+}
+
+variable "db_rds_id" {
+ type = string
+ sensitive = true
+}
+
+variable "db_admin_password" {
+ type = string
+ sensitive = true
+}
+
+variable "db_user_password" {
+ type = string
+ sensitive = true
+}
+
+variable "litellm_master_key" {
+ type = string
+ sensitive = true
+}
+
+variable "gcloud_auth" {
+ type = string
+ sensitive = true
+}
+
+variable "host_name" {
+ type = string
+ sensitive = true
+}
+
+variable "vpc_name" {
+ type = string
+}
+
+variable "azs" {
+ type = list(string)
+ default = ["a", "b", "c"]
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/metrics-server/main.tf b/infra/aws/us-east-2/k8s/metrics-server/main.tf
index fe2967e..6d5cd99 100644
--- a/infra/aws/us-east-2/k8s/metrics-server/main.tf
+++ b/infra/aws/us-east-2/k8s/metrics-server/main.tf
@@ -1,42 +1,6 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 2.17.0"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "addon_namespace" {
- type = string
- default = "kube-system"
-}
-
-variable "addon_version" {
- type = string
- default = "3.13.0"
-}
-
provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
+ region = var.region
+ profile = var.profile
}
data "aws_eks_cluster" "this" {
@@ -60,7 +24,21 @@ module "metrics-server" {
namespace = var.addon_namespace
chart_version = var.addon_version
- node_selector = {
- "node.amazonaws.io/managed-by" = "asg"
+ tolerations = [{
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }]
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [{
+ key = "karpenter.sh/nodepool"
+ operator = "In"
+ values = ["system"]
+ }]
+ }]
+ }
+ }
}
}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/metrics-server/terragrunt.hcl b/infra/aws/us-east-2/k8s/metrics-server/terragrunt.hcl
new file mode 100644
index 0000000..d7c1154
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/metrics-server/terragrunt.hcl
@@ -0,0 +1,20 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks"
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ addon_namespace=include.root.locals.METRICS_SRV_ADDON_NAMESPACE
+ addon_version=include.root.locals.METRICS_SRV_ADDON_VERSION
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/metrics-server/variables.tf b/infra/aws/us-east-2/k8s/metrics-server/variables.tf
new file mode 100644
index 0000000..6e7dfc0
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/metrics-server/variables.tf
@@ -0,0 +1,22 @@
+variable "cluster_name" {
+ type = string
+}
+
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "addon_namespace" {
+ type = string
+ default = "kube-system"
+}
+
+variable "addon_version" {
+ type = string
+ default = "3.13.0"
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/monitoring/dashboards/aibridge-demoable.json b/infra/aws/us-east-2/k8s/monitoring/dashboards/aibridge-demoable.json
deleted file mode 100644
index 70d570f..0000000
--- a/infra/aws/us-east-2/k8s/monitoring/dashboards/aibridge-demoable.json
+++ /dev/null
@@ -1,1382 +0,0 @@
-{
- "annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": {
- "type": "grafana",
- "uid": "-- Grafana --"
- },
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "type": "dashboard"
- }
- ]
- },
- "editable": true,
- "fiscalYearStartMonth": 0,
- "graphTooltip": 0,
- "id": 138,
- "links": [],
- "panels": [
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 0
- },
- "id": 11,
- "panels": [],
- "title": "Usage leaderboards",
- "type": "row"
- },
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "text",
- "value": 0
- }
- ]
- },
- "unit": "short"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 12,
- "w": 4,
- "x": 0,
- "y": 1
- },
- "id": 3,
- "options": {
- "colorMode": "value",
- "graphMode": "area",
- "justifyMode": "auto",
- "orientation": "auto",
- "percentChangeColorMode": "standard",
- "reduceOptions": {
- "calcs": [
- "lastNotNull"
- ],
- "fields": "",
- "values": false
- },
- "showPercentChange": false,
- "textMode": "auto",
- "wideLayout": true
- },
- "pluginVersion": "12.1.0",
- "targets": [
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "editorMode": "code",
- "format": "table",
- "rawQuery": true,
- "rawSql": "select sum(t.input_tokens) as input,\nsum(t.output_tokens) as output,\nsum(\n COALESCE(\n t.metadata->>'cache_read_input', -- Anthropic\n t.metadata->>'prompt_cached' -- OpenAI\n )::int\n) AS cache_read_input,\nsum((t.metadata->>'cache_creation_input')::int) AS cache_creation_input -- Anthropic\nfrom aibridge_token_usages t\njoin aibridge_interceptions i on t.interception_id = i.id\njoin users u on i.initiator_id = u.id\nwhere $__timeFilter(i.started_at)\n AND u.username ~ '${username:regex}'\n AND i.provider ~ '${provider:regex}'\n AND i.model ~ '${model:regex}'\norder by input desc",
- "refId": "A",
- "sql": {
- "columns": [
- {
- "parameters": [],
- "type": "function"
- }
- ],
- "groupBy": [
- {
- "property": {
- "type": "string"
- },
- "type": "groupBy"
- }
- ],
- "limit": 50
- }
- }
- ],
- "title": "Total usage for $username",
- "transformations": [
- {
- "id": "organize",
- "options": {
- "excludeByName": {},
- "includeByName": {},
- "indexByName": {
- "cache_creation_input": 3,
- "cache_read_input": 2,
- "input": 0,
- "output": 1
- },
- "renameByName": {
- "cache_creation_input": "Cache Write",
- "cache_read_input": "Cache Read",
- "input": "Input",
- "output": "Ouput"
- }
- }
- }
- ],
- "type": "stat"
- },
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "custom": {
- "axisBorderShow": false,
- "axisCenteredZero": false,
- "axisColorMode": "text",
- "axisLabel": "",
- "axisPlacement": "auto",
- "fillOpacity": 80,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "viz": false
- },
- "lineWidth": 1,
- "scaleDistribution": {
- "type": "linear"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": 0
- }
- ]
- },
- "unit": "short"
- },
- "overrides": [
- {
- "matcher": {
- "id": "byName",
- "options": "output"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "fixedColor": "yellow",
- "mode": "fixed"
- }
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Cache Read"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "fixedColor": "green",
- "mode": "fixed"
- }
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Input"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "fixedColor": "orange",
- "mode": "fixed"
- }
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Cache Write"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "fixedColor": "light-red",
- "mode": "fixed"
- }
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 12,
- "w": 20,
- "x": 4,
- "y": 1
- },
- "id": 1,
- "options": {
- "barRadius": 0,
- "barWidth": 0.97,
- "fullHighlight": false,
- "groupWidth": 0.7,
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom",
- "showLegend": true
- },
- "orientation": "auto",
- "showValue": "auto",
- "stacking": "none",
- "tooltip": {
- "hideZeros": false,
- "mode": "single",
- "sort": "none"
- },
- "xTickLabelRotation": 0,
- "xTickLabelSpacing": 0
- },
- "pluginVersion": "12.1.0",
- "targets": [
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "editorMode": "code",
- "format": "table",
- "rawQuery": true,
- "rawSql": "select u.username, sum(t.input_tokens) as input,\nsum(t.output_tokens) as output,\nsum(\n COALESCE(\n t.metadata->>'cache_read_input', -- Anthropic\n t.metadata->>'prompt_cached' -- OpenAI\n )::int\n) AS cache_read_input,\nsum((t.metadata->>'cache_creation_input')::int) AS cache_creation_input -- Anthropic\nfrom aibridge_token_usages t\njoin aibridge_interceptions i on t.interception_id = i.id\njoin users u on i.initiator_id = u.id\nwhere $__timeFilter(i.started_at)\n AND u.username ~ '${username:regex}'\n AND i.provider ~ '${provider:regex}'\n AND i.model ~ '${model:regex}'\ngroup by u.username\norder by input desc",
- "refId": "A",
- "sql": {
- "columns": [
- {
- "parameters": [],
- "type": "function"
- }
- ],
- "groupBy": [
- {
- "property": {
- "type": "string"
- },
- "type": "groupBy"
- }
- ],
- "limit": 50
- }
- }
- ],
- "title": "Leaderboard per user",
- "transformations": [
- {
- "id": "organize",
- "options": {
- "excludeByName": {},
- "includeByName": {},
- "indexByName": {},
- "renameByName": {
- "cache_creation_input": "Cache Write",
- "cache_read_input": "Cache Read",
- "input": "Input",
- "output": "Output",
- "username": ""
- }
- }
- }
- ],
- "type": "barchart"
- },
- {
- "datasource": {
- "type": "datasource",
- "uid": "-- Mixed --"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "custom": {
- "align": "auto",
- "cellOptions": {
- "type": "auto"
- },
- "inspect": false
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": 0
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "short"
- },
- "overrides": [
- {
- "matcher": {
- "id": "byRegexp",
- "options": "/.*Cost.*/"
- },
- "properties": [
- {
- "id": "unit",
- "value": "currencyUSD"
- },
- {
- "id": "decimals",
- "value": 2
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 9,
- "w": 24,
- "x": 0,
- "y": 13
- },
- "id": 12,
- "options": {
- "cellHeight": "sm",
- "footer": {
- "countRows": false,
- "fields": "",
- "reducer": [
- "sum"
- ],
- "show": true
- },
- "frameIndex": 0,
- "showHeader": true,
- "sortBy": [
- {
- "desc": true,
- "displayName": "Total Cost"
- }
- ]
- },
- "pluginVersion": "12.1.0",
- "targets": [
- {
- "columns": [],
- "computed_columns": [],
- "datasource": {
- "type": "yesoreyeram-infinity-datasource",
- "uid": "dezthk9i9m48we"
- },
- "filterExpression": "",
- "filters": [],
- "format": "table",
- "global_query_id": "",
- "hide": false,
- "pagination_mode": "none",
- "parser": "backend",
- "refId": "A",
- "root_selector": "$ ~> $each(function($v, $k) {\n {\n \"model\": $k,\n \"input_cost_per_token\": $v.input_cost_per_token ? $v.input_cost_per_token : 0,\n \"output_cost_per_token\": $v.output_cost_per_token ? $v.output_cost_per_token : 0,\n \"cache_creation_input_token_cost\": $v.cache_creation_input_token_cost ? $v.cache_creation_input_token_cost : 0,\n \"cache_read_input_token_cost\": $v.cache_read_input_token_cost ? $v.cache_read_input_token_cost : 0\n }\n})",
- "source": "url",
- "type": "json",
- "url": "",
- "url_options": {
- "data": "",
- "method": "GET"
- }
- },
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "editorMode": "code",
- "format": "table",
- "hide": false,
- "rawQuery": true,
- "rawSql": "select i.provider, i.model,\nsum(t.input_tokens) as input,\nsum(t.output_tokens) as output,\nsum(\n COALESCE(\n t.metadata->>'cache_read_input', -- Anthropic\n t.metadata->>'prompt_cached' -- OpenAI\n )::int\n) AS cache_read_input,\nsum((t.metadata->>'cache_creation_input')::int) AS cache_creation_input -- Anthropic\nfrom aibridge_token_usages t\njoin aibridge_interceptions i on t.interception_id = i.id\njoin users u on i.initiator_id = u.id\nwhere $__timeFilter(i.started_at)\n AND u.username ~ '${username:regex}'\n AND i.provider ~ '${provider:regex}'\n AND i.model ~ '${model:regex}'\ngroup by i.provider, i.model\norder by input desc",
- "refId": "B",
- "sql": {
- "columns": [
- {
- "parameters": [],
- "type": "function"
- }
- ],
- "groupBy": [
- {
- "property": {
- "type": "string"
- },
- "type": "groupBy"
- }
- ],
- "limit": 50
- }
- }
- ],
- "title": "Approximate Cost",
- "transformations": [
- {
- "id": "joinByField",
- "options": {
- "byField": "model",
- "mode": "inner"
- }
- },
- {
- "id": "calculateField",
- "options": {
- "alias": "Input Cost",
- "binary": {
- "left": {
- "matcher": {
- "id": "byName",
- "options": "input_cost_per_token A"
- }
- },
- "operator": "*",
- "right": {
- "matcher": {
- "id": "byName",
- "options": "input"
- }
- }
- },
- "mode": "binary",
- "reduce": {
- "include": [
- "input_cost_per_token A",
- "input"
- ],
- "reducer": "sum"
- }
- }
- },
- {
- "id": "calculateField",
- "options": {
- "alias": "Output Cost",
- "binary": {
- "left": {
- "matcher": {
- "id": "byName",
- "options": "output_cost_per_token A"
- }
- },
- "operator": "*",
- "right": {
- "matcher": {
- "id": "byName",
- "options": "output"
- }
- }
- },
- "mode": "binary",
- "reduce": {
- "reducer": "sum"
- }
- }
- },
- {
- "id": "calculateField",
- "options": {
- "alias": "Cache Read Cost",
- "binary": {
- "left": {
- "matcher": {
- "id": "byName",
- "options": "cache_read_input_token_cost A"
- }
- },
- "operator": "*",
- "right": {
- "matcher": {
- "id": "byName",
- "options": "cache_read_input"
- }
- }
- },
- "mode": "binary",
- "reduce": {
- "reducer": "sum"
- }
- }
- },
- {
- "id": "calculateField",
- "options": {
- "alias": "Cache Write Cost",
- "binary": {
- "left": {
- "matcher": {
- "id": "byName",
- "options": "cache_creation_input_token_cost A"
- }
- },
- "operator": "*",
- "right": {
- "matcher": {
- "id": "byName",
- "options": "cache_creation_input"
- }
- }
- },
- "mode": "binary",
- "reduce": {
- "reducer": "sum"
- }
- }
- },
- {
- "id": "calculateField",
- "options": {
- "alias": "Total Cost",
- "binary": {
- "left": {
- "matcher": {
- "id": "byType",
- "options": "number"
- }
- },
- "right": {
- "fixed": ""
- }
- },
- "cumulative": {
- "field": "Input Cost",
- "reducer": "sum"
- },
- "mode": "reduceRow",
- "reduce": {
- "include": [
- "Input Cost",
- "Output Cost",
- "Cache Read Cost",
- "Cache Write Cost"
- ],
- "reducer": "sum"
- }
- }
- },
- {
- "id": "organize",
- "options": {
- "excludeByName": {
- "cache_creation_input": false,
- "cache_creation_input_token_cost A": true,
- "cache_read_input": false,
- "cache_read_input_token_cost A": true,
- "input": false,
- "input_cost_per_token A": true,
- "output": false,
- "output_cost_per_token A": true
- },
- "includeByName": {},
- "indexByName": {
- "Cache Read Cost": 13,
- "Cache Write Cost": 14,
- "Input Cost": 11,
- "Output Cost": 12,
- "Total Cost": 2,
- "cache_creation_input": 10,
- "cache_creation_input_token_cost A": 3,
- "cache_read_input": 9,
- "cache_read_input_token_cost A": 4,
- "input": 7,
- "input_cost_per_token A": 5,
- "model": 1,
- "output": 8,
- "output_cost_per_token A": 6,
- "provider": 0
- },
- "renameByName": {
- "cache_creation_input": "Cache Write",
- "cache_read_input": "Cache Read",
- "input": "Input",
- "model": "Model",
- "output": "Output",
- "provider": "Provider"
- }
- }
- }
- ],
- "type": "table"
- },
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 22
- },
- "id": 10,
- "panels": [],
- "title": "Interceptions",
- "type": "row"
- },
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": 0
- }
- ]
- },
- "unit": "short"
- },
- "overrides": [
- {
- "matcher": {
- "id": "byName",
- "options": "output"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "fixedColor": "orange",
- "mode": "fixed"
- }
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 12,
- "w": 4,
- "x": 0,
- "y": 23
- },
- "id": 5,
- "interval": "1m",
- "maxDataPoints": 500,
- "options": {
- "colorMode": "value",
- "graphMode": "area",
- "justifyMode": "auto",
- "orientation": "auto",
- "percentChangeColorMode": "standard",
- "reduceOptions": {
- "calcs": [
- "lastNotNull"
- ],
- "fields": "",
- "values": false
- },
- "showPercentChange": false,
- "textMode": "auto",
- "wideLayout": true
- },
- "pluginVersion": "12.1.0",
- "targets": [
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "editorMode": "code",
- "format": "table",
- "rawQuery": true,
- "rawSql": "select count(*) from aibridge_interceptions\nWHERE started_at > $__timeFrom() AND started_at <= $__timeTo()\nAND provider ~ '${provider:regex}'\nAND model ~ '${model:regex}'",
- "refId": "A",
- "sql": {
- "columns": [
- {
- "name": "COUNT",
- "parameters": [
- {
- "name": "id",
- "type": "functionParameter"
- }
- ],
- "type": "function"
- }
- ],
- "groupBy": [
- {
- "property": {
- "name": "started_at",
- "type": "string"
- },
- "type": "groupBy"
- }
- ],
- "limit": 50
- },
- "table": "aibridge_interceptions"
- }
- ],
- "title": "Total interceptions",
- "type": "stat"
- },
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisBorderShow": false,
- "axisCenteredZero": false,
- "axisColorMode": "text",
- "axisLabel": "",
- "axisPlacement": "auto",
- "fillOpacity": 80,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "viz": false
- },
- "lineWidth": 1,
- "scaleDistribution": {
- "type": "linear"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "fieldMinMax": false,
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": 0
- }
- ]
- },
- "unit": "short"
- },
- "overrides": [
- {
- "matcher": {
- "id": "byName",
- "options": "output"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "fixedColor": "orange",
- "mode": "fixed"
- }
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 12,
- "w": 20,
- "x": 4,
- "y": 23
- },
- "id": 4,
- "maxDataPoints": 30,
- "options": {
- "barRadius": 0,
- "barWidth": 0.97,
- "fullHighlight": false,
- "groupWidth": 0.7,
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom",
- "showLegend": true
- },
- "orientation": "auto",
- "showValue": "auto",
- "stacking": "normal",
- "text": {
- "valueSize": 10
- },
- "tooltip": {
- "hideZeros": false,
- "mode": "single",
- "sort": "none"
- },
- "xTickLabelRotation": -45,
- "xTickLabelSpacing": 0
- },
- "pluginVersion": "12.1.0",
- "targets": [
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "editorMode": "code",
- "format": "time_series",
- "rawQuery": true,
- "rawSql": "SELECT\n$__timeGroupAlias(i.started_at, $__interval, NULL),\ncount(i.id) AS value,\nu.username AS metric\nFROM aibridge_interceptions i\njoin users u ON i.initiator_id = u.id\nWHERE\n$__timeFilter(i.started_at)\nAND u.username ~ '${username:regex}'\nAND i.provider ~ '${provider:regex}'\nAND i.model ~ '${model:regex}'\nGROUP BY u.username, $__timeGroup(i.started_at, $__interval)\nORDER BY $__timeGroup(i.started_at, $__interval)",
- "refId": "A",
- "sql": {
- "columns": [
- {
- "name": "COUNT",
- "parameters": [
- {
- "name": "id",
- "type": "functionParameter"
- }
- ],
- "type": "function"
- }
- ],
- "groupBy": [
- {
- "property": {
- "name": "started_at",
- "type": "string"
- },
- "type": "groupBy"
- }
- ],
- "limit": 50
- },
- "table": "aibridge_interceptions"
- }
- ],
- "title": "Interceptions over time by user",
- "type": "barchart"
- },
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 35
- },
- "id": 9,
- "panels": [],
- "title": "Prompts & tool calls details",
- "type": "row"
- },
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "custom": {
- "align": "auto",
- "cellOptions": {
- "type": "auto",
- "wrapText": false
- },
- "inspect": true
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": 0
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- }
- },
- "overrides": [
- {
- "matcher": {
- "id": "byName",
- "options": "Interception ID"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 357
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Model"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 240
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Provider"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 157
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Username"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 188
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 14,
- "w": 24,
- "x": 0,
- "y": 36
- },
- "id": 7,
- "options": {
- "cellHeight": "sm",
- "footer": {
- "countRows": false,
- "fields": "",
- "reducer": [
- "sum"
- ],
- "show": false
- },
- "showHeader": true,
- "sortBy": [
- {
- "desc": true,
- "displayName": "Created At"
- }
- ]
- },
- "pluginVersion": "12.1.0",
- "targets": [
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "editorMode": "code",
- "format": "table",
- "rawQuery": true,
- "rawSql": "SELECT i.id,\n u.username,\n i.provider,\n i.model,\n 'REDACTED' as prompt,\n p.created_at\nFROM aibridge_user_prompts p\nJOIN aibridge_interceptions i ON p.interception_id = i.id\nJOIN users u ON i.initiator_id = u.id\nWHERE $__timeFilter(i.started_at)\n AND u.username ~ '${username:regex}'\n AND i.provider ~ '${provider:regex}'\n AND i.model ~ '${model:regex}'\nORDER BY p.created_at DESC;",
- "refId": "A",
- "sql": {
- "columns": [
- {
- "parameters": [],
- "type": "function"
- }
- ],
- "groupBy": [
- {
- "property": {
- "type": "string"
- },
- "type": "groupBy"
- }
- ],
- "limit": 50
- }
- }
- ],
- "title": "User Prompts",
- "transformations": [
- {
- "id": "organize",
- "options": {
- "excludeByName": {},
- "includeByName": {},
- "indexByName": {},
- "renameByName": {
- "created_at": "Created At",
- "id": "Interception ID",
- "input": "Tool Input",
- "invocation_error": "Tool Error",
- "model": "Model",
- "prompt": "Prompt",
- "provider": "Provider",
- "server_url": "MCP Server",
- "tool": "Tool Name",
- "username": "Username"
- }
- }
- }
- ],
- "type": "table"
- },
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "custom": {
- "align": "auto",
- "cellOptions": {
- "type": "auto",
- "wrapText": false
- },
- "inspect": true
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": 0
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- }
- },
- "overrides": [
- {
- "matcher": {
- "id": "byName",
- "options": "Tool Name"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 342
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "invocation_error"
- },
- "properties": [
- {
- "id": "custom.cellOptions",
- "value": {
- "applyToRow": true,
- "type": "color-background",
- "wrapText": false
- }
- },
- {
- "id": "noValue"
- },
- {
- "id": "mappings",
- "value": [
- {
- "options": {
- "match": "null",
- "result": {
- "color": "green",
- "index": 0
- }
- },
- "type": "special"
- },
- {
- "options": {
- "pattern": ".+",
- "result": {
- "color": "red",
- "index": 1
- }
- },
- "type": "regex"
- }
- ]
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Tool Input"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 309
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Interception ID"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 357
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Model"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 240
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 14,
- "w": 24,
- "x": 0,
- "y": 50
- },
- "id": 6,
- "options": {
- "cellHeight": "sm",
- "footer": {
- "countRows": false,
- "fields": "",
- "reducer": [
- "sum"
- ],
- "show": false
- },
- "showHeader": true,
- "sortBy": [
- {
- "desc": true,
- "displayName": "Created At"
- }
- ]
- },
- "pluginVersion": "12.1.0",
- "targets": [
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "editorMode": "code",
- "format": "table",
- "rawQuery": true,
- "rawSql": "select i.id, u.username, i.provider, i.model, t.server_url, t.tool, 'REDACTED' as input, CASE WHEN t.invocation_error IS NULL THEN '' ELSE 'REDACTED' END as invocation_error, t.created_at FROM aibridge_tool_usages t\njoin aibridge_interceptions i ON t.interception_id = i.id\njoin users u on i.initiator_id = u.id\nwhere $__timeFilter(i.started_at)\nAND u.username ~ '${username:regex}'\nAND i.provider ~ '${provider:regex}'\nAND i.model ~ '${model:regex}'\norder by t.created_at desc",
- "refId": "A",
- "sql": {
- "columns": [
- {
- "parameters": [],
- "type": "function"
- }
- ],
- "groupBy": [
- {
- "property": {
- "type": "string"
- },
- "type": "groupBy"
- }
- ],
- "limit": 50
- }
- }
- ],
- "title": "Tool Calls",
- "transformations": [
- {
- "id": "organize",
- "options": {
- "excludeByName": {},
- "includeByName": {},
- "indexByName": {},
- "renameByName": {
- "created_at": "Created At",
- "id": "Interception ID",
- "input": "Tool Input",
- "invocation_error": "Tool Error",
- "model": "Model",
- "provider": "Provider",
- "server_url": "MCP Server",
- "tool": "Tool Name",
- "username": "Username"
- }
- }
- }
- ],
- "type": "table"
- }
- ],
- "preload": false,
- "refresh": "1m",
- "schemaVersion": 41,
- "tags": [],
- "templating": {
- "list": [
- {
- "allValue": ".+",
- "current": {
- "text": "All",
- "value": [
- "$__all"
- ]
- },
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "definition": "select username from users where deleted=false;",
- "description": "",
- "includeAll": true,
- "multi": true,
- "name": "username",
- "options": [],
- "query": "select username from users where deleted=false;",
- "refresh": 1,
- "regex": "",
- "sort": 1,
- "type": "query"
- },
- {
- "allValue": ".+",
- "current": {
- "text": "All",
- "value": [
- "$__all"
- ]
- },
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "definition": "SELECT DISTINCT provider FROM aibridge_interceptions WHERE provider IS NOT NULL ORDER BY 1;",
- "description": "",
- "includeAll": true,
- "multi": true,
- "name": "provider",
- "options": [],
- "query": "SELECT DISTINCT provider FROM aibridge_interceptions WHERE provider IS NOT NULL ORDER BY 1;",
- "refresh": 1,
- "regex": "",
- "sort": 1,
- "type": "query"
- },
- {
- "allValue": ".+",
- "current": {
- "text": "All",
- "value": [
- "$__all"
- ]
- },
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "aezivxsm5pnggc"
- },
- "definition": "SELECT DISTINCT model FROM aibridge_interceptions WHERE model IS NOT NULL AND provider ~ '${provider:regex}' ORDER BY 1;",
- "description": "",
- "includeAll": true,
- "multi": true,
- "name": "model",
- "options": [],
- "query": "SELECT DISTINCT model FROM aibridge_interceptions WHERE model IS NOT NULL AND provider ~ '${provider:regex}' ORDER BY 1;",
- "refresh": 1,
- "regex": "",
- "sort": 1,
- "type": "query"
- }
- ]
- },
- "time": {
- "from": "now-7d",
- "to": "now"
- },
- "timepicker": {},
- "timezone": "utc",
- "title": "aibridge [DEMOABLE]",
- "uid": "0c61d33f-c809-4184-9e88-cb27e2d9d223",
- "version": 2
-}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/monitoring/dashboards/aibridge-example.json b/infra/aws/us-east-2/k8s/monitoring/dashboards/aibridge-example.json
deleted file mode 100644
index 304d49a..0000000
--- a/infra/aws/us-east-2/k8s/monitoring/dashboards/aibridge-example.json
+++ /dev/null
@@ -1,1411 +0,0 @@
-{
- "__inputs": [
- {
- "name": "DS_CODER-OBSERVABILITY-RO",
- "label": "coder-observability-ro",
- "description": "",
- "type": "datasource",
- "pluginId": "grafana-postgresql-datasource",
- "pluginName": "PostgreSQL"
- },
- {
- "name": "DS_LITELLM-PRICING-DATA",
- "label": "litellm-pricing-data",
- "description": "",
- "type": "datasource",
- "pluginId": "yesoreyeram-infinity-datasource",
- "pluginName": "Infinity"
- }
- ],
- "__elements": {},
- "__requires": [
- {
- "type": "panel",
- "id": "barchart",
- "name": "Bar chart",
- "version": ""
- },
- {
- "type": "grafana",
- "id": "grafana",
- "name": "Grafana",
- "version": "12.1.0"
- },
- {
- "type": "datasource",
- "id": "grafana-postgresql-datasource",
- "name": "PostgreSQL",
- "version": "12.1.0"
- },
- {
- "type": "panel",
- "id": "stat",
- "name": "Stat",
- "version": ""
- },
- {
- "type": "panel",
- "id": "table",
- "name": "Table",
- "version": ""
- },
- {
- "type": "datasource",
- "id": "yesoreyeram-infinity-datasource",
- "name": "Infinity",
- "version": "3.6.0"
- }
- ],
- "annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": {
- "type": "grafana",
- "uid": "-- Grafana --"
- },
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "type": "dashboard"
- }
- ]
- },
- "editable": true,
- "fiscalYearStartMonth": 0,
- "graphTooltip": 0,
- "id": null,
- "links": [],
- "panels": [
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 0
- },
- "id": 11,
- "panels": [],
- "title": "Usage leaderboards",
- "type": "row"
- },
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "custom": {
- "axisBorderShow": false,
- "axisCenteredZero": false,
- "axisColorMode": "text",
- "axisLabel": "",
- "axisPlacement": "auto",
- "fillOpacity": 80,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "viz": false
- },
- "lineWidth": 1,
- "scaleDistribution": {
- "type": "linear"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": 0
- }
- ]
- },
- "unit": "short"
- },
- "overrides": [
- {
- "matcher": {
- "id": "byName",
- "options": "output"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "fixedColor": "yellow",
- "mode": "fixed"
- }
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Cache Read"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "fixedColor": "green",
- "mode": "fixed"
- }
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Input"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "fixedColor": "orange",
- "mode": "fixed"
- }
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Cache Write"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "fixedColor": "light-red",
- "mode": "fixed"
- }
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 12,
- "w": 20,
- "x": 0,
- "y": 1
- },
- "id": 1,
- "options": {
- "barRadius": 0,
- "barWidth": 0.97,
- "fullHighlight": false,
- "groupWidth": 0.7,
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom",
- "showLegend": true
- },
- "orientation": "auto",
- "showValue": "auto",
- "stacking": "none",
- "tooltip": {
- "hideZeros": false,
- "mode": "single",
- "sort": "none"
- },
- "xTickLabelRotation": 0,
- "xTickLabelSpacing": 0
- },
- "pluginVersion": "12.1.0",
- "targets": [
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "editorMode": "code",
- "format": "table",
- "rawQuery": true,
- "rawSql": "select u.username, sum(t.input_tokens) as input,\nsum(t.output_tokens) as output,\nsum(\n COALESCE(\n t.metadata->>'cache_read_input', -- Anthropic\n t.metadata->>'prompt_cached' -- OpenAI\n )::int\n) AS cache_read_input,\nsum((t.metadata->>'cache_creation_input')::int) AS cache_creation_input -- Anthropic\nfrom aibridge_token_usages t\njoin aibridge_interceptions i on t.interception_id = i.id\njoin users u on i.initiator_id = u.id\nwhere $__timeFilter(i.started_at)\n AND u.username ~ '${username:regex}'\n AND i.provider ~ '${provider:regex}'\n AND i.model ~ '${model:regex}'\ngroup by u.username\norder by input desc",
- "refId": "A",
- "sql": {
- "columns": [
- {
- "parameters": [],
- "type": "function"
- }
- ],
- "groupBy": [
- {
- "property": {
- "type": "string"
- },
- "type": "groupBy"
- }
- ],
- "limit": 50
- }
- }
- ],
- "title": "Leaderboard per user",
- "transformations": [
- {
- "id": "organize",
- "options": {
- "excludeByName": {},
- "includeByName": {},
- "indexByName": {},
- "renameByName": {
- "cache_creation_input": "Cache Write",
- "cache_read_input": "Cache Read",
- "input": "Input",
- "output": "Output",
- "username": ""
- }
- }
- }
- ],
- "type": "barchart"
- },
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "text",
- "value": 0
- }
- ]
- },
- "unit": "short"
- },
- "overrides": []
- },
- "gridPos": {
- "h": 12,
- "w": 4,
- "x": 20,
- "y": 1
- },
- "id": 3,
- "options": {
- "colorMode": "value",
- "graphMode": "area",
- "justifyMode": "auto",
- "orientation": "auto",
- "percentChangeColorMode": "standard",
- "reduceOptions": {
- "calcs": ["lastNotNull"],
- "fields": "",
- "values": false
- },
- "showPercentChange": false,
- "textMode": "auto",
- "wideLayout": true
- },
- "pluginVersion": "12.1.0",
- "targets": [
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "editorMode": "code",
- "format": "table",
- "rawQuery": true,
- "rawSql": "select sum(t.input_tokens) as input,\nsum(t.output_tokens) as output,\nsum(\n COALESCE(\n t.metadata->>'cache_read_input', -- Anthropic\n t.metadata->>'prompt_cached' -- OpenAI\n )::int\n) AS cache_read_input,\nsum((t.metadata->>'cache_creation_input')::int) AS cache_creation_input -- Anthropic\nfrom aibridge_token_usages t\njoin aibridge_interceptions i on t.interception_id = i.id\njoin users u on i.initiator_id = u.id\nwhere $__timeFilter(i.started_at)\n AND u.username ~ '${username:regex}'\n AND i.provider ~ '${provider:regex}'\n AND i.model ~ '${model:regex}'\norder by input desc",
- "refId": "A",
- "sql": {
- "columns": [
- {
- "parameters": [],
- "type": "function"
- }
- ],
- "groupBy": [
- {
- "property": {
- "type": "string"
- },
- "type": "groupBy"
- }
- ],
- "limit": 50
- }
- }
- ],
- "title": "Total usage for $username",
- "transformations": [
- {
- "id": "organize",
- "options": {
- "excludeByName": {},
- "includeByName": {},
- "indexByName": {
- "cache_creation_input": 3,
- "cache_read_input": 2,
- "input": 0,
- "output": 1
- },
- "renameByName": {
- "cache_creation_input": "Cache Write",
- "cache_read_input": "Cache Read",
- "input": "Input",
- "output": "Output"
- }
- }
- }
- ],
- "type": "stat"
- },
- {
- "datasource": {
- "type": "datasource",
- "uid": "-- Mixed --"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "custom": {
- "align": "auto",
- "cellOptions": {
- "type": "auto"
- },
- "inspect": false
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": 0
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- },
- "unit": "short"
- },
- "overrides": [
- {
- "matcher": {
- "id": "byRegexp",
- "options": "/.*Cost.*/"
- },
- "properties": [
- {
- "id": "unit",
- "value": "currencyUSD"
- },
- {
- "id": "decimals",
- "value": 2
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 9,
- "w": 24,
- "x": 0,
- "y": 13
- },
- "id": 12,
- "options": {
- "cellHeight": "sm",
- "footer": {
- "countRows": false,
- "fields": "",
- "reducer": ["sum"],
- "show": true
- },
- "frameIndex": 0,
- "showHeader": true,
- "sortBy": [
- {
- "desc": true,
- "displayName": "Total Cost"
- }
- ]
- },
- "pluginVersion": "12.1.0",
- "targets": [
- {
- "columns": [],
- "computed_columns": [],
- "datasource": {
- "type": "yesoreyeram-infinity-datasource",
- "uid": "${DS_LITELLM-PRICING-DATA}"
- },
- "filterExpression": "",
- "filters": [],
- "format": "table",
- "global_query_id": "",
- "hide": false,
- "pagination_mode": "none",
- "parser": "backend",
- "refId": "A",
- "root_selector": "$ ~> $each(function($v, $k) {\n {\n \"model\": $k,\n \"input_cost_per_token\": $v.input_cost_per_token ? $v.input_cost_per_token : 0,\n \"output_cost_per_token\": $v.output_cost_per_token ? $v.output_cost_per_token : 0,\n \"cache_creation_input_token_cost\": $v.cache_creation_input_token_cost ? $v.cache_creation_input_token_cost : 0,\n \"cache_read_input_token_cost\": $v.cache_read_input_token_cost ? $v.cache_read_input_token_cost : 0\n }\n})",
- "source": "url",
- "type": "json",
- "url": "",
- "url_options": {
- "data": "",
- "method": "GET"
- }
- },
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "editorMode": "code",
- "format": "table",
- "hide": false,
- "rawQuery": true,
- "rawSql": "select i.provider, i.model,\nsum(t.input_tokens) as input,\nsum(t.output_tokens) as output,\nsum(\n COALESCE(\n t.metadata->>'cache_read_input', -- Anthropic\n t.metadata->>'prompt_cached' -- OpenAI\n )::int\n) AS cache_read_input,\nsum((t.metadata->>'cache_creation_input')::int) AS cache_creation_input -- Anthropic\nfrom aibridge_token_usages t\njoin aibridge_interceptions i on t.interception_id = i.id\njoin users u on i.initiator_id = u.id\nwhere $__timeFilter(i.started_at)\n AND u.username ~ '${username:regex}'\n AND i.provider ~ '${provider:regex}'\n AND i.model ~ '${model:regex}'\ngroup by i.provider, i.model\norder by input desc",
- "refId": "B",
- "sql": {
- "columns": [
- {
- "parameters": [],
- "type": "function"
- }
- ],
- "groupBy": [
- {
- "property": {
- "type": "string"
- },
- "type": "groupBy"
- }
- ],
- "limit": 50
- }
- }
- ],
- "title": "Approximate Cost",
- "transformations": [
- {
- "id": "joinByField",
- "options": {
- "byField": "model",
- "mode": "inner"
- }
- },
- {
- "id": "calculateField",
- "options": {
- "alias": "Input Cost",
- "binary": {
- "left": {
- "matcher": {
- "id": "byName",
- "options": "input_cost_per_token A"
- }
- },
- "operator": "*",
- "right": {
- "matcher": {
- "id": "byName",
- "options": "input"
- }
- }
- },
- "mode": "binary",
- "reduce": {
- "include": ["input_cost_per_token A", "input"],
- "reducer": "sum"
- }
- }
- },
- {
- "id": "calculateField",
- "options": {
- "alias": "Output Cost",
- "binary": {
- "left": {
- "matcher": {
- "id": "byName",
- "options": "output_cost_per_token A"
- }
- },
- "operator": "*",
- "right": {
- "matcher": {
- "id": "byName",
- "options": "output"
- }
- }
- },
- "mode": "binary",
- "reduce": {
- "reducer": "sum"
- }
- }
- },
- {
- "id": "calculateField",
- "options": {
- "alias": "Cache Read Cost",
- "binary": {
- "left": {
- "matcher": {
- "id": "byName",
- "options": "cache_read_input_token_cost A"
- }
- },
- "operator": "*",
- "right": {
- "matcher": {
- "id": "byName",
- "options": "cache_read_input"
- }
- }
- },
- "mode": "binary",
- "reduce": {
- "reducer": "sum"
- }
- }
- },
- {
- "id": "calculateField",
- "options": {
- "alias": "Cache Write Cost",
- "binary": {
- "left": {
- "matcher": {
- "id": "byName",
- "options": "cache_creation_input_token_cost A"
- }
- },
- "operator": "*",
- "right": {
- "matcher": {
- "id": "byName",
- "options": "cache_creation_input"
- }
- }
- },
- "mode": "binary",
- "reduce": {
- "reducer": "sum"
- }
- }
- },
- {
- "id": "calculateField",
- "options": {
- "alias": "Total Cost",
- "binary": {
- "left": {
- "matcher": {
- "id": "byType",
- "options": "number"
- }
- },
- "right": {
- "fixed": ""
- }
- },
- "cumulative": {
- "field": "Input Cost",
- "reducer": "sum"
- },
- "mode": "reduceRow",
- "reduce": {
- "include": [
- "Input Cost",
- "Output Cost",
- "Cache Read Cost",
- "Cache Write Cost"
- ],
- "reducer": "sum"
- }
- }
- },
- {
- "id": "organize",
- "options": {
- "excludeByName": {
- "cache_creation_input": false,
- "cache_creation_input_token_cost A": true,
- "cache_read_input": false,
- "cache_read_input_token_cost A": true,
- "input": false,
- "input_cost_per_token A": true,
- "output": false,
- "output_cost_per_token A": true
- },
- "includeByName": {},
- "indexByName": {
- "Cache Read Cost": 12,
- "Cache Write Cost": 13,
- "Input Cost": 10,
- "Output Cost": 11,
- "Total Cost": 14,
- "cache_creation_input": 9,
- "cache_creation_input_token_cost A": 2,
- "cache_read_input": 8,
- "cache_read_input_token_cost A": 3,
- "input": 6,
- "input_cost_per_token A": 4,
- "model": 1,
- "output": 7,
- "output_cost_per_token A": 5,
- "provider": 0
- },
- "renameByName": {
- "cache_creation_input": "Cache Write",
- "cache_read_input": "Cache Read",
- "input": "Input",
- "model": "Model",
- "output": "Output",
- "provider": "Provider"
- }
- }
- }
- ],
- "type": "table"
- },
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 22
- },
- "id": 10,
- "panels": [],
- "title": "Interceptions",
- "type": "row"
- },
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "axisBorderShow": false,
- "axisCenteredZero": false,
- "axisColorMode": "text",
- "axisLabel": "",
- "axisPlacement": "auto",
- "fillOpacity": 80,
- "gradientMode": "none",
- "hideFrom": {
- "legend": false,
- "tooltip": false,
- "viz": false
- },
- "lineWidth": 1,
- "scaleDistribution": {
- "type": "linear"
- },
- "thresholdsStyle": {
- "mode": "off"
- }
- },
- "fieldMinMax": false,
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": 0
- }
- ]
- },
- "unit": "short"
- },
- "overrides": [
- {
- "matcher": {
- "id": "byName",
- "options": "output"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "fixedColor": "orange",
- "mode": "fixed"
- }
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 12,
- "w": 20,
- "x": 0,
- "y": 23
- },
- "id": 4,
- "maxDataPoints": 30,
- "options": {
- "barRadius": 0,
- "barWidth": 0.97,
- "fullHighlight": false,
- "groupWidth": 0.7,
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom",
- "showLegend": true
- },
- "orientation": "auto",
- "showValue": "auto",
- "stacking": "normal",
- "text": {
- "valueSize": 10
- },
- "tooltip": {
- "hideZeros": false,
- "mode": "single",
- "sort": "none"
- },
- "xTickLabelRotation": -45,
- "xTickLabelSpacing": 0
- },
- "pluginVersion": "12.1.0",
- "targets": [
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "editorMode": "code",
- "format": "time_series",
- "rawQuery": true,
- "rawSql": "SELECT\n$__timeGroupAlias(i.started_at, $__interval, NULL),\ncount(i.id) AS value,\nu.username AS metric\nFROM aibridge_interceptions i\njoin users u ON i.initiator_id = u.id\nWHERE\n$__timeFilter(i.started_at)\nAND u.username ~ '${username:regex}'\nAND i.provider ~ '${provider:regex}'\nAND i.model ~ '${model:regex}'\nGROUP BY u.username, $__timeGroup(i.started_at, $__interval)\nORDER BY $__timeGroup(i.started_at, $__interval)",
- "refId": "A",
- "sql": {
- "columns": [
- {
- "name": "COUNT",
- "parameters": [
- {
- "name": "id",
- "type": "functionParameter"
- }
- ],
- "type": "function"
- }
- ],
- "groupBy": [
- {
- "property": {
- "name": "started_at",
- "type": "string"
- },
- "type": "groupBy"
- }
- ],
- "limit": 50
- },
- "table": "aibridge_interceptions"
- }
- ],
- "title": "Interceptions over time by user",
- "type": "barchart"
- },
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": 0
- }
- ]
- },
- "unit": "short"
- },
- "overrides": [
- {
- "matcher": {
- "id": "byName",
- "options": "output"
- },
- "properties": [
- {
- "id": "color",
- "value": {
- "fixedColor": "orange",
- "mode": "fixed"
- }
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 12,
- "w": 4,
- "x": 20,
- "y": 23
- },
- "id": 5,
- "interval": "1m",
- "maxDataPoints": 500,
- "options": {
- "colorMode": "value",
- "graphMode": "area",
- "justifyMode": "auto",
- "orientation": "auto",
- "percentChangeColorMode": "standard",
- "reduceOptions": {
- "calcs": ["lastNotNull"],
- "fields": "",
- "values": false
- },
- "showPercentChange": false,
- "textMode": "auto",
- "wideLayout": true
- },
- "pluginVersion": "12.1.0",
- "targets": [
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "editorMode": "code",
- "format": "table",
- "rawQuery": true,
- "rawSql": "select count(*) from aibridge_interceptions\nWHERE started_at > $__timeFrom() AND started_at <= $__timeTo()\nAND provider ~ '${provider:regex}'\nAND model ~ '${model:regex}'",
- "refId": "A",
- "sql": {
- "columns": [
- {
- "name": "COUNT",
- "parameters": [
- {
- "name": "id",
- "type": "functionParameter"
- }
- ],
- "type": "function"
- }
- ],
- "groupBy": [
- {
- "property": {
- "name": "started_at",
- "type": "string"
- },
- "type": "groupBy"
- }
- ],
- "limit": 50
- },
- "table": "aibridge_interceptions"
- }
- ],
- "title": "Total interceptions",
- "type": "stat"
- },
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 35
- },
- "id": 9,
- "panels": [],
- "title": "Prompts & tool calls details",
- "type": "row"
- },
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "custom": {
- "align": "auto",
- "cellOptions": {
- "type": "auto",
- "wrapText": false
- },
- "inspect": true
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": 0
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- }
- },
- "overrides": [
- {
- "matcher": {
- "id": "byName",
- "options": "Interception ID"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 357
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Model"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 240
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Provider"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 157
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Username"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 188
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 14,
- "w": 24,
- "x": 0,
- "y": 36
- },
- "id": 7,
- "options": {
- "cellHeight": "sm",
- "footer": {
- "countRows": false,
- "fields": "",
- "reducer": ["sum"],
- "show": false
- },
- "showHeader": true,
- "sortBy": [
- {
- "desc": true,
- "displayName": "Created At"
- }
- ]
- },
- "pluginVersion": "12.1.0",
- "targets": [
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "editorMode": "code",
- "format": "table",
- "rawQuery": true,
- "rawSql": "SELECT i.id,\n u.username,\n i.provider,\n i.model,\n p.prompt,\n p.created_at\nFROM aibridge_user_prompts p\nJOIN aibridge_interceptions i ON p.interception_id = i.id\nJOIN users u ON i.initiator_id = u.id\nWHERE $__timeFilter(i.started_at)\n AND u.username ~ '${username:regex}'\n AND i.provider ~ '${provider:regex}'\n AND i.model ~ '${model:regex}'\nORDER BY p.created_at DESC;",
- "refId": "A",
- "sql": {
- "columns": [
- {
- "parameters": [],
- "type": "function"
- }
- ],
- "groupBy": [
- {
- "property": {
- "type": "string"
- },
- "type": "groupBy"
- }
- ],
- "limit": 50
- }
- }
- ],
- "title": "User Prompts",
- "transformations": [
- {
- "id": "organize",
- "options": {
- "excludeByName": {},
- "includeByName": {},
- "indexByName": {},
- "renameByName": {
- "created_at": "Created At",
- "id": "Interception ID",
- "input": "Tool Input",
- "invocation_error": "Tool Error",
- "model": "Model",
- "prompt": "Prompt",
- "provider": "Provider",
- "server_url": "MCP Server",
- "tool": "Tool Name",
- "username": "Username"
- }
- }
- }
- ],
- "type": "table"
- },
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "custom": {
- "align": "auto",
- "cellOptions": {
- "type": "auto",
- "wrapText": false
- },
- "inspect": true
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green",
- "value": 0
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- }
- },
- "overrides": [
- {
- "matcher": {
- "id": "byName",
- "options": "Tool Name"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 342
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "invocation_error"
- },
- "properties": [
- {
- "id": "custom.cellOptions",
- "value": {
- "applyToRow": true,
- "type": "color-background",
- "wrapText": false
- }
- },
- {
- "id": "noValue"
- },
- {
- "id": "mappings",
- "value": [
- {
- "options": {
- "match": "null",
- "result": {
- "color": "green",
- "index": 0
- }
- },
- "type": "special"
- },
- {
- "options": {
- "pattern": ".+",
- "result": {
- "color": "red",
- "index": 1
- }
- },
- "type": "regex"
- }
- ]
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Tool Input"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 309
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Interception ID"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 357
- }
- ]
- },
- {
- "matcher": {
- "id": "byName",
- "options": "Model"
- },
- "properties": [
- {
- "id": "custom.width",
- "value": 240
- }
- ]
- }
- ]
- },
- "gridPos": {
- "h": 14,
- "w": 24,
- "x": 0,
- "y": 50
- },
- "id": 6,
- "options": {
- "cellHeight": "sm",
- "footer": {
- "countRows": false,
- "fields": "",
- "reducer": ["sum"],
- "show": false
- },
- "showHeader": true,
- "sortBy": [
- {
- "desc": true,
- "displayName": "Created At"
- }
- ]
- },
- "pluginVersion": "12.1.0",
- "targets": [
- {
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "editorMode": "code",
- "format": "table",
- "rawQuery": true,
- "rawSql": "select i.id, u.username, i.provider, i.model, t.server_url, t.tool, t.input, t.invocation_error, t.created_at FROM aibridge_tool_usages t\njoin aibridge_interceptions i ON t.interception_id = i.id\njoin users u on i.initiator_id = u.id\nwhere $__timeFilter(i.started_at)\nAND u.username ~ '${username:regex}'\nAND i.provider ~ '${provider:regex}'\nAND i.model ~ '${model:regex}'\norder by t.created_at desc",
- "refId": "A",
- "sql": {
- "columns": [
- {
- "parameters": [],
- "type": "function"
- }
- ],
- "groupBy": [
- {
- "property": {
- "type": "string"
- },
- "type": "groupBy"
- }
- ],
- "limit": 50
- }
- }
- ],
- "title": "Tool Calls",
- "transformations": [
- {
- "id": "organize",
- "options": {
- "excludeByName": {},
- "includeByName": {},
- "indexByName": {},
- "renameByName": {
- "created_at": "Created At",
- "id": "Interception ID",
- "input": "Tool Input",
- "invocation_error": "Tool Error",
- "model": "Model",
- "provider": "Provider",
- "server_url": "MCP Server",
- "tool": "Tool Name",
- "username": "Username"
- }
- }
- }
- ],
- "type": "table"
- }
- ],
- "refresh": "1m",
- "schemaVersion": 41,
- "tags": [],
- "templating": {
- "list": [
- {
- "allValue": ".+",
- "current": {},
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "definition": "select username from users where deleted=false;",
- "description": "",
- "includeAll": true,
- "multi": true,
- "name": "username",
- "options": [],
- "query": "select username from users where deleted=false;",
- "refresh": 1,
- "regex": "",
- "sort": 1,
- "type": "query"
- },
- {
- "allValue": ".+",
- "current": {},
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "definition": "SELECT DISTINCT provider FROM aibridge_interceptions WHERE provider IS NOT NULL ORDER BY 1;",
- "description": "",
- "includeAll": true,
- "multi": true,
- "name": "provider",
- "options": [],
- "query": "SELECT DISTINCT provider FROM aibridge_interceptions WHERE provider IS NOT NULL ORDER BY 1;",
- "refresh": 1,
- "regex": "",
- "sort": 1,
- "type": "query"
- },
- {
- "allValue": ".+",
- "current": {},
- "datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "postgres"
- },
- "definition": "SELECT DISTINCT model FROM aibridge_interceptions WHERE model IS NOT NULL AND provider ~ '${provider:regex}' ORDER BY 1;",
- "description": "",
- "includeAll": true,
- "multi": true,
- "name": "model",
- "options": [],
- "query": "SELECT DISTINCT model FROM aibridge_interceptions WHERE model IS NOT NULL AND provider ~ '${provider:regex}' ORDER BY 1;",
- "refresh": 1,
- "regex": "",
- "sort": 1,
- "type": "query"
- }
- ]
- },
- "time": {
- "from": "now-7d",
- "to": "now"
- },
- "timepicker": {},
- "timezone": "utc",
- "title": "aibridge",
- "uid": "0c61d33f-c809-4184-9e88-cb27e2d9d224",
- "version": 43,
- "weekStart": ""
-}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/monitoring/dashboards/aibridge.json b/infra/aws/us-east-2/k8s/monitoring/dashboards/aibridge.json
index 0d1fb72..f7a5286 100644
--- a/infra/aws/us-east-2/k8s/monitoring/dashboards/aibridge.json
+++ b/infra/aws/us-east-2/k8s/monitoring/dashboards/aibridge.json
@@ -5,7 +5,7 @@
"label": "coder-observability-ro",
"description": "",
"type": "datasource",
- "pluginId": "grafana-postgresql-datasource",
+ "pluginId": "postgres",
"pluginName": "PostgreSQL"
}
],
@@ -25,7 +25,7 @@
},
{
"type": "datasource",
- "id": "grafana-postgresql-datasource",
+ "id": "postgres",
"name": "PostgreSQL",
"version": "12.1.0"
},
@@ -79,8 +79,8 @@
},
{
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"fieldConfig": {
"defaults": {
@@ -216,8 +216,8 @@
"targets": [
{
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"editorMode": "code",
"format": "table",
@@ -265,8 +265,8 @@
},
{
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"fieldConfig": {
"defaults": {
@@ -313,8 +313,8 @@
"targets": [
{
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"editorMode": "code",
"format": "table",
@@ -379,8 +379,8 @@
},
{
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"fieldConfig": {
"defaults": {
@@ -476,8 +476,8 @@
"targets": [
{
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"editorMode": "code",
"format": "time_series",
@@ -516,8 +516,8 @@
},
{
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"fieldConfig": {
"defaults": {
@@ -582,8 +582,8 @@
"targets": [
{
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"editorMode": "code",
"format": "table",
@@ -635,8 +635,8 @@
},
{
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"fieldConfig": {
"defaults": {
@@ -744,8 +744,8 @@
"targets": [
{
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"editorMode": "code",
"format": "table",
@@ -798,8 +798,8 @@
},
{
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"fieldConfig": {
"defaults": {
@@ -951,8 +951,8 @@
"targets": [
{
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"editorMode": "code",
"format": "table",
@@ -1012,8 +1012,8 @@
"allValue": ".+",
"current": {},
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"definition": "select username from users where deleted=false;",
"description": "",
@@ -1031,8 +1031,8 @@
"allValue": ".+",
"current": {},
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"definition": "SELECT DISTINCT provider FROM aibridge_interceptions WHERE provider IS NOT NULL ORDER BY 1;",
"description": "",
@@ -1050,8 +1050,8 @@
"allValue": ".+",
"current": {},
"datasource": {
- "type": "grafana-postgresql-datasource",
- "uid": "grafana-postgresql-datasource"
+ "type": "postgres",
+ "uid": "postgres"
},
"definition": "SELECT DISTINCT model FROM aibridge_interceptions WHERE model IS NOT NULL AND provider ~ '$${provider:regex}' ORDER BY 1;",
"description": "",
diff --git a/infra/aws/us-east-2/k8s/monitoring/dashboards/boundary.json b/infra/aws/us-east-2/k8s/monitoring/dashboards/boundary.json
new file mode 100644
index 0000000..5990323
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/monitoring/dashboards/boundary.json
@@ -0,0 +1,875 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "target": {
+ "limit": 100,
+ "matchAny": false,
+ "tags": [],
+ "type": "dashboard"
+ },
+ "type": "dashboard"
+ }
+ ]
+ },
+ "description": "This dashboard shows HTTP requests audited by agent boundaries within Coder workspaces to provide visibility into workspace network activity.\n\nWhat it shows:\n - Total count of allowed and denied outbound HTTP requests\n - Top 20 most frequently accessed allowed domains\n - Top 20 most frequently blocked domains\n - Recent allowed requests with details (time, domain, method, path, workspace owner, workspace name, template ID)\n - Recent denied requests with the same details\n\nWho it's for:\n - Platform administrators and template administrators who need to audit workspace network activity and define agent boundary policies\n - Security team members who want to know what HTTP requests AI agents made in Coder workspaces for security incident review\n - Agent Boundaries policy owners who want to refine network access controls/security posture\n\nFilters available:\n - Template ID\n - HTTP request domain\n - Workspace owner",
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 0,
+ "links": [],
+ "panels": [
+ {
+ "datasource": {
+ "type": "loki",
+ "uid": "loki"
+ },
+ "description": "Total number of requests allowed/denied",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "{decision=\"allow\"}"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Allowed"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "{decision=\"deny\"}"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Denied"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 7,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 5,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "percentChangeColorMode": "standard",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showPercentChange": false,
+ "textMode": "auto",
+ "wideLayout": true
+ },
+ "pluginVersion": "12.1.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "loki",
+ "uid": "loki"
+ },
+ "direction": "backward",
+ "editorMode": "code",
+ "expr": "sum by (decision) (count_over_time({ ${NON_WORKSPACE_SELECTOR}, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=~`deny|allow` | owner=~`$owner` | domain=~`$domain` | template_id=~`$template_id` | template_version_id=~`$template_version_id` [$__range]))",
+ "queryType": "range",
+ "refId": "A"
+ }
+ ],
+ "title": "Request Totals",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "loki",
+ "uid": "loki"
+ },
+ "description": "Top 20 allowed domains for HTTP requests",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "custom": {
+ "align": "auto",
+ "cellOptions": {
+ "type": "auto"
+ },
+ "filterable": false,
+ "inspect": false
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Domain"
+ },
+ "properties": [
+ {
+ "id": "custom.width",
+ "value": 340
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 12,
+ "w": 12,
+ "x": 0,
+ "y": 7
+ },
+ "id": 1,
+ "options": {
+ "cellHeight": "sm",
+ "footer": {
+ "countRows": false,
+ "enablePagination": false,
+ "fields": "",
+ "reducer": [
+ "sum"
+ ],
+ "show": false
+ },
+ "frameIndex": 1,
+ "showHeader": true,
+ "sortBy": [
+ {
+ "desc": true,
+ "displayName": "Count"
+ }
+ ]
+ },
+ "pluginVersion": "12.1.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "loki",
+ "uid": "loki"
+ },
+ "direction": "backward",
+ "editorMode": "code",
+ "expr": "topk(20, sum by (domain) (count_over_time({ ${NON_WORKSPACE_SELECTOR}, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=`allow` | owner=~`$owner` | template_id=~`$template_id` | template_version_id=~`$template_version_id` | regexp `http_url=(?Phttps?)://(?P[^/:]+)` | domain=~`$domain` [$__auto])))",
+ "legendFormat": "",
+ "queryType": "instant",
+ "refId": "A"
+ }
+ ],
+ "title": "Top Allowed Domains",
+ "transformations": [
+ {
+ "id": "organize",
+ "options": {
+ "excludeByName": {
+ "Time": true
+ },
+ "includeByName": {},
+ "indexByName": {},
+ "renameByName": {
+ "Time": "",
+ "Value #A": "Count",
+ "domain": "Domain"
+ }
+ }
+ },
+ {
+ "id": "sortBy",
+ "options": {
+ "fields": {},
+ "sort": [
+ {
+ "desc": true,
+ "field": "Count"
+ }
+ ]
+ }
+ }
+ ],
+ "type": "table"
+ },
+ {
+ "datasource": {
+ "type": "loki",
+ "uid": "loki"
+ },
+ "description": "Top 20 denied domains for HTTP requests",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "custom": {
+ "align": "auto",
+ "cellOptions": {
+ "type": "auto"
+ },
+ "filterable": false,
+ "inspect": false
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Domain"
+ },
+ "properties": [
+ {
+ "id": "custom.width",
+ "value": 382
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 12,
+ "w": 12,
+ "x": 12,
+ "y": 7
+ },
+ "id": 2,
+ "options": {
+ "cellHeight": "sm",
+ "footer": {
+ "countRows": false,
+ "enablePagination": false,
+ "fields": "",
+ "reducer": [
+ "sum"
+ ],
+ "show": false
+ },
+ "frameIndex": 1,
+ "showHeader": true,
+ "sortBy": [
+ {
+ "desc": true,
+ "displayName": "Count"
+ }
+ ]
+ },
+ "pluginVersion": "12.1.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "loki",
+ "uid": "loki"
+ },
+ "direction": "backward",
+ "editorMode": "code",
+ "expr": "topk(20, sum by (domain) (count_over_time({ ${NON_WORKSPACE_SELECTOR}, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=`deny` | owner=~`$owner` | template_id=~`$template_id` | template_version_id=~`$template_version_id` | regexp `http_url=(?Phttps?)://(?P[^/:]+)` | domain=~`$domain` [$__auto])))",
+ "legendFormat": "",
+ "queryType": "instant",
+ "refId": "A"
+ }
+ ],
+ "title": "Top Denied Domains",
+ "transformations": [
+ {
+ "id": "organize",
+ "options": {
+ "excludeByName": {
+ "Time": true
+ },
+ "includeByName": {},
+ "indexByName": {},
+ "renameByName": {
+ "Time": "",
+ "Value #A": "Count",
+ "domain": "Domain"
+ }
+ }
+ },
+ {
+ "id": "sortBy",
+ "options": {
+ "fields": {},
+ "sort": [
+ {
+ "desc": true,
+ "field": "Count"
+ }
+ ]
+ }
+ }
+ ],
+ "type": "table"
+ },
+ {
+ "datasource": {
+ "type": "loki",
+ "uid": "loki"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "custom": {
+ "align": "auto",
+ "cellOptions": {
+ "type": "auto"
+ },
+ "inspect": false
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Time"
+ },
+ "properties": [
+ {
+ "id": "custom.width",
+ "value": 282
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Domain"
+ },
+ "properties": [
+ {
+ "id": "custom.width",
+ "value": 185
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "method"
+ },
+ "properties": [
+ {
+ "id": "custom.width",
+ "value": 73
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "path"
+ },
+ "properties": [
+ {
+ "id": "custom.width",
+ "value": 397
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Workspace Owner"
+ },
+ "properties": [
+ {
+ "id": "custom.width",
+ "value": 144
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Template ID"
+ },
+ "properties": [
+ {
+ "id": "custom.width",
+ "value": 195
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Workspace Name"
+ },
+ "properties": [
+ {
+ "id": "custom.width",
+ "value": 204
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 12,
+ "w": 24,
+ "x": 0,
+ "y": 19
+ },
+ "id": 3,
+ "options": {
+ "cellHeight": "sm",
+ "footer": {
+ "countRows": false,
+ "fields": "",
+ "reducer": [
+ "sum"
+ ],
+ "show": false
+ },
+ "showHeader": true,
+ "sortBy": []
+ },
+ "pluginVersion": "12.1.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "loki",
+ "uid": "loki"
+ },
+ "direction": "backward",
+ "editorMode": "code",
+ "expr": "{ ${NON_WORKSPACE_SELECTOR}, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=`allow` | owner=~`$owner` | template_id=~`$template_id` | template_version_id=~`$template_version_id` | regexp `http_url=https?://(?P[^/?# ]+)(?P/[^?# ]*)?` | domain=~`$domain` | line_format `time=\"{{.event_time}}\" method=\"{{.http_method}}\" domain=\"{{.domain}}\" path=\"{{.path}}\" owner=\"{{.owner}}\" workspace_name=\"{{.workspace_name}}\" template_id=\"{{.template_id}}\" template_version_id=\"{{.template_version_id}}\"`",
+ "queryType": "range",
+ "refId": "A"
+ }
+ ],
+ "title": "Most recent allowed requests",
+ "transformations": [
+ {
+ "id": "extractFields",
+ "options": {
+ "delimiter": "|",
+ "format": "kvp",
+ "keepTime": false,
+ "replace": true,
+ "source": "Line"
+ }
+ },
+ {
+ "id": "organize",
+ "options": {
+ "excludeByName": {},
+ "includeByName": {
+ "domain": true,
+ "method": true,
+ "owner": true,
+ "path": true,
+ "template_id": true,
+ "template_version_id": true,
+ "time": true,
+ "workspace_name": true
+ },
+ "indexByName": {
+ "domain": 2,
+ "method": 1,
+ "owner": 4,
+ "path": 3,
+ "template_id": 6,
+ "time": 0,
+ "workspace_name": 5
+ },
+ "renameByName": {
+ "domain": "Domain",
+ "method": "Method",
+ "owner": "Workspace Owner",
+ "path": "Path",
+ "template_id": "Template ID",
+ "template_version_id": "Template Version ID",
+ "time": "Time",
+ "workspace_name": "Workspace Name"
+ }
+ }
+ },
+ {
+ "id": "sortBy",
+ "options": {
+ "fields": {},
+ "sort": [
+ {
+ "desc": true,
+ "field": "Time"
+ }
+ ]
+ }
+ },
+ {
+ "id": "limit",
+ "options": {
+ "limitField": "10"
+ }
+ }
+ ],
+ "type": "table"
+ },
+ {
+ "datasource": {
+ "type": "loki",
+ "uid": "loki"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "custom": {
+ "align": "auto",
+ "cellOptions": {
+ "type": "auto"
+ },
+ "inspect": false
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": 0
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Time"
+ },
+ "properties": [
+ {
+ "id": "custom.width",
+ "value": 282
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Domain"
+ },
+ "properties": [
+ {
+ "id": "custom.width",
+ "value": 185
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "method"
+ },
+ "properties": [
+ {
+ "id": "custom.width",
+ "value": 73
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "path"
+ },
+ "properties": [
+ {
+ "id": "custom.width",
+ "value": 397
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Workspace Owner"
+ },
+ "properties": [
+ {
+ "id": "custom.width",
+ "value": 152
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 12,
+ "w": 24,
+ "x": 0,
+ "y": 31
+ },
+ "id": 6,
+ "options": {
+ "cellHeight": "sm",
+ "footer": {
+ "countRows": false,
+ "fields": "",
+ "reducer": [
+ "sum"
+ ],
+ "show": false
+ },
+ "showHeader": true,
+ "sortBy": []
+ },
+ "pluginVersion": "12.1.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "loki",
+ "uid": "loki"
+ },
+ "direction": "backward",
+ "editorMode": "code",
+ "expr": "{ ${NON_WORKSPACE_SELECTOR}, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=`deny` | owner=~`$owner` | template_id=~`$template_id` | template_version_id=~`$template_version_id` | regexp `http_url=https?://(?P[^/?# ]+)(?P/[^?# ]*)?` | domain=~`$domain` | line_format `time=\"{{.event_time}}\" method=\"{{.http_method}}\" domain=\"{{.domain}}\" path=\"{{.path}}\" owner=\"{{.owner}}\" workspace_name=\"{{.workspace_name}}\" template_id=\"{{.template_id}}\" template_version_id=\"{{.template_version_id}}\"`",
+ "queryType": "range",
+ "refId": "A"
+ }
+ ],
+ "title": "Most recent denied requests",
+ "transformations": [
+ {
+ "id": "extractFields",
+ "options": {
+ "delimiter": "|",
+ "format": "kvp",
+ "keepTime": false,
+ "replace": true,
+ "source": "Line"
+ }
+ },
+ {
+ "id": "organize",
+ "options": {
+ "excludeByName": {},
+ "includeByName": {
+ "domain": true,
+ "method": true,
+ "owner": true,
+ "path": true,
+ "template_id": true,
+ "template_version_id": true,
+ "time": true,
+ "workspace_name": true
+ },
+ "indexByName": {
+ "domain": 2,
+ "method": 1,
+ "owner": 4,
+ "path": 3,
+ "template_id": 6,
+ "time": 0,
+ "workspace_name": 5
+ },
+ "renameByName": {
+ "domain": "Domain",
+ "method": "Method",
+ "owner": "Workspace Owner",
+ "path": "Path",
+ "template_id": "Template ID",
+ "template_version_id": "Template Version ID",
+ "time": "Time",
+ "workspace_name": "Workspace Name"
+ }
+ }
+ },
+ {
+ "id": "sortBy",
+ "options": {
+ "fields": {},
+ "sort": [
+ {
+ "desc": true,
+ "field": "Time"
+ }
+ ]
+ }
+ },
+ {
+ "id": "limit",
+ "options": {
+ "limitField": "10"
+ }
+ }
+ ],
+ "type": "table"
+ }
+ ],
+ "preload": false,
+ "refresh": "${DASHBOARD_REFRESH}",
+ "schemaVersion": 41,
+ "tags": [],
+ "templating": {
+ "list": [
+ {
+ "current": {
+ "text": "",
+ "value": ""
+ },
+ "description": "Filter by request domain",
+ "label": "Domain",
+ "name": "domain",
+ "options": [
+ {
+ "selected": true,
+ "text": "",
+ "value": ""
+ }
+ ],
+ "query": "",
+ "type": "textbox"
+ },
+ {
+ "current": {
+ "text": "",
+ "value": ""
+ },
+ "description": "Filter requests by workspace owner",
+ "label": "Workspace Owner",
+ "name": "owner",
+ "options": [
+ {
+ "selected": true,
+ "text": "",
+ "value": ""
+ }
+ ],
+ "query": "",
+ "type": "textbox"
+ },
+ {
+ "current": {
+ "text": "",
+ "value": ""
+ },
+ "description": "Filter requests by template ID (UUID). Template IDs can be found via the CLI with \"coder templates list\".",
+ "label": "Template ID",
+ "name": "template_id",
+ "options": [
+ {
+ "selected": true,
+ "text": "",
+ "value": ""
+ }
+ ],
+ "query": "",
+ "type": "textbox"
+ },
+ {
+ "current": {
+ "text": "",
+ "value": ""
+ },
+ "description": "Filter by template version ID (UUID). A templates version IDs can be found via the CLI with \"coder templates versions list \".",
+ "label": "Template Version ID",
+ "name": "template_version_id",
+ "options": [
+ {
+ "selected": true,
+ "text": "",
+ "value": ""
+ }
+ ],
+ "query": "",
+ "type": "textbox"
+ }
+ ]
+ },
+ "time": {
+ "from": "now-${DASHBOARD_TIMERANGE}",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "browser",
+ "title": "Coder Agent Boundaries",
+ "uid": "agent-boundaries",
+ "version": 1,
+ "weekStart": ""
+}
diff --git a/infra/aws/us-east-2/k8s/monitoring/dashboards/status.json b/infra/aws/us-east-2/k8s/monitoring/dashboards/status.json
index 082998f..763c62c 100644
--- a/infra/aws/us-east-2/k8s/monitoring/dashboards/status.json
+++ b/infra/aws/us-east-2/k8s/monitoring/dashboards/status.json
@@ -821,7 +821,7 @@
"uid": "prometheus"
},
"editorMode": "code",
- "expr": "min(up{job=\"${PROMETHEUS_JOB}\"}) or vector(0)",
+ "expr": "vector(1)",
"instant": true,
"legendFormat": "__auto",
"range": false,
@@ -1380,123 +1380,6 @@
"title": "Grafana Agent",
"type": "stat"
},
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "description": "",
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "thresholds"
- },
- "mappings": [
- {
- "options": {
- "0": {
- "color": "red",
- "index": 1,
- "text": "Unhealthy"
- },
- "1": {
- "color": "green",
- "index": 0,
- "text": "Healthy"
- }
- },
- "type": "value"
- },
- {
- "options": {
- "match": "null",
- "result": {
- "color": "orange",
- "index": 2,
- "text": "Unknown"
- }
- },
- "type": "special"
- },
- {
- "options": {
- "match": "empty",
- "result": {
- "color": "orange",
- "index": 3,
- "text": "Unknown"
- }
- },
- "type": "special"
- },
- {
- "options": {
- "match": "null+nan",
- "result": {
- "index": 4,
- "text": "Unknown"
- }
- },
- "type": "special"
- }
- ],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "red",
- "value": null
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- }
- },
- "overrides": []
- },
- "gridPos": {
- "h": 5,
- "w": 4,
- "x": 0,
- "y": 14
- },
- "id": 12,
- "options": {
- "colorMode": "value",
- "graphMode": "area",
- "justifyMode": "auto",
- "orientation": "auto",
- "reduceOptions": {
- "calcs": [
- "lastNotNull"
- ],
- "fields": "",
- "values": false
- },
- "showPercentChange": false,
- "textMode": "auto",
- "wideLayout": true
- },
- "pluginVersion": "10.4.0",
- "targets": [
- {
- "datasource": {
- "type": "prometheus",
- "uid": "prometheus"
- },
- "editorMode": "code",
- "expr": "prometheus_config_last_reload_successful{job=\"${PROMETHEUS_JOB}\"}",
- "instant": true,
- "legendFormat": "__auto",
- "range": false,
- "refId": "A"
- }
- ],
- "title": "Prometheus Config",
- "type": "stat"
- },
{
"datasource": {
"type": "prometheus",
diff --git a/infra/aws/us-east-2/k8s/monitoring/main.tf b/infra/aws/us-east-2/k8s/monitoring/main.tf
index 6291ee3..3ef0173 100644
--- a/infra/aws/us-east-2/k8s/monitoring/main.tf
+++ b/infra/aws/us-east-2/k8s/monitoring/main.tf
@@ -1,86 +1,29 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- coderd = {
- source = "coder/coderd"
- }
- acme = {
- source = "vancluever/acme"
- }
- tls = {
- source = "hashicorp/tls"
- }
- }
- backend "s3" {}
-}
-
-##
-# Cluster Authentication Inputs
-##
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "addon_namespace" {
- type = string
- default = "observability"
-}
-
-variable "helm_coder_observability_timeout" {
- type = number
- default = 120 # In Seconds
+provider "aws" {
+ region = var.region
+ profile = var.profile
}
-variable "helm_coder_observability_version" {
- type = string
- default = "0.6.1"
+data "aws_eks_cluster" "this" {
+ name = var.cluster_name
}
-variable "helm_prometheus_operator_version" {
- type = string
- default = "24.0.2"
+data "aws_eks_cluster_auth" "this" {
+ name = var.cluster_name
}
-variable "helm_prometheus_operator_timeout" {
- type = number
- default = 120 # In Seconds
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
}
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
+data "aws_db_instance" "coder" {
+ db_instance_identifier = var.coder_db_rds_id
}
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
+data "aws_s3_bucket" "loki" {
+ bucket = var.loki_s3_bucket_name
}
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
+data "aws_region" "this" {}
provider "helm" {
kubernetes = {
@@ -96,247 +39,156 @@ provider "kubernetes" {
token = data.aws_eks_cluster_auth.this.token
}
-##
-# Coder DB Inputs
-##
-
-variable "coder_db_username" {
- type = string
- sensitive = true
-}
-
-variable "coder_db_password" {
- description = "Coder's DB password"
- type = string
- sensitive = true
-}
-
-variable "coder_db_host" {
- type = string
- sensitive = true
-}
-
-variable "coder_db_port" {
- type = number
- default = 5432
-}
-
-##
-# Coder Scraping Configs
-##
-
-variable "coderd_selector" {
- type = string
- default = "pod=~`coder.*`, pod!~`.*provisioner.*`"
-}
-
-variable "provisionerd_selector" {
- type = string
- default = "pod=~`coder-provisioner.*"
-}
-
-variable "coder_workspaces_selector" {
- type = string
- default = "namespace=`coder-workspaces`"
-}
-
-variable "coderd_namespace" {
- type = string
- default = "coder"
-}
-
-##
-# Loki Inputs
-##
+locals {
+ dashboards-path = "${path.module}/dashboards"
+ # coderd_selector = "pod=~`coder.*`, pod!~`.*provisioner.*`, namespace=`${local.coderd_namespace}`"
+ # coderd_selector = "pod=~`coder.*`, pod!~`.*provisioner.*`, namespace=~`(coder)`"
+ coderd_selector = "pod=~`coder.*`, namespace=~`coder`"
-variable "loki_iam_role_name" {
- type = string
- default = "loki-s3-access"
-}
+ provisionerd_selector = "pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)`"
-variable "loki_s3_chunk_bucket_name" {
- type = string
- sensitive = true
-}
+ # workspaces_selector = "namespace=`coder-ws*`"
+ workspaces_selector = "pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)`"
+ non_workspaces_selector = "namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`"
-variable "loki_s3_ruler_bucket_name" {
- type = string
- sensitive = true
+ dashboard_timerange = "12h"
+ dashboard_refresh = "30s"
}
-variable "loki_s3_bucket_region" {
- type = string
+locals {
+ common_name = replace(replace(var.domain_name, "https://", ""), "http://", "")
+ ssl_vol_friendly_name = replace(local.common_name, ".", "-")
}
-variable "loki_replicas" {
- type = number
- default = 3
+resource "kubernetes_manifest" "cert" {
- validation {
- condition = var.loki_replicas >= 0
- error_message = "'loki_replicas' must be >= 0."
+ field_manager {
+ force_conflicts = true
+ }
+ manifest = {
+ apiVersion = "cert-manager.io/v1"
+ kind = "Certificate"
+ metadata = {
+ labels = {} # var.cert_labels
+ name = local.ssl_vol_friendly_name
+ namespace = module.monitoring.namespace
+ }
+ spec = {
+ secretName = local.ssl_vol_friendly_name
+ # commonName = local.common_name
+ dnsNames = [local.common_name]
+ duration = "${90 * 24}h"
+ renewBefore = "8h"
+ additionalOutputFormats = [{
+ type = "CombinedPEM"
+ }, {
+ type = "DER"
+ }]
+ issuerRef = {
+ kind = "ClusterIssuer"
+ name = "issuer"
+ }
+ }
}
}
-##
-# Grafana Inputs
-##
-
-variable "grafana_security_key" {
- description = "Security Key used for signing data source settings e.g. secrets + passwords"
- type = string
- sensitive = true
-}
-
-variable "grafana_auth_username" {
- description = "Grafana Endpoint username"
- type = string
- sensitive = true
-}
-
-variable "grafana_auth_password" {
- description = "Grafana Endpoint password"
- type = string
- sensitive = true
-}
-
-variable "grafana_db_name" {
- description = "Grafana DB name"
- type = string
- default = "grafana"
-}
-
-variable "grafana_db_user" {
- description = "Grafana DB username"
- type = string
- default = "grafana"
-}
-
-variable "grafana_db_password" {
- description = "Grafana DB password"
- type = string
- sensitive = true
-}
-
-variable "grafana_db_host" {
- description = "Grafana DB hostname"
- type = string
- sensitive = true
+data "kubernetes_secret_v1" "cert" {
+ metadata {
+ name = kubernetes_manifest.cert.manifest.spec.secretName
+ namespace = module.monitoring.namespace
+ }
}
-variable "grafana_admin_username" {
- description = "Grafana Admin username"
- type = string
- sensitive = true
+locals {
+ pem_blocks = [
+ for block in regexall(
+ "-----BEGIN CERTIFICATE-----[\\s\\S]*?-----END CERTIFICATE-----",
+ data.kubernetes_secret_v1.cert.data["tls.crt"]
+ ) : block
+ ]
}
-variable "grafana_admin_password" {
- description = "Grafana Admin password"
- type = string
- sensitive = true
+resource "kubernetes_secret_v1" "acm-cert" {
+ metadata {
+ name = "acm-${kubernetes_manifest.cert.manifest.spec.secretName}"
+ namespace = module.monitoring.namespace
+ }
+ data = {
+ "leaf.crt" = local.pem_blocks[0]
+ "intermediate.crt" = join("\n", slice(local.pem_blocks, 1, length(local.pem_blocks)))
+ "tls.key" = data.kubernetes_secret_v1.cert.data["tls.key"]
+ }
}
-variable "grafana_root_domain" {
- type = string
+resource "kubernetes_manifest" "acm-cert" {
+ field_manager {
+ force_conflicts = true
+ }
+ manifest = {
+ apiVersion = "acm.services.k8s.aws/v1alpha1"
+ kind = "Certificate"
+ metadata = {
+ name = local.ssl_vol_friendly_name
+ namespace = module.monitoring.namespace
+ }
+ spec = {
+ certificate = {
+ key = "leaf.crt"
+ name = kubernetes_secret_v1.acm-cert.metadata[0].name
+ namespace = module.monitoring.namespace
+ }
+ certificateChain = {
+ key = "intermediate.crt"
+ name = kubernetes_secret_v1.acm-cert.metadata[0].name
+ namespace = module.monitoring.namespace
+ }
+ privateKey = {
+ key = "tls.key"
+ name = kubernetes_secret_v1.acm-cert.metadata[0].name
+ namespace = module.monitoring.namespace
+ }
+ }
+ }
}
-variable "grafana_subdomain" {
- type = string
-}
+data "aws_acm_certificate" "grafana" {
-variable "grafana_cert_name" {
- type = string
- default = "grafana-certs"
-}
+ depends_on = [kubernetes_manifest.acm-cert]
-variable "grafana_cert_mnt_path" {
- type = string
- default = "/etc/ssl/certs"
+ region = "us-east-1" # CloudFront requirement
+ domain = var.domain_name
+ statuses = ["ISSUED"]
}
locals {
- dashboards-path = "${path.module}/dashboards"
- # coderd_selector = "pod=~`coder.*`, pod!~`.*provisioner.*`, namespace=`${local.coderd_namespace}`"
- coderd_selector = "pod=~`coder.*`, pod!~`.*provisioner.*`, namespace=~`(coder)`"
-
- provisionerd_selector = "pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)`"
-
- # workspaces_selector = "namespace=`coder-ws*`"
- workspaces_selector = "pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)`"
- non_workspaces_selector = "namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`"
-
- dashboard_timerange = "12h"
- dashboard_refresh = "30s"
-}
-
-module "monitoring" {
- source = "../../../../../modules/k8s/bootstrap/monitoring"
-
- namespace = var.addon_namespace
- helm_coder_observability_version = var.helm_coder_observability_version
- helm_coder_observability_timeout = var.helm_coder_observability_timeout
- helm_prometheus_operator_version = var.helm_prometheus_operator_version
- helm_prometheus_operator_timeout = var.helm_prometheus_operator_timeout
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- coder_db_username = var.coder_db_username
- coder_db_password = var.coder_db_password
- coder_db_host = var.coder_db_host
- coder_db_port = var.coder_db_port
-
- loki_s3_chunk_bucket_name = var.loki_s3_chunk_bucket_name
- loki_s3_ruler_bucket_name = var.loki_s3_ruler_bucket_name
- loki_s3_bucket_region = var.loki_s3_bucket_region
- loki_iam_role_name = var.loki_iam_role_name
- loki_replicas = var.loki_replicas
-
- grafana_security_key = var.grafana_security_key
- grafana_auth_username = var.grafana_auth_username
- grafana_auth_password = var.grafana_auth_password
- grafana_db_user = var.grafana_db_user
- grafana_db_name = var.grafana_db_name
- grafana_db_password = var.grafana_db_password
- grafana_db_host = var.grafana_db_host
- grafana_admin_username = var.grafana_admin_username
- grafana_admin_password = var.grafana_admin_password
- grafana_root_domain = var.grafana_root_domain
- grafana_subdomain = var.grafana_subdomain
- grafana_cert_name = var.grafana_cert_name
- grafana_cert_mnt_path = var.grafana_cert_mnt_path
- grafana_replicas = 2
- grafana_service_annotations = {
- "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "instance"
- "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
- "service.beta.kubernetes.io/aws-load-balancer-attributes" = "deletion_protection.enabled=true"
- }
-
- dashboards = [
- {
- name = "coder-dashboard-status"
- localPath = "${local.dashboards-path}/status.json"
+ azs = slice(var.azs, 0, 1)
+ pub_subs = [for az in local.azs : "${var.vpc_name}-public-${data.aws_region.this.region}${az}"]
+ dashboard_config_maps = {
+ "coder-dashboard-status" = {
+ local_path = "${local.dashboards-path}/status.json"
+ mount_path = "/var/lib/grafana/dashboards/coder/0"
args = {
- HELM_NAMESPACE = var.addon_namespace
+ HELM_NAMESPACE = var.namespace
CODERD_SELECTOR = local.coderd_selector
PROVISIONERD_SELECTOR = local.provisionerd_selector
WORKSPACES_SELECTOR = local.workspaces_selector
- PROMETHEUS_JOB = "${var.addon_namespace}/prometheus/server"
- LOKI_JOB = "${var.addon_namespace}/loki"
- GRAFANA_AGENT_JOB = "${var.addon_namespace}/grafana-agent/grafana-agent"
+ PROMETHEUS_JOB = "${var.namespace}/prometheus/server"
+ LOKI_JOB = "${var.namespace}/loki"
+ GRAFANA_AGENT_JOB = "${var.namespace}/grafana-agent/grafana-agent"
}
},
- {
- name = "coder-dashboard-coderd"
- localPath = "${local.dashboards-path}/coderd.json"
+ "coder-dashboard-coderd" = {
+ local_path = "${local.dashboards-path}/coderd.json"
+ mount_path = "/var/lib/grafana/dashboards/coder/1"
args = {
DASHBOARD_TIMERANGE = local.dashboard_timerange
DASHBOARD_REFRESH = local.dashboard_refresh
CODERD_SELECTOR = local.coderd_selector
}
},
- {
- name = "coder-dashboard-provisionerd"
- localPath = "${local.dashboards-path}/provisionerd.json"
+ "coder-dashboard-provisionerd" = {
+ local_path = "${local.dashboards-path}/provisionerd.json"
+ mount_path = "/var/lib/grafana/dashboards/coder/2"
args = {
DASHBOARD_TIMERANGE = local.dashboard_timerange
DASHBOARD_REFRESH = local.dashboard_refresh
@@ -344,9 +196,9 @@ module "monitoring" {
NON_WORKSPACES_SELECTOR = local.non_workspaces_selector
}
},
- {
- name = "coder-dashboard-workspaces"
- localPath = "${local.dashboards-path}/workspaces.json"
+ "coder-dashboard-workspaces" = {
+ local_path = "${local.dashboards-path}/workspaces.json"
+ mount_path = "/var/lib/grafana/dashboards/coder/3"
args = {
DASHBOARD_TIMERANGE = local.dashboard_timerange
DASHBOARD_REFRESH = local.dashboard_refresh
@@ -354,9 +206,9 @@ module "monitoring" {
NON_WORKSPACES_SELECTOR = local.non_workspaces_selector
}
},
- {
- name = "coder-dashboard-workspace-detail"
- localPath = "${local.dashboards-path}/workspace_detail.json"
+ "coder-dashboard-workspace-detail" = {
+ local_path = "${local.dashboards-path}/workspace_detail.json"
+ mount_path = "/var/lib/grafana/dashboards/coder/4"
args = {
DASHBOARD_TIMERANGE = local.dashboard_timerange
DASHBOARD_REFRESH = local.dashboard_refresh
@@ -364,70 +216,282 @@ module "monitoring" {
NON_WORKSPACES_SELECTOR = local.non_workspaces_selector
}
},
- {
- name = "coder-dashboard-prebuilds"
- localPath = "${local.dashboards-path}/prebuilds.json"
+ "coder-dashboard-prebuilds" = {
+ local_path = "${local.dashboards-path}/prebuilds.json"
+ mount_path = "/var/lib/grafana/dashboards/coder/5"
args = {
DASHBOARD_TIMERANGE = local.dashboard_timerange
DASHBOARD_REFRESH = local.dashboard_refresh
}
},
- {
- name = "coder-dashboard-aibridge"
- localPath = "${local.dashboards-path}/aibridge.json"
- args = {}
+ "coder-dashboard-aibridge" = {
+ local_path = "${local.dashboards-path}/aibridge.json"
+ mount_path = "/var/lib/grafana/dashboards/coder/6"
+ args = {}
},
+ "coder-dashboard-boundary" = {
+ local_path = "${local.dashboards-path}/boundary.json"
+ mount_path = "/var/lib/grafana/dashboards/coder/7"
+ args = {
+ DASHBOARD_TIMERANGE = local.dashboard_timerange
+ DASHBOARD_REFRESH = local.dashboard_refresh
+ NON_WORKSPACE_SELECTOR = local.non_workspaces_selector
+ }
+ }
# {
# name = "coder-dashboard-proxyd"
- # localPath = "${local.dashboards-path}/proxyd.json"
+ # local_path = "${local.dashboards-path}/proxyd.json"
# args = {
# DASHBOARD_TIMERANGE = local.dashboard_timerange
# DASHBOARD_REFRESH = local.dashboard_refresh
# CODERD_SELECTOR = local.coderd_selector
# }
# }
- ]
+ }
+ default_dashboard_path = "${local.dashboard_config_maps["coder-dashboard-status"].mount_path}/${element(split("/", local.dashboard_config_maps["coder-dashboard-status"].local_path), -1)}"
+ # release_name = "coder"
+ # chart_name = "coder"
+ # namespace = "coder"
+}
+
+resource "aws_eip" "grafana" {
+ count = length(local.pub_subs)
+ domain = "vpc"
+ public_ipv4_pool = "amazon"
+ tags = {
+ Name = "grafana-eip-${count.index}"
+ }
+}
- # service_annotations = {
- # "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "instance"
- # "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
- # "service.beta.kubernetes.io/aws-load-balancer-attributes" = "deletion_protection.enabled=true"
- # }
- # node_selector = {
- # "node.coder.io/managed-by" = "karpenter"
- # "node.coder.io/used-for" = "coder"
- # }
- # tolerations = [{
- # key = "dedicated"
- # operator = "Equal"
- # value = "coder"
- # effect = "NoSchedule"
- # }]
- # topology_spread_constraints = [{
- # max_skew = 1
- # topology_key = "kubernetes.io/hostname"
- # when_unsatisfiable = "ScheduleAnyway"
- # label_selector = {
- # match_labels = {
- # "app.kubernetes.io/name" = "coder"
- # "app.kubernetes.io/part-of" = "coder"
- # }
- # }
- # match_label_keys = [
- # "app.kubernetes.io/instance"
- # ]
- # }]
- # pod_anti_affinity_preferred_during_scheduling_ignored_during_execution = [{
- # weight = 100
- # pod_affinity_term = {
- # label_selector = {
- # match_labels = {
- # "app.kubernetes.io/instance" = "coder-v2"
- # "app.kubernetes.io/name" = "coder"
- # "app.kubernetes.io/part-of" = "coder"
- # }
- # }
- # topology_key = "kubernetes.io/hostname"
- # }
- # }]
+module "monitoring" {
+
+ source = "../../../../../modules/k8s/bootstrap/monitoring"
+
+ chart_version = var.chart_version
+ chart_timeout = var.chart_timeout
+ cluster_name = var.cluster_name
+ cluster_oidc_provider_arn = data.aws_iam_openid_connect_provider.this.arn
+
+ vpc_name = var.vpc_name
+
+ namespace = var.namespace
+
+ lb_class = "service.k8s.aws/nlb"
+ domain_name = var.domain_name
+ acm_certificate_arn = data.aws_acm_certificate.grafana.arn
+
+ coder = {
+ db = {
+ host = data.aws_db_instance.coder.endpoint
+ database = data.aws_db_instance.coder.db_name
+ password = var.coder_db_password
+ username = var.coder_db_username
+ }
+ selector = {
+ coderd = "pod=~`coder.*`, pod!~`.*provisioner.*`, namespace=~`(coder)`"
+ provisionerd = "pod=~`coder-provisioner.*`, namespace=~`(coder)`"
+ workspaces = "pod!~`coder.*`, namespace=~`(coder)`"
+ ctrl_plane_ns = "coder"
+ ext_prov_ns = "coder"
+ }
+ }
+ grafana = {
+ replicas = 0
+ admin = {
+ username = var.grafana_admin_username
+ password = var.grafana_admin_password
+ }
+ db = {
+ host = "" # data.aws_db_instance.grafana.endpoint
+ database = "" # data.aws_db_instance.grafana.db_name
+ password = var.grafana_db_password
+ username = var.grafana_db_user
+ }
+ svc = {
+ port = 443
+ annots = {
+ "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "ip"
+ "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
+ "service.beta.kubernetes.io/aws-load-balancer-attributes" = "deletion_protection.enabled=false"
+ "service.beta.kubernetes.io/aws-load-balancer-target-group-attributes" = "stickiness.enabled=true,stickiness.type=source_ip"
+ "service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol" = "TCP"
+ "service.beta.kubernetes.io/aws-load-balancer-healthcheck-port" = 3000
+ "service.beta.kubernetes.io/aws-load-balancer-eip-allocations" = join(",", aws_eip.grafana.*.allocation_id)
+ "service.beta.kubernetes.io/aws-load-balancer-subnets" = join(",", local.pub_subs)
+ }
+ }
+ pv = {
+ enabled = false
+ storageClass = "gp3-automode"
+ }
+ tolerations = [{
+ key = "platform"
+ value = "observability-platform"
+ effect = "NoSchedule"
+ }]
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [{
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [for az in local.azs : "${data.aws_region.this.region}${az}"]
+ }, {
+ key = "node.coder.io/used-for",
+ operator = "In",
+ values = ["observability-platform"]
+ }, {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = ["arm64"]
+ }]
+ }]
+ }
+ }
+ podAntiAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = [{
+ topologyKey = "kubernetes.io/hostname"
+ labelSelector = {
+ matchLabels = {
+ "app.kubernetes.io/name" = "grafana"
+ }
+ }
+ }]
+ }
+ }
+ }
+ daemonset_node_selector = {}
+ mount_ssl = {
+ enable = true
+ secret_name = kubernetes_manifest.cert.manifest.metadata.name
+ mount_path = "/tmp/grafana/ssl/"
+ }
+ prometheus = {
+ ooo_window = "1800s"
+ readiness = {
+ period = 60
+ failure_threshold = 100
+ }
+ liveness = {
+ period = 60
+ failure_threshold = 100
+ }
+ pv = {
+ enabled = true
+ storageClass = "gp3-automode"
+ size = "512Mi"
+ }
+ rsrc = { # Let it be unbounded and run on it's own Node.
+ requests = {
+ cpu = "2"
+ memory = "8Gi"
+ }
+ limits = {
+ cpu = "6"
+ memory = "12Gi"
+ }
+ }
+ tolerations = [{
+ key = "platform"
+ value = "prometheus"
+ effect = "NoSchedule"
+ }]
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [{
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [for az in local.azs : "${data.aws_region.this.region}${az}"]
+ }, {
+ key = "node.coder.io/used-for"
+ operator = "In"
+ values = ["prometheus"]
+ }, {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = ["arm64"]
+ }]
+ }]
+ }
+ }
+ }
+ }
+ alertmanager = {
+ pv = {
+ enabled = true
+ storageClass = "gp3-automode"
+ size = "10Gi"
+ }
+ tolerations = [{
+ key = "platform"
+ value = "observability-platform"
+ effect = "NoSchedule"
+ }]
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [{
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [for az in local.azs : "${data.aws_region.this.region}${az}"]
+ }, {
+ key = "node.coder.io/used-for",
+ operator = "In",
+ values = ["observability-platform"]
+ }, {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = ["arm64"]
+ }]
+ }]
+ }
+ }
+ }
+ }
+ loki = {
+ s3 = {
+ chunks_bucket = data.aws_s3_bucket.loki.id
+ ruler_bucket = data.aws_s3_bucket.loki.id
+ region = data.aws_s3_bucket.loki.bucket_region
+ }
+ pv = {
+ enabled = true
+ storageClass = "gp3-automode"
+ }
+ tolerations = [{
+ key = "platform"
+ value = "observability-platform"
+ effect = "NoSchedule"
+ }]
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [{
+ matchExpressions = [{
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [for az in local.azs : "${data.aws_region.this.region}${az}"]
+ }, {
+ key = "node.coder.io/used-for",
+ operator = "In",
+ values = ["observability-platform"]
+ }, {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = ["arm64"]
+ }]
+ }]
+ }
+ }
+ }
+ }
+ dashboards = {
+ use_builtins = false
+ default_home_path = local.default_dashboard_path
+ config_maps = local.dashboard_config_maps
+ }
}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/monitoring/output.yaml b/infra/aws/us-east-2/k8s/monitoring/output.yaml
new file mode 100644
index 0000000..4ebabe6
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/monitoring/output.yaml
@@ -0,0 +1,26211 @@
+# data.aws_acm_certificate.grafana:
+data "aws_acm_certificate" "grafana" {
+ arn = "arn:aws:acm:us-east-1:750246862020:certificate/efdb05fe-1fea-4b84-a87d-f9ed160ec434"
+ certificate = <<-EOT
+ -----BEGIN CERTIFICATE-----
+ MIIFATCCA+mgAwIBAgISBuAmeoEqIH3z3a3loH3yceWHMA0GCSqGSIb3DQEBCwUA
+ MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD
+ EwNSMTIwHhcNMjYwMzIxMDU0MDEwWhcNMjYwNjE5MDU0MDA5WjAfMR0wGwYDVQQD
+ ExRncmFmYW5hLmFpLmNvZGVyLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+ AQoCggEBAN0Mdle0xxSY7OKzfK1rjxqs/AOO8Yep7lKYVn/HzpjWjGYmxoDz9NkI
+ RK9TBzL5gYRcQoatk/qNhZrG04sk46JcQQ3YImg21SbY9AGqUGoKPWZU1JlkPpdB
+ xqXE1BJcRCfSQMrvySpEsNdGv3RrCgdIRxlehQJwbeJDGWFyLFj+HHZuilSgeIKW
+ SDL2CUpbPWQgd45BA7UFIe3OB8XUo3SrZzz1nNX1RSRJsRwzaiM1O8QC+36TW+3y
+ tc2nF4SrYF4cpRzPTaAKsJ1Jl7PE+BSkgsuSXA/U1iRKWi4K/YVaIl+Ys1NAu0/6
+ YuZm+mcv6sHTjENP7hUAlhN8x8Hg3QUCAwEAAaOCAiEwggIdMA4GA1UdDwEB/wQE
+ AwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW
+ BBRX6hU1SNepA9wPBg7y2UiMh6FkuzAfBgNVHSMEGDAWgBQAtSnyLY5vMeibTK14
+ Pvrc6QzR0jAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAKGF2h0dHA6Ly9yMTIu
+ aS5sZW5jci5vcmcvMB8GA1UdEQQYMBaCFGdyYWZhbmEuYWkuY29kZXIuY29tMBMG
+ A1UdIAQMMAowCAYGZ4EMAQIBMC0GA1UdHwQmMCQwIqAgoB6GHGh0dHA6Ly9yMTIu
+ Yy5sZW5jci5vcmcvOC5jcmwwggEMBgorBgEEAdZ5AgQCBIH9BIH6APgAdQCWl2S/
+ VViXrfdDh2g3CEJ36fA61fak8zZuRqQ/D8qpxgAAAZ0PHgqzAAAEAwBGMEQCIFzP
+ evEdgyEiKp3Q9z3a1xjueU51M3C9nBTUW4maJBrhAiBxDDr6/biemXUuf1ErSawg
+ +tIvL9jQ6Lhdpq6qwPgUAwB/ABqLnWlKV5jImaDKiL30j8C0VmDMw2ANH3H0af/H
+ 0ayjAAABnQ8eDoMACAAABQBXlbOZBAMASDBGAiEA7eGMYdKD1OAlpoAiakmTwzAb
+ beLkAxHdcPT/F/shfZ4CIQDDLQE6195CmIq/wLn0W33ivdANIxLrQ8HbXXVyS3RH
+ QzANBgkqhkiG9w0BAQsFAAOCAQEA2ODHh/Tnx3kKq4M0c74SIgUphXSW3l+W4ghy
+ akh+pcxHmrzDlucGqKg7TcKI93SsT/l0ZbhkieZ6dfwsGenI9QubUYkVBJPkACMO
+ UN1CaMWv0pcp+9sqXRuM9Zf8Zz+auMVfJ5Fv6N/4CiSHAsCAvqmzOBvUI3O9jQn2
+ MAROcyjL3fiXD7u4Qvqfo0JbGqbyuuNdf5slkCv3Sdc1Sl4ZSqsxIDdAWlsEZ+/5
+ 47B8cuC/calMbPij+8SAexcexMiDVuPuN8UdIPTzHQqwdI+A3S8jGntzSVxGZ4+V
+ xzxjMInf60vDHijNRf1zGbP7LO9IyvEb1fRqFIiIuOpAIXSWmg==
+ -----END CERTIFICATE-----
+ EOT
+ certificate_chain = <<-EOT
+ -----BEGIN CERTIFICATE-----
+ MIIFBjCCAu6gAwIBAgIRAMISMktwqbSRcdxA9+KFJjwwDQYJKoZIhvcNAQELBQAw
+ TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
+ cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw
+ WhcNMjcwMzEyMjM1OTU5WjAzMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
+ RW5jcnlwdDEMMAoGA1UEAxMDUjEyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+ CgKCAQEA2pgodK2+lP474B7i5Ut1qywSf+2nAzJ+Npfs6DGPpRONC5kuHs0BUT1M
+ 5ShuCVUxqqUiXXL0LQfCTUA83wEjuXg39RplMjTmhnGdBO+ECFu9AhqZ66YBAJpz
+ kG2Pogeg0JfT2kVhgTU9FPnEwF9q3AuWGrCf4yrqvSrWmMebcas7dA8827JgvlpL
+ Thjp2ypzXIlhZZ7+7Tymy05v5J75AEaz/xlNKmOzjmbGGIVwx1Blbzt05UiDDwhY
+ XS0jnV6j/ujbAKHS9OMZTfLuevYnnuXNnC2i8n+cF63vEzc50bTILEHWhsDp7CH4
+ WRt/uTp8n1wBnWIEwii9Cq08yhDsGwIDAQABo4H4MIH1MA4GA1UdDwEB/wQEAwIB
+ hjAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwEgYDVR0TAQH/BAgwBgEB
+ /wIBADAdBgNVHQ4EFgQUALUp8i2ObzHom0yteD763OkM0dIwHwYDVR0jBBgwFoAU
+ ebRZ5nu25eQBc4AIiMgaWPbpm24wMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAC
+ hhZodHRwOi8veDEuaS5sZW5jci5vcmcvMBMGA1UdIAQMMAowCAYGZ4EMAQIBMCcG
+ A1UdHwQgMB4wHKAaoBiGFmh0dHA6Ly94MS5jLmxlbmNyLm9yZy8wDQYJKoZIhvcN
+ AQELBQADggIBAI910AnPanZIZTKS3rVEyIV29BWEjAK/duuz8eL5boSoVpHhkkv3
+ 4eoAeEiPdZLj5EZ7G2ArIK+gzhTlRQ1q4FKGpPPaFBSpqV/xbUb5UlAXQOnkHn3m
+ FVj+qYv87/WeY+Bm4sN3Ox8BhyaU7UAQ3LeZ7N1X01xxQe4wIAAE3JVLUCiHmZL+
+ qoCUtgYIFPgcg350QMUIWgxPXNGEncT921ne7nluI02V8pLUmClqXOsCwULw+PVO
+ ZCB7qOMxxMBoCUeL2Ll4oMpOSr5pJCpLN3tRA2s6P1KLs9TSrVhOk+7LX28NMUlI
+ usQ/nxLJID0RhAeFtPjyOCOscQBA53+NRjSCak7P4A5jX7ppmkcJECL+S0i3kXVU
+ y5Me5BbrU8973jZNv/ax6+ZK6TM8jWmimL6of6OrX7ZU6E2WqazzsFrLG3o2kySb
+ zlhSgJ81Cl4tv3SbYiYXnJExKQvzf83DYotox3f0fwv7xln1A2ZLplCb0O+l/AK0
+ YE0DS2FPxSAHi0iwMfW2nNHJrXcY3LLHD77gRgje4Eveubi2xxa+Nmk/hmhLdIET
+ iVDFanoCrMVIpQ59XWHkzdFmoHXHBV7oibVjGSO7ULSQ7MJ1Nz51phuDJSgAIU7A
+ 0zrLnOrAj/dfrlEWRhCvAgbuwLZX1A2sjNjXoPOHbsPiy+lO1KF8/XY7
+ -----END CERTIFICATE-----
+ EOT
+ domain = "grafana.ai.coder.com"
+ id = "arn:aws:acm:us-east-1:750246862020:certificate/efdb05fe-1fea-4b84-a87d-f9ed160ec434"
+ most_recent = false
+ region = "us-east-1"
+ status = "ISSUED"
+ statuses = [
+ "ISSUED",
+ ]
+ tags = {
+ "services.k8s.aws/controller-version" = "acm-1.3.4"
+ "services.k8s.aws/namespace" = "observability"
+ }
+}
+
+# data.aws_db_instance.coder:
+data "aws_db_instance" "coder" {
+ address = "aienv.clcmw4qgylx0.us-east-2.rds.amazonaws.com"
+ allocated_storage = 40
+ auto_minor_version_upgrade = true
+ availability_zone = "us-east-2b"
+ backup_retention_period = 0
+ ca_cert_identifier = "rds-ca-rsa2048-g1"
+ database_insights_mode = "standard"
+ db_cluster_identifier = [90mnull[0m[0m
+ db_instance_arn = "arn:aws:rds:us-east-2:750246862020:db:aienv"
+ db_instance_class = "db.m5.large"
+ db_instance_identifier = "aienv"
+ db_instance_port = 0
+ db_name = "coder"
+ db_parameter_groups = [
+ "default.postgres15",
+ ]
+ db_subnet_group = "aienv-subnet-group"
+ enabled_cloudwatch_logs_exports = []
+ endpoint = "aienv.clcmw4qgylx0.us-east-2.rds.amazonaws.com:5432"
+ engine = "postgres"
+ engine_version = "15.12"
+ hosted_zone_id = "Z2XHWR1WZ565X2"
+ id = "aienv"
+ iops = 0
+ kms_key_id = [90mnull[0m[0m
+ license_model = "postgresql-license"
+ master_username = "coder"
+ max_allocated_storage = 0
+ monitoring_interval = 0
+ monitoring_role_arn = [90mnull[0m[0m
+ multi_az = false
+ network_type = "IPV4"
+ option_group_memberships = [
+ "default:postgres-15",
+ ]
+ port = 5432
+ preferred_backup_window = "10:03-10:33"
+ preferred_maintenance_window = "tue:04:18-tue:04:48"
+ publicly_accessible = false
+ region = "us-east-2"
+ replicate_source_db = [90mnull[0m[0m
+ resource_id = "db-SPMYAULZGZ67VUAKHKY3IYGX4I"
+ storage_encrypted = false
+ storage_throughput = 0
+ storage_type = "gp2"
+ tags = {
+ "Name" = "aienv"
+ }
+ timezone = [90mnull[0m[0m
+ upgrade_rollout_order = "second"
+ vpc_security_groups = [
+ "sg-0cd40006010ebb02b",
+ ]
+}
+
+# data.aws_db_instance.grafana:
+data "aws_db_instance" "grafana" {
+ address = "aienv-grafana.clcmw4qgylx0.us-east-2.rds.amazonaws.com"
+ allocated_storage = 50
+ auto_minor_version_upgrade = true
+ availability_zone = "us-east-2b"
+ backup_retention_period = 0
+ ca_cert_identifier = "rds-ca-rsa2048-g1"
+ database_insights_mode = "standard"
+ db_cluster_identifier = [90mnull[0m[0m
+ db_instance_arn = "arn:aws:rds:us-east-2:750246862020:db:aienv-grafana"
+ db_instance_class = "db.m5.large"
+ db_instance_identifier = "aienv-grafana"
+ db_instance_port = 0
+ db_name = "grafana"
+ db_parameter_groups = [
+ "default.postgres15",
+ ]
+ db_subnet_group = "aienv-subnet-group"
+ enabled_cloudwatch_logs_exports = []
+ endpoint = "aienv-grafana.clcmw4qgylx0.us-east-2.rds.amazonaws.com:5432"
+ engine = "postgres"
+ engine_version = "15.12"
+ hosted_zone_id = "Z2XHWR1WZ565X2"
+ id = "aienv-grafana"
+ iops = 0
+ kms_key_id = [90mnull[0m[0m
+ license_model = "postgresql-license"
+ master_username = "grafana"
+ max_allocated_storage = 0
+ monitoring_interval = 0
+ monitoring_role_arn = [90mnull[0m[0m
+ multi_az = false
+ network_type = "IPV4"
+ option_group_memberships = [
+ "default:postgres-15",
+ ]
+ port = 5432
+ preferred_backup_window = "07:22-07:52"
+ preferred_maintenance_window = "tue:05:51-tue:06:21"
+ publicly_accessible = false
+ region = "us-east-2"
+ replicate_source_db = [90mnull[0m[0m
+ resource_id = "db-C2RAQDARRSOGRRPOJWQQKQXY3Y"
+ storage_encrypted = false
+ storage_throughput = 0
+ storage_type = "gp2"
+ tags = {
+ "Name" = "aienv-grafana"
+ }
+ timezone = [90mnull[0m[0m
+ upgrade_rollout_order = "second"
+ vpc_security_groups = [
+ "sg-0cd40006010ebb02b",
+ ]
+}
+
+# data.aws_eks_cluster.this:
+data "aws_eks_cluster" "this" {
+ access_config = [
+ {
+ authentication_mode = "API_AND_CONFIG_MAP"
+ bootstrap_cluster_creator_admin_permissions = true
+ },
+ ]
+ arn = "arn:aws:eks:us-east-2:750246862020:cluster/aienv"
+ certificate_authority = [
+ {
+ data = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJQm9JanRQaFZoQm93RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpBeU1qRXdNREkzTlRSYUZ3MHpOakF5TVRrd01ETXlOVFJhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUM5eVpyUE9FS0dFM2RnaHlYQXh6YTROZVdSUHZEazRMYlZ5RHkwUHowZFBoVFRQeXRPZDRNOHZQRzkKSFJJOFpKR3JUUWNuZDRhdFdvMzFOaHVCWUtUTytvVzZENy9ZcENDbHBqdXcreHN1YmxINFdaUE9MQjd5dWVsNwpIUkgwWlVQUFk3YWNyRWVUZzM3WHQrZkx6OWl4eXRkd1dxT1R0K3ZwcWZHQUJOcnBQQktjTkh1OVlZUnBXOW5NClZTODcycW5MRG15S3JMNzd5Nmh5aXlkaDVUUDdiaW5KQXFrNnYxeFF5cTJhMmZaNlhERmFmbGR5UXVDSHpkYjEKbTVCaEg2aG1jN2d6d3Facm9zWEtUMUM0bWYxN3hUS3N6SGdvdWcranA1SytITUJkSFdWbldqSnVJNG4xY3JPZwpFNXhGVjN4bld3NUpCY1hybTVvZmNGZ0hvY1VQQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJRc1hlVnVqZGhDTmphYUtORm8rU01JVnFCMTNqQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ0Y4TG1MbXh4dAovTFZaamZWVVR0UzgvZ0RRTDZaSWtFcVd3Yjk3dXV4d0ppdk5OUGZ5UUg1a0hIY0M5NnVXd21LMlJGWHlCUXl2CllaQzYzZG1GWndCdUhWL0JVZ05mRWlPR1ZBVFRtOG5PeDQ5anBVUkNOUEpXRkQ4eEdWUU9FNURQQ1NBbUhuS3EKZEtRaU9FZ1lGL1J1S0FUcGk1V3pCOCtPK3llYzZ6cFR1N1p6OGpaOUlZWE5YazRyRkRuRUFqRXZyU0JFQ1BFZgp0MHU1c0xSZ1NzK1NrWm9lQ3J3d2EyczRDbTVLSms5cW1KUW12TmpaeURxTzV2VG01UVlwUE1VQk5IdHQvdDBuClNVeE8xRTdFb0FycFlMaEZqNHZJeUI1NmM0dFJmcmVkaUlXV3dBeDZJV0ZQMmZpMCtOVEx5ZCtaS3BaL1ZVZFkKR0ZDR3BwZDVFTEI3Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
+ },
+ ]
+ compute_config = [
+ {
+ enabled = true
+ node_pools = []
+ node_role_arn = [90mnull[0m[0m
+ },
+ ]
+ control_plane_scaling_config = [
+ {
+ tier = "standard"
+ },
+ ]
+ created_at = "2026-02-21T00:27:17Z"
+ deletion_protection = false
+ enabled_cluster_log_types = [
+ "api",
+ "audit",
+ "authenticator",
+ ]
+ endpoint = "https://3FBD20508B38BD670EEBFDCD8B595F21.gr7.us-east-2.eks.amazonaws.com"
+ id = "aienv"
+ identity = [
+ {
+ oidc = [
+ {
+ issuer = "https://oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21"
+ },
+ ]
+ },
+ ]
+ kubernetes_network_config = [
+ {
+ elastic_load_balancing = [
+ {
+ enabled = true
+ },
+ ]
+ ip_family = "ipv4"
+ service_ipv4_cidr = "172.20.0.0/16"
+ service_ipv6_cidr = [90mnull[0m[0m
+ },
+ ]
+ name = "aienv"
+ outpost_config = []
+ platform_version = "eks.8"
+ region = "us-east-2"
+ remote_network_config = []
+ role_arn = "arn:aws:iam::750246862020:role/aienv-cluster-20260221002707829100000002"
+ status = "ACTIVE"
+ storage_config = [
+ {
+ block_storage = [
+ {
+ enabled = true
+ },
+ ]
+ },
+ ]
+ tags = {
+ "Name" = "aienv"
+ "karpenter.sh/discovery" = "aienv"
+ }
+ upgrade_policy = [
+ {
+ support_type = "EXTENDED"
+ },
+ ]
+ version = "1.35"
+ vpc_config = [
+ {
+ cluster_security_group_id = "sg-0127b226c557f0ac3"
+ endpoint_private_access = true
+ endpoint_public_access = true
+ public_access_cidrs = [
+ "0.0.0.0/0",
+ ]
+ security_group_ids = [
+ "sg-03753ff6e537ac70a",
+ ]
+ subnet_ids = [
+ "subnet-051d16c19e37d0bc1",
+ "subnet-05545af984558cec0",
+ "subnet-0b174a3b86bc06dd0",
+ ]
+ vpc_id = "vpc-05bbe4817c590a330"
+ },
+ ]
+ zonal_shift_config = []
+}
+
+# data.aws_eks_cluster_auth.this:
+data "aws_eks_cluster_auth" "this" {
+ id = "aienv"
+ name = "aienv"
+ region = "us-east-2"
+ token = (sensitive value)
+}
+
+# data.aws_iam_openid_connect_provider.this:
+data "aws_iam_openid_connect_provider" "this" {
+ arn = "arn:aws:iam::750246862020:oidc-provider/oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21"
+ client_id_list = [
+ "sts.amazonaws.com",
+ ]
+ id = "arn:aws:iam::750246862020:oidc-provider/oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21"
+ tags = {
+ "Name" = "aienv"
+ "karpenter.sh/discovery" = "aienv"
+ }
+ thumbprint_list = [
+ "06b25927c42a721631c1efd9431e648fa62e1e39",
+ ]
+ url = "oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21"
+}
+
+# data.aws_region.this:
+data "aws_region" "this" {
+ description = "US East (Ohio)"
+ endpoint = "ec2.us-east-2.amazonaws.com"
+ id = "us-east-2"
+ name = "us-east-2"
+ region = "us-east-2"
+}
+
+# data.aws_s3_bucket.loki:
+data "aws_s3_bucket" "loki" {
+ arn = "arn:aws:s3:::aienv-v2-logs"
+ bucket = "aienv-v2-logs"
+ bucket_domain_name = "aienv-v2-logs.s3.amazonaws.com"
+ bucket_region = "us-east-2"
+ bucket_regional_domain_name = "aienv-v2-logs.s3.us-east-2.amazonaws.com"
+ hosted_zone_id = "Z2O1EMRO9K5GLX"
+ id = "aienv-v2-logs"
+ region = "us-east-2"
+}
+
+# data.kubernetes_secret_v1.cert:
+data "kubernetes_secret_v1" "cert" {
+ data = (sensitive value)
+ id = "observability/grafana-ai-coder-com"
+ immutable = false
+ type = "kubernetes.io/tls"
+
+ metadata {
+ annotations = {
+ "cert-manager.io/alt-names" = "grafana.ai.coder.com"
+ "cert-manager.io/certificate-name" = "grafana-ai-coder-com"
+ "cert-manager.io/common-name" = "grafana.ai.coder.com"
+ "cert-manager.io/ip-sans" = [90mnull[0m[0m
+ "cert-manager.io/issuer-group" = [90mnull[0m[0m
+ "cert-manager.io/issuer-kind" = "ClusterIssuer"
+ "cert-manager.io/issuer-name" = "issuer"
+ "cert-manager.io/uri-sans" = [90mnull[0m[0m
+ }
+ generate_name = [90mnull[0m[0m
+ generation = 0
+ labels = {
+ "controller.cert-manager.io/fao" = "true"
+ }
+ name = "grafana-ai-coder-com"
+ namespace = "observability"
+ resource_version = "28650121"
+ uid = "e0830dea-7770-4a09-86d2-c2fbf69ea817"
+ }
+}
+
+# aws_eip.grafana[0]:
+resource "aws_eip" "grafana" {
+ allocation_id = "eipalloc-0533bf84460b51c11"
+ arn = "arn:aws:ec2:us-east-2:750246862020:elastic-ip/eipalloc-0533bf84460b51c11"
+ association_id = [90mnull[0m[0m
+ carrier_ip = [90mnull[0m[0m
+ customer_owned_ip = [90mnull[0m[0m
+ customer_owned_ipv4_pool = [90mnull[0m[0m
+ domain = "vpc"
+ id = "eipalloc-0533bf84460b51c11"
+ instance = [90mnull[0m[0m
+ network_border_group = "us-east-2"
+ network_interface = [90mnull[0m[0m
+ private_dns = "ip-10-0-1-23.us-east-2.compute.internal"
+ private_ip = [90mnull[0m[0m
+ ptr_record = [90mnull[0m[0m
+ public_dns = "ec2-3-143-78-136.us-east-2.compute.amazonaws.com"
+ public_ip = "3.143.78.136"
+ public_ipv4_pool = "amazon"
+ region = "us-east-2"
+ tags = {
+ "Name" = "grafana-eip-0"
+ }
+ tags_all = {
+ "Name" = "grafana-eip-0"
+ }
+}
+
+# kubernetes_manifest.acm-cert:
+resource "kubernetes_manifest" "acm-cert" {
+ manifest = {
+ apiVersion = "acm.services.k8s.aws/v1alpha1"
+ kind = "Certificate"
+ metadata = {
+ name = "grafana-ai-coder-com"
+ namespace = "observability"
+ }
+ spec = {
+ certificate = {
+ key = "leaf.crt"
+ name = "acm-grafana-ai-coder-com"
+ namespace = "observability"
+ }
+ certificateChain = {
+ key = "intermediate.crt"
+ name = "acm-grafana-ai-coder-com"
+ namespace = "observability"
+ }
+ privateKey = {
+ key = "tls.key"
+ name = "acm-grafana-ai-coder-com"
+ namespace = "observability"
+ }
+ }
+ }
+ object = {
+ apiVersion = "acm.services.k8s.aws/v1alpha1"
+ kind = "Certificate"
+ metadata = {
+ annotations = [90mnull[0m[0m
+ creationTimestamp = [90mnull[0m[0m
+ deletionGracePeriodSeconds = [90mnull[0m[0m
+ deletionTimestamp = [90mnull[0m[0m
+ finalizers = [
+ "finalizers.acm.services.k8s.aws/Certificate",
+ ]
+ generateName = [90mnull[0m[0m
+ generation = [90mnull[0m[0m
+ labels = [90mnull[0m[0m
+ managedFields = [90mnull[0m[0m
+ name = "grafana-ai-coder-com"
+ namespace = "observability"
+ ownerReferences = [90mnull[0m[0m
+ resourceVersion = [90mnull[0m[0m
+ selfLink = [90mnull[0m[0m
+ uid = [90mnull[0m[0m
+ }
+ spec = {
+ certificate = {
+ key = "leaf.crt"
+ name = "acm-grafana-ai-coder-com"
+ namespace = "observability"
+ }
+ certificateARN = [90mnull[0m[0m
+ certificateAuthorityARN = [90mnull[0m[0m
+ certificateAuthorityRef = {
+ from = {
+ name = [90mnull[0m[0m
+ namespace = [90mnull[0m[0m
+ }
+ }
+ certificateChain = {
+ key = "intermediate.crt"
+ name = "acm-grafana-ai-coder-com"
+ namespace = "observability"
+ }
+ domainName = [90mnull[0m[0m
+ domainValidationOptions = [
+ {
+ domainName = "grafana.ai.coder.com"
+ validationDomain = [90mnull[0m[0m
+ },
+ ]
+ exportTo = {
+ key = [90mnull[0m[0m
+ name = [90mnull[0m[0m
+ namespace = [90mnull[0m[0m
+ }
+ keyAlgorithm = "RSA-2048"
+ options = {
+ certificateTransparencyLoggingPreference = "DISABLED"
+ }
+ privateKey = {
+ key = "tls.key"
+ name = "acm-grafana-ai-coder-com"
+ namespace = "observability"
+ }
+ subjectAlternativeNames = [
+ "grafana.ai.coder.com",
+ ]
+ tags = [90mnull[0m[0m
+ }
+ }
+
+ field_manager {
+ force_conflicts = true
+ }
+}
+
+# kubernetes_manifest.cert:
+resource "kubernetes_manifest" "cert" {
+ manifest = {
+ apiVersion = "cert-manager.io/v1"
+ kind = "Certificate"
+ metadata = {
+ labels = {}
+ name = "grafana-ai-coder-com"
+ namespace = "observability"
+ }
+ spec = {
+ additionalOutputFormats = [
+ {
+ type = "CombinedPEM"
+ },
+ {
+ type = "DER"
+ },
+ ]
+ dnsNames = [
+ "grafana.ai.coder.com",
+ ]
+ duration = "2160h"
+ issuerRef = {
+ kind = "ClusterIssuer"
+ name = "issuer"
+ }
+ renewBefore = "8h"
+ secretName = "grafana-ai-coder-com"
+ }
+ }
+ object = {
+ apiVersion = "cert-manager.io/v1"
+ kind = "Certificate"
+ metadata = {
+ annotations = [90mnull[0m[0m
+ creationTimestamp = [90mnull[0m[0m
+ deletionGracePeriodSeconds = [90mnull[0m[0m
+ deletionTimestamp = [90mnull[0m[0m
+ finalizers = [90mnull[0m[0m
+ generateName = [90mnull[0m[0m
+ generation = [90mnull[0m[0m
+ labels = [90mnull[0m[0m
+ managedFields = [90mnull[0m[0m
+ name = "grafana-ai-coder-com"
+ namespace = "observability"
+ ownerReferences = [90mnull[0m[0m
+ resourceVersion = [90mnull[0m[0m
+ selfLink = [90mnull[0m[0m
+ uid = [90mnull[0m[0m
+ }
+ spec = {
+ additionalOutputFormats = [
+ {
+ type = "CombinedPEM"
+ },
+ {
+ type = "DER"
+ },
+ ]
+ commonName = [90mnull[0m[0m
+ dnsNames = [
+ "grafana.ai.coder.com",
+ ]
+ duration = "2160h"
+ emailAddresses = [90mnull[0m[0m
+ encodeUsagesInRequest = [90mnull[0m[0m
+ ipAddresses = [90mnull[0m[0m
+ isCA = [90mnull[0m[0m
+ issuerRef = {
+ group = [90mnull[0m[0m
+ kind = "ClusterIssuer"
+ name = "issuer"
+ }
+ keystores = {
+ jks = {
+ alias = [90mnull[0m[0m
+ create = [90mnull[0m[0m
+ password = [90mnull[0m[0m
+ passwordSecretRef = {
+ key = [90mnull[0m[0m
+ name = [90mnull[0m[0m
+ }
+ }
+ pkcs12 = {
+ create = [90mnull[0m[0m
+ password = [90mnull[0m[0m
+ passwordSecretRef = {
+ key = [90mnull[0m[0m
+ name = [90mnull[0m[0m
+ }
+ profile = [90mnull[0m[0m
+ }
+ }
+ literalSubject = [90mnull[0m[0m
+ nameConstraints = {
+ critical = [90mnull[0m[0m
+ excluded = {
+ dnsDomains = [90mnull[0m[0m
+ emailAddresses = [90mnull[0m[0m
+ ipRanges = [90mnull[0m[0m
+ uriDomains = [90mnull[0m[0m
+ }
+ permitted = {
+ dnsDomains = [90mnull[0m[0m
+ emailAddresses = [90mnull[0m[0m
+ ipRanges = [90mnull[0m[0m
+ uriDomains = [90mnull[0m[0m
+ }
+ }
+ otherNames = [90mnull[0m[0m
+ privateKey = {
+ algorithm = [90mnull[0m[0m
+ encoding = [90mnull[0m[0m
+ rotationPolicy = [90mnull[0m[0m
+ size = [90mnull[0m[0m
+ }
+ renewBefore = "8h"
+ renewBeforePercentage = [90mnull[0m[0m
+ revisionHistoryLimit = [90mnull[0m[0m
+ secretName = "grafana-ai-coder-com"
+ secretTemplate = {
+ annotations = [90mnull[0m[0m
+ labels = [90mnull[0m[0m
+ }
+ signatureAlgorithm = [90mnull[0m[0m
+ subject = {
+ countries = [90mnull[0m[0m
+ localities = [90mnull[0m[0m
+ organizationalUnits = [90mnull[0m[0m
+ organizations = [90mnull[0m[0m
+ postalCodes = [90mnull[0m[0m
+ provinces = [90mnull[0m[0m
+ serialNumber = [90mnull[0m[0m
+ streetAddresses = [90mnull[0m[0m
+ }
+ uris = [90mnull[0m[0m
+ usages = [90mnull[0m[0m
+ }
+ }
+
+ field_manager {
+ force_conflicts = true
+ }
+}
+
+# kubernetes_secret_v1.acm-cert:
+resource "kubernetes_secret_v1" "acm-cert" {
+ binary_data_wo = (write-only attribute)
+ data = (sensitive value)
+ data_wo = (write-only attribute)
+ id = "observability/acm-grafana-ai-coder-com"
+ immutable = false
+ type = "Opaque"
+ wait_for_service_account_token = true
+
+ metadata {
+ annotations = {}
+ generate_name = [90mnull[0m[0m
+ generation = 0
+ labels = {}
+ name = "acm-grafana-ai-coder-com"
+ namespace = "observability"
+ resource_version = "29227897"
+ uid = "72e8ad9e-80ea-4ac8-b74b-8787949be841"
+ }
+}
+
+
+# module.monitoring.data.aws_caller_identity.this:
+data "aws_caller_identity" "this" {
+ account_id = "750246862020"
+ arn = "arn:aws:sts::750246862020:assumed-role/AWSReservedSSO_AWSAdministratorAccess_93fb9e9f44bd25bb/jullian@coder.com"
+ id = "750246862020"
+ user_id = "AROA25LRSTTCBCK4VJ4NA:jullian@coder.com"
+}
+
+# module.monitoring.data.aws_iam_policy_document.grafana:
+data "aws_iam_policy_document" "grafana" {
+ id = "149203050"
+ json = jsonencode(
+ {
+ Statement = [
+ {
+ Action = [
+ "aps:QueryMetrics",
+ "aps:ListWorkspaces",
+ "aps:GetSeries",
+ "aps:GetMetricMetadata",
+ "aps:GetLabels",
+ "aps:DescribeWorkspace",
+ ]
+ Effect = "Allow"
+ Resource = "*"
+ },
+ ]
+ Version = "2012-10-17"
+ }
+ )
+ minified_json = jsonencode(
+ {
+ Statement = [
+ {
+ Action = [
+ "aps:QueryMetrics",
+ "aps:ListWorkspaces",
+ "aps:GetSeries",
+ "aps:GetMetricMetadata",
+ "aps:GetLabels",
+ "aps:DescribeWorkspace",
+ ]
+ Effect = "Allow"
+ Resource = "*"
+ },
+ ]
+ Version = "2012-10-17"
+ }
+ )
+ version = "2012-10-17"
+
+ statement {
+ actions = [
+ "aps:DescribeWorkspace",
+ "aps:GetLabels",
+ "aps:GetMetricMetadata",
+ "aps:GetSeries",
+ "aps:ListWorkspaces",
+ "aps:QueryMetrics",
+ ]
+ effect = "Allow"
+ not_actions = []
+ not_resources = []
+ resources = [
+ "*",
+ ]
+ sid = [90mnull[0m[0m
+ }
+}
+
+# module.monitoring.data.aws_iam_policy_document.grafana-sts:
+data "aws_iam_policy_document" "grafana-sts" {
+ id = "3359387141"
+ json = jsonencode(
+ {
+ Statement = [
+ {
+ Action = "sts:AssumeRole"
+ Condition = {
+ StringEquals = {
+ "aws:SourceAccount" = "750246862020"
+ }
+ StringLike = {
+ "aws:SourceArn" = "arn:aws:grafana:us-east-2:750246862020:/workspaces/*"
+ }
+ }
+ Effect = "Allow"
+ Principal = {
+ Service = "grafana.amazonaws.com"
+ }
+ },
+ ]
+ Version = "2012-10-17"
+ }
+ )
+ minified_json = jsonencode(
+ {
+ Statement = [
+ {
+ Action = "sts:AssumeRole"
+ Condition = {
+ StringEquals = {
+ "aws:SourceAccount" = "750246862020"
+ }
+ StringLike = {
+ "aws:SourceArn" = "arn:aws:grafana:us-east-2:750246862020:/workspaces/*"
+ }
+ }
+ Effect = "Allow"
+ Principal = {
+ Service = "grafana.amazonaws.com"
+ }
+ },
+ ]
+ Version = "2012-10-17"
+ }
+ )
+ version = "2012-10-17"
+
+ statement {
+ actions = [
+ "sts:AssumeRole",
+ ]
+ effect = "Allow"
+ not_actions = []
+ not_resources = []
+ resources = []
+ sid = [90mnull[0m[0m
+
+ condition {
+ test = "StringEquals"
+ values = [
+ "750246862020",
+ ]
+ variable = "aws:SourceAccount"
+ }
+ condition {
+ test = "StringLike"
+ values = [
+ "arn:aws:grafana:us-east-2:750246862020:/workspaces/*",
+ ]
+ variable = "aws:SourceArn"
+ }
+
+ principals {
+ identifiers = [
+ "grafana.amazonaws.com",
+ ]
+ type = "Service"
+ }
+ }
+}
+
+# module.monitoring.data.aws_iam_policy_document.this:
+data "aws_iam_policy_document" "this" {
+ id = "2588094592"
+ json = jsonencode(
+ {
+ Statement = [
+ {
+ Action = [
+ "s3:PutObject",
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:DeleteObject",
+ ]
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::aienv-v2-logs/*",
+ "arn:aws:s3:::aienv-v2-logs",
+ ]
+ Sid = "LokiChunksBucket"
+ },
+ {
+ Action = [
+ "s3:PutObject",
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:DeleteObject",
+ ]
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::aienv-v2-logs/*",
+ "arn:aws:s3:::aienv-v2-logs",
+ ]
+ Sid = "LokiRulerBucket"
+ },
+ ]
+ Version = "2012-10-17"
+ }
+ )
+ minified_json = jsonencode(
+ {
+ Statement = [
+ {
+ Action = [
+ "s3:PutObject",
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:DeleteObject",
+ ]
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::aienv-v2-logs/*",
+ "arn:aws:s3:::aienv-v2-logs",
+ ]
+ Sid = "LokiChunksBucket"
+ },
+ {
+ Action = [
+ "s3:PutObject",
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:DeleteObject",
+ ]
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::aienv-v2-logs/*",
+ "arn:aws:s3:::aienv-v2-logs",
+ ]
+ Sid = "LokiRulerBucket"
+ },
+ ]
+ Version = "2012-10-17"
+ }
+ )
+ version = "2012-10-17"
+
+ statement {
+ actions = [
+ "s3:DeleteObject",
+ "s3:GetObject",
+ "s3:ListBucket",
+ "s3:PutObject",
+ ]
+ effect = "Allow"
+ not_actions = []
+ not_resources = []
+ resources = [
+ "arn:aws:s3:::aienv-v2-logs",
+ "arn:aws:s3:::aienv-v2-logs/*",
+ ]
+ sid = "LokiChunksBucket"
+ }
+ statement {
+ actions = [
+ "s3:DeleteObject",
+ "s3:GetObject",
+ "s3:ListBucket",
+ "s3:PutObject",
+ ]
+ effect = "Allow"
+ not_actions = []
+ not_resources = []
+ resources = [
+ "arn:aws:s3:::aienv-v2-logs",
+ "arn:aws:s3:::aienv-v2-logs/*",
+ ]
+ sid = "LokiRulerBucket"
+ }
+}
+
+# module.monitoring.data.aws_region.this:
+data "aws_region" "this" {
+ description = "US East (Ohio)"
+ endpoint = "ec2.us-east-2.amazonaws.com"
+ id = "us-east-2"
+ name = "us-east-2"
+ region = "us-east-2"
+}
+
+# module.monitoring.data.aws_subnets.private:
+data "aws_subnets" "private" {
+ id = "us-east-2"
+ ids = [
+ "subnet-051d16c19e37d0bc1",
+ "subnet-0b174a3b86bc06dd0",
+ "subnet-05545af984558cec0",
+ ]
+ region = "us-east-2"
+ tags = {
+ "Name" = "*private*"
+ }
+
+ filter {
+ name = "vpc-id"
+ values = [
+ "vpc-05bbe4817c590a330",
+ ]
+ }
+}
+
+# module.monitoring.data.aws_vpc.this:
+data "aws_vpc" "this" {
+ arn = "arn:aws:ec2:us-east-2:750246862020:vpc/vpc-05bbe4817c590a330"
+ cidr_block = "10.0.0.0/16"
+ cidr_block_associations = [
+ {
+ association_id = "vpc-cidr-assoc-080288b4316185cac"
+ cidr_block = "10.0.0.0/16"
+ state = "associated"
+ },
+ ]
+ default = false
+ dhcp_options_id = "dopt-04a1ab17be8d962b9"
+ enable_dns_hostnames = true
+ enable_dns_support = true
+ enable_network_address_usage_metrics = false
+ id = "vpc-05bbe4817c590a330"
+ instance_tenancy = "default"
+ ipv6_association_id = [90mnull[0m[0m
+ ipv6_cidr_block = [90mnull[0m[0m
+ main_route_table_id = "rtb-0b71e379806322489"
+ owner_id = "750246862020"
+ region = "us-east-2"
+ tags = {
+ "Name" = "aienv"
+ }
+}
+
+# module.monitoring.data.kubernetes_service_v1.loki-gateway:
+data "kubernetes_service_v1" "loki-gateway" {
+ id = "observability/loki-gateway"
+ spec = [
+ {
+ allocate_load_balancer_node_ports = true
+ cluster_ip = "172.20.146.252"
+ cluster_ips = [
+ "172.20.146.252",
+ ]
+ external_ips = []
+ external_name = [90mnull[0m[0m
+ external_traffic_policy = "Cluster"
+ health_check_node_port = 0
+ internal_traffic_policy = "Cluster"
+ ip_families = [
+ "IPv4",
+ ]
+ ip_family_policy = "SingleStack"
+ load_balancer_class = [90mnull[0m[0m
+ load_balancer_ip = [90mnull[0m[0m
+ load_balancer_source_ranges = []
+ port = [
+ {
+ app_protocol = [90mnull[0m[0m
+ name = "http-metrics"
+ node_port = 32001
+ port = 80
+ protocol = "TCP"
+ target_port = "http-metrics"
+ },
+ ]
+ publish_not_ready_addresses = false
+ selector = {
+ "app.kubernetes.io/component" = "gateway"
+ "app.kubernetes.io/instance" = "coder-observe"
+ "app.kubernetes.io/name" = "loki"
+ }
+ session_affinity = "None"
+ session_affinity_config = []
+ type = "LoadBalancer"
+ },
+ ]
+ status = [
+ {
+ load_balancer = [
+ {
+ ingress = [
+ {
+ hostname = "ab9dd01908f9641e883bbfb55a20d82c-309251381.us-east-2.elb.amazonaws.com"
+ ip = [90mnull[0m[0m
+ ip_mode = [90mnull[0m[0m
+ },
+ ]
+ },
+ ]
+ },
+ ]
+
+ metadata {
+ annotations = {
+ "meta.helm.sh/release-name" = "coder-observe"
+ "meta.helm.sh/release-namespace" = "observability"
+ "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "ip"
+ "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internal"
+ }
+ generation = 0
+ labels = {
+ "app.kubernetes.io/component" = "gateway"
+ "app.kubernetes.io/instance" = "coder-observe"
+ "app.kubernetes.io/managed-by" = "Helm"
+ "app.kubernetes.io/name" = "loki"
+ "app.kubernetes.io/version" = "3.1.0"
+ "helm.sh/chart" = "loki-6.7.4"
+ }
+ name = "loki-gateway"
+ namespace = "observability"
+ resource_version = "27838281"
+ uid = "b9dd0190-8f96-41e8-83bb-fb55a20d82cf"
+ }
+}
+
+# module.monitoring.aws_cloudfront_distribution.grafana:
+resource "aws_cloudfront_distribution" "grafana" {
+ aliases = [
+ "grafana.ai.coder.com",
+ ]
+ anycast_ip_list_id = [90mnull[0m[0m
+ arn = "arn:aws:cloudfront::750246862020:distribution/E2A95S8B9B7ZE2"
+ caller_reference = "terraform-20260321191316294400000001"
+ continuous_deployment_policy_id = [90mnull[0m[0m
+ default_root_object = [90mnull[0m[0m
+ domain_name = "d7b9yo6pqyr2t.cloudfront.net"
+ enabled = true
+ etag = "ETVPDKIKX0DER"
+ hosted_zone_id = "Z2FDTNDATAQYW2"
+ http_version = "http2"
+ id = "E2A95S8B9B7ZE2"
+ in_progress_validation_batches = 0
+ is_ipv6_enabled = false
+ last_modified_time = "2026-03-21 19:17:38.906 +0000 UTC"
+ logging_v1_enabled = false
+ price_class = "PriceClass_100"
+ retain_on_delete = false
+ staging = false
+ status = "Deployed"
+ tags = {}
+ tags_all = {}
+ trusted_key_groups = [
+ {
+ enabled = false
+ items = []
+ },
+ ]
+ trusted_signers = [
+ {
+ enabled = false
+ items = []
+ },
+ ]
+ wait_for_deployment = true
+ web_acl_id = [90mnull[0m[0m
+
+ default_cache_behavior {
+ allowed_methods = [
+ "DELETE",
+ "GET",
+ "HEAD",
+ "OPTIONS",
+ "PATCH",
+ "POST",
+ "PUT",
+ ]
+ cache_policy_id = [90mnull[0m[0m
+ cached_methods = [
+ "GET",
+ "HEAD",
+ ]
+ compress = false
+ default_ttl = 0
+ field_level_encryption_id = [90mnull[0m[0m
+ max_ttl = 0
+ min_ttl = 0
+ origin_request_policy_id = [90mnull[0m[0m
+ realtime_log_config_arn = [90mnull[0m[0m
+ response_headers_policy_id = [90mnull[0m[0m
+ smooth_streaming = false
+ target_origin_id = "ALB-Grafana"
+ trusted_key_groups = []
+ trusted_signers = []
+ viewer_protocol_policy = "redirect-to-https"
+
+ forwarded_values {
+ headers = []
+ query_string = true
+ query_string_cache_keys = []
+
+ cookies {
+ forward = "all"
+ whitelisted_names = []
+ }
+ }
+
+ grpc_config {
+ enabled = false
+ }
+ }
+
+ origin {
+ connection_attempts = 3
+ connection_timeout = 10
+ domain_name = "g-93e5b46622.grafana-workspace.us-east-2.amazonaws.com"
+ origin_access_control_id = [90mnull[0m[0m
+ origin_id = "ALB-Grafana"
+ origin_path = [90mnull[0m[0m
+ response_completion_timeout = 0
+
+ custom_origin_config {
+ http_port = 80
+ https_port = 443
+ ip_address_type = [90mnull[0m[0m
+ origin_keepalive_timeout = 5
+ origin_protocol_policy = "https-only"
+ origin_read_timeout = 30
+ origin_ssl_protocols = [
+ "TLSv1.2",
+ ]
+ }
+ }
+
+ restrictions {
+ geo_restriction {
+ locations = []
+ restriction_type = "none"
+ }
+ }
+
+ viewer_certificate {
+ acm_certificate_arn = "arn:aws:acm:us-east-1:750246862020:certificate/efdb05fe-1fea-4b84-a87d-f9ed160ec434"
+ cloudfront_default_certificate = false
+ iam_certificate_id = [90mnull[0m[0m
+ minimum_protocol_version = "TLSv1"
+ ssl_support_method = "sni-only"
+ }
+}
+
+# module.monitoring.aws_grafana_role_association.admins:
+resource "aws_grafana_role_association" "admins" {
+ group_ids = []
+ id = "g-93e5b46622/ADMIN"
+ region = "us-east-2"
+ role = "ADMIN"
+ user_ids = [
+ "24c85468-90e1-70c7-3498-bc5695b7c6f0",
+ ]
+ workspace_id = "g-93e5b46622"
+}
+
+# module.monitoring.aws_grafana_role_association.viewer:
+resource "aws_grafana_role_association" "viewer" {
+ group_ids = []
+ id = "g-93e5b46622/VIEWER"
+ region = "us-east-2"
+ role = "VIEWER"
+ user_ids = [
+ "743804f8-30d1-705d-9ed9-d1905cbac291",
+ "d4a80408-70f1-70a0-d637-d3372eb29d29",
+ "d4a8a458-c041-70eb-a4c4-94db19a67d83",
+ ]
+ workspace_id = "g-93e5b46622"
+}
+
+# module.monitoring.aws_grafana_workspace.this:
+resource "aws_grafana_workspace" "this" {
+ account_access_type = "ORGANIZATION"
+ arn = "arn:aws:grafana:us-east-2:750246862020:/workspaces/g-93e5b46622"
+ authentication_providers = [
+ "AWS_SSO",
+ ]
+ configuration = jsonencode(
+ {
+ plugins = {
+ pluginAdminEnabled = false
+ }
+ unifiedAlerting = {
+ enabled = false
+ }
+ }
+ )
+ data_sources = [
+ "PROMETHEUS",
+ "CLOUDWATCH",
+ ]
+ description = [90mnull[0m[0m
+ endpoint = "g-93e5b46622.grafana-workspace.us-east-2.amazonaws.com"
+ grafana_version = "10.4"
+ id = "g-93e5b46622"
+ name = "coder-observe"
+ notification_destinations = []
+ organization_role_name = [90mnull[0m[0m
+ organizational_units = [
+ "r-4vw4",
+ "ou-4vw4-avnmq38g",
+ "ou-4vw4-2qki2hxj",
+ ]
+ permission_type = "CUSTOMER_MANAGED"
+ region = "us-east-2"
+ role_arn = "arn:aws:iam::750246862020:role/coder-observe-grafana"
+ saml_configuration_status = [90mnull[0m[0m
+ stack_set_name = [90mnull[0m[0m
+ tags = {}
+ tags_all = {}
+
+ vpc_configuration {
+ security_group_ids = [
+ "sg-062b577bd0d00159f",
+ ]
+ subnet_ids = [
+ "subnet-051d16c19e37d0bc1",
+ "subnet-05545af984558cec0",
+ "subnet-0b174a3b86bc06dd0",
+ ]
+ }
+}
+
+# module.monitoring.aws_grafana_workspace_service_account.admin:
+resource "aws_grafana_workspace_service_account" "admin" {
+ grafana_role = "ADMIN"
+ id = "g-93e5b46622,2"
+ name = "admin"
+ region = "us-east-2"
+ service_account_id = "2"
+ workspace_id = "g-93e5b46622"
+}
+
+# module.monitoring.aws_grafana_workspace_service_account.viewer:
+resource "aws_grafana_workspace_service_account" "viewer" {
+ grafana_role = "VIEWER"
+ id = "g-93e5b46622,4"
+ name = "viewer"
+ region = "us-east-2"
+ service_account_id = "4"
+ workspace_id = "g-93e5b46622"
+}
+
+# module.monitoring.aws_grafana_workspace_service_account_token.admin:
+resource "aws_grafana_workspace_service_account_token" "admin" {
+ created_at = "2026-03-20T21:44:32Z"
+ expires_at = "2026-04-19T21:44:32Z"
+ id = "g-93e5b46622,2,1"
+ key = (sensitive value)
+ name = "admin"
+ region = "us-east-2"
+ seconds_to_live = 2592000
+ service_account_id = "2"
+ service_account_token_id = "1"
+ workspace_id = "g-93e5b46622"
+}
+
+# module.monitoring.aws_iam_policy.policy:
+resource "aws_iam_policy" "policy" {
+ arn = "arn:aws:iam::750246862020:policy/coder-observe-grafana"
+ attachment_count = 1
+ description = "AWS Managed Grafana Policy"
+ id = "arn:aws:iam::750246862020:policy/coder-observe-grafana"
+ name = "coder-observe-grafana"
+ name_prefix = [90mnull[0m[0m
+ path = "/"
+ policy = jsonencode(
+ {
+ Statement = [
+ {
+ Action = [
+ "aps:QueryMetrics",
+ "aps:ListWorkspaces",
+ "aps:GetSeries",
+ "aps:GetMetricMetadata",
+ "aps:GetLabels",
+ "aps:DescribeWorkspace",
+ ]
+ Effect = "Allow"
+ Resource = "*"
+ },
+ ]
+ Version = "2012-10-17"
+ }
+ )
+ policy_id = "ANPA25LRSTTCOJRNBCTOP"
+ tags = {}
+ tags_all = {}
+}
+
+# module.monitoring.aws_iam_role.grafana:
+resource "aws_iam_role" "grafana" {
+ arn = "arn:aws:iam::750246862020:role/coder-observe-grafana"
+ assume_role_policy = jsonencode(
+ {
+ Statement = [
+ {
+ Action = "sts:AssumeRole"
+ Condition = {
+ StringEquals = {
+ "aws:SourceAccount" = "750246862020"
+ }
+ StringLike = {
+ "aws:SourceArn" = "arn:aws:grafana:us-east-2:750246862020:/workspaces/*"
+ }
+ }
+ Effect = "Allow"
+ Principal = {
+ Service = "grafana.amazonaws.com"
+ }
+ },
+ ]
+ Version = "2012-10-17"
+ }
+ )
+ create_date = "2026-03-20T21:29:40Z"
+ description = [90mnull[0m[0m
+ force_detach_policies = false
+ id = "coder-observe-grafana"
+ managed_policy_arns = [
+ "arn:aws:iam::750246862020:policy/coder-observe-grafana",
+ "arn:aws:iam::aws:policy/service-role/AmazonGrafanaCloudWatchAccess",
+ ]
+ max_session_duration = 3600
+ name = "coder-observe-grafana"
+ name_prefix = [90mnull[0m[0m
+ path = "/"
+ permissions_boundary = [90mnull[0m[0m
+ tags = {}
+ tags_all = {}
+ unique_id = "AROA25LRSTTCCZZOHQP3M"
+}
+
+# module.monitoring.aws_iam_role_policy_attachment.grafana["AmazonGrafanaCloudWatchAccess"]:
+resource "aws_iam_role_policy_attachment" "grafana" {
+ id = "coder-observe-grafana/arn:aws:iam::aws:policy/service-role/AmazonGrafanaCloudWatchAccess"
+ policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonGrafanaCloudWatchAccess"
+ role = "coder-observe-grafana"
+}
+
+# module.monitoring.aws_iam_role_policy_attachment.grafana["coder-observe-grafana"]:
+resource "aws_iam_role_policy_attachment" "grafana" {
+ id = "coder-observe-grafana/arn:aws:iam::750246862020:policy/coder-observe-grafana"
+ policy_arn = "arn:aws:iam::750246862020:policy/coder-observe-grafana"
+ role = "coder-observe-grafana"
+}
+
+# module.monitoring.aws_prometheus_workspace.this:
+resource "aws_prometheus_workspace" "this" {
+ alias = "coder-observe"
+ arn = "arn:aws:aps:us-east-2:750246862020:workspace/ws-be12ffb7-baf0-4c53-a4cc-4bd651b1643f"
+ id = "ws-be12ffb7-baf0-4c53-a4cc-4bd651b1643f"
+ kms_key_arn = [90mnull[0m[0m
+ prometheus_endpoint = "https://aps-workspaces.us-east-2.amazonaws.com/workspaces/ws-be12ffb7-baf0-4c53-a4cc-4bd651b1643f/"
+ region = "us-east-2"
+ tags = {}
+ tags_all = {}
+}
+
+# module.monitoring.aws_security_group.grafana:
+resource "aws_security_group" "grafana" {
+ arn = "arn:aws:ec2:us-east-2:750246862020:security-group/sg-062b577bd0d00159f"
+ description = "SG for Grafana - All Egress traffic"
+ egress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
+ ]
+ description = [90mnull[0m[0m
+ from_port = 0
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "-1"
+ security_groups = []
+ self = false
+ to_port = 0
+ },
+ ]
+ id = "sg-062b577bd0d00159f"
+ ingress = []
+ name = "coder-observe-grafana"
+ name_prefix = [90mnull[0m[0m
+ owner_id = "750246862020"
+ region = "us-east-2"
+ revoke_rules_on_delete = false
+ tags = {
+ "Name" = "Customer-Managed AWS Managed Grafana"
+ }
+ tags_all = {
+ "Name" = "Customer-Managed AWS Managed Grafana"
+ }
+ vpc_id = "vpc-05bbe4817c590a330"
+}
+
+# module.monitoring.aws_vpc_security_group_egress_rule.grafana:
+resource "aws_vpc_security_group_egress_rule" "grafana" {
+ arn = "arn:aws:ec2:us-east-2:750246862020:security-group-rule/sgr-0d2ce2cee512e8dc7"
+ cidr_ipv4 = "0.0.0.0/0"
+ id = "sgr-0d2ce2cee512e8dc7"
+ ip_protocol = "-1"
+ region = "us-east-2"
+ security_group_id = "sg-062b577bd0d00159f"
+ security_group_rule_id = "sgr-0d2ce2cee512e8dc7"
+ tags_all = {}
+}
+
+# module.monitoring.grafana_dashboard.this["coder-dashboard-aibridge"]:
+resource "grafana_dashboard" "this" {
+ config_json = jsonencode(
+ {
+ __elements = {}
+ __inputs = [
+ {
+ description = ""
+ label = "coder-observability-ro"
+ name = "DS_CODER-OBSERVABILITY-RO"
+ pluginId = "postgres"
+ pluginName = "PostgreSQL"
+ type = "datasource"
+ },
+ ]
+ __requires = [
+ {
+ id = "barchart"
+ name = "Bar chart"
+ type = "panel"
+ version = ""
+ },
+ {
+ id = "grafana"
+ name = "Grafana"
+ type = "grafana"
+ version = "12.1.0"
+ },
+ {
+ id = "postgres"
+ name = "PostgreSQL"
+ type = "datasource"
+ version = "12.1.0"
+ },
+ {
+ id = "stat"
+ name = "Stat"
+ type = "panel"
+ version = ""
+ },
+ {
+ id = "table"
+ name = "Table"
+ type = "panel"
+ version = ""
+ },
+ ]
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = true
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ type = "dashboard"
+ },
+ ]
+ }
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ links = []
+ panels = [
+ {
+ collapsed = false
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 0
+ }
+ panels = []
+ title = "Usage leaderboards"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ fillOpacity = 80
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ lineWidth = 1
+ scaleDistribution = {
+ type = "linear"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "output"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "yellow"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Cache Read"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Input"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Cache Write"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "light-red"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 12
+ w = 20
+ x = 0
+ y = 1
+ }
+ options = {
+ barRadius = 0
+ barWidth = 0.97
+ fullHighlight = false
+ groupWidth = 0.7
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ orientation = "auto"
+ showValue = "auto"
+ stacking = "none"
+ tooltip = {
+ hideZeros = false
+ mode = "single"
+ sort = "none"
+ }
+ xTickLabelRotation = 0
+ xTickLabelSpacing = 0
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ editorMode = "code"
+ format = "table"
+ rawQuery = true
+ rawSql = <<-EOT
+ select u.username, sum(t.input_tokens) as input,
+ sum(t.output_tokens) as output,
+ sum(
+ COALESCE(
+ t.metadata->>'cache_read_input', -- Anthropic
+ t.metadata->>'prompt_cached' -- OpenAI
+ )::int
+ ) AS cache_read_input,
+ sum((t.metadata->>'cache_creation_input')::int) AS cache_creation_input -- Anthropic
+ from aibridge_token_usages t
+ join aibridge_interceptions i on t.interception_id = i.id
+ join users u on i.initiator_id = u.id
+ where $__timeFilter(i.started_at)
+ AND u.username ~ '${username:regex}'
+ AND i.provider ~ '${provider:regex}'
+ AND i.model ~ '${model:regex}'
+ group by u.username
+ order by input desc
+ EOT
+ refId = "A"
+ sql = {
+ columns = [
+ {
+ parameters = []
+ type = "function"
+ },
+ ]
+ groupBy = [
+ {
+ property = {
+ type = "string"
+ }
+ type = "groupBy"
+ },
+ ]
+ limit = 50
+ }
+ },
+ ]
+ title = "Leaderboard per user"
+ transformations = [
+ {
+ id = "organize"
+ options = {
+ excludeByName = {}
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ cache_creation_input = "Cache Write"
+ cache_read_input = "Cache Read"
+ input = "Input"
+ output = "Output"
+ username = ""
+ }
+ }
+ },
+ ]
+ type = "barchart"
+ },
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = 0
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 12
+ w = 4
+ x = 20
+ y = 1
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ editorMode = "code"
+ format = "table"
+ rawQuery = true
+ rawSql = <<-EOT
+ select sum(t.input_tokens) as input,
+ sum(t.output_tokens) as output,
+ sum(
+ COALESCE(
+ t.metadata->>'cache_read_input', -- Anthropic
+ t.metadata->>'prompt_cached' -- OpenAI
+ )::int
+ ) AS cache_read_input,
+ sum((t.metadata->>'cache_creation_input')::int) AS cache_creation_input -- Anthropic
+ from aibridge_token_usages t
+ join aibridge_interceptions i on t.interception_id = i.id
+ join users u on i.initiator_id = u.id
+ where $__timeFilter(i.started_at)
+ AND u.username ~ '${username:regex}'
+ AND i.provider ~ '${provider:regex}'
+ AND i.model ~ '${model:regex}'
+ order by input desc
+ EOT
+ refId = "A"
+ sql = {
+ columns = [
+ {
+ parameters = []
+ type = "function"
+ },
+ ]
+ groupBy = [
+ {
+ property = {
+ type = "string"
+ }
+ type = "groupBy"
+ },
+ ]
+ limit = 50
+ }
+ },
+ ]
+ title = "Total usage for $username"
+ transformations = [
+ {
+ id = "organize"
+ options = {
+ excludeByName = {}
+ includeByName = {}
+ indexByName = {
+ cache_creation_input = 3
+ cache_read_input = 2
+ input = 0
+ output = 1
+ }
+ renameByName = {
+ cache_creation_input = "Cache Write"
+ cache_read_input = "Cache Read"
+ input = "Input"
+ output = "Output"
+ }
+ }
+ },
+ ]
+ type = "stat"
+ },
+ {
+ collapsed = false
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 22
+ }
+ panels = []
+ title = "Interceptions"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ fillOpacity = 80
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ lineWidth = 1
+ scaleDistribution = {
+ type = "linear"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ fieldMinMax = false
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "output"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 12
+ w = 20
+ x = 0
+ y = 23
+ }
+ maxDataPoints = 30
+ options = {
+ barRadius = 0
+ barWidth = 0.97
+ fullHighlight = false
+ groupWidth = 0.7
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ orientation = "auto"
+ showValue = "auto"
+ stacking = "normal"
+ text = {
+ valueSize = 10
+ }
+ tooltip = {
+ hideZeros = false
+ mode = "single"
+ sort = "none"
+ }
+ xTickLabelRotation = -45
+ xTickLabelSpacing = 0
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ editorMode = "code"
+ format = "time_series"
+ rawQuery = true
+ rawSql = <<-EOT
+ SELECT
+ $__timeGroupAlias(i.started_at, $__interval, NULL),
+ count(i.id) AS value,
+ u.username AS metric
+ FROM aibridge_interceptions i
+ join users u ON i.initiator_id = u.id
+ WHERE
+ $__timeFilter(i.started_at)
+ AND u.username ~ '${username:regex}'
+ AND i.provider ~ '${provider:regex}'
+ AND i.model ~ '${model:regex}'
+ GROUP BY u.username, $__timeGroup(i.started_at, $__interval)
+ ORDER BY $__timeGroup(i.started_at, $__interval)
+ EOT
+ refId = "A"
+ sql = {
+ columns = [
+ {
+ name = "COUNT"
+ parameters = [
+ {
+ name = "id"
+ type = "functionParameter"
+ },
+ ]
+ type = "function"
+ },
+ ]
+ groupBy = [
+ {
+ property = {
+ name = "started_at"
+ type = "string"
+ }
+ type = "groupBy"
+ },
+ ]
+ limit = 50
+ }
+ table = "aibridge_interceptions"
+ },
+ ]
+ title = "Interceptions over time by user"
+ type = "barchart"
+ },
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "output"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 12
+ w = 4
+ x = 20
+ y = 23
+ }
+ interval = "1m"
+ maxDataPoints = 500
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ editorMode = "code"
+ format = "table"
+ rawQuery = true
+ rawSql = <<-EOT
+ select count(*) from aibridge_interceptions
+ WHERE started_at > $__timeFrom() AND started_at <= $__timeTo()
+ AND provider ~ '${provider:regex}'
+ AND model ~ '${model:regex}'
+ EOT
+ refId = "A"
+ sql = {
+ columns = [
+ {
+ name = "COUNT"
+ parameters = [
+ {
+ name = "id"
+ type = "functionParameter"
+ },
+ ]
+ type = "function"
+ },
+ ]
+ groupBy = [
+ {
+ property = {
+ name = "started_at"
+ type = "string"
+ }
+ type = "groupBy"
+ },
+ ]
+ limit = 50
+ }
+ table = "aibridge_interceptions"
+ },
+ ]
+ title = "Total interceptions"
+ type = "stat"
+ },
+ {
+ collapsed = false
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 35
+ }
+ panels = []
+ title = "Prompts & tool calls details"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ wrapText = false
+ }
+ inspect = true
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Interception ID"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 357
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Model"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 240
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Provider"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 157
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Username"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 188
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 14
+ w = 24
+ x = 0
+ y = 36
+ }
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ fields = ""
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ showHeader = true
+ sortBy = [
+ {
+ desc = true
+ displayName = "Created At"
+ },
+ ]
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ editorMode = "code"
+ format = "table"
+ rawQuery = true
+ rawSql = <<-EOT
+ SELECT i.id,
+ u.username,
+ i.provider,
+ i.model,
+ p.prompt,
+ p.created_at
+ FROM aibridge_user_prompts p
+ JOIN aibridge_interceptions i ON p.interception_id = i.id
+ JOIN users u ON i.initiator_id = u.id
+ WHERE $__timeFilter(i.started_at)
+ AND u.username ~ '${username:regex}'
+ AND i.provider ~ '${provider:regex}'
+ AND i.model ~ '${model:regex}'
+ ORDER BY p.created_at DESC;
+ EOT
+ refId = "A"
+ sql = {
+ columns = [
+ {
+ parameters = []
+ type = "function"
+ },
+ ]
+ groupBy = [
+ {
+ property = {
+ type = "string"
+ }
+ type = "groupBy"
+ },
+ ]
+ limit = 50
+ }
+ },
+ ]
+ title = "User Prompts"
+ transformations = [
+ {
+ id = "organize"
+ options = {
+ excludeByName = {}
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ created_at = "Created At"
+ id = "Interception ID"
+ input = "Tool Input"
+ invocation_error = "Tool Error"
+ model = "Model"
+ prompt = "Prompt"
+ provider = "Provider"
+ server_url = "MCP Server"
+ tool = "Tool Name"
+ username = "Username"
+ }
+ }
+ },
+ ]
+ type = "table"
+ },
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ wrapText = false
+ }
+ inspect = true
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Tool Name"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 342
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "invocation_error"
+ }
+ properties = [
+ {
+ id = "custom.cellOptions"
+ value = {
+ applyToRow = true
+ type = "color-background"
+ wrapText = false
+ }
+ },
+ {
+ id = "noValue"
+ },
+ {
+ id = "mappings"
+ value = [
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "green"
+ index = 0
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ pattern = ".+"
+ result = {
+ color = "red"
+ index = 1
+ }
+ }
+ type = "regex"
+ },
+ ]
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Tool Input"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 309
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Interception ID"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 357
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Model"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 240
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 14
+ w = 24
+ x = 0
+ y = 50
+ }
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ fields = ""
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ showHeader = true
+ sortBy = [
+ {
+ desc = true
+ displayName = "Created At"
+ },
+ ]
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ editorMode = "code"
+ format = "table"
+ rawQuery = true
+ rawSql = <<-EOT
+ select i.id, u.username, i.provider, i.model, t.server_url, t.tool, t.input, t.invocation_error, t.created_at FROM aibridge_tool_usages t
+ join aibridge_interceptions i ON t.interception_id = i.id
+ join users u on i.initiator_id = u.id
+ where $__timeFilter(i.started_at)
+ AND u.username ~ '${username:regex}'
+ AND i.provider ~ '${provider:regex}'
+ AND i.model ~ '${model:regex}'
+ order by t.created_at desc
+ EOT
+ refId = "A"
+ sql = {
+ columns = [
+ {
+ parameters = []
+ type = "function"
+ },
+ ]
+ groupBy = [
+ {
+ property = {
+ type = "string"
+ }
+ type = "groupBy"
+ },
+ ]
+ limit = 50
+ }
+ },
+ ]
+ title = "Tool Calls"
+ transformations = [
+ {
+ id = "organize"
+ options = {
+ excludeByName = {}
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ created_at = "Created At"
+ id = "Interception ID"
+ input = "Tool Input"
+ invocation_error = "Tool Error"
+ model = "Model"
+ provider = "Provider"
+ server_url = "MCP Server"
+ tool = "Tool Name"
+ username = "Username"
+ }
+ }
+ },
+ ]
+ type = "table"
+ },
+ ]
+ refresh = "1m"
+ schemaVersion = 41
+ tags = []
+ templating = {
+ list = [
+ {
+ allValue = ".+"
+ current = {}
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ definition = "select username from users where deleted=false;"
+ description = ""
+ includeAll = true
+ multi = true
+ name = "username"
+ options = []
+ query = "select username from users where deleted=false;"
+ refresh = 1
+ regex = ""
+ sort = 1
+ type = "query"
+ },
+ {
+ allValue = ".+"
+ current = {}
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ definition = "SELECT DISTINCT provider FROM aibridge_interceptions WHERE provider IS NOT NULL ORDER BY 1;"
+ description = ""
+ includeAll = true
+ multi = true
+ name = "provider"
+ options = []
+ query = "SELECT DISTINCT provider FROM aibridge_interceptions WHERE provider IS NOT NULL ORDER BY 1;"
+ refresh = 1
+ regex = ""
+ sort = 1
+ type = "query"
+ },
+ {
+ allValue = ".+"
+ current = {}
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ definition = "SELECT DISTINCT model FROM aibridge_interceptions WHERE model IS NOT NULL AND provider ~ '${provider:regex}' ORDER BY 1;"
+ description = ""
+ includeAll = true
+ multi = true
+ name = "model"
+ options = []
+ query = "SELECT DISTINCT model FROM aibridge_interceptions WHERE model IS NOT NULL AND provider ~ '${provider:regex}' ORDER BY 1;"
+ refresh = 1
+ regex = ""
+ sort = 1
+ type = "query"
+ },
+ ]
+ }
+ time = {
+ from = "now-7d"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder AI Bridge"
+ uid = "0c61d33f-c809-4184-9e88-cb27e2d9d224"
+ weekStart = ""
+ }
+ )
+ dashboard_id = 4
+ folder = [90mnull[0m[0m
+ id = "0:0c61d33f-c809-4184-9e88-cb27e2d9d224"
+ org_id = "0"
+ uid = "0c61d33f-c809-4184-9e88-cb27e2d9d224"
+ url = "https://g-93e5b46622.grafana-workspace.us-east-2.amazonaws.com/d/0c61d33f-c809-4184-9e88-cb27e2d9d224/coder-ai-bridge"
+ version = 1
+}
+
+# module.monitoring.grafana_dashboard.this["coder-dashboard-boundary"]:
+resource "grafana_dashboard" "this" {
+ config_json = jsonencode(
+ {
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = true
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ target = {
+ limit = 100
+ matchAny = false
+ tags = []
+ type = "dashboard"
+ }
+ type = "dashboard"
+ },
+ ]
+ }
+ description = <<-EOT
+ This dashboard shows HTTP requests audited by agent boundaries within Coder workspaces to provide visibility into workspace network activity.
+
+ What it shows:
+ - Total count of allowed and denied outbound HTTP requests
+ - Top 20 most frequently accessed allowed domains
+ - Top 20 most frequently blocked domains
+ - Recent allowed requests with details (time, domain, method, path, workspace owner, workspace name, template ID)
+ - Recent denied requests with the same details
+
+ Who it's for:
+ - Platform administrators and template administrators who need to audit workspace network activity and define agent boundary policies
+ - Security team members who want to know what HTTP requests AI agents made in Coder workspaces for security incident review
+ - Agent Boundaries policy owners who want to refine network access controls/security posture
+
+ Filters available:
+ - Template ID
+ - HTTP request domain
+ - Workspace owner
+ EOT
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ links = []
+ panels = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ description = "Total number of requests allowed/denied"
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "{decision=\"allow\"}"
+ }
+ properties = [
+ {
+ id = "displayName"
+ value = "Allowed"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "{decision=\"deny\"}"
+ }
+ properties = [
+ {
+ id = "displayName"
+ value = "Denied"
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 24
+ x = 0
+ y = 0
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ direction = "backward"
+ editorMode = "code"
+ expr = "sum by (decision) (count_over_time({ namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=~`deny|allow` | owner=~`$owner` | domain=~`$domain` | template_id=~`$template_id` | template_version_id=~`$template_version_id` [$__range]))"
+ queryType = "range"
+ refId = "A"
+ },
+ ]
+ title = "Request Totals"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ description = "Top 20 allowed domains for HTTP requests"
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ }
+ filterable = false
+ inspect = false
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Domain"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 340
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 12
+ w = 12
+ x = 0
+ y = 7
+ }
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ enablePagination = false
+ fields = ""
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ frameIndex = 1
+ showHeader = true
+ sortBy = [
+ {
+ desc = true
+ displayName = "Count"
+ },
+ ]
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ direction = "backward"
+ editorMode = "code"
+ expr = "topk(20, sum by (domain) (count_over_time({ namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=`allow` | owner=~`$owner` | template_id=~`$template_id` | template_version_id=~`$template_version_id` | regexp `http_url=(?Phttps?)://(?P[^/:]+)` | domain=~`$domain` [$__auto])))"
+ legendFormat = ""
+ queryType = "instant"
+ refId = "A"
+ },
+ ]
+ title = "Top Allowed Domains"
+ transformations = [
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = true
+ }
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ Time = ""
+ "Value #A" = "Count"
+ domain = "Domain"
+ }
+ }
+ },
+ {
+ id = "sortBy"
+ options = {
+ fields = {}
+ sort = [
+ {
+ desc = true
+ field = "Count"
+ },
+ ]
+ }
+ },
+ ]
+ type = "table"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ description = "Top 20 denied domains for HTTP requests"
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ }
+ filterable = false
+ inspect = false
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Domain"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 382
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 12
+ w = 12
+ x = 12
+ y = 7
+ }
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ enablePagination = false
+ fields = ""
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ frameIndex = 1
+ showHeader = true
+ sortBy = [
+ {
+ desc = true
+ displayName = "Count"
+ },
+ ]
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ direction = "backward"
+ editorMode = "code"
+ expr = "topk(20, sum by (domain) (count_over_time({ namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=`deny` | owner=~`$owner` | template_id=~`$template_id` | template_version_id=~`$template_version_id` | regexp `http_url=(?Phttps?)://(?P[^/:]+)` | domain=~`$domain` [$__auto])))"
+ legendFormat = ""
+ queryType = "instant"
+ refId = "A"
+ },
+ ]
+ title = "Top Denied Domains"
+ transformations = [
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = true
+ }
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ Time = ""
+ "Value #A" = "Count"
+ domain = "Domain"
+ }
+ }
+ },
+ {
+ id = "sortBy"
+ options = {
+ fields = {}
+ sort = [
+ {
+ desc = true
+ field = "Count"
+ },
+ ]
+ }
+ },
+ ]
+ type = "table"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ }
+ inspect = false
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Time"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 282
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Domain"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 185
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "method"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 73
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "path"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 397
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Workspace Owner"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 144
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Template ID"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 195
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Workspace Name"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 204
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 12
+ w = 24
+ x = 0
+ y = 19
+ }
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ fields = ""
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ showHeader = true
+ sortBy = []
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ direction = "backward"
+ editorMode = "code"
+ expr = "{ namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=`allow` | owner=~`$owner` | template_id=~`$template_id` | template_version_id=~`$template_version_id` | regexp `http_url=https?://(?P[^/?# ]+)(?P/[^?# ]*)?` | domain=~`$domain` | line_format `time=\"{{.event_time}}\" method=\"{{.http_method}}\" domain=\"{{.domain}}\" path=\"{{.path}}\" owner=\"{{.owner}}\" workspace_name=\"{{.workspace_name}}\" template_id=\"{{.template_id}}\" template_version_id=\"{{.template_version_id}}\"`"
+ queryType = "range"
+ refId = "A"
+ },
+ ]
+ title = "Most recent allowed requests"
+ transformations = [
+ {
+ id = "extractFields"
+ options = {
+ delimiter = "|"
+ format = "kvp"
+ keepTime = false
+ replace = true
+ source = "Line"
+ }
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {}
+ includeByName = {
+ domain = true
+ method = true
+ owner = true
+ path = true
+ template_id = true
+ template_version_id = true
+ time = true
+ workspace_name = true
+ }
+ indexByName = {
+ domain = 2
+ method = 1
+ owner = 4
+ path = 3
+ template_id = 6
+ time = 0
+ workspace_name = 5
+ }
+ renameByName = {
+ domain = "Domain"
+ method = "Method"
+ owner = "Workspace Owner"
+ path = "Path"
+ template_id = "Template ID"
+ template_version_id = "Template Version ID"
+ time = "Time"
+ workspace_name = "Workspace Name"
+ }
+ }
+ },
+ {
+ id = "sortBy"
+ options = {
+ fields = {}
+ sort = [
+ {
+ desc = true
+ field = "Time"
+ },
+ ]
+ }
+ },
+ {
+ id = "limit"
+ options = {
+ limitField = "10"
+ }
+ },
+ ]
+ type = "table"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ }
+ inspect = false
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Time"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 282
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Domain"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 185
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "method"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 73
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "path"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 397
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Workspace Owner"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 152
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 12
+ w = 24
+ x = 0
+ y = 31
+ }
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ fields = ""
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ showHeader = true
+ sortBy = []
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ direction = "backward"
+ editorMode = "code"
+ expr = "{ namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=`deny` | owner=~`$owner` | template_id=~`$template_id` | template_version_id=~`$template_version_id` | regexp `http_url=https?://(?P[^/?# ]+)(?P/[^?# ]*)?` | domain=~`$domain` | line_format `time=\"{{.event_time}}\" method=\"{{.http_method}}\" domain=\"{{.domain}}\" path=\"{{.path}}\" owner=\"{{.owner}}\" workspace_name=\"{{.workspace_name}}\" template_id=\"{{.template_id}}\" template_version_id=\"{{.template_version_id}}\"`"
+ queryType = "range"
+ refId = "A"
+ },
+ ]
+ title = "Most recent denied requests"
+ transformations = [
+ {
+ id = "extractFields"
+ options = {
+ delimiter = "|"
+ format = "kvp"
+ keepTime = false
+ replace = true
+ source = "Line"
+ }
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {}
+ includeByName = {
+ domain = true
+ method = true
+ owner = true
+ path = true
+ template_id = true
+ template_version_id = true
+ time = true
+ workspace_name = true
+ }
+ indexByName = {
+ domain = 2
+ method = 1
+ owner = 4
+ path = 3
+ template_id = 6
+ time = 0
+ workspace_name = 5
+ }
+ renameByName = {
+ domain = "Domain"
+ method = "Method"
+ owner = "Workspace Owner"
+ path = "Path"
+ template_id = "Template ID"
+ template_version_id = "Template Version ID"
+ time = "Time"
+ workspace_name = "Workspace Name"
+ }
+ }
+ },
+ {
+ id = "sortBy"
+ options = {
+ fields = {}
+ sort = [
+ {
+ desc = true
+ field = "Time"
+ },
+ ]
+ }
+ },
+ {
+ id = "limit"
+ options = {
+ limitField = "10"
+ }
+ },
+ ]
+ type = "table"
+ },
+ ]
+ preload = false
+ refresh = "30s"
+ schemaVersion = 41
+ tags = []
+ templating = {
+ list = [
+ {
+ current = {
+ text = ""
+ value = ""
+ }
+ description = "Filter by request domain"
+ label = "Domain"
+ name = "domain"
+ options = [
+ {
+ selected = true
+ text = ""
+ value = ""
+ },
+ ]
+ query = ""
+ type = "textbox"
+ },
+ {
+ current = {
+ text = ""
+ value = ""
+ }
+ description = "Filter requests by workspace owner"
+ label = "Workspace Owner"
+ name = "owner"
+ options = [
+ {
+ selected = true
+ text = ""
+ value = ""
+ },
+ ]
+ query = ""
+ type = "textbox"
+ },
+ {
+ current = {
+ text = ""
+ value = ""
+ }
+ description = "Filter requests by template ID (UUID). Template IDs can be found via the CLI with \"coder templates list\"."
+ label = "Template ID"
+ name = "template_id"
+ options = [
+ {
+ selected = true
+ text = ""
+ value = ""
+ },
+ ]
+ query = ""
+ type = "textbox"
+ },
+ {
+ current = {
+ text = ""
+ value = ""
+ }
+ description = "Filter by template version ID (UUID). A templates version IDs can be found via the CLI with \"coder templates versions list \"."
+ label = "Template Version ID"
+ name = "template_version_id"
+ options = [
+ {
+ selected = true
+ text = ""
+ value = ""
+ },
+ ]
+ query = ""
+ type = "textbox"
+ },
+ ]
+ }
+ time = {
+ from = "now-12h"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder Agent Boundaries"
+ uid = "agent-boundaries"
+ weekStart = ""
+ }
+ )
+ dashboard_id = 1
+ folder = [90mnull[0m[0m
+ id = "0:agent-boundaries"
+ org_id = "0"
+ uid = "agent-boundaries"
+ url = "https://g-93e5b46622.grafana-workspace.us-east-2.amazonaws.com/d/agent-boundaries/coder-agent-boundaries"
+ version = 1
+}
+
+# module.monitoring.grafana_dashboard.this["coder-dashboard-coderd"]:
+resource "grafana_dashboard" "this" {
+ config_json = jsonencode(
+ {
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = true
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ target = {
+ limit = 100
+ matchAny = false
+ tags = []
+ type = "dashboard"
+ }
+ type = "dashboard"
+ },
+ ]
+ }
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ links = []
+ panels = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Down"
+ }
+ properties = [
+ {
+ id = "thresholds"
+ value = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 1
+ },
+ ]
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 6
+ x = 0
+ y = 0
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "count(up{ pod=~`coder.*`, namespace=~`coder` } == 1) or vector(0)"
+ instant = true
+ legendFormat = "Up"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "(count(up{ pod=~`coder.*`, namespace=~`coder` } == 0) or vector(0)) > 0"
+ hide = false
+ instant = true
+ legendFormat = "Down"
+ range = false
+ refId = "B"
+ },
+ ]
+ title = "Replicas"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 6
+ x = 6
+ y = 0
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ One or more replicas are required to be running in order to serve the control-plane.
+
+ See [High Availability](https://coder.com/docs/v2/latest/admin/high-availability) for details on how to
+ run multiple `coderd` replicas.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "#EAB839"
+ value = 0.9
+ },
+ {
+ color = "red"
+ value = 1
+ },
+ ]
+ }
+ unit = "percentunit"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Enabled"
+ }
+ properties = [
+ {
+ id = "mappings"
+ value = [
+ {
+ options = {
+ "0" = {
+ index = 1
+ text = "No"
+ }
+ "1" = {
+ index = 0
+ text = "Yes"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ },
+ {
+ id = "thresholds"
+ value = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 6
+ x = 12
+ y = 0
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "last",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_license_user_limit_enabled)"
+ instant = true
+ legendFormat = "Enabled"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ (
+ max(coderd_license_active_users) / max(coderd_license_limit_users)
+ ) > 0
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "Usage"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "Enterprise License"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 6
+ x = 18
+ y = 0
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = "If you would like to try Coder's [Enterprise features](https://coder.com/docs/v2/latest/enterprise), you can [request a trial license](https://coder.com/docs/v2/latest/faqs#how-do-i-add-an-enterprise-license)."
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "never"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byRegexp"
+ options = "/(Requested|Limit)/"
+ }
+ properties = [
+ {
+ id = "custom.lineStyle"
+ value = {
+ dash = [
+ 0,
+ 10,
+ ]
+ fill = "dot"
+ }
+ },
+ {
+ id = "custom.fillOpacity"
+ value = 5
+ },
+ {
+ id = "custom.drawStyle"
+ value = "line"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Requested"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Limit"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 6
+ x = 0
+ y = 6
+ }
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum by (pod) (rate(container_cpu_usage_seconds_total{ pod=~`coder.*`, namespace=~`coder` }[$__rate_interval]))"
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max(kube_pod_container_resource_limits{ pod=~`coder.*`, namespace=~`coder`, resource=\"cpu\"})"
+ hide = false
+ instant = false
+ legendFormat = "Limit"
+ range = true
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max(kube_pod_container_resource_requests{ pod=~`coder.*`, namespace=~`coder`, resource=\"cpu\"})"
+ hide = false
+ instant = false
+ legendFormat = "Requested"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "CPU Usage Seconds"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 6
+ x = 6
+ y = 6
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ The cumulative CPU used per core-second. If `coderd` was using a full CPU core, that would be represented as 1 second.
+
+ Requests & limits are shown if set.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ fixedColor = "red"
+ mode = "shades"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ decimals = 0
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Requested"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Limit"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "red"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 4
+ x = 12
+ y = 6
+ }
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ sum by (reason) (
+ count_over_time(kube_pod_container_status_terminated_reason{ pod=~`coder.*`, namespace=~`coder` }[$__interval])
+ )
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "{{reason}}"
+ range = true
+ refId = "C"
+ },
+ ]
+ title = "Terminations"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ decimals = 0
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 0.0001
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 6
+ w = 2
+ x = 16
+ y = 6
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "mean",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(increase(kube_pod_container_status_restarts_total{ pod=~`coder.*`, namespace=~`coder` }[$__range]))"
+ hide = false
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "B"
+ },
+ ]
+ title = "Restarts"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 6
+ x = 18
+ y = 6
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ Pods can be terminated for several reasons:
+ - `OOMKilled`: pod exceeded its defined memory limit or was terminated by the OS for using excessive memory (if no limit defined)
+ - `Error`: usually attributeable to a configuration problem
+ - `Evicted`: pod has been evicted from node for overusing resources and will be rescheduled on another node is possible
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "never"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ unit = "bytes"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byRegexp"
+ options = "/(Requested|Limit)/"
+ }
+ properties = [
+ {
+ id = "custom.lineStyle"
+ value = {
+ dash = [
+ 0,
+ 10,
+ ]
+ fill = "dot"
+ }
+ },
+ {
+ id = "custom.fillOpacity"
+ value = 5
+ },
+ {
+ id = "custom.drawStyle"
+ value = "line"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Requested"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Limit"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 6
+ x = 0
+ y = 12
+ }
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max by (pod) (container_memory_working_set_bytes{ pod=~`coder.*`, namespace=~`coder` })"
+ hide = false
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max(kube_pod_container_resource_limits{ pod=~`coder.*`, namespace=~`coder`, resource=\"memory\"})"
+ hide = false
+ instant = false
+ legendFormat = "Limit"
+ range = true
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max(kube_pod_container_resource_requests{ pod=~`coder.*`, namespace=~`coder`, resource=\"memory\"})"
+ hide = false
+ instant = false
+ legendFormat = "Requested"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "RAM Usage"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 6
+ x = 6
+ y = 12
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ This shows the total memory used by each `coderd` container; it is the same metric which the [OOM killer](https://www.kernel.org/doc/gorman/html/understand/understand016.html) uses.
+
+ Requests & limits are shown if set.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "orange"
+ value = 100
+ },
+ {
+ color = "red"
+ value = 500
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Errors"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "short"
+ },
+ {
+ id = "thresholds"
+ value = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 1
+ },
+ ]
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 3
+ w = 4
+ x = 12
+ y = 12
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "mean",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "quantile(0.5, coder_pubsub_send_latency_seconds)"
+ instant = false
+ legendFormat = "Send"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "quantile(0.5, coder_pubsub_receive_latency_seconds)"
+ hide = false
+ instant = false
+ legendFormat = "Receive"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "Pubsub Latency (Median)"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Errors"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "short"
+ },
+ {
+ id = "thresholds"
+ value = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 1
+ },
+ ]
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 2
+ x = 16
+ y = 12
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "mean",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ (
+ sum(increase(coder_pubsub_latency_measure_errs_total[$__range]))
+ / count(coder_pubsub_latency_measure_errs_total)
+ ) or vector(0)
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "Errors"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "Pubsub Errors"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 6
+ x = 18
+ y = 12
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ `coderd` uses Postgres for passing messages between subcomponents for coordination and signalling;
+ this is called "pubsub" (or publish-subscribe).
+
+ We measure the time for messages to be sent and received. Latencies higher than 500ms will likely lead to
+ your Coder deployment feeling sluggish. High latency is usually an indication that your Postgres server is under-resourced on CPU.
+
+ High values for median should be concerning,
+ while the 90th percentile shows the outliers.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "orange"
+ value = 100
+ },
+ {
+ color = "red"
+ value = 500
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Errors"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "short"
+ },
+ {
+ id = "thresholds"
+ value = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 1
+ },
+ ]
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 3
+ w = 4
+ x = 12
+ y = 15
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "mean",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "quantile(0.9, coder_pubsub_send_latency_seconds)"
+ instant = false
+ legendFormat = "Send"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "quantile(0.9, coder_pubsub_receive_latency_seconds)"
+ hide = false
+ instant = false
+ legendFormat = "Receive"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "Pubsub Latency (P90)"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 0
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "never"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ unit = "reqps"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 6
+ w = 6
+ x = 0
+ y = 18
+ }
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum by(pod) (rate(coderd_api_requests_processed_total{ pod=~`coder.*`, namespace=~`coder` }[$__rate_interval]))"
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ ]
+ title = "API Requests"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 6
+ x = 6
+ y = 18
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ This shows the number of requests per second each `coderd` replica is handling.
+
+ Heavy skewing towards a single `coderd` replica indicates faulty loadbalancing.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ ]
+ refresh = "30s"
+ schemaVersion = 39
+ tags = []
+ templating = {
+ list = []
+ }
+ time = {
+ from = "now-12h"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder Control Plane"
+ uid = "coderd"
+ weekStart = ""
+ }
+ )
+ dashboard_id = 3
+ folder = [90mnull[0m[0m
+ id = "0:coderd"
+ org_id = "0"
+ uid = "coderd"
+ url = "https://g-93e5b46622.grafana-workspace.us-east-2.amazonaws.com/d/coderd/coder-control-plane"
+ version = 1
+}
+
+# module.monitoring.grafana_dashboard.this["coder-dashboard-prebuilds"]:
+resource "grafana_dashboard" "this" {
+ config_json = jsonencode(
+ {
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = true
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ type = "dashboard"
+ },
+ ]
+ }
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ links = []
+ panels = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ fixedColor = "text"
+ mode = "fixed"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 4
+ w = 4
+ x = 0
+ y = 0
+ }
+ interval = "30s"
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "vertical"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(max(coderd_prebuilt_workspaces_desired) by (template_name, preset_name)) or vector(0)"
+ instant = true
+ interval = ""
+ legendFormat = "Desired"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(max(coderd_prebuilt_workspaces_running) by (template_name, preset_name)) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Running"
+ range = false
+ refId = "D"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(max(coderd_prebuilt_workspaces_eligible) by (template_name, preset_name)) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Eligible"
+ range = false
+ refId = "E"
+ },
+ ]
+ title = "Current: Global"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ fixedColor = "text"
+ mode = "fixed"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 4
+ w = 4
+ x = 4
+ y = 0
+ }
+ interval = "30s"
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "vertical"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(max by (template_name, preset_name) (coderd_prebuilt_workspaces_created_total)) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Created"
+ range = false
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(max by (template_name, preset_name) (coderd_prebuilt_workspaces_failed_total)) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Failed"
+ range = false
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(max by (template_name, preset_name) (coderd_prebuilt_workspaces_claimed_total)) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Claimed"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "All Time: Global"
+ type = "stat"
+ },
+ {
+ collapsed = false
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 4
+ }
+ panels = []
+ repeat = "preset"
+ title = "$preset"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ fixedColor = "text"
+ mode = "fixed"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 3
+ w = 6
+ x = 0
+ y = 5
+ }
+ interval = "30s"
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "vertical"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_prebuilt_workspaces_created_total{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Created"
+ range = false
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_prebuilt_workspaces_failed_total{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Failed"
+ range = false
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_prebuilt_workspaces_claimed_total{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Claimed"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "All Time: $preset"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ axisSoftMax = 10
+ axisSoftMin = 0
+ barAlignment = 0
+ barWidthFactor = 0.6
+ drawStyle = "line"
+ fillOpacity = 13
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "smooth"
+ lineStyle = {
+ fill = "solid"
+ }
+ lineWidth = 2
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "never"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ decimals = 0
+ fieldMinMax = false
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Failed"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "red"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Created"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "blue"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Desired"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "purple"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Running"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "yellow"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Eligible"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Claimed"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "dark-green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 9
+ x = 6
+ y = 5
+ }
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ hideZeros = false
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "floor(max(increase(coderd_prebuilt_workspaces_created_total{template_name=~\"$template\", preset_name=~\"$preset\"}[$__rate_interval]))) or vector(0)"
+ hide = false
+ instant = false
+ interval = ""
+ legendFormat = "Created"
+ range = true
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "floor(max(increase(coderd_prebuilt_workspaces_failed_total{template_name=~\"$template\", preset_name=~\"$preset\"}[$__rate_interval]))) or vector(0)"
+ hide = false
+ instant = false
+ interval = ""
+ legendFormat = "Failed"
+ range = true
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "floor(max(increase(coderd_prebuilt_workspaces_claimed_total{template_name=~\"$template\", preset_name=~\"$preset\"}[$__rate_interval]))) or vector(0)"
+ hide = false
+ instant = false
+ interval = ""
+ legendFormat = "Claimed"
+ range = true
+ refId = "F"
+ },
+ ]
+ title = "Pool Operations: $preset"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ axisSoftMax = 10
+ axisSoftMin = 0
+ barAlignment = 0
+ barWidthFactor = 0.6
+ drawStyle = "line"
+ fillOpacity = 18
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "smooth"
+ lineStyle = {
+ fill = "solid"
+ }
+ lineWidth = 2
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "never"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ decimals = 0
+ fieldMinMax = false
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Desired"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "purple"
+ mode = "fixed"
+ }
+ },
+ {
+ id = "custom.lineStyle"
+ value = {
+ dash = [
+ 10,
+ 10,
+ ]
+ fill = "dash"
+ }
+ },
+ {
+ id = "custom.fillOpacity"
+ value = 85
+ },
+ {
+ id = "custom.fillBelowTo"
+ value = "Running"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Running"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "yellow"
+ mode = "fixed"
+ }
+ },
+ {
+ id = "custom.fillBelowTo"
+ value = "Eligible"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Eligible"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 9
+ x = 15
+ y = 5
+ }
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ hideZeros = false
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max(coderd_prebuilt_workspaces_desired{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ instant = false
+ interval = ""
+ legendFormat = "Desired"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max(coderd_prebuilt_workspaces_running{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ hide = false
+ instant = false
+ interval = ""
+ legendFormat = "Running"
+ range = true
+ refId = "D"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max(coderd_prebuilt_workspaces_eligible{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ hide = false
+ instant = false
+ interval = ""
+ legendFormat = "Eligible"
+ range = true
+ refId = "E"
+ },
+ ]
+ title = "Pool Capacity: $preset"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ fixedColor = "text"
+ mode = "fixed"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 3
+ w = 6
+ x = 0
+ y = 8
+ }
+ interval = "30s"
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "vertical"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_prebuilt_workspaces_desired{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ instant = true
+ interval = ""
+ legendFormat = "Desired"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_prebuilt_workspaces_running{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Running"
+ range = false
+ refId = "D"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_prebuilt_workspaces_eligible{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Eligible"
+ range = false
+ refId = "E"
+ },
+ ]
+ title = "Current: $preset"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = "Compares the total number of regular workspace creations to prebuilt workspace claims to date."
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 3
+ w = 6
+ x = 0
+ y = 11
+ }
+ options = {
+ colorMode = "none"
+ graphMode = "none"
+ justifyMode = "auto"
+ orientation = "horizontal"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "value_and_name"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(max by (template_name, preset_name) (
+ coderd_workspace_creation_total{
+ template_name=~"$template", preset_name=~"$preset"
+ }
+ )) or vector(0)
+ EOT
+ instant = false
+ legendFormat = "Regular workspaces created"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ sum(max by (template_name, preset_name) (
+ coderd_prebuilt_workspaces_claimed_total{
+ template_name=~"$template", preset_name=~"$preset"
+ }
+ )) or vector(0)
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "Prebuilt workspaces claimed"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "All Time: Regular vs Prebuilt"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = "Median (p50) build time in seconds for Regular Workspace Creation, Prebuilt Workspace Creation, and Prebuilt Workspace Claim"
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ barWidthFactor = 0.6
+ drawStyle = "line"
+ fillOpacity = 0
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "smooth"
+ lineStyle = {
+ dash = [
+ 10,
+ 10,
+ ]
+ fill = "dash"
+ }
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Regular Creation"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "blue"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Prebuild Creation"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "yellow"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Prebuild Claim"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 9
+ x = 6
+ y = 11
+ }
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ hideZeros = false
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ editorMode = "code"
+ expr = <<-EOT
+ histogram_quantile(0.5,
+ sum(
+ coderd_workspace_creation_duration_seconds{
+ template_name=~"$template", preset_name=~"$preset", type="regular"
+ }
+ )
+ )
+ or vector(0)
+ EOT
+ legendFormat = "Regular Creation"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ histogram_quantile(0.5,
+ sum(
+ coderd_workspace_creation_duration_seconds{
+ template_name=~"$template", preset_name=~"$preset", type="prebuild"
+ }
+ )
+ )
+ or vector(0)
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "Prebuild Creation"
+ range = true
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ histogram_quantile(0.5,
+ sum(
+ coderd_prebuilt_workspace_claim_duration_seconds{
+ template_name=~"$template", preset_name=~"$preset"
+ }
+ )
+ )
+ or vector(0)
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "Prebuild Claim"
+ range = true
+ refId = "C"
+ },
+ ]
+ title = "Workspace Build Latency p50"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = "95th-percentile (p95) build time in seconds for Regular Workspace Creation, Prebuilt Workspace Creation, and Prebuilt Workspace Claim."
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ barWidthFactor = 0.6
+ drawStyle = "line"
+ fillOpacity = 0
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "smooth"
+ lineStyle = {
+ dash = [
+ 10,
+ 10,
+ ]
+ fill = "dash"
+ }
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Regular Creation"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "blue"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Prebuild Creation"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "yellow"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Prebuild Claim"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 9
+ x = 15
+ y = 11
+ }
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ hideZeros = false
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ editorMode = "code"
+ expr = <<-EOT
+ histogram_quantile(0.95,
+ sum(
+ coderd_workspace_creation_duration_seconds{
+ template_name=~"$template", preset_name=~"$preset", type="regular"
+ }
+ )
+ )
+ or vector(0)
+ EOT
+ legendFormat = "Regular Creation"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ histogram_quantile(0.95,
+ sum(
+ coderd_workspace_creation_duration_seconds{
+ template_name=~"$template", preset_name=~"$preset", type="prebuild"
+ }
+ )
+ )
+ or vector(0)
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "Prebuild Creation"
+ range = true
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ histogram_quantile(0.95,
+ sum(
+ coderd_prebuilt_workspace_claim_duration_seconds{
+ template_name=~"$template", preset_name=~"$preset"
+ }
+ )
+ )
+ or vector(0)
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "Prebuild Claim"
+ range = true
+ refId = "C"
+ },
+ ]
+ title = "Workspace Build Latency p95"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ max = 100
+ min = 0
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = 0
+ },
+ {
+ color = "#EAB839"
+ value = 50
+ },
+ {
+ color = "green"
+ value = 75
+ },
+ ]
+ }
+ unit = "percent"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 3
+ w = 6
+ x = 0
+ y = 14
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ editorMode = "code"
+ expr = <<-EOT
+ clamp_max(
+ 100 *
+ (
+ sum(
+ coderd_prebuilt_workspaces_claimed_total{
+ template_name="$template", preset_name="$preset"
+ }
+ ) or vector(0)
+ )
+ /
+ clamp_min(
+ (
+ sum(
+ coderd_prebuilt_workspaces_claimed_total{
+ template_name="$template", preset_name="$preset"
+ }
+ ) or vector(0))
+ +
+ (
+ sum(
+ coderd_workspace_creation_total{
+ template_name="$template", preset_name="$preset"
+ }
+ ) or vector(0)),
+ 1
+ ),
+ 100
+ )
+ EOT
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ ]
+ title = "All Time: Prebuilds Usage %"
+ type = "stat"
+ },
+ ]
+ preload = false
+ refresh = "30s"
+ schemaVersion = 41
+ tags = []
+ templating = {
+ list = [
+ {
+ current = {
+ text = "coder"
+ value = "coder"
+ }
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ definition = "label_values(coderd_prebuilt_workspaces_desired,template_name)"
+ includeAll = false
+ label = "Template"
+ name = "template"
+ options = []
+ query = {
+ qryType = 1
+ query = "label_values(coderd_prebuilt_workspaces_desired,template_name)"
+ refId = "PrometheusVariableQueryEditor-VariableQuery"
+ }
+ refresh = 1
+ regex = ""
+ type = "query"
+ },
+ {
+ current = {
+ text = [
+ "All",
+ ]
+ value = [
+ "$__all",
+ ]
+ }
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ definition = "label_values(coderd_prebuilt_workspaces_desired{template_name=~\"$template\"},preset_name)"
+ includeAll = true
+ label = "Preset"
+ multi = true
+ name = "preset"
+ options = []
+ query = {
+ qryType = 1
+ query = "label_values(coderd_prebuilt_workspaces_desired{template_name=~\"$template\"},preset_name)"
+ refId = "PrometheusVariableQueryEditor-VariableQuery"
+ }
+ refresh = 1
+ regex = ""
+ type = "query"
+ },
+ ]
+ }
+ time = {
+ from = "now-12h"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder Prebuilds"
+ uid = "cej6jysyme22oa"
+ }
+ )
+ dashboard_id = 8
+ folder = [90mnull[0m[0m
+ id = "0:cej6jysyme22oa"
+ org_id = "0"
+ uid = "cej6jysyme22oa"
+ url = "https://g-93e5b46622.grafana-workspace.us-east-2.amazonaws.com/d/cej6jysyme22oa/coder-prebuilds"
+ version = 1
+}
+
+# module.monitoring.grafana_dashboard.this["coder-dashboard-provisionerd"]:
+resource "grafana_dashboard" "this" {
+ config_json = jsonencode(
+ {
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = true
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ target = {
+ limit = 100
+ matchAny = false
+ tags = []
+ type = "dashboard"
+ }
+ type = "dashboard"
+ },
+ ]
+ }
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ links = []
+ panels = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 6
+ x = 0
+ y = 0
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(coderd_provisionerd_num_daemons{pod=~`coder.*`, pod!~`.*provisioner.*`})"
+ instant = true
+ legendFormat = "Built-in"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(coderd_provisionerd_num_daemons{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` })"
+ hide = false
+ instant = true
+ legendFormat = "External"
+ range = false
+ refId = "B"
+ },
+ ]
+ title = "Provisioners"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 6
+ x = 6
+ y = 0
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ Provisioners are responsible for building workspaces.
+
+ `coderd` runs built-in provisioners by default. Control this with the `CODER_PROVISIONER_DAEMONS` environment variable or `--provisioner-daemons` flag.
+
+ You can also consider [External Provisioners](https://coder.com/docs/v2/latest/admin/provisioners). Running both built-in and external provisioners is perfectly valid,
+ although dedicated (external) provisioners will generally give the best build performance.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 6
+ x = 12
+ y = 0
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "last",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "(sum(coderd_provisionerd_jobs_current) > 0) or vector(0)"
+ instant = false
+ legendFormat = "Current"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(coderd_provisionerd_num_daemons)"
+ hide = false
+ instant = true
+ legendFormat = "Capacity"
+ range = false
+ refId = "B"
+ },
+ ]
+ title = "Builds"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 6
+ x = 18
+ y = 0
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ The maximum number of simultaneous builds is equivalent to the number of `provisionerd` daemons running.
+
+ The "Capacity" panel shows the how many simultaneous builds are possible.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ fieldMinMax = false
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 6
+ x = 0
+ y = 7
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "none"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "histogram_quantile(0.5, sum by(le) (rate(coderd_provisionerd_job_timings_seconds_bucket[$__range])))"
+ hide = false
+ instant = true
+ legendFormat = "Median"
+ range = false
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "histogram_quantile(0.9, sum by(le) (rate(coderd_provisionerd_job_timings_seconds_bucket[$__range])))"
+ hide = false
+ instant = true
+ legendFormat = "90th Percentile"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Build Times"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 6
+ x = 6
+ y = 7
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ This shows the median and 90th percentile workspace build times.
+
+ Long build times can impede developers' productivity while they wait for workspaces to start or be created.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "normal"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ decimals = 0
+ fieldMinMax = false
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "failed"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ {
+ id = "displayName"
+ value = "Failure"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "success"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ {
+ id = "displayName"
+ value = "Success"
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 6
+ x = 12
+ y = 7
+ }
+ interval = "1h"
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum by (status) (increase(coderd_provisionerd_job_timings_seconds_count[$__interval]))"
+ hide = false
+ instant = false
+ interval = "1h"
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ ]
+ title = "Build Count Per Hour"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 6
+ x = 18
+ y = 7
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = "_NOTE: this will not show the current hour._"
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "never"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ fieldMinMax = false
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byRegexp"
+ options = "/(Limit|Requested)/"
+ }
+ properties = [
+ {
+ id = "custom.drawStyle"
+ value = "line"
+ },
+ {
+ id = "custom.fillOpacity"
+ value = 5
+ },
+ {
+ id = "custom.lineStyle"
+ value = {
+ dash = [
+ 0,
+ 10,
+ ]
+ fill = "dot"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Limit"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Requested"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 6
+ x = 0
+ y = 14
+ }
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum by (pod) (rate(container_cpu_usage_seconds_total{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` }[$__rate_interval]))"
+ hide = false
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(kube_pod_container_resource_limits{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` , resource=\"cpu\"})"
+ hide = false
+ instant = false
+ legendFormat = "Limit"
+ range = true
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(kube_pod_container_resource_requests{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` , resource=\"cpu\"})"
+ hide = false
+ instant = false
+ legendFormat = "Requested"
+ range = true
+ refId = "C"
+ },
+ ]
+ title = "CPU Usage Seconds"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 6
+ x = 6
+ y = 14
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ The cumulative CPU used per core-second. If the process was using a full CPU core, that would be represented as 1 second.
+
+ Requests & limits are shown if set.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "never"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ fieldMinMax = false
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "bytes"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byRegexp"
+ options = "/(Limit|Requested)/"
+ }
+ properties = [
+ {
+ id = "custom.drawStyle"
+ value = "line"
+ },
+ {
+ id = "custom.fillOpacity"
+ value = 5
+ },
+ {
+ id = "custom.lineStyle"
+ value = {
+ dash = [
+ 0,
+ 10,
+ ]
+ fill = "dot"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Limit"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Requested"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 6
+ x = 12
+ y = 14
+ }
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max by (pod) (container_memory_working_set_bytes{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` })"
+ hide = false
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(kube_pod_container_resource_limits{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` , resource=\"memory\"})"
+ hide = false
+ instant = false
+ legendFormat = "Limit"
+ range = true
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(kube_pod_container_resource_requests{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` , resource=\"memory\"})"
+ hide = false
+ instant = false
+ legendFormat = "Requested"
+ range = true
+ refId = "C"
+ },
+ ]
+ title = "RAM Usage"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 6
+ x = 18
+ y = 14
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ This shows the total memory used by each container; it is the same metric which the [OOM killer](https://www.kernel.org/doc/gorman/html/understand/understand016.html) uses.
+
+ Requests & limits are shown if set.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ gridPos = {
+ h = 18
+ w = 18
+ x = 0
+ y = 21
+ }
+ options = {
+ dedupStrategy = "exact"
+ enableLogDetails = true
+ prettifyLogMessage = false
+ showCommonLabels = false
+ showLabels = false
+ showTime = true
+ sortOrder = "Descending"
+ wrapLogMessage = false
+ }
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ editorMode = "code"
+ expr = "{ namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=~\"(.*runner|terraform|provisioner.*)\"}"
+ queryType = "range"
+ refId = "A"
+ },
+ ]
+ title = "Logs"
+ type = "logs"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 6
+ x = 18
+ y = 21
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = "This panel shows all logs across built-in and [external provisioners](https://coder.com/docs/v2/latest/admin/provisioners)."
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ ]
+ refresh = "30s"
+ schemaVersion = 39
+ tags = []
+ templating = {
+ list = []
+ }
+ time = {
+ from = "now-12h"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder Provisioners"
+ uid = "provisionerd"
+ weekStart = ""
+ }
+ )
+ dashboard_id = 2
+ folder = [90mnull[0m[0m
+ id = "0:provisionerd"
+ org_id = "0"
+ uid = "provisionerd"
+ url = "https://g-93e5b46622.grafana-workspace.us-east-2.amazonaws.com/d/provisionerd/coder-provisioners"
+ version = 1
+}
+
+# module.monitoring.grafana_dashboard.this["coder-dashboard-status"]:
+resource "grafana_dashboard" "this" {
+ config_json = jsonencode(
+ {
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = false
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ target = {
+ limit = 100
+ matchAny = false
+ tags = []
+ type = "dashboard"
+ }
+ type = "dashboard"
+ },
+ ]
+ }
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ links = []
+ panels = [
+ {
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 0
+ }
+ title = "Application"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Down"
+ }
+ properties = [
+ {
+ id = "thresholds"
+ value = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 1
+ },
+ ]
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 4
+ x = 0
+ y = 1
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "count(up{ pod=~`coder.*`, namespace=~`coder` } == 1) or vector(0) > 0"
+ instant = true
+ legendFormat = "Up"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "count(up{ pod=~`coder.*`, namespace=~`coder` } == 0) or vector(0) > 0"
+ hide = false
+ instant = true
+ legendFormat = "Down"
+ range = false
+ refId = "B"
+ },
+ ]
+ title = "Coder Replicas"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 4
+ x = 4
+ y = 1
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(coderd_provisionerd_num_daemons{ pod=~`coder.*`, namespace=~`coder` })"
+ instant = true
+ legendFormat = "Built-in"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(coderd_provisionerd_num_daemons{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` })"
+ hide = false
+ instant = true
+ legendFormat = "External"
+ range = false
+ refId = "B"
+ },
+ ]
+ title = "Provisioners"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ }
+ mappings = []
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "failed"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ {
+ id = "displayName"
+ value = "Failed"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "success"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ {
+ id = "displayName"
+ value = "Success"
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 4
+ x = 8
+ y = 1
+ }
+ options = {
+ displayLabels = [
+ "name",
+ "value",
+ ]
+ legend = {
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ values = [
+ "percent",
+ ]
+ }
+ pieType = "pie"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "round(sum by (status) (increase(coderd_provisionerd_job_timings_seconds_count{pod!=``}[$__range])))"
+ instant = true
+ legendFormat = "{{status}}"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Workspace Builds"
+ type = "piechart"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 4
+ x = 12
+ y = 1
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ count(kube_pod_status_ready{condition="true", pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)`} == 1)
+ or
+ sum(max by (workspace_owner, template_name, template_version) (coderd_workspace_latest_build_status{status="succeeded", workspace_transition="start"}))
+ or
+ vector(0)
+ EOT
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Running Workspaces"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ decimals = 0
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Down"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Up"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byRegexp"
+ options = "/.*RAM/"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "bytes"
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 4
+ x = 16
+ y = 1
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(
+ max_over_time(
+ rate(container_cpu_usage_seconds_total{ pod=~`coder.*`, namespace=~`coder` }[1h:1m])
+ [$__range:]
+ )
+ )
+ EOT
+ instant = true
+ legendFormat = "Control Plane CPU"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(
+ max_over_time(
+ rate(container_cpu_usage_seconds_total{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` }[1h:1m])
+ [$__range:]
+ )
+ )
+ EOT
+ hide = false
+ instant = true
+ legendFormat = "Provisioner CPU"
+ range = false
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(
+ max_over_time(
+ container_memory_working_set_bytes{ pod=~`coder.*`, namespace=~`coder` }
+ [$__range:]
+ )
+ )
+ EOT
+ hide = false
+ instant = true
+ legendFormat = "Control Plane RAM"
+ range = false
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(
+ max_over_time(
+ container_memory_working_set_bytes{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` }
+ [$__range:]
+ )
+ )
+ EOT
+ hide = false
+ instant = true
+ legendFormat = "Provisioner RAM"
+ range = false
+ refId = "D"
+ },
+ ]
+ title = "Resource Usage High Watermark (Cumulative)"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Down"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Up"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 4
+ x = 20
+ y = 1
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "min(pg_up) or vector(0)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Postgres"
+ type = "stat"
+ },
+ {
+ collapsed = false
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 8
+ }
+ panels = []
+ title = "Observability Tools"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Down"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Up"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 0
+ y = 9
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "vector(1)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Prometheus"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Down"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Up"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 4
+ y = 9
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "min(up{job=\"observability/loki/write\"}) or vector(0)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Loki Write Path"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Down"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Up"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 8
+ y = 9
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "min(up{job=\"observability/loki/read\"}) or vector(0)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Loki Read Path"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Down"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Up"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 12
+ y = 9
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "min(up{job=\"observability/loki/backend\", container=\"loki\"}) or vector(0)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Loki Backend"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Down"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Up"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 16
+ y = 9
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "min(up{job=\"observability/loki/canary\"}) or vector(0)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Loki Canary"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 20
+ y = 9
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "last",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "sum(up{job=\"observability/grafana-agent/grafana-agent\"}) or vector(0)"
+ instant = true
+ legendFormat = "Current"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "count(up{job=\"observability/grafana-agent/grafana-agent\"})"
+ instant = true
+ legendFormat = "Capacity"
+ range = false
+ refId = "B"
+ },
+ ]
+ title = "Grafana Agent"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Unhealthy"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Healthy"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 4
+ y = 14
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "min(loki_runtime_config_last_reload_successful) or vector(0)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Loki Config"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Unhealthy"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Healthy"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 8
+ y = 14
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "min(agent_config_last_load_successful{job=\"observability/grafana-agent/grafana-agent\"}) or vector(0)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Grafana Agent Config"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "percentunit"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Retention Limit"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "red"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Write-Ahead Log"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "purple"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Storage"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "#f9f9fb"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 12
+ y = 14
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ (
+ prometheus_tsdb_wal_storage_size_bytes{job="observability/prometheus/server"} +
+ prometheus_tsdb_storage_blocks_bytes{job="observability/prometheus/server"} +
+ prometheus_tsdb_symbol_table_size_bytes{job="observability/prometheus/server"}
+ )
+ /
+ prometheus_tsdb_retention_limit_bytes{job="observability/prometheus/server"}
+ EOT
+ instant = false
+ legendFormat = "Retention limit used"
+ range = true
+ refId = "A"
+ },
+ ]
+ title = "Prometheus Storage"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "none"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 16
+ y = 14
+ }
+ options = {
+ colorMode = "value"
+ graphMode = "none"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ titleSize = 20
+ valueSize = 35
+ }
+ textMode = "auto"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(kube_pod_container_resource_requests{namespace=\"observability\", resource=\"cpu\"})"
+ hide = false
+ instant = true
+ legendFormat = "Requested"
+ range = false
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(
+ max_over_time(
+ rate(container_cpu_usage_seconds_total{namespace="observability"}[$__rate_interval])
+ [$__range:]
+ )
+ )
+ EOT
+ hide = false
+ instant = true
+ legendFormat = "High Watermark"
+ range = false
+ refId = "D"
+ },
+ ]
+ title = "CPU"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "bytes"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 20
+ y = 14
+ }
+ options = {
+ colorMode = "none"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "vertical"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ titleSize = 20
+ valueSize = 35
+ }
+ textMode = "value_and_name"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(kube_pod_container_resource_requests{namespace=\"observability\", resource=\"memory\"})"
+ hide = false
+ instant = true
+ legendFormat = "Requested"
+ range = false
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(
+ max_over_time(container_memory_working_set_bytes{namespace="observability"}[$__range])
+ )
+ EOT
+ instant = true
+ legendFormat = "High Watermark"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "RAM"
+ type = "stat"
+ },
+ ]
+ refresh = "30s"
+ schemaVersion = 39
+ tags = []
+ templating = {
+ list = []
+ }
+ time = {
+ from = "now-24h"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder Status"
+ uid = "coder-status"
+ weekStart = ""
+ }
+ )
+ dashboard_id = 5
+ folder = [90mnull[0m[0m
+ id = "0:coder-status"
+ org_id = "0"
+ uid = "coder-status"
+ url = "https://g-93e5b46622.grafana-workspace.us-east-2.amazonaws.com/d/coder-status/coder-status"
+ version = 5
+}
+
+# module.monitoring.grafana_dashboard.this["coder-dashboard-workspace-detail"]:
+resource "grafana_dashboard" "this" {
+ config_json = jsonencode(
+ {
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = true
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ type = "dashboard"
+ },
+ ]
+ }
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ links = []
+ panels = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ description = ""
+ gridPos = {
+ h = 1.2
+ w = 24
+ x = 0
+ y = 0
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = "**HINT**: use the dropdowns above to filter by specific workspace(s)."
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "blue"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "CPUs Requested"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "none"
+ },
+ {
+ id = "decimals"
+ value = 2
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "RAM Requested"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "bytes"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "PVC Capacity"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "bytes"
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 4
+ w = 20
+ x = 0
+ y = 1.2
+ }
+ options = {
+ colorMode = "none"
+ graphMode = "none"
+ justifyMode = "center"
+ orientation = "vertical"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = "/.*/"
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ titleSize = 20
+ valueSize = 40
+ }
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "group by (template_name) (coderd_agents_up{workspace_name=~\"$workspace_name\"})"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "Template Name"
+ range = false
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "group by (template_version) (coderd_agents_up{workspace_name=~\"$workspace_name\"})"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "Template Version"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "group by (username) (coderd_agents_up{workspace_name=~\"$workspace_name\"})"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "Owner"
+ range = false
+ refId = "C"
+ },
+ ]
+ title = "Details"
+ transformations = [
+ {
+ id = "concatenate"
+ options = {}
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = true
+ "Value #A" = true
+ "Value #B" = true
+ "Value #C" = true
+ "Value #D" = true
+ }
+ includeByName = {}
+ indexByName = {
+ "CPUs Requested" = 7
+ "PVC Capacity" = 9
+ "RAM Requested" = 8
+ Time = 0
+ "Value #A" = 5
+ "Value #B" = 3
+ "Value #C" = 6
+ template_name = 2
+ template_version = 4
+ username = 1
+ }
+ renameByName = {
+ "Value #C" = ""
+ lifecycle_state = "Agent State"
+ template_name = "Template"
+ template_version = "Template Version"
+ username = "Owner"
+ }
+ }
+ },
+ ]
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 8
+ w = 4
+ x = 20
+ y = 1.2
+ }
+ links = [
+ {
+ title = "Provisioners Dashboard"
+ url = "/d/provisionerd/provisioners?${__url_time_range}"
+ },
+ ]
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = "Essential information about the selected workspace."
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "blue"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "CPUs Requested"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "none"
+ },
+ {
+ id = "decimals"
+ value = 2
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "RAM Requested"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "bytes"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "PVC Capacity"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "bytes"
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 4
+ w = 20
+ x = 0
+ y = 5.2
+ }
+ options = {
+ colorMode = "none"
+ graphMode = "none"
+ justifyMode = "center"
+ orientation = "vertical"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = "/.*/"
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ titleSize = 20
+ valueSize = 40
+ }
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(kube_pod_container_resource_requests{pod=~\".*$workspace_name.*\", pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)`, resource=\"cpu\"})"
+ format = "time_series"
+ hide = false
+ instant = true
+ legendFormat = "CPUs Requested"
+ range = false
+ refId = "D"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(kube_pod_container_resource_requests{pod=~\".*$workspace_name.*\", pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)`, resource=\"memory\"})"
+ format = "time_series"
+ hide = false
+ instant = true
+ legendFormat = "RAM Requested"
+ range = false
+ refId = "E"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(
+ kube_pod_spec_volumes_persistentvolumeclaims_info{pod=~".*$workspace_name.*", pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` }
+ * on(persistentvolumeclaim) group_right
+ group by (persistentvolumeclaim, persistentvolume) (
+ label_replace(
+ kube_persistentvolume_claim_ref,
+ "persistentvolumeclaim",
+ "$1",
+ "name",
+ "(.+)"
+ )
+ )
+ * on (persistentvolume)
+ kube_persistentvolume_capacity_bytes
+ )
+ EOT
+ format = "time_series"
+ hide = false
+ instant = true
+ legendFormat = "PVC Capacity"
+ range = false
+ refId = "F"
+ },
+ ]
+ title = "Resources"
+ transformations = [
+ {
+ id = "concatenate"
+ options = {}
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = true
+ "Value #A" = true
+ "Value #B" = true
+ "Value #C" = true
+ "Value #D" = true
+ }
+ includeByName = {}
+ indexByName = {
+ "CPUs Requested" = 7
+ "PVC Capacity" = 9
+ "RAM Requested" = 8
+ Time = 0
+ "Value #A" = 5
+ "Value #B" = 3
+ "Value #C" = 6
+ template_name = 2
+ template_version = 4
+ username = 1
+ }
+ renameByName = {
+ "Value #C" = ""
+ lifecycle_state = "Agent State"
+ template_name = "Template"
+ template_version = "Template Version"
+ username = "Owner"
+ }
+ }
+ },
+ ]
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ mappings = [
+ {
+ options = {
+ created = {
+ color = "light-blue"
+ index = 1
+ text = "Created"
+ }
+ off = {
+ color = "text"
+ index = 8
+ text = "Off"
+ }
+ ready = {
+ color = "green"
+ index = 0
+ text = "Ready"
+ }
+ shutdown_error = {
+ color = "red"
+ index = 7
+ text = "Shutdown Error"
+ }
+ shutdown_timeout = {
+ color = "purple"
+ index = 6
+ text = "Shutdown Timeout"
+ }
+ shutting_down = {
+ color = "light-purple"
+ index = 5
+ text = "Shutting Down"
+ }
+ start_error = {
+ color = "red"
+ index = 4
+ text = "Start Error"
+ }
+ start_timeout = {
+ color = "orange"
+ index = 3
+ text = "Start Timeout"
+ }
+ starting = {
+ color = "super-light-green"
+ index = 2
+ text = "Starting"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "text"
+ index = 9
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "text"
+ index = 10
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 6
+ w = 4
+ x = 0
+ y = 9.2
+ }
+ options = {
+ colorMode = "background"
+ graphMode = "none"
+ justifyMode = "auto"
+ orientation = "horizontal"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = "/^lifecycle_state$/"
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ valueSize = 50
+ }
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max by (lifecycle_state) (coderd_agents_connections{workspace_name=~\"$workspace_name\"})"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "D"
+ },
+ ]
+ title = "Agent Lifecycle State"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ mappings = [
+ {
+ options = {
+ "-1" = {
+ color = "light-orange"
+ index = 0
+ text = "Not completed yet"
+ }
+ }
+ type = "value"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "#EAB839"
+ value = 60
+ },
+ {
+ color = "red"
+ value = 120
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 6
+ w = 3
+ x = 4
+ y = 9.2
+ }
+ options = {
+ colorMode = "background"
+ graphMode = "none"
+ justifyMode = "auto"
+ orientation = "horizontal"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = "/^Value$/"
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ valueSize = 50
+ }
+ textMode = "value"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_agentstats_startup_script_seconds{workspace_name=~\"$workspace_name\"}) or vector(-1)"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "C"
+ },
+ ]
+ title = "Agent Startup Script Execution Time"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 6
+ w = 3
+ x = 7
+ y = 9.2
+ }
+ options = {
+ colorMode = "background"
+ graphMode = "none"
+ justifyMode = "center"
+ orientation = "horizontal"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = "/.*/"
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ titleSize = 20
+ valueSize = 50
+ }
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ max by (app) (
+ label_replace(
+ {workspace_name=~"$workspace_name", __name__=~"coderd_agentstats_session_count_.*"},
+ "app",
+ "$1",
+ "__name__",
+ "coderd_agentstats_session_count_(.*)"
+ )
+ )>0
+ EOT
+ format = "time_series"
+ hide = false
+ instant = true
+ legendFormat = "{{app}}"
+ range = false
+ refId = "C"
+ },
+ ]
+ title = "App Session Counts"
+ transformations = [
+ {
+ id = "concatenate"
+ options = {}
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = true
+ }
+ includeByName = {}
+ indexByName = {}
+ renameByName = {}
+ }
+ },
+ ]
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byRegexp"
+ options = "/.*Bytes/"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "bytes"
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 10
+ x = 10
+ y = 9.2
+ }
+ options = {
+ colorMode = "none"
+ graphMode = "none"
+ justifyMode = "center"
+ orientation = "vertical"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = "/.*/"
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ titleSize = 20
+ valueSize = 50
+ }
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_agents_connection_latencies_seconds{workspace_name=~\"$workspace_name\"})"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "Connection Latency"
+ range = false
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(sum by (pod) (sum_over_time(coderd_agentstats_rx_bytes{workspace_name=~\"$workspace_name\"}[$__range])))"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "Received Bytes"
+ range = false
+ refId = "rx"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(sum by (pod) (sum_over_time(coderd_agentstats_tx_bytes{workspace_name=~\"$workspace_name\"}[$__range])))"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "Transmitted Bytes"
+ range = false
+ refId = "tx"
+ },
+ ]
+ title = "Networking"
+ transformations = [
+ {
+ id = "merge"
+ options = {}
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = true
+ }
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ "Value #A" = "Received Bytes"
+ "Value #B" = "Transmitted Bytes"
+ "Value #C" = "Connection Latency"
+ "Value #rx" = "Received Bytes"
+ "Value #tx" = "Transmitted Bytes"
+ }
+ }
+ },
+ ]
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 4
+ x = 20
+ y = 9.2
+ }
+ links = [
+ {
+ title = "Provisioners Dashboard"
+ url = "/d/provisionerd/provisioners?${__url_time_range}"
+ },
+ ]
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ Essential information about this workspace's agent.
+
+ Read more about the agent [here](https://coder.com/docs/v2/latest/about/architecture#agents).
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ }
+ filterable = true
+ inspect = false
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "status"
+ }
+ properties = [
+ {
+ id = "custom.cellOptions"
+ value = {
+ type = "color-text"
+ }
+ },
+ {
+ id = "mappings"
+ value = [
+ {
+ options = {
+ failed = {
+ color = "orange"
+ index = 1
+ text = "Failure"
+ }
+ success = {
+ color = "green"
+ index = 0
+ text = "Success"
+ }
+ }
+ type = "value"
+ },
+ ]
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Workspace Transition"
+ }
+ properties = [
+ {
+ id = "custom.cellOptions"
+ value = {
+ type = "color-text"
+ }
+ },
+ {
+ id = "mappings"
+ value = [
+ {
+ options = {
+ DESTROY = {
+ color = "red"
+ index = 0
+ }
+ START = {
+ color = "blue"
+ index = 1
+ }
+ STOP = {
+ color = "purple"
+ index = 2
+ }
+ }
+ type = "value"
+ },
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 20
+ x = 0
+ y = 15.2
+ }
+ interval = ""
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ enablePagination = true
+ fields = []
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ showHeader = true
+ sortBy = [
+ {
+ desc = true
+ displayName = "Time"
+ },
+ ]
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum by (workspace_name, workspace_owner, status, template_name, template_version, workspace_transition) (
+ # Since new series are created and are initially set to a value of 1, we cannot use "increase" (because an increase from to 1 does not yield 1).
+ # So we compare the current series to an interval ago to see if we have any new series and then sum the series we find.
+ ((
+ coderd_workspace_builds_total{workspace_name=~"$workspace_name"} -
+ coderd_workspace_builds_total{workspace_name=~"$workspace_name"} offset $__interval
+ ) >= 0)
+ or coderd_workspace_builds_total{workspace_name=~"$workspace_name"}
+ ) > 0
+ EOT
+ format = "table"
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ ]
+ title = "Build Log"
+ transformations = [
+ {
+ disabled = true
+ id = "groupBy"
+ options = {
+ fields = {
+ Count = {
+ aggregations = [
+ "sum",
+ ]
+ operation = "aggregate"
+ }
+ Status = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Template Name" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Template Version" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ Total = {
+ aggregations = [
+ "sum",
+ ]
+ operation = "aggregate"
+ }
+ Value = {
+ aggregations = [
+ "sum",
+ ]
+ operation = "aggregate"
+ }
+ "Workspace Name" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Workspace Ownert" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Workspace Transition" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ status = {
+ aggregations = []
+ operation = "groupby"
+ }
+ template_name = {
+ aggregations = []
+ operation = "groupby"
+ }
+ template_version = {
+ aggregations = []
+ operation = "groupby"
+ }
+ workspace_name = {
+ aggregations = []
+ operation = "groupby"
+ }
+ workspace_owner = {
+ aggregations = []
+ operation = "groupby"
+ }
+ workspace_transition = {
+ aggregations = []
+ operation = "groupby"
+ }
+ }
+ }
+ },
+ {
+ id = "sortBy"
+ options = {
+ fields = {}
+ sort = [
+ {
+ desc = true
+ field = "Value"
+ },
+ ]
+ }
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = false
+ }
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ Value = "Count"
+ "Value (sum)" = "Total"
+ status = "Status"
+ template_name = "Template Name"
+ template_version = "Template Version"
+ workspace_name = "Workspace Name"
+ workspace_owner = "Workspace Owner"
+ workspace_transition = "Workspace Transition"
+ }
+ }
+ },
+ ]
+ type = "table"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 4
+ x = 20
+ y = 15.2
+ }
+ links = [
+ {
+ title = "Provisioners Dashboard"
+ url = "/d/provisionerd/provisioners?${__url_time_range}"
+ },
+ ]
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ This table shows a reverse-chronological log of all workspace builds.
+
+ The "Count" field shows the count of events which occurred within a minute, grouped by all columns.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ gridPos = {
+ h = 10
+ w = 20
+ x = 0
+ y = 22.2
+ }
+ options = {
+ dedupStrategy = "exact"
+ enableLogDetails = true
+ prettifyLogMessage = false
+ showCommonLabels = false
+ showLabels = false
+ showTime = true
+ sortOrder = "Descending"
+ wrapLogMessage = false
+ }
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ editorMode = "code"
+ expr = "{namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=~\"(.*runner|terraform|provisioner.*)\"} |~ \"$workspace_name\" | line_format `{{ printf \"[\\033[35m\" }}{{.pod}}{{ printf \"\\033[0m]\\t\" }}{{ __line__ }}`"
+ hide = false
+ queryType = "range"
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ editorMode = "code"
+ expr = "{pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)`, pod=~\".*($workspace_name).*\"} | line_format `{{ printf \"[\\033[32m\" }}{{.pod}}{{ printf \"\\033[0m]\\t\" }}{{ __line__ }}`"
+ hide = false
+ queryType = "range"
+ refId = "B"
+ },
+ ]
+ title = "Logs"
+ type = "logs"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 10
+ w = 4
+ x = 20
+ y = 22.2
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ The logs to the left come both from provisioners and workspace logs.
+
+ Provisioner logs matching the name filter are highlighted in magenta, while
+ workspace logs matching the name filter are highlighted in green.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ ]
+ refresh = "30s"
+ schemaVersion = 39
+ tags = []
+ templating = {
+ list = [
+ {
+ allValue = ""
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ definition = "label_values(coderd_agents_up,workspace_name)"
+ hide = 0
+ includeAll = false
+ label = "Workspace Name Filter"
+ multi = false
+ name = "workspace_name"
+ options = []
+ query = {
+ qryType = 1
+ query = "label_values(coderd_agents_up,workspace_name)"
+ refId = "PrometheusVariableQueryEditor-VariableQuery"
+ }
+ refresh = 2
+ regex = ""
+ skipUrlSync = false
+ sort = 1
+ type = "query"
+ },
+ ]
+ }
+ time = {
+ from = "now-12h"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder Workspace Detail"
+ uid = "workspace-detail"
+ weekStart = ""
+ }
+ )
+ dashboard_id = 6
+ folder = [90mnull[0m[0m
+ id = "0:workspace-detail"
+ org_id = "0"
+ uid = "workspace-detail"
+ url = "https://g-93e5b46622.grafana-workspace.us-east-2.amazonaws.com/d/workspace-detail/coder-workspace-detail"
+ version = 1
+}
+
+# module.monitoring.grafana_dashboard.this["coder-dashboard-workspaces"]:
+resource "grafana_dashboard" "this" {
+ config_json = jsonencode(
+ {
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = true
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ type = "dashboard"
+ },
+ ]
+ }
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ links = []
+ panels = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ description = ""
+ gridPos = {
+ h = 1.2
+ w = 24
+ x = 0
+ y = 0
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = "**HINT**: use the dropdowns above to filter by specific workspaces and/or templates."
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 1.2
+ }
+ title = "Resources"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 1
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 8
+ w = 10
+ x = 0
+ y = 2.2
+ }
+ options = {
+ legend = {
+ calcs = [
+ "mean",
+ "stdDev",
+ "min",
+ "max",
+ "lastNotNull",
+ ]
+ displayMode = "table"
+ placement = "bottom"
+ showLegend = true
+ sortBy = "Max"
+ sortDesc = true
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "desc"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "sum by (pod) (rate(container_cpu_usage_seconds_total{ pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` }[$__rate_interval]))"
+ hide = false
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "CPU Usage"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 1
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "bytes"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 8
+ w = 10
+ x = 10
+ y = 2.2
+ }
+ options = {
+ legend = {
+ calcs = [
+ "mean",
+ "stdDev",
+ "min",
+ "max",
+ "lastNotNull",
+ ]
+ displayMode = "table"
+ placement = "bottom"
+ showLegend = true
+ sortBy = "Max"
+ sortDesc = true
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "desc"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max by (pod) (container_memory_working_set_bytes{ pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` })"
+ hide = false
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "RAM Usage"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 8
+ w = 4
+ x = 20
+ y = 2.2
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ The cumulative CPU used per core-second. If a workspace was using a full CPU core, that would be represented as 1 second.
+
+ See the Kubernetes [documentation](https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/#cpu-units) for more details.
+
+ The total memory used by each workspace container is represented; it is the same metric which the [OOM killer](https://www.kernel.org/doc/gorman/html/understand/understand016.html) uses.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 1
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ decimals = 0
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "none"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 8
+ w = 10
+ x = 0
+ y = 10.2
+ }
+ options = {
+ legend = {
+ calcs = [
+ "sum",
+ ]
+ displayMode = "table"
+ placement = "bottom"
+ showLegend = true
+ sortBy = "Max"
+ sortDesc = true
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "desc"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ sum by (pod) (
+ round(increase(kube_pod_container_status_restarts_total{ pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` }[$__interval]))
+ ) > 0
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "Pod Restarts"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 1
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ decimals = 0
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "none"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 8
+ w = 10
+ x = 10
+ y = 10.2
+ }
+ options = {
+ legend = {
+ calcs = [
+ "sum",
+ ]
+ displayMode = "table"
+ placement = "bottom"
+ showLegend = true
+ sortBy = "Max"
+ sortDesc = true
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "desc"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ sum by (pod, reason) (
+ count_over_time(kube_pod_container_status_terminated_reason{ pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` }[$__interval])
+ )
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "{{pod}}:{{reason}}"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "Terminations"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 8
+ w = 4
+ x = 20
+ y = 10.2
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ Pods can be terminated for several reasons:
+ - `OOMKilled`: pod exceeded its defined memory limit or was terminated by the OS for using excessive memory (if no limit defined)
+ - `Error`: usually attributeable to a configuration problem
+ - `Evicted`: pod has been evicted from node for overusing resources and will be rescheduled on another node is possible
+
+ Pod restarts are not necessarily problematic, but they are worth noting.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ collapsed = false
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 18.2
+ }
+ panels = []
+ title = "Builds"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 1
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "normal"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "DESTROY"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "red"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "STOP"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "purple"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "START"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "blue"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 8
+ w = 10
+ x = 0
+ y = 19.2
+ }
+ interval = "5m"
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "desc"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ sum by (workspace_transition) (
+ (
+ # Since new series are created and are initially set to a value of 1, we cannot use "increase" (because an increase from to 1 does not yield 1).
+ # So we compare the current series to an interval ago to see if we have any new series and then sum the series we find.
+ (
+ coderd_workspace_builds_total{status="success", workspace_name=~"$workspace_name", template_name=~"$template_name"} -
+ coderd_workspace_builds_total{status="success", workspace_name=~"$workspace_name", template_name=~"$template_name"} offset $__interval
+ ) >= 0)
+ or coderd_workspace_builds_total{status="success", workspace_name=~"$workspace_name", template_name=~"$template_name"}
+ ) > 0
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "Successful Builds by State"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "normal"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "DESTROY"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "red"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "STOP"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "purple"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "START"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "blue"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 8
+ w = 10
+ x = 10
+ y = 19.2
+ }
+ interval = "5m"
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "desc"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ sum by (workspace_transition) (
+ (
+ # Since new series are created and are initially set to a value of 1, we cannot use "increase" (because an increase from to 1 does not yield 1).
+ # So we compare the current series to an interval ago to see if we have any new series and then sum the series we find.
+ (
+ coderd_workspace_builds_total{status="failed", workspace_name=~"$workspace_name", template_name=~"$template_name"} -
+ coderd_workspace_builds_total{status="failed", workspace_name=~"$workspace_name", template_name=~"$template_name"} offset $__interval
+ ) >= 0)
+ or coderd_workspace_builds_total{status="failed", workspace_name=~"$workspace_name", template_name=~"$template_name"}
+ ) > 0
+ EOT
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ ]
+ title = "Unsuccessful Builds by State"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 8
+ w = 4
+ x = 20
+ y = 19.2
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ Workspaces "transition" between `STOP`, `START`, and `DESTROY` states.
+
+ Workspaces transition between states when a "build" is initiated, which is an execution of `terraform` against the chosen template.
+
+ Use the "Build Count" table to identify workspace owners which may be struggling with template builds, in order to proactively reach out to them with assistance.
+
+ Consult the [Template documentation](https://coder.com/docs/v2/latest/templates) for more information.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ }
+ filterable = true
+ inspect = false
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "status"
+ }
+ properties = [
+ {
+ id = "custom.cellOptions"
+ value = {
+ type = "color-text"
+ }
+ },
+ {
+ id = "mappings"
+ value = [
+ {
+ options = {
+ failed = {
+ color = "orange"
+ index = 1
+ text = "Failure"
+ }
+ success = {
+ color = "green"
+ index = 0
+ text = "Success"
+ }
+ }
+ type = "value"
+ },
+ ]
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Workspace Transition"
+ }
+ properties = [
+ {
+ id = "custom.cellOptions"
+ value = {
+ type = "color-text"
+ }
+ },
+ {
+ id = "mappings"
+ value = [
+ {
+ options = {
+ DESTROY = {
+ color = "red"
+ index = 0
+ }
+ START = {
+ color = "blue"
+ index = 1
+ }
+ STOP = {
+ color = "purple"
+ index = 2
+ }
+ }
+ type = "value"
+ },
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 10
+ w = 20
+ x = 0
+ y = 27.2
+ }
+ interval = ""
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ enablePagination = true
+ fields = []
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ showHeader = true
+ sortBy = [
+ {
+ desc = true
+ displayName = "Time"
+ },
+ ]
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum by (workspace_name, workspace_owner, status, template_name, template_version, workspace_transition) (
+ # Since new series are created and are initially set to a value of 1, we cannot use "increase" (because an increase from to 1 does not yield 1).
+ # So we compare the current series to an interval ago to see if we have any new series and then sum the series we find.
+ ((
+ coderd_workspace_builds_total{workspace_name=~"$workspace_name", template_name=~"$template_name"} -
+ coderd_workspace_builds_total{workspace_name=~"$workspace_name", template_name=~"$template_name"} offset $__interval
+ ) >= 0)
+ or coderd_workspace_builds_total{workspace_name=~"$workspace_name", template_name=~"$template_name"}
+ ) > 0
+ EOT
+ format = "table"
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ ]
+ title = "Build Log"
+ transformations = [
+ {
+ disabled = true
+ id = "groupBy"
+ options = {
+ fields = {
+ Count = {
+ aggregations = [
+ "sum",
+ ]
+ operation = "aggregate"
+ }
+ Status = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Template Name" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Template Version" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ Total = {
+ aggregations = [
+ "sum",
+ ]
+ operation = "aggregate"
+ }
+ Value = {
+ aggregations = [
+ "sum",
+ ]
+ operation = "aggregate"
+ }
+ "Workspace Name" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Workspace Ownert" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Workspace Transition" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ status = {
+ aggregations = []
+ operation = "groupby"
+ }
+ template_name = {
+ aggregations = []
+ operation = "groupby"
+ }
+ template_version = {
+ aggregations = []
+ operation = "groupby"
+ }
+ workspace_name = {
+ aggregations = []
+ operation = "groupby"
+ }
+ workspace_owner = {
+ aggregations = []
+ operation = "groupby"
+ }
+ workspace_transition = {
+ aggregations = []
+ operation = "groupby"
+ }
+ }
+ }
+ },
+ {
+ id = "sortBy"
+ options = {
+ fields = {}
+ sort = [
+ {
+ desc = true
+ field = "Value"
+ },
+ ]
+ }
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = false
+ }
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ Value = "Count"
+ "Value (sum)" = "Total"
+ status = "Status"
+ template_name = "Template Name"
+ template_version = "Template Version"
+ workspace_name = "Workspace Name"
+ workspace_owner = "Workspace Owner"
+ workspace_transition = "Workspace Transition"
+ }
+ }
+ },
+ ]
+ type = "table"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 10
+ w = 4
+ x = 20
+ y = 27.2
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ This table shows a reverse-chronological log of all workspace builds.
+
+ The "Count" field shows the count of events which occurred within a minute, grouped by all columns.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ }
+ mappings = []
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 5
+ x = 0
+ y = 37.2
+ }
+ interval = "1h"
+ options = {
+ displayLabels = [
+ "name",
+ ]
+ legend = {
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ values = [
+ "percent",
+ ]
+ }
+ pieType = "pie"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "none"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "count by (workspace_owner) (coderd_workspace_latest_build_status{template_name=~\"$template_name\"})"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Workspace by User"
+ type = "piechart"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ }
+ mappings = []
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 5
+ x = 5
+ y = 37.2
+ }
+ interval = "1h"
+ options = {
+ displayLabels = [
+ "name",
+ ]
+ legend = {
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ values = [
+ "percent",
+ ]
+ }
+ pieType = "pie"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "none"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "count by (workspace_owner, template_name) (coderd_workspace_latest_build_status{template_name=~\"$template_name\"})"
+ instant = true
+ legendFormat = "{{workspace_owner}}:{{template_name}}"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Workspace by User/Template"
+ type = "piechart"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ }
+ mappings = []
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 5
+ x = 10
+ y = 37.2
+ }
+ interval = "1h"
+ options = {
+ displayLabels = [
+ "name",
+ ]
+ legend = {
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ values = [
+ "percent",
+ ]
+ }
+ pieType = "pie"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "none"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "count by (template_name) (coderd_workspace_latest_build_status{template_name=~\"$template_name\"})"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Template Usage"
+ type = "piechart"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ }
+ mappings = []
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 5
+ x = 15
+ y = 37.2
+ }
+ interval = "1h"
+ options = {
+ displayLabels = []
+ legend = {
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ values = [
+ "percent",
+ ]
+ }
+ pieType = "pie"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "none"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "count by (template_name, template_version) (coderd_workspace_latest_build_status{template_name=~\"$template_name\"})"
+ instant = true
+ legendFormat = "{{template_name}}:{{template_version}}"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Template Version Usage"
+ type = "piechart"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 4
+ x = 20
+ y = 37.2
+ }
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ These charts show the distribution of workspaces and templates.
+
+ Use these charts to identify which users have outdated templates, and which templates are the most/least popular in your organisation.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ collapsed = false
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 44.2
+ }
+ panels = []
+ title = "Logs"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ gridPos = {
+ h = 10
+ w = 20
+ x = 0
+ y = 45.2
+ }
+ options = {
+ dedupStrategy = "exact"
+ enableLogDetails = true
+ prettifyLogMessage = false
+ showCommonLabels = false
+ showLabels = false
+ showTime = false
+ sortOrder = "Descending"
+ wrapLogMessage = true
+ }
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ editorMode = "code"
+ expr = "{ namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=~\"(.*runner|terraform|provisioner.*)\"} |~ \"$workspace_name\" or \"$template_name\""
+ queryType = "range"
+ refId = "A"
+ },
+ ]
+ title = "Logs"
+ type = "logs"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 10
+ w = 4
+ x = 20
+ y = 45.2
+ }
+ links = [
+ {
+ title = "Provisioners Dashboard"
+ url = "/d/provisionerd/provisioners?${__url_time_range}"
+ },
+ ]
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ These are the logs produced by the [Provisioners](/d/provisionerd/provisioners?${__url_time_range}).
+
+ Use the dropdowns at the top to filter the logs down to a specific workspace and/or template.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ ]
+ refresh = "30s"
+ schemaVersion = 39
+ tags = []
+ templating = {
+ list = [
+ {
+ allValue = ""
+ current = {
+ selected = true
+ text = [
+ "All",
+ ]
+ value = [
+ "$__all",
+ ]
+ }
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ definition = "label_values(coderd_workspace_builds_total,workspace_name)"
+ hide = 0
+ includeAll = true
+ label = "Workspace Name Filter"
+ multi = true
+ name = "workspace_name"
+ options = []
+ query = {
+ qryType = 1
+ query = "label_values(coderd_workspace_builds_total,workspace_name)"
+ refId = "PrometheusVariableQueryEditor-VariableQuery"
+ }
+ refresh = 2
+ regex = ""
+ skipUrlSync = false
+ sort = 1
+ type = "query"
+ },
+ {
+ allValue = ""
+ current = {
+ selected = true
+ text = [
+ "All",
+ ]
+ value = [
+ "$__all",
+ ]
+ }
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ definition = "label_values(coderd_workspace_builds_total,template_name)"
+ hide = 0
+ includeAll = true
+ label = "Template Name Filter"
+ multi = true
+ name = "template_name"
+ options = []
+ query = {
+ qryType = 1
+ query = "label_values(coderd_workspace_builds_total,template_name)"
+ refId = "PrometheusVariableQueryEditor-VariableQuery"
+ }
+ refresh = 2
+ regex = ""
+ skipUrlSync = false
+ sort = 1
+ type = "query"
+ },
+ ]
+ }
+ time = {
+ from = "now-12h"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder Workspaces"
+ uid = "workspaces"
+ weekStart = ""
+ }
+ )
+ dashboard_id = 7
+ folder = [90mnull[0m[0m
+ id = "0:workspaces"
+ org_id = "0"
+ uid = "workspaces"
+ url = "https://g-93e5b46622.grafana-workspace.us-east-2.amazonaws.com/d/workspaces/coder-workspaces"
+ version = 1
+}
+
+# module.monitoring.grafana_data_source.cloudwatch:
+resource "grafana_data_source" "cloudwatch" {
+ access_mode = "proxy"
+ basic_auth_enabled = false
+ basic_auth_username = [90mnull[0m[0m
+ database_name = [90mnull[0m[0m
+ id = "1:ffgmjtsy2uxogc"
+ is_default = false
+ json_data_encoded = jsonencode(
+ {
+ authType = "default"
+ defaultRegion = "us-east-2"
+ }
+ )
+ name = "cloudwatch"
+ org_id = "1"
+ secure_json_data_encoded = (sensitive value)
+ type = "cloudwatch"
+ uid = "ffgmjtsy2uxogc"
+ url = [90mnull[0m[0m
+ username = [90mnull[0m[0m
+}
+
+# module.monitoring.grafana_data_source.loki-gateway:
+resource "grafana_data_source" "loki-gateway" {
+ access_mode = "proxy"
+ basic_auth_enabled = false
+ basic_auth_username = [90mnull[0m[0m
+ database_name = [90mnull[0m[0m
+ id = "1:efgmmv5m20wsgb"
+ is_default = false
+ json_data_encoded = jsonencode({})
+ name = "loki"
+ org_id = "1"
+ type = "loki"
+ uid = "efgmmv5m20wsgb"
+ url = "http://ab9dd01908f9641e883bbfb55a20d82c-309251381.us-east-2.elb.amazonaws.com"
+ username = [90mnull[0m[0m
+}
+
+# module.monitoring.grafana_data_source.postgres:
+resource "grafana_data_source" "postgres" {
+ access_mode = "proxy"
+ basic_auth_enabled = false
+ basic_auth_username = [90mnull[0m[0m
+ database_name = [90mnull[0m[0m
+ id = "1:cfgmmv670x4aoa"
+ is_default = false
+ json_data_encoded = jsonencode({})
+ name = "postgres"
+ org_id = "1"
+ secure_json_data_encoded = (sensitive value)
+ type = "postgres"
+ uid = "cfgmmv670x4aoa"
+ url = (sensitive value)
+ username = (sensitive value)
+}
+
+# module.monitoring.grafana_data_source.prometheus:
+resource "grafana_data_source" "prometheus" {
+ access_mode = "proxy"
+ basic_auth_enabled = false
+ basic_auth_username = [90mnull[0m[0m
+ database_name = [90mnull[0m[0m
+ id = "1:efgmimxclccn4a"
+ is_default = true
+ json_data_encoded = jsonencode(
+ {
+ httpMethod = "POST"
+ sigV4Auth = true
+ sigV4AuthType = "default"
+ sigV4Region = "us-east-2"
+ }
+ )
+ name = "prometheus"
+ org_id = "1"
+ type = "prometheus"
+ uid = "efgmimxclccn4a"
+ url = "https://aps-workspaces.us-east-2.amazonaws.com/workspaces/ws-be12ffb7-baf0-4c53-a4cc-4bd651b1643f/"
+ username = [90mnull[0m[0m
+}
+
+# module.monitoring.grafana_organization_preferences.this:
+resource "grafana_organization_preferences" "this" {
+ home_dashboard_uid = "coder-status"
+ id = "0"
+ org_id = "0"
+ theme = "system"
+ timezone = "browser"
+ week_start = "monday"
+}
+
+# module.monitoring.helm_release.coder-observe:
+resource "helm_release" "coder-observe" {
+ atomic = false
+ chart = "coder-observability"
+ cleanup_on_fail = false
+ create_namespace = false
+ dependency_update = false
+ disable_crd_hooks = false
+ disable_openapi_validation = false
+ disable_webhooks = false
+ force_update = false
+ id = "coder-observe"
+ lint = false
+ max_history = 10
+ metadata = {
+ app_version = ""
+ chart = "coder-observability"
+ first_deployed = 1771892988
+ last_deployed = 1774381845
+ name = "coder-observe"
+ namespace = "observability"
+ notes = <<-EOT
+ 1. Get the application URL by running these commands:
+ export POD_NAME=$(kubectl get pods --namespace observability -l "app.kubernetes.io/name=alertmanager,app.kubernetes.io/instance=coder-observe" -o jsonpath="{.items[0].metadata.name}")
+ echo "Visit http://127.0.0.1:80 to use your application"
+ kubectl --namespace observability port-forward $POD_NAME 80:80
+
+ kube-state-metrics is a simple service that listens to the Kubernetes API server and generates metrics about the state of the objects.
+ The exposed metrics can be found here:
+ https://github.com/kubernetes/kube-state-metrics/blob/master/docs/README.md#exposed-metrics
+
+ The metrics are exported on the HTTP endpoint /metrics on the listening port.
+ In your case, kube-state-metrics.observability.svc.cluster.local:8080/metrics
+
+ They are served either as plaintext or protobuf depending on the Accept header.
+ They are designed to be consumed either by Prometheus itself or by a scraper that is compatible with scraping a Prometheus client endpoint.
+
+ 1. Get the application URL by running these commands:
+ export POD_NAME=$(kubectl get pods --namespace observability -l "app.kubernetes.io/name=prometheus-node-exporter,app.kubernetes.io/instance=coder-observe" -o jsonpath="{.items[0].metadata.name}")
+ echo "Visit http://127.0.0.1:9100 to use your application"
+ kubectl port-forward --namespace observability $POD_NAME 9100
+ The Prometheus server can be accessed via port 80 on the following DNS name from within your cluster:
+ prometheus.observability.svc.cluster.local
+
+
+ Get the Prometheus server URL by running these commands in the same shell:
+ export POD_NAME=$(kubectl get pods --namespace observability -l "app.kubernetes.io/name=prometheus,app.kubernetes.io/instance=coder-observe" -o jsonpath="{.items[0].metadata.name}")
+ kubectl --namespace observability port-forward $POD_NAME 9090
+ #################################################################################
+ ###### WARNING: Persistence is disabled!!! You will lose your data when #####
+ ###### the Server pod is terminated. #####
+ #################################################################################
+
+
+ The Prometheus alertmanager can be accessed via port 80 on the following DNS name from within your cluster:
+ alertmanager.observability.svc.cluster.local
+
+
+ Get the Alertmanager URL by running these commands in the same shell:
+ export POD_NAME=$(kubectl get pods --namespace observability -l "app.kubernetes.io/name=alertmanager,app.kubernetes.io/instance=coder-observe" -o jsonpath="{.items[0].metadata.name}")
+ kubectl --namespace observability port-forward $POD_NAME 9093
+ #################################################################################
+ ###### WARNING: Pod Security Policy has been disabled by default since #####
+ ###### it deprecated after k8s 1.25+. use #####
+ ###### (index .Values "prometheus-node-exporter" "rbac" #####
+ ###### . "pspEnabled") with (index .Values #####
+ ###### "prometheus-node-exporter" "rbac" "pspAnnotations") #####
+ ###### in case you still need it. #####
+ #################################################################################
+
+
+
+ For more information on running Prometheus, visit:
+ https://prometheus.io/
+
+ ***********************************************************************
+ Welcome to Grafana Loki
+ Chart version: 6.7.4
+ Chart Name: loki
+ Loki version: 3.1.0
+ ***********************************************************************
+
+ ** Please be patient while the chart is being deployed **
+
+ Tip:
+
+ Watch the deployment status using the command: kubectl get pods -w --namespace observability
+
+ If pods are taking too long to schedule make sure pod affinity can be fulfilled in the current cluster.
+
+ ***********************************************************************
+ Installed components:
+ ***********************************************************************
+ * gateway
+ * read
+ * write
+ * backend
+
+
+ ***********************************************************************
+ Sending logs to Loki
+ ***********************************************************************
+
+ Loki has been configured with a gateway (nginx) to support reads and writes from a single component.
+
+ You can send logs from inside the cluster using the cluster DNS:
+
+ http://loki-gateway.observability.svc.cluster.local/loki/api/v1/push
+
+ You can test to send data from outside the cluster by port-forwarding the gateway to your local machine:
+
+ kubectl port-forward --namespace observability svc/loki-gateway 3100:80 &
+
+ And then using http://127.0.0.1:3100/loki/api/v1/push URL as shown below:
+
+ ```
+ curl -H "Content-Type: application/json" -XPOST -s "http://127.0.0.1:3100/loki/api/v1/push" \
+ --data-raw "{\"streams\": [{\"stream\": {\"job\": \"test\"}, \"values\": [[\"$(date +%s)000000000\", \"fizzbuzz\"]]}]}"
+ ```
+
+ Then verify that Loki did received the data using the following command:
+
+ ```
+ curl "http://127.0.0.1:3100/loki/api/v1/query_range" --data-urlencode 'query={job="test"}' | jq .data.result
+ ```
+
+ ***********************************************************************
+ Connecting Grafana to Loki
+ ***********************************************************************
+
+ If Grafana operates within the cluster, you'll set up a new Loki datasource by utilizing the following URL:
+
+ http://loki-gateway.observability.svc.cluster.local/
+ EOT
+ revision = 102
+ values = jsonencode(
+ {
+ global = {
+ coder = {
+ coderdSelector = "pod=~`coder.*`, pod!~`.*provisioner.*`, namespace=~`(coder)`"
+ controlPlaneNamespace = "coder"
+ externalProvisionersNamespace = "coder"
+ provisionerdSelector = "pod=~`coder-provisioner.*`, namespace=~`(coder)`"
+ workspacesSelector = "pod!~`coder.*`, namespace=~`(coder)`"
+ }
+ dashboards = {
+ enabled = false
+ }
+ postgres = {
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [
+ {
+ matchExpressions = [
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [
+ "us-east-2a",
+ ]
+ },
+ {
+ key = "node.coder.io/used-for"
+ operator = "In"
+ values = [
+ "prometheus",
+ ]
+ },
+ {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = [
+ "arm64",
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ }
+ }
+ database = "coder"
+ exporter = {
+ enabled = true
+ }
+ hostname = "aienv.clcmw4qgylx0.us-east-2.rds.amazonaws.com"
+ mountSecret = ""
+ password = "Coder123!!"
+ port = "5432"
+ sslmode = "require"
+ username = "coder"
+ }
+ }
+ grafana = {
+ adminPassword = "P@kcEPulli3UrE247*I^7dk!m6#%MZX!"
+ adminUser = "coder"
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [
+ {
+ matchExpressions = [
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [
+ "us-east-2a",
+ ]
+ },
+ {
+ key = "node.coder.io/used-for"
+ operator = "In"
+ values = [
+ "observability-platform",
+ ]
+ },
+ {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = [
+ "arm64",
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ }
+ podAntiAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = [
+ {
+ labelSelector = {
+ matchLabels = {
+ "app.kubernetes.io/name" = "grafana"
+ }
+ }
+ topologyKey = "kubernetes.io/hostname"
+ },
+ ]
+ }
+ }
+ assertNoLeakedSecrets = false
+ datasources = {
+ "datasources.yaml" = {
+ datasources = [
+ {
+ access = "proxy"
+ editable = false
+ isDefault = true
+ jsonData = {
+ httpMethod = "POST"
+ sigV4Auth = true
+ sigV4AuthType = "default"
+ sigV4Region = "us-east-2"
+ }
+ name = "metrics"
+ timeout = 905
+ type = "prometheus"
+ uid = "prometheus"
+ url = "https://aps-workspaces.us-east-2.amazonaws.com/workspaces/ws-be12ffb7-baf0-4c53-a4cc-4bd651b1643f/"
+ },
+ {
+ access = "proxy"
+ editable = false
+ isDefault = false
+ name = "pyroscope"
+ timeout = 905
+ type = "grafana-pyroscope-datasource"
+ uid = "pyroscope"
+ url = "http://pyroscope.observability.svc:4040"
+ },
+ {
+ access = "proxy"
+ editable = false
+ isDefault = false
+ name = "traces"
+ timeout = 905
+ type = "tempo"
+ uid = "tempo"
+ url = "http://tempo.observability.svc:3200"
+ },
+ ]
+ }
+ }
+ enabled = false
+ env = {
+ GF_SECURITY_DISABLE_INITIAL_ADMIN_CREATION = false
+ }
+ extraConfigmapMounts = [
+ {
+ configMap = "coder-dashboard-aibridge"
+ mountPath = "/var/lib/grafana/dashboards/coder/6"
+ name = "coder-dashboard-aibridge"
+ optional = true
+ readOnly = false
+ },
+ {
+ configMap = "coder-dashboard-boundary"
+ mountPath = "/var/lib/grafana/dashboards/coder/7"
+ name = "coder-dashboard-boundary"
+ optional = true
+ readOnly = false
+ },
+ {
+ configMap = "coder-dashboard-coderd"
+ mountPath = "/var/lib/grafana/dashboards/coder/1"
+ name = "coder-dashboard-coderd"
+ optional = true
+ readOnly = false
+ },
+ {
+ configMap = "coder-dashboard-prebuilds"
+ mountPath = "/var/lib/grafana/dashboards/coder/5"
+ name = "coder-dashboard-prebuilds"
+ optional = true
+ readOnly = false
+ },
+ {
+ configMap = "coder-dashboard-provisionerd"
+ mountPath = "/var/lib/grafana/dashboards/coder/2"
+ name = "coder-dashboard-provisionerd"
+ optional = true
+ readOnly = false
+ },
+ {
+ configMap = "coder-dashboard-status"
+ mountPath = "/var/lib/grafana/dashboards/coder/0"
+ name = "coder-dashboard-status"
+ optional = true
+ readOnly = false
+ },
+ {
+ configMap = "coder-dashboard-workspace-detail"
+ mountPath = "/var/lib/grafana/dashboards/coder/4"
+ name = "coder-dashboard-workspace-detail"
+ optional = true
+ readOnly = false
+ },
+ {
+ configMap = "coder-dashboard-workspaces"
+ mountPath = "/var/lib/grafana/dashboards/coder/3"
+ name = "coder-dashboard-workspaces"
+ optional = true
+ readOnly = false
+ },
+ ]
+ extraSecretMounts = [
+ {
+ mountPath = "/tmp/grafana/ssl/"
+ name = "grafana-ai-coder-com"
+ readOnly = true
+ secretName = "grafana-ai-coder-com"
+ },
+ ]
+ "grafana.ini" = {
+ app_mode = "production"
+ auth = {
+ sigv4_auth_enabled = true
+ }
+ "auth.anonymous" = {
+ enabled = false
+ }
+ dashboards = {
+ default_home_dashboard_path = "/var/lib/grafana/dashboards/coder/0/status.json"
+ }
+ database = {
+ host = "aienv-grafana.clcmw4qgylx0.us-east-2.rds.amazonaws.com"
+ name = "grafana"
+ password = "\"\"\"pn#RJuJmC#C0PJ0xM*p5db%QGpQAvoLw\"\"\""
+ port = "5432"
+ ssl_mode = "require"
+ username = "grafana"
+ }
+ instance_name = "Coder Environment"
+ security = {
+ cookie_domain = "grafana.ai.coder.com"
+ cookie_samesite = "lax"
+ cookie_secure = true
+ secret_key = "d95196f6beeaa37678d7ddc786410162"
+ }
+ server = {
+ cert_file = "/tmp/grafana/ssl/tls.crt"
+ cert_key = "/tmp/grafana/ssl/tls.key"
+ domain = "grafana.ai.coder.com"
+ enforce_domain = false
+ http_port = 3000
+ protocol = "https"
+ root_url = "https://grafana.ai.coder.com"
+ }
+ users = {
+ allow_sign_up = false
+ }
+ }
+ livenessProbe = {
+ httpGet = {
+ scheme = "HTTPS"
+ }
+ }
+ nodeSelector = {}
+ persistence = {
+ enabled = false
+ }
+ podAnnotations = {
+ "prometheus.io/port" = "3000"
+ "prometheus.io/scheme" = "https"
+ "prometheus.io/scrape" = "true"
+ }
+ readinessProbe = {
+ httpGet = {
+ scheme = "HTTPS"
+ }
+ }
+ replicas = 0
+ resources = {
+ limits = [90mnull[0m[0m
+ requests = [90mnull[0m[0m
+ }
+ service = {
+ annotations = {
+ "service.beta.kubernetes.io/aws-load-balancer-attributes" = "deletion_protection.enabled=false"
+ "service.beta.kubernetes.io/aws-load-balancer-eip-allocations" = "eipalloc-0533bf84460b51c11"
+ "service.beta.kubernetes.io/aws-load-balancer-healthcheck-port" = "3000"
+ "service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol" = "TCP"
+ "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "ip"
+ "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
+ "service.beta.kubernetes.io/aws-load-balancer-subnets" = "aienv-public-us-east-2a"
+ "service.beta.kubernetes.io/aws-load-balancer-target-group-attributes" = "stickiness.enabled=true,stickiness.type=source_ip"
+ }
+ enabled = true
+ externalTrafficPolicy = "Cluster"
+ internalTrafficPolicy = "Cluster"
+ loadBalancerClass = "service.k8s.aws/nlb"
+ port = 443
+ targetPort = 3000
+ type = "LoadBalancer"
+ }
+ serviceAccount = {
+ annotations = {
+ "eks.amazonaws.com/role-arn" = "arn:aws:iam::750246862020:role/aienv/us-east-2/observability-access-20260320095145755600000002"
+ }
+ }
+ tolerations = [
+ {
+ effect = "NoSchedule"
+ key = "platform"
+ value = "observability-platform"
+ },
+ ]
+ useStatefulSet = true
+ }
+ grafana-agent = {
+ enabled = false
+ }
+ loki = {
+ backend = {
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [
+ {
+ matchExpressions = [
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [
+ "us-east-2a",
+ ]
+ },
+ {
+ key = "node.coder.io/used-for"
+ operator = "In"
+ values = [
+ "observability-platform",
+ ]
+ },
+ {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = [
+ "arm64",
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ }
+ }
+ persistence = {
+ volumeClaimsEnabled = false
+ }
+ replicas = 1
+ tolerations = [
+ {
+ effect = "NoSchedule"
+ key = "platform"
+ value = "observability-platform"
+ },
+ ]
+ }
+ chunksCache = {
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [
+ {
+ matchExpressions = [
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [
+ "us-east-2a",
+ ]
+ },
+ {
+ key = "node.coder.io/used-for"
+ operator = "In"
+ values = [
+ "observability-platform",
+ ]
+ },
+ {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = [
+ "arm64",
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ }
+ }
+ persistence = {
+ enabled = true
+ storageClass = "gp3-automode"
+ }
+ replicas = 1
+ tolerations = [
+ {
+ effect = "NoSchedule"
+ key = "platform"
+ value = "observability-platform"
+ },
+ ]
+ }
+ gateway = {
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [
+ {
+ matchExpressions = [
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [
+ "us-east-2a",
+ ]
+ },
+ {
+ key = "node.coder.io/used-for"
+ operator = "In"
+ values = [
+ "observability-platform",
+ ]
+ },
+ {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = [
+ "arm64",
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ }
+ }
+ podAnnotatiosn = {
+ "prometheus.io/scrape" = "true"
+ }
+ replicas = 1
+ service = {
+ annotations = {
+ "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "ip"
+ "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internal"
+ }
+ type = "LoadBalancer"
+ }
+ tolerations = [
+ {
+ effect = "NoSchedule"
+ key = "platform"
+ value = "observability-platform"
+ },
+ ]
+ }
+ loki = {
+ rulerConfig = {
+ remote_write = {
+ clients = {
+ fake = {
+ url = "https://aps-workspaces.us-east-2.amazonaws.com/workspaces/ws-be12ffb7-baf0-4c53-a4cc-4bd651b1643f//api/v1/remote_write"
+ }
+ }
+ enabled = true
+ }
+ }
+ storage = {
+ bucketNames = {
+ chunks = "aienv-v2-logs"
+ ruler = "aienv-v2-logs"
+ }
+ s3 = {
+ region = "us-east-2"
+ }
+ type = "s3"
+ }
+ }
+ lokiCanary = {
+ nodeSelector = {}
+ tolerations = [
+ {
+ effect = "NoSchedule"
+ operator = "Exists"
+ },
+ ]
+ }
+ minio = {
+ enabled = false
+ }
+ read = {
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [
+ {
+ matchExpressions = [
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [
+ "us-east-2a",
+ ]
+ },
+ {
+ key = "node.coder.io/used-for"
+ operator = "In"
+ values = [
+ "observability-platform",
+ ]
+ },
+ {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = [
+ "arm64",
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ }
+ }
+ podAnnotatiosn = {
+ "prometheus.io/scrape" = "true"
+ }
+ replicas = 1
+ tolerations = [
+ {
+ effect = "NoSchedule"
+ key = "platform"
+ value = "observability-platform"
+ },
+ ]
+ }
+ resultsCache = {
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [
+ {
+ matchExpressions = [
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [
+ "us-east-2a",
+ ]
+ },
+ {
+ key = "node.coder.io/used-for"
+ operator = "In"
+ values = [
+ "observability-platform",
+ ]
+ },
+ {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = [
+ "arm64",
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ }
+ }
+ replicas = 1
+ tolerations = [
+ {
+ effect = "NoSchedule"
+ key = "platform"
+ value = "observability-platform"
+ },
+ ]
+ }
+ serviceAccount = {
+ annotations = {
+ "eks.amazonaws.com/role-arn" = "arn:aws:iam::750246862020:role/aienv/us-east-2/observability-access-20260320095145755600000002"
+ }
+ create = true
+ }
+ write = {
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [
+ {
+ matchExpressions = [
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [
+ "us-east-2a",
+ ]
+ },
+ {
+ key = "node.coder.io/used-for"
+ operator = "In"
+ values = [
+ "observability-platform",
+ ]
+ },
+ {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = [
+ "arm64",
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ }
+ }
+ persistence = {
+ volumeClaimsEnabled = false
+ }
+ replicas = 1
+ tolerations = [
+ {
+ effect = "NoSchedule"
+ key = "platform"
+ value = "observability-platform"
+ },
+ ]
+ }
+ }
+ prometheus = {
+ alertmanager = {
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [
+ {
+ matchExpressions = [
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [
+ "us-east-2a",
+ ]
+ },
+ {
+ key = "node.coder.io/used-for"
+ operator = "In"
+ values = [
+ "observability-platform",
+ ]
+ },
+ {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = [
+ "arm64",
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ }
+ }
+ enabled = true
+ nodeSelector = {}
+ persistence = {
+ enabled = true
+ storageClass = "gp3-automode"
+ }
+ replicas = 0
+ resources = {}
+ tolerations = [
+ {
+ effect = "NoSchedule"
+ key = "platform"
+ value = "observability-platform"
+ },
+ ]
+ }
+ configmapReload = {
+ prometheus = {
+ enabled = false
+ extraArgs = {
+ watch-interval = "30s"
+ }
+ }
+ }
+ enabled = true
+ kube-state-metrics = {
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [
+ {
+ matchExpressions = [
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [
+ "us-east-2a",
+ ]
+ },
+ {
+ key = "node.coder.io/used-for"
+ operator = "In"
+ values = [
+ "prometheus",
+ ]
+ },
+ {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = [
+ "arm64",
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ }
+ }
+ enabled = true
+ podAnnotations = {
+ "prometheus.io/scrape" = "true"
+ }
+ tolerations = [
+ {
+ effect = "NoSchedule"
+ key = "platform"
+ value = "prometheus"
+ },
+ ]
+ }
+ server = {
+ affinity = {
+ nodeAffinity = {
+ requiredDuringSchedulingIgnoredDuringExecution = {
+ nodeSelectorTerms = [
+ {
+ matchExpressions = [
+ {
+ key = "topology.kubernetes.io/zone"
+ operator = "In"
+ values = [
+ "us-east-2a",
+ ]
+ },
+ {
+ key = "node.coder.io/used-for"
+ operator = "In"
+ values = [
+ "prometheus",
+ ]
+ },
+ {
+ key = "beta.kubernetes.io/arch"
+ operator = "In"
+ values = [
+ "arm64",
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ }
+ }
+ extraArgs = {}
+ extraFlags = [
+ "web.enable-lifecycle",
+ "web.enable-remote-write-receiver",
+ ]
+ livenessProbeFailureThreshold = 10
+ livenessProbeInitialDelaySeconds = 60
+ livenessProbePeriodSeconds = 60
+ livenessProbetimeoutSeconds = 60
+ nodeSelector = {}
+ persistentVolume = {
+ enabled = false
+ }
+ readinessProbeFailureThreshold = 100
+ readinessProbeInitialDelay = 60
+ readinessProbePeriodSeconds = 60
+ readinessProbeTimeout = 60
+ replicaCount = 0
+ resources = {
+ limits = {
+ cpu = "6"
+ memory = "12Gi"
+ }
+ requests = {
+ cpu = "2"
+ memory = "8Gi"
+ }
+ }
+ retention = "1h"
+ tolerations = [
+ {
+ effect = "NoSchedule"
+ key = "platform"
+ value = "prometheus"
+ },
+ ]
+ tsdb = {
+ out_of_order_time_window = "1800s"
+ }
+ }
+ }
+ runbookViewer = {
+ enabled = false
+ }
+ sqlExporter = {
+ enabled = false
+ }
+ }
+ )
+ version = "0.7.0-rc.1"
+ }
+ name = "coder-observe"
+ namespace = "observability"
+ pass_credentials = false
+ recreate_pods = false
+ render_subchart_notes = true
+ replace = false
+ repository = "https://helm.coder.com/observability"
+ reset_values = false
+ reuse_values = false
+ set_wo = (write-only attribute)
+ skip_crds = false
+ status = "deployed"
+ take_ownership = false
+ timeout = 360
+ upgrade_install = true
+ values = [
+ (sensitive value),
+ ]
+ verify = false
+ version = "0.7.0-rc.1"
+ wait = true
+ wait_for_jobs = true
+}
+
+# module.monitoring.helm_release.grafana-agent:
+resource "helm_release" "grafana-agent" {
+ atomic = false
+ chart = "grafana-agent"
+ cleanup_on_fail = false
+ create_namespace = false
+ dependency_update = false
+ disable_crd_hooks = false
+ disable_openapi_validation = false
+ disable_webhooks = false
+ force_update = false
+ id = "grafana-agent"
+ lint = false
+ max_history = 0
+ metadata = {
+ app_version = "v0.40.3"
+ chart = "grafana-agent"
+ first_deployed = 1774052979
+ last_deployed = 1774063564
+ name = "grafana-agent"
+ namespace = "observability"
+ notes = <<-EOT
+ Welcome to Grafana Agent!
+ EOT
+ revision = 4
+ values = jsonencode(
+ {
+ agent = {
+ clustering = {
+ enabled = true
+ }
+ configMap = {
+ create = false
+ key = "config.river"
+ name = "collector-config"
+ }
+ extraArgs = [
+ "--disable-reporting=true",
+ ]
+ mode = "flow"
+ mounts = {
+ dockercontainers = true
+ varlog = true
+ }
+ }
+ commonRelabellings = <<-EOT
+ rule {
+ source_labels = ["__meta_kubernetes_namespace"]
+ target_label = "namespace"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_name"]
+ target_label = "pod"
+ }
+ // coalesce the following labels and pick the first value; we'll use this to define the "job" label
+ rule {
+ source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_component", "app", "__meta_kubernetes_pod_container_name"]
+ separator = "/"
+ target_label = "__meta_app"
+ action = "replace"
+ regex = "^/*([^/]+?)(?:/.*)?$" // split by the delimiter if it exists, we only want the first one
+ replacement = "$1"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_namespace", "__meta_kubernetes_pod_label_app_kubernetes_io_name", "__meta_app"]
+ separator = "/"
+ target_label = "job"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_container_name"]
+ target_label = "container"
+ }
+ rule {
+ regex = "__meta_kubernetes_pod_label_(statefulset_kubernetes_io_pod_name|controller_revision_hash)"
+ action = "labeldrop"
+ }
+ rule {
+ regex = "pod_template_generation"
+ action = "labeldrop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_phase"]
+ regex = "Pending|Succeeded|Failed|Completed"
+ action = "drop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_node_name"]
+ action = "replace"
+ target_label = "node"
+ }
+ rule {
+ action = "labelmap"
+ regex = "__meta_kubernetes_pod_annotation_prometheus_io_param_(.+)"
+ replacement = "__param_$1"
+ }
+ EOT
+ controller = {
+ nodeSelector = {}
+ podAnnotations = {
+ "prometheus.io/scheme" = "http"
+ "prometheus.io/scrape" = "true"
+ }
+ tolerations = [
+ {
+ effect = "NoSchedule"
+ operator = "Exists"
+ },
+ ]
+ type = "daemonset"
+ updateStrategy = {
+ rollingUpdate = {
+ maxSurge = 0
+ maxUnavailable = "100%"
+ }
+ type = "RollingUpdate"
+ }
+ }
+ crds = {
+ create = false
+ }
+ discovery = <<-EOT
+ // Discover k8s nodes
+ discovery.kubernetes "nodes" {
+ role = "node"
+ }
+
+ // Discover k8s pods
+ discovery.kubernetes "pods" {
+ role = "pod"
+ selectors {
+ role = "pod"
+ field = "spec.nodeName=" + env("HOSTNAME")
+ }
+ }
+ EOT
+ extraBlocks = ""
+ podLogsRelabelRules = ""
+ podMetricsRelabelRules = ""
+ serviceAccount = {
+ create = false
+ name = "grafana-agent"
+ }
+ withOTLPReceiver = false
+ }
+ )
+ version = "0.37.0"
+ }
+ name = "grafana-agent"
+ namespace = "observability"
+ pass_credentials = false
+ recreate_pods = false
+ render_subchart_notes = true
+ replace = false
+ repository = "https://grafana.github.io/helm-charts"
+ reset_values = false
+ reuse_values = false
+ set_wo = (write-only attribute)
+ skip_crds = false
+ status = "deployed"
+ take_ownership = false
+ timeout = 600
+ upgrade_install = true
+ values = [
+ <<-EOT
+ "agent":
+ "clustering":
+ "enabled": true
+ "configMap":
+ "create": false
+ "key": "config.river"
+ "name": "collector-config"
+ "extraArgs":
+ - "--disable-reporting=true"
+ "mode": "flow"
+ "mounts":
+ "dockercontainers": true
+ "varlog": true
+ "commonRelabellings": |
+ rule {
+ source_labels = ["__meta_kubernetes_namespace"]
+ target_label = "namespace"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_name"]
+ target_label = "pod"
+ }
+ // coalesce the following labels and pick the first value; we'll use this to define the "job" label
+ rule {
+ source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_component", "app", "__meta_kubernetes_pod_container_name"]
+ separator = "/"
+ target_label = "__meta_app"
+ action = "replace"
+ regex = "^/*([^/]+?)(?:/.*)?$" // split by the delimiter if it exists, we only want the first one
+ replacement = "$1"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_namespace", "__meta_kubernetes_pod_label_app_kubernetes_io_name", "__meta_app"]
+ separator = "/"
+ target_label = "job"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_container_name"]
+ target_label = "container"
+ }
+ rule {
+ regex = "__meta_kubernetes_pod_label_(statefulset_kubernetes_io_pod_name|controller_revision_hash)"
+ action = "labeldrop"
+ }
+ rule {
+ regex = "pod_template_generation"
+ action = "labeldrop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_phase"]
+ regex = "Pending|Succeeded|Failed|Completed"
+ action = "drop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_node_name"]
+ action = "replace"
+ target_label = "node"
+ }
+ rule {
+ action = "labelmap"
+ regex = "__meta_kubernetes_pod_annotation_prometheus_io_param_(.+)"
+ replacement = "__param_$1"
+ }
+ "controller":
+ "nodeSelector": {}
+ "podAnnotations":
+ "prometheus.io/scheme": "http"
+ "prometheus.io/scrape": "true"
+ "tolerations":
+ - "effect": "NoSchedule"
+ "operator": "Exists"
+ "type": "daemonset"
+ "updateStrategy":
+ "rollingUpdate":
+ "maxSurge": 0
+ "maxUnavailable": "100%"
+ "type": "RollingUpdate"
+ "crds":
+ "create": false
+ "discovery": |
+ // Discover k8s nodes
+ discovery.kubernetes "nodes" {
+ role = "node"
+ }
+
+ // Discover k8s pods
+ discovery.kubernetes "pods" {
+ role = "pod"
+ selectors {
+ role = "pod"
+ field = "spec.nodeName=" + env("HOSTNAME")
+ }
+ }
+ "extraBlocks": ""
+ "podLogsRelabelRules": ""
+ "podMetricsRelabelRules": ""
+ "serviceAccount":
+ "create": false
+ "name": "grafana-agent"
+ "withOTLPReceiver": false
+ EOT,
+ ]
+ verify = false
+ version = "0.37.0"
+ wait = true
+ wait_for_jobs = true
+}
+
+# module.monitoring.kubernetes_config_map_v1.collector-config:
+resource "kubernetes_config_map_v1" "collector-config" {
+ binary_data = {}
+ data = {
+ "config.river" = <<-EOT
+ // Discover k8s nodes
+ discovery.kubernetes "nodes" {
+ role = "node"
+ }
+
+ // Discover k8s pods
+ discovery.kubernetes "pods" {
+ role = "pod"
+ selectors {
+ role = "pod"
+ field = "spec.nodeName=" + env("HOSTNAME")
+ }
+ }
+
+
+ discovery.relabel "pod_logs" {
+ targets = discovery.kubernetes.pods.targets
+
+ rule {
+ source_labels = ["__meta_kubernetes_namespace"]
+ target_label = "namespace"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_name"]
+ target_label = "pod"
+ }
+ // coalesce the following labels and pick the first value; we'll use this to define the "job" label
+ rule {
+ source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_component", "app", "__meta_kubernetes_pod_container_name"]
+ separator = "/"
+ target_label = "__meta_app"
+ action = "replace"
+ regex = "^/*([^/]+?)(?:/.*)?$" // split by the delimiter if it exists, we only want the first one
+ replacement = "$1"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_namespace", "__meta_kubernetes_pod_label_app_kubernetes_io_name", "__meta_app"]
+ separator = "/"
+ target_label = "job"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_container_name"]
+ target_label = "container"
+ }
+ rule {
+ regex = "__meta_kubernetes_pod_label_(statefulset_kubernetes_io_pod_name|controller_revision_hash)"
+ action = "labeldrop"
+ }
+ rule {
+ regex = "pod_template_generation"
+ action = "labeldrop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_phase"]
+ regex = "Pending|Succeeded|Failed|Completed"
+ action = "drop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_node_name"]
+ action = "replace"
+ target_label = "node"
+ }
+ rule {
+ action = "labelmap"
+ regex = "__meta_kubernetes_pod_annotation_prometheus_io_param_(.+)"
+ replacement = "__param_$1"
+ }
+
+ rule {
+ source_labels = ["__meta_kubernetes_pod_uid", "__meta_kubernetes_pod_container_name"]
+ separator = "/"
+ action = "replace"
+ replacement = "/var/log/pods/*$1/*.log"
+ target_label = "__path__"
+ }
+ rule {
+ action = "replace"
+ source_labels = ["__meta_kubernetes_pod_container_id"]
+ regex = "^(\\w+):\\/\\/.+$"
+ replacement = "$1"
+ target_label = "tmp_container_runtime"
+ }
+ }
+
+ discovery.relabel "pod_metrics" {
+ targets = discovery.kubernetes.pods.targets
+
+ rule {
+ source_labels = ["__meta_kubernetes_namespace"]
+ target_label = "namespace"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_name"]
+ target_label = "pod"
+ }
+ // coalesce the following labels and pick the first value; we'll use this to define the "job" label
+ rule {
+ source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_component", "app", "__meta_kubernetes_pod_container_name"]
+ separator = "/"
+ target_label = "__meta_app"
+ action = "replace"
+ regex = "^/*([^/]+?)(?:/.*)?$" // split by the delimiter if it exists, we only want the first one
+ replacement = "$1"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_namespace", "__meta_kubernetes_pod_label_app_kubernetes_io_name", "__meta_app"]
+ separator = "/"
+ target_label = "job"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_container_name"]
+ target_label = "container"
+ }
+ rule {
+ regex = "__meta_kubernetes_pod_label_(statefulset_kubernetes_io_pod_name|controller_revision_hash)"
+ action = "labeldrop"
+ }
+ rule {
+ regex = "pod_template_generation"
+ action = "labeldrop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_phase"]
+ regex = "Pending|Succeeded|Failed|Completed"
+ action = "drop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_node_name"]
+ action = "replace"
+ target_label = "node"
+ }
+ rule {
+ action = "labelmap"
+ regex = "__meta_kubernetes_pod_annotation_prometheus_io_param_(.+)"
+ replacement = "__param_$1"
+ }
+
+ // drop ports that do not expose Prometheus metrics, but might otherwise be exposed by a container which *also*
+ // exposes an HTTP port which exposes metrics
+ rule {
+ source_labels = ["__meta_kubernetes_pod_container_port_name"]
+ regex = "grpc|http-(memberlist|console)"
+ action = "drop"
+ }
+ // adapted from the Prometheus helm chart
+ // https://github.com/prometheus-community/helm-charts/blob/862870fc3c847e32479b509e511584d5283126a3/charts/prometheus/values.yaml#L1070
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_prometheus_io_scrape"]
+ action = "keep"
+ regex = "true"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_prometheus_io_scheme"]
+ action = "replace"
+ regex = "(https?)"
+ target_label = "__scheme__"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_prometheus_io_path"]
+ action = "replace"
+ target_label = "__metrics_path__"
+ regex = "(.+)"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_prometheus_io_port", "__meta_kubernetes_pod_ip"]
+ action = "replace"
+ regex = "(\\d+);(([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})"
+ replacement = "[$2]:$1"
+ target_label = "__address__"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_prometheus_io_port", "__meta_kubernetes_pod_ip"]
+ action = "replace"
+ regex = "(\\d+);((([0-9]+?)(\\.|$)){4})"
+ replacement = "$2:$1"
+ target_label = "__address__"
+ }
+ }
+
+ discovery.relabel "pod_pprof" {
+ targets = discovery.kubernetes.pods.targets
+
+ rule {
+ source_labels = ["__meta_kubernetes_namespace"]
+ target_label = "namespace"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_name"]
+ target_label = "pod"
+ }
+ // coalesce the following labels and pick the first value; we'll use this to define the "job" label
+ rule {
+ source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_component", "app", "__meta_kubernetes_pod_container_name"]
+ separator = "/"
+ target_label = "__meta_app"
+ action = "replace"
+ regex = "^/*([^/]+?)(?:/.*)?$" // split by the delimiter if it exists, we only want the first one
+ replacement = "$1"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_namespace", "__meta_kubernetes_pod_label_app_kubernetes_io_name", "__meta_app"]
+ separator = "/"
+ target_label = "job"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_container_name"]
+ target_label = "container"
+ }
+ rule {
+ regex = "__meta_kubernetes_pod_label_(statefulset_kubernetes_io_pod_name|controller_revision_hash)"
+ action = "labeldrop"
+ }
+ rule {
+ regex = "pod_template_generation"
+ action = "labeldrop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_phase"]
+ regex = "Pending|Succeeded|Failed|Completed"
+ action = "drop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_node_name"]
+ action = "replace"
+ target_label = "node"
+ }
+ rule {
+ action = "labelmap"
+ regex = "__meta_kubernetes_pod_annotation_prometheus_io_param_(.+)"
+ replacement = "__param_$1"
+ }
+
+ // The relabeling allows the actual pod scrape endpoint to be configured via the
+ // following annotations:
+ //
+ // * `pyroscope.io/scrape`: Only scrape pods that have a value of `true`.
+ // * `pyroscope.io/application-name`: Name of the application being profiled.
+ // * `pyroscope.io/scheme`: If the metrics endpoint is secured then you will need
+ // to set this to `https` & most likely set the `tls_config` of the scrape config.
+ // * `pyroscope.io/port`: Scrape the pod on the indicated port.
+ //
+ // Kubernetes labels will be added as Pyroscope labels on metrics via the
+ // `labelmap` relabeling action.
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_pyroscope_io_scrape"]
+ action = "keep"
+ regex = "true"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_pyroscope_io_application_name"]
+ action = "replace"
+ target_label = "__name__"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_pyroscope_io_scheme"]
+ action = "replace"
+ regex = "(https?)"
+ target_label = "__scheme__"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_pyroscope_io_port", "__meta_kubernetes_pod_ip"]
+ action = "replace"
+ regex = "(\\d+);(([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})"
+ replacement = "[$2]:$1"
+ target_label = "__address__"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_pyroscope_io_port", "__meta_kubernetes_pod_ip"]
+ action = "replace"
+ regex = "(\\d+);((([0-9]+?)(\\.|$)){4})"
+ replacement = "$2:$1"
+ target_label = "__address__"
+ }
+ }
+
+ local.file_match "pod_logs" {
+ path_targets = discovery.relabel.pod_logs.output
+ }
+
+ loki.source.file "pod_logs" {
+ targets = local.file_match.pod_logs.targets
+ forward_to = [loki.process.pod_logs.receiver]
+ }
+
+ loki.process "pod_logs" {
+ stage.match {
+ selector = "{tmp_container_runtime=\"containerd\"}"
+ // the cri processing stage extracts the following k/v pairs: log, stream, time, flags
+ stage.cri {}
+ // Set the extract flags and stream values as labels
+ stage.labels {
+ values = {
+ flags = "",
+ stream = "",
+ }
+ }
+ }
+
+ // if the label tmp_container_runtime from above is docker parse using docker
+ stage.match {
+ selector = "{tmp_container_runtime=\"docker\"}"
+ // the docker processing stage extracts the following k/v pairs: log, stream, time
+ stage.docker {}
+
+ // Set the extract stream value as a label
+ stage.labels {
+ values = {
+ stream = "",
+ }
+ }
+ }
+
+ // drop the temporary container runtime label as it is no longer needed
+ stage.label_drop {
+ values = ["tmp_container_runtime"]
+ }
+
+ // parse Coder logs and extract level & logger for efficient filtering
+ stage.match {
+ selector = "{pod=~\"coder.*\"}" // TODO: make configurable
+
+ stage.multiline {
+ firstline = "^(?P\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\.\\d{3})"
+ max_wait_time = "10s"
+ }
+
+ stage.regex {
+ expression = "^(?P\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\.\\d{3})\\s\\[(?P\\w+)\\]\\s\\s(?P[^:]+):\\s(?P.+)"
+ }
+
+ stage.timestamp {
+ source = "ts"
+ format = "2006-01-02 15:04:05.000"
+ action_on_failure = "fudge" // rather have inaccurate time than drop the log line
+ }
+
+ stage.labels {
+ values = {
+ level = "",
+ logger = "",
+ }
+ }
+ }
+
+ forward_to = [loki.write.loki.receiver]
+ }
+
+ loki.write "loki" {
+ endpoint {
+ url = "http://loki-gateway.observability.svc/loki/api/v1/push"
+ }
+ }
+
+
+
+ prometheus.scrape "pods" {
+ targets = discovery.relabel.pod_metrics.output
+ forward_to = [prometheus.relabel.pods.receiver]
+
+ scrape_interval = "30s"
+ scrape_timeout = "12s"
+ enable_protobuf_negotiation = false
+ }
+
+ // These are metric_relabel_configs while discovery.relabel are relabel_configs.
+ // See https://github.com/grafana/agent/blob/main/internal/converter/internal/prometheusconvert/prometheusconvert.go#L95-L106
+ prometheus.relabel "pods" {
+ forward_to = [prometheus.remote_write.default.receiver]
+
+ // Drop kube-state-metrics' labels which clash with ours
+ rule {
+ source_labels = ["__name__", "container"]
+ regex = "kube_pod.+;(.+)"
+ target_label = "container"
+ replacement = ""
+ }
+ rule {
+ source_labels = ["__name__", "pod"]
+ regex = "kube_pod.+;(.+)"
+ target_label = "pod"
+ replacement = ""
+ }
+ rule {
+ source_labels = ["__name__", "namespace"]
+ regex = "kube_pod.+;(.+)"
+ target_label = "namespace"
+ replacement = ""
+ }
+ rule {
+ source_labels = ["__name__", "exported_container"]
+ // don't replace an empty label
+ regex = "^kube_pod.+;(.+)$"
+ target_label = "container"
+ replacement = "$1"
+ }
+ rule {
+ source_labels = ["__name__", "exported_pod"]
+ // don't replace an empty label
+ regex = "^kube_pod.+;(.+)$"
+ target_label = "pod"
+ replacement = "$1"
+ }
+ rule {
+ source_labels = ["__name__", "exported_namespace"]
+ // don't replace an empty label
+ regex = "^kube_pod.+;(.+)$"
+ target_label = "namespace"
+ replacement = "$1"
+ }
+ rule {
+ regex = "^(exported_.*|image_.*|container_id|id|uid)$"
+ action = "labeldrop"
+ }
+ }
+
+ discovery.relabel "cadvisor" {
+ targets = discovery.kubernetes.nodes.targets
+ rule {
+ source_labels = ["__meta_kubernetes_node_name"]
+ regex = env("HOSTNAME")
+ action = "keep"
+ }
+ rule {
+ replacement = "/metrics/cadvisor"
+ target_label = "__metrics_path__"
+ }
+ }
+
+ prometheus.scrape "cadvisor" {
+ targets = discovery.relabel.cadvisor.output
+ forward_to = [ prometheus.relabel.cadvisor.receiver ]
+ scheme = "https"
+ tls_config {
+ insecure_skip_verify = true
+ }
+ bearer_token_file = "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ scrape_interval = "30s"
+ scrape_timeout = "12s"
+ enable_protobuf_negotiation = false
+ }
+
+ prometheus.relabel "cadvisor" {
+ forward_to = [ prometheus.remote_write.default.receiver ]
+
+ // Drop empty container labels, addressing https://github.com/google/cadvisor/issues/2688
+ rule {
+ source_labels = ["__name__","container"]
+ separator = "@"
+ regex = "(container_cpu_.*|container_fs_.*|container_memory_.*)@"
+ action = "drop"
+ }
+ // Drop empty image labels, addressing https://github.com/google/cadvisor/issues/2688
+ rule {
+ source_labels = ["__name__","image"]
+ separator = "@"
+ regex = "(container_cpu_.*|container_fs_.*|container_memory_.*|container_network_.*)@"
+ action = "drop"
+ }
+ // Drop irrelevant series
+ rule {
+ source_labels = ["container"]
+ regex = "^POD$"
+ action = "drop"
+ }
+ // Drop unnecessary labels
+ rule {
+ source_labels = ["id"]
+ target_label = "id"
+ replacement = ""
+ }
+ rule {
+ source_labels = ["job"]
+ target_label = "job"
+ replacement = ""
+ }
+ rule {
+ source_labels = ["name"]
+ target_label = "name"
+ replacement = ""
+ }
+ }
+
+ prometheus.remote_write "default" {
+ wal {
+ truncate_frequency = "30m"
+ max_keepalive_time = "1h"
+ min_keepalive_time = "5m"
+ }
+ endpoint {
+ send_native_histograms = false
+ url = "https://aps-workspaces.us-east-2.amazonaws.com/workspaces/ws-be12ffb7-baf0-4c53-a4cc-4bd651b1643f/api/v1/remote_write"
+
+ sigv4 {
+ region = "us-east-2"
+ }
+
+ // drop instance label which unnecessarily adds new series when pods are restarted, since pod IPs are dynamically assigned
+ // NOTE: "__address__" is mapped to "instance", so will contain :
+ write_relabel_config {
+ regex = "instance"
+ action = "labeldrop"
+ }
+ }
+ }
+ EOT
+ }
+ id = "observability/collector-config"
+ immutable = false
+
+ metadata {
+ annotations = {}
+ generate_name = [90mnull[0m[0m
+ generation = 0
+ labels = {}
+ name = "collector-config"
+ namespace = "observability"
+ resource_version = "28494386"
+ uid = "feed75d5-535c-49e2-ac00-7ccb3768f7df"
+ }
+}
+
+# module.monitoring.kubernetes_config_map_v1.dashboard["coder-dashboard-aibridge"]:
+resource "kubernetes_config_map_v1" "dashboard" {
+ binary_data = {}
+ data = {
+ "aibridge.json" = jsonencode(
+ {
+ __elements = {}
+ __inputs = [
+ {
+ description = ""
+ label = "coder-observability-ro"
+ name = "DS_CODER-OBSERVABILITY-RO"
+ pluginId = "postgres"
+ pluginName = "PostgreSQL"
+ type = "datasource"
+ },
+ ]
+ __requires = [
+ {
+ id = "barchart"
+ name = "Bar chart"
+ type = "panel"
+ version = ""
+ },
+ {
+ id = "grafana"
+ name = "Grafana"
+ type = "grafana"
+ version = "12.1.0"
+ },
+ {
+ id = "postgres"
+ name = "PostgreSQL"
+ type = "datasource"
+ version = "12.1.0"
+ },
+ {
+ id = "stat"
+ name = "Stat"
+ type = "panel"
+ version = ""
+ },
+ {
+ id = "table"
+ name = "Table"
+ type = "panel"
+ version = ""
+ },
+ ]
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = true
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ type = "dashboard"
+ },
+ ]
+ }
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ id = [90mnull[0m[0m
+ links = []
+ panels = [
+ {
+ collapsed = false
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 0
+ }
+ id = 11
+ panels = []
+ title = "Usage leaderboards"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ fillOpacity = 80
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ lineWidth = 1
+ scaleDistribution = {
+ type = "linear"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "output"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "yellow"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Cache Read"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Input"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Cache Write"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "light-red"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 12
+ w = 20
+ x = 0
+ y = 1
+ }
+ id = 1
+ options = {
+ barRadius = 0
+ barWidth = 0.97
+ fullHighlight = false
+ groupWidth = 0.7
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ orientation = "auto"
+ showValue = "auto"
+ stacking = "none"
+ tooltip = {
+ hideZeros = false
+ mode = "single"
+ sort = "none"
+ }
+ xTickLabelRotation = 0
+ xTickLabelSpacing = 0
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ editorMode = "code"
+ format = "table"
+ rawQuery = true
+ rawSql = <<-EOT
+ select u.username, sum(t.input_tokens) as input,
+ sum(t.output_tokens) as output,
+ sum(
+ COALESCE(
+ t.metadata->>'cache_read_input', -- Anthropic
+ t.metadata->>'prompt_cached' -- OpenAI
+ )::int
+ ) AS cache_read_input,
+ sum((t.metadata->>'cache_creation_input')::int) AS cache_creation_input -- Anthropic
+ from aibridge_token_usages t
+ join aibridge_interceptions i on t.interception_id = i.id
+ join users u on i.initiator_id = u.id
+ where $__timeFilter(i.started_at)
+ AND u.username ~ '${username:regex}'
+ AND i.provider ~ '${provider:regex}'
+ AND i.model ~ '${model:regex}'
+ group by u.username
+ order by input desc
+ EOT
+ refId = "A"
+ sql = {
+ columns = [
+ {
+ parameters = []
+ type = "function"
+ },
+ ]
+ groupBy = [
+ {
+ property = {
+ type = "string"
+ }
+ type = "groupBy"
+ },
+ ]
+ limit = 50
+ }
+ },
+ ]
+ title = "Leaderboard per user"
+ transformations = [
+ {
+ id = "organize"
+ options = {
+ excludeByName = {}
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ cache_creation_input = "Cache Write"
+ cache_read_input = "Cache Read"
+ input = "Input"
+ output = "Output"
+ username = ""
+ }
+ }
+ },
+ ]
+ type = "barchart"
+ },
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = 0
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 12
+ w = 4
+ x = 20
+ y = 1
+ }
+ id = 3
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ editorMode = "code"
+ format = "table"
+ rawQuery = true
+ rawSql = <<-EOT
+ select sum(t.input_tokens) as input,
+ sum(t.output_tokens) as output,
+ sum(
+ COALESCE(
+ t.metadata->>'cache_read_input', -- Anthropic
+ t.metadata->>'prompt_cached' -- OpenAI
+ )::int
+ ) AS cache_read_input,
+ sum((t.metadata->>'cache_creation_input')::int) AS cache_creation_input -- Anthropic
+ from aibridge_token_usages t
+ join aibridge_interceptions i on t.interception_id = i.id
+ join users u on i.initiator_id = u.id
+ where $__timeFilter(i.started_at)
+ AND u.username ~ '${username:regex}'
+ AND i.provider ~ '${provider:regex}'
+ AND i.model ~ '${model:regex}'
+ order by input desc
+ EOT
+ refId = "A"
+ sql = {
+ columns = [
+ {
+ parameters = []
+ type = "function"
+ },
+ ]
+ groupBy = [
+ {
+ property = {
+ type = "string"
+ }
+ type = "groupBy"
+ },
+ ]
+ limit = 50
+ }
+ },
+ ]
+ title = "Total usage for $username"
+ transformations = [
+ {
+ id = "organize"
+ options = {
+ excludeByName = {}
+ includeByName = {}
+ indexByName = {
+ cache_creation_input = 3
+ cache_read_input = 2
+ input = 0
+ output = 1
+ }
+ renameByName = {
+ cache_creation_input = "Cache Write"
+ cache_read_input = "Cache Read"
+ input = "Input"
+ output = "Output"
+ }
+ }
+ },
+ ]
+ type = "stat"
+ },
+ {
+ collapsed = false
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 22
+ }
+ id = 10
+ panels = []
+ title = "Interceptions"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ fillOpacity = 80
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ lineWidth = 1
+ scaleDistribution = {
+ type = "linear"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ fieldMinMax = false
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "output"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 12
+ w = 20
+ x = 0
+ y = 23
+ }
+ id = 4
+ maxDataPoints = 30
+ options = {
+ barRadius = 0
+ barWidth = 0.97
+ fullHighlight = false
+ groupWidth = 0.7
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ orientation = "auto"
+ showValue = "auto"
+ stacking = "normal"
+ text = {
+ valueSize = 10
+ }
+ tooltip = {
+ hideZeros = false
+ mode = "single"
+ sort = "none"
+ }
+ xTickLabelRotation = -45
+ xTickLabelSpacing = 0
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ editorMode = "code"
+ format = "time_series"
+ rawQuery = true
+ rawSql = <<-EOT
+ SELECT
+ $__timeGroupAlias(i.started_at, $__interval, NULL),
+ count(i.id) AS value,
+ u.username AS metric
+ FROM aibridge_interceptions i
+ join users u ON i.initiator_id = u.id
+ WHERE
+ $__timeFilter(i.started_at)
+ AND u.username ~ '${username:regex}'
+ AND i.provider ~ '${provider:regex}'
+ AND i.model ~ '${model:regex}'
+ GROUP BY u.username, $__timeGroup(i.started_at, $__interval)
+ ORDER BY $__timeGroup(i.started_at, $__interval)
+ EOT
+ refId = "A"
+ sql = {
+ columns = [
+ {
+ name = "COUNT"
+ parameters = [
+ {
+ name = "id"
+ type = "functionParameter"
+ },
+ ]
+ type = "function"
+ },
+ ]
+ groupBy = [
+ {
+ property = {
+ name = "started_at"
+ type = "string"
+ }
+ type = "groupBy"
+ },
+ ]
+ limit = 50
+ }
+ table = "aibridge_interceptions"
+ },
+ ]
+ title = "Interceptions over time by user"
+ type = "barchart"
+ },
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "output"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 12
+ w = 4
+ x = 20
+ y = 23
+ }
+ id = 5
+ interval = "1m"
+ maxDataPoints = 500
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ editorMode = "code"
+ format = "table"
+ rawQuery = true
+ rawSql = <<-EOT
+ select count(*) from aibridge_interceptions
+ WHERE started_at > $__timeFrom() AND started_at <= $__timeTo()
+ AND provider ~ '${provider:regex}'
+ AND model ~ '${model:regex}'
+ EOT
+ refId = "A"
+ sql = {
+ columns = [
+ {
+ name = "COUNT"
+ parameters = [
+ {
+ name = "id"
+ type = "functionParameter"
+ },
+ ]
+ type = "function"
+ },
+ ]
+ groupBy = [
+ {
+ property = {
+ name = "started_at"
+ type = "string"
+ }
+ type = "groupBy"
+ },
+ ]
+ limit = 50
+ }
+ table = "aibridge_interceptions"
+ },
+ ]
+ title = "Total interceptions"
+ type = "stat"
+ },
+ {
+ collapsed = false
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 35
+ }
+ id = 9
+ panels = []
+ title = "Prompts & tool calls details"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ wrapText = false
+ }
+ inspect = true
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Interception ID"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 357
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Model"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 240
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Provider"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 157
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Username"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 188
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 14
+ w = 24
+ x = 0
+ y = 36
+ }
+ id = 7
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ fields = ""
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ showHeader = true
+ sortBy = [
+ {
+ desc = true
+ displayName = "Created At"
+ },
+ ]
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ editorMode = "code"
+ format = "table"
+ rawQuery = true
+ rawSql = <<-EOT
+ SELECT i.id,
+ u.username,
+ i.provider,
+ i.model,
+ p.prompt,
+ p.created_at
+ FROM aibridge_user_prompts p
+ JOIN aibridge_interceptions i ON p.interception_id = i.id
+ JOIN users u ON i.initiator_id = u.id
+ WHERE $__timeFilter(i.started_at)
+ AND u.username ~ '${username:regex}'
+ AND i.provider ~ '${provider:regex}'
+ AND i.model ~ '${model:regex}'
+ ORDER BY p.created_at DESC;
+ EOT
+ refId = "A"
+ sql = {
+ columns = [
+ {
+ parameters = []
+ type = "function"
+ },
+ ]
+ groupBy = [
+ {
+ property = {
+ type = "string"
+ }
+ type = "groupBy"
+ },
+ ]
+ limit = 50
+ }
+ },
+ ]
+ title = "User Prompts"
+ transformations = [
+ {
+ id = "organize"
+ options = {
+ excludeByName = {}
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ created_at = "Created At"
+ id = "Interception ID"
+ input = "Tool Input"
+ invocation_error = "Tool Error"
+ model = "Model"
+ prompt = "Prompt"
+ provider = "Provider"
+ server_url = "MCP Server"
+ tool = "Tool Name"
+ username = "Username"
+ }
+ }
+ },
+ ]
+ type = "table"
+ },
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ wrapText = false
+ }
+ inspect = true
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Tool Name"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 342
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "invocation_error"
+ }
+ properties = [
+ {
+ id = "custom.cellOptions"
+ value = {
+ applyToRow = true
+ type = "color-background"
+ wrapText = false
+ }
+ },
+ {
+ id = "noValue"
+ },
+ {
+ id = "mappings"
+ value = [
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "green"
+ index = 0
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ pattern = ".+"
+ result = {
+ color = "red"
+ index = 1
+ }
+ }
+ type = "regex"
+ },
+ ]
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Tool Input"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 309
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Interception ID"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 357
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Model"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 240
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 14
+ w = 24
+ x = 0
+ y = 50
+ }
+ id = 6
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ fields = ""
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ showHeader = true
+ sortBy = [
+ {
+ desc = true
+ displayName = "Created At"
+ },
+ ]
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ editorMode = "code"
+ format = "table"
+ rawQuery = true
+ rawSql = <<-EOT
+ select i.id, u.username, i.provider, i.model, t.server_url, t.tool, t.input, t.invocation_error, t.created_at FROM aibridge_tool_usages t
+ join aibridge_interceptions i ON t.interception_id = i.id
+ join users u on i.initiator_id = u.id
+ where $__timeFilter(i.started_at)
+ AND u.username ~ '${username:regex}'
+ AND i.provider ~ '${provider:regex}'
+ AND i.model ~ '${model:regex}'
+ order by t.created_at desc
+ EOT
+ refId = "A"
+ sql = {
+ columns = [
+ {
+ parameters = []
+ type = "function"
+ },
+ ]
+ groupBy = [
+ {
+ property = {
+ type = "string"
+ }
+ type = "groupBy"
+ },
+ ]
+ limit = 50
+ }
+ },
+ ]
+ title = "Tool Calls"
+ transformations = [
+ {
+ id = "organize"
+ options = {
+ excludeByName = {}
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ created_at = "Created At"
+ id = "Interception ID"
+ input = "Tool Input"
+ invocation_error = "Tool Error"
+ model = "Model"
+ provider = "Provider"
+ server_url = "MCP Server"
+ tool = "Tool Name"
+ username = "Username"
+ }
+ }
+ },
+ ]
+ type = "table"
+ },
+ ]
+ refresh = "1m"
+ schemaVersion = 41
+ tags = []
+ templating = {
+ list = [
+ {
+ allValue = ".+"
+ current = {}
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ definition = "select username from users where deleted=false;"
+ description = ""
+ includeAll = true
+ multi = true
+ name = "username"
+ options = []
+ query = "select username from users where deleted=false;"
+ refresh = 1
+ regex = ""
+ sort = 1
+ type = "query"
+ },
+ {
+ allValue = ".+"
+ current = {}
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ definition = "SELECT DISTINCT provider FROM aibridge_interceptions WHERE provider IS NOT NULL ORDER BY 1;"
+ description = ""
+ includeAll = true
+ multi = true
+ name = "provider"
+ options = []
+ query = "SELECT DISTINCT provider FROM aibridge_interceptions WHERE provider IS NOT NULL ORDER BY 1;"
+ refresh = 1
+ regex = ""
+ sort = 1
+ type = "query"
+ },
+ {
+ allValue = ".+"
+ current = {}
+ datasource = {
+ type = "postgres"
+ uid = "postgres"
+ }
+ definition = "SELECT DISTINCT model FROM aibridge_interceptions WHERE model IS NOT NULL AND provider ~ '${provider:regex}' ORDER BY 1;"
+ description = ""
+ includeAll = true
+ multi = true
+ name = "model"
+ options = []
+ query = "SELECT DISTINCT model FROM aibridge_interceptions WHERE model IS NOT NULL AND provider ~ '${provider:regex}' ORDER BY 1;"
+ refresh = 1
+ regex = ""
+ sort = 1
+ type = "query"
+ },
+ ]
+ }
+ time = {
+ from = "now-7d"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder AI Bridge"
+ uid = "0c61d33f-c809-4184-9e88-cb27e2d9d224"
+ version = 43
+ weekStart = ""
+ }
+ )
+ }
+ id = "observability/coder-dashboard-aibridge"
+ immutable = false
+
+ metadata {
+ annotations = {}
+ generate_name = [90mnull[0m[0m
+ generation = 0
+ labels = {}
+ name = "coder-dashboard-aibridge"
+ namespace = "observability"
+ resource_version = "2025175"
+ uid = "9d542e42-0b1e-4383-8645-66a159e399f2"
+ }
+}
+
+# module.monitoring.kubernetes_config_map_v1.dashboard["coder-dashboard-boundary"]:
+resource "kubernetes_config_map_v1" "dashboard" {
+ binary_data = {}
+ data = {
+ "boundary.json" = jsonencode(
+ {
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = true
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ target = {
+ limit = 100
+ matchAny = false
+ tags = []
+ type = "dashboard"
+ }
+ type = "dashboard"
+ },
+ ]
+ }
+ description = <<-EOT
+ This dashboard shows HTTP requests audited by agent boundaries within Coder workspaces to provide visibility into workspace network activity.
+
+ What it shows:
+ - Total count of allowed and denied outbound HTTP requests
+ - Top 20 most frequently accessed allowed domains
+ - Top 20 most frequently blocked domains
+ - Recent allowed requests with details (time, domain, method, path, workspace owner, workspace name, template ID)
+ - Recent denied requests with the same details
+
+ Who it's for:
+ - Platform administrators and template administrators who need to audit workspace network activity and define agent boundary policies
+ - Security team members who want to know what HTTP requests AI agents made in Coder workspaces for security incident review
+ - Agent Boundaries policy owners who want to refine network access controls/security posture
+
+ Filters available:
+ - Template ID
+ - HTTP request domain
+ - Workspace owner
+ EOT
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ links = []
+ panels = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ description = "Total number of requests allowed/denied"
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "{decision=\"allow\"}"
+ }
+ properties = [
+ {
+ id = "displayName"
+ value = "Allowed"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "{decision=\"deny\"}"
+ }
+ properties = [
+ {
+ id = "displayName"
+ value = "Denied"
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 24
+ x = 0
+ y = 0
+ }
+ id = 5
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ direction = "backward"
+ editorMode = "code"
+ expr = "sum by (decision) (count_over_time({ namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=~`deny|allow` | owner=~`$owner` | domain=~`$domain` | template_id=~`$template_id` | template_version_id=~`$template_version_id` [$__range]))"
+ queryType = "range"
+ refId = "A"
+ },
+ ]
+ title = "Request Totals"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ description = "Top 20 allowed domains for HTTP requests"
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ }
+ filterable = false
+ inspect = false
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Domain"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 340
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 12
+ w = 12
+ x = 0
+ y = 7
+ }
+ id = 1
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ enablePagination = false
+ fields = ""
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ frameIndex = 1
+ showHeader = true
+ sortBy = [
+ {
+ desc = true
+ displayName = "Count"
+ },
+ ]
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ direction = "backward"
+ editorMode = "code"
+ expr = "topk(20, sum by (domain) (count_over_time({ namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=`allow` | owner=~`$owner` | template_id=~`$template_id` | template_version_id=~`$template_version_id` | regexp `http_url=(?Phttps?)://(?P[^/:]+)` | domain=~`$domain` [$__auto])))"
+ legendFormat = ""
+ queryType = "instant"
+ refId = "A"
+ },
+ ]
+ title = "Top Allowed Domains"
+ transformations = [
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = true
+ }
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ Time = ""
+ "Value #A" = "Count"
+ domain = "Domain"
+ }
+ }
+ },
+ {
+ id = "sortBy"
+ options = {
+ fields = {}
+ sort = [
+ {
+ desc = true
+ field = "Count"
+ },
+ ]
+ }
+ },
+ ]
+ type = "table"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ description = "Top 20 denied domains for HTTP requests"
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ }
+ filterable = false
+ inspect = false
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Domain"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 382
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 12
+ w = 12
+ x = 12
+ y = 7
+ }
+ id = 2
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ enablePagination = false
+ fields = ""
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ frameIndex = 1
+ showHeader = true
+ sortBy = [
+ {
+ desc = true
+ displayName = "Count"
+ },
+ ]
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ direction = "backward"
+ editorMode = "code"
+ expr = "topk(20, sum by (domain) (count_over_time({ namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=`deny` | owner=~`$owner` | template_id=~`$template_id` | template_version_id=~`$template_version_id` | regexp `http_url=(?Phttps?)://(?P[^/:]+)` | domain=~`$domain` [$__auto])))"
+ legendFormat = ""
+ queryType = "instant"
+ refId = "A"
+ },
+ ]
+ title = "Top Denied Domains"
+ transformations = [
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = true
+ }
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ Time = ""
+ "Value #A" = "Count"
+ domain = "Domain"
+ }
+ }
+ },
+ {
+ id = "sortBy"
+ options = {
+ fields = {}
+ sort = [
+ {
+ desc = true
+ field = "Count"
+ },
+ ]
+ }
+ },
+ ]
+ type = "table"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ }
+ inspect = false
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Time"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 282
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Domain"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 185
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "method"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 73
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "path"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 397
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Workspace Owner"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 144
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Template ID"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 195
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Workspace Name"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 204
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 12
+ w = 24
+ x = 0
+ y = 19
+ }
+ id = 3
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ fields = ""
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ showHeader = true
+ sortBy = []
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ direction = "backward"
+ editorMode = "code"
+ expr = "{ namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=`allow` | owner=~`$owner` | template_id=~`$template_id` | template_version_id=~`$template_version_id` | regexp `http_url=https?://(?P[^/?# ]+)(?P/[^?# ]*)?` | domain=~`$domain` | line_format `time=\"{{.event_time}}\" method=\"{{.http_method}}\" domain=\"{{.domain}}\" path=\"{{.path}}\" owner=\"{{.owner}}\" workspace_name=\"{{.workspace_name}}\" template_id=\"{{.template_id}}\" template_version_id=\"{{.template_version_id}}\"`"
+ queryType = "range"
+ refId = "A"
+ },
+ ]
+ title = "Most recent allowed requests"
+ transformations = [
+ {
+ id = "extractFields"
+ options = {
+ delimiter = "|"
+ format = "kvp"
+ keepTime = false
+ replace = true
+ source = "Line"
+ }
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {}
+ includeByName = {
+ domain = true
+ method = true
+ owner = true
+ path = true
+ template_id = true
+ template_version_id = true
+ time = true
+ workspace_name = true
+ }
+ indexByName = {
+ domain = 2
+ method = 1
+ owner = 4
+ path = 3
+ template_id = 6
+ time = 0
+ workspace_name = 5
+ }
+ renameByName = {
+ domain = "Domain"
+ method = "Method"
+ owner = "Workspace Owner"
+ path = "Path"
+ template_id = "Template ID"
+ template_version_id = "Template Version ID"
+ time = "Time"
+ workspace_name = "Workspace Name"
+ }
+ }
+ },
+ {
+ id = "sortBy"
+ options = {
+ fields = {}
+ sort = [
+ {
+ desc = true
+ field = "Time"
+ },
+ ]
+ }
+ },
+ {
+ id = "limit"
+ options = {
+ limitField = "10"
+ }
+ },
+ ]
+ type = "table"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ }
+ inspect = false
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Time"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 282
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Domain"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 185
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "method"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 73
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "path"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 397
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Workspace Owner"
+ }
+ properties = [
+ {
+ id = "custom.width"
+ value = 152
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 12
+ w = 24
+ x = 0
+ y = 31
+ }
+ id = 6
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ fields = ""
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ showHeader = true
+ sortBy = []
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ direction = "backward"
+ editorMode = "code"
+ expr = "{ namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=`coderd.agentrpc`} |= `boundary_request` | logfmt | decision=`deny` | owner=~`$owner` | template_id=~`$template_id` | template_version_id=~`$template_version_id` | regexp `http_url=https?://(?P[^/?# ]+)(?P/[^?# ]*)?` | domain=~`$domain` | line_format `time=\"{{.event_time}}\" method=\"{{.http_method}}\" domain=\"{{.domain}}\" path=\"{{.path}}\" owner=\"{{.owner}}\" workspace_name=\"{{.workspace_name}}\" template_id=\"{{.template_id}}\" template_version_id=\"{{.template_version_id}}\"`"
+ queryType = "range"
+ refId = "A"
+ },
+ ]
+ title = "Most recent denied requests"
+ transformations = [
+ {
+ id = "extractFields"
+ options = {
+ delimiter = "|"
+ format = "kvp"
+ keepTime = false
+ replace = true
+ source = "Line"
+ }
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {}
+ includeByName = {
+ domain = true
+ method = true
+ owner = true
+ path = true
+ template_id = true
+ template_version_id = true
+ time = true
+ workspace_name = true
+ }
+ indexByName = {
+ domain = 2
+ method = 1
+ owner = 4
+ path = 3
+ template_id = 6
+ time = 0
+ workspace_name = 5
+ }
+ renameByName = {
+ domain = "Domain"
+ method = "Method"
+ owner = "Workspace Owner"
+ path = "Path"
+ template_id = "Template ID"
+ template_version_id = "Template Version ID"
+ time = "Time"
+ workspace_name = "Workspace Name"
+ }
+ }
+ },
+ {
+ id = "sortBy"
+ options = {
+ fields = {}
+ sort = [
+ {
+ desc = true
+ field = "Time"
+ },
+ ]
+ }
+ },
+ {
+ id = "limit"
+ options = {
+ limitField = "10"
+ }
+ },
+ ]
+ type = "table"
+ },
+ ]
+ preload = false
+ refresh = "30s"
+ schemaVersion = 41
+ tags = []
+ templating = {
+ list = [
+ {
+ current = {
+ text = ""
+ value = ""
+ }
+ description = "Filter by request domain"
+ label = "Domain"
+ name = "domain"
+ options = [
+ {
+ selected = true
+ text = ""
+ value = ""
+ },
+ ]
+ query = ""
+ type = "textbox"
+ },
+ {
+ current = {
+ text = ""
+ value = ""
+ }
+ description = "Filter requests by workspace owner"
+ label = "Workspace Owner"
+ name = "owner"
+ options = [
+ {
+ selected = true
+ text = ""
+ value = ""
+ },
+ ]
+ query = ""
+ type = "textbox"
+ },
+ {
+ current = {
+ text = ""
+ value = ""
+ }
+ description = "Filter requests by template ID (UUID). Template IDs can be found via the CLI with \"coder templates list\"."
+ label = "Template ID"
+ name = "template_id"
+ options = [
+ {
+ selected = true
+ text = ""
+ value = ""
+ },
+ ]
+ query = ""
+ type = "textbox"
+ },
+ {
+ current = {
+ text = ""
+ value = ""
+ }
+ description = "Filter by template version ID (UUID). A templates version IDs can be found via the CLI with \"coder templates versions list \"."
+ label = "Template Version ID"
+ name = "template_version_id"
+ options = [
+ {
+ selected = true
+ text = ""
+ value = ""
+ },
+ ]
+ query = ""
+ type = "textbox"
+ },
+ ]
+ }
+ time = {
+ from = "now-12h"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder Agent Boundaries"
+ uid = "agent-boundaries"
+ version = 1
+ weekStart = ""
+ }
+ )
+ }
+ id = "observability/coder-dashboard-boundary"
+ immutable = false
+
+ metadata {
+ annotations = {}
+ generate_name = [90mnull[0m[0m
+ generation = 0
+ labels = {}
+ name = "coder-dashboard-boundary"
+ namespace = "observability"
+ resource_version = "2024557"
+ uid = "55aeca39-51df-4ed9-b2d9-57681b51d6dc"
+ }
+}
+
+# module.monitoring.kubernetes_config_map_v1.dashboard["coder-dashboard-coderd"]:
+resource "kubernetes_config_map_v1" "dashboard" {
+ binary_data = {}
+ data = {
+ "coderd.json" = jsonencode(
+ {
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = true
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ target = {
+ limit = 100
+ matchAny = false
+ tags = []
+ type = "dashboard"
+ }
+ type = "dashboard"
+ },
+ ]
+ }
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ links = []
+ panels = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Down"
+ }
+ properties = [
+ {
+ id = "thresholds"
+ value = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 1
+ },
+ ]
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 6
+ x = 0
+ y = 0
+ }
+ id = 10
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "count(up{ pod=~`coder.*`, namespace=~`coder` } == 1) or vector(0)"
+ instant = true
+ legendFormat = "Up"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "(count(up{ pod=~`coder.*`, namespace=~`coder` } == 0) or vector(0)) > 0"
+ hide = false
+ instant = true
+ legendFormat = "Down"
+ range = false
+ refId = "B"
+ },
+ ]
+ title = "Replicas"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 6
+ x = 6
+ y = 0
+ }
+ id = 18
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ One or more replicas are required to be running in order to serve the control-plane.
+
+ See [High Availability](https://coder.com/docs/v2/latest/admin/high-availability) for details on how to
+ run multiple `coderd` replicas.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "#EAB839"
+ value = 0.9
+ },
+ {
+ color = "red"
+ value = 1
+ },
+ ]
+ }
+ unit = "percentunit"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Enabled"
+ }
+ properties = [
+ {
+ id = "mappings"
+ value = [
+ {
+ options = {
+ "0" = {
+ index = 1
+ text = "No"
+ }
+ "1" = {
+ index = 0
+ text = "Yes"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ },
+ {
+ id = "thresholds"
+ value = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 6
+ x = 12
+ y = 0
+ }
+ id = 32
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "last",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_license_user_limit_enabled)"
+ instant = true
+ legendFormat = "Enabled"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ (
+ max(coderd_license_active_users) / max(coderd_license_limit_users)
+ ) > 0
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "Usage"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "Enterprise License"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 6
+ x = 18
+ y = 0
+ }
+ id = 33
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = "If you would like to try Coder's [Enterprise features](https://coder.com/docs/v2/latest/enterprise), you can [request a trial license](https://coder.com/docs/v2/latest/faqs#how-do-i-add-an-enterprise-license)."
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "never"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byRegexp"
+ options = "/(Requested|Limit)/"
+ }
+ properties = [
+ {
+ id = "custom.lineStyle"
+ value = {
+ dash = [
+ 0,
+ 10,
+ ]
+ fill = "dot"
+ }
+ },
+ {
+ id = "custom.fillOpacity"
+ value = 5
+ },
+ {
+ id = "custom.drawStyle"
+ value = "line"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Requested"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Limit"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 6
+ x = 0
+ y = 6
+ }
+ id = 25
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum by (pod) (rate(container_cpu_usage_seconds_total{ pod=~`coder.*`, namespace=~`coder` }[$__rate_interval]))"
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max(kube_pod_container_resource_limits{ pod=~`coder.*`, namespace=~`coder`, resource=\"cpu\"})"
+ hide = false
+ instant = false
+ legendFormat = "Limit"
+ range = true
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max(kube_pod_container_resource_requests{ pod=~`coder.*`, namespace=~`coder`, resource=\"cpu\"})"
+ hide = false
+ instant = false
+ legendFormat = "Requested"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "CPU Usage Seconds"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 6
+ x = 6
+ y = 6
+ }
+ id = 26
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ The cumulative CPU used per core-second. If `coderd` was using a full CPU core, that would be represented as 1 second.
+
+ Requests & limits are shown if set.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ fixedColor = "red"
+ mode = "shades"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ decimals = 0
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Requested"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Limit"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "red"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 4
+ x = 12
+ y = 6
+ }
+ id = 30
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ sum by (reason) (
+ count_over_time(kube_pod_container_status_terminated_reason{ pod=~`coder.*`, namespace=~`coder` }[$__interval])
+ )
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "{{reason}}"
+ range = true
+ refId = "C"
+ },
+ ]
+ title = "Terminations"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ decimals = 0
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 0.0001
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 6
+ w = 2
+ x = 16
+ y = 6
+ }
+ id = 34
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "mean",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(increase(kube_pod_container_status_restarts_total{ pod=~`coder.*`, namespace=~`coder` }[$__range]))"
+ hide = false
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "B"
+ },
+ ]
+ title = "Restarts"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 6
+ x = 18
+ y = 6
+ }
+ id = 31
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ Pods can be terminated for several reasons:
+ - `OOMKilled`: pod exceeded its defined memory limit or was terminated by the OS for using excessive memory (if no limit defined)
+ - `Error`: usually attributeable to a configuration problem
+ - `Evicted`: pod has been evicted from node for overusing resources and will be rescheduled on another node is possible
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "never"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ unit = "bytes"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byRegexp"
+ options = "/(Requested|Limit)/"
+ }
+ properties = [
+ {
+ id = "custom.lineStyle"
+ value = {
+ dash = [
+ 0,
+ 10,
+ ]
+ fill = "dot"
+ }
+ },
+ {
+ id = "custom.fillOpacity"
+ value = 5
+ },
+ {
+ id = "custom.drawStyle"
+ value = "line"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Requested"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Limit"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 6
+ x = 0
+ y = 12
+ }
+ id = 29
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max by (pod) (container_memory_working_set_bytes{ pod=~`coder.*`, namespace=~`coder` })"
+ hide = false
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max(kube_pod_container_resource_limits{ pod=~`coder.*`, namespace=~`coder`, resource=\"memory\"})"
+ hide = false
+ instant = false
+ legendFormat = "Limit"
+ range = true
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max(kube_pod_container_resource_requests{ pod=~`coder.*`, namespace=~`coder`, resource=\"memory\"})"
+ hide = false
+ instant = false
+ legendFormat = "Requested"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "RAM Usage"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 6
+ x = 6
+ y = 12
+ }
+ id = 28
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ This shows the total memory used by each `coderd` container; it is the same metric which the [OOM killer](https://www.kernel.org/doc/gorman/html/understand/understand016.html) uses.
+
+ Requests & limits are shown if set.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "orange"
+ value = 100
+ },
+ {
+ color = "red"
+ value = 500
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Errors"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "short"
+ },
+ {
+ id = "thresholds"
+ value = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 1
+ },
+ ]
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 3
+ w = 4
+ x = 12
+ y = 12
+ }
+ id = 16
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "mean",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "quantile(0.5, coder_pubsub_send_latency_seconds)"
+ instant = false
+ legendFormat = "Send"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "quantile(0.5, coder_pubsub_receive_latency_seconds)"
+ hide = false
+ instant = false
+ legendFormat = "Receive"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "Pubsub Latency (Median)"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Errors"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "short"
+ },
+ {
+ id = "thresholds"
+ value = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 1
+ },
+ ]
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 2
+ x = 16
+ y = 12
+ }
+ id = 22
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "mean",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ (
+ sum(increase(coder_pubsub_latency_measure_errs_total[$__range]))
+ / count(coder_pubsub_latency_measure_errs_total)
+ ) or vector(0)
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "Errors"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "Pubsub Errors"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 6
+ x = 18
+ y = 12
+ }
+ id = 19
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ `coderd` uses Postgres for passing messages between subcomponents for coordination and signalling;
+ this is called "pubsub" (or publish-subscribe).
+
+ We measure the time for messages to be sent and received. Latencies higher than 500ms will likely lead to
+ your Coder deployment feeling sluggish. High latency is usually an indication that your Postgres server is under-resourced on CPU.
+
+ High values for median should be concerning,
+ while the 90th percentile shows the outliers.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "orange"
+ value = 100
+ },
+ {
+ color = "red"
+ value = 500
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Errors"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "short"
+ },
+ {
+ id = "thresholds"
+ value = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 1
+ },
+ ]
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 3
+ w = 4
+ x = 12
+ y = 15
+ }
+ id = 21
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "mean",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "quantile(0.9, coder_pubsub_send_latency_seconds)"
+ instant = false
+ legendFormat = "Send"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "quantile(0.9, coder_pubsub_receive_latency_seconds)"
+ hide = false
+ instant = false
+ legendFormat = "Receive"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "Pubsub Latency (P90)"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 0
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "never"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ unit = "reqps"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 6
+ w = 6
+ x = 0
+ y = 18
+ }
+ id = 35
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum by(pod) (rate(coderd_api_requests_processed_total{ pod=~`coder.*`, namespace=~`coder` }[$__rate_interval]))"
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ ]
+ title = "API Requests"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 6
+ x = 6
+ y = 18
+ }
+ id = 36
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ This shows the number of requests per second each `coderd` replica is handling.
+
+ Heavy skewing towards a single `coderd` replica indicates faulty loadbalancing.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ ]
+ refresh = "30s"
+ schemaVersion = 39
+ tags = []
+ templating = {
+ list = []
+ }
+ time = {
+ from = "now-12h"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder Control Plane"
+ uid = "coderd"
+ version = 6
+ weekStart = ""
+ }
+ )
+ }
+ id = "observability/coder-dashboard-coderd"
+ immutable = false
+
+ metadata {
+ annotations = {}
+ generate_name = [90mnull[0m[0m
+ generation = 0
+ labels = {}
+ name = "coder-dashboard-coderd"
+ namespace = "observability"
+ resource_version = "1952612"
+ uid = "cb8aa90b-a3f2-4591-a2c3-c1663d8e10ea"
+ }
+}
+
+# module.monitoring.kubernetes_config_map_v1.dashboard["coder-dashboard-prebuilds"]:
+resource "kubernetes_config_map_v1" "dashboard" {
+ binary_data = {}
+ data = {
+ "prebuilds.json" = jsonencode(
+ {
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = true
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ type = "dashboard"
+ },
+ ]
+ }
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ id = 132
+ links = []
+ panels = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ fixedColor = "text"
+ mode = "fixed"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 4
+ w = 4
+ x = 0
+ y = 0
+ }
+ id = 49
+ interval = "30s"
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "vertical"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(max(coderd_prebuilt_workspaces_desired) by (template_name, preset_name)) or vector(0)"
+ instant = true
+ interval = ""
+ legendFormat = "Desired"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(max(coderd_prebuilt_workspaces_running) by (template_name, preset_name)) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Running"
+ range = false
+ refId = "D"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(max(coderd_prebuilt_workspaces_eligible) by (template_name, preset_name)) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Eligible"
+ range = false
+ refId = "E"
+ },
+ ]
+ title = "Current: Global"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ fixedColor = "text"
+ mode = "fixed"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 4
+ w = 4
+ x = 4
+ y = 0
+ }
+ id = 48
+ interval = "30s"
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "vertical"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(max by (template_name, preset_name) (coderd_prebuilt_workspaces_created_total)) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Created"
+ range = false
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(max by (template_name, preset_name) (coderd_prebuilt_workspaces_failed_total)) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Failed"
+ range = false
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(max by (template_name, preset_name) (coderd_prebuilt_workspaces_claimed_total)) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Claimed"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "All Time: Global"
+ type = "stat"
+ },
+ {
+ collapsed = false
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 4
+ }
+ id = 2
+ panels = []
+ repeat = "preset"
+ title = "$preset"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ fixedColor = "text"
+ mode = "fixed"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 3
+ w = 6
+ x = 0
+ y = 5
+ }
+ id = 1
+ interval = "30s"
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "vertical"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_prebuilt_workspaces_created_total{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Created"
+ range = false
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_prebuilt_workspaces_failed_total{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Failed"
+ range = false
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_prebuilt_workspaces_claimed_total{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Claimed"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "All Time: $preset"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ axisSoftMax = 10
+ axisSoftMin = 0
+ barAlignment = 0
+ barWidthFactor = 0.6
+ drawStyle = "line"
+ fillOpacity = 13
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "smooth"
+ lineStyle = {
+ fill = "solid"
+ }
+ lineWidth = 2
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "never"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ decimals = 0
+ fieldMinMax = false
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Failed"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "red"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Created"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "blue"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Desired"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "purple"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Running"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "yellow"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Eligible"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Claimed"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "dark-green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 9
+ x = 6
+ y = 5
+ }
+ id = 38
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ hideZeros = false
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "floor(max(increase(coderd_prebuilt_workspaces_created_total{template_name=~\"$template\", preset_name=~\"$preset\"}[$__rate_interval]))) or vector(0)"
+ hide = false
+ instant = false
+ interval = ""
+ legendFormat = "Created"
+ range = true
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "floor(max(increase(coderd_prebuilt_workspaces_failed_total{template_name=~\"$template\", preset_name=~\"$preset\"}[$__rate_interval]))) or vector(0)"
+ hide = false
+ instant = false
+ interval = ""
+ legendFormat = "Failed"
+ range = true
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "floor(max(increase(coderd_prebuilt_workspaces_claimed_total{template_name=~\"$template\", preset_name=~\"$preset\"}[$__rate_interval]))) or vector(0)"
+ hide = false
+ instant = false
+ interval = ""
+ legendFormat = "Claimed"
+ range = true
+ refId = "F"
+ },
+ ]
+ title = "Pool Operations: $preset"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ axisSoftMax = 10
+ axisSoftMin = 0
+ barAlignment = 0
+ barWidthFactor = 0.6
+ drawStyle = "line"
+ fillOpacity = 18
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "smooth"
+ lineStyle = {
+ fill = "solid"
+ }
+ lineWidth = 2
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "never"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ decimals = 0
+ fieldMinMax = false
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Desired"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "purple"
+ mode = "fixed"
+ }
+ },
+ {
+ id = "custom.lineStyle"
+ value = {
+ dash = [
+ 10,
+ 10,
+ ]
+ fill = "dash"
+ }
+ },
+ {
+ id = "custom.fillOpacity"
+ value = 85
+ },
+ {
+ id = "custom.fillBelowTo"
+ value = "Running"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Running"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "yellow"
+ mode = "fixed"
+ }
+ },
+ {
+ id = "custom.fillBelowTo"
+ value = "Eligible"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Eligible"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 9
+ x = 15
+ y = 5
+ }
+ id = 5
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ hideZeros = false
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max(coderd_prebuilt_workspaces_desired{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ instant = false
+ interval = ""
+ legendFormat = "Desired"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max(coderd_prebuilt_workspaces_running{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ hide = false
+ instant = false
+ interval = ""
+ legendFormat = "Running"
+ range = true
+ refId = "D"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max(coderd_prebuilt_workspaces_eligible{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ hide = false
+ instant = false
+ interval = ""
+ legendFormat = "Eligible"
+ range = true
+ refId = "E"
+ },
+ ]
+ title = "Pool Capacity: $preset"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ fixedColor = "text"
+ mode = "fixed"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 3
+ w = 6
+ x = 0
+ y = 8
+ }
+ id = 31
+ interval = "30s"
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "vertical"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_prebuilt_workspaces_desired{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ instant = true
+ interval = ""
+ legendFormat = "Desired"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_prebuilt_workspaces_running{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Running"
+ range = false
+ refId = "D"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_prebuilt_workspaces_eligible{template_name=~\"$template\", preset_name=~\"$preset\"}) or vector(0)"
+ hide = false
+ instant = true
+ interval = ""
+ legendFormat = "Eligible"
+ range = false
+ refId = "E"
+ },
+ ]
+ title = "Current: $preset"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = "Compares the total number of regular workspace creations to prebuilt workspace claims to date."
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 3
+ w = 6
+ x = 0
+ y = 11
+ }
+ id = 51
+ options = {
+ colorMode = "none"
+ graphMode = "none"
+ justifyMode = "auto"
+ orientation = "horizontal"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "value_and_name"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(max by (template_name, preset_name) (
+ coderd_workspace_creation_total{
+ template_name=~"$template", preset_name=~"$preset"
+ }
+ )) or vector(0)
+ EOT
+ instant = false
+ legendFormat = "Regular workspaces created"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ sum(max by (template_name, preset_name) (
+ coderd_prebuilt_workspaces_claimed_total{
+ template_name=~"$template", preset_name=~"$preset"
+ }
+ )) or vector(0)
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "Prebuilt workspaces claimed"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "All Time: Regular vs Prebuilt"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = "Median (p50) build time in seconds for Regular Workspace Creation, Prebuilt Workspace Creation, and Prebuilt Workspace Claim"
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ barWidthFactor = 0.6
+ drawStyle = "line"
+ fillOpacity = 0
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "smooth"
+ lineStyle = {
+ dash = [
+ 10,
+ 10,
+ ]
+ fill = "dash"
+ }
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Regular Creation"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "blue"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Prebuild Creation"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "yellow"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Prebuild Claim"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 9
+ x = 6
+ y = 11
+ }
+ id = 50
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ hideZeros = false
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ editorMode = "code"
+ expr = <<-EOT
+ histogram_quantile(0.5,
+ sum(
+ coderd_workspace_creation_duration_seconds{
+ template_name=~"$template", preset_name=~"$preset", type="regular"
+ }
+ )
+ )
+ or vector(0)
+ EOT
+ legendFormat = "Regular Creation"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ histogram_quantile(0.5,
+ sum(
+ coderd_workspace_creation_duration_seconds{
+ template_name=~"$template", preset_name=~"$preset", type="prebuild"
+ }
+ )
+ )
+ or vector(0)
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "Prebuild Creation"
+ range = true
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ histogram_quantile(0.5,
+ sum(
+ coderd_prebuilt_workspace_claim_duration_seconds{
+ template_name=~"$template", preset_name=~"$preset"
+ }
+ )
+ )
+ or vector(0)
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "Prebuild Claim"
+ range = true
+ refId = "C"
+ },
+ ]
+ title = "Workspace Build Latency p50"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = "95th-percentile (p95) build time in seconds for Regular Workspace Creation, Prebuilt Workspace Creation, and Prebuilt Workspace Claim."
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ barWidthFactor = 0.6
+ drawStyle = "line"
+ fillOpacity = 0
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "smooth"
+ lineStyle = {
+ dash = [
+ 10,
+ 10,
+ ]
+ fill = "dash"
+ }
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = 0
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Regular Creation"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "blue"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Prebuild Creation"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "yellow"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Prebuild Claim"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 9
+ x = 15
+ y = 11
+ }
+ id = 53
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ hideZeros = false
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ editorMode = "code"
+ expr = <<-EOT
+ histogram_quantile(0.95,
+ sum(
+ coderd_workspace_creation_duration_seconds{
+ template_name=~"$template", preset_name=~"$preset", type="regular"
+ }
+ )
+ )
+ or vector(0)
+ EOT
+ legendFormat = "Regular Creation"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ histogram_quantile(0.95,
+ sum(
+ coderd_workspace_creation_duration_seconds{
+ template_name=~"$template", preset_name=~"$preset", type="prebuild"
+ }
+ )
+ )
+ or vector(0)
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "Prebuild Creation"
+ range = true
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ histogram_quantile(0.95,
+ sum(
+ coderd_prebuilt_workspace_claim_duration_seconds{
+ template_name=~"$template", preset_name=~"$preset"
+ }
+ )
+ )
+ or vector(0)
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "Prebuild Claim"
+ range = true
+ refId = "C"
+ },
+ ]
+ title = "Workspace Build Latency p95"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ max = 100
+ min = 0
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = 0
+ },
+ {
+ color = "#EAB839"
+ value = 50
+ },
+ {
+ color = "green"
+ value = 75
+ },
+ ]
+ }
+ unit = "percent"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 3
+ w = 6
+ x = 0
+ y = 14
+ }
+ id = 54
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ percentChangeColorMode = "standard"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "12.1.0"
+ targets = [
+ {
+ editorMode = "code"
+ expr = <<-EOT
+ clamp_max(
+ 100 *
+ (
+ sum(
+ coderd_prebuilt_workspaces_claimed_total{
+ template_name="$template", preset_name="$preset"
+ }
+ ) or vector(0)
+ )
+ /
+ clamp_min(
+ (
+ sum(
+ coderd_prebuilt_workspaces_claimed_total{
+ template_name="$template", preset_name="$preset"
+ }
+ ) or vector(0))
+ +
+ (
+ sum(
+ coderd_workspace_creation_total{
+ template_name="$template", preset_name="$preset"
+ }
+ ) or vector(0)),
+ 1
+ ),
+ 100
+ )
+ EOT
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ ]
+ title = "All Time: Prebuilds Usage %"
+ type = "stat"
+ },
+ ]
+ preload = false
+ refresh = "30s"
+ schemaVersion = 41
+ tags = []
+ templating = {
+ list = [
+ {
+ current = {
+ text = "coder"
+ value = "coder"
+ }
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ definition = "label_values(coderd_prebuilt_workspaces_desired,template_name)"
+ includeAll = false
+ label = "Template"
+ name = "template"
+ options = []
+ query = {
+ qryType = 1
+ query = "label_values(coderd_prebuilt_workspaces_desired,template_name)"
+ refId = "PrometheusVariableQueryEditor-VariableQuery"
+ }
+ refresh = 1
+ regex = ""
+ type = "query"
+ },
+ {
+ current = {
+ text = [
+ "All",
+ ]
+ value = [
+ "$__all",
+ ]
+ }
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ definition = "label_values(coderd_prebuilt_workspaces_desired{template_name=~\"$template\"},preset_name)"
+ includeAll = true
+ label = "Preset"
+ multi = true
+ name = "preset"
+ options = []
+ query = {
+ qryType = 1
+ query = "label_values(coderd_prebuilt_workspaces_desired{template_name=~\"$template\"},preset_name)"
+ refId = "PrometheusVariableQueryEditor-VariableQuery"
+ }
+ refresh = 1
+ regex = ""
+ type = "query"
+ },
+ ]
+ }
+ time = {
+ from = "now-12h"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder Prebuilds"
+ uid = "cej6jysyme22oa"
+ version = 5
+ }
+ )
+ }
+ id = "observability/coder-dashboard-prebuilds"
+ immutable = false
+
+ metadata {
+ annotations = {}
+ generate_name = [90mnull[0m[0m
+ generation = 0
+ labels = {}
+ name = "coder-dashboard-prebuilds"
+ namespace = "observability"
+ resource_version = "1950124"
+ uid = "4b126cfd-e889-4a55-af21-dee6a7848f74"
+ }
+}
+
+# module.monitoring.kubernetes_config_map_v1.dashboard["coder-dashboard-provisionerd"]:
+resource "kubernetes_config_map_v1" "dashboard" {
+ binary_data = {}
+ data = {
+ "provisionerd.json" = jsonencode(
+ {
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = true
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ target = {
+ limit = 100
+ matchAny = false
+ tags = []
+ type = "dashboard"
+ }
+ type = "dashboard"
+ },
+ ]
+ }
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ links = []
+ panels = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 6
+ x = 0
+ y = 0
+ }
+ id = 17
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(coderd_provisionerd_num_daemons{pod=~`coder.*`, pod!~`.*provisioner.*`})"
+ instant = true
+ legendFormat = "Built-in"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(coderd_provisionerd_num_daemons{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` })"
+ hide = false
+ instant = true
+ legendFormat = "External"
+ range = false
+ refId = "B"
+ },
+ ]
+ title = "Provisioners"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 6
+ x = 6
+ y = 0
+ }
+ id = 20
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ Provisioners are responsible for building workspaces.
+
+ `coderd` runs built-in provisioners by default. Control this with the `CODER_PROVISIONER_DAEMONS` environment variable or `--provisioner-daemons` flag.
+
+ You can also consider [External Provisioners](https://coder.com/docs/v2/latest/admin/provisioners). Running both built-in and external provisioners is perfectly valid,
+ although dedicated (external) provisioners will generally give the best build performance.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 6
+ x = 12
+ y = 0
+ }
+ id = 21
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "last",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "(sum(coderd_provisionerd_jobs_current) > 0) or vector(0)"
+ instant = false
+ legendFormat = "Current"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(coderd_provisionerd_num_daemons)"
+ hide = false
+ instant = true
+ legendFormat = "Capacity"
+ range = false
+ refId = "B"
+ },
+ ]
+ title = "Builds"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 6
+ x = 18
+ y = 0
+ }
+ id = 22
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ The maximum number of simultaneous builds is equivalent to the number of `provisionerd` daemons running.
+
+ The "Capacity" panel shows the how many simultaneous builds are possible.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ fieldMinMax = false
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 6
+ x = 0
+ y = 7
+ }
+ id = 23
+ options = {
+ colorMode = "value"
+ graphMode = "none"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "histogram_quantile(0.5, sum by(le) (rate(coderd_provisionerd_job_timings_seconds_bucket[$__range])))"
+ hide = false
+ instant = true
+ legendFormat = "Median"
+ range = false
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "histogram_quantile(0.9, sum by(le) (rate(coderd_provisionerd_job_timings_seconds_bucket[$__range])))"
+ hide = false
+ instant = true
+ legendFormat = "90th Percentile"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Build Times"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 6
+ x = 6
+ y = 7
+ }
+ id = 24
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ This shows the median and 90th percentile workspace build times.
+
+ Long build times can impede developers' productivity while they wait for workspaces to start or be created.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "normal"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ decimals = 0
+ fieldMinMax = false
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "failed"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ {
+ id = "displayName"
+ value = "Failure"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "success"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ {
+ id = "displayName"
+ value = "Success"
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 6
+ x = 12
+ y = 7
+ }
+ id = 25
+ interval = "1h"
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum by (status) (increase(coderd_provisionerd_job_timings_seconds_count[$__interval]))"
+ hide = false
+ instant = false
+ interval = "1h"
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ ]
+ title = "Build Count Per Hour"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 6
+ x = 18
+ y = 7
+ }
+ id = 26
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = "_NOTE: this will not show the current hour._"
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "never"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ fieldMinMax = false
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byRegexp"
+ options = "/(Limit|Requested)/"
+ }
+ properties = [
+ {
+ id = "custom.drawStyle"
+ value = "line"
+ },
+ {
+ id = "custom.fillOpacity"
+ value = 5
+ },
+ {
+ id = "custom.lineStyle"
+ value = {
+ dash = [
+ 0,
+ 10,
+ ]
+ fill = "dot"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Limit"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Requested"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 6
+ x = 0
+ y = 14
+ }
+ id = 28
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum by (pod) (rate(container_cpu_usage_seconds_total{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` }[$__rate_interval]))"
+ hide = false
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(kube_pod_container_resource_limits{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` , resource=\"cpu\"})"
+ hide = false
+ instant = false
+ legendFormat = "Limit"
+ range = true
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(kube_pod_container_resource_requests{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` , resource=\"cpu\"})"
+ hide = false
+ instant = false
+ legendFormat = "Requested"
+ range = true
+ refId = "C"
+ },
+ ]
+ title = "CPU Usage Seconds"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 6
+ x = 6
+ y = 14
+ }
+ id = 30
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ The cumulative CPU used per core-second. If the process was using a full CPU core, that would be represented as 1 second.
+
+ Requests & limits are shown if set.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "never"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ fieldMinMax = false
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "bytes"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byRegexp"
+ options = "/(Limit|Requested)/"
+ }
+ properties = [
+ {
+ id = "custom.drawStyle"
+ value = "line"
+ },
+ {
+ id = "custom.fillOpacity"
+ value = 5
+ },
+ {
+ id = "custom.lineStyle"
+ value = {
+ dash = [
+ 0,
+ 10,
+ ]
+ fill = "dot"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Limit"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Requested"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 6
+ x = 12
+ y = 14
+ }
+ id = 29
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "single"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max by (pod) (container_memory_working_set_bytes{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` })"
+ hide = false
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(kube_pod_container_resource_limits{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` , resource=\"memory\"})"
+ hide = false
+ instant = false
+ legendFormat = "Limit"
+ range = true
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(kube_pod_container_resource_requests{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` , resource=\"memory\"})"
+ hide = false
+ instant = false
+ legendFormat = "Requested"
+ range = true
+ refId = "C"
+ },
+ ]
+ title = "RAM Usage"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 6
+ x = 18
+ y = 14
+ }
+ id = 31
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ This shows the total memory used by each container; it is the same metric which the [OOM killer](https://www.kernel.org/doc/gorman/html/understand/understand016.html) uses.
+
+ Requests & limits are shown if set.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ gridPos = {
+ h = 18
+ w = 18
+ x = 0
+ y = 21
+ }
+ id = 27
+ options = {
+ dedupStrategy = "exact"
+ enableLogDetails = true
+ prettifyLogMessage = false
+ showCommonLabels = false
+ showLabels = false
+ showTime = true
+ sortOrder = "Descending"
+ wrapLogMessage = false
+ }
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ editorMode = "code"
+ expr = "{ namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=~\"(.*runner|terraform|provisioner.*)\"}"
+ queryType = "range"
+ refId = "A"
+ },
+ ]
+ title = "Logs"
+ type = "logs"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 6
+ x = 18
+ y = 21
+ }
+ id = 32
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = "This panel shows all logs across built-in and [external provisioners](https://coder.com/docs/v2/latest/admin/provisioners)."
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ ]
+ refresh = "30s"
+ schemaVersion = 39
+ tags = []
+ templating = {
+ list = []
+ }
+ time = {
+ from = "now-12h"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder Provisioners"
+ uid = "provisionerd"
+ version = 10
+ weekStart = ""
+ }
+ )
+ }
+ id = "observability/coder-dashboard-provisionerd"
+ immutable = false
+
+ metadata {
+ annotations = {}
+ generate_name = [90mnull[0m[0m
+ generation = 0
+ labels = {}
+ name = "coder-dashboard-provisionerd"
+ namespace = "observability"
+ resource_version = "1950129"
+ uid = "6ffaa44d-968b-4d4c-922b-1253474bdff4"
+ }
+}
+
+# module.monitoring.kubernetes_config_map_v1.dashboard["coder-dashboard-status"]:
+resource "kubernetes_config_map_v1" "dashboard" {
+ binary_data = {}
+ data = {
+ "status.json" = jsonencode(
+ {
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = false
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ target = {
+ limit = 100
+ matchAny = false
+ tags = []
+ type = "dashboard"
+ }
+ type = "dashboard"
+ },
+ ]
+ }
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ links = []
+ panels = [
+ {
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 0
+ }
+ id = 9
+ title = "Application"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Down"
+ }
+ properties = [
+ {
+ id = "thresholds"
+ value = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 1
+ },
+ ]
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 4
+ x = 0
+ y = 1
+ }
+ id = 10
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "count(up{ pod=~`coder.*`, namespace=~`coder` } == 1) or vector(0) > 0"
+ instant = true
+ legendFormat = "Up"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "count(up{ pod=~`coder.*`, namespace=~`coder` } == 0) or vector(0) > 0"
+ hide = false
+ instant = true
+ legendFormat = "Down"
+ range = false
+ refId = "B"
+ },
+ ]
+ title = "Coder Replicas"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 4
+ x = 4
+ y = 1
+ }
+ id = 16
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(coderd_provisionerd_num_daemons{ pod=~`coder.*`, namespace=~`coder` })"
+ instant = true
+ legendFormat = "Built-in"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(coderd_provisionerd_num_daemons{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` })"
+ hide = false
+ instant = true
+ legendFormat = "External"
+ range = false
+ refId = "B"
+ },
+ ]
+ title = "Provisioners"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ }
+ mappings = []
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "failed"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "orange"
+ mode = "fixed"
+ }
+ },
+ {
+ id = "displayName"
+ value = "Failed"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "success"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "green"
+ mode = "fixed"
+ }
+ },
+ {
+ id = "displayName"
+ value = "Success"
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 4
+ x = 8
+ y = 1
+ }
+ id = 17
+ options = {
+ displayLabels = [
+ "name",
+ "value",
+ ]
+ legend = {
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ values = [
+ "percent",
+ ]
+ }
+ pieType = "pie"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "none"
+ }
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "round(sum by (status) (increase(coderd_provisionerd_job_timings_seconds_count{pod!=``}[$__range])))"
+ instant = true
+ legendFormat = "{{status}}"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Workspace Builds"
+ type = "piechart"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 4
+ x = 12
+ y = 1
+ }
+ id = 18
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ count(kube_pod_status_ready{condition="true", pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)`} == 1)
+ or
+ sum(max by (workspace_owner, template_name, template_version) (coderd_workspace_latest_build_status{status="succeeded", workspace_transition="start"}))
+ or
+ vector(0)
+ EOT
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Running Workspaces"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ decimals = 0
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Down"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Up"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byRegexp"
+ options = "/.*RAM/"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "bytes"
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 4
+ x = 16
+ y = 1
+ }
+ id = 15
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(
+ max_over_time(
+ rate(container_cpu_usage_seconds_total{ pod=~`coder.*`, namespace=~`coder` }[1h:1m])
+ [$__range:]
+ )
+ )
+ EOT
+ instant = true
+ legendFormat = "Control Plane CPU"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(
+ max_over_time(
+ rate(container_cpu_usage_seconds_total{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` }[1h:1m])
+ [$__range:]
+ )
+ )
+ EOT
+ hide = false
+ instant = true
+ legendFormat = "Provisioner CPU"
+ range = false
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(
+ max_over_time(
+ container_memory_working_set_bytes{ pod=~`coder.*`, namespace=~`coder` }
+ [$__range:]
+ )
+ )
+ EOT
+ hide = false
+ instant = true
+ legendFormat = "Control Plane RAM"
+ range = false
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(
+ max_over_time(
+ container_memory_working_set_bytes{ pod=~`coder-provisioner.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` }
+ [$__range:]
+ )
+ )
+ EOT
+ hide = false
+ instant = true
+ legendFormat = "Provisioner RAM"
+ range = false
+ refId = "D"
+ },
+ ]
+ title = "Resource Usage High Watermark (Cumulative)"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Down"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Up"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 4
+ x = 20
+ y = 1
+ }
+ id = 19
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "min(pg_up) or vector(0)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Postgres"
+ type = "stat"
+ },
+ {
+ collapsed = false
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 8
+ }
+ id = 3
+ panels = []
+ title = "Observability Tools"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Down"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Up"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 0
+ y = 9
+ }
+ id = 1
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "vector(1)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Prometheus"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Down"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Up"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 4
+ y = 9
+ }
+ id = 4
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "min(up{job=\"observability/loki/write\"}) or vector(0)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Loki Write Path"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Down"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Up"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 8
+ y = 9
+ }
+ id = 5
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "min(up{job=\"observability/loki/read\"}) or vector(0)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Loki Read Path"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Down"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Up"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 12
+ y = 9
+ }
+ id = 6
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "min(up{job=\"observability/loki/backend\", container=\"loki\"}) or vector(0)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Loki Backend"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Down"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Up"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 16
+ y = 9
+ }
+ id = 7
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "min(up{job=\"observability/loki/canary\"}) or vector(0)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Loki Canary"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "green"
+ value = 1
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 20
+ y = 9
+ }
+ id = 8
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "last",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "sum(up{job=\"observability/grafana-agent/grafana-agent\"}) or vector(0)"
+ instant = true
+ legendFormat = "Current"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "count(up{job=\"observability/grafana-agent/grafana-agent\"})"
+ instant = true
+ legendFormat = "Capacity"
+ range = false
+ refId = "B"
+ },
+ ]
+ title = "Grafana Agent"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Unhealthy"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Healthy"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 4
+ y = 14
+ }
+ id = 14
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "min(loki_runtime_config_last_reload_successful) or vector(0)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Loki Config"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = [
+ {
+ options = {
+ "0" = {
+ color = "red"
+ index = 1
+ text = "Unhealthy"
+ }
+ "1" = {
+ color = "green"
+ index = 0
+ text = "Healthy"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "orange"
+ index = 2
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "orange"
+ index = 3
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null+nan"
+ result = {
+ index = 4
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "red"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 8
+ y = 14
+ }
+ id = 13
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "min(agent_config_last_load_successful{job=\"observability/grafana-agent/grafana-agent\"}) or vector(0)"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Grafana Agent Config"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "percentunit"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "Retention Limit"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "red"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Write-Ahead Log"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "purple"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Storage"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "#f9f9fb"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 12
+ y = 14
+ }
+ id = 11
+ options = {
+ colorMode = "value"
+ graphMode = "area"
+ justifyMode = "auto"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ (
+ prometheus_tsdb_wal_storage_size_bytes{job="observability/prometheus/server"} +
+ prometheus_tsdb_storage_blocks_bytes{job="observability/prometheus/server"} +
+ prometheus_tsdb_symbol_table_size_bytes{job="observability/prometheus/server"}
+ )
+ /
+ prometheus_tsdb_retention_limit_bytes{job="observability/prometheus/server"}
+ EOT
+ instant = false
+ legendFormat = "Retention limit used"
+ range = true
+ refId = "A"
+ },
+ ]
+ title = "Prometheus Storage"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "none"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 16
+ y = 14
+ }
+ id = 20
+ options = {
+ colorMode = "value"
+ graphMode = "none"
+ justifyMode = "center"
+ orientation = "auto"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ titleSize = 20
+ valueSize = 35
+ }
+ textMode = "auto"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(kube_pod_container_resource_requests{namespace=\"observability\", resource=\"cpu\"})"
+ hide = false
+ instant = true
+ legendFormat = "Requested"
+ range = false
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(
+ max_over_time(
+ rate(container_cpu_usage_seconds_total{namespace="observability"}[$__rate_interval])
+ [$__range:]
+ )
+ )
+ EOT
+ hide = false
+ instant = true
+ legendFormat = "High Watermark"
+ range = false
+ refId = "D"
+ },
+ ]
+ title = "CPU"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "bytes"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 5
+ w = 4
+ x = 20
+ y = 14
+ }
+ id = 21
+ options = {
+ colorMode = "none"
+ graphMode = "area"
+ justifyMode = "center"
+ orientation = "vertical"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ titleSize = 20
+ valueSize = 35
+ }
+ textMode = "value_and_name"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(kube_pod_container_resource_requests{namespace=\"observability\", resource=\"memory\"})"
+ hide = false
+ instant = true
+ legendFormat = "Requested"
+ range = false
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(
+ max_over_time(container_memory_working_set_bytes{namespace="observability"}[$__range])
+ )
+ EOT
+ instant = true
+ legendFormat = "High Watermark"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "RAM"
+ type = "stat"
+ },
+ ]
+ refresh = "30s"
+ schemaVersion = 39
+ tags = []
+ templating = {
+ list = []
+ }
+ time = {
+ from = "now-24h"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder Status"
+ uid = "coder-status"
+ version = 1
+ weekStart = ""
+ }
+ )
+ }
+ id = "observability/coder-dashboard-status"
+ immutable = false
+
+ metadata {
+ annotations = {}
+ generate_name = [90mnull[0m[0m
+ generation = 0
+ labels = {}
+ name = "coder-dashboard-status"
+ namespace = "observability"
+ resource_version = "28509042"
+ uid = "d3c35fd5-2f4c-4d11-adb1-5c5e90eb04fd"
+ }
+}
+
+# module.monitoring.kubernetes_config_map_v1.dashboard["coder-dashboard-workspace-detail"]:
+resource "kubernetes_config_map_v1" "dashboard" {
+ binary_data = {}
+ data = {
+ "workspace_detail.json" = jsonencode(
+ {
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = true
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ type = "dashboard"
+ },
+ ]
+ }
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ links = []
+ panels = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ description = ""
+ gridPos = {
+ h = 1.2
+ w = 24
+ x = 0
+ y = 0
+ }
+ id = 28
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = "**HINT**: use the dropdowns above to filter by specific workspace(s)."
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "blue"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "CPUs Requested"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "none"
+ },
+ {
+ id = "decimals"
+ value = 2
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "RAM Requested"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "bytes"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "PVC Capacity"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "bytes"
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 4
+ w = 20
+ x = 0
+ y = 1.2
+ }
+ id = 29
+ options = {
+ colorMode = "none"
+ graphMode = "none"
+ justifyMode = "center"
+ orientation = "vertical"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = "/.*/"
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ titleSize = 20
+ valueSize = 40
+ }
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "group by (template_name) (coderd_agents_up{workspace_name=~\"$workspace_name\"})"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "Template Name"
+ range = false
+ refId = "B"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "group by (template_version) (coderd_agents_up{workspace_name=~\"$workspace_name\"})"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "Template Version"
+ range = false
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "group by (username) (coderd_agents_up{workspace_name=~\"$workspace_name\"})"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "Owner"
+ range = false
+ refId = "C"
+ },
+ ]
+ title = "Details"
+ transformations = [
+ {
+ id = "concatenate"
+ options = {}
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = true
+ "Value #A" = true
+ "Value #B" = true
+ "Value #C" = true
+ "Value #D" = true
+ }
+ includeByName = {}
+ indexByName = {
+ "CPUs Requested" = 7
+ "PVC Capacity" = 9
+ "RAM Requested" = 8
+ Time = 0
+ "Value #A" = 5
+ "Value #B" = 3
+ "Value #C" = 6
+ template_name = 2
+ template_version = 4
+ username = 1
+ }
+ renameByName = {
+ "Value #C" = ""
+ lifecycle_state = "Agent State"
+ template_name = "Template"
+ template_version = "Template Version"
+ username = "Owner"
+ }
+ }
+ },
+ ]
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 8
+ w = 4
+ x = 20
+ y = 1.2
+ }
+ id = 38
+ links = [
+ {
+ title = "Provisioners Dashboard"
+ url = "/d/provisionerd/provisioners?${__url_time_range}"
+ },
+ ]
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = "Essential information about the selected workspace."
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "blue"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "CPUs Requested"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "none"
+ },
+ {
+ id = "decimals"
+ value = 2
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "RAM Requested"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "bytes"
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "PVC Capacity"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "bytes"
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 4
+ w = 20
+ x = 0
+ y = 5.2
+ }
+ id = 36
+ options = {
+ colorMode = "none"
+ graphMode = "none"
+ justifyMode = "center"
+ orientation = "vertical"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = "/.*/"
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ titleSize = 20
+ valueSize = 40
+ }
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(kube_pod_container_resource_requests{pod=~\".*$workspace_name.*\", pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)`, resource=\"cpu\"})"
+ format = "time_series"
+ hide = false
+ instant = true
+ legendFormat = "CPUs Requested"
+ range = false
+ refId = "D"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "sum(kube_pod_container_resource_requests{pod=~\".*$workspace_name.*\", pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)`, resource=\"memory\"})"
+ format = "time_series"
+ hide = false
+ instant = true
+ legendFormat = "RAM Requested"
+ range = false
+ refId = "E"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum(
+ kube_pod_spec_volumes_persistentvolumeclaims_info{pod=~".*$workspace_name.*", pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` }
+ * on(persistentvolumeclaim) group_right
+ group by (persistentvolumeclaim, persistentvolume) (
+ label_replace(
+ kube_persistentvolume_claim_ref,
+ "persistentvolumeclaim",
+ "$1",
+ "name",
+ "(.+)"
+ )
+ )
+ * on (persistentvolume)
+ kube_persistentvolume_capacity_bytes
+ )
+ EOT
+ format = "time_series"
+ hide = false
+ instant = true
+ legendFormat = "PVC Capacity"
+ range = false
+ refId = "F"
+ },
+ ]
+ title = "Resources"
+ transformations = [
+ {
+ id = "concatenate"
+ options = {}
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = true
+ "Value #A" = true
+ "Value #B" = true
+ "Value #C" = true
+ "Value #D" = true
+ }
+ includeByName = {}
+ indexByName = {
+ "CPUs Requested" = 7
+ "PVC Capacity" = 9
+ "RAM Requested" = 8
+ Time = 0
+ "Value #A" = 5
+ "Value #B" = 3
+ "Value #C" = 6
+ template_name = 2
+ template_version = 4
+ username = 1
+ }
+ renameByName = {
+ "Value #C" = ""
+ lifecycle_state = "Agent State"
+ template_name = "Template"
+ template_version = "Template Version"
+ username = "Owner"
+ }
+ }
+ },
+ ]
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ mappings = [
+ {
+ options = {
+ created = {
+ color = "light-blue"
+ index = 1
+ text = "Created"
+ }
+ off = {
+ color = "text"
+ index = 8
+ text = "Off"
+ }
+ ready = {
+ color = "green"
+ index = 0
+ text = "Ready"
+ }
+ shutdown_error = {
+ color = "red"
+ index = 7
+ text = "Shutdown Error"
+ }
+ shutdown_timeout = {
+ color = "purple"
+ index = 6
+ text = "Shutdown Timeout"
+ }
+ shutting_down = {
+ color = "light-purple"
+ index = 5
+ text = "Shutting Down"
+ }
+ start_error = {
+ color = "red"
+ index = 4
+ text = "Start Error"
+ }
+ start_timeout = {
+ color = "orange"
+ index = 3
+ text = "Start Timeout"
+ }
+ starting = {
+ color = "super-light-green"
+ index = 2
+ text = "Starting"
+ }
+ }
+ type = "value"
+ },
+ {
+ options = {
+ match = "empty"
+ result = {
+ color = "text"
+ index = 9
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ {
+ options = {
+ match = "null"
+ result = {
+ color = "text"
+ index = 10
+ text = "Unknown"
+ }
+ }
+ type = "special"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "text"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 6
+ w = 4
+ x = 0
+ y = 9.2
+ }
+ id = 35
+ options = {
+ colorMode = "background"
+ graphMode = "none"
+ justifyMode = "auto"
+ orientation = "horizontal"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = "/^lifecycle_state$/"
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ valueSize = 50
+ }
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max by (lifecycle_state) (coderd_agents_connections{workspace_name=~\"$workspace_name\"})"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "D"
+ },
+ ]
+ title = "Agent Lifecycle State"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ mappings = [
+ {
+ options = {
+ "-1" = {
+ color = "light-orange"
+ index = 0
+ text = "Not completed yet"
+ }
+ }
+ type = "value"
+ },
+ ]
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "#EAB839"
+ value = 60
+ },
+ {
+ color = "red"
+ value = 120
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 6
+ w = 3
+ x = 4
+ y = 9.2
+ }
+ id = 33
+ options = {
+ colorMode = "background"
+ graphMode = "none"
+ justifyMode = "auto"
+ orientation = "horizontal"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = "/^Value$/"
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ valueSize = 50
+ }
+ textMode = "value"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_agentstats_startup_script_seconds{workspace_name=~\"$workspace_name\"}) or vector(-1)"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "C"
+ },
+ ]
+ title = "Agent Startup Script Execution Time"
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 6
+ w = 3
+ x = 7
+ y = 9.2
+ }
+ id = 39
+ options = {
+ colorMode = "background"
+ graphMode = "none"
+ justifyMode = "center"
+ orientation = "horizontal"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = "/.*/"
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ titleSize = 20
+ valueSize = 50
+ }
+ textMode = "value_and_name"
+ wideLayout = false
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ max by (app) (
+ label_replace(
+ {workspace_name=~"$workspace_name", __name__=~"coderd_agentstats_session_count_.*"},
+ "app",
+ "$1",
+ "__name__",
+ "coderd_agentstats_session_count_(.*)"
+ )
+ )>0
+ EOT
+ format = "time_series"
+ hide = false
+ instant = true
+ legendFormat = "{{app}}"
+ range = false
+ refId = "C"
+ },
+ ]
+ title = "App Session Counts"
+ transformations = [
+ {
+ id = "concatenate"
+ options = {}
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = true
+ }
+ includeByName = {}
+ indexByName = {}
+ renameByName = {}
+ }
+ },
+ ]
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byRegexp"
+ options = "/.*Bytes/"
+ }
+ properties = [
+ {
+ id = "unit"
+ value = "bytes"
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 6
+ w = 10
+ x = 10
+ y = 9.2
+ }
+ id = 34
+ options = {
+ colorMode = "none"
+ graphMode = "none"
+ justifyMode = "center"
+ orientation = "vertical"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = "/.*/"
+ values = false
+ }
+ showPercentChange = false
+ text = {
+ titleSize = 20
+ valueSize = 50
+ }
+ textMode = "auto"
+ wideLayout = true
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(coderd_agents_connection_latencies_seconds{workspace_name=~\"$workspace_name\"})"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "Connection Latency"
+ range = false
+ refId = "C"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(sum by (pod) (sum_over_time(coderd_agentstats_rx_bytes{workspace_name=~\"$workspace_name\"}[$__range])))"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "Received Bytes"
+ range = false
+ refId = "rx"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "max(sum by (pod) (sum_over_time(coderd_agentstats_tx_bytes{workspace_name=~\"$workspace_name\"}[$__range])))"
+ format = "table"
+ hide = false
+ instant = true
+ legendFormat = "Transmitted Bytes"
+ range = false
+ refId = "tx"
+ },
+ ]
+ title = "Networking"
+ transformations = [
+ {
+ id = "merge"
+ options = {}
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = true
+ }
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ "Value #A" = "Received Bytes"
+ "Value #B" = "Transmitted Bytes"
+ "Value #C" = "Connection Latency"
+ "Value #rx" = "Received Bytes"
+ "Value #tx" = "Transmitted Bytes"
+ }
+ }
+ },
+ ]
+ type = "stat"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 6
+ w = 4
+ x = 20
+ y = 9.2
+ }
+ id = 40
+ links = [
+ {
+ title = "Provisioners Dashboard"
+ url = "/d/provisionerd/provisioners?${__url_time_range}"
+ },
+ ]
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ Essential information about this workspace's agent.
+
+ Read more about the agent [here](https://coder.com/docs/v2/latest/about/architecture#agents).
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ }
+ filterable = true
+ inspect = false
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "status"
+ }
+ properties = [
+ {
+ id = "custom.cellOptions"
+ value = {
+ type = "color-text"
+ }
+ },
+ {
+ id = "mappings"
+ value = [
+ {
+ options = {
+ failed = {
+ color = "orange"
+ index = 1
+ text = "Failure"
+ }
+ success = {
+ color = "green"
+ index = 0
+ text = "Success"
+ }
+ }
+ type = "value"
+ },
+ ]
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Workspace Transition"
+ }
+ properties = [
+ {
+ id = "custom.cellOptions"
+ value = {
+ type = "color-text"
+ }
+ },
+ {
+ id = "mappings"
+ value = [
+ {
+ options = {
+ DESTROY = {
+ color = "red"
+ index = 0
+ }
+ START = {
+ color = "blue"
+ index = 1
+ }
+ STOP = {
+ color = "purple"
+ index = 2
+ }
+ }
+ type = "value"
+ },
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 7
+ w = 20
+ x = 0
+ y = 15.2
+ }
+ id = 6
+ interval = ""
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ enablePagination = true
+ fields = []
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ showHeader = true
+ sortBy = [
+ {
+ desc = true
+ displayName = "Time"
+ },
+ ]
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum by (workspace_name, workspace_owner, status, template_name, template_version, workspace_transition) (
+ # Since new series are created and are initially set to a value of 1, we cannot use "increase" (because an increase from to 1 does not yield 1).
+ # So we compare the current series to an interval ago to see if we have any new series and then sum the series we find.
+ ((
+ coderd_workspace_builds_total{workspace_name=~"$workspace_name"} -
+ coderd_workspace_builds_total{workspace_name=~"$workspace_name"} offset $__interval
+ ) >= 0)
+ or coderd_workspace_builds_total{workspace_name=~"$workspace_name"}
+ ) > 0
+ EOT
+ format = "table"
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ ]
+ title = "Build Log"
+ transformations = [
+ {
+ disabled = true
+ id = "groupBy"
+ options = {
+ fields = {
+ Count = {
+ aggregations = [
+ "sum",
+ ]
+ operation = "aggregate"
+ }
+ Status = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Template Name" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Template Version" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ Total = {
+ aggregations = [
+ "sum",
+ ]
+ operation = "aggregate"
+ }
+ Value = {
+ aggregations = [
+ "sum",
+ ]
+ operation = "aggregate"
+ }
+ "Workspace Name" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Workspace Ownert" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Workspace Transition" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ status = {
+ aggregations = []
+ operation = "groupby"
+ }
+ template_name = {
+ aggregations = []
+ operation = "groupby"
+ }
+ template_version = {
+ aggregations = []
+ operation = "groupby"
+ }
+ workspace_name = {
+ aggregations = []
+ operation = "groupby"
+ }
+ workspace_owner = {
+ aggregations = []
+ operation = "groupby"
+ }
+ workspace_transition = {
+ aggregations = []
+ operation = "groupby"
+ }
+ }
+ }
+ },
+ {
+ id = "sortBy"
+ options = {
+ fields = {}
+ sort = [
+ {
+ desc = true
+ field = "Value"
+ },
+ ]
+ }
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = false
+ }
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ Value = "Count"
+ "Value (sum)" = "Total"
+ status = "Status"
+ template_name = "Template Name"
+ template_version = "Template Version"
+ workspace_name = "Workspace Name"
+ workspace_owner = "Workspace Owner"
+ workspace_transition = "Workspace Transition"
+ }
+ }
+ },
+ ]
+ type = "table"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 4
+ x = 20
+ y = 15.2
+ }
+ id = 37
+ links = [
+ {
+ title = "Provisioners Dashboard"
+ url = "/d/provisionerd/provisioners?${__url_time_range}"
+ },
+ ]
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ This table shows a reverse-chronological log of all workspace builds.
+
+ The "Count" field shows the count of events which occurred within a minute, grouped by all columns.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ gridPos = {
+ h = 10
+ w = 20
+ x = 0
+ y = 22.2
+ }
+ id = 7
+ options = {
+ dedupStrategy = "exact"
+ enableLogDetails = true
+ prettifyLogMessage = false
+ showCommonLabels = false
+ showLabels = false
+ showTime = true
+ sortOrder = "Descending"
+ wrapLogMessage = false
+ }
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ editorMode = "code"
+ expr = "{namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=~\"(.*runner|terraform|provisioner.*)\"} |~ \"$workspace_name\" | line_format `{{ printf \"[\\033[35m\" }}{{.pod}}{{ printf \"\\033[0m]\\t\" }}{{ __line__ }}`"
+ hide = false
+ queryType = "range"
+ refId = "A"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ editorMode = "code"
+ expr = "{pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)`, pod=~\".*($workspace_name).*\"} | line_format `{{ printf \"[\\033[32m\" }}{{.pod}}{{ printf \"\\033[0m]\\t\" }}{{ __line__ }}`"
+ hide = false
+ queryType = "range"
+ refId = "B"
+ },
+ ]
+ title = "Logs"
+ type = "logs"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 10
+ w = 4
+ x = 20
+ y = 22.2
+ }
+ id = 24
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ The logs to the left come both from provisioners and workspace logs.
+
+ Provisioner logs matching the name filter are highlighted in magenta, while
+ workspace logs matching the name filter are highlighted in green.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ ]
+ refresh = "30s"
+ schemaVersion = 39
+ tags = []
+ templating = {
+ list = [
+ {
+ allValue = ""
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ definition = "label_values(coderd_agents_up,workspace_name)"
+ hide = 0
+ includeAll = false
+ label = "Workspace Name Filter"
+ multi = false
+ name = "workspace_name"
+ options = []
+ query = {
+ qryType = 1
+ query = "label_values(coderd_agents_up,workspace_name)"
+ refId = "PrometheusVariableQueryEditor-VariableQuery"
+ }
+ refresh = 2
+ regex = ""
+ skipUrlSync = false
+ sort = 1
+ type = "query"
+ },
+ ]
+ }
+ time = {
+ from = "now-12h"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder Workspace Detail"
+ uid = "workspace-detail"
+ version = 9
+ weekStart = ""
+ }
+ )
+ }
+ id = "observability/coder-dashboard-workspace-detail"
+ immutable = false
+
+ metadata {
+ annotations = {}
+ generate_name = [90mnull[0m[0m
+ generation = 0
+ labels = {}
+ name = "coder-dashboard-workspace-detail"
+ namespace = "observability"
+ resource_version = "1950128"
+ uid = "db1a6e4b-bfa0-466e-84b5-ae50142023ae"
+ }
+}
+
+# module.monitoring.kubernetes_config_map_v1.dashboard["coder-dashboard-workspaces"]:
+resource "kubernetes_config_map_v1" "dashboard" {
+ binary_data = {}
+ data = {
+ "workspaces.json" = jsonencode(
+ {
+ annotations = {
+ list = [
+ {
+ builtIn = 1
+ datasource = {
+ type = "grafana"
+ uid = "-- Grafana --"
+ }
+ enable = true
+ hide = true
+ iconColor = "rgba(0, 211, 255, 1)"
+ name = "Annotations & Alerts"
+ type = "dashboard"
+ },
+ ]
+ }
+ editable = true
+ fiscalYearStartMonth = 0
+ graphTooltip = 0
+ links = []
+ panels = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ description = ""
+ gridPos = {
+ h = 1.2
+ w = 24
+ x = 0
+ y = 0
+ }
+ id = 28
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = "**HINT**: use the dropdowns above to filter by specific workspaces and/or templates."
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 1.2
+ }
+ id = 31
+ title = "Resources"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 1
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "s"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 8
+ w = 10
+ x = 0
+ y = 2.2
+ }
+ id = 33
+ options = {
+ legend = {
+ calcs = [
+ "mean",
+ "stdDev",
+ "min",
+ "max",
+ "lastNotNull",
+ ]
+ displayMode = "table"
+ placement = "bottom"
+ showLegend = true
+ sortBy = "Max"
+ sortDesc = true
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "desc"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "sum by (pod) (rate(container_cpu_usage_seconds_total{ pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` }[$__rate_interval]))"
+ hide = false
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "CPU Usage"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 1
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "bytes"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 8
+ w = 10
+ x = 10
+ y = 2.2
+ }
+ id = 37
+ options = {
+ legend = {
+ calcs = [
+ "mean",
+ "stdDev",
+ "min",
+ "max",
+ "lastNotNull",
+ ]
+ displayMode = "table"
+ placement = "bottom"
+ showLegend = true
+ sortBy = "Max"
+ sortDesc = true
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "desc"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = "max by (pod) (container_memory_working_set_bytes{ pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` })"
+ hide = false
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "RAM Usage"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 8
+ w = 4
+ x = 20
+ y = 2.2
+ }
+ id = 36
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ The cumulative CPU used per core-second. If a workspace was using a full CPU core, that would be represented as 1 second.
+
+ See the Kubernetes [documentation](https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/#cpu-units) for more details.
+
+ The total memory used by each workspace container is represented; it is the same metric which the [OOM killer](https://www.kernel.org/doc/gorman/html/understand/understand016.html) uses.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 1
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ decimals = 0
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "none"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 8
+ w = 10
+ x = 0
+ y = 10.2
+ }
+ id = 38
+ options = {
+ legend = {
+ calcs = [
+ "sum",
+ ]
+ displayMode = "table"
+ placement = "bottom"
+ showLegend = true
+ sortBy = "Max"
+ sortDesc = true
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "desc"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ sum by (pod) (
+ round(increase(kube_pod_container_status_restarts_total{ pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` }[$__interval]))
+ ) > 0
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "Pod Restarts"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 1
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "none"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ decimals = 0
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "none"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 8
+ w = 10
+ x = 10
+ y = 10.2
+ }
+ id = 39
+ options = {
+ legend = {
+ calcs = [
+ "sum",
+ ]
+ displayMode = "table"
+ placement = "bottom"
+ showLegend = true
+ sortBy = "Max"
+ sortDesc = true
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "desc"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ sum by (pod, reason) (
+ count_over_time(kube_pod_container_status_terminated_reason{ pod!~`coder.*`, namespace=~`(coder-ws|coder-ws-experiment|coder-ws-demo)` }[$__interval])
+ )
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "{{pod}}:{{reason}}"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "Terminations"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 8
+ w = 4
+ x = 20
+ y = 10.2
+ }
+ id = 40
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ Pods can be terminated for several reasons:
+ - `OOMKilled`: pod exceeded its defined memory limit or was terminated by the OS for using excessive memory (if no limit defined)
+ - `Error`: usually attributeable to a configuration problem
+ - `Evicted`: pod has been evicted from node for overusing resources and will be rescheduled on another node is possible
+
+ Pod restarts are not necessarily problematic, but they are worth noting.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ collapsed = false
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 18.2
+ }
+ id = 30
+ panels = []
+ title = "Builds"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 1
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "normal"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "DESTROY"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "red"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "STOP"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "purple"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "START"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "blue"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 8
+ w = 10
+ x = 0
+ y = 19.2
+ }
+ id = 2
+ interval = "5m"
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "desc"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ sum by (workspace_transition) (
+ (
+ # Since new series are created and are initially set to a value of 1, we cannot use "increase" (because an increase from to 1 does not yield 1).
+ # So we compare the current series to an interval ago to see if we have any new series and then sum the series we find.
+ (
+ coderd_workspace_builds_total{status="success", workspace_name=~"$workspace_name", template_name=~"$template_name"} -
+ coderd_workspace_builds_total{status="success", workspace_name=~"$workspace_name", template_name=~"$template_name"} offset $__interval
+ ) >= 0)
+ or coderd_workspace_builds_total{status="success", workspace_name=~"$workspace_name", template_name=~"$template_name"}
+ ) > 0
+ EOT
+ hide = false
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "B"
+ },
+ ]
+ title = "Successful Builds by State"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ axisBorderShow = false
+ axisCenteredZero = false
+ axisColorMode = "text"
+ axisLabel = ""
+ axisPlacement = "auto"
+ barAlignment = 0
+ drawStyle = "bars"
+ fillOpacity = 100
+ gradientMode = "none"
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ insertNulls = false
+ lineInterpolation = "linear"
+ lineWidth = 1
+ pointSize = 5
+ scaleDistribution = {
+ type = "linear"
+ }
+ showPoints = "auto"
+ spanNulls = false
+ stacking = {
+ group = "A"
+ mode = "normal"
+ }
+ thresholdsStyle = {
+ mode = "off"
+ }
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "DESTROY"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "red"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "STOP"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "purple"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "START"
+ }
+ properties = [
+ {
+ id = "color"
+ value = {
+ fixedColor = "blue"
+ mode = "fixed"
+ }
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 8
+ w = 10
+ x = 10
+ y = 19.2
+ }
+ id = 1
+ interval = "5m"
+ options = {
+ legend = {
+ calcs = []
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "desc"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ expr = <<-EOT
+ sum by (workspace_transition) (
+ (
+ # Since new series are created and are initially set to a value of 1, we cannot use "increase" (because an increase from to 1 does not yield 1).
+ # So we compare the current series to an interval ago to see if we have any new series and then sum the series we find.
+ (
+ coderd_workspace_builds_total{status="failed", workspace_name=~"$workspace_name", template_name=~"$template_name"} -
+ coderd_workspace_builds_total{status="failed", workspace_name=~"$workspace_name", template_name=~"$template_name"} offset $__interval
+ ) >= 0)
+ or coderd_workspace_builds_total{status="failed", workspace_name=~"$workspace_name", template_name=~"$template_name"}
+ ) > 0
+ EOT
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ ]
+ title = "Unsuccessful Builds by State"
+ type = "timeseries"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 8
+ w = 4
+ x = 20
+ y = 19.2
+ }
+ id = 34
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ Workspaces "transition" between `STOP`, `START`, and `DESTROY` states.
+
+ Workspaces transition between states when a "build" is initiated, which is an execution of `terraform` against the chosen template.
+
+ Use the "Build Count" table to identify workspace owners which may be struggling with template builds, in order to proactively reach out to them with assistance.
+
+ Consult the [Template documentation](https://coder.com/docs/v2/latest/templates) for more information.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "thresholds"
+ }
+ custom = {
+ align = "auto"
+ cellOptions = {
+ type = "auto"
+ }
+ filterable = true
+ inspect = false
+ }
+ mappings = []
+ thresholds = {
+ mode = "absolute"
+ steps = [
+ {
+ color = "green"
+ value = [90mnull[0m[0m
+ },
+ {
+ color = "red"
+ value = 80
+ },
+ ]
+ }
+ unit = "short"
+ }
+ overrides = [
+ {
+ matcher = {
+ id = "byName"
+ options = "status"
+ }
+ properties = [
+ {
+ id = "custom.cellOptions"
+ value = {
+ type = "color-text"
+ }
+ },
+ {
+ id = "mappings"
+ value = [
+ {
+ options = {
+ failed = {
+ color = "orange"
+ index = 1
+ text = "Failure"
+ }
+ success = {
+ color = "green"
+ index = 0
+ text = "Success"
+ }
+ }
+ type = "value"
+ },
+ ]
+ },
+ ]
+ },
+ {
+ matcher = {
+ id = "byName"
+ options = "Workspace Transition"
+ }
+ properties = [
+ {
+ id = "custom.cellOptions"
+ value = {
+ type = "color-text"
+ }
+ },
+ {
+ id = "mappings"
+ value = [
+ {
+ options = {
+ DESTROY = {
+ color = "red"
+ index = 0
+ }
+ START = {
+ color = "blue"
+ index = 1
+ }
+ STOP = {
+ color = "purple"
+ index = 2
+ }
+ }
+ type = "value"
+ },
+ ]
+ },
+ ]
+ },
+ ]
+ }
+ gridPos = {
+ h = 10
+ w = 20
+ x = 0
+ y = 27.2
+ }
+ id = 6
+ interval = ""
+ options = {
+ cellHeight = "sm"
+ footer = {
+ countRows = false
+ enablePagination = true
+ fields = []
+ reducer = [
+ "sum",
+ ]
+ show = false
+ }
+ showHeader = true
+ sortBy = [
+ {
+ desc = true
+ displayName = "Time"
+ },
+ ]
+ }
+ pluginVersion = "10.4.0"
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = <<-EOT
+ sum by (workspace_name, workspace_owner, status, template_name, template_version, workspace_transition) (
+ # Since new series are created and are initially set to a value of 1, we cannot use "increase" (because an increase from to 1 does not yield 1).
+ # So we compare the current series to an interval ago to see if we have any new series and then sum the series we find.
+ ((
+ coderd_workspace_builds_total{workspace_name=~"$workspace_name", template_name=~"$template_name"} -
+ coderd_workspace_builds_total{workspace_name=~"$workspace_name", template_name=~"$template_name"} offset $__interval
+ ) >= 0)
+ or coderd_workspace_builds_total{workspace_name=~"$workspace_name", template_name=~"$template_name"}
+ ) > 0
+ EOT
+ format = "table"
+ instant = false
+ legendFormat = "__auto"
+ range = true
+ refId = "A"
+ },
+ ]
+ title = "Build Log"
+ transformations = [
+ {
+ disabled = true
+ id = "groupBy"
+ options = {
+ fields = {
+ Count = {
+ aggregations = [
+ "sum",
+ ]
+ operation = "aggregate"
+ }
+ Status = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Template Name" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Template Version" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ Total = {
+ aggregations = [
+ "sum",
+ ]
+ operation = "aggregate"
+ }
+ Value = {
+ aggregations = [
+ "sum",
+ ]
+ operation = "aggregate"
+ }
+ "Workspace Name" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Workspace Ownert" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ "Workspace Transition" = {
+ aggregations = []
+ operation = "groupby"
+ }
+ status = {
+ aggregations = []
+ operation = "groupby"
+ }
+ template_name = {
+ aggregations = []
+ operation = "groupby"
+ }
+ template_version = {
+ aggregations = []
+ operation = "groupby"
+ }
+ workspace_name = {
+ aggregations = []
+ operation = "groupby"
+ }
+ workspace_owner = {
+ aggregations = []
+ operation = "groupby"
+ }
+ workspace_transition = {
+ aggregations = []
+ operation = "groupby"
+ }
+ }
+ }
+ },
+ {
+ id = "sortBy"
+ options = {
+ fields = {}
+ sort = [
+ {
+ desc = true
+ field = "Value"
+ },
+ ]
+ }
+ },
+ {
+ id = "organize"
+ options = {
+ excludeByName = {
+ Time = false
+ }
+ includeByName = {}
+ indexByName = {}
+ renameByName = {
+ Value = "Count"
+ "Value (sum)" = "Total"
+ status = "Status"
+ template_name = "Template Name"
+ template_version = "Template Version"
+ workspace_name = "Workspace Name"
+ workspace_owner = "Workspace Owner"
+ workspace_transition = "Workspace Transition"
+ }
+ }
+ },
+ ]
+ type = "table"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 10
+ w = 4
+ x = 20
+ y = 27.2
+ }
+ id = 29
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ This table shows a reverse-chronological log of all workspace builds.
+
+ The "Count" field shows the count of events which occurred within a minute, grouped by all columns.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ }
+ mappings = []
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 5
+ x = 0
+ y = 37.2
+ }
+ id = 8
+ interval = "1h"
+ options = {
+ displayLabels = [
+ "name",
+ ]
+ legend = {
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ values = [
+ "percent",
+ ]
+ }
+ pieType = "pie"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "none"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "count by (workspace_owner) (coderd_workspace_latest_build_status{template_name=~\"$template_name\"})"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Workspace by User"
+ type = "piechart"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ }
+ mappings = []
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 5
+ x = 5
+ y = 37.2
+ }
+ id = 9
+ interval = "1h"
+ options = {
+ displayLabels = [
+ "name",
+ ]
+ legend = {
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ values = [
+ "percent",
+ ]
+ }
+ pieType = "pie"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "none"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "count by (workspace_owner, template_name) (coderd_workspace_latest_build_status{template_name=~\"$template_name\"})"
+ instant = true
+ legendFormat = "{{workspace_owner}}:{{template_name}}"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Workspace by User/Template"
+ type = "piechart"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ }
+ mappings = []
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 5
+ x = 10
+ y = 37.2
+ }
+ id = 4
+ interval = "1h"
+ options = {
+ displayLabels = [
+ "name",
+ ]
+ legend = {
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ values = [
+ "percent",
+ ]
+ }
+ pieType = "pie"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "none"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "count by (template_name) (coderd_workspace_latest_build_status{template_name=~\"$template_name\"})"
+ instant = true
+ legendFormat = "__auto"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Template Usage"
+ type = "piechart"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ fieldConfig = {
+ defaults = {
+ color = {
+ mode = "palette-classic"
+ }
+ custom = {
+ hideFrom = {
+ legend = false
+ tooltip = false
+ viz = false
+ }
+ }
+ mappings = []
+ unit = "short"
+ }
+ overrides = []
+ }
+ gridPos = {
+ h = 7
+ w = 5
+ x = 15
+ y = 37.2
+ }
+ id = 5
+ interval = "1h"
+ options = {
+ displayLabels = []
+ legend = {
+ displayMode = "list"
+ placement = "bottom"
+ showLegend = true
+ values = [
+ "percent",
+ ]
+ }
+ pieType = "pie"
+ reduceOptions = {
+ calcs = [
+ "lastNotNull",
+ ]
+ fields = ""
+ values = false
+ }
+ tooltip = {
+ mode = "multi"
+ sort = "none"
+ }
+ }
+ targets = [
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ editorMode = "code"
+ exemplar = false
+ expr = "count by (template_name, template_version) (coderd_workspace_latest_build_status{template_name=~\"$template_name\"})"
+ instant = true
+ legendFormat = "{{template_name}}:{{template_version}}"
+ range = false
+ refId = "A"
+ },
+ ]
+ title = "Template Version Usage"
+ type = "piechart"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 7
+ w = 4
+ x = 20
+ y = 37.2
+ }
+ id = 24
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ These charts show the distribution of workspaces and templates.
+
+ Use these charts to identify which users have outdated templates, and which templates are the most/least popular in your organisation.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ {
+ collapsed = false
+ gridPos = {
+ h = 1
+ w = 24
+ x = 0
+ y = 44.2
+ }
+ id = 32
+ panels = []
+ title = "Logs"
+ type = "row"
+ },
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ gridPos = {
+ h = 10
+ w = 20
+ x = 0
+ y = 45.2
+ }
+ id = 7
+ options = {
+ dedupStrategy = "exact"
+ enableLogDetails = true
+ prettifyLogMessage = false
+ showCommonLabels = false
+ showLabels = false
+ showTime = false
+ sortOrder = "Descending"
+ wrapLogMessage = true
+ }
+ targets = [
+ {
+ datasource = {
+ type = "loki"
+ uid = "loki"
+ }
+ editorMode = "code"
+ expr = "{ namespace=~`(coder|coder-ws|coder-ws-experiment|coder-ws-demo)`, logger=~\"(.*runner|terraform|provisioner.*)\"} |~ \"$workspace_name\" or \"$template_name\""
+ queryType = "range"
+ refId = "A"
+ },
+ ]
+ title = "Logs"
+ type = "logs"
+ },
+ {
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ description = ""
+ gridPos = {
+ h = 10
+ w = 4
+ x = 20
+ y = 45.2
+ }
+ id = 22
+ links = [
+ {
+ title = "Provisioners Dashboard"
+ url = "/d/provisionerd/provisioners?${__url_time_range}"
+ },
+ ]
+ options = {
+ code = {
+ language = "plaintext"
+ showLineNumbers = false
+ showMiniMap = false
+ }
+ content = <<-EOT
+ These are the logs produced by the [Provisioners](/d/provisionerd/provisioners?${__url_time_range}).
+
+ Use the dropdowns at the top to filter the logs down to a specific workspace and/or template.
+ EOT
+ mode = "markdown"
+ }
+ pluginVersion = "10.4.0"
+ transparent = true
+ type = "text"
+ },
+ ]
+ refresh = "30s"
+ schemaVersion = 39
+ tags = []
+ templating = {
+ list = [
+ {
+ allValue = ""
+ current = {
+ selected = true
+ text = [
+ "All",
+ ]
+ value = [
+ "$__all",
+ ]
+ }
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ definition = "label_values(coderd_workspace_builds_total,workspace_name)"
+ hide = 0
+ includeAll = true
+ label = "Workspace Name Filter"
+ multi = true
+ name = "workspace_name"
+ options = []
+ query = {
+ qryType = 1
+ query = "label_values(coderd_workspace_builds_total,workspace_name)"
+ refId = "PrometheusVariableQueryEditor-VariableQuery"
+ }
+ refresh = 2
+ regex = ""
+ skipUrlSync = false
+ sort = 1
+ type = "query"
+ },
+ {
+ allValue = ""
+ current = {
+ selected = true
+ text = [
+ "All",
+ ]
+ value = [
+ "$__all",
+ ]
+ }
+ datasource = {
+ type = "prometheus"
+ uid = "prometheus"
+ }
+ definition = "label_values(coderd_workspace_builds_total,template_name)"
+ hide = 0
+ includeAll = true
+ label = "Template Name Filter"
+ multi = true
+ name = "template_name"
+ options = []
+ query = {
+ qryType = 1
+ query = "label_values(coderd_workspace_builds_total,template_name)"
+ refId = "PrometheusVariableQueryEditor-VariableQuery"
+ }
+ refresh = 2
+ regex = ""
+ skipUrlSync = false
+ sort = 1
+ type = "query"
+ },
+ ]
+ }
+ time = {
+ from = "now-12h"
+ to = "now"
+ }
+ timepicker = {}
+ timezone = "browser"
+ title = "Coder Workspaces"
+ uid = "workspaces"
+ version = 2
+ weekStart = ""
+ }
+ )
+ }
+ id = "observability/coder-dashboard-workspaces"
+ immutable = false
+
+ metadata {
+ annotations = {}
+ generate_name = [90mnull[0m[0m
+ generation = 0
+ labels = {}
+ name = "coder-dashboard-workspaces"
+ namespace = "observability"
+ resource_version = "1950126"
+ uid = "638c3360-6341-4d5a-9992-e1ceedb690b3"
+ }
+}
+
+# module.monitoring.kubernetes_namespace_v1.this:
+resource "kubernetes_namespace_v1" "this" {
+ id = "observability"
+ wait_for_default_service_account = false
+
+ metadata {
+ annotations = {}
+ generate_name = [90mnull[0m[0m
+ generation = 0
+ labels = {}
+ name = "observability"
+ resource_version = "989487"
+ uid = "cab25c43-51a1-4ae9-85ff-1231d49aeb72"
+ }
+}
+
+# module.monitoring.kubernetes_service_account_v1.grafana-agent:
+resource "kubernetes_service_account_v1" "grafana-agent" {
+ automount_service_account_token = true
+ default_secret_name = [90mnull[0m[0m
+ id = "observability/grafana-agent"
+
+ metadata {
+ annotations = {
+ "eks.amazonaws.com/role-arn" = "arn:aws:iam::750246862020:role/aienv/us-east-2/observability-access-20260320095145755600000002"
+ }
+ generate_name = [90mnull[0m[0m
+ generation = 0
+ labels = {}
+ name = "grafana-agent"
+ namespace = "observability"
+ resource_version = "27397370"
+ uid = "110457a4-6f4a-4b10-8c39-a16b58f117d5"
+ }
+}
+
+# module.monitoring.random_id.grafana_server_secret:
+resource "random_id" "grafana_server_secret" {
+ b64_std = "2VGW9r7qo3Z4193HhkEBYg=="
+ b64_url = "2VGW9r7qo3Z4193HhkEBYg"
+ byte_length = 16
+ dec = "288866113041522404156633309371057635682"
+ hex = "d95196f6beeaa37678d7ddc786410162"
+ id = "2VGW9r7qo3Z4193HhkEBYg"
+ keepers = {
+ "grafana_admin_password" = (sensitive value)
+ "grafana_admin_username" = (sensitive value)
+ }
+}
+
+
+# module.monitoring.module.iam-policy.aws_iam_policy.this:
+resource "aws_iam_policy" "this" {
+ arn = "arn:aws:iam::750246862020:policy/aienv/us-east-2/ObservabilityAccess-us-east-2-20260320095145717300000001"
+ attachment_count = 1
+ description = "Loki S3 policy"
+ id = "arn:aws:iam::750246862020:policy/aienv/us-east-2/ObservabilityAccess-us-east-2-20260320095145717300000001"
+ name = "ObservabilityAccess-us-east-2-20260320095145717300000001"
+ name_prefix = "ObservabilityAccess-us-east-2-"
+ path = "/aienv/us-east-2/"
+ policy = jsonencode(
+ {
+ Statement = [
+ {
+ Action = [
+ "s3:PutObject",
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:DeleteObject",
+ ]
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::aienv-v2-logs/*",
+ "arn:aws:s3:::aienv-v2-logs",
+ ]
+ Sid = "LokiChunksBucket"
+ },
+ {
+ Action = [
+ "s3:PutObject",
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:DeleteObject",
+ ]
+ Effect = "Allow"
+ Resource = [
+ "arn:aws:s3:::aienv-v2-logs/*",
+ "arn:aws:s3:::aienv-v2-logs",
+ ]
+ Sid = "LokiRulerBucket"
+ },
+ ]
+ Version = "2012-10-17"
+ }
+ )
+ policy_id = "ANPA25LRSTTCPP4Q5DRGD"
+ tags = {}
+ tags_all = {}
+}
+
+
+# module.monitoring.module.oidc-role.data.aws_iam_policy_document.sts:
+data "aws_iam_policy_document" "sts" {
+ id = "3722905854"
+ json = jsonencode(
+ {
+ Statement = [
+ {
+ Action = "sts:AssumeRoleWithWebIdentity"
+ Condition = {
+ StringEquals = {
+ "oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21:aud" = "sts.amazonaws.com"
+ }
+ StringLike = {
+ "oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21:sub" = "system:serviceaccount:*:*"
+ }
+ }
+ Effect = "Allow"
+ Principal = {
+ Federated = "arn:aws:iam::750246862020:oidc-provider/oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21"
+ }
+ },
+ ]
+ Version = "2012-10-17"
+ }
+ )
+ minified_json = jsonencode(
+ {
+ Statement = [
+ {
+ Action = "sts:AssumeRoleWithWebIdentity"
+ Condition = {
+ StringEquals = {
+ "oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21:aud" = "sts.amazonaws.com"
+ }
+ StringLike = {
+ "oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21:sub" = "system:serviceaccount:*:*"
+ }
+ }
+ Effect = "Allow"
+ Principal = {
+ Federated = "arn:aws:iam::750246862020:oidc-provider/oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21"
+ }
+ },
+ ]
+ Version = "2012-10-17"
+ }
+ )
+ version = "2012-10-17"
+
+ statement {
+ actions = [
+ "sts:AssumeRoleWithWebIdentity",
+ ]
+ effect = "Allow"
+ not_actions = []
+ not_resources = []
+ resources = []
+ sid = [90mnull[0m[0m
+
+ condition {
+ test = "StringEquals"
+ values = [
+ "sts.amazonaws.com",
+ ]
+ variable = "oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21:aud"
+ }
+ condition {
+ test = "StringLike"
+ values = [
+ "system:serviceaccount:*:*",
+ ]
+ variable = "oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21:sub"
+ }
+
+ principals {
+ identifiers = [
+ "arn:aws:iam::750246862020:oidc-provider/oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21",
+ ]
+ type = "Federated"
+ }
+ }
+}
+
+# module.monitoring.module.oidc-role.aws_iam_role.this:
+resource "aws_iam_role" "this" {
+ arn = "arn:aws:iam::750246862020:role/aienv/us-east-2/observability-access-20260320095145755600000002"
+ assume_role_policy = jsonencode(
+ {
+ Statement = [
+ {
+ Action = "sts:AssumeRoleWithWebIdentity"
+ Condition = {
+ StringEquals = {
+ "oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21:aud" = "sts.amazonaws.com"
+ }
+ StringLike = {
+ "oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21:sub" = "system:serviceaccount:*:*"
+ }
+ }
+ Effect = "Allow"
+ Principal = {
+ Federated = "arn:aws:iam::750246862020:oidc-provider/oidc.eks.us-east-2.amazonaws.com/id/3FBD20508B38BD670EEBFDCD8B595F21"
+ }
+ },
+ ]
+ Version = "2012-10-17"
+ }
+ )
+ create_date = "2026-03-20T09:51:45Z"
+ description = [90mnull[0m[0m
+ force_detach_policies = false
+ id = "observability-access-20260320095145755600000002"
+ managed_policy_arns = [
+ "arn:aws:iam::750246862020:policy/aienv/us-east-2/ObservabilityAccess-us-east-2-20260320095145717300000001",
+ "arn:aws:iam::aws:policy/AmazonPrometheusQueryAccess",
+ "arn:aws:iam::aws:policy/AmazonPrometheusRemoteWriteAccess",
+ "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess",
+ ]
+ max_session_duration = 3600
+ name = "observability-access-20260320095145755600000002"
+ name_prefix = "observability-access-"
+ path = "/aienv/us-east-2/"
+ permissions_boundary = [90mnull[0m[0m
+ tags = {}
+ tags_all = {}
+ unique_id = "AROA25LRSTTCFQHCGNEKF"
+}
+
+# module.monitoring.module.oidc-role.aws_iam_role_policy_attachment.this["AmazonPrometheusQueryAccess"]:
+resource "aws_iam_role_policy_attachment" "this" {
+ id = "observability-access-20260320095145755600000002/arn:aws:iam::aws:policy/AmazonPrometheusQueryAccess"
+ policy_arn = "arn:aws:iam::aws:policy/AmazonPrometheusQueryAccess"
+ role = "observability-access-20260320095145755600000002"
+}
+
+# module.monitoring.module.oidc-role.aws_iam_role_policy_attachment.this["AmazonPrometheusRemoteWriteAccess"]:
+resource "aws_iam_role_policy_attachment" "this" {
+ id = "observability-access-20260320095145755600000002/arn:aws:iam::aws:policy/AmazonPrometheusRemoteWriteAccess"
+ policy_arn = "arn:aws:iam::aws:policy/AmazonPrometheusRemoteWriteAccess"
+ role = "observability-access-20260320095145755600000002"
+}
+
+# module.monitoring.module.oidc-role.aws_iam_role_policy_attachment.this["AmazonS3ReadOnlyAccess"]:
+resource "aws_iam_role_policy_attachment" "this" {
+ id = "observability-access-20260320095145755600000002/arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
+ policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
+ role = "observability-access-20260320095145755600000002"
+}
+
+# module.monitoring.module.oidc-role.aws_iam_role_policy_attachment.this["LokiS3Policy"]:
+resource "aws_iam_role_policy_attachment" "this" {
+ id = "observability-access-20260320095145755600000002/arn:aws:iam::750246862020:policy/aienv/us-east-2/ObservabilityAccess-us-east-2-20260320095145717300000001"
+ policy_arn = "arn:aws:iam::750246862020:policy/aienv/us-east-2/ObservabilityAccess-us-east-2-20260320095145717300000001"
+ role = "observability-access-20260320095145755600000002"
+}
diff --git a/infra/aws/us-east-2/k8s/monitoring/terragrunt.hcl b/infra/aws/us-east-2/k8s/monitoring/terragrunt.hcl
new file mode 100644
index 0000000..1dd0ff3
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/monitoring/terragrunt.hcl
@@ -0,0 +1,49 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks",
+ "../../rds",
+ "../../s3",
+ "../cert-manager",
+ "../lb-controller",
+ "../litellm",
+ "../coder-server",
+ "../other" # Deploy's auxillary manifests
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ chart_version=include.root.locals.CODER_OBSRV_CHART_VERSION
+ namespace=include.root.locals.CODER_OBSRV_CHART_NAMESPACE
+
+ domain_name=include.root.locals.GRAFANA_DOMAIN_NAME
+ vpc_name=include.root.locals.CODER_VPC_NAME
+ azs=include.root.locals.CODER_VPC_AZS
+ private_subnet_suffix=include.root.locals.CODER_PRIVATE_SUBNET_SUFFIX
+
+ loki_s3_bucket_name=include.root.locals.LOKI_S3_BUCKET_NAME
+ loki_s3_bucket_region=include.root.locals.LOKI_S3_BUCKET_REGION
+
+ coder_db_rds_id=include.root.locals.CODER_DB_RDS_ID
+ coder_db_username=include.root.locals.CODER_DB_USERNAME
+ coder_db_password=include.root.locals.CODER_DB_PASSWORD
+
+ grafana_db_rds_id=include.root.locals.GRAFANA_DB_RDS_ID
+ grafana_db_user=include.root.locals.GRAFANA_DB_USERNAME
+ grafana_db_password=include.root.locals.GRAFANA_DB_PASSWORD
+
+ grafana_auth_username=include.root.locals.GRAFANA_USERNAME
+ grafana_auth_password=include.root.locals.GRAFANA_PASSWORD
+
+ grafana_admin_username=include.root.locals.GRAFANA_ADMIN_USERNAME
+ grafana_admin_password=include.root.locals.GRAFANA_ADMIN_PASSWORD
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/monitoring/variables.tf b/infra/aws/us-east-2/k8s/monitoring/variables.tf
new file mode 100644
index 0000000..585f47a
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/monitoring/variables.tf
@@ -0,0 +1,144 @@
+##
+# Cluster Authentication Inputs
+##
+
+variable "cluster_name" {
+ type = string
+}
+
+variable "region" {
+ type = string
+}
+
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "namespace" {
+ type = string
+ default = "observability"
+}
+
+variable "chart_timeout" {
+ type = number
+ default = 360 # In Seconds
+}
+
+variable "chart_version" {
+ type = string
+ default = "0.7.0-rc.1"
+}
+
+##
+# Coder DB Inputs
+##
+
+variable "coder_db_rds_id" {
+ type = string
+}
+
+variable "coder_db_username" {
+ type = string
+ sensitive = true
+}
+
+variable "coder_db_password" {
+ description = "Coder's DB password"
+ type = string
+ sensitive = true
+}
+
+##
+# Coder Scraping Configs
+##
+
+variable "coderd_selector" {
+ type = string
+ default = "pod=~`coder.*`, pod!~`.*provisioner.*`"
+}
+
+variable "provisionerd_selector" {
+ type = string
+ default = "pod=~`coder-provisioner.*"
+}
+
+variable "coder_workspaces_selector" {
+ type = string
+ default = "namespace=`coder-workspaces`"
+}
+
+variable "coderd_namespace" {
+ type = string
+ default = "coder"
+}
+
+##
+# Loki Inputs
+##
+
+variable "loki_s3_bucket_name" {
+ type = string
+}
+
+variable "loki_s3_bucket_region" {
+ type = string
+}
+
+##
+# Grafana Inputs
+##
+
+variable "grafana_auth_username" {
+ description = "Grafana Endpoint username"
+ type = string
+ sensitive = true
+}
+
+variable "grafana_auth_password" {
+ description = "Grafana Endpoint password"
+ type = string
+ sensitive = true
+}
+
+variable "grafana_db_user" {
+ description = "Grafana DB username"
+ type = string
+ default = "grafana"
+}
+
+variable "grafana_db_password" {
+ description = "Grafana DB password"
+ type = string
+ sensitive = true
+}
+
+variable "grafana_db_rds_id" {
+ description = "Grafana RDS DB ID"
+ type = string
+}
+
+variable "grafana_admin_username" {
+ description = "Grafana Admin username"
+ type = string
+ sensitive = true
+}
+
+variable "grafana_admin_password" {
+ description = "Grafana Admin password"
+ type = string
+ sensitive = true
+}
+
+variable "domain_name" {
+ type = string
+}
+
+variable "vpc_name" {
+ type = string
+}
+
+variable "azs" {
+ type = list(string)
+ default = ["a", "b", "c"]
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/other/main.tf b/infra/aws/us-east-2/k8s/other/main.tf
new file mode 100644
index 0000000..ab2c641
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/other/main.tf
@@ -0,0 +1,773 @@
+provider "aws" {
+ region = var.region
+ profile = var.profile
+}
+
+data "aws_eks_cluster" "this" {
+ name = var.cluster_name
+}
+
+data "aws_eks_cluster_auth" "this" {
+ name = var.cluster_name
+}
+
+data "aws_iam_openid_connect_provider" "this" {
+ url = data.aws_eks_cluster.this.identity[0].oidc[0].issuer
+}
+
+data "aws_region" "this" {}
+
+data "aws_caller_identity" "this" {}
+
+data "aws_vpc" "this" {
+ tags = {
+ Name = var.vpc_name
+ }
+}
+
+data "aws_subnets" "private" {
+ filter {
+ name = "vpc-id"
+ values = [data.aws_vpc.this.id]
+ }
+
+ tags = {
+ Name = "*${var.private_subnet_suffix}*"
+ }
+}
+
+provider "helm" {
+ kubernetes = {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+ }
+}
+
+provider "kubernetes" {
+ host = data.aws_eks_cluster.this.endpoint
+ cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
+ token = data.aws_eks_cluster_auth.this.token
+}
+
+##
+# Manifest Setup Post Addon-Deployment
+# Includes auxiliary resources depending on CRDs
+##
+
+##
+# EBS CSI StorageClasses
+##
+
+resource "kubernetes_manifest" "gp3" {
+ manifest = {
+ apiVersion = "storage.k8s.io/v1"
+ kind = "StorageClass"
+ metadata = {
+ name = "gp3"
+ annotations = {
+ "storageclass.kubernetes.io/is-default-class" = "true"
+ }
+ }
+ provisioner = "ebs.csi.aws.com"
+ volumeBindingMode = "WaitForFirstConsumer"
+ allowVolumeExpansion = true
+ allowedTopologies = [{
+ matchLabelExpressions = [{
+ key = "topology.ebs.csi.aws.com/zone"
+ values = [for az in var.azs : "${data.aws_region.this.region}${az}"]
+ }]
+ }]
+ parameters = {
+ type = "gp3"
+ encrypted = "true"
+ }
+ }
+}
+
+resource "kubernetes_manifest" "automode-gp3" {
+ manifest = {
+ apiVersion = "storage.k8s.io/v1"
+ kind = "StorageClass"
+ metadata = {
+ name = "gp3-automode"
+ }
+ provisioner = "ebs.csi.eks.amazonaws.com"
+ volumeBindingMode = "WaitForFirstConsumer"
+ allowVolumeExpansion = true
+ allowedTopologies = [{
+ matchLabelExpressions = [{
+ key = "eks.amazonaws.com/compute-type"
+ values = ["auto"]
+ }]
+ }]
+ parameters = {
+ type = "gp3"
+ encrypted = "true"
+ }
+ }
+}
+
+##
+# NodeClass(es) for Coder (Server, Provisioner, & Workspaces)
+##
+
+data "kubernetes_service_account_v1" "kptr" {
+ metadata {
+ name = "node-role"
+ namespace = "karpenter"
+ }
+}
+
+data "kubernetes_service_account_v1" "auto" {
+ metadata {
+ name = "auto-mode-node-role"
+ namespace = "default"
+ }
+}
+
+data "aws_iam_roles" "auto-mode" {
+ name_regex = "${var.cluster_name}-eks-auto-*"
+ path_prefix = "/${data.aws_region.this.region}/"
+}
+
+locals {
+ nodeclass_configs = {
+ "platform" = {
+ api_version = "eks.amazonaws.com/v1"
+ kind = "NodeClass"
+ subnet_selector = [for subnet_id in data.aws_subnets.private.ids : { id = subnet_id }]
+ sg_selector = [{ id = data.aws_eks_cluster.this.vpc_config[0].cluster_security_group_id }]
+ network_policy = "DefaultAllow"
+ network_policy_event_logs = "Disabled"
+ snat_policy = "Disabled"
+ ephemeral_storage = {
+ iops = 3000
+ size = "80Gi"
+ throughput = 125
+ }
+ role = data.kubernetes_service_account_v1.auto.metadata[0].annotations["eks.amazonaws.com/role-name"]
+ tags = {
+ Name = "platform-node"
+ }
+ }
+ "coder-provisioner" = {
+ api_version = "eks.amazonaws.com/v1"
+ kind = "NodeClass"
+ subnet_selector = [for subnet_id in data.aws_subnets.private.ids : { id = subnet_id }]
+ sg_selector = [{ id = data.aws_eks_cluster.this.vpc_config[0].cluster_security_group_id }]
+ network_policy = "DefaultAllow"
+ network_policy_event_logs = "Disabled"
+ snat_policy = "Disabled"
+ ephemeral_storage = {
+ iops = 3000
+ size = "80Gi"
+ throughput = 125
+ }
+ role = data.kubernetes_service_account_v1.auto.metadata[0].annotations["eks.amazonaws.com/role-name"]
+ tags = {
+ Name = "coder-provisioner-node"
+ }
+ }
+ "coder-workspace" = {
+ api_version = "karpenter.k8s.aws/v1"
+ kind = "EC2NodeClass"
+ user_data = <<-EOT
+ MIME-Version: 1.0
+ Content-Type: multipart/mixed; boundary="//"
+
+ --//
+ Content-Type: application/node.eks.aws
+
+ apiVersion: node.eks.aws/v1alpha1
+ kind: NodeConfig
+ spec:
+ kubelet:
+ config:
+ registryPullQPS: 30
+ --//--
+ EOT
+ subnet_selector = [for subnet_id in data.aws_subnets.private.ids : { id = subnet_id }]
+ ami_selector = [{ alias = "al2023@latest" }]
+ sg_selector = [{ id = data.aws_eks_cluster.this.vpc_config[0].cluster_security_group_id }]
+ block_device_mappings = [{
+ deviceName = "/dev/xvda"
+ ebs = {
+ volumeSize = "200Gi"
+ volumeType = "gp3"
+ encrypted = false
+ deleteOnTermination = true
+ }
+ }]
+ role = data.kubernetes_service_account_v1.kptr.metadata[0].annotations["eks.amazonaws.com/role-name"]
+ tags = {
+ Name = "coder-workspace-node"
+ }
+ }
+ }
+}
+
+resource "kubernetes_manifest" "nodeclass" {
+
+ for_each = local.nodeclass_configs
+
+ manifest = {
+ apiVersion = each.value.api_version
+ kind = each.value.kind
+ metadata = {
+ name = each.key
+ }
+ spec = merge({
+ role = each.value.role
+ tags = try(each.value.tags, null)
+ subnetSelectorTerms = each.value.subnet_selector
+ securityGroupSelectorTerms = each.value.sg_selector
+ }, each.value.kind == "NodeClass" ? {
+ networkPolicy = each.value.network_policy
+ networkPolicyEventLogs = each.value.network_policy_event_logs
+ snatPolicy = each.value.snat_policy
+ ephemeralStorage = each.value.ephemeral_storage
+ } : null, each.value.kind == "EC2NodeClass" ? {
+ amiSelectorTerms = each.value.ami_selector
+ blockDeviceMappings = each.value.block_device_mappings
+ userData = each.value.user_data
+ }: null)
+ }
+}
+
+##
+# NodePool(s) for Coder (Server, Provisioner, & Workspaces)
+##
+
+locals {
+ nodepool_configs = {
+ "system" = {
+ disruption = {
+ consolidation_policy = "WhenEmptyOrUnderutilized"
+ consolidate_after = "72h"
+ }
+ instance_types = ["c6g.large", "c6g.xlarge", "c6g.2xlarge"]
+ node_class_ref = {
+ group = "eks.amazonaws.com"
+ kind = "NodeClass"
+ name = "platform"
+ }
+ taints = [{
+ key = "CriticalAddonsOnly"
+ value = "true"
+ effect = "NoSchedule"
+ }]
+ }
+ "observability-platform" = {
+ disruption = {
+ consolidation_policy = "WhenEmpty"
+ consolidate_after = "72h"
+ }
+ instance_types = ["c6g.large", "c6g.xlarge"]
+ node_class_ref = {
+ group = "eks.amazonaws.com"
+ kind = "NodeClass"
+ name = "platform"
+ }
+ taints = [{
+ key = "platform"
+ value = "observability-platform"
+ effect = "NoSchedule"
+ }]
+ }
+ # "grafana" = {
+ # disruption = {
+ # consolidation_policy = "WhenEmpty"
+ # consolidate_after = "72h"
+ # }
+ # instance_types = ["c6g.medium", "c6g.xlarge"]
+ # node_class_ref = {
+ # group = "eks.amazonaws.com"
+ # kind = "NodeClass"
+ # name = "platform"
+ # }
+ # taints = [{
+ # key = "platform"
+ # value = "grafana"
+ # effect = "NoSchedule"
+ # }]
+ # }
+ "alertmanager" = {
+ disruption = {
+ consolidation_policy = "WhenEmpty"
+ consolidate_after = "72h"
+ }
+ instance_types = ["c6g.medium", "c6g.xlarge"]
+ node_class_ref = {
+ group = "eks.amazonaws.com"
+ kind = "NodeClass"
+ name = "platform"
+ }
+ taints = [{
+ key = "platform"
+ value = "alertmanager"
+ effect = "NoSchedule"
+ }]
+ }
+ "prometheus" = {
+ disruption = {
+ consolidation_policy = "WhenEmpty"
+ consolidate_after = "72h"
+ }
+ instance_types = ["c6g.2xlarge"]
+ node_class_ref = {
+ group = "eks.amazonaws.com"
+ kind = "NodeClass"
+ name = "platform"
+ }
+ taints = [{
+ key = "platform"
+ value = "prometheus"
+ effect = "NoSchedule"
+ }]
+ }
+ "loki" = {
+ disruption = {
+ consolidation_policy = "WhenEmpty"
+ consolidate_after = "72h"
+ }
+ instance_types = ["c6g.medium", "c6g.xlarge"]
+ node_class_ref = {
+ group = "eks.amazonaws.com"
+ kind = "NodeClass"
+ name = "platform"
+ }
+ taints = [{
+ key = "platform"
+ value = "loki"
+ effect = "NoSchedule"
+ }]
+ }
+ "litellm" = {
+ disruption = {
+ consolidation_policy = "WhenEmptyOrUnderutilized"
+ consolidate_after = "8h"
+ }
+ instance_types = ["c6g.xlarge","c6g.2xlarge","c6g.4xlarge"]
+ node_class_ref = {
+ group = "eks.amazonaws.com"
+ kind = "NodeClass"
+ name = "platform"
+ }
+ taints = [{
+ key = "platform"
+ value = "litellm"
+ effect = "NoSchedule"
+ }]
+ }
+ "coder-server" = {
+ disruption = {
+ consolidation_policy = "WhenEmptyOrUnderutilized"
+ consolidate_after = "8h"
+ }
+ instance_types = ["c6g.xlarge","c6g.2xlarge","c6g.4xlarge"]
+ node_class_ref = {
+ group = "eks.amazonaws.com"
+ kind = "NodeClass"
+ name = "platform"
+ }
+ taints = [{
+ key = "platform"
+ value = "coder-server"
+ effect = "NoSchedule"
+ }]
+ }
+ "coder-provisioner" = {
+ disruption = {
+ consolidation_policy = "WhenEmptyOrUnderutilized"
+ consolidate_after = "8h"
+ budgets = [{
+ nodes = "100%"
+ }]
+ }
+ node_expires_after = "8h"
+ instance_types = ["c6g.large", "c6g.xlarge", "c6g.2xlarge", "c6g.4xlarge"]
+ node_class_ref = {
+ group = "eks.amazonaws.com"
+ kind = "NodeClass"
+ name = "coder-provisioner"
+ }
+ taints = [{
+ key = "coder"
+ value = "provisioner"
+ effect = "NoSchedule"
+ }]
+ }
+ "coder-workspace" = {
+ disruption = {
+ consolidation_policy = "WhenEmptyOrUnderutilized"
+ consolidate_after = "4h"
+ budgets = [{
+ nodes = "100%"
+ }]
+ }
+ instance_types = ["c6a.4xlarge","c6a.8xlarge"]
+ node_class_ref = {
+ group = "karpenter.k8s.aws"
+ kind = "EC2NodeClass"
+ name = "coder-workspace"
+ }
+ taints = []
+ }
+ "coder-workspace-static" = {
+ disruption = {
+ consolidation_policy = "WhenEmptyOrUnderutilized"
+ consolidate_after = "0s"
+ budgets = [{
+ nodes = "100%"
+ }]
+ }
+ replicas = 2
+ limits = {
+ nodes = 100
+ }
+ instance_types = ["c6a.4xlarge","c6a.8xlarge"]
+ node_class_ref = {
+ group = "karpenter.k8s.aws"
+ kind = "EC2NodeClass"
+ name = "coder-workspace"
+ }
+ taints = []
+ }
+ }
+}
+
+resource "kubernetes_manifest" "nodepool" {
+
+ depends_on = [kubernetes_manifest.nodeclass]
+ for_each = local.nodepool_configs
+
+ field_manager {
+ force_conflicts = true
+ }
+
+ wait {
+ condition {
+ type = "Ready"
+ status = "True"
+ }
+ }
+
+ timeouts {
+ create = "10m"
+ update = "10m"
+ delete = "30s"
+ }
+
+ manifest = {
+ apiVersion = "karpenter.sh/v1"
+ kind = "NodePool"
+ metadata = {
+ name = each.key
+ }
+ spec = merge(try(each.value.replicas, null) == null ? {
+ disruption = {
+ consolidationPolicy = try(each.value.disruption.consolidation_policy, "WhenEmptyOrUnderutilized")
+ consolidateAfter = try(each.value.disruption.consolidate_after, "0s")
+ budgets = try(each.value.disruption.budgets, [{ nodes = "10%" }])
+ }
+ } : null, try(each.value.replicas, null) != null ? {
+ replicas = each.value.replicas
+ } : null, try(each.value.limits, null) != null ? {
+ limits = each.value.limits
+ } : null, {
+ template = {
+ metadata = {
+ labels = {
+ "node.coder.io/instance" = "coder-v2"
+ "node.coder.io/managed-by" = "karpenter"
+ "node.coder.io/name" = "coder"
+ "node.coder.io/part-of" = "coder"
+ "node.coder.io/used-for" = each.key
+ }
+ }
+ spec = {
+ # https://docs.aws.amazon.com/eks/latest/userguide/automode.html#_features
+ # 21 days (504 hours i.e. ExpireAfter + TerminationGracePeriod) maximum lifetime for AutoMode
+ # https://karpenter.sh/docs/concepts/nodepools/
+ # "Never" works for Karpenter though.
+ expireAfter = try(each.value.node_expires_after, each.value.node_class_ref != "karpenter.k8s.aws" ? "480h" : "Never")
+ taints = each.value.taints == null ? [{
+ key = "dedicated"
+ value = each.key
+ effect = "NoSchedule"
+ }] : each.value.taints
+ requirements = [{
+ key = "kubernetes.io/arch"
+ operator = "In"
+ values = ["amd64", "arm64"]
+ }, {
+ key = "kubernetes.io/os"
+ operator = "In"
+ values = ["linux"]
+ }, {
+ key = "karpenter.sh/capacity-type"
+ operator = "In"
+ values = ["spot", "on-demand"]
+ }, {
+ key = "node.kubernetes.io/instance-type"
+ operator = "In"
+ values = each.value.instance_types
+ }]
+ nodeClassRef = each.value.node_class_ref
+ }
+ }
+ })
+ }
+
+ lifecycle {
+ ignore_changes = [ manifest.spec.replicas ]
+ }
+}
+
+##
+# Setup Cert-Manager ClusterIssuer
+##
+
+locals {
+ cf_secret_key = "key"
+}
+
+resource "kubernetes_secret_v1" "cf" {
+ metadata {
+ name = "cloudflare-token"
+ namespace = var.cloudflare_secret_namespace
+ annotations = {
+ "custom.kubernetes.secret/key" = local.cf_secret_key
+ "custom.kubernetes.secret/email" = var.cloudflare_email
+ }
+ }
+ data = {
+ (local.cf_secret_key) = var.cloudflare_api_token
+ }
+}
+
+resource "kubernetes_manifest" "issuer" {
+
+ field_manager {
+ force_conflicts = true
+ }
+
+ wait {
+ condition {
+ type = "Ready"
+ status = "True"
+ }
+ }
+
+ timeouts {
+ create = "10m"
+ update = "10m"
+ delete = "30s"
+ }
+
+ manifest = {
+ apiVersion = "cert-manager.io/v1"
+ kind = "ClusterIssuer"
+ metadata = {
+ labels = {}
+ name = var.cluster_issuer_name
+ }
+ spec = {
+ acme = {
+ privateKeySecretRef = {
+ name = var.cluster_issuer_priv_key_ref
+ }
+ server = "https://acme-v02.api.letsencrypt.org/directory"
+ solvers = [
+ {
+ dns01 = {
+ cloudflare = {
+ apiTokenSecretRef = {
+ key = kubernetes_secret_v1.cf.metadata[0].annotations["custom.kubernetes.secret/key"]
+ name = kubernetes_secret_v1.cf.metadata[0].name
+ }
+ email = kubernetes_secret_v1.cf.metadata[0].annotations["custom.kubernetes.secret/email"]
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+}
+
+##
+# Image Prefetch DaemonSet. Add images to warm new Coder nodes with workspace image.
+##
+
+locals {
+ prewarm_imgs = [
+ "codercom/enterprise-java:latest",
+ "codercom/enterprise-golang:latest",
+ "codercom/enterprise-node:latest",
+ "codercom/enterprise-base:ubuntu",
+ "public.ecr.aws/f7a1d7a4/coder-aienv:1.1.4"
+ ]
+}
+
+resource "kubernetes_daemon_set_v1" "img-fetch" {
+
+ for_each = toset([
+ "coder-workspace",
+ "coder-workspace-static"
+ ])
+
+ metadata {
+ name = "imgs-for-${each.key}"
+ namespace = "default"
+ labels = {
+ "app.kubernetes.io/name" = "img-fetch"
+ "app.kubernetes.io/part-of" = "coder-workspaces"
+ }
+ }
+
+ spec {
+ selector {
+ match_labels = {
+ "app.kubernetes.io/name" = "img-fetch"
+ }
+ }
+
+ template {
+ metadata {
+ labels = {
+ "app.kubernetes.io/name" = "img-fetch"
+ }
+ }
+
+ spec {
+
+ # Select's Coder-specific nodes. Do not pull on system nodes.
+ node_selector = kubernetes_manifest.nodepool[each.key].manifest.spec.template.metadata.labels
+
+ toleration {
+ key = "dedicated"
+ value = each.key
+ effect = "NoSchedule"
+ }
+
+ termination_grace_period_seconds = 5
+
+ dynamic "init_container" {
+ for_each = toset(local.prewarm_imgs)
+ content {
+ name = replace(init_container.value, "/\\W/", "-")
+ image = init_container.value
+ command = []
+ }
+ }
+
+ container {
+ name = "pause"
+ image = "registry.k8s.io/pause:3.9"
+
+ resources {
+ requests = {
+ cpu = "1m"
+ memory = "1Mi"
+ }
+ limits = {
+ cpu = "10m"
+ memory = "10Mi"
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+locals {
+ reg_mirror = "${data.aws_caller_identity.this.account_id}.dkr.ecr.${var.region}.amazonaws.com"
+ reg_suffix = {
+ "ghcr" = "ghcr.io"
+ "k8s" = "registry.k8s.io"
+ "quay" = "quay.io"
+ "docker-hub" = "index.docker.io"
+ "ecr-public" = "public.ecr.aws"
+ }
+}
+
+resource "kubernetes_manifest" "mutate_img_policy" {
+ manifest = {
+ apiVersion = "policies.kyverno.io/v1"
+ kind = "MutatingPolicy"
+ metadata = {
+ name = "mutate-ws-image"
+ }
+ spec = {
+ matchConstraints = {
+ matchPolicy = "Equivalent"
+ namespaceSelector = {
+ matchExpressions = [{
+ key = "kubernetes.io/metadata.name"
+ operator = "In"
+ values = [
+ "default",
+ "litellm",
+ "observability",
+ "ebs-controller",
+ "coder",
+ "coder-ws-demo",
+ "coder-ws-experiment",
+ "coder-ws"
+ ]
+ }]
+ }
+ objectSelector = {
+ matchExpressions = [
+ {
+ key = "app.kubernetes.io/name"
+ operator = "NotIn"
+ values = [
+ # "coder-provisioner",
+ # "coder"
+ "test"
+ ]
+ },
+ {
+ key = "app.kubernetes.io/managed-by"
+ operator = "NotIn"
+ values = [
+ # "Helm",
+ "test"
+ ]
+ }
+ ]
+ }
+ resourceRules = [
+ {
+ apiGroups = [""]
+ apiVersions = ["v1"]
+ operations = ["CREATE", "UPDATE"]
+ resources = ["pods"]
+ }
+ ]
+ }
+ mutations = [ for k in ["containers", "initContainers", "ephemeralContainers"] : {
+ patchType = "JSONPatch"
+ jsonPatch = {
+ expression = <<-EOT
+ object.spec.?${k}.orValue([]).map(c,
+ %{ for suffix,reg in local.reg_suffix ~}
+ image(c.image).registry() == "${reg}" ?
+ JSONPatch{
+ op: "replace",
+ path: "/spec/${k}/" + string(object.spec.?${k}.orValue([]).indexOf(c)) + "/image",
+ value: "${local.reg_mirror}" + "/" + "${suffix}" + "/" + string(image(c.image).repository()) + ":" + string(image(c.image).tag())
+ } :
+ %{ endfor ~}
+ null
+ ).filter(p, p != null)
+ EOT
+ }
+ } ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/other/terragrunt.hcl b/infra/aws/us-east-2/k8s/other/terragrunt.hcl
new file mode 100644
index 0000000..eced489
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/other/terragrunt.hcl
@@ -0,0 +1,25 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = [
+ "../../eks"
+ ]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+
+ vpc_name=include.root.locals.CODER_VPC_NAME
+ azs=include.root.locals.CODER_VPC_AZS
+ private_subnet_suffix=include.root.locals.CODER_PRIVATE_SUBNET_SUFFIX
+
+ cloudflare_api_token=include.root.locals.CF_TOKEN
+ cloudflare_secret_namespace=include.root.locals.CRTMGR_ADDON_NAMESPACE
+ cloudflare_email=include.root.locals.CF_EMAIL
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/k8s/other/variables.tf b/infra/aws/us-east-2/k8s/other/variables.tf
new file mode 100644
index 0000000..7872760
--- /dev/null
+++ b/infra/aws/us-east-2/k8s/other/variables.tf
@@ -0,0 +1,51 @@
+variable "profile" {
+ type = string
+ default = "default"
+}
+
+variable "region" {
+ type = string
+ default = "us-east-2"
+}
+
+variable "vpc_name" {
+ type = string
+}
+
+variable "private_subnet_suffix" {
+ type = string
+ default = "private"
+}
+
+variable "cluster_name" {
+ type = string
+}
+
+variable "cluster_issuer_name" {
+ type = string
+ default = "issuer"
+}
+
+variable "cluster_issuer_priv_key_ref" {
+ type = string
+ default = "issuer-account-key"
+}
+
+variable "azs" {
+ type = list(string)
+ default = ["a", "b", "c"]
+}
+
+variable "cloudflare_api_token" {
+ type = string
+ sensitive = true
+}
+
+variable "cloudflare_secret_namespace" {
+ type = string
+ default = "cert-manager"
+}
+
+variable "cloudflare_email" {
+ type = string
+}
diff --git a/infra/aws/us-east-2/rds/main.tf b/infra/aws/us-east-2/rds/main.tf
index cd2e8c7..1c68e1c 100644
--- a/infra/aws/us-east-2/rds/main.tf
+++ b/infra/aws/us-east-2/rds/main.tf
@@ -1,169 +1,68 @@
-terraform {
- required_version = ">= 1.0"
- required_providers {
- aws = {
- source = "hashicorp/aws"
- version = ">= 5.46"
- }
- }
- backend "s3" {}
-}
-
-variable "database_name" {
- description = "Database name"
- type = string
-}
-
-variable "master_username" {
- description = "Database root username"
- type = string
-}
-
-variable "master_password" {
- description = "Database root password"
- type = string
-}
-
-variable "litellm_username" {
- type = string
-}
-
-variable "litellm_password" {
- type = string
- sensitive = true
-}
-
-variable "grafana_username" {
- type = string
-}
-
-variable "grafana_password" {
- type = string
- sensitive = true
-}
-
-variable "name" {
- description = "Name of resource and tag prefix"
- type = string
-}
-
-variable "region" {
- description = "The aws region for database deployment"
- type = string
-}
-
-variable "private_subnet_ids" {
- description = "The deployed private subnet for the database"
- type = list(string)
-}
-
-variable "vpc_id" {
- description = "The deployed vpc id for the database"
- type = string
-}
-
-variable "allocated_storage" {
- description = "The allocated storage size in gb"
- default = "20"
- type = string
-}
-
-variable "engine_version" {
- description = "The version to deploy"
- default = "15.7"
- type = string
-}
-
-variable "instance_class" {
- description = "The size of db instance class to deploy"
- default = "db.m5.large"
- type = string
-}
-
-variable "profile" {
- type = string
-}
-
provider "aws" {
region = var.region
profile = var.profile
}
-# https://developer.hashicorp.com/terraform/tutorials/aws/aws-rds
-resource "aws_db_subnet_group" "db_subnet_group" {
- name = "${var.name}-db-subnet-group"
- subnet_ids = var.private_subnet_ids
-
+data "aws_vpc" "this" {
tags = {
- Name = "${var.name}-db-subnet-group"
+ Name = var.vpc_name
}
}
-resource "aws_db_instance" "db" {
- identifier = "${var.name}-db"
- instance_class = var.instance_class
- allocated_storage = var.allocated_storage
- engine = "postgres"
- engine_version = "15.12"
- # backup_retention_period = 7
- username = var.master_username
- password = var.master_password
- db_name = "coder"
- db_subnet_group_name = aws_db_subnet_group.db_subnet_group.name
- vpc_security_group_ids = [aws_security_group.allow-port-5432.id]
- publicly_accessible = false
- skip_final_snapshot = false
+data "aws_subnets" "private" {
+ filter {
+ name = "vpc-id"
+ values = [data.aws_vpc.this.id]
+ }
tags = {
- Name = "${var.name}-rds-db"
- }
- lifecycle {
- ignore_changes = [
- snapshot_identifier
- ]
+ Name = "*${var.private_subnet_suffix}*"
}
}
-resource "aws_db_instance" "litellm" {
- identifier = "litellm"
- instance_class = "db.m5.large"
- allocated_storage = 50
- engine = "postgres"
- engine_version = "15.12"
- username = var.litellm_username
- password = var.litellm_password
- db_name = "litellm"
- db_subnet_group_name = aws_db_subnet_group.db_subnet_group.name
- vpc_security_group_ids = [aws_security_group.allow-port-5432.id]
- publicly_accessible = false
- skip_final_snapshot = false
+data "aws_region" "this" {}
+data "aws_caller_identity" "this" {}
+
+# https://developer.hashicorp.com/terraform/tutorials/aws/aws-rds
+resource "aws_db_subnet_group" "db_subnet_group" {
+ name = var.db_subnet_group_name
+ subnet_ids = data.aws_subnets.private.ids
tags = {
- Name = "litellm"
+ Name = var.db_subnet_group_name
}
- lifecycle {
- ignore_changes = [
- snapshot_identifier
- ]
+}
+
+resource "random_id" "coder" {
+ keepers = {
+ id = var.coder_db_rds_id
}
+ byte_length = 8
}
-resource "aws_db_instance" "grafana" {
- identifier = "grafana"
- instance_class = "db.m5.large"
- allocated_storage = 50
- engine = "postgres"
- engine_version = "15.12"
- username = var.grafana_username
- password = var.grafana_password
- db_name = "grafana"
- db_subnet_group_name = aws_db_subnet_group.db_subnet_group.name
- vpc_security_group_ids = [aws_security_group.allow-port-5432.id]
- publicly_accessible = false
- skip_final_snapshot = false
+resource "aws_db_instance" "coder" {
+ identifier = var.coder_db_rds_id
+ instance_class = var.instance_class
+ storage_type = "gp2"
+ allocated_storage = 40
+ engine = "postgres"
+ engine_version = "15.17"
+ # backup_retention_period = 7
+ username = var.coder_username
+ db_name = "coder"
+ db_subnet_group_name = aws_db_subnet_group.db_subnet_group.name
+ vpc_security_group_ids = [aws_security_group.allow-port-5432.id]
+ publicly_accessible = false
+ snapshot_identifier = "aidemo-db-2-22-26-7-20-pm-pst"
+ final_snapshot_identifier = "snap-${random_id.coder.hex}"
+ skip_final_snapshot = false
+
+ iam_database_authentication_enabled = true
+ apply_immediately = true
+ manage_master_user_password = true
tags = {
- Name = "grafana"
+ Name = var.coder_db_rds_id
}
lifecycle {
ignore_changes = [
@@ -172,10 +71,6 @@ resource "aws_db_instance" "grafana" {
}
}
-data "aws_vpc" "this" {
- id = var.vpc_id
-}
-
resource "aws_vpc_security_group_ingress_rule" "postgres" {
security_group_id = aws_security_group.allow-port-5432.id
cidr_ipv4 = data.aws_vpc.this.cidr_block
@@ -191,31 +86,10 @@ resource "aws_vpc_security_group_egress_rule" "all" {
}
resource "aws_security_group" "allow-port-5432" {
- vpc_id = var.vpc_id
- name = "${var.name}-all-port-5432"
+ vpc_id = data.aws_vpc.this.id
+ name = "rds-traffic"
description = "security group for postgres all egress traffic"
tags = {
- Name = "${var.name}-postgres-allow-5432"
+ Name = "rds-traffic"
}
-}
-
-output "rds_port" {
- description = "Database instance port"
- value = aws_db_instance.db.port
-}
-
-output "rds_username" {
- description = "Database instance root username"
- value = aws_db_instance.db.username
-}
-
-output "rds_address" {
- description = "Database instance address"
- value = aws_db_instance.db.address
-}
-
-output "rds_password" {
- description = "Database instance root password"
- value = aws_db_instance.db.password
- sensitive = true
-}
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/rds/terragrunt.hcl b/infra/aws/us-east-2/rds/terragrunt.hcl
new file mode 100644
index 0000000..0c69426
--- /dev/null
+++ b/infra/aws/us-east-2/rds/terragrunt.hcl
@@ -0,0 +1,35 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+dependencies {
+ paths = ["../vpc"]
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ vpc_name=include.root.locals.CODER_VPC_NAME
+ db_subnet_group_name=include.root.locals.CODER_DB_SUBNET_GROUP_NAME
+ private_subnet_suffix=include.root.locals.CODER_PRIVATE_SUBNET_SUFFIX
+
+ coder_db_rds_id=include.root.locals.CODER_DB_RDS_ID
+ coder_db_name=include.root.locals.CODER_DB_NAME
+ coder_username=include.root.locals.CODER_DB_USERNAME
+ coder_password=include.root.locals.CODER_DB_PASSWORD
+
+
+ grafana_db_rds_id=include.root.locals.GRAFANA_DB_RDS_ID
+ grafana_db_name=include.root.locals.GRAFANA_DB_NAME
+ grafana_username=include.root.locals.GRAFANA_DB_USERNAME
+ grafana_password=include.root.locals.GRAFANA_DB_PASSWORD
+
+
+ litellm_db_rds_id=include.root.locals.LITELLM_DB_RDS_ID
+ litellm_db_name=include.root.locals.LITELLM_DB_NAME
+ litellm_username=include.root.locals.LITELLM_DB_USERNAME
+ litellm_password=include.root.locals.LITELLM_DB_ADMIN_PASSWORD
+
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/rds/variables.tf b/infra/aws/us-east-2/rds/variables.tf
new file mode 100644
index 0000000..224c7e8
--- /dev/null
+++ b/infra/aws/us-east-2/rds/variables.tf
@@ -0,0 +1,99 @@
+variable "profile" {
+ type = string
+}
+
+variable "region" {
+ description = "The aws region for database deployment"
+ type = string
+}
+
+variable "coder_db_rds_id" {
+ description = "Database name"
+ type = string
+}
+
+variable "coder_username" {
+ description = "Database root username"
+ type = string
+}
+
+variable "coder_password" {
+ description = "Database root password"
+ type = string
+}
+
+variable "coder_db_name" {
+ description = "Database name"
+ type = string
+}
+
+variable "litellm_db_rds_id" {
+ type = string
+ default = "litellm"
+}
+
+variable "litellm_db_name" {
+ type = string
+ default = "litellm"
+}
+
+variable "litellm_username" {
+ type = string
+}
+
+variable "litellm_password" {
+ type = string
+ sensitive = true
+}
+
+variable "grafana_db_rds_id" {
+ type = string
+ default = "grafana"
+}
+
+variable "grafana_db_name" {
+ type = string
+ default = "grafana"
+}
+
+variable "grafana_username" {
+ type = string
+}
+
+variable "grafana_password" {
+ type = string
+ sensitive = true
+}
+
+variable "db_subnet_group_name" {
+ description = "RDS DB Subnet Group Name"
+ type = string
+}
+
+variable "private_subnet_suffix" {
+ description = "The deployed private subnet's suffix for the database."
+ type = string
+}
+
+variable "vpc_name" {
+ description = "The deployed vpc id for the database"
+ type = string
+}
+
+variable "allocated_storage" {
+ description = "The allocated storage size in gb"
+ default = "20"
+ type = string
+}
+
+variable "engine_version" {
+ description = "The version to deploy"
+ default = "15.7"
+ type = string
+}
+
+variable "instance_class" {
+ description = "The size of db instance class to deploy"
+ default = "db.m5.large"
+ type = string
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/s3/main.tf b/infra/aws/us-east-2/s3/main.tf
index 5f6cb25..44f4c62 100644
--- a/infra/aws/us-east-2/s3/main.tf
+++ b/infra/aws/us-east-2/s3/main.tf
@@ -1,45 +1,8 @@
-terraform {
- required_version = ">= 1.0"
- required_providers {
- aws = {
- source = "hashicorp/aws"
- version = ">= 5.46"
- }
- }
- backend "s3" {}
-}
-
-##
-# AWS Provider Inputs
-##
-
-variable "region" {
- description = "The aws region for database deployment"
- type = string
-}
-
-variable "profile" {
- type = string
-}
-
provider "aws" {
region = var.region
profile = var.profile
}
-##
-# Loki Inputs
-##
-
-variable "loki_s3_bucket_name" {
- type = string
-}
-
-variable "loki_s3_bucket_tags" {
- type = map(string)
- default = {}
-}
-
resource "aws_s3_bucket" "loki" {
bucket = var.loki_s3_bucket_name
tags = var.loki_s3_bucket_tags
diff --git a/infra/aws/us-east-2/s3/terragrunt.hcl b/infra/aws/us-east-2/s3/terragrunt.hcl
new file mode 100644
index 0000000..9c65077
--- /dev/null
+++ b/infra/aws/us-east-2/s3/terragrunt.hcl
@@ -0,0 +1,11 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ loki_s3_bucket_name=include.root.locals.LOKI_S3_BUCKET_NAME
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/s3/variables.tf b/infra/aws/us-east-2/s3/variables.tf
new file mode 100644
index 0000000..22493d0
--- /dev/null
+++ b/infra/aws/us-east-2/s3/variables.tf
@@ -0,0 +1,25 @@
+##
+# AWS Provider Inputs
+##
+
+variable "region" {
+ description = "The aws region for database deployment"
+ type = string
+}
+
+variable "profile" {
+ type = string
+}
+
+##
+# Loki Inputs
+##
+
+variable "loki_s3_bucket_name" {
+ type = string
+}
+
+variable "loki_s3_bucket_tags" {
+ type = map(string)
+ default = {}
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/vpc/main.tf b/infra/aws/us-east-2/vpc/main.tf
index 7feac0d..cb89eea 100644
--- a/infra/aws/us-east-2/vpc/main.tf
+++ b/infra/aws/us-east-2/vpc/main.tf
@@ -1,218 +1,98 @@
-terraform {
- required_version = ">= 1.0"
- required_providers {
- aws = {
- source = "hashicorp/aws"
- version = ">= 5.46"
- }
- }
- backend "s3" {}
-}
-
-variable "region" {
- description = "The aws region for the vpc"
- type = string
-}
-
-variable "name" {
- description = "Name for created resources and tag prefix"
- type = string
-}
-
-variable "cluster_name" {
- description = "Cluster name"
- type = string
-}
-
-variable "profile" {
- type = string
-}
-
provider "aws" {
region = var.region
profile = var.profile
}
-module "vpc" {
- source = "terraform-aws-modules/vpc/aws"
- name = var.name
-
- enable_nat_gateway = false
- enable_dns_hostnames = true
-
- cidr = "10.0.0.0/16"
- azs = ["${var.region}a", "${var.region}b", "${var.region}c"]
- private_subnets = ["10.0.20.0/24", "10.0.21.0/24", "10.0.23.0/24"]
- public_subnets = ["10.0.10.0/24", "10.0.11.0/24", "10.0.12.0/24"]
-
- private_subnet_tags = {
- "kubernetes.io/cluster/${var.cluster_name}" : "owned"
+locals {
+ # Subnet Discovery - https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/deploy/subnet_discovery/#subnet-filtering
+ tags_lb_subnet_discovery = {
+ "kubernetes.io/cluster/${var.cluster_name}" = "owned"
}
-
- public_subnet_tags = {
- "kubernetes.io/role/elb" : 1
- "kubernetes.io/cluster/${var.cluster_name}" : "owned"
+ # Internet-Facing LB - https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/deploy/subnet_discovery/#subnet-role-tag
+ tags_public_lb = {
+ "kubernetes.io/role/elb" = 1
}
-}
-
-module "fck-nat" {
- source = "RaJiska/fck-nat/aws"
-
- name = "${var.name}-fck-nat"
- vpc_id = module.vpc.vpc_id
- subnet_id = module.vpc.public_subnets[0]
- ha_mode = true # Enables high-availability mode
- # eip_allocation_ids = ["eipalloc-abc1234"] # Allocation ID of an existing EIP
- use_cloudwatch_agent = true # Enables Cloudwatch agent and have metrics reported
-
- update_route_tables = true
- route_tables_ids = {
- "route-table1" = module.vpc.private_route_table_ids[0]
- "route-table2" = module.vpc.private_route_table_ids[1]
- "route-table3" = module.vpc.private_route_table_ids[2]
+ tags_public_subnet = merge(
+ local.tags_lb_subnet_discovery,
+ local.tags_public_lb
+ )
+ tags_private_subnet = {
+ "karpenter.sh/discovery" = "${var.cluster_name}"
}
+ # Use variable to configure AZ just in case 1 AZ isn't accessible
+ # https://repost.aws/questions/QUgdQev4KETKG_Bwev9tMtRQ/is-it-possible-to-enable-3rd-availability-zone-in-us-west-1#AN9eAH55FwTC-NSSp-FjIfTQ
+ availability_zones = [for az in var.azs : "${var.region}${az}"]
+ public_subnet_cidrs = [for index, _ in var.azs : cidrsubnet(var.vpc_cidr, 8, index + 1)]
+ private_subnet_cidrs = [for index, _ in var.azs : cidrsubnet(var.vpc_cidr, 2, index + 1)]
}
+module "vpc" {
+ source = "terraform-aws-modules/vpc/aws"
+ version = "~> 6.6.0"
-##
-# Coder Subnets
-##
+ name = var.vpc_name
-locals {
- global_subnet_tags = {
- "kubernetes.io/cluster/${var.cluster_name}" = "owned"
- }
- coder_server_tags = merge(local.global_subnet_tags, {
- "subnet.coder.io/coder-server/owned-by" = var.cluster_name
- })
- coder_provisioner_tags = merge(local.global_subnet_tags, {
- "subnet.coder.io/coder-provisioner/owned-by" = var.cluster_name
- })
- coder_ws_tags = merge(local.global_subnet_tags, {
- "subnet.coder.io/coder-ws-all/owned-by" = var.cluster_name
- })
-}
+ enable_nat_gateway = false
+ enable_dns_hostnames = true
-module "coder-ws-subnet1-az3" {
- source = "../../../../modules/network/subnet/private"
- name = var.name
- vpc_id = module.vpc.vpc_id
- eni_id = module.fck-nat.eni_id
- cidr_block = "10.0.16.0/22"
- availability_zone = "${var.region}c"
- subnet_tags = local.coder_ws_tags
-}
+ cidr = var.vpc_cidr
+ azs = local.availability_zones
-module "coder-ws-subnet2-az3" {
- source = "../../../../modules/network/subnet/private"
- name = var.name
- vpc_id = module.vpc.vpc_id
- eni_id = module.fck-nat.eni_id
- cidr_block = "10.0.24.0/22"
- availability_zone = "${var.region}c"
- subnet_tags = local.coder_ws_tags
-}
+ public_subnet_suffix = var.public_subnet_suffix
+ public_subnets = [for index, _ in var.azs : local.public_subnet_cidrs[index]]
+ public_subnet_tags = local.tags_public_subnet
-module "coder-ws-subnet3-az3" {
- source = "../../../../modules/network/subnet/private"
- name = var.name
- vpc_id = module.vpc.vpc_id
- eni_id = module.fck-nat.eni_id
- cidr_block = "10.0.28.0/22"
- availability_zone = "${var.region}c"
- subnet_tags = local.coder_ws_tags
-}
+ private_subnet_suffix = var.private_subnet_suffix
+ private_subnets = [for index, _ in var.azs : local.private_subnet_cidrs[index]]
+ private_subnet_tags = local.tags_private_subnet
-module "coder-server-subnet1-az1" {
- source = "../../../../modules/network/subnet/private"
- name = var.name
- vpc_id = module.vpc.vpc_id
- eni_id = module.fck-nat.eni_id
- cidr_block = "10.0.32.0/22"
- availability_zone = "${var.region}c"
- subnet_tags = local.coder_server_tags
+ tags = {}
}
-module "coder-server-subnet1-az2" {
- source = "../../../../modules/network/subnet/private"
- name = var.name
- vpc_id = module.vpc.vpc_id
- eni_id = module.fck-nat.eni_id
- cidr_block = "10.0.48.0/22"
- availability_zone = "${var.region}b"
- subnet_tags = local.coder_server_tags
-}
-
-module "coder-prov-subnet1-az2" {
- source = "../../../../modules/network/subnet/private"
- name = var.name
- vpc_id = module.vpc.vpc_id
- eni_id = module.fck-nat.eni_id
- cidr_block = "10.0.36.0/22"
- availability_zone = "${var.region}c"
- subnet_tags = local.coder_provisioner_tags
-}
-
-##
-# Kubernetes System Subnets
-##
-
locals {
- system_subnet_tags = merge(local.global_subnet_tags, {
- "subnet.amazonaws.io/system/owned-by" = var.name
- })
+ endpoints = toset(["ecr.dkr", "ecr.api", "s3"])
}
-module "system-subnet1-az1" {
- source = "../../../../modules/network/subnet/private"
- name = var.name
- vpc_id = module.vpc.vpc_id
- eni_id = module.fck-nat.eni_id
- cidr_block = "10.0.40.0/22"
- availability_zone = "${var.region}a"
- subnet_tags = local.system_subnet_tags
+resource "aws_security_group" "svc-ep" {
+ name_prefix = "${var.vpc_name}-vpc-ep"
+ description = "Associated to ECR/S3 VPC Endpoints"
+ vpc_id = module.vpc.vpc_id
}
-module "system-subnet1-az2" {
- source = "../../../../modules/network/subnet/private"
- name = var.name
- vpc_id = module.vpc.vpc_id
- eni_id = module.fck-nat.eni_id
- cidr_block = "10.0.44.0/22"
- availability_zone = "${var.region}b"
- subnet_tags = local.system_subnet_tags
+resource "aws_vpc_security_group_ingress_rule" "https" {
+ security_group_id = aws_security_group.svc-ep.id
+ cidr_ipv4 = module.vpc.vpc_cidr_block
+ from_port = 443
+ to_port = 443
+ ip_protocol = "tcp"
}
-output "region" {
- value = var.region
- description = "VPC Region"
-}
+resource "aws_vpc_endpoint" "svc" {
-output "vpc_id" {
- value = module.vpc.vpc_id
- description = "The VPC ID"
-}
+ for_each = local.endpoints
-output "public_subnet_ids" {
- value = module.vpc.public_subnets
- description = "List of public subnet IDs"
+ vpc_id = module.vpc.vpc_id
+ private_dns_enabled = each.value != "s3"
+ service_name = "com.amazonaws.${var.region}.${each.value}"
+ vpc_endpoint_type = each.value != "s3" ? "Interface" : "Gateway"
+ security_group_ids = each.value != "s3" ? [ aws_security_group.svc-ep.id ] : null
+ subnet_ids = each.value != "s3" ? module.vpc.private_subnets : null
+ route_table_ids = each.value == "s3" ? module.vpc.private_route_table_ids : null
}
-output "private_subnet_ids" {
- value = concat(module.vpc.private_subnets, [])
- description = "List of private subnet IDs"
-}
+module "nat" {
+ source = "RaJiska/fck-nat/aws"
+ version = "~> 1.4.0"
+
+ name = var.nat_name
+ vpc_id = module.vpc.vpc_id
+ subnet_id = module.vpc.public_subnets[0]
+ ha_mode = true # Enables high-availability mode
+ use_cloudwatch_agent = true # Enables Cloudwatch agent and have metrics reported
+ instance_type = "c7gn.medium"
-output "other_private_subnet_ids" {
- value = concat([
- module.coder-ws-subnet1-az3.subnet_id,
- module.coder-ws-subnet2-az3.subnet_id,
- module.coder-ws-subnet3-az3.subnet_id,
- module.coder-server-subnet1-az1.subnet_id,
- module.coder-server-subnet1-az2.subnet_id,
- module.coder-prov-subnet1-az2.subnet_id,
- module.system-subnet1-az1.subnet_id,
- module.system-subnet1-az2.subnet_id,
- ])
- description = "List of other private subnet IDs for Coder"
+ update_route_tables = true
+ route_tables_ids = {
+ for index, _ in var.azs : "rtb-${index}" => module.vpc.private_route_table_ids[index]
+ }
}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/vpc/terragrunt.hcl b/infra/aws/us-east-2/vpc/terragrunt.hcl
new file mode 100644
index 0000000..235f81a
--- /dev/null
+++ b/infra/aws/us-east-2/vpc/terragrunt.hcl
@@ -0,0 +1,17 @@
+include "root" {
+ path = find_in_parent_folders("root.hcl")
+ expose = true
+}
+
+inputs = {
+ profile=include.root.locals.CODER_AWS_PROFILE
+ region=include.root.locals.CODER_AWS_REGION
+
+ cluster_name=include.root.locals.CODER_CLUSTER_NAME
+ vpc_name=include.root.locals.CODER_VPC_NAME
+ vpc_cidr=include.root.locals.CODER_VPC_CIDR
+ azs=include.root.locals.CODER_VPC_AZS
+ nat_name=include.root.locals.CODER_VPC_NAT_NAME
+ public_subnet_suffix=include.root.locals.CODER_PUBLIC_SUBNET_SUFFIX
+ private_subnet_suffix=include.root.locals.CODER_PRIVATE_SUBNET_SUFFIX
+}
\ No newline at end of file
diff --git a/infra/aws/us-east-2/vpc/variables.tf b/infra/aws/us-east-2/vpc/variables.tf
new file mode 100644
index 0000000..6ff8d7d
--- /dev/null
+++ b/infra/aws/us-east-2/vpc/variables.tf
@@ -0,0 +1,48 @@
+variable "profile" {
+ type = string
+}
+
+variable "region" {
+ description = "The aws region for the vpc"
+ type = string
+}
+
+variable "cluster_name" {
+ description = "Cluster name"
+ type = string
+}
+
+variable "azs" {
+ type = list(string)
+ default = ["a", "b", "c"]
+
+ validation {
+ condition = length(var.azs) <= 3
+ error_message = "There should only be at most 3 availability zones specified."
+ }
+}
+
+variable "vpc_name" {
+ description = "Name for created resources and tag prefix"
+ type = string
+}
+
+variable "vpc_cidr" {
+ type = string
+ default = "10.0.0.0/16"
+}
+
+variable "private_subnet_suffix" {
+ type = string
+ default = "private"
+}
+
+variable "public_subnet_suffix" {
+ type = string
+ default = "public"
+}
+
+variable "nat_name" {
+ description = "Name for created resources and tag prefix"
+ type = string
+}
\ No newline at end of file
diff --git a/infra/aws/us-west-2/eks/main.tf b/infra/aws/us-west-2/eks/main.tf
deleted file mode 100644
index c6e3ec8..0000000
--- a/infra/aws/us-west-2/eks/main.tf
+++ /dev/null
@@ -1,202 +0,0 @@
-terraform {
- required_version = ">= 1.0"
- required_providers {
- aws = {
- source = "hashicorp/aws"
- version = ">= 5.100.0"
- }
- }
- backend "s3" {}
-}
-
-variable "name" {
- description = "The resource name and tag prefix"
- type = string
-}
-
-variable "profile" {
- type = string
-}
-
-variable "region" {
- description = "The aws region to deploy eks cluster"
- type = string
-}
-
-variable "cluster_version" {
- description = "The eks version"
- type = string
-}
-
-variable "cluster_instance_type" {
- description = "EKS Instance Size/Type"
- default = "t3.xlarge"
- type = string
-}
-
-provider "aws" {
- region = var.region
- profile = var.profile
-}
-
-##
-# Cluster Infrastructure
-##
-
-locals {
- system_subnet_tags = {
- "subnet.amazonaws.io/system/owned-by" = var.name
- }
- provisioner_subnet_tags = {
- "subnet.amazonaws.io/coder-provisioner/owned-by" = var.name
- }
- ws_all_subnet_tags = {
- "subnet.amazonaws.io/coder-ws-all/owned-by" = var.name
- }
- system_sg_tags = {
- "subnet.amazonaws.io/system/owned-by" = var.name
- }
- provisioner_sg_tags = {
- "sg.amazonaws.io/coder-provisioner/owned-by" = var.name
- }
- ws_all_sg_tags = {
- "sg.amazonaws.io/coder-ws-all/owned-by" = var.name
- }
- cluster_asg_node_labels = {
- "node.amazonaws.io/managed-by" = "asg"
- }
-}
-
-data "aws_region" "this" {}
-
-module "eks-network" {
- source = "../../../../modules/network/eks-vpc"
-
- name = var.name
- vpc_cidr_block = "10.0.0.0/16"
- public_subnets = {
- "system0" = {
- cidr_block = "10.0.10.0/24"
- availability_zone = "${data.aws_region.this.name}a"
- map_public_ip_on_launch = true
- private_dns_hostname_type_on_launch = "ip-name"
- }
- "system1" = {
- cidr_block = "10.0.11.0/24"
- availability_zone = "${data.aws_region.this.name}b"
- map_public_ip_on_launch = true
- private_dns_hostname_type_on_launch = "ip-name"
- }
- }
- private_subnets = {
- "system0" = {
- cidr_block = "10.0.20.0/24"
- availability_zone = "${data.aws_region.this.name}a"
- private_dns_hostname_type_on_launch = "ip-name"
- tags = local.system_subnet_tags
- }
- "system1" = {
- cidr_block = "10.0.21.0/24"
- availability_zone = "${data.aws_region.this.name}b"
- private_dns_hostname_type_on_launch = "ip-name"
- tags = local.system_subnet_tags
- }
- "provisioner" = {
- cidr_block = "10.0.22.0/24"
- availability_zone = "${data.aws_region.this.name}a"
- map_public_ip_on_launch = true
- private_dns_hostname_type_on_launch = "ip-name"
- tags = local.provisioner_subnet_tags
- }
- "ws-all" = {
- cidr_block = "10.0.16.0/22"
- availability_zone = "${data.aws_region.this.name}b"
- map_public_ip_on_launch = true
- private_dns_hostname_type_on_launch = "ip-name"
- tags = local.ws_all_subnet_tags
- }
- }
-}
-
-data "aws_iam_policy_document" "sts" {
- statement {
- effect = "Allow"
- actions = ["sts:*"]
- resources = ["*"]
- }
-}
-
-resource "aws_iam_policy" "sts" {
- name_prefix = "sts"
- path = "/"
- description = "Assume Role Policy"
- policy = data.aws_iam_policy_document.sts.json
-}
-
-module "cluster" {
- source = "terraform-aws-modules/eks/aws"
- version = "~> 20.0"
-
- vpc_id = module.eks-network.vpc_id
- subnet_ids = toset(concat(concat(
- module.eks-network.public_subnet_ids,
- module.eks-network.private_subnet_ids),
- module.eks-network.intra_subnet_ids
- ))
-
- cluster_name = var.name
- cluster_version = var.cluster_version
- cluster_endpoint_public_access = true
- cluster_endpoint_private_access = true
-
- create_cluster_security_group = true
- create_node_security_group = true
- node_security_group_tags = merge(
- local.system_sg_tags,
- merge(local.provisioner_sg_tags, local.ws_all_sg_tags)
- )
- create_iam_role = true
- cluster_addons = {
- coredns = {
- most_recent = true
- }
- kube-proxy = {
- most_recent = true
- }
- vpc-cni = {
- most_recent = true
- configuration_values = jsonencode({
- enableNetworkPolicy = "true"
- nodeAgent = {
- enablePolicyEventLogs = "true"
- }
- })
- }
- }
-
- # Disable KMS, speed up cluster creation times. Enable if encryption is necessary.
- attach_cluster_encryption_policy = false
- create_kms_key = false
- cluster_encryption_config = {}
- enable_cluster_creator_admin_permissions = true
- enable_irsa = true
-
- eks_managed_node_groups = {
- system = {
- min_size = 0
- max_size = 10
- desired_size = 0 # Cant be modified after creation. Override from AWS Console
- labels = local.cluster_asg_node_labels
-
- instance_types = [var.cluster_instance_type]
- capacity_type = "ON_DEMAND"
- iam_role_additional_policies = {
- AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
- STSAssumeRole = aws_iam_policy.sts.arn
- }
-
- # System Nodes should not be public
- subnet_ids = module.eks-network.private_subnet_ids
- }
- }
-}
\ No newline at end of file
diff --git a/infra/aws/us-west-2/k8s/cert-manager/main.tf b/infra/aws/us-west-2/k8s/cert-manager/main.tf
deleted file mode 100644
index 9f242db..0000000
--- a/infra/aws/us-west-2/k8s/cert-manager/main.tf
+++ /dev/null
@@ -1,90 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = "3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "addon_namespace" {
- type = string
- default = "cert-manager"
-}
-
-variable "addon_version" {
- type = string
- default = "1.13.3"
-}
-
-variable "cloudflare_api_token" {
- type = string
- sensitive = true
-}
-
-variable "cloudflare_email" {
- type = string
- sensitive = true
-}
-
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
-
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
-
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
-
-provider "helm" {
- kubernetes = {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
- }
-}
-
-provider "kubernetes" {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
-}
-
-module "cert-manager" {
- source = "../../../../../modules/k8s/bootstrap/cert-manager"
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- namespace = var.addon_namespace
- helm_version = var.addon_version
- cloudflare_token_secret = var.cloudflare_api_token
- cloudflare_token_secret_email = var.cloudflare_email
-}
\ No newline at end of file
diff --git a/infra/aws/us-west-2/k8s/coder-proxy/main.tf b/infra/aws/us-west-2/k8s/coder-proxy/main.tf
deleted file mode 100644
index 3b5cf7b..0000000
--- a/infra/aws/us-west-2/k8s/coder-proxy/main.tf
+++ /dev/null
@@ -1,211 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- coderd = {
- source = "coder/coderd"
- }
- acme = {
- source = "vancluever/acme"
- }
- tls = {
- source = "hashicorp/tls"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "acme_server_url" {
- type = string
- default = "https://acme-v02.api.letsencrypt.org/directory"
-}
-
-variable "acme_registration_email" {
- type = string
-}
-
-variable "addon_version" {
- type = string
- default = "2.25.1"
-}
-
-variable "coder_proxy_name" {
- type = string
-}
-
-variable "coder_proxy_display_name" {
- type = string
-}
-
-variable "coder_proxy_icon" {
- type = string
-}
-
-variable "coder_access_url" {
- type = string
- # sensitive = true
-}
-
-variable "coder_proxy_url" {
- type = string
- # sensitive = true
-}
-
-variable "coder_proxy_wildcard_url" {
- type = string
- # sensitive = true
-}
-
-variable "coder_token" {
- type = string
- sensitive = true
-}
-
-variable "image_repo" {
- type = string
- sensitive = true
-}
-
-variable "image_tag" {
- type = string
- default = "latest"
-}
-
-variable "kubernetes_ssl_secret_name" {
- type = string
-}
-
-variable "kubernetes_create_ssl_secret" {
- type = bool
- default = true
-}
-
-variable "cloudflare_api_token" {
- type = string
- sensitive = true
-}
-
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
-
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
-
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
-
-provider "helm" {
- kubernetes = {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
- }
-}
-
-provider "kubernetes" {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
-}
-
-provider "coderd" {
- url = var.coder_access_url
- token = var.coder_token
-}
-
-provider "acme" {
- server_url = var.acme_server_url
-}
-
-module "coder-proxy" {
- source = "../../../../../modules/k8s/bootstrap/coder-proxy"
-
- namespace = "coder-proxy"
- acme_registration_email = var.acme_registration_email
- acme_days_until_renewal = 90
- replica_count = 2
- helm_version = var.addon_version
- image_repo = var.image_repo
- image_tag = var.image_tag
- primary_access_url = var.coder_access_url
- proxy_access_url = var.coder_proxy_url
- proxy_wildcard_url = var.coder_proxy_wildcard_url
- coder_proxy_name = var.coder_proxy_name
- coder_proxy_display_name = var.coder_proxy_display_name
- coder_proxy_icon = var.coder_proxy_icon
- proxy_token_config = {
- name = "coder-proxy"
- }
- cloudflare_api_token = var.cloudflare_api_token
- ssl_cert_config = {
- name = var.kubernetes_ssl_secret_name
- create_secret = var.kubernetes_create_ssl_secret
- }
- service_annotations = {
- "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "instance"
- "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internet-facing"
- "service.beta.kubernetes.io/aws-load-balancer-attributes" = "deletion_protection.enabled=true"
- }
- node_selector = {
- "node.coder.io/managed-by" = "karpenter"
- "node.coder.io/used-for" = "coder-proxy"
- }
- tolerations = [{
- key = "dedicated"
- operator = "Equal"
- value = "coder-proxy"
- effect = "NoSchedule"
- }]
- topology_spread_constraints = [{
- max_skew = 1
- topology_key = "kubernetes.io/hostname"
- when_unsatisfiable = "ScheduleAnyway"
- label_selector = {
- match_labels = {
- "app.kubernetes.io/name" = "coder"
- "app.kubernetes.io/part-of" = "coder"
- }
- }
- match_label_keys = [
- "app.kubernetes.io/instance"
- ]
- }]
- pod_anti_affinity_preferred_during_scheduling_ignored_during_execution = [{
- weight = 100
- pod_affinity_term = {
- label_selector = {
- match_labels = {
- "app.kubernetes.io/instance" = "coder-v2"
- "app.kubernetes.io/name" = "coder"
- "app.kubernetes.io/part-of" = "coder"
- }
- }
- topology_key = "kubernetes.io/hostname"
- }
- }]
-}
\ No newline at end of file
diff --git a/infra/aws/us-west-2/k8s/coder-ws/main.tf b/infra/aws/us-west-2/k8s/coder-ws/main.tf
deleted file mode 100644
index 31c49ac..0000000
--- a/infra/aws/us-west-2/k8s/coder-ws/main.tf
+++ /dev/null
@@ -1,209 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- coderd = {
- source = "coder/coderd"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "provisioner_addon_version" {
- type = string
- default = "2.23.0"
-}
-
-variable "logstream_addon_version" {
- type = string
- default = "0.0.11"
-}
-
-variable "coder_access_url" {
- type = string
- sensitive = true
-}
-
-variable "coder_token" {
- type = string
- sensitive = true
-}
-
-variable "image_repo" {
- type = string
- sensitive = true
-}
-
-variable "image_tag" {
- type = string
- default = "latest"
-}
-
-variable "rotate_key_image_repo" {
- type = string
- sensitive = true
-}
-
-variable "rotate_key_image_tag" {
- type = string
- sensitive = true
-}
-
-variable "aws_secret_id" {
- type = string
- sensitive = true
-}
-
-variable "aws_secret_region" {
- type = string
- sensitive = true
-}
-
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
-
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
-
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
-
-provider "helm" {
- kubernetes = {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
- }
-}
-
-provider "kubernetes" {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
-}
-
-provider "coderd" {
- url = var.coder_access_url
- token = var.coder_token
-}
-
-locals {
- service_account_labels = {
- "app.kubernetes.io/instance" = "coder-provisioner"
- "app.kubernetes.io/name" = "coder-provisioner"
- "app.kubernetes.io/part-of" = "coder-provisioner"
- }
- node_selector = {
- "node.coder.io/managed-by" = "karpenter"
- "node.coder.io/used-for" = "coder-provisioner"
- }
- tolerations = [{
- key = "dedicated"
- operator = "Equal"
- value = "coder-provisioner"
- effect = "NoSchedule"
- }]
-}
-
-module "default-ws" {
- source = "../../../../../modules/k8s/bootstrap/coder-provisioner"
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- coder_organization_name = "coder"
-
- namespace = "coder-ws"
- image_repo = var.image_repo
- image_tag = var.image_tag
- ws_service_account_name = "coder-ws"
- ws_service_account_labels = local.service_account_labels
- provisioner_service_account_name = "coder"
- replica_count = 6
- primary_access_url = var.coder_access_url
- env_vars = {
- CODER_PROMETHEUS_ENABLE = "false"
- CODER_PROMETHEUS_COLLECT_AGENT_STATS = "false"
- CODER_PROMETHEUS_COLLECT_DB_METRICS = "false"
- }
- node_selector = local.node_selector
- tolerations = local.tolerations
-}
-
-module "experiment-ws" {
- source = "../../../../../modules/k8s/bootstrap/coder-provisioner"
-
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- coder_organization_name = "experiment"
-
- namespace = "coder-ws-experiment"
- image_repo = var.image_repo
- image_tag = var.image_tag
- ws_service_account_name = "coder-ws-experiment"
- ws_service_account_labels = local.service_account_labels
- provisioner_service_account_name = "coder"
- replica_count = 2
- primary_access_url = var.coder_access_url
- env_vars = {
- CODER_PROMETHEUS_ENABLE = "false"
- CODER_PROMETHEUS_COLLECT_AGENT_STATS = "false"
- CODER_PROMETHEUS_COLLECT_DB_METRICS = "false"
- }
- node_selector = local.node_selector
- tolerations = local.tolerations
-}
-
-module "demo-ws" {
- source = "../../../../../modules/k8s/bootstrap/coder-provisioner"
-
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- coder_organization_name = "demo"
-
- namespace = "coder-ws-demo"
- image_repo = var.image_repo
- image_tag = var.image_tag
- ws_service_account_name = "coder-ws-demo"
- ws_service_account_labels = local.service_account_labels
- provisioner_service_account_name = "coder"
- replica_count = 2
- primary_access_url = var.coder_access_url
- env_vars = {
- CODER_PROMETHEUS_ENABLE = "false"
- CODER_PROMETHEUS_COLLECT_AGENT_STATS = "false"
- CODER_PROMETHEUS_COLLECT_DB_METRICS = "false"
- }
- node_selector = local.node_selector
- tolerations = local.tolerations
-}
\ No newline at end of file
diff --git a/infra/aws/us-west-2/k8s/ebs-controller/main.tf b/infra/aws/us-west-2/k8s/ebs-controller/main.tf
deleted file mode 100644
index e882e1d..0000000
--- a/infra/aws/us-west-2/k8s/ebs-controller/main.tf
+++ /dev/null
@@ -1,72 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "addon_version" {
- type = string
- default = "2.22.1"
-}
-
-variable "addon_namespace" {
- type = string
- default = "default"
-}
-
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
-
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
-
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
-
-provider "helm" {
- kubernetes = {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
- }
-}
-
-module "ebs-controller" {
- source = "../../../../../modules/k8s/bootstrap/ebs-controller"
- cluster_name = data.aws_eks_cluster.this.name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- namespace = var.addon_namespace
- chart_version = var.addon_version
-}
\ No newline at end of file
diff --git a/infra/aws/us-west-2/k8s/karpenter/main.tf b/infra/aws/us-west-2/k8s/karpenter/main.tf
deleted file mode 100644
index 3ec5d48..0000000
--- a/infra/aws/us-west-2/k8s/karpenter/main.tf
+++ /dev/null
@@ -1,199 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "addon_version" {
- type = string
-}
-
-variable "addon_namespace" {
- type = string
- default = "default"
-}
-
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
-
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
-
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
-
-provider "helm" {
- kubernetes = {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
- }
-}
-
-provider "kubernetes" {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
-}
-
-locals {
- global_node_labels = {
- "node.coder.io/instance" = "coder-v2"
- "node.coder.io/managed-by" = "karpenter"
- }
- global_node_reqs = [{
- key = "kubernetes.io/arch"
- operator = "In"
- values = ["amd64"]
- }, {
- key = "kubernetes.io/os"
- operator = "In"
- values = ["linux"]
- }, {
- key = "kubernetes.sh/capacity-type"
- operator = "In"
- values = ["spot", "on-demand"]
- }]
- provisioner_subnet_tags = {
- "subnet.amazonaws.io/coder-provisioner/owned-by" = var.cluster_name
- }
- ws_all_subnet_tags = {
- "subnet.amazonaws.io/coder-ws-all/owned-by" = var.cluster_name
- }
- provisioner_sg_tags = {
- "sg.amazonaws.io/coder-provisioner/owned-by" = var.cluster_name
- }
- ws_all_sg_tags = {
- "sg.amazonaws.io/coder-ws-all/owned-by" = var.cluster_name
- }
-}
-
-locals {
- nodepool_configs = [{
- name = "coder-proxy"
- node_labels = merge(local.global_node_labels, {
- "node.coder.io/name" = "coder"
- "node.coder.io/part-of" = "coder"
- "node.coder.io/used-for" = "coder-proxy"
- })
- node_taints = [{
- key = "dedicated"
- value = "coder-proxy"
- effect = "NoSchedule"
- }]
- node_requirements = concat(local.global_node_reqs, [{
- key = "node.kubernetes.io/instance-type"
- operator = "In"
- values = ["m5a.xlarge", "m6a.xlarge"]
- }])
- node_class_ref_name = "coder-proxy-class"
- }, {
- name = "coder-provisioner"
- node_labels = merge(local.global_node_labels, {
- "node.coder.io/name" = "coder"
- "node.coder.io/part-of" = "coder"
- "node.coder.io/used-for" = "coder-provisioner"
- })
- node_taints = [{
- key = "dedicated"
- value = "coder-provisioner"
- effect = "NoSchedule"
- }]
- node_requirements = concat(local.global_node_reqs, [{
- key = "node.kubernetes.io/instance-type"
- operator = "In"
- values = ["m5a.4xlarge", "m6a.4xlarge"]
- }])
- node_class_ref_name = "coder-provisioner-class"
- }, {
- name = "coder-ws-all"
- node_labels = merge(local.global_node_labels, {
- "node.coder.io/name" = "coder"
- "node.coder.io/part-of" = "coder"
- "node.coder.io/used-for" = "coder-ws-all"
- })
- node_taints = [{
- key = "dedicated"
- value = "coder-ws"
- effect = "NoSchedule"
- }]
- node_requirements = concat(local.global_node_reqs, [{
- key = "node.kubernetes.io/instance-type"
- operator = "In"
- values = ["c6a.32xlarge", "c5a.32xlarge"]
- }])
- node_class_ref_name = "coder-ws-class"
- disruption_consolidate_after = "30m"
- }]
-}
-
-module "karpenter-addon" {
- source = "../../../../../modules/k8s/bootstrap/karpenter"
- cluster_name = var.cluster_name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- namespace = var.addon_namespace
- chart_version = var.addon_version
- node_selector = {
- "node.amazonaws.io/managed-by" : "asg"
- }
- ec2nodeclass_configs = [{
- name = "coder-proxy-class"
- subnet_selector_tags = local.provisioner_subnet_tags
- sg_selector_tags = local.provisioner_sg_tags
- }, {
- name = "coder-ws-class"
- subnet_selector_tags = local.ws_all_subnet_tags
- sg_selector_tags = local.ws_all_sg_tags
- ami_alias = "al2023@latest" # Use /dev/xvda
- user_data = <<-EOF
- apiVersion: node.eks.aws/v1alpha1
- kind: NodeConfig
- spec:
- kubelet:
- config:
- registryPullQPS: 30
- EOF
- block_device_mappings = [{
- device_name = "/dev/xvda"
- ebs = {
- volume_size = "500Gi"
- volume_type = "gp3"
- }
- }] }, {
- name = "coder-provisioner-class"
- subnet_selector_tags = local.provisioner_subnet_tags
- sg_selector_tags = local.provisioner_sg_tags
- }]
-}
\ No newline at end of file
diff --git a/infra/aws/us-west-2/k8s/lb-controller/main.tf b/infra/aws/us-west-2/k8s/lb-controller/main.tf
deleted file mode 100644
index 6bab002..0000000
--- a/infra/aws/us-west-2/k8s/lb-controller/main.tf
+++ /dev/null
@@ -1,84 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "addon_namespace" {
- type = string
- default = "default"
-}
-
-variable "addon_version" {
- type = string
- default = "1.13.2"
-}
-
-variable "use_cert_manager" {
- type = bool
- default = false
-}
-
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
-
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
-
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
-
-provider "helm" {
- kubernetes = {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
- }
-}
-
-module "lb-controller" {
- source = "../../../../../modules/k8s/bootstrap/lb-controller"
- cluster_name = data.aws_eks_cluster.this.name
- cluster_oidc_provider_arn = var.cluster_oidc_provider_arn
-
- namespace = var.addon_namespace
- chart_version = var.addon_version
- enable_cert_manager = var.use_cert_manager
- service_target_eni_sg_tags = {
- Name = "aidemo-node"
- }
- cluster_asg_node_labels = { # var.karpenter_node_labels
- "node.amazonaws.io/managed-by" = "asg"
- }
-}
\ No newline at end of file
diff --git a/infra/aws/us-west-2/k8s/metrics-server/main.tf b/infra/aws/us-west-2/k8s/metrics-server/main.tf
deleted file mode 100644
index 8b50a90..0000000
--- a/infra/aws/us-west-2/k8s/metrics-server/main.tf
+++ /dev/null
@@ -1,66 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- helm = {
- source = "hashicorp/helm"
- version = ">= 3.1.1"
- }
- }
- backend "s3" {}
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_region" {
- type = string
-}
-
-variable "cluster_profile" {
- type = string
- default = "default"
-}
-
-variable "addon_namespace" {
- type = string
- default = "kube-system"
-}
-
-variable "addon_version" {
- type = string
- default = "3.13.0"
-}
-
-provider "aws" {
- region = var.cluster_region
- profile = var.cluster_profile
-}
-
-data "aws_eks_cluster" "this" {
- name = var.cluster_name
-}
-
-data "aws_eks_cluster_auth" "this" {
- name = var.cluster_name
-}
-
-provider "helm" {
- kubernetes = {
- host = data.aws_eks_cluster.this.endpoint
- cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data)
- token = data.aws_eks_cluster_auth.this.token
- }
-}
-
-module "metrics-server" {
- source = "../../../../../modules/k8s/bootstrap/metrics-server"
-
- namespace = var.addon_namespace
- chart_version = var.addon_version
- node_selector = {
- "node.amazonaws.io/managed-by" = "asg"
- }
-}
\ No newline at end of file
diff --git a/modules/coder/provisioner/main.tf b/modules/coder/provisioner/main.tf
index 1381722..0287f7e 100644
--- a/modules/coder/provisioner/main.tf
+++ b/modules/coder/provisioner/main.tf
@@ -32,16 +32,24 @@ variable "provisioner_tags" {
# Resources
##
-resource "random_id" "provisioner_key_name" {
+resource "random_string" "provisioner_key_name" {
keepers = {
# Generate a new ID only when a key is defined
provisioner_key_name = "${var.provisioner_key_name}"
}
- byte_length = 8
+ length = 8
+ special = true
+ numeric = true
+ lower = true
+ override_special = "-"
+}
+
+locals {
+ provisioner_key_name = var.provisioner_key_name == "" ? lower(random_string.provisioner_key_name.result) : var.provisioner_key_name
}
resource "coderd_provisioner_key" "key" {
- name = var.provisioner_key_name == "" ? random_id.provisioner_key_name.id : var.provisioner_key_name
+ name = local.provisioner_key_name
organization_id = var.organization_id
tags = var.provisioner_tags
}
@@ -52,7 +60,7 @@ resource "coderd_provisioner_key" "key" {
output "provisioner_key_name" {
description = "Coder Provisioner Key Name"
- value = var.provisioner_key_name == "" ? random_id.provisioner_key_name.id : var.provisioner_key_name
+ value = local.provisioner_key_name
}
output "provisioner_key_secret" {
diff --git a/modules/coder/template/main.tf b/modules/coder/template/main.tf
new file mode 100644
index 0000000..c629570
--- /dev/null
+++ b/modules/coder/template/main.tf
@@ -0,0 +1,74 @@
+terraform {
+ required_version = ">= 1.0"
+ required_providers {
+ coderd = {
+ source = "coder/coderd"
+ version = "0.0.12"
+ }
+ archive = {
+ source = "hashicorp/archive"
+ }
+ time = {
+ source = "hashicorp/time"
+ }
+ }
+}
+
+variable "template_config" {
+ type = object({
+ name = string
+ display_name = string
+ dir = string
+ description = string
+ icon = string
+ org_id = string
+ tf_vars = optional(list(object({
+ name = string
+ value = string
+ })), [])
+ })
+}
+
+variable "archive_config" {
+ type = object({
+ type = optional(string, "zip")
+ excludes = optional(list(string), [])
+ output_path = string
+ })
+}
+
+variable "time_static_triggers" {
+ type = map(string)
+ default = {}
+}
+
+data "archive_file" "this" {
+ type = var.archive_config.type
+ excludes = var.archive_config.excludes
+ source_dir = var.template_config.dir
+ output_path = var.archive_config.output_path
+}
+
+resource "time_static" "this" {
+ triggers = merge({
+ run_on_checksum = "${data.archive_file.this.id}"
+ run_on_tf_vars = jsonencode(var.template_config.tf_vars)
+ }, var.time_static_triggers)
+}
+
+resource "coderd_template" "this" {
+ name = var.template_config.name
+ organization_id = var.template_config.org_id
+ display_name = var.template_config.display_name
+ description = var.template_config.description
+ icon = var.template_config.icon
+ versions = [
+ {
+ name = "stable-${formatdate("YYYY-MM-DD_hh-mm-ss", time_static.this.rfc3339)}"
+ description = "The stable version of the template."
+ directory = var.template_config.dir
+ active = true
+ tf_vars = toset(var.template_config.tf_vars)
+ }
+ ]
+}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/acme-cloudflare-ssl/main.tf b/modules/k8s/bootstrap/acme-cloudflare-ssl/main.tf
deleted file mode 100644
index d74b0fb..0000000
--- a/modules/k8s/bootstrap/acme-cloudflare-ssl/main.tf
+++ /dev/null
@@ -1,127 +0,0 @@
-terraform {
- required_providers {
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- coderd = {
- source = "coder/coderd"
- }
- acme = {
- source = "vancluever/acme"
- }
- tls = {
- source = "hashicorp/tls"
- }
- null = {
- source = "hashicorp/null"
- }
- }
-}
-
-variable "dns_names" {
- type = list(string)
-}
-
-variable "common_name" {
- type = string
-}
-
-variable "acme_registration_email" {
- type = string
-}
-
-variable "acme_days_until_renewal" {
- type = number
- default = 30
-}
-
-variable "acme_revoke_certificate" {
- type = bool
- default = true
-}
-
-variable "cloudflare_api_token" {
- type = string
- sensitive = true
-}
-
-variable "kubernetes_secret_name" {
- type = string
-}
-
-variable "kubernetes_namespace" {
- type = string
- default = "default"
-}
-
-resource "null_resource" "registration-email" {
- triggers = {
- registration_email = var.acme_registration_email
- }
-}
-
-resource "acme_registration" "this" {
- email_address = var.acme_registration_email
-
- lifecycle {
- replace_triggered_by = [null_resource.registration-email]
- }
-}
-
-resource "tls_private_key" "this" {
- algorithm = "RSA"
- rsa_bits = 4096
-}
-
-resource "tls_cert_request" "this" {
- private_key_pem = tls_private_key.this.private_key_pem
- dns_names = var.dns_names
- subject {
- common_name = var.common_name
- }
-}
-
-resource "acme_certificate" "this" {
- account_key_pem = acme_registration.this.account_key_pem
- certificate_request_pem = tls_cert_request.this.cert_request_pem
- min_days_remaining = var.acme_days_until_renewal
- revoke_certificate_on_destroy = var.acme_revoke_certificate
- revoke_certificate_reason = "cessation-of-operation"
- dns_challenge {
- provider = "cloudflare"
- config = {
- CF_DNS_API_TOKEN = var.cloudflare_api_token
- }
- }
-}
-
-locals {
- full_chain = "${acme_certificate.this.certificate_pem}${acme_certificate.this.issuer_pem}"
-}
-
-resource "kubernetes_secret" "coder-proxy-tls" {
- metadata {
- name = var.kubernetes_secret_name
- namespace = var.kubernetes_namespace
- }
- data = {
- "tls.key" = tls_private_key.this.private_key_pem
- "tls.crt" = local.full_chain
- }
- type = "kubernetes_namespace.io/tls"
-
-}
-
-output "private_key_pem" {
- value = tls_private_key.this.private_key_pem
- sensitive = true
-}
-
-output "full_chain_certificate_pem" {
- value = local.full_chain
- sensitive = true
-}
-
-output "kubernetes_secret_name" {
- value = kubernetes_secret.coder-proxy-tls.metadata[0].name
-}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/cert-manager/main.tf b/modules/k8s/bootstrap/cert-manager/main.tf
index 919a2cf..9d30022 100644
--- a/modules/k8s/bootstrap/cert-manager/main.tf
+++ b/modules/k8s/bootstrap/cert-manager/main.tf
@@ -13,31 +13,10 @@ terraform {
}
}
-
variable "cluster_name" {
type = string
}
-variable "role_name" {
- type = string
- default = ""
-}
-
-variable "policy_name" {
- type = string
- default = ""
-}
-
-variable "policy_resource_region" {
- type = string
- default = ""
-}
-
-variable "policy_resource_account" {
- type = string
- default = ""
-}
-
variable "cluster_oidc_provider_arn" {
type = string
}
@@ -61,34 +40,26 @@ variable "helm_version" {
default = "v1.18.2"
}
-##
-# ACME Certificate Inputs
-##
-
-variable "cloudflare_token_secret_name" {
- type = string
- default = "cloudflare-token"
-}
-
-variable "cloudflare_token_secret_key" {
- type = string
- default = "token.key"
+variable "node_selector" {
+ type = map(string)
+ default = {
+ "kubernetes.io/os" = "linux"
+ }
}
-variable "cloudflare_token_secret" {
- type = string
- sensitive = true
+variable "tolerations" {
+ type = list(map(any))
+ default = []
}
-variable "cloudflare_token_secret_email" {
- type = string
- sensitive = true
+variable "affinity" {
+ type = map(any)
+ default = {}
}
-variable "issuer_private_key_secret_name" {
- type = string
- default = "issuer-account-key"
-}
+##
+# ACME Certificate Inputs
+##
variable "acme_server_url" {
type = string
@@ -104,38 +75,7 @@ data "aws_region" "this" {}
data "aws_caller_identity" "this" {}
-locals {
- region = var.policy_resource_region == "" ? data.aws_region.this.region : var.policy_resource_region
- account_id = var.policy_resource_account == "" ? data.aws_caller_identity.this.account_id : var.policy_resource_account
- policy_name = var.policy_name == "" ? "CertManager-${data.aws_region.this.region}" : var.policy_name
- role_name = var.role_name == "" ? "cert-manager-${data.aws_region.this.region}" : var.role_name
-}
-
-module "policy" {
- source = "../../../security/policy"
- name = local.policy_name
- path = "/"
- description = "CertManager for Route53 Policy"
- policy_json = data.aws_iam_policy_document.route53.json
-}
-
-module "oidc-role" {
- source = "../../../security/role/access-entry"
- name = local.role_name
- cluster_name = var.cluster_name
- policy_arns = {
- "CertManagerRoute53" = module.policy.policy_arn
- }
- cluster_policy_arns = {
- "AmazonEKSClusterAdminPolicy" = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy",
- }
- oidc_principals = {
- "${var.cluster_oidc_provider_arn}" = ["system:serviceaccount:*:*"]
- }
- tags = var.tags
-}
-
-resource "kubernetes_namespace" "this" {
+resource "kubernetes_namespace_v1" "this" {
metadata {
name = var.namespace
}
@@ -143,7 +83,7 @@ resource "kubernetes_namespace" "this" {
resource "helm_release" "cert-manager" {
name = "cert-manager"
- namespace = kubernetes_namespace.this.metadata[0].name
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
chart = "cert-manager"
repository = "oci://quay.io/jetstack/charts"
create_namespace = false
@@ -158,96 +98,170 @@ resource "helm_release" "cert-manager" {
crds = {
enabled = true
}
+ nodeSelector = var.node_selector
+ tolerations = var.tolerations
+ affinity = var.affinity
+ webhook = {
+ tolerations = var.tolerations
+ affinity = var.affinity
+ }
+ cainjector = {
+ tolerations = var.tolerations
+ affinity = var.affinity
+ }
+ startupapicheck = {
+ tolerations = var.tolerations
+ affinity = var.affinity
+ }
})]
}
-resource "kubernetes_service_account" "route53" {
+##
+# Use Route53 for the DNS01 Challenge Provider
+##
+
+variable "r53_config" {
+ type = object({
+ enabled = bool
+ region = optional(string, "")
+ account = optional(string, "")
+ role_name = optional(string, "crt-mgr")
+ policy_name = optional(string, "crt-mgr")
+ })
+ default = {
+ enabled = false
+ region = ""
+ account = ""
+ role_name = "crt-mgr"
+ policy_name = "crt-mgr"
+ }
+}
+
+locals {
+ region = var.r53_config.region == "" ? data.aws_region.this.region : var.r53_config.region
+ account_id = var.r53_config.account == "" ? data.aws_caller_identity.this.account_id : var.r53_config.account
+}
+
+module "policy" {
+
+ count = var.r53_config.enabled ? 1 : 0
+
+ source = "../../../security/policy"
+ name = var.r53_config.policy_name
+ path = "/${var.cluster_name}/${local.region}/"
+ description = "Cert-Manager for R53 Policy"
+ policy_json = data.aws_iam_policy_document.route53.json
+}
+
+module "oidc-role" {
+
+ count = var.r53_config.enabled ? 1 : 0
+
+ source = "../../../security/role/access-entry"
+ name = var.r53_config.role_name
+ path = "/${var.cluster_name}/${local.region}/"
+ cluster_name = var.cluster_name
+ policy_arns = {
+ "CertManagerR53" = module.policy[0].policy_arn
+ }
+ cluster_policy_arns = {
+ "AmazonEKSClusterAdminPolicy" = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy",
+ }
+ oidc_principals = {
+ "${var.cluster_oidc_provider_arn}" = ["system:serviceaccount:*:*"]
+ }
+ tags = var.tags
+}
+
+resource "kubernetes_service_account_v1" "r53" {
+
+ count = var.r53_config.enabled ? 1 : 0
+
metadata {
- name = "cert-manager-acme-dns01-route53"
- namespace = kubernetes_namespace.this.metadata[0].name
+ name = "${var.r53_config.role_name}"
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
annotations = {
- "eks.amazonaws.com/role-arn" = module.oidc-role.role_arn
+ "eks.amazonaws.com/role-arn" = module.oidc-role[0].role_arn
}
}
}
-resource "kubernetes_role" "route53" {
+resource "kubernetes_role_v1" "r53" {
+
+ count = var.r53_config.enabled ? 1 : 0
+
metadata {
- name = "cert-manager-acme-dns01-route53-tokenrequest"
- namespace = kubernetes_namespace.this.metadata[0].name
+ name = "${var.r53_config.role_name}-tokenrequest"
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
}
rule {
api_groups = [""]
resources = ["serviceaccounts/token"]
- resource_names = [kubernetes_service_account.route53.metadata[0].name]
+ resource_names = [kubernetes_service_account_v1.r53[0].metadata[0].name]
verbs = ["create"]
}
}
-resource "kubernetes_role_binding" "route53" {
+resource "kubernetes_role_binding_v1" "r53" {
+
+ count = var.r53_config.enabled ? 1 : 0
+
metadata {
- name = "cert-manager-acme-dns01-route53-tokenrequest"
- namespace = kubernetes_namespace.this.metadata[0].name
+ name = "${var.r53_config.role_name}-tokenrequest"
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
}
subject {
kind = "ServiceAccount"
name = "cert-manager"
- namespace = kubernetes_namespace.this.metadata[0].name
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "Role"
- name = kubernetes_role.route53.metadata[0].name
+ name = kubernetes_role_v1.r53[0].metadata[0].name
}
}
-resource "kubernetes_secret" "cloudflare" {
- metadata {
- name = var.cloudflare_token_secret_name
- namespace = kubernetes_namespace.this.metadata[0].name
- }
- data = {
- "${var.cloudflare_token_secret_key}" = var.cloudflare_token_secret
+##
+# Use CloudFlare for the DNS01 Challenge Provider
+##
+
+variable "cf_config" {
+ type = object({
+ enabled = bool
+ name = optional(string, "cloudflare")
+ key = optional(string, "token.key")
+ token = string
+ email = string
+ })
+ default = {
+ enabled = false
+ name = "cloudflare"
+ key = "token.key"
+ token = ""
+ email = ""
}
+ sensitive = true
}
-resource "kubernetes_manifest" "default-cluster-issuer" {
+locals {
+ cf_annot_sec_key = "custom.kubernetes.secret/key"
+ cf_annot_email_key = "custom.kubernetes.secret/email"
+}
- depends_on = [
- helm_release.cert-manager,
- kubernetes_secret.cloudflare
- ]
+resource "kubernetes_secret_v1" "cloudflare" {
- field_manager {
- force_conflicts = true
- }
- manifest = {
- apiVersion = "cert-manager.io/v1"
- kind = "ClusterIssuer"
- metadata = {
- labels = {}
- name = "issuer"
- }
- spec = {
- acme = {
- privateKeySecretRef = {
- name = var.issuer_private_key_secret_name
- }
- server = var.acme_server_url
- solvers = [
- {
- dns01 = {
- cloudflare = {
- apiTokenSecretRef = {
- key = var.cloudflare_token_secret_key
- name = kubernetes_secret.cloudflare.metadata[0].name
- }
- email = var.cloudflare_token_secret_email
- }
- }
- }
- ]
- }
+ count = var.cf_config.enabled ? 1 : 0
+
+ metadata {
+ name = var.cf_config.name
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ annotations = {
+ "${local.cf_annot_sec_key}" = var.cf_config.key
+ "${local.cf_annot_email_key}" = var.cf_config.email
}
}
+ data = {
+ "${var.cf_config.key}" = var.cf_config.token
+ }
}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/coder-logstream/main.tf b/modules/k8s/bootstrap/coder-logstream/main.tf
new file mode 100644
index 0000000..269f907
--- /dev/null
+++ b/modules/k8s/bootstrap/coder-logstream/main.tf
@@ -0,0 +1,117 @@
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ }
+ helm = {
+ source = "hashicorp/helm"
+ version = ">= 2.17.0"
+ }
+ kubernetes = {
+ source = "hashicorp/kubernetes"
+ }
+ }
+}
+
+variable "release_name" {
+ description = "The release name of the installed Helm app."
+ type = string
+ default = "coder-logstream-kube"
+}
+
+variable "chart_name" {
+ description = "The chart name of the installed Helm app."
+ type = string
+ default = "coder-logstream-kube"
+}
+
+variable "helm_timeout" {
+ type = number
+ default = 300 # In Seconds
+}
+
+variable "chart_version" {
+ type = string
+ default = "v0.0.14"
+}
+
+variable "namespace" {
+ type = string
+ default = "coder-logstream-kube"
+}
+
+variable "image_repo" {
+ type = string
+ default = "ghcr.io/coder/coder-logstream-kube"
+}
+
+variable "image_tag" {
+ type = string
+ default = "v0.0.14"
+}
+
+variable "image_pull_policy" {
+ type = string
+ default = "IfNotPresent"
+}
+
+variable "coder" {
+ type = object({
+ access_url = string
+ ws_ns = optional(list(string), [])
+ })
+}
+
+variable "node_selector" {
+ type = map(string)
+ default = {}
+}
+
+variable "affinity" {
+ type = any
+ default = {}
+}
+
+variable "tolerations" {
+ type = list(any)
+ default = []
+}
+
+resource "kubernetes_namespace_v1" "logstream" {
+ metadata {
+ name = var.namespace
+ }
+}
+
+resource "helm_release" "coder-logstream" {
+
+ name = var.release_name
+ namespace = kubernetes_namespace_v1.logstream.metadata[0].name
+ chart = var.chart_name
+ repository = "https://helm.coder.com/logstream-kube"
+ create_namespace = false
+ upgrade_install = true
+ skip_crds = false
+ wait = true
+ wait_for_jobs = true
+ version = var.chart_version
+ timeout = var.helm_timeout
+
+ values = [yamlencode({
+ url = var.coder.access_url
+ namespaces = var.coder.ws_ns
+ image = {
+ repo = var.image_repo
+ tag = var.image_tag
+ pullPolicy = var.image_pull_policy
+ }
+
+ nodeSelector = var.node_selector
+ affinity = var.affinity
+ tolerations = var.tolerations
+ })]
+}
+
+output "namespace" {
+ value = kubernetes_namespace_v1.logstream.metadata[0].name
+}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/coder-provisioner/main.tf b/modules/k8s/bootstrap/coder-provisioner/main.tf
index 6f3d36b..35dbf32 100644
--- a/modules/k8s/bootstrap/coder-provisioner/main.tf
+++ b/modules/k8s/bootstrap/coder-provisioner/main.tf
@@ -16,128 +16,97 @@ terraform {
}
}
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_oidc_provider_arn" {
+variable "chart_name" {
type = string
+ default = "coder-provisioner"
}
-variable "role_name" {
- type = string
- default = ""
-}
-
-variable "policy_name" {
- type = string
- default = ""
-}
-
-variable "policy_resource_region" {
+variable "chart_version" {
type = string
- default = ""
-}
-
-variable "policy_resource_account" {
- type = string
- default = ""
-}
-
-variable "tags" {
- type = map(string)
- default = {}
+ default = "2.30.0"
}
-variable "namespace" {
- type = string
-}
-
-variable "image_repo" {
- type = string
-}
-
-variable "image_tag" {
- type = string
-}
-
-variable "image_pull_policy" {
- type = string
- default = "IfNotPresent"
-}
-
-variable "image_pull_secrets" {
- type = list(string)
- default = []
-}
-
-variable "provisioner_chart_version" {
- type = string
- default = "2.23.0"
+variable "chart_timeout" {
+ type = number
+ default = 300
}
-variable "logstream_chart_version" {
+variable "release_name" {
type = string
- default = "0.0.11"
+ default = "coder-provisioner"
}
-variable "primary_access_url" {
+variable "cluster_name" {
type = string
}
-variable "ws_service_account_name" {
+variable "cluster_oidc_provider_arn" {
type = string
}
-variable "ws_service_account_labels" {
- type = map(string)
- default = {}
-}
-
-variable "ws_service_account_annotations" {
+variable "tags" {
type = map(string)
default = {}
}
-variable "provisioner_service_account_name" {
- type = string
- default = "coder"
-}
-
-variable "provisioner_service_account_annotations" {
- type = map(string)
- default = {}
+variable "coder" {
+ type = object({
+ access_url = string
+ prov_tags = optional(map(string), {})
+ prov_secret_key = optional(string, "key")
+ org_name = optional(string, "coder")
+ ws_ns = optional(list(string), [])
+ ws_extra_rules = optional(list(any), [])
+ image_repo = optional(string, "ghcr.io/coder/coder")
+ image_tag = optional(string, "latest")
+ image_pull_policy = optional(string, "IfNotPresent")
+ image_pull_secrets = optional(list(string), null)
+ env_vars = optional(map(string), {})
+ rep_cnt = optional(number, 1)
+ tf_debug_mode = optional(bool, true)
+ trace_logs = optional(bool, true)
+ })
+ # sensitive = true
}
-variable "replica_count" {
- type = number
- default = 0
+variable "namespace" {
+ type = string
+ default = "coder-provisioner"
}
-variable "env_vars" {
- type = map(string)
- default = {}
+variable "svc_acc" {
+ type = object({
+ create = optional(bool, true)
+ name = optional(string, "coder-provisioner")
+ annots = optional(map(string), {})
+ iam_policy_arns = optional(map(string), {})
+ })
+ default = {
+ create = true
+ name = "coder-provisioner"
+ annots = {}
+ }
}
-variable "resource_requests" {
+variable "rsrc_req" {
type = object({
cpu = string
memory = string
})
default = {
- cpu = "250m"
- memory = "512Mi"
+ cpu = "1"
+ memory = "1Gi"
}
}
-variable "resource_limits" {
+variable "rsrc_lim" {
type = object({
cpu = string
memory = string
})
default = {
- cpu = "1000m"
- memory = "2Gi"
+ cpu = "1"
+ memory = "1Gi"
}
}
@@ -147,16 +116,11 @@ variable "node_selector" {
}
variable "tolerations" {
- type = list(object({
- key = string
- operator = optional(string, "Equal")
- value = string
- effect = optional(string, "NoSchedule")
- }))
+ type = any
default = []
}
-variable "topology_spread_constraints" {
+variable "topology_spread" {
type = list(object({
max_skew = number
topology_key = string
@@ -169,40 +133,9 @@ variable "topology_spread_constraints" {
default = []
}
-variable "pod_anti_affinity_preferred_during_scheduling_ignored_during_execution" {
- type = list(object({
- weight = number
- pod_affinity_term = object({
- label_selector = object({
- match_labels = map(string)
- })
- topology_key = string
- })
- }))
- default = []
-}
-
-variable "provisioner_secret" {
- type = object({
- key_secret_name = optional(string, "psk")
- key_secret_key = optional(string, "token.key")
- termination_grace_period_seconds = optional(number, 600)
- })
- default = {
- key_secret_name = "psk"
- key_secret_key = "token.key"
- termination_grace_period_seconds = 600
- }
-}
-
-variable "coder_organization_name" {
- type = string
- default = "coder"
-}
-
-variable "coder_provisioner_key_name" {
- type = string
- default = ""
+variable "affinity" {
+ type = any
+ default = {}
}
##
@@ -210,36 +143,35 @@ variable "coder_provisioner_key_name" {
##
data "aws_region" "this" {}
-
data "aws_caller_identity" "this" {}
data "coderd_organization" "this" {
- name = var.coder_organization_name
+ name = var.coder.org_name
}
locals {
- region = var.policy_resource_region == "" ? data.aws_region.this.region : var.policy_resource_region
- account_id = var.policy_resource_account == "" ? data.aws_caller_identity.this.account_id : var.policy_resource_account
- policy_name = var.policy_name == "" ? "Provisioner-${data.aws_region.this.region}" : var.policy_name
- role_name = var.role_name == "" ? "provisioner-${data.aws_region.this.region}" : var.role_name
+ region = data.aws_region.this.region
+ account_id = data.aws_caller_identity.this.account_id
+ policy_name = "Provisioner-${data.aws_region.this.region}"
+ role_name = "provisioner-${data.aws_region.this.region}"
}
-module "provisioner-policy" {
+module "iam-policy" {
source = "../../../security/policy"
name = local.policy_name
path = "/"
- description = "Coder Terraform External Provisioner Policy"
- policy_json = data.aws_iam_policy_document.provisioner-policy.json
+ description = "Coder External Provisioner Policy"
+ policy_json = data.aws_iam_policy_document.ext-prov.json
}
-module "provisioner-oidc-role" {
+module "oidc-role" {
source = "../../../security/role/access-entry"
name = local.role_name
cluster_name = var.cluster_name
- policy_arns = {
+ policy_arns = merge({
"AmazonEC2ReadOnlyAccess" = "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
- "TFProvisionerPolicy" = module.provisioner-policy.policy_arn
- }
+ "TFProvisionerPolicy" = module.iam-policy.policy_arn
+ }, var.svc_acc.iam_policy_arns)
cluster_policy_arns = {}
oidc_principals = {
"${var.cluster_oidc_provider_arn}" = ["system:serviceaccount:*:*"]
@@ -247,82 +179,117 @@ module "provisioner-oidc-role" {
tags = var.tags
}
-locals {
- primary_env_vars = {
- CODER_URL = var.primary_access_url
- }
- env_vars = [
- for k, v in merge(local.primary_env_vars, var.env_vars) : { name = k, value = v }
- ]
- topology_spread_constraints = [
- for v in var.topology_spread_constraints : {
- maxSkew = v.max_skew
- topologyKey = v.topology_key
- whenUnsatisfiable = v.when_unsatisfiable
- labelSelector = v.label_selector
- matchLabelKeys = v.match_label_keys
- }
- ]
- pod_anti_affinity_preferred_during_scheduling_ignored_during_execution = [
- for v in var.pod_anti_affinity_preferred_during_scheduling_ignored_during_execution : {
- weight = v.weight
- podAffinityTerm = {
- labelSelector = v.pod_affinity_term.label_selector
- topologyKey = v.pod_affinity_term.topology_key
- }
- }
- ]
-}
-
-module "coder-provisioner-key" {
+module "ext-prov" {
source = "../../../coder/provisioner"
organization_id = data.coderd_organization.this.id
- provisioner_tags = {
- region = data.aws_region.this.region
- }
+ provisioner_tags = var.coder.prov_tags
}
-resource "kubernetes_namespace" "this" {
+resource "kubernetes_namespace_v1" "this" {
metadata {
name = var.namespace
}
}
+resource "kubernetes_secret_v1" "ext-prov" {
+ metadata {
+ name = module.ext-prov.provisioner_key_name
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ annotations = {
+ "custom.kubernetes.secret/key" = var.coder.prov_secret_key
+ }
+ }
+ type = "Opaque"
+ data = {
+ "${var.coder.prov_secret_key}" = module.ext-prov.provisioner_key_secret
+ }
+}
+
+locals {
+ topology_spread = [
+ for k, v in var.topology_spread : {
+ maxSkew = v.max_skew
+ topologyKey = v.topology_key
+ whenUnsatisfiable = v.when_unsatisfiable
+ labelSelector = {
+ matchLabels = try(v.label_selector.match_labels, {})
+ }
+ matchLabelKeys = v.match_label_keys
+ }
+ ]
+}
+
resource "helm_release" "coder-provisioner" {
- name = "coder-provisioner"
- namespace = kubernetes_namespace.this.metadata[0].name
- chart = "coder-provisioner"
- repository = "https://helm.coder.com/v2"
+ name = var.release_name
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ chart = var.chart_name
+ # repository = "https://helm.coder.com/v2"
+ repository = "oci://ghcr.io/jatcod3r/charts"
create_namespace = false
upgrade_install = true
skip_crds = false
wait = true
wait_for_jobs = true
- version = var.provisioner_chart_version
- timeout = 120 # in seconds
+ version = var.chart_version
+ timeout = var.chart_timeout
values = [yamlencode({
coder = {
image = {
- repo = var.image_repo
- tag = var.image_tag
- pullPolicy = var.image_pull_policy
- pullSecrets = var.image_pull_secrets
+ repo = var.coder.image_repo
+ tag = var.coder.image_tag
+ pullPolicy = var.coder.image_pull_policy
+ pullSecrets = var.coder.image_pull_secrets
}
serviceAccount = {
workspacePerms = true
enableDeployments = true
- name = var.provisioner_service_account_name
- disableCreate = false
+ name = var.svc_acc.name
+ disableCreate = !var.svc_acc.create
+ workspaceNamespaces = [ for v in var.coder.ws_ns : { name = v } ]
+ extraRules = var.coder.ws_extra_rules
annotations = merge({
- "eks.amazonaws.com/role-arn" = module.provisioner-oidc-role.role_arn
- }, var.provisioner_service_account_annotations)
+ "eks.amazonaws.com/role-arn" = module.oidc-role.role_arn
+ }, var.svc_acc.annots)
}
podAnnotations = {
"prometheus.io/scrape" = "true"
"prometheus.io/port" = "2112"
}
- env = local.env_vars
+ env = [
+ for k, v in merge({
+ CODER_URL = var.coder.access_url
+ }, var.coder.env_vars) : { name = k, value = v }
+ ]
+ volumeClaimTemplates = [{
+ metadata = {
+ name = "cache"
+ }
+ spec = {
+ accessModes = ["ReadWriteOnce"]
+ storageClassName = "gp3-automode"
+ resources = {
+ requests = {
+ storage = "10Gi"
+ }
+ }
+ }
+ }]
+ # volumes = [{
+ # name = "cache"
+ # persistentVolumeClaim = {
+ # claimName = kubernetes_persistent_volume_claim_v1.cache.metadata[0].name
+ # readOnly = false
+ # }
+ # }]
+ volumeMounts = [{
+ mountPath = "/home/coder/.cache/coder"
+ name = "cache"
+ readOnly = false
+ }]
+ podSecurityContext = {
+ fsGroup = 1000
+ }
securityContext = {
runAsNonRoot = true
runAsUser = 1000
@@ -334,104 +301,52 @@ resource "helm_release" "coder-provisioner" {
allowPrivilegeEscalation = false
}
resources = {
- requests = var.resource_requests
- limits = var.resource_limits
+ requests = var.rsrc_req
+ limits = var.rsrc_lim
}
nodeSelector = var.node_selector
- replicaCount = var.replica_count
+ replicaCount = var.coder.rep_cnt
tolerations = var.tolerations
- topologySpreadConstraints = local.topology_spread_constraints
- affinity = {
- podAntiAffinity = {
- preferredDuringSchedulingIgnoredDuringExecution = local.pod_anti_affinity_preferred_during_scheduling_ignored_during_execution
- }
- }
+ topologySpreadConstraints = local.topology_spread
+ affinity = var.affinity
}
provisionerDaemon = {
- keySecretKey = var.provisioner_secret.key_secret_key
- keySecretName = var.provisioner_secret.key_secret_name
- terminationGracePeriodSeconds = var.provisioner_secret.termination_grace_period_seconds
+ keySecretKey = kubernetes_secret_v1.ext-prov.metadata[0].annotations["custom.kubernetes.secret/key"]
+ keySecretName = kubernetes_secret_v1.ext-prov.metadata[0].name
+ terminationGracePeriodSeconds = 600
}
})]
}
-resource "kubernetes_secret" "coder-provisioner-key" {
- metadata {
- name = var.provisioner_secret.key_secret_name
- namespace = kubernetes_namespace.this.metadata[0].name
- }
- data = {
- "${var.provisioner_secret.key_secret_key}" = module.coder-provisioner-key.provisioner_key_secret
- }
- type = "Opaque"
-}
-
-resource "helm_release" "coder-logstream" {
- name = "coder-logstream-kube"
- namespace = kubernetes_namespace.this.metadata[0].name
- chart = "coder-logstream-kube"
- repository = "https://helm.coder.com/logstream-kube"
- create_namespace = false
- upgrade_install = true
- skip_crds = false
- wait = true
- wait_for_jobs = true
- version = var.logstream_chart_version
- timeout = 120 # in seconds
-
- values = [yamlencode({
- url = var.primary_access_url
- })]
-}
-
-module "ws-policy" {
- source = "../../../security/policy"
- name = local.policy_name
- path = "/"
- description = "Coder Workspace IAM Policy"
- policy_json = data.aws_iam_policy_document.ws-policy.json
-}
-
-module "ws-service-role" {
- source = "../../../security/role/service"
- name = "ws-service-role"
- policy_arns = {
- "AmazonEC2ContainerServiceforEC2Role" = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role",
- "AmazonSSMManagedInstanceCore" = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
- "WorkspacePolicy" = module.ws-policy.policy_arn
- }
- service_actions = ["sts:AssumeRole"]
- service_principals = [
- "ec2.amazonaws.com"
- ]
- tags = var.tags
-}
-
-module "ws-oidc-role" {
- source = "../../../security/role/access-entry"
- name = "ws-container-role"
- policy_arns = {
- "WorkspacePolicy" = module.ws-policy.policy_arn
- }
- cluster_name = var.cluster_name
- cluster_policy_arns = {}
- oidc_principals = {
- "${var.cluster_oidc_provider_arn}" = ["system:serviceaccount:*:*"]
- }
- tags = var.tags
-}
-
-resource "kubernetes_service_account" "ws" {
- metadata {
- name = var.ws_service_account_name
- namespace = kubernetes_namespace.this.metadata[0].name
- labels = var.ws_service_account_labels
- annotations = merge({
- "eks.amazonaws.com/role-arn" = module.ws-oidc-role.role_arn
- }, var.ws_service_account_annotations)
- }
-}
-
-output "oidc_role_arn" {
- value = module.provisioner-oidc-role.role_arn
+# resource "kubernetes_persistent_volume_claim_v1" "cache" {
+# metadata {
+# name = "cache"
+# namespace = var.namespace
+# labels = {}
+# annotations = {}
+# }
+# wait_until_bound = false
+# spec {
+# storage_class_name = "gp3-automode"
+# access_modes = ["ReadWriteOncePod"]
+# resources {
+# requests = {
+# storage = "50Gi"
+# }
+# }
+# }
+# }
+
+output "coderd_organization_id" {
+
+ depends_on = [ helm_release.coder-provisioner ]
+
+ value = data.coderd_organization.this.id
+}
+
+output "k8s_namespace" {
+
+ depends_on = [ helm_release.coder-provisioner ]
+
+ value = kubernetes_namespace_v1.this.metadata[0].name
}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/coder-provisioner/policy.tf b/modules/k8s/bootstrap/coder-provisioner/policy.tf
index 1457d62..75b8155 100644
--- a/modules/k8s/bootstrap/coder-provisioner/policy.tf
+++ b/modules/k8s/bootstrap/coder-provisioner/policy.tf
@@ -1,177 +1,38 @@
-data "aws_iam_policy_document" "provisioner-policy" {
+data "aws_iam_policy_document" "ext-prov" {
statement {
- sid = "EC2InstanceLifecycle"
effect = "Allow"
actions = [
- "ec2:RunInstances",
- "ec2:StartInstances",
- "ec2:StopInstances",
- "ec2:TerminateInstances",
+ "ec2:GetDefaultCreditSpecification",
+ "ec2:DescribeIamInstanceProfileAssociations",
+ "ec2:DescribeTags",
"ec2:DescribeInstances",
- "ec2:RebootInstances",
- "ec2:ModifyInstanceAttribute",
- "ec2:DescribeInstanceAttribute",
- "ec2:MonitorInstances"
- ]
- resources = [
- "arn:aws:ec2:${local.region}:${local.account_id}:*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*/*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*:*",
- "arn:aws:ec2:${local.region}::image/*"
- ]
- }
-
- statement {
- sid = "EC2ManageHostLifecycle"
- effect = "Allow"
- actions = [
- "ec2:AllocateHosts",
- "ec2:ModifyHosts",
- "ec2:ReleaseHosts"
- ]
- resources = [
- "arn:aws:ec2:${local.region}:${local.account_id}:*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*/*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*:*",
- "arn:aws:ec2:${local.region}::image/*"
- ]
- }
-
- statement {
- sid = "EBSVolumeLifecycle"
- effect = "Allow"
- actions = [
- "ec2:CreateVolume",
- "ec2:AttachVolume",
- "ec2:DetachVolume",
- "ec2:DeleteVolume",
- "ec2:DescribeVolumes",
- ]
- resources = [
- "arn:aws:ec2:${local.region}:${local.account_id}:*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*/*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*:*",
- ]
- }
-
- statement {
- sid = "SecurityGroupLifecycle"
- effect = "Allow"
- actions = [
- "ec2:CreateSecurityGroup",
- "ec2:AuthorizeSecurityGroupIngress",
- "ec2:AuthorizeSecurityGroupEgress",
- "ec2:RevokeSecurityGroupIngress",
- "ec2:RevokeSecurityGroupEgress",
- "ec2:DeleteSecurityGroup",
- "ec2:DescribeSecurityGroups",
- ]
- resources = [
- "arn:aws:ec2:${local.region}:${local.account_id}:*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*/*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*:*",
- ]
- }
-
- statement {
- sid = "TagLifecycle"
- effect = "Allow"
- actions = [
+ "ec2:DescribeInstanceTypes",
+ "ec2:DescribeInstanceStatus",
"ec2:CreateTags",
- "ec2:DeleteTags",
- ]
- resources = [
- "arn:aws:ec2:${local.region}:${local.account_id}:*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*/*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*:*",
- ]
- }
-
- statement {
- sid = "NetworkInterfaceLifecycle"
- effect = "Allow"
- actions = [
- "ec2:CreateNetworkInterface",
- "ec2:AttachNetworkInterface",
- "ec2:DetachNetworkInterface",
- "ec2:DeleteNetworkInterface",
- "ec2:DescribeNetworkInterfaces",
- "ec2:ModifyNetworkInterfaceAttribute",
- ]
- resources = [
- "arn:aws:ec2:${local.region}:${local.account_id}:*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*/*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*:*",
- ]
- }
-
- statement {
- sid = "ECRAuth"
- effect = "Allow"
- actions = [
- "ecr:GetAuthorizationToken"
- ]
- resources = ["*"]
- }
-
- statement {
- sid = "ECRDownloadImages"
- effect = "Allow"
- actions = [
- "ecr:BatchCheckLayerAvailability",
- "ecr:BatchGetImage",
- "ecr:GetDownloadUrlForLayer"
+ "ec2:RunInstances",
+ "ec2:DescribeInstanceCreditSpecifications",
+ "ec2:DescribeImages",
+ "ec2:ModifyDefaultCreditSpecification",
+ "ec2:DescribeVolumes"
]
resources = ["*"]
}
statement {
- sid = "ECRUploadImages"
- effect = "Allow"
- actions = [
- "ecr:CompleteLayerUpload",
- "ecr:UploadLayerPart",
- "ecr:InitiateLayerUpload",
- "ecr:BatchCheckLayerAvailability",
- "ecr:PutImage",
- "ecr:BatchGetImage"
- ]
- resources = ["arn:aws:ecr:${local.region}:${local.account_id}:repository/*"]
- }
-
- statement {
- sid = "IAMReadOnly"
effect = "Allow"
actions = [
- "iam:Get*",
- "iam:List*"
- ]
- resources = ["arn:aws:iam::${local.account_id}:*"]
- }
-
- statement {
- sid = "IAMPassRole"
- effect = "Allow"
- actions = [
- "iam:PassRole",
- ]
- resources = ["arn:aws:iam::${local.account_id}:*"]
- }
-}
-
-data "aws_iam_policy_document" "ws-policy" {
- statement {
- sid = "AllowModelInvocation"
- effect = "Allow"
- actions = [
- "bedrock:InvokeModel",
- "bedrock:InvokeModelWithResponseStream",
- "bedrock:ListInferenceProfiles"
- ]
- resources = [
- "arn:aws:bedrock:*:*:*",
- "arn:aws:bedrock:*:*:*/*",
- "arn:aws:bedrock:*:*:*:*",
+ "ec2:DescribeInstanceAttribute",
+ "ec2:UnmonitorInstances",
+ "ec2:TerminateInstances",
+ "ec2:StartInstances",
+ "ec2:StopInstances",
+ "ec2:DeleteTags",
+ "ec2:MonitorInstances",
+ "ec2:CreateTags",
+ "ec2:RunInstances",
+ "ec2:ModifyInstanceAttribute",
+ "ec2:ModifyInstanceCreditSpecification"
]
+ resources = ["arn:aws:ec2:*:*:instance/*"]
}
}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/coder-proxy/main.tf b/modules/k8s/bootstrap/coder-proxy/main.tf
index 034bd5a..bdb2865 100644
--- a/modules/k8s/bootstrap/coder-proxy/main.tf
+++ b/modules/k8s/bootstrap/coder-proxy/main.tf
@@ -13,9 +13,6 @@ terraform {
coderd = {
source = "coder/coderd"
}
- acme = {
- source = "vancluever/acme"
- }
tls = {
source = "hashicorp/tls"
}
@@ -26,85 +23,54 @@ terraform {
# Coderd Inputs
##
-variable "coder_proxy_name" {
- type = string
-}
-
-variable "coder_proxy_display_name" {
- type = string
-}
-
-variable "coder_proxy_icon" {
- type = string
+variable "proxy" {
+ type = object({
+ access_url = string
+ wildcard_url = string
+ coder_access_url = string
+ mount_ssl = optional(bool, false)
+ mount_ssl_name = optional(string, "cert")
+ name = string
+ display_name = string
+ icon = optional(string, "")
+ rep_cnt = optional(number, 1)
+ image_repo = optional(string, "ghcr.io/coder/coder")
+ image_tag = optional(string, "latest")
+ image_pull_policy = optional(string, "IfNotPresent")
+ image_pull_secrets = optional(list(string), null)
+ trace_logs = optional(bool, true)
+ log_filter = optional(string, ".*")
+ })
}
##
-# TLS/SSL Inputs
+# Kubernetes Inputs
##
-variable "acme_registration_email" {
- type = string
- default = ""
-}
-
-variable "acme_days_until_renewal" {
- type = number
- default = 30
-}
-
-variable "acme_revoke_certificate" {
- type = bool
- default = true
+variable "release_name" {
+ description = "The release name of the installed Helm app."
+ type = string
+ default = "coder-proxy"
}
-variable "cloudflare_api_token" {
- type = string
- default = ""
- sensitive = true
+variable "chart_name" {
+ description = "The chart name of the installed Helm app."
+ type = string
+ default = "coder"
}
-
-##
-# Kubernetes Inputs
-##
-
variable "namespace" {
type = string
}
-variable "helm_timeout" {
+variable "chart_timeout" {
type = number
- default = 120 # In Seconds
+ default = 300 # In Seconds
}
-variable "helm_version" {
+variable "chart_version" {
type = string
- default = "2.25.1"
-}
-
-variable "image_repo" {
- type = string
- default = "ghcr.io/coder/coder"
-}
-
-variable "image_tag" {
- type = string
- default = "latest"
-}
-
-variable "image_pull_policy" {
- type = string
- default = "IfNotPresent"
-}
-
-variable "image_pull_secrets" {
- type = list(string)
- default = []
-}
-
-variable "replica_count" {
- type = number
- default = 0
+ default = "2.30.0"
}
variable "env_vars" {
@@ -112,7 +78,7 @@ variable "env_vars" {
default = {}
}
-variable "load_balancer_class" {
+variable "lb_class" {
type = string
default = "service.k8s.aws/nlb"
}
@@ -129,22 +95,16 @@ variable "resource_request" {
}
variable "resource_limit" {
- type = object({
- cpu = string
- memory = string
- })
- default = {
- cpu = "500m"
- memory = "1Gi"
- }
+ type = map(string)
+ default = {}
}
-variable "service_annotations" {
+variable "svc_annot" {
type = map(string)
default = {}
}
-variable "service_account_annotations" {
+variable "svc_acc_annot" {
type = map(string)
default = {}
}
@@ -155,16 +115,11 @@ variable "node_selector" {
}
variable "tolerations" {
- type = list(object({
- key = string
- operator = optional(string, "Equal")
- value = string
- effect = optional(string, "NoSchedule")
- }))
+ type = list(any)
default = []
}
-variable "topology_spread_constraints" {
+variable "topology_spread" {
type = list(object({
max_skew = number
topology_key = string
@@ -177,209 +132,166 @@ variable "topology_spread_constraints" {
default = []
}
-variable "pod_anti_affinity_preferred_during_scheduling_ignored_during_execution" {
- type = list(object({
- weight = number
- pod_affinity_term = object({
- label_selector = object({
- match_labels = map(string)
- })
- topology_key = string
- })
- }))
- default = []
-}
-
-variable "primary_access_url" {
- type = string
-}
-
-variable "proxy_access_url" {
- type = string
-}
-
-variable "proxy_wildcard_url" {
- type = string
+variable "affinity" {
+ type = any
+ default = {}
}
-variable "termination_grace_period_seconds" {
+variable "termination_grace_period" {
type = number
default = 600
}
-variable "ssl_cert_config" {
- type = object({
- name = string
- create_secret = optional(bool, true)
- })
- default = {
- name = "coder-proxy-tls"
- create_secret = true
- }
-}
-
-variable "proxy_token_config" {
- type = object({
- name = optional(string, "proxy-token")
- key = optional(string, "proxy.key")
- })
- default = {
- name = "proxy-token"
- key = "proxy.key"
- }
-}
-
-
resource "coderd_workspace_proxy" "this" {
- name = var.coder_proxy_name
- display_name = var.coder_proxy_display_name
- icon = var.coder_proxy_icon
+ name = var.proxy.name
+ display_name = var.proxy.display_name
+ icon = var.proxy.icon
}
-resource "kubernetes_namespace" "this" {
+locals {
+ proxy = {
+ CODER_ACCESS_URL = var.proxy.access_url
+ CODER_WILDCARD_ACCESS_URL = var.proxy.wildcard_url
+ CODER_PRIMARY_ACCESS_URL = var.proxy.coder_access_url
+ CODER_PROXY_SESSION_TOKEN = coderd_workspace_proxy.this.session_token
+ CODER_TRACE_LOGS = var.proxy.trace_logs
+ CODER_LOG_FILTER = var.proxy.log_filter
+ }
+ secrets = {
+ CODER_PROXY_SESSION_TOKEN = local.proxy["CODER_PROXY_SESSION_TOKEN"]
+ }
+ secret_key = "key"
+ secret_keys = keys(local.secrets)
+ topology_spread = [
+ for k, v in var.topology_spread : {
+ maxSkew = v.max_skew
+ topologyKey = v.topology_key
+ whenUnsatisfiable = v.when_unsatisfiable
+ labelSelector = {
+ matchLabels = try(v.label_selector.match_labels, {})
+ }
+ matchLabelKeys = v.match_label_keys
+ }
+ ]
+ env = concat([ for k,v in merge(
+ local.proxy
+ ) : {
+ name = k,
+ value = tostring(v)
+ } if lookup(local.secrets, k, null) == null ], [
+ for k,v in local.secrets : {
+ name = k,
+ valueFrom = {
+ secretKeyRef = {
+ name = replace(lower(k), "_", "-"),
+ key = local.secret_key
+ }
+ }
+ } if v != null
+ ])
+}
+
+resource "kubernetes_namespace_v1" "this" {
metadata {
name = var.namespace
}
}
-resource "kubernetes_secret" "coder-proxy-key" {
+resource "kubernetes_secret_v1" "coder" {
+
+ for_each = toset(local.secret_keys)
+
metadata {
- name = var.proxy_token_config.name
- namespace = kubernetes_namespace.this.metadata[0].name
+ name = replace(lower(each.key), "_", "-")
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ annotations = {
+ "custom.kubernetes.secret/key" = local.secret_key
+ }
}
data = {
- "${var.proxy_token_config.key}" = coderd_workspace_proxy.this.session_token
+ "${local.secret_key}" = sensitive(local.secrets[each.key])
}
- type = "Opaque"
}
-locals {
- common_name = trimprefix(trimprefix(var.proxy_access_url, "https://"), "http://")
- wildcard_name = trimprefix(trimprefix(var.proxy_wildcard_url, "https://"), "http://")
-}
+resource "kubernetes_service_v1" "coder" {
+
+ wait_for_load_balancer = true
-resource "kubernetes_manifest" "certificate" {
-
- count = var.ssl_cert_config.create_secret ? 1 : 0
-
- field_manager {
- force_conflicts = true
+ metadata {
+ name = var.release_name
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ labels = {}
+ annotations = var.svc_annot
}
- manifest = {
- apiVersion = "cert-manager.io/v1"
- kind = "Certificate"
- metadata = {
- labels = {} # var.cert_labels
- name = var.ssl_cert_config.name
- namespace = kubernetes_namespace.this.metadata[0].name
+ spec {
+ type = "LoadBalancer"
+ load_balancer_class = var.lb_class
+ port {
+ name = "http"
+ port = 80
+ protocol = "TCP"
+ target_port = "http"
}
- spec = {
- secretName = var.ssl_cert_config.name
- commonName = local.common_name
- dnsNames = [local.common_name, local.wildcard_name]
- duration = "${var.acme_days_until_renewal * 24}h"
- renewBefore = "8h"
- issuerRef = {
- kind = "ClusterIssuer"
- name = "issuer"
- }
+ port {
+ name = "https"
+ port = 443
+ protocol = "TCP"
+ target_port = var.proxy.mount_ssl ? "https" : "http"
}
- }
-}
-
-locals {
- primary_env_vars = {
- CODER_PRIMARY_ACCESS_URL = var.primary_access_url
- CODER_ACCESS_URL = var.proxy_access_url
- CODER_WILDCARD_ACCESS_URL = var.proxy_wildcard_url
- }
- env_vars = concat([
- for k, v in merge(local.primary_env_vars, var.env_vars) : { name = k, value = v }
- ], [{
- name = "CODER_PROXY_SESSION_TOKEN"
- valueFrom = {
- secretKeyRef = {
- name = kubernetes_secret.coder-proxy-key.metadata[0].name
- key = var.proxy_token_config.key
- }
- }
- }])
- pod_anti_affinity_preferred_during_scheduling_ignored_during_execution = [
- for k, v in var.pod_anti_affinity_preferred_during_scheduling_ignored_during_execution : {
- weight = v.weight
- podAffinityTerm = {
- labelSelector = {
- matchLabels = try(v.pod_affinity_term.label_selector.match_labels, {})
- }
- topologyKey = try(v.pod_affinity_term.topology_key, {})
- }
- }
- ]
- topology_spread_constraints = [
- for k, v in var.topology_spread_constraints : {
- maxSkew = v.max_skew
- topologyKey = v.topology_key
- whenUnsatisfiable = v.when_unsatisfiable
- labelSelector = {
- matchLabels = try(v.label_selector.match_labels, {})
- }
- matchLabelKeys = v.match_label_keys
-
+ selector = {
+ "app.kubernetes.io/instance" = var.release_name
+ "app.kubernetes.io/name" = var.chart_name
+ "app.kubernetes.io/part-of" = var.chart_name
}
- ]
+ }
}
resource "helm_release" "coder-proxy" {
- name = "coder-v2"
- namespace = kubernetes_namespace.this.metadata[0].name
- chart = "coder"
+ name = var.release_name
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ chart = var.chart_name
repository = "https://helm.coder.com/v2"
create_namespace = false
upgrade_install = true
skip_crds = false
wait = true
wait_for_jobs = true
- version = var.helm_version
- timeout = var.helm_timeout
+ version = var.chart_version
+ timeout = var.chart_timeout
values = [yamlencode({
coder = {
image = {
- repo = var.image_repo
- tag = var.image_tag
- pullPolicy = var.image_pull_policy
- pullSecrets = var.image_pull_secrets
+ repo = var.proxy.image_repo
+ tag = var.proxy.image_tag
+ pullPolicy = var.proxy.image_pull_policy
+ pullSecrets = var.proxy.image_pull_secrets
}
workspaceProxy = true
- env = local.env_vars
- tls = {
- secretNames = [var.ssl_cert_config.name]
- }
+ env = local.env
service = {
- enable = true
- type = "LoadBalancer"
- sessionAffinity = "None"
- externalTrafficPolicy = "Cluster"
- loadBalancerClass = var.load_balancer_class
- annotations = var.service_annotations
+ enable = false
+ }
+ tls = {
+ secretNames = var.proxy.mount_ssl ? [ var.proxy.mount_ssl_name ] : []
}
- replicaCount = var.replica_count
+ replicaCount = var.proxy.rep_cnt
resources = {
requests = var.resource_request
limits = var.resource_limit
}
serviceAccount = {
- annotations = var.service_account_annotations
+ annotations = var.svc_acc_annot
}
nodeSelector = var.node_selector
tolerations = var.tolerations
- topologySpreadConstraints = local.topology_spread_constraints
- affinity = {
- podAntiAffinity = {
- preferredDuringSchedulingIgnoredDuringExecution = local.pod_anti_affinity_preferred_during_scheduling_ignored_during_execution
- }
- }
- terminationGracePeriodSeconds = var.termination_grace_period_seconds
+ topologySpreadConstraints = local.topology_spread
+ affinity = var.affinity
+ terminationGracePeriodSeconds = var.termination_grace_period
}
})]
+}
+
+output "namespace" {
+ value = kubernetes_namespace_v1.this.metadata[0].name
}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/coder-server/main.tf b/modules/k8s/bootstrap/coder-server/main.tf
index a3e80ad..8b195c6 100644
--- a/modules/k8s/bootstrap/coder-server/main.tf
+++ b/modules/k8s/bootstrap/coder-server/main.tf
@@ -10,15 +10,21 @@ terraform {
kubernetes = {
source = "hashicorp/kubernetes"
}
- acme = {
- source = "vancluever/acme"
- }
- tls = {
- source = "hashicorp/tls"
- }
}
}
+variable "release_name" {
+ description = "The release name of the installed Helm app."
+ type = string
+ default = "coder"
+}
+
+variable "chart_name" {
+ description = "The chart name of the installed Helm app."
+ type = string
+ default = "coder"
+}
+
variable "cluster_name" {
type = string
}
@@ -29,50 +35,24 @@ variable "cluster_oidc_provider_arn" {
variable "policy_resource_region" {
type = string
- default = ""
+ default = null
}
variable "policy_resource_account" {
type = string
- default = ""
+ default = null
}
variable "policy_name" {
type = string
- default = ""
+ default = "coder-srv"
}
variable "role_name" {
type = string
- default = ""
-}
-
-##
-# TLS/SSL Inputs
-##
-
-variable "acme_registration_email" {
- type = string
- default = ""
-}
-
-variable "acme_days_until_renewal" {
- type = number
- default = 30
-}
-
-variable "acme_revoke_certificate" {
- type = bool
- default = true
-}
-
-variable "cloudflare_api_token" {
- type = string
- default = ""
- sensitive = true
+ default = "coder-srv"
}
-
##
# Kubernetes Inputs
##
@@ -83,47 +63,58 @@ variable "namespace" {
variable "helm_timeout" {
type = number
- default = 120 # In Seconds
+ default = 300 # In Seconds
}
-variable "helm_version" {
+variable "chart_version" {
type = string
default = "2.25.1"
}
-variable "image_repo" {
- type = string
- default = "ghcr.io/coder/coder"
-}
-
-variable "image_tag" {
- type = string
- default = "latest"
-}
-
-variable "image_pull_policy" {
- type = string
- default = "IfNotPresent"
-}
-
-variable "image_pull_secrets" {
- type = list(string)
- default = []
-}
-
-variable "replica_count" {
- type = number
- default = 0
+variable "coder" {
+ type = object({
+ access_url = string
+ wildcard_url = string
+ redirect = optional(bool, true)
+ mount_ssl = optional(bool, false)
+ mount_ssl_name = optional(string, "cert")
+ image_repo = optional(string, "ghcr.io/coder/coder")
+ image_tag = optional(string, "latest")
+ image_pull_policy = optional(string, "IfNotPresent")
+ image_pull_secrets = optional(list(string), null)
+ csp_policy = optional(string, null)
+ env_vars = optional(map(string), {})
+ rep_cnt = optional(number, 1)
+ prov_rep_cnt = optional(number, 2)
+ prov_force_cancel_interval = optional(string, "10m0s")
+ quiet_hours = optional(string, "CRON_TZ=America/Los_Angeles 50 23 * * *")
+ allow_custom_quiet = optional(bool, true)
+ tf_debug_mode = optional(bool, true)
+ trace_logs = optional(bool, true)
+ enable_tracing = optional(bool, true)
+ swagger_enable = optional(bool, true)
+ update_check = optional(bool, true)
+ cli_upgr_msg = optional(bool, true)
+ log_filter = optional(string, ".*")
+ })
}
-variable "env_vars" {
- type = map(string)
- default = {}
+variable "prometheus" {
+ type = object({
+ enable = optional(bool, true)
+ collect_agent_status = optional(bool, true)
+ collect_db_metrics = optional(bool, true)
+ })
+ default = {
+ enable = false
+ collect_agent_status = false
+ collect_db_metrics = false
+ }
}
-variable "load_balancer_class" {
- type = string
- default = "service.k8s.aws/nlb"
+variable "termination_grace_period" {
+ type = number
+ default = 600
}
variable "resource_request" {
@@ -137,23 +128,27 @@ variable "resource_request" {
}
}
+variable "tags" {
+ type = map(string)
+ default = {}
+}
+
variable "resource_limit" {
- type = object({
- cpu = string
- memory = string
- })
- default = {
- cpu = "4000m"
- memory = "8Gi"
- }
+ type = map(any)
+ default = {}
}
-variable "service_annotations" {
+variable "svc_annot" {
type = map(string)
default = {}
}
-variable "service_account_annotations" {
+variable "lb_class" {
+ type = string
+ default = "service.k8s.aws/nlb"
+}
+
+variable "svc_acc_annot" {
type = map(string)
default = {}
}
@@ -164,16 +159,11 @@ variable "node_selector" {
}
variable "tolerations" {
- type = list(object({
- key = string
- operator = optional(string, "Equal")
- value = string
- effect = optional(string, "NoSchedule")
- }))
+ type = list(any)
default = []
}
-variable "topology_spread_constraints" {
+variable "topology_spread" {
type = list(object({
max_skew = number
topology_key = string
@@ -186,220 +176,212 @@ variable "topology_spread_constraints" {
default = []
}
-variable "pod_anti_affinity_preferred_during_scheduling_ignored_during_execution" {
- type = list(object({
- weight = number
- pod_affinity_term = object({
- label_selector = object({
- match_labels = map(string)
- })
- topology_key = string
- })
- }))
- default = []
-}
-
-variable "primary_access_url" {
- type = string
-}
-
-variable "wildcard_access_url" {
- type = string
-}
-
-variable "termination_grace_period_seconds" {
- type = number
- default = 600
+variable "affinity" {
+ type = any
+ default = {}
}
-variable "ssl_cert_config" {
+variable "db" {
type = object({
- name = string
- create_secret = optional(bool, true)
+ url = string
+ username = string
+ password = string
+ db = optional(string, "coder")
+ pg_auth = optional(string, "password")
})
- default = {
- name = "coder-tls"
- create_secret = true
- }
}
-variable "db_secret_name" {
- type = string
- default = "postgres"
-}
-
-variable "db_secret_key" {
- type = string
- default = "url"
-}
-
-variable "db_secret_url" {
- type = string
- sensitive = true
-}
-
-variable "oidc_config" {
+variable "oidc" {
type = object({
+ enable = bool
sign_in_text = string
icon_url = string
- scopes = list(string)
+ scopes = optional(list(string), null)
email_domain = string
+ issuer_url = optional(string, null)
+ client_id = optional(string, null)
+ client_secret = optional(string, null)
})
+ default = {
+ enable = false
+ sign_in_text = null
+ icon_url = null
+ scopes = null
+ email_domain = null
+ issuer_url = null
+ client_id = null
+ client_secret = null
+ }
}
-variable "oidc_secret_name" {
- type = string
- default = "oidc"
-}
-
-variable "oidc_secret_issuer_url_key" {
- type = string
- default = "issuer-url"
-}
-
-variable "oidc_secret_issuer_url" {
- type = string
- sensitive = true
-}
-
-variable "oidc_secret_client_id_key" {
- type = string
- default = "client-id"
-}
-
-variable "oidc_secret_client_id" {
- type = string
- sensitive = true
-}
-
-variable "oidc_secret_client_secret_key" {
- type = string
- default = "client-secret"
-}
-
-variable "oidc_secret_client_secret" {
- type = string
- sensitive = true
-}
-
-variable "oauth_secret_name" {
- type = string
- default = "oauth"
-}
-
-variable "oauth_secret_client_id_key" {
- type = string
- default = "client-id"
-}
-
-variable "oauth_secret_client_id" {
- type = string
- sensitive = true
-}
-
-variable "oauth_secret_client_secret_key" {
- type = string
- default = "client-secret"
-}
-
-variable "oauth_secret_client_secret" {
- type = string
- sensitive = true
-}
-
-variable "github_external_auth_config" {
+variable "oauth2" {
type = object({
- id = string
- type = optional(string, "github")
+ enable = bool
+ default_provider_enable = bool
+ allow_signups = bool
+ device_flow = bool
+ allowed_orgs = list(string) # Empty list means allow everyone
+ client_id = string
+ client_secret = string
+ use_extern_auth = bool
})
default = {
- id = "primary-github"
- type = "github"
+ enable = false
+ default_provider_enable = false
+ allow_signups = true
+ device_flow = false
+ allowed_orgs = []
+ client_id = null
+ client_secret = null
+ use_extern_auth = false
}
}
-variable "github_external_auth_secret_name" {
- type = string
- default = "github-external-auth"
-}
-
-variable "github_external_auth_secret_client_id_key" {
- type = string
- default = "client-id"
-}
-
-variable "github_external_auth_secret_client_id" {
- type = string
- sensitive = true
-}
-
-variable "github_external_auth_secret_client_secret_key" {
- type = string
- default = "client-secret"
-}
-
-variable "github_external_auth_secret_client_secret" {
- type = string
- sensitive = true
-}
-
-variable "coder_builtin_provisioner_count" {
- type = number
- default = 3
-}
-
-variable "coder_experiments" {
- type = list(string)
- default = []
-}
-
-variable "coder_github_allowed_orgs" {
- type = list(string)
+variable "extern_auth" {
+ type = list(object({
+ id = string
+ type = string
+ client_id = string
+ client_secret = string
+ auth_url = optional(string, null)
+ token_url = optional(string, null)
+ revoke_url = optional(string, null)
+ validate_url = optional(string, null)
+ regex = optional(string, null)
+ }))
default = []
}
-variable "prometheus_port" {
- type = number
- default = 2112
-}
-
-variable "openai_llm_endpoint" {
- type = string
- sensitive = true
- default = ""
-}
-
-variable "openai_llm_secret_name" {
- type = string
- default = "coder-openai-llm-key"
-}
-
-variable "openai_llm_key" {
- type = string
- sensitive = true
- default = ""
-}
-
-variable "anthropic_llm_endpoint" {
- type = string
- sensitive = true
- default = ""
-}
-
-variable "anthropic_llm_secret_name" {
- type = string
- default = "coder-anthropic-llm-key"
+variable "aibridge" {
+ type = object({
+ enabled = bool
+ enable_structured_logging = optional(bool, true)
+ })
+ default = {
+ enabled = false
+ enable_structured_logging = true
+ }
}
-variable "anthropic_llm_key" {
- type = string
- sensitive = true
- default = ""
-}
+locals {
+ coder = {
+ CODER_ACCESS_URL = var.coder.access_url
+ CODER_WILDCARD_ACCESS_URL = var.coder.wildcard_url
+
+ # TLS Termination handled on the LB
+ CODER_REDIRECT_TO_ACCESS_URL = var.coder.mount_ssl
+ CODER_TLS_ENABLE = var.coder.mount_ssl
+
+ CODER_ENABLE_TERRAFORM_DEBUG_MODE = var.coder.tf_debug_mode
+ CODER_TRACE_LOGS = var.coder.trace_logs
+ CODER_TRACE_ENABLE = var.coder.enable_tracing
+ CODER_LOG_FILTER = var.coder.log_filter
+ CODER_SWAGGER_ENABLE = var.coder.swagger_enable
+ CODER_UPDATE_CHECK = var.coder.update_check
+ CODER_CLI_UPGRADE_MESSAGE = var.coder.cli_upgr_msg
+
+ CODER_PROVISIONER_DAEMONS = var.coder.prov_rep_cnt
+ CODER_PROVISIONER_FORCE_CANCEL_INTERVAL = var.coder.prov_force_cancel_interval
+ CODER_QUIET_HOURS_DEFAULT_SCHEDULE = var.coder.quiet_hours
+ CODER_ALLOW_CUSTOM_QUIET_HOURS = var.coder.allow_custom_quiet
+ }
+ prom = {
+ CODER_PROMETHEUS_ENABLE = var.prometheus.enable
+ CODER_PROMETHEUS_COLLECT_AGENT_STATS = var.prometheus.collect_agent_status
+ CODER_PROMETHEUS_COLLECT_DB_METRICS = var.prometheus.collect_db_metrics
+ }
+ db = merge({
+ CODER_PG_AUTH = var.db.pg_auth
+ }, var.db.pg_auth == "awsiamrds" ? {
+ CODER_PG_CONNECTION_URL = "postgresql://${var.db.username}@${var.db.url}/${var.db.db}"
+ } : {
+ CODER_PG_CONNECTION_URL = "postgresql://${var.db.username}:${var.db.password}@${var.db.url}/${var.db.db}"
+ })
+ oidc = !var.oidc.enable ? {} : {
+ CODER_OIDC_ISSUER_URL = var.oidc.issuer_url
+ CODER_OIDC_CLIENT_ID = var.oidc.client_id
+ CODER_OIDC_CLIENT_SECRET = var.oidc.client_secret
+ CODER_OIDC_SIGN_IN_TEXT = var.oidc.sign_in_text
+ CODER_OIDC_ICON_URL = var.oidc.icon_url
+ CODER_OIDC_SCOPES = join(",", var.oidc.scopes)
+ CODER_OIDC_EMAIL_DOMAIN = var.oidc.email_domain
+ }
+ oauth2 = !var.oauth2.enable ? {
+ CODER_OAUTH2_GITHUB_DEFAULT_PROVIDER_ENABLE = false
+ } : {
+ CODER_OAUTH2_GITHUB_DEFAULT_PROVIDER_ENABLE = var.oauth2.default_provider_enable
+ CODER_OAUTH2_GITHUB_CLIENT_ID = var.oauth2.client_id
+ CODER_OAUTH2_GITHUB_CLIENT_SECRET = var.oauth2.client_secret
+ CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS = var.oauth2.allow_signups
+ CODER_OAUTH2_GITHUB_DEVICE_FLOW = var.oauth2.device_flow
+ CODER_OAUTH2_GITHUB_ALLOW_EVERYONE = "${length(var.oauth2.allowed_orgs) == 0}"
+ CODER_OAUTH2_GITHUB_ALLOWED_ORGS = join(",", var.oauth2.allowed_orgs)
+ }
+ extern_auth = merge([for index, obj in var.extern_auth : {
+ "CODER_EXTERNAL_AUTH_${index}_ID" = obj.id
+ "CODER_EXTERNAL_AUTH_${index}_TYPE" = obj.type
+ "CODER_EXTERNAL_AUTH_${index}_CLIENT_ID" = obj.client_id
+ "CODER_EXTERNAL_AUTH_${index}_CLIENT_SECRET" = obj.client_secret
+ "CODER_EXTERNAL_AUTH_${index}_AUTH_URL" = obj.auth_url
+ "CODER_EXTERNAL_AUTH_${index}_TOKEN_URL" = obj.token_url
+ "CODER_EXTERNAL_AUTH_${index}_REVOKE_URL" = obj.revoke_url
+ "CODER_EXTERNAL_AUTH_${index}_VALIDATE_URL" = obj.validate_url
+ "CODER_EXTERNAL_AUTH_${index}_REGEX" = obj.regex
+ }]...)
+ aibridge = merge(
+ {
+ CODER_AIBRIDGE_ENABLED = var.aibridge.enabled
+ CODER_AIBRIDGE_STRUCTURED_LOGGING = var.aibridge.enable_structured_logging
+ }
+ )
+ secrets = merge({
+ CODER_OAUTH2_GITHUB_CLIENT_SECRET = try(local.oauth2["CODER_OAUTH2_GITHUB_CLIENT_SECRET"], null)
+ CODER_OIDC_CLIENT_SECRET = try(local.oidc["CODER_OIDC_CLIENT_SECRET"], null)
+ CODER_PG_CONNECTION_URL = try(local.db["CODER_PG_CONNECTION_URL"], null)
+ }, { for index, obj in var.extern_auth :
+ "CODER_EXTERNAL_AUTH_${index}_CLIENT_SECRET" => obj.client_secret
+ })
+ secret_key = "key"
+ secret_keys = keys(local.secrets)
+ env = concat([ for k,v in merge(
+ local.coder,
+ local.prom,
+ local.db,
+ local.oidc,
+ local.oauth2,
+ local.extern_auth,
+ local.aibridge,
+ var.coder.env_vars
+ ) : {
+ name = k,
+ value = tostring(v)
+ } if lookup(local.secrets, k, null) == null ], [
+ for k,v in local.secrets : {
+ name = k,
+ valueFrom = {
+ secretKeyRef = {
+ name = replace(lower(k), "_", "-"),
+ key = local.secret_key
+ }
+ }
+ } if v != null
+ ])
+}
+
+resource "kubernetes_secret_v1" "coder" {
+
+ for_each = toset(local.secret_keys)
-variable "tags" {
- type = map(string)
- default = {}
+ metadata {
+ name = replace(lower(each.key), "_", "-")
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ annotations = {
+ "custom.kubernetes.secret/key" = local.secret_key
+ }
+ }
+ data = {
+ "${local.secret_key}" = sensitive(local.secrets[each.key])
+ }
}
data "aws_region" "this" {}
@@ -407,164 +389,8 @@ data "aws_region" "this" {}
data "aws_caller_identity" "this" {}
locals {
- github_allow_everyone = length(var.coder_github_allowed_orgs) == 0
- primary_env_vars = {
- CODER_ACCESS_URL = var.primary_access_url
- CODER_WILDCARD_ACCESS_URL = var.wildcard_access_url
- CODER_REDIRECT_TO_ACCESS_URL = true
- CODER_PG_AUTH = "password"
-
- CODER_OIDC_SIGN_IN_TEXT = var.oidc_config.sign_in_text
- CODER_OIDC_ICON_URL = var.oidc_config.icon_url
- CODER_OIDC_SCOPES = join(",", var.oidc_config.scopes)
- CODER_OIDC_EMAIL_DOMAIN = var.oidc_config.email_domain
-
- CODER_OAUTH2_GITHUB_DEFAULT_PROVIDER_ENABLE = false
- CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS = true
- CODER_OAUTH2_GITHUB_DEVICE_FLOW = false
- "${local.github_allow_everyone ? "CODER_OAUTH2_GITHUB_ALLOW_EVERYONE" : "CODER_OAUTH2_GITHUB_ALLOWED_ORGS"}" = "${local.github_allow_everyone ? "true" : join(",", var.coder_github_allowed_orgs)}"
-
- CODER_EXTERNAL_AUTH_0_ID = "primary-github"
- CODER_EXTERNAL_AUTH_0_TYPE = "github"
-
- CODER_ENABLE_TERRAFORM_DEBUG_MODE = true
- CODER_TRACE_LOGS = true
- CODER_LOG_FILTER = ".*"
- CODER_SWAGGER_ENABLE = true
- CODER_UPDATE_CHECK = true
- CODER_CLI_UPGRADE_MESSAGE = true
-
- CODER_PROVISIONER_DAEMONS = var.coder_builtin_provisioner_count
- CODER_PROVISIONER_FORCE_CANCEL_INTERVAL = "10m0s"
- CODER_QUIET_HOURS_DEFAULT_SCHEDULE = "CRON_TZ=America/Los_Angeles 50 23 * * *"
- CODER_ALLOW_CUSTOM_QUIET_HOURS = true
-
- CODER_PROMETHEUS_ENABLE = true
- CODER_PROMETHEUS_COLLECT_AGENT_STATS = true
- CODER_PROMETHEUS_COLLECT_DB_METRICS = true
- CODER_PROMETHEUS_ADDRESS = "127.0.0.1:${var.prometheus_port}"
-
- CODER_AIBRIDGE_ENABLED = var.openai_llm_endpoint != "" || var.anthropic_llm_endpoint != ""
-
- # Experimental Coder Features
- CODER_EXPERIMENTS = join(",", var.coder_experiments)
- # Needed by the ai-tasks experiment to embed workspace apps running on subdomains in iframes
- CODER_ADDITIONAL_CSP_POLICY = "frame-src ${var.primary_access_url}"
- }
- env_vars = concat([
- for k, v in merge(local.primary_env_vars, var.env_vars) : { name = k, value = tostring(v) }
- ], concat([{
- name = "CODER_PG_CONNECTION_URL"
- valueFrom = {
- secretKeyRef = {
- name = var.db_secret_name
- key = var.db_secret_key
- }
- }
- }, {
- name = "CODER_OIDC_ISSUER_URL"
- valueFrom = {
- secretKeyRef = {
- name = var.oidc_secret_name
- key = var.oidc_secret_issuer_url_key
- }
- }
- }, {
- name = "CODER_OIDC_CLIENT_ID"
- valueFrom = {
- secretKeyRef = {
- name = var.oidc_secret_name
- key = var.oidc_secret_client_id_key
- }
- }
- }, {
- name = "CODER_OIDC_CLIENT_SECRET"
- valueFrom = {
- secretKeyRef = {
- name = var.oidc_secret_name
- key = var.oidc_secret_client_secret_key
- }
- }
- }, {
- name = "CODER_OAUTH2_GITHUB_CLIENT_ID"
- valueFrom = {
- secretKeyRef = {
- name = var.oauth_secret_name
- key = var.oauth_secret_client_id_key
- }
- }
- }, {
- name = "CODER_OAUTH2_GITHUB_CLIENT_SECRET"
- valueFrom = {
- secretKeyRef = {
- name = var.oauth_secret_name
- key = var.oauth_secret_client_secret_key
- }
- }
- }, {
- name = "CODER_EXTERNAL_AUTH_0_CLIENT_ID"
- valueFrom = {
- secretKeyRef = {
- name = var.oauth_secret_name
- key = var.oauth_secret_client_id_key
- }
- }
- }, {
- name = "CODER_EXTERNAL_AUTH_0_CLIENT_SECRET"
- valueFrom = {
- secretKeyRef = {
- name = var.oauth_secret_name
- key = var.oauth_secret_client_secret_key
- }
- }
- },
- ], concat(var.anthropic_llm_endpoint != "" ? [{
- name = "CODER_AIBRIDGE_ANTHROPIC_KEY"
- valueFrom = {
- secretKeyRef = {
- name = var.anthropic_llm_secret_name
- key = "key"
- }
- }
- }, {
- name = "CODER_AIBRIDGE_ANTHROPIC_BASE_URL"
- valueFrom = {
- secretKeyRef = {
- name = var.anthropic_llm_secret_name
- key = "base_url"
- }
- }
- }] : [],
- var.openai_llm_endpoint != "" ? [{
- name = "CODER_AIBRIDGE_OPENAI_KEY"
- valueFrom = {
- secretKeyRef = {
- name = var.openai_llm_secret_name
- key = "key"
- }
- }
- }, {
- name = "CODER_AIBRIDGE_OPENAI_BASE_URL"
- valueFrom = {
- secretKeyRef = {
- name = var.openai_llm_secret_name
- key = "base_url"
- }
- }
- }] : [])))
- pod_anti_affinity_preferred_during_scheduling_ignored_during_execution = [
- for k, v in var.pod_anti_affinity_preferred_during_scheduling_ignored_during_execution : {
- weight = v.weight
- podAffinityTerm = {
- labelSelector = {
- matchLabels = try(v.pod_affinity_term.label_selector.match_labels, {})
- }
- topologyKey = try(v.pod_affinity_term.topology_key, {})
- }
- }
- ]
- topology_spread_constraints = [
- for k, v in var.topology_spread_constraints : {
+ topology_spread = [
+ for k, v in var.topology_spread : {
maxSkew = v.max_skew
topologyKey = v.topology_key
whenUnsatisfiable = v.when_unsatisfiable
@@ -577,30 +403,40 @@ locals {
}
locals {
- region = var.policy_resource_region == "" ? data.aws_region.this.region : var.policy_resource_region
- account_id = var.policy_resource_account == "" ? data.aws_caller_identity.this.account_id : var.policy_resource_account
- policy_name = var.policy_name == "" ? "Server-${data.aws_region.this.region}" : var.policy_name
- role_name = var.role_name == "" ? "server-${data.aws_region.this.region}" : var.role_name
+ region = data.aws_region.this.region
+ account_id = data.aws_caller_identity.this.account_id
}
module "provisioner-policy" {
- count = var.coder_builtin_provisioner_count == 0 ? 0 : 1
+
+ count = var.coder.prov_rep_cnt == 0 ? 0 : 1
+
source = "../../../security/policy"
- name = local.policy_name
- path = "/"
+ name = var.policy_name
+ path = "/${var.cluster_name}/${local.region}/"
description = "Coder Terraform External Provisioner Policy"
- policy_json = data.aws_iam_policy_document.provisioner-policy.json
+ policy_json = data.aws_iam_policy_document.provisioner.json
+}
+
+module "rds-policy" {
+ source = "../../../security/policy"
+ name = "${var.policy_name}-${local.rds_db_name}"
+ path = "/${var.cluster_name}/${local.region}/"
+ description = "Coder DB IAM Access Policy"
+ policy_json = data.aws_iam_policy_document.rds.json
}
module "provisioner-oidc-role" {
- count = var.coder_builtin_provisioner_count == 0 ? 0 : 1
source = "../../../security/role/access-entry"
- name = local.role_name
+ name = var.role_name
+ path = "/${var.cluster_name}/${local.region}/"
cluster_name = var.cluster_name
- policy_arns = {
+ policy_arns = merge({
"AmazonEC2ReadOnlyAccess" = "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
+ "CoderRDSDBPolicy" = module.rds-policy.policy_arn
+ }, var.coder.prov_rep_cnt == 0 ? {} : {
"TFProvisionerPolicy" = module.provisioner-policy[0].policy_arn
- }
+ })
cluster_policy_arns = {}
oidc_principals = {
"${var.cluster_oidc_provider_arn}" = ["system:serviceaccount:*:*"]
@@ -608,183 +444,123 @@ module "provisioner-oidc-role" {
tags = var.tags
}
-resource "kubernetes_namespace" "this" {
+resource "kubernetes_namespace_v1" "this" {
metadata {
name = var.namespace
}
}
+resource "kubernetes_service_v1" "coder" {
+
+ wait_for_load_balancer = true
+
+ metadata {
+ name = var.release_name
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ labels = {}
+ annotations = var.svc_annot
+ }
+ spec {
+ type = "LoadBalancer"
+ load_balancer_class = var.lb_class
+ port {
+ name = "http"
+ port = 80
+ protocol = "TCP"
+ target_port = "http"
+ }
+ port {
+ name = "https"
+ port = 443
+ protocol = "TCP"
+ target_port = var.coder.mount_ssl ? "https" : "http"
+ }
+ selector = {
+ "app.kubernetes.io/instance" = var.chart_name
+ "app.kubernetes.io/name" = var.release_name
+ }
+ }
+}
+
+resource "kubernetes_service_v1" "prometheus" {
+ count = var.prometheus.enable ? 1 : 0
+ metadata {
+ name = "${var.release_name}-prometheus"
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ labels = {}
+ }
+ spec {
+ type = "ClusterIP"
+ cluster_ip = "None"
+ port {
+ name = "http"
+ protocol = "TCP"
+ port = 2112
+ target_port = 2112
+ }
+ selector = {
+ "app.kubernetes.io/instance" = var.chart_name
+ "app.kubernetes.io/name" = var.release_name
+ }
+ }
+}
+
resource "helm_release" "coder-server" {
- name = "coder"
- namespace = kubernetes_namespace.this.metadata[0].name
- chart = "coder"
+
+ name = var.release_name
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ chart = var.chart_name
repository = "https://helm.coder.com/v2"
create_namespace = false
upgrade_install = true
skip_crds = false
wait = true
wait_for_jobs = true
- version = var.helm_version
+ version = var.chart_version
timeout = var.helm_timeout
values = [yamlencode({
coder = {
image = {
- repo = var.image_repo
- tag = var.image_tag
- pullPolicy = var.image_pull_policy
- pullSecrets = var.image_pull_secrets
- }
- env = local.env_vars
- tls = {
- secretNames = [var.ssl_cert_config.name]
+ repo = var.coder.image_repo
+ tag = var.coder.image_tag
+ pullPolicy = var.coder.image_pull_policy
+ pullSecrets = var.coder.image_pull_secrets
}
- podAnnotations = {
+ env = local.env
+ annotations = var.prometheus.enable ? {
"prometheus.io/scrape" = "true"
- "prometheus.io/port" = "2112"
- }
+ "prometheus.io/port" = kubernetes_service_v1.prometheus[0].spec[0].port[0].port
+ } : {}
+ podAnnotations = var.prometheus.enable ? {
+ "prometheus.io/scrape" = "true"
+ "prometheus.io/port" = kubernetes_service_v1.prometheus[0].spec[0].port[0].port
+ } : {}
service = {
- enable = true
- type = "LoadBalancer"
- sessionAffinity = "None"
- externalTrafficPolicy = "Cluster"
- loadBalancerClass = var.load_balancer_class
- annotations = var.service_annotations
+ enable = false
}
- replicaCount = var.replica_count
+ tls = {
+ secretNames = var.coder.mount_ssl ? [ var.coder.mount_ssl_name ] : []
+ }
+ replicaCount = var.coder.rep_cnt
resources = {
requests = var.resource_request
limits = var.resource_limit
}
serviceAccount = {
- annotations = var.coder_builtin_provisioner_count == 0 ? var.service_account_annotations : merge({
- "eks.amazonaws.com/role-arn" : module.provisioner-oidc-role[0].role_arn
- }, var.service_account_annotations)
+ annotations = merge({
+ "eks.amazonaws.com/role-arn" : module.provisioner-oidc-role.role_arn
+ }, var.svc_acc_annot)
}
nodeSelector = var.node_selector
tolerations = var.tolerations
- topologySpreadConstraints = local.topology_spread_constraints
- affinity = {
- podAntiAffinity = {
- preferredDuringSchedulingIgnoredDuringExecution = local.pod_anti_affinity_preferred_during_scheduling_ignored_during_execution
- }
- }
- terminationGracePeriodSeconds = var.termination_grace_period_seconds
+ topologySpreadConstraints = local.topology_spread
+ affinity = var.affinity
+ terminationGracePeriodSeconds = var.termination_grace_period
}
})]
}
-resource "kubernetes_secret" "pg-connection" {
- metadata {
- name = var.db_secret_name
- namespace = kubernetes_namespace.this.metadata[0].name
- }
- data = {
- "${var.db_secret_key}" = var.db_secret_url
- }
- type = "Opaque"
-}
-
-resource "kubernetes_secret" "oidc" {
- metadata {
- name = var.oidc_secret_name
- namespace = kubernetes_namespace.this.metadata[0].name
- }
- data = {
- "${var.oidc_secret_issuer_url_key}" = var.oidc_secret_issuer_url
- "${var.oidc_secret_client_id_key}" = var.oidc_secret_client_id
- "${var.oidc_secret_client_secret_key}" = var.oidc_secret_client_secret
- }
- type = "Opaque"
-}
-
-resource "kubernetes_secret" "oauth" {
- metadata {
- name = var.oauth_secret_name
- namespace = kubernetes_namespace.this.metadata[0].name
- }
- data = {
- "${var.oauth_secret_client_id_key}" = var.oauth_secret_client_id
- "${var.oauth_secret_client_secret_key}" = var.oauth_secret_client_secret
- }
- type = "Opaque"
-}
-
-resource "kubernetes_secret" "external_auth" {
- metadata {
- name = var.github_external_auth_secret_name
- namespace = kubernetes_namespace.this.metadata[0].name
- }
- data = {
- "${var.github_external_auth_secret_client_id_key}" = var.github_external_auth_secret_client_id
- "${var.github_external_auth_secret_client_secret_key}" = var.github_external_auth_secret_client_secret
- }
- type = "Opaque"
-}
-
-resource "kubernetes_secret" "openai-llm-secret" {
- count = var.openai_llm_endpoint != "" ? 1 : 0
- metadata {
- name = var.openai_llm_secret_name
- namespace = kubernetes_namespace.this.metadata[0].name
- }
- data = {
- base_url = var.openai_llm_endpoint
- key = var.openai_llm_key
- }
- type = "Opaque"
-}
-
-resource "kubernetes_secret" "anthropic-llm-secret" {
- count = var.anthropic_llm_endpoint != "" ? 1 : 0
- metadata {
- name = var.anthropic_llm_secret_name
- namespace = kubernetes_namespace.this.metadata[0].name
- }
- data = {
- base_url = var.anthropic_llm_endpoint
- key = var.anthropic_llm_key
- }
- type = "Opaque"
-}
-
-locals {
- common_name = trimprefix(trimprefix(var.primary_access_url, "https://"), "http://")
- wildcard_name = trimprefix(trimprefix(var.wildcard_access_url, "https://"), "http://")
-}
-
-module "acme-cloudflare-ssl" {
- source = "../acme-cloudflare-ssl"
- count = var.ssl_cert_config.create_secret ? 1 : 0
-
- dns_names = [local.common_name, local.wildcard_name]
- common_name = local.common_name
- kubernetes_secret_name = var.ssl_cert_config.name
- kubernetes_namespace = kubernetes_namespace.this.metadata[0].name
- acme_registration_email = var.acme_registration_email
- acme_days_until_renewal = var.acme_days_until_renewal
- acme_revoke_certificate = var.acme_revoke_certificate
- cloudflare_api_token = var.cloudflare_api_token
-}
-
-resource "kubernetes_service" "prometheus" {
- metadata {
- name = "coder-prometheus"
- namespace = kubernetes_namespace.this.metadata[0].name
- # labels = local.app_labels
- }
- spec {
- type = "ClusterIP"
- cluster_ip = "None"
- port {
- name = "prom-http"
- protocol = "TCP"
- port = 2112
- target_port = var.prometheus_port
- }
- selector = {
- "app.kubernetes.io/instance" = "coder-v2"
- "app.kubernetes.io/name" = "coder"
- }
- }
+output "namespace" {
+ value = kubernetes_namespace_v1.this.metadata[0].name
}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/coder-server/policy.tf b/modules/k8s/bootstrap/coder-server/policy.tf
index 8caf963..30d6c79 100644
--- a/modules/k8s/bootstrap/coder-server/policy.tf
+++ b/modules/k8s/bootstrap/coder-server/policy.tf
@@ -1,176 +1,56 @@
-data "aws_iam_policy_document" "provisioner-policy" {
- statement {
- sid = "EC2InstanceLifecycle"
- effect = "Allow"
- actions = [
- "ec2:RunInstances",
- "ec2:StartInstances",
- "ec2:StopInstances",
- "ec2:TerminateInstances",
- "ec2:DescribeInstances",
- "ec2:RebootInstances",
- "ec2:ModifyInstanceAttribute",
- "ec2:DescribeInstanceAttribute"
- ]
- resources = [
- "arn:aws:ec2:${local.region}:${local.account_id}:*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*/*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*:*",
- "arn:aws:ec2:${local.region}::image/*"
- ]
- }
-
- statement {
- sid = "EC2ManageHostLifecycle"
- effect = "Allow"
- actions = [
- "ec2:AllocateHosts",
- "ec2:ModifyHosts",
- "ec2:ReleaseHosts"
- ]
- resources = [
- "arn:aws:ec2:${local.region}:${local.account_id}:*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*/*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*:*",
- "arn:aws:ec2:${local.region}::image/*"
- ]
- }
-
- statement {
- sid = "EBSVolumeLifecycle"
- effect = "Allow"
- actions = [
- "ec2:CreateVolume",
- "ec2:AttachVolume",
- "ec2:DetachVolume",
- "ec2:DeleteVolume",
- "ec2:DescribeVolumes",
- ]
- resources = [
- "arn:aws:ec2:${local.region}:${local.account_id}:*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*/*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*:*",
- ]
- }
+locals {
+ rds_db_name = split(".", var.db.url)[0]
+}
- statement {
- sid = "SecurityGroupLifecycle"
- effect = "Allow"
- actions = [
- "ec2:CreateSecurityGroup",
- "ec2:AuthorizeSecurityGroupIngress",
- "ec2:AuthorizeSecurityGroupEgress",
- "ec2:RevokeSecurityGroupIngress",
- "ec2:RevokeSecurityGroupEgress",
- "ec2:DeleteSecurityGroup",
- "ec2:DescribeSecurityGroups",
- ]
- resources = [
- "arn:aws:ec2:${local.region}:${local.account_id}:*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*/*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*:*",
- ]
- }
+data "aws_db_instance" "coder" {
+ db_instance_identifier = local.rds_db_name
+}
+data "aws_iam_policy_document" "provisioner" {
statement {
- sid = "TagLifecycle"
effect = "Allow"
actions = [
+ "ec2:GetDefaultCreditSpecification",
+ "ec2:DescribeIamInstanceProfileAssociations",
+ "ec2:DescribeTags",
+ "ec2:DescribeInstances",
+ "ec2:DescribeInstanceTypes",
+ "ec2:DescribeInstanceStatus",
"ec2:CreateTags",
- "ec2:DeleteTags",
- ]
- resources = [
- "arn:aws:ec2:${local.region}:${local.account_id}:*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*/*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*:*",
- ]
- }
-
- statement {
- sid = "NetworkInterfaceLifecycle"
- effect = "Allow"
- actions = [
- "ec2:CreateNetworkInterface",
- "ec2:AttachNetworkInterface",
- "ec2:DetachNetworkInterface",
- "ec2:DeleteNetworkInterface",
- "ec2:DescribeNetworkInterfaces",
- "ec2:ModifyNetworkInterfaceAttribute",
- ]
- resources = [
- "arn:aws:ec2:${local.region}:${local.account_id}:*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*/*",
- "arn:aws:ec2:${local.region}:${local.account_id}:*:*",
- ]
- }
-
- statement {
- sid = "ECRAuth"
- effect = "Allow"
- actions = [
- "ecr:GetAuthorizationToken"
- ]
- resources = ["*"]
- }
-
- statement {
- sid = "ECRDownloadImages"
- effect = "Allow"
- actions = [
- "ecr:BatchCheckLayerAvailability",
- "ecr:BatchGetImage",
- "ecr:GetDownloadUrlForLayer"
+ "ec2:RunInstances",
+ "ec2:DescribeInstanceCreditSpecifications",
+ "ec2:DescribeImages",
+ "ec2:ModifyDefaultCreditSpecification",
+ "ec2:DescribeVolumes"
]
resources = ["*"]
}
statement {
- sid = "ECRUploadImages"
effect = "Allow"
actions = [
- "ecr:CompleteLayerUpload",
- "ecr:UploadLayerPart",
- "ecr:InitiateLayerUpload",
- "ecr:BatchCheckLayerAvailability",
- "ecr:PutImage",
- "ecr:BatchGetImage"
- ]
- resources = ["arn:aws:ecr:${local.region}:${local.account_id}:repository/*"]
- }
-
- statement {
- sid = "IAMReadOnly"
- effect = "Allow"
- actions = [
- "iam:Get*",
- "iam:List*"
- ]
- resources = ["arn:aws:iam::${local.account_id}:*"]
- }
-
- statement {
- sid = "IAMPassRole"
- effect = "Allow"
- actions = [
- "iam:PassRole",
+ "ec2:DescribeInstanceAttribute",
+ "ec2:UnmonitorInstances",
+ "ec2:TerminateInstances",
+ "ec2:StartInstances",
+ "ec2:StopInstances",
+ "ec2:DeleteTags",
+ "ec2:MonitorInstances",
+ "ec2:CreateTags",
+ "ec2:RunInstances",
+ "ec2:ModifyInstanceAttribute",
+ "ec2:ModifyInstanceCreditSpecification"
]
- resources = ["arn:aws:iam::${local.account_id}:*"]
+ resources = ["arn:aws:ec2:*:*:instance/*"]
}
}
-data "aws_iam_policy_document" "ws-policy" {
+data "aws_iam_policy_document" "rds" {
statement {
- sid = "AllowModelInvocation"
- effect = "Allow"
- actions = [
- "bedrock:InvokeModel",
- "bedrock:InvokeModelWithResponseStream",
- "bedrock:ListInferenceProfiles"
- ]
+ effect = "Allow"
+ actions = ["rds-db:connect"]
resources = [
- "arn:aws:bedrock:*:*:*",
- "arn:aws:bedrock:*:*:*/*",
- "arn:aws:bedrock:*:*:*:*",
+ "arn:aws:rds-db:${local.region}:${local.account_id}:dbuser:${data.aws_db_instance.coder.resource_id}/${var.db.username}"
]
}
}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/ebs-controller/main.tf b/modules/k8s/bootstrap/ebs-csi/main.tf
similarity index 77%
rename from modules/k8s/bootstrap/ebs-controller/main.tf
rename to modules/k8s/bootstrap/ebs-csi/main.tf
index 84e6eef..1db1a10 100644
--- a/modules/k8s/bootstrap/ebs-controller/main.tf
+++ b/modules/k8s/bootstrap/ebs-csi/main.tf
@@ -5,7 +5,7 @@ terraform {
}
helm = {
source = "hashicorp/helm"
- version = "2.17.0"
+ version = ">= 2.17.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
@@ -48,6 +48,26 @@ variable "service_account_annotations" {
default = {}
}
+variable "node_selector" {
+ type = map(string)
+ default = {}
+}
+
+variable "affinity" {
+ type = any
+ default = {}
+}
+
+variable "tolerations" {
+ type = list(map(any))
+ default = []
+}
+
+variable "topology_spread" {
+ type = list(any)
+ default = []
+}
+
variable "replace" {
type = bool
default = false
@@ -56,13 +76,14 @@ variable "replace" {
data "aws_region" "this" {}
locals {
- role_name = var.role_name == "" ? "ebs-controller-${data.aws_region.this.region}" : var.role_name
+ role_name = var.role_name == "" ? "ebs-ctrl" : var.role_name
}
module "oidc-role" {
source = "../../../security/role/access-entry"
name = local.role_name
cluster_name = var.cluster_name
+ path = "/${var.cluster_name}/${data.aws_region.this.region}/"
policy_arns = {
"AmazonEBSCSIDriverPolicy" = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy"
}
@@ -87,7 +108,7 @@ resource "helm_release" "ebs-controller" {
wait = true
wait_for_jobs = true
version = var.chart_version
- timeout = 120 # in seconds
+ timeout = 300 # in seconds
values = [yamlencode({
controller = {
@@ -97,6 +118,10 @@ resource "helm_release" "ebs-controller" {
"eks.amazonaws.com/role-arn" = module.oidc-role.role_arn
}, var.service_account_annotations)
}
+ nodeSelector = var.node_selector
+ tolerations = var.tolerations
+ topologySpreadConstraints = var.topology_spread
+ affinity = var.affinity
}
})]
}
diff --git a/modules/k8s/bootstrap/external-dns/main.tf b/modules/k8s/bootstrap/external-dns/main.tf
new file mode 100644
index 0000000..94949f3
--- /dev/null
+++ b/modules/k8s/bootstrap/external-dns/main.tf
@@ -0,0 +1,195 @@
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ }
+ helm = {
+ source = "hashicorp/helm"
+ version = ">= 2.17.0"
+ }
+ kubernetes = {
+ source = "hashicorp/kubernetes"
+ }
+ }
+}
+
+##
+# https://kubernetes-sigs.github.io/external-dns/latest/
+# https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.13.2/docs/install/iam_policy.json
+##
+
+variable "cluster_name" {
+ type = string
+}
+
+variable "cluster_oidc_provider_arn" {
+ type = string
+}
+
+variable "role_name" {
+ type = string
+ default = ""
+}
+
+variable "policy_name" {
+ type = string
+ default = ""
+}
+
+variable "policy_resource_region" {
+ type = string
+ default = ""
+}
+
+variable "policy_resource_account" {
+ type = string
+ default = ""
+}
+
+variable "tags" {
+ type = map(string)
+ default = {}
+}
+
+variable "namespace" {
+ type = string
+}
+
+variable "chart_version" {
+ type = string
+ default = "1.20.0"
+}
+
+variable "node_selector" {
+ type = map(string)
+ default = {}
+}
+
+variable "tolerations" {
+ type = list(map(any))
+ default = []
+}
+
+data "aws_region" "this" {}
+
+data "aws_caller_identity" "this" {}
+
+variable "r53_config" {
+ type = object({
+ enabled = bool
+ region = optional(string, "")
+ account = optional(string, "")
+ role_name = optional(string, "ext-dns")
+ policy_name = optional(string, "ext-dns")
+ })
+ default = {
+ enabled = false
+ region = ""
+ account = ""
+ role_name = "ext-dns"
+ policy_name = "ext-dns"
+ }
+}
+
+locals {
+ region = var.r53_config.region == "" ? data.aws_region.this.region : var.r53_config.region
+ account_id = var.r53_config.account == "" ? data.aws_caller_identity.this.account_id : var.r53_config.account
+}
+
+module "policy" {
+
+ count = var.r53_config.enabled ? 1 : 0
+
+ source = "../../../security/policy"
+ name = var.r53_config.policy_name
+ path = "/${var.cluster_name}/${local.region}/"
+ description = "External DNS Policy."
+ policy_json = data.aws_iam_policy_document.this.json
+}
+
+module "oidc-role" {
+
+ count = var.r53_config.enabled ? 1 : 0
+
+ source = "../../../security/role/access-entry"
+ name = var.r53_config.role_name
+ path = "/${var.cluster_name}/${local.region}/"
+ cluster_name = var.cluster_name
+ policy_arns = {
+ "ExternalDNS" = module.policy[0].policy_arn
+ }
+ cluster_policy_arns = {
+ "AmazonEKSClusterAdminPolicy" = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy",
+ }
+ oidc_principals = {
+ "${var.cluster_oidc_provider_arn}" = ["system:serviceaccount:*:*"]
+ }
+ tags = var.tags
+}
+
+variable "cf_config" {
+ type = object({
+ enabled = bool
+ token = string
+ email = string
+ })
+ default = {
+ enabled = false
+ token = ""
+ email = ""
+ }
+ sensitive = true
+}
+
+locals {
+ # https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md
+ provider_aws = !var.r53_config.enabled ? null : {
+ provider = { name = "aws" }
+ env = [{
+ name = "AWS_DEFAULT_REGION"
+ value = data.aws_region.this.region
+ }]
+ serviceAccount = {
+ annotations = {
+ "eks.amazonaws.com/role-arn" = module.oidc-role[0].role_arn
+ }
+ }
+ sources = [
+ "ingress",
+ "service"
+ ]
+ extraArgs = [ "--aws-zone-match-parent" ]
+ }
+ # https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/cloudflare.md
+ provider_cf = !var.cf_config.enabled ? null : {
+ provider = { name = "cloudflare" }
+ env = [{
+ name = "CF_API_TOKEN"
+ value = var.cf_config.token
+ }]
+ }
+ values = merge(local.provider_cf, merge(local.provider_aws, {
+ registry = "txt"
+ txtPrefix = "%%{record_type}-txt-."
+ txtOwnerId = "coder"
+ policy = "sync" # Force cleanup + insertion of record.
+ nodeSelector = var.node_selector
+ tolerations = var.tolerations
+ }))
+}
+
+resource "helm_release" "chart" {
+ name = "external-dns"
+ namespace = var.namespace
+ chart = "external-dns"
+ repository = "https://kubernetes-sigs.github.io/external-dns/"
+ create_namespace = true
+ upgrade_install = true
+ skip_crds = false
+ wait = true
+ wait_for_jobs = true
+ version = var.chart_version
+ timeout = 120 # in seconds
+
+ values = [yamlencode(local.values)]
+}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/external-dns/policy.tf b/modules/k8s/bootstrap/external-dns/policy.tf
new file mode 100644
index 0000000..c6d376a
--- /dev/null
+++ b/modules/k8s/bootstrap/external-dns/policy.tf
@@ -0,0 +1,20 @@
+data "aws_iam_policy_document" "this" {
+
+ statement {
+ effect = "Allow"
+ actions = ["route53:ListHostedZones"]
+ resources = ["*"]
+ }
+
+ statement {
+ effect = "Allow"
+ actions = [
+ "route53:ChangeResourceRecordSets",
+ "route53:ListResourceRecordSets",
+ "route53:ListTagsForResources"
+ ]
+ resources = [
+ "arn:aws:route53:::hostedzone/*"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/external-secrets/main.tf b/modules/k8s/bootstrap/external-secrets/main.tf
new file mode 100644
index 0000000..c6a43c0
--- /dev/null
+++ b/modules/k8s/bootstrap/external-secrets/main.tf
@@ -0,0 +1,137 @@
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ }
+ helm = {
+ source = "hashicorp/helm"
+ version = ">= 2.17.0"
+ }
+ kubernetes = {
+ source = "hashicorp/kubernetes"
+ }
+ }
+}
+
+##
+# https://kubernetes-sigs.github.io/external-dns/latest/
+# https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.13.2/docs/install/iam_policy.json
+##
+
+variable "cluster_name" {
+ type = string
+}
+
+variable "cluster_oidc_provider_arn" {
+ type = string
+}
+
+variable "role_name" {
+ type = string
+ default = ""
+}
+
+variable "policy_name" {
+ type = string
+ default = ""
+}
+
+variable "policy_resource_region" {
+ type = string
+ default = ""
+}
+
+variable "policy_resource_account" {
+ type = string
+ default = ""
+}
+
+variable "tags" {
+ type = map(string)
+ default = {}
+}
+
+variable "namespace" {
+ type = string
+}
+
+variable "chart_version" {
+ type = string
+ default = "1.20.0"
+}
+
+variable "node_selector" {
+ type = map(string)
+ default = {}
+}
+
+variable "tolerations" {
+ type = list(map(any))
+ default = []
+}
+
+data "aws_region" "this" {}
+
+data "aws_caller_identity" "this" {}
+
+locals {
+ region = var.policy_resource_region == "" ? data.aws_region.this.region : var.policy_resource_region
+ account_id = var.policy_resource_account == "" ? data.aws_caller_identity.this.account_id : var.policy_resource_account
+ policy_name = var.policy_name == "" ? "ext-sec" : var.policy_name
+ role_name = var.role_name == "" ? "ext-sec" : var.role_name
+}
+
+module "policy" {
+ source = "../../../security/policy"
+ name = local.policy_name
+ path = "/${var.cluster_name}/${data.aws_region.this.region}/"
+ description = "External Secrets Policy."
+ policy_json = data.aws_iam_policy_document.this.json
+}
+
+module "oidc-role" {
+ source = "../../../security/role/access-entry"
+ name = local.role_name
+ path = "/${var.cluster_name}/${data.aws_region.this.region}/"
+ cluster_name = var.cluster_name
+ policy_arns = {
+ "ExternalSecrets" = module.policy.policy_arn
+ }
+ cluster_policy_arns = {
+ "AmazonEKSClusterAdminPolicy" = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy",
+ }
+ oidc_principals = {
+ "${var.cluster_oidc_provider_arn}" = ["system:serviceaccount:*:*"]
+ }
+ tags = var.tags
+}
+
+resource "helm_release" "chart" {
+ name = "external-secrets"
+ namespace = var.namespace
+ chart = "external-secrets"
+ repository = "https://charts.external-secrets.io"
+ create_namespace = true
+ upgrade_install = true
+ skip_crds = false
+ wait = true
+ wait_for_jobs = true
+ version = var.chart_version
+ timeout = 120 # in seconds
+
+ values = [yamlencode({
+ nodeSelector = var.node_selector
+ tolerations = var.tolerations
+ webhook = {
+ tolerations = var.tolerations
+ }
+ certController = {
+ tolerations = var.tolerations
+ }
+ serviceAccount = {
+ annotations = {
+ "eks.amazonaws.com/role-arn" = module.oidc-role.role_arn
+ }
+ }
+ })]
+}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/external-secrets/policy.tf b/modules/k8s/bootstrap/external-secrets/policy.tf
new file mode 100644
index 0000000..f699ba7
--- /dev/null
+++ b/modules/k8s/bootstrap/external-secrets/policy.tf
@@ -0,0 +1,42 @@
+data "aws_iam_policy_document" "this" {
+
+ statement {
+ effect = "Allow"
+ actions = [
+ "secretsmanager:ListSecrets",
+ "secretsmanager:BatchGetSecretValue"
+ ]
+ resources = ["*"]
+ }
+
+ # Batch get secrets
+ statement {
+ effect = "Allow"
+ actions = [
+ "secretsmanager:GetResourcePolicy",
+ "secretsmanager:GetSecretValue",
+ "secretsmanager:DescribeSecret",
+ "secretsmanager:ListSecretVersionIds"
+ ]
+ resources = [
+ "arn:aws:secretsmanager:${local.region}:${local.account_id}:secret:*"
+ ]
+ }
+
+ # Batch get secrets
+ statement {
+ effect = "Allow"
+ actions = [
+ "secretsmanager:CreateSecret",
+ "secretsmanager:PutSecretValue",
+ "secretsmanager:TagResource",
+ "secretsmanager:DeleteSecret",
+ "secretsmanager:GetResourcePolicy",
+ "secretsmanager:PutResourcePolicy",
+ "secretsmanager:DeleteResourcePolicy"
+ ]
+ resources = [
+ "arn:aws:secretsmanager:${local.region}:${local.account_id}:secret:*"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/fetch-and-store/main.tf b/modules/k8s/bootstrap/fetch-and-store/main.tf
deleted file mode 100644
index f4b50ef..0000000
--- a/modules/k8s/bootstrap/fetch-and-store/main.tf
+++ /dev/null
@@ -1,303 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "policy_name" {
- type = string
- default = ""
-}
-
-variable "role_name" {
- type = string
- default = ""
-}
-
-variable "namespace" {
- type = string
-}
-
-variable "name" {
- type = string
- default = "fetch-and-store"
-}
-
-variable "image_repo" {
- type = string
-}
-
-variable "image_tag" {
- type = string
-}
-
-variable "fetch_and_store_script_file_name" {
- type = string
- default = "fetch-and-store.py"
-}
-
-variable "fetch_and_store_script_pip_file_name" {
- type = string
- default = "requirements.txt"
-}
-
-variable "tags" {
- type = map(string)
- default = {}
-}
-
-data "aws_region" "this" {}
-
-data "aws_caller_identity" "this" {}
-
-locals {
- app_labels = {
- "app.kubernetes.io/name" : var.name
- "app.kubernetes.io/part-of" : var.name
- }
- policy_name = var.policy_name == "" ? "FetchAndStore-${data.aws_region.this.region}" : var.policy_name
- role_name = var.role_name == "" ? "fetch-and-store-${data.aws_region.this.region}" : var.role_name
-}
-
-module "policy" {
- source = "../../../security/policy"
- name = local.policy_name
- path = "/"
- description = "Fetch-and-Store Image Policy"
- policy_json = data.aws_iam_policy_document.this.json
-}
-
-module "oidc-role" {
- source = "../../../security/role/access-entry"
- name = local.role_name
- cluster_name = var.cluster_name
- policy_arns = {
- "FetchAndStore" = module.policy.policy_arn
- }
- cluster_policy_arns = {
- "AmazonEKSClusterAdminPolicy" = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy",
- }
- oidc_principals = {
- "${var.cluster_oidc_provider_arn}" = ["system:serviceaccount:*:*"]
- }
- tags = var.tags
-}
-
-resource "kubernetes_namespace" "this" {
- metadata {
- name = var.namespace
- }
-}
-
-resource "kubernetes_role" "this" {
- metadata {
- name = var.name
- namespace = kubernetes_namespace.this.metadata[0].name
- labels = local.app_labels
- }
- rule {
- api_groups = [""]
- resources = ["configmaps"]
- resource_names = ["fetch-and-store"]
- verbs = ["get", "create", "update", "patch"]
- }
-}
-
-resource "kubernetes_service_account" "this" {
- metadata {
- name = var.name
- namespace = kubernetes_namespace.this.metadata[0].name
- annotations = {
- "eks.amazonaws.com/role-arn" : module.oidc-role.role_arn
- }
- labels = local.app_labels
- }
-}
-
-resource "kubernetes_role_binding" "this" {
- metadata {
- name = var.name
- namespace = kubernetes_namespace.this.metadata[0].name
- labels = local.app_labels
- }
- role_ref {
- api_group = "rbac.authorization.k8s.io"
- kind = "Role"
- name = kubernetes_role.this.metadata[0].name
- }
- subject {
- kind = "ServiceAccount"
- name = kubernetes_service_account.this.metadata[0].name
- namespace = kubernetes_namespace.this.metadata[0].name
- }
-}
-
-resource "kubernetes_config_map" "script" {
- metadata {
- name = "python-fetch-and-store"
- namespace = kubernetes_namespace.this.metadata[0].name
- labels = local.app_labels
- }
- data = {
- "${var.fetch_and_store_script_file_name}" = file("${path.module}/scripts/${var.fetch_and_store_script_file_name}")
- }
-}
-
-resource "kubernetes_config_map" "pip" {
- metadata {
- name = "python-pip-requirements"
- namespace = kubernetes_namespace.this.metadata[0].name
- labels = local.app_labels
- }
- data = {
- "${var.fetch_and_store_script_pip_file_name}" = file("${path.module}/scripts/${var.fetch_and_store_script_pip_file_name}")
- }
-}
-
-resource "kubernetes_manifest" "this" {
- field_manager {
- force_conflicts = true
- }
- manifest = {
- apiVersion = "batch/v1"
- kind = "CronJob"
- metadata = {
- labels = local.app_labels
- name = var.name
- namespace = kubernetes_namespace.this.metadata[0].name
- }
- spec = {
- schedule = "0 0 * * 1"
- successfulJobsHistoryLimit = 0
- failedJobsHistoryLimit = 1
- suspend = false
- timeZone = "America/Vancouver"
- concurrencyPolicy = "Replace"
- jobTemplate = {
- metadata = {
- labels = local.app_labels
- }
- spec = {
- parallelism = 1
- template = {
- metadata = {
- labels = local.app_labels
- }
- spec = {
- serviceAccountName = kubernetes_service_account.this.metadata[0].name
- restartPolicy = "OnFailure"
- initContainers = [{
- name = "fetch"
- image = "ghcr.io/coder/coder-preview:latest"
- imagePullPolicy = "IfNotPresent"
- command = split(" ", "/bin/sh -c exit 0")
- }, {
- name = "docker-sidecar"
- image = "docker:dind"
- restartPolicy = "Always"
- imagePullPolicy = "IfNotPresent"
- command = split(" ", "dockerd -H tcp://127.0.0.1:2375")
- env = [{
- name = "DOCKER_HOST"
- value = "tcp://localhost:2375"
- }]
- resources = {
- limits = {
- cpu = "1"
- ephemeral-storage = "10Gi"
- memory = "2Gi"
- }
- requests = {
- ephemeral-storage = "5Gi"
- }
- }
- securityContext = {
- allowPrivilegeEscalation = true
- privileged = true
- runAsUser = 0
- }
- }]
- containers = [{
- name = "store"
- image = "${var.image_repo}:${var.image_tag}"
- imagePullPolicy = "IfNotPresent"
- command = ["bash", "-c", "pip install -r /tmp/${var.fetch_and_store_script_pip_file_name} && python /tmp/${var.fetch_and_store_script_file_name}"]
- resources = {
- limits = {
- cpu = "2"
- ephemeral-storage = "20Gi"
- memory = "9Gi"
- }
- requests = {
- cpu = "1"
- ephemeral-storage = "10Gi"
- memory = "1Gi"
- }
- }
- env = [{
- name = "DOCKER_HOST"
- value = "tcp://localhost:2375"
- }, {
- name = "DESIRED_TAG"
- value = "latest"
- }, {
- name = "GIT_URL"
- value = "https://github.com/coder/coder"
- }, {
- name = "AWS_ACCOUNT_ID"
- value = data.aws_caller_identity.this.account_id
- }, {
- name = "AWS_REGION"
- value = data.aws_region.this.region
- }]
- volumeMounts = [{
- name = kubernetes_config_map.script.metadata[0].name
- mountPath = "/tmp/${var.fetch_and_store_script_file_name}"
- subPath = var.fetch_and_store_script_file_name
- }, {
- name = kubernetes_config_map.pip.metadata[0].name
- mountPath = "/tmp/${var.fetch_and_store_script_pip_file_name}"
- subPath = var.fetch_and_store_script_pip_file_name
- }]
- }]
- volumes = [{
- name = kubernetes_config_map.script.metadata[0].name
- configMap = {
- name = kubernetes_config_map.script.metadata[0].name
- defaultMode = 511 # Equivalent to 777
- items = [{
- key = var.fetch_and_store_script_file_name
- path = var.fetch_and_store_script_file_name
- }]
- }
- }, {
- name = kubernetes_config_map.pip.metadata[0].name
- configMap = {
- name = kubernetes_config_map.pip.metadata[0].name
- defaultMode = 511 # Equivalent to 777
- items = [{
- key = var.fetch_and_store_script_pip_file_name
- path = var.fetch_and_store_script_pip_file_name
- }]
- }
- }]
- }
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/fetch-and-store/policy.tf b/modules/k8s/bootstrap/fetch-and-store/policy.tf
deleted file mode 100644
index 441d6b8..0000000
--- a/modules/k8s/bootstrap/fetch-and-store/policy.tf
+++ /dev/null
@@ -1,31 +0,0 @@
-data "aws_iam_policy_document" "this" {
- statement {
- effect = "Allow"
- actions = ["ecr:GetAuthorizationToken"]
- resources = ["*"]
- }
-
- statement {
- effect = "Allow"
- actions = [
- "ecr:BatchCheckLayerAvailability",
- "ecr:BatchGetImage",
- "ecr:GetDownloadUrlForLayer"
- ]
- resources = ["*"]
- }
-
- statement {
- effect = "Allow"
- actions = [
- "ecr:CompleteLayerUpload",
- "ecr:UploadLayerPart",
- "ecr:InitiateLayerUpload",
- "ecr:BatchCheckLayerAvailability",
- "ecr:PutImage",
- "ecr:BatchGetImage",
- "ecr:DescribeRepositories",
- ]
- resources = ["arn:aws:ecr:${data.aws_region.this.region}:${data.aws_caller_identity.this.account_id}:repository/*"]
- }
-}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/fetch-and-store/scripts/fetch-and-store.py b/modules/k8s/bootstrap/fetch-and-store/scripts/fetch-and-store.py
deleted file mode 100644
index 5a6da52..0000000
--- a/modules/k8s/bootstrap/fetch-and-store/scripts/fetch-and-store.py
+++ /dev/null
@@ -1,87 +0,0 @@
-from git import Repo
-from os import environ
-import tempfile, boto3, docker, base64, logging
-
-class FetchAndStoreFromECR:
- def __init__(self, regionName, profileName=""):
- self.logger = logging.getLogger(type(self).__name__)
-
- kwargs = {
- 'region_name': regionName
- }
- if profileName:
- kwargs['profile_name'] = profileName
-
- session = boto3.Session(**kwargs)
- ecr = session.client('ecr')
-
- auth = ecr.get_authorization_token()['authorizationData'][0]
-
- username, token = base64.b64decode(auth['authorizationToken']).decode().split(":")
- self.auth = {
- 'username': username,
- 'password': token,
- 'registry': auth['proxyEndpoint']
- }
-
- self.client = docker.from_env()
- self.client.login(**self.auth)
-
- def getLatestCommitSha(self, url, branch="main"):
- with tempfile.TemporaryDirectory() as tmpDir:
- self.logger.info(f"Temporary Dir: {tmpDir}")
- workingTree = Repo.clone_from(url, tmpDir)
- commitPrefix = workingTree.commit(branch).hexsha[:9]
-
- return commitPrefix
-
- def getDigest(self, image):
- tag = f"{image.attrs['Id'].replace(":", "-")}.att"
- self.logger.info(f"Image Digest: {tag}")
- return tag
-
- def fetch(self, repo, tag="latest", auth={}):
- res = self.client.images.pull(repo, tag=tag, auth_config=auth)
- self.logger.info(res)
- return res
-
- def tag(self, image, repo: str, tag: str):
- res = image.tag(repo, tag=tag)
- self.logger.info(res)
- return res
-
- def store(self, repo, tag="latest", auth={}):
- res = self.client.images.push(repo, tag=tag, auth_config=auth, stream=True, decode=True)
- for line in res:
- self.logger.info(line)
- return res
-
- def fetch_and_store(self, desired, desiredTag, target, targetTag=""):
- image = self.fetch(desired, desiredTag)
- if not targetTag:
- targetTag = self.getDigest(image)
- self.tag(image, target, targetTag)
- self.store(target, targetTag, self.auth)
-
-profileName = environ.get("AWS_PROFILE_NAME", "")
-accountId = environ.get("AWS_ACCOUNT_ID")
-regionName = environ.get("AWS_REGION", "us-east-2")
-desiredImg = environ.get("DESIRED_IMAGE", "ghcr.io/coder/coder-preview")
-desiredTag = environ.get("DESIRED_TAG", "latest")
-targetImg = environ.get("TARGET_IMAGE", f"{accountId}.dkr.ecr.{regionName}.amazonaws.com/coder-preview")
-targetTag = environ.get("TARGET_TAG", "")
-gitUrl = environ.get("GIT_URL", "https://github.com/coder/coder")
-
-logging.basicConfig(level=logging.INFO)
-
-client = FetchAndStoreFromECR(regionName, profileName)
-
-client.fetch_and_store(
- desiredImg, desiredTag,
- targetImg, targetTag
-)
-
-client.fetch_and_store(
- desiredImg, desiredTag,
- targetImg, "latest"
-)
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/fetch-and-store/scripts/requirements.txt b/modules/k8s/bootstrap/fetch-and-store/scripts/requirements.txt
deleted file mode 100644
index db4ab11..0000000
--- a/modules/k8s/bootstrap/fetch-and-store/scripts/requirements.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-boto3==1.40.46
-botocore==1.40.46
-certifi==2025.10.5
-charset-normalizer==3.4.3
-docker==7.1.0
-gitdb==4.0.12
-GitPython==3.1.45
-idna==3.10
-jmespath==1.0.1
-python-dateutil==2.9.0.post0
-requests==2.32.5
-s3transfer==0.14.0
-six==1.17.0
-smmap==5.0.2
-urllib3==2.5.0
diff --git a/modules/k8s/bootstrap/karpenter/main.tf b/modules/k8s/bootstrap/karpenter/main.tf
index 429be43..0e594e8 100644
--- a/modules/k8s/bootstrap/karpenter/main.tf
+++ b/modules/k8s/bootstrap/karpenter/main.tf
@@ -21,6 +21,10 @@ variable "cluster_oidc_provider_arn" {
type = string
}
+variable "cluster_oidc_provider" {
+ type = string
+}
+
variable "namespace" {
type = string
default = "karpenter"
@@ -89,47 +93,38 @@ variable "karpenter_node_role_tags" {
default = {}
}
-variable "ec2nodeclass_configs" {
- type = list(object({
- name = string
- node_role_name = optional(string, "")
- ami_alias = optional(string, "al2023@latest")
- subnet_selector_tags = map(string)
- sg_selector_tags = map(string)
- user_data = optional(string, "")
- block_device_mappings = optional(list(object({
- device_name = string
- ebs = object({
- volume_size = string
- volume_type = string
- encrypted = optional(bool, false)
- delete_on_termination = optional(bool, true)
- })
- })), [])
- }))
- default = []
+variable "iam_role_use_name_prefix" {
+ type = bool
+ default = true
}
-variable "nodepool_configs" {
+variable "node_iam_role_use_name_prefix" {
+ type = bool
+ default = true
+}
+
+
+variable "topology_spread" {
type = list(object({
- name = string
- node_labels = map(string)
- node_taints = optional(list(object({
- key = string
- value = string
- effect = string
- })), [])
- node_requirements = optional(list(object({
- key = string
- operator = string
- values = list(string)
- })), [])
- node_class_ref_name = string
- node_expires_after = optional(string, "Never")
- disruption_consolidation_policy = optional(string, "WhenEmpty")
- disruption_consolidate_after = optional(string, "1m")
+ max_skew = number
+ topology_key = string
+ when_unsatisfiable = optional(string, "DoNotSchedule")
+ label_selector = object({
+ match_labels = map(string)
+ })
+ match_label_keys = optional(list(string), [])
}))
- default = []
+ default = [{
+ max_skew = 1
+ topology_key = "topology.kubernetes.io/zone"
+ when_unsatisfiable = "DoNotSchedule"
+ label_selector = {
+ match_labels = {
+ "app.kubernetes.io/instance" = "karpenter"
+ "app.kubernetes.io/name" = "karpenter"
+ }
+ }
+ }]
}
variable "node_selector" {
@@ -137,15 +132,30 @@ variable "node_selector" {
default = {}
}
+variable "tolerations" {
+ type = list(map(any))
+ default = []
+}
+
+variable "affinity" {
+ type = any
+ default = {}
+}
+
+variable "replicas" {
+ type = number
+ default = 2
+}
+
data "aws_region" "this" {}
locals {
- std_karpenter_format = "${var.cluster_name}-kptr-${data.aws_region.this.region}"
+ std_karpenter_format = "kptr"
karpenter_queue_name = var.karpenter_queue_name == "" ? "${var.cluster_name}-kptr" : var.karpenter_queue_name
- karpenter_queue_rule_name = var.karpenter_queue_rule_name == "" ? "${var.cluster_name}-kptr" : var.karpenter_queue_rule_name
- karpenter_controller_role_name = var.karpenter_controller_role_name == "" ? local.std_karpenter_format : var.karpenter_controller_role_name
+ # karpenter_queue_rule_name = var.karpenter_queue_rule_name == "" ? "${var.cluster_name}-kptr" : var.karpenter_queue_rule_name
+ karpenter_controller_role_name = var.karpenter_controller_role_name == "" ? "${local.std_karpenter_format}-ctrl" : var.karpenter_controller_role_name
karpenter_controller_policy_name = var.karpenter_controller_policy_name == "" ? local.std_karpenter_format : var.karpenter_controller_policy_name
- karpenter_node_role_name = var.karpenter_node_role_name == "" ? local.std_karpenter_format : var.karpenter_node_role_name
+ karpenter_node_role_name = var.karpenter_node_role_name == "" ? "${local.std_karpenter_format}-node" : var.karpenter_node_role_name
}
data "aws_iam_policy_document" "sts" {
@@ -157,25 +167,55 @@ data "aws_iam_policy_document" "sts" {
}
resource "aws_iam_policy" "sts" {
- name_prefix = "sts"
- path = "/"
+ name_prefix = "${var.cluster_name}-sts-"
+ path = "/${var.cluster_name}/${data.aws_region.this.region}/${local.std_karpenter_format}/"
description = "Assume Role Policy"
policy = data.aws_iam_policy_document.sts.json
}
+data "aws_iam_policy_document" "kptr_ctrl_assume_role_policy" {
+ statement {
+ effect = "Allow"
+
+ principals {
+ type = "Federated"
+ identifiers = [var.cluster_oidc_provider_arn]
+ }
+
+ condition {
+ test = "StringEquals"
+ variable = "${var.cluster_oidc_provider}:sub"
+ values = ["system:serviceaccount:karpenter:karpenter"]
+ }
+
+ # https://aws.amazon.com/premiumsupport/knowledge-center/eks-troubleshoot-oidc-and-irsa/?nc1=h_ls
+ condition {
+ test = "StringEquals"
+ variable = "${var.cluster_oidc_provider}:aud"
+ values = ["sts.amazonaws.com"]
+ }
+
+ actions = ["sts:AssumeRoleWithWebIdentity"]
+ }
+}
+
module "karpenter" {
source = "terraform-aws-modules/eks/aws//modules/karpenter"
- version = "20.34.0" # "21.3.1"
+ version = "21.14.0"
cluster_name = var.cluster_name
queue_name = local.karpenter_queue_name
- rule_name_prefix = "${local.karpenter_queue_rule_name}-"
+ rule_name_prefix = ""
# Karpenter Controller Role
create_iam_role = true
iam_role_name = local.karpenter_controller_role_name
- iam_role_use_name_prefix = true
+ iam_role_use_name_prefix = var.iam_role_use_name_prefix
+ iam_role_path = "/${var.cluster_name}/${data.aws_region.this.region}/"
iam_role_policies = var.karpenter_controller_role_policies
+ iam_role_source_assume_policy_documents = [
+ data.aws_iam_policy_document.kptr_ctrl_assume_role_policy.json,
+ ]
# Karpenter Controller Policies
iam_policy_use_name_prefix = true
@@ -189,30 +229,54 @@ module "karpenter" {
# Karpenter Node Role
create_node_iam_role = true
node_iam_role_name = local.karpenter_node_role_name
- node_iam_role_use_name_prefix = true
+ node_iam_role_use_name_prefix = var.node_iam_role_use_name_prefix
+ node_iam_role_path = "/${var.cluster_name}/${data.aws_region.this.region}/"
node_iam_role_additional_policies = merge({
AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
STSAssumeRole = aws_iam_policy.sts.arn
}, var.karpenter_node_role_policies)
- enable_irsa = true
- enable_pod_identity = false
+ create_pod_identity_association = false
enable_spot_termination = true
- irsa_oidc_provider_arn = var.cluster_oidc_provider_arn
+ ### DEPRECATED in v21.
+ # enable_irsa = true
+ # irsa_oidc_provider_arn = var.cluster_oidc_provider_arn
+ # enable_pod_identity = false
+
# tags = merge(var.tags, var.karpenter_tags)
# iam_role_tags = merge(var.tags, var.karpenter_role_tags)
# node_iam_role_tags = merge(var.tags, var.karpenter_node_role_tags)
}
+resource "kubernetes_namespace_v1" "this" {
+ metadata {
+ name = var.namespace
+ }
+}
+
+locals {
+ topology_spread = [
+ for k, v in var.topology_spread : {
+ maxSkew = v.max_skew
+ topologyKey = v.topology_key
+ whenUnsatisfiable = v.when_unsatisfiable
+ labelSelector = {
+ matchLabels = try(v.label_selector.match_labels, {})
+ }
+ matchLabelKeys = v.match_label_keys
+ }
+ ]
+}
+
resource "helm_release" "karpenter" {
- depends_on = [module.karpenter]
+ depends_on = [ module.karpenter ]
name = "karpenter"
- namespace = var.namespace
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
chart = "karpenter"
repository = "oci://public.ecr.aws/karpenter"
- create_namespace = true
+ create_namespace = false
upgrade_install = true
skip_crds = false
wait = true
@@ -223,62 +287,56 @@ resource "helm_release" "karpenter" {
values = [yamlencode({
controller = {
resources = {
- limits = {
- cpu = "500m"
- memory = "512Mi"
- }
- requests = {
- cpu = "100m"
- memory = "256Mi"
- }
+ limits = null
+ requests = null
}
}
dnsPolicy = "ClusterFirst"
nodeSelector = var.node_selector
- replicas = 2
+ replicas = var.replicas
serviceAccount = {
annotations = {
"eks.amazonaws.com/role-arn" = module.karpenter.iam_role_arn
}
}
+ tolerations = var.tolerations
+ topologySpreadConstraints = local.topology_spread
+ affinity = var.affinity
settings = {
clusterName = var.cluster_name
featureGates = {
spotToSpotConsolidation = true
+ staticCapacity = true
}
interruptionQueue = module.karpenter.queue_name
}
})]
}
-module "ec2nodeclass" {
- count = length(var.ec2nodeclass_configs)
- source = "../../objects/ec2nodeclass"
- name = var.ec2nodeclass_configs[count.index].name
- node_role_name = var.ec2nodeclass_configs[count.index].node_role_name == "" ? module.karpenter.node_iam_role_name : var.ec2nodeclass_configs[count.index].node_role_name
- ami_alias = var.ec2nodeclass_configs[count.index].ami_alias
- subnet_selector_tags = var.ec2nodeclass_configs[count.index].subnet_selector_tags
- sg_selector_tags = var.ec2nodeclass_configs[count.index].sg_selector_tags
- block_device_mappings = var.ec2nodeclass_configs[count.index].block_device_mappings
- user_data = var.ec2nodeclass_configs[count.index].user_data
+resource "kubernetes_service_account_v1" "ctrl-role" {
+
+ depends_on = [helm_release.karpenter]
+
+ metadata {
+ name = "ctrl-role"
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ annotations = {
+ "eks.amazonaws.com/role-arn" = module.karpenter.iam_role_arn
+ "eks.amazonaws.com/role-name" = module.karpenter.iam_role_name
+ }
+ }
}
-resource "kubernetes_manifest" "ec2nodeclass" {
+resource "kubernetes_service_account_v1" "node-role" {
+
depends_on = [helm_release.karpenter]
- count = length(var.ec2nodeclass_configs)
- manifest = yamldecode(module.ec2nodeclass[count.index].manifest)
-}
-
-# module "nodepool" {
-# count = length(local.nodepool_configs)
-# source = "../objects/nodepool"
-# name = local.nodepool_configs[count.index].name
-# node_labels = local.nodepool_configs[count.index].node_labels
-# node_taints = local.nodepool_configs[count.index].node_taints
-# node_requirements = local.nodepool_configs[count.index].node_requirements
-# node_class_ref_name = local.nodepool_configs[count.index].node_class_ref_name
-# node_expires_after = local.nodepool_configs[count.index].node_expires_after
-# disruption_consolidation_policy = local.nodepool_configs[count.index].disruption_consolidation_policy
-# disruption_consolidate_after = local.nodepool_configs[count.index].disruption_consolidate_after
-# }
+ metadata {
+ name = "node-role"
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ annotations = {
+ "eks.amazonaws.com/role-arn" = module.karpenter.node_iam_role_arn
+ "eks.amazonaws.com/role-name" = module.karpenter.node_iam_role_name
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/lb-controller/main.tf b/modules/k8s/bootstrap/lb-controller/main.tf
index 55a3118..a69eb2b 100644
--- a/modules/k8s/bootstrap/lb-controller/main.tf
+++ b/modules/k8s/bootstrap/lb-controller/main.tf
@@ -5,7 +5,7 @@ terraform {
}
helm = {
source = "hashicorp/helm"
- version = "2.17.0"
+ version = ">= 2.17.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
@@ -18,6 +18,11 @@ terraform {
# https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.13.2/docs/install/iam_policy.json
##
+variable "release_name" {
+ type = string
+ default = "aws-load-balancer-controller"
+}
+
variable "cluster_name" {
type = string
}
@@ -36,16 +41,6 @@ variable "policy_name" {
default = ""
}
-variable "policy_resource_region" {
- type = string
- default = ""
-}
-
-variable "policy_resource_account" {
- type = string
- default = ""
-}
-
variable "tags" {
type = map(string)
default = {}
@@ -57,6 +52,7 @@ variable "namespace" {
variable "chart_version" {
type = string
+ default = "3.0.0"
}
variable "enable_cert_manager" {
@@ -79,21 +75,52 @@ variable "cluster_asg_node_labels" {
default = {}
}
+variable "vpc_id" {
+ description = "(Optional). Set this when your pods can't use the IMDS to auto-determine this"
+ type = string
+ default = ""
+}
+
+variable "node_selector" {
+ type = map(string)
+ default = {}
+}
+
+variable "tolerations" {
+ type = list(map(any))
+ default = []
+}
+
+variable "topology_spread" {
+ type = list(any)
+ default = []
+}
+
+variable "affinity" {
+ type = any
+ default = {}
+}
+
+variable "create_alb_class" {
+ type = bool
+ default = true
+}
+
data "aws_region" "this" {}
data "aws_caller_identity" "this" {}
locals {
- region = var.policy_resource_region == "" ? data.aws_region.this.region : var.policy_resource_region
- account_id = var.policy_resource_account == "" ? data.aws_caller_identity.this.account_id : var.policy_resource_account
- policy_name = var.policy_name == "" ? "LBController-${data.aws_region.this.region}" : var.policy_name
- role_name = var.role_name == "" ? "lb-controller-${data.aws_region.this.region}" : var.role_name
+ account_id = data.aws_caller_identity.this.account_id
+ region = data.aws_region.this.region
+ policy_name = var.policy_name == "" ? "lb-ctrl" : var.policy_name
+ role_name = var.role_name == "" ? "lb-ctrl" : var.role_name
}
module "policy" {
source = "../../../security/policy"
name = local.policy_name
- path = "/"
+ path = "/${var.cluster_name}/${data.aws_region.this.region}/"
description = "AWS Load Balancer Controller Policy"
policy_json = data.aws_iam_policy_document.this.json
}
@@ -101,6 +128,7 @@ module "policy" {
module "oidc-role" {
source = "../../../security/role/access-entry"
name = local.role_name
+ path = "/${var.cluster_name}/${data.aws_region.this.region}/"
cluster_name = var.cluster_name
policy_arns = {
"AmazonEKSLoadBalancingPolicy" = "arn:aws:iam::aws:policy/AmazonEKSLoadBalancingPolicy",
@@ -123,7 +151,7 @@ locals {
}
resource "helm_release" "lb-controller" {
- name = "aws-load-balancer-controller"
+ name = var.release_name
namespace = var.namespace
chart = "aws-load-balancer-controller"
repository = "https://aws.github.io/eks-charts"
@@ -133,7 +161,7 @@ resource "helm_release" "lb-controller" {
wait = true
wait_for_jobs = true
version = var.chart_version
- timeout = 120 # in seconds
+ timeout = 300 # in seconds
values = [yamlencode({
clusterName = var.cluster_name
@@ -145,48 +173,16 @@ resource "helm_release" "lb-controller" {
automountServiceAccountToken = true
imagePullSecrets = []
}
+ vpcId = var.vpc_id
enableCertManager = var.enable_cert_manager
- nodeSelector = var.cluster_asg_node_labels
+ nodeSelector = var.node_selector
+ tolerations = var.tolerations
+ topologySpreadConstraints = var.topology_spread
+ affinity = var.affinity
serviceTargetENISGTags = local.service_target_eni_sg_tags
- })]
-}
-
-resource "kubernetes_manifest" "alb-class-params" {
- depends_on = [helm_release.lb-controller]
- manifest = {
- apiVersion = "elbv2.k8s.aws/v1beta1"
- kind = "IngressClassParams"
- metadata = {
- labels = {
- "app.kubernetes.io/name" : "aws-load-balancer-controller"
- }
- name = "alb"
- }
- }
-}
-
-resource "kubernetes_manifest" "alb-class" {
- depends_on = [helm_release.lb-controller, kubernetes_manifest.alb-class-params]
- manifest = {
- apiVersion = "networking.k8s.io/v1"
- kind = "IngressClass"
- metadata = {
- labels = {
- "app.kubernetes.io/name" : "aws-load-balancer-controller"
- }
- name = "alb"
+ serviceMutatorWebhookConfig = {
+ # Ref - https://github.com/awslabs/data-on-eks/issues/458
+ failurePolicy = "Ignore"
}
- spec = {
- controller = "ingress.k8s.aws/alb"
- parameters = {
- apiGroup = "elbv2.k8s.aws"
- kind = "IngressClassParams"
- name = "alb"
- }
- }
- }
-}
-
-output "oidc_role_arn" {
- value = module.oidc-role.role_arn
+ })]
}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/litellm-generate-key/main.tf b/modules/k8s/bootstrap/litellm-generate-key/main.tf
deleted file mode 100644
index 4b14834..0000000
--- a/modules/k8s/bootstrap/litellm-generate-key/main.tf
+++ /dev/null
@@ -1,296 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "role_name" {
- type = string
- default = ""
-}
-
-variable "policy_name" {
- type = string
- default = ""
-}
-
-variable "namespace" {
- type = string
- default = "litellm"
-}
-
-variable "name" {
- type = string
- default = "rotate-key"
-}
-
-variable "image_repo" {
- type = string
-}
-
-variable "image_tag" {
- type = string
-}
-
-variable "rotate_key_script_file_name" {
- type = string
- default = "rotate.sh"
-}
-
-variable "litellm_key_secret_name" {
- type = string
- default = "litellm.env"
-}
-
-variable "litellm_key_master_secret_key" {
- type = string
- default = "master"
-}
-
-variable "litellm_key_salt_secret_key" {
- type = string
- default = "salt"
-}
-
-variable "litellm_master_key" {
- type = string
- sensitive = true
- default = ""
-}
-
-variable "litellm_salt_key" {
- type = string
- sensitive = true
- default = ""
-}
-
-variable "litellm_create_secret" {
- type = bool
- default = false
-}
-
-variable "litellm_url" {
- type = string
-}
-
-variable "tags" {
- type = map(string)
- default = {}
-}
-
-variable "secret_id" {
- type = string
- sensitive = true
-}
-
-variable "secret_region" {
- type = string
- sensitive = true
-}
-
-data "aws_region" "this" {}
-
-data "aws_caller_identity" "this" {}
-
-locals {
- app_labels = {
- "app.kubernetes.io/name" : var.name
- "app.kubernetes.io/part-of" : var.name
- }
- policy_name = var.policy_name == "" ? "LiteLLM-Create-${data.aws_region.this.region}" : var.policy_name
- role_name = var.role_name == "" ? "litellm-create-${data.aws_region.this.region}" : var.role_name
-}
-
-module "oidc-role" {
- source = "../../../security/role/access-entry"
- name = local.role_name
- cluster_name = var.cluster_name
- policy_arns = {
- "SecretsManagerReadWrite" = "arn:aws:iam::aws:policy/SecretsManagerReadWrite"
- }
- cluster_policy_arns = {
- "AmazonEKSClusterAdminPolicy" = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy",
- }
- oidc_principals = {
- "${var.cluster_oidc_provider_arn}" = ["system:serviceaccount:*:*"]
- }
- tags = var.tags
-}
-
-resource "kubernetes_role" "this" {
- metadata {
- name = var.name
- namespace = var.namespace
- labels = local.app_labels
- }
- rule {
- api_groups = [""]
- resources = ["secrets"]
- resource_names = [var.litellm_key_secret_name]
- verbs = ["get", "create", "update", "patch"]
- }
- rule {
- api_groups = ["apps", "extensions"]
- resources = ["deployments"]
- resource_names = [var.name]
- verbs = ["get", "patch"]
- }
-}
-
-resource "kubernetes_service_account" "this" {
- metadata {
- name = var.name
- namespace = var.namespace
- annotations = {
- "eks.amazonaws.com/role-arn" : module.oidc-role.role_arn
- }
- labels = local.app_labels
- }
-}
-
-resource "kubernetes_role_binding" "this" {
- metadata {
- name = var.name
- namespace = var.namespace
- labels = local.app_labels
- }
- role_ref {
- api_group = "rbac.authorization.k8s.io"
- kind = "Role"
- name = kubernetes_role.this.metadata[0].name
- }
- subject {
- kind = "ServiceAccount"
- name = kubernetes_service_account.this.metadata[0].name
- namespace = var.namespace
- }
-}
-
-resource "kubernetes_config_map" "this" {
- metadata {
- name = var.name
- namespace = var.namespace
- labels = local.app_labels
- }
- data = {
- "${var.rotate_key_script_file_name}" = templatefile("${path.module}/scripts/${var.rotate_key_script_file_name}", {})
- }
-}
-
-locals {
- primary_env_vars = {
- LITELLM_URL = var.litellm_url
- AWS_SECRETS_MANAGER_ID = var.secret_id
- AWS_SECRET_REGION = var.secret_region
- KEY_NAME = "litellm-tmp"
- KEY_DURATION = "8h"
- USERNAME = "robot_user.ai"
- USER_EMAIL = "robot_user@gmail.com"
- }
- secret_env_vars = {
- LITELLM_MASTER_KEY = {
- name = var.litellm_key_secret_name
- key = var.litellm_key_master_secret_key
- }
- LITELLM_SALT_KEY = {
- name = var.litellm_key_secret_name
- key = var.litellm_key_salt_secret_key
- }
- }
-}
-
-resource "kubernetes_secret" "key" {
- count = var.litellm_create_secret ? 1 : 0
- metadata {
- name = var.litellm_key_secret_name
- namespace = var.namespace
- labels = local.app_labels
- }
- data = {
- "${var.litellm_key_master_secret_key}" = var.litellm_master_key
- "${var.litellm_key_salt_secret_key}" = var.litellm_salt_key
- }
-}
-
-
-resource "kubernetes_cron_job_v1" "this" {
- metadata {
- name = var.name
- namespace = var.namespace
- labels = local.app_labels
- }
- spec {
- timezone = "America/Vancouver"
- successful_jobs_history_limit = 0
- failed_jobs_history_limit = 1
- concurrency_policy = "Replace"
- schedule = "*/5 * * * *"
- job_template {
- metadata {
- labels = local.app_labels
- }
- spec {
- parallelism = 1
- template {
- metadata {
- labels = local.app_labels
- }
- spec {
- service_account_name = kubernetes_service_account.this.metadata[0].name
- restart_policy = "OnFailure"
- container {
- name = var.name
- image = "${var.image_repo}:${var.image_tag}"
- image_pull_policy = "IfNotPresent"
- command = split(" ", "/bin/bash -c /tmp/${var.rotate_key_script_file_name}")
- dynamic "env" {
- for_each = local.primary_env_vars
- content {
- name = env.key
- value = tostring(env.value)
- }
- }
- dynamic "env" {
- for_each = local.secret_env_vars
- content {
- name = env.key
- value_from {
- secret_key_ref {
- name = env.value.name
- key = env.value.key
- }
- }
- }
- }
- volume_mount {
- name = kubernetes_config_map.this.metadata[0].name
- mount_path = "/tmp/${var.rotate_key_script_file_name}"
- sub_path = var.rotate_key_script_file_name
- }
- }
- volume {
- name = kubernetes_config_map.this.metadata[0].name
- config_map {
- name = kubernetes_config_map.this.metadata[0].name
- default_mode = "0777"
- }
- }
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/litellm-generate-key/scripts/rotate.sh b/modules/k8s/bootstrap/litellm-generate-key/scripts/rotate.sh
deleted file mode 100644
index 83a35cf..0000000
--- a/modules/k8s/bootstrap/litellm-generate-key/scripts/rotate.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-
-set -eo pipefail
-
-curl -L -X POST "$LITELLM_URL/user/new" \
- -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
- -H 'Content-Type: application/json' \
- -d "{\"username\":\"$USERNAME\",\"user_id\":\"$USERNAME\",\"email\":\"$USER_EMAIL\",\"key_alias\":\"$KEY_NAME\",\"duration\":\"$KEY_DURATION\"}" || true ;
-
-curl -L -X POST "$LITELLM_URL/key/delete" \
- -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
- -H 'Content-Type: application/json' \
- -d "{\"key_aliases\":[\"$KEY_NAME\"]}" || true ;
-
-NEW_LITELLM_USER_KEY=$(curl -L -X POST $LITELLM_URL/key/generate \
- -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
- -H 'Content-Type: application/json' \
- -d "{\"key_alias\":\"$KEY_NAME\",\"duration\":\"$KEY_DURATION\",\"metadata\":{\"user_id\":\"$USERNAME\"}}" | jq -r '.key') ;
-
-aws secretsmanager put-secret-value \
- --region $AWS_SECRET_REGION \
- --secret-id $AWS_SECRETS_MANAGER_ID \
- --secret-string "{\"LITELLM_MASTER_KEY\":\"$NEW_LITELLM_USER_KEY\"}" || \
-aws secretsmanager create-secret \
- --region $AWS_SECRET_REGION \
- --name $AWS_SECRETS_MANAGER_ID \
- --secret-string "{\"LITELLM_MASTER_KEY\":\"$NEW_LITELLM_USER_KEY\"}"
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/litellm-rotate-key/main.tf b/modules/k8s/bootstrap/litellm-rotate-key/main.tf
deleted file mode 100644
index 69246c9..0000000
--- a/modules/k8s/bootstrap/litellm-rotate-key/main.tf
+++ /dev/null
@@ -1,212 +0,0 @@
-terraform {
- required_providers {
- aws = {
- source = "hashicorp/aws"
- }
- kubernetes = {
- source = "hashicorp/kubernetes"
- }
- }
-}
-
-variable "cluster_name" {
- type = string
-}
-
-variable "cluster_oidc_provider_arn" {
- type = string
-}
-
-variable "policy_name" {
- type = string
- default = ""
-}
-
-variable "role_name" {
- type = string
- default = ""
-}
-
-variable "namespace" {
- type = string
-}
-
-variable "name" {
- type = string
- default = "rotate-key"
-}
-
-variable "image_repo" {
- type = string
-}
-
-variable "image_tag" {
- type = string
-}
-
-variable "secret_id" {
- type = string
- sensitive = true
-}
-
-variable "secret_region" {
- type = string
- sensitive = true
-}
-
-variable "rotate_key_script_file_name" {
- type = string
- default = "rotate.sh"
-}
-
-variable "tags" {
- type = map(string)
- default = {}
-}
-
-data "aws_region" "this" {}
-
-data "aws_caller_identity" "this" {}
-
-locals {
- app_labels = {
- "app.kubernetes.io/name" : var.name
- "app.kubernetes.io/part-of" : var.name
- }
- policy_name = var.policy_name == "" ? "LiteLLM-Swap-${data.aws_region.this.region}" : var.policy_name
- role_name = var.role_name == "" ? "litellm-swap-${data.aws_region.this.region}" : var.role_name
-}
-
-module "oidc-role" {
- source = "../../../security/role/access-entry"
- name = local.role_name
- cluster_name = var.cluster_name
- policy_arns = {
- "SecretsManagerReadWrite" = "arn:aws:iam::aws:policy/SecretsManagerReadWrite"
- }
- cluster_policy_arns = {
- "AmazonEKSClusterAdminPolicy" = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy",
- }
- oidc_principals = {
- "${var.cluster_oidc_provider_arn}" = ["system:serviceaccount:*:*"]
- }
- tags = var.tags
-}
-
-resource "kubernetes_role" "this" {
- metadata {
- name = var.name
- namespace = var.namespace
- labels = local.app_labels
- }
- rule {
- api_groups = [""]
- resources = ["secrets"]
- verbs = ["get", "create", "update", "patch", "list"]
- }
-}
-
-resource "kubernetes_service_account" "this" {
- metadata {
- name = var.name
- namespace = var.namespace
- annotations = {
- "eks.amazonaws.com/role-arn" : module.oidc-role.role_arn
- }
- labels = local.app_labels
- }
-}
-
-resource "kubernetes_role_binding" "this" {
- metadata {
- name = var.name
- namespace = var.namespace
- labels = local.app_labels
- }
- role_ref {
- api_group = "rbac.authorization.k8s.io"
- kind = "Role"
- name = kubernetes_role.this.metadata[0].name
- }
- subject {
- kind = "ServiceAccount"
- name = kubernetes_service_account.this.metadata[0].name
- namespace = var.namespace
- }
-}
-
-resource "kubernetes_config_map" "this" {
- metadata {
- name = var.name
- namespace = var.namespace
- labels = local.app_labels
- }
- data = {
- "${var.rotate_key_script_file_name}" = templatefile("${path.module}/scripts/${var.rotate_key_script_file_name}", {})
- }
-}
-
-locals {
- primary_env_vars = {
- AWS_SECRETS_MANAGER_ID = var.secret_id
- AWS_SECRET_REGION = var.secret_region
- K8S_NAMESPACE = var.namespace
- }
-}
-
-resource "kubernetes_cron_job_v1" "this" {
- metadata {
- name = var.name
- namespace = var.namespace
- labels = local.app_labels
- }
- spec {
- timezone = "America/Vancouver"
- successful_jobs_history_limit = 0
- failed_jobs_history_limit = 1
- concurrency_policy = "Replace"
- schedule = "0 */4 * * *"
- job_template {
- metadata {
- labels = local.app_labels
- }
- spec {
- parallelism = 1
- template {
- metadata {
- labels = local.app_labels
- }
- spec {
- service_account_name = kubernetes_service_account.this.metadata[0].name
- restart_policy = "OnFailure"
- container {
- name = var.name
- image = "${var.image_repo}:${var.image_tag}"
- image_pull_policy = "IfNotPresent"
- command = split(" ", "/bin/bash -c /tmp/${var.rotate_key_script_file_name}")
- dynamic "env" {
- for_each = local.primary_env_vars
- content {
- name = env.key
- value = tostring(env.value)
- }
- }
- volume_mount {
- name = kubernetes_config_map.this.metadata[0].name
- mount_path = "/tmp/${var.rotate_key_script_file_name}"
- sub_path = var.rotate_key_script_file_name
- }
- }
- volume {
- name = kubernetes_config_map.this.metadata[0].name
- config_map {
- name = kubernetes_config_map.this.metadata[0].name
- default_mode = "0777"
- }
- }
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/litellm-rotate-key/scripts/rotate.sh b/modules/k8s/bootstrap/litellm-rotate-key/scripts/rotate.sh
deleted file mode 100644
index 542e363..0000000
--- a/modules/k8s/bootstrap/litellm-rotate-key/scripts/rotate.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-
-set -eo pipefail
-
-LITELLM_MASTER_KEY=$(aws secretsmanager get-secret-value \
- --region $AWS_SECRET_REGION \
- --secret-id $AWS_SECRETS_MANAGER_ID | jq -r '.SecretString' | jq -r '.LITELLM_MASTER_KEY')
-
-kubectl create secret generic litellm -n $K8S_NAMESPACE -o yaml --dry-run=client \
- --from-literal=token=$LITELLM_MASTER_KEY | kubectl apply -f -
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/litellm/README.md b/modules/k8s/bootstrap/litellm/README.md
new file mode 100644
index 0000000..4560108
--- /dev/null
+++ b/modules/k8s/bootstrap/litellm/README.md
@@ -0,0 +1,156 @@
+# LiteLLM Helm Chart Terraform Module
+
+This Terraform module deploys [LiteLLM](https://github.com/BerriAI/litellm) as a Kubernetes Helm chart on AWS EKS. LiteLLM provides a unified proxy interface to multiple LLM providers, configured here to route requests to AWS Bedrock models (Claude Sonnet and Haiku) across multiple AWS regions.
+
+## Overview
+
+This module:
+- Deploys LiteLLM proxy to an EKS cluster using the official Helm chart
+ - https://artifacthub.io/packages/helm/litellm/litellm-helm
+- Configures AWS IAM roles for service accounts (IRSA) for Bedrock access
+- Sets up load balancer exposure via AWS Network Load Balancer
+- Configures multi-region failover for Claude models
+- Includes a PostgreSQL database for LiteLLM state management
+
+## Architecture
+
+```
+┌─────────────┐
+│ Client │
+└──────┬──────┘
+ │
+ ▼
+┌─────────────────────────┐
+│ AWS NLB (Port 4000) │
+└──────────┬──────────────┘
+ │
+ ▼
+ ┌──────────────┐
+ │ LiteLLM │
+ │ Proxy │
+ └──────┬───────┘
+ │
+ ├──► AWS Bedrock (us-east-2)
+ ├──► AWS Bedrock (us-east-1)
+ └──► AWS Bedrock (us-west-2)
+```
+
+## Prerequisites
+
+- AWS EKS cluster (default: `aidemo-eks`)
+- AWS CLI configured with appropriate profile
+- Terraform >= 1.0
+- Helm provider >= 3.1.1
+- Kubernetes provider >= 3.0.1
+
+## Variables
+
+| Variable | Type | Default | Description |
+|----------|------|---------|-------------|
+| `cluster_name` | string | `aidemo-eks` | Name of the EKS cluster |
+| `cluster_region` | string | `us-east-2` | AWS region where the cluster is located |
+| `cluster_profile` | string | `demo-coder` | AWS CLI profile to use |
+| `namespace` | string | `litellm-tmp` | Kubernetes namespace for LiteLLM |
+| `chart_version` | string | `0.1.830` | Version of the LiteLLM Helm chart |
+| `cluster_oidc_provider_arn` | string | (see main.tf) | ARN of the EKS OIDC provider |
+
+## Configured Models
+
+The proxy is configured with the following AWS Bedrock models across multiple regions for high availability:
+
+### Claude Sonnet 4.5
+- Model: `anthropic.claude-sonnet-4-5-20250929-v1:0`
+- Regions: `us-east-2`, `us-east-1`, `us-west-2`
+
+### Claude 3 Haiku
+- Model: `anthropic.claude-3-haiku-20240307-v1:0`
+- Regions: `us-east-2`, `us-east-1`, `us-west-2`
+
+## Usage
+
+### Deploy the Module
+
+```bash
+terraform init
+terraform plan
+terraform apply
+```
+
+### Testing the Deployment
+
+Use the included test script to verify the proxy is working:
+
+```bash
+./test.sh
+```
+
+This script configures Claude Code to use the LiteLLM proxy as the API endpoint.
+
+### Get the Load Balancer URL
+
+```bash
+kubectl get svc -n litellm-tmp litellm
+```
+
+### Making API Requests
+
+```bash
+curl http://:4000/v1/chat/completions \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer sk-2Uw00oiTMSxwEtHA19" \
+ -d '{
+ "model": "anthropic.claude-sonnet-4-5-20250929-v1:0",
+ "messages": [{"role": "user", "content": "Hello!"}]
+ }'
+```
+
+## Configuration Details
+
+### IAM Role for Service Account (IRSA)
+
+The module creates an IAM role with:
+- AWS Bedrock Limited Access policy
+- EKS Cluster Admin policy
+- Trust relationship with the EKS OIDC provider
+
+The service account annotation automatically injects AWS credentials into the pod.
+
+### Service Configuration
+
+- **Type**: LoadBalancer (AWS Network Load Balancer)
+- **Port**: 4000
+- **Scheme**: Internet-facing
+- **Target Type**: Instance
+
+### Database
+
+- **Type**: PostgreSQL (standalone via Bitnami chart)
+- **Purpose**: Stores LiteLLM configuration and state
+- **Migration**: Automatic schema migration on deployment
+
+### Security
+
+- Master key authentication required for API access
+- AWS credentials managed via IRSA (no static credentials)
+- Secrets managed via Kubernetes secrets
+
+## Files
+
+- `main.tf` - Main Terraform configuration
+- `values.yaml` - Helm chart values template
+- `test.sh` - Quick test script for Claude Code integration
+
+## Notes
+
+- The load balancer is internet-facing by default
+- Multi-region configuration provides automatic failover
+- PostgreSQL credentials default to weak passwords and should be overridden in production
+- The test script includes a hardcoded API key for testing purposes
+
+## Cleanup
+
+```bash
+terraform destroy
+```
+
+This will remove the Helm release, namespace, and associated IAM resources.
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/litellm/main.tf b/modules/k8s/bootstrap/litellm/main.tf
index 8b54c7c..6b1477a 100644
--- a/modules/k8s/bootstrap/litellm/main.tf
+++ b/modules/k8s/bootstrap/litellm/main.tf
@@ -3,530 +3,507 @@ terraform {
aws = {
source = "hashicorp/aws"
}
+ helm = {
+ source = "hashicorp/helm"
+ version = "3.1.1"
+ }
kubernetes = {
source = "hashicorp/kubernetes"
+ version = "3.0.1"
}
}
}
variable "cluster_name" {
type = string
+ default = "aidemo-eks"
}
-variable "cluster_oidc_provider_arn" {
+variable "cluster_region" {
type = string
+ default = "us-east-2"
}
-variable "role_name" {
+variable "cluster_profile" {
type = string
- default = ""
+ default = "demo-coder"
}
-variable "policy_name" {
+variable "namespace" {
type = string
- default = ""
+ default = "litellm"
}
-variable "policy_resource_region" {
+variable "chart_version" {
type = string
- default = ""
+ default = "0.1.830"
+ # 1.81.13
}
-variable "policy_resource_account" {
- type = string
- default = ""
+variable "cluster_oidc_provider_arn" {
+ type = string
}
-variable "namespace" {
- type = string
- default = "litellm"
+provider "aws" {
+ region = var.cluster_region
+ profile = var.cluster_profile
}
-variable "name" {
- type = string
- default = "litellm"
+data "aws_eks_cluster" "this" {
+ name = var.cluster_name
}
-variable "replicas" {
- type = number
- default = 0
+data "aws_eks_cluster_auth" "this" {
+ name = var.cluster_name
}
-variable "image_repo" {
- type = string
- default = "ghcr.io/berriai/litellm"
-}
+data "aws_caller_identity" "this" {}
-variable "image_tag" {
- type = string
- default = "v1.72.6-stable"
-}
+data "aws_region" "this" {}
-variable "app_container_port" {
- type = number
- default = 4000
+locals {
+ account_id = data.aws_caller_identity.this.account_id
+ region = data.aws_region.this.region
}
-variable "host_name" {
- type = string
+variable "tags" {
+ type = map(string)
+ default = {}
}
-variable "resource_requests" {
- type = object({
- cpu = string
- memory = string
- })
- default = {
- cpu = "250m"
- memory = "512Mi"
- }
+variable "replicas" {
+ type = number
+ default = 1
}
-variable "resource_limits" {
+variable "image_config" {
type = object({
- cpu = string
- memory = string
+ repo = optional(string, "ghcr.io/berriai/litellm-database")
+ pull_policy = optional(string, "IfNotPresent")
+ tag = optional(string, "main-latest")
})
- default = {
- cpu = "500m"
- memory = "1Gi"
- }
+ default = {}
}
-variable "aws_ingress_certificate_arn" {
- type = string
+variable "litellm_master_key" {
+ type = string
sensitive = true
-}
-variable "aws_bedrock_region" {
- type = string
- default = "us-east-2"
+ validation {
+ condition = startswith(var.litellm_master_key, "sk-")
+ error_message = "The LiteLLM master key must start with 'sk-'."
+ }
}
-variable "db_url_secret_name" {
- type = string
- default = "url"
-}
-
-variable "db_url_secret_key" {
- type = string
- default = "postgres.env"
+variable "litellm_master_secret_name" {
+ type = string
+ default = "masterkey"
}
-variable "db_url" {
- type = string
+variable "db" {
+ type = object({
+ use_existing = optional(bool, false)
+ secret_name = optional(string, "postgres")
+ db_name = optional(string, "litellm")
+ username = optional(string, "litellm")
+ admin_password = optional(string, "NoTaGrEaTpAsSwOrD")
+ user_password = optional(string, "NoTaGrEaTpAsSwOrD")
+ endpoint = optional(string, "localhost")
+ port = optional(number, 5432)
+ auth_type = optional(string, "postgres")
+ })
+ default = {
+ use_existing = false
+ secret_name = "postgres"
+ db_name = "litellm"
+ username = "litellm"
+ admin_password = "NoTaGrEaTpAsSwOrD"
+ user_password = "NoTaGrEaTpAsSwOrD"
+ endpoint = "localhost"
+ port = 5432
+ auth_type = "postgres"
+ }
sensitive = true
}
-variable "redis_host_secret_name" {
- type = string
- default = "redis.env"
-}
-
-variable "redis_host_secret_key" {
- type = string
- default = "host"
-}
-
-variable "redis_password_secret_key" {
- type = string
- default = "password"
-}
-
-variable "redis_host" {
- type = string
- sensitive = true
+variable "service_account_annotations" {
+ type = map(string)
+ default = {}
}
-variable "redis_password" {
- type = string
- sensitive = true
+variable "service_lb_class" {
+ type = string
+ default = "service.k8s.aws/nlb"
}
-variable "litellm_key_secret_name" {
- type = string
- default = "litellm.env"
+variable "svc_annots" {
+ type = map(string)
+ default = {}
}
-variable "litellm_key_master_secret_key" {
- type = string
- default = "master"
+variable "svc_port" {
+ type = number
+ default = 80
}
-variable "litellm_key_salt_secret_key" {
- type = string
- default = "salt"
+variable "health_port" {
+ type = number
+ default = 8081
}
-variable "litellm_salt_key" {
- type = string
- sensitive = true
+variable "proxy_config" {
+ type = any
+ default = {}
}
-variable "litellm_master_key" {
- type = string
- sensitive = true
+variable "env_vars" {
+ type = map(string)
+ default = {}
}
-variable "gcloud_auth_secret_name" {
- type = string
- default = "gcloud-auth"
+variable "mounts" {
+ type = list(object({
+ secret_name = optional(string, "")
+ path = optional(string, "")
+ read_only = optional(bool, false)
+ }))
+ default = []
}
-variable "gcloud_auth_secret_key" {
- type = string
- default = "service_account.json"
+variable "resource_request" {
+ type = object({
+ cpu = string
+ memory = string
+ })
+ default = {
+ cpu = "2000m"
+ memory = "4Gi"
+ }
}
-variable "gcloud_auth_file_path" {
- type = string
- default = "/tmp"
+variable "resource_limit" {
+ type = map(any)
+ default = {}
}
-variable "gcloud_auth" {
- type = string
- sensitive = true
+variable "autoscaling_min_replicas" {
+ type = number
+ default = 1
}
-variable "litellm_config_name" {
- type = string
- default = "config-yaml"
+variable "autoscaling_max_replicas" {
+ type = number
+ default = 5
}
-variable "litellm_config_key" {
- type = string
- default = "config.yaml"
+variable "autoscaling_target_cpu_use" {
+ type = number
+ description = "The target CPU utilization percentage for autoscaling."
+ default = 80
}
-variable "litellm_config_middleware_name" {
- type = string
- default = "strip-header-middleware-py"
+variable "autoscaling_target_memory_use" {
+ type = number
+ description = "The target memory utilization percentage for autoscaling."
+ default = 80
}
-variable "litellm_config_middleware_key" {
- type = string
- default = "strip_header_middleware.py"
+variable "topology_spread" {
+ type = list(any)
+ default = []
}
-variable "tags" {
- type = map(string)
+variable "node_selector" {
+ type = map(string)
default = {}
}
-data "aws_region" "this" {}
-
-data "aws_caller_identity" "this" {}
-
-locals {
- app_labels = {
- "app.kubernetes.io/name" : var.name
- "app.kubernetes.io/part-of" : var.name
- }
-}
-
-resource "kubernetes_namespace" "this" {
- metadata {
- name = var.namespace
- }
-}
-
-resource "kubernetes_ingress_v1" "this" {
- metadata {
- name = var.name
- namespace = kubernetes_namespace.this.metadata[0].name
- annotations = {
- "alb.ingress.kubernetes.io/certificate-arn" = var.aws_ingress_certificate_arn
- "alb.ingress.kubernetes.io/group.order" = 10
- "alb.ingress.kubernetes.io/listen-ports" = "[{\"HTTPS\":443}]"
- "alb.ingress.kubernetes.io/scheme" = "internet-facing"
- "alb.ingress.kubernetes.io/unhealthy-threshold-count" = 3
- }
- labels = local.app_labels
- }
- spec {
- ingress_class_name = "alb"
- rule {
- host = var.host_name
- http {
- path {
- backend {
- service {
- name = var.name
- port {
- number = 80
- }
- }
- }
- path = "/"
- path_type = "Prefix"
- }
- }
- }
- }
-}
-
-resource "kubernetes_service" "this" {
- metadata {
- name = var.name
- namespace = kubernetes_namespace.this.metadata[0].name
- labels = local.app_labels
- }
- spec {
- type = "NodePort"
- internal_traffic_policy = "Cluster"
- ip_families = ["IPv4"]
- ip_family_policy = "SingleStack"
- port {
- name = "http"
- protocol = "TCP"
- port = 80
- target_port = "http"
- }
- selector = {
- app = var.name
- }
- }
+variable "tolerations" {
+ type = any
+ default = {}
}
-locals {
- policy_name = var.policy_name == "" ? "LiteLLM-BR-${data.aws_region.this.region}" : var.policy_name
- role_name = var.role_name == "" ? "litellm-br-${data.aws_region.this.region}" : var.role_name
+variable "affinity" {
+ type = any
+ default = {}
}
-module "bedrock-policy" {
+module "rds-policy" {
source = "../../../security/policy"
- name = local.policy_name
- path = "/"
- description = "LiteLLM Bedrock IAM Policy"
- policy_json = data.aws_iam_policy_document.bedrock-policy.json
+ name = "${local.rds_db_name}-${var.db.db_name}"
+ path = "/${var.cluster_name}/${local.region}/"
+ description = "LiteLLM DB IAM Access Policy"
+ policy_json = data.aws_iam_policy_document.rds.json
}
-module "bedrock-oidc-role" {
- source = "../../../security/role/access-entry"
- name = local.role_name
+module "oidc-role" {
+ source = "../../../security/role/access-entry"
+ name = "LiteLLM-Bedrock"
+ cluster_name = var.cluster_name
policy_arns = {
- "BedrockPolicy" = module.bedrock-policy.policy_arn
+ "AmazonBedrockLimitedAccess" = "arn:aws:iam::aws:policy/AmazonBedrockLimitedAccess"
+ "LiteLLMRDSDBPolicy" = module.rds-policy.policy_arn
+ }
+ cluster_policy_arns = {
+ "AmazonEKSClusterAdminPolicy" = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"
}
- cluster_name = var.cluster_name
- cluster_policy_arns = {}
oidc_principals = {
"${var.cluster_oidc_provider_arn}" = ["system:serviceaccount:*:*"]
}
tags = var.tags
}
-resource "kubernetes_service_account" "litellm" {
+resource "kubernetes_namespace_v1" "litellm" {
metadata {
- name = var.name
- namespace = kubernetes_namespace.this.metadata[0].name
- annotations = {
- "eks.amazonaws.com/role-arn" : module.bedrock-oidc-role.role_arn
- }
- labels = local.app_labels
- }
-}
-
-resource "kubernetes_secret" "postgres" {
- metadata {
- name = var.db_url_secret_name
- namespace = kubernetes_namespace.this.metadata[0].name
- labels = local.app_labels
- }
- data = {
- "${var.db_url_secret_key}" = var.db_url
+ name = var.namespace
}
}
-resource "kubernetes_secret" "redis" {
+resource "kubernetes_secret_v1" "master-key" {
metadata {
- name = var.redis_host_secret_name
- namespace = kubernetes_namespace.this.metadata[0].name
- labels = local.app_labels
+ name = var.litellm_master_secret_name
+ namespace = kubernetes_namespace_v1.litellm.metadata[0].name
}
data = {
- "${var.redis_host_secret_key}" = var.redis_host
- "${var.redis_password_secret_key}" = var.redis_password
+ password = var.litellm_master_key
}
+ type = "Opaque"
}
-resource "kubernetes_secret" "key" {
+resource "kubernetes_secret_v1" "db-auth" {
metadata {
- name = var.litellm_key_secret_name
- namespace = kubernetes_namespace.this.metadata[0].name
- labels = local.app_labels
+ name = nonsensitive(var.db.secret_name)
+ namespace = kubernetes_namespace_v1.litellm.metadata[0].name
}
data = {
- "${var.litellm_key_master_secret_key}" = var.litellm_master_key
- "${var.litellm_key_salt_secret_key}" = var.litellm_salt_key
+ username = nonsensitive(var.db.username)
+ postgres-password = var.db.admin_password
+ password = var.db.user_password
+ endpoint = var.db.endpoint
}
+ type = "Opaque"
}
-resource "kubernetes_secret" "gcloud" {
- metadata {
- name = var.gcloud_auth_secret_name
- namespace = kubernetes_namespace.this.metadata[0].name
- labels = local.app_labels
- }
- data = {
- "${var.gcloud_auth_secret_key}" = var.gcloud_auth
+variable "mount_ssl" {
+ type = object({
+ enable = optional(bool, true)
+ secret_name = optional(string, "ssl-cert")
+ path = optional(string, "")
+ key_name = optional(string, "tls.key")
+ crt_name = optional(string, "tls.crt")
+ pem_name = optional(string, "tls-combined.pem")
+ })
+ default = {
+ enable = false
+ name = "ssl-cert"
+ path = "/tmp/ssl/ssl-cert"
+ key_name = "tls.key"
+ crt_name = "tls.crt"
+ pem_name = "tls-combined.pem"
}
}
-resource "kubernetes_config_map" "config" {
- metadata {
- name = var.litellm_config_name
- namespace = kubernetes_namespace.this.metadata[0].name
- labels = local.app_labels
- }
- data = {
- "${var.litellm_config_key}" = templatefile("${path.module}/scripts/${var.litellm_config_key}", {
- GCP_CRED_PATH = "${var.gcloud_auth_file_path}/${var.gcloud_auth_secret_key}"
- })
- }
+variable "chart_name" {
+ type = string
+ default = "litellm-helm"
}
-resource "kubernetes_config_map" "middleware" {
- metadata {
- name = var.litellm_config_middleware_name
- namespace = kubernetes_namespace.this.metadata[0].name
- labels = local.app_labels
- }
- data = {
- "${var.litellm_config_middleware_key}" = templatefile("${path.module}/scripts/${var.litellm_config_middleware_key}", {})
- }
+variable "release_name" {
+ type = string
+ default = "litellm"
}
locals {
- primary_env_vars = {
- AWS_REGION_NAME = var.aws_bedrock_region
- DOCS_URL = "/swagger"
- LITELLM_LOG = "ERROR"
- LITELLM_LOG_LEVEL = "ERROR"
- LITELLM_MODE = "PRODUCTION"
- REDIS_PORT = "6379"
- REDIS_SSL = "True"
- }
- secret_env_vars = {
- DATABASE_URL = {
- name = var.db_url_secret_name
- key = var.db_url_secret_key
+ volumes = [ for v in var.mounts : merge(v.secret_name != "" ? {
+ name = v.secret_name
+ secret = {
+ secretName = v.secret_name
+ optional = false
}
- LITELLM_MASTER_KEY = {
- name = var.litellm_key_secret_name
- key = var.litellm_key_master_secret_key
+ } : null) ]
+ volumeMounts = [ for v in var.mounts : merge(v.secret_name != "" ? {
+ name = v.secret_name
+ mountPath = v.path
+ readOnly = v.read_only
+ } : null) ]
+}
+
+resource "helm_release" "litellm" {
+ name = var.release_name
+ namespace = var.namespace
+ chart = var.chart_name
+ repository = "oci://ghcr.io/berriai"
+ create_namespace = true
+ upgrade_install = true
+ skip_crds = false
+ replace = true
+ wait = true
+ wait_for_jobs = false
+ reuse_values = false
+ version = var.chart_version
+ timeout = 360 # in seconds
+ max_history = 20
+
+ values = [yamlencode({
+ replicaCount = var.replicas
+ image = {
+ repository = var.image_config.repo
+ pullPolicy = var.image_config.pull_policy
+ tag = var.image_config.tag
}
- LITELLM_SALT_KEY = {
- name = var.litellm_key_secret_name
- key = var.litellm_key_salt_secret_key
+ imagePullSecrets = []
+ nameOverride = "litellm"
+ fullnameOverride = ""
+ serviceAccount = {
+ create = true
+ automount = true
+ annotations = merge({
+ "eks.amazonaws.com/role-arn" = module.oidc-role.role_arn
+ }, var.service_account_annotations)
}
- REDIS_HOST = {
- name = var.redis_host_secret_name
- key = var.redis_host_secret_key
+ service = {
+ type = "LoadBalancer"
+ loadBalancerClass = var.service_lb_class
+ port = var.svc_port
+ annotations = var.svc_annots
}
- REDIS_PASSWORD = {
- name = var.redis_host_secret_name
- key = var.redis_password_secret_key
+ separateHealthApp = true
+ separateHealthPort = var.health_port
+
+ masterkeySecretName = kubernetes_secret_v1.master-key.metadata[0].name
+ masterkeySecretKey = "password"
+
+ proxyConfigMap = {
+ create = true
}
- }
-}
-resource "kubernetes_deployment" "litellm" {
- metadata {
- name = var.name
- namespace = kubernetes_namespace.this.metadata[0].name
- labels = merge(local.app_labels, {
- app = var.name
- })
- }
- spec {
- replicas = var.replicas
- strategy {
- type = "RollingUpdate"
+ proxy_config = var.proxy_config
+
+ resources = {
+ requests = var.resource_request
+ limits = var.resource_limit
}
- selector {
- match_labels = {
- app = var.name
- }
+ autoscaling = {
+ enabled = true
+ minReplicas = var.autoscaling_min_replicas
+ maxReplicas = var.autoscaling_max_replicas
+ targetCPUUtilizationPercentage = var.autoscaling_target_cpu_use
+ targetMemoryUtilizationPercentage = var.autoscaling_target_memory_use
}
- template {
- metadata {
- annotations = {}
- labels = {
- app = var.name
- }
+
+ nodeSelector = var.node_selector
+ topologySpreadConstraints = var.topology_spread
+ tolerations = var.tolerations
+ affinity = var.affinity
+
+ db = merge({
+ deployStandalone = var.db.endpoint == "localhost"
+ useExisting = nonsensitive(var.db.use_existing)
+ database = nonsensitive(var.db.db_name)
+
+ secret = {
+ name = kubernetes_secret_v1.db-auth.metadata[0].name
+ usernameKey = "username"
+ passwordKey = "password"
+ # Optional: when set, DATABASE_HOST will be sourced from this secret key instead of db.endpoint
+ endpointKey = "endpoint"
}
- spec {
- service_account_name = kubernetes_service_account.litellm.metadata[0].name
- container {
- name = var.name
- image = "${var.image_repo}:${var.image_tag}"
- command = split(" ", "litellm --port ${var.app_container_port} --config /app/${var.litellm_config_key} --detailed_debug")
- dynamic "env" {
- for_each = local.primary_env_vars
- content {
- name = env.key
- value = tostring(env.value)
- }
- }
- dynamic "env" {
- for_each = local.secret_env_vars
- content {
- name = env.key
- value_from {
- secret_key_ref {
- name = env.value.name
- key = env.value.key
- }
- }
- }
- }
- port {
- container_port = var.app_container_port
- name = "http"
- protocol = "TCP"
- }
- resources {
- limits = var.resource_limits
- requests = var.resource_requests
- }
- volume_mount {
- mount_path = "/app/${var.litellm_config_key}"
- name = kubernetes_config_map.config.metadata[0].name
- read_only = false
- sub_path = var.litellm_config_key
- }
- volume_mount {
- mount_path = "/app/${var.litellm_config_middleware_key}"
- name = kubernetes_config_map.middleware.metadata[0].name
- read_only = false
- sub_path = var.litellm_config_middleware_key
- }
- volume_mount {
- mount_path = var.gcloud_auth_file_path
- name = kubernetes_secret.gcloud.metadata[0].name
- read_only = true
- sub_path = ""
- }
- }
- volume {
- name = kubernetes_config_map.config.metadata[0].name
- config_map {
- name = kubernetes_config_map.config.metadata[0].name
- }
+ useStackgresOperator = false
+ }, var.db.auth_type == "awsiamrds" ? {
+ url = "postgresql://$(DATABASE_USERNAME)@$(DATABASE_HOST)/$(DATABASE_NAME):$(DATABASE_PORT)"
+ }: {
+ url = "postgresql://$(DATABASE_USERNAME):$(DATABASE_PASSWORD)@$(DATABASE_HOST):$(DATABASE_PORT)/$(DATABASE_NAME)"
+ })
+
+ # Settings for Bitnami postgresql chart (if db.deployStandalone is true, ignored otherwise)
+ postgresql = {
+ architecture = "standalone"
+ auth = {
+ username = var.db.username
+ database = "litellm"
+ enablePostgresUser = true
+
+ # A secret is created by this chart (litellm-helm) with the credentials that the new Postgres instance should use.
+ existingSecret = kubernetes_secret_v1.db-auth.metadata[0].name
+ secretKeys = {
+ adminPasswordKey = "postgres-password"
+ userPasswordKey = "password"
}
- volume {
- name = kubernetes_config_map.middleware.metadata[0].name
- config_map {
- name = kubernetes_config_map.middleware.metadata[0].name
- }
+ }
+ }
+
+ redis = {
+ enabled = false
+ architecture = "standalone"
+ }
+
+ migrationJob = {
+ enabled = true # Enable or disable the schema migration Job
+ retries = 3 # Number of retries for the Job in case of failure
+ backoffLimit = 4 # Backoff limit for Job restarts
+ disableSchemaUpdate = false # Skip schema migrations for specific environments. When True, the job will exit with code 0.
+ annotations = {}
+ ttlSecondsAfterFinished = 120
+ resources = {}
+ # requests:
+ # cpu: 100m
+ # memory: 100Mi
+ extraContainers = []
+
+ # Hook configuration
+ hooks = {
+ argocd = {
+ enabled = false
}
- volume {
- name = kubernetes_secret.gcloud.metadata[0].name
- secret {
- secret_name = kubernetes_secret.gcloud.metadata[0].name
- }
+ helm = {
+ enabled = false
}
}
}
- }
+
+
+ envVars = merge({
+ NO_DOCS = "False"
+ DATABASE_PORT = var.db.port
+ DATABASE_USER = var.db.username
+ }, var.db.auth_type == "awsiamrds" ? {
+ IAM_TOKEN_DB_AUTH = "True"
+ } : {},
+ var.mount_ssl.enable ? {
+ SSL_CERT_FILE = "${var.mount_ssl.path}/${var.mount_ssl.pem_name}"
+ SSL_KEYFILE_PATH = "${var.mount_ssl.path}/${var.mount_ssl.key_name}"
+ SSL_CERTFILE_PATH = "${var.mount_ssl.path}/${var.mount_ssl.crt_name}"
+ SSL_VERIFY = "False"
+ } : {},
+ var.env_vars
+ )
+
+ extraEnvVars = {}
+
+ # Additional volumes on the output Deployment definition.
+
+ volumes = concat(
+ var.mount_ssl.enable ? [{
+ name = var.mount_ssl.secret_name
+ secret = {
+ secretName = var.mount_ssl.secret_name
+ optional = false
+ }
+ }] : [],
+ local.volumes
+ )
+
+ volumeMounts = concat(
+ var.mount_ssl.enable ? [{
+ name = var.mount_ssl.secret_name
+ mountPath = var.mount_ssl.path
+ readOnly = true
+ }] : [],
+ local.volumeMounts
+ )
+ })]
+}
+
+output "namespace" {
+ value = kubernetes_namespace_v1.litellm.metadata[0].name
}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/litellm/policy.tf b/modules/k8s/bootstrap/litellm/policy.tf
index 3238223..5fa639f 100644
--- a/modules/k8s/bootstrap/litellm/policy.tf
+++ b/modules/k8s/bootstrap/litellm/policy.tf
@@ -1,16 +1,17 @@
-data "aws_iam_policy_document" "bedrock-policy" {
+locals {
+ rds_db_name = split(".", var.db.endpoint)[0]
+}
+
+data "aws_db_instance" "litellm" {
+ db_instance_identifier = local.rds_db_name
+}
+
+data "aws_iam_policy_document" "rds" {
statement {
- sid = "AllowModelInvocation"
- effect = "Allow"
- actions = [
- "bedrock:InvokeModel",
- "bedrock:InvokeModelWithResponseStream",
- "bedrock:ListInferenceProfiles"
- ]
+ effect = "Allow"
+ actions = ["rds-db:connect"]
resources = [
- "arn:aws:bedrock:*:*:*",
- "arn:aws:bedrock:*:*:*/*",
- "arn:aws:bedrock:*:*:*:*",
+ "arn:aws:rds-db:${local.region}:${local.account_id}:dbuser:${data.aws_db_instance.litellm.resource_id}/${var.db.username}"
]
}
}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/litellm/scripts/config.yaml b/modules/k8s/bootstrap/litellm/scripts/config.yaml
deleted file mode 100644
index cb34afe..0000000
--- a/modules/k8s/bootstrap/litellm/scripts/config.yaml
+++ /dev/null
@@ -1,220 +0,0 @@
-model_list:
- # AWS Bedrock
- ##
- # Ohio Models
- ##
- - model_name: anthropic.claude.haiku
- model_info:
- base_model: bedrock/us.anthropic.claude-3-haiku-20240307-v1:0
- litellm_params:
- model: bedrock/us.anthropic.claude-3-haiku-20240307-v1:0
- aws_region_name: us-east-2
- rpm: 800
- tpm: 600000
- - model_name: anthropic.claude.sonnet
- model_info:
- base_model: bedrock/us.anthropic.claude-3-5-sonnet-20241022-v2:0
- litellm_params:
- model: bedrock/us.anthropic.claude-3-5-sonnet-20241022-v2:0
- aws_region_name: us-east-2
- rpm: 100
- tpm: 800000
- - model_name: anthropic.claude.haiku
- model_info:
- base_model: bedrock/us.anthropic.claude-3-5-haiku-20241022-v1:0
- litellm_params:
- model: bedrock/us.anthropic.claude-3-5-haiku-20241022-v1:0
- aws_region_name: us-east-2
- rpm: 10
- tpm: 50000
- - model_name: anthropic.claude.sonnet
- model_info:
- base_model: bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0
- litellm_params:
- model: bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0
- aws_region_name: us-east-2
- rpm: 4
- tpm: 10000
-
- ##
- # Oregon Models
- ##
- - model_name: anthropic.claude.haiku
- model_info:
- base_model: bedrock/us.anthropic.claude-3-haiku-20240307-v1:0
- litellm_params:
- model: bedrock/us.anthropic.claude-3-haiku-20240307-v1:0
- aws_region_name: us-west-2
- rpm: 20
- tpm: 40000
- - model_name: anthropic.claude.haiku
- model_info:
- base_model: bedrock/us.anthropic.claude-3-5-haiku-20241022-v1:0
- litellm_params:
- model: bedrock/us.anthropic.claude-3-5-haiku-20241022-v1:0
- aws_region_name: us-west-2
- rpm: 20
- tpm: 40000
- - model_name: anthropic.claude.sonnet
- litellm_params:
- model: bedrock/us.anthropic.claude-3-sonnet-20240229-v1:0
- aws_region_name: us-west-2
- rpm: 10
- tpm: 20000
- - model_name: anthropic.claude.sonnet
- model_info:
- base_model: bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0
- litellm_params:
- model: bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0
- aws_region_name: us-west-2
- rpm: 4
- tpm: 10000
-
- ##
- # Virginia Models
- ##
- - model_name: anthropic.claude.haiku
- model_info:
- base_model: bedrock/us.anthropic.claude-3-haiku-20240307-v1:0
- litellm_params:
- model: bedrock/us.anthropic.claude-3-haiku-20240307-v1:0
- aws_region_name: us-east-1
- rpm: 20
- tpm: 40000
- - model_name: anthropic.claude.haiku
- model_info:
- base_model: bedrock/us.anthropic.claude-3-5-haiku-20241022-v1:0
- litellm_params:
- model: bedrock/us.anthropic.claude-3-5-haiku-20241022-v1:0
- aws_region_name: us-east-1
- rpm: 20
- tpm: 40000
- - model_name: anthropic.claude.sonnet
- model_info:
- base_model: bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0
- litellm_params:
- model: bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0
- aws_region_name: us-east-1
- rpm: 4
- tpm: 10000
-
- # GCP Vertex
- - model_name: anthropic.claude.sonnet
- model_info:
- base_model: vertex_ai/claude-3-7-sonnet@20250219
- litellm_params:
- model: vertex_ai/claude-3-7-sonnet@20250219
- vertex_project: coder-vertex-demos
- vertex_location: europe-west1
- vertex_credentials: ${GCP_CRED_PATH}
- rpm: 280
- tpm: 1500000
- - model_name: anthropic.claude.sonnet
- model_info:
- base_model: vertex_ai/claude-3-7-sonnet@20250219
- litellm_params:
- model: vertex_ai/claude-3-7-sonnet@20250219
- vertex_project: coder-vertex-demos
- vertex_location: us-east5
- vertex_credentials: ${GCP_CRED_PATH}
- rpm: 385
- tpm: 2500000
- - model_name: anthropic.claude.haiku
- model_info:
- base_model: vertex_ai/claude-3-haiku@20240307
- litellm_params:
- model: vertex_ai/claude-3-haiku@20240307
- vertex_project: coder-vertex-demos
- vertex_location: europe-west1
- vertex_credentials: ${GCP_CRED_PATH}
- rpm: 370
- tpm: 907000
- - model_name: anthropic.claude.haiku
- model_info:
- base_model: vertex_ai/claude-3-haiku@20240307
- litellm_params:
- model: vertex_ai/claude-3-haiku@20240307
- vertex_project: coder-vertex-demos
- vertex_location: us-east5
- vertex_credentials: ${GCP_CRED_PATH}
- rpm: 1220
- tpm: 3000000
- - model_name: anthropic.claude.haiku
- model_info:
- base_model: vertex_ai/claude-3-haiku@20240307
- litellm_params:
- model: vertex_ai/claude-3-haiku@20240307
- vertex_project: coder-vertex-demos
- vertex_location: us-central1
- vertex_credentials: ${GCP_CRED_PATH}
- rpm: 1220
- tpm: 3000000
- - model_name: anthropic.claude.sonnet
- model_info:
- base_model: vertex_ai/claude-3-5-sonnet@20240620
- litellm_params:
- model: vertex_ai/claude-3-5-sonnet@20240620
- vertex_project: coder-vertex-demos
- vertex_location: europe-west1
- vertex_credentials: ${GCP_CRED_PATH}
- rpm: 650
- tpm: 3000000
- - model_name: anthropic.claude.sonnet
- model_info:
- base_model: vertex_ai/claude-3-5-sonnet@20240620
- litellm_params:
- model: vertex_ai/claude-3-5-sonnet@20240620
- vertex_project: coder-vertex-demos
- vertex_location: us-east5
- vertex_credentials: ${GCP_CRED_PATH}
- rpm: 600
- tpm: 2775000
- - model_name: anthropic.claude.sonnet
- model_info:
- base_model: vertex_ai/claude-3-5-sonnet-v2@20241022
- litellm_params:
- model: vertex_ai/claude-3-5-sonnet-v2@20241022
- vertex_project: coder-vertex-demos
- vertex_location: europe-west1
- vertex_credentials: ${GCP_CRED_PATH}
- rpm: 275
- tpm: 1670000
- - model_name: anthropic.claude.sonnet
- model_info:
- base_model: vertex_ai/claude-3-5-sonnet-v2@20241022
- litellm_params:
- model: vertex_ai/claude-3-5-sonnet-v2@20241022
- vertex_project: coder-vertex-demos
- vertex_location: us-east5
- vertex_credentials: ${GCP_CRED_PATH}
- rpm: 450
- tpm: 2720000
-
-litellm_settings:
- num_retries: 2
- request_timeout: 45
- allowed_fails: 3
- cooldown_time: 30
- set_verbose: true
- json_logs: false
- cache: true
- callbacks:
- - strip_header_middleware.strip_header_callback
-
-general_settings:
- store_model_in_db: true
- store_prompts_in_spend_logs: true
- proxy_batch_write_at: 60
- database_connection_pool_limit: 10
-
- disable_error_logs: true
- allow_requests_on_db_unavailable: true
-
-router_settings:
- routing_strategy: usage-based-routing-v2
- num_retries: 2
- redis_host: os.environ/REDIS_HOST
- redis_password: os.environ/REDIS_PASSWORD
- redis_port: os.environ/REDIS_PORT
- redis_ssl: os.environ/REDIS_SSL
- timeout: 30
diff --git a/modules/k8s/bootstrap/litellm/scripts/strip_header_middleware.py b/modules/k8s/bootstrap/litellm/scripts/strip_header_middleware.py
deleted file mode 100644
index 39bdff8..0000000
--- a/modules/k8s/bootstrap/litellm/scripts/strip_header_middleware.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from litellm.integrations.custom_logger import CustomLogger
-import litellm
-from litellm.proxy.proxy_server import UserAPIKeyAuth, DualCache
-from typing import Optional, Literal
-
-class HeaderHandler(CustomLogger):
- def __init__(self):
- pass
-
- async def async_pre_call_hook(self, user_api_key_dict: UserAPIKeyAuth, cache: DualCache, data: dict, call_type: Literal[
- "completion",
- "text_completion",
- "embeddings",
- "image_generation",
- "moderation",
- "audio_transcription",
- ]):
-
- v = data["proxy_server_request"]["headers"].pop("anthropic-beta", None)
- if v not in [None, "claude-code-20250219"]:
- data["proxy_server_request"]["headers"]["anthropic-beta"] = v
-
- v = data.get("provider_specific_header", {}).get("extra_headers", {}).pop("anthropic-beta", None)
- if v not in [None, "claude-code-20250219"]:
- data["provider_specific_header"]["extra_headers"]["anthropic-beta"] = v
-
- v = data.get("litellm_metadata", {}).get("headers", {}).pop("anthropic-beta", None)
- if v not in [None, "claude-code-20250219"]:
- data["litellm_metadata"]["headers"]["anthropic-beta"] = v
- print(str(data))
- return data
-
-strip_header_callback = HeaderHandler()
diff --git a/modules/k8s/bootstrap/metrics-server/main.tf b/modules/k8s/bootstrap/metrics-server/main.tf
index 1ec1567..a8512be 100644
--- a/modules/k8s/bootstrap/metrics-server/main.tf
+++ b/modules/k8s/bootstrap/metrics-server/main.tf
@@ -2,7 +2,7 @@ terraform {
required_providers {
helm = {
source = "hashicorp/helm"
- version = "2.17.0"
+ version = ">= 2.17.0"
}
}
}
@@ -22,6 +22,16 @@ variable "node_selector" {
default = {}
}
+variable "tolerations" {
+ type = list(map(any))
+ default = []
+}
+
+variable "affinity" {
+ type = map(any)
+ default = {}
+}
+
data "aws_region" "this" {}
data "aws_caller_identity" "this" {}
@@ -37,11 +47,11 @@ resource "helm_release" "metrics-server" {
wait = true
wait_for_jobs = true
version = var.chart_version
- timeout = 120 # in seconds
+ timeout = 300 # in seconds
values = [yamlencode({
- nodeSelector = {
- "node.amazonaws.io/managed-by" : "asg"
- }
+ nodeSelector = var.node_selector
+ tolerations = var.tolerations
+ affinity = var.affinity
})]
}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/monitoring/alertmanager.yaml b/modules/k8s/bootstrap/monitoring/alertmanager.yaml
new file mode 100644
index 0000000..61ce2a2
--- /dev/null
+++ b/modules/k8s/bootstrap/monitoring/alertmanager.yaml
@@ -0,0 +1,29 @@
+apiVersion: v1
+data:
+ alertmanager.yml: |
+ global: {}
+ receivers:
+ - name: default-receiver
+ route:
+ group_interval: 5m
+ group_wait: 10s
+ receiver: default-receiver
+ repeat_interval: 3h
+ templates:
+ - /etc/alertmanager/*.tmpl
+kind: ConfigMap
+metadata:
+ annotations:
+ meta.helm.sh/release-name: coder-observe
+ meta.helm.sh/release-namespace: observability
+ creationTimestamp: "2026-02-24T00:29:59Z"
+ labels:
+ app.kubernetes.io/instance: coder-observe
+ app.kubernetes.io/managed-by: Helm
+ app.kubernetes.io/name: alertmanager
+ app.kubernetes.io/version: v0.27.0
+ helm.sh/chart: alertmanager-1.11.0
+ name: alertmanager
+ namespace: observability
+ resourceVersion: "1810842"
+ uid: 01931dbb-533c-4b95-9747-fcb8a6fd6d48
diff --git a/modules/k8s/bootstrap/monitoring/collector-config.river b/modules/k8s/bootstrap/monitoring/collector-config.river
new file mode 100644
index 0000000..9215ec5
--- /dev/null
+++ b/modules/k8s/bootstrap/monitoring/collector-config.river
@@ -0,0 +1,504 @@
+// Discover k8s nodes
+discovery.kubernetes "nodes" {
+ role = "node"
+}
+
+// Discover k8s pods
+discovery.kubernetes "pods" {
+ role = "pod"
+ selectors {
+ role = "pod"
+ field = "spec.nodeName=" + env("HOSTNAME")
+ }
+}
+
+
+discovery.relabel "pod_logs" {
+ targets = discovery.kubernetes.pods.targets
+
+ rule {
+ source_labels = ["__meta_kubernetes_namespace"]
+ target_label = "namespace"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_name"]
+ target_label = "pod"
+ }
+ // coalesce the following labels and pick the first value; we'll use this to define the "job" label
+ rule {
+ source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_component", "app", "__meta_kubernetes_pod_container_name"]
+ separator = "/"
+ target_label = "__meta_app"
+ action = "replace"
+ regex = "^/*([^/]+?)(?:/.*)?$" // split by the delimiter if it exists, we only want the first one
+ replacement = "$1"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_namespace", "__meta_kubernetes_pod_label_app_kubernetes_io_name", "__meta_app"]
+ separator = "/"
+ target_label = "job"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_container_name"]
+ target_label = "container"
+ }
+ rule {
+ regex = "__meta_kubernetes_pod_label_(statefulset_kubernetes_io_pod_name|controller_revision_hash)"
+ action = "labeldrop"
+ }
+ rule {
+ regex = "pod_template_generation"
+ action = "labeldrop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_phase"]
+ regex = "Pending|Succeeded|Failed|Completed"
+ action = "drop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_node_name"]
+ action = "replace"
+ target_label = "node"
+ }
+ rule {
+ action = "labelmap"
+ regex = "__meta_kubernetes_pod_annotation_prometheus_io_param_(.+)"
+ replacement = "__param_$1"
+ }
+
+ rule {
+ source_labels = ["__meta_kubernetes_pod_uid", "__meta_kubernetes_pod_container_name"]
+ separator = "/"
+ action = "replace"
+ replacement = "/var/log/pods/*$1/*.log"
+ target_label = "__path__"
+ }
+ rule {
+ action = "replace"
+ source_labels = ["__meta_kubernetes_pod_container_id"]
+ regex = "^(\\w+):\\/\\/.+$"
+ replacement = "$1"
+ target_label = "tmp_container_runtime"
+ }
+}
+
+discovery.relabel "pod_metrics" {
+ targets = discovery.kubernetes.pods.targets
+
+ rule {
+ source_labels = ["__meta_kubernetes_namespace"]
+ target_label = "namespace"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_name"]
+ target_label = "pod"
+ }
+ // coalesce the following labels and pick the first value; we'll use this to define the "job" label
+ rule {
+ source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_component", "app", "__meta_kubernetes_pod_container_name"]
+ separator = "/"
+ target_label = "__meta_app"
+ action = "replace"
+ regex = "^/*([^/]+?)(?:/.*)?$" // split by the delimiter if it exists, we only want the first one
+ replacement = "$1"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_namespace", "__meta_kubernetes_pod_label_app_kubernetes_io_name", "__meta_app"]
+ separator = "/"
+ target_label = "job"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_container_name"]
+ target_label = "container"
+ }
+ rule {
+ regex = "__meta_kubernetes_pod_label_(statefulset_kubernetes_io_pod_name|controller_revision_hash)"
+ action = "labeldrop"
+ }
+ rule {
+ regex = "pod_template_generation"
+ action = "labeldrop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_phase"]
+ regex = "Pending|Succeeded|Failed|Completed"
+ action = "drop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_node_name"]
+ action = "replace"
+ target_label = "node"
+ }
+ rule {
+ action = "labelmap"
+ regex = "__meta_kubernetes_pod_annotation_prometheus_io_param_(.+)"
+ replacement = "__param_$1"
+ }
+
+ // drop ports that do not expose Prometheus metrics, but might otherwise be exposed by a container which *also*
+ // exposes an HTTP port which exposes metrics
+ rule {
+ source_labels = ["__meta_kubernetes_pod_container_port_name"]
+ regex = "grpc|http-(memberlist|console)"
+ action = "drop"
+ }
+ // adapted from the Prometheus helm chart
+ // https://github.com/prometheus-community/helm-charts/blob/862870fc3c847e32479b509e511584d5283126a3/charts/prometheus/values.yaml#L1070
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_prometheus_io_scrape"]
+ action = "keep"
+ regex = "true"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_prometheus_io_scheme"]
+ action = "replace"
+ regex = "(https?)"
+ target_label = "__scheme__"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_prometheus_io_path"]
+ action = "replace"
+ target_label = "__metrics_path__"
+ regex = "(.+)"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_prometheus_io_port", "__meta_kubernetes_pod_ip"]
+ action = "replace"
+ regex = "(\\d+);(([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})"
+ replacement = "[$2]:$1"
+ target_label = "__address__"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_prometheus_io_port", "__meta_kubernetes_pod_ip"]
+ action = "replace"
+ regex = "(\\d+);((([0-9]+?)(\\.|$)){4})"
+ replacement = "$2:$1"
+ target_label = "__address__"
+ }
+}
+
+discovery.relabel "pod_pprof" {
+ targets = discovery.kubernetes.pods.targets
+
+ rule {
+ source_labels = ["__meta_kubernetes_namespace"]
+ target_label = "namespace"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_name"]
+ target_label = "pod"
+ }
+ // coalesce the following labels and pick the first value; we'll use this to define the "job" label
+ rule {
+ source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_component", "app", "__meta_kubernetes_pod_container_name"]
+ separator = "/"
+ target_label = "__meta_app"
+ action = "replace"
+ regex = "^/*([^/]+?)(?:/.*)?$" // split by the delimiter if it exists, we only want the first one
+ replacement = "$1"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_namespace", "__meta_kubernetes_pod_label_app_kubernetes_io_name", "__meta_app"]
+ separator = "/"
+ target_label = "job"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_container_name"]
+ target_label = "container"
+ }
+ rule {
+ regex = "__meta_kubernetes_pod_label_(statefulset_kubernetes_io_pod_name|controller_revision_hash)"
+ action = "labeldrop"
+ }
+ rule {
+ regex = "pod_template_generation"
+ action = "labeldrop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_phase"]
+ regex = "Pending|Succeeded|Failed|Completed"
+ action = "drop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_node_name"]
+ action = "replace"
+ target_label = "node"
+ }
+ rule {
+ action = "labelmap"
+ regex = "__meta_kubernetes_pod_annotation_prometheus_io_param_(.+)"
+ replacement = "__param_$1"
+ }
+
+ // The relabeling allows the actual pod scrape endpoint to be configured via the
+ // following annotations:
+ //
+ // * `pyroscope.io/scrape`: Only scrape pods that have a value of `true`.
+ // * `pyroscope.io/application-name`: Name of the application being profiled.
+ // * `pyroscope.io/scheme`: If the metrics endpoint is secured then you will need
+ // to set this to `https` & most likely set the `tls_config` of the scrape config.
+ // * `pyroscope.io/port`: Scrape the pod on the indicated port.
+ //
+ // Kubernetes labels will be added as Pyroscope labels on metrics via the
+ // `labelmap` relabeling action.
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_pyroscope_io_scrape"]
+ action = "keep"
+ regex = "true"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_pyroscope_io_application_name"]
+ action = "replace"
+ target_label = "__name__"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_pyroscope_io_scheme"]
+ action = "replace"
+ regex = "(https?)"
+ target_label = "__scheme__"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_pyroscope_io_port", "__meta_kubernetes_pod_ip"]
+ action = "replace"
+ regex = "(\\d+);(([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})"
+ replacement = "[$2]:$1"
+ target_label = "__address__"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_annotation_pyroscope_io_port", "__meta_kubernetes_pod_ip"]
+ action = "replace"
+ regex = "(\\d+);((([0-9]+?)(\\.|$)){4})"
+ replacement = "$2:$1"
+ target_label = "__address__"
+ }
+}
+
+local.file_match "pod_logs" {
+ path_targets = discovery.relabel.pod_logs.output
+}
+
+loki.source.file "pod_logs" {
+ targets = local.file_match.pod_logs.targets
+ forward_to = [loki.process.pod_logs.receiver]
+}
+
+loki.process "pod_logs" {
+ stage.match {
+ selector = "{tmp_container_runtime=\"containerd\"}"
+ // the cri processing stage extracts the following k/v pairs: log, stream, time, flags
+ stage.cri {}
+ // Set the extract flags and stream values as labels
+ stage.labels {
+ values = {
+ flags = "",
+ stream = "",
+ }
+ }
+ }
+
+ // if the label tmp_container_runtime from above is docker parse using docker
+ stage.match {
+ selector = "{tmp_container_runtime=\"docker\"}"
+ // the docker processing stage extracts the following k/v pairs: log, stream, time
+ stage.docker {}
+
+ // Set the extract stream value as a label
+ stage.labels {
+ values = {
+ stream = "",
+ }
+ }
+ }
+
+ // drop the temporary container runtime label as it is no longer needed
+ stage.label_drop {
+ values = ["tmp_container_runtime"]
+ }
+
+ // parse Coder logs and extract level & logger for efficient filtering
+ stage.match {
+ selector = "{pod=~\"coder.*\"}" // TODO: make configurable
+
+ stage.multiline {
+ firstline = "^(?P\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\.\\d{3})"
+ max_wait_time = "10s"
+ }
+
+ stage.regex {
+ expression = "^(?P\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}\\.\\d{3})\\s\\[(?P\\w+)\\]\\s\\s(?P[^:]+):\\s(?P.+)"
+ }
+
+ stage.timestamp {
+ source = "ts"
+ format = "2006-01-02 15:04:05.000"
+ action_on_failure = "fudge" // rather have inaccurate time than drop the log line
+ }
+
+ stage.labels {
+ values = {
+ level = "",
+ logger = "",
+ }
+ }
+ }
+
+ forward_to = [loki.write.loki.receiver]
+}
+
+loki.write "loki" {
+ endpoint {
+ url = "${LOKI_ENDPOINT}"
+ }
+}
+
+
+
+prometheus.scrape "pods" {
+ targets = discovery.relabel.pod_metrics.output
+ forward_to = [prometheus.relabel.pods.receiver]
+
+ scrape_interval = "30s"
+ scrape_timeout = "12s"
+ enable_protobuf_negotiation = false
+}
+
+// These are metric_relabel_configs while discovery.relabel are relabel_configs.
+// See https://github.com/grafana/agent/blob/main/internal/converter/internal/prometheusconvert/prometheusconvert.go#L95-L106
+prometheus.relabel "pods" {
+ forward_to = [prometheus.remote_write.default.receiver]
+
+ // Drop kube-state-metrics' labels which clash with ours
+ rule {
+ source_labels = ["__name__", "container"]
+ regex = "kube_pod.+;(.+)"
+ target_label = "container"
+ replacement = ""
+ }
+ rule {
+ source_labels = ["__name__", "pod"]
+ regex = "kube_pod.+;(.+)"
+ target_label = "pod"
+ replacement = ""
+ }
+ rule {
+ source_labels = ["__name__", "namespace"]
+ regex = "kube_pod.+;(.+)"
+ target_label = "namespace"
+ replacement = ""
+ }
+ rule {
+ source_labels = ["__name__", "exported_container"]
+ // don't replace an empty label
+ regex = "^kube_pod.+;(.+)$"
+ target_label = "container"
+ replacement = "$1"
+ }
+ rule {
+ source_labels = ["__name__", "exported_pod"]
+ // don't replace an empty label
+ regex = "^kube_pod.+;(.+)$"
+ target_label = "pod"
+ replacement = "$1"
+ }
+ rule {
+ source_labels = ["__name__", "exported_namespace"]
+ // don't replace an empty label
+ regex = "^kube_pod.+;(.+)$"
+ target_label = "namespace"
+ replacement = "$1"
+ }
+ rule {
+ regex = "^(exported_.*|image_.*|container_id|id|uid)$"
+ action = "labeldrop"
+ }
+}
+
+discovery.relabel "cadvisor" {
+ targets = discovery.kubernetes.nodes.targets
+ rule {
+ source_labels = ["__meta_kubernetes_node_name"]
+ regex = env("HOSTNAME")
+ action = "keep"
+ }
+ rule {
+ replacement = "/metrics/cadvisor"
+ target_label = "__metrics_path__"
+ }
+}
+
+prometheus.scrape "cadvisor" {
+ targets = discovery.relabel.cadvisor.output
+ forward_to = [ prometheus.relabel.cadvisor.receiver ]
+ scheme = "https"
+ tls_config {
+ insecure_skip_verify = true
+ }
+ bearer_token_file = "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ scrape_interval = "30s"
+ scrape_timeout = "12s"
+ enable_protobuf_negotiation = false
+}
+
+prometheus.relabel "cadvisor" {
+ forward_to = [ prometheus.remote_write.default.receiver ]
+
+ // Drop empty container labels, addressing https://github.com/google/cadvisor/issues/2688
+ rule {
+ source_labels = ["__name__","container"]
+ separator = "@"
+ regex = "(container_cpu_.*|container_fs_.*|container_memory_.*)@"
+ action = "drop"
+ }
+ // Drop empty image labels, addressing https://github.com/google/cadvisor/issues/2688
+ rule {
+ source_labels = ["__name__","image"]
+ separator = "@"
+ regex = "(container_cpu_.*|container_fs_.*|container_memory_.*|container_network_.*)@"
+ action = "drop"
+ }
+ // Drop irrelevant series
+ rule {
+ source_labels = ["container"]
+ regex = "^POD$"
+ action = "drop"
+ }
+ // Drop unnecessary labels
+ rule {
+ source_labels = ["id"]
+ target_label = "id"
+ replacement = ""
+ }
+ rule {
+ source_labels = ["job"]
+ target_label = "job"
+ replacement = ""
+ }
+ rule {
+ source_labels = ["name"]
+ target_label = "name"
+ replacement = ""
+ }
+}
+
+prometheus.remote_write "default" {
+ wal {
+ truncate_frequency = "30m"
+ max_keepalive_time = "1h"
+ min_keepalive_time = "5m"
+ }
+ endpoint {
+ send_native_histograms = false
+ url = "${AWS_PROMETHEUS_ENDPOINT}"
+
+ sigv4 {
+ region = "${AWS_PROMETHEUS_REGION}"
+ }
+
+ // drop instance label which unnecessarily adds new series when pods are restarted, since pod IPs are dynamically assigned
+ // NOTE: "__address__" is mapped to "instance", so will contain :
+ write_relabel_config {
+ regex = "instance"
+ action = "labeldrop"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/monitoring/main.tf b/modules/k8s/bootstrap/monitoring/main.tf
new file mode 100644
index 0000000..ab956fc
--- /dev/null
+++ b/modules/k8s/bootstrap/monitoring/main.tf
@@ -0,0 +1,1225 @@
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ }
+ helm = {
+ source = "hashicorp/helm"
+ version = ">= 2.17.0"
+ }
+ kubernetes = {
+ source = "hashicorp/kubernetes"
+ }
+ grafana = {
+ source = "grafana/grafana"
+ }
+ }
+}
+
+variable "chart_version" {
+ type = string
+ default = "0.6.2"
+}
+
+variable "chart_timeout" {
+ type = number
+ default = 300
+}
+
+variable "cluster_name" {
+ type = string
+}
+
+variable "cluster_oidc_provider_arn" {
+ type = string
+}
+
+variable "namespace" {
+ type = string
+ default = "coder-observe"
+}
+
+variable "dashboards" {
+ type = object({
+ use_builtins = optional(bool, true)
+ default_home_path = optional(string, "")
+ config_maps = optional(map(object({
+ mount_path = optional(string, "")
+ local_path = optional(string, "")
+ args = optional(map(string), {})
+ read_only = optional(bool, false)
+ optional = optional(bool, true)
+ })))
+ })
+ default = {
+ use_builtins = true
+ config_maps = {}
+ }
+}
+
+variable "coder" {
+ type = object({
+ db = object({
+ host = string
+ password = string
+ username = string
+ database = string
+ sslmode = optional(string, "require")
+ })
+ selector = object({
+ coderd = string
+ provisionerd = string
+ workspaces = string
+ ctrl_plane_ns = string
+ ext_prov_ns = string
+ })
+ })
+ sensitive = true
+}
+
+variable "grafana" {
+ type = object({
+ instance_name = optional(string, "Coder Environment")
+ admin = object({
+ username = string
+ password = string
+ })
+ db = object({
+ host = string
+ password = string
+ username = string
+ database = string
+ sslmode = optional(string, "require")
+ })
+ svc = object({
+ port = optional(number, 80)
+ annots = optional(map(string), {})
+ })
+ replicas = optional(number, 1)
+ tolerations = optional(list(any), [])
+ affinity = optional(any, {})
+ topology_spread = optional(list(any), [])
+ node_selector = optional(map(string), {})
+ rsrc = optional(object({
+ requests = optional(object({
+ cpu = optional(string, "2")
+ memory = optional(string, "4Gi")
+ }), null)
+ limits = optional(object({
+ cpu = optional(string, "2")
+ memory = optional(string, "4Gi")
+ }), null)
+ }), {
+ requests = null
+ limits = null
+ })
+ })
+ sensitive = true
+}
+
+variable "loki" {
+ type = object({
+ s3 = object({
+ chunks_bucket = string
+ ruler_bucket = string
+ region = string
+ })
+ tolerations = optional(list(any), [])
+ affinity = optional(any, {})
+ topology_spread = optional(list(any), [])
+ node_selector = optional(map(string), {})
+ pv = object({
+ enabled = optional(bool, true)
+ storageClass = optional(string, "gp3")
+ })
+ rsrc = optional(object({
+ requests = optional(object({
+ cpu = optional(string, "2")
+ memory = optional(string, "4Gi")
+ }), null)
+ limits = optional(object({
+ cpu = optional(string, "2")
+ memory = optional(string, "4Gi")
+ }), null)
+ }), {
+ requests = null
+ limits = null
+ })
+ })
+}
+
+variable "prometheus" {
+ type = object({
+ ooo_window = optional(string, "1800s")
+ pv = object({
+ enabled = optional(bool, true)
+ storageClass = optional(string, "gp3")
+ size = optional(string, "12Gi")
+ })
+ tolerations = optional(list(any), [])
+ affinity = optional(any, {})
+ topology_spread = optional(list(any), [])
+ node_selector = optional(map(string), {})
+ liveliness = optional(object({
+ initial_delay = optional(number, 60)
+ timeout = optional(number, 60)
+ period = optional(number, 60)
+ failure_threshold = optional(number, 10)
+ }), {})
+ readiness = optional(object({
+ initial_delay = optional(number, 60)
+ timeout = optional(number, 60)
+ period = optional(number, 60)
+ failure_threshold = optional(number, 10)
+ }), {})
+ rsrc = optional(object({
+ requests = optional(object({
+ cpu = optional(string, "2")
+ memory = optional(string, "4Gi")
+ }), null)
+ limits = optional(object({
+ cpu = optional(string, "2")
+ memory = optional(string, "4Gi")
+ }), null)
+ }), {
+ requests = null
+ limits = null
+ })
+ })
+}
+
+variable "alertmanager" {
+ type = object({
+ enabled = optional(bool, true)
+ pv = object({
+ enabled = optional(bool, false)
+ storageClass = optional(string, "gp3")
+ })
+ tolerations = optional(list(any), [])
+ affinity = optional(any, {})
+ topology_spread = optional(list(any), [])
+ node_selector = optional(map(string), {})
+ rsrc = optional(object({
+ requests = optional(object({
+ cpu = optional(string, "2")
+ memory = optional(string, "4Gi")
+ }), {})
+ limits = optional(object({
+ cpu = optional(string, "2")
+ memory = optional(string, "4Gi")
+ }), {})
+ }), {
+ requests = {}
+ limits = {}
+ })
+ })
+}
+
+variable "mount_ssl" {
+ type = object({
+ enable = optional(bool, true)
+ secret_name = optional(string, "ssl-cert")
+ mount_path = optional(string, "")
+ key_name = optional(string, "tls.key")
+ crt_name = optional(string, "tls.crt")
+ })
+ default = {
+ enable = false
+ secret_name = "ssl-cert"
+ mount_path = ""
+ key_name = "tls.key"
+ crt_name = "tls.crt"
+ }
+}
+
+variable "lb_class" {
+ type = string
+ default = "service.k8s.aws/nlb"
+}
+
+variable "tolerations" {
+ type = list(any)
+ default = []
+}
+
+variable "system_tolerations" {
+ description = "(Optional) Override if you need to adjust where critical monitoring addons need to be moved."
+ type = list(map(any))
+ default = [{
+ key = "CriticalAddonsOnly"
+ operator = "Exists"
+ }]
+}
+
+variable "system_affinity" {
+ description = "(Optional) Override if you need to adjust where critical monitoring addons need to be moved."
+ type = any
+ default = {}
+}
+
+variable "daemonset_tolerations" {
+ description = "(Optional) Override if you need to adjust where monitoring DaemonSets need to be placed."
+ type = list(any)
+ default = [{
+ effect = "NoSchedule"
+ operator = "Exists"
+ }]
+}
+
+variable "daemonset_node_selector" {
+ description = "(Optional) Override if you need to adjust where monitoring DaemonSets need to be placed."
+ type = map(string)
+ default = {}
+}
+
+variable "vpc_name" {
+ type = string
+}
+
+variable "private_subnet_suffix" {
+ type = string
+ default = "private"
+}
+
+variable "domain_name" {
+ type = string
+ default = ""
+}
+
+variable "acm_certificate_arn" {
+ type = string
+ default = ""
+}
+
+data "aws_region" "this" {}
+
+data "aws_caller_identity" "this" {}
+
+data "aws_vpc" "this" {
+ tags = {
+ Name = var.vpc_name
+ }
+}
+
+data "aws_subnets" "private" {
+ filter {
+ name = "vpc-id"
+ values = [data.aws_vpc.this.id]
+ }
+
+ tags = {
+ Name = "*${var.private_subnet_suffix}*"
+ }
+}
+
+locals {
+ role_name = "observability-access"
+ policy_name = "ObservabilityAccess-${data.aws_region.this.region}"
+}
+
+module "iam-policy" {
+ source = "../../../security/policy"
+ name = local.policy_name
+ path = "/${var.cluster_name}/${data.aws_region.this.region}/"
+ description = "Loki S3 policy"
+ policy_json = data.aws_iam_policy_document.this.json
+}
+
+
+module "oidc-role" {
+ source = "../../../security/role/access-entry"
+ name = local.role_name
+ path = "/${var.cluster_name}/${data.aws_region.this.region}/"
+ cluster_name = var.cluster_name
+ policy_arns = {
+ "AmazonS3ReadOnlyAccess" = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess",
+ "LokiS3Policy" = module.iam-policy.policy_arn
+ "AmazonPrometheusQueryAccess" = "arn:aws:iam::aws:policy/AmazonPrometheusQueryAccess"
+ "AmazonPrometheusRemoteWriteAccess" = "arn:aws:iam::aws:policy/AmazonPrometheusRemoteWriteAccess"
+ }
+ cluster_policy_arns = {
+ "AmazonEKSClusterAdminPolicy" = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy",
+ }
+ oidc_principals = {
+ "${var.cluster_oidc_provider_arn}" = ["system:serviceaccount:*:*"]
+ }
+ tags = {}
+}
+
+resource "kubernetes_namespace_v1" "this" {
+ metadata {
+ name = var.namespace
+ }
+}
+
+resource "kubernetes_config_map_v1" "dashboard" {
+
+ for_each = var.dashboards.config_maps
+
+ metadata {
+ name = each.key
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ }
+ data = {
+ (element(split("/", each.value.local_path), -1)) = templatefile(each.value.local_path, each.value.args)
+ }
+}
+
+resource "random_id" "grafana_server_secret" {
+ keepers = {
+ # Generate a new secret if the admin password changes
+ grafana_admin_username = var.grafana.admin.username
+ grafana_admin_password = var.grafana.admin.password
+ }
+ byte_length = 16
+}
+
+locals {
+ name = "coder-observe"
+ coder_db_host = split(":", var.coder.db.host)[0]
+ coder_db_port = split(":", var.coder.db.host)[1]
+ grafana_db_host = split(":", var.grafana.db.host)[0]
+ # grafana_db_port = split(":", var.grafana.db.host)[1]
+}
+
+resource "helm_release" "coder-observe" {
+ name = local.name
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ chart = "coder-observability"
+ repository = "https://helm.coder.com/observability"
+ create_namespace = false
+ upgrade_install = true
+ skip_crds = false
+ wait = true
+ wait_for_jobs = true
+ version = var.chart_version
+ timeout = var.chart_timeout
+ max_history = 10
+
+ values = [yamlencode({
+ global = {
+ coder = {
+ coderdSelector = var.coder.selector.coderd
+ provisionerdSelector = var.coder.selector.provisionerd
+ workspacesSelector = var.coder.selector.workspaces
+ controlPlaneNamespace = var.coder.selector.ctrl_plane_ns
+ externalProvisionersNamespace = var.coder.selector.ext_prov_ns
+ }
+ postgres = {
+ exporter = {
+ enabled = true
+ }
+ hostname = local.coder_db_host
+ port = local.coder_db_port
+ password = var.coder.db.password
+ username = var.coder.db.username
+ database = var.coder.db.database
+ sslmode = var.coder.db.sslmode
+ mountSecret = ""
+ affinity = var.prometheus.affinity
+ }
+ dashboards = {
+ enabled = var.dashboards.use_builtins
+ }
+ }
+ prometheus = {
+ enabled = true
+ # prometheus-node-exporter = {
+ # tolerations = var.daemonset_tolerations
+ # nodeSelector = var.daemonset_node_selector
+ # }
+ kube-state-metrics = {
+ enabled = true
+ podAnnotations = {
+ "prometheus.io/scrape" = "true"
+ }
+ affinity = var.prometheus.affinity
+ tolerations = var.prometheus.tolerations
+ }
+ configmapReload = {
+ prometheus = {
+ enabled = false
+ extraArgs = {
+ "watch-interval" = "30s"
+ }
+ }
+ }
+ server = {
+ tsdb = {
+ out_of_order_time_window = var.prometheus.ooo_window
+ }
+ replicaCount = 0
+ retention = "1h"
+ extraFlags = [
+ # "storage.tsdb.wal-compression",
+ "web.enable-lifecycle",
+ "web.enable-remote-write-receiver"
+ ]
+ extraArgs = {
+ # "storage.tsdb.retention.time" = "1d"
+ # "storage.tsdb.min-block-duration" = "2h"
+ # "storage.tsdb.max-block-duration" = "2h"
+ }
+ persistentVolume = { # var.prometheus.pv
+ enabled = false
+ }
+ nodeSelector = var.prometheus.node_selector
+ tolerations = var.prometheus.tolerations
+ affinity = var.prometheus.affinity
+ resources = var.prometheus.rsrc
+
+ livenessProbeInitialDelaySeconds = var.prometheus.liveliness.initial_delay
+ livenessProbetimeoutSeconds = var.prometheus.liveliness.timeout
+ livenessProbePeriodSeconds = var.prometheus.liveliness.period
+ livenessProbeFailureThreshold = var.prometheus.liveliness.failure_threshold
+
+ readinessProbeInitialDelay = var.prometheus.readiness.initial_delay
+ readinessProbeTimeout = var.prometheus.readiness.timeout
+ readinessProbePeriodSeconds = var.prometheus.readiness.period
+ readinessProbeFailureThreshold = var.prometheus.readiness.failure_threshold
+ }
+ alertmanager = {
+ replicas = 0
+ enabled = var.alertmanager.enabled
+ persistence = var.alertmanager.pv
+ nodeSelector = var.alertmanager.node_selector
+ tolerations = var.alertmanager.tolerations
+ affinity = var.alertmanager.affinity
+ # resources = var.alertmanager.rsrc
+ resources = {}
+ }
+ }
+ grafana = {
+ enabled = false
+ # https://github.com/grafana/helm-charts/blob/grafana-7.3.7/charts/grafana/values.yaml#L1313-L1321
+ assertNoLeakedSecrets = false
+ adminUser = var.grafana.admin.username
+ adminPassword = var.grafana.admin.password
+ env = {
+ GF_SECURITY_DISABLE_INITIAL_ADMIN_CREATION = false
+ }
+ "grafana.ini" = {
+ app_mode = "production"
+ auth = {
+ sigv4_auth_enabled = true
+ }
+ "auth.anonymous" = {
+ enabled = false
+ }
+ dashboards = {
+ default_home_dashboard_path = var.dashboards.default_home_path
+ }
+ instance_name = var.grafana.instance_name
+ database = {
+ host = local.grafana_db_host
+ port = 5432
+ name = var.grafana.db.database
+ username = var.grafana.db.username
+ password = "\"\"\"${var.grafana.db.password}\"\"\""
+ ssl_mode = var.grafana.db.sslmode
+ }
+ security = {
+ secret_key = random_id.grafana_server_secret.hex
+ cookie_secure = true
+ cookie_samesite = "lax"
+ cookie_domain = var.domain_name
+ }
+ server = merge(var.mount_ssl.enable ? {
+ cert_key = "${trimsuffix(var.mount_ssl.mount_path, "/")}/${var.mount_ssl.key_name}"
+ cert_file = "${trimsuffix(var.mount_ssl.mount_path, "/")}/${var.mount_ssl.crt_name}"
+ protocol = "https"
+ root_url = "https://${var.domain_name}"
+ } : {
+ protocol = "http"
+ root_url = "http://${var.domain_name}"
+ }, {
+ domain = var.domain_name
+ enforce_domain = false
+ http_port = 3000
+ })
+ users = {
+ allow_sign_up = false
+ }
+ }
+ nodeSelector = var.grafana.node_selector
+ tolerations = var.grafana.tolerations
+ affinity = var.grafana.affinity
+ replicas = 0 # var.grafana.replicas
+ useStatefulSet = true
+ resources = var.grafana.rsrc
+ readinessProbe = {
+ httpGet = {
+ scheme = var.mount_ssl.enable ? "HTTPS" : "HTTP"
+ }
+ }
+ livenessProbe = {
+ httpGet = {
+ scheme = var.mount_ssl.enable ? "HTTPS" : "HTTP"
+ }
+ }
+ persistence = {
+ enabled = false
+ }
+ podAnnotations = {
+ "prometheus.io/port" = "3000"
+ "prometheus.io/scheme" = var.mount_ssl.enable ? "https" : "http"
+ "prometheus.io/scrape" = "true"
+ }
+ serviceAccount = {
+ annotations = {
+ "eks.amazonaws.com/role-arn" = module.oidc-role.role_arn
+ }
+ }
+ service = {
+ enabled = true
+ externalTrafficPolicy = "Cluster"
+ internalTrafficPolicy = "Cluster"
+ loadBalancerClass = var.lb_class
+ port = var.grafana.svc.port
+ targetPort = 3000
+ type = "LoadBalancer"
+ annotations = var.grafana.svc.annots
+ }
+ extraConfigmapMounts = [ for k, v in var.dashboards.config_maps : {
+ name = k
+ configMap = kubernetes_config_map_v1.dashboard[k].metadata[0].name
+ mountPath = v.mount_path
+ readOnly = v.read_only
+ optional = v.optional
+ } ]
+ extraSecretMounts = var.mount_ssl.enable ? [{
+ name = var.mount_ssl.secret_name
+ mountPath = var.mount_ssl.mount_path
+ secretName = var.mount_ssl.secret_name
+ readOnly = true
+ }] : []
+ datasources = {
+ "datasources.yaml" = {
+ datasources = [
+ {
+ name = "metrics"
+ type = "prometheus"
+ url = aws_prometheus_workspace.this.prometheus_endpoint
+ access = "proxy"
+ isDefault = true
+ editable = false
+ timeout = 905
+ uid = "prometheus"
+ jsonData = {
+ sigV4AuthType = "default"
+ httpMethod = "POST"
+ sigV4Auth = true
+ sigV4Region = data.aws_region.this.region
+ }
+ },
+ {
+ name = "pyroscope"
+ type = "grafana-pyroscope-datasource"
+ url = "http://pyroscope.${var.namespace}.svc:4040"
+ isDefault = false
+ editable = false
+ access = "proxy"
+ timeout = 905
+ uid = "pyroscope"
+ },
+ {
+ name = "traces"
+ type = "tempo"
+ url = "http://tempo.${var.namespace}.svc:3200"
+ access = "proxy"
+ isDefault = false
+ editable = false
+ timeout = 905
+ uid = "tempo"
+ }
+ ]
+ }
+ }
+ }
+ grafana-agent = {
+ enabled = false
+ }
+ sqlExporter = {
+ enabled = false
+ }
+ runbookViewer = {
+ enabled = false
+ }
+ loki = {
+ loki = {
+ storage = {
+ bucketNames = {
+ chunks = var.loki.s3.chunks_bucket
+ ruler = var.loki.s3.ruler_bucket
+ }
+ s3 = {
+ region = var.loki.s3.region
+ }
+ type = "s3"
+ }
+ rulerConfig = {
+ remote_write = {
+ enabled = true
+ clients = {
+ fake = {
+ url = "${aws_prometheus_workspace.this.prometheus_endpoint}/api/v1/remote_write"
+ }
+ }
+ }
+ }
+ }
+ lokiCanary = {
+ tolerations = var.daemonset_tolerations
+ nodeSelector = var.daemonset_node_selector
+ }
+ backend = {
+ replicas = 1
+ tolerations = var.loki.tolerations
+ affinity = var.loki.affinity
+ persistence = {
+ volumeClaimsEnabled = false
+ # storageClass = var.storage_class
+ }
+ }
+ resultsCache = {
+ replicas = 1
+ tolerations = var.loki.tolerations
+ affinity = var.loki.affinity
+ }
+ chunksCache = {
+ replicas = 1
+ tolerations = var.loki.tolerations
+ affinity = var.loki.affinity
+ persistence = var.loki.pv
+ }
+ write = {
+ replicas = 1
+ tolerations = var.loki.tolerations
+ affinity = var.loki.affinity
+ persistence = {
+ volumeClaimsEnabled = false
+ # storageClass = var.storage_class
+ }
+ }
+ read = {
+ replicas = 1
+ tolerations = var.loki.tolerations
+ affinity = var.loki.affinity
+ podAnnotatiosn = {
+ "prometheus.io/scrape" = "true"
+ }
+ }
+ minio = {
+ enabled = false
+ # tolerations = var.system_tolerations
+ }
+ gateway = {
+ replicas = 1
+ tolerations = var.loki.tolerations
+ affinity = var.loki.affinity
+ podAnnotatiosn = {
+ "prometheus.io/scrape" = "true"
+ }
+ service = {
+ type = "LoadBalancer"
+ annotations = {
+ "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "ip"
+ "service.beta.kubernetes.io/aws-load-balancer-scheme" = "internal"
+ }
+ }
+ }
+ serviceAccount = {
+ create = true
+ annotations = {
+ "eks.amazonaws.com/role-arn" = module.oidc-role.role_arn
+ }
+ }
+ }
+ })]
+}
+
+data "aws_iam_policy_document" "grafana-sts" {
+ statement {
+ effect = "Allow"
+ principals {
+ type = "Service"
+ identifiers = ["grafana.amazonaws.com"]
+ }
+ actions = ["sts:AssumeRole"]
+ condition {
+ test = "StringEquals"
+ variable = "aws:SourceAccount"
+ values = ["${data.aws_caller_identity.this.account_id}"]
+ }
+ condition {
+ test = "StringLike"
+ variable = "aws:SourceArn"
+ values = ["arn:aws:grafana:${data.aws_region.this.region}:${data.aws_caller_identity.this.account_id}:/workspaces/*"]
+ }
+ }
+}
+
+resource "aws_iam_role" "grafana" {
+ name = "${local.name}-grafana"
+ path = "/"
+ assume_role_policy = data.aws_iam_policy_document.grafana-sts.json
+}
+
+data "aws_iam_policy_document" "grafana" {
+ statement {
+ effect = "Allow"
+ actions = [
+ "aps:ListWorkspaces",
+ "aps:DescribeWorkspace",
+ "aps:QueryMetrics",
+ "aps:GetLabels",
+ "aps:GetSeries",
+ "aps:GetMetricMetadata"
+ ]
+ resources = ["*"]
+ }
+}
+
+resource "aws_iam_policy" "policy" {
+ name = "${local.name}-grafana"
+ description = "AWS Managed Grafana Policy"
+ policy = data.aws_iam_policy_document.grafana.json
+}
+
+resource "aws_iam_role_policy_attachment" "grafana" {
+
+ for_each = {
+ "${local.name}-grafana" = aws_iam_policy.policy.arn
+ "AmazonGrafanaCloudWatchAccess" = "arn:aws:iam::aws:policy/service-role/AmazonGrafanaCloudWatchAccess"
+ }
+
+ role = aws_iam_role.grafana.name
+ policy_arn = each.value
+}
+
+resource "aws_security_group" "grafana" {
+ vpc_id = data.aws_vpc.this.id
+ name = "${local.name}-grafana"
+ description = "SG for Grafana - All Egress traffic"
+ tags = {
+ Name = "Customer-Managed AWS Managed Grafana"
+ }
+}
+
+resource "aws_vpc_security_group_egress_rule" "grafana" {
+ security_group_id = aws_security_group.grafana.id
+ cidr_ipv4 = "0.0.0.0/0"
+ ip_protocol = -1
+}
+
+locals {
+ # Root, Coder, Customer
+ ous = ["r-4vw4", "ou-4vw4-avnmq38g", "ou-4vw4-2qki2hxj"]
+ admin_iam_identity_ids = [
+ "24c85468-90e1-70c7-3498-bc5695b7c6f0", # Jullian
+ ]
+ viewer_group_iam_identity_ids = [
+ "a498a458-b0c1-70a8-face-e9480207b880"
+ ]
+ viewer_iam_identity_ids = [
+ "d4a80408-70f1-70a0-d637-d3372eb29d29", # Dave Ahr
+ "743804f8-30d1-705d-9ed9-d1905cbac291", # Michael Patterson
+ "d4a8a458-c041-70eb-a4c4-94db19a67d83", # Matt Colton,
+ "347844f8-e031-70f5-78cc-7ebcdeec102c", # Jakub
+ ]
+}
+
+data "aws_ssoadmin_instances" "this" {
+ region = "us-east-1"
+}
+
+data "aws_identitystore_group" "aws_administrator" {
+ identity_store_id = one(data.aws_ssoadmin_instances.this.identity_store_ids)
+ region = "us-east-1"
+
+ alternate_identifier {
+ unique_attribute {
+ attribute_path = "DisplayName"
+ attribute_value = "CoderCSAWSAdmin"
+ }
+ }
+}
+
+data "aws_identitystore_group_memberships" "aws_admins" {
+ identity_store_id = one(data.aws_ssoadmin_instances.this.identity_store_ids)
+ region = "us-east-1"
+ group_id = data.aws_identitystore_group.aws_administrator.group_id
+}
+
+resource "aws_grafana_workspace" "this" {
+
+ name = local.name
+
+ account_access_type = "ORGANIZATION"
+ organizational_units = local.ous
+ authentication_providers = ["AWS_SSO"]
+ permission_type = "CUSTOMER_MANAGED"
+ region = data.aws_region.this.region
+ data_sources = ["PROMETHEUS", "CLOUDWATCH"]
+ grafana_version = "10.4"
+ role_arn = aws_iam_role.grafana.arn
+
+ vpc_configuration {
+ security_group_ids = [
+ aws_security_group.grafana.id
+ ]
+ subnet_ids = toset(concat(
+ data.aws_subnets.private.ids
+ ))
+ }
+}
+
+resource "aws_grafana_role_association" "admins" {
+ role = "ADMIN"
+ user_ids = local.admin_iam_identity_ids
+ group_ids = [data.aws_identitystore_group.aws_administrator.group_id]
+ workspace_id = aws_grafana_workspace.this.id
+}
+
+resource "aws_grafana_workspace_service_account" "admin" {
+ name = "admin"
+ grafana_role = "ADMIN"
+ workspace_id = aws_grafana_workspace.this.id
+}
+
+resource "random_pet" "token_name" {}
+
+resource "aws_grafana_workspace_service_account_token" "admin" {
+ name = random_pet.token_name.id
+ service_account_id = aws_grafana_workspace_service_account.admin.service_account_id
+ seconds_to_live = 2591999 # 30 days
+ workspace_id = aws_grafana_workspace.this.id
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
+
+resource "aws_grafana_role_association" "viewer" {
+ role = "VIEWER"
+ user_ids = local.viewer_iam_identity_ids
+ group_ids = [data.aws_identitystore_group.aws_administrator.group_id]
+ workspace_id = aws_grafana_workspace.this.id
+}
+
+resource "aws_grafana_workspace_service_account" "viewer" {
+ name = "viewer"
+ grafana_role = "VIEWER"
+ workspace_id = aws_grafana_workspace.this.id
+}
+
+resource "aws_prometheus_workspace" "this" {
+ alias = local.name
+}
+
+# resource "aws_prometheus_rule_group_namespace" "coder_alerts" {
+# name = "coder-alerts"
+# workspace_id = aws_prometheus_workspace.coder.id
+# data = file("${path.module}/alert-rules.yaml")
+# }
+
+resource "aws_cloudfront_distribution" "grafana" {
+ enabled = true
+ aliases = [var.domain_name]
+ default_root_object = ""
+ price_class = "PriceClass_100"
+
+ origin {
+ domain_name = aws_grafana_workspace.this.endpoint
+ origin_id = "ALB-Grafana"
+
+ custom_origin_config {
+ http_port = 80
+ https_port = 443
+ origin_protocol_policy = "https-only"
+ origin_ssl_protocols = ["TLSv1.2"]
+ }
+ }
+
+ default_cache_behavior {
+ allowed_methods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
+ cached_methods = ["GET", "HEAD"]
+ target_origin_id = "ALB-Grafana"
+
+ forwarded_values {
+ query_string = true
+ cookies {
+ forward = "all"
+ }
+ }
+
+ viewer_protocol_policy = "redirect-to-https"
+ }
+
+ restrictions {
+ geo_restriction {
+ restriction_type = "none"
+ }
+ }
+
+ viewer_certificate {
+ acm_certificate_arn = var.acm_certificate_arn
+ ssl_support_method = "sni-only"
+ }
+}
+
+provider "grafana" {
+ url = "https://${aws_grafana_workspace.this.endpoint}"
+ auth = aws_grafana_workspace_service_account_token.admin.key
+ retries = 100
+ retry_wait = 10
+ retry_status_codes = toset(["429","5xx"])
+}
+
+resource "grafana_data_source" "cloudwatch" {
+ type = "cloudwatch"
+ name = "cloudwatch"
+ access_mode = "proxy"
+
+ json_data_encoded = jsonencode({
+ defaultRegion = "${data.aws_region.this.region}"
+ authType = "default"
+ })
+}
+
+resource "grafana_data_source" "prometheus" {
+ type = "prometheus"
+ name = "prometheus"
+ url = aws_prometheus_workspace.this.prometheus_endpoint
+ access_mode = "proxy"
+ is_default = true
+
+ basic_auth_enabled = false
+ json_data_encoded = jsonencode({
+ sigV4AuthType = "default"
+ httpMethod = "POST"
+ sigV4Auth = true
+ sigV4Region = data.aws_region.this.region
+ })
+}
+
+data "kubernetes_service_v1" "loki-gateway" {
+
+ depends_on = [helm_release.coder-observe]
+
+ metadata {
+ name = "loki-gateway"
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ }
+}
+
+resource "grafana_data_source" "loki-gateway" {
+ type = "loki"
+ name = "loki"
+ access_mode = "proxy"
+ url = "http://${data.kubernetes_service_v1.loki-gateway.status[0].load_balancer[0].ingress[0].hostname}"
+}
+
+resource "grafana_data_source" "postgres" {
+ type = "postgres"
+ name = "postgres"
+ url = "${local.coder_db_host}:${local.coder_db_port}"
+ access_mode = "proxy"
+ is_default = false
+
+ username = var.grafana.db.username
+
+ json_data_encoded = jsonencode({
+ database = "coder"
+ sslmode = "require"
+ postgresVersion = "903" # Set your specific version: https://registry.terraform.io/providers/grafana/grafana/1.28.2/docs/resources/data_source#postgres_version-1
+ timescaledb = false # Toggle true if using TimescaleDB
+ })
+
+ secure_json_data_encoded = jsonencode({
+ password = var.grafana.db.password
+ })
+}
+
+resource "grafana_dashboard" "this" {
+ for_each = var.dashboards.config_maps
+ config_json = templatefile(each.value.local_path, each.value.args)
+}
+
+resource "grafana_organization_preferences" "this" {
+ home_dashboard_uid = grafana_dashboard.this["coder-dashboard-status"].uid
+ theme = "system"
+ timezone = "browser"
+ week_start = "monday"
+}
+
+resource "kubernetes_config_map_v1" "collector-config" {
+
+ metadata {
+ name = "collector-config"
+ namespace = var.namespace
+ labels = {}
+ annotations = {}
+ }
+ data = {
+ "config.river" = templatefile("${path.module}/collector-config.river", {
+ LOKI_ENDPOINT = "http://loki-gateway.${var.namespace}.svc/loki/api/v1/push"
+ AWS_PROMETHEUS_ENDPOINT = "${trimsuffix(aws_prometheus_workspace.this.prometheus_endpoint, "/")}/api/v1/remote_write"
+ AWS_PROMETHEUS_REGION = data.aws_region.this.region
+ })
+ }
+}
+
+resource "kubernetes_service_account_v1" "grafana-agent" {
+ metadata {
+ name = "grafana-agent"
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ annotations = {
+ "eks.amazonaws.com/role-arn" = module.oidc-role.role_arn
+ }
+ }
+ automount_service_account_token = true
+}
+
+resource "helm_release" "grafana-agent" {
+ name = "grafana-agent"
+ namespace = kubernetes_namespace_v1.this.metadata[0].name
+ chart = "grafana-agent"
+ repository = "https://grafana.github.io/helm-charts"
+ create_namespace = false
+ upgrade_install = true
+ skip_crds = false
+ wait = true
+ wait_for_jobs = true
+ version = "0.37.0"
+ timeout = 600
+
+ values = [yamlencode({
+ agent = {
+ mode = "flow"
+
+ configMap = {
+ name = kubernetes_config_map_v1.collector-config.metadata[0].name
+ key = "config.river"
+ create = false
+ }
+
+ clustering = {
+ enabled = true
+ }
+
+ extraArgs = [
+ "--disable-reporting=true"
+ ]
+
+ mounts = {
+ varlog = true
+ dockercontainers = true
+ }
+ }
+
+ controller = {
+ type = "daemonset"
+ podAnnotations = {
+ "prometheus.io/scheme" = "http"
+ "prometheus.io/scrape" = "true"
+ }
+ tolerations = var.daemonset_tolerations
+ nodeSelector = var.daemonset_node_selector
+ updateStrategy = {
+ type = "RollingUpdate"
+ rollingUpdate = {
+ maxSurge = 0
+ maxUnavailable = "100%"
+ }
+ }
+ }
+
+ serviceAccount = {
+ name = kubernetes_service_account_v1.grafana-agent.metadata[0].name
+ create = false
+ }
+
+ crds = {
+ create = false
+ }
+
+ withOTLPReceiver = false
+
+ discovery = <<-EOT
+ // Discover k8s nodes
+ discovery.kubernetes "nodes" {
+ role = "node"
+ }
+
+ // Discover k8s pods
+ discovery.kubernetes "pods" {
+ role = "pod"
+ selectors {
+ role = "pod"
+ field = "spec.nodeName=" + env("HOSTNAME")
+ }
+ }
+ EOT
+
+ extraBlocks = ""
+ podMetricsRelabelRules = ""
+ podLogsRelabelRules = ""
+
+ commonRelabellings = <<-EOF
+ rule {
+ source_labels = ["__meta_kubernetes_namespace"]
+ target_label = "namespace"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_name"]
+ target_label = "pod"
+ }
+ // coalesce the following labels and pick the first value; we'll use this to define the "job" label
+ rule {
+ source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_component", "app", "__meta_kubernetes_pod_container_name"]
+ separator = "/"
+ target_label = "__meta_app"
+ action = "replace"
+ regex = "^/*([^/]+?)(?:/.*)?$" // split by the delimiter if it exists, we only want the first one
+ replacement = "$1"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_namespace", "__meta_kubernetes_pod_label_app_kubernetes_io_name", "__meta_app"]
+ separator = "/"
+ target_label = "job"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_container_name"]
+ target_label = "container"
+ }
+ rule {
+ regex = "__meta_kubernetes_pod_label_(statefulset_kubernetes_io_pod_name|controller_revision_hash)"
+ action = "labeldrop"
+ }
+ rule {
+ regex = "pod_template_generation"
+ action = "labeldrop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_phase"]
+ regex = "Pending|Succeeded|Failed|Completed"
+ action = "drop"
+ }
+ rule {
+ source_labels = ["__meta_kubernetes_pod_node_name"]
+ action = "replace"
+ target_label = "node"
+ }
+ rule {
+ action = "labelmap"
+ regex = "__meta_kubernetes_pod_annotation_prometheus_io_param_(.+)"
+ replacement = "__param_$1"
+ }
+ EOF
+ })]
+}
+
+output "namespace" {
+ value = kubernetes_namespace_v1.this.metadata[0].name
+}
\ No newline at end of file
diff --git a/modules/k8s/bootstrap/monitoring/policy.tf b/modules/k8s/bootstrap/monitoring/policy.tf
new file mode 100644
index 0000000..41f01b5
--- /dev/null
+++ b/modules/k8s/bootstrap/monitoring/policy.tf
@@ -0,0 +1,31 @@
+data "aws_iam_policy_document" "this" {
+ statement {
+ sid = "LokiChunksBucket"
+ effect = "Allow"
+ actions = [
+ "s3:PutObject",
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:DeleteObject"
+ ]
+ resources = [
+ "arn:aws:s3:::${var.loki.s3.chunks_bucket}/*",
+ "arn:aws:s3:::${var.loki.s3.chunks_bucket}"
+ ]
+ }
+
+ statement {
+ sid = "LokiRulerBucket"
+ effect = "Allow"
+ actions = [
+ "s3:PutObject",
+ "s3:ListBucket",
+ "s3:GetObject",
+ "s3:DeleteObject"
+ ]
+ resources = [
+ "arn:aws:s3:::${var.loki.s3.ruler_bucket}/*",
+ "arn:aws:s3:::${var.loki.s3.ruler_bucket}"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/modules/k8s/objects/ec2nodeclass/main.tf b/modules/k8s/objects/ec2nodeclass/main.tf
index dd56139..2b3b6bc 100644
--- a/modules/k8s/objects/ec2nodeclass/main.tf
+++ b/modules/k8s/objects/ec2nodeclass/main.tf
@@ -23,6 +23,11 @@ variable "sg_selector_tags" {
default = {}
}
+variable "user_data" {
+ type = string
+ default = ""
+}
+
variable "block_device_mappings" {
type = list(object({
device_name = string
@@ -69,6 +74,7 @@ output "manifest" {
tags = var.sg_selector_tags
}]
blockDeviceMappings = local.block_device_mappings
+ userData = var.user_data
}
})
}
\ No newline at end of file