先说容器网络这块。刚开始用默认的bridge网络模式,发现微服务之间经常出现调用超时。通过tcpdump抓包分析,发现容器跨节点通信时存在明显的丢包现象。后来改成macvlan模式,又遇到交换机ARP表爆炸的问题,直接把物理网络搞出广播风暴。最后被迫上了Calico方案,配合BGP协议实现容器网络与物理网络互通。这里有个细节:记得调整iptables规则,避免kube-proxy和Docker自带的iptables规则冲突。我们当时就因为没清理历史规则,导致NodePort服务始终无法正常访问。
存储方案的选择更是让人头大。最初直接挂载宿主机的目录到容器里,结果发现并发写文件时经常出现权限混乱。后来改用CephFS,又遇到小文件读写性能差的问题。经过测试发现,当单个Pod同时读写超过200个小文件时,IO延迟会从20ms飙升到800ms。最终方案是分级存储:热点数据用本地SSD盘做hostPath,冷数据放到CephFS,重要数据再通过Velero做定期快照到对象存储。特别要注意的是,在K8s里挂载CephFS时必须设置mountOptions中的noatime参数,否则inode缓存会快速耗尽。
配置管理这块也踩过坑。最初把应用配置直接打包进镜像,每次改配置都要重新构建镜像。后来改用ConfigMap,又发现中文配置项经常出现乱码。最后摸索出的最佳实践是:基础环境变量用ConfigMap,敏感信息用Secret,动态配置通过Apollo配置中心管理。特别提醒:ConfigMap挂载为volume时,要设置subPath避免覆盖整个目录,否则容器内其他文件会神秘消失。
监控诊断方面,传统的那套监控体系基本报废。我们最初尝试在容器里安装Agent,结果发现容器重启后监控数据就丢了。后来改用DaemonSet方式部署监控采集器,又遇到资源竞争问题。最终方案是:业务容器通过sidecar模式输出日志和指标,由Prometheus统一采集,再通过Grafana展示。关键是要给每个容器设置合理的resource.limits,否则某个容器内存泄漏时会把整个节点拖垮。我们就遇到过因为JVM堆内存设置过大,导致节点OOM被系统kill的情况。
健康检查的配置看似简单,实则暗藏玄机。刚开始只配置了存活探针,结果应用假死时探针始终返回成功。后来补上了就绪探针,又因为检测间隔设置太短,导致Pod在启动过程中就被重启了七八次。血泪教训:livenessProbe的initialDelaySeconds一定要大于应用真实启动时间,readinessProbe的failureThreshold要适当放宽,特别是对Java应用这种启动慢的类型。
镜像仓库的维护也是痛点多发区。自建Harbor仓库时没做垃圾回收,半年时间500G的磁盘就被僵尸镜像占满了。后来设置自动清理策略时又误删了生产环境正在使用的历史镜像,导致部署失败。现在我们的策略是:主分支镜像保留30天,特性分支镜像保留7天,所有镜像都打上CI流水线编号作为标签。
最后给几个关键建议:第一,容器化改造前必须先做好日志规范,所有应用必须输出结构化日志;第二,网络策略要提前规划,用NetworkPolicy做好微服务间的访问控制;第三,一定要搭建镜像安全扫描流程,我们曾发现基础镜像里有高危漏洞;第四,资源限制必须配置,这是保证集群稳定的生命线。
经过半年折腾,现在我们的容器平台总算稳定了。最大体会是:容器化不是简单地把应用塞进Docker,而是要从架构设计、开发流程到运维体系都做出相应调整。下次有机会再和大家聊聊我们在服务网格落地时踩的新坑。