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.
This commit is contained in:
Suriya
2026-07-18 16:08:15 +05:30
parent 91dd240431
commit 0a8c3b0374
14 changed files with 303 additions and 270 deletions

View File

@@ -7,9 +7,9 @@ import nats
import os import os
async def purge_messages(): async def purge_messages():
nats_url = "nats://nats.workolik.com:4222" nats_url = os.getenv("NATS_URL", "nats://nats.workolik.com:4222")
nats_user = "admin" nats_user = os.getenv("NATS_USER", "admin")
nats_password = "package@321#" nats_password = os.getenv("NATS_PASSWORD", "")
try: try:
print(f"Connecting to NATS at {nats_url}...") print(f"Connecting to NATS at {nats_url}...")

View File

@@ -1,77 +1,54 @@
#!/usr/bin/env python3 #!/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 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 asyncio
import nats import nats
import os import os
import sys import sys
async def setup_jetstream(): WORKER_DOMAINS = [
"""Configure JetStream with two streams (DELIVERIES, RIDER) and per-subject consumers.""" {"stream": "ORDERS", "consumer": "orders-worker", "subjects": [
nats_url = os.getenv("NATS_URL", "nats://nats.workolik.com:4222") "api.v1.mob.orders.createorder",
nats_user = os.getenv("NATS_USER", "admin") ]},
nats_password = os.getenv("NATS_PASSWORD", "package@321#") {"stream": "DELIVERIES", "consumer": "deliveries-worker", "subjects": [
# Stream definitions
streams = {
"DELIVERIES": {
"subjects": [
"api.v1.deliveries.createdeliveries", "api.v1.deliveries.createdeliveries",
"api.v1.deliveries.updatedelivery", "api.v1.deliveries.updatedelivery",
"api.v2.deliveries.createdeliverylog", "api.v2.deliveries.createdeliverylog",
], ]},
}, {"stream": "CUSTOMERS", "consumer": "customers-worker", "retention": "work", "subjects": [
"RIDER": { "api.v1.mob.customers.login",
"subjects": [ "api.v1.mob.customers.create",
]},
{"stream": "RIDER", "consumer": "rider-logs-worker", "subjects": [
"api.v2.partners.createriderlog", "api.v2.partners.createriderlog",
"api.v2.partners.createbreaklog", "api.v2.partners.createbreaklog",
"api.v2.partners.updatebreaklog", "api.v2.partners.updatebreaklog",
], ]},
}, {"stream": "PRODUCTS", "consumer": "products-worker", "subjects": [
"ORDERS": {
"subjects": [
"api.v1.mob.orders.createorder",
],
},
"PRODUCTS": {
"subjects": [
"api.v1.web.products.create", "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 = { def durable_name(consumer: str, subject: str) -> str:
"DELIVERIES": { """Mirrors worker.py's durable-name derivation exactly."""
"api.v1.deliveries.createdeliveries": "deliveries_createdeliveries", suffix = subject.replace(".", "_").replace("*", "all").replace(">", "all")
"api.v1.deliveries.updatedelivery": "deliveries_updatedelivery", return f"{consumer}_{suffix}"
"api.v2.deliveries.createdeliverylog": "deliveries_createdeliverylog",
},
"RIDER": { async def setup_jetstream():
"api.v2.partners.createriderlog": "rider_createriderlog", nats_url = os.getenv("NATS_URL", "nats://nats.workolik.com:4222")
"api.v2.partners.createbreaklog": "rider_createbreaklog", nats_user = os.getenv("NATS_USER", "admin")
"api.v2.partners.updatebreaklog": "rider_updatebreaklog", nats_password = os.getenv("NATS_PASSWORD", "")
},
"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",
},
}
try: try:
print(f"Connecting to NATS at {nats_url}...") print(f"Connecting to NATS at {nats_url}...")
@@ -85,17 +62,16 @@ async def setup_jetstream():
js = nc.jetstream() js = nc.jetstream()
# Create / recreate streams # 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: try:
info = await js.stream_info(stream_name) info = await js.stream_info(stream_name)
print(f"⚠️ Stream '{stream_name}' already exists with subjects={info.config.subjects}, updating...") 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( await js.update_stream(
name=stream_name, name=stream_name,
subjects=cfg["subjects"], subjects=subjects,
storage="memory", storage="memory",
retention=retention_policy, retention=retention_policy,
max_age=24 * 60 * 60, max_age=24 * 60 * 60,
@@ -106,13 +82,9 @@ async def setup_jetstream():
except Exception as e: except Exception as e:
if "not found" in str(e).lower() or "404" in str(e).lower(): if "not found" in str(e).lower() or "404" in str(e).lower():
print(f"Creating stream '{stream_name}'...") 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( await js.add_stream(
name=stream_name, name=stream_name,
subjects=cfg["subjects"], subjects=subjects,
storage="memory", storage="memory",
retention=retention_policy, retention=retention_policy,
max_age=24 * 60 * 60, max_age=24 * 60 * 60,
@@ -123,10 +95,14 @@ async def setup_jetstream():
else: else:
print(f"⚠️ Could not inspect stream '{stream_name}': {e}") 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...") print("\nConfiguring consumers...")
for stream_name, subject_map in consumers.items(): for domain in WORKER_DOMAINS:
for subject, durable in subject_map.items(): stream_name = domain["stream"]
consumer = domain["consumer"]
for subject in domain["subjects"]:
durable = durable_name(consumer, subject)
try: try:
print(f"Creating consumer '{durable}' on stream '{stream_name}' for subject '{subject}'...") print(f"Creating consumer '{durable}' on stream '{stream_name}' for subject '{subject}'...")
await js.add_consumer( await js.add_consumer(
@@ -136,7 +112,7 @@ async def setup_jetstream():
ack_policy="explicit", ack_policy="explicit",
deliver_policy="all", deliver_policy="all",
max_deliver=5, max_deliver=5,
ack_wait=30, ack_wait=60,
) )
print(f"✅ Consumer '{durable}' created") print(f"✅ Consumer '{durable}' created")
except Exception as e: except Exception as e:
@@ -147,12 +123,12 @@ async def setup_jetstream():
print("\n✅ JetStream setup complete!") print("\n✅ JetStream setup complete!")
print(" Streams:") print(" Streams:")
for name, cfg in streams.items(): for domain in WORKER_DOMAINS:
print(f" - {name}: {', '.join(cfg['subjects'])}") print(f" - {domain['stream']}: {', '.join(domain['subjects'])}")
print(" Consumers:") print(" Consumers:")
for stream_name, subject_map in consumers.items(): for domain in WORKER_DOMAINS:
for subject, durable in subject_map.items(): for subject in domain["subjects"]:
print(f" - {durable}: stream={stream_name}, subject={subject}") print(f" - {durable_name(domain['consumer'], subject)}: stream={domain['stream']}, subject={subject}")
await nc.close() await nc.close()
sys.exit(0) sys.exit(0)
@@ -163,4 +139,3 @@ async def setup_jetstream():
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(setup_jetstream()) asyncio.run(setup_jetstream())

View File

@@ -51,16 +51,18 @@ def update_yaml_with_script(yaml_path, script_path, key_line_start):
print(f"Successfully updated {yaml_path}") print(f"Successfully updated {yaml_path}")
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Update Fiesta Gateway # Update Fiesta Gateway
update_yaml_with_script( update_yaml_with_script(
r'e:\nats\kubernetes\manifests\nearle\fiesta-gateway.yaml', os.path.join(REPO_ROOT, 'manifests', 'nearle', 'fiesta-gateway.yaml'),
r'e:\nats\kubernetes\conf\app.py', os.path.join(REPO_ROOT, 'conf', 'app.py'),
' app.py: |' ' app.py: |'
) )
# Update Worker Script # Update Worker Script
update_yaml_with_script( update_yaml_with_script(
r'e:\nats\kubernetes\manifests\core\worker-script.yaml', os.path.join(REPO_ROOT, 'manifests', 'core', 'worker-script.yaml'),
r'e:\nats\kubernetes\conf\worker.py', os.path.join(REPO_ROOT, 'conf', 'worker.py'),
' worker.py: |' ' worker.py: |'
) )

View File

@@ -1,93 +1,63 @@
#!/bin/bash #!/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 "=========================================="
echo "🔍 KUBERNETES DEPLOYMENT STATUS" echo "🔍 KUBERNETES DEPLOYMENT STATUS"
echo "==========================================" echo "=========================================="
echo "" echo ""
echo "📦 1. PODS STATUS (All Namespaces)" echo "📦 1. NODES"
echo "-----------------------------------"
kubectl get nodes -o wide
echo ""
echo "📦 2. PODS (All Namespaces)"
echo "-----------------------------------" echo "-----------------------------------"
kubectl get pods -A -o wide kubectl get pods -A -o wide
echo "" echo ""
echo "📦 2. NATS-BACKEND NAMESPACE - PODS" for ns in "${NAMESPACES[@]}"; do
echo "-----------------------------------" echo "=========================================="
kubectl get pods -n nats-backend -o wide echo "📦 Namespace: ${ns}"
echo "=========================================="
echo "-- Pods --"
kubectl get pods -n "${ns}" -o wide 2>/dev/null || echo " (namespace not found)"
echo "" echo ""
echo "🌐 3. SERVICES & LOAD BALANCER" echo "-- Services --"
echo "-----------------------------------" kubectl get svc -n "${ns}" -o wide 2>/dev/null
kubectl get svc -n nats-backend -o wide
echo "" echo ""
echo "⚖️ 4. LOAD BALANCER DETAILS" echo "-- StatefulSets / Deployments --"
echo "-----------------------------------" kubectl get statefulsets,deployments -n "${ns}" -o wide 2>/dev/null
kubectl get svc fastapi-lb -n nats-backend -o yaml | grep -A 10 "spec:"
echo "" echo ""
echo "🚀 5. K3S KUBERNETES LOAD BALANCER PODS (klipper-lb)" echo "-- HPA / PodDisruptionBudgets --"
echo "-----------------------------------" kubectl get hpa,pdb -n "${ns}" 2>/dev/null
kubectl get pods -n kube-system -l "svccontroller.k3s.cattle.io/svcname=fastapi-lb" -o wide
echo "" echo ""
echo "📊 6. DEPLOYMENTS & REPLICAS" echo "-- Recent Events (last 10) --"
echo "-----------------------------------" kubectl get events -n "${ns}" --sort-by='.lastTimestamp' 2>/dev/null | tail -10
kubectl get deployments -n nats-backend -o wide
echo "" echo ""
done
echo "📈 7. HORIZONTAL POD AUTOSCALER (HPA)" echo "🌍 GATEWAY / HTTPROUTE / INGRESS (All Namespaces)"
echo "-----------------------------------"
kubectl get hpa -n nats-backend
echo ""
echo "🔗 8. ENDPOINTS (Service Backends)"
echo "-----------------------------------"
kubectl get endpoints -n nats-backend
echo ""
echo "🌍 9. INGRESS/GATEWAY STATUS"
echo "-----------------------------------" echo "-----------------------------------"
kubectl get gateway -A 2>/dev/null || echo "No Gateway API resources found" 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" kubectl get ingress -A 2>/dev/null || echo "No Ingress resources found"
echo "" echo ""
echo "📋 10. RECENT EVENTS (Last 20)" echo "💾 RESOURCE USAGE (CPU/Memory)"
echo "-----------------------------------" echo "-----------------------------------"
kubectl get events -n nats-backend --sort-by='.lastTimestamp' | tail -20 kubectl top pods -A 2>/dev/null || echo "Metrics server not available"
echo ""
echo "💾 11. RESOURCE USAGE (CPU/Memory)"
echo "-----------------------------------"
kubectl top pods -n nats-backend 2>/dev/null || echo "Metrics server not available"
kubectl top nodes 2>/dev/null || echo "Metrics server not available" kubectl top nodes 2>/dev/null || echo "Metrics server not available"
echo "" 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 "=========================================="
echo "✅ Status Check Complete!" echo "✅ Status Check Complete!"
echo "==========================================" echo "=========================================="

View File

@@ -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

15
shfiles/deploy-ingress.sh Normal file
View File

@@ -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."

View File

@@ -9,16 +9,29 @@ NAMESPACE="nearle"
echo "🔎 Ensuring namespace '${NAMESPACE}' exists..." echo "🔎 Ensuring namespace '${NAMESPACE}' exists..."
kubectl apply -f manifests/nearle/nearle-namespace.yaml 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..." echo "🚀 Deploying Services..."
kubectl apply -f manifests/nearle/nearle-jupiter.yaml kubectl apply -f manifests/nearle/nearle-jupiter.yaml
kubectl apply -f manifests/nearle/nearle-titan.yaml kubectl apply -f manifests/nearle/nearle-titan.yaml
kubectl apply -f manifests/nearle/nearle-fiesta.yaml kubectl apply -f manifests/nearle/nearle-fiesta.yaml
kubectl apply -f manifests/nearle/nearle-ariane.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 ""
echo "✅ Nearle stack deployment applied." echo "✅ Nearle stack deployment applied."
echo "📋 Current status:" echo "📋 Current status:"
echo " kubectl get all -n ${NAMESPACE}" kubectl get all -n "${NAMESPACE}" || true

View File

@@ -1,10 +1,12 @@
#!/bin/bash #!/bin/bash
# Kubernetes Deployment Script for 3-Node Cluster # Deploy the full stack to Kubernetes (core, nearle, alaska, doormile, ingress)
# Usage: ./deploy.sh # 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 # Check if kubectl is available
if ! command -v kubectl &> /dev/null; then if ! command -v kubectl &> /dev/null; then
@@ -23,46 +25,29 @@ kubectl cluster-info || {
NODE_COUNT=$(kubectl get nodes --no-headers | wc -l) NODE_COUNT=$(kubectl get nodes --no-headers | wc -l)
echo "📊 Cluster has $NODE_COUNT node(s)" echo "📊 Cluster has $NODE_COUNT node(s)"
# Apply namespace echo ""
echo "📦 Creating namespace..." echo "===== Core stack (NATS workers) ====="
kubectl apply -f manifests/namespace.yaml bash shfiles/deploy-core-stack.sh
# 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 ""
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 ""
echo "📋 Check status:" echo "📋 Check status:"
echo " kubectl get pods -n nats-backend -o wide" echo " bash shfiles/check-k8s-status.sh"
echo " kubectl get nodes"
echo " kubectl get hpa -n nats-backend"
echo "" 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 ""

View File

@@ -1,9 +1,13 @@
#!/bin/bash #!/bin/bash
# Setup JetStream stream and consumer for NATS # Setup JetStream stream and consumer for NATS
set -euo pipefail
echo "🚀 Setting up NATS JetStream..." 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 # Check if Python is available
if ! command -v python3 &> /dev/null; then if ! command -v python3 &> /dev/null; then
@@ -17,11 +21,19 @@ if ! python3 -c "import nats" 2>/dev/null; then
pip3 install nats-py pip3 install nats-py
fi fi
# Set environment variables # Pull live credentials from the cluster's Secret instead of hardcoding them
export NATS_URL="nats://nats.workolik.com:4222" # here - avoids yet another copy of the password to keep in sync if rotated.
export NATS_USER="admin" if command -v kubectl &> /dev/null && kubectl get secret nats-credentials -n core &> /dev/null 2>&1; then
export NATS_PASSWORD="package@321#" 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 # Run the setup script
python3 scripts/setup_jetstream.py python3 scripts/setup_jetstream.py

4
terraform/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.terraform/
*.tfstate
*.tfstate.*
crash.log

22
terraform/.terraform.lock.hcl generated Normal file
View File

@@ -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",
]
}

View File

@@ -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.)

View File

@@ -1,20 +1,26 @@
# Create namespaces using Terraform # 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 { metadata {
name = "core" name = "core"
} }
} }
resource "kubernetes_namespace" "nearle" { resource "kubernetes_namespace_v1" "nearle" {
metadata { metadata {
name = "nearle" name = "nearle"
} }
} }
resource "kubernetes_namespace" "alaska" { resource "kubernetes_namespace_v1" "alaska" {
metadata { metadata {
name = "alaska" name = "alaska"
} }
} }
resource "kubernetes_namespace_v1" "doormile" {
metadata {
name = "doormile"
}
}

View File

@@ -1,45 +1,93 @@
# Manage the Nearle Stack (Jupiter, Atlantis, Fiesta) # Manage the Nearle Stack (Jupiter, Atlantis, Fiesta) plus the shared core
# This is the "All-in-one" Terraform control for your major services # 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 locals {
resource "kubernetes_manifest" "nearle_jupiter_sts" { # Splits a multi-document YAML file into a map keyed by
manifest = yamldecode(file("${path.module}/../manifests/nearle/jupiter-sts.yaml")) # "<kind>/<namespace>/<name>" (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
} }
resource "kubernetes_manifest" "nearle_jupiter_svc" { atlantis_docs = {
manifest = yamldecode(file("${path.module}/../manifests/nearle/jupiter-svc.yaml")) 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
} }
# 2. Atlantis Service fiesta_docs = {
resource "kubernetes_manifest" "nearle_atlantis_sts" { for doc in [
manifest = yamldecode(file("${path.module}/../manifests/nearle/atlantis-sts.yaml")) 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
} }
resource "kubernetes_manifest" "nearle_atlantis_svc" { core_workers_docs = {
manifest = yamldecode(file("${path.module}/../manifests/nearle/atlantis-svc.yaml")) 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
} }
# 3. Fiesta Service core_ingress_docs = {
resource "kubernetes_manifest" "nearle_fiesta_sts" { for doc in [
manifest = yamldecode(file("${path.module}/../manifests/nearle/fiesta-sts.yaml")) 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
} }
resource "kubernetes_manifest" "nearle_fiesta_svc" { traefik_middlewares_docs = {
manifest = yamldecode(file("${path.module}/../manifests/nearle/fiesta-svc.yaml")) 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
}
} }
# 4. Workers (CPU-heavy isolated nodes) # 1. Jupiter (StatefulSet + Service)
resource "kubernetes_manifest" "nearle_jupiter" {
for_each = local.jupiter_docs
manifest = each.value
}
# 2. Atlantis (StatefulSet + Service + HTTPRoute)
resource "kubernetes_manifest" "nearle_atlantis" {
for_each = local.atlantis_docs
manifest = each.value
}
# 3. Fiesta (StatefulSet + Service + HTTPRoute)
resource "kubernetes_manifest" "nearle_fiesta" {
for_each = local.fiesta_docs
manifest = each.value
}
# 4. Workers (CPU-heavy, isolated node pool - 5 StatefulSets)
resource "kubernetes_manifest" "core_workers" { resource "kubernetes_manifest" "core_workers" {
# This uses your existing workers.yaml file for_each = local.core_workers_docs
# Note: Since this file has MANY documents, I recommend splitting it the same way as above. manifest = each.value
manifest = yamldecode(file("${path.module}/../manifests/core/workers.yaml"))
} }
# 5. Ingress (Unified routing that replaces Docker-side Nginx) # 5. Ingress (queue.workolik.com, jupiter/fiesta/atlantis.nearle.app)
resource "kubernetes_manifest" "core_ingress" { 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" { resource "kubernetes_manifest" "traefik_middlewares" {
manifest = yamldecode(file("${path.module}/../manifests/core/traefik-middlewares.yaml")) for_each = local.traefik_middlewares_docs
manifest = each.value
} }