Files
awoooi/k8s/awoooi-prod/06-deployment-api.yaml
2026-07-15 02:04:56 +08:00

363 lines
16 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-14 Codex: keep the bounded two-replica serving topology only with
# startup DDL delegated to CD migration preflight below. The initial canary
# briefly reached 2/2 Ready and 200m/238m CPU, then failed when both API pods
# re-entered init_db and saturated at the 1 CPU limit. The independent
# verifier therefore requires the DDL gate, DB CONNECTION LIMIT 12 receipt,
# 2/2 Ready, zero restart delta, and unsaturated CPU for a full probe window.
# Roll back to replicas=1 if any post-deploy condition fails.
replicas: 2
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: "31de3a709815f33a11ba6de6e78b771b7160a245"
- 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: "31de3a709815f33a11ba6de6e78b771b7160a245"
- 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: DATABASE_BOOTSTRAP_DDL_ENABLED
# 2026-07-14 Codex: production schema DDL has one owner. CD
# migration preflight applies/verifies it before API rollout;
# API startup emits an explicit no-write ownership receipt.
value: "false"
- 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: WAZUH_API_BASE_URL
valueFrom:
secretKeyRef: {name: awoooi-secrets, key: WAZUH_API_BASE_URL, optional: true}
- name: WAZUH_API_USERNAME
valueFrom:
secretKeyRef: {name: awoooi-secrets, key: WAZUH_API_USERNAME, optional: true}
- name: WAZUH_API_PASSWORD
valueFrom:
secretKeyRef: {name: awoooi-secrets, key: WAZUH_API_PASSWORD, optional: true}
- name: USE_AI_ROUTER
value: "true"
- name: ENABLE_NEMOTRON_COLLABORATION
# Strict incident provider chain forbids Nemo/NVIDIA/Claude insertion.
value: "false"
- 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:
# The production 8-way read verifier saturated the former 1-core
# limit and caused liveness restarts despite ample node/quota headroom.
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2"
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