最近在项目中尝试用Kubernetes做容器编排,搭建了一套测试环境,整个过程踩了不少坑,走了不少弯路。Kubernetes是现在最火的容器编排工具,是云原生时代的核心技术,功能强大,生态完善,但是学习曲线也比较陡峭,概念很多,配置也比较复杂,刚开始用的时候,很容易踩坑。
今天就来分享一下我初学Kubernetes的踩坑总结和实战经验,包括Kubernetes的核心概念、搭建过程、常见问题、配置技巧、最佳实践等,希望能给正在学习Kubernetes的朋友一些参考,少踩坑,少走弯路。
一、为什么要学Kubernetes
先说说为什么要学Kubernetes吧。现在容器化越来越普及,Docker已经成为了容器的标准,但是Docker只是容器运行时,只能管理单个容器,当服务多了,容器多了,就需要一个容器编排工具,来管理大量的容器,包括服务发现、负载均衡、自动扩缩容、滚动更新、故障自愈、存储管理、网络管理等。
容器编排工具有很多,比如Kubernetes、Docker Swarm、Mesos、Nomad等,但是现在Kubernetes已经成为了事实标准,几乎所有的云厂商都支持Kubernetes,生态也最完善,社区最活跃,所以学习Kubernetes,是现在后端开发和运维的必备技能。
我们的项目,之前用的是Docker Compose做容器编排,但是Docker Compose只能在单台机器上运行,不支持跨主机编排,也不支持自动扩缩容、滚动更新等高级功能,随着项目规模的增长,已经不够用了,所以我们决定迁移到Kubernetes,学习和使用Kubernetes,也是为了跟上技术发展的趋势,提升我们的技术能力和系统的可扩展性。
二、Kubernetes的核心概念
在说踩坑经验之前,先简单介绍一下Kubernetes的核心概念,理解了这些概念,才能更好地使用Kubernetes。
Kubernetes的核心概念很多,这里只介绍最常用、最核心的几个:
1. Pod
Pod是Kubernetes中最小的调度单元,一个Pod可以包含一个或多个容器,这些容器共享网络和存储,就像在同一台机器上一样。一般来说,一个Pod里只放一个容器,除非有特殊情况,比如sidecar模式,才会在一个Pod里放多个容器。
Pod是短暂的,是可以随时创建和销毁的,所以不要把数据存在Pod里,要把数据存在持久化存储里。
2. Deployment
Deployment是用来管理Pod的,它定义了Pod的模板、副本数量、更新策略等,Deployment会保证Pod的副本数量符合预期,如果某个Pod挂了,Deployment会自动创建一个新的Pod,保证服务的可用性。
Deployment还支持滚动更新,可以逐步更新Pod的版本,不中断服务,也支持回滚,如果更新出了问题,可以快速回滚到之前的版本。
一般来说,我们的应用服务,都是用Deployment来管理的。
3. Service
Service是用来做服务发现和负载均衡的。因为Pod是短暂的,IP地址会变化,所以不能直接用Pod的IP来访问服务,需要用Service来暴露服务,Service有一个固定的IP和域名,会把请求转发到后端的Pod,实现负载均衡。
Service有几种类型:
- ClusterIP:集群内部访问,默认类型,只能在集群内部访问。
- NodePort:通过节点的端口访问,可以从集群外部访问。
- LoadBalancer:通过云厂商的负载均衡器访问,需要云厂商支持。
- ExternalName:把外部服务映射到集群内部。
一般来说,集群内部的服务通信用ClusterIP,需要从外部访问的服务用NodePort或者LoadBalancer。
4. Namespace
Namespace是用来做资源隔离的,可以把集群分成多个命名空间,不同的命名空间之间的资源是隔离的,比如可以把开发环境、测试环境、生产环境放在不同的命名空间,互不干扰。
一般来说,一个项目或者一个环境,用一个Namespace,方便管理和隔离。
5. ConfigMap和Secret
ConfigMap是用来存储配置信息的,比如配置文件、环境变量等,可以把配置和镜像分开,不用每次改配置都重新构建镜像。
Secret和ConfigMap类似,但是用来存储敏感信息,比如密码、密钥、证书等,Secret的数据是加密存储的,更安全。
6. Volume和PersistentVolume
Volume是用来存储数据的,因为Pod是短暂的,容器销毁之后,数据就没了,所以需要用Volume来持久化存储数据。Volume有很多种类型,比如emptyDir、hostPath、nfs、configMap、secret、persistentVolumeClaim等。
PersistentVolume(PV)是持久化存储卷,是集群中的一块存储资源,可以由管理员预先创建,也可以由存储类动态创建。PersistentVolumeClaim(PVC)是用户对存储的请求,用户创建PVC,Kubernetes会自动把PVC绑定到合适的PV上,然后Pod就可以使用这个存储了。
7. Ingress
Ingress是用来做HTTP/HTTPS路由的,可以把不同的域名或者路径,路由到不同的Service,实现统一的入口和负载均衡。Ingress需要配合Ingress Controller使用,比如Nginx Ingress Controller、Traefik等。
一般来说,集群对外的HTTP/HTTPS服务,都用Ingress来暴露,比NodePort和LoadBalancer更灵活,更强大。
这些是Kubernetes最核心的概念,理解了这些,就能基本使用Kubernetes了。当然,Kubernetes还有很多其他的概念,比如StatefulSet、DaemonSet、Job、CronJob、HPA、RBAC、NetworkPolicy等,这些可以在使用的过程中慢慢学习。
三、搭建Kubernetes集群的踩坑经验
了解了核心概念之后,现在来说说搭建Kubernetes集群的踩坑经验。我搭建的是测试环境,用的是kubeadm工具,在三台虚拟机上搭建的,一主两从,整个过程踩了不少坑。
1. 系统和环境准备的坑
搭建Kubernetes集群,首先要准备系统环境,这一步就有很多坑。
- 操作系统版本:Kubernetes对操作系统版本有要求,太老的系统不支持,我一开始用的是CentOS 6,结果发现不支持,后来换成了CentOS 7,才可以。建议用Ubuntu 18.04/20.04或者CentOS 7/8,这些是官方支持的系统。
- 关闭防火墙:Kubernetes的组件之间需要通信,端口很多,如果防火墙开着,会导致通信失败,所以要关闭防火墙,或者开放对应的端口。我一开始没关防火墙,结果节点加入集群失败,排查了很久才发现是防火墙的问题。
- 关闭SELinux:SELinux是Linux的安全模块,会限制容器的访问,导致很多问题,所以要关闭SELinux,或者设置为permissive模式。
- 关闭交换分区:Kubernetes要求关闭交换分区(swap),不然kubelet启动会失败。我一开始没关swap,结果kubelet启动失败,报错,后来关闭了swap才正常。关闭swap可以用swapoff -a命令,还要修改/etc/fstab文件,注释掉swap的行,不然重启之后又会开启。
- 主机名和IP配置:每个节点的主机名要不一样,并且要能互相解析,最好在/etc/hosts文件里配置所有节点的IP和主机名,确保能互相通信。IP地址要固定,不要用DHCP,不然IP变了,集群就出问题了。
- 时间同步:集群中的所有节点,时间要同步,不然会导致证书验证失败、认证失败等问题。要用ntp或者chrony做时间同步,确保所有节点的时间一致。
这些环境准备的坑,都是很基础的,但是很容易忽略,一旦忽略,就会导致各种奇怪的问题,排查起来很麻烦,所以搭建之前,一定要把环境准备好。
2. 镜像下载的坑
Kubernetes的组件很多,需要下载很多镜像,但是这些镜像大多在国外的镜像仓库,国内访问很慢,甚至访问不了,这是国内用户最大的坑。
我一开始用官方的配置,结果镜像下载失败,一直卡着,后来才知道是网络问题。解决方法有几个:
- 用国内的镜像源,比如阿里云的镜像源,在kubeadm init的时候,指定--image-repository参数,用阿里云的镜像仓库。
- 提前手动下载需要的镜像,然后打标签,改成kubeadm需要的镜像名。
- 配置Docker的镜像加速器,比如阿里云的镜像加速器,提高镜像下载速度。
我用的是阿里云的镜像源,在kubeadm init的时候加上--image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers,这样镜像就从阿里云下载,速度很快,不会失败。
除了Kubernetes的组件镜像,还有应用的镜像,如果用的是Docker Hub的镜像,国内访问也很慢,也要配置镜像加速器,或者用国内的镜像仓库,比如阿里云容器镜像服务、腾讯云容器镜像服务等,把镜像同步到国内,提高下载速度。
3. kubeadm init的坑
环境准备好,镜像也能下载了,就可以用kubeadm init初始化主节点了,这一步也有一些坑。
- 指定pod-network-cidr:初始化的时候,要指定--pod-network-cidr参数,这是Pod的网络段,不同的网络插件(比如Flannel、Calico)需要的网段不一样,要根据网络插件的要求来指定,不然网络插件会工作不正常,Pod之间无法通信。我用的是Flannel,需要指定--pod-network-cidr=10.244.0.0/16。
- 指定apiserver-advertise-address:如果主节点有多个网卡,要指定--apiserver-advertise-address参数,指定API Server对外广播的地址,不然可能会用错网卡的地址,导致节点无法加入集群。
- 保存kubeadm join命令:kubeadm init成功之后,会输出一个kubeadm join命令,这个命令是用来让从节点加入集群的,里面有token和hash,一定要保存好,不然从节点就没法加入集群了。如果忘了,可以用kubeadm token create --print-join-command命令重新生成。
我一开始初始化的时候,忘了指定--pod-network-cidr,结果后来装Flannel的时候,Pod网络不正常,Pod之间无法通信,排查了很久才发现是这个问题,后来重新初始化,加上了这个参数,才正常。
4. 网络插件的坑
Kubernetes本身不提供网络功能,需要安装网络插件(CNI),比如Flannel、Calico、Weave等,不然Pod之间无法通信,CoreDNS也无法正常工作。
我用的是Flannel,因为它比较简单,适合初学者。安装Flannel也有坑:
- Flannel的镜像也是国外的,国内下载慢,需要提前下载或者用国内镜像。
- Flannel需要的pod-network-cidr是10.244.0.0/16,kubeadm init的时候一定要指定这个网段,不然Flannel会工作不正常。
- Flannel需要在所有节点上安装,不是只在主节点安装,因为每个节点都需要网络插件来配置Pod网络。
我一开始只在主节点装了Flannel,结果从节点的Pod网络不正常,后来才知道,Flannel是DaemonSet,会自动在所有节点上安装,但是需要等一会儿,让Pod调度到所有节点上,不是装完马上就好的。
网络插件装好之后,可以用kubectl get pods -n kube-system命令,看看所有的Pod是不是都Running了,特别是CoreDNS和Flannel的Pod,如果都Running了,说明网络正常了。
5. 从节点加入集群的坑
主节点初始化好,网络插件也装好了,就可以让从节点加入集群了,用kubeadm join命令,这一步也有一些坑。
- 从节点也要做环境准备,和主节点一样,关闭防火墙、SELinux、swap,配置时间同步、主机名解析等,不然加入集群会失败。
- 从节点也要能下载镜像,因为加入集群之后,会启动kube-proxy、Flannel等Pod,需要下载对应的镜像。
- join命令里的地址和端口要正确,是主节点的API Server地址,默认是6443端口。
- 如果加入失败,可以用kubeadm reset命令重置,然后重新加入,重置之后要清理一下/var/lib/cni、/etc/cni等目录,不然可能会有残留。
我一开始从节点加入的时候,忘了关swap,结果kubelet启动失败,加入失败,后来关了swap,重置之后重新加入,才成功。还有一次,从节点的防火墙没关,导致和主节点通信失败,加入失败,关了防火墙就好了。
四、使用Kubernetes的踩坑经验
集群搭建好之后,就可以开始使用Kubernetes部署应用了,在使用的过程中,也踩了不少坑。
1. 镜像拉取的坑
部署应用的时候,首先遇到的就是镜像拉取的问题。如果镜像在私有仓库,需要配置镜像拉取密钥(imagePullSecrets),不然会拉取失败,报ImagePullBackOff错误。
配置方法:
- 先用kubectl create secret docker-registry命令创建一个docker-registry类型的Secret,指定仓库地址、用户名、密码、邮箱。
- 然后在Deployment的Pod模板里,指定imagePullSecrets,引用这个Secret,这样Kubernetes就能用这个密钥来拉取私有仓库的镜像了。
还有,如果镜像标签是latest,Kubernetes默认会每次都拉取镜像,不管本地有没有,这样如果仓库访问慢,就会导致Pod启动慢,甚至失败。建议用具体的版本标签,不要用latest,并且设置imagePullPolicy为IfNotPresent,这样本地有镜像的话,就不用重新拉取了。
2. 端口和服务暴露的坑
应用部署好之后,需要暴露服务,让外部能访问,这一步也有坑。
- 容器的端口要和应用实际监听的端口一致,不然Service转发过去,连接会被拒绝。
- Service的targetPort要和容器的端口一致,不然转发不到正确的端口。
- 如果用NodePort暴露服务,NodePort的范围默认是30000-32767,不能用这个范围之外的端口,而且要确保节点的防火墙开放了这个端口,不然外部访问不了。
- 如果用Ingress暴露HTTP服务,要先安装Ingress Controller,不然Ingress规则不会生效,而且Ingress的域名要能解析到集群的节点IP,不然访问不了。
我一开始部署应用,Service的targetPort写错了,写成了80,但是容器实际监听的是3000,结果访问的时候一直连接被拒绝,排查了很久才发现是端口写错了,改了之后就正常了。
3. 健康检查的坑
Kubernetes支持健康检查,包括livenessProbe(存活检查)和readinessProbe(就绪检查),存活检查用来判断容器是不是活着,如果失败了,会重启容器;就绪检查用来判断容器是不是准备好了,可以接收请求,如果失败了,会把容器从Service的后端摘除,不转发请求。
健康检查很重要,但是配置不好也会踩坑:
- 健康检查的路径和端口要正确,不然会一直失败,导致容器不断重启,或者一直不就绪,无法接收请求。
- 健康检查的初始延迟(initialDelaySeconds)要设置合理,要给应用足够的启动时间,不然应用还没启动完,健康检查就开始了,会失败,导致容器被重启。
- 健康检查的超时时间和周期也要设置合理,不要太短,不然应用响应慢一点就会被判定为失败。
我一开始配置健康检查,initialDelaySeconds设得太短了,应用还没启动完,健康检查就失败了,结果容器不断重启,一直启动不起来,后来把初始延迟调大了,才正常。
4. 资源限制的坑
Kubernetes支持给容器设置资源限制,包括CPU和内存的requests和limits,requests是调度的时候保证的资源,limits是容器最多能用的资源。设置资源限制很重要,能防止某个容器占用太多资源,影响其他容器。
但是资源限制设置不好也会踩坑:
- CPU的limits如果设得太低,应用会被限流,响应变慢,性能很差。
- 内存的limits如果设得太低,应用会因为OOM(内存溢出)被杀死,不断重启。
- 如果不设置资源限制,容器可能会占用节点的所有资源,导致节点不稳定,其他容器也受影响。
我一开始给一个Java应用设置的内存limits是512Mi,结果Java应用启动的时候就OOM了,被杀死,不断重启,后来把内存limits调到1Gi,才正常。所以设置资源限制的时候,要了解应用的资源需求,不要设得太低,也不要不设。
5. 配置和密钥管理的坑
应用的配置和密钥,不要硬编码在镜像里,要用ConfigMap和Secret来管理,这样改配置不用重新构建镜像,也更安全。
使用ConfigMap和Secret的时候,也有一些坑:
- ConfigMap和Secret要和Pod在同一个Namespace,不然Pod引用不到。
- ConfigMap和Secret更新之后,已经挂载的Pod不会自动更新,需要手动重启Pod,或者用Reloader之类的工具自动更新。
- Secret虽然是加密存储的,但是在Pod里挂载之后,容器里是可以看到明文的,所以不要把特别敏感的信息放在Secret里,或者用更安全的方式管理,比如Vault。
- ConfigMap和Secret的大小有限制,不能太大,一般不超过1MB,大的配置文件不要用ConfigMap,要用Volume挂载或者其他方式。
我一开始改了ConfigMap之后,以为应用会自动更新配置,结果等了很久都没更新,后来才知道,ConfigMap更新之后,已经挂载的Pod不会自动更新,需要重启Pod,重启之后配置就更新了。
五、Kubernetes的最佳实践
踩了这么多坑,也总结了一些最佳实践,分享给大家:
1. 用声明式配置,不要用命令式操作
Kubernetes推荐用声明式配置,也就是写YAML文件,然后用kubectl apply来部署,不要用kubectl run、kubectl expose之类的命令式操作,因为声明式配置可以版本管理,可以重复部署,更可靠,也更方便回滚。
把所有的配置都写成YAML文件,放在Git仓库里管理,这样每次修改都有记录,出了问题可以回滚,也可以在不同的环境复用。
2. 为每个应用设置健康检查和资源限制
健康检查和资源限制是生产环境必备的,健康检查能保证应用出问题的时候自动重启,自动摘除故障实例;资源限制能保证应用不会占用太多资源,影响其他应用。每个应用都应该配置livenessProbe、readinessProbe,以及CPU和内存的requests和limits。
3. 用Namespace做环境和项目隔离
不同的环境(开发、测试、生产)、不同的项目,要用不同的Namespace,这样资源隔离,权限隔离,不会互相干扰,也方便管理和清理。不要把所有的东西都放在default Namespace里,那样很乱,也不安全。
4. 用标签和选择器来组织资源
Kubernetes的标签(Label)是一个很强大的功能,可以给资源打标签,然后用标签选择器来筛选和管理资源。要合理使用标签,比如给应用打app、version、env、team等标签,这样可以方便地筛选、分组、管理资源,也方便Service、Deployment等资源的关联。
5. 做好日志和监控
Kubernetes集群里的Pod是短暂的,是动态的,所以日志和监控非常重要,不然出了问题很难排查。
- 日志:用EFK(Elasticsearch + Fluentd + Kibana)或者ELK栈来收集和管理日志,把所有容器的日志都收集起来,集中存储和查询,不要登录到容器里看日志。
- 监控:用Prometheus + Grafana来做监控,收集集群、节点、Pod、应用的指标,可视化展示,配置告警,有异常马上通知。
- 链路追踪:如果是微服务架构,还要用Jaeger或者Zipkin做分布式链路追踪,方便排查问题。
6. 做好备份和灾难恢复
Kubernetes集群虽然有高可用,但是还是要做好备份和灾难恢复,比如etcd的备份、持久化存储的备份、配置的备份等,万一集群出了问题,可以快速恢复。
etcd是Kubernetes的数据库,所有的集群状态都存在etcd里,所以一定要定期备份etcd,最好每天备份,保留最近几天的备份。持久化存储的数据也要定期备份,比如数据库的数据,要用工具定期备份,上传到对象存储。
7. 从简单开始,逐步深入
Kubernetes很复杂,功能很多,不要一开始就追求大而全,把所有功能都用上,那样会很混乱,也容易出问题。可以从简单开始,先把基本的功能用起来,比如Deployment、Service、ConfigMap这些,用熟了之后,再逐步引入更高级的功能,比如HPA、Istio、GitOps等,循序渐进,逐步深入。
六、学习Kubernetes的建议
最后,给想学习Kubernetes的朋友一些建议:
1. 先理解核心概念,再动手实践
Kubernetes的概念很多,一开始可能会觉得混乱,所以先花点时间,理解核心概念,比如Pod、Deployment、Service、Namespace这些,理解了概念之后,再动手实践,搭建集群,部署应用,这样会更容易上手。
2. 多动手,多踩坑,多总结
学习Kubernetes,光看书看文档是不够的,一定要多动手,自己搭建集群,部署应用,遇到问题,自己排查解决,踩的坑多了,自然就熟练了。每次踩坑之后,都要总结一下,记录下来,避免以后再踩同样的坑。
3. 用好官方文档和社区资源
Kubernetes的官方文档非常完善,也非常详细,遇到问题,首先查官方文档,大部分问题都能在文档里找到答案。还有社区资源,比如GitHub、Stack Overflow、知乎、公众号等,很多人分享了Kubernetes的经验和教程,遇到问题也可以在社区里提问,寻求帮助。
4. 关注云原生生态
Kubernetes不是孤立的,它有一个庞大的云原生生态,比如Docker、Helm、Prometheus、Grafana、Istio、Knative、ArgoCD等,这些工具和Kubernetes配合使用,能发挥更大的威力。学习Kubernetes的同时,也要关注云原生生态,了解相关的工具和技术,这样才能更好地使用Kubernetes。
七、写在最后
Kubernetes初探:踩坑总结与实战经验。
以上就是我初学Kubernetes的踩坑总结和实战经验,从核心概念、搭建集群、使用应用,到最佳实践、学习建议,都做了一个比较全面的总结。
Kubernetes是现在最火的容器编排工具,是云原生时代的核心技术,功能强大,生态完善,但是学习曲线也比较陡峭,概念多,配置复杂,刚开始学的时候,确实会踩很多坑,走很多弯路。但是只要多动手,多实践,多总结,慢慢就会熟练,就能体会到Kubernetes的强大和便利。
我现在也只是初学,还有很多东西需要学习,很多功能还没有用到,这篇文章只是我这段时间学习的总结,可能有不对的地方,欢迎大家指正。也希望我的这些踩坑经验,能给正在学习Kubernetes的朋友一些参考,少踩坑,少走弯路,更快地上手Kubernetes。
技术在不断发展,云原生是未来的趋势,Kubernetes作为云原生的核心,是每个后端开发和运维都应该掌握的技能。希望大家都能学好Kubernetes,用好Kubernetes,跟上技术发展的趋势,提升自己的技术能力,做出更好的系统。
最后,用一句话结尾:"Kubernetes不是银弹,但是它是目前最好的容器编排工具;学习曲线虽然陡峭,但是值得投入时间和精力去掌握。"愿我们都能学好Kubernetes,用好Kubernetes,在云原生时代,乘风破浪,不断进步。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录