一年前,我怀着对容器技术的热情,开始学习Kubernetes,想把我们的微服务架构迁移到K8s上。

那时候,K8s很火,几乎每个技术会议都在讲K8s,每个技术团队都在讨论K8s。我觉得,K8s是未来的趋势,是微服务架构的最佳实践,我们团队也应该跟上潮流,把服务迁移到K8s上。

于是,我开始了K8s的学习和实践之旅。从一开始的兴奋,到中间的迷茫,再到后来的崩溃,最后差点放弃。这一年,我经历了太多,踩了太多的坑,也学到了太多。

今天,我就来记录我K8s生产实践从入门到放弃(差点放弃)的经历,希望我的经历,能给正在学习和使用K8s的朋友一些参考和提醒。

一、入门:兴奋期

最开始学习K8s的时候,我是很兴奋的。

我看了K8s的官方文档,学了K8s的基本概念:Pod、Deployment、Service、ConfigMap、Secret、Namespace等。我觉得K8s的设计很优雅,概念很清晰,功能很强大。

然后,我在本地用minikube搭建了一个单节点的K8s集群,部署了一个简单的应用。当我看到应用成功运行,通过Service可以访问的时候,我特别兴奋,觉得K8s也没有那么难嘛,很容易上手。

接着,我又学习了K8s的一些高级功能,比如滚动更新、健康检查、自动扩缩容、资源限制、配置管理等。我觉得这些功能太强大了,有了K8s,运维变得简单多了,再也不用手动部署、手动扩容、手动管理配置了。

那时候,我对K8s充满了信心,觉得我们的服务迁移到K8s上之后,一切都会变得很美好。我甚至已经开始规划,什么时候把所有服务都迁移到K8s上,什么时候实现完全自动化运维。

现在回头看,那时候的我,还是太天真了。本地的minikube和生产环境的K8s集群,完全是两回事。minikube上能跑通,不代表生产环境就能稳定运行。

二、搭建集群:第一个坑

兴奋期过后,我开始搭建生产环境的K8s集群。

我们的生产环境有十几台服务器,我打算用kubeadm搭建一个多节点的K8s集群。本以为按照官方文档一步步来,很快就能搭好,结果没想到,第一步就踩坑了。

坑一:网络问题

kubeadm初始化集群的时候,需要从Google的镜像仓库拉取镜像,但是我们的服务器在国内,访问不了Google的镜像仓库,镜像拉取失败,集群初始化失败。

这个问题,我折腾了很久。最后是通过配置镜像加速器,或者手动从国内的镜像仓库拉取镜像,再重新打标签,才解决了这个问题。

坑二:内核版本问题

集群初始化成功之后,节点加入集群的时候,又遇到了问题。有些节点的内核版本太低(3.10.0),K8s的一些功能不支持,比如overlay网络、IPVS等,导致节点状态异常,Pod调度失败。

最后,我把所有节点的内核都升级到了4.4.x,问题才解决。

坑三:网络插件问题

集群搭好之后,需要安装网络插件(CNI),比如Flannel、Calico等。我选了Flannel,因为它比较简单。但是安装了Flannel之后,Pod之间的网络不通,Pod之间无法通信。

排查了很久,发现是防火墙的问题,Flannel需要的端口没有开放。开放了端口之后,网络才通。

但是,后来我们又遇到了Flannel的性能问题,跨节点的Pod通信延迟很高,而且不稳定。最后,我们换成了Calico,性能和稳定性才好了一些。

坑四:Docker存储驱动问题

集群搭好之后,运行了一段时间,又遇到了Docker的存储驱动问题。我们的节点用的是overlay2存储驱动,但是内核版本是3.10.0,overlay2有内存泄漏的bug(这个问题我在之前的文章里详细讲过),导致节点内存被耗尽,Pod频繁OOM重启。

最后,我们把所有节点的内核升级到了4.4.x,这个问题才彻底解决。

搭建集群这一步,我就踩了这么多坑,花了将近一个月的时间,才把集群搭建好,稳定运行。那时候,我已经开始觉得,K8s没有我想象的那么简单了。

三、迁移服务:更多的坑

集群搭好之后,我开始把服务迁移到K8s上。本以为集群搭好了,迁移服务就是写几个YAML文件的事情,结果没想到,迁移服务的坑更多。

坑五:Docker镜像问题

首先是Docker镜像的问题。我们的服务,之前是直接部署在服务器上的,没有Docker化。要迁移到K8s上,首先要把服务打包成Docker镜像。

写Dockerfile的时候,踩了很多坑:

  • 镜像体积太大,部署慢,占用磁盘空间多
  • 镜像构建慢,每次构建都要下载依赖,耗时很长
  • 镜像里有敏感信息(比如密码、密钥),不安全
  • 多阶段构建不熟悉,镜像优化做得不好

最后,我学习了Docker镜像优化的最佳实践,用多阶段构建、.dockerignore、合理的层缓存等,把镜像体积减小了很多,构建速度也提升了。

坑六:配置管理问题

然后是配置管理的问题。我们的服务,之前的配置是写在配置文件里的,不同环境(开发、测试、生产)有不同的配置。迁移到K8s上之后,用ConfigMap和Secret来管理配置。

但是,ConfigMap和Secret的使用,也踩了很多坑:

  • 配置更新之后,Pod不会自动重新加载配置,需要手动重启Pod,或者用sidecar来热加载
  • Secret默认是base64编码的,不是加密的,敏感信息不够安全
  • 配置和镜像耦合,更新配置需要重新构建镜像,或者用环境变量,管理麻烦
  • 多环境配置管理混乱,容易出错

最后,我们引入了配置中心(比如Apollo、Nacos),把配置从K8s的ConfigMap中抽离出来,用配置中心统一管理,才解决了配置管理的问题。

坑七:服务发现和负载均衡问题

然后是服务发现和负载均衡的问题。K8s的Service提供了服务发现和负载均衡的功能,但是在实际使用中,也踩了很多坑:

  • Service的负载均衡是基于iptables的,性能一般,在服务多、流量大的情况下,iptables规则太多,性能下降明显
  • 服务之间的调用,没有熔断、降级、限流等功能,一个服务出问题,可能导致级联失败
  • 外部访问服务,用NodePort或者LoadBalancer,都有局限性,NodePort端口范围有限,LoadBalancer需要云厂商支持
  • 灰度发布、蓝绿部署等高级发布策略,K8s原生的Service支持不好,需要用Ingress或者Service Mesh

最后,我们引入了Ingress(Nginx Ingress)做七层负载均衡,引入了服务网格(Istio)做服务治理,才解决了这些问题。但是,引入了更多的组件,复杂度也更高了,踩的坑也更多了。

坑八:持久化存储问题

然后是持久化存储的问题。我们有一些有状态的服务,比如数据库、缓存、消息队列等,需要持久化存储。K8s提供了PV和PVC来管理持久化存储,但是在实际使用中,也踩了很多坑:

  • 本地存储的话,Pod调度到哪个节点,数据就在哪个节点,Pod迁移之后数据丢失,不灵活
  • 网络存储(比如NFS、Ceph)的话,性能不如本地存储,而且配置复杂,稳定性也有问题
  • 存储的扩容、备份、恢复,都比较麻烦,需要额外的工具和方案
  • 有状态服务的部署、升级、扩缩容,都比无状态服务复杂很多

最后,我们的方案是:无状态服务全部迁移到K8s上,有状态服务(数据库、缓存、消息队列等)暂时不迁移到K8s上,还是用传统的方式部署,专门的服务器,专门的运维。等K8s的存储方案更成熟了,再考虑迁移有状态服务。

坑九:日志和监控问题

然后是日志和监控的问题。K8s集群里,Pod是动态的,会频繁创建和销毁,传统的日志和监控方案不适用了。

我们花了很多时间,搭建了基于ELK的日志收集系统,基于Prometheus+Grafana的监控系统,基于Jaeger的分布式追踪系统。这些系统的搭建和维护,本身就很复杂,踩了很多坑:

  • 日志收集,用Fluentd或者Filebeat,配置复杂,性能问题,日志丢失等
  • 监控指标太多,告警风暴,分不清哪些是重要的告警
  • 分布式追踪,采样率问题,性能开销,链路不完整等
  • 这些系统本身也需要高可用,也需要监控和运维

最后,这些系统搭好之后,我们才有了基本的可观测性,出了问题能排查。但是维护这些系统,也花了我们很多时间和精力。

迁移服务这一步,我踩了更多的坑,花了好几个月的时间,才把大部分无状态服务迁移到K8s上,并且搭建了配套的日志、监控、追踪系统。那时候,我已经开始有点崩溃了,觉得K8s太复杂了,维护成本太高了。

四、生产环境运维:崩溃期

服务迁移到K8s上之后,我以为终于可以松一口气了,结果没想到,生产环境的运维,才是真正的噩梦。

坑十:集群升级问题

K8s的版本更新很快,几乎每三个月就有一个新版本。我们的集群用了一段时间之后,版本就落后了,需要升级。

但是,K8s集群的升级,是一件非常危险的事情。升级过程中,可能会出现API不兼容、插件不兼容、节点升级失败、服务中断等问题。

我们第一次升级集群的时候,就出了问题:升级之后,一个网络插件不兼容,导致整个集群的网络不通,所有服务都不可用,紧急回滚之后才恢复。那次事故,影响了好几个小时,现在想起来还心有余悸。

从那以后,我们对集群升级非常谨慎,每次升级之前,都要在测试环境充分测试,确认所有组件都兼容,然后在业务低峰期滚动升级,并且做好回滚方案。即使这样,每次升级还是提心吊胆的。

坑十一:节点故障问题

生产环境的节点,偶尔会出现故障,比如硬件故障、系统崩溃、网络中断等。K8s有故障转移的机制,节点故障之后,上面的Pod会被调度到其他健康的节点上。

但是,在实际使用中,节点故障也踩了很多坑:

  • 节点故障之后,Pod调度到其他节点,需要时间,这段时间服务不可用
  • 有状态的Pod,调度到其他节点之后,数据丢失或者不一致
  • 节点故障之后,没有及时发现,导致服务长时间不可用
  • 节点恢复之后,Pod不会自动调度回来,资源分配不均衡

最后,我们完善了节点监控和告警,节点故障之后能及时发现和处理;对于关键服务,做了多副本和反亲和部署,确保节点故障之后服务不受影响;定期做节点故障演练,验证故障转移机制是否正常。

坑十二:资源问题

K8s的资源管理,也是一个大坑。我们一开始,没有给Pod设置资源请求和限制(requests和limits),结果导致:

  • 某个Pod占用了大量资源,影响了其他Pod的正常运行
  • 节点资源耗尽,Pod调度失败,或者OOM被杀死
  • 资源利用率低,很多节点的资源闲置,浪费钱

后来,我们给所有Pod都设置了资源请求和限制,情况好了一些。但是,资源请求和限制的设置,也是一门学问:

  • 设置得太小,Pod不够用,OOM被杀死,或者CPU throttling
  • 设置得太大,资源浪费,节点能调度的Pod少
  • 不同服务的资源需求不一样,需要根据实际情况调整
  • 服务的资源需求会变化,需要定期调整

最后,我们用了VPA(Vertical Pod Autoscaler)来自动调整Pod的资源请求,用HPA(Horizontal Pod Autoscaler)来根据流量自动扩缩容,才比较好地解决了资源管理的问题。但是,这些组件的配置和维护,也很复杂。

坑十三:安全问题

K8s的安全,也是一个很重要的问题。我们一开始,对K8s的安全不够重视,结果踩了很多坑:

  • 镜像里有漏洞,被黑客利用,入侵了集群
  • 服务账户权限过大,被黑客利用,窃取了数据
  • 网络策略没有配置,服务之间可以任意访问,横向渗透风险大
  • Secret没有加密,敏感信息泄露

后来,我们花了很多时间,做K8s的安全加固:

  • 镜像安全扫描,部署前扫描镜像漏洞
  • 最小权限原则,服务账户只给必要的权限
  • 配置网络策略,限制服务之间的访问
  • Secret加密,或者用外部的密钥管理服务
  • 定期做安全审计和渗透测试

安全这一块,水也很深,需要持续投入和维护。

坑十四:人才问题

最后,也是最大的问题:人才。

K8s太复杂了,需要专门的运维人员来维护。但是,懂K8s的运维人员,市场上很稀缺,工资也很高。我们团队一开始,只有我一个人在搞K8s,我既要做开发,又要做运维,还要踩坑排查问题,根本忙不过来。

有一段时间,我几乎每天都在处理K8s的问题,加班到很晚,周末也经常被叫起来处理故障。那时候,我真的快崩溃了,觉得K8s就是一个无底洞,投入了这么多时间和精力,但是还是问题不断,稳定性还不如以前的传统部署方式。

我甚至开始怀疑,我们是不是真的需要K8s?我们的服务规模,是不是还没到需要K8s的程度?我们是不是为了技术而技术,盲目上K8s,反而增加了复杂度和维护成本?

那段时间,我真的差点放弃了,想把服务从K8s上迁回去,回到传统的部署方式。

五、坚持:慢慢变好

就在我快要放弃的时候,发生了一件事情,让我坚持了下来。

有一次,我们做一个大促活动,流量是平时的好几倍。如果是以前的传统部署方式,我们需要提前手动扩容,准备很多服务器,大促之后再缩容,很麻烦,也很浪费。

但是,因为我们的服务已经迁移到K8s上了,并且配置了HPA自动扩缩容。大促期间,流量上来之后,HPA自动把服务的副本数扩容了好几倍,流量下去之后,又自动缩容了。整个过程,完全自动化,不需要人工干预,服务也很稳定,没有出问题。

那次大促之后,我突然觉得,K8s还是有价值的。虽然它很复杂,维护成本很高,但是它带来的好处也是实实在在的:自动化部署、自动扩缩容、故障自愈、环境一致性、资源利用率提升等。这些好处,在服务规模大了之后,会越来越明显。

从那以后,我不再想着放弃了,而是静下心来,慢慢把K8s用好,把坑一个个填上,把系统一点点稳定下来。

我做了以下这些事情:

  1. 完善文档和运维手册:把K8s的搭建、配置、运维、故障排查等,都写成文档,形成知识库,方便团队成员学习和参考。
  2. 培训团队成员:组织K8s的培训,让团队成员都了解K8s的基本概念和操作,不要只有我一个人懂K8s。
  3. 完善监控和告警:把K8s集群和服务的监控告警完善好,出了问题能及时发现和处理。
  4. 定期做故障演练:定期做故障演练,比如节点故障、网络分区、服务不可用等,验证系统的容错能力,也让团队成员熟悉故障处理流程。
  5. 逐步优化和演进:不要追求一步到位,根据团队的实际情况和业务需求,逐步优化和演进。先把基础功能用好,再慢慢引入高级功能。
  6. 关注社区和最佳实践:关注K8s社区的动态,学习最佳实践,借鉴别人的经验,少踩坑。

慢慢地,我们的K8s集群越来越稳定了,出问题的次数越来越少了,处理问题的速度也越来越快了。团队成员也越来越熟悉K8s了,不再是我一个人在扛了。

现在,我们的K8s集群已经稳定运行了大半年,大部分无状态服务都运行在K8s上,部署、扩缩容、故障恢复都实现了自动化,运维效率提升了很多,开发人员也可以更专注于业务开发了。

虽然K8s还是有很多坑,还是需要持续投入和维护,但是我不再后悔上K8s了。因为我看到了K8s带来的价值,也看到了团队的成长。

六、经验总结

回顾这一年的K8s生产实践经历,我总结了一些经验,希望能给正在学习和使用K8s的朋友一些参考。

经验一:不要盲目上K8s,先评估需求

K8s不是银弹,不是所有团队、所有项目都适合用K8s。在决定上K8s之前,先评估一下:

  • 你的服务规模有多大?服务少、流量小的话,传统部署方式可能更简单、更稳定。
  • 你的团队有没有能力维护K8s?K8s很复杂,需要专门的运维人员,没有这个能力的话,上K8s就是给自己挖坑。
  • 你的业务有没有K8s的刚需?比如频繁部署、自动扩缩容、多环境一致性等,如果没有这些刚需,K8s的价值不大。

不要为了技术而技术,不要因为K8s火就盲目上K8s。先评估需求,再决定要不要上,什么时候上,怎么上。

经验二:从小处着手,逐步迁移

如果决定上K8s,不要一上来就把所有服务都迁移过去。先拿一个非核心的、简单的服务做试点,熟悉K8s的基本操作和坑,积累经验。然后再逐步迁移更多的服务,从非核心到核心,从简单到复杂。

迁移过程中,做好回滚方案,万一出问题,能快速回滚到传统部署方式,减少影响。

经验三:重视文档和培训

K8s很复杂,概念多、组件多、坑多。一定要重视文档和培训,把K8s的搭建、配置、运维、故障排查等,都写成文档,形成知识库。同时,培训团队成员,让大家都了解K8s,不要只有一两个人懂,否则这两个人就是瓶颈,也是风险点。

经验四:完善监控和告警

K8s集群和服务的监控告警,一定要完善。没有监控告警,出了问题都不知道,等用户反馈了才知道,影响就大了。

监控要覆盖:集群层面(节点、组件、资源)、服务层面(Pod、Deployment、Service)、业务层面(响应时间、错误率、吞吐量)。告警要合理,避免告警风暴,重要的告警要及时通知到人。

经验五:定期做故障演练

K8s的故障转移、自愈等功能,不能只在理论上相信,要定期做故障演练,验证这些功能是否正常。比如,随机杀掉一个Pod,看看会不会自动重建;随机关掉一个节点,看看服务会不会自动转移;模拟网络分区,看看系统的容错能力。

故障演练,不仅能验证系统的可靠性,也能让团队成员熟悉故障处理流程,真出问题的时候不慌。

经验六:保持耐心,持续投入

K8s的学习和使用,是一个长期的过程,不可能一蹴而就。要有耐心,踩坑是正常的,出问题是正常的,关键是从坑和问题中学习,不断优化和改进。

K8s也在快速发展,新功能、新特性不断出现,需要持续学习和跟进。不要指望学一次就一劳永逸,要保持学习的心态,持续投入。

结语

K8s生产实践,从入门到放弃(差点放弃),我经历了很多,也学到了很多。

K8s是一个强大但是复杂的工具,它能带来很多好处,但是也需要很多投入。不要盲目上K8s,也不要因为K8s复杂就害怕它。根据自己的实际情况,理性评估,谨慎决策,逐步推进,持续投入,就能把K8s用好,发挥它的价值。

最后,用一句话总结我的经历:"K8s不是银弹,但是也不是洪水猛兽。它是一个工具,用好了能提升效率,用不好会增加负担。关键在于,你有没有想清楚为什么要用,有没有能力用好。"

愿每一个K8s实践者,都能少踩坑,多收获,把K8s用好,让技术为业务服务。