Files
awoooi/docs/adr/ADR-011-networkpolicy-governance.md
OG T 14c81f728f docs: 新增 ADR-025 告警鏈路 E2E 驗證 + 更新 Skills
新增:
- 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>
2026-03-26 15:34:12 +08:00

12 KiB
Raw Blame History

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 問題

根本原因:

  1. 任何有 kubectl 權限的人都可以直接修改 NetworkPolicy
  2. 修改後沒有任何告警或審計
  3. Git 版本與叢集版本經常不同步
  4. 沒有 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