feat(foundry): add sentinel to on-prem k3s cluster via Cloudflare Tunnel#119
Conversation
Parallel apps-foundry/ + manifests-foundry/ tree for cutover to the foundry k3s cluster. Existing EKS tree (apps/, manifests/) stays put until the on-prem stack is verified in production. - Terraform: cloudflare_zero_trust_tunnel_cloudflared (gr-foundry) + remote-managed ingress rules + 4 proxied CNAMEs for sentinel-v5, mapache, vault, argocd. random provider added. - cloudflared Deployment (2 replicas) as the tunnel connector; token bootstrapped manually from terraform output. - Sentinel/mapache/vault manifests copied verbatim from the EKS tree, diverging only in ingress.yaml (Traefik, no ALB annotations, no TLS block) and postgres.yaml (public gr-postgres.gauchoracing.com hostname instead of the internal EC2 hostname). - argocd-server-public Ingress adds argocd.gauchoracing.com to the foundry ArgoCD alongside foundry-infra's internal Ingress. - Cutover runbook embedded as comments in foundry.tf + apps-foundry/*.
Terraform plan:
|
| step | result |
|---|---|
| fmt | success |
| init | success |
| validate | success |
| plan | success |
plan output
module.postgres.random_password.postgres: Refreshing state... [id=none]
module.clickhouse.random_password.admin: Refreshing state... [id=none]
module.mqtt.random_password.mqtt_tcm26: Refreshing state... [id=none]
module.mqtt.random_password.mqtt_mapache: Refreshing state... [id=none]
module.origin_cert.tls_private_key.this: Refreshing state... [id=4c5b5e0a4a4f13723ba2aebe888a7cb50529fcc0]
data.cloudflare_zone.gauchoracing: Reading...
data.cloudflare_accounts.current: Reading...
module.mqtt.random_password.mqtt: Refreshing state... [id=none]
module.origin_cert.tls_cert_request.this: Refreshing state... [id=9398eb1d26e54eb59b18d476ced10810e80172e5]
module.origin_cert.cloudflare_origin_ca_certificate.this: Refreshing state... [id=307819530070461722629184968406649377122389744032]
data.cloudflare_zone.gauchoracing: Read complete after 0s [id=5ac5ae9c6086e4b55c5e1b21ca963d94]
module.eks.module.eks.module.kms.data.aws_partition.current[0]: Reading...
module.eks.module.eks.data.aws_iam_policy_document.node_assume_role_policy[0]: Reading...
module.postgres.data.aws_ami.al2023_arm64: Reading...
module.clickhouse.data.aws_ami.al2023_arm64: Reading...
module.mqtt.data.aws_ami.al2023_arm64: Reading...
module.eks.module.eks.data.aws_iam_policy_document.assume_role_policy[0]: Reading...
module.clickhouse.aws_ebs_volume.data: Refreshing state... [id=vol-0e312e8d71875ec89]
module.postgres.aws_ebs_volume.data: Refreshing state... [id=vol-02b203218b57629b4]
module.eks.module.eks.data.aws_iam_policy_document.assume_role_policy[0]: Read complete after 0s [id=2830595799]
module.eks.module.eks.data.aws_iam_policy_document.node_assume_role_policy[0]: Read complete after 0s [id=3518401652]
module.eks.module.eks.module.kms.data.aws_partition.current[0]: Read complete after 0s [id=aws]
module.eks.module.eks.data.aws_partition.current[0]: Reading...
module.eks.module.eks.data.aws_partition.current[0]: Read complete after 0s [id=aws]
module.eks.module.eks.data.aws_caller_identity.current[0]: Reading...
module.eks.module.eks.module.kms.data.aws_caller_identity.current[0]: Reading...
module.eks.module.eks.aws_cloudwatch_log_group.this[0]: Refreshing state... [id=/aws/eks/gr-prod/cluster]
module.eks.module.eks.module.kms.data.aws_caller_identity.current[0]: Read complete after 0s [id=211125506628]
module.vpc.module.vpc.aws_vpc.this[0]: Refreshing state... [id=vpc-06e13a97395396a3b]
cloudflare_ruleset.ssl_overrides: Refreshing state... [id=5a5ed8237d6f48418c172979ffe5da81]
module.eks.module.eks.data.aws_caller_identity.current[0]: Read complete after 0s [id=211125506628]
module.eks.module.eks.aws_iam_role.eks_auto[0]: Refreshing state... [id=gr-prod-eks-auto-20260601094833482500000004]
module.eks.module.eks.aws_iam_role.this[0]: Refreshing state... [id=gr-prod-cluster-20260601094833481300000002]
module.eks.module.eks.data.aws_iam_session_context.current[0]: Reading...
module.origin_cert.aws_acm_certificate.this: Refreshing state... [id=arn:aws:acm:us-west-2:211125506628:certificate/d10d5205-6d4b-4798-a152-293c69174660]
module.eks.module.eks.data.aws_iam_policy_document.custom[0]: Reading...
module.eks.module.eks.data.aws_iam_policy_document.custom[0]: Read complete after 0s [id=513122117]
module.eks.module.eks.aws_iam_role_policy_attachment.eks_auto["AmazonEC2ContainerRegistryPullOnly"]: Refreshing state... [id=gr-prod-eks-auto-20260601094833482500000004/arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPullOnly]
module.eks.module.eks.aws_iam_role_policy_attachment.eks_auto["AmazonEKSWorkerNodeMinimalPolicy"]: Refreshing state... [id=gr-prod-eks-auto-20260601094833482500000004/arn:aws:iam::aws:policy/AmazonEKSWorkerNodeMinimalPolicy]
module.eks.module.eks.data.aws_iam_session_context.current[0]: Read complete after 0s [id=arn:aws:sts::211125506628:assumed-role/github-actions-terraform/GitHubActions]
module.eks.module.eks.aws_iam_policy.custom[0]: Refreshing state... [id=arn:aws:iam::211125506628:policy/gr-prod-cluster-20260601094833480900000001]
module.eks.module.eks.aws_iam_role_policy_attachment.this["AmazonEKSClusterPolicy"]: Refreshing state... [id=gr-prod-cluster-20260601094833481300000002/arn:aws:iam::aws:policy/AmazonEKSClusterPolicy]
module.eks.module.eks.aws_iam_role_policy_attachment.this["AmazonEKSComputePolicy"]: Refreshing state... [id=gr-prod-cluster-20260601094833481300000002/arn:aws:iam::aws:policy/AmazonEKSComputePolicy]
module.eks.module.eks.aws_iam_role_policy_attachment.this["AmazonEKSLoadBalancingPolicy"]: Refreshing state... [id=gr-prod-cluster-20260601094833481300000002/arn:aws:iam::aws:policy/AmazonEKSLoadBalancingPolicy]
module.eks.module.eks.aws_iam_role_policy_attachment.this["AmazonEKSNetworkingPolicy"]: Refreshing state... [id=gr-prod-cluster-20260601094833481300000002/arn:aws:iam::aws:policy/AmazonEKSNetworkingPolicy]
module.eks.module.eks.aws_iam_role_policy_attachment.this["AmazonEKSBlockStoragePolicy"]: Refreshing state... [id=gr-prod-cluster-20260601094833481300000002/arn:aws:iam::aws:policy/AmazonEKSBlockStoragePolicy]
module.eks.module.eks.aws_iam_role_policy_attachment.custom[0]: Refreshing state... [id=gr-prod-cluster-20260601094833481300000002/arn:aws:iam::211125506628:policy/gr-prod-cluster-20260601094833480900000001]
module.eks.module.eks.module.kms.data.aws_iam_policy_document.this[0]: Reading...
module.eks.module.eks.module.kms.data.aws_iam_policy_document.this[0]: Read complete after 0s [id=922405470]
module.eks.module.eks.module.kms.aws_kms_key.this[0]: Refreshing state... [id=7768801a-b38a-4c26-8bc3-7bf6fe2aac86]
module.mqtt.data.aws_ami.al2023_arm64: Read complete after 1s [id=ami-0c0d650b2def3db6e]
module.clickhouse.data.aws_ami.al2023_arm64: Read complete after 1s [id=ami-0c0d650b2def3db6e]
module.postgres.data.aws_ami.al2023_arm64: Read complete after 1s [id=ami-0c0d650b2def3db6e]
module.eks.module.eks.module.kms.aws_kms_alias.this["cluster"]: Refreshing state... [id=alias/eks/gr-prod]
module.eks.module.eks.aws_iam_policy.cluster_encryption[0]: Refreshing state... [id=arn:aws:iam::211125506628:policy/gr-prod-cluster-ClusterEncryption20260601094855072000000006]
module.eks.module.eks.aws_iam_role_policy_attachment.cluster_encryption[0]: Refreshing state... [id=gr-prod-cluster-20260601094833481300000002/arn:aws:iam::211125506628:policy/gr-prod-cluster-ClusterEncryption20260601094855072000000006]
module.vpc.module.vpc.aws_default_route_table.default[0]: Refreshing state... [id=rtb-08f817bde5f65eb92]
module.vpc.module.vpc.aws_default_security_group.this[0]: Refreshing state... [id=sg-0a592b2169fd42df8]
module.vpc.module.vpc.aws_default_network_acl.this[0]: Refreshing state... [id=acl-0fd92b5b8eb95b2f9]
module.eks.module.eks.aws_security_group.cluster[0]: Refreshing state... [id=sg-0cac44db03a686436]
module.mqtt.aws_security_group.this: Refreshing state... [id=sg-0f5a2dc492283dafe]
module.clickhouse.aws_security_group.this: Refreshing state... [id=sg-0dee964416e7aaeb5]
module.postgres.aws_security_group.this: Refreshing state... [id=sg-08a5b2e02e0540520]
module.vpc.module.vpc.aws_route_table.public[0]: Refreshing state... [id=rtb-0815845194166b58b]
module.eks.module.eks.aws_security_group.node[0]: Refreshing state... [id=sg-0b19db83dbe18cbf1]
module.vpc.module.vpc.aws_subnet.public[0]: Refreshing state... [id=subnet-0264aaaa19faa70f5]
module.vpc.module.vpc.aws_subnet.public[1]: Refreshing state... [id=subnet-0182a0562244a6fac]
module.vpc.module.vpc.aws_subnet.public[2]: Refreshing state... [id=subnet-0a5a8299f6da9bc59]
module.vpc.module.vpc.aws_route_table.private[0]: Refreshing state... [id=rtb-0c18db918f54ad033]
module.vpc.module.vpc.aws_internet_gateway.this[0]: Refreshing state... [id=igw-0ace83040de106603]
module.vpc.module.vpc.aws_subnet.private[0]: Refreshing state... [id=subnet-09fbaccd0b3aaab85]
module.vpc.module.vpc.aws_subnet.private[1]: Refreshing state... [id=subnet-022e58c410c24d794]
module.vpc.module.vpc.aws_subnet.private[2]: Refreshing state... [id=subnet-06540460bfd7d06a2]
module.mqtt.aws_security_group_rule.ingress_cidr[0]: Refreshing state... [id=sgrule-1294033340]
module.clickhouse.aws_security_group_rule.ingress_cidr["8123"]: Refreshing state... [id=sgrule-467233960]
module.clickhouse.aws_security_group_rule.ingress_cidr["9000"]: Refreshing state... [id=sgrule-2026118668]
module.vpc.module.vpc.aws_eip.nat[0]: Refreshing state... [id=eipalloc-015b9b6ae09534761]
module.vpc.module.vpc.aws_route.public_internet_gateway[0]: Refreshing state... [id=r-rtb-0815845194166b58b1080289494]
module.eks.module.eks.aws_security_group_rule.node["ingress_self_coredns_tcp"]: Refreshing state... [id=sgrule-1577514230]
module.eks.module.eks.aws_security_group_rule.node["ingress_cluster_10251_webhook"]: Refreshing state... [id=sgrule-1337801498]
module.eks.module.eks.aws_security_group_rule.node["ingress_cluster_9443_webhook"]: Refreshing state... [id=sgrule-3115694346]
module.eks.module.eks.aws_security_group_rule.node["ingress_cluster_kubelet"]: Refreshing state... [id=sgrule-2079615841]
module.eks.module.eks.aws_security_group_rule.node["ingress_self_coredns_udp"]: Refreshing state... [id=sgrule-4200159001]
module.eks.module.eks.aws_security_group_rule.node["ingress_cluster_6443_webhook"]: Refreshing state... [id=sgrule-3460871904]
module.eks.module.eks.aws_security_group_rule.node["ingress_cluster_443"]: Refreshing state... [id=sgrule-500562133]
module.eks.module.eks.aws_security_group_rule.node["ingress_cluster_4443_webhook"]: Refreshing state... [id=sgrule-118149494]
module.eks.module.eks.aws_security_group_rule.node["ingress_cluster_8443_webhook"]: Refreshing state... [id=sgrule-3709110977]
module.eks.module.eks.aws_security_group_rule.node["egress_all"]: Refreshing state... [id=sgrule-4004824215]
module.eks.module.eks.aws_security_group_rule.node["ingress_nodes_ephemeral"]: Refreshing state... [id=sgrule-504933240]
module.postgres.aws_security_group_rule.ingress_cidr[0]: Refreshing state... [id=sgrule-3721507580]
module.eks.module.eks.aws_security_group_rule.cluster["ingress_nodes_443"]: Refreshing state... [id=sgrule-535925259]
module.vpc.module.vpc.aws_route_table_association.public[0]: Refreshing state... [id=rtbassoc-09913d53f1ff40442]
module.vpc.module.vpc.aws_route_table_association.public[2]: Refreshing state... [id=rtbassoc-01d455080d37c3108]
module.vpc.module.vpc.aws_route_table_association.public[1]: Refreshing state... [id=rtbassoc-041ffe3137ec2ff7a]
module.vpc.module.vpc.aws_route_table_association.private[2]: Refreshing state... [id=rtbassoc-0b736817e30c38444]
module.vpc.module.vpc.aws_route_table_association.private[0]: Refreshing state... [id=rtbassoc-0c836864d0eccc7fc]
module.vpc.module.vpc.aws_route_table_association.private[1]: Refreshing state... [id=rtbassoc-07ea61af9805b07cb]
module.vpc.module.vpc.aws_nat_gateway.this[0]: Refreshing state... [id=nat-019992cce709b8681]
module.clickhouse.aws_security_group_rule.ingress_sg["sg-0b19db83dbe18cbf1-8123"]: Refreshing state... [id=sgrule-4177740833]
module.clickhouse.aws_security_group_rule.ingress_sg["sg-0b19db83dbe18cbf1-9000"]: Refreshing state... [id=sgrule-2466486093]
module.mqtt.aws_security_group_rule.ingress_sg["sg-0b19db83dbe18cbf1"]: Refreshing state... [id=sgrule-1062873908]
module.postgres.aws_security_group_rule.ingress_sg["sg-0b19db83dbe18cbf1"]: Refreshing state... [id=sgrule-1130224857]
module.clickhouse.aws_instance.this: Refreshing state... [id=i-0f862cc6460b5d98a]
module.mqtt.aws_instance.this: Refreshing state... [id=i-0bf98528bc8e9dab0]
module.postgres.aws_instance.this: Refreshing state... [id=i-013aab40e28a0b6b7]
module.vpc.module.vpc.aws_route.private_nat_gateway[0]: Refreshing state... [id=r-rtb-0c18db918f54ad0331080289494]
module.eks.module.eks.aws_eks_cluster.this[0]: Refreshing state... [id=gr-prod]
module.eks.module.eks.time_sleep.this[0]: Refreshing state... [id=2026-06-01T10:46:10Z]
module.eks.module.eks.data.tls_certificate.this[0]: Reading...
module.eks.module.eks.aws_eks_access_entry.this["arn-aws-iam--211125506628-user-admin-cli"]: Refreshing state... [id=gr-prod:arn:aws:iam::211125506628:user/admin-cli]
module.eks.module.eks.aws_eks_access_entry.this["arn-aws-iam--211125506628-role-github-actions-terraform"]: Refreshing state... [id=gr-prod:arn:aws:iam::211125506628:role/github-actions-terraform]
module.eks.module.eks.aws_eks_access_policy_association.this["arn-aws-iam--211125506628-user-admin-cli_admin"]: Refreshing state... [id=gr-prod#arn:aws:iam::211125506628:user/admin-cli#arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy]
module.eks.module.eks.aws_eks_access_policy_association.this["arn-aws-iam--211125506628-role-github-actions-terraform_admin"]: Refreshing state... [id=gr-prod#arn:aws:iam::211125506628:role/github-actions-terraform#arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy]
module.eks.module.eks.data.tls_certificate.this[0]: Read complete after 1s [id=f97f646c2cd14cc0db0f757f0fccc96abbbe2af5]
module.eks.module.eks.aws_iam_openid_connect_provider.oidc_provider[0]: Refreshing state... [id=arn:aws:iam::211125506628:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/21512EE80634956C7C9D0B9647C70224]
module.argocd.helm_release.argocd: Refreshing state... [id=argocd]
module.mqtt.aws_eip.this[0]: Refreshing state... [id=eipalloc-000796d397533c6d8]
cloudflare_dns_record.gr_mqtt: Refreshing state... [id=53badd7d1ab83280dc57c671ee486f90]
module.clickhouse.aws_volume_attachment.data: Refreshing state... [id=vai-843739595]
module.clickhouse.aws_eip.this[0]: Refreshing state... [id=eipalloc-0970fced638b7d8d9]
module.postgres.aws_volume_attachment.data: Refreshing state... [id=vai-3584522434]
module.postgres.aws_eip.this[0]: Refreshing state... [id=eipalloc-06d6c59b1e0a49482]
cloudflare_dns_record.gr_clickhouse: Refreshing state... [id=d6f3d3c09db5c56703a7b107d6ac3f29]
cloudflare_dns_record.gr_postgres: Refreshing state... [id=a69d68353c27a536e84d7448af48e3f0]
data.cloudflare_accounts.current: Still reading... [00m10s elapsed]
data.cloudflare_accounts.current: Still reading... [00m20s elapsed]
data.cloudflare_accounts.current: Still reading... [00m30s elapsed]
data.cloudflare_accounts.current: Still reading... [00m40s elapsed]
data.cloudflare_accounts.current: Still reading... [00m50s elapsed]
data.cloudflare_accounts.current: Still reading... [01m00s elapsed]
data.cloudflare_accounts.current: Still reading... [01m10s elapsed]
data.cloudflare_accounts.current: Still reading... [01m20s elapsed]
data.cloudflare_accounts.current: Still reading... [01m30s elapsed]
data.cloudflare_accounts.current: Still reading... [01m40s elapsed]
data.cloudflare_accounts.current: Still reading... [01m50s elapsed]
data.cloudflare_accounts.current: Still reading... [02m00s elapsed]
data.cloudflare_accounts.current: Still reading... [02m10s elapsed]
data.cloudflare_accounts.current: Still reading... [02m20s elapsed]
data.cloudflare_accounts.current: Still reading... [02m30s elapsed]
data.cloudflare_accounts.current: Still reading... [02m40s elapsed]
data.cloudflare_accounts.current: Still reading... [02m50s elapsed]
data.cloudflare_accounts.current: Still reading... [03m00s elapsed]
data.cloudflare_accounts.current: Still reading... [03m10s elapsed]
data.cloudflare_accounts.current: Still reading... [03m20s elapsed]
data.cloudflare_accounts.current: Read complete after 3m28s
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
<= read (data resources)
Terraform will perform the following actions:
# data.cloudflare_zero_trust_tunnel_cloudflared_token.foundry will be read during apply
# (config refers to values not yet known)
<= data "cloudflare_zero_trust_tunnel_cloudflared_token" "foundry" {
+ account_id = "b5a9f67e75f4f8aba5be9500f6a098a5"
+ token = (sensitive value)
+ tunnel_id = (known after apply)
}
# cloudflare_zero_trust_tunnel_cloudflared.foundry will be created
+ resource "cloudflare_zero_trust_tunnel_cloudflared" "foundry" {
+ account_id = "b5a9f67e75f4f8aba5be9500f6a098a5"
+ account_tag = (known after apply)
+ config_src = "cloudflare"
+ connections = (known after apply)
+ conns_active_at = (known after apply)
+ conns_inactive_at = (known after apply)
+ created_at = (known after apply)
+ deleted_at = (known after apply)
+ id = (known after apply)
+ metadata = (known after apply)
+ name = "gr-foundry"
+ remote_config = (known after apply)
+ status = (known after apply)
+ tun_type = (known after apply)
+ tunnel_secret = (sensitive value)
}
# cloudflare_zero_trust_tunnel_cloudflared_config.foundry will be created
+ resource "cloudflare_zero_trust_tunnel_cloudflared_config" "foundry" {
+ account_id = "b5a9f67e75f4f8aba5be9500f6a098a5"
+ config = {
+ ingress = [
+ {
+ service = "http://traefik.kube-system.svc.cluster.local:80"
},
]
}
+ created_at = (known after apply)
+ id = (known after apply)
+ source = (known after apply)
+ tunnel_id = (known after apply)
+ version = (known after apply)
}
# random_bytes.foundry_tunnel_secret will be created
+ resource "random_bytes" "foundry_tunnel_secret" {
+ base64 = (sensitive value)
+ hex = (sensitive value)
+ length = 32
}
Plan: 3 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ foundry_tunnel_id = (known after apply)
+ foundry_tunnel_token = (sensitive value)
Warning: Resource Destruction Considerations
with cloudflare_zero_trust_tunnel_cloudflared_config.foundry,
on foundry.tf line 81, in resource "cloudflare_zero_trust_tunnel_cloudflared_config" "foundry":
81: resource "cloudflare_zero_trust_tunnel_cloudflared_config" "foundry" {
This resource cannot be destroyed from Terraform. If you create this
resource, it will be present in the API until manually deleted.
─────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.
Releasing state lock. This may take a few moments...
Drop mapache, vault, vault-k8s-operator, and argocd-server-ingress from apps-foundry + manifests-foundry. Remove them from the tunnel ingress rules and DNS records in foundry.tf. Only sentinel-v5.gauchoracing.com routes through the gr-foundry tunnel in this cutover; the other three hostnames stay on EKS until sentinel bakes. Adding a hostname later is one entry in the tunnel ingress list + one entry in local.foundry_hostnames + copying its manifest set from manifests/ to manifests-foundry/ with the same ingress + postgres adjustments used here. The initial commit's history is noisier this way (46 files added, 26 files removed) but avoids force-pushing during review. Squash-merge on merge if a clean history matters.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 699c3ef745
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # | ||
| # kubectl -n mapache create secret generic mapache-secrets \ | ||
| # --from-literal=DATABASE_PASSWORD="$(cd ../../infra/environments/prod && terraform output -raw postgres_password)" \ | ||
| # --from-literal=MQTT_PASSWORD="$(cd ../../infra/environments/prod && terraform output -raw mqtt_password)" \ |
There was a problem hiding this comment.
Populate MQTT_PASSWORD from mapache credential
When following this foundry bootstrap command, MQTT_PASSWORD gets the mqtt_password output, but the foundry mapache pods set MQTT_USER=mapache (for example in manifests-foundry/mapache/live.yaml and gr26.yaml). The NanoMQ module pairs the mapache user with mqtt_password_mapache, so these clients will fail MQTT auth after cutover unless this literal uses terraform output -raw mqtt_password_mapache or the manifests switch back to the gr26 user.
Useful? React with 👍 / 👎.
| # | ||
| # kubectl --context eks -n sentinel get secret sentinel-secrets -o yaml \ | ||
| # | grep -v '^\s*resourceVersion:\|^\s*uid:\|^\s*creationTimestamp:\|^\s*namespace:' \ | ||
| # | kubectl --context foundry apply -f - |
There was a problem hiding this comment.
Apply the copied secret into sentinel namespace
When using the documented EKS-to-foundry copy path, the previous pipe strips the source namespace: and this kubectl apply does not set -n sentinel, so the secret will be created in the foundry context's current namespace (usually default) instead of where the Sentinel deployments read sentinel-secrets. Add -n sentinel here (or preserve/set the namespace) so the cutover does not leave Sentinel pods without their required secret.
Useful? React with 👍 / 👎.
…scaling Replaces the explicit-hostname tunnel config + terraform-managed DNS records with a single catch-all tunnel rule that forwards everything to Traefik, and delegates DNS reconciliation to external-dns on the foundry cluster. Adding a new hostname is now one edit: the Ingress in the service's manifest set. No terraform change, no CF dashboard step. Same pattern scales to N clusters: each cluster runs its own tunnel + its own external-dns with a distinct txtOwnerId, managing its own disjoint hostname set in the shared gauchoracing.com zone. - foundry.tf: tunnel config -> single catch-all rule; drop cloudflare_dns_record.foundry + local.foundry_hostnames; update runbook comment for the new bootstrap sequence. - apps-foundry/external-dns.yaml: Helm-chart Application with txtOwnerId=gr-foundry, txtPrefix=fdry-. Reads the tunnel UUID from a manually-created ConfigMap (populated from `terraform output`) so the tunnel ID never has to live in git. --default-targets forces every record to CNAME the tunnel regardless of the Ingress's LB status field. - manifests-foundry/sentinel/ingress.yaml: add external-dns.alpha.kubernetes.io/cloudflare-proxied annotation so external-dns writes the record orange-cloud.
First slice of the EKS → foundry k3s migration. Ports sentinel (only) to the on-prem cluster via a Cloudflare Tunnel; mapache, vault, argocd stay on EKS until this bakes. Structured so a second on-prem cluster can drop in with the same pattern later.
Architecture
gr-foundry) with a single catch-all ingress rule → foundry's Traefik service. No hostname list in the tunnel config.txtOwnerId: gr-foundry) writes Cloudflare records for each foundry Ingress it sees.--default-targetsforces every record to CNAME the tunnel.manifests/sentinel/with only two file changes: Ingress switches to Traefik + addscloudflare-proxiedannotation; postgres ExternalName points at the publicgr-postgres.gauchoracing.comhostname.Adding another hostname (mapache/vault/argocd next slices) is one file — the Ingress in that service's manifest set. No terraform edit, no CF dashboard click.
Adding another cluster is the same shape: its own tunnel + its own external-dns with a distinct
txtOwnerId.What was intentionally skipped
Cutover runbook
terraform apply— creates the tunnel. No DNS records created here, so no 409 with EKS external-dns.kubectl apply -f kubernetes/bootstrap/root-foundry.yamlon the foundry ArgoCD.sentinel-secrets(command inapps-foundry/sentinel.yaml).sentinel-v5.gauchoracing.comrecord via CF dashboard (or scale EKS external-dns to 0). Foundry external-dns writes the new CNAME within its --interval (1m default). Traffic flips within CF TTL.Test plan
terraform planshows only the tunnel + config as adds, no DNS records, no drift on other resources.terraform output -raw foundry_tunnel_idreturns a UUID.root-foundry.yamlon foundry; ArgoCD reports Applications Missing/OutOfSync until secrets are populated, then Healthy.kubectl exec deploy/core -- nc -vz gr-postgres.gauchoracing.com 5432succeeds.kubectl port-forward -n sentinel svc/kerbecs 10310:10310+curl -H 'Host: sentinel-v5.gauchoracing.com' http://localhost:10310/api/core/keysreturns JWKS before flipping DNS.issclaim.