Cut over Vault and Gitea traffic from GCE/Legion to GKE

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
This commit is contained in:
Will Anderson
2026-05-04 23:46:20 -05:00
parent 010d81d6d9
commit 872a834989
6 changed files with 104 additions and 43 deletions
@@ -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-<hash>-vault-vault-helm-gke-8200-<hash>
# 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)
+8 -2
View File
@@ -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
+66 -30
View File
@@ -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
}
}
+1 -1
View File
@@ -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:
+17 -2
View File
@@ -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
}
+4 -8
View File
@@ -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"