Files
awoooi/k8s/awoooi-prod/06-deployment-api.yaml
2026-07-11 20:25:19 +08:00

339 lines
14 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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=120sOllama 已等待回應
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