Initial commit

This commit is contained in:
2026-07-18 12:00:33 +05:30
commit caac8413e9
83 changed files with 10262 additions and 0 deletions

View File

@@ -0,0 +1,362 @@
apiVersion: v1
kind: Namespace
metadata:
name: alaska
labels:
name: alaska
environment: production
app.kubernetes.io/name: alaska
app.kubernetes.io/managed-by: manuals
---
apiVersion: v1
kind: ConfigMap
metadata:
name: alaska-config
namespace: alaska
labels:
app.kubernetes.io/name: alaska-config
app.kubernetes.io/part-of: alaska
data:
NATS_URL: "nats://nats.workolik.com:4222"
LOG_LEVEL: "info"
ALLOWED_ORIGINS: "http://localhost:3001,http://localhost:3000,https://queue.workolik.com,https://console.nearlexpress.com"
EXTERNAL_BASE_URL: "https://jupiter.nearle.app"
WORKER_CONCURRENCY: "10"
RETRY_ATTEMPTS: "5"
RETRY_DELAY_SECONDS: "5"
NATS_STREAM: "EVENTS"
NATS_SUBJECT: "api.>"
NATS_CONSUMER: "worker_consumer"
---
apiVersion: v1
kind: Secret
metadata:
name: nats-credentials
namespace: alaska
labels:
app.kubernetes.io/name: nats-credentials
app.kubernetes.io/part-of: alaska
type: Opaque
stringData:
username: admin
password: package@321#
---
apiVersion: v1
kind: Secret
metadata:
name: external-endpoint-secrets
namespace: alaska
labels:
app.kubernetes.io/name: external-endpoint-secrets
app.kubernetes.io/part-of: alaska
type: Opaque
stringData:
api_key: "" # Add your API key securely
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: deliveries
namespace: alaska
labels:
app.kubernetes.io/name: deliveries
app.kubernetes.io/instance: deliveries-primary
app.kubernetes.io/part-of: alaska
app.kubernetes.io/component: backend
spec:
serviceName: "deliveries" # Required for StatefulSet
replicas: 4
selector:
matchLabels:
app.kubernetes.io/name: deliveries
app.kubernetes.io/instance: deliveries-primary
template:
metadata:
labels:
app.kubernetes.io/name: deliveries
app.kubernetes.io/instance: deliveries-primary
app.kubernetes.io/part-of: alaska
app.kubernetes.io/component: backend
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8000"
prometheus.io/path: "/metrics"
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 2000
tolerations:
- key: dedicated
operator: Equal
value: apps
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/app
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- deliveries
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: deliveries
containers:
- name: deliveries
image: workolik360/alaska:v1.2.0
imagePullPolicy: Always
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
ports:
- containerPort: 8000
name: http
envFrom:
- configMapRef:
name: alaska-config
env:
- name: NATS_USER
valueFrom:
secretKeyRef:
name: nats-credentials
key: username
- name: NATS_PASSWORD
valueFrom:
secretKeyRef:
name: nats-credentials
key: password
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
name: deliveries-service
namespace: alaska
labels:
app.kubernetes.io/name: deliveries
app.kubernetes.io/part-of: alaska
spec:
type: ClusterIP
ports:
- port: 8000
targetPort: 8000
protocol: TCP
name: http
selector:
app.kubernetes.io/name: deliveries
app.kubernetes.io/instance: deliveries-primary
sessionAffinity: None
---
apiVersion: v1
kind: Service
metadata:
name: deliveries-loadbalancer
namespace: alaska
labels:
app.kubernetes.io/name: deliveries
app.kubernetes.io/part-of: alaska
annotations:
# Service health check port for LoadBalancer
# service.kubernetes.io/klipper-lb.healthcheck-port: "8201"
spec:
type: NodePort
externalTrafficPolicy: Cluster
selector:
app.kubernetes.io/name: deliveries
app.kubernetes.io/instance: deliveries-primary
ports:
- name: http
port: 8201 # external LB port
targetPort: 8000 # API container port
nodePort: 30662
protocol: TCP
- name: https
port: 8441 # optional HTTPS passthrough
targetPort: 8000
protocol: TCP
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: deliveries-pdb
namespace: alaska
labels:
app.kubernetes.io/name: deliveries
app.kubernetes.io/part-of: alaska
spec:
minAvailable: 50%
selector:
matchLabels:
app.kubernetes.io/name: deliveries
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: deliveries-hpa
namespace: alaska
labels:
app.kubernetes.io/name: deliveries
app.kubernetes.io/part-of: alaska
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: StatefulSet
name: deliveries
minReplicas: 4
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
namespace: alaska
labels:
app.kubernetes.io/name: gateway
app.kubernetes.io/part-of: alaska
spec:
gatewayClassName: standard
listeners:
- name: http
protocol: HTTP
port: 8201
allowedRoutes:
namespaces:
from: All
- name: https
protocol: HTTPS
port: 8441
allowedRoutes:
namespaces:
from: All
tls:
mode: Terminate
certificateRefs:
- name: deliveries-tls-cert
- name: nearle-http
protocol: HTTP
port: 8202
allowedRoutes:
namespaces:
from: All
- name: nearle-https
protocol: HTTPS
port: 8442
allowedRoutes:
namespaces:
from: All
tls:
mode: Terminate
certificateRefs:
- name: nearle-tls-cert
namespace: nearle # Must copy secret to alaska or use ReferenceGrant. For now assume secret is in Alaska or copied.
# Actually, simpler: Use 'nearle-tls-cert' but putting secret in alaska namespace is required for cross-namespace ref usually unless ReferenceGrant used.
# Let's keep it simple: We will COPY the secret to 'alaska' namespace.
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: deliveries-route
namespace: alaska
labels:
app.kubernetes.io/name: deliveries-route
app.kubernetes.io/part-of: alaska
spec:
parentRefs:
- name: gateway
namespace: alaska
hostnames:
- "queue.workolik.com"
rules:
- matches:
- path:
type: PathPrefix
value: /live
backendRefs:
- name: deliveries-service
port: 8000
weight: 100
backendRefs:
- name: deliveries-service
port: 8000
weight: 100
- matches:
- path:
type: PathPrefix
value: /live/api/v1/mob/orders
backendRefs:
- name: fiesta
namespace: nearle
port: 80
weight: 100
- matches:
- path:
type: PathPrefix
value: /live/api/v1/web/products
backendRefs:
- name: fiesta
namespace: nearle
port: 80
weight: 100
- matches:
- path:
type: PathPrefix
value: /health
backendRefs:
- name: deliveries-service
port: 8000
weight: 100

View File

@@ -0,0 +1,328 @@
# Kubernetes Dashboard - Official Web UI
# Deploy with: kubectl apply -f manifests/alaska/k8s-dashboard.yaml
apiVersion: v1
kind: Namespace
metadata:
name: kubernetes-dashboard
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
---
apiVersion: v1
kind: Secret
metadata:
name: admin-user
namespace: kubernetes-dashboard
annotations:
kubernetes.io/service-account.name: "admin-user"
type: kubernetes.io/service-account-token
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubernetes-dashboard-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubernetes-dashboard
namespace: kubernetes-dashboard
labels:
k8s-app: kubernetes-dashboard
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: kubernetes-dashboard
template:
metadata:
labels:
k8s-app: kubernetes-dashboard
spec:
containers:
- name: kubernetes-dashboard
image: kubernetesui/dashboard:v2.7.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8443
protocol: TCP
args:
- --auto-generate-certificates
- --namespace=kubernetes-dashboard
- --enable-skip-login
- --enable-insecure-login
- --insecure-port=9090
volumeMounts:
- name: kubernetes-dashboard-certs
mountPath: /certs
- name: tmp-volume
mountPath: /tmp
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 1001
runAsGroup: 2001
livenessProbe:
httpGet:
scheme: HTTPS
path: /
port: 8443
initialDelaySeconds: 30
timeoutSeconds: 30
periodSeconds: 10
failureThreshold: 3
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
volumes:
- name: kubernetes-dashboard-certs
secret:
secretName: kubernetes-dashboard-certs
- name: tmp-volume
emptyDir: {}
serviceAccountName: kubernetes-dashboard
nodeSelector:
"kubernetes.io/os": linux
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
---
apiVersion: v1
kind: Service
metadata:
name: kubernetes-dashboard
namespace: kubernetes-dashboard
labels:
k8s-app: kubernetes-dashboard
spec:
type: ClusterIP
ports:
- port: 443
targetPort: 8443
protocol: TCP
name: https
- port: 9090
targetPort: 9090
protocol: TCP
name: http
selector:
k8s-app: kubernetes-dashboard
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kubernetes-dashboard
rules:
- apiGroups: [""]
resources: ["*"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["*"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["*"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubernetes-dashboard
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: kubernetes-dashboard-secrets
namespace: kubernetes-dashboard
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "update", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: kubernetes-dashboard-secrets
namespace: kubernetes-dashboard
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: kubernetes-dashboard-secrets
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kubernetes-dashboard
---
apiVersion: v1
kind: Secret
metadata:
name: kubernetes-dashboard-certs
namespace: kubernetes-dashboard
type: Opaque
---
apiVersion: v1
kind: Secret
metadata:
name: kubernetes-dashboard-csrf
namespace: kubernetes-dashboard
type: Opaque
data:
csrf: "" # Will be auto-generated by dashboard
---
apiVersion: v1
kind: ConfigMap
metadata:
name: dashboard-proxy-config
namespace: kubernetes-dashboard
data:
nginx.conf: |
events {
worker_connections 1024;
}
http {
upstream k8s_dashboard {
server kubernetes-dashboard:443;
}
server {
listen 8083;
server_name _;
location / {
proxy_pass https://k8s_dashboard;
proxy_ssl_verify off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
}
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: dashboard-proxy
namespace: kubernetes-dashboard
labels:
app.kubernetes.io/name: dashboard-proxy
app.kubernetes.io/part-of: kubernetes-dashboard
spec:
serviceName: "dashboard-proxy"
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: dashboard-proxy
template:
metadata:
labels:
app.kubernetes.io/name: dashboard-proxy
app.kubernetes.io/part-of: kubernetes-dashboard
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 8083
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
volumes:
- name: nginx-config
configMap:
name: dashboard-proxy-config
---
apiVersion: v1
kind: Service
metadata:
name: dashboard-proxy
namespace: kubernetes-dashboard
labels:
app.kubernetes.io/name: dashboard-proxy
spec:
type: NodePort
selector:
app.kubernetes.io/name: dashboard-proxy
ports:
- port: 8083
targetPort: 8083
nodePort: 30826
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: dashboard-loadbalancer
namespace: kubernetes-dashboard
labels:
app.kubernetes.io/name: kubernetes-dashboard
app.kubernetes.io/component: loadbalancer
spec:
type: NodePort
selector:
k8s-app: kubernetes-dashboard
ports:
- name: http
port: 9090
targetPort: 9090 # Dashboard HTTP port
nodePort: 30827 # Fixed NodePort for nginx proxy
protocol: TCP

View File

@@ -0,0 +1,19 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: core-config
namespace: core
labels:
app.kubernetes.io/name: core-config
app.kubernetes.io/part-of: core
data:
NATS_URL: "nats://66.116.226.161:4222"
LOG_LEVEL: "info"
ALLOWED_ORIGINS: "http://localhost:3001,http://localhost:3000,https://queue.workolik.com,https://console.nearlexpress.com"
EXTERNAL_BASE_URL: "https://jupiter.nearle.app"
WORKER_CONCURRENCY: "10"
RETRY_ATTEMPTS: "5"
RETRY_DELAY_SECONDS: "5"
NATS_STREAM: "EVENTS"
NATS_SUBJECT: "api.>"
NATS_CONSUMER: "worker_consumer"

View File

@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: core
labels:
name: core

View File

@@ -0,0 +1,25 @@
apiVersion: v1
kind: Secret
metadata:
name: nats-credentials
namespace: core
labels:
app.kubernetes.io/name: nats-credentials
app.kubernetes.io/part-of: core
type: Opaque
stringData:
username: admin
password: package@321#
---
apiVersion: v1
kind: Secret
metadata:
name: external-endpoint-secrets
namespace: core
labels:
app.kubernetes.io/name: external-endpoint-secrets
app.kubernetes.io/part-of: core
type: Opaque
stringData:
api_key: "" # Add your API key securely

View File

@@ -0,0 +1,76 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: queue-ingress
namespace: alaska
annotations:
traefik.ingress.kubernetes.io/router.middlewares: alaska-common-cors@kubernetescrd
traefik.ingress.kubernetes.io/router.tls: "true"
# Cert resolver if you have it configured globally in Traefik (usually k3s-provided)
# traefik.ingress.kubernetes.io/router.tls.certresolver: "letsencrypt"
spec:
rules:
- host: queue.workolik.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: deliveries-service
port:
number: 8000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nearle-ingress
namespace: nearle
annotations:
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
rules:
- host: jupiter.nearle.app
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: jupiter-cors-proxy
port:
number: 80
- host: fiesta.nearle.app
http:
paths:
- path: /live/api/v1/mob/orders/createorder
pathType: Prefix
backend:
service:
name: fiesta
port:
number: 8000
- path: /live/api/v1/web/products/create
pathType: Prefix
backend:
service:
name: fiesta
port:
number: 8000
- path: /
pathType: Prefix
backend:
service:
name: fiesta
port:
number: 80
- host: atlantis.nearle.app
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: atlantis
port:
number: 80

View File

@@ -0,0 +1,12 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: core
resources:
- core-namespace.yaml
- core-secrets.yaml
- core-config.yaml
- worker-script.yaml
- workers.yaml
- worker-pdb.yaml

View File

@@ -0,0 +1,41 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: common-cors
namespace: alaska
spec:
headers:
accessControlAllowMethods:
- GET
- POST
- PUT
- PATCH
- DELETE
- OPTIONS
accessControlAllowOriginList:
- "*"
accessControlAllowHeaders:
- "*"
accessControlMaxAge: 600
addVaryHeader: true
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: common-cors
namespace: nearle
spec:
headers:
accessControlAllowMethods:
- GET
- POST
- PUT
- PATCH
- DELETE
- OPTIONS
accessControlAllowOriginList:
- "*"
accessControlAllowHeaders:
- "*"
accessControlMaxAge: 600
addVaryHeader: true

View File

@@ -0,0 +1,13 @@
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: worker-pdb
namespace: core
labels:
app.kubernetes.io/name: worker
app.kubernetes.io/part-of: core
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: worker

View File

@@ -0,0 +1,261 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: worker-script
namespace: core
data:
worker.py: |
#!/usr/bin/env python3
"""
NATS Worker - "New Idea" Implementation
- Single Domain per Worker (Stream + Consumer)
- Persistent HTTP Session (Connection Pooling)
- JetStream Native Retries (NAK)
- No internal retry loops
"""
import os
import json
import asyncio
import aiohttp
import nats
from nats.errors import TimeoutError as NatsTimeoutError
from prometheus_client import Counter, Histogram, Gauge, start_http_server
import signal
import sys
from typing import Any, Dict, List, Union, Tuple
# Prometheus metrics
messages_processed = Counter('worker_messages_processed_total', 'Total messages processed', ['status', 'endpoint'])
message_duration = Histogram('worker_message_duration_seconds', 'Message processing duration', ['endpoint'])
messages_in_flight = Gauge('worker_messages_in_flight', 'Messages currently being processed')
# Configuration
NATS_URL = os.getenv("NATS_URL", "nats://nats-server:4222")
NATS_USER = os.getenv("NATS_USER", "admin")
NATS_PASSWORD = os.getenv("NATS_PASSWORD", "package@321#")
# Domain Configuration (Env Vars from Deployment)
NATS_STREAM = os.getenv("NATS_STREAM", "ORDERS")
NATS_CONSUMER = os.getenv("NATS_CONSUMER", "orders-worker")
# Supports comma-separated subjects: "api.v1.orders.*,api.v2.orders.*"
FILTER_SUBJECTS = os.getenv("FILTER_SUBJECT", "api.v1.mob.orders.*").split(",")
WORKER_CONCURRENCY = int(os.getenv("WORKER_CONCURRENCY", "10"))
BASE_URL = os.getenv("EXTERNAL_BASE_URL", "https://jupiter.nearle.app")
# Endpoint mapping is still useful for constructing the target URL
ENDPOINT_MAPPING = {
"/live/api/v1/deliveries/createdeliveries": f"{BASE_URL}/live/api/v1/deliveries/createdeliveries",
"/live/api/v1/deliveries/updatedelivery": f"{BASE_URL}/live/api/v1/deliveries/updatedelivery",
"/live/api/v2/partners/createriderlog": f"{BASE_URL}/live/api/v2/partners/createriderlog",
"/live/api/v2/deliveries/createdeliverylog": f"{BASE_URL}/live/api/v2/deliveries/createdeliverylog",
"/live/api/v2/partners/createbreaklog": f"{BASE_URL}/live/api/v2/partners/createbreaklog",
"/live/api/v2/partners/updatebreaklog": f"{BASE_URL}/live/api/v2/partners/updatebreaklog",
"/live/api/v1/mob/orders/createorder": f"{BASE_URL}/live/api/v1/mob/orders/createorder",
"/live/api/v1/web/products/create": f"{BASE_URL}/live/api/v1/web/products/create",
"/live/api/v1/mob/customers/login": f"{BASE_URL}/live/api/v1/mob/customers/login",
"/live/api/v1/mob/customers/create": f"{BASE_URL}/live/api/v1/mob/customers/create",
}
# Global State
nc = None
js = None
session: aiohttp.ClientSession = None
running = True
semaphore = None # Initialized in main
def signal_handler(sig, frame):
global running
print("\n?? Shutdown signal received...")
running = False
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
async def forward_to_external(endpoint: str, payload: dict, api_key: str = None, method: str = "POST") -> tuple[str, Any]:
"""
Forward to external API using global session.
Returns (status_code, response_data).
Does NOT retry internally.
"""
external_url = ENDPOINT_MAPPING.get(endpoint)
if not external_url:
print(f"?? Unknown endpoint: {endpoint}, dropping.")
return "DROP", None
headers = {"Content-Type": "application/json"}
if api_key:
headers["Authorization"] = f"Bearer {api_key}"
data_to_forward = payload.get("data", payload)
# Payload Normalization for logs
if endpoint == "/live/api/v2/deliveries/createdeliverylog":
if isinstance(data_to_forward, dict):
data_to_forward = [data_to_forward]
elif not isinstance(data_to_forward, list):
data_to_forward = [data_to_forward]
http_method = method.upper() if method else "POST"
try:
# We use the global 'session' here
async with session.request(
method=http_method,
url=external_url,
json=data_to_forward if http_method != "GET" else None,
params=data_to_forward if http_method == "GET" else None,
headers=headers,
timeout=aiohttp.ClientTimeout(total=30)
) as response:
body = None
try:
body = await response.json()
except:
body = await response.text()
if 200 <= response.status < 300:
return "OK", body
elif response.status >= 500:
print(f"?? Server Error {response.status} from {endpoint}: {body}")
return "RETRY", body
else:
print(f"?? Client Error {response.status} from {endpoint}: {body}")
return "DROP", body # 4xx errors should generally not be retried infinitely
except asyncio.TimeoutError:
print(f"?? Timeout calling {endpoint}")
return "RETRY", None
except Exception as e:
print(f"?? Network/Client Error calling {endpoint}: {e}")
return "RETRY", None
async def process_message(msg):
async with semaphore:
messages_in_flight.inc()
start_time = asyncio.get_event_loop().time()
endpoint = "unknown"
try:
payload = json.loads(msg.data.decode())
endpoint = payload.get("endpoint", payload.get("original_path", "unknown"))
http_method = payload.get("method", "POST")
print(f"?? Processing {endpoint}")
api_key = os.getenv("EXTERNAL_ENDPOINT_API_KEY", "")
# Single attempt
status, response_data = await forward_to_external(endpoint, payload, api_key, http_method)
if status == "OK":
await msg.ack()
messages_processed.labels(status="success", endpoint=endpoint).inc()
# Request-Reply Logic
if msg.reply:
reply_payload = json.dumps(response_data) if isinstance(response_data, (dict, list)) else str(response_data)
await nc.publish(msg.reply, reply_payload.encode())
elif status == "DROP":
# Unrecoverable error or unknown endpoint
await msg.term() # Terminate stops redelivery
messages_processed.labels(status="dropped", endpoint=endpoint).inc()
else: # RETRY
# Let JetStream handle backoff
await msg.nak(delay=2) # Custom delay before redelivery if desired, or just nak()
messages_processed.labels(status="retried", endpoint=endpoint).inc()
except json.JSONDecodeError:
print("?? Invalid JSON, terminating message")
await msg.term()
except Exception as e:
print(f"? Critical Worker Error: {e}")
await msg.nak()
finally:
messages_in_flight.dec()
duration = asyncio.get_event_loop().time() - start_time
message_duration.labels(endpoint=endpoint).observe(duration)
async def main():
global nc, js, session, semaphore
print(f"?? Starting Worker for Domain: {NATS_CONSUMER}")
print(f"?? Stream: {NATS_STREAM}, Subjects: {FILTER_SUBJECTS}")
print(f"?? Concurrency: {WORKER_CONCURRENCY}")
semaphore = asyncio.Semaphore(WORKER_CONCURRENCY)
session = aiohttp.ClientSession()
try:
nc = await nats.connect(
servers=[NATS_URL],
user=NATS_USER,
password=NATS_PASSWORD,
reconnect_time_wait=2,
max_reconnect_attempts=-1
)
js = nc.jetstream()
print("? Connected to NATS JetStream")
# Create Pull Subscriptions for each filter subject
# All sharing the same Consumer Name ensuring load balancing if multiple pods run this
subs = []
for subject in FILTER_SUBJECTS:
subject = subject.strip()
if not subject: continue
# FIX: On WorkQueue streams, we cannot reuse the same durable name for different filters
# We append a sanitized version of the subject to ensure uniqueness per filter
clean_subject_suffix = subject.replace(".", "_").replace("*", "all").replace(">", "all")
durable_name = f"{NATS_CONSUMER}_{clean_subject_suffix}"
print(f"?? Subscribing to {subject} on durable consumer '{durable_name}'")
try:
sub = await js.pull_subscribe(
subject,
durable=durable_name,
stream=NATS_STREAM
)
subs.append(sub)
except Exception as e:
print(f"? Failed to subscribe to {subject}: {e}")
if not subs:
print("? No active subscriptions!")
return
start_http_server(9090)
print("?? Metrics on :9090")
while running:
# Poll all subscriptions
for sub in subs:
try:
# Batch size 10, short timeout to keep loop responsive
msgs = await sub.fetch(10, timeout=0.5)
for m in msgs:
asyncio.create_task(process_message(m))
except NatsTimeoutError:
pass
except Exception as e:
print(f"?? Fetch Error: {e}")
await asyncio.sleep(1)
# Small sleep to prevent tight loop if no messages
# await asyncio.sleep(0.01)
except Exception as e:
print(f"?? Fatal Error: {e}")
finally:
if session:
await session.close()
if nc:
await nc.close()
print("?? Worker Shutdown")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,129 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: worker
namespace: core
labels:
app.kubernetes.io/name: worker
app.kubernetes.io/instance: worker-primary
app.kubernetes.io/part-of: core
app.kubernetes.io/component: worker
spec:
serviceName: "worker" # Required for StatefulSet
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: worker
app.kubernetes.io/instance: worker-primary
template:
metadata:
labels:
app.kubernetes.io/name: worker
app.kubernetes.io/instance: worker-primary
app.kubernetes.io/part-of: core
app.kubernetes.io/component: worker
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 2000
tolerations:
- key: dedicated
operator: Equal
value: workers
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/worker
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- worker
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: worker
containers:
- name: worker
image: workolik360/nats-worker:v1.1.0
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
ports:
- containerPort: 9090
name: metrics
command: ["python3", "-u", "/scripts/worker.py"]
volumeMounts:
- name: worker-script-vol
mountPath: /scripts
envFrom:
- configMapRef:
name: core-config
env:
- name: NATS_USER
valueFrom:
secretKeyRef:
name: nats-credentials
key: username
- name: NATS_PASSWORD
valueFrom:
secretKeyRef:
name: nats-credentials
key: password
- name: EXTERNAL_ENDPOINT_API_KEY
valueFrom:
secretKeyRef:
name: external-endpoint-secrets
key: api_key
optional: true
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /metrics
port: 9090
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /metrics
port: 9090
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
volumes:
- name: worker-script-vol
configMap:
name: worker-script

542
manifests/core/workers.yaml Normal file
View File

@@ -0,0 +1,542 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: worker-orders
namespace: core
labels:
app.kubernetes.io/name: worker-orders
app.kubernetes.io/component: worker
spec:
serviceName: "worker-orders"
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: worker-orders
template:
metadata:
labels:
app.kubernetes.io/name: worker-orders
app.kubernetes.io/component: worker
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"
spec:
tolerations:
- key: dedicated
operator: Equal
value: workers
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/worker
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- worker-orders
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: worker-orders
containers:
- name: worker
image: workolik360/nats-worker:v1.1.0
imagePullPolicy: IfNotPresent
command: ["python3", "-u", "/scripts/worker.py"]
volumeMounts:
- name: worker-script-vol
mountPath: /scripts
envFrom:
- configMapRef:
name: core-config
env:
- name: NATS_STREAM
value: "ORDERS"
- name: NATS_CONSUMER
value: "orders-worker"
- name: FILTER_SUBJECT
value: "api.v1.mob.orders.createorder"
- name: WORKER_CONCURRENCY
value: "20"
- name: NATS_USER
valueFrom:
secretKeyRef:
name: nats-credentials
key: username
- name: NATS_PASSWORD
valueFrom:
secretKeyRef:
name: nats-credentials
key: password
- name: EXTERNAL_ENDPOINT_API_KEY
valueFrom:
secretKeyRef:
name: external-endpoint-secrets
key: api_key
optional: true
- name: EXTERNAL_BASE_URL
value: "http://10.43.229.168"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
ports:
- containerPort: 9090
name: metrics
volumes:
- name: worker-script-vol
configMap:
name: worker-script
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: worker-deliveries
namespace: core
labels:
app.kubernetes.io/name: worker-deliveries
app.kubernetes.io/component: worker
spec:
serviceName: "worker-deliveries"
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: worker-deliveries
template:
metadata:
labels:
app.kubernetes.io/name: worker-deliveries
app.kubernetes.io/component: worker
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"
spec:
tolerations:
- key: dedicated
operator: Equal
value: workers
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/worker
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- worker-deliveries
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: worker-deliveries
containers:
- name: worker
image: workolik360/nats-worker:v1.1.0
imagePullPolicy: IfNotPresent
command: ["python3", "-u", "/scripts/worker.py"]
volumeMounts:
- name: worker-script-vol
mountPath: /scripts
envFrom:
- configMapRef:
name: core-config
env:
- name: NATS_STREAM
value: "DELIVERIES"
- name: NATS_CONSUMER
value: "deliveries-worker"
- name: FILTER_SUBJECT
value: "api.v1.deliveries.createdeliveries,api.v1.deliveries.updatedelivery,api.v2.deliveries.createdeliverylog"
- name: WORKER_CONCURRENCY
value: "10"
- name: NATS_USER
valueFrom:
secretKeyRef:
name: nats-credentials
key: username
- name: NATS_PASSWORD
valueFrom:
secretKeyRef:
name: nats-credentials
key: password
- name: EXTERNAL_BASE_URL
value: "http://10.43.224.63"
resources:
requests:
memory: "128Mi"
cpu: "80m"
limits:
memory: "256Mi"
cpu: "400m"
ports:
- containerPort: 9090
name: metrics
volumes:
- name: worker-script-vol
configMap:
name: worker-script
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: worker-customers
namespace: core
labels:
app.kubernetes.io/name: worker-customers
app.kubernetes.io/component: worker
spec:
serviceName: "worker-customers"
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: worker-customers
template:
metadata:
labels:
app.kubernetes.io/name: worker-customers
app.kubernetes.io/component: worker
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"
spec:
tolerations:
- key: dedicated
operator: Equal
value: workers
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/worker
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- worker-customers
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: worker-customers
containers:
- name: worker
image: workolik360/nats-worker:v1.1.0
imagePullPolicy: IfNotPresent
command: ["python3", "-u", "/scripts/worker.py"]
volumeMounts:
- name: worker-script-vol
mountPath: /scripts
envFrom:
- configMapRef:
name: core-config
env:
- name: NATS_STREAM
value: "CUSTOMERS"
- name: NATS_CONSUMER
value: "customers-worker"
- name: FILTER_SUBJECT
value: "api.v1.mob.customers.login,api.v1.mob.customers.create"
- name: WORKER_CONCURRENCY
value: "30"
- name: NATS_USER
valueFrom:
secretKeyRef:
name: nats-credentials
key: username
- name: NATS_PASSWORD
valueFrom:
secretKeyRef:
name: nats-credentials
key: password
- name: EXTERNAL_ENDPOINT_API_KEY
valueFrom:
secretKeyRef:
name: external-endpoint-secrets
key: api_key
optional: true
- name: EXTERNAL_BASE_URL
value: "http://10.43.229.168"
resources:
requests:
memory: "128Mi"
cpu: "60m"
limits:
memory: "256Mi"
cpu: "300m"
ports:
- containerPort: 9090
name: metrics
volumes:
- name: worker-script-vol
configMap:
name: worker-script
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: worker-rider-logs
namespace: core
labels:
app.kubernetes.io/name: worker-rider-logs
app.kubernetes.io/component: worker
spec:
serviceName: "worker-rider-logs"
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: worker-rider-logs
template:
metadata:
labels:
app.kubernetes.io/name: worker-rider-logs
app.kubernetes.io/component: worker
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"
spec:
tolerations:
- key: dedicated
operator: Equal
value: workers
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/worker
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- worker-rider-logs
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: worker-rider-logs
containers:
- name: worker
image: workolik360/nats-worker:v1.1.0
imagePullPolicy: IfNotPresent
command: ["python3", "-u", "/scripts/worker.py"]
volumeMounts:
- name: worker-script-vol
mountPath: /scripts
envFrom:
- configMapRef:
name: core-config
env:
- name: NATS_STREAM
value: "RIDER"
- name: NATS_CONSUMER
value: "rider-logs-worker"
- name: FILTER_SUBJECT
value: "api.v2.partners.createriderlog,api.v2.partners.createbreaklog,api.v2.partners.updatebreaklog"
- name: WORKER_CONCURRENCY
value: "10"
- name: NATS_USER
valueFrom:
secretKeyRef:
name: nats-credentials
key: username
- name: NATS_PASSWORD
valueFrom:
secretKeyRef:
name: nats-credentials
key: password
- name: EXTERNAL_ENDPOINT_API_KEY
valueFrom:
secretKeyRef:
name: external-endpoint-secrets
key: api_key
optional: true
- name: EXTERNAL_BASE_URL
value: "http://10.43.224.63"
resources:
requests:
memory: "128Mi"
cpu: "40m"
limits:
memory: "128Mi"
cpu: "200m"
ports:
- containerPort: 9090
name: metrics
volumes:
- name: worker-script-vol
configMap:
name: worker-script
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: worker-products
namespace: core
labels:
app.kubernetes.io/name: worker-products
app.kubernetes.io/component: worker
spec:
serviceName: "worker-products"
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: worker-products
template:
metadata:
labels:
app.kubernetes.io/name: worker-products
app.kubernetes.io/component: worker
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"
spec:
tolerations:
- key: dedicated
operator: Equal
value: workers
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/worker
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- worker-products
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: worker-products
containers:
- name: worker
image: workolik360/nats-worker:v1.1.0
imagePullPolicy: IfNotPresent
command: ["python3", "-u", "/scripts/worker.py"]
volumeMounts:
- name: worker-script-vol
mountPath: /scripts
envFrom:
- configMapRef:
name: core-config
env:
- name: NATS_STREAM
value: "PRODUCTS"
- name: NATS_CONSUMER
value: "products-worker"
- name: FILTER_SUBJECT
value: "api.v1.web.products.create"
- name: WORKER_CONCURRENCY
value: "5"
- name: NATS_USER
valueFrom:
secretKeyRef:
name: nats-credentials
key: username
- name: NATS_PASSWORD
valueFrom:
secretKeyRef:
name: nats-credentials
key: password
- name: EXTERNAL_ENDPOINT_API_KEY
valueFrom:
secretKeyRef:
name: external-endpoint-secrets
key: api_key
optional: true
- name: EXTERNAL_BASE_URL
value: "http://10.43.229.168"
resources:
requests:
memory: "128Mi"
cpu: "40m"
limits:
memory: "256Mi"
cpu: "200m"
ports:
- containerPort: 9090
name: metrics
volumes:
- name: worker-script-vol
configMap:
name: worker-script

View File

@@ -0,0 +1,13 @@
{
"type": "service_account",
"project_id": "doormile-abee7",
"private_key_id": "66e9b8b66fb40961271f095dd924935eff19452b",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDLBD8DlrQwvwo8\ngkW9j/z1+eLPZYAT2TA4xWT1l+i8LV5he8wZ2TjxJT385OGS+zjIVUHQKtvStdDB\no9/0NXnSC6n88puNhQp9xCRjxEU+FLiSnqJGRUzSO4pu+0TuukGsouDII6QhJFNf\ndmtTOMmjV4V+wAOLfbE3qI5kZQ5trqf8wNBPZKxp9fhrqHdP080HUbOs14jiSrHl\n3OYRj0ZhDTsavboV6QTbdJn5ivH19nboFfdzuJrS5ApMhLqIGdoSEqjjQj8t6f6I\nm1RgQMfztIZLRPliA5MyJieEDZZdy5mmDQDnqCP6zFtH+WRVBe9Lo2A7F+Rf1vv4\n8/5FRd3HAgMBAAECggEACmEEsbPGMYnKxa4pT5gpaA/m6xB23EzpvLVGxJGIGfq9\nzQENvbKPyTBMu32eFKwYSpGlRDW0uFCIRCYTIIKNYFItVhu3HSSUlTpuW9VgbtyT\nVRecFziaxVK68JKTAxttmRxYnpLH5NPdGU/OC9qm4F1smz7Iz5xU75ID+Zj7BFtm\nkjZsgU9ALT7ikm205KhjYepc16ZQODIvvGW8vdNwxaxNl+5+RpkXdq+NK3KiAjK4\noqsuvDRRcchHttXU/qhri7f4nT0VBIuMnPDtRWGEaWtgCt4urLFAD4fZE91i1SHe\n7vV0iYdS9lzjrwl3Umzcf4k0pSPN8Sky1ZjN9hvF5QKBgQD+p2NMePwHY4uksA98\nVWT6y6haM+rIGxSsBL06UnCGO4E1gEWd++G5osDNc9hQk29xFg64pyIPKovw/Qez\nZ+gyCySC8KJoC4q740mxqn1EmANHXAvxGUMgPHmeiX+j3i1MwJWG1w7uyLwQ2216\nEgHy6Mqzk3jLUnD8vXf08CAUxQKBgQDMFvrRwRwpqsLR2yHWiu8uWykU276m1mKI\n8saR/iQ+jXQ9NZsgVdOjaV9EIv7jLwzSwEcXHVh9+SB5pnlCOhovGmmiSIdLVr5f\n1XOyWY6n2NzM6tm2RXjWOnwpVviOtdAwVVWXQNwaNbRLMZZkv+0bpOwk7i/ONv2z\n1bNP5pHJGwKBgQCo8LeJxkG9TScZTMwZOjXRxEaeAQ9jTcP7EvHOvV51Twi7S6vj\n0XTAyN6L7qy1V/p41Z9SlV6dHSbV66euN+LtQEMqsyWrTOzvFSyQt4B/fzG9p60s\neAv2WOh+as3JEt6Oqr0IRahcdcN+k3Qfpc6xLgs78zwR91GJ+tCYKK2/2QKBgBNX\nfi/Q0F12jH4c+Wj+w8ot44fRh5ECMlZuQ7lkT4UKHOEMKoZ2+9a5zsiDVIqtU9bJ\nWDNtmYxexuAgkc9f+ElMhIRXwVK3htVIQm29pRJtMAfPnhBzJw+OfWPhlS7ZNkDO\nZY0vHWzyeALEUU99DPiYubSKSkeN2J9pelPxMxHPAoGBAOOTd5XJRQOnFpd0eVwE\nlJ65zeQyT82V1OX/OPLKOcFwzNNKYF0Toto98nLK5rqqAGNUPp0fBMdUi7b76i79\nwve7aEZtCQWbhQsOUXsWocZyr8w5EexYtbxWa6q7jzmJaL/UfagE8pZWUI2k59CN\nRajHiJG83UxGMUN8KbSgv7h7\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-fbsvc@doormile-abee7.iam.gserviceaccount.com",
"client_id": "108510619961572391178",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40doormile-abee7.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}

View File

@@ -0,0 +1,89 @@
apiVersion: v1
kind: Namespace
metadata:
name: doormile
Read 1 file
Found the critical issue — INTERNAL_API_KEY not set in the manifest means
all /internal/* endpoode explicitly rejects empty keys). That plus the NATS_URL duplicate.
Here's the corrected miletruth.yaml with all missing env vars added. Run
this on the server:
cat > /root/kuberneteh.yaml << 'EOF'
apiVersion: v1
kind: Namespace
metadata:
name: doormile
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: doormile
namespace: doormile
spec:
serviceName: "doormile-service"
replicas: 3
selector:
matchLabels:
app: doormile
app-group: doormile-api
template:
metadata:
labels:
app: doormile
app-group: do
spec:
containers:
- name: doormile
image: doormi
imagePullPolicy: Always
env:
- name: ENV
value: "production"
- name: APP_PORT
value: "8081"
- name: DB_HO
value: "31.97.228.132"
- name: DB_PO
value: "5433"
- name: DB_NA
value: "logistics"
- name: DB_US
value: "admin"
- name: DB_PASSWORD
value: "Pac
- name: REDIS_HOST
value: "31.97.228.132"
- name: REDIS
value: "6379"
- name: REDIS_USER
value: "adm
- name: REDIS_PASSWORD
value: "Package@321#"
- name: JWT_S
value: "DoormileSuperSecretJWTKey2026!"
- name: NATS_
value: "nats://66.116.226.161:4223"
- name: NATS_
value: "doormile"
- name: NATS_PASSWORD
value: "Pac
- name: INTERNAL_API_KEY
value: "doormile-internal-2024"
---
apiVersion: v1
kind: Service
metadata:
name: doormile-service
namespace: doormile
spec:
type: NodePort
selector:
app-group: doormile-api
ports:
- protocol: TCP
port: 8081
targetPort: 808
nodePort: 30830

View File

@@ -0,0 +1,61 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: atlantis
namespace: nearle
labels:
app: atlantis
spec:
serviceName: "atlantis"
replicas: 2
selector:
matchLabels:
app: atlantis
template:
metadata:
labels:
app: atlantis
spec:
tolerations:
- key: dedicated
operator: Equal
value: apps
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/app
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: atlantis
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: atlantis
containers:
- name: backend
image: nearlecommerce/atlantis:v0.0.41
imagePullPolicy: Always
ports:
- containerPort: 3000
env:
- name: PORT
value: "3000"
envFrom:
- configMapRef:
name: nearle-config
- secretRef:
name: app-secrets

View File

@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: atlantis
namespace: nearle
labels:
app: atlantis
spec:
type: NodePort
ports:
- port: 80
targetPort: 3000
nodePort: 30825
protocol: TCP
selector:
app: atlantis

View File

@@ -0,0 +1,514 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: fiesta-gateway-script
namespace: nearle
data:
app.py: |
#!/usr/bin/env python3
"""
FastAPI application with multiple endpoints that publish to NATS JetStream
Each endpoint corresponds to an external API that workers will forward to
"""
import os
import json
import asyncio
from fastapi import FastAPI, HTTPException, Request, Body
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request as StarletteRequest
from starlette.responses import Response as StarletteResponse
from pydantic import BaseModel
import nats
import nats.errors
from prometheus_client import Counter, Histogram, generate_latest, REGISTRY
from starlette.responses import Response
import uvicorn
from typing import Optional, Dict, Any, List, Union
# Prometheus metrics
request_count = Counter('http_requests_total', 'Total HTTP requests', ['method', 'endpoint', 'status'])
request_duration = Histogram('http_request_duration_seconds', 'HTTP request duration', ['method', 'endpoint'])
app = FastAPI(title="NATS Backend API - Multi-Endpoint", version="1.0.0")
# Custom CORS middleware to ensure headers are always added
class CORSHeaderMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: StarletteRequest, call_next):
origin = request.headers.get("origin", "*")
response = await call_next(request)
response.headers["Access-Control-Allow-Origin"] = origin
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "*"
response.headers["Access-Control-Allow-Credentials"] = "false"
response.headers["Access-Control-Max-Age"] = "600"
return response
# Add custom CORS middleware first
app.add_middleware(CORSHeaderMiddleware)
# Also add FastAPI's CORS middleware as backup
app.add_middleware(
CORSMiddleware,
allow_origin_regex=r".*", # Match all origins
allow_credentials=False,
allow_methods=["*"], # Allow all HTTP methods
allow_headers=["*"], # Allow all headers
expose_headers=["*"], # Expose all headers
max_age=600,
)
# NATS connection (will be initialized on startup)
nc = None
js = None
# Configuration
NATS_URL = os.getenv("NATS_URL", "nats://nats-server:4222")
NATS_USER = os.getenv("NATS_USER", "admin")
NATS_PASSWORD = os.getenv("NATS_PASSWORD", "package@321#")
# Endpoint to NATS subject mapping
ENDPOINT_ROUTES = {
"/live/api/v1/deliveries/createdeliveries": "api.v1.deliveries.createdeliveries",
"/live/api/v1/deliveries/updatedelivery": "api.v1.deliveries.updatedelivery",
"/live/api/v2/partners/createriderlog": "api.v2.partners.createriderlog",
"/live/api/v2/deliveries/createdeliverylog": "api.v2.deliveries.createdeliverylog",
"/live/api/v2/partners/createbreaklog": "api.v2.partners.createbreaklog",
"/live/api/v2/partners/updatebreaklog": "api.v2.partners.updatebreaklog",
"/live/api/v1/mob/orders/createorder": "api.v1.mob.orders.createorder",
"/live/api/v1/web/products/create": "api.v1.web.products.create",
# Customer Endpoints (Sync)
"/live/api/v1/mob/customers/login": "api.v1.mob.customers.login",
"/live/api/v1/mob/customers/create": "api.v1.mob.customers.create",
}
@app.on_event("startup")
async def startup():
"""Initialize NATS connection on startup"""
global nc, js
try:
print(f"Connecting to NATS at {NATS_URL}...")
nc = await nats.connect(
servers=[NATS_URL],
user=NATS_USER,
password=NATS_PASSWORD,
reconnect_time_wait=2,
max_reconnect_attempts=10
)
js = nc.jetstream()
print("✅ Connected to NATS JetStream")
print(f"✅ Configured {len(ENDPOINT_ROUTES)} endpoint routes")
except Exception as e:
print(f"❌ Failed to connect to NATS: {e}")
raise
@app.on_event("shutdown")
async def shutdown():
"""Close NATS connection on shutdown"""
global nc
if nc:
await nc.close()
print("NATS connection closed")
@app.options("/{full_path:path}")
async def options_handler(full_path: str, request: Request):
"""Handle OPTIONS requests for CORS preflight"""
origin = request.headers.get("origin")
return JSONResponse(
status_code=200,
content={},
headers={
"Access-Control-Allow-Origin": origin if origin else "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Credentials": "false",
"Access-Control-Max-Age": "600",
}
)
@app.get("/health")
async def health():
"""Health check endpoint"""
return {"status": "healthy", "nats_connected": nc.is_connected if nc else False}
@app.get("/ready")
async def ready():
"""Readiness check endpoint"""
if nc and nc.is_connected:
return {"status": "ready"}
raise HTTPException(status_code=503, detail="Not ready")
async def publish_to_nats(endpoint: str, data: Union[Dict[str, Any], List[Dict[str, Any]]], request_method: str = "POST"):
"""Publish message to NATS with endpoint metadata"""
payload = {
"endpoint": endpoint,
"method": request_method,
"data": data,
"received_at": int(asyncio.get_event_loop().time() * 1000),
"original_path": endpoint
}
# Get NATS subject for this endpoint
subject = ENDPOINT_ROUTES.get(endpoint, "api.unknown")
if js:
try:
ack = await js.publish(subject, json.dumps(payload).encode())
return ack.seq
except Exception as e:
print(f"❌ Failed to publish to NATS: {e}")
raise HTTPException(status_code=500, detail=f"Failed to publish message: {str(e)}")
else:
raise HTTPException(status_code=503, detail="NATS not connected")
async def publish_request_to_nats(endpoint: str, data: Dict[str, Any], request_method: str = "POST", timeout: int = 10):
"""
Publish to NATS and WAIT for a reply (Request-Reply pattern).
Used for synchronous endpoints like Login.
"""
payload = {
"endpoint": endpoint,
"method": request_method,
"data": data,
"received_at": int(asyncio.get_event_loop().time() * 1000),
"original_path": endpoint
}
subject = ENDPOINT_ROUTES.get(endpoint, "api.unknown")
if not js:
raise HTTPException(status_code=503, detail="NATS by connected")
try:
# Create a unique inbox for the reply
inbox = nc.new_inbox()
# Subscribe to the inbox first
sub = await nc.subscribe(inbox, max_msgs=1)
# Publish request with reply inbox
# Note: We use js.publish to ensure it goes to the Stream (Queue), but attach a reply subject
await js.publish(subject, json.dumps(payload).encode(), reply=inbox)
# Wait for valid response
try:
msg = await sub.next_msg(timeout=timeout)
response_data = json.loads(msg.data.decode())
return response_data
except nats.errors.TimeoutError:
raise HTTPException(status_code=504, detail="Gateway Timeout: Upstream service did not respond in time")
finally:
await sub.unsubscribe()
except HTTPException:
raise
except Exception as e:
print(f"❌ Failed to request from NATS: {e}")
raise HTTPException(status_code=500, detail=f"RPC Error: {str(e)}")
# Endpoint 1: Update Delivery (v1) - External API requires PUT
@app.put("/live/api/v1/deliveries/updatedelivery")
async def update_delivery_v1(data: Dict[str, Any], request: Request):
"""Update Delivery endpoint - forwards to NATS (PUT only, as external API requires PUT)"""
start_time = asyncio.get_event_loop().time()
endpoint = "/live/api/v1/deliveries/updatedelivery"
method = "PUT" # Always use PUT for this endpoint
try:
message_id = await publish_to_nats(endpoint, data, method)
duration = asyncio.get_event_loop().time() - start_time
request_duration.labels(method="PUT", endpoint=endpoint).observe(duration)
request_count.labels(method="PUT", endpoint=endpoint, status="200").inc()
return JSONResponse(
status_code=200,
content={"status": "accepted", "message_id": message_id, "endpoint": endpoint}
)
except HTTPException:
raise
except Exception as e:
request_count.labels(method="PUT", endpoint=endpoint, status="500").inc()
print(f"❌ Error processing {endpoint}: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Endpoint: Create Deliveries (v1)
@app.post("/live/api/v1/deliveries/createdeliveries")
async def create_deliveries_v1(data: Union[Dict[str, Any], List[Dict[str, Any]]], request: Request):
"""Create Deliveries endpoint - forwards payload to NATS"""
start_time = asyncio.get_event_loop().time()
endpoint = "/live/api/v1/deliveries/createdeliveries"
method = request.method
try:
message_id = await publish_to_nats(endpoint, data, method)
duration = asyncio.get_event_loop().time() - start_time
request_duration.labels(method=method, endpoint=endpoint).observe(duration)
request_count.labels(method=method, endpoint=endpoint, status="200").inc()
return JSONResponse(
status_code=200,
content={"status": "accepted", "message_id": message_id, "endpoint": endpoint}
)
except HTTPException:
raise
except Exception as e:
request_count.labels(method=method, endpoint=endpoint, status="500").inc()
print(f"❌ Error processing {endpoint}: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Endpoint 2: Create Rider Log (v2)
@app.post("/live/api/v2/partners/createriderlog")
async def create_rider_log_v2(data: Dict[str, Any], request: Request):
"""Create Rider Log endpoint - forwards to NATS"""
start_time = asyncio.get_event_loop().time()
endpoint = "/live/api/v2/partners/createriderlog"
method = request.method
try:
message_id = await publish_to_nats(endpoint, data, method)
duration = asyncio.get_event_loop().time() - start_time
request_duration.labels(method=method, endpoint=endpoint).observe(duration)
request_count.labels(method=method, endpoint=endpoint, status="200").inc()
return JSONResponse(
status_code=200,
content={"status": "accepted", "message_id": message_id, "endpoint": endpoint}
)
except HTTPException:
raise
except Exception as e:
request_count.labels(method=method, endpoint=endpoint, status="500").inc()
print(f"❌ Error processing {endpoint}: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Endpoint 3: Create Delivery Log (v2)
@app.post("/live/api/v2/deliveries/createdeliverylog")
async def create_delivery_log_v2(
request: Request,
data: List[Dict[str, Any]] = Body(
...,
example=[{
"logid": 0,
"tenantid": 1,
"partnerid": 44,
"locationid": 1,
"orderheaderid": 123456,
"deliveryid": 654321,
"userid": 1111,
"orderid": "1-20231624",
"orderstatus": "active",
"starttime": "2025-12-10 17:51:03",
"logdate": "2025-12-10 18:14:04",
"latitude": "11.0050664",
"longitude": "76.9508776"
}]
)
):
"""
Create Delivery Log endpoint - forwards to NATS.
Accepts either a single dict or a list of dicts to align with external API expectations.
"""
start_time = asyncio.get_event_loop().time()
endpoint = "/live/api/v2/deliveries/createdeliverylog"
method = request.method
try:
# Expect only a list of dicts
if not isinstance(data, list) or not all(isinstance(item, dict) for item in data):
raise HTTPException(status_code=422, detail="Body must be a list of objects")
normalized_data = data
message_id = await publish_to_nats(endpoint, normalized_data, method)
duration = asyncio.get_event_loop().time() - start_time
request_duration.labels(method=method, endpoint=endpoint).observe(duration)
request_count.labels(method=method, endpoint=endpoint, status="200").inc()
return JSONResponse(
status_code=200,
content={"status": "accepted", "message_id": message_id, "endpoint": endpoint}
)
except HTTPException:
raise
except Exception as e:
request_count.labels(method=method, endpoint=endpoint, status="500").inc()
print(f"❌ Error processing {endpoint}: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Endpoint 4: Create Break Rider Log (v2)
@app.post("/live/api/v2/partners/createbreaklog")
async def create_break_log_v2(data: Dict[str, Any], request: Request):
"""Create Break Rider Log endpoint - forwards to NATS"""
start_time = asyncio.get_event_loop().time()
endpoint = "/live/api/v2/partners/createbreaklog"
method = request.method
try:
message_id = await publish_to_nats(endpoint, data, method)
duration = asyncio.get_event_loop().time() - start_time
request_duration.labels(method=method, endpoint=endpoint).observe(duration)
request_count.labels(method=method, endpoint=endpoint, status="200").inc()
return JSONResponse(
status_code=200,
content={"status": "accepted", "message_id": message_id, "endpoint": endpoint}
)
except HTTPException:
raise
except Exception as e:
request_count.labels(method=method, endpoint=endpoint, status="500").inc()
print(f"❌ Error processing {endpoint}: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Endpoint 5: Update Break Rider Log (v2) - Supports both POST and PUT
@app.post("/live/api/v2/partners/updatebreaklog")
@app.put("/live/api/v2/partners/updatebreaklog")
async def update_break_log_v2(data: Dict[str, Any], request: Request):
"""Update Break Rider Log endpoint - forwards to NATS"""
start_time = asyncio.get_event_loop().time()
endpoint = "/live/api/v2/partners/updatebreaklog"
method = request.method # Will be POST or PUT
try:
message_id = await publish_to_nats(endpoint, data, method)
duration = asyncio.get_event_loop().time() - start_time
request_duration.labels(method=method, endpoint=endpoint).observe(duration)
request_count.labels(method=method, endpoint=endpoint, status="200").inc()
return JSONResponse(
status_code=200,
content={"status": "accepted", "message_id": message_id, "endpoint": endpoint}
)
except HTTPException:
raise
except Exception as e:
request_count.labels(method=method, endpoint=endpoint, status="500").inc()
print(f"❌ Error processing {endpoint}: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/metrics")
async def metrics():
"""Prometheus metrics endpoint"""
return Response(content=generate_latest(REGISTRY), media_type="text/plain")
@app.get("/routes")
async def list_routes():
"""List all configured routes"""
return {
"routes": ENDPOINT_ROUTES,
"total": len(ENDPOINT_ROUTES)
}
# Endpoint 6: Create Order (Mob V1)
@app.post("/live/api/v1/mob/orders/createorder")
async def create_order_mob_v1(data: Dict[str, Any], request: Request):
"""Create Order endpoint (Mobile) - forwards to NATS"""
start_time = asyncio.get_event_loop().time()
endpoint = "/live/api/v1/mob/orders/createorder"
method = request.method
try:
message_id = await publish_to_nats(endpoint, data, method)
duration = asyncio.get_event_loop().time() - start_time
request_duration.labels(method=method, endpoint=endpoint).observe(duration)
request_count.labels(method=method, endpoint=endpoint, status="200").inc()
return JSONResponse(
status_code=200,
content={"status": "accepted", "message_id": message_id, "endpoint": endpoint}
)
except HTTPException:
raise
except Exception as e:
request_count.labels(method=method, endpoint=endpoint, status="500").inc()
print(f"❌ Error processing {endpoint}: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Endpoint 7: Create Product (Web V1)
@app.post("/live/api/v1/web/products/create")
async def create_product_web_v1(data: Dict[str, Any], request: Request):
"""Create Product endpoint (Web) - forwards to NATS"""
start_time = asyncio.get_event_loop().time()
endpoint = "/live/api/v1/web/products/create"
method = request.method
try:
message_id = await publish_to_nats(endpoint, data, method)
duration = asyncio.get_event_loop().time() - start_time
request_duration.labels(method=method, endpoint=endpoint).observe(duration)
request_count.labels(method=method, endpoint=endpoint, status="200").inc()
return JSONResponse(
status_code=200,
content={"status": "accepted", "message_id": message_id, "endpoint": endpoint}
)
except HTTPException:
raise
except Exception as e:
request_count.labels(method=method, endpoint=endpoint, status="500").inc()
print(f"❌ Error processing {endpoint}: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Endpoint 8: Customer Login (Sync Request-Reply)
@app.post("/live/api/v1/mob/customers/login")
async def customer_login(data: Dict[str, Any], request: Request):
"""Customer Login - Waits for response from worker"""
start_time = asyncio.get_event_loop().time()
endpoint = "/live/api/v1/mob/customers/login"
method = request.method
try:
# Wait for reply!
response_data = await publish_request_to_nats(endpoint, data, method)
duration = asyncio.get_event_loop().time() - start_time
request_duration.labels(method=method, endpoint=endpoint).observe(duration)
request_count.labels(method=method, endpoint=endpoint, status="200").inc()
# Return the actual backend response
return JSONResponse(
status_code=200,
content=response_data
)
except HTTPException:
raise
except Exception as e:
request_count.labels(method=method, endpoint=endpoint, status="500").inc()
print(f"❌ Error processing {endpoint}: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Endpoint 9: Customer Create (Sync Request-Reply)
@app.post("/live/api/v1/mob/customers/create")
async def customer_create(data: Dict[str, Any], request: Request):
"""Customer Create - Waits for response from worker"""
start_time = asyncio.get_event_loop().time()
endpoint = "/live/api/v1/mob/customers/create"
method = request.method
try:
# Wait for reply!
response_data = await publish_request_to_nats(endpoint, data, method)
duration = asyncio.get_event_loop().time() - start_time
request_duration.labels(method=method, endpoint=endpoint).observe(duration)
request_count.labels(method=method, endpoint=endpoint, status="200").inc()
return JSONResponse(
status_code=200,
content=response_data
)
except HTTPException:
raise
except Exception as e:
request_count.labels(method=method, endpoint=endpoint, status="500").inc()
print(f"❌ Error processing {endpoint}: {e}")
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)

View File

@@ -0,0 +1,99 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: fiesta
namespace: nearle
labels:
app: fiesta
spec:
serviceName: "fiesta"
replicas: 3
selector:
matchLabels:
app: fiesta
template:
metadata:
labels:
app: fiesta
spec:
tolerations:
- key: dedicated
operator: Equal
value: apps
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/app
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: fiesta
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: fiesta
containers:
- name: backend
image: nearlecommerce/fiesta:v1.3.67
imagePullPolicy: Always
ports:
- containerPort: 1122
envFrom:
- configMapRef:
name: nearle-config
- secretRef:
name: app-secrets
env:
- name: PORT
value: "1122"
- name: NATS_USER
valueFrom:
secretKeyRef:
name: nats-credentials
key: username
- name: NATS_PASSWORD
valueFrom:
secretKeyRef:
name: nats-credentials
key: password
- name: gateway
image: workolik360/alaska:v1.2.0
imagePullPolicy: Always
ports:
- containerPort: 8000
name: http
volumeMounts:
- name: gateway-script
mountPath: /app/app.py
subPath: app.py
envFrom:
- configMapRef:
name: nearle-config
env:
- name: NATS_USER
valueFrom:
secretKeyRef:
name: nats-credentials
key: username
- name: NATS_PASSWORD
valueFrom:
secretKeyRef:
name: nats-credentials
key: password
volumes:
- name: gateway-script
configMap:
name: fiesta-gateway-script

View File

@@ -0,0 +1,21 @@
apiVersion: v1
kind: Service
metadata:
name: fiesta
namespace: nearle
labels:
app: fiesta
spec:
type: NodePort
ports:
- port: 80
targetPort: 1122
nodePort: 30823
protocol: TCP
name: main
- port: 8000
targetPort: 8000
name: gateway
protocol: TCP
selector:
app: fiesta

View File

@@ -0,0 +1,97 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: jupiter-cors-config
namespace: nearle
data:
nginx.conf: |
events {}
http {
upstream jupiter_backend {
server jupiter:80;
}
server {
listen 80;
location / {
proxy_pass http://jupiter_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, PATCH, DELETE' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With, Accept, Origin, X-Auth-Token' always;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, PATCH, DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With, Accept, Origin, X-Auth-Token';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}
}
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jupiter-cors-proxy
namespace: nearle
labels:
app: jupiter-cors-proxy
spec:
replicas: 1
selector:
matchLabels:
app: jupiter-cors-proxy
template:
metadata:
labels:
app: jupiter-cors-proxy
spec:
tolerations:
- key: dedicated
operator: Equal
value: apps
effect: NoSchedule
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
resources:
requests:
memory: "32Mi"
cpu: "10m"
limits:
memory: "64Mi"
cpu: "100m"
volumes:
- name: nginx-config
configMap:
name: jupiter-cors-config
---
apiVersion: v1
kind: Service
metadata:
name: jupiter-cors-proxy
namespace: nearle
labels:
app: jupiter-cors-proxy
spec:
selector:
app: jupiter-cors-proxy
ports:
- port: 80
targetPort: 80
protocol: TCP

View File

@@ -0,0 +1,77 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: jupiter
namespace: nearle
labels:
app: jupiter
spec:
serviceName: "jupiter"
replicas: 3
selector:
matchLabels:
app: jupiter
template:
metadata:
labels:
app: jupiter
spec:
tolerations:
- key: dedicated
operator: Equal
value: apps
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/app
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: jupiter
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: jupiter
containers:
- name: jupiter
image: nearlecommerce/jupiter:v2.7.53
imagePullPolicy: Always
ports:
- containerPort: 1009
env:
- name: PORT
value: "1009"
- name: TZ
value: "Asia/Kolkata"
volumeMounts:
- name: tz-config
mountPath: /etc/localtime
readOnly: true
- name: tz-data
mountPath: /usr/share/zoneinfo
readOnly: true
envFrom:
- configMapRef:
name: nearle-config
- secretRef:
name: app-secrets
volumes:
- name: tz-config
hostPath:
path: /usr/share/zoneinfo/Asia/Kolkata
- name: tz-data
hostPath:
path: /usr/share/zoneinfo

View File

@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: jupiter
namespace: nearle
labels:
app: jupiter
spec:
type: NodePort
ports:
- port: 80
targetPort: 1009
nodePort: 30822
protocol: TCP
selector:
app: jupiter

View File

@@ -0,0 +1,23 @@
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: nearle
labels:
app: fiesta
type: Opaque
stringData:
# The IP of your BigRock Server
DATABASE_HOST: "66.116.207.225"
DB_HOST: "66.116.207.225"
# The user we confirmed works
DATABASE_USERNAME: "admin"
DB_USER: "admin"
# The password we confirmed works
DATABASE_PASSWORD: "Package@123#"
DB_PASSWORD: "Package@123#"
# The rest...
JWT_SECRET_KEY: "nearle"

View File

@@ -0,0 +1,99 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: ariane
namespace: nearle
labels:
app: ariane
spec:
serviceName: "ariane"
replicas: 3
selector:
matchLabels:
app: ariane
template:
metadata:
labels:
app: ariane
spec:
tolerations:
- key: dedicated
operator: Equal
value: apps
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/app
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: ariane
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: ariane
containers:
- name: backend
image: nearlecommerce/ariane:v1.0.22
imagePullPolicy: Always
ports:
- containerPort: 1000
env:
- name: PORT
value: "1000"
envFrom:
- configMapRef:
name: nearle-config
- secretRef:
name: app-secrets
---
apiVersion: v1
kind: Service
metadata:
name: ariane
namespace: nearle
labels:
app: ariane
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 1000
protocol: TCP
selector:
app: ariane
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: ariane-route
namespace: nearle
labels:
app: ariane
spec:
parentRefs:
- name: gateway
namespace: alaska
hostnames:
- "ariane.nearle.app"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: ariane
port: 80

View File

@@ -0,0 +1,100 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: atlantis
namespace: nearle
labels:
app: atlantis
spec:
serviceName: "atlantis"
replicas: 2
selector:
matchLabels:
app: atlantis
template:
metadata:
labels:
app: atlantis
spec:
tolerations:
- key: dedicated
operator: Equal
value: apps
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/app
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: atlantis
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: atlantis
containers:
- name: backend
image: nearlecommerce/atlantis:v0.0.41
imagePullPolicy: Always
ports:
- containerPort: 3000
env:
- name: PORT
value: "3000"
envFrom:
- configMapRef:
name: nearle-config
- secretRef:
name: app-secrets
---
apiVersion: v1
kind: Service
metadata:
name: atlantis
namespace: nearle
labels:
app: atlantis
spec:
type: NodePort
ports:
- port: 80
targetPort: 3000
nodePort: 30825
protocol: TCP
selector:
app: atlantis
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: atlantis-route
namespace: nearle
labels:
app: atlantis
spec:
parentRefs:
- name: gateway
namespace: alaska
hostnames:
- "atlantis.nearle.app"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: atlantis
port: 80

View File

@@ -0,0 +1,23 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: nearle-config
namespace: nearle
labels:
app: fiesta
data:
NATS_URL: "nats://66.116.226.161:4222"
LOG_LEVEL: "info"
# The stream is set to ORDERS as per your request
NATS_STREAM: "ORDERS"
# Base subject pattern - the app likely appends the path to this or uses it as a listener filter
NATS_SUBJECT: "api.>"
ALLOWED_ORIGINS: "*"
ENV: "production"
DATABASE_NAME: "nearledb"
DB_NAME: "nearledb"
DATABASE_PORT: "5432"
DB_PORT: "5432"
DATABASE_SERVER_HOST: "66.116.207.225"
DB_HOST: "66.116.207.225"
USER_CONTEXT_KEY: "nearle"

View File

@@ -0,0 +1,157 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: fiesta
namespace: nearle
labels:
app: fiesta
spec:
serviceName: "fiesta"
replicas: 3
selector:
matchLabels:
app: fiesta
template:
metadata:
labels:
app: fiesta
spec:
tolerations:
- key: dedicated
operator: Equal
value: apps
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/app
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: fiesta
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: fiesta
containers:
- name: backend
image: nearlecommerce/fiesta:v1.3.50
imagePullPolicy: Always
ports:
- containerPort: 1122
envFrom:
- configMapRef:
name: nearle-config
- secretRef:
name: app-secrets
env:
- name: PORT
value: "1122"
- name: NATS_USER
valueFrom:
secretKeyRef:
name: nats-credentials
key: username
- name: NATS_PASSWORD
valueFrom:
secretKeyRef:
name: nats-credentials
key: password
- name: gateway
image: workolik360/alaska:v1.2.0
imagePullPolicy: Always
ports:
- containerPort: 8000
name: http
volumeMounts:
- name: gateway-script
mountPath: /app/app.py
subPath: app.py
envFrom:
- configMapRef:
name: nearle-config
env:
- name: NATS_USER
valueFrom:
secretKeyRef:
name: nats-credentials
key: username
- name: NATS_PASSWORD
valueFrom:
secretKeyRef:
name: nats-credentials
key: password
volumes:
- name: gateway-script
configMap:
name: fiesta-gateway-script
---
apiVersion: v1
kind: Service
metadata:
name: fiesta
namespace: nearle
labels:
app: fiesta
spec:
type: NodePort
ports:
- port: 80
targetPort: 1122
nodePort: 30823
protocol: TCP
name: main
- port: 8000
targetPort: 8000
name: gateway
protocol: TCP
selector:
app: fiesta
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: fiesta-route
namespace: nearle
labels:
app: fiesta
spec:
parentRefs:
- name: gateway
namespace: alaska
hostnames:
- "fiesta.nearle.app"
rules:
- matches:
- path:
type: PathPrefix
value: /live/api/v1/mob/orders/createorder
backendRefs:
- name: fiesta
port: 8000
- matches:
- path:
type: PathPrefix
value: /live/api/v1/web/products/create
backendRefs:
- name: fiesta
port: 8000
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: fiesta
port: 80

View File

@@ -0,0 +1,27 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
namespace: nearle
labels:
app.kubernetes.io/name: gateway
app.kubernetes.io/part-of: nearle
spec:
gatewayClassName: standard
listeners:
- name: http
protocol: HTTP
port: 8202
allowedRoutes:
namespaces:
from: Same
- name: https
protocol: HTTPS
port: 8442
allowedRoutes:
namespaces:
from: Same
tls:
mode: Terminate
certificateRefs:
- name: nearle-tls-cert

View File

@@ -0,0 +1,94 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: jupiter
namespace: nearle
labels:
app: jupiter
spec:
serviceName: "jupiter"
replicas: 3
selector:
matchLabels:
app: jupiter
template:
metadata:
labels:
app: jupiter
spec:
tolerations:
- key: dedicated
operator: Equal
value: apps
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/app
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: jupiter
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: jupiter
containers:
- name: jupiter
image: nearlecommerce/jupiter:v2.7.31
imagePullPolicy: Always
ports:
- containerPort: 1009
env:
- name: PORT
value: "1009"
- name: TZ
value: "Asia/Kolkata"
volumeMounts:
- name: tz-config
mountPath: /etc/localtime
readOnly: true
- name: tz-data
mountPath: /usr/share/zoneinfo
readOnly: true
envFrom:
- configMapRef:
name: nearle-config
- secretRef:
name: app-secrets
volumes:
- name: tz-config
hostPath:
path: /usr/share/zoneinfo/Asia/Kolkata
- name: tz-data
hostPath:
path: /usr/share/zoneinfo
---
apiVersion: v1
kind: Service
metadata:
name: jupiter
namespace: nearle
labels:
app: jupiter
spec:
type: NodePort
ports:
- port: 80
targetPort: 1009
nodePort: 30822
protocol: TCP
selector:
app: jupiter

View File

@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: nearle
labels:
name: nearle

View File

@@ -0,0 +1,14 @@
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-alaska-gateway
namespace: nearle
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: alaska
to:
- group: ""
kind: Service
name: fiesta

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Secret
metadata:
name: nats-credentials
namespace: nearle
labels:
app: fiesta
type: Opaque
stringData:
username: admin
password: package@321#

View File

@@ -0,0 +1,91 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: titan
namespace: nearle
labels:
app: titan
spec:
serviceName: "titan"
replicas: 3
selector:
matchLabels:
app: titan
template:
metadata:
labels:
app: titan
spec:
tolerations:
- key: dedicated
operator: Equal
value: apps
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.workolik/app
operator: In
values:
- "true"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: titan
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: titan
containers:
- name: backend
image: groomgear/groomgear:v1.0.41
imagePullPolicy: Always
ports:
- containerPort: 1006
---
apiVersion: v1
kind: Service
metadata:
name: titan
namespace: nearle
labels:
app: titan
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 1006
protocol: TCP
selector:
app: titan
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: titan-route
namespace: nearle
labels:
app: titan
spec:
parentRefs:
- name: gateway
namespace: alaska
hostnames:
- "titan.nearle.app"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: titan
port: 80