# 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: AGENT99_SRE_ALERT_RELAY_TOKEN valueFrom: secretKeyRef: {name: awoooi-secrets, key: AGENT99_SRE_ALERT_RELAY_TOKEN, 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: "afe15662f339660db9c8fc9270e27217358df311" - 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: "afe15662f339660db9c8fc9270e27217358df311" - 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: AWOOOI_API_BACKGROUND_TASKS_ENABLED value: "false" - 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