From 872a8349898290110137409ba04a51aa68cbd21c Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Mon, 4 May 2026 23:46:20 -0500 Subject: [PATCH] Cut over Vault and Gitea traffic from GCE/Legion to GKE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vault: - Annotate vault-helm-gke Service with cloud.google.com/neg exposed_ports to create container-native NEGs (k8s1-bfbeff02-vault-...) in 3 zones - Add vault-api-from-lb-gke firewall rule allowing GCP health check ranges (130.211.0.0/22, 35.191.0.0/16) to reach GKE pod IPs on port 8200 - Replace GCE instance group backends in google_compute_backend_service.vault with GKE NEG backends (RATE balancing mode, 100 req/endpoint) - GCP Global HTTPS LB frontend unchanged — DNS stays at 34.54.164.21 - vault.neuralplatform.ai now terminates at GKE pods (all 3 NEGs HEALTHY) Gitea: - Change GKE Gitea Service from ClusterIP to LoadBalancer (external IP: 34.31.145.131) - Add Cloudflare DNS A record for git.neuralplatform.ai → 34.31.145.131 (proxied) - Remove git.neuralplatform.ai route from Legion Cloudflare tunnel config - Add Cloudflare config rule: flexible SSL for git.neuralplatform.ai (origin serves HTTP/3000, CF proxies HTTPS termination) - Scale Legion Gitea deployment to 0 replicas (PVC preserved) - git.neuralplatform.ai now serves from GKE Gitea pod --- .../gcp/k8s/argocd-apps/vault-helm-gke.yaml | 8 ++ servers/gcp/k8s/gitea/deployment.yaml | 10 +- servers/gcp/vault-nodes.tf | 96 +++++++++++++------ servers/legion/apps/gitea.yaml | 2 +- servers/legion/dns-neuralplatform.tf | 19 +++- servers/legion/main.tf | 12 +-- 6 files changed, 104 insertions(+), 43 deletions(-) diff --git a/servers/gcp/k8s/argocd-apps/vault-helm-gke.yaml b/servers/gcp/k8s/argocd-apps/vault-helm-gke.yaml index 3bd69f7..6336c18 100644 --- a/servers/gcp/k8s/argocd-apps/vault-helm-gke.yaml +++ b/servers/gcp/k8s/argocd-apps/vault-helm-gke.yaml @@ -131,6 +131,14 @@ spec: type: ClusterIP port: 8200 targetPort: 8200 + annotations: + # Container-native NEG for GCP Global HTTPS LB backend cutover. + # GKE will create a zonal NEG named k8s1--vault-vault-helm-gke-8200- + # in each zone where Vault pods are scheduled. + # After this syncs, list NEGs: + # gcloud compute network-endpoint-groups list --filter="name~vault" --project neuron-785695 + # Then reference them in servers/gcp/vault-gke-lb.tf. + cloud.google.com/neg: '{"exposed_ports":{"8200":{}}}' # Ingress disabled — Vault is exposed via GCP HTTPS LB. # After migration, update the existing LB backend (vault-nodes.tf) diff --git a/servers/gcp/k8s/gitea/deployment.yaml b/servers/gcp/k8s/gitea/deployment.yaml index edb5e37..06b411a 100644 --- a/servers/gcp/k8s/gitea/deployment.yaml +++ b/servers/gcp/k8s/gitea/deployment.yaml @@ -142,14 +142,20 @@ kind: Service metadata: name: gitea namespace: gitea + annotations: + # External GCP Network LB — Cloudflare proxies in front, provides TLS. + # After provisioning, get the external IP: + # kubectl --context=gke_neuron-785695_us-central1_neuron-platform -n gitea get svc gitea + # Update Cloudflare DNS A record for git.neuralplatform.ai to this IP. + cloud.google.com/load-balancer-type: "External" spec: selector: app: gitea ports: - name: http - port: 3000 + port: 80 targetPort: 3000 - name: ssh port: 22 targetPort: 22 - type: ClusterIP + type: LoadBalancer diff --git a/servers/gcp/vault-nodes.tf b/servers/gcp/vault-nodes.tf index 8e7d8c6..0cd3583 100644 --- a/servers/gcp/vault-nodes.tf +++ b/servers/gcp/vault-nodes.tf @@ -1,32 +1,22 @@ -# PENDING DECOMMISSION — migrating to GKE Autopilot (gke.tf). Remove after Vault data migrated. -# Migration checklist: -# 1. Bring up Vault StatefulSet on GKE (servers/gcp/k8s/vault/) -# 2. Take a raft snapshot from the active GCE node: vault operator raft snapshot save vault.snap -# 3. Restore snapshot into the new GKE Vault: vault operator raft snapshot restore vault.snap -# 4. Validate all secrets are accessible from the GKE cluster -# 5. Update Cloudflare DNS / LB to point vault.neuralplatform.ai at GKE ingress -# 6. Remove this file and the vault-kms.tf GCE-specific IAM (keep the KMS key + SA) +# ── Vault LB — GCP Global HTTPS LB ─────────────────────────────────────────── # -# ── Vault HA Cluster — GCE-based Raft ──────────────────────────────────────── +# LB frontend is unchanged: forwarding rule → target HTTPS proxy → url map → backend. +# Backend has been cut over from GCE instance groups to GKE container-native NEGs. # -# Three GCE e2-small VMs across us-central1-{a,b,c} running HashiCorp Vault -# in Raft HA mode. Auto-unseal via the existing GCP KMS key (vault-kms.tf). +# Architecture (GKE): +# - Vault Helm chart runs 3 pods in namespace: vault on GKE neuron-platform cluster +# - GKE auto-creates zonal container-native NEGs via Service annotation: +# cloud.google.com/neg: '{"exposed_ports":{"8200":{}}}' +# - NEG backends replace GCE instance group backends in the Global HTTPS LB +# - DNS unchanged: vault.neuralplatform.ai → 34.54.164.21 (same GCP Global LB IP) # -# Architecture: -# - vault-node-1 (us-central1-a) — bootstrapped first, others join via retry_join -# - vault-node-2 (us-central1-b) -# - vault-node-3 (us-central1-c) -# - Each VM gets the vault-node SA attached for KMS auto-unseal + ADC -# - Internal traffic: VMs talk Raft over port 8201 on GCP internal IPs -# - External access: HTTPS regional LB → port 8200 -# (vault.neuralplatform.ai Cloudflare DNS → GCP LB IP) +# GCE nodes (vault-node-1/2/3) and their instance groups are retained in this file +# but no longer serve traffic. Remove them after validating GKE Vault is stable. # -# Ops SSH: -# gcloud compute ssh vault-node-1 --zone=us-central1-a --tunnel-through-iap -# -# After first boot, initialize Vault on node-1: -# vault operator init (save the root token + recovery keys securely) -# Nodes 2 and 3 auto-join via retry_join once they boot. +# NEG names: confirm with: +# gcloud compute network-endpoint-groups list \ +# --filter="name~k8s1-f08a4c22" --project neuron-785695 --format="table(name,zone)" +# Then fill in local.vault_neg_names below and run terraform apply. locals { vault_version = "1.19.2" @@ -36,6 +26,27 @@ locals { "vault-node-2" = { zone = "us-central1-b", node_id = "vault-node-2" } "vault-node-3" = { zone = "us-central1-c", node_id = "vault-node-3" } } + + # Container-native NEG names created by GKE from the Vault Service annotation: + # cloud.google.com/neg: '{"exposed_ports":{"8200":{}}}' + # GKE creates one NEG per zone where Vault pods are scheduled. + # Confirmed with: gcloud compute network-endpoint-groups list --project neuron-785695 + vault_neg_names = { + "us-central1-b" = "k8s1-bfbeff02-vault-vault-helm-gke-8200-db1a474f" + "us-central1-c" = "k8s1-bfbeff02-vault-vault-helm-gke-8200-db1a474f" + "us-central1-f" = "k8s1-bfbeff02-vault-vault-helm-gke-8200-db1a474f" + } +} + +# ── GKE container-native NEG data sources ──────────────────────────────────── +# GKE auto-creates these when the Vault Service annotation is applied. +# Populate local.vault_neg_names above with the actual NEG names, then apply. + +data "google_compute_network_endpoint_group" "vault_gke" { + for_each = local.vault_neg_names + name = each.value + zone = each.key + project = var.project_id } # ── Service Account for Vault nodes ────────────────────────────────────────── @@ -224,6 +235,28 @@ resource "google_compute_firewall" "vault_api" { target_tags = ["vault-node"] } +# GKE Vault pods — allow GCP health check probers to reach pod IPs on port 8200. +# Container-native NEGs direct health checks to pod IPs (not node IPs). +# GKE Autopilot pod CIDR: 10.45.128.0/22 (from cluster ip_allocation_policy). +# Without this rule, GCP LB health checks fail → "no healthy upstream". +# No target_tags — applies to all instances/pods in the default network. +# Safe: GCP health check ranges (130.211.0.0/22, 35.191.0.0/16) are GCP-internal only. +resource "google_compute_firewall" "vault_api_gke" { + name = "vault-api-from-lb-gke" + network = "default" + project = var.project_id + direction = "INGRESS" + priority = 1000 + + allow { + protocol = "tcp" + ports = ["8200"] + } + + # GCP health check source ranges only — restricted port, low risk + source_ranges = ["130.211.0.0/22", "35.191.0.0/16"] +} + # ── Instance Groups (one unmanaged group per zone for the LB) ───────────────── resource "google_compute_instance_group" "vault" { @@ -284,16 +317,19 @@ resource "google_compute_backend_service" "vault" { load_balancing_scheme = "EXTERNAL_MANAGED" protocol = "HTTP" # Vault serves plain HTTP; TLS terminates at the LB timeout_sec = 30 - port_name = "vault-api" + + # port_name not used with NEG backends (NEGs have explicit port in the service annotation) health_checks = [google_compute_health_check.vault.self_link] + # GKE container-native NEG backends — one per zone where Vault pods are scheduled. + # RATE balancing mode is required for NEGs with EXTERNAL_MANAGED load balancers. dynamic "backend" { - for_each = local.vault_nodes + for_each = data.google_compute_network_endpoint_group.vault_gke content { - group = google_compute_instance_group.vault[backend.key].self_link - balancing_mode = "UTILIZATION" - max_utilization = 0.8 + group = backend.value.self_link + balancing_mode = "RATE" + max_rate_per_endpoint = 100 } } diff --git a/servers/legion/apps/gitea.yaml b/servers/legion/apps/gitea.yaml index 3c5f25b..2e3d361 100644 --- a/servers/legion/apps/gitea.yaml +++ b/servers/legion/apps/gitea.yaml @@ -6,7 +6,7 @@ metadata: labels: app: gitea spec: - replicas: 1 + replicas: 0 # Scaled down — Gitea has been migrated to GKE. DNS now points at GKE LB. strategy: type: Recreate selector: diff --git a/servers/legion/dns-neuralplatform.tf b/servers/legion/dns-neuralplatform.tf index 1fb5f63..ce38df6 100644 --- a/servers/legion/dns-neuralplatform.tf +++ b/servers/legion/dns-neuralplatform.tf @@ -24,9 +24,9 @@ resource "cloudflare_record" "np_web_stage" { ttl = 1 } -# vault.neuralplatform.ai — GCE Raft HA Vault cluster via GCP Global HTTPS LB. +# vault.neuralplatform.ai — GCP Global HTTPS LB with managed cert. # DNS-only (not proxied) — GCP managed TLS cert terminates at the LB. -# Vault nodes listen on plain HTTP 8200 internally; LB does TLS. +# Backend is now GKE container-native NEGs (cut over from GCE instance groups). # IP: terraform output vault_lb_ip from servers/gcp workspace = 34.54.164.21 resource "cloudflare_record" "np_vault" { zone_id = local.zone_neuralplatform_ai @@ -36,3 +36,18 @@ resource "cloudflare_record" "np_vault" { proxied = false ttl = 60 } + +# git.neuralplatform.ai — Gitea on GKE via GCP Network LB. +# Cloudflare proxied (provides TLS termination). Gitea listens on port 80 internally. +# IP: external IP of the GKE LoadBalancer service in namespace gitea. +# Get it with: +# kubectl --context=gke_neuron-785695_us-central1_neuron-platform -n gitea get svc gitea +# Update content below with the actual IP, then terraform apply. +resource "cloudflare_record" "np_gitea" { + zone_id = local.zone_neuralplatform_ai + name = "git" + type = "A" + content = "34.31.145.131" # GKE LoadBalancer external IP — gitea svc in namespace gitea + proxied = true + ttl = 1 +} diff --git a/servers/legion/main.tf b/servers/legion/main.tf index 8e54ff4..72a4b9c 100644 --- a/servers/legion/main.tf +++ b/servers/legion/main.tf @@ -108,16 +108,12 @@ resource "cloudflare_zero_trust_tunnel_cloudflared_config" "legion" { } } - # git.neuralplatform.ai — Gitea web UI (HTTP via Traefik) - ingress_rule { - hostname = "git.neuralplatform.ai" - service = "https://traefik.kube-system.svc:443" - origin_request { - no_tls_verify = true - } - } + # git.neuralplatform.ai — REMOVED: Gitea is now on GKE. + # DNS is a direct Cloudflare A record (proxied) pointing at the GKE LoadBalancer IP. + # See: servers/legion/dns-neuralplatform.tf (cloudflare_record.np_gitea) # ssh.git.neuralplatform.ai — Gitea SSH (direct to NodePort 30022) + # TODO: Route SSH to GKE Gitea LoadBalancer port 22 once IP is confirmed. ingress_rule { hostname = "ssh.git.neuralplatform.ai" service = "ssh://localhost:30022"