说到 Kubernetes(K8s),很多开发兄弟的第一反应都是“真香”之后的“真香真难伺候”。我们团队在落地这套架构的时候,也经历过从“部署即胜利”到“半夜被报警电话叫醒”的血泪史。今天不聊那些枯燥的官方文档概念,直接把我们踩过的坑、修过的bug、优化过的性能指标摊开来聊聊。毕竟,知道怎么修比知道什么是Pod重要多了。
一、 起步阶段:那些你以为简单其实致命的配置陷阱
很多项目一开始设计得很完美,微服务拆分得干干净净,结果一上K8s,集群直接瘫痪。回头看,问题大多出在初始资源配置的“随手一写”上。
1.1 资源请求(Requests)与限制(Limits)的博弈
这是新手最容易忽视,也是线上故障最高发的点之一。很多同学在写YAML时,直接这么写:
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
看起来挺合理对吧?但这里有个巨大的隐患:QoS(服务质量)等级的误判。
如果你只设置了Requests,没设置Limits,Pod的QoS等级是Burstable;如果Requests等于Limits,是Guaranteed;如果只设置了Limits,没设置Requests,那这个Pod在节点资源紧张时,会被优先驱逐(Eviction),因为它没有声明自己的“保底需求”。
实战案例: 记得有一次,一个Java微服务因为没有限制CPU,在启动阶段进行大量的类加载和索引构建,CPU瞬间飙到100%并持续占用。由于没有Limit,它吃掉了节点上所有其他Pod的CPU配额,导致整个节点上的Nginx入口 Pod 因为CPU Throttling(限流)而响应超时,用户端大面积报错。
避坑指南:
- 必须同时设置 Requests 和 Limits。
- CPU Limit 要保守:如果你的应用是CPU密集型,Limit 设为 Request 的 1-2 倍;如果是内存密集型,内存 Limit 最好设为 Request 的 1.5 倍左右,给堆外内存(Off-heap)留出空间。
- 开启 Cgroup 内存限制支持:对于 Java 应用,记得在 Jvm 启动参数里加上
-XX:+UseContainerSupport,否则 JVM 感知不到容器限制,可能会申请超过容器限额的内存,导致被 OOM Kill。
1.2 镜像拉取策略与镜像仓库认证
别以为镜像推上去就万事大吉。在私有仓库(如 Harbor、阿里云 ACR)部署时,imagePullSecrets 配置不当会导致 Pod 一直处于 ImagePullBackOff 状态。
常见错误排查:
当遇到 ImagePullBackOff 时,首先检查:
kubectl describe pod <pod-name> -n <namespace>
看 Events 部分。如果是 unauthorized: authentication required,说明 Secret 没挂载对或者 Secret 里的用户名密码过期了。
最佳实践:
使用 imagePullPolicy: IfNotPresent 在测试环境,减少网络负担;在生产环境,务必使用 Always 或者结合镜像标签(Tag)使用 IfNotPresent,避免缓存旧版镜像导致线上bug。
二、 网络篇:Service 与 Ingress 的血雨腥风
微服务之间调用、外部流量进入,全靠网络层。这里的问题往往表现为“能通但不能快”或者“间歇性不可用”。
2.1 Service 类型选错的代价
很多团队习惯性地对所有 Service 都使用 NodePort,觉得这样暴露端口最直观。但在大规模集群中,这是性能杀手。
对比分析:
- ClusterIP:默认类型,只在集群内部可达,性能最好,无额外开销。
- NodePort:在每个 Node 上开放一个端口,流量需要经过 iptables/ipvs 规则转发两次(从 Node Port -> ClusterIP -> Pod IP),延迟增加。
- LoadBalancer:依赖云厂商的 LB,适合对外暴露,但成本高,且受限于云厂商配额。
实战建议: 对外暴露统一使用 Ingress + LoadBalancer 类型的 Ingress Controller(如 Nginx Ingress 或 Traefik)。Ingress 作为七层负载均衡,可以做路由、SSL 终止、限流,比直接暴露 Service 灵活且高效得多。
2.2 DNS 解析慢与 CoreDNS 瓶颈
随着服务数量增加,集群内的 DNS 查询压力暴增。很多开发者发现,服务间调用首次连接特别慢,后续就正常了。这通常是 DNS 缓存未命中或 CoreDNS 资源不足导致的。
排查步骤:
- 检查 CoreDNS 的 Pod 资源限制:
# coreDNS deployment.yaml 片段
resources:
requests:
cpu: 100m
memory: 70Mi
limits:
cpu: 200m
memory: 170Mi
如果内存限制太低,CoreDNS 可能会因为内存不足而重启,导致 DNS 解析抖动。
- 增加 CoreDNS 副本数。默认可能是 1 或 2 个副本,在大规模集群中建议至少 3 个,并配合
maxReplicas进行 Horizontal Pod Autoscaler (HPA) 配置。
优化技巧:
在应用侧,适当调大 DNS 缓存时间。例如,在 Java 应用中设置 -Dsun.net.inetaddr.ttl=30,表示 DNS 解析结果缓存 30 秒,减少频繁查询对 CoreDNS 的压力。
2.3 NetworkPolicy 的安全盲区
很多团队为了省事,完全不放 NetworkPolicy,或者随便写一个允许所有流量的规则。这等于给集群开了大门。
建议: 采用“默认拒绝,最小权限开放”的策略。先编写一个默认的 Deny 策略,然后逐步为每个微服务添加允许其上下游通信的 Policy。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
然后在具体的服务中,只开放必要的端口和来源。
三、 稳定性与高可用:如何避免单点故障
K8s 的设计初衷就是高可用,但如果部署不当,反而会把单点故障放大。
3.1 副本数与反亲和性(Anti-Affinity)
默认情况下,K8s 可能会把同一个 Deployment 的所有 Pod 调度到同一台 Node 上。一旦这台机器宕机,整个服务就挂了。
必须配置 Pod 反亲和性:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- my-service
topologyKey: "kubernetes.io/hostname"
这段配置强制要求相同标签的 Pod 不能调度到同一台主机上,确保即使一台机器挂了,服务仍然可用。
3.2 健康检查:Liveness 与 Readiness 的区别
很多开发者把 Liveness 和 Readiness 写得一模一样,这是错误的。
- Liveness Probe:问“你还活着吗?”如果失败,K8s 会重启容器。用于检测应用是否死锁或陷入无限循环。
- Readiness Probe:问“你可以接收流量了吗?”如果失败,K8s 会从 Service 的 Endpoints 中移除该 Pod,但不会重启它。用于检测应用是否还在初始化、依赖的数据库是否连接成功。
实战坑点:
如果一个应用启动很慢(比如 Spring Boot 需要 2 分钟),而你的 Liveness Probe 设置 initialDelaySeconds=10,那么应用还没启动完,K8s 就会认为它死了,反复重启,导致应用永远无法上线(CrashLoopBackOff)。
正确姿势:
- Readiness:
initialDelaySeconds设置得大一些(如 30-60 秒),periodSeconds设为 5-10 秒。 - Liveness:
initialDelaySeconds也要相应调大,或者使用exec命令执行一个轻量的心跳检查,而不是依赖 HTTP 端口(因为 HTTP Server 可能还没起来)。
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 120
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 60
periodSeconds: 5
3.3 Pod 优先级与预emption
当集群资源紧张时,K8s 需要决定驱逐哪些 Pod。这时候 PriorityClass 就派上用场了。
给核心业务(如网关、数据库客户端)设置高优先级,给批处理任务(如数据同步、日志采集)设置低优先级。这样在资源不足时,K8s 会优先驱逐低优先级的 Pod,保住核心业务。
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for core services."
然后在 Deployment 中引用:
spec:
priorityClassName: high-priority
四、 存储篇:无状态与有状态服务的分离
K8s 擅长管理无状态服务,但对于有状态服务(如 MySQL、Redis),如果配置不当,数据丢失或服务中断的风险极高。
4.1 PersistentVolume (PV) 与 PersistentVolumeClaim (PVC) 的绑定
很多团队直接使用动态 Provisioner,而没有考虑存储类(StorageClass)的性能差异。
建议: 为不同的业务场景创建不同的 StorageClass。
- ssd:用于数据库主库,要求高 IOPS。
- standard:用于日志存储、备份数据,成本较低。
- nfs:用于共享配置文件或静态资源。
在 PVC 中明确指定 StorageClass,避免系统默认使用性能较差的存储。
4.2 StatefulSet 的有序部署与扩容
对于有状态服务,务必使用 StatefulSet 而不是 Deployment。StatefulSet 保证了 Pod 的名称、顺序和唯一性,这对于数据库主从复制、消息队列的分区管理至关重要。
扩容时的坑:
StatefulSet 扩容时是有序进行的(从 0 到 N),这虽然保证了稳定性,但速度较慢。在紧急扩容时,可以通过修改 updateStrategy 为 OnDelete 或手动删除 Pod 来加速,但需谨慎操作,确保数据一致性。
4.3 数据备份与恢复
不要指望 K8s 本身能备份数据。必须建立独立的备份机制。
- 对于 MySQL,可以使用 Velero 或专门的备份工具(如 XtraBackup)定期备份到对象存储(S3/OSS)。
- 定期演练恢复流程,否则备份就是伪备份。
五、 监控与日志:看不见就是盲区
“你无法优化你无法衡量的东西。” 没有监控的 K8s 集群就像是在黑夜中开车。
5.1 监控栈选型:Prometheus + Grafana
这是 K8s 生态的事实标准。
- Prometheus:负责采集指标(Metrics)。
- Grafana:负责可视化展示。
- Alertmanager:负责告警通知。
关键指标监控:
- 节点层面:CPU 使用率、内存使用率、磁盘 I/O、网络吞吐量。
- Pod 层面:CPU/内存使用量、重启次数、吞吐量(QPS)、延迟(Latency)、错误率。
- 应用层面:JVM 堆内存、GC 次数、数据库连接池状态、RPC 调用链。
避坑提示: Prometheus 的存储是时间序列,长时间保留大量细粒度指标会占用巨大磁盘。建议:
- 短期(7-30天)保留高粒度数据。
- 长期归档到低成本存储(如 S3 + Thanos 或 Cortex)。
- 合理设置
scrape_interval和evaluation_interval,不要所有指标都 15s 采集一次。
5.2 日志收集:EFK vs LKM
- ELK (Elasticsearch, Logstash, Kibana):功能强大,但资源消耗大,维护复杂。
- EFK (Elasticsearch, Fluentd, Kibana):用 Fluentd 替换 Logstash,更轻量。
- Loki + Promtail + Grafana:由 Grafana 实验室开发,与 Prometheus 集成更好,存储成本更低,适合大规模集群。
实战选择: 如果集群规模不大(<100 节点),EFK 足够;如果规模大且关注成本,推荐 Loki。注意,日志收集 DaemonSet 的资源限制要合理,避免收集组件本身吃掉过多集群资源。
六、 持续交付(CI/CD)与 GitOps
手动 kubectl apply 上线在生产环境是危险的。自动化发布流程不仅能提高效率,还能减少人为错误。
6.1 主流方案对比
- Jenkins + K8s Plugin:老牌方案,灵活但配置复杂,维护 Jenkins 本身也是负担。
- GitLab CI/CD:集成度高,适合使用 GitLab 的团队,但 Pipeline 编写复杂。
- Argo CD (GitOps):当前最流行的方案。以 Git 为单一事实来源,Argo CD 自动同步 Git 仓库中的 YAML 配置到 K8s 集群。
6.2 为什么推荐 Argo CD?
核心理念:声明式配置。你只需要在 Git 中维护 desired state(期望状态),Argo CD 会不断观察集群的 current state(当前状态),并自动修复偏差。
优势:
- 版本控制:所有变更都有 Git 记录,可随时回滚。
- 可视化:Web UI 清晰展示应用状态和差异。
- 自动同步:支持自动检测变更并应用,或手动审批后应用。
- 多集群管理:一个 Argo CD 实例可以管理多个 K8s 集群。
简易工作流程:
- 开发人员修改应用代码,提交代码到 Git。
- CI 工具(如 Jenkins/GitLab)构建 Docker 镜像,推送到镜像仓库,并更新 K8s YAML 中的镜像版本号。
- 镜像版本号提交到 GitOps 仓库(应用配置仓库)。
- Argo CD 检测到 Git 仓库变更,自动将新版本应用到 K8s 集群。
- 应用健康检查通过后,部署完成。
七、 性能优化与成本控制
集群跑起来后,成本和性能是管理层最关心的问题。
7.1 资源超卖与弹性伸缩
- CPU 超卖:由于大多数应用不会长期占用 100% CPU,可以适当超卖 CPU。例如,请求总和可以是节点 CPU 核数的 2-3 倍。
- HPA (Horizontal Pod Autoscaler):基于 CPU、内存或自定义指标(如 QPS)自动伸缩 Pod 数量。设置合理的最小和最大副本数,既保证性能,又避免资源浪费。
- VPA (Vertical Pod Autoscaler):自动调整单个 Pod 的资源请求和限制,适合初期调试和长期优化资源配置。
7.2 集群节点优化
- 预留系统资源:在节点启动 kubelet 时,通过
--system-reserved和--kube-reserved参数预留 CPU 和内存给系统进程和 Kubelet 自身,避免业务 Pod 挤占系统资源导致节点不稳定。 - 专用节点:将不同性质的工作负载调度到不同节点。例如,将计算密集型任务调度到高性能节点,将长期运行的基础组件(如 CoreDNS、监控组件)调度到 dedicated 节点,避免相互干扰。
7.3 关闭不必要的组件
默认安装的 K8s 集群可能会有一些不用的组件(如 Dashboard、某些 CNI 插件)。定期审查并关闭不需要的组件,减少资源占用和安全攻击面。
结语:K8s 是一场持久战
K8s 的学习曲线确实陡峭,但它带来的弹性、可扩展性和运维自动化收益是巨大的。避坑的关键在于:理解原理、从小规模试点开始、建立完善的监控和告警、坚持 GitOps 实践。
不要指望一次配置就能一劳永逸,集群是需要持续运营和优化的。希望这份指南能帮你少熬几个通宵,多睡几个安稳觉。如果还有什么具体的故障场景想探讨,随时交流!
