Kubernetes已经成为容器编排的事实标准,我用K8s也有三年了。从最初的1.17版本到现在的1.20,踩了很多坑,也总结了很多经验。本文是我使用K8s三年的一些感悟和总结,包括集群搭建、资源管理、网络配置、存储方案、监控告警、安全加固等方面的经验和教训。如果你正在使用K8s或者准备使用K8s,希望这篇文章能给你一些参考,帮你少走弯路。
一、我和K8s的缘分
先说说我是怎么开始用K8s的吧。
三年前,我们公司的业务开始快速增长,原来的部署方式已经跟不上了。那时候我们用的是传统的部署方式,代码写好之后打包成Jar包,然后手动上传到服务器,重启服务。这种方式效率很低,部署一次要花很长时间,而且很容易出错。
后来我们开始用Docker,把应用打包成镜像,部署起来方便了很多。但是随着服务越来越多,容器的管理又成了问题。几十个容器,手动管理根本忙不过来。这时候我们开始了解Kubernetes。
刚开始接触K8s的时候,我觉得它太复杂了。Pod、Service、Deployment、ConfigMap、Ingress,一大堆概念,看得我头都大了。而且K8s的架构也很复杂,Master节点、Node节点、etcd、API Server、Scheduler、Controller Manager,每个组件都有自己的作用。
但是没办法,业务需要,只能硬着头皮学。我花了大概一个月的时间,看官方文档,看教程,自己在虚拟机上搭集群,做实验。慢慢地,我对K8s有了基本的了解,然后开始在测试环境部署,最后上了生产环境。
这三年来,我们的K8s集群从最初的3个节点,发展到现在的几十个节点,承载了上百个服务。K8s的版本也从1.17升级到了1.20。在这个过程中,我踩了很多坑,也学到了很多东西。下面就把这些经验和教训分享给大家。
二、集群搭建:不要自己造轮子
第一个道理是关于集群搭建的。
刚开始的时候,我们想自己手动搭建K8s集群,觉得这样可以更深入地理解K8s的原理。于是我们按照官方文档,一个组件一个组件地安装,配置证书,配置网络,配置存储。折腾了好几天,终于把集群搭起来了。
但是这个集群问题很多。今天这个组件挂了,明天那个证书过期了,后天网络又不通了。而且升级版本的时候特别麻烦,要一个组件一个组件地升级,很容易出问题。
后来我们改用了kubeadm来搭建集群,发现简单了很多。kubeadm把很多复杂的配置都自动化了,几条命令就能把集群搭起来。而且升级版本也很方便,用kubeadm upgrade命令就能完成。
再后来,我们开始用托管的K8s服务(比如阿里云的ACK、腾讯云的TKE)。发现更加省心了,不需要自己管理Master节点,不需要担心etcd的备份和恢复,不需要操心集群的升级和维护。只需要专注于自己的应用部署就行。
所以我的建议是:如果是学习目的,可以自己手动搭建集群,深入理解K8s的原理。但是如果是生产环境,尽量用kubeadm或者托管服务,不要自己造轮子。自己搭建的集群维护成本很高,而且很容易出问题。
还有一个建议是,集群的节点不要太少。最少3个Master节点(保证高可用),3个Worker节点。不要用单Master节点的集群,一旦Master挂了,整个集群就不可用了。etcd也要部署在独立的节点上,不要和其他组件混部,保证etcd的性能和稳定性。
三、资源管理:一定要做资源限制
第二个道理是关于资源管理的。
刚开始用K8s的时候,我们没有给Pod设置资源限制(requests和limits)。结果有一次,某个应用出现了内存泄漏,把整个节点的内存都吃光了,导致这个节点上的所有Pod都被OOM杀掉了,影响了很多服务。
从那以后,我们给所有的Pod都设置了资源限制。requests是调度的时候用的,告诉K8s这个Pod至少需要多少资源。limits是运行时用的,限制这个Pod最多能用多少资源。
设置资源限制有几个好处。第一,可以防止某个Pod占用太多资源影响其他Pod。第二,可以让K8s更合理地调度Pod,把Pod调度到有足够资源的节点上。第三,可以根据资源使用情况来规划集群的容量,知道需要多少个节点。
但是设置资源限制也不是一件容易的事情。设置得太小,应用会因为资源不足而卡顿或者被OOM杀掉。设置得太大,会造成资源浪费,集群的利用率很低。
我们的经验是,先根据应用的历史资源使用情况设置一个初始值,然后在运行过程中监控实际的资源使用情况,再不断调整。可以用VPA(Vertical Pod Autoscaler)来自动分析和推荐资源限制,很方便。
另外,一定要给命名空间设置ResourceQuota,限制每个命名空间最多能用多少资源。这样可以防止某个团队的应用占用太多集群资源,影响其他团队。
四、网络配置:选对CNI很重要
第三个道理是关于网络配置的。
K8s的网络是通过CNI插件实现的。常见的CNI插件有Flannel、Calico、Canal、Weave、Cilium等。刚开始的时候,我们随便选了Flannel,因为它最简单,最容易部署。
但是用了一段时间之后,我们发现Flannel的功能比较有限。它只提供了基本的网络连通性,不支持NetworkPolicy(网络策略)。随着服务越来越多,我们需要做网络隔离,比如测试环境的服务不能访问生产环境的服务,这时候Flannel就不够用了。
后来我们换成了Calico。Calico支持NetworkPolicy,可以做精细的网络隔离。而且Calico的性能也不错,社区很活跃,文档也很完善。
换CNI插件是一件很麻烦的事情,需要重新配置整个集群的网络,还要把所有的Pod重建一遍。所以在一开始就要选对CNI插件,不要等到后面再换。
我的建议是,如果只是简单的测试环境,对网络没有特殊要求,可以用Flannel,简单快速。如果是生产环境,需要网络隔离和安全策略,建议用Calico或者Cilium。Cilium是基于eBPF的,性能更好,功能更强大,但是学习成本也更高。
另外,Service的类型也要选对。ClusterIP只能在集群内部访问,NodePort可以通过节点的端口访问,LoadBalancer可以通过云服务商的负载均衡器访问。生产环境建议用Ingress来统一管理外部访问,不要用NodePort,因为NodePort的端口范围有限,而且不方便管理。
五、存储方案:有状态应用要谨慎
第四个道理是关于存储的。
K8s对无状态应用的支持非常好,Deployment可以轻松地创建和管理多个副本,滚动升级也很方便。但是对有状态应用的支持就比较复杂了,比如数据库、消息队列、缓存这些需要持久化存储的应用。
刚开始的时候,我们想把MySQL也部署到K8s上,用PersistentVolume来存储数据。结果遇到了很多问题。比如PV的挂载很慢,导致Pod启动时间很长。比如节点故障的时候,PV不能及时挂载到新的节点上,导致服务不可用。比如备份和恢复很麻烦,没有方便的工具。
后来我们把MySQL从K8s上迁了出来,用独立的服务器或者云数据库来部署。K8s上只部署无状态的应用,比如Web服务、API服务、后台任务等。这样稳定了很多。
我的建议是,不要盲目地把所有应用都部署到K8s上。无状态应用非常适合K8s,但是有状态应用要谨慎。如果一定要在K8s上部署有状态应用,建议用StatefulSet,配合稳定的存储类(StorageClass),并且做好备份和恢复的方案。
存储类的选择也很重要。如果是在云上,可以用云服务商提供的云盘存储类,性能和稳定性都有保障。如果是自建集群,可以用Ceph、GlusterFS等分布式存储,但是运维成本比较高。本地存储(Local Volume)性能好,但是节点故障的时候数据会丢失,只适合对数据可靠性要求不高的场景。
还有一个建议是,重要的数据一定要定期备份。不管是在K8s上还是在K8s外,备份都是必须的。可以用Velero这个工具来备份K8s的资源和持久化数据,很方便。
六、监控告警:不能只监控节点
第五个道理是关于监控告警的。
刚开始的时候,我们只监控了节点的CPU、内存、磁盘这些基础指标。觉得只要节点正常,集群就正常。但是有一次,节点都正常,但是某个Pod一直重启,导致服务不可用,我们过了很久才发现。
从那以后,我们建立了完整的监控体系,包括几个层次。
第一个层次是节点监控,监控每个节点的CPU、内存、磁盘、网络等基础指标。用Node Exporter + Prometheus + Grafana来实现。
第二个层次是Pod监控,监控每个Pod的CPU、内存、网络、重启次数等指标。用cAdvisor(K8s自带)+ Prometheus + Grafana来实现。
第三个层次是应用监控,监控应用的业务指标,比如请求量、响应时间、错误率、队列长度等。在应用代码中埋点,用Prometheus Client暴露指标,或者用Jaeger做分布式追踪。
第四个层次是集群组件监控,监控K8s核心组件的状态,比如API Server、Scheduler、Controller Manager、etcd等。确保这些组件正常运行。
告警也要分级别。P0级别的告警(比如服务不可用、节点宕机)要立即通知,电话或者短信通知。P1级别的告警(比如Pod重启、资源使用率过高)要及时通知,企业微信或者钉钉通知。P2级别的告警(比如磁盘使用率超过80%)可以汇总通知,每天看一次就行。
不要设置太多告警,否则大家会麻木,真正重要的告警反而被忽略了。告警一定要精准,只在需要人工介入的时候才告警。
七、安全加固:不要忽视安全
第六个道理是关于安全的。
K8s的安全是一个很容易被忽视的方面。很多人觉得K8s默认就很安全,其实不是的。K8s有很多安全风险点,如果不加固,很容易被攻击。
我们就遇到过一次安全事件。因为我们的API Server没有做访问控制,被人扫描到了,然后通过API Server创建了一个有特权的Pod,在节点上执行了命令,挖了一段时间的矿。后来我们发现节点的CPU使用率异常高,才发现这个问题。
从那以后,我们对集群做了全面的安全加固。
第一,开启RBAC(基于角色的访问控制)。给不同的用户和服务账号分配不同的权限,遵循最小权限原则。不要用cluster-admin这个超级管理员权限给普通用户或者应用。
第二,API Server不要暴露在公网上。API Server只允许在内部网络访问,或者通过VPN访问。如果必须暴露在公网上,一定要开启认证和授权,并且限制访问IP。
第三,不要让Pod以root用户运行。在Dockerfile中指定非root用户,或者在Pod的SecurityContext中设置runAsNonRoot: true。防止容器被攻破之后获得root权限。
第四,不要使用特权容器(privileged: true)。特权容器可以访问宿主机的所有设备,非常危险。除非有特殊需求,否则不要用特权容器。
第五,定期更新K8s版本和基础镜像。K8s和基础镜像会不断有安全漏洞更新,要及时升级,修复已知的漏洞。
第六,用网络策略(NetworkPolicy)做网络隔离。默认情况下,K8s中所有Pod之间都是可以互相访问的。用网络策略可以限制Pod之间的访问,减少攻击面。
安全不是一次性的工作,而是持续的过程。要定期做安全审计,检查集群中有没有安全隐患。
八、配置管理:用ConfigMap和Secret
第七个道理是关于配置管理的。
刚开始的时候,我们把配置信息直接写在镜像里,或者写在代码里。这样每次修改配置都要重新构建镜像,重新部署,非常麻烦。而且不同环境(开发、测试、生产)的配置不一样,要维护多个版本的镜像,很容易出错。
后来我们开始用ConfigMap和Secret来管理配置。ConfigMap用来存非敏感的配置信息,比如应用配置文件、环境变量等。Secret用来存敏感信息,比如密码、密钥、证书等。
用ConfigMap和Secret有几个好处。第一,配置和镜像分离,修改配置不需要重新构建镜像。第二,不同环境可以用不同的配置,但是用同一个镜像。第三,Secret是加密存储的,比直接写在代码里安全。
但是用ConfigMap和Secret也要注意一些问题。第一,ConfigMap和Secret更新之后,已经挂载的Pod不会自动更新配置,需要重启Pod或者用热更新的方式。第二,Secret虽然是加密存储的,但是默认的加密方式是base64编码,不是真正的加密。如果对安全性要求高,需要开启K8s的加密配置,或者用外部的密钥管理服务(比如Vault)。
另外,配置不要太分散。如果配置太多,可以考虑用配置中心(比如Nacos、Apollo、Consul)来统一管理配置,而不是用很多个ConfigMap。配置中心可以提供配置的版本管理、灰度发布、实时推送等功能,比ConfigMap更强大。
九、日志管理:不要用kubectl logs看日志
第八个道理是关于日志管理的。
刚开始的时候,我们看日志都是用kubectl logs命令。但是这种方式有很多问题。第一,Pod重启之后,之前的日志就没了。第二,一个服务有多个副本的时候,要一个一个Pod地看日志,很麻烦。第三,不能搜索和过滤日志,找问题很费劲。
后来我们建立了集中式的日志系统。用Fluentd或者Filebeat在每个节点上收集日志,发送到Elasticsearch,然后用Kibana来查询和展示日志。这就是经典的EFK(Elasticsearch + Fluentd + Kibana)架构。
有了集中式的日志系统之后,排查问题方便了很多。可以按时间、服务、级别等条件搜索日志,可以跨多个Pod聚合日志,可以看到Pod重启之前的日志。大大提高了排查问题的效率。
建立日志系统的时候要注意几个问题。第一,日志的格式要统一,最好用JSON格式,方便解析和搜索。第二,日志量很大的时候,Elasticsearch的压力会很大,要合理设置分片和副本,并且定期清理旧日志。第三,敏感信息不要打印到日志里,比如密码、身份证号、手机号等,要做脱敏处理。
另外,日志的级别也要控制好。生产环境不要打太多DEBUG级别的日志,否则日志量会非常大,存储成本很高,也会影响性能。生产环境一般用INFO级别就够了,排查问题的时候再临时打开DEBUG。
十、升级和运维:制定规范和流程
第九个道理是关于升级和运维的。
K8s的升级是一件很重要的事情。K8s社区每3个月发布一个小版本,每个版本的支持周期是一年左右。如果不及时升级,版本太旧了就不再维护了,有安全漏洞也不会修复。
但是升级K8s也有风险,新版本可能会有不兼容的变化,可能会导致应用出问题。所以升级之前一定要做好充分的准备。
我们的升级流程是这样的。第一,先看新版本的更新日志,了解有哪些新特性和不兼容的变化。第二,在测试环境升级,测试所有的应用是否正常。第三,在生产环境的一个小集群上升级,观察一段时间。第四,在生产环境的大集群上升级,选择业务低峰期操作,并且做好回滚的准备。
除了K8s本身的升级,应用的部署也要有规范。我们制定了CI/CD流程,代码提交之后自动构建镜像,自动部署到测试环境,测试通过之后手动确认部署到生产环境。生产环境的部署用滚动更新,保证服务不中断。并且每个部署都要有回滚的能力,出了问题可以快速回滚到上一个版本。
运维方面,我们制定了值班制度,每天有人值班,负责处理告警和突发事件。并且有详细的运维手册,记录了常见问题的处理方法,新同事也能快速上手。
K8s的运维不是一件简单的事情,需要有规范和流程,不能靠个人经验。只有建立了完善的规范和流程,才能保证集群的稳定运行。
十一、写在最后
用了三年K8s,我最大的感受是:K8s是一个强大但是复杂的系统。
它的强大之处在于,它提供了一套完整的容器编排方案,可以自动化地部署、扩展、管理应用。用了K8s之后,我们的部署效率大大提高,系统的稳定性也大大提升。
它的复杂之处在于,它涉及的概念和组件太多了,学习曲线很陡峭。而且K8s本身也在快速发展,新特性不断涌现,需要持续学习。
这三年来,我踩了很多坑,也学到了很多东西。上面分享的这些道理,都是我用实际的故障和教训换来的。希望能帮助正在使用K8s或者准备使用K8s的朋友,少走一些弯路。
最后我想说的是,K8s不是银弹,不是所有的应用都适合用K8s。如果你的服务很少,流量很小,用K8s可能反而增加了复杂度。这时候用简单的部署方式可能更合适。但是如果你的服务很多,需要频繁部署,需要弹性伸缩,那么K8s绝对是一个好选择。
技术没有最好的,只有最合适的。根据自己的实际情况选择合适的技术,才是最重要的。
用一句话结束本文:"K8s给了我们管理容器的超能力,但真正的稳定性来自于对细节的敬畏和对规范的坚持。"愿每一个K8s使用者都能驾驭这个强大的工具,让它为业务创造更大的价值。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录