339 lines
14 KiB
YAML
339 lines
14 KiB
YAML
# AWOOOI Backend (FastAPI) Deployment
|
||
# 負責人: CIO
|
||
# 版本: v1.1
|
||
# 日期: 2026-03-20
|
||
# 更新: 2026-03-28 - 新增 startupProbe + revisionHistoryLimit:3 (Phase K0.5/K0.7)
|
||
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: awoooi-api
|
||
namespace: awoooi-prod
|
||
labels:
|
||
app: awoooi-api
|
||
system: awoooi
|
||
environment: prod
|
||
spec:
|
||
# 2026-07-01 Codex: temporary post-reboot recovery cap. The production
|
||
# database role `awoooi` currently has CONNECTION LIMIT 2, so a two-replica
|
||
# API rollout deadlocks when the old pod still holds DB connections. Restore
|
||
# replicas=2 only after the DB role limit / pool budget is raised and verified.
|
||
replicas: 1
|
||
revisionHistoryLimit: 3
|
||
selector:
|
||
matchLabels:
|
||
app: awoooi-api
|
||
environment: prod
|
||
system: awoooi
|
||
strategy:
|
||
type: RollingUpdate
|
||
rollingUpdate:
|
||
# 2026-06-13 Codex: no surge keeps topology spread honest during rollouts.
|
||
# With surge=1, two new pods can both schedule to the opposite node while
|
||
# old pods are terminating, then become imbalanced after old pods exit.
|
||
maxSurge: 0
|
||
# 2026-05-24 Codex: allow one unavailable replica so rollout can replace
|
||
# a bad old ReplicaSet instead of deadlocking at 1/2 when probes regress.
|
||
maxUnavailable: 1
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: awoooi-api
|
||
system: awoooi
|
||
environment: prod
|
||
annotations:
|
||
awoooi.dev/topology-rebalance-generation: "2026-06-13T13:05:00+08:00"
|
||
spec:
|
||
# 2026-07-01 Codex: topology spread must not deadlock rollouts when the
|
||
# active prod placement is pinned to one eligible node after reboot.
|
||
# Keep the skew preference, but allow scheduling so GitOps deploys can finish.
|
||
topologySpreadConstraints:
|
||
- maxSkew: 1
|
||
topologyKey: kubernetes.io/hostname
|
||
whenUnsatisfiable: ScheduleAnyway
|
||
labelSelector:
|
||
matchLabels:
|
||
app: awoooi-api
|
||
environment: prod
|
||
system: awoooi
|
||
# API is read-only. All Kubernetes/host mutations belong to the worker.
|
||
serviceAccountName: awoooi-api
|
||
automountServiceAccountToken: true
|
||
containers:
|
||
- name: api
|
||
# 映像標籤由 CI/CD 動態注入 (格式: {sha}-{run_id})
|
||
# Harbor 金庫: 110 主機 (192.168.0.110:5000)
|
||
image: 192.168.0.110:5000/library/api:IMAGE_TAG_PLACEHOLDER
|
||
imagePullPolicy: Always
|
||
ports:
|
||
- containerPort: 8000
|
||
name: http
|
||
envFrom:
|
||
- configMapRef:
|
||
name: awoooi-config
|
||
# 2026-04-12 ogt: env 優先於 envFrom — 覆蓋 configmap 特定值
|
||
# 說明: Kubernetes env: 優先於 envFrom:,用於 live-patch 後需同步回 Git
|
||
env:
|
||
- name: DATABASE_URL
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: awoooi-api-db
|
||
key: DATABASE_URL
|
||
- name: REDIS_URL
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: awoooi-secrets
|
||
key: REDIS_URL
|
||
optional: true
|
||
- name: GEMINI_API_KEY
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: GEMINI_API_KEY, optional: true}
|
||
- name: CLAUDE_API_KEY
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: CLAUDE_API_KEY, optional: true}
|
||
- name: ANTHROPIC_API_KEY
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: ANTHROPIC_API_KEY, optional: true}
|
||
- name: NVIDIA_API_KEY
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: NVIDIA_API_KEY, optional: true}
|
||
- name: OPENCLAW_TG_BOT_TOKEN
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: OPENCLAW_TG_BOT_TOKEN, optional: true}
|
||
- name: OPENCLAW_TG_CHAT_ID
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: OPENCLAW_TG_CHAT_ID, optional: true}
|
||
- name: OPENCLAW_TG_USER_WHITELIST
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: OPENCLAW_TG_USER_WHITELIST, optional: true}
|
||
- name: SRE_GROUP_CHAT_ID
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: SRE_GROUP_CHAT_ID, optional: true}
|
||
- name: WEBHOOK_HMAC_SECRET
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: WEBHOOK_HMAC_SECRET, optional: true}
|
||
- name: AWOOOP_OPERATOR_API_KEY
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: AWOOOP_OPERATOR_API_KEY, optional: true}
|
||
- name: JWT_SECRET
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: JWT_SECRET, optional: true}
|
||
- name: JWT_ALGORITHM
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: JWT_ALGORITHM, optional: true}
|
||
- name: LANGFUSE_PUBLIC_KEY
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: LANGFUSE_PUBLIC_KEY, optional: true}
|
||
- name: LANGFUSE_SECRET_KEY
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: LANGFUSE_SECRET_KEY, optional: true}
|
||
- name: SENTRY_DSN
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: SENTRY_DSN, optional: true}
|
||
- name: SENTRY_AUTH_TOKEN
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: SENTRY_AUTH_TOKEN, optional: true}
|
||
- name: ARGOCD_API_TOKEN
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: ARGOCD_API_TOKEN, optional: true}
|
||
- name: GITEA_API_TOKEN
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: GITEA_API_TOKEN, optional: true}
|
||
- name: GITEA_WEBHOOK_SECRET
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: GITEA_WEBHOOK_SECRET, optional: true}
|
||
- name: NEMOTRON_BOT_TOKEN
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: NEMOTRON_BOT_TOKEN, optional: true}
|
||
- name: OPENCLAW_BOT_TOKEN
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: OPENCLAW_BOT_TOKEN, optional: true}
|
||
- name: SMTP_HOST
|
||
valueFrom:
|
||
secretKeyRef: {name: awoooi-secrets, key: SMTP_HOST, optional: true}
|
||
- name: AWOOOI_BUILD_COMMIT_SHA
|
||
# 2026-06-29 Codex: CD rewrites this to the deployed image tag so
|
||
# production deploy readback does not rely on a stale static snapshot.
|
||
value: "8424af5343422cafb81eb888019631c39d212671"
|
||
- name: AWOOOI_DESIRED_API_IMAGE_TAG
|
||
# 2026-06-30 Codex: CD rewrites this alongside AWOOOI_BUILD_COMMIT_SHA.
|
||
# Production readback compares runtime image truth against this
|
||
# GitOps desired tag instead of doing a slow Gitea raw fetch.
|
||
value: "8424af5343422cafb81eb888019631c39d212671"
|
||
- name: DATABASE_POOL_SIZE
|
||
# 2026-07-01 Codex: production role `awoooi` currently has a low
|
||
# connection limit. Keep API pool conservative until DB role
|
||
# limit is raised and verified.
|
||
value: "1"
|
||
- name: DATABASE_MAX_OVERFLOW
|
||
value: "0"
|
||
- name: DATABASE_NULL_POOL
|
||
# 2026-07-11 Codex: release each API connection after use while
|
||
# workload-specific DB identities are being split and verified.
|
||
value: "true"
|
||
- name: IWOOOS_WAZUH_READONLY_ENABLED
|
||
# 2026-06-30 Codex: controlled GitOps enablement after owner
|
||
# metadata, manager registry acceptance, dry-run, rollback, and
|
||
# post-enable verifier refs are committed. Secret values remain
|
||
# injected only through awoooi-secrets via envFrom above.
|
||
value: "true"
|
||
- name: IWOOOS_WAZUH_EXPECTED_MIN_AGENT_COUNT
|
||
value: "6"
|
||
- name: USE_AI_ROUTER
|
||
value: "true"
|
||
- name: ENABLE_NEMOTRON_COLLABORATION
|
||
# 2026-04-15 ogt: 重新啟用 — asyncio.wait_for=120s,Ollama 已等待回應
|
||
value: "true"
|
||
- name: NEMOTRON_TIMEOUT_SECONDS
|
||
value: "55"
|
||
- name: TELEGRAM_ENABLE_POLLING
|
||
# 188/OpenClaw is the single getUpdates owner.
|
||
value: "false"
|
||
- name: OLLAMA_URL
|
||
value: "http://192.168.0.110:11435" # 2026-05-25 Codex: GCP-A via 110 proxy; health cooldown protects noisy offline probes
|
||
- name: OLLAMA_SECONDARY_URL
|
||
value: "http://192.168.0.110:11436" # 2026-05-25 Codex: GCP-B via 110 proxy; fallback only after GCP-A is unavailable
|
||
- name: OLLAMA_FALLBACK_URL
|
||
value: "http://192.168.0.110:11437" # 2026-05-25 Codex: 111 via 110 proxy before Gemini
|
||
- name: ALERT_AI_ALLOW_CLOUD_FALLBACK
|
||
value: "true" # Gemini 只作 GCP-A → GCP-B → 111 全失敗後的備援
|
||
- name: ALERT_AI_ENFORCE_OLLAMA_FIRST
|
||
value: "true" # 告警診斷強制先走 GCP-A → GCP-B → 111
|
||
- name: ALERT_OLLAMA_MODEL
|
||
value: "qwen3:14b" # 2026-05-05 Codex: 告警以解決問題為目標,可等待深度診斷
|
||
- name: OLLAMA_HEALTH_CHECK_MODEL
|
||
value: "gemma3:4b" # 2026-05-05 Codex: 避免 health probe 載入 qwen2.5 7B 污染 GCP alert lane
|
||
- name: OLLAMA_EMBEDDING_MODEL
|
||
value: "bge-m3:latest"
|
||
- name: OPENCLAW_DEFAULT_MODEL
|
||
value: "qwen2.5:7b-instruct"
|
||
- name: OPENCLAW_TIMEOUT
|
||
value: "120"
|
||
- name: OLLAMA_DIAGNOSE_TIMEOUT_SECONDS
|
||
value: "300"
|
||
- name: INCIDENT_LLM_TIMEOUT_SECONDS
|
||
value: "360"
|
||
- name: AGENT_DEBATE_GLOBAL_TIMEOUT_SEC
|
||
value: "420"
|
||
- name: AGENT_DIAGNOSTICIAN_TIMEOUT_SEC
|
||
value: "100"
|
||
- name: AGENT_SOLVER_TIMEOUT_SEC
|
||
value: "80"
|
||
- name: PROMETHEUS_MULTIPROC_DIR
|
||
value: "/tmp/awoooi-prometheus-multiproc"
|
||
- name: SSH_MCP_ENABLED
|
||
value: "false"
|
||
- name: ENABLE_AWOOOP_ANSIBLE_CHECK_MODE_WORKER
|
||
value: "false"
|
||
- name: ENABLE_IWOOOS_WAZUH_MANAGER_POSTURE_EXECUTOR
|
||
value: "true"
|
||
- name: AWOOOP_ANSIBLE_CHECK_MODE_INTERVAL_SECONDS
|
||
value: "300"
|
||
- name: AWOOOP_ANSIBLE_CHECK_MODE_BATCH_LIMIT
|
||
value: "1"
|
||
- name: AWOOOP_ANSIBLE_CHECK_MODE_TIMEOUT_SECONDS
|
||
value: "180"
|
||
- name: AWOOOP_ANSIBLE_CHECK_MODE_STARTUP_SLEEP_SECONDS
|
||
value: "120"
|
||
- name: AWOOOP_ANSIBLE_CHECK_MODE_TRANSPORT_PROFILE
|
||
value: "ssh_mcp"
|
||
- name: AWOOOP_ANSIBLE_CHECK_MODE_CANDIDATE_MAX_AGE_HOURS
|
||
value: "24"
|
||
- name: AWOOOP_ANSIBLE_CHECK_MODE_TRANSPORT_COOLDOWN_SECONDS
|
||
value: "21600"
|
||
- name: ENABLE_AWOOOP_ANSIBLE_CANDIDATE_BACKFILL_WORKER
|
||
value: "false"
|
||
- name: AWOOOP_ANSIBLE_CANDIDATE_BACKFILL_INTERVAL_SECONDS
|
||
value: "600"
|
||
- name: AWOOOP_ANSIBLE_CANDIDATE_BACKFILL_BATCH_LIMIT
|
||
value: "2"
|
||
- name: AWOOOP_ANSIBLE_CANDIDATE_BACKFILL_WINDOW_HOURS
|
||
value: "24"
|
||
- name: AWOOOP_ANSIBLE_CANDIDATE_BACKFILL_STARTUP_SLEEP_SECONDS
|
||
value: "60"
|
||
- name: AWOOOI_REBOOT_SLO_PROMETHEUS_READBACK_ENABLED
|
||
value: "true"
|
||
- name: AWOOOI_REBOOT_SLO_PROMETHEUS_QUERY_URL
|
||
value: "http://192.168.0.110:9090/api/v1/query"
|
||
volumeMounts:
|
||
# 2026-04-08 Claude Sonnet 4.6: Sprint 5.1 Guardrail — service registry YAML
|
||
# 掛載到 /app/ops/config/ 讓 _find_registry_path() 可找到
|
||
- name: service-registry
|
||
mountPath: /app/ops/config/service-registry.yaml
|
||
subPath: service-registry.yaml
|
||
readOnly: true
|
||
- name: prometheus-multiproc
|
||
mountPath: /tmp/awoooi-prometheus-multiproc
|
||
resources:
|
||
requests:
|
||
cpu: "200m"
|
||
memory: "512Mi"
|
||
limits:
|
||
cpu: "1"
|
||
memory: "1Gi"
|
||
livenessProbe:
|
||
httpGet:
|
||
# 2026-05-24 Codex: K8s probes must stay lightweight. The full
|
||
# health endpoint checks Ollama/OpenClaw/SigNoz and can exceed
|
||
# kubelet timeout when an external provider is degraded.
|
||
path: /api/v1/health/live
|
||
port: 8000
|
||
initialDelaySeconds: 30
|
||
periodSeconds: 10
|
||
timeoutSeconds: 5
|
||
failureThreshold: 3
|
||
readinessProbe:
|
||
httpGet:
|
||
path: /api/v1/health/ready
|
||
port: 8000
|
||
initialDelaySeconds: 5
|
||
periodSeconds: 5
|
||
timeoutSeconds: 3
|
||
failureThreshold: 3
|
||
# 2026-06-03 Codex: DB bootstrap DDL + background worker wiring can exceed
|
||
# 60s during rolling deploys. Keep liveness strict after startup, but allow
|
||
# cold-start to finish instead of killing the pod mid-bootstrap.
|
||
startupProbe:
|
||
httpGet:
|
||
path: /api/v1/health/live
|
||
port: 8000
|
||
initialDelaySeconds: 5
|
||
periodSeconds: 5
|
||
timeoutSeconds: 5
|
||
failureThreshold: 60
|
||
# 反親和性 - 分散到不同節點
|
||
affinity:
|
||
podAntiAffinity:
|
||
preferredDuringSchedulingIgnoredDuringExecution:
|
||
- weight: 100
|
||
podAffinityTerm:
|
||
labelSelector:
|
||
matchLabels:
|
||
app: awoooi-api
|
||
topologyKey: kubernetes.io/hostname
|
||
volumes:
|
||
# 2026-04-08 Claude Sonnet 4.6: Sprint 5.1 Guardrail — service registry
|
||
- name: service-registry
|
||
configMap:
|
||
name: service-registry
|
||
- name: prometheus-multiproc
|
||
emptyDir: {}
|
||
|
||
---
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: awoooi-api-svc
|
||
namespace: awoooi-prod
|
||
labels:
|
||
app: awoooi-api
|
||
spec:
|
||
type: NodePort
|
||
selector:
|
||
app: awoooi-api
|
||
ports:
|
||
- port: 8000
|
||
targetPort: 8000
|
||
nodePort: 32334
|
||
name: http
|