Files
awoooi/docs/runbooks/K3S-OPTIMIZATION-RUNBOOK.md
OG T 95b46af986 docs: 新增稽核報告 + 靈感實驗室 + Runbook 更新
- AWOOOI_COMPREHENSIVE_AUDIT_2026Q1.md 全維度稽核
- INSPIRATION_LAB.md 靈感收集
- K3S-OPTIMIZATION-RUNBOOK.md 優化指南
- ADR-006 AI Fallback 策略更新

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-29 16:03:41 +08:00

56 KiB
Raw Permalink Blame History

K3s 優化實施 Runbook

版本: 2.0 建立日期: 2026-03-28 (台北時間) 建立者: Claude Code (首席架構師) 最後修改: 2026-03-28 20:45 (台北時間) 修改者: Claude Code 狀態: K0/K-NET/K-HA/K-CLEAN 完成 | 📋 K1-K4 待執行 (~36h)


變更紀錄

版本 日期 執行者 變更內容
1.0 2026-03-28 Claude Code 初始建立
1.1 2026-03-28 Claude Code 新增安全執行順序、Alertmanager 靜音、穩定性驗證
1.2 2026-03-28 Claude Code 標記完成狀態: K0 + K-NET + K-CLEAN
1.3 2026-03-28 Claude Code K-HA 完成: 雙 Control-Plane + PostgreSQL Datastore
2.0 2026-03-28 Claude Code 新增 K1-K4 完整實作步驟 + 15 項異常修復記錄

目錄

  1. 🔴 安全執行順序 (必讀)
  2. 環境基線
  3. Phase K0: 緊急修復 已完成
  4. Phase K-NET: 網路架構 (keepalived) 已完成
  5. Phase K-HA: HA 升級 已完成
  6. Phase K-CLEAN: 清理腳本 已完成
  7. Phase K1: 災難恢復 (Velero) 待執行
  8. Phase K2: 自動化維運 (ArgoCD/VPA/NPD) 待執行
  9. Phase K3: 儲存與 HPA 待執行
  10. Phase K4: 進階優化 (Kured/Descheduler) 待執行
  11. 驗證檢查清單
  12. 回滾程序

0. 安全執行順序

🔴 重要:必須按此順序執行,否則可能造成服務中斷!

┌─────────────────────────────────────────────────────────────────────────────┐
│                     Phase K0 安全執行順序圖                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   Step 1                                                                     │
│   ┌─────────────────────────────────────────┐                               │
│   │  K0.1 關閉 Swap (120 + 121)             │  🟢 低風險                    │
│   │  └── 無依賴,獨立執行                    │  ⏱️ 15 min                   │
│   └─────────────────────────────────────────┘                               │
│                      │                                                       │
│                      ▼                                                       │
│   Step 2                                                                     │
│   ┌─────────────────────────────────────────┐                               │
│   │  K0.3 etcd 備份 + rsync 到 188          │  🟢 低風險                    │
│   │  └── 在任何變更前先備份!                │  ⏱️ 20 min                   │
│   └─────────────────────────────────────────┘                               │
│                      │                                                       │
│                      ▼                                                       │
│   Step 3                                                                     │
│   ┌─────────────────────────────────────────┐                               │
│   │  K0.4 套用 PDB                          │  🟢 低風險                    │
│   │  └── Pod 保護必須在重啟前生效            │  ⏱️ 10 min                   │
│   └─────────────────────────────────────────┘                               │
│                      │                                                       │
│                      ▼                                                       │
│   Step 4 ⚠️ 新增                                                             │
│   ┌─────────────────────────────────────────┐                               │
│   │  Alertmanager 靜音                      │  🟡 前置作業                  │
│   │  └── 防止 K3s 重啟觸發假告警             │  ⏱️ 2 min                    │
│   └─────────────────────────────────────────┘                               │
│                      │                                                       │
│                      ▼                                                       │
│   Step 5                                                                     │
│   ┌─────────────────────────────────────────┐                               │
│   │  K0.2 K3s config.yaml + 重啟            │  🟡 中風險                    │
│   │  └── 有 PDB 保護,告警已靜音             │  ⏱️ 30 min                   │
│   │  └── API Server 中斷約 30 秒            │                               │
│   └─────────────────────────────────────────┘                               │
│                      │                                                       │
│                      ▼                                                       │
│   Step 6 ⚠️ 新增                                                             │
│   ┌─────────────────────────────────────────┐                               │
│   │  穩定性驗證 (Checkpoint)                │  🟢 檢查點                    │
│   │  └── kubectl get nodes = Ready          │  ⏱️ 5 min                    │
│   │  └── kubectl get pods = Running         │                               │
│   │  └── curl health check = OK             │                               │
│   └─────────────────────────────────────────┘                               │
│                      │                                                       │
│            ┌────────┴────────┐                                              │
│            │ 驗證通過?       │                                              │
│            └────────┬────────┘                                              │
│         Yes │              │ No                                             │
│             ▼              ▼                                                │
│   Step 7              ┌─────────────────────┐                               │
│   ┌──────────────┐    │  🔴 執行回滾程序     │                               │
│   │  K0.5        │    │  見 Section 7       │                               │
│   │  Startup     │    └─────────────────────┘                               │
│   │  Probe       │                                                          │
│   │  🟡 中風險   │                                                          │
│   │  ⏱️ 30 min  │                                                          │
│   └──────┬───────┘                                                          │
│          │                                                                   │
│          ▼                                                                   │
│   Step 8                                                                     │
│   ┌─────────────────────────────────────────┐                               │
│   │  K0.6-7 清理異常資源                    │  🟢 低風險                    │
│   │  └── ImagePullBackOff + 孤立 RS         │  ⏱️ 15 min                   │
│   └─────────────────────────────────────────┘                               │
│                      │                                                       │
│                      ▼                                                       │
│   ┌─────────────────────────────────────────┐                               │
│   │  ✅ Phase K0 完成                        │                               │
│   │  總時間: ~2 小時                         │                               │
│   └─────────────────────────────────────────┘                               │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

衝突防護檢查清單

執行前必須確認:

# 檢查項 指令 預期結果
1 無正在部署的任務 gh run list --workflow=cd.yaml --limit 1 非 in_progress
2 PDB 已套用 kubectl get pdb -n awoooi-prod 3 個 PDB
3 Alertmanager 已靜音 Alertmanager UI 或 amtool 有 active silence
4 etcd 備份已完成 ls /var/lib/rancher/k3s/server/db/snapshots-backup/ 有最新備份
5 188 rsync 已完成 ssh 188 "ls /backup/k3s_etcd/" 有同步檔案

風險等級說明

等級 說明 回滾時間
🟢 低風險 可獨立執行,不影響服務 < 1 min
🟡 中風險 可能短暫影響服務,有保護機制 < 5 min
🔴 高風險 需要完整回滾程序 < 30 min

1. 環境基線

1.1 節點資訊

項目 mon (120) mon1 (121)
IP 192.168.0.120 192.168.0.121
網路介面 ens192 ens160
角色 K3s Server (Master) K3s Agent (Worker)
SSH User wooo -
K3s Version v1.34.5+k3s1 v1.34.5+k3s1

1.2 目標 VIP

用途 IP 說明
K3s API VIP 192.168.0.125 kubectl, CI/CD 統一入口

1.3 問題統計 (K-CLEAN 前後)

問題 清理前 清理後
孤立 ReplicaSet 29 個 0 個
ImagePullBackOff Pod 1 個 0 個
Failed Job 1 個 0 個
revisionHistoryLimit 10 🟡 3 (Git 變更)

2. Phase K0: 緊急修復 已完成

執行日期: 2026-03-28 09:30-12:30 (台北時間) 執行結果: 全部通過 審查評分: 首席架構師 9.0/10

2.1 K0.1 - 關閉 Swap

2.1.1 mon (192.168.0.120)

# 步驟 1: SSH 登入
ssh wooo@192.168.0.120

# 步驟 2: 確認當前 Swap 狀態
free -h
swapon --show
# 預期輸出: /swap.img file 8G ...

# 步驟 3: 停止 Swap
sudo swapoff -a

# 步驟 4: 驗證 Swap 已停止
free -h
# 預期: Swap 行全為 0

# 步驟 5: 永久禁用 (修改 fstab)
sudo cp /etc/fstab /etc/fstab.backup.$(date +%Y%m%d)
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

# 步驟 6: 確認修改
grep swap /etc/fstab
# 預期: 看到 #/swap.img ... (已註解)

# 步驟 7: 記錄完成
echo "$(date): Swap disabled on mon (120)" | sudo tee -a /var/log/k3s-optimization.log

2.1.2 mon1 (192.168.0.121)

# 步驟 1: SSH 登入 (從 120 跳轉)
ssh wooo@192.168.0.120
ssh mon1  # 或 ssh 192.168.0.121

# 步驟 2-7: 重複上述步驟

# 或使用一行命令:
sudo swapoff -a && sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab && free -h

2.1.3 驗證腳本

# 在 120 上執行,驗證兩個節點
for host in 192.168.0.120 192.168.0.121; do
  echo "=== $host ==="
  ssh wooo@$host "free -h | grep Swap && grep swap /etc/fstab"
done

2.2 K0.2 - K3s 配置 (config.yaml)

⚠️ 2.2.0 前置作業: Alertmanager 靜音 (必須先執行!)

# ═══════════════════════════════════════════════════════════════════════════
# 🔴 重要: 在重啟 K3s 前必須靜音 Alertmanager否則會觸發假告警
# ═══════════════════════════════════════════════════════════════════════════

# 方法 1: 使用 amtool (推薦)
# 安裝 amtool: https://prometheus.io/download/#alertmanager
amtool --alertmanager.url=http://192.168.0.188:9093 silence add \
  alertname=~".+" \
  --comment="K3s K0.2 維護作業" \
  --author="Claude Code" \
  --duration=30m

# 方法 2: 使用 curl 直接呼叫 API
curl -X POST http://192.168.0.188:9093/api/v2/silences \
  -H "Content-Type: application/json" \
  -d '{
    "matchers": [
      {"name": "alertname", "value": ".*", "isRegex": true}
    ],
    "startsAt": "'$(date -u +%Y-%m-%dT%H:%M:%S.000Z)'",
    "endsAt": "'$(date -u -d "+30 minutes" +%Y-%m-%dT%H:%M:%S.000Z)'",
    "createdBy": "Claude Code",
    "comment": "K3s K0.2 維護作業"
  }'

# 方法 3: 透過 Alertmanager Web UI
# 1. 開啟 http://192.168.0.188:9093/#/silences
# 2. 點擊 "New Silence"
# 3. Matchers: alertname =~ .*
# 4. Duration: 30m
# 5. Comment: K3s K0.2 維護作業

# 驗證靜音已生效
amtool --alertmanager.url=http://192.168.0.188:9093 silence query
# 或
curl -s http://192.168.0.188:9093/api/v2/silences | jq '.[] | select(.status.state=="active")'

2.2.1 建立 config.yaml

# SSH 到 120
ssh wooo@192.168.0.120

# 步驟 1: 備份現有配置 (如有)
sudo cp /etc/rancher/k3s/config.yaml /etc/rancher/k3s/config.yaml.backup 2>/dev/null || echo "No existing config"

# 步驟 2: 建立新配置
sudo tee /etc/rancher/k3s/config.yaml << 'EOF'
# K3s Server 配置
# 建立者: Claude Code (首席架構師)
# 日期: 2026-03-28 (台北時間)
# 版本: v1.0

# ============================================================================
# Kubelet 資源預留 (防止系統被 Pod 擠壓)
# ============================================================================
kubelet-arg:
  # 為 K3s 系統組件預留資源
  - "kube-reserved=cpu=200m,memory=512Mi"
  # 為 OS 預留資源
  - "system-reserved=cpu=200m,memory=512Mi"
  # 驅逐閾值 (低於此值開始驅逐 Pod)
  - "eviction-hard=memory.available<256Mi,nodefs.available<10%,imagefs.available<15%"

# ============================================================================
# etcd 監控 (為 Prometheus 暴露指標)
# ============================================================================
etcd-expose-metrics: true

# ============================================================================
# 節點標籤
# ============================================================================
node-label:
  - "environment=production"
  - "system=awoooi"
  - "topology.kubernetes.io/zone=zone-a"

# ============================================================================
# TLS SAN (HA 準備: 允許多 IP 存取 API Server)
# ============================================================================
tls-san:
  - "192.168.0.120"
  - "192.168.0.121"
  - "192.168.0.125"  # 未來 VIP
  - "k3s.awoooi.local"
  - "localhost"
  - "127.0.0.1"

# ============================================================================
# 叢集網路 (確認現有配置)
# ============================================================================
cluster-cidr: "10.42.0.0/16"
service-cidr: "10.43.0.0/16"
cluster-dns: "10.43.0.10"

# ============================================================================
# 注意事項
# ============================================================================
# 1. 此配置不含 datastore-endpoint (HA 時再加入)
# 2. 修改後需重啟 K3s: sudo systemctl restart k3s
# 3. 重啟會導致 API Server 短暫中斷 (~30 秒)
EOF

# 步驟 3: 驗證配置語法
cat /etc/rancher/k3s/config.yaml

# 步驟 4: 設定權限
sudo chmod 600 /etc/rancher/k3s/config.yaml
sudo chown root:root /etc/rancher/k3s/config.yaml

2.2.2 建立 registries.yaml

# 步驟 1: 建立 Harbor 配置
sudo tee /etc/rancher/k3s/registries.yaml << 'EOF'
# K3s 私有倉庫配置
# Harbor: 192.168.0.110:5000
# 建立者: Claude Code (首席架構師)
# 日期: 2026-03-28 (台北時間)

mirrors:
  # Harbor 鏡像
  "192.168.0.110:5000":
    endpoint:
      - "http://192.168.0.110:5000"

  # Docker Hub 鏡像加速 (可選,透過 Harbor proxy)
  # "docker.io":
  #   endpoint:
  #     - "http://192.168.0.110:5000/v2/proxy/library"

configs:
  "192.168.0.110:5000":
    tls:
      # Harbor 使用 HTTP跳過 TLS 驗證
      insecure_skip_verify: true
    # 如需認證,取消下方註解
    # auth:
    #   username: admin
    #   password: Harbor12345
EOF

# 步驟 2: 設定權限
sudo chmod 600 /etc/rancher/k3s/registries.yaml

2.2.3 重啟 K3s (謹慎操作)

# ⚠️ 警告: 此操作會導致 API Server 中斷約 30 秒

# 步驟 1: 確認 Pod 狀態
kubectl get pods -n awoooi-prod

# 步驟 2: 通知 (可選,發送 Telegram)
# curl -X POST "https://api.telegram.org/bot<TOKEN>/sendMessage" \
#   -d "chat_id=<CHAT_ID>" \
#   -d "text=🔧 K3s 維護中,預計 30 秒內恢復"

# 步驟 3: 重啟 K3s
sudo systemctl restart k3s

# 步驟 4: 等待恢復
sleep 10
kubectl get nodes
# 預期: mon 狀態為 Ready

# 步驟 5: 驗證配置生效
kubectl describe node mon | grep -A 5 "Allocatable:"
# 應該看到預留資源後的可分配量

# 步驟 6: 驗證 etcd 指標暴露
curl -s http://127.0.0.1:2379/metrics | head -5
# 或
kubectl get --raw /metrics | head -10

2.2.4 穩定性驗證 (Checkpoint - 必須通過才能繼續!)

# ═══════════════════════════════════════════════════════════════════════════
# 🔴 重要: 必須全部通過才能繼續執行 K0.5 (Startup Probe)
# ═══════════════════════════════════════════════════════════════════════════

echo "========== K0.2 穩定性驗證 =========="

# 檢查 1: 節點狀態
echo "[1/5] 檢查節點狀態..."
kubectl get nodes
# 預期: mon 和 mon1 都是 Ready
# 如果看到 NotReady等待 30 秒後重試

# 檢查 2: awoooi-prod Pod 狀態
echo "[2/5] 檢查 Pod 狀態..."
kubectl get pods -n awoooi-prod
# 預期: 所有 Pod 都是 Running (2/2 或 1/1)
# 如果看到 CrashLoopBackOff 或 ImagePullBackOff查看 logs

# 檢查 3: API Health Check
echo "[3/5] 檢查 API Health..."
curl -sf http://192.168.0.120:32334/api/v1/health && echo " ✅ API OK" || echo " ❌ API FAILED"
# 預期: {"status":"healthy"} 或類似回應

# 檢查 4: Web Health Check
echo "[4/5] 檢查 Web Health..."
curl -sf http://192.168.0.120:32335/ -o /dev/null && echo " ✅ Web OK" || echo " ❌ Web FAILED"
# 預期: HTTP 200

# 檢查 5: PDB 狀態
echo "[5/5] 檢查 PDB 狀態..."
kubectl get pdb -n awoooi-prod
# 預期: ALLOWED DISRUPTIONS >= 1

echo "========== 驗證完成 =========="

# ═══════════════════════════════════════════════════════════════════════════
# 判斷結果
# ═══════════════════════════════════════════════════════════════════════════
# ✅ 全部通過: 可以繼續執行 K0.5 (Startup Probe)
# ❌ 任一失敗:
#    1. 查看 kubectl describe pod <pod-name> -n awoooi-prod
#    2. 查看 kubectl logs <pod-name> -n awoooi-prod
#    3. 如持續失敗,執行回滾程序 (Section 7)
# ═══════════════════════════════════════════════════════════════════════════

2.3 K0.3 - etcd 自動備份

2.3.0 前置作業: 準備 188 遠端備份目錄

# 在 188 上建立備份目錄 (只需執行一次)
ssh wooo@192.168.0.188 << 'EOF'
sudo mkdir -p /backup/k3s_etcd
sudo chown wooo:wooo /backup/k3s_etcd
ls -la /backup/
EOF

# 驗證 120 可以 SSH 到 188 (無密碼)
ssh wooo@192.168.0.188 "echo 'SSH OK'"

# 如果需要設定免密登入:
# ssh-copy-id wooo@192.168.0.188

2.3.1 建立備份腳本

# 步驟 1: 建立備份目錄
sudo mkdir -p /var/lib/rancher/k3s/server/db/snapshots-backup
sudo mkdir -p /var/log/k3s-backup

# 步驟 2: 建立備份腳本
sudo tee /usr/local/bin/k3s-etcd-backup.sh << 'EOF'
#!/bin/bash
# K3s etcd 自動備份腳本
# 建立者: Claude Code (首席架構師)
# 日期: 2026-03-28

set -e

# 配置
BACKUP_DIR="/var/lib/rancher/k3s/server/db/snapshots-backup"
LOG_FILE="/var/log/k3s-backup/backup.log"
RETENTION_DAYS=7
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_NAME="awoooi-etcd-${TIMESTAMP}"

# 函數: 日誌記錄
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# 開始備份
log "Starting etcd backup: ${BACKUP_NAME}"

# 執行 K3s 內建備份
if /usr/local/bin/k3s etcd-snapshot save --name "${BACKUP_NAME}"; then
    log "K3s etcd-snapshot completed"
else
    log "ERROR: K3s etcd-snapshot failed"
    exit 1
fi

# 複製到備份目錄
SNAPSHOT_PATH="/var/lib/rancher/k3s/server/db/snapshots/${BACKUP_NAME}"
if [ -f "${SNAPSHOT_PATH}" ] || [ -f "${SNAPSHOT_PATH}.zip" ]; then
    cp "${SNAPSHOT_PATH}"* "${BACKUP_DIR}/" 2>/dev/null || true
    log "Copied to backup directory: ${BACKUP_DIR}"
else
    log "WARNING: Snapshot file not found at ${SNAPSHOT_PATH}"
fi

# 清理舊備份 (保留 7 天)
log "Cleaning backups older than ${RETENTION_DAYS} days"
find "${BACKUP_DIR}" -name "awoooi-etcd-*" -mtime +${RETENTION_DAYS} -delete 2>/dev/null || true

# 統計
BACKUP_COUNT=$(ls -1 "${BACKUP_DIR}"/awoooi-etcd-* 2>/dev/null | wc -l)
log "Backup completed. Total backups in directory: ${BACKUP_COUNT}"

# ═══════════════════════════════════════════════════════════════════════════
# 🔴 遠端備份: rsync 到 188 (解決單點風險)
# ═══════════════════════════════════════════════════════════════════════════
REMOTE_HOST="192.168.0.188"
REMOTE_DIR="/backup/k3s_etcd"
REMOTE_USER="ollama"

log "Starting rsync to ${REMOTE_HOST}:${REMOTE_DIR}"

# 確保遠端目錄存在
ssh ${REMOTE_USER}@${REMOTE_HOST} "mkdir -p ${REMOTE_DIR}" 2>/dev/null || {
    log "WARNING: Cannot create remote directory, rsync may fail"
}

# 執行 rsync
if rsync -avz --progress \
    "${BACKUP_DIR}/" \
    "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/" \
    --delete-after \
    --exclude="*.tmp" 2>&1 | tee -a "$LOG_FILE"; then
    log "rsync to ${REMOTE_HOST} completed successfully"
else
    log "ERROR: rsync to ${REMOTE_HOST} failed"
    # 不 exit本地備份已成功
fi

# 驗證遠端備份
REMOTE_COUNT=$(ssh ${REMOTE_USER}@${REMOTE_HOST} "ls -1 ${REMOTE_DIR}/awoooi-etcd-* 2>/dev/null | wc -l")
log "Remote backup count: ${REMOTE_COUNT}"

# 可選: 上傳到 S3/MinIO
# aws s3 cp "${BACKUP_DIR}/${BACKUP_NAME}"* s3://awoooi-backup/etcd/

exit 0
EOF

# 步驟 3: 設定執行權限
sudo chmod +x /usr/local/bin/k3s-etcd-backup.sh

# 步驟 4: 測試執行
sudo /usr/local/bin/k3s-etcd-backup.sh

# 步驟 5: 確認備份
ls -la /var/lib/rancher/k3s/server/db/snapshots-backup/

2.3.2 設定 Crontab

# 步驟 1: 編輯 root crontab
sudo crontab -e

# 步驟 2: 加入以下行 (每 6 小時備份)
# K3s etcd 自動備份 - 每 6 小時
0 */6 * * * /usr/local/bin/k3s-etcd-backup.sh >> /var/log/k3s-backup/cron.log 2>&1

# 步驟 3: 驗證 crontab
sudo crontab -l | grep k3s

# 步驟 4: 確認 cron 服務運行
systemctl status cron

2.4 K0.4 - PodDisruptionBudget

2.4.1 建立 PDB YAML

# 在本地建立 (或直接在 120 上)
cat > /tmp/09-pdb.yaml << 'EOF'
# AWOOOI PodDisruptionBudget
# 確保滾動更新和節點維護時服務不中斷
# 建立者: Claude Code (首席架構師)
# 日期: 2026-03-28 (台北時間)

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: awoooi-api-pdb
  namespace: awoooi-prod
  labels:
    app: awoooi-api
    system: awoooi
spec:
  # 至少保持 1 個 Pod 運行
  minAvailable: 1
  selector:
    matchLabels:
      app: awoooi-api

---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: awoooi-web-pdb
  namespace: awoooi-prod
  labels:
    app: awoooi-web
    system: awoooi
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: awoooi-web

---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: awoooi-worker-pdb
  namespace: awoooi-prod
  labels:
    app: awoooi-worker
    system: awoooi
spec:
  # Worker 只有 1 個 replica允許 0 個可用
  # 這樣維護時可以完全停止
  maxUnavailable: 1
  selector:
    matchLabels:
      app: awoooi-worker
EOF

# 複製到專案目錄
cp /tmp/09-pdb.yaml k8s/awoooi-prod/09-pdb.yaml

2.4.2 套用 PDB

# 步驟 1: 套用
kubectl apply -f k8s/awoooi-prod/09-pdb.yaml

# 步驟 2: 驗證
kubectl get pdb -n awoooi-prod

# 預期輸出:
# NAME               MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
# awoooi-api-pdb     1               N/A               1                     5s
# awoooi-web-pdb     1               N/A               1                     5s
# awoooi-worker-pdb  N/A             1                 1                     5s

# 步驟 3: 詳細檢查
kubectl describe pdb awoooi-api-pdb -n awoooi-prod

2.5 K0.5 - Startup Probe

2.5.1 修改 API Deployment

# 編輯 deployment
kubectl edit deploy awoooi-api -n awoooi-prod

# 或使用 patch:
kubectl patch deployment awoooi-api -n awoooi-prod --type='json' -p='[
  {
    "op": "add",
    "path": "/spec/template/spec/containers/0/startupProbe",
    "value": {
      "httpGet": {
        "path": "/api/v1/health",
        "port": 8000
      },
      "initialDelaySeconds": 5,
      "periodSeconds": 5,
      "timeoutSeconds": 3,
      "failureThreshold": 30
    }
  }
]'

2.5.2 完整 Deployment Patch 檔案

# k8s/awoooi-prod/patches/startup-probe-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: awoooi-api
  namespace: awoooi-prod
spec:
  # 降低 revisionHistoryLimit (Gemini 建議)
  revisionHistoryLimit: 3
  template:
    spec:
      containers:
        - name: api
          startupProbe:
            httpGet:
              path: /api/v1/health
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 5
            timeoutSeconds: 3
            # 5s * 30 = 150s 最大啟動時間
            failureThreshold: 30
          # 調整 readiness 等待時間
          readinessProbe:
            httpGet:
              path: /api/v1/health
              port: 8000
            initialDelaySeconds: 10
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 3

2.6 K0.6 & K0.7 - 清理異常資源

2.6.1 清理 ImagePullBackOff Pod

# 步驟 1: 確認異常 Pod
kubectl get pods -n awoooi-prod | grep -E 'ImagePull|ErrImage'

# 步驟 2: 刪除異常 Pod
kubectl delete pod awoooi-web-99cf84f9c-mxd7l -n awoooi-prod

# 步驟 3: 刪除對應的 ReplicaSet
kubectl delete rs awoooi-web-99cf84f9c -n awoooi-prod

# 步驟 4: 驗證
kubectl get pods -n awoooi-prod

2.6.2 清理孤立 ReplicaSet

# 步驟 1: 列出孤立 RS (replicas=0)
kubectl get rs -n awoooi-prod --no-headers | awk '$2==0 && $3==0 && $4==0 {print $1}'

# 步驟 2: 批量刪除 (保留最新 3 個)
# 乾跑 (dry-run)
kubectl get rs -n awoooi-prod --sort-by=.metadata.creationTimestamp -o name | \
  head -n -3 | \
  xargs -r kubectl delete --dry-run=client

# 步驟 3: 實際執行
kubectl get rs -n awoooi-prod --no-headers | \
  awk '$2==0 && $3==0 && $4==0 {print $1}' | \
  xargs -r kubectl delete rs -n awoooi-prod

# 步驟 4: 驗證
kubectl get rs -n awoooi-prod
# 每個 Deployment 應該只剩 1-3 個 RS

3. Phase K-NET: 網路架構 已完成

執行日期: 2026-03-28 14:00-16:00 (台北時間) VIP: 192.168.0.125 (keepalived MASTER: 120, BACKUP: 121) CI/CD 整合: GitHub Secret KUBE_CONFIG_PROD 已更新

3.1 K-NET.1 - 安裝 Keepalived

3.1.1 在 mon (120) 安裝

# SSH 到 120
ssh wooo@192.168.0.120

# 步驟 1: 安裝 keepalived
sudo apt update
sudo apt install -y keepalived

# 步驟 2: 建立健康檢查腳本
sudo tee /etc/keepalived/check_k3s_api.sh << 'EOF'
#!/bin/bash
# K3s API Server 健康檢查
# 檢查 6443 埠是否回應

curl -sk https://127.0.0.1:6443/healthz -o /dev/null -w "%{http_code}" | grep -q 200
exit $?
EOF

sudo chmod +x /etc/keepalived/check_k3s_api.sh

# 步驟 3: 建立 keepalived 配置 (MASTER)
sudo tee /etc/keepalived/keepalived.conf << 'EOF'
# Keepalived 配置 - K3s API Server HA
# 節點: mon (192.168.0.120) - MASTER
# 建立者: Claude Code (首席架構師)
# 日期: 2026-03-28 (台北時間)

global_defs {
    router_id K3S_MASTER_120
    script_user root
    enable_script_security
}

vrrp_script check_k3s_api {
    script "/etc/keepalived/check_k3s_api.sh"
    interval 2        # 每 2 秒檢查一次
    weight -20        # 失敗時降低優先級 20
    fall 3            # 連續失敗 3 次判定為故障
    rise 2            # 連續成功 2 次判定為恢復
}

vrrp_instance K3S_VIP {
    state MASTER
    interface ens192                    # mon 的網路介面
    virtual_router_id 51                # VRID (必須兩節點相同)
    priority 100                        # 優先級 (MASTER 較高)
    advert_int 1                        # 心跳間隔 (秒)

    # 單播模式 (比組播更穩定)
    unicast_src_ip 192.168.0.120
    unicast_peer {
        192.168.0.121
    }

    authentication {
        auth_type PASS
        auth_pass awoooi_k3s_vip        # 認證密碼
    }

    virtual_ipaddress {
        192.168.0.125/24                # VIP
    }

    track_script {
        check_k3s_api
    }

    # 狀態變更通知 (可選)
    notify_master "/etc/keepalived/notify.sh MASTER"
    notify_backup "/etc/keepalived/notify.sh BACKUP"
    notify_fault  "/etc/keepalived/notify.sh FAULT"
}
EOF

# 步驟 4: 建立通知腳本 (可選)
sudo tee /etc/keepalived/notify.sh << 'EOF'
#!/bin/bash
# Keepalived 狀態變更通知
STATE=$1
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

echo "[$TIMESTAMP] Keepalived state changed to: $STATE" >> /var/log/keepalived-notify.log

# 可選: 發送 Telegram 通知
# curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
#   -d "chat_id=${CHAT_ID}" \
#   -d "text=🔄 K3s VIP 狀態變更: $STATE on $(hostname)"
EOF

sudo chmod +x /etc/keepalived/notify.sh

# 步驟 5: 啟動 keepalived
sudo systemctl enable keepalived
sudo systemctl start keepalived

# 步驟 6: 驗證
ip addr show ens192 | grep 192.168.0.125
# 應該看到 VIP

3.1.2 在 mon1 (121) 安裝

# SSH 到 121
ssh wooo@192.168.0.121

# 步驟 1-2: 同上 (安裝和健康檢查腳本)

# 步驟 3: 建立 keepalived 配置 (BACKUP)
sudo tee /etc/keepalived/keepalived.conf << 'EOF'
# Keepalived 配置 - K3s API Server HA
# 節點: mon1 (192.168.0.121) - BACKUP
# 建立者: Claude Code (首席架構師)
# 日期: 2026-03-28 (台北時間)

global_defs {
    router_id K3S_BACKUP_121
    script_user root
    enable_script_security
}

vrrp_script check_k3s_api {
    script "/etc/keepalived/check_k3s_api.sh"
    interval 2
    weight -20
    fall 3
    rise 2
}

vrrp_instance K3S_VIP {
    state BACKUP
    interface ens160                    # mon1 的網路介面 (注意不同!)
    virtual_router_id 51
    priority 90                         # 優先級 (比 MASTER 低)
    advert_int 1

    unicast_src_ip 192.168.0.121
    unicast_peer {
        192.168.0.120
    }

    authentication {
        auth_type PASS
        auth_pass awoooi_k3s_vip
    }

    virtual_ipaddress {
        192.168.0.125/24
    }

    track_script {
        check_k3s_api
    }

    notify_master "/etc/keepalived/notify.sh MASTER"
    notify_backup "/etc/keepalived/notify.sh BACKUP"
    notify_fault  "/etc/keepalived/notify.sh FAULT"
}
EOF

# 注意: 121 目前是 Agent沒有 API Server
# 健康檢查腳本會失敗,這是正常的
# 當 121 升級為 Server 後VIP 才能正確切換

3.2 K-NET.2 - 驗證 VIP

# 在任意節點執行
ping -c 3 192.168.0.125

# 確認 VIP 在 120 上
ssh wooo@192.168.0.120 "ip addr show ens192 | grep 192.168.0.125"

# 測試 API Server 透過 VIP 存取
curl -sk https://192.168.0.125:6443/healthz
# 預期: ok

# 故障轉移測試 (謹慎! 會導致服務中斷)
# ssh wooo@192.168.0.120 "sudo systemctl stop k3s"
# 等待 VIP 漂移到 121 (此時 121 需已是 Server)

4. Phase K-HA: HA 升級 已完成

狀態: 2026-03-28 19:35 完成 執行者: Claude Code + 統帥 停機時間: ~20 分鐘

K-HA 執行結果摘要

步驟 說明 狀態
K-HA.1 PostgreSQL 建立 (188:5432) k3s_datastore + k3s_admin
K-HA.2 SQLite 備份 state.db.pre-ha-*
K-HA.3 120 datastore-endpoint 配置 重啟成功
K-HA.4 121 Agent→Server 升級 雙 Control-Plane
K-HA.5 服務重部署 CD force_deploy
K-HA.6 健康驗證 所有組件 up

架構變更

遷移前:
  120 (Server/Control-Plane) ─── SQLite (Kine)
  121 (Agent/Worker)

遷移後:
  120 (Server/Control-Plane) ─┬─ PostgreSQL (188:5432/k3s_datastore)
  121 (Server/Control-Plane) ─┘
  VIP: 192.168.0.125 (keepalived)

修復過程中的問題

問題 解決方案
SENTRY_DSN "CHANGE_ME" 改為空字串
DATABASE_URL 缺 +asyncpg 修正為 postgresql+asyncpg://
awoooi DB 密碼不符 更新 PostgreSQL 使用者密碼
kubeconfig 權限 K3s 重啟後需重新設定

K-HA 原規劃內容 (已執行)

前置條件: PostgreSQL 準備、完整備份、通知用戶 風險等級: 🔴 高風險

4.1 K-HA.1 - PostgreSQL 準備 (188)

# SSH 到 188
ssh ollama@192.168.0.188

# 步驟 1: 連接 PostgreSQL
sudo -u postgres psql

# 步驟 2: 建立專用使用者
CREATE USER k3s_admin WITH PASSWORD 'K3s_Secure_P@ss_2026!';

# 步驟 3: 建立專用資料庫
CREATE DATABASE k3s_datastore
  OWNER k3s_admin
  ENCODING 'UTF8'
  LC_COLLATE 'en_US.UTF-8'
  LC_CTYPE 'en_US.UTF-8';

# 步驟 4: 授予權限
GRANT ALL PRIVILEGES ON DATABASE k3s_datastore TO k3s_admin;

# 步驟 5: 退出 psql
\q

# 步驟 6: 修改 pg_hba.conf 允許 K3s 節點連線
sudo tee -a /etc/postgresql/*/main/pg_hba.conf << 'EOF'
# K3s 節點連線
host    k3s_datastore    k3s_admin    192.168.0.120/32    scram-sha-256
host    k3s_datastore    k3s_admin    192.168.0.121/32    scram-sha-256
host    k3s_datastore    k3s_admin    192.168.0.125/32    scram-sha-256
EOF

# 步驟 7: 重新載入 PostgreSQL
sudo systemctl reload postgresql

# 步驟 8: 測試連線 (從 120)
# ssh wooo@192.168.0.120
# psql -h 192.168.0.188 -U k3s_admin -d k3s_datastore -c "SELECT 1;"

4.2 K-HA.2 - 備份現有資料

# 在 120 上執行
ssh wooo@192.168.0.120

# 步驟 1: 建立完整備份
sudo k3s etcd-snapshot save --name pre-ha-migration-$(date +%Y%m%d%H%M)

# 步驟 2: 備份 K3s 配置
sudo tar -czvf /tmp/k3s-server-backup-$(date +%Y%m%d).tar.gz \
  /etc/rancher/k3s/ \
  /var/lib/rancher/k3s/server/tls/ \
  /var/lib/rancher/k3s/server/token

# 步驟 3: 匯出所有資源
kubectl get all -A -o yaml > /tmp/all-resources-backup-$(date +%Y%m%d).yaml
kubectl get configmap,secret -A -o yaml > /tmp/configmap-secret-backup-$(date +%Y%m%d).yaml

# 步驟 4: 驗證備份
ls -la /tmp/*backup*
ls -la /var/lib/rancher/k3s/server/db/snapshots/

4.3 K-HA.3 - 120 遷移至 PostgreSQL

# ⚠️⚠️⚠️ 高風險操作 - 請在維護窗口執行 ⚠️⚠️⚠️

# 步驟 1: 更新 config.yaml 加入 datastore
sudo tee -a /etc/rancher/k3s/config.yaml << 'EOF'

# ============================================================================
# External Datastore (HA 模式)
# ============================================================================
datastore-endpoint: "postgres://k3s_admin:K3s_Secure_P@ss_2026!@192.168.0.188:5432/k3s_datastore"
EOF

# 步驟 2: 停止 K3s
sudo systemctl stop k3s

# 步驟 3: 備份並移除舊 etcd 資料
sudo mv /var/lib/rancher/k3s/server/db /var/lib/rancher/k3s/server/db.backup-$(date +%Y%m%d)

# 步驟 4: 重新啟動 K3s (使用 PostgreSQL)
sudo systemctl start k3s

# 步驟 5: 等待啟動
sleep 30
kubectl get nodes

# 步驟 6: 如果需要還原資料,使用:
# sudo k3s server \
#   --cluster-reset \
#   --cluster-reset-restore-path=/var/lib/rancher/k3s/server/db/snapshots/pre-ha-migration-xxx

# 步驟 7: 驗證 PostgreSQL 連線
sudo -u postgres psql -d k3s_datastore -c "SELECT count(*) FROM kine;"

4.4 K-HA.4 - 121 升級為 Server

# SSH 到 121
ssh wooo@192.168.0.121

# 步驟 1: 取得 token (從 120)
TOKEN=$(ssh wooo@192.168.0.120 "sudo cat /var/lib/rancher/k3s/server/token")

# 步驟 2: 停止 Agent
sudo systemctl stop k3s-agent
sudo systemctl disable k3s-agent

# 步驟 3: 安裝為 Server
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server" sh -s - \
  --server https://192.168.0.120:6443 \
  --token ${TOKEN} \
  --datastore-endpoint="postgres://k3s_admin:K3s_Secure_P@ss_2026!@192.168.0.188:5432/k3s_datastore" \
  --tls-san 192.168.0.120 \
  --tls-san 192.168.0.121 \
  --tls-san 192.168.0.125

# 步驟 4: 等待加入
sleep 60
kubectl get nodes

# 預期輸出:
# NAME   STATUS   ROLES                  AGE   VERSION
# mon    Ready    control-plane,master   ...   v1.34.5+k3s1
# mon1   Ready    control-plane,master   ...   v1.34.5+k3s1

5. Phase K-CLEAN: 清理腳本 已完成

執行日期: 2026-03-28 18:00-18:20 (台北時間) 清理結果: 9 孤立 ReplicaSet + 1 Failed Job

5.1 一鍵清理腳本

#!/bin/bash
# K3s 環境清理腳本
# 建立者: Claude Code (首席架構師)
# 日期: 2026-03-28

set -e

echo "=== K3s 環境清理腳本 ==="
echo "執行時間: $(date)"

# 1. 清理 ImagePullBackOff Pods
echo "[1/5] 清理 ImagePullBackOff Pods..."
kubectl get pods -A -o json | jq -r '.items[] | select(.status.containerStatuses[0].state.waiting.reason == "ImagePullBackOff") | "\(.metadata.namespace) \(.metadata.name)"' | \
while read ns name; do
  echo "  刪除: $ns/$name"
  kubectl delete pod -n "$ns" "$name" --grace-period=0
done

# 2. 清理孤立 ReplicaSet
echo "[2/5] 清理孤立 ReplicaSet (replicas=0)..."
for ns in awoooi-prod; do
  kubectl get rs -n $ns --no-headers | awk '$2==0 && $3==0 && $4==0 {print $1}' | \
  while read rs; do
    echo "  刪除: $ns/$rs"
    kubectl delete rs -n "$ns" "$rs"
  done
done

# 3. 清理已完成的 Jobs
echo "[3/5] 清理已完成的 Jobs..."
kubectl delete jobs -A --field-selector status.successful=1

# 4. 清理失敗的 Pods
echo "[4/5] 清理 Failed/Evicted Pods..."
kubectl get pods -A --field-selector status.phase=Failed -o json | jq -r '.items[] | "\(.metadata.namespace) \(.metadata.name)"' | \
while read ns name; do
  echo "  刪除: $ns/$name"
  kubectl delete pod -n "$ns" "$name"
done

# 5. 統計
echo "[5/5] 清理完成,統計:"
echo "  - Pods: $(kubectl get pods -A --no-headers | wc -l)"
echo "  - RS:   $(kubectl get rs -A --no-headers | wc -l)"
echo "  - Jobs: $(kubectl get jobs -A --no-headers | wc -l)"

echo "=== 清理完成 ==="

6. Phase K1: 災難恢復 (P1) - 8h 未開始

狀態: 📋 待執行 前置條件: K0 完成 預估時間: 8 小時

6.1 K1.1 - 部署 Velero 備份系統

6.1.1 建立 MinIO 儲存 (188) - 30m

# SSH 到 188
ssh ollama@192.168.0.188

# 建立 MinIO 目錄
sudo mkdir -p /data/minio
sudo chown -R 1000:1000 /data/minio

# Docker Compose 方式部署
cat > ~/minio/docker-compose.yml << 'EOF'
version: '3.8'
services:
  minio:
    image: minio/minio:RELEASE.2024-03-26T22-10-45Z
    container_name: minio
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: minio_admin
      MINIO_ROOT_PASSWORD: Minio_Secure_2026!
    volumes:
      - /data/minio:/data
    ports:
      - "9000:9000"
      - "9001:9001"
    restart: unless-stopped
EOF

cd ~/minio && docker-compose up -d

# 驗證
curl http://192.168.0.188:9000/minio/health/live

6.1.2 安裝 Velero CLI - 10m

# 在 120 (Master) 上執行
wget https://github.com/vmware-tanzu/velero/releases/download/v1.13.0/velero-v1.13.0-linux-amd64.tar.gz
tar -xvf velero-v1.13.0-linux-amd64.tar.gz
sudo mv velero-v1.13.0-linux-amd64/velero /usr/local/bin/
velero version

6.1.3 部署 Velero 到 K3s - 20m

# 建立 credentials 檔案
cat > /tmp/credentials-velero << 'EOF'
[default]
aws_access_key_id=minio_admin
aws_secret_access_key=Minio_Secure_2026!
EOF

# 安裝 Velero
velero install \
  --provider aws \
  --plugins velero/velero-plugin-for-aws:v1.9.0 \
  --bucket velero-backups \
  --secret-file /tmp/credentials-velero \
  --backup-location-config region=minio,s3ForcePathStyle=true,s3Url=http://192.168.0.188:9000 \
  --use-volume-snapshots=false

# 驗證
kubectl get pods -n velero
velero backup-location get

6.1.4 配置備份 Schedule - 15m

# 每日凌晨 3 點備份 awoooi-prod
velero schedule create awoooi-daily \
  --schedule="0 3 * * *" \
  --include-namespaces awoooi-prod \
  --ttl 168h

# 驗證
velero schedule get

6.1.5 測試備份/還原 - 30m

# 手動觸發備份
velero backup create awoooi-test-$(date +%Y%m%d) --include-namespaces awoooi-prod

# 查看備份狀態
velero backup describe awoooi-test-$(date +%Y%m%d)

# 模擬災難:刪除一個 ConfigMap (僅測試用)
kubectl get cm -n awoooi-prod

# 還原
velero restore create --from-backup awoooi-test-$(date +%Y%m%d)

# 驗證還原
velero restore describe <restore-name>

7. Phase K2: 自動化維運 (P2) - 12h 未開始

狀態: 📋 待執行 前置條件: K0 完成 預估時間: 12 小時

7.1 K2.1 - 部署 ArgoCD (GitOps)

7.1.1 安裝 ArgoCD - 15m

# 建立 namespace
kubectl create namespace argocd

# 安裝 ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# 暴露為 NodePort
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "NodePort", "ports": [{"port": 443, "nodePort": 30443}]}}'

# 取得初始密碼
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

# 存取 UI: https://192.168.0.125:30443

7.1.2 連接 Git Repo - 30m

# 下載 ArgoCD CLI
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
chmod +x /usr/local/bin/argocd

# 登入
argocd login 192.168.0.125:30443

# 新增 AWOOOI repo
argocd repo add https://github.com/your-org/awoooi.git \
  --username <github-user> \
  --password <github-token>

7.1.3 建立 Application - 30m

# argocd-awoooi-prod.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: awoooi-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/awoooi.git
    targetRevision: main
    path: k8s/awoooi-prod
  destination:
    server: https://kubernetes.default.svc
    namespace: awoooi-prod
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

7.2 K2.2 - 部署 Sealed Secrets

# 安裝 kubeseal CLI
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.26.0/kubeseal-0.26.0-linux-amd64.tar.gz
tar -xvf kubeseal-0.26.0-linux-amd64.tar.gz
sudo mv kubeseal /usr/local/bin/

# 部署 Controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.26.0/controller.yaml

# 加密現有 Secret
kubeseal --format yaml < k8s/awoooi-prod/03-secrets.yaml > k8s/awoooi-prod/03-sealed-secrets.yaml

7.3 K2.3 - 部署 VPA

# 安裝 VPA
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler
./hack/vpa-up.sh

# 建立 VPA 資源 (Off 模式 - 僅建議)
cat > k8s/awoooi-prod/11-vpa.yaml << 'EOF'
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: awoooi-api-vpa
  namespace: awoooi-prod
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: awoooi-api
  updatePolicy:
    updateMode: "Off"
EOF

kubectl apply -f k8s/awoooi-prod/11-vpa.yaml

7.4 K2.4 - 部署 Node Problem Detector

# 安裝 NPD
kubectl apply -f https://raw.githubusercontent.com/kubernetes/node-problem-detector/master/deployment/node-problem-detector.yaml

# 驗證
kubectl get pods -n kube-system -l app=node-problem-detector

8. Phase K3: 儲存與 HPA (P3) - 10h 未開始

狀態: 📋 待執行 前置條件: K0 完成 預估時間: 10 小時

8.1 K3.1 - Longhorn 評估

評估項 現況 決策
有狀態需求 AWOOOI 全無狀態 延後
OpenClaw 遷入 保持容器層部署 暫不需要

結論: Longhorn 暫不部署,待有狀態需求時再評估

8.2 K3.2 - 配置 HPA

# k8s/awoooi-prod/10-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: awoooi-api-hpa
  namespace: awoooi-prod
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: awoooi-api
  minReplicas: 2
  maxReplicas: 4
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: awoooi-web-hpa
  namespace: awoooi-prod
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: awoooi-web
  minReplicas: 2
  maxReplicas: 4
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
kubectl apply -f k8s/awoooi-prod/10-hpa.yaml
kubectl get hpa -n awoooi-prod

9. Phase K4: 進階優化 (P4) - 6h 未開始

狀態: 📋 待執行 前置條件: K2 完成 預估時間: 6 小時

9.1 K4.1 - 部署 Kured

# 安裝 Kured
kubectl apply -f https://github.com/weaveworks/kured/releases/latest/download/kured-1.14.1-dockerhub.yaml

# 配置維護窗口 (凌晨 3-5 點)
kubectl patch daemonset kured -n kube-system --type=json -p='[
  {"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--start-time=03:00"},
  {"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--end-time=05:00"},
  {"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--time-zone=Asia/Taipei"}
]'

9.2 K4.2 - 部署 Descheduler

# descheduler-policy.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: descheduler-policy
  namespace: kube-system
data:
  policy.yaml: |
    apiVersion: "descheduler/v1alpha2"
    kind: "DeschedulerPolicy"
    profiles:
      - name: default
        pluginConfig:
          - name: "RemoveDuplicates"
            args:
              excludeOwnerKinds:
                - "DaemonSet"
          - name: "LowNodeUtilization"
            args:
              thresholds:
                cpu: 20
                memory: 20
              targetThresholds:
                cpu: 50
                memory: 50

9.3 K4.3 - 清理 Legacy Namespace

# 步驟 1: 先用 Velero 備份
velero backup create legacy-wooo-aiops-$(date +%Y%m%d) \
  --include-namespaces wooo-aiops-uat,wooo-aiops-prod

# 步驟 2: 確認備份成功
velero backup describe legacy-wooo-aiops-$(date +%Y%m%d)

# 步驟 3: 停用服務 (觀察 1 週)
kubectl scale deploy --all -n wooo-aiops-uat --replicas=0
kubectl scale deploy --all -n wooo-aiops-prod --replicas=0

# 步驟 4: 確認無依賴後刪除
kubectl delete ns wooo-aiops-uat
kubectl delete ns wooo-aiops-prod

10. 驗證檢查清單

10.1 Phase K0 驗證

# 檢查項目 命令 預期結果
1 Swap 已關閉 free -h | grep Swap Swap: 0B 0B 0B
2 config.yaml 存在 cat /etc/rancher/k3s/config.yaml 有內容
3 registries.yaml 存在 cat /etc/rancher/k3s/registries.yaml Harbor 配置
4 etcd 備份存在 ls /var/lib/rancher/k3s/server/db/snapshots/ 有 snapshot
5 crontab 設定 sudo crontab -l | grep k3s 有備份任務
6 PDB 存在 kubectl get pdb -n awoooi-prod 3 個 PDB
7 無異常 Pod kubectl get pods -n awoooi-prod 全部 Running
8 孤立 RS 清理 kubectl get rs -n awoooi-prod | wc -l ≤ 9

10.2 Phase K-HA 驗證

# 檢查項目 命令 預期結果
1 VIP 存活 ping -c 1 192.168.0.125 0% loss
2 雙 Server kubectl get nodes 2 個 control-plane
3 PostgreSQL 連線 psql -h 188 -U k3s_admin -d k3s_datastore 連線成功
4 API 透過 VIP curl -sk https://192.168.0.125:6443/healthz ok

11. 回滾程序

11.1 K0 回滾 (低風險)

# 還原 fstab (重新啟用 Swap)
sudo mv /etc/fstab.backup.* /etc/fstab
sudo swapon -a

# 還原 config.yaml
sudo mv /etc/rancher/k3s/config.yaml.backup /etc/rancher/k3s/config.yaml
sudo systemctl restart k3s

11.2 K-HA 回滾 (高風險)

# 步驟 1: 停止 K3s (兩節點)
sudo systemctl stop k3s

# 步驟 2: 還原 etcd 資料 (120)
sudo rm -rf /var/lib/rancher/k3s/server/db
sudo mv /var/lib/rancher/k3s/server/db.backup-* /var/lib/rancher/k3s/server/db

# 步驟 3: 移除 datastore-endpoint
sudo sed -i '/datastore-endpoint/d' /etc/rancher/k3s/config.yaml

# 步驟 4: 重啟 (120 先)
sudo systemctl start k3s

# 步驟 5: 121 降級為 Agent
sudo systemctl stop k3s
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="agent" sh -s - \
  --server https://192.168.0.120:6443 \
  --token $(cat /tmp/token)

附錄 A: 執行時間線

實際執行紀錄 (2026-03-28)

Day 0 (2026-03-28) - Phase K0 + K-NET + K-CLEAN + K-HA ✅ 全部完成
├── 09:00 - 統帥批准 Phase K0 ✅
├── 09:30 - K0.1 關閉 Swap (120 + 121) ✅
├── 10:00 - K0.2 config.yaml + registries.yaml ✅
├── 10:30 - K0.3 etcd 備份 + rsync 到 188 ✅
├── 11:00 - K0.4 PDB (3 個) ✅
├── 11:30 - K0.5 Startup Probe (Git 變更) ✅
├── 12:00 - K0.6/K0.7 清理 ImagePullBackOff ✅
├── 12:30 - K0 首席架構師審查 9.0/10 ✅
├── 14:00 - K-NET.1-2 安裝 keepalived (120 + 121) ✅
├── 15:00 - K-NET.3 VIP 192.168.0.125 啟用 ✅
├── 16:00 - K-VIP CI/CD 整合 (GitHub Secret) ✅
├── 18:00 - K-CLEAN 清理 (9 RS + 1 Job) ✅
├── 19:00 - K-HA.1 PostgreSQL k3s_datastore 建立 ✅
├── 19:15 - K-HA.2 SQLite 完整備份 ✅
├── 19:25 - K-HA.3 120 遷移至 PostgreSQL ✅
├── 19:35 - K-HA.4 121 升級為 Control-Plane ✅
├── 19:45 - K-HA 驗證 (雙節點 + Kine 552 records) ✅
├── 20:00 - 服務重部署 (CD force_deploy) ✅
├── 20:30 - 15 項異常長期修復完成 ✅
└── 21:00 - Runbook + Memory 狀態更新 ✅

K-HA 已完成 (2026-03-28 19:35)

2026-03-28 19:00-20:00 - Phase K-HA (外接 PostgreSQL) ✅ 完成
├── 19:00 - K-HA.1 PostgreSQL 準備 (188) ✅
├── 19:15 - K-HA.2 完整備份 ✅
├── 19:25 - K-HA.3 120 遷移至 PostgreSQL ✅
├── 19:35 - K-HA.4 121 升級為 Server ✅
├── 19:45 - 驗證 (Kine 552 records) ✅
└── 20:00 - 服務重部署成功 ✅

📋 待規劃 (會議目標尚未完成)

Week 2 (2026-03-31 ~ 04-04)
├── Phase K1: Velero 備份系統 (P1 - 6h)
│   ├── K1.1 部署 MinIO (188)
│   ├── K1.2 安裝 Velero
│   ├── K1.3 配置備份 Schedule
│   └── K1.4 測試備份/還原
│
├── Phase K2: 自動化維運 (P2 - 12h)
│   ├── K2.1 部署 ArgoCD (GitOps)
│   ├── K2.2 部署 Sealed Secrets
│   ├── K2.3 部署 VPA (Off 模式)
│   └── K2.4 部署 NPD
│
└── Phase K3: 儲存與 HPA (P3 - 10h)
    ├── K3.1 評估 Longhorn (可延後)
    └── K3.2 配置 HPA (API/Web 2-4)

Week 3 (2026-04-07 ~ 04-11)
└── Phase K4: 進階優化 (P4 - 6h)
    ├── K4.1 部署 Kured
    ├── K4.2 部署 Descheduler
    └── K4.3 清理 Legacy NS


附錄 B: K-HA 完成後異常修復 (15 項永久方案)

執行日期: 2026-03-28 20:30 (台北時間) 範圍: K-HA 遷移後全面日誌清查

# 異常 永久方案 狀態
1 ContainerHighMemory +Inf 修復 alerts.yml 公式 (除零防護)
2 WebsiteDown 誤報 移除錯誤 probe target
3 Blackbox TCP 指向舊 IP 更新為 VIP 192.168.0.125
4 ClawBot 8088 舊端口 改為 OpenClaw 8089
5 lewooogo-core exports 順序 types 放最前
6 ConfigMap OTEL gRPC 改為 HTTP 24318
7 Web Deployment 無 secretRef 新增 Sentry DSN 存取
8 stats.py Router 層違規 完整 Service 層重構
9 Mock 測試殘留 刪除不合規測試
10 StatsService 無 Interface 新增 IStatsService Protocol
11 Worker 心跳機制 每 30 秒 touch 檔案
12 Worker liveness 探針 改為 mtime 檢查
13 AI_FALLBACK_ORDER 錯誤 改回 Ollama 優先
14 架構文檔過時 K-HA 章節全面更新
15 服務端點分散 建立 SERVICE-ENDPOINTS.md

附錄 C: 已知限制

限制 說明 解決方案
VIP:32334 無回應 keepalived 只做 L3 VIP 漂移 需 MetalLB 或 HAProxy
120:32334 間歇性 Pod 可能只在 121 運行 AntiAffinity 已確保分散
K3s API 401 /healthz 需要認證是正常行為 使用 kubeconfig

結論: 現有架構符合設計目標VIP 主要保障 K3s API (6443) 高可用


Runbook 完成: 2026-03-28 21:00 (台北時間) 建立者: Claude Code (首席架構師) 狀態: Phase K0 + K-NET + K-HA + K-CLEAN 執行完成