【架构实战】Kubernetes运维实践从入门到放弃再到精通字数统计约3600字一、一个简单迁移的血泪史2023年初CTO 在全员大会上宣布“我们要全面上云所有服务迁移到 Kubernetes”我当时觉得这有什么难的不就是 Docker 换个编排方式吗于是信心满满地领了任务带着两个同事开始干。第一个服务——用户中心Spring Boot 应用一个数据库一个 Redis——简单得不能再简单了。三天后我坐在工位上怀疑人生Pod 起来 30 秒就被 Kill原因是健康检查配置错误应用还没初始化完就被判定为不健康Service 能访问但时灵时不灵原来是selector的 label 写错了一个字母ConfigMap 里的配置改了但 Pod 里的应用根本没感知到因为不会自动重载凌晨服务挂了HPA 没起作用因为资源 Request 设得太高集群根本没资源可以扩简单的用户中心用了一周才稳定运行。后面还有 30 多个服务等着。那一刻我真的想放弃。但我没有。半年后所有服务稳定运行在 K8s 上自动扩缩容、滚动更新、故障自愈全部就绪。回头看踩过的每一个坑都是通向精通的必经之路。这篇文章就是这段旅程的浓缩。二、K8s 核心概念速通在动手之前先搞清楚 K8s 里最关键的几个概念和它们的关系Cluster ├── Node (工作节点) │ ├── Pod (最小调度单元包含一个或多个容器) │ │ ├── Container (应用容器) │ │ └── Container (Sidecar如日志采集) │ ├── Pod │ └── ... ├── Master (控制面) │ ├── API Server (一切操作的入口) │ ├── Scheduler (决定 Pod 跑在哪个 Node) │ ├── Controller Manager (维护期望状态) │ └── etcd (存储集群状态)关键抽象层级资源作用类比Pod最小运行单元一台虚拟机上的一个进程组Deployment管理 Pod 的副本数和更新策略进程管理器Service为 Pod 提供稳定的访问入口负载均衡器ConfigMap/Secret配置和敏感信息管理配置文件HPA自动扩缩容弹性伸缩策略IngressHTTP 路由入口Nginx 反向代理Namespace资源隔离文件夹/项目核心理念声明式而非命令式。你告诉 K8s “我要 3 个 Pod”K8s 会一直确保有 3 个 Pod 在运行。如果挂了一个自动拉起新的。这就是期望状态 vs 实际状态的核心循环。三、核心配置实战3.1 Deployment应用部署# deployment.yamlapiVersion:apps/v1kind:Deploymentmetadata:name:payment-servicenamespace:productionlabels:app:payment-servicespec:replicas:3selector:matchLabels:app:payment-service# 滚动更新策略strategy:type:RollingUpdaterollingUpdate:maxSurge:1# 更新时最多多出1个PodmaxUnavailable:0# 更新时不允许有Pod不可用template:metadata:labels:app:payment-serviceversion:v2.1.0spec:containers:-name:payment-serviceimage:registry.example.com/payment-service:v2.1.0ports:-containerPort:8080# 资源限制——这行千万不能漏resources:requests:cpu:500m# 保证分配0.5核memory:512Mi# 保证分配512MBlimits:cpu:2000m# 最多用2核memory:1Gi# 最多用1GB# 存活检查容器是否还在正常运行livenessProbe:httpGet:path:/actuator/health/livenessport:8080initialDelaySeconds:60# 关键给应用启动时间periodSeconds:15failureThreshold:3# 就绪检查是否可以接收流量readinessProbe:httpGet:path:/actuator/health/readinessport:8080initialDelaySeconds:30periodSeconds:10failureThreshold:3# 环境变量env:-name:SPRING_PROFILES_ACTIVEvalue:production-name:DB_PASSWORDvalueFrom:secretKeyRef:name:payment-secretkey:db-password-name:JAVA_OPTSvalue:-Xms512m -Xmx768m -XX:UseG1GC# 配置文件挂载volumeMounts:-name:configmountPath:/app/configvolumes:-name:configconfigMap:name:payment-config3.2 Service Ingress服务暴露# service.yamlapiVersion:v1kind:Servicemetadata:name:payment-servicenamespace:productionspec:selector:app:payment-service# 必须和 Deployment 的 label 完全一致ports:-port:80targetPort:8080type:ClusterIP# 集群内部访问---# ingress.yamlapiVersion:networking.k8s.io/v1kind:Ingressmetadata:name:payment-ingressnamespace:productionannotations:nginx.ingress.kubernetes.io/rewrite-target:/nginx.ingress.kubernetes.io/ssl-redirect:true# 限流配置nginx.ingress.kubernetes.io/limit-rps:100spec:ingressClassName:nginxtls:-hosts:-payment.example.comsecretName:payment-tlsrules:-host:payment.example.comhttp:paths:-path:/pathType:Prefixbackend:service:name:payment-serviceport:number:803.3 HPA自动扩缩容# hpa.yamlapiVersion:autoscaling/v2kind:HorizontalPodAutoscalermetadata:name:payment-hpanamespace:productionspec:scaleTargetRef:apiVersion:apps/v1kind:Deploymentname:payment-serviceminReplicas:3maxReplicas:20metrics:-type:Resourceresource:name:cputarget:type:UtilizationaverageUtilization:70# CPU使用率超过70%就扩容-type:Resourceresource:name:memorytarget:type:UtilizationaverageUtilization:80behavior:scaleUp:stabilizationWindowSeconds:60# 扩容前观察60秒policies:-type:Podsvalue:2periodSeconds:60scaleDown:stabilizationWindowSeconds:300# 缩容前等5分钟确认policies:-type:Percentvalue:10# 每次最多缩10%的PodperiodSeconds:60四、实战案例案例一零停机滚动更新# 更新镜像版本kubectlsetimage deployment/payment-service\payment-serviceregistry.example.com/payment-service:v2.2.0\-nproduction# 实时观察滚动更新状态kubectl rollout status deployment/payment-service-nproduction# 如果出问题一键回滚kubectl rollout undo deployment/payment-service-nproduction# 查看历史版本kubectl rollouthistorydeployment/payment-service-nproduction因为配置了maxUnavailable: 0和readinessProbeK8s 的更新流程是创建 1 个新 PodmaxSurge1新 Pod 通过 readinessProbe 检查后加入 Service 的 Endpoints旧 Pod 从 Endpoints 移除优雅关闭有 terminationGracePeriodSeconds重复直到所有 Pod 都更新整个过程流量零中断。案例二资源优化节省 40% 成本我们做过一次全集群的资源审计发现一个惊人的数据平均 CPU 实际使用率只有 Request 的 15%内存使用率也只有 30%。大量资源被浪费了。优化方案# 安装 metrics-serverHPA 依赖它kubectl apply-fhttps://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml# 查看每个 Pod 的实际资源使用kubectltoppods-nproduction --sort-bycpu# 根据实际使用调整 Request# 之前requests.cpu2000m, 实际使用 200m# 之后requests.cpu500m, limits.cpu2000m调整前后对比指标优化前优化后集群 CPU Request 总量96 核58 核集群 Memory Request 总量192 GB120 GB月云服务器成本¥86,000¥51,600实际性能表现无变化无变化省了 40% 的钱服务性能没有任何下降。Request 不是越多越安全设得太高反而导致 HPA 无法触发扩容集群没有足够资源调度新 Pod。五、踩坑实录坑 1健康检查把应用查死了现象Pod 不断重启CrashLoopBackOff。根因initialDelaySeconds设为 5 秒但 Spring Boot 应用启动需要 40-60 秒。5 秒后探针就开始探测连续失败 3 次就判定不健康K8s 杀掉容器重启。永远起不来。解决livenessProbe:httpGet:path:/actuator/health/livenessport:8080initialDelaySeconds:90# 给足启动时间periodSeconds:15failureThreshold:3# 更好的方案使用 startupProbestartupProbe:httpGet:path:/actuator/health/livenessport:8080failureThreshold:30# 最多允许失败30次periodSeconds:5# 每5秒检查一次# 总等待时间 30 * 5 150秒足够慢应用启动了startupProbe是 K8s 1.18 引入的特性专门解决慢启动问题。在 startupProbe 成功之前livenessProbe 不会执行。坑 2ConfigMap 更新了但应用没生效现象修改了 ConfigMap等了半天应用行为没变。根因ConfigMap 挂载到 Pod 是通过 kubelet 定期同步的默认 60 秒随机延迟而且很多应用不会自动 watch 配置文件变化。解决# 方案1在 Deployment 的 annotation 中注入 ConfigMap 的 hash# ConfigMap 变化 → hash 变化 → 触发 Pod 滚动更新spec:template:metadata:annotations:configHash:{{ .Values.configMapHash }}spec:containers:-name:appenvFrom:-configMapRef:name:app-config# 方案2推荐使用 Reloader 工具自动检测 ConfigMap/Secret 变化并触发滚动更新# helm install reloader stakater/reloader# 在 ConfigMap 上加 annotation# annotations:# reloader.stakater.com/auto: true坑 3Pod 被OOMKilled 但 Limits 还没到现象Java 应用频繁 OOMKilled但监控显示 JVM 堆内存远没到 Limit。根因Java 进程的内存 ≠ JVM 堆内存。JVM 还有 Metaspace、线程栈、直接内存、JIT 编译缓存等非堆开销。容器 Limits 限制的是整个进程的 RSS实际物理内存包括所有这些。解决# 方案1显式设置 JVM 内存留出非堆空间env:-name:JAVA_OPTSvalue:-Xms512m -Xmx768m# Limits 是 1Gi堆只设 768m留 256m 给非堆# 方案2使用 JDK 8u191 的容器感知特性env:-name:JAVA_OPTSvalue:-XX:MaxRAMPercentage75.0# 自动感知容器内存Limit的75%作为堆上限resources:limits:memory:1Gi经验法则Java 应用的容器内存 Limit 至少是-Xmx的 1.5 倍。更推荐用MaxRAMPercentage让 JVM 自己算。坑 4Namespace 资源配额导致部署失败现象kubectl apply报错Insufficient cpu, Insufficient memory。根因集群有足够的资源但 Namespace 配了 ResourceQuota这个 Namespace 的资源配额用完了。# 查看 Namespace 的资源配额kubectl get resourcequota-nproduction kubectl describe resourcequota-nproduction解决调整 ResourceQuota 或清理不再需要的资源。教训一定要给每个 Namespace 设置 ResourceQuota 和 LimitRange否则一个团队的服务可能吃掉整个集群的资源拖垮所有人。六、运维工具箱日常运维最常用的命令清单# 排查 Pod 问题kubectl describe podpod-name-nns# 看事件最常用的排查手段kubectl logspod-name-nns-f--tail100# 实时看日志kubectl logspod-name-nns-ccontainer# 多容器时指定容器kubectlexec-itpod-name-nns-- /bin/sh# 进入容器# 排查网络问题kubectl get endpointsservice-name-nns# 检查 Service 是否关联到 Podkubectl run debug--imagebusybox-it--rm--wget-qO- http://service-name# 从集群内部测试连通性# 节点维护kubectl cordonnode-name# 标记节点不可调度kubectl drainnode-name--ignore-daemonsets# 驱逐节点上的所有Podkubectl uncordonnode-name# 恢复调度# 快速查看集群状态kubectl get pods-A--field-selectorstatus.phase!Running# 所有非Running的Podkubectl get events-A--sort-by.lastTimestamp|tail-20# 最近的事件七、总结与思考核心收获K8s 的学习曲线陡峭但回报巨大。前期的痛苦换来的是后期的自动化和自愈能力。关键是不要在没理解核心概念的情况下就上手操作。资源 Request/Limits 是 K8s 调度的基石。设错了一切都会出问题——设高了浪费钱且 HPA 失效设低了应用被 OOMKilled。必须基于实际监控数据来调整。健康检查是 K8s 的命脉。没有正确配置 livenessProbe/readinessProbe 的应用K8s 无法判断它是否健康故障自愈就无从谈起。思考题你的应用启动需要多久initialDelaySeconds设置合理吗有没有考虑过 startupProbe你有没有做过集群资源审计CPU 和内存的 Request/实际使用比例是多少如果整个 K8s Master 节点挂了你的服务会怎样数据会丢失吗提示理解 etcd 的角色个人观点K8s 是一个入门门槛高但天花板也高的系统。我见过很多团队把它用成了高级 Docker Run——只用了 Deployment Service其他能力全浪费了。这就像买了一辆跑车只用来买菜。K8s 的真正价值在于让运维从手工操作变成代码管理。你的部署策略、扩缩容规则、资源配额全部是 YAML 文件可以版本控制、Code Review、自动化。这才是Infrastructure as Code的精髓。不过也要清醒不是所有团队都需要 K8s。如果你的服务只有三五个运维团队只有一两个人Docker Compose 或许就够了。工具是为业务服务的不是反过来。