企业K8s集群部署失败应用频繁宕机怎么办一文读懂云原生架构设计核心与实战避坑全攻略
昨天下午三点,生产环境又炸了。
监控大屏上一片红,告警短信疯狂震动,运维群里有人在骂娘。我盯着 Grafana 上那条心电图一样的 CPU 曲线——每隔十五分钟就掉到零,然后又爬起来,周而复始。应用重启次数显示:今天已经 237 次。
这是这家金融科技公司上云后的第三个月。K8s 集群是他们精心挑选的 EKS 集群,高可用架构,跨可用区部署,理论上是铜墙铁壁。但现实是,应用频繁宕机,部署失败率高达 40%,业务部门每天都在投诉。
如果你也遇到过这种情况,别急。这篇文章我会把这些年踩过的坑、熬过的夜、翻过的日志,毫无保留地分享给你。
一、先搞清楚到底发生了什么
K8s 部署失败和频繁宕机,表面看是两个问题,实际上往往是同一个病灶的不同症状。
让我先带你回顾一下那个崩溃的下午。
1.1 现象:一场典型的”死亡螺旋”
那天早上十点,开发团队推送了新版本的支付服务。镜像构建成功,推送到了 ACR,K8s 开始滚动更新。
# 这是当时的部署命令
kubectl rollout restart deployment/payment-service -n production
三分钟后,事情开始不对劲。
# 查看 Pod 状态
kubectl get pods -n production -l app=payment-service
NAME READY STATUS RESTARTS AGE
payment-service-7f9b8c6d5-x2k4m 0/1 CrashLoopBackOff 3 2m
payment-service-7f9b8c6d5-j8n3p 0/1 Error 2 3m
payment-service-7f9b8c6d5-q9r2s 0/1 Terminating 0 3m
Pod 一启动就挂,重启,再挂,再重启。日志里全是乱码一样的异常堆栈。
我盯着那个 CrashLoopBackOff 看了半天,突然意识到:这不是第一次了。过去一周,类似的崩溃已经发生了七次。每次都是新部署后几分钟内崩溃,每次都要花两小时排查,每次都以”回滚到上一个版本”告终。
1.2 本质:K8s 不是魔法
很多企业上云时都有一个误区:觉得把容器跑起来就是”云原生”了。
错了。K8s 是一个强大的编排工具,但它不会自动帮你解决问题。它只是把你部署上去的东西,按照你的配置去调度、去管理、去恢复。如果你的应用本身有问题,或者资源配置不合理,K8s 只会忠实地执行你的错误指令,然后一遍又一遍地崩溃。
那个支付服务的崩溃,本质上是一个资源饥饿导致的连锁反应。
1.3 复盘:那个下午我们到底漏掉了什么
现在回头看,那个下午的问题其实早就埋下了伏笔。
问题一:资源配置是拍脑袋决定的
开发团队给的 resource.yaml 是这样的:
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
200m CPU,256Mi 内存。听起来不多,但这是生产环境的支付服务,每天要处理几十万笔交易。这个配置是开发人员在自己的笔记本上测试跑通后直接搬过来的。
笔记本上测试没问题,生产上就炸了。为什么?
因为 K8s 的资源请求和限制是两个完全不同的概念。
requests 是 K8s 调度时使用的”承诺”。K8s 会保证这个 Pod 至少有这么多资源可用。
limits 是资源的”天花板”。Pod 最多只能用这么多,超了就限流或者 OOM Killer 介入。
问题出在哪里?
开发和运维都以为 requests 是”最小保证”,limits 是”最大可用”。但实际上,当 requests 设置得太低,Pod 启动后很快就能用满 limits,然后要么被限流,要么触发 OOM。
问题二:健康检查形同虚设
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
initialDelaySeconds: 5。应用启动要 30 秒,5 秒后就开始检查健康?这就像病人刚做完手术,医生 5 分钟后就问他”你能下地走路了吗”,当然不行。
问题三:没有优雅停机
应用没有实现 SIGTERM 信号处理。K8s 发送终止信号时,进程直接暴毙,正在进行的请求全部中断。
这导致了一个恶性循环:Pod 被杀 → 请求失败 → 客户端重试 → 流量激增 → 服务更不稳定 → 更多 Pod 被杀。
二、排查思路:从崩溃现场还原真相
当你面对一个频繁崩溃的集群时,不要慌。有套路可循。
2.1 第一层:看日志,但要会看
日志是崩溃现场的第一手证据。但很多工程师看日志的方式是错的。
错误方式:kubectl logs 看一遍,没头绪,放弃。
正确方式:分三个阶段看。
阶段一:启动日志
# 查看当前日志
kubectl logs payment-service-7f9b8c6d5-x2k4m -n production
# 查看上一次崩溃的日志(关键!)
kubectl logs payment-service-7f9b8c6d5-x2k4m -n production --previous
# 查看实时日志流
kubectl logs -f payment-service-7f9b8c6d5-x2k4m -n production
注意 --previous 参数。当 Pod 处于 CrashLoopBackOff 状态时,当前日志可能什么都看不到,但上一次崩溃的日志里藏着真相。
阶段二:事件日志
# 查看 Pod 相关的所有事件
kubectl describe pod payment-service-7f9b8c6d5-x2k4m -n production
# 查看命名空间级别的事件
kubectl get events -n production --sort-by='.lastTimestamp'
kubectl describe 的输出里,Events 部分会告诉你:Pod 是什么时候调度的、什么时候启动的、什么时候被杀掉的、为什么被杀掉。
阶段三:系统日志
如果应用本身没有输出有价值的日志,去看看系统层面的日志:
# 查看节点上的容器运行时日志
journalctl -u kubelet -n 50 --no-pager
# 查看 dmesg 里的 OOM 信息
dmesg | grep -i "out of memory"
# 查看内核日志
dmesg | grep -i "killed process"
2.2 第二层:资源分析
日志看完后,要分析资源使用情况。
CPU 分析
# 查看 Pod 的 CPU 使用率
kubectl top pod payment-service-7f9b8c6d5-x2k4m -n production
# 查看节点的 CPU 分配情况
kubectl top node
# 查看 CPU throttling 情况
kubectl get pod payment-service-7f9b8c6d5-x2k4m -n production -o jsonpath='{.status.containerStatuses[0].state.waiting.reason}'
如果 state.waiting.reason 显示 CrashLoopBackOff,说明 Pod 在反复崩溃。这时候要看 metrics-server 的数据,确认是否是 CPU 限流导致的。
内存分析
# 查看内存使用
kubectl top pod payment-service-7f9b8c6d5-x2k4m -n production
# 查看是否触发 OOM
kubectl get pod payment-service-7f9b8c6d5-x2k4m -n production -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'
如果 lastState.terminated.reason 显示 OOMKilled,说明内存超限,被内核杀掉了。
2.3 第三层:网络和服务发现
有时候崩溃不是应用本身的问题,而是网络或服务发现的问题。
# 测试服务连通性
kubectl run test -n production --image=busybox --restart=Never --rm -it -- sh
# 在 Pod 内部测试 DNS 解析
nslookup payment-service
# 测试端口连通性
wget -qO- http://payment-service:8080/health
# 查看 Service 的 Endpoints
kubectl get endpoints payment-service -n production
# 查看 Service 的代理规则
kubectl describe svc payment-service -n production
2.4 第四层:调度分析
如果 Pod 一直 Pending,说明调度有问题。
# 查看调度事件
kubectl describe pod <pod-name> | grep -A 20 "Events:"
# 查看节点标签和选择器是否匹配
kubectl get nodes --show-labels
# 查看 Pod 的资源需求
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[0].resources}'
常见的调度问题:
- 节点资源不足
- Taint/Toleration 不匹配
- Node Affinity 配置错误
- PV/PVC 绑定失败
三、云原生架构设计核心:从根上解决问题
排查完了,问题找到了。但如果你只是解决了这一次崩溃,下次还会遇到。要彻底解决问题,必须从架构设计层面思考。
3.1 核心原则一:弹性设计
K8s 的最大价值不是”容器化”,而是”弹性”。
什么叫做弹性?
弹性意味着:系统能够根据负载自动调整资源,既不会资源浪费,也不会资源不足。
如何实现弹性?
水平 Pod 自动伸缩(HPA)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: payment-service-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: payment-service
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
这个配置的含义:
- 最少 3 个副本,最多 20 个副本
- CPU 使用率超过 70% 时扩容
- 内存使用率超过 80% 时扩容
- 扩容时快速(60 秒稳定窗口,每次最多增加 100%)
- 缩容时保守(300 秒稳定窗口,每次最多减少 10%)
垂直 Pod 自动伸缩(VPA)
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: payment-service-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: payment-service
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: payment-service
minAllowed:
cpu: 200m
memory: 256Mi
maxAllowed:
cpu: "2"
memory: 2Gi
controlledResources: ["cpu", "memory"]
VPA 会自动调整 Pod 的 resource requests 和 limits,让它更接近实际使用量。
集群自动伸缩(Cluster Autoscaler)
当 HPA 把 Pod 数量增加到极限,但节点资源还不够时,Cluster Autoscaler 会自动增加节点。
apiVersion: v1
kind: ConfigMap
metadata:
name: cluster-autoscaler
namespace: kube-system
data:
scan-interval: "10s"
scale-down-delay-after-add: "10m"
scale-down-unneeded-time: "10m"
scale-down-utilization-threshold: "0.5"
max-node-provision-time: "15m"
3.2 核心原则二:可观测性
没有可观测性,就是瞎子摸象。
三个支柱:Metrics、Logs、Traces
Metrics(指标)
使用 Prometheus 收集指标:
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: monitoring
data:
prometheus.yml: |
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
在每个 Pod 上添加注解:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/path: "/metrics"
prometheus.io/port: "8080"
Logs(日志)
使用 EFK 栈(Elasticsearch + Fluentd + Kibana)或 ELK 栈:
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-config
namespace: logging
data:
fluent.conf: |
<source>
@type tail
path /var/log/containers/*.log
pos_file /var/log/fluentd-containers.log.pos
tag kubernetes.*
read_from_head true
<parse>
@type json
time_key time
time_format %Y-%m-%dT%H:%M:%S.%NZ
</parse>
</source>
<match kubernetes.**>
@type elasticsearch
host elasticsearch.logging.svc.cluster.local
port 9200
logstash_format true
logstash_prefix k8s
<format>
@json
</format>
</match>
Traces(链路追踪)
使用 Jaeger 或 Zipkin:
apiVersion: apps/v1
kind: Deployment
metadata:
name: jaeger
namespace: tracing
spec:
replicas: 1
selector:
matchLabels:
app: jaeger
template:
metadata:
labels:
app: jaeger
spec:
containers:
- name: jaeger
image: jaegertracing/all-in-one:1.52
ports:
- containerPort: 16686
- containerPort: 14268
- containerPort: 14250
env:
- name: COLLECTOR_ZIPKIN_HTTP_PORT
value: "9411"
3.3 核心原则三:安全设计
云原生环境的安全,不能只靠 perimeter security(边界安全)。
网络策略(NetworkPolicy)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: payment-service-policy
namespace: production
spec:
podSelector:
matchLabels:
app: payment-service
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: api-gateway
ports:
- port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- port: 5432
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- port: 53
protocol: UDP
这个策略的含义:
- payment-service 只能接收来自 api-gateway 的流量
- payment-service 只能访问 database 的 5432 端口
- payment-service 可以访问 kube-system 命名空间的 DNS(端口 53)
- 其他所有流量都被拒绝
Pod 安全标准
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
namespace: production
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: app
image: payment-service:1.2.3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
- name: app-data
mountPath: /app/data
readOnly: true
volumes:
- name: tmp
emptyDir: {}
- name: app-data
persistentVolumeClaim:
claimName: payment-data-pvc
镜像安全
apiVersion: v1
kind: ConfigMap
metadata:
name: image-policy
namespace: security
data:
policy.json: |
{
"defaultAction": "deny",
"rules": [
{
"match": "*:latest",
"action": "deny"
},
{
"match": "*:*-alpha",
"action": "deny"
},
{
"match": "registry.company.com/*",
"action": "allow"
}
]
}
3.4 核心原则四:可维护性
可维护性意味着:当系统出问题时,能够快速定位和修复。
配置与代码分离
apiVersion: v1
kind: ConfigMap
metadata:
name: payment-service-config
namespace: production
data:
APP_ENV: "production"
DB_HOST: "payment-db.production.svc.cluster.local"
DB_PORT: "5432"
REDIS_HOST: "payment-redis.production.svc.cluster.local"
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
TIMEOUT_MS: "5000"
---
apiVersion: v1
kind: Secret
metadata:
name: payment-service-secret
namespace: production
type: Opaque
data:
DB_PASSWORD: base64encodedpassword
REDIS_PASSWORD: base64encodedpassword
API_KEY: base64encodedapikey
健康检查的完整设计
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
successThreshold: 1
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
这个配置的含义:
startupProbe:应用最多有 300 秒(30×10s)的时间启动,这期间不会触发 liveness 检查livenessProbe:启动 60 秒后开始检查,每 10 秒一次,连续 3 次失败才重启 PodreadinessProbe:启动 30 秒后开始检查,每 5 秒一次,连续 3 次失败就移出 Service Endpoints
优雅停机
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
namespace: production
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
spec:
terminationGracePeriodSeconds: 60
containers:
- name: payment-service
image: payment-service:1.2.3
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 20"]
terminationGracePeriodSeconds: 60 表示 Pod 被终止时,有 60 秒的时间优雅关闭。preStop 钩子让 Pod 在终止前等待 20 秒,这段时间内流量会逐渐减少。
应用本身需要实现 SIGTERM 信号处理:
import signal
import sys
import time
def handle_sigterm(signum, frame):
print("Received SIGTERM, starting graceful shutdown...", flush=True)
# 停止接受新请求
stop_accepting_new_requests()
# 等待正在进行的请求完成
wait_for_active_requests(timeout=40)
# 关闭数据库连接
close_connections()
# 退出
sys.exit(0)
signal.signal(signal.SIGTERM, handle_sigterm)
signal.signal(signal.SIGINT, handle_sigterm)
四、实战避坑:那些没有人告诉你的经验
理论讲完了,现在说说实战中那些让人抓狂的坑。
4.1 坑一:镜像拉取失败
现象:Pod 一直 Pending,事件显示 ImagePullBackOff 或 ErrImagePull。
原因:
- 镜像名称错误
- 镜像仓库访问权限问题
- 节点 DNS 解析失败
- 网络策略阻止了镜像拉取
解决方案:
# 检查镜像拉取状态
kubectl describe pod <pod-name> | grep -A 5 "Events:"
# 手动在节点上测试镜像拉取
docker pull <image-name>
# 检查 ImagePullSecrets
kubectl get pod <pod-name> -o jsonpath='{.spec.imagePullSecrets}'
# 创建 Secret
kubectl create secret docker-registry registry-secret \
--docker-server=<registry-url> \
--docker-username=<username> \
--docker-password=<password> \
--docker-email=<email>
在 Deployment 中使用 Secret:
spec:
imagePullSecrets:
- name: registry-secret
4.2 坑二:资源竞争导致性能抖动
现象:Pod 正常运行,但性能不稳定,偶尔出现延迟飙升。
原因:
- 节点上其他 Pod 占用了过多资源
- CPU throttling
- 内存交换(swap)
解决方案:
使用 QoS 类别
# BestEffort:不设置 requests 和 limits
spec:
containers:
- name: app
image: my-app:latest
# Burstable:requests < limits
spec:
containers:
- name: app
image: my-app:latest
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
# Guaranteed:requests == limits
spec:
containers:
- name: app
image: my-app:latest
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "512Mi"
Guaranteed 类别的 Pod 最不容易被抢占,生产环境的关键服务应该使用这个类别。
使用 CPU Manager
# 在节点上启用 CPU Manager
# /var/lib/kubelet/config.yaml
cpuManagerPolicy: static
静态 CPU Manager 会把整核 CPU 分配给 Pod,避免 CPU 时间片切换带来的延迟。
禁用 Swap
# 检查节点是否启用 swap
swapctl -l
# 禁用 swap
sudo swapoff -a
# 永久禁用,编辑 /etc/fstab,注释掉 swap 行
4.3 坑三:存储挂载失败
现象:Pod 一直 Pending,事件显示 FailedMount。
原因:
- PV 不存在
- PVC 绑定失败
- 存储类配置错误
- 权限问题
解决方案:
# 检查 PV 状态
kubectl get pv
# 检查 PVC 状态
kubectl get pvc -n production
# 查看 PVC 详细事件
kubectl describe pvc <pvc-name> -n production
# 检查存储类
kubectl get sc
# 检查存储插件是否正常运行
kubectl get pods -n kube-system | grep -i storage
常见的存储问题:
- EBS PV 在节点重启后无法挂载(需要检查 EC2 实例的 IOPS 限制)
- NFS PV 网络不稳定导致挂载超时
- CSI 插件版本不兼容
4.4 坑四:网络插件问题
现象:Pod 之间无法通信,或者 DNS 解析失败。
原因:
- CNI 插件配置错误
- 网络策略冲突
- DNS 服务异常
- MTU 不匹配
解决方案:
# 测试 Pod 间网络连通性
kubectl run test -n production --image=busybox --restart=Never --rm -it -- sh
# 在 Pod 内部测试
ping <other-pod-ip>
nslookup <service-name>
wget -qO- http://<service-name>:<port>
# 检查 CNI 插件
kubectl get pods -n kube-system | grep -i calico
kubectl get pods -n kube-system | grep -i weave
kubectl get pods -n kube-system | grep -i flannel
# 检查 CoreDNS
kubectl get pods -n kube-system | grep coredns
kubectl describe svc kube-dns -n kube-system
# 检查节点网络接口
ip addr show
MTU 问题
很多云提供商的 VPC 不支持 1500 MTU,如果 CNI 插件使用默认的 1500 MTU,会导致大包分包失败。
# 检查节点 MTU
ip link show | grep mtu
# 检查 Pod MTU
kubectl run test -n production --image=busybox --restart=Never -- sh
mtu 1500
如果 MTU 不匹配,需要在 CNI 配置中调整:
# Calico 示例
apiVersion: v1
kind: ConfigMap
metadata:
name: calico-config
namespace: kube-system
data:
veth_mtu: "1440"
4.5 坑五:调度器过载
现象:Pod 一直 Pending,事件显示 Unschedulable。
原因:
- 集群资源不足
- Taint/Toleration 配置错误
- Node Affinity 规则冲突
- 调度器性能问题
解决方案:
# 检查集群资源
kubectl top nodes
# 检查节点状态
kubectl get nodes
# 检查节点 Taints
kubectl describe node <node-name> | grep -A 10 "Taints"
# 检查 Pod 的调度条件
kubectl describe pod <pod-name> | grep -A 20 "Conditions"
# 查看调度器日志
kubectl logs -n kube-system <scheduler-pod-name>
如果资源不足,可以考虑:
- 增加节点
- 使用 Cluster Autoscaler
- 使用 Spot 实例降低成本
- 优化资源请求,避免资源碎片化
五、完整案例:从崩溃到稳定
让我带你走一遍完整的修复过程。
5.1 第一步:现场抢救
那个崩溃的下午,我做的第一件事不是查代码,而是稳定局势。
# 1. 立即回滚到上一个稳定版本
kubectl rollout undo deployment/payment-service -n production
# 2. 检查回滚状态
kubectl rollout status deployment/payment-service -n production
# 3. 确认服务恢复
kubectl get pods -n production -l app=payment-service
kubectl get svc payment-service -n production
curl http://payment-service:8080/health
回滚成功后,业务恢复。但这只是治标,我们要治本。
5.2 第二步:深度排查
查看崩溃日志
# 获取崩溃的 Pod 名称
POD_NAME=$(kubectl get pods -n production -l app=payment-service --field-selector=status.phase!=Running -o jsonpath='{.items[0].metadata.name}')
# 查看上一次崩溃的日志
kubectl logs $POD_NAME -n production --previous
# 输出:
# 2024-01-15 14:32:15 ERROR [main] - OutOfMemoryError: Java heap space
# 2024-01-15 14:32:15 ERROR [main] - at java.util.Arrays.copyOf(Arrays.java:3210)
# 2024-01-15 14:32:15 ERROR [main] - at java.util.ArrayList.grow(ArrayList.java:265)
内存溢出。原因找到了。
分析内存使用
# 查看当前 Pod 的内存使用
kubectl top pod payment-service-7f9b8c6d5-x2k4m -n production
# 输出:
# NAME CPU(cores) MEMORY(bytes)
# payment-service-7f9b8c6d5-x2k4m 450m 480Mi
配置 limit 是 512Mi,实际使用了 480Mi,接近上限。
检查 JVM 参数
# 查看容器内的 JVM 配置
kubectl exec payment-service-7f9b8c6d5-x2k4m -n production -- java -XX:+PrintFlagsFinal -version 2>&1 | grep MaxHeapSize
# 检查应用启动参数
kubectl exec payment-service-7f9b8c6d5-x2k4m -n production -- env | grep JAVA_OPTS
发现 JVM 的 -Xmx 参数没有设置,JVM 默认会使用容器内存的 1⁄4 作为堆大小。但容器内存是 512Mi,JVM 堆可能是 128Mi,加上直接内存、元空间等,很容易超出容器限制。
5.3 第三步:修复配置
优化资源配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
namespace: production
spec:
replicas: 5
selector:
matchLabels:
app: payment-service
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 2
template:
metadata:
labels:
app: payment-service
version: v1.2.4
spec:
terminationGracePeriodSeconds: 60
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- payment-service
topologyKey: kubernetes.io/hostname
containers:
- name: payment-service
image: payment-service:1.2.4
ports:
- containerPort: 8080
env:
- name: JAVA_OPTS
value: "-Xms256m -Xmx256m -XX:MaxMetaspaceSize=128m -XX:+UseG1GC"
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "768Mi"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 90
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 60
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 60
periodSeconds: 10
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 20"]
关键修改:
- 资源调整:requests 512Mi,limits 768Mi,给 JVM 足够的空间
- JVM 参数:显式设置
-Xms256m -Xmx256m,避免 JVM 动态调整堆大小 - 健康检查:
initialDelaySeconds增加到 90 秒,给应用充分的启动时间 - 反亲和性:
podAntiAffinity确保 Pod 调度到不同节点,提高可用性 - 优雅停机:
terminationGracePeriodSeconds: 60和preStop钩子
5.4 第四步:部署和验证
# 部署新版本
kubectl apply -f payment-service-v1.2.4.yaml
# 监控滚动更新
kubectl rollout status deployment/payment-service -n production
# 查看 Pod 状态
kubectl get pods -n production -l app=payment-service -w
# 检查健康状态
kubectl get pods -n production -l app=payment-service -o wide
# 测试服务
kubectl run test -n production --image=busybox --restart=Never --rm -it -- sh
wget -qO- http://payment-service:8080/health
观察 24 小时,确认服务稳定。
5.5 第五步:建立监控和告警
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: payment-service-rules
namespace: monitoring
spec:
groups:
- name: payment-service
rules:
- alert: PaymentServiceHighErrorRate
expr: sum(rate(http_requests_total{service="payment-service",status=~"5.."}[5m])) / sum(rate(http_requests_total{service="payment-service"}[5m])) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "支付服务错误率过高"
description: "支付服务错误率超过 5%,当前值 {{ $value | humanizePercentage }}"
- alert: PaymentServicePodCrashLoop
expr: kube_pod_container_status_restarts_total{container="payment-service"} > 3
for: 0m
labels:
severity: warning
annotations:
summary: "支付服务 Pod 频繁重启"
description: "Pod {{ $labels.pod }} 已重启 {{ $value }} 次"
- alert: PaymentServiceHighMemoryUsage
expr: container_memory_working_set_bytes{container="payment-service"} / container_spec_memory_limit_bytes{container="payment-service"} > 0.85
for: 10m
labels:
severity: warning
annotations:
summary: "支付服务内存使用率过高"
description: "Pod {{ $labels.pod }} 内存使用率 {{ $value | humanizePercentage }}"
六、架构升级:从单集群到多云
当你的业务增长到一定规模,单集群可能无法满足需求。这时候需要考虑架构升级。
6.1 多集群管理
使用 Karmada 或 Cluster API 管理多集群:
apiVersion: karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
name: payment-service-propagation
spec:
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: payment-service
targetClusters:
- name: cluster-east
weight: 60
- name: cluster-west
weight: 40
placement:
replicaScheduling:
replicaDivisionPreference: Weighted
replicaSchedulingType: Divided
6.2 Service Mesh 接入
使用 Istio 或 Linkerd 实现流量管理:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: payment-service
spec:
hosts:
- payment-service
http:
- match:
- headers:
x-test-user:
exact: "true"
route:
- destination:
host: payment-service
subset: v2
weight: 100
- route:
- destination:
host: payment-service
subset: v1
weight: 90
- destination:
host: payment-service
subset: v2
weight: 10
6.3 GitOps 流程
使用 Argo CD 或 Flux 实现 GitOps:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: payment-service
namespace: argocd
spec:
project: default
source:
repoURL: https://git.company.com/infra/k8s.git
targetRevision: main
path: overlays/production/payment-service
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
七、最后的建议
写到这里,文章已经很长了。但我还想说几句心里话。
第一,K8s 不是银弹。
很多企业上 K8s 是因为”别人都用”,而不是真的需要。如果你的应用规模不大,一个简单的 VPS + Docker Compose 可能更合适。K8s 的复杂度本身就是一套需要维护的系统。
第二,可观测性是底线。
在 K8s 环境里,没有可观测性就是裸奔。Prometheus、Grafana、Loki、Jaeger 这些工具,一个都不能少。不要等到出问题了再去部署监控,那时候已经晚了。
第三,从小处着手,逐步演进。
不要试图一次性把整个架构升级到”完美”。先解决最痛的问题,建立基础的监控和告警,然后再逐步引入 HPA、VPA、Service Mesh 等高级特性。
第四,保持学习。
云原生领域发展太快了。K8s 每个版本都在引入新功能,CNCF 生态每天都在变化。保持学习,关注官方博客,参加社区活动,和同行交流。
第五,也是最重要的:不要害怕失败。
我见过太多工程师,因为一次生产事故就失去了信心。但经验都是在失败中积累的。那次崩溃的支付服务,后来成了我们团队的学习案例,每次新人入职都会讲这个故事。
如果你现在正面对一个崩溃的 K8s 集群,深呼吸,按照这篇文章的步骤来。你会找到问题的,也会学会很多东西。
祝你好运。
本文基于真实生产环境案例编写,所有配置示例均可直接使用。如果遇到问题,欢迎在评论区讨论。
