案例:某金融企业多集群落地
一句话定位:两地三中心、2000 节点、等保三级、日交易千万级,一家城商行如何在 K8s 上落地金融级多集群,并通过容灾演练与合规整改把"能部署"变成"敢上线"。
写在前面
金融行业对 K8s 的态度过去几年很矛盾:一边是业务侧喊着要云原生、要微服务、要弹性;另一边是科技安全侧盯着等保、审计、容灾,生怕容器化后失控。我所在的团队 2023 年开始负责某城商行的容器云平台建设,从单集群试点到多集群生产,前后跑了 14 个月,踩的坑大多不在 K8s 本身,而在"金融场景对架构的额外约束"上。
这篇文章复盘的是 2024 年 Q2 完成的多集群生产化项目:两地三中心、2000 节点、20+ 业务系统、日交易峰值 1200 万笔。重点不是讲 K8s 怎么装,而是讲在金融约束下,多集群的拓扑怎么设计、网络怎么互通、应用怎么分发、容灾怎么演练、合规怎么落地。这些经验对同样在金融、政企行业做 K8s 落地的同学应该有直接参考价值。
金融场景的容灾不是"能切就行",而是"切完数据不丢、业务不断、审计可查",这三个要求把方案复杂度提升了一个数量级。
案例概览
| 维度 | 内容 |
|---|---|
| 客户 | 某城商行(资产规模 8000 亿级) |
| 业务规模 | 20+ 核心业务系统,日交易峰值 1200 万笔 |
| 基础设施 | 两地三中心:同城 A/B 机房 + 异地灾备 C 机房 |
| 集群规模 | 6 个 K8s 集群,共 2000 节点 |
| 合规要求 | 等保三级、银保监 153 号文、人行金融科技合规 |
| 时间跨度 | 2023/03 立项 → 2024/06 生产上线 → 2024/09 容灾演练通过 |
| 关键挑战 | 网络互通、数据一致性、容灾 RPO/RTO、审计合规 |
| 最终效果 | RPO=0(同步双写)、RTO<5 分钟、审计 100% 覆盖 |
集群拓扑:
┌─────────────────────────────┐
│ 统一控制平面 │
│ (Karmada + ArgoCD + 自研) │
└──────────────┬──────────────┘
│
┌──────────────────────────┼──────────────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 同城A机房 │◀──专线10ms──▶│ 同城B机房 │◀──专线30ms──▶│ 异地C机房 │
│ k8s-prod │ │ k8s-dr │ │ k8s-gdr │
│ 800节点 │ │ 800节点 │ │ 400节点 │
│ 主交易 │ │ 同城备 │ │ 异地灾备 │
└─────┬───┘ └─────┬───┘ └─────┬───┘
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ MySQL主 │◀──同步复制──▶│ MySQL从 │◀──异步复制──▶│ MySQL从 │
│ Redis主 │◀──同步复制──▶│ Redis从 │◀──异步复制──▶│ Redis从 │
└─────────┘ └─────────┘ └─────────┘
一、背景与挑战
1.1 业务背景
该行原有核心系统跑在传统物理机 + 中间件上,2022 年启动"分布式核心"改造,新核心基于微服务架构,需容器化部署。一期上线 20+ 系统:核心账务、支付清算、信贷、风控、客户管理、手机银行后端等。日交易峰值 1200 万笔,其中核心账务 300 万笔,对数据库一致性要求极高。
1.2 合规约束
金融场景的特殊性主要体现在合规要求上,这是和互联网公司最大的区别:
- 等保三级:三级要求"安全标记 + 强制访问控制 + 安全审计 + 边界防护 + 双因子认证",映射到 K8s 上意味着 Pod 级别隔离、全量 API 审计、网络策略必须开启。
- 银保监 153 号文:要求"重要信息系统应具备同城双活 + 异地灾备能力",RTO ≤ 5 分钟,RPO ≤ 1 分钟。
- 数据本地化:客户敏感数据不得跨中心明文传输,必须加密 + 脱敏。
- 审计可追溯:所有运维操作、配置变更、应用部署必须有审计日志,保留 ≥ 180 天。
1.3 技术挑战
- 多集群网络互通:三个机房三种网络环境(同城 VPC peering、异地专线),CNI 跨集群 Pod 通信如何打通?
- 应用分发一致性:20+ 系统、上千个微服务,如何保证多集群配置一致、版本一致?
- 容灾切换:RPO=0 要求强一致同步,但同步双写性能损耗大,如何在保证一致性的前提下控制延迟?
- 数据一致性校验:切换后如何验证数据真的没丢?传统主从校验工具在容器化场景下不适用。
二、方案设计
2.1 多集群架构选型
我们对比了三种方案:
| 方案 | 优点 | 缺点 | 结论 |
|---|---|---|---|
| 单集群跨机房 | 运维简单 | etcd 跨机房延迟敏感,脑裂风险 | 否决 |
| 多集群 + Karmada | 统一调度、应用分发原生支持 | 社区成熟度一般、金融案例少 | 选用(控制平面) |
| 多集群 + ArgoCD | GitOps 成熟、审计友好 | 不做调度、需自研应用分发 | 选用(应用下发) |
最终采用 Karmada(集群调度)+ ArgoCD(应用下发)+ 自研控制台(运维 + 审计) 的组合:
#mermaid-svg-BUkMcUBpv61whUB6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BUkMcUBpv61whUB6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BUkMcUBpv61whUB6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BUkMcUBpv61whUB6 .error-icon{fill:#552222;}#mermaid-svg-BUkMcUBpv61whUB6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BUkMcUBpv61whUB6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BUkMcUBpv61whUB6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BUkMcUBpv61whUB6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BUkMcUBpv61whUB6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BUkMcUBpv61whUB6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BUkMcUBpv61whUB6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BUkMcUBpv61whUB6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BUkMcUBpv61whUB6 .marker.cross{stroke:#333333;}#mermaid-svg-BUkMcUBpv61whUB6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BUkMcUBpv61whUB6 p{margin:0;}#mermaid-svg-BUkMcUBpv61whUB6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-BUkMcUBpv61whUB6 .cluster-label text{fill:#333;}#mermaid-svg-BUkMcUBpv61whUB6 .cluster-label span{color:#333;}#mermaid-svg-BUkMcUBpv61whUB6 .cluster-label span p{background-color:transparent;}#mermaid-svg-BUkMcUBpv61whUB6 .label text,#mermaid-svg-BUkMcUBpv61whUB6 span{fill:#333;color:#333;}#mermaid-svg-BUkMcUBpv61whUB6 .node rect,#mermaid-svg-BUkMcUBpv61whUB6 .node circle,#mermaid-svg-BUkMcUBpv61whUB6 .node ellipse,#mermaid-svg-BUkMcUBpv61whUB6 .node polygon,#mermaid-svg-BUkMcUBpv61whUB6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BUkMcUBpv61whUB6 .rough-node .label text,#mermaid-svg-BUkMcUBpv61whUB6 .node .label text,#mermaid-svg-BUkMcUBpv61whUB6 .image-shape .label,#mermaid-svg-BUkMcUBpv61whUB6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-BUkMcUBpv61whUB6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-BUkMcUBpv61whUB6 .rough-node .label,#mermaid-svg-BUkMcUBpv61whUB6 .node .label,#mermaid-svg-BUkMcUBpv61whUB6 .image-shape .label,#mermaid-svg-BUkMcUBpv61whUB6 .icon-shape .label{text-align:center;}#mermaid-svg-BUkMcUBpv61whUB6 .node.clickable{cursor:pointer;}#mermaid-svg-BUkMcUBpv61whUB6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-BUkMcUBpv61whUB6 .arrowheadPath{fill:#333333;}#mermaid-svg-BUkMcUBpv61whUB6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BUkMcUBpv61whUB6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BUkMcUBpv61whUB6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BUkMcUBpv61whUB6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-BUkMcUBpv61whUB6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BUkMcUBpv61whUB6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-BUkMcUBpv61whUB6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BUkMcUBpv61whUB6 .cluster text{fill:#333;}#mermaid-svg-BUkMcUBpv61whUB6 .cluster span{color:#333;}#mermaid-svg-BUkMcUBpv61whUB6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BUkMcUBpv61whUB6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BUkMcUBpv61whUB6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-BUkMcUBpv61whUB6 .icon-shape,#mermaid-svg-BUkMcUBpv61whUB6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BUkMcUBpv61whUB6 .icon-shape p,#mermaid-svg-BUkMcUBpv61whUB6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-BUkMcUBpv61whUB6 .icon-shape .label rect,#mermaid-svg-BUkMcUBpv61whUB6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BUkMcUBpv61whUB6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-BUkMcUBpv61whUB6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-BUkMcUBpv61whUB6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Git 仓库 - 单一可信源
ArgoCD ApplicationSet
Karmada 控制平面
k8s-prod 同城A
k8s-dr 同城B
k8s-gdr 异地C
自研运维控制台
审计日志中心
统一监控告警
2.2 网络互通方案
网络是金融多集群最复杂的部分。我们的设计原则是"Pod 通信走 Overlay、服务通信走 Ingress、数据通信走专线":
┌──────────────────────────────────────────────────────────┐
│ 应用层:Service Mesh(Istio 跨集群) │
│ - 跨集群服务发现(east-west gateway) │
│ - mTLS 加密 │
│ - 流量调度(同机房优先 + 故障切流) │
├──────────────────────────────────────────────────────────┤
│ CNI 层:Calico BGP(reflect) + Submariner │
│ - 同城机房 Pod 直通(BGP Peering) │
│ - 异地机房走 Submariner 隧道(IPSec 加密) │
├──────────────────────────────────────────────────────────┤
│ 物理层:专线 + VPC Peering │
│ - 同城 A-B:VPC Peering,延迟 2ms │
│ - 同城-异地:专线,延迟 30ms,带宽 10Gbps │
│ - 全程 IPSec 加密(合规要求) │
└──────────────────────────────────────────────────────────┘
2.3 容灾切换设计
按银保监要求,RTO ≤ 5 分钟、RPO ≤ 1 分钟。我们采用"同城双活 + 异地灾备":
- 同城 A/B:MySQL 主主同步(半同步复制),Redis 集群跨机房部署,应用双活负载,故障时切流不切数据。
- 异地 C:MySQL 异步复制(RPO ≤ 30s),应用冷备(常态不接流量),仅同城双挂才启用。
切换决策矩阵:
| 故障场景 | 切换动作 | RTO | RPO |
|---|---|---|---|
| A 机房单节点故障 | K8s 自愈,无人工介入 | < 30s | 0 |
| A 机房网络抖动 | 切流到 B(保持数据同步) | < 1min | 0 |
| A 机房整体故障 | 切流到 B + DB 主从切换 | < 5min | 0 |
| 同城 A/B 双挂 | 切到异地 C + DB 提升从库 | < 30min | ≤ 30s |
三、实施过程
3.1 第一阶段:网络互通(2024/02 - 2024/03)
3.1.1 同城 Pod 互通(Calico BGP)
同城 A/B 机房 Pod 需要直通,走 Calico BGP reflect 方案:
yaml
# A 机房 calico BGP 配置(BGP peering 到 B 机房)
apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
name: default
spec:
logSeverityScreen: Info
nodeToNodeMeshEnabled: false # 关闭全互联,用 reflect
asNumber: 64512
---
apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
name: bgp-peer-to-b-rr
spec:
peerIP: 10.20.0.10 # B 机房 RR 节点
asNumber: 64513
node: node-a-rr-01 # A 机房 RR 节点
routerID: 10.10.0.10
---
# A 机房 Pod CIDR: 10.10.0.0/16
# B 机房 Pod CIDR: 10.20.0.0/16
# 路由通过 BGP 互通,Pod 直达
3.1.2 异地 Pod 互通(Submariner)
异地延迟 30ms,BGP 直通成本高,改用 Submariser IPSec 隧道:
bash
# 安装 submariner broker(在 Karmada 控制平面集群)
subctl deploy-broker --kubeconfig karmada.kubeconfig
# A 机房集群注册
subctl join --kubeconfig a.kubeconfig \
--broker-cluster karmada.kubeconfig \
--clusterid prod-a \
--cable-driver ipsec \
--natt=false
# C 机房集群注册
subctl join --kubeconfig c.kubeconfig \
--broker-cluster karmada.kubeconfig \
--clusterid gdr-c \
--cable-driver ipsec \
--natt=false
# 验证跨集群 Pod 通信
kubectl --kubeconfig a.kubeconfig exec -it test-pod -- \
ping 10.40.0.5 # C 机房 Pod IP
3.1.3 跨集群服务发现(Istio)
服务层用 Istio east-west gateway 实现跨集群服务发现:
yaml
# Istio 跨集群 ServiceEntry
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: pay-svc-cross-cluster
namespace: prod
spec:
hosts:
- pay-svc.prod.svc.cluster.local
location: MESH_INTERNAL
ports:
- number: 8080
name: http
protocol: HTTP
resolution: DNS
endpoints:
- address: pay-svc.prod.svc.cluster.local
locality: region-a/zone-a # 同城 A 优先
weight: 80
- address: pay-svc.prod.svc.cluster.local
locality: region-a/zone-b
weight: 20
- address: pay-gdr-svc.prod.svc.cluster.local
locality: region-c/zone-c
weight: 0 # 异地常态不接流量
3.2 第二阶段:多集群应用分发(2024/03 - 2024/04)
3.2.1 GitOps + ArgoCD ApplicationSet
所有应用配置在 Git 仓库,ArgoCD 负责下发到多集群:
yaml
# ArgoCD ApplicationSet - 同城双活下发
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: core-banking
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: prod-a
url: https://k8s-prod-a:6443
weight: "50"
- cluster: prod-b
url: https://k8s-prod-b:6443
weight: "50"
template:
metadata:
name: 'core-banking-{{cluster}}'
spec:
project: banking-prod
source:
repoURL: https://git.example.com/banking/core-banking
targetRevision: main
path: manifests/overlays/{{cluster}}
destination:
server: '{{url}}'
namespace: banking
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- PruneLast=true
3.2.2 Karmada 调度策略
Karmada 负责跨集群调度,核心用 SpreadByCluster 保证多活:
yaml
# Karmada PropagationPolicy
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
name: pay-svc-propagation
namespace: banking
spec:
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: pay-svc
placement:
clusterAffinity:
clusterNames:
- prod-a
- prod-b
- gdr-c
replicaScheduling:
replicaSchedulingType: Divided
replicaDivisionPreference: Weighted
weightPreference:
staticWeightList:
- targetCluster:
clusterNames: [prod-a]
weight: 50
- targetCluster:
clusterNames: [prod-b]
weight: 50
- targetCluster:
clusterNames: [gdr-c]
weight: 0 # 异地常态不调度
spreadConstraints:
- spreadByLabel: topology.kubernetes.io/zone
maxGroups: 1
minGroups: 1
3.3 第三阶段:容灾演练(2024/05 - 2024/06)
3.3.1 演练方案
演练分三次,逐级加码:
| 轮次 | 场景 | 范围 | 时间 |
|---|---|---|---|
| 第1轮 | 切流到同城 B,数据不切 | 应用层 | 周末凌晨 2 点 |
| 第2轮 | A 机房 DB 主从切换 + 应用切流 | 应用 + DB | 周末凌晨 2 点 |
| 第3轮 | 同城双挂,切异地 C | 全量 | 演练环境 |
3.3.2 切流脚本(同城 A→B)
bash
#!/bin/bash
# dr_switch_a_to_b.sh - 同城 A 切流到 B
set -euo pipefail
LOG() { echo "[$(date '+%F %T')] $*"; }
# Step 1: 健康检查 B 集群
LOG "检查 B 集群健康状态"
kubectl --kubeconfig b.kubeconfig get nodes --no-headers | \
awk '$2=="Ready"' | wc -l | xargs -I{} test {} -ge 800 || { LOG "B 集群节点不足"; exit 1; }
# Step 2: B 集群应用扩容到全量
LOG "B 集群应用扩容"
for svc in pay-svc core-svc account-svc; do
kubectl --kubeconfig b.kubeconfig scale deploy $svc -n banking --replicas=50
done
# Step 3: 等待 B 集群 Pod Ready
LOG "等待 B 集群 Pod 就绪"
kubectl --kubeconfig b.kubeconfig wait deploy -n banking --all \
--for=jsonpath='{.status.readyReplicas}'=50 --timeout=180s
# Step 4: 流量权重切换(Istio VirtualService)
LOG "切换流量到 B 集群"
kubectl --kubeconfig karmada.kubeconfig apply -f - <<EOF
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: pay-svc-vs
namespace: banking
spec:
hosts: [pay-svc.banking.svc.cluster.local]
http:
- route:
- destination:
host: pay-svc.banking.svc.cluster.local
subset: zone-a
weight: 0
- destination:
host: pay-svc.banking.svc.cluster.local
subset: zone-b
weight: 100
EOF
# Step 5: 观察业务指标 5 分钟
LOG "观察 5 分钟业务指标"
sleep 300
# Step 6: 确认稳定后,A 集群缩容
LOG "A 集群缩容"
for svc in pay-svc core-svc account-svc; do
kubectl --kubeconfig a.kubeconfig scale deploy $svc -n banking --replicas=10
done
LOG "切流完成"
3.3.3 数据一致性校验
DB 切换后必须校验数据,我们用 pt-table-checksum 改造的方案:
bash
# 主从数据校验(基于 GTID)
pt-table-checksum \
--host=master-b \
--user=checker \
--password=*** \
--recursion-method=hosts \
--no-check-binlog-format \
--databases=core_accounting \
--tables=account_balance,transaction_log \
--chunk-size=10000 \
--replicate=percona.checksums
# 查看差异
mysql -h master-b -e "
SELECT db, tbl, chunk, this_cnt, master_cnt,
this_crc <> master_crc AS diff
FROM percona.checksums
WHERE this_crc <> master_crc;"
3.4 第四阶段:合规整改(2024/04 - 2024/05)
3.4.1 等保三级整改清单
| 项 | 要求 | K8s 落地 |
|---|---|---|
| 身份认证 | 双因子 | 接入行内统一 IAM,OIDC + 动态口令 |
| 访问控制 | 强制访问 | Pod Security Admission + NetworkPolicy |
| 安全审计 | 全量审计 | apiserver audit + Falco 运行时审计 |
| 边界防护 | 入侵检测 | Calico 网络策略 + 入侵检测 HIDS |
| 数据加密 | 传输 + 存储 | mTLS + 存储加密(CSI 加密卷) |
| 漏洞扫描 | 定期 | Trivy 镜像扫描 + 节点 CIS 扫描 |
3.4.2 审计日志配置
yaml
# apiserver audit policy
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# 敏感操作全量记录
- level: RequestResponse
resources:
- group: ""
resources: ["secrets", "configmaps"]
- level: RequestResponse
verbs: ["create", "update", "patch", "delete"]
resources:
- group: "apps"
resources: ["deployments", "daemonsets", "statefulsets"]
# 读操作只记录元数据
- level: Metadata
verbs: ["get", "list", "watch"]
# 日志类不记录
- level: None
namespaces: ["kube-system", "logging"]
审计日志通过 filebeat 采集到 ELK,保留 365 天,关键操作接入行内 SOC 平台。
3.4.3 Pod 安全策略(Pod Security Admission)
yaml
# Namespace 级别 Pod Security
apiVersion: v1
kind: Namespace
metadata:
name: banking
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
---
# NetworkPolicy - 默认拒绝,按需放通
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: banking
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: pay-svc-allow
namespace: banking
spec:
podSelector:
matchLabels:
app: pay-svc
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: gateway # 只允许 gateway 访问
ports:
- protocol: TCP
port: 8080
四、踩坑与应急
4.1 跨集群服务调用超时(2024/03 20)
现象:同城 A 集群调用 B 集群服务,偶发 2 秒超时,业务报"跨机房调用慢"。
定位:抓包发现不是业务慢,是 DNS 解析慢。Istio 跨集群服务发现依赖 CoreDNS 的 stubdomain,但 CoreDNS 默认缓存 30 秒,且没配负缓存,导致频繁回源。
修复:
yaml
# CoreDNS stubdomain + 缓存优化
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
cache 60 # 正缓存 60 秒
cache 30 denial # 负缓存 30 秒
forward . /etc/resolv.conf
}
cluster-b.local:53 {
forward . 10.20.0.10 # B 集群 DNS
cache 120
}
同时调整 Istio sidecar 的 DNS 缓存。修复后跨集群调用 P99 稳定在 30ms。
4.2 ArgoCD 同步风暴(2024/04 12)
现象:某次 Git 提交后,ArgoCD 同时触发 200+ Application 同步,apiserver CPU 飙到 95%,同步全部卡死。
定位:ArgoCD 默认无并发限制,所有 Application 同时 sync 打爆 apiserver。
修复:
yaml
# argocd-cm 限制同步并发
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
application.instanceLabelKey: argocd.argoproj.io/instance
# 限制并发同步数
server.sync.max.concurrent: "20"
并改为"按业务系统分批同步",每批 20 个,间隔 30 秒。
4.3 容灾演练 DB 主从切换卡住(2024/05 18)
现象:第 2 轮演练,MySQL A→B 主从切换,半同步复制卡住 90 秒才完成。
定位:半同步复制要求至少一个从库 ACK,但 B 机房有个延迟从库(故意延迟 1 小时,用于误操作恢复),它的 ACK 永远到不了,触发半同步超时降级异步。
修复:把延迟从库从半同步复制组剔除,改为基于 binlog dump 的异步独立同步。同时把半同步超时从 10 秒调到 3 秒,超过即降级,避免写请求堆积。
sql
-- 半同步配置优化
SET GLOBAL rpl_semi_sync_master_timeout = 3000; -- 3 秒
SET GLOBAL rpl_semi_sync_master_wait_for_slave_count = 1;
五、复盘与改进
5.1 上线效果
- 容灾切换 RTO 实测 4 分 12 秒(目标 5 分钟)
- RPO=0(同城)、RPO ≤ 30s(异地)
- 资源利用率从原物理机方案的 25% 提升到 58%
- 应用发布时间从 2 小时缩短到 15 分钟
- 审计日志覆盖 100%,通过等保三级测评
5.2 经验教训
- 金融多集群的核心不是 K8s,是数据层:DB/中间件的跨机房一致性才是难点,K8s 只是应用分发层。
- 容灾演练要敢切真生产:演练环境永远发现不了真问题,第 3 轮异地演练我们坚持在准生产环境做,发现 7 个隐患。
- GitOps 审计天然友好:所有变更都在 Git 里,审计追溯零成本,这在金融场景是巨大优势。
- 网络方案要分层:别指望一种网络方案打天下,Pod/Service/数据三层各选各的。
- 合规要前置:别等上线才补合规,等保三级要求从架构设计阶段就要嵌入,我们项目专门有"合规 review"环节。
- 半同步复制要调参:默认参数不适合跨机房,timeout 和 wait_count 必须根据网络延迟调。
5.3 长期改进
- 推进"单元化架构",把跨机房调用从"尽力而为"变成"架构保证"
- 引入 Karmada 1.12 的故障自动迁移能力,减少人工切换
- 审计日志接入 AI 分析,异常操作实时告警
- 探索"异地多活",把异地 C 从灾备升级为双活
六、可复用产出
6.1 容灾切换 SOP
# 同城 A→B 切换 SOP
1. 触发条件:A 机房整体故障 / 网络中断 > 2 分钟
2. 决策权限:值班指挥(技术总监)确认,运维执行
3. 执行步骤:
3.1 健康检查 B 集群(kubectl get nodes)
3.2 B 集群应用扩容到全量
3.3 等待 Pod Ready(超时 3 分钟则回滚)
3.4 Istio 流量切换 A→B
3.5 观察 5 分钟业务指标
3.6 DB 主从切换(若 A 整体故障)
3.7 数据一致性校验
3.8 通知业务方确认
4. 回滚条件:B 集群 5 分钟内未恢复业务指标 80%
5. 演练频率:每季度 1 次
6.2 合规整改清单(金融 K8s 专版)
| 类别 | 检查项 | 工具/配置 |
|---|---|---|
| 身份 | 集群 API 双因子 | OIDC + OTP |
| 身份 | kubeconfig 定期轮换 | 脚本 + 审计 |
| 访问 | RBAC 最小权限 | Namespace 级 Role |
| 访问 | Pod Security restricted | PSA |
| 网络 | NetworkPolicy 默认拒绝 | Calico |
| 网络 | 跨集群流量加密 | mTLS/IPSec |
| 审计 | apiserver 全量审计 | audit policy |
| 审计 | 运行时行为审计 | Falco |
| 加密 | Secret 加密存储 | etcd KMS |
| 加密 | 存储卷加密 | CSI + KMS |
| 镜像 | 私有仓库 + 扫描 | Harbor + Trivy |
| 镜像 | 只读根文件系统 | securityContext |
| 合规 | 节点 CIS 扫描 | kube-bench |
| 合规 | 定期渗透测试 | 第三方 |
思考题
- 同城双活场景下,如果 DB 半同步复制持续降级为异步,你的告警和应急策略是什么?
- Karmada 和 ArgoCD 在多集群分发上职责如何划分?能否只用其中一个?
- 金融场景下,Pod 安全合规和研发效率如何平衡?restricted 策略会不会影响业务?
延伸阅读
- Karmada 官方文档:https://karmada.io/docs/
- 银保监《商业银行信息科技风险管理指引》
- Istio 跨集群服务网格:Multi-Cluster Install
- 等保 2.0 三级要求与 K8s 落地对照
- Submariner 跨集群网络:https://submariner.io/