从传统单体到金融级多活K8s架构,我们如何将99.99%可用性承诺落地为800+个YAML文件
某国有银行电子商城(日活峰值120万,SKU超300万,大促期间QPS峰值突破8万)在2024年底完成了从VMware虚拟机向Kubernetes的全量迁移。该项目历时9个月,涉及6个核心交易子系统、23个中台微服务,最终交付的K8s集群横跨3个可用区(同城双活+异地灾备),集群控制面SLA达到99.99%,数据面Pod调度延迟P99控制在120ms以内。
本文不聊"安装minikube"或"单机部署",直接复盘生产级高可用集群基建 + 金融级安全合规 + 大促弹性扩缩容的全套实战方案。文中所有YAML/Shell均经过生产环境脱敏验证,附赠踩坑血泪史。
可用区 | 角色 | 节点数量 | 配置 | 网络延迟 |
|---|---|---|---|---|
AZ-A(主生产) | 控制面 + 工作负载 | 5 Master + 20 Worker | 16C64G / 1.6T NVMe SSD | - |
AZ-B(同城备) | 控制面 + 工作负载 | 3 Master + 15 Worker | 16C64G / 1.6T NVMe SSD | < 2ms (光纤直连) |
AZ-C(异地灾备) | 仅数据面(冷备) | 0 Master + 10 Worker | 8C32G / SSD | 35ms (专线) |
关键设计决策:
5.15.131-1.el9(修复CVE-2023-44487漏洞)。# /etc/sysctl.d/99-k8s-hardening.conf
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.ip_local_reserved_ports = 30000-32767 # 避免NodePort冲突
net.core.somaxconn = 32768
net.ipv4.tcp_max_syn_backlog = 8096
net.core.netdev_max_backlog = 20000
fs.file-max = 2097152
fs.inotify.max_user_instances = 8192
fs.inotify.max_user_watches = 524288
# 关闭Swap(K8s强制要求)
swapoff -a && sed -i '/swap/d' /etc/fstab
# 加载内核模块(适用于Calico eBPF模式)
modprobe br_netfilter
modprobe overlay
cat > /etc/modules-load.d/k8s.conf << EOF
overlay
br_netfilter
EOF我们不使用云厂商SLB,而是自建 2台独立LB节点(主备)+ 虚拟IP(VIP),作为API Server的入口。
/etc/haproxy/haproxy.cfg(关键段):
global
log /dev/log local0
maxconn 100000
user haproxy
group haproxy
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
defaults
mode tcp
timeout connect 5s
timeout client 30s
timeout server 30s
retries 3
frontend k8s-api
bind *:6443
default_backend k8s-masters
backend k8s-masters
balance roundrobin
option tcp-check
# 5台Master节点
server master-1 10.0.1.11:6443 check fall 3 rise 2 inter 5s
server master-2 10.0.1.12:6443 check fall 3 rise 2 inter 5s
server master-3 10.0.1.13:6443 check fall 3 rise 2 inter 5s
server master-4 10.0.2.11:6443 check fall 3 rise 2 inter 5s # AZ-B
server master-5 10.0.2.12:6443 check fall 3 rise 2 inter 5s # AZ-BKeepalived配置(VIP 10.0.0.100/32):
vrrp_instance VI_1 {
state MASTER # 备机为BACKUP
interface eth0
virtual_router_id 51
priority 150 # 备机降低至100
advert_int 1
authentication {
auth_type PASS
auth_pass ${KEEPALIVED_PWD}
}
virtual_ipaddress {
10.0.0.100/32
}
track_script {
chk_haproxy
}
}
# 健康检查脚本
vrrp_script chk_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 2
weight 20
}kubeadm-config.yaml:
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.28.8
controlPlaneEndpoint: "10.0.0.100:6443" # VIP
apiServer:
certSANs:
- "10.0.0.100"
- "k8s-api.bank.internal"
extraArgs:
max-requests-inflight: "400"
max-mutating-requests-inflight: "200"
audit-log-path: "/var/log/k8s/audit.log"
audit-log-maxsize: "100"
audit-log-maxage: "7"
audit-policy-file: "/etc/kubernetes/audit-policy.yaml"
extraVolumes:
- name: audit
hostPath: /var/log/k8s
mountPath: /var/log/k8s
readOnly: false
- name: audit-policy
hostPath: /etc/kubernetes/audit-policy.yaml
mountPath: /etc/kubernetes/audit-policy.yaml
readOnly: true
controllerManager:
extraArgs:
node-cidr-mask-size: "24"
bind-address: "0.0.0.0"
terminated-pod-gc-threshold: "5000"
scheduler:
extraArgs:
bind-address: "0.0.0.0"
percentage-of-nodes-to-score: "30"
etcd:
external:
endpoints:
- https://10.0.1.101:2379
- https://10.0.1.102:2379
- https://10.0.1.103:2379
caFile: /etc/kubernetes/pki/etcd-ca.crt
certFile: /etc/kubernetes/pki/etcd-client.crt
keyFile: /etc/kubernetes/pki/etcd-client.key
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
systemReserved:
cpu: 500m
memory: 1Gi
kubeReserved:
cpu: 500m
memory: 1Gi
evictionHard:
memory.available: "500Mi"
nodefs.available: "5%"
imagefs.available: "10%"执行初始化(分阶段):
# 生成证书(有效期10年)
kubeadm init phase certs all --config=kubeadm-config.yaml
# 手工替换证书有效期(使用Go API修改,略)
# 正式初始化
kubeadm init --config=kubeadm-config.yaml --upload-certs
# 获取join命令(含certificate-key)
kubeadm init phase upload-certs --upload-certs其他Master节点加入时需指定--control-plane,并使用--certificate-key。
银行要求默认拒绝对外暴露,且东西流量必须加密(WireGuard)以及细粒度隔离。
custom-resources.yaml:
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
name: default
spec:
calicoNetwork:
ipPools:
- blockSize: 26
cidr: 172.20.0.0/16
encapsulation: VXLANCrossSubnet # 跨网段VXLAN,同网段直连
natOutgoing: Enabled
nodeAddressAutodetectionV4:
interface: "eth.*|bond.*"
linuxDataplane: BPF # 启用eBPF替代iptables
---
apiVersion: operator.tigera.io/v1
kind: APIServer
metadata:
name: default
spec: {}性能基准:eBPF模式替换kube-proxy,Service转发延迟降低28%(对比iptables),CPU占用减少15%。
我们为支付服务(pay-svc)只允许来自order-svc和gateway-svc的访问,且限定端口8080:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: pay-service-policy
namespace: prod
spec:
podSelector:
matchLabels:
app: pay-service
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: prod
podSelector:
matchExpressions:
- {key: app, operator: In, values: [order-svc, gateway-svc]}
ports:
- protocol: TCP
port: 8080
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53 # 仅允许DNS
- to:
- podSelector:
matchLabels:
app: redis-cache
ports:
- protocol: TCP
port: 6379踩坑提醒:银行旧版应用依赖
127.0.0.1本地通信,但在Pod中实际是独立网络命名空间。我们通过添加hostNetwork: true的特例容器解决了历史包袱,但严格审计了这类高危Pod。
电子商城的商品图片(海量小文件) 和订单日志需要持久化,我们采用Rook-Ceph提供块存储(RWO)和共享文件系统(RWX)。
cluster.yaml(精简):
yaml
apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
name: rook-ceph
namespace: rook-ceph
spec:
cephVersion:
image: quay.io/ceph/ceph:v17.2.6
dataDirHostPath: /var/lib/rook
mon:
count: 5
allowMultiplePerNode: false
mgr:
count: 2
dashboard:
enabled: true
crashCollector:
disable: false
storage:
useAllNodes: true
useAllDevices: false
config:
databaseSizeMB: "10240" # 10GB for rocksdb
journalSizeMB: "10240"
nodes:
- name: "worker-01"
devices:
- name: "/dev/nvme1n1"
- name: "/dev/nvme2n1"创建StorageClass:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rook-ceph-block-fast
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
clusterID: rook-ceph
pool: replicapool
imageFormat: "2"
imageFeatures: layering
csi.storage.k8s.io/fstype: ext4
csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
reclaimPolicy: Retain
allowVolumeExpansion: true生产调优:RBD mirror池开启fast-diff和object-map,使得快照创建从5秒降低到200ms,极大加速了CI环境的回滚速度。
电商系统外部流量路径: Internet → 云WAF(硬件) → 4层SLB → Nginx Ingress → 后端Service。
values.yaml (Helm):
controller:
kind: DaemonSet
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
nodeSelector:
node-role.kubernetes.io/ingress: "true"
service:
type: ClusterIP # 因为hostNetwork,无需NodePort
publishService:
enabled: false
config:
worker-processes: "auto"
worker-connections: "20480"
use-geoip2: "true"
geoip2-database: /etc/nginx/geoip/GeoLite2-City.mmdb
http-sni: "true"
ssl-protocols: "TLSv1.2 TLSv1.3"
ssl-ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"
proxy-body-size: "100m"
proxy-read-timeout: "120"
keepalive: "3200"
keepalive-requests: "10000"
upstream-keepalive-timeout: "120"
# 大促专项优化
worker-shutdown-timeout: "300s"
load-balance: "ewma" # 使用最少连接加权算法
ingressClassResource:
name: nginx-bank
enabled: true
default: true金丝雀发布支持:在Ingress注解中启用nginx.ingress.kubernetes.io/canary-weight: "10",将10%流量引至新版本,银行风控要求灰度观察至少30分钟。
银行交易系统坚决不用云托管RDS(合规要求),我们在K8s内自建MySQL InnoDB Cluster和Redis Enterprise。
apiVersion: mysql.oracle.com/v2
kind: InnoDBCluster
metadata:
name: order-db-cluster
spec:
secretName: order-db-secret
tlsUseSelfSigned: true
instances: 3
router:
instances: 2
datadirVolumeClaimTemplate:
storageClassName: rook-ceph-block-fast
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Ti
podSpec:
resources:
limits:
cpu: "8"
memory: 32Gi
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app.kubernetes.io/component: mysql
topologyKey: kubernetes.io/hostname配合Percona XtraBackup做每日全量备份到对象存储(MinIO)。
我们使用bitnami/redis-cluster Helm Chart,关闭appendfsync everysec改为no(由硬件BBU电池保障),换取更高吞吐。
persistence:
enabled: true
storageClass: "rook-ceph-block-fast"
size: 100Gi
redis:
configmap: |-
maxmemory 24gb
maxmemory-policy allkeys-lru
save 900 1
save 300 10
stop-writes-on-bgsave-error no
cluster:
nodes: 12 # 6 master + 6 replica
replicas: 1
updateStrategy: rollingUpdate大促抢购场景下,订单积压于MQ。我们引入KEDA(Kubernetes Event-driven Autoscaling)替代原生HPA,基于rabbitmq.queue_length进行精准扩容。
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --namespace keda --version 2.12.0apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: order-consumer-scaler
namespace: prod
spec:
scaleTargetRef:
name: order-consumer-deploy
kind: Deployment
apiVersion: apps/v1
minReplicaCount: 10
maxReplicaCount: 80
pollingInterval: 5 # 每5秒检查一次
cooldownPeriod: 120 # 缩容冷静期2分钟
triggers:
- type: rabbitmq
metadata:
queueName: order.pending
host: amqp://rabbitmq.prod.svc.cluster.local:5672
queueLength: "100"
protocol: http
authenticationRef:
name: rabbitmq-trigger-auth
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: rabbitmq-trigger-auth
spec:
secretTargetRef:
- parameter: host
name: rabbitmq-secret
key: host
- parameter: username
name: rabbitmq-secret
key: username
- parameter: password
name: rabbitmq-secret
key: password实际压测:队列堆积至5000条时,Pod在45秒内从10个扩容至68个,完美覆盖峰值。
prometheus:
prometheusSpec:
replicas: 2
retention: 30d
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: rook-ceph-block-fast
resources:
requests:
storage: 5Ti
externalLabels:
cluster: "bank-prod-az-a"
thanos:
baseImage: quay.io/thanos/thanos
version: v0.33.0
objectStorageConfig:
name: thanos-objstore
key: config.yamlloki:
structuredConfig:
ingester:
chunk_encoding: snappy
chunk_target_size: 1572864
querier:
max_concurrent: 20
storage_config:
boltdb_shipper:
active_index_directory: /var/loki/index
cache_location: /var/loki/cache
aws:
s3: s3://loki-bucket/
s3forcepathstyle: true
storage:
type: s3成本优化:日志保留7天热存,>7天转至Glacier归档。
我们在Prometheus中配置了交易成功率暴跌告警(非单纯的CPU告警):
groups:
- name: business_sla
rules:
- alert: HighOrderFailureRate
expr: |
(
sum(rate(order_service_errors_total[1m])) by (service)
/
sum(rate(order_service_requests_total[1m])) by (service)
) > 0.01
for: 2m
labels:
severity: critical
team: payment
annotations:
summary: "订单失败率超过1%,当前{{ $value }}"序号 | 现象 | 根因 | 解决方案 |
|---|---|---|---|
1 | API Server在压测时Too many requests,Pods无法调度 | max-requests-inflight默认400过小 | 调整为2000,同时增大kube-apiserver内存至16Gi |
2 | ETCD存储空间频繁报警(150%增长/天) | watch历史数据未压缩,默认压缩间隔1小时 | 设置--experimental-compaction-retention=1m,并每6小时执行etcdctl defrag |
3 | Ceph RBD PVC挂载慢(30s+) | rbd default features包含deep-flatten等导致内核态锁竞争 | 在StorageClass中指定imageFeatures: layering,禁用其他高级特性 |
4 | Nginx Ingress OOM(内存暴涨) | 开启geoip2且数据库加载到内存,每个Worker重复加载 | 设置geoip2-shared-memory: true并增加proxy-buffer-size |
5 | 节点DiskPressure导致Pod被驱逐 | 容器日志/var/log/containers未轮转,单个文件超10GB | 修改containerd配置max_size = 100M,配合logrotate每日切割 |
银行合规要求每年至少2次机房级切换演练。我们使用Chaos Mesh进行常态化故障测试:
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
name: az-a-partition
spec:
action: partition
mode: all
selector:
namespaces:
- prod
labelSelectors:
topology.kubernetes.io/zone: "az-a"
direction: to
target:
mode: all
selector:
namespaces:
- prod
labelSelectors:
topology.kubernetes.io/zone: "az-b"
duration: "5m"通过每周注入网络分区、Pod杀、节点关机等故障,验证Topology Spread Constraints和PodDisruptionBudget是否生效。
# PDB保证至少3个订单Pod存活
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: order-pdb
spec:
minAvailable: 3
selector:
matchLabels:
app: order-service指标项 | 目标值 | 实测P99 |
|---|---|---|
集群可用性 | 99.99% | 99.993%(年度故障23分钟) |
API Server响应延迟 | < 200ms | 62ms |
Pod启动时间(含拉镜像) | < 30s | 21s (含Harbor加速) |
跨AZ网络抖动 | < 3ms | 1.8ms |
大促最大Pod数 | - | 单集群峰值 2,340 Pods |
自动扩容响应 | < 60s | 43s(KEDA触发) |
cert-manager + Venafi对接内部CA,实现证书自动轮换,避免手动续期灾难。calico-node的FELIX_IPTABLESREFRESHINTERVAL和FELIX_MAXIPSETSNUMBER,默认值在万级Service下会频繁导致iptables-restore锁死。原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。