Kubernates组件
概括:
Kubernates集群主要由Master节点和Node节点组成,Master节点是控制节点,Node节点为工作节点。
Master节点中有四大关键的组件:ApiServer、Scheduler、Controller-manager、etcd。
而Node节点主要由kubelet、kube-proxy以及运行的pod组成。
具体来说,主要包括以下几点:
- 服务发现与调度
- 负载均衡
- 服务自愈
- 服务弹性扩容
- 横向扩容
- 存储卷挂载
ApiServer
负责对外提供restful的Kubernetes API服务,其他Master组件都通过调用api server提供的rest接口实现各自的功能,如controller就是通过api server来实时监控各个资源的状态的。
访问控制入口,所有对集群的资源访问、管理等等操作,都是由此提供的api。
个人理解上,与微服务中的网关非常相似,集认证、授权、api注册和发现机制。
Scheduler
集群资源调度者,按照预定的调度策略将pod调度岛相应的Node节点上。
容器的自动装箱,我们也会把它叫做 scheduling,就是“调度”,把一个容器放到一个集群的某一个机器上,Kubernetes 会帮助我们去做存储的编排,让存储生命周期与容器的生命周期能有一个连接。
监听新建pod副本信息,并通过调度算法为该pod选择一个最合适的Node节点。
会检索到所有符合该pod要求的Node节点,执行pod调度逻辑。调度成功之后,会将pod信息绑定到目标节点上,同时将信息写入到etcd中。
一旦绑定,就由Node上的kubelet接手pod的接下来的生命周期管理。如果把scheduler看成一个黑匣子,那么它的输入是pod和由多个Node组成的列表,输出是pod和一个Node的绑定,即将这个pod部署到这个Node上。
Kubernetes目前提供了调度算法,但是同样也保留了接口,用户可以根据自己的需求定义自己的调度算法。
Etcd
是 Kubernetes 提供的一个高可用的键值数据库,用于保存集群所有的网络配置和资源对象的状态信息,也就是保存了整个集群的状态。数据变更都是通过api server进行的。整个kubernetes系统中一共有两个服务需要用到etcd用来协同和存储配置,分别是:
- 网络插件flannel,其它网络插件也需要用到etcd存储网络的配置信息;
- kubernetes本身,包括各种资源对象的状态和元信息配置。
通俗点的理解,yaml配置文件即为kubernates的状态,以及每个pod的yaml的文件也是集群的状态。
Controller-manager
Controller Manager 由 kube-controller-manager 和 cloud-controller-manager 组成,是 Kubernetes 的大脑。
它通过 ApiServer监控整个集群的状态,并确保集群处于预期的工作状态。
何为预期的工作状态?
启动集群时,为集群配置了一系列参数,这些参数,即为你期望的集群的样子,以及pod的yaml文件同理。
负责维护集群的状态,比如故障检测、自动扩展、滚动更新等。
每个资源一般都对应有一个控制器,这些controller通过api server实时监控各个资源的状态,controller manager就是负责管理这些控制器的。
当有资源因为故障导致状态变化,controller就会尝试将系统由“现有状态”恢复到“期待状态”,保证其下每一个controller所对应的资源始终处于期望状态。
比如我们通过api server创建一个pod,当这个pod创建成功后,api server的任务就算完成了。而后面保证pod的状态始终和我们预期的一样的重任就由controller manager去保证了。
Master Node && Worker Node
Master 中主主要是以上讲的四大组件,所以Master相当于大脑,作为整个k8s集群的管理层。
所以Master会做高可用,因为在实际生产中,如果单机Master挂了,后果也是非常严重的,妥妥的P0级别生产事故。
而Worker Node(以下简称Node)中运行的即为我们自己的应用,如何设计Master和Node实现高可用,涉及到容器编排。
Kubernetes 的 Node 是真正运行业务负载的,每个业务负载会以 Pod 的形式运行。
一个 Pod 中运行的一个或者多个容器,真正去运行这些 Pod 的组件的是叫做 kubelet,也就是 Node 上最为关键的组件,它通过 API Server 接收到所需要 Pod 运行的状态,然后提交到我们下面的这个 Container Runtime 组件中。
Pod
Kubernates将所有内容都抽象为资源,用户需要通过操作资源来管理Kubernates
Kubernates本质上是一个集群系统,pod是kubernates中的最小单元,而容器只能在Pod中运行,
Pod中可运行一个容器,也可运行一组容器。
Pod中的应用成功运行起来后,就要考虑如何访问其中的服务,即Kubernates提供了Service资源实现这个功能
Pod本质上是无状态的,但是应用往往需要存储数据,默认的Pod,重启后将恢复初始状态,即数据将全部清空。
所以Pod引入了存储系统,即挂载数据卷。
这里涉及到Kubernates的设计,个人感觉抽象出这么多东西,更易于开发者对Kubernates的理解。
为什么要抽象出Pod?为什么不以容器为最小的操作单位?
Pod中的容器们运行在一个逻辑“主机”上。他们使用同一个网络命名空间(network namespace,换句话讲,就是同样的IP地址和端口空间),以及同样的IPC(inter-process communication,进程间通信)命名空间,他们还使用共享卷(shared volume)。这些特征使得Pod内的容器能互相高效地通信。
个人理解,假如你有一组紧耦合的应用容器,这一组容器需要运行在同一个共享空间,那么如果以容器为最小单位进行管理,那就需要额外的为这一组紧耦合的容器处理他们的通信问题等等。
以Pod为最小单位管理,在pod中,可以有共享的网络namespace,可以直接进行通信,并且更易管理这一组容器。
但是!
并不推荐在一个容器里运行多个容器,这样会使调试应用变得困难,粒度也更大。
首先,这会违反“一个容器一个进程”规范。这个规范很重要,因为当一个容器中有多个进程时,调试会变得非常困难,因为不同进程的日志会混在一起,而且很难去管理这些进程的生命周期。其次,为一个应用使用多个容器会更简单、更直接、能解耦软件依赖。而且,更细粒度的容器可以在团队间复用。
20221/11/02 03:43
跟明新大佬讨论一番,发现自己有几点理解错误。
一个是node的含义,node就是一台物理机上通常情况只有一个node
第二个问题,假如Node有3个,而pod有5个,这样可能无法完全负载均衡,那么scheduler在计算资源分配时,可能导致pod漂移到别的节点。
对于hostPath挂载来说,这样是很危险的,可能漂移以后,数据就无法持久化或者出现错乱。
Deployment
Deployment 是在 Pod 这个抽象上更为上层的一个抽象,它可以定义一组 Pod 的副本数目、以及这个 Pod 的版本。
一般大家用 Deployment 这个抽象来做应用的真正的管理,而 Pod 是组成 Deployment 最小的单元。
Kubernetes 是通过 Controller,也就是我们刚才提到的控制器去维护 Deployment 中 Pod 的数目,它也会去帮助 Deployment 自动恢复失败的 Pod。
比如说我可以定义一个 Deployment,这个 Deployment 里面需要两个 Pod,当一个 Pod 失败的时候,控制器就会监测到,它重新把 Deployment 中的 Pod 数目从一个恢复到两个,通过再去新生成一个 Pod。通过控制器,我们也会帮助完成发布的策略。比如说进行滚动升级,进行重新生成的升级,或者进行版本的回滚。
Service
个人理解,Service是Kubernates对于访问同一应用服务抽象出来的一个门面,可以简单理解这种设计模式是门面模式,跟微服务的Gateway有点相似,因为service的实现中是有负载均衡的。
Kubernates Service是一种抽象,一个Pod的逻辑分组,为什么是逻辑分组呢?
首先要明白,一个Kubernates集群,它可能在多个Node。
既然是一台机器只能有一个Node,那么我同一个Pod,可能有n个副本,那么这些副本根据负载均衡的策略,会被scheduler根据资源计算应该调度到哪个Node,最后n个Pod分布在不同Node,实现负载均衡。
再看回来,Service后面的pod并不是真的在同一个机器同一个组,它只是逻辑上的一个分组。
下图体现了所有的Service都是一个门面。
Kube-Proxy
真正实现Service负载均衡的是Kube-Proxy
Kubernetes也遵循了上述常规做法,运行在每个Node上的kube-proxy进程其实就是一个智能的软件负载均衡器,它负责把对Service的请求转发到后端的某个Pod实例上,并在内部实现服务的负载均衡与会话保持机制。
但Kubernetes发明了一种很巧妙又影响深远的设计: Service不是共用一个负载均衡器的IP地址,而是每个Service分配了一个全局唯一的虚拟IP地址,这个虚拟IP被称为Cluster IP,这样一来,每个服务就变成了具备唯一IP地址的“通信节点”,服务调用就变成了最基础的TCP网络通信问题。
网络模型
Kubernates网络模型旨在解决四个不同的问题:
- Docker容器和Docker容器之间的网络
- Pod与Pod之间的网络
- Pod与Service之间的网络
- Internet与Service之间的网络
接下来就以这四个问题进行讨论。
同一个Pod内容器间的通信
首先要明确,一个Pod内可以运行多个容器,那必然就要解决Pod内容器网络通信的问题。
- 在k8s中每个Pod中管理着一组Docker容器,这些Docker容器共享同一个网络命名空间。
- Pod中的每个Docker容器拥有与Pod相同的IP和port地址空间,并且由于他们在同一个网络命名空间,他们之间可以通过localhost相互访问。 什么机制让同一个Pod内的多个docker容器相互通信那?其实是使用Docker的一种网络模型:–net=container
container共享模式
每一个Pod内有一个叫做pause的容器,在Pod内启动Docker容器时候使用 –net=container就可以让当前Docker容器加入到Pod容器拥有的网络命名空间(pause容器)
container模式指定新创建的Docker容器和已经存在的一个容器共享一个网络命名空间,而不是和宿主机共享。新创建的Docker容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。
什么意思呢?我的理解就是在pod中,每一个容器都是一个进程,那么进程间的通信,不就是在一个共享的网络空间吗?有自己的ip和端口,就能互相通信了。
还有一种通信方式:docker虚拟网桥
nginx与php-fpm是两个容器,通过对接到网桥的接口,由虚拟网桥(相当于交换机)进行通信。
无论是什么通信方式 都需要借助一个媒介,毕竟容器间的网络空间是天然隔离的
Pod与Pod之间的网络通信
Pod与Pod之间,可能是在同一个Node上,也可能在不同Node上,我们分别讨论。
- k8s中,每个Pod拥有一个ip地址,不同的Pod之间可以直接使用改ip与彼此进行通讯
- 在同一个Node上,从Pod的视角看,它存在于自己的网络命名空间中,并且需要与该Node上的其他网络命名空间上的Pod进行通信。
每一个pod独享一个网络命名空间,所以需要将pod之间通过连接到root网络命名空间进行通信。
基于Linuxd虚拟以太网设备,每一个Pod的网络命名空间都有由两个虚拟接口组成的veth对使不同的网络命名空间链接起来,这些虚拟接口分布在多个网络命名空间上(这里是指多个Pod上)。
为了让多个Pod的网络命名空间链接起来,我们可以让veth对的一端链接到root网络命名空间(宿主机的),另一端链接到Pod的网络命名空间。
每对Veth就像一根接插电缆,连接两侧并允许流量在它们之间流动;这种veth对可以推广到同一个Node上任意多的Pod上,如上图这里展示使用veth对链接每个Pod到虚拟机的root网络命名空间。
链接起来了,也并没有真正实现pod之间通信,root命名空间还需要把每个pod的veth进行链接,这里就要使用到虚拟网桥,虚拟网桥与中继路由非常相似。
linux以太网桥(Linux Ethernet bridge)是一个虚拟的2层网络设备,目的是把多个以太网段链接起来。
网桥维护了一个转发表,通过检查转发表通过它传输的数据包的目的地并决定是否将数据包传递到连接到网桥的其他网段,网桥代码通过查看网络中每个以太网设备特有的MAC地址来决定是传输数据还是丢弃数据。
虚拟网桥也实现了arp协议,通过arp协议来获取接入的pod的ip和mac地址等等信息,并记录下来形成一个路由映射表。
也就是通过路由表,来查询pod要通信的目的地,通过ip地址找到正确的mac地址,发往相应的设备。
不同Node中的Pod之间通信
如图,不同Node之间pod通信,主要增加了两个物理机间的真实网卡的交互以及中间的网络传输。
其他的过程就是与上面类似。
关于网络插件
说起k8s网络不得不说CNI(container network interface·)网络容器接口,这是一种网络标准设计,是由谷歌和coreos联合制定的一种网络标准。
这个就是实现k8s网络插件的基础,常用的如下图,这种标准,不管创建还是销毁容器,都可以轻松配置网络。
Flannel网络模型
VXLAN
VXLAN是Flannel默认和推荐的模式。当我们使用默认配置安装Flannel时,它会为每个节点分配一个24位子网,并在每个节点上创建两张虚机网卡:
cni0和flannel.1。cni0是一个网桥设备,类似于docker0,节点上所有的Pod都通过veth pair的形式与cni0相连。flannel.1则是一个VXLAN类型的设备,充当VTEP的角色,实现对VXLAN报文的封包解包。
本质上,跨Node通信,需要构造一个完整的UDP报文,供各个层进行解析,实现数据包传输。
host-gw模式
这种模式性能相对于vxlan模式性能更佳,因为没有解包和封包的过程。
在host-gw模式下,由于不涉及VXLAN的封包解包,不再需要flannel.1虚机网卡。 flanneld 负责为各节点设置路由 ,将对应节点Pod子网的下一跳地址指向对应的节点的IP,如图中路由表①所示。
网络模型小结
对于如何来配置网络,k8s在网络这块自身并没有实现网络规划的具体逻辑,而是制定了一套CNI(Container Network Interface)接口规范,开放给社区来实现。
最重要的一点是,Kubernates使用了虚拟化技术,如虚拟化网络设备,虚拟化网络命名空间,使得各个pod之间进行了隔离,同时又为了解决通信进行了连接,这在一定程度上也是解耦合,方便对Pod进行管理。
数据卷挂载
数据挂载有emptyDir、hostPath、nfs三种方式。
emptyDir
假设你并未在deployment中配置挂载类型,那么默认就是emptyDir,有pause容器创建一个临时空间,生命周期与Pod相同,。
hostPath
这种数据挂载宿主机上的文件和目录,可以用于持久化。
关于pod漂移的问题
假如原pod在nodeA,那么挂载到宿主机的目录也是也在NodeA的宿主机上。此时scheduler发现NodeA的资源紧张,将pod转移到NodeB上,以实现负载均衡,那么此时已持久化在NodeA的数据将会对于Pod不可见
NFS
因为NFS (Network File System) 是一个网络文件系统,它允许网络中的计算机之间通过TCP/IP网络共享资源,这样子就可以帮助我们解决k8是数据储存的问题。
k8s是容器编排系统,容器就涉及到cpu,内存,硬盘等调度分配,nfs就是k8s储存的一个解决方案。
这种存储方式是企业中使用比较多的,因为由于微服务的风靡,一个Service存在多个pod,那么这多个pod可能就需要共享同一个存储资源,此时就需要用到nfs。
我们能将 NFS 挂载到Pod 中,不像 emptyDir 那样会在删除 Pod 的同时也会被删除,nfs 卷的内容在删除 Pod 时会被保存,卷只是被卸载。
这意味着 nfs 卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享,NFS 卷可以由多个pod同时挂载。
PV、PVC、StorageClass
Kubernates真的抽象了很多东西,不得不再次吐槽。
PV
言归正传,PV(PersistentVolume)是K8s中对存储资源的抽象,是容器可以使用的资源,为了动态根据用户需求创建PV。
PV是对底层网络共享存储的抽象,将共享存储定义为一种“资源” ,比如Node也是容器应用可以消费的资源。PV由管理员创建和配置,与共享存储的具体实现直接相关。
PVC
PVC(PersistentVolumeClaims )则是用户对存储资源的一个“申请”,就像Pod消费Node资源一样,PVC能够消费PV资源。PVC可以申请特定的存储空间和访问模式。
StorageClass
在一个大规模的Kubernetes集群里,可能有成千上万个PVC,这就意味着运维人员必须实现创建出这个多个PV,此外,随着项目的需要,会有新的PVC不断被提交,那么运维人员就需要不断的添加新的,满足要求的PV,否则新的Pod就会因为PVC绑定不到PV而导致创建失败.而且通过 PVC 请求到一定的存储空间也很有可能不足以满足应用对于存储设备的各种需求。
而且不同的应用程序对于存储性能的要求可能也不尽相同,比如读写速度、并发性能等,为了解决这一问题,Kubernetes 又为我们引入了一个新的资源对象:StorageClass,通过 StorageClass 的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据 StorageClass 的描述就可以非常直观的知道各种存储资源的具体特性了,这样就可以根据应用的特性去申请合适的存储资源了。
属实有点抽象了,我形象地比喻一下,PV是食堂里固定的10种菜,StorageClasss是菜上面的标签,用户可以根据这十种菜的标签自行选择,可能不太妥当,但是可以帮助理解。
StorageClass,用于标记存储资源的特性和性能,管理员可以将存储资源定义为某种类别,正如存储设备对于自身的配置描述(Profile)。根据StorageClass的描述可以直观的得知各种存储资源的特性,就可以根据应用对存储资源的需求去申请存储资源了。存储卷可以按需创建。
PVC与PV绑定时会根据storageClassName(存储类名称)和accessModes(访问模式)判断哪些PV符合绑定需求。然后再根据存储量大小判断,首先存PV储量必须大于或等于PVC声明量;其次就是PV存储量越接近PVC声明量,那么优先级就越高(PV量越小优先级越高)。
以上PV、PVC、StorageClass 皆可在Yaml配置文件中进行定义,这也算是K8s抽象出来的高明之处了。
2022.11.17更新
Ingress
Ingress 意味着入口。
Kubernetes Ingress 引导外部流量到 ingress controller ,ingress controller 是集群里的一个 L7 负载均衡器
其实服务可以通过nodeport暴露,默认端口范围是30000-32767,但是这样多个服务启动非常难以统一进行管理
下图可以看到,Load Banlancer(以下简称LB)在Ingress之前,作为4层的负载均衡设备,它是无法识别应用层的协议的(如http、https)
换句话说,不会终结 TLS 连接,也不区别是那个 website 的流量,仅仅通过 IP/PORT 进行转发。
Load Banlancer作用:
LB可以从 internet 接收流量,然后转发到集群内的服务上。
注意,技术上,不使用 ingress 而使用 LoadBalance service type 是可以的。
ingress与ingress-controller
要理解ingress,需要区分两个概念,ingress和ingress-controller:
- ingress对象: 指的是k8s中的一个api对象,一般用yaml配置。作用是定义请求如何转发到service的规则,可以理解为配置模板。
- ingress-controller: 具体实现反向代理及负载均衡的程序,对ingress定义的规则进行解析,根据配置的规则来实现请求转发。
简单来说,ingress-controller才是负责具体转发的组件,通过各种方式将它暴露在集群入口,外部对集群的请求流量会先到ingress-controller,而ingress对象是用来告诉ingress-controller该如何转发请求,比如哪些域名哪些path要转发到哪些服务等等。
Ingress要解决的问题
技术上,不使用 ingress 而使用 LoadBalance service type 是可以的,只是架构会不一样。
最直观的,就是让外部能够访问集群,如果你的service的type都是ClusterIP,即只能支持集群内部访问,而通过Ingress就可以从外部访问到这种service。
Ingress Controller 通过跟 Ingress 交互得知某个域名对应哪个 service,再通过跟 kubernetes API 交互得知 service 地址等信息。
再者是外界只需要通过一个公网ip访问到ingress 以后,通过ingress-controller解析域名(此处需要自己配置访问规则,例如www.a.com访问的是seviceA)
如下图,通过外部的Load Balancer,转发到到Ingress,这里说一下,集群外部的LB是工作在四层(即ip层),而ingress是工作在七层(可解析http/https协议)。
这里解释一下,客户端会携带虚拟域名(如图中的foo.bar.com),到达LB,随后LB通过负载均衡策略,转发至Ingress,Ingress-controller会转发至对应Service,这样完全就做到了一个域名+uri就完成了请求,但值得注意的是,LB一般都是收费服务。
更牛的APISIX
APISIX 是基于 OpenResty + etcd 实现的云原生、高性能、可扩展的微服务 API 网关。它是国人开源,目前已经进入 Apache 进行孵化。
其实思考过一个问题,Ingress只能工作在7层吗?当然不是!
Ingress和Service只是Kubernates抽象出来的资源对象,Ingress真正的是实现是Ingress-Controller,而Ingress-Controller可以是Ingress-Nginx,当然也可以是更强大的Ingress-APISIX!
外部Load Banlancer + Ingress-nginx存在的问题:
说它强大,是因为ApiSix可以解析4层和7层的协议,这样可以不使用外部的LB,个人认为,LB不在Kubernates集群中,所以无法知道LB是否健康,需要额外的监控程序去监控LB应用,增加了系统的复杂度。
将apisix服务以nodePort暴露,就可以通过apisix访问到集群内部了,同时它支持多种语言编写插件,完全可以代替SpirngCloud GateWay!
例如Gateway的一些鉴权逻辑,可以通过java SDK的形式嵌入到apisix,在一些大厂已经有落地。
并且 APISIX 还支持 A/B 测试、金丝雀发布(灰度发布)、蓝绿部署、监控报警、服务可观测性、服务治理等等高级功能,这在作为微服务 API 网关非常重要的特性。
2022.11.18更新
与明新讨论到:
Kubernates中,所有的应用,包括master node的etcd、controller-manager、scheduler、apiserver、ingress-controller,都是以服务的形式启动的,而服务背后都是
Kubernates中踩坑
此部分更新我在学习中踩坑记录。
数据挂载问题
首先运行一个pod,它就是无状态的
何为无状态?
假如不配置挂载方式,那么Pod会默认挂载到临时目录,但是,pod重启或者删除时,所有的记录都将清除,再次启动应用时就恢复到初始的状态了。
一般我们运行应用,肯定是有持久化数据的,以此来保存应用的状态。
在开始部署pod时,未挂载数据,导致重启pod时数据全部丢失,最后经过学习,发现了不同的部署方式,而我是单集群,那么果断采用了HostPath进行数据挂载。
Kubernates故障检测重启
这里也是一个大坑。
以此记录整个排查过程。
现象
云服务器出现一阵一阵的卡顿,并且无法连接到控制台,cpu、内存、磁盘io负载都很高,从监控上看,磁盘io负载与内存负载图像一致。
排查过程
首先,认为是被攻击了,因为我无法连接进控制台,那么可能是SYN洪水攻击,导致半连接队列被占满,所以我连接不进去。
当连接进去以后,执行并查看半连接队列
[root@VM-4-11-centos ~]# netstat -natp | grep SYN_RECV | wc -l
0
可见返回0,排除SYN洪水攻击的可能,同时查看外网流入流量,并没有外网流量流入。
然后,进入到控制台以后使用top指令并根据cpu或者memory占用进行排序。
注意到内存交换率较高,这明显是内存不足的表现。
并且有Java应用不断重启,判断是我部署的应用,我将它kill掉,但是过一会儿又重启了,问题出在这里。
后来经过资料,k8s对于启动失败的Pod,会不断重试,有一个默认上限次数。
那问题来了,为什么会启动失败呢?
原因
经过检查启动的yaml配置文件,配置了3个Pod副本,但是!
我2c2g的云服务器根本没有这么多资源起3个副本,所以导致了一直重启一直失败。
还有一个很重要的原因,由于我是单node,Pod无法被schelduer调度到调度到别的node进行负载均衡,所以多Node实现负载均衡也是容器编排的一项很重要的工作。
总结
因为是deployment部署,那么这3个pod副本就算作一个服务,必须3个同时启动成功了,才算是整个service启动成功。 所以Pod没有完全启动起来,K8s认为这个服务没有启动成功,持续重试。
其实最开始就应该去看pod日志的,一开始的排查思路就出现了问题,这一点是对于k8s理解尚浅导致的。