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

340 lines
12 KiB
Markdown
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.
# 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) ✅
```yaml
# 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"
```
```bash
# 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)
```yaml
# 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)
```yaml
# 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)
```yaml
# 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]
```
```yaml
# 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
```
---
## 回滾機制 (統帥要求補充)
### 基線管理
```bash
k8s/awoooi-prod/
├── 02-network-policy.yaml # 當前版本
├── .baselines/
│ ├── LATEST_KNOWN_GOOD.yaml # 最後驗證通過版本
│ └── 02-network-policy.{date}.yaml # 歷史版本
```
### 一鍵回滾
```bash
# 緊急回滾 (< 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 規則最佳實踐
```yaml
# ✅ 正確的 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
```