Kubernetes API Server的核心功能是提供了Kubernetes各类资源对象(如Pod、RC、Service等)的增、删、改、查及Watch等HTTP Rest接口,成为集群内各个功能模块之间数据交互和通信的中心枢纽,是整个系统的数据总线和数据中心。除此之外,它还有以下一些功能特性:
k8s API Server通过一个名为kube-apiserver的进程提供服务,该进程运行在Master节点上。在默认情况下,kube-apiserver进程在本机的8080端口(对应参数--insecure-port)提供REST服务。我们可以同时启动HTTPS安全端口(--secure-prt=6443)来启动安全机制,加强REST API访问的安全性。
(1)apiserver方式一REST接口:通常我们可以通过命令行工具kubectl 来与k8s API Server交互,它们之间的接口是REST调用。
下面使用curl进行一些测试验证。
登录Master节点,查看k8s API的版本信息:
[root@localhost ~]# curl localhost:8080/api
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "10.0.2.5:6443"
}
]
}[root@localhost ~]#
运行下面的curl命令,分别返回集群中的Pod列表、Service列表、RC列表:
curl localhost:8080/api/v1/pods
curl localhost:8080/api/v1/services
curl localhost:8080/api/v1/replicationcontrollers
(2)方式二:如果我们只想对外暴露部分REST服务,则可以在Master或其他任何节点上通过运行kubectl proxy进程启动一个内部代理来实现
运行下面的命令,在8001端口启动代理,并且拒绝客户端访问RC的API:
[root@localhost ~]# kubectl proxy --reject-paths="^/api/v1/replicationcontrollers" --port=8001 --v=2
Starting to serve on 127.0.0.1:8001
已经禁止访问:
[root@localhost ~]# curl localhost:8001/api/v1/replicationcontrollers
<h3>Unauthorized</h3>
kubectl proxy具有很多特性,最实用的一个是提供了简单有效的安全机制,比如采用白名单方式限制非法客户的访问时,只要增加下面的参数即可:
--accept-hosts="^localhost,^127\\.0\\.0\\.1
(3)方式三:通过编程的方式调用k8s API Server
通常用来实现分布式集群搭建,或者开发基于k8s的管理平台。
k8s API Server最主要的REST接口是资源对象的增删改查,另外还有一类特殊的REST接口—k8s Proxy API接口,这类接口的作用是代理REST请求,即kubernetes API Server把收到的REST请求转发到某个Node上的kubelet守护进程的REST端口上,由该kubelet进程负责响应。
关于Node相关的接口的REST路径为:/api/v1/proxy/nodes/{name},其中{name}为节点的名称或IP地址。
包括以下几个具体的接口:
/api/v1/proxy/nodes/{name}/pods/ #列出指定节点内所有Pod的信息
/api/v1/proxy/nodes/{name}/stats/ #列出指定节点内物理资源的统计信息
/api/v1/prxoy/nodes/{name}/spec/ #列出指定节点的概要信息
需要说明的是:这里获取的Pod信息来自Node而非etcd数据库,两者时间点可能存在偏差。
此外,如果在kubelet进程启动时加--enable-debugging-handles=true参数,那么kubernetes Proxy API还会增加以下接口:
/api/v1/proxy/nodes/{name}/run #在节点上运行某个容器
/api/v1/proxy/nodes/{name}/exec #在节点上的某个容器中运行某条命令
/api/v1/proxy/nodes/{name}/attach #在节点上attach某个容器
/api/v1/proxy/nodes/{name}/portForward #实现节点上的Pod端口转发
/api/v1/proxy/nodes/{name}/logs #列出节点的各类日志信息
/api/v1/proxy/nodes/{name}/metrics #列出和该节点相关的Metrics信息
/api/v1/proxy/nodes/{name}/runningpods #列出节点内运行中的Pod信息
/api/v1/proxy/nodes/{name}/debug/pprof #列出节点内当前web服务的状态,包括CPU和内存的使用情况
(2)k8s Proxy API关于Pod的相关接口
通过这些接口,我们可以访问Pod里某个容器提供的服务。
/api/v1/proxy/namespaces/{namespace}/pods/{name}/{path:*} #访问pod的某个服务接口
/api/v1/proxy/namespaces/{namespace}/pods/{name} #访问Pod
/api/v1/proxy/namespaces/{namespace}/services/{name}/{path:*} #访问service的某个服务接口
/api/v1/proxy/namespaces/{namespace}/services/{name} #访问service
Pod的proxy接口的作用是在kubernetes集群之外访问某个pod容器的服务(HTTP服务),可以用Proxy API实现,这种场景多用于管理目的,比如逐一排查Service的Pod副本,检查哪些Pod的服务存在异常问题。
注:k8s Proxy API关于Pod的相关接口在v1.8.8版本的一个实验环境中验证测试失败,问题待解。
kubernetes API Server作为集群的核心,负责集群各功能模块之间的通信,集群内各个功能模块通过API Server将信息存入etcd,当需要获取和操作这些数据时,通过API Server提供的REST接口(GET\LIST\WATCH方法)来实现,从而实现各模块之间的信息交互。
主要有以下三类通信交互的场景:
1)kubelet与API Server交互
每个Node节点上的kubelet定期就会调用API Server的REST接口报告自身状态,API Server接收这些信息后,将节点状态信息更新到etcd中。kubelet也通过API Server的Watch接口监听Pod信息,如果监听到新的Pod副本被调度绑定到本节点,则执行Pod对应的容器的创建和启动逻辑;如果监听到Pod对象被删除,则删除本节点上的相应的Pod容器;如果监听到修改Pod信息,则kubelet监听到变化后,会相应的修改本节点的Pod容器。
2)kube-controller-manager与API Server交互
kube-controller-manager中的Node Controller模块通过API Server提供的Watch接口,实时监控Node的信息,并做相应处理。
3)kube-scheduler与API Server交互
Scheduler通过API Server的Watch接口监听到新建Pod副本的信息后,它会检索所有符合该Pod要求的Node列表,开始执行Pod调度逻辑。调度成功后将Pod绑定到目标节点上。
为了缓解各模块对API Server的访问压力,各功能模块都采用缓存机制来缓存数据,各功能模块定时从API Server获取指定的资源对象信息(LIST/WATCH方法),然后将信息保存到本地缓存,功能模块在某些情况下不直接访问API Server,而是通过访问缓存数据来间接访问API Server。
Controller Manager作为集群内部的管理控制中心,负责集群内的Node、Pod副本、服务端点(Endpoint)、命名空间(Namespace)、服务账号(ServiceAccount)、资源定额(ResourceQuota)的管理,当某个Node意外宕机时,Controller Manager会及时发现此故障并执行自动化修复流程,确保集群始终处于预期的工作状态。
Controller Manager内部包含多种Controller,每种Controller都负责一种具体的控制流程,而Controller Manager正是这些Controller的核心
每个Controller通过API Server提供的接口实时监控整个集群的每个资源对象的当前状态,当发生各种故障导致系统状态发生变化时,会尝试着将系统状态从"现有状态"修正到"期望状态"。
这里的Replication Controller所指的是副本控制器,并不是RC资源对象,注意不要混淆。
Replication Controller 副本控制器的核心作用是确保在任何时候集群中一个RC所关联的Pod副本数始终保持预设值。需要注意的一点是:只有当Pod的重启策略是Always的时候(RestartPolicy=Always),副本控制器才会管理该Pod的操作(例如创建、销毁、重启等)。
Replication Controller 副本控制器在管理Pod时的一些特征:
最好不要越过RC而直接创建Pod,因为Replication Controller会通过RC管理Pod副本,实现自动创建、补足、替换、删除Pod副本,这样能提高系统的容灾能力,减少由于节点崩溃等意外状况造成的损失。即使你的应用程序只用到一个Pod副本,我们也强烈建议使用RC来定义Pod。
Replication Controller的职责:
1)确保当前集群中有且仅有N个Pod实例,N是RC中定义的Pod副本数量。
2)通过调整RC中的spec.replicas属性值来实现系统扩容或缩容。
3)通过改变RC中的Pod模板(主要是镜像版本)来实现系统的滚动升级。
与上述三类职责所对应的是Replication Controller的三类典型使用场景:
1)重新调度,当发生节点故障或Pod被意外终止运行时,可以重新调度保证集群中仍然运行指定的副本数。
2)弹性伸缩,通过手动或自动扩容代理修复副本控制器的spec.replicas属性,可以实现弹性伸缩。
3)滚动更新,推荐的方式是创建一个新的只有一个副本的RC,若新的RC副本数量加1,则旧的RC的副本数量减1,直到这个旧的RC的副本数量为零,然后删除该旧的RC。kubectl rolling-update命令就是按该推荐方式所实现的。
kubelet进程在启动时会通过API Server注册自身的节点信息,并定时向API Server汇报状态信息,API Server接收到信息后将信息更新到etcd中。etcd中存储的节点信息包括节点健康状况、节点资源、节点名称、节点地址信息、操作系统版本、Docker版本、kubelet版本等。节点健康状况包含"就绪"(True)、"未就绪"(False)和"未知"(Unknown)三种。
Node Controller通过API Server实时获取Node的相关信息,实现管理和监控集群中的各个Node节点的相关控制功能。
Node Controller的核心工作流程如下图所示:
对流程中关键点的解释如下:
(1)Controller Manager在启动时如果设置了--cluster-cidr参数,那么为每个没有设置Spec.PodCIDR的Node节点生成一个CIDR地址,并用该CIDR地址设置节点的Spec.PodCIDR属性,防止不同的节点的CIDR地址发生冲突。
(2)Controller Manager在处理节点的状态监控管理时的具体流程见以上流程图。
(3)逐个读取节点信息,如果节点状态变成非“就绪”状态,则将节点加入待删除队列,否则将节点从该队列删除。如果节点状态为非"就绪"状态,则删除etcd中的节点信息,并删除和该节点相关的Pod等资源信息。
资源配额管理确保指定的资源对象在任何时候都不会超量占用系统物理资源。
目前k8s支持如下三个层次的资源配额管理:
1)容器级别:对CPU和Memory进行限制
2)Pod级别:对一个Pod内所有容器的可用资源进行限制
3)Namespace级别:是Namespace级别(多租户)的限制,包括:
k8s配额管理是通过Admission Control(准入控制)来控制的。 Admission Control当前提供两种配额约束的方式,分别是LimitRanger和ResourceQuota。
其中LimitRanger作用于Pod和Container上,而ResourceQuota则作用于Namespace上,限定一个Namespace里的各类资源的使用总额。
ResourceQuota Controller流程图:
用户通过API Server可以创建新的Namespace并保存在etcd中,Namespace Controller定时通过API Server读取这些Namespace信息。
如果Namespace被API标记为优雅删除(即设置删除期限,DeletionTimestamp),则将该Namespace状态设置为“Terminating”并保存到etcd中。同时Namespace Controller删除该Namespace下的ServiceAccount、RC、Pod、Secret、PersistentVolume、ListRange、ResourceQuota和Event等资源对象。
当Namespace状态设置为“Terminating”后,由Admission Controller的NamespaceLifecycle插件来阻止为该Namespace创建新的资源。同时,在Namespace Controller删除完该Namespace中的所有资源对象后,Namespace Controller对该Namespace执行finalize操作,删除Namespace的spec.finalizers域中的信息。
如下图所示,Endpoints表示一个Service对应的所有Pod副本的访问地址,而Endpoints Controller就是负责生成和维护所有Endpoints对象的控制器。
Endpoints表示了一个Service对应的所有Pod副本的访问地址,而Endpoints Controller负责生成和维护所有Endpoints对象的控制器。它负责监听Service和对应的Pod副本的变化。
kube-proxy进程获取每个Service的Endpoints,实现Service的负载均衡功能。
Service Controller
Service Controller是属于kubernetes集群与外部的云平台之间的一个接口控制器。Service Controller监听Service变化,如果是一个LoadBalancer类型的Service,则确保外部的云平台上对该Service对应的LoadBalancer实例被相应地创建、删除及更新路由转发表。
k8s Scheduler在整个系统中承担了"承上启下"的重要功能。承上,是指它负责接收Controller Manager创建的新Pod,为其安排一个落脚的“家”, 即目标Node。启下,是指安置工作完成后,目标Node上的kubelet服务进程接管后继工作,负责Pod生命周期中的“下半生”。
具体来说,k8s Scheduler的作用是将待调度的Pod(API新创建的Pod、Controller Manager为补足副本而创建的Pod等)按照特定的调度算法和调度策略绑定到集群中的某个合适的Node上,并将绑定信息写入etcd中。
在整个调度过程中涉及三个对象,分别是:待调度Pod列表、可用Node列表,以及调度算法和策略。
随后,目标节点上的kubelet通过API Server监听到k8s Scheduler产生的Pod绑定事件,然后获取对应的Pod清单,下载Image镜像,并启动容器。
完整的流程如下所示:
k8s Scheduler当前提供的默认调度流程分为以下两步:
1)预选调度过程,即遍历所有目标Node,筛选出符合要求的候选节点,kubernetes内置了多种预选策略(xxx Predicates)供用户选择。
2)确定最优节点,在第一步的基础上采用优选策略(xxx Priority)计算出每个候选节点的积分,最高积分者胜出。
k8s Scheduler的调度流程是通过插件方式加载的“调度算法提供者”(AlgorithmProvider)具体实现的。一个AlgorithmProvider其实就是包括了一组预选策略与一组优选策略的结构体。
注册AlgorithmProvider的函数如下:
func RegisterAlgorithmProvider(name string, predicateKeys, priorityKeys util.StringSet)
它包含三个参数:
Scheduler中可用的预选策略包含:NoDiskConflict, PodFitsResources, PodSelectorMatches, PodFirstHost, CheckNodeLabelPresence, CheckServiceAffinity和PodFitsPorts策略等。
其默认的AlgorithmProvider加载的预选策略Predicates包括:PodFitsPorts, PodFitsResources, NoDiskConflict, MatchNodeSelector(PodSelectorMatches) 和 HostName(PodFitsHost),即每个节点只有通过前面提及的5个默认预选策略后,才能初步被选中,进入下一个流程。
1)NoDiskConflict
判断备选Pod的gcePersistentDisk或AWSElasticBlockStore和备选的节点中已存在的Pod是否存在冲突。检测过程如下:
2)PodFitsResources
判断备选节点的资源是否满足备选Pod的需求,检测过程如下:
3)PodSelectorMatches
判断备选节点是否包含备选Pod的标签选择器指定的标签。
4)PodFitsHost
判断备选Pod的spec.nodeName域所指定的节点名称和备选节点的名称是否一致,如果一致,则返回true,否则返回false。
5)CheckNodeLabelPresence
如果用户在配置文件中指定了该策略,则Scheduler会通过RegisterCustomFitPredicate方法注册该策略。该策略用于判断策略列出的标签在备选节点中存在时,是否选择该备选节点。
6)CheckServiceAffinity
如果用户在配置文件中指定了该策略,则Scheduler会通过RegisterCustomFitPredicate方法注册该策略。该策略用于判断备选节点是否包含策略指定的标签,或包含和备选Pod在相同Service和Namespace下的Pod所在节点的标签列表。如果存在,则返回true,否则返回false。
7)PodFitsPorts
判断备选Pod所用的端口列表中的端口是否在备选节点中已被占用,如果被占用,则返回false,否则返回true。
Scheduler中的优选策略包含:LeastRequestedPriority、CalculateNodeLabelPriority和BalancedResourceAllocation等。
每个节点通过优选策略时都会算出一个得分,计算各项得分,最终选出得分值最大的节点作为优选的结果(也是调度算法的结果)。
下面是对优选策略的详细说明:
1) LeastRequestedPriority
优先从备选节点列表中选择资源消耗最小的节点(CPU+内存)。
2) CalculateNodeLabelPriority
如果用户在配置文件中指定了该策略,则scheduler会通过RegisterCustomPriorityFunction方法注册该策略。该策略用于判断策略列出的标签在备选节点中存在时,是否选择该备选节点。
如果备选节点的标签在优先策略的标签列表中且优选策略的presence为true,或者备选节点的标签不在优选策略的标签列表中且优选策略的presence值为false,则备选节点score=10,否则备选节点score=0。
3) BalancedResourceAllocation
优先从备选节点列表中选择各项资源使用率最均衡的节点。
在kubernetes集群中,每个Node节点(又称Minion)上都会启动一个kubelet服务进程。该进程用于处理Master节点下发到本节点的任务,管理Pod和其中的容器。每个kubelet进程会在API Server上注册节点自身信息,定期向Master节点汇报节点资源的使用情况,并通过cAdvisor监控容器和节点资源。
节点通过设置kubelet的启动参数“--register-node”,来决定是否自动主动地向API Server注册自己,默认为true。
在自注册时,kubelet启动时还包含以下参数:
当前每个kubelet被授予创建和修改任务节点的权限,但实践中,它仅仅创建和修改自己。未来将会限制这一权限,仅允许它修改和创建其所在节点的权限。
在集群运行过程中,如果遇到资源不足的情况,则用户很容易通过添加机器及利用kubelet的自注册模式来实现扩容。
如果系统管理员希望手动创建节点信息,则可以通过设置--register-node=false即可。
通过--node-status-update-frequency参数,可以配置kubelet向API Server报告节点状态的时间频率,默认为间隔10s。
kubelet通过以下几种方式获取自身Node上所需要运行的Pod清单。
所有以非API Server方式创建的Pod都叫做Static Pod。kubelet将Static Pod状态汇报给API Server,API Server为该Static Pod创建一个Mirror Pod和其相匹配。Mirror Pod的状态将真实反映Static Pod的状态。当Static Pod被删除时,与之相对应的Mirror Pod也会被删除。
在本文只讨论通过API Server获得Pod清单的方式。kubelet通过API Server Client使用Watch加List的方式监听etcd中/registry/nodes/${当前节点名称}和/registry/pods的目录,将获取的信息同步到本地缓存中。
kubelet监听etcd,所以针对Pod的操作将会被kubelet监听到。如果发现有新的绑定到本节点的Pod,则按照Pod清单的要求创建该Pod。
如果发现本地Pod被修改,则kubelet会做出相应的修改,比如删除Pod中的某个容器时,则通过Docker Client删除该容器。
如果发现删除本节点的Pod,则删除相应的Pod,并通过Docker Client删除Pod中的容器。
kubelet读取监听到的信息,如果是创建和修改Pod任务,则做如下处理:
1)为该Pod创建一个数据目录。
2)从API Server读取该Pod清单。
3)为该Pod挂载外部卷(External Volume)
4)下载Pod用到的Secret。
5)检查已运行在节点中的Pod,如果该Pod没有容器或Pause容器没有启动,则先停止Pod里所有容器的进程。如果在Pod中有需要删除的容器,则删除这些容器。
6)用"kubernetes/pause"镜像为每个Pod创建一个容器。该Pause容器用于接管Pod中所有其它容器的网络。
7)为Pod中的每个容器做如下处理:
Pod的健康状态由两类探针来检查:LivenessProbe和ReadinessProbe。
LivenessProbe
kubelet定期执行LivenessProbe探针来判断容器的健康状态。
LivenessProbe参数:
LivenessProbe三种实现方式
1)ExecAction
在一个容器内部执行一个命令,如果该命令状态返回值为0,则表明容器健康。
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec
spec:
containers:
- name: liveness
image: busybox
args:
- /bin/sh
- -c
- echo ok > /tmp/health;sleep 10;rm -fr /tmp/health;sleep 600
livenessProbe:
exec:
command:
- cat
- /tmp/health
initialDelaySeconds: 15
timeoutSeconds: 1
2)TCPSocketAction
通过容器IP地址和端口号执行TCP检查,如果能够建立TCP连接,则表明容器健康。
apiVersion: v1
kind: Pod
metadata:
name: pod-with-healthcheck
spec:
containers:
- name: nginx
image: nginx
ports:
- containnerPort: 80
livenessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 15
timeoutSeconds: 1
3)HTTPGetAction
通过容器的IP地址、端口号及路径调用HTTP Get方法,如果响应的状态码大于等于200且小于等于400,则认为容器健康。
apiVersion: v1
kind: Pod
metadata:
name: pod-with-healthcheck
spec:
containers:
- name: nginx
image: nginx
ports:
- containnerPort: 80
livenessProbe:
httpGet:
path: /_status/healthz
port: 80
initialDelaySeconds: 15
timeoutSeconds: 1
ReadinessProbe
在kubernetes集群中,应用程序的执行情况可以在不同的级别上监测到。这些级别包括:容器、Pod、Service和整个集群。
Heapster项目为k8s提供了一个基本的监控平台,它是集群级别的监控和事件数据集成器(Aggregator)。Heapster作为Pod运行在k8s集群中,通过kubelet发现所有运行在集群中的节点,并查看来自这些节点的资源使用状况信息。
kubelet通过cAdvisor获取其所在节点及容器的数据,Heapster通过带着关联标签的Pod分组这些信息,这些数据被推动一个可配置的后端,用于存储和展示。当前支持的后端包括InfluxDB(with Grafana for Visualization)和Google Cloud Monitoring。
cAdvisor是一个开源的分析容器资源使用率和性能特性的代理工具。在k8s项目中,cAdvisor被集成到k8s代码中,cAdvisor自动查找所有在其所在节点上的容器,自动采集CPU、内存、文件系统和网络使用的统计信息。
cAdvisor通过它所在节点机的Root容器,采集并分析该节点机的全面使用情况。
在大部分k8s集群中,cAdvisor通过它所在节点机的4194端口暴露一个简单的UI。
小结:
kubelet作为连接k8s Master和各节点机之间的桥梁,管理运行在节点机上的Pod和容器。kubelet将每个Pod转换成它的成员容器,同时从cAdvisor获取单独的容器使用统计信息,然后通过该REST API暴露这些聚合后的Pod资源使用的统计信息。
为了支持集群的水平扩展、高可用性,k8s抽象出来Service的概念。Service是一组Pod的抽象,它会根据访问策略(如负载均衡策略)来访问这组Pod。
k8s在创建服务时会为服务分配一个虚拟的IP地址,客户端通过访问这个虚拟的IP地址来访问服务,而服务则负责将请求转发到后端的Pod上。这就相当于是一个反向代理。但是它和普通的反射代理有一些不同之处:首先,它的IP地址是虚拟的;其次是,它的部署和启停是k8s统一自动管理的。
Service在很多情况下只是一个概念,而真正将Service的作用落实的是背后的kube-proxy服务进程。
在k8s集群的每个Node上都会运行一个kube-proxy服务进程,这个进程可以看作Service的透明代理兼负载均衡器,其核心功能是将到某个Service的访问请求转发到后端的多个Pod实例上。对每一个TCP类型的k8s Service,kube-proxy都会在本地Node上建立一个SocketServer来负责接收请求,然后均匀发送到后端某个Pod的端口上,这个过程默认采用Round Robin负载均衡算法。另外,k8s也提供通过修改Service的service.spec.sessionAffinity参数的值来实现会话保持特性的定向转发。如果设置的值为"ClientIP",则将来自同一个ClientIP的请求都转发到同一个后端Pod上。
Service的ClusterIP与NodePort等概念是kube-proxy服务通过iptables的NAT转换实现的,kube-proxy在运行过程中动态创建与Service相关的iptables规则,这些规则实现了Cluster IP及NodePort的请求流量重定向到kube-proxy进程上对应服务的代理端口的功能。
由于iptables机制针对的是本地的kube-proxy端口,所以每个Node上都要运行kube-proxy组件。
综上所述,由于kube-proxy的作用:在Service的调用过程中客户端无须关心后端有几个Pod,中间过程的通信、负载均衡及故障恢复都是透明的。
访问Service的请求,不论是用ClusterIP+TargetPort的方式,还是用节点机IPO+NodePort的方式,都被节点机的iptables规则重定向到kube-proxy监听Service服务代理端口。如下图所示。
kube-proxy通过查询和监听API Server中Service与Endpoints的变化,为每个Service都建立了一个"服务代理对象",并自动同步。服务代理对象是kube-proxy程序内部的一种数据结构,它包括一个用于监听此服务请求的SocketServer,SocketServer的端口是随机选择的一个本地空闲端口。
此外,kube-proxy内部也创建了一个负载均衡器——LoadBalancer,LoadBalancer上保存了Service到对应的后端Endpoint列表的动态路由转发路由表,而具体的路由选择则取决于RoundRobin负载均衡算法及Service的Session会话保持(SessionAffinity)这两个特性。
1)针对发生变化的Service列表,kube-proxy的具体处理流程如下:
(1)如果该Service没有设置集群IP(ClusterIP),则不做任何处理,否则,获取该Service的所有端口定义列表(spec.ports域)。
(2)逐个读取服务端口定义列表中的端口信息,根据端口名称、Service名称和Namespace判断本地是否已经存在对应的服务代理对象,如果不存在则新建;如果存在并且Service端口被修改过,则先删除iptables中和该Service端口相关的规则,关闭服务代理对象,然后走新建流程,即为该Service端口分配服务代理对象并为该Service创建相关的iptables规则。
(3)更新负载均衡器组件中对应Service的转发地址列表,对于新建的Service,确定转发时的会话保持策略。
(4)对于已删除的Service则进行清理。
2)针对Endpoint的变化,kube-proxy会自动更新负载均衡器中对应Service的转发地址列表。
3)以上两个步骤,kube-proxy针对iptables所做的一些具体操作如下:
kube-proxy在启动时和监听到Service或Endpoint的变化后,会在本机iptables的NAT表中添加4条规则链。
(1)KUBE-PORTALS-CONTAINER:从容器中通过Service Cluster IP和端口号访问Service的请求。
(2)KUBE-PORTALS-HOST:从主机中通过Service Cluster IP和端口号访问Service的请求。
(3)KUBE-NODEPORT-CONTAINER:从容器中通过Service的NodePort端口号访问Service的请求。
(4)KUBE-NODEPORT-HOST:从主机中通过Service的NodePort端口号访问Service的请求。
Kubernetes通过在目标node的iptables中的nat表的PREROUTING和POSTROUTING链中创建一系列的自定义链 (这些自定义链主要是“KUBE-SERVICES”链、“KUBE-POSTROUTING”链、每个服务所对应的“KUBE-SVC-XXXXXXXXXXXXXXXX”链和“KUBE-SEP-XXXXXXXXXXXXXXXX”链),然后通过这些自定义链对流经到该node的数据包做DNAT和SNAT操作以实现路由、负载均衡和地址转换。
基于iptables的kube-proxy的实现代码在pkg/proxy/iptables/proxier.go,其主要职责包括两大块,一块是侦听Service更新事件,并更新Service相关的iptables规则,一块是侦听Endpoint更新事件,更新Endpoint相关的iptables规则,将包请求转入Endpoint对应的Pod,如果某个Service尚没有Pod创建,那么针对此Service的请求将会被drop掉。
kube-proxy对iptables的链进行了扩充,自定义了KUBE-SERVICES,KUBE-NODEPORTS,KUBE-POSTROUTING,KUBE-MARK-MASQ和KUBE-MARK-DROP五个链,并主要通过为
KUBE-SERVICES chain增加rule来配制Routing Traffic规则。
在iptables表中,通过iptables-save可以看到在Nat表中创建好的这些链。
:KUBE-MARK-DROP - [0:0] /*对于未能匹配到跳转规则的traffic set mark 0x8000,有此标记的数据包会在filter表drop掉*/
:KUBE-MARK-MASQ - [0:0] /*对于符合条件的包 set mark 0x4000, 有此标记的数据包会在KUBE-POSTROUTING chain中统一做MASQUERADE*/
:KUBE-NODEPORTS - [0:0] /*针对通过nodeport访问的package做的操作*/
:KUBE-POSTROUTING - [0:0]
:KUBE-SERVICES - [0:0] /*操作跳转规则的主要chain*/
为默认的PREROUTING,Output和POSTROUTING chain增加规则,跳转至Kubernetes自定义的新chain。
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
对于KUBE-MARK-MASQ链中所有规则设置了Kubernetes独有MARK标记,在KUBE-POSTROUTING链中对NODE节点上匹配Kubernetes独有MARK标记的数据包,进行SNAT处理。
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
Kube-proxy接着对每个服务创建“KUBE-SVC-”链,并在Nat表中将KUBE-SERVICES链中每个目标地址是Service的数据包导入这个“KUBE-SVC-”链,如果Endpoint尚未创建,KUBE-SVC-链中没有规则,任何Incoming Packets在规则匹配失败后会被KUBE-MARK-DROP。
如果一个Service对应的Pod有多个Replicas,在iptables中会有多条记录,并通过 -m statistic --mode random --probability来控制比率。
PREROUTING chain的最终跳转规则,抽象为下图:
转发规则的包匹配规则部分如下所示:
-m comment --comment SERVICESTRING -p PROTOCOL -m PROTOCOL --dport DESTPORT -d
其中:
转发规则的跳转部分(-j部分):
如果Service类型为NodePort,则kube-proxy在iptables中除了添加上面介绍的规则外,还会为每个Service创建由NodePort端口到kube-proxy所在主机IP+Service代理服务所监听的端口的转发规则。
转发规则的包匹配规则部分如下所示:
-m comment --comment SERVICESTRING -p PROTOCOL -m PROTOCOL --dport NODEPORT
上面所列的内容用于匹配目的端口为"$NODEPORT"的包。
转发规则的跳转部分(-j部分)和前面提及的跳转规则一致。
[root@worknode1 ~]# iptables-save