理论篇


K8S 集群的核心组件,包括数据库 etcd调度器 scheduler集群入口 API Server控制器 Controller服务代理 kube-proxy 以及直接管理具体业务容 器的 kubelet。这些组件逻辑上可以被分为三个部分:核心组件 etc 数据库,对 etcd 进行直接操作的入口组件 API Server,以及其他组件。这里的“其他组件”之所以 可以被划分为一类,是因为它们都可以被看做是集群的控制器。

控制器原理

这个冰箱包括五个组件:箱体、制冷系统、照明系统、温控器以及门。冰箱只有 两个功能:当有人打开冰箱门的时候,冰箱内的灯会自动开启;当有人按下温控器的 时候,制冷系统会根据温度设置,调节冰箱内温度。

我们可以简单抽象成两个部分:统一的操作入口和冰箱的所有 组件。在这里,用户只有通过入口,才能操作冰箱。这个入口提供给用户两个接口: 开关门和调节温控器。用户执行这两个接口的时候,入口会分别调整冰箱门和温控器 的状态。

 我们替每个 组件分别实现一个控制器是更为合理的选择。同时我们实现一个控制器管理器来统一 维护所有这些控制器,来保证这些控制器在正常工作。我们把监控冰箱组件状态变化这件事情,交给一个新的模块 SharedInformer 来实现。SharedInformer 作为控制器的代理,替控制器监控冰箱 组件的状态变化,并根据控制器的喜好,把不同组件状态的变化,通知给对应的控制 器。通过优化,这样的 SharedInformer 可以极大的缓解冰箱入口的压力。控制器通过 ListWatcher 给冰箱入口发送一个查询然后等待,当冰箱组件有变化的时候,入口通 过分块的 http 响应通知控制器。


集群网络详解

 

集群网络搭建

初始阶段

集群的创建,基于云资源 VPC 和 ECS,在创建完 VPC 和 ECS 之后,我 们基本上可以得到如下图的资源配置。我们得到一个 VPC,这个 VPC 的网段是 192.168.0.0/16,我们得到若干 ECS,他们从 VPC 网段里分配到 IP 地址。

集群阶段

在以上出初始资源的基础上,我们利用集群创建控制台得到集群 CIDR。这 个值会以参数的形式传给集群节点 provision 脚本,并被脚本传给集群节点配置工 具 kubeadm。kubeadm 最后把这个参数写入集群控制器静态 Pod 的 yaml 文件 kube-controller-manager.yaml。

集群控制器有了这个参数,在节点 kubelet 注册节点到集群的时候,集群控制 器会为每个注册节点,划分一个子网出来,即为每个节点分配 podCIDR。如上图, Node B 的子网是 172.16.8.1/25,而 Node A 的子网是 172.16.0.128/25。这个配 置会记录到集群 node 的 podCIDR 数据项里。

节点阶段 

K8S 有了集群 CIDR,以及为每个节点划分的 podCIDR。 在此基础上,集群会下发 flanneld 到每个阶段上,进一步搭建节点上,可以给 Pod 使用的网络框架。

第一个是集群通过 Cloud Controller Manager 给 VPC 配置路由表项。路由表项对每个节点有一条。每一条的意思是, 如果 VPC 路由收到目的地址是某一个节点 podCIDR 的 IP 地址,那么路由会把这个 网络包转发到对应的 ECS 上。

第二个是创建虚拟网桥 cni0,以及与 cni0 相关的路 由。这些配置的作用是,从阶段外部进来的网络包,如果目的 IP 是 podCIDR,则会 被节点转发到 cni0 虚拟局域网里。从逻辑上来说,cni0 属于节点网 络,不属于 Pod 网络

Pod 阶段 

在前边的三个阶段,集群实际上已经为 Pod 之间搭建了网络通信的干道。这个时候,如果集群把一个 Pod 调度到节点上kubelet 会通过 flannel cni 为这个 Pod 本身创建网络命名空间和 veth 设备,然后,把其中一个 veth 设备加入到 cni0 虚拟 网桥里,并为 Pod 内的 veth 设备配置 ip 地址。这样 Pod 就和网络通信的干道连接 在了一起。这里需要强调的是,前一节的 flanneld 和这一节的 flannel cni 完全是两 个组件。flanneld 是一个 daemonset 下发到每个节点的 pod,它的作用是搭建网络(干道),而 flannel cni 是节点创建的时候,通过 kubernetes-cni 这个 rpm 包安装 的 cni 插件,其被 kubelet 调用,用来为具体的 pod 创建网络(分枝)。

通信

本地通信,说的是 Pod 内部,不同容器之前通信。因为 Pod 内网容器之间 共享一个网络协议栈,所以他们之间的通信,可以通过 loopback 设备完成。

同节点 Pod 之间的通信,是 cni0 虚拟网桥内部的通信,这相当于一个二层局域 网内部设备通信。

跨节点 Pod 通信略微复杂一点,但也很直观,发送端数据包,通过 cni0 网桥的 网关,流转到节点上,然后经过节点 eth0 发送给 VPC 路由。这里不会经过任何封 包操作。当 VPC 路由收到数据包时,它通过查询路由表,确认数据包目的地,并把 数据包发送给对应的 ECS 节点。而进去节点之后,因为 flanneld 在节点上创建了真 的 cni0 的路由,所以数据包会被发送到目的地的 cni0 局域网,再到目的地 Pod。

最后一种情况,Pod 与非 Pod 网络的实体通信,需要经过节点上 iptables 规则 做 snat,而此规则就是 flanneld 依据命令行 --ip-masq 选项做的配置。


集群伸缩原理

节点增加原理

可以给集群增加节点的方式有,添加已有节点,集群扩容,和 自动伸缩。


认证与调度

入口

Kubernetes 作为操作系统,和普通的操作系统一样,有 API 的概念。有了 API,集群就有了入口;有了 API,我们使用集群,才能得其门而入。Kubernetes 的 API 被实现为运行在集群节点上的组件 API Server。这个组件是典型的 web 服务 器程序,通过对外暴露 http(s) 接口来提供服务。

择优而居

调度算法需要解决的问题,是替 pod 选择一个舒适的“居所”,让 pod 所定义的 任务可以在这个节点上顺利地完成。

为了实现“择优而居”的目标,Kubernetes 集群调度算法采用了两步走的策 略:第一步,从所有节点中排除不满足条件的节点,即预选;第二步,给剩余的节点 打分,最后得分高者胜出,即优选

预选

比 较 常 见 的 两 个 预 选 规 则 是 PodFitsResourcesPredPodFitsHostPortsPred。前一个规则用来判断,一个节点上的剩余资源,是不是能够满足 pod 的 需求;而后一个规则,检查一个节点上某一个端口是不是已经被其他 pod 所使用了。

优选

CPU 和内存是调度算法考量的两种主要资源,但考量的方式并不是简单 的,剩余 CPU、内存资源越多,得分就越高。

LeastResourceAllocationBalancedResourceAllocation。前一种方式计算 pod 调度到节点之后,节点剩余 CPU 和内存占 总 CPU 和内存的比例,比例越高得分就越高;第二种方式计算节点上 CPU 和内存 使用比例之差的绝对值,绝对值越大,得分越少


集群服务的三个要点和一种实现

K8S 集群服务的本质是什么

概念上来讲,K8S 集群的服务,其实就是负载均衡、或反向代理

在 K8S 集群中,服务的实现,实际上是为每一个集群节点上,部署了一个反向 代理 Sidecar。而所有对集群服务的访问,都会被节点上的反向代理转换成对服务后 端容器组的访问。

把服务照进现实

K8S 集群的服务,本质上是负载均衡,即反向代理;同时我们知道了,在实际实现中,这个反向代理,并不是部署在集群某一个节点上,而是作为集群节点的边车,部署在每个节点上的

在这里把服务照进反向代理这个现实的,是 K8S 集群的一个控制器,即 kube- proxy。关于 K8S 集群控制器的原理,请参考我另外一篇关于控制器的文章。简单来说,kube-proxy 作为部署在集群节点上的控制器,它们通过集群 API Server 监听 着集群状态变化。当有新的服务被创建的时候,kube-proxy 则会把集群服务的状态、属性,翻译成反向代理的配置。

一种实现 

K8S 集群节点实现服务反向代理的方法,目前主要有三种,即 userspaceiptables 以及 ipvs。今天我们只深入分析 iptables 的方式,底层网络基于阿里云 flannel 集群网络。

过滤器框架

netfilter 实际上就是一个过滤器框架。netfilter 在网络包收发及路由的 管道上,一共切了 5 个口,分别是 PREROUTING,FORWARD,POSTROUT- ING,INPUT 以及 OUTPUT;同时 netfilter 定义了包括 nat、filter 在内的若干个网 络包处理方式。

 

节点网络大图

K8S 集群节点的网络全貌。横向来看节点上的网络环境,被 分割成不同的网络命名空间,包括主机网络命名空间和 Pod 网络命名空间; 纵向来看每个网络命名空间包括完整的网络栈,从应用到协议栈,再到网络设备。

在网络设备这一层,我们通过 cni0 虚拟网桥,组建出系统内部的一个虚拟局域 网Pod 网络通过 veth 对连接到这个虚拟局域网内。cni0 虚拟局域网通过主机路由 以及网口 eth0 与外部通信


镜像拉取这件小事 

OAuth 协议是为了解决上述问题而设计的一种标准方案,我们的讨论针对 2.0 版本。

这个协议其实就做了两件事情:

  • 在用户授权的情况下,三方应用获取 token 所表示的临时访问权限;

  • 然后三方应用使用这个 token 去获取资源。

Docker 扮演的角色

镜像仓库 Registry 的实现,目前使用“把账户密码给三方应用”的方式。即假 设用户对 Docker 足够信任,用户直接将账户密码交给 Docker,然后 Docker 使用 账户密码跟鉴权服务器申请临时 token。

理解 docker login

我们在拉取私有镜像之前,要使用 docker login 命令来登录镜像仓库。 这里的登录其实并没有和镜像仓库建立什么会话之类的关系。登录主要就做了三件事情:

第一件事情是跟用户要账户密码。

第二件事情,docker 访问镜像仓库的 https 地址,并通过挑战 v2 接口来确 认,接口是否会返回 Docker-Distribution-Api-Version 头字段。这件事情在协议图中没有对应的步骤。它的作用跟 ping 差不多,只是确认下 v2 镜像仓库是否在线,以及版本是否匹配。

第三件事情,docker 使用用户提供的账户密码,访问 Www-Authenticate 头字段返回的鉴权服务器的地址 Bearer realm。

如果这个访问成功,则鉴权服务器会返回 jwt 格式的 token 给 docker,然后 docker 会把账户密码编码并保存在用户目录的 .docker/docker.json 文件里。

K8s 实现的私有镜像自动拉取

K8s 集群一般会管理多个节点,每个节点都有自己的 docker 环境。如果让用 户分别到集群节点上登录镜像仓库,这显然是很不方便的。为了解决这个问题,K8s 实现了自动拉取镜像的功能。这个功能的核心,是把 docker.json 内容编码,并以 Secret 的方式作为 Pod 定义的一部分传给 Kubelet。

具体来说,步骤如下:

  1. 创建 secret。这个 secret 的 .dockerconfigjson 数据项包括了一份 base64 编码的 docker.json 文件;

  2. 创建 pod,且 pod 编排中 imagePullSecrets 指向第一步创建的 secret;

  3. Kubelet 作为集群控制器,监控着集群的变化。当它发现新的 pod 被创建,

    就会通过 API Server 获取 pod 的定义,这包括 imagePullSecrets 引用的

    secret;

  4. Kubelet 调用 docker 创建容器且把 .dockerconfigjson 传给 docker;

  5. 最后 docker 使用解码出来的账户密码拉取镜像,这和上一节的方法一致。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐