本文主要介绍k8s的核心原理,包括浅析各个模块的运行逻辑和k8s中的网络通讯。
第一部分:模块
核心架构
以上是在k8s中的各个模块,下面就来详细介绍一下各个模块的作用和原理。
1、API Server
API Server是集群管理的API入口,控制资源配额,提供了完备的安全机制,包括增删改查的Rest API,也包括一些实时监听的Watch接口。
以下是一个简单的样例,通过访问API可以直接获取信息:
[root@VM-0-11-centos ~]# curl -i 'http://localhost:8080/api'
HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 24 Oct 2023 00:40:21 GMT
Content-Length: 183
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "172.27.0.11:6443"
}
]
}
通过API Server的接口可以操作k8s集群或者查看集群内的详细信息,除了上面的接口还有如下接口可以尝试:
curl localhost:8080/api/v1/pods # 返回集群的pod列表
curl localhost:8080/api/v1/services # 返回集群的service列表
curl localhost:8080/api/v1/replicationcontrollers # 返回集群的rc列表
通过API Server我们不仅可以查询Master上面的信息,还可以通过API Server将请求转发到某个Node上,如:
curl -i 'http://127.0.0.1:8080/api/v1/proxy/nodes/127.0.0.1/pods/' # 指定节点127.0.0.1上的所有Pod信息
curl -i 'http://127.0.0.1:8080/api/v1/proxy/nodes/127.0.0.1/stats/' # 指定节点内物理资源的统计信息
curl -i 'http://127.0.0.1:8080/api/v1/proxy/nodes/127.0.0.1/spec/' # 指定节点的概要信息
curl -i 'http://127.0.0.1:8080/api/v1/proxy/nodes/127.0.0.1/metrics' # 节点上的metrics信息
curl -i 'http://127.0.0.1:8080/api/v1/proxy/nodes/127.0.0.1/logs/' # 节点上的日志信息
... # 其他一些相关的API可以查看官方文档
2、模块之间通讯
从k8s的基础架构图中我们知道,k8s包括:API Server,kubelet,etcd,controller-manager,scheduler等,那他们之间是如何通讯的?
(1)etcd是存储各个模块的状态信息,操作数据等;
(2)每个Node节点上运行着kubelet进程,每隔一个时间周期就会调用一次API Server的接口,报告当前Node状态,同时API Server收到这些信息以后,存入etcd中;
(3)kubelet除了定时上报信息,还有监听API Server的Watch接口,如果需要在本节点调度Pod,则会执行创建或者销毁等逻辑;
(4)controller-manager也会与API Server交互,通过API Server的Watch接口,监听kubelet上报过来的Node信息,如果Node挂了则负责重新调度,当然controller-manager还控制着其他各个controller模块;
(5)scheduler是通过API Server监听Pod信息后,负责检索所有符合该Pod条件的Node列表,执行Pod的调度,调度成功后将Pod绑定到目标节点上;
3、contoller-manager
contoller-manager是集群内的管理中心,负责集群的Node,Pod,Endpoint,Namespace,ServiceAccount,ResourceQuota等的管理,当Node出现问题时,contoller-manager负责及时发现故障,并执行自动修复流程,确保达到预期的工作状态。
在k8s中有一些逻辑contoller,分别管理集群的每一个部分的控制权,下面是常用的介绍:
- ReplicationController:核心作用是确保-个RC所关联的Pod数量保持预设值,如果发现超过则销毁一些,如果发现少了则增加一些,异常情况会销毁重新调度到其他Node节点下,其中RC使用场景是:重新调度,弹性扩缩容和滚动更新;
- NodeController:作用是通过API Server实时获取Node信息,实现管理和监控集群的Node节点。NodeController会从kubelet上报的信息中判断节点状态信息,如果节点状态变成非"就绪",则将节点加入待删除队列,如果变成非就绪同时指定了 --cloud-provider选项,则调用cloudprovider查看节点信息,如果有故障则删除节点和节点上的相关的Pod等资源;
- ResourceQuotaController:资源配额管理,作用是确保指定的资源对象在任何时候都不会超量占用系统物理资源,防止雪崩。ResourceQuota作用于Namespace,限定一个Namespace里的各类资源使用总额;
- EndpointsController:负责监听Service和对应Pod的副本变化,如果Service被删除,则删除关联的Endpoints对象,如果被修改则更新Pod信息和Endpoints;
4、scheduler
前面说到controller是负责创建Pod,那么如何调度到目标节点?scheduler的作用就是将待调度的Pod,按照特定的调度算法和调度策略调度到合适的Node节点上,并将信息保存到etcd中,scheduler写入信息后,节点上的kubelet通过监听API Server获取到对应需要调度的Pod,执行镜像下载,启动容器等。
scheduler架构图
scheduler调度简单分为两步:
(1)预选调度过程,遍历所有的目标Node,筛选候选节点;
(2)确定最优节点,通过策略计算每个候选节点的分数,然后最高分数节点获胜后确认调度节点;
其中scheduler调度流程可以通过AlgorithmProvider实现,其注册函数如下:
func RegisterAlgorithmProvider(name string, predicateKeys, priorityKeys util.StringSet)
- name:调度名称
- predicateKeys:算法预选策略集合,包括NoDiskConflict,PodFitsResources,PodSelectorMatches,PodFitsHost等,主要是通过加载多种策略来选择Node节点
- priorityKeys:算法优先策略集合,包括LeastRequestedPriority,CalculateNodeLabelPriority,BalacedResourceAllocation等,通过计算节点得分来决策Node节点
5、kubelet
在k8s集群中,每个Node节点上都会启动一个kubelet进程,从前面可以看出kubelet主要作用是符合帮助Master代理执行各种在Node上的命令,那具体会执行哪些呢?
(1)注册
kubelet启动后,可以向API Server注册,定时向API Server发送节点的新消息,然后API Server收到消息后都会写入etcd中,有一些常用的启动命令如下。
- --api-servers:API Server的ip和端口
- --node-status-update-frequency:kubelet上报API Server的时间周期,默认是10s
(2)Pod管理
kubelet主要的作用就是监听Pod任务,创建或者删除,其步骤是:
- 从API Server读取Pod任务清理
- 挂载外部卷到Pod中
- 下载Pod所需的安全文件Secret
- 检查是否已经有运行的Pod,如果Pod没有容器或者pause容器没有启动,则停止Pod的所有容器
- 为每一个Pod启动pause容器,通过pause容器共享当前pod的所有资源
- 启动pause容器后,接着计算容器hash,通过hash查找到容器,如果找到则不处理,没有找到停止之前关联的并停止容器
(3)监控
kubelet在节点上要监听Pod和Node,这样才能将信息上报给API Server,以便Master节点做出决策。
- kubelet监控Node节点上的Pod是否正常,其中Pod提供了LivenessProbe和ReadinessProbe两种探针检测方式,具体的yaml配置可以看看《云原生二十篇|Kubernetes基础知识》,这里kubelet底层是通过定时调用
ExecAction
,TCPSocketAction
和HTTPAction
实现LivenessProbe功能,从而判断容器是否正常 - kubelet监控Node本身的信息和各个Pod的信息并上报,比如磁盘,CPU,IO性能等,其原理是kubelet通过cAdvisor获取对应的监控数据
6、kube-proxy
从前面的文章我们已经知道,可以通过Service提供对集群外的服务,其背后的服务实际上是Node上的kube-proxy模块,那kube-proxy具体做了哪些事情?
(1)每个Node上都会启动kube-proxy进程,该进程相当于是一个透明代理,功能是将请求转发到后端的Pod上;
(2)kube-proxy运行过程中,动态创建与Service相关的Iptables规则,实现Cluster IP+NodePort的流量重定向,并将流量都转发到kube-proxy对应服务的代理端口中;
(3)kube-proxy查询和监听API Server中的Service和Endpoints的变化,创建服务代理对象,创建一个随机端口,将Pod与Node本地端口映射,然后通过修改Iptables的规则,将请求转发给映射端口,从而经过DNAT转发到Pod内部;
不过值得注意的是现在k8s提供IPVS的模式,解决了Iptables配置规则太多导致的性能问题,具体网络的一些详细的内容放到网络部分具体讲解。
第二部分:网络
k8s实现网络通讯主要包括两个部分:集群内和集群外,其中集群内通讯又包括Pod之间的通讯,容器之间的通讯,具体容器之间的通讯可以看《云原生二十篇|Docker网络篇》这篇文章,这里已经详细讲述Docker的通讯原理,其他的部分接下来我们一起探索。
1、Pod到Pod之间的网络
Pod与Pod的网络通讯包括同一个Node下的Pod和不同Node下的Pod:
(1)相同Node下的Pod
bridge
同一个Pod下的通讯其实就是容器和容器之间通讯,其容器通讯如下:
- 通过
Network Namespace, bridge和veth pair
这三个虚拟设备实现一个简单的二层网络,不同的namespace实现了不同容器的网络隔离让他们分别有自己的ip,通过veth pair
连接到docker0
网桥上实现了容器间和宿主机的互通; - 容器与外部或者主机通过端口映射通讯是借助
iptables
,通过路由转发到docker0
,容器通过查询CAM
表,或者UDP
广播获得指定目标地址的MAC地址,最后将数据包通过指定目标地址的连接在docker0
上的veth pair
设备,发送到容器内部的eth0
网卡上;
(2)不同Node下的Pod
- 不同的Pod在k8s中都会有独立的虚拟IP,当然这个IP只能在k8s当前集群内通讯,不会冲突;
- 不同的Pod下的通讯看似容器通讯,其实通讯比Docker要简单一些,不需要端口映射,因为PodIP全局唯一了,所以只需要在每个Node节点上增加
ip route
,这样每个Node节点就像路由器负责找到对应的ip; - k8s的etcd中存储了Pod的IP与Node映射关系,Pod的IP在哪个Node下,Node下有哪些IP,这里会以资源的形式统一管理;
- 从上图看出Pod本身是多个容器组成,这里的通讯方式根据安装的网络插件实现;
2、Service到Pod之间网络
官方架构图
Service是一组pod的服务抽象,相当于一组pod的LB,负责将请求分发给对应的pod,Service会为这个LB提供一个IP,一般称为cluster IP,下面我们来查看一下Service,并查看路由转发规则:
执行如下:
[root@VM-0-11-centos ~]# kubectl get services
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes 10.254.0.1 <none> 443/TCP 14d
ngx-service 10.254.214.193 <nodes> 80:32500/TCP 5m
查看iptable,执行iptables-save:
...
[root@VM-0-11-centos ~]# iptables-save|grep 32500
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/ngx-service:" -m tcp --dport 32500 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/ngx-service:" -m tcp --dport 32500 -j KUBE-SVC-UY6H3HDLGIYXAG5H
[root@VM-0-11-centos ~]# iptables-save|grep KUBE-SVC-UY6H3HDLGIYXAG5H
:KUBE-SVC-UY6H3HDLGIYXAG5H - [0:0]
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/ngx-service:" -m tcp --dport 32500 -j KUBE-SVC-UY6H3HDLGIYXAG5H
-A KUBE-SERVICES -d 10.254.214.193/32 -p tcp -m comment --comment "default/ngx-service: cluster IP" -m tcp --dport 80 -j KUBE-SVC-UY6H3HDLGIYXAG5H
-A KUBE-SVC-UY6H3HDLGIYXAG5H -m comment --comment "default/ngx-service:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-NRDWGG4MSHANWBLB
-A KUBE-SVC-UY6H3HDLGIYXAG5H -m comment --comment "default/ngx-service:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-AV5WRKSNPI6I35GT
-A KUBE-SVC-UY6H3HDLGIYXAG5H -m comment --comment "default/ngx-service:" -j KUBE-SEP-T3MMHN7MMLAMVXYV
[root@VM-0-11-centos ~]# iptables-save|grep KUBE-SEP-AV5WRKSNPI6I35GT
:KUBE-SEP-AV5WRKSNPI6I35GT - [0:0]
-A KUBE-SEP-AV5WRKSNPI6I35GT -s 172.17.0.4/32 -m comment --comment "default/ngx-service:" -j KUBE-MARK-MASQ
-A KUBE-SEP-AV5WRKSNPI6I35GT -p tcp -m comment --comment "default/ngx-service:" -m tcp -j DNAT --to-destination 172.17.0.4:8081
-A KUBE-SVC-UY6H3HDLGIYXAG5H -m comment --comment "default/ngx-service:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-AV5WRKSNPI6I35GT
从上面信息可以看到clusterIP的Port和NodePort都会将32500和80转发(执行iptables-save|grep KUBE-SVC-UY6H3HDLGIYXAG5H
的输出)到KUBE-SVC-UY6H3HDLGIYXAG5H
,然后KUBE-SVC-UY6H3HDLGIYXAG5H
对应-j KUBE-SVC-UY6H3HDLGIYXAG5H
,最后KUBE-SEP-AV5WRKSNPI6I35GT
映射到-A KUBE-SEP-AV5WRKSNPI6I35GT -p tcp -m comment --comment "default/ngx-service:" -m tcp -j DNAT --to-destination 172.17.0.4:8081
,将流量进行导向到后端的pod上。
当有多个Pod服务,Service如何实现负载均衡呢?可以看到上面的第二行命令iptables-save|grep KUBE-SVC-UY6H3HDLGIYXAG5H
输出--mode random --probability
,这是利用了iptables的--probability的特性,一定概率进入某个Pod中。
3、开源的网络组件
以下是一些支持k8s的开源网络组件,这里简单介绍:
(1)Flannel
Flannel
主要功能:
- 为每个Node上的Pod分配相互不冲突的IP地址
- 基于k8s集群内建立一个Overlay Network,通过Overlay Network可以将数据包原封不动的转发给目标Pod
(2)Calico
Calico
是一个纯三层的虚拟网络方案,Calico为
每个容器分配一个IP,每个host都是router,把不同host的容器连接起来,与VxLAN不同的是,Calico
不对数据包做额外封装,不需要NAT和端口映射,扩展性和性能都很好,Calico
依赖etcd
在不同主机间共享和交换信息,存储Calico
网络状态。
(3)Weave
Weave
是Weaveworks开发的容器网络解决方案,Weave
创建的虚拟网络可以将部署在多个主机上的容器连接起来。
- 对容器来说,
Weave
就像一个巨大的以太网交换机,所有容器都被接入这个交换机,容器可以直接通信,无需 NAT 和端口映射 Weave
的DNS
模块使容器可以通过hostname
访问Weave
不依赖分布式数据库(例如etcd
和consul
)交换网络信息,每个主机上只需运行Weave
组件就能建立起跨主机容器网络
参考
(1)https://zhuanlan.zhihu.com/p/476740348
(2)https://zhuanlan.zhihu.com/p/450263411?utm_id=0
(3)https://zhuanlan.zhihu.com/p/637672338