Kubernetes [kubə’netis]简称K8S或Kube,平时使用K8S主力是Minikube,在几个项目生产实践中,则使用GCP/AWS/阿里云的对应的K8S实现,GKE/EKS/ACK。以下是作者在项目运维中获得和经反思提炼的知识总结,部分理解带有个人观点。


1、Kubernetes 基础概念

Kubernetes集群基础概念.jpg

理解 Pods

Kubernetes 中最小计算单元,内含pause和一个或一组进程。深入pods技术细节见Kubernetes Pod 网络精髓:pause 容器详解

理解网络

Kubernetes有2个网络地址池CIDR分别供Pod和Service。Service和Pod都是有IP的网络实体,会消耗ip池地址,且各IP互通(由CNI实现)。Pod还是个计算实体承载服务,Service则按一定规则转发流量到0个或多个pod。实现模型理解见浅聊 Kubernetes 网络模型

理解Service

Service本质就是dns解析+proxy转发,通过Service名:port方式就能访问真正的实体pod,类似非K8S生态的consul,但K8S内部服务名字的dns解析都是高度自动化的,无感知的。

有状态下pod漂移与vm漂移

ip在pod生命周期内不变,但pod会因销毁重建导致ip漂移。内存与未绑定文件在pod销毁期间就清空了。相比而言,vm漂移技术成熟,ip内存文件都是一起在线飘到新地方,不存在重启销毁问题。


2、理解Kubernetes技术

1. 社区角度:

从2015年7月Kubernetes v1.0正式发布至今v1.22版本,基于Kubernetes的生态已经非常庞大,直接导致go的流行,运维云原生化趋势。 kubernetes结合devops概念图.png

2. 工程师角度:

绝大部分编排k8s的中间产物都是yaml文件,所以k8s工程师被称为yaml工程师。但其实ansible/salt工程师才是最先全用yaml的。 K8S工程师写yaml.png

3. 技术角度:

内核技术升级是上层技术革新的基础,当年nginx使用epoll网络IO模型率先实现10K并发,如今docker/Kube实际上是内核cgroup/namespace进程环境限制/隔离加内核netfilter网络模块的上层产物。——技术早已出现,只是换个地方而更被人熟知,这现象可称为【军用科技民用化】。

4. 运维效用角度:

自动化套件ansible/salt也是非常成熟的,另一方面非容器或非linux机的自动化集群管理并非k8s强项,k8s仅在支持容器化的项目上高效,而大部分项目可以容器化(0.9*0.9依然接近1,但不是全部)。 kubernetes对比ansible.png

5. 基于作者理解:

  • k8s是“定义即状态”的运维实现,对应软件代码“定义即实现”具体指发展到IDL层面(接口描述语言)。
  • k8s内置电池,一个k8s集群相当于实现了一个跑在容器的逻辑机房(计算+网络+存储都是可扩展的),外加一个ansible/salt控制中心。
  • k8s本身学习难度大,非初学者友好,需要较深的linux内核/网络基本做底,但k8s生态的文章,精品率更高,反过来能加速学习容器化与linux相关知识的过程。

3、Kube入门学习建议

💡 理解上把容器对标物,从虚拟机转变到进程来。

  1. 容器是一个新概念,从字面上并不好理解其真本质:隔离
  2. 容器更像一个进程,而不是一个轻量的虚拟机。

💡 个人学习k8s路径选型

  1. 从单机上的容器(docker/containerd)出发,跳一步到单机的K8S(minikube),再到多节点K8S(minikube),再适应云商的改版K8S
  2. Ingress也有很多种,建议如果熟悉nginx,先从ingress-nginx入手。
  3. 服务治理Istio囊括范围非常大,k8s只是组件之一,容易找不着北。个人觉得不适合用来入门k8s。
  4. 官方dashboard已经很好,配合K9S工具已经够用了。一个管UI,一个管console。k9s真是vi党的福音。
  5. lens也是好工具,管理国内集群无明显延迟感。国外可能会稍卡顿。

💡 容器 ≈ 进程+网络。

  1. 除了理解linux内核,也需要iptables/ipvs理解能力。
  2. 网络理解更难啃些,建议不要跳过docker网络直接理解k8s网络cni。 内核netfilter图标.png

💡 使用docker-compose作为对照参考

  1. 对比中理解docker编排/k8s编排各自的长短处。会对k8s理解得更加全面和立体。
  2. docker-compose也是一股容器编排方面的重要力量。有余力者学1得2。

💡 个人经验最佳学习搭档官方文档书籍 + minikube实践 。

  1. 官方文档要重复看,坚持看,一般看第三遍就懂了。
  2. minikube是官方首推的学习和测试k8s环境,只需单机docker环境即可启动一个完备的k8s集群,很轻量,最快支持最新k8s版本。
  3. 推荐第三方书的话 《kubernetes网络权威指南》

4、Kube最佳实践,经验建议

💡 打包images兼容非K8S容器环境运行,更利运维部署,以及开发调试。【Projzz, Projxx】

  • Projzz的镜像兼容docker,并且cp内部某套集群就是用docker部署的,线上则全用K8S部署的。
  • Projxx的Pod启动是准备3-4个部分容器,最终合并到最终容器的emptyDir来running,如果选这种模式确实只能k8s了。

做一个通用的镜像,能独立运行。用好动态部分来让同一镜像变成不同的runtime容器,如volume挂载,配置,环境env变量都是动态的。

💡 相似微服务可以统一镜像,使用APP环境变量启动镜像的不同部分【Projyy】

相对于各微服务独立打包镜像的分包模式,还有一种简单的全包模式。

Projyy镜像都是java进程,启动脚本命令基本一样,只是目录和jar包不同,使用了轻量的全包模式。通过指定APP环境变量,启动对应微服务。兼容docker/k8s。

全包镜像模式好处:

  1. 统一的Dockerfile,entrypoint.sh不用维护多个文件。
  2. 减少镜像总占用空间,版本管理起来更轻量简洁。(harbor界面也简洁了)
  3. 更接近vm时代supervisor的管理整代码包的方式。更好理解。

💡 用ExternalName型和EndPoints型的服务进行Kube内部服务名固定。【Projxx】

用Service映射内部/外部服务简化架构:

  1. 组件依赖以服务SVC出现供微服务调用。Projxx依赖为zoo-svr, mysql-svr, redis-svr 与 zoo-svr-out, mysql-svr-out。

  2. SVC底层的EndPoints/ExternalName映射各集群不同。如正式va集群是中心,微服务调用mysql-svr-out/mysql-svr-out底层相同,sg集群则不同。

  3. 如下图,屏蔽底层差异后,研发不用关心底层,mysql等服务的映射关系由运维定义和修改,容灾切换。

Projxx图解: Kubernetes统一SVC名指向外部依赖.png

💡 在namespace级别隔离业务环境,而非集群级别隔离。

减少k8s集群,理想状态是一个测试集群(dev/qa/pre/time/audit服)+一个正式集群。【Projyy】

为了让单集群支持多环境,需要以下条件:

  1. yaml/chart中不写死namespace,使用kubectl -n 和 helm -n 在运行时指定namespace。
  2. 节点映射到pod中的目录或文件名,需要保证不同namespace间不不一样。注意hostpaht和local映射。
  3. ingress域名不能在多namespace里共用,不同namespace用不同域名。(一般已经天然满足的)
  4. daemonset组件处理好不同namespace里不同环境的情况。比如filebeat以daemonset部署时可以收集多namespace的日志。

💡 善用hostpath在pod层面实现节点的初始化,日志清理等,不建议ssh到node进行初始化。【Projzz】

将node的东西暴露给pod,就可以从pod层面处理好node的事项。以下推荐度逐渐递减

  • 以crontab/job加hostpath挂载处理初始化,还能定时清理日志等。
  • 以daemonset加hostpath挂载
  • 使用initpod模式加hostpath挂载

💡 多用Deployment而不是多用StatefulSet【Projxx

如无必要,Deployment好于StatefulSet,尤其在大副本数滚动更新时StatefulSet慢

  • 默认情况,Deployments更新2批次完成,而StatefulSet逐个重启,虽然可优化,但不如Deployment快和便捷。
  • 需要hostname/dns固定时间,启动顺序(如mysql主从集群)有要求时才特别的需要StatefulSet。
  • Ro的Account登陆校验,Bgame跨服,Global模块,用不到特殊情况,account更新比较慢,理论上改成Deployments会更优。

💡 其他建议:

  1. kubectx/kubens实际上在交互shell时才比较好用图省敲键盘。在脚本或自动化流程中,多使用–kubeconfig和–context参数指定集群更好。
  2. Fairy使用kustomize跑着也很稳,其求同存异的patch思路,也是很好的,免去helm那样写go模版。
  3. pod/service/namespace的名字尽量精简,Linux内核的主机名字段限定了FQDN最多 64 个字符
  4. ingress尽量合并以减少总量,像gcp的gke就存在多于15个ingress是重载非常慢的问题。
  5. 珍惜两个cidr地址的使用,太大了网络ip使用量,即pods/svc太多,会导致集群内耗过多。 这类比开发微服务太小散,会导致cpu内耗在(反)序列化中。某字节内部资料说有10%
  6. 尽量减少总容器量,比如微服务较多时,daemonset的filebeat部署,比inject/sidecar类型的部署模式省容器。
  7. 提升副本数不如提升配置,副本数20升级60, 在业务代码支持情况下,不如保持20个,配置升3倍。
  8. 总的来说,本质上就是微服务不是越多越好,目前业界有种不良趋势,服务拆得太小。把服务越拆越细,这事本身也像一个技术界内卷行为。

5、Kube带来的问题反思

1. 云厂商各自实现部分,并不统一,造成割裂,在复杂(云)之上制造复杂(Kube)。

正例:

基于LB的证书解密实现,阿里云/AWS均是annotation注释指向某个cert证书对象的id。运维开发的效用在于屏蔽这些差异。参考【某_ingress_chart】代码。

反例:

基于LB的whiteiplist实现,阿里云 / AWS不同。annotation注释指向一块acl对象id / 使用spec中loadBalancerSourceRanges。兼容起来麻烦,也只能运维配置,最终可能还不如在游戏层实现【Projxx】

2. 产生大量不好识别的16进制名称的LB

LoadBalance型服务,产生的名字不规律。难识别。

3. ingress 与动静文件分离问题

使用nginx ingress也无法做到动静分离,只能将静态文件另外部署,如cdn,ingress后配nginx服等等。

How to setup ingress to serve static content on kubernetes? #323



原文出自发表的https://blog.pythonwood.com/2021/12/K8S运维总结之关于Kubernetes项目最佳实践的思考/



扩展阅读