From 0a8c3b0374f39d9b304200b10aacfba9f4874236 Mon Sep 17 00:00:00 2001 From: Suriya Date: Sat, 18 Jul 2026 16:08:15 +0530 Subject: [PATCH] Fix deployment tooling: shell scripts and Terraform Terraform (validated with the real terraform CLI - was never actually run against this cluster, no state file existed): - Delete main.tf: it declared a duplicate kubernetes_namespace.core (also in namespaces.tf) and a duplicate provider "kubernetes" block (also in providers.tf), both hard errors that would fail `terraform plan` immediately. - Fix workloads.tf references to 6 files deleted in the manifest cleanup (jupiter-sts/svc, atlantis-sts/svc, fiesta-sts/svc) - now points at the canonical nearle-jupiter/atlantis/fiesta.yaml. - Fix every kubernetes_manifest resource: they fed multi-document YAML (multiple '---'-separated docs per file) straight into yamldecode(), which only parses a single document. Rewrote using a split-on-'---' + for_each pattern, confirmed safe first by checking separator counts exactly match document counts for every affected file (no embedded '---' inside any script/config content). - Add the doormile namespace; rename kubernetes_namespace to kubernetes_namespace_v1 (fixes a deprecation warning). - `terraform validate` now passes clean. Shell scripts: - deploy-nearle-stack.sh only applied 4 of the ~13 files in manifests/nearle/ - missing the ConfigMap/Secrets fiesta/jupiter/ titan/ariane need via envFrom, the fiesta gateway script ConfigMap, atlantis entirely, and the Gateway/ReferenceGrant/jupiter-cors-proxy resources. Now applies every file (verified by diffing the directory listing against the script). - Added deploy-doormile.sh and deploy-ingress.sh - nothing previously applied ingress-unified.yaml or traefik-middlewares.yaml at all. - Rewrote deploy.sh as an orchestrator calling all of the above in order (previously referenced a manifests/namespace.yaml layout that hasn't existed since before this repo's initial commit). - Rewrote check-k8s-status.sh to check the real namespaces (core/nearle/alaska/doormile/kubernetes-dashboard) instead of a 'nats-backend' namespace that never existed in this repo. - Fixed a `cd` bug in setup-jetstream.sh that made it change into shfiles/ and then look for scripts/setup_jetstream.py there (a child directory that doesn't exist) - it could never have found its own target file. Now pulls NATS credentials from the live nats-credentials Secret instead of a third hardcoded copy. Python scripts: - sync_manifests.py had hardcoded Windows paths (e:\nats\kubernetes\...) - replaced with paths relative to the script's own location so it actually runs here (or anywhere). Verified by running it. - setup_jetstream.py created durable consumers under different names than worker.py computes at runtime ({NATS_CONSUMER}_{subject}), so its max_deliver/ack_wait settings never actually reached the consumers workers bind to. Naming now derived with the same logic worker.py uses - verified all 10 derived names match workers.yaml exactly. - purge-old-messages.py had hardcoded NATS credentials with no env var override at all - fixed to match the pattern used everywhere else. --- scripts/purge-old-messages.py | 6 +- scripts/setup_jetstream.py | 141 ++++++++++++++------------------- scripts/sync_manifests.py | 10 ++- shfiles/check-k8s-status.sh | 98 ++++++++--------------- shfiles/deploy-doormile.sh | 15 ++++ shfiles/deploy-ingress.sh | 15 ++++ shfiles/deploy-nearle-stack.sh | 17 +++- shfiles/deploy.sh | 69 +++++++--------- shfiles/setup-jetstream.sh | 24 ++++-- terraform/.gitignore | 4 + terraform/.terraform.lock.hcl | 22 +++++ terraform/main.tf | 34 -------- terraform/namespaces.tf | 14 +++- terraform/workloads.tf | 104 +++++++++++++++++------- 14 files changed, 303 insertions(+), 270 deletions(-) create mode 100644 shfiles/deploy-doormile.sh create mode 100644 shfiles/deploy-ingress.sh create mode 100644 terraform/.gitignore create mode 100644 terraform/.terraform.lock.hcl delete mode 100644 terraform/main.tf diff --git a/scripts/purge-old-messages.py b/scripts/purge-old-messages.py index f955b53..fd24a1f 100644 --- a/scripts/purge-old-messages.py +++ b/scripts/purge-old-messages.py @@ -7,9 +7,9 @@ import nats import os async def purge_messages(): - nats_url = "nats://nats.workolik.com:4222" - nats_user = "admin" - nats_password = "package@321#" + nats_url = os.getenv("NATS_URL", "nats://nats.workolik.com:4222") + nats_user = os.getenv("NATS_USER", "admin") + nats_password = os.getenv("NATS_PASSWORD", "") try: print(f"Connecting to NATS at {nats_url}...") diff --git a/scripts/setup_jetstream.py b/scripts/setup_jetstream.py index 47afcf9..0f49a96 100644 --- a/scripts/setup_jetstream.py +++ b/scripts/setup_jetstream.py @@ -1,77 +1,54 @@ #!/usr/bin/env python3 """ -Setup JetStream stream and consumer for NATS +Setup JetStream streams and consumers for NATS Run this after NATS is deployed and running + +Durable consumer names are derived with the exact same logic worker.py uses +at runtime (NATS_CONSUMER + sanitized subject). WORKER_DOMAINS below must be +kept in sync with the NATS_STREAM / NATS_CONSUMER / FILTER_SUBJECT env vars +in manifests/core/workers.yaml - if they drift apart, the consumers created +here (with their max_deliver/ack_wait settings) will never be the ones the +workers actually bind to, and this script's config becomes a no-op. """ import asyncio import nats import os import sys +WORKER_DOMAINS = [ + {"stream": "ORDERS", "consumer": "orders-worker", "subjects": [ + "api.v1.mob.orders.createorder", + ]}, + {"stream": "DELIVERIES", "consumer": "deliveries-worker", "subjects": [ + "api.v1.deliveries.createdeliveries", + "api.v1.deliveries.updatedelivery", + "api.v2.deliveries.createdeliverylog", + ]}, + {"stream": "CUSTOMERS", "consumer": "customers-worker", "retention": "work", "subjects": [ + "api.v1.mob.customers.login", + "api.v1.mob.customers.create", + ]}, + {"stream": "RIDER", "consumer": "rider-logs-worker", "subjects": [ + "api.v2.partners.createriderlog", + "api.v2.partners.createbreaklog", + "api.v2.partners.updatebreaklog", + ]}, + {"stream": "PRODUCTS", "consumer": "products-worker", "subjects": [ + "api.v1.web.products.create", + ]}, +] + + +def durable_name(consumer: str, subject: str) -> str: + """Mirrors worker.py's durable-name derivation exactly.""" + suffix = subject.replace(".", "_").replace("*", "all").replace(">", "all") + return f"{consumer}_{suffix}" + + async def setup_jetstream(): - """Configure JetStream with two streams (DELIVERIES, RIDER) and per-subject consumers.""" nats_url = os.getenv("NATS_URL", "nats://nats.workolik.com:4222") nats_user = os.getenv("NATS_USER", "admin") - nats_password = os.getenv("NATS_PASSWORD", "package@321#") - - # Stream definitions - streams = { - "DELIVERIES": { - "subjects": [ - "api.v1.deliveries.createdeliveries", - "api.v1.deliveries.updatedelivery", - "api.v2.deliveries.createdeliverylog", - ], - }, - "RIDER": { - "subjects": [ - "api.v2.partners.createriderlog", - "api.v2.partners.createbreaklog", - "api.v2.partners.updatebreaklog", - ], - }, - "ORDERS": { - "subjects": [ - "api.v1.mob.orders.createorder", - ], - }, - "PRODUCTS": { - "subjects": [ - "api.v1.web.products.create", - ], - }, - "CUSTOMERS": { - "subjects": [ - "api.v1.mob.customers.login", - "api.v1.mob.customers.create", - ], - "retention": "work" # Special handling for Login queue: delete immediately after ack - }, - } - - # Per-subject durable consumers - consumers = { - "DELIVERIES": { - "api.v1.deliveries.createdeliveries": "deliveries_createdeliveries", - "api.v1.deliveries.updatedelivery": "deliveries_updatedelivery", - "api.v2.deliveries.createdeliverylog": "deliveries_createdeliverylog", - }, - "RIDER": { - "api.v2.partners.createriderlog": "rider_createriderlog", - "api.v2.partners.createbreaklog": "rider_createbreaklog", - "api.v2.partners.updatebreaklog": "rider_updatebreaklog", - }, - "ORDERS": { - "api.v1.mob.orders.createorder": "orders_createorder", - }, - "PRODUCTS": { - "api.v1.web.products.create": "products_create", - }, - "CUSTOMERS": { - "api.v1.mob.customers.login": "customers_login", - "api.v1.mob.customers.create": "customers_create", - }, - } + nats_password = os.getenv("NATS_PASSWORD", "") try: print(f"Connecting to NATS at {nats_url}...") @@ -85,17 +62,16 @@ async def setup_jetstream(): js = nc.jetstream() # Create / recreate streams - for stream_name, cfg in streams.items(): + for domain in WORKER_DOMAINS: + stream_name = domain["stream"] + subjects = domain["subjects"] + retention_policy = domain.get("retention", "limits") try: info = await js.stream_info(stream_name) print(f"⚠️ Stream '{stream_name}' already exists with subjects={info.config.subjects}, updating...") - - # Determine retention policy - retention_policy = cfg.get("retention", "limits") - await js.update_stream( name=stream_name, - subjects=cfg["subjects"], + subjects=subjects, storage="memory", retention=retention_policy, max_age=24 * 60 * 60, @@ -106,13 +82,9 @@ async def setup_jetstream(): except Exception as e: if "not found" in str(e).lower() or "404" in str(e).lower(): print(f"Creating stream '{stream_name}'...") - - # Determine retention policy (use 'limits' by default, 'work' for queues) - retention_policy = cfg.get("retention", "limits") - await js.add_stream( name=stream_name, - subjects=cfg["subjects"], + subjects=subjects, storage="memory", retention=retention_policy, max_age=24 * 60 * 60, @@ -123,10 +95,14 @@ async def setup_jetstream(): else: print(f"⚠️ Could not inspect stream '{stream_name}': {e}") - # Create durable consumers per subject + # Create durable consumers per subject - names match what worker.py + # computes at runtime, so max_deliver/ack_wait here actually apply. print("\nConfiguring consumers...") - for stream_name, subject_map in consumers.items(): - for subject, durable in subject_map.items(): + for domain in WORKER_DOMAINS: + stream_name = domain["stream"] + consumer = domain["consumer"] + for subject in domain["subjects"]: + durable = durable_name(consumer, subject) try: print(f"Creating consumer '{durable}' on stream '{stream_name}' for subject '{subject}'...") await js.add_consumer( @@ -136,7 +112,7 @@ async def setup_jetstream(): ack_policy="explicit", deliver_policy="all", max_deliver=5, - ack_wait=30, + ack_wait=60, ) print(f"✅ Consumer '{durable}' created") except Exception as e: @@ -147,12 +123,12 @@ async def setup_jetstream(): print("\n✅ JetStream setup complete!") print(" Streams:") - for name, cfg in streams.items(): - print(f" - {name}: {', '.join(cfg['subjects'])}") + for domain in WORKER_DOMAINS: + print(f" - {domain['stream']}: {', '.join(domain['subjects'])}") print(" Consumers:") - for stream_name, subject_map in consumers.items(): - for subject, durable in subject_map.items(): - print(f" - {durable}: stream={stream_name}, subject={subject}") + for domain in WORKER_DOMAINS: + for subject in domain["subjects"]: + print(f" - {durable_name(domain['consumer'], subject)}: stream={domain['stream']}, subject={subject}") await nc.close() sys.exit(0) @@ -163,4 +139,3 @@ async def setup_jetstream(): if __name__ == "__main__": asyncio.run(setup_jetstream()) - diff --git a/scripts/sync_manifests.py b/scripts/sync_manifests.py index b9d0bc9..f5c7034 100644 --- a/scripts/sync_manifests.py +++ b/scripts/sync_manifests.py @@ -51,16 +51,18 @@ def update_yaml_with_script(yaml_path, script_path, key_line_start): print(f"Successfully updated {yaml_path}") +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + # Update Fiesta Gateway update_yaml_with_script( - r'e:\nats\kubernetes\manifests\nearle\fiesta-gateway.yaml', - r'e:\nats\kubernetes\conf\app.py', + os.path.join(REPO_ROOT, 'manifests', 'nearle', 'fiesta-gateway.yaml'), + os.path.join(REPO_ROOT, 'conf', 'app.py'), ' app.py: |' ) # Update Worker Script update_yaml_with_script( - r'e:\nats\kubernetes\manifests\core\worker-script.yaml', - r'e:\nats\kubernetes\conf\worker.py', + os.path.join(REPO_ROOT, 'manifests', 'core', 'worker-script.yaml'), + os.path.join(REPO_ROOT, 'conf', 'worker.py'), ' worker.py: |' ) diff --git a/shfiles/check-k8s-status.sh b/shfiles/check-k8s-status.sh index d069cda..a48c35e 100644 --- a/shfiles/check-k8s-status.sh +++ b/shfiles/check-k8s-status.sh @@ -1,93 +1,63 @@ #!/bin/bash +# Status check across every namespace actually in use by this cluster. +# Usage: ./shfiles/check-k8s-status.sh + +NAMESPACES=(core nearle alaska doormile kubernetes-dashboard) echo "==========================================" echo "🔍 KUBERNETES DEPLOYMENT STATUS" echo "==========================================" echo "" -echo "📦 1. PODS STATUS (All Namespaces)" +echo "📦 1. NODES" +echo "-----------------------------------" +kubectl get nodes -o wide +echo "" + +echo "📦 2. PODS (All Namespaces)" echo "-----------------------------------" kubectl get pods -A -o wide echo "" -echo "📦 2. NATS-BACKEND NAMESPACE - PODS" -echo "-----------------------------------" -kubectl get pods -n nats-backend -o wide -echo "" +for ns in "${NAMESPACES[@]}"; do + echo "==========================================" + echo "📦 Namespace: ${ns}" + echo "==========================================" -echo "🌐 3. SERVICES & LOAD BALANCER" -echo "-----------------------------------" -kubectl get svc -n nats-backend -o wide -echo "" + echo "-- Pods --" + kubectl get pods -n "${ns}" -o wide 2>/dev/null || echo " (namespace not found)" + echo "" -echo "⚖️ 4. LOAD BALANCER DETAILS" -echo "-----------------------------------" -kubectl get svc fastapi-lb -n nats-backend -o yaml | grep -A 10 "spec:" -echo "" + echo "-- Services --" + kubectl get svc -n "${ns}" -o wide 2>/dev/null + echo "" -echo "🚀 5. K3S KUBERNETES LOAD BALANCER PODS (klipper-lb)" -echo "-----------------------------------" -kubectl get pods -n kube-system -l "svccontroller.k3s.cattle.io/svcname=fastapi-lb" -o wide -echo "" + echo "-- StatefulSets / Deployments --" + kubectl get statefulsets,deployments -n "${ns}" -o wide 2>/dev/null + echo "" -echo "📊 6. DEPLOYMENTS & REPLICAS" -echo "-----------------------------------" -kubectl get deployments -n nats-backend -o wide -echo "" + echo "-- HPA / PodDisruptionBudgets --" + kubectl get hpa,pdb -n "${ns}" 2>/dev/null + echo "" -echo "📈 7. HORIZONTAL POD AUTOSCALER (HPA)" -echo "-----------------------------------" -kubectl get hpa -n nats-backend -echo "" + echo "-- Recent Events (last 10) --" + kubectl get events -n "${ns}" --sort-by='.lastTimestamp' 2>/dev/null | tail -10 + echo "" +done -echo "🔗 8. ENDPOINTS (Service Backends)" -echo "-----------------------------------" -kubectl get endpoints -n nats-backend -echo "" - -echo "🌍 9. INGRESS/GATEWAY STATUS" +echo "🌍 GATEWAY / HTTPROUTE / INGRESS (All Namespaces)" echo "-----------------------------------" kubectl get gateway -A 2>/dev/null || echo "No Gateway API resources found" +kubectl get httproute -A 2>/dev/null || echo "No HTTPRoute resources found" kubectl get ingress -A 2>/dev/null || echo "No Ingress resources found" echo "" -echo "📋 10. RECENT EVENTS (Last 20)" +echo "💾 RESOURCE USAGE (CPU/Memory)" echo "-----------------------------------" -kubectl get events -n nats-backend --sort-by='.lastTimestamp' | tail -20 -echo "" - -echo "💾 11. RESOURCE USAGE (CPU/Memory)" -echo "-----------------------------------" -kubectl top pods -n nats-backend 2>/dev/null || echo "Metrics server not available" +kubectl top pods -A 2>/dev/null || echo "Metrics server not available" kubectl top nodes 2>/dev/null || echo "Metrics server not available" echo "" -echo "🔍 12. FASTAPI POD LOGS (Last 30 lines)" -echo "-----------------------------------" -kubectl logs -n nats-backend -l app=fastapi-backend --tail=30 2>/dev/null || echo "No FastAPI pods found" -echo "" - -echo "🔍 13. WORKER POD LOGS (Last 30 lines)" -echo "-----------------------------------" -kubectl logs -n nats-backend -l app=nats-worker --tail=30 2>/dev/null || echo "No worker pods found" -echo "" - -echo "🌐 14. NETWORK FLOW CHECK" -echo "-----------------------------------" -echo "Load Balancer External IP/Port:" -kubectl get svc fastapi-lb -n nats-backend -o jsonpath='{.status.loadBalancer.ingress[0].ip}:{.spec.ports[0].port}' 2>/dev/null || echo "Checking NodePort..." -kubectl get svc fastapi-lb -n nats-backend -o jsonpath='NodePort: {.spec.ports[0].nodePort}' 2>/dev/null -echo "" -echo "FastAPI Service ClusterIP:" -kubectl get svc fastapi-backend -n nats-backend -o jsonpath='{.spec.clusterIP}:{.spec.ports[0].port}' 2>/dev/null -echo "" - -echo "✅ 15. HEALTH CHECK" -echo "-----------------------------------" -kubectl run health-check --rm -i --restart=Never --image=curlimages/curl -- curl -s http://fastapi-backend.nats-backend:8000/health 2>/dev/null || echo "Health check failed" -echo "" - echo "==========================================" echo "✅ Status Check Complete!" echo "==========================================" - diff --git a/shfiles/deploy-doormile.sh b/shfiles/deploy-doormile.sh new file mode 100644 index 0000000..254edab --- /dev/null +++ b/shfiles/deploy-doormile.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Deploy "doormile" stack to Kubernetes +# Usage: ./deploy-doormile.sh + +set -euo pipefail + +NAMESPACE="doormile" + +echo "🚀 Deploying Doormile Stack..." +kubectl apply -f manifests/doormile/miletruth.yaml + +echo "" +echo "✅ Doormile deployment applied." +echo "📋 Current status:" +kubectl get all -n "${NAMESPACE}" || true diff --git a/shfiles/deploy-ingress.sh b/shfiles/deploy-ingress.sh new file mode 100644 index 0000000..826851a --- /dev/null +++ b/shfiles/deploy-ingress.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Deploy shared Ingress resources and Traefik CORS middlewares +# (queue.workolik.com, jupiter/fiesta/atlantis.nearle.app, alaska + nearle CORS) +# Usage: ./deploy-ingress.sh + +set -euo pipefail + +echo "🌐 Applying Ingress resources..." +kubectl apply -f manifests/core/ingress-unified.yaml + +echo "🎛️ Applying Traefik CORS middlewares..." +kubectl apply -f manifests/core/traefik-middlewares.yaml + +echo "" +echo "✅ Ingress & middlewares applied." diff --git a/shfiles/deploy-nearle-stack.sh b/shfiles/deploy-nearle-stack.sh index 26dc67a..92d16bb 100644 --- a/shfiles/deploy-nearle-stack.sh +++ b/shfiles/deploy-nearle-stack.sh @@ -9,16 +9,29 @@ NAMESPACE="nearle" echo "🔎 Ensuring namespace '${NAMESPACE}' exists..." kubectl apply -f manifests/nearle/nearle-namespace.yaml +echo "🔐 Applying config & secrets..." +kubectl apply -f manifests/nearle/nearle-config.yaml +kubectl apply -f manifests/nearle/nearle-secrets.yaml +kubectl apply -f manifests/nearle/nearle-app-secrets.yaml +echo "📜 Applying fiesta gateway script ConfigMap..." +kubectl apply -f manifests/nearle/fiesta-gateway.yaml + +echo "🌐 Applying Gateway API resources..." +kubectl apply -f manifests/nearle/nearle-gateway.yaml +kubectl apply -f manifests/nearle/nearle-reference-grant.yaml echo "🚀 Deploying Services..." kubectl apply -f manifests/nearle/nearle-jupiter.yaml kubectl apply -f manifests/nearle/nearle-titan.yaml kubectl apply -f manifests/nearle/nearle-fiesta.yaml kubectl apply -f manifests/nearle/nearle-ariane.yaml +kubectl apply -f manifests/nearle/nearle-atlantis.yaml + +echo "🧭 Deploying Jupiter CORS proxy..." +kubectl apply -f manifests/nearle/jupiter-cors-proxy.yaml echo "" echo "✅ Nearle stack deployment applied." echo "📋 Current status:" -echo " kubectl get all -n ${NAMESPACE}" - +kubectl get all -n "${NAMESPACE}" || true diff --git a/shfiles/deploy.sh b/shfiles/deploy.sh index e47194e..153f0de 100644 --- a/shfiles/deploy.sh +++ b/shfiles/deploy.sh @@ -1,10 +1,12 @@ #!/bin/bash -# Kubernetes Deployment Script for 3-Node Cluster -# Usage: ./deploy.sh +# Deploy the full stack to Kubernetes (core, nearle, alaska, doormile, ingress) +# Usage: ./shfiles/deploy.sh (run from the repo root, or anywhere - it cd's there itself) -set -e +set -euo pipefail -echo "🚀 Deploying to Kubernetes (3-Node Cluster)..." +cd "$(dirname "$0")/.." + +echo "🚀 Deploying full stack..." # Check if kubectl is available if ! command -v kubectl &> /dev/null; then @@ -23,46 +25,29 @@ kubectl cluster-info || { NODE_COUNT=$(kubectl get nodes --no-headers | wc -l) echo "📊 Cluster has $NODE_COUNT node(s)" -# Apply namespace -echo "📦 Creating namespace..." -kubectl apply -f manifests/namespace.yaml - -# Apply secrets -echo "🔐 Creating secrets..." -kubectl apply -f manifests/secrets.yaml - -# Apply FastAPI -echo "🐍 Deploying FastAPI backend..." -kubectl apply -f manifests/fastapi-deployment.yaml -kubectl apply -f manifests/fastapi-service.yaml -kubectl apply -f manifests/fastapi-hpa.yaml - -# Apply Workers -echo "👷 Deploying NATS workers..." -kubectl apply -f manifests/worker-deployment.yaml -kubectl apply -f manifests/worker-hpa.yaml - -# Apply Gateway (optional - only if Gateway API is installed) -read -p "Deploy Gateway API? (requires Gateway API controller) [y/N] " -n 1 -r -echo -if [[ $REPLY =~ ^[Yy]$ ]]; then - echo "🚪 Deploying Gateway API..." - kubectl apply -f manifests/gateway.yaml -fi +echo "" +echo "===== Core stack (NATS workers) =====" +bash shfiles/deploy-core-stack.sh echo "" -echo "✅ Deployment complete!" +echo "===== Nearle stack (jupiter, titan, fiesta, ariane, atlantis) =====" +bash shfiles/deploy-nearle-stack.sh + +echo "" +echo "===== Alaska stack (deliveries gateway + dashboard) =====" +bash shfiles/deploy-alaska.sh + +echo "" +echo "===== Doormile stack =====" +bash shfiles/deploy-doormile.sh + +echo "" +echo "===== Ingress & Traefik middlewares =====" +bash shfiles/deploy-ingress.sh + +echo "" +echo "✅ Full deployment complete!" echo "" echo "📋 Check status:" -echo " kubectl get pods -n nats-backend -o wide" -echo " kubectl get nodes" -echo " kubectl get hpa -n nats-backend" +echo " bash shfiles/check-k8s-status.sh" echo "" -echo "📊 View pod distribution across nodes:" -echo " kubectl get pods -n nats-backend -o wide | grep -E 'NAME|fastapi|worker'" -echo "" -echo "📝 View logs:" -echo " kubectl logs -f deployment/fastapi-backend -n nats-backend" -echo " kubectl logs -f deployment/nats-worker -n nats-backend" -echo "" - diff --git a/shfiles/setup-jetstream.sh b/shfiles/setup-jetstream.sh index 6589a50..9ef4452 100644 --- a/shfiles/setup-jetstream.sh +++ b/shfiles/setup-jetstream.sh @@ -1,9 +1,13 @@ #!/bin/bash # Setup JetStream stream and consumer for NATS +set -euo pipefail + echo "🚀 Setting up NATS JetStream..." -cd "$(dirname "$0")" +# scripts/ is a sibling of shfiles/, both under the repo root - cd there so +# the relative path below resolves regardless of where this is invoked from. +cd "$(dirname "$0")/.." # Check if Python is available if ! command -v python3 &> /dev/null; then @@ -17,11 +21,19 @@ if ! python3 -c "import nats" 2>/dev/null; then pip3 install nats-py fi -# Set environment variables -export NATS_URL="nats://nats.workolik.com:4222" -export NATS_USER="admin" -export NATS_PASSWORD="package@321#" +# Pull live credentials from the cluster's Secret instead of hardcoding them +# here - avoids yet another copy of the password to keep in sync if rotated. +if command -v kubectl &> /dev/null && kubectl get secret nats-credentials -n core &> /dev/null 2>&1; then + export NATS_USER + NATS_USER=$(kubectl get secret nats-credentials -n core -o jsonpath='{.data.username}' | base64 -d) + export NATS_PASSWORD + NATS_PASSWORD=$(kubectl get secret nats-credentials -n core -o jsonpath='{.data.password}' | base64 -d) +else + echo "⚠️ Could not read nats-credentials Secret from the cluster (kubectl not available or not connected)." + echo " Set NATS_USER / NATS_PASSWORD yourself before running this script." +fi + +export NATS_URL="${NATS_URL:-nats://66.116.226.161:4222}" # Run the setup script python3 scripts/setup_jetstream.py - diff --git a/terraform/.gitignore b/terraform/.gitignore new file mode 100644 index 0000000..2785e8e --- /dev/null +++ b/terraform/.gitignore @@ -0,0 +1,4 @@ +.terraform/ +*.tfstate +*.tfstate.* +crash.log diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 0000000..7cd1c2f --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,22 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/kubernetes" { + version = "3.2.1" + constraints = ">= 2.10.0" + hashes = [ + "h1:mcG69DdvaQvDNQzIo+SLVekECRLiNKavq5jbp/yieOU=", + "zh:067fe16a852d42e0f571712e36cb3e71855f917ea2041415e155f56ebc480d7f", + "zh:2815e174f8f0f032ea3a64f2196740ad000a39f88ae5646e7061bf15ed589f62", + "zh:2f94f6b689c59c43e596e724228f2861095d02c2a2ac2a257a4619667135ac75", + "zh:3e807310c84f11561b9ba06b978f03c46cfeca2e84ad0803d34d5d30a8a637cb", + "zh:5cba6f92202c60cac6898141356420709f5341b80ae4c360725cc647f86188ff", + "zh:72b841b6f0820d8f87c3d7c5a3611c35121ab9a4c1db4ea7a98b0319f209e474", + "zh:74770b892ee9b04829d92318d9e8ca96f8143b0c6c766e4141901908173fd01d", + "zh:7a723c8ebf9e218d0f7a0cfe6c0437f2b5eeb7ae015a14fad16e0f7fd9ef79ab", + "zh:a0f5073b2636a3894d4e9dd1b6853d5f324dd78728313bff79b842e5e9eca96f", + "zh:c13241cba993ef63a537beb6a1caf00e233bb045b50d197349530de0ee3276d5", + "zh:d52826f4b0227b7db99ea4a1d48f49a0bfb440563c92ebd2f8faec273c856c2d", + "zh:dc1cf5505a39a264a650b0830f74150ad02368787e5ead89e4007034f8f47831", + ] +} diff --git a/terraform/main.tf b/terraform/main.tf deleted file mode 100644 index 4f11560..0000000 --- a/terraform/main.tf +++ /dev/null @@ -1,34 +0,0 @@ -terraform { - required_providers { - kubernetes = { - source = "hashicorp/kubernetes" - version = ">= 2.0.0" - } - } -} - -provider "kubernetes" { - # This tells Terraform how to connect to your k3s cluster. - # Usually it looks for the file in ~/.kube/config. - config_path = "~/.kube/config" -} - -# Example: Manage a Kubernetes Namespace using Terraform -resource "kubernetes_namespace" "core" { - metadata { - name = "core" - labels = { - name = "core" - environment = "production" - } - } -} - -# Practical: Point Terraform to your updated .yaml files -# Instead of you running 'kubectl apply', Terraform will do it. - -resource "kubernetes_manifest" "worker_orders" { - manifest = yamldecode(file("${path.module}/../manifests/core/workers.yaml")) -} - -# (Add more resources here for nearle, alaska, etc.) diff --git a/terraform/namespaces.tf b/terraform/namespaces.tf index db1697c..16a7aeb 100644 --- a/terraform/namespaces.tf +++ b/terraform/namespaces.tf @@ -1,20 +1,26 @@ # Create namespaces using Terraform -# This makes sure the zones 'core', 'nearle', 'alaska' are always present and correctly labeled. +# This makes sure the zones 'core', 'nearle', 'alaska', 'doormile' are always present and correctly labeled. -resource "kubernetes_namespace" "core" { +resource "kubernetes_namespace_v1" "core" { metadata { name = "core" } } -resource "kubernetes_namespace" "nearle" { +resource "kubernetes_namespace_v1" "nearle" { metadata { name = "nearle" } } -resource "kubernetes_namespace" "alaska" { +resource "kubernetes_namespace_v1" "alaska" { metadata { name = "alaska" } } + +resource "kubernetes_namespace_v1" "doormile" { + metadata { + name = "doormile" + } +} diff --git a/terraform/workloads.tf b/terraform/workloads.tf index 51e2677..1605c22 100644 --- a/terraform/workloads.tf +++ b/terraform/workloads.tf @@ -1,45 +1,93 @@ -# Manage the Nearle Stack (Jupiter, Atlantis, Fiesta) -# This is the "All-in-one" Terraform control for your major services +# Manage the Nearle Stack (Jupiter, Atlantis, Fiesta) plus the shared core +# workers, Ingress and Traefik middlewares. +# +# NOTE: each of these manifest files contains MULTIPLE '---'-separated YAML +# documents (e.g. a StatefulSet + Service + HTTPRoute in one file). Terraform's +# built-in yamldecode() only parses a single document, so each file is split +# on the '---' separator first and turned into a for_each map, one +# kubernetes_manifest per document. This has been verified safe for these +# specific files (the number of '---' lines matches document-count minus one +# in each case - no embedded '---' inside any script/config content). -# 1. Jupiter Service -resource "kubernetes_manifest" "nearle_jupiter_sts" { - manifest = yamldecode(file("${path.module}/../manifests/nearle/jupiter-sts.yaml")) +locals { + # Splits a multi-document YAML file into a map keyed by + # "//" (namespace omitted for cluster-scoped + # resources), suitable for a kubernetes_manifest for_each. + jupiter_docs = { + for doc in [ + for chunk in split("\n---\n", file("${path.module}/../manifests/nearle/nearle-jupiter.yaml")) : + yamldecode(chunk) if trimspace(chunk) != "" + ] : "${doc.kind}/${lookup(doc.metadata, "namespace", "")}/${doc.metadata.name}" => doc + } + + atlantis_docs = { + for doc in [ + for chunk in split("\n---\n", file("${path.module}/../manifests/nearle/nearle-atlantis.yaml")) : + yamldecode(chunk) if trimspace(chunk) != "" + ] : "${doc.kind}/${lookup(doc.metadata, "namespace", "")}/${doc.metadata.name}" => doc + } + + fiesta_docs = { + for doc in [ + for chunk in split("\n---\n", file("${path.module}/../manifests/nearle/nearle-fiesta.yaml")) : + yamldecode(chunk) if trimspace(chunk) != "" + ] : "${doc.kind}/${lookup(doc.metadata, "namespace", "")}/${doc.metadata.name}" => doc + } + + core_workers_docs = { + for doc in [ + for chunk in split("\n---\n", file("${path.module}/../manifests/core/workers.yaml")) : + yamldecode(chunk) if trimspace(chunk) != "" + ] : "${doc.kind}/${lookup(doc.metadata, "namespace", "")}/${doc.metadata.name}" => doc + } + + core_ingress_docs = { + for doc in [ + for chunk in split("\n---\n", file("${path.module}/../manifests/core/ingress-unified.yaml")) : + yamldecode(chunk) if trimspace(chunk) != "" + ] : "${doc.kind}/${lookup(doc.metadata, "namespace", "")}/${doc.metadata.name}" => doc + } + + traefik_middlewares_docs = { + for doc in [ + for chunk in split("\n---\n", file("${path.module}/../manifests/core/traefik-middlewares.yaml")) : + yamldecode(chunk) if trimspace(chunk) != "" + ] : "${doc.kind}/${lookup(doc.metadata, "namespace", "")}/${doc.metadata.name}" => doc + } } -resource "kubernetes_manifest" "nearle_jupiter_svc" { - manifest = yamldecode(file("${path.module}/../manifests/nearle/jupiter-svc.yaml")) +# 1. Jupiter (StatefulSet + Service) +resource "kubernetes_manifest" "nearle_jupiter" { + for_each = local.jupiter_docs + manifest = each.value } -# 2. Atlantis Service -resource "kubernetes_manifest" "nearle_atlantis_sts" { - manifest = yamldecode(file("${path.module}/../manifests/nearle/atlantis-sts.yaml")) +# 2. Atlantis (StatefulSet + Service + HTTPRoute) +resource "kubernetes_manifest" "nearle_atlantis" { + for_each = local.atlantis_docs + manifest = each.value } -resource "kubernetes_manifest" "nearle_atlantis_svc" { - manifest = yamldecode(file("${path.module}/../manifests/nearle/atlantis-svc.yaml")) +# 3. Fiesta (StatefulSet + Service + HTTPRoute) +resource "kubernetes_manifest" "nearle_fiesta" { + for_each = local.fiesta_docs + manifest = each.value } -# 3. Fiesta Service -resource "kubernetes_manifest" "nearle_fiesta_sts" { - manifest = yamldecode(file("${path.module}/../manifests/nearle/fiesta-sts.yaml")) -} - -resource "kubernetes_manifest" "nearle_fiesta_svc" { - manifest = yamldecode(file("${path.module}/../manifests/nearle/fiesta-svc.yaml")) -} - -# 4. Workers (CPU-heavy isolated nodes) +# 4. Workers (CPU-heavy, isolated node pool - 5 StatefulSets) resource "kubernetes_manifest" "core_workers" { - # This uses your existing workers.yaml file - # Note: Since this file has MANY documents, I recommend splitting it the same way as above. - manifest = yamldecode(file("${path.module}/../manifests/core/workers.yaml")) + for_each = local.core_workers_docs + manifest = each.value } -# 5. Ingress (Unified routing that replaces Docker-side Nginx) +# 5. Ingress (queue.workolik.com, jupiter/fiesta/atlantis.nearle.app) resource "kubernetes_manifest" "core_ingress" { - manifest = yamldecode(file("${path.module}/../manifests/core/ingress-unified.yaml")) + for_each = local.core_ingress_docs + manifest = each.value } +# 6. Traefik CORS middlewares (alaska + nearle) resource "kubernetes_manifest" "traefik_middlewares" { - manifest = yamldecode(file("${path.module}/../manifests/core/traefik-middlewares.yaml")) + for_each = local.traefik_middlewares_docs + manifest = each.value }