礼记《学者有四失》里说“人之学也,或失则多”,这是给我提醒每篇推文最好只聊一个概念。前一篇文章着重介绍了一下Cilium的各种炫酷的花式玩法,今天我们来看一个最最基本的功能:IPAM(IP Address Management)。
IPAM的概念很简单,就是IP地址管理。DHCP就是IPAM常用的一种工具。可为什么我们要在这里单独聊它呢?因为概念虽然简单,但在容器网络这个场景里,有它特殊的实现方式和业务挑战。
图 1:Cluster内,Pod间通信示意图
在开始聊IPAM之前,我们先来看看图1。这是一张示意图,它画出了一个cluster内部,Pod内容器间通信,Pod间(跨主机)通信的典型场景。这张图里面还画出了一些额外的信息:
我们可以看到除了Pod内容器间通信是可以直接用localhost之外,不同的Pod之间通信是需要用到pod IP的。那随之而来的问题是:pod IP地址是谁给分配的?又是在什么时候安排上的?如果Pod没了,这个IP地址被谁收走了?
你肯定觉得在隐秘的角落,应该有一个类似DHCP的东西在控制着这一切,但好像K8s里面又没有提到DHCP这个事情。其实答案很简单:CNI插件。
Cilium用到了一个叫K8s CRD(Custom Resouce Definition)的技术。所谓CRD,就是有一些功能K8s没有提供,但是呢K8s通过插件的方式外包给第三方。CRD是K8s生态中核心扩展机制(另外一个核心扩展机制是Custom API Server)。
图 2:CRD在K8s apiserver中的位置示意图(图片取自书籍《Programming Kubernetes》)
如图2所示,在kube-apiserver中有一个模块叫apiextensions-apiserver来单独服务CRD。它的位置仅靠着K8s native-resource。
CRD是K8s的一个resource,用来描述对Custom Resouce(CR)的定义,而后者则是基于该CRD而创建的资源。来一句绕口令吧:CRD是CR的定义,CR是CRD的实例。
下面的一小段示例用来向K8s注册CR definition,好让K8s知道有一个第三方定义的resouce存在。
apiVersion: "apiextensions.k8s.io/v1beta1"
kind: CustomResourceDefinition
metadata:
name: ciliumnodes.cilium.io
spec:
group: cilium.io
names:
kind: CiliumNode
shortNames:
- cn
- ciliumn
...
而下面这段另外的示例用来基于前一步注册的CRD来创建CR。这段示例仅展示了部分内容。K8s收到这样的请求会创建一个数据结构,填充内容并存放到etcd中,数据的结构从CRD中得到,如spec.group、spec.names、spec.ipam等等,而数据内容其实就是由下面这段声明式yaml来填充。每一份这样的数据被叫作CR实例(CR instance)。
apiVersion: "cilium.io/v2"
kind: CiliumNode
metadata:
name: "cilium-2"
spec:
ipam:
pool:
10.0.1.78: {}
...
上面这两段示例也说明了使用CRD的标准方式:先注册CRD,再创建CR。
CR和K8s自带的resource如Pod,Namespace,Deployment一样,也是一种resource,只不过它是由第三方自定义的,用于提供和K8s native-resource一样的使用体验,如果不注意,你甚至都不会在意这个resource是第三方提供的。为啥能有这么自然的使用体验呢?因为CRD也是被K8s apiserver一起无差别处理的,CR和native-resource存放的位置都一样,都是存放在etcd中。
但和K8s自带的resource位于Core group不同,CR一般位于第三方自己的Group(Group-Version-Resource中的Group)内。具体到Cilium,它定义了若干个CR,其中一个叫CiliumNodes
,位于Group cilium.io
,Version v2
。
你可以通过如下命令获取CiliumNodes这个CRD的详细内容,如果你的环境恰好使用了Cilium的话。
kubectl get crds ciliumnodes.cilium.io -o yaml
CRD的背后有一个叫K8s Controller的服务以Pod方式在K8s环境里运行,以响应K8s外包过来的各种请求。
图 3:Customer Controller内部结构图
如图3所示,对于一个recource,一旦API server端有针对它的实例创建、删除或者更新的操作,Informer都会收到"事件通知"。
于此同时Controller内部会运行有一个Control Loop,这个loop的作用很明显,就是消化掉与此resource相关的各个通知事件。
注意区分resource相关的增加、更新、删除相关的event和top-level Event对象的区别。前者是事件通知机制,而后者则和Pod一样也是一种resource,可以将其看成是一个反映系统运行状态的日志(Log)系统。
聊完Controller,我们来看看Cilium是如何利用Controller来完成IP地址管理的。
图 4:Cilium IPAM示意图
图4的右方出现了一个cilium-operator
的图示。Operator作为K8s里的一个概念,于2016年被CoreDNS提出。它是一个Controller,它的出现大大简化了“有状态应用”的部署复杂度,我们大致了解到这个地方即可,后续二哥会单独开一篇聊聊Operator。
整个K8s cluster只运行有一份cilium-operator Pod,而cilium-agent则在每个Node上都运行一份。
当cilium-operator发现有一个新的CiliumNodes
资源被创建后,它会从它的供货商那里获取一批与此Node相关的IP地址块,Node的如hostname等信息则通过读取CR资源实例得到。
前面我们提到每个Node上都会运行有cilium-agent,它会从这个新建CR的spec.ipam.available
这里拿到一个IP地址。这个过程中cilium-operator就像是一个公益超市,批发进货,散卖加回收,还没有赚差价。
你也可以执行下面的命令,看看与每个Node相关的IPAM信息。cilium-agent通过client-go即可非常方便地拿到和你看到的一模一样的数据。二哥为了你能更快速地得到更直观的了解,贴心地截了一张图(下图5)放在这里。你可以看到这个CR的实例大概长什么样子。
# 此处的cn是CiliumNodes的缩写,在CRD的spec.shortNames中定义。
kubectl get cn -o yaml
图 5:kubectl get cn cilium-2 -o yaml 输出截图
图4仅仅是一个示意图,实际上Controller的IP供货商有很多种,除了图中所示的AWS ENI(Elastic Network Interface)之外,还有Azure,GKE等等可供选择。从不同的供货商那里获取IP地址块的方式也不尽相同。
如果你的cluster环境是完全自己管理的,我们知道K8s自身就是可以通过kubeadmin来设置CIDR的,像下面的命令一样,你也可以按照自己的喜好随意设置。
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
图 6:Cilium IPAM时序图
说了这么多,我觉得还是有点抽象,所以我把图6放上来了。因为图4主要涉及到的是AWS ENI,所以我在图6中将涉及到AWS ENI的部分用红框标识出来了。
图6这张时序图初看起来比较复杂,一眼望去不知道重点是什么。因为它是对图4的细节放大,所以包含了很多的信息。放大到什么程度呢?它把从容器创建到IP分配这个过程中所有的参与者和各自参与时机都画出来了。
另外从这张图里面,我们也可以看到在容器创建过程中,kubelet,CRI,还有CNI plugin之间是如何分工的。可以很明显地看到CNI插件其实做的事情非常简单,大部分情况下,它只是个摆设,真正的活还是得靠它背后勤劳的小蜜蜂们来完成,比如这里的cilium-agent。