新增: - ADR-025: 告警鏈路 E2E 驗證架構 (2026-03-26 事故教訓) 更新: - ADR-011: 新增 DNS 規則最佳實踐 (附錄 B) - Skill 04: 新增 NetworkPolicy DNS 規則 + CoreDNS 設定 - Skill 05: 新增告警鏈路 Smoke Test 要求 - CLAUDE.md: 新增告警鏈路驗證到任務前必讀 事故根因: 1. URL 路徑錯誤 (webhook vs webhooks) 2. NetworkPolicy DNS 規則標籤不匹配 3. CoreDNS 上游 DNS 依賴 systemd-resolved Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
12 KiB
12 KiB
ADR-011: NetworkPolicy 變更治理架構
狀態: 提案 日期: 2026-03-23 決策者: 統帥 觸發: 多次 NetworkPolicy 變更導致生產事故
問題陳述
事故時間線:
├── 2026-03-20: Worker 無法連 Redis → 發現 Egress 被阻擋
├── 2026-03-22: OTEL 上報失敗 → 發現 Port 24317/24318 未開
├── 2026-03-23: Y 按鈕執行超時 → K8s API 192.168.0.120:6443 未開
└── 每次都是「事後診斷」才發現 NetworkPolicy 問題
根本原因:
- 任何有 kubectl 權限的人都可以直接修改 NetworkPolicy
- 修改後沒有任何告警或審計
- Git 版本與叢集版本經常不同步
- 沒有 Dry-Run / Diff 機制防止錯誤
決策:三層防護架構
┌──────────────────────────────────────────────────────────────────────┐
│ NetworkPolicy 變更治理架構 │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ Layer 1: GitOps (Single Source of Truth) │
│ ══════════════════════════════════════════ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 開發者 │ ──▶ │ PR 審核 │ ──▶ │ ArgoCD │ │
│ │ 修改 YAML │ │ 至少 2 人 │ │ 自動同步 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Git │ │ GitHub │ │ K8s │ │
│ │ Commit │ │ Actions │ │ Cluster │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ Layer 2: Policy Validation (防錯) │
│ ═══════════════════════════════════ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Kyverno / OPA Gatekeeper │ │
│ │ ──────────────────────────────── │ │
│ │ Rule 1: NetworkPolicy 必須有 system 標籤 │ │
│ │ Rule 2: 禁止 podSelector: {} (空選擇器) 覆蓋既有規則 │ │
│ │ Rule 3: Egress 必須明確指定 Port (禁止開放全部) │ │
│ │ Rule 4: 生產環境 NetworkPolicy 必須有註解說明 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ Layer 3: 變更告警 (監控) │
│ ════════════════════════════ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Falco / kube-state-metrics + Alertmanager │ │
│ │ ──────────────────────────────────────────── │ │
│ │ Alert: NetworkPolicy 被創建/修改/刪除 │ │
│ │ Alert: 直接 kubectl apply (繞過 GitOps) │ │
│ │ Alert: 非白名單用戶修改關鍵資源 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
實施計畫
Phase 1: 立即措施 (Day 1) ✅
# 1. 為所有 NetworkPolicy 加上變更元數據
metadata:
annotations:
awoooi.io/last-modified: "2026-03-23T15:30:00Z"
awoooi.io/modified-by: "ogt"
awoooi.io/change-reason: "修復 K8s API 連線 - Y 按鈕執行超時"
awoooi.io/ticket: "AWOOOI-123"
# 2. 設定 kubectl 審計 (立即啟用)
# 在 K3s server 加入:
--audit-policy-file=/etc/rancher/k3s/audit-policy.yaml
--audit-log-path=/var/log/k3s/audit.log
Phase 2: GitOps (Week 1)
# ArgoCD Application (輕量版)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: awoooi-networkpolicy
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/your-org/awoooi.git
path: k8s/awoooi-prod
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: awoooi-prod
syncPolicy:
automated:
prune: false # 禁止自動刪除
selfHeal: true # 有人手動改會自動復原
syncOptions:
- CreateNamespace=false
Phase 3: Policy Validation (Week 2)
# Kyverno Policy: 強制 NetworkPolicy 必須有註解
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-networkpolicy-annotations
spec:
validationFailureAction: Enforce
rules:
- name: require-change-reason
match:
resources:
kinds:
- NetworkPolicy
namespaces:
- awoooi-prod
validate:
message: "NetworkPolicy 必須有 awoooi.io/change-reason 註解"
pattern:
metadata:
annotations:
awoooi.io/change-reason: "?*"
awoooi.io/modified-by: "?*"
Phase 4: 變更告警 (Week 2)
# Falco Rule: NetworkPolicy 變更告警
- rule: NetworkPolicy Modified
desc: 偵測 NetworkPolicy 被創建、修改或刪除
condition: >
k8s_audit and
ka.target.resource = "networkpolicies" and
ka.verb in (create, update, patch, delete)
output: >
🚨 NetworkPolicy 變更告警
[%ka.verb] %ka.target.namespace/%ka.target.name
by %ka.user.name from %ka.sourceips
priority: WARNING
tags: [network, security, k8s]
# Prometheus Alert: 直接 kubectl 修改 (繞過 GitOps)
- alert: DirectKubectlNetworkPolicyChange
expr: |
increase(apiserver_audit_event_total{
verb=~"create|update|patch|delete",
resource="networkpolicies",
user_agent!~"argocd.*"
}[5m]) > 0
for: 0m
labels:
severity: critical
annotations:
summary: "有人直接 kubectl 修改 NetworkPolicy (繞過 GitOps)"
description: "{{ $labels.user }} 在 {{ $labels.namespace }} 修改了 NetworkPolicy"
PR 審核流程 (CODEOWNERS)
# .github/CODEOWNERS
# NetworkPolicy 變更需要 CIO + SRE 雙重審核
k8s/*/02-network-policy.yaml @awoooi/cio @awoooi/sre-team
k8s/*/networkpolicy*.yaml @awoooi/cio @awoooi/sre-team
回滾機制 (統帥要求補充)
基線管理
k8s/awoooi-prod/
├── 02-network-policy.yaml # 當前版本
├── .baselines/
│ ├── LATEST_KNOWN_GOOD.yaml # 最後驗證通過版本
│ └── 02-network-policy.{date}.yaml # 歷史版本
一鍵回滾
# 緊急回滾 (< 30 秒)
./scripts/rollback-networkpolicy.sh awoooi-prod
# 回滾到特定日期
./scripts/rollback-networkpolicy.sh awoooi-prod 2026-03-22
Drift 偵測 + 人工決策 (禁止盲目自動回滾)
- 每 5 分鐘比對叢集狀態 vs Git 基線
- 偵測到差異 → 立即告警 + 顯示差異
- 人工判斷: 合法變更 → 同步到 Git;錯誤變更 → 執行回滾
- 唯一自動回滾條件: 變更導致健康檢查失敗 + 變更在 10 分鐘內 + 基線曾健康
回滾 vs 修復 決策樹
偵測到問題
│
├─ 影響生產?
│ ├─ 是 → 立即回滾 (< 30 秒)
│ └─ 否 → 評估後決定
│
└─ 回滾後
├─ 記錄 RCA
├─ 修復根因
└─ 更新基線
驗收標準
| 項目 | 狀態 |
|---|---|
| NetworkPolicy 只能透過 PR 修改 | ⬜ |
| PR 需要 2 人審核 | ⬜ |
| 直接 kubectl apply 會觸發告警 | ⬜ |
| 告警發送到 Telegram + OpenClaw | ⬜ |
| 審計日誌保留 90 天 | ⬜ |
| Kyverno 強制註解規則 | ⬜ |
| 基線快照每日保存 | ⬜ |
| 一鍵回滾腳本可用 | ⬜ |
| Drift 偵測每 5 分鐘執行 | ⬜ |
| 回滾耗時 < 30 秒 | ⬜ |
成本
| 元件 | 方案 | 成本 |
|---|---|---|
| GitOps | ArgoCD (已有) | $0 |
| Policy | Kyverno (開源) | $0 |
| 告警 | Falco + Alertmanager (開源) | $0 |
| 總計 | $0 |
附錄 A: 2026-03-23 事故根因分析
2026-03-23 Y 按鈕執行超時
根因:
NetworkPolicy allow-required-egress 遺漏 K8s API 實際端點
問題鏈:
1. ClusterIP 10.43.0.1:443 已允許 ✓
2. 但實際流量路由到 192.168.0.120:6443 ✗
3. 192.168.0.120 在 192.168.0.0/16 排除範圍內 → 被阻擋
修復:
新增 192.168.0.120:6443 到 allow-required-egress
教訓:
1. K8s Service ClusterIP ≠ 實際 Endpoint
2. NetworkPolicy 需要允許完整路由路徑
3. 變更前應該用 dry-run 驗證
附錄 B: 2026-03-26 DNS 規則事故根因分析
2026-03-26 兩天無 Telegram 告警
根因 1: Alertmanager URL 路徑錯誤
設定: /api/v1/webhook/alertmanager (單數)
實際: /api/v1/webhooks/alertmanager (複數)
結果: 404 Not Found
根因 2: NetworkPolicy DNS 規則標籤錯誤
設定: podSelector 要求 environment=prod, system=awoooi
實際: CoreDNS 只有 k8s-app=kube-dns
結果: Pod 無法連接 CoreDNS
根因 3: CoreDNS 上游 DNS 設定錯誤
設定: forward . /etc/resolv.conf → 127.0.0.53 (systemd-resolved)
實際: 容器內無法使用 127.0.0.53
結果: 外部 DNS 解析失敗
修復:
1. Alertmanager ConfigMap 修正 URL 路徑
2. NetworkPolicy 使用正確的 namespaceSelector
3. CoreDNS 改用 8.8.8.8 1.1.1.1
教訓:
1. URL 路徑必須經過 E2E 測試驗證 (ADR-025)
2. NetworkPolicy DNS 規則必須使用 namespace selector
3. CoreDNS 不能依賴宿主機的 systemd-resolved
DNS 規則最佳實踐
# ✅ 正確的 DNS 規則寫法
- ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns