前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Kubernetes](二)Kubernetes核心组件解读(下)

[Kubernetes](二)Kubernetes核心组件解读(下)

原创
作者头像
baron
发布2019-12-05 15:43:32
9770
发布2019-12-05 15:43:32
举报

kubelet

kubelet组件是Kubernetes集群工作节点上最重要的组件进程,它负责管理和维护在这台主机上运行着的所有容器。本质上,它的工作可以归结为使得pod的运行状态(status)与它的期望值(spec)一致。

kubelet的启动过程

  • (1) kubelet需要启动的主要进程是KubeletServer,它所需加载的重要属性包括kubelet本身的属性、接入的runtime容器所需的基础信息以及定义kubelet与整个集群进行交互所需的信息。
  • (2) 进行如下一系列的初始化工作。

1、选取APIServerList的第一个APIServer,创建一个APIServer的客户端。

2、如果上一步骤执行成功,则再创建一个APIServer的客户端用于向APIServer发送event对象。

3、初始化cloud provider。当然,如果集群的kubelet组件并没有运行在cloud provider上,该步骤将跳过。

4、创建并启动cAdvisor服务进程,返回一个cAdvisor的http客户端,IP和Port分别是localhost和CAdvisorPort的值。如果CAdvisorPort设置为0,将不启用cadvisor。

5、创建ContainerManager,为Docker daemon, kubelet等进程创建cgroups,并确保它们运行时使用的资源在限额之内。

6、对kubelet进程应用OOMScoreAdj值,即向/proc/self/oom_score_adj文件中写人OOMScoreAdj的值(默认值为-999 )。 OOMScoreAdj是用于描述在该进程发生内存溢出时被强行终止的可能性,分数越高,进程越有可能被杀死;其合法范围是-1000, 1000。换句话说,这里希望kubelet是最不容易被杀死的进程(之一)。

7、配置kubelet支持的pod配置方式,包括文件、url以及APIServer,支持多种方式一起使用。

  • (3) 初始化工作完成后,实例化一个真正的kubelet进程。重点值得关注的有以下几点:

1、创建工作节点本地的service和node的cache,并且使用list/watch机制持续对其进行更新。

2、创建DiskSpaceManager,用以与cadvisor配合进行工作节点的磁盘管理,这与kubelet是否接受新的pod在该工作节点上运行有密切关系。

3、创建ContainerRefManager,用以记录每个container及其对应的引用的映射关系,主要用于在pod更新或者删除时进行事件的记录。

4、创建VolumeManager,用以记录每个pod及其挂载的volume的映射关系。

5、创建OOMWatcher,用以从cadvisor中获取系统的内存溢出(Out Of Memory , OOM)事件,并对其进行记录。

6、初始化kubelet网络插件,可以指定传入一个文件夹中的plugin作为kubelet的网络插件。

7、创建LivenessManager,用以维护容器及其对应的probe结果的映射关系,用以进行pod的健康检查。

8、创建podCache来缓存pod的本地状态。

9、创建PodManager,用以存储和管理对pod的访问。值得注意的是,kubelet支持3种更新pod的方式,其中通过文件和url创建的pod是不能自动被APIServer感知的,称其为static pod。为了监控这些pod的状态,kubelet会为每个static pod在相同的namespace下创建一个同名的mirror pod,用以反应static pod的更新状态。

10、配置hairpin NAT。

11、创建container runtime,支持docker和rkt。

12、创建PLEG ( pod lifecycle event generator )。为了严密监控容器运行情况,kubelet在过去采用了为每个pod启动一个goroutine来进行周期性轮询的方法,即使在pod的spec没有变化的情况下依旧如此。这种做法会消耗大量的CPU资源,在性能上不尽如人意。为了改变这个现状,Kubernetes在v 1.2.0中引入了PLEG,专门进行pod变化的监控,避免了并发的pod worker来进行轮询工作。

13、创建镜像垃圾回收对象containerGC。

14、创建imageManager理容器镜像的生命周期,处理镜像的垃圾回收工作。

15、创建statusManager,用以向APIServer同步pod实际状态的更新。

16、创建probeManager,用作pod健康检查的探针。

17、初始化volume插件。

18、创建RuntimeCache,用以缓存pod列表。

19、创建reasonCache,用以缓存每个容器对应的最新的失败原因信息。

20、创建podWorker。每个pod将对应一个podWorker用以同步pod状态信息。

kubelet启动完成后通过事件收集器向APIServer发送一个kubelet已经启动的event,表明集群新加入了一个新的工作节点,kubelet将这一过程称为BirthCry,即“出生的啼哭”。并且开始进行容器和镜像的垃圾回收,对应的时间间隔分别为1分钟和5分钟。

  • (4) 根据Runonce的值选择运行仅一次kubelet进程或在后台持续运行kubelet进程,如果Runonce为true,则kubelet根据容器配置文件的内容创建pod后就退出;否则,将以goroutine的方式持续运行kubelet。
  • 另外,默认启用kubelet Server的功能,它将根据admin的配置创建HTTP Server或HTTPS Server,监听10250端口。同时,创建一个HTTP Server监听10255端口,用于heapster向kubelet收集统计信息。

kubelet与cAdvisor的交互

cAdvisor主要负责收集工作节点上的容器信息及宿主机信息,下面将一一进行介绍:

  • 容器信息

获取容器信息的URL形如:/api/{api version}/containers/\<absolute container name>。绝

对容器名(absolute containere)与URL的对应关系如表所示。

绝对容器名和URL的对应关系.png
绝对容器名和URL的对应关系.png

绝对容器名/下包含整个宿主机上所有容器(包括Docker容器)的资源信息,而绝对容器名/docker下才包含所有Docker容器的资源信息。如果想获取特定Docker容器的资源信息,绝对容器名字段需要填入/docker/{container ID}。

  • 宿主机信息

类似地,还可以访问URL: /api/{api version}/machine来获取宿主机的资源信息。要获取当前宿主机的资源信息。

kubelet垃圾回收机制

垃圾回收机制主要涵盖两个方面:容器回收和镜像回收。此处以docker这种容器runtime为例进行说明。

  • Docker容器的垃圾回收

Docker容器回收策略主要涉及3个因素,如表所示:

Docker容器垃圾回收涉及的因素.png
Docker容器垃圾回收涉及的因素.png

(1) 获取所有可以被kubelet垃圾回收的容器。

调用一次Docker客户端API获取工作节点上所有由kubelet创建的容器信息,形成一个容器列表,这些容器可能处于不同的生命周期状态,包括正在运行的和已经停止运行的。注意,需要通过命名规则来判断容器是否由kubelet创建并维护,如果忽略了这一点可能会因为擅自删除某些容器而惹恼用户。

遍历该列表,过滤出所有可回收的容器。所谓可回收的容器必须同时满足两个条件:已经停止运行;创建时间距离现在达到预设的报废时间MinAge。

过滤出所有符合条件的可回收容器后,kubelet会将这些容器以所属的pod及容器名对为单位放到一个集合(evictUnits)中,并根据pod创建时间的早晚进行排序,创建时间越早的pod对应的容器越排在前面。注意,在创建evictUnits的过程中,需要解析容器及其对应的pod名字,解析失败的容器称为unidentifiedContainers。

(2) 根据垃圾回收策略回收镜像。

首先,删除unidentifiedContainers以及被删除的pod对应的容器。这部分容器的删除不需要考虑回收策略中MaxPerPodContainer和MaxContainers。

如果podMaxPerpodContainer的值大于等于0,则遍历evictUnits中所有的pod,如果某个pod内的可回收容器数量大于MaxPerpodContainer,则删除多出的容器及其日志存储目录,其中创建时间较早的容器优先被删除。

如果MaxContainers的值大于等于0且evictUnits中的容器总数也大于MaxContainers,则执行以下两步:

  • 先逐一删除pod中的容器,直到每个pod内的可回收容器数=MaxContainers/evictUnits的大小,如果删除之后某个pod内的容器数<1,则置为1,目的是为每个pod尽量至少保留一个可回收容器。
  • 如果此时可回收容器的总数还是大于MaxContainers,则按创建时间的先后顺序删除容器,较早创建的容器优先被删除。
  • Docker镜像的垃圾回收

Docker镜像回收策略主要涉及3个因素,如表所示:

Docker镜像回收及涉及的因素.png
Docker镜像回收及涉及的因素.png

在Kubernetes中,Docker镜像的垃圾回收步骤如下所示:

(1) 首先,调用cadvisor客户端API获取工作节点的文件系统信息,包括文件系统所在磁盘设备、挂载点、磁盘空间总容量(capacity)、磁盘空间使用量(usage)和等。如果capacity为0,返回错误,并记录下InvalidDiskCapacity的事件。

(2) 如果磁盘空间使用率百分比(usage*1oo/capacity)大于或等于预设的使用率上限HighThresholdPercent,则触发镜像的垃圾回收服务来释放磁盘空间,否则本轮检测结束,不进行任何回收工作。至于具体回收多少磁盘空间,使用以下公式计算:

代码语言:txt
复制
  amountToFree := usage-(int64(im.policy.LowThresholdPercent)*capacity/100)

其实就是释放超出Low下hresholdPercent的那部分磁盘空间。

那么kubelet会选择删除哪些镜像来释放磁盘空间呢?

首先,获取镜像信息。参考当时的时间(Time.Now())kubelet会调用Docker客户端查询工作节点上所有的Docker镜像和容器,获取每个Docker镜像是否正被容器使用、占用的磁盘空间大小等信息,生成一个系统当前存在的镜像列表imageRecords,该列表中记录着每个镜像的最早被检测到的时间、最后使用时间(如果正被使用则使用当前时间值)和镜像大小;删除imageRecords中不存在的镜像的记录。

然后,根据镜像最后使用时间的大小进行排序,时间戳值越小即最后使用时间越早的镜像越排在前面。如果最后使用时间相同,则按照最早被检测到的时间排序,时间戳越小排在越前面。

最后,删除镜像。遍历imageRecords中的所有镜像,如果该镜像的最后使用时间小于执行第一步时的时间戳,且该镜像的存在时间大于MinAge,则删除该镜像,并且将删除Docker镜像计入释放的磁盘空间值,如果释放的空间总量大于等于前面公式计算得到的amountToFree值,则本轮镜像回收工作结束。否则,则记录一条失败事件,说明释放的空间未达到预期。

kubelet如何同步工作节点状态

首先,kubelet调用APIServer API向etcd获取包含当前工作节点状态信息的node对象,查询的键值就是kubelet所在工作节点的主机名。

然后,调用cAdvisor客户端API获取当前工作节点的宿主机信息,更新前面步骤获取到的node对象。

这些宿主机信息包括以下几点:

  • 工作节点IP地址。
  • 工作节点的机器信息,包括内核版本、操作系统版本、docker版本、kubelet监听的端口、
  • 工作节点上现有的容器镜像。
  • 工作节点的磁盘使用情况—即是否有out of disk事件。
  • 工作节点是否Ready。在node对象的状态字段更新工作节点状态,并且更新时间戳,则node controller就可以凭这些信息是否及时来判定一个工作节点是否健康。
  • 工作节点是否可以被调度podo

最后,kubelet再次调用APIServer API将上述更新持久化到etcd里。

kube-proxy

Kubernetes基于service、endpoint等概念为用户提供了一种服务发现和反向代理服务,而kube-proxy就是这种服务的底层实现机制。kube-proxy支持TCP和UDP连接转发,默认情况下基于Round Robin算法将客户端流量转发到与service对应的一组后端pod。在服务发现的实现上,Kube-proxy使用etcd的watch机制,监控集群中service和endpoint对象数据的动态变化,并且维护一个从service到endpoint的映射关系,从而保证了后端pod的IP变化不会对访问者造成影响。另外kube-proxy还支持session affinity(即会话保持或粘滞会话)。

下面我们以iptables模式对kube-proxy进行解读。

kube-proxy的启动过程

(1) 新建一个ProxyServer,包括两个功能性的结构的创建,负责流量转发的proxier和负责负载均衡的endpointsHandler。

(2) 运行ProxyServer。如果启用了健康检查服务功能,则运行kube-proxy的HTTP健康检查服务器,监听HealthzPort。同时,同样像kubelet一样发出birthCry(即记录一条已经创建完毕并开始运行kube-proxy的事件),并且开启同步工作。

proxier

前面已经介绍过,kube-proxy中工作的主要服务是proxier,而LoadBalancer只负责执行负载均衡算法来选择某个pod。默认情况下,proxier绑定在BindAddress上运行,并需要根据etcd上service对象的数据变化实时更新宿主机的防火墙规则链。由于每个工作节点上都有一个kube-proxy在工作,所以无论在哪个节点上访问service的virtual IP比如11.1.1.88,都可以被转发到任意一个被代理pod上。可见,由proxier负责的维护service和iptables规则尤为重要。这个过程通过OnServiceUpdate方法实现,该方法的参数就是从etcd中获取的变更service对象列表,下面将分别分析userspace和iptables模式下的proxier的工作流程。

  • userspace模式

(1) 遍历期望service对象列表,检查每个servie对象是否合法。维护了一个activeServices,用于记录service对象是否活跃。

对于用户指定不为该service对象设置cluster IP的情况,则跳过后续检查。否则,在activeServices中标记该service处于活跃状态。由于可能存在多端口service,因此对Service对象的每个port,都检查该socket连接是否存在以及新旧连接是否相同;如果协议、cluster IP及其端口、nodePort, externalIPs, loadBalancerStatus以及sessionAffinityType中的任意一个不相同,则判定为新旧连接不相同。如果service与期望一致,则跳过后续检查。否则,则proxier在本地创建或者更新该service实例。如果该service存在,进行更新操作,即首先将旧的service关闭并停止,并创建新的service实例。否则,则直接进行创建工作。

删除proxier维护的service状态信息表(serviceMap)中且不在。ctiveServices记录里的service。

(2) 删除service实例的关键在于在宿主机上关闭通向旧的service的通道。对任何一个Kubernetes service(包括两个系统service)实例,kube-proxy都在其运行的宿主机上维护两条流量通道,分别对应于两条iptables链---------KUBE-PORTALS-CONTAINER和KUBE-PORTALS一HOST。所以,这一步proxier就必须删除iptables的nat表中以上两个链上的与该service相关的所有规则。

(3) 新建一个service实例。首先,根据service的协议(TCP/UDP)在本机上为其分配一个指定协议的端口。接着,启动一个goroutine监听该随机端口上的数据,并建立一条从上述端口到service endpoint的TCP/UDP连接。连接成功建立后,填充该service实例的各属性值并在service状态信息表中插人该service实例。然后,开始为这个service配置iptables,即根据该service实例的入口IP地址(包括私有和公有IP地址)、入口端口、proxier监听的IP地址、随机端口等信息,使用iptables在KUBE-PORTALS-CONTAINER和KUBE-PORTALS-HOST链上添加相应的IP数据包转发规则。最后,以service id(由service的namespace, service名称和service端口名组成)为key值,调用LB接口在本地添加一条记录Service实例与service endpoint的映射关系。

  • iptables模式

iptables式下的proxier只负责在发现存在变更时更新iptables规则,而不再为每个service打开一个本地端口,所有流量转发到pod的工作将交由iptables来完成。OnServiceUpdate的具体工作步骤如下:

(1) 遍历期望service对象列表,检查每个service对象是否合法,并更新其维护的serviceMap,使其与期望列表保持同步(包括创建新的service、更新过时的service以及删除不再存在的service)。

(2) 更新iptables规则。注意,这个步骤通过一个名为syncProxyRules的方法完成,在这个方法中涉及了service及endpoint两部分更新对于iptables规则的调整。处于代码完整性和逻辑严密性的考虑,此处将两部分内容合并到此处进行讲解。具体步骤如下:

1、确保filter和NAT表中”KUBE-SERVICES”链(chain)的存在,若不存在,则为其创建。

代码语言:txt
复制
   iptables -t filter -N KUBE-SERVICESiptables -t nat -N KUBE-SERVICES

2、确保filter表和NAT表中”KUBE-SERV工CES”规则(rule)的存在,若不存在,则为其创建。

代码语言:txt
复制
   iptables -I OUTPUT -t filter -m comment --comment "kubernetes service portals" -j KUBE-SERVICESiptables -I OUTPUT -t nat -m comment --comment "kubernetes service portals" -j KOBE-SERVICESiptables -I PREROUTING -t nat -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

3、确保nat表中”KUBE-POSTROUTING”链的存在,若不存在,则为其创建。

代码语言:txt
复制
   iptables -t nat -N KUBE-POSTROUTING

4、确保nat.中”KOBE-POSTROUTING”规则的存在,若不存在,则为其创建。

代码语言:txt
复制
   iptables -I POSTROUTING -t nat -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING

5、保存当前filter,将以冒号开头的那些行(即链)存入existingFilterChains中,这是一个以iptables规则Target为键、链为值的map.

代码语言:txt
复制
   iptables-save -t filter
   

6、保存当前nat表,将以冒号开头的那些行(即链)存入existingNATChains中,这同样是一个以iptables规则Target为键、链为值的map。

代码语言:txt
复制
   iptables-save -t nat
   

7、将existingFilterChains中的‘'KUBE-SERVICES”链写入filterChains(一个以*filter为开头的buffer)中。*

8、将existingNATChains中”KUBE-SERVICES"、"KUBE-NODEPORTS"、"KUBE-POSTROUTING"、"KUBE-MARK-MASO”链写入natChains(一个以*nat为开头的buffer)中。

9、在natRules(一个buffer)中写入如下数据。分别用于之后创建’'KUBE-POSTROUTING'’和"KUBE-MARK-MASO"规则。

代码语言:txt
复制
   A KUBE-POSTROUTING -m comment "kubenetes service traffic requiring SNAT" -m mark --mark ${masqueradeMark} -j MASQUERADE
   A KUBE-MARK-MASQ -j MARK --set-xmark ${masquerademark}
   

10、遍历proxier维护的serviceMap结构(保存着最新的service对象),为每个service执行如下操作:

  • 首先获得该service对应的iptables链,命名形式为‘'KUBE-SVC-{hash值}”(如”KUBE-SVC-OKIBPPLEBEZLXS53")。
  • 在existingNATChains中查找其是否存在,如果存在,则直接将该链写入natChains,否则在natChains写入一条新链(如:KUBE-SVC-OKIBPPLEBEZLXS53 -0:0)。
  • 在activeNATChains(一个以链名为键的map)中标记该链为活跃状态。
  • 加入clusterIP对应的iptables规则。根据proxier参数MasqueradeAll的不同(该参数用于决定是否对所有请求都进行源地址转换),在natRules中写入形如如下两条规则中的一条,前一条对应参数为true的情况。
代码语言:txt
复制
     -A KUBE-SERVICES -m comment --comment "${svcName} cluster IP" -m ${protocol} -p ${protocol) -d ${cluster-ip}/32 --dport ${port} -j ${masqueradeMark} 
     
     -A KUBE-SERVICES -m comment --comment "${svcName} cluster IP" -m ${protocol} -p ${protocol} -d ${cluster-ip}/32 --dport ${port} -j KUBE-SVC-{hash值}
     
  • 处理externalIPs,在natRules中添加如下iptables规则。注意,如果该externalIPs是一个本地IP,则还需要将其对应的port打开。
代码语言:txt
复制
     -A KUBE-SERVICES -m comment --comment "${svcName} external IP" -m ${protocol} -p ${protocol} -d ${external-ip}/32 --dport ${port} -j ${masqueradeMark} 
     
     -A KUBE-SERVICES -m comment --comment "${svcName}
     external IP" -m ${protocol} -p ${protocol} -d ${external-ip}/32 -dport ${port} -m physdev ! --physdev-is-in -m addrtype ! --src-type LOCAL -j KUBE-SVC-{hash值}
     
     -A KUBE-SERVICES -m comment --comment "${svcName} external IP" -m ${protocol} -p ${protocol} -d ${external-ip}/32 --dport ${port} -m addrtype --dst-type LOCAL -j KUBE-SVC-{hash值}
     
  • 处理loadBalancer ingress,在natRules中添加如下iptables规则。
代码语言:txt
复制
     -A KUBE-SERVICES -m comment "${svcName} loadbalancer IP" -m ${protocol} -p ${protocol} -d ${ingress-ip}/32 --dport ${port} -j ${masqueradeMark}
     
     -A KUBE-SERVICES -m comment --comment "${svcName} loadbalancer IP" -m ${protocol} -p ${protocol} -d ${ingress-ip}/32 --dport ${port} -j KUBE-SVC-{hash值}
     
  • 处理nodePort。首先要在本地打开一个端口,然后在natRules添加如下iptables规则。
代码语言:txt
复制
     -A KUBE_NODEPORTS -m comment --commetn "${svcName}" -m ${protocol} -p ${protocol} -d ${ingress-ip}/32 --dport ${port} -j ${masqueradeMark}
     
     -A KUBE-NODEPORTS -m comment --comment "${svcName}" -m ${protocol} -p ${protocol} -d ${ingress-ip}/32 --dport ${port} -j KUBE-SVC-{hash值}
     
  • 如果一个service没有可用的后端endpoint,那么需要拒绝对其的请求。在filterRules中添加如下iptables规则。
代码语言:txt
复制
     -A KUBE_SERVICES -m comment --comment "${svcName} has no endpoints" -m ${protocol} -p ${protocol} -d ${cluster-ip}/32 --dport ${port} -j REJECT
     

至此,所有与service相关的iptables规则就已经全部创建完毕了。接下来,将为endpoint创建链和iptables规则。注意,下面的步骤12仍然处于步骤10中的循环里,即遍历service中。

11、遍历proxier维护的endpointsMap结构(以service为键,对应的endpoint列表为值的map),为每个endpoint执行如下操作:

  • 获得每个endpoint对应的iptables链,命名形式为”KUBE-SEP-{hash值}”(如KUBE-SEP-XL4YDER4UGY502IL)。
  • 在existingNATChains中查找该链是否存在,如果存在,则直接将该链写入natChains,否则在natChains写入一条新链(如:KUBE-SEP-XL4YDER4UGY502IL -0:0)。
  • 在activeNATChains中将该链标记为活跃状态。

12、首先考虑session affinity规则。为启用了该功能的service在natRules中加入如下iptables规则。

代码语言:txt
复制
      -A KUBE-SVC-{hash值} -m comment --comment ${svcName} -m recent --name KUBE-SEP-{hash值} --rcheck --seconds${stickyMaxAgeSeconds} --reap -j KUBE-SEP-{hash值}
    

13、接下来采用load balance规则,将一个service的流量分散到各个endpoint上。

  • 对于除了最后一个endpoint的其他endpoint,在natRules中加入如下规则。可以看到,这里出现了一个随机分配的机制,每条规则被选中的概率是1/(该service对应的endpoint数目-1)。
代码语言:txt
复制
      -A KUBE-SVC-{hash值} -m comment --comment ${svcName} -m statistic --mode random --probability 1.0/(${endpoint-number}-1) -j KUBE-SEP-{hash值}
      
  • 对于最后一个endpoint,在natRules中加入如下规则,说明在此前各条均没有匹配到iptables规则的情况下,则一定从这个endpoint来接收访问该service的请求。
代码语言:txt
复制
      -A KUBE-SVC-{hash值} -m comment --comment ${svcName} -j KUBE-SEP-{hash值}
      
  • 创建导向endpoint的iptables规则,在natRules中加入如下iptables规则,进行源地址解析。
代码语言:txt
复制
      -A KUBE-SEP-{hash值} -m comment --comment ${svcName} -s ${endpoint(pod)-ip} -j ${masqueradeMark}
      
  • 进行目的地址解析。在natRules中加入如下iptables规则。如果该service有session affnity规则,加入第一条iptables规则,否则加入第二条。
代码语言:txt
复制
      -A KUBE-SEP-{hash值} -m comment --comment ${svcName} -m recent --name KUBE-SEP-{hash值} --set -m ${protocol} -p ${protocol} -j DNAT --to-destination ${endpoints(pod)-ip}
      
      -A KUBE-SEP-{hash值} -m comment --comment ${svcName} -m ${protocol} -p ${protocol} -j DNAT --to-destination ${endpoint(pod)-ip}
      

至此,所有导向endpoint的iptables规则也基本创建完毕了。

14、清除existingNATChains中不处于活跃状态的service和endpoint对应的链。并且在natChains写入这些链,同时在natRules写入-X ${chain},使得可以安全地删除这些链。

15、在natRules中写入最后一条iptables规则,用于访问”KUBE-SERVICES”的流量接入到"KUBE-NODEPORTS"。

代码语言:txt
复制
      -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports;NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS

16、最后,为filterRules和natRules写入COMMIT,并且将其拼接起来,并通过iptables-restore将其导入到iptables中,完成根据service和endpoint的更新而同步iptables规则的任务。

17、处理不需要再占用端口的释放。

18、删除nat表中旧的源地址转换的iptables规则。

代码语言:txt
复制
      iptables -t nat -D POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4d415351 -j KUBE-MARK-MASQ
    

endpointsHandler

endpointHandler在选择后端时默认采用Round Robin算法,同时需要兼顾session affinity等要求。

  • userspace模式

前面已经介绍过,当访问请求经过iptables转发至proxier之后,选择一个pod的工作就需要交给endpointsHandler。 userspace模式下的endpointsHandler本质上是一个loadBalancer ( LB),它不仅能够按照策略选择出一个service endpoint(后端pod ),还需要能实时更新并维护service对应的endpoint实例信息。这两个过程分别对应loadBalancer的两个处理逻辑,即NextEndpoint和OnEndpointsUpdate。下面将逐一进行分析。

  • NextEndpoint

NextEndpoint方法核心调度算法是Round-Robin,每次一个请求到达,它的目的地都应该“下一个pod"。但是在LoadBalancer中,这个Round-Robin算法还能够同时考虑“Session Affinity”的因素,即如果用户指定这个service需要考虑会话亲密性,那么对于一个给定的客户端,NextEndpoint会一直返回它上一次访问到的那个pod直至会话过期。这个具体的工作流程如下所示。

(1) 根据请求中提供的service id(由namespace, service名和service端口号组成),查找该service代理的pod端点列表(一个ip:port形式的字符串链表)、当前的endpoint的索引值和该service的Session Affinity(SA)属性等。

(2) Session Affinity有两种类型:None和ClientIP,如果SA的类型是ClientIP,则来自同一个客户端IP的请求在一段时间内都将重定向到同一个后端pod,这样也就简洁做到了访问的会话粘性(Session Sticky ), SA的最长保活时间决定了这个时间段的长度,默认值是180分钟;如果SA的类型为空(None),则不进行任何会话记录。

(3) 假如这个service不需要SA功能,或者上述SA已经超时了,那么IoadBalance啥直接将当前的endpoint索引值++1,再对endpoint列表长度取余作为下一个可用endpoint的索引值。

(4) 当然,如果是由于SA超时引起的步骤(3), LoadBalancer还会为步骤(3)中最终被访问的那个pod建立Session Affinity实例并设置时间戳,这样下次这个ClientIP来的请求就一定会继续落在这个pod上。

  • OnEndpointsUpdate

知道了LoadBalancer如何选择一个“合适”的pod,再来看一下它如何保证它所知道的被代理pod列表总是最“准确”的。

由于所有被代理pod的变化最后都会反映到etcd里面对应的pod数据上,所以存储在etcd中的pod对象总可以认为是用户的期望值,代表了endpoint的“理想世界”,而LoadBalancer内存中的endpoint对象则反映了service对象与实际后端pod的“现实世界”。因此,OnEndpointsUpdate方法的作用就是用“理想世界”的endpoint对象同步“现实世界”的endpoint,这个同步的过程就是一旦etcd中的endpoint信息发生变化,那么LoadBalancer就会把endpoint列表(理想世界)加载进来,然后通过对比注册新添的endpoint到自己的service信息中,或者删除那些已经不存在的endpoint ,同时更新service Affnity数据。

  • iptables模式

正如上文所述,iptables式下的endpointsHandle体质上由proxier担任。它不再处理具体的选取service后端endpoint的工作,而只负责跟进endpoint对应的iptables规则。

  • OnEndpointsUpdate方法

接收到etcd中en即oint对象的更新列表后,更新其维护的endpointsMap,包括更新、创建和删除其中的service和endpoint对应记录。

其后的关键步骤syncProxyRules已经在上面展开,此处不再赘述。

核心组件协作流程

至此,Kubernetes中主要的组件我们都有了大致的了解。接下来,我们梳理一下在Kubernetes的全局视图下,当执行一些指令时这些组件之间是如何协作的,这样的流程解析对于读者将来对Kubernetes进行调试、排错和二次开发都是非常有帮助的。

创建pod

如图所示,当客户端发起一个创建pod的请求后,kubectl向APIServer的/pods端点发送一个HTTP POST请求,请求的内容即客户端提供的pod资源配置文件。

APIServer收到该REST API请求后会进行一系列的验证操作,包括用户认证、授权和资源配额控制等。验证通过后,APIServer调用etcd的存储接口在后台数据库中创建一个pod对象。

scheduler使用APIServer的API,定期从etcd获取/监测系统中可用的工作节点列表和待调度pod,并使用调度策略为pod选择一个运行的工作节点,这个过程也就是绑定(bind)。

绑定成功后,scheduler会调用APIServer的API在etcd中创建一个 binding对象,描述在一个工作节点上绑定运行的所有pod信息。同时kubelet会监听APIServer上pod的更新,如果发现有pod更新信息,则会自动在podWorker的同步周期中更新对应的pod。

这正是Kubernetes实现中“一切皆资源”的体现,即所有实体对象,消息等都是作为etcd里保存起来的一种资源来对待,其他所有组件间协作都通过基于APIServer的数据交换,组件间一种松耦合的状态。

创建replication controller

如图所示,当客户端发起一个创建replication controller的请求后,kubectl向APIServer的/controllers端点发送一个HTTP POST请求,请求的内容即客户端提供的replication controller资源配置文件。

与创建pod类似,APIServer收到该REST API请求后会进行一系列的验证操作。验证通过后,APIServer调用etcd的存储接口在后台数据库中创建一个replication controller对象。

controller manager会定期调用APIServer的API获取期望replication controller对象列表。再遍历期望RC对象列表,对每个RC,调用APIServer的API获取对应的pod集的实际状态信息。然后,同步replication controller的pod期望值与pod的实际状态值,创建指定副本数的pod。

创建service

如图所示,当客户端发起一个创建service的请求后,kubectl向APIServer的/services端点发送一个HTTP POST请求,请求的内容即客户端提供的service资源配置文件。

同样,APIServer收到该REST API请求后会进行一系列的验证操作。验证通过后,APIServer调用etcd的存储接口在后台数据库中创建一个service对象。

创建service示意图.png
创建service示意图.png

kube-proxy会定期调用APIServer的API获取期望service对象列表,然后再遍历期望service对象列表。对每个service,调用APIServer的API获取对应的pod集的信息,并从pod信息列表中提取pod IP和容器端口号封装成endpoint对象,然后调用APIServer的API在etcd中创建该对象。

  • userspace kube-proxy

对每个新建的service, kube-proxy会为其在本地分配一个随机端口号,并相应地创建一个ProxySocket,随后使用iptablesl具在宿主机上建立一条从ServiceProxy到ProxySocket的链路。同时,kube-prxoy后台启动一个协程监听ProxySocket上的数据并根据endpoint实例的信息(例如IP,port和session affinity属性等)将来自客户端的请求转发给相应的service后端pod。

  • iptables kube-proxy

对于每个新建的service,kube-proxy会为其创建对应的iptables。来自客户端的请求将由内核态iptables负责转发给service后端pod完成。

最后,kube-proxy会定期调用APIServer的API获取期望service和endpoint列表并与本地的service和endpoint实例同步。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • kubelet
  • kube-proxy
  • 核心组件协作流程
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档