MetricsServer、Prometheus、CNI
参考:Kubernetes 入门实战课 | 极客时间 (opens new window) 第 30-31 讲
# 1. 系统监控:如何使用Metrics Server和Prometheus?
如果我们想把集群管理好,还缺少一个很重要的方面——集群的可观测性。我们希望集群的整体运行状况对我们透明可见,从而更方便地做好集群的运维工作。
但是观测集群是不能用“探针”这种简单的方式的,所以今天我就带你一起来看看 Kubernetes 为集群提供的两种系统级别的监控项目:Metrics Server 和 Prometheus,以及基于它们的水平自动伸缩对象 HorizontalPodAutoscaler。
# 1.1 Metrics Server
在 Linux 中可以通过 top 命令来查看当前系统的 CPU 和内存利用率,它是性能分析和调优的基本工具。Kubernetes 也提供了类似的命令:kubectl top,不过默认情况下这个命令不会生效,必须要安装一个插件 Metrics Server 才可以。
Metrics Server 是一个专门用来收集 Kubernetes 核心资源指标(metrics)的工具,它定时从所有节点的 kubelet 里采集信息,但是对集群的整体性能影响极小,每个节点只大约会占用 1m 的 CPU 和 2MB 的内存,所以性价比非常高。
下图展示了 Metrics Server 的工作方式:它调用kubelet的API拿到节点和Pod的指标,再把这些信息交给apiserver,这样kubectl、HPA就可以利用apiserver来读取指标了:
Metrics Server 的官网 https://github.com/kubernetes-sigs/metrics-server (opens new window) 给出了说明文档和安装步骤。可以查阅相关资料进行安装。
Metrics Server 属于名字空间“kube-system”,可以用 kubectl get pod -n kube-system
命令来查看它是否正常运行:
现在有了Metrics Server插件,我们就可以使用命令 kubectl top
来查看Kubernetes集群当前的资源状态了。它有两个子命令:
kubectl top node
查看节点的资源使用率kubectl top pod -n kube-system
查看Pod的资源使用率
从这个截图里你可以看到:
- 集群里两个节点CPU使用率都不高,分别是8%和4%,但内存用的很多,master节点用了差不多一半(48%),而worker节点几乎用满了(89%)。
- 名字空间“kube-system”里有很多Pod,其中apiserver最消耗资源,使用了75m的CPU和363MB的内存。
# 1.2 HorizontalPodAutoscaler
有了 Metrics Server,我们就可以轻松地查看集群的资源使用状况了,不过它另外一个更重要的功能是辅助实现应用的“水平自动伸缩”。
我们之前说过 kubectl scale
可以手工调整实例数量,但人工很难准确把握时机,难以及时应对生产环境中突发的大流量,所以最好能把这个“扩容”“缩容”也变成自动化的操作。
Kubernetes 为此就定义了一个新的API 对象,叫做 HorizontalPodAutoscaler,简称是 hpa。顾名思义,它是专门用来自动伸缩 Pod 数量的对象,适用于 Deployment 和 StatefulSet,但不能用于 DaemonSet(原因很明显吧)。
HorizontalPodAutoscaler 的能力完全基于 Metrics Server,它从 Metrics Server 获取当前应用的运行指标,主要是 CPU 使用率,再依据预定的策略增加或者减少 Pod 的数量。
下面我们就来看看该怎么使用 HorizontalPodAutoscaler,首先要定义 Deployment 和 Service,创建一个 Nginx 应用,作为自动伸缩的目标对象:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ngx-hpa-dep
spec:
replicas: 1
selector:
matchLabels:
app: ngx-hpa-dep
template:
metadata:
labels:
app: ngx-hpa-dep
spec:
containers:
- image: nginx:alpine
name: nginx
ports:
- containerPort: 80
resources:
requests:
cpu: 50m
memory: 10Mi
limits:
cpu: 100m
memory: 20Mi
---
apiVersion: v1
kind: Service
metadata:
name: ngx-hpa-svc
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: ngx-hpa-dep
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
在这个YAML里我只部署了一个Nginx实例,名字是 ngx-hpa-dep
。注意在它的 spec 里一定要用 resources
字段写清楚资源配额,否则 HorizontalPodAutoscaler 会无法获取 Pod 的指标,也就无法实现自动化扩缩容。
一个 HorizontalPodAutoscaler 的 YAML 描述文件是这样:
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: ngx-hpa
spec:
maxReplicas: 10
minReplicas: 2
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ngx-hpa-dep
targetCPUUtilizationPercentage: 5
2
3
4
5
6
7
8
9
10
11
12
13
里面主要有三个参数值:
- minReplicas:Pod 数量的最小值,也就是缩容的下限。
- maxReplicas:Pod 数量的最大值,也就是扩容的上限。
- targetCPUUtilizationPercentage:CPU 使用率指标,当大于这个值时扩容,小于这个值时缩容。
我们再使用命令 kubectl apply
创建这个HorizontalPodAutoscaler后,它会发现Deployment里的实例只有1个,不符合min定义的下限的要求,就先扩容到2个:
从这张截图里你可以看到,HorizontalPodAutoscaler会根据YAML里的描述,找到要管理的Deployment,把Pod数量调整成2个,再通过Metrics Server不断地监测Pod的CPU使用率。
下面我们来给Nginx加上压力流量,运行一个测试Pod,使用的镜像是“httpd:alpine”,它里面有HTTP性能测试工具 ab(Apache Bench):
kubectl run test -it --image=httpd:alpine -- sh
然后我们用命令 ab -c 10 -t 60 -n 1000000 'http://ngx-hpa-svc/'
向Nginx发送一百万个请求,持续1分钟,再用 kubectl get hpa
来观察HorizontalPodAutoscaler的运行状况:
因为Metrics Server大约每15秒采集一次数据,所以HorizontalPodAutoscaler的自动化扩容和缩容也是按照这个时间点来逐步处理的。当它发现目标的CPU使用率超过了预定的5%后,就会以2的倍数开始扩容,一直到数量上限,然后持续监控一段时间,如果CPU使用率回落,就会再缩容到最小值。
# 1.3 Prometheus
Metrics Server 能够获取的指标还是太少了(只有CPU和内存),想要监控到更多更全面的应用运行状况,还得请出这方面的权威项目:Prometheus。
其实,Prometheus的历史比Kubernetes还要早一些,它最初是由Google的离职员工在2012年创建的开源项目,灵感来源于Borg配套的BorgMon监控系统。后来在2016年,Prometheus作为第二个项目加入了CNCF,并在2018年继Kubernetes之后顺利毕业,成为了CNCF的不折不扣的“二当家”,也是云原生监控领域的“事实标准”。
Prometheus 本身也是一个庞大的系统,这里只做一个简略的介绍。下图是 Prometheus 的官方架构图:
Prometheus 系统的核心是它的 Server,里面有一个时序数据库 TSDB,用来存储监控数据,另一个组件 Retrieval 使用拉取(Pull)的方式从各个目标收集数据,再通过 HTTP Server 把这些数据交给外界使用。
在Prometheus Server之外还有三个重要的组件:
- Push Gateway,用来适配一些特殊的监控目标,把默认的Pull模式转变为Push模式。
- Alert Manager,告警中心,预先设定规则,发现问题时就通过邮件等方式告警。
- Grafana 是图形化界面,可以定制大量直观的监控仪表盘。
由于同属于CNCF,所以Prometheus自然就是“云原生”,在Kubernetes里运行是顺理成章的事情。不过它包含的组件实在是太多,部署起来有点麻烦,这里我选用了“ kube-prometheus”项目(https://github.com/prometheus-operator/kube-prometheus/ (opens new window)),感觉操作起来比较容易些。
这里安装部署的过程不再讲解,可参考相关资料。
Prometheus 的对象都在 Namespace monitoring 里,创建之后可以用 kubectl get pod -n monitoring
来查看状态:
确定这些 Pod 都运行正常,我们再用 kubectl get svc -n monitoring
来看看它对外的服务端口:
这两个服务就在节点上开了端口,Grafana是“30358”,Prometheus有两个端口,其中“9090”对应的“30827”是Web端口。
在浏览器里输入节点的IP地址,再加上端口号 30827,就可以看到 Prometheus 自带的 Web 界面:
Web界面上有一个查询框,可以使用PromQL来查询指标,生成可视化图表,比如在这个截图里我就选择了“node_memory_Active_bytes”这个指标,意思是当前正在使用的内存容量。
Prometheus的Web界面比较简单,通常只用来调试、测试,不适合实际监控。我们再来看Grafana,访问节点的端口“30358”,它会要求你先登录,默认的用户名和密码都是“admin”:
Grafana内部已经预置了很多强大易用的仪表盘,你可以在左侧菜单栏的“Dashboards - Browse”里任意挑选一个:
比如我选择了“Kubernetes / Compute Resources / Namespace (Pods)”这个仪表盘,就会出来一个非常漂亮图表,比Metrics Server的 kubectl top
命令要好看得多,各种数据一目了然:
如果对 Prometheus 感兴趣,可以参考相关学习资料。
# 1.4 小结
在云原生时代,系统的透明性和可观测性是非常重要的。今天我们一起学习了Kubernetes里的两个系统监控项目:命令行方式的Metrics Server、图形化界面的Prometheus,利用好它们就可以让我们随时掌握Kubernetes集群的运行状态,做到“明察秋毫”。
简单小结一下:
- Metrics Server是一个Kubernetes插件,能够收集系统的核心资源指标,相关的命令是
kubectl top
。 - Prometheus是云原生监控领域的“事实标准”,用PromQL语言来查询数据,配合Grafana可以展示直观的图形界面,方便监控。
- HorizontalPodAutoscaler实现了应用的自动水平伸缩功能,它从Metrics Server获取应用的运行指标,再实时调整Pod数量,可以很好地应对突发流量。
# 2. 网络通信:CNI 是怎么回事?又是怎么工作的?
现在我们已经知道 Kubernetes 是一个集群操作系统,能够管理大量计算节点和运行在里面的应用。不过,还有一个很重要的基础知识我们还没有学习,那就是“网络通信”。
我们在部署的时候使用过 Flannel 网络插件,今天我们就讲讲 k8s 的网络接口标准 CNI,以及 Calico、Cilium 等性能更好的网络插件。
# 2.1 Kubernetes 的网络模型
我们先回顾一下 docker 的网络知识。下图展示了 docker 最常用的 bridge 网络模式:
Docker会创建一个名字叫“docker0”的网桥,默认是私有网段“172.17.0.0/16”。每个容器都会创建一个虚拟网卡对(veth pair),两个虚拟网卡分别“插”在容器和网桥上,这样容器之间就可以互联互通了。
Docker的网络方案简单有效,但问题是它只局限在单机环境里工作,跨主机通信非常困难(需要做端口映射和网络地址转换)。
针对Docker的网络缺陷,Kubernetes提出了一个自己的网络模型“IP-per-pod”,能够很好地适应集群系统的网络需求,它有下面的这 4 点基本假设:
- 集群里的每个 Pod 都会有唯一的一个 IP 地址。
- Pod 里的所有容器共享这个IP地址。
- 集群里的所有 Pod 都属于同一个网段。
- Pod 直接可以基于 IP 地址直接访问另一个 Pod,不需要做麻烦的网络地址转换(NAT)。
下图是一个 Kubernetes 的网络模型示意图:
这个网络让 Pod 摆脱了主机的硬限制,是一个“平坦”的网络模型,很好理解,通信自然也非常简单。因为 Pod 都具有独立的IP地址,相当于一台虚拟机,而且直连互通,也就可以很容易地实施域名解析、负载均衡、服务发现等工作,以前的运维经验都能够直接使用,对应用的管理和迁移都非常友好。
# 2.2 什么是 CNI
Kubernetes定义的这个网络模型很完美,但要把这个模型落地实现就不那么容易了。所以Kubernetes就专门制定了一个标准:CNI(Container Networking Interface)。
CNI 为网络插件定义了一系列通用接口,开发者只要遵循这个规范就可以接入 Kubernetes,为 Pod 创建虚拟网卡、分配IP地址、设置路由规则,最后就能够实现“IP-per-pod”网络模型。
依据实现技术的不同,CNI 插件可以大致上分成“Overlay”“Route”和“Underlay”三种:
- Overlay 的原意是“覆盖”,是指它构建了一个工作在真实底层网络之上的“逻辑网络”,把原始的Pod网络数据封包,再通过下层网络发送出去,到了目的地再拆包。因为这个特点,它对底层网络的要求低,适应性强,缺点就是有额外的传输成本,性能较低。
- Route 也是在底层网络之上工作,但它没有封包和拆包,而是使用系统内置的路由功能来实现Pod跨主机通信。它的好处是性能高,不过对底层网络的依赖性比较强,如果底层不支持就没办法工作了。
- Underlay 就是直接用底层网络来实现CNI,也就是说Pod和宿主机都在一个网络里,Pod和宿主机是平等的。它对底层的硬件和网络的依赖性是最强的,因而不够灵活,但性能最高。
自从2015年CNI发布以来,由于它的接口定义宽松,有很大的自由发挥空间,所以社区里就涌现出了非常多的网络插件,我们之前部署 Kubernetes 是所使用的Flannel 就是其中之一。
Flannel(https://github.com/flannel-io/flannel/ (opens new window))由CoreOS公司(已被Redhat收购)开发,最早是一种Overlay模式的网络插件,使用UDP和VXLAN技术,后来又用Host-Gateway技术支持了Route模式。Flannel简单易用,是Kubernetes里最流行的CNI插件,但它在性能方面表现不是太好,所以一般不建议在生产环境里使用。
现在还有两个常用CNI插件:Calico、Cilium,我们做个简略的介绍。
Calico( https://github.com/projectcalico/calico (opens new window))是一种Route模式的网络插件,使用BGP协议(Border Gateway Protocol)来维护路由信息,性能要比Flannel好,而且支持多种网络策略,具备数据加密、安全隔离、流量整形等功能。
Cilium( https://github.com/cilium/cilium (opens new window))是一个比较新的网络插件,同时支持Overlay模式和Route模式,它的特点是深度使用了Linux eBPF技术,在内核层次操作网络数据,所以性能很高,可以灵活实现各种功能。在2021年它加入了CNCF,成为了孵化项目,是非常有前途的CNI插件。
# 2.3 CNI 插件是怎么工作的
Flannel 比较简单,我们先以它为例看看 CNI 在 Kubernetes 里的工作方式。
这里必须要说明一点,计算机网络很复杂,有IP地址、MAC地址、网段、网卡、网桥、路由等许许多多的概念,而且数据会流经多个设备,理清楚脉络比较麻烦,今天我们会做一个大概的描述,不会讲那些太底层的细节。
我们先来在实验环境里用 Deployment 创建 3 个 Nginx Pod,作为研究对象:
kubectl create deploy ngx-dep --image=nginx:alpine --replicas=3
使用命令 kubectl get pod
可以看到,有两个Pod运行在master节点上,IP地址分别是“10.10.0.3”“10.10.0.4”,另一个Pod运行在worker节点上,IP地址是“10.10.1.77”:
Flannel默认使用的是基于VXLAN的Overlay模式,整个集群的网络结构我画了一张示意图,你可以对比一下Docker的网络结构:
从单机的角度来看的话,Flannel的网络结构和Docker几乎是一模一样的,只不过网桥换成了“cni0”,而不是“docker0”。
接下来我们来操作一下,看看Pod里的虚拟网卡是如何接入cni0网桥的。
在Pod里执行命令 ip addr
就可以看到它里面的虚拟网卡“eth0”:
你需要注意它的形式,第一个数字“3”是序号,意思是第3号设备,“@if45”就是它另一端连接的虚拟网卡,序号是45。
因为这个Pod的宿主机是master,我们就要登录到master节点,看看这个节点上的网络情况,同样还是用命令 ip addr
:
这里就可以看到宿主机(master)节点上的第45号设备了,它的名字是 veth41586979@if3
,“veth”表示它是一个虚拟网卡,而后面的“@if3”就是Pod里对应的3号设备,也就是“eth0”网卡了。
那么“cni0”网桥的信息该怎么查看呢?这需要在宿主机(master)上使用命令 brctl show
:
从这张截图里,你可以发现“cni0”网桥上有4个虚拟网卡,第三个就是“veth41586979”,所以这个网卡就被“插”在了“cni0”网桥上,然后因为虚拟网卡的“结对”特性,Pod也就连上了“cni0”网桥。
单纯用Linux命令不太容易看清楚网卡和网桥的联系,所以我把它们整合在了下面的图里,加上了虚线标记,这样你就能更清晰地理解Pod、veth和cni0的引用关系了:
使用同样的方式,你可以知道另一个Pod “10.10.0.4”的网卡是 veth2b3ef56d@if3
,它也在“cni0”网桥上,所以借助这个网桥,本机的Pod就可以直接通信。
弄清楚了本机网络,我们再来看跨主机的网络,它的关键是节点的路由表,用命令 route
查看:
它告诉我们有这些信息:
- 10.10.0.0/24网段的数据,都要走cni0设备,也就是“cni0”网桥。
- 10.10.1.0/24网段的数据,都要走flannel.1设备,也就是Flannel。
- 192.168.10.0/24网段的数据,都要走ens160设备,也就是我们宿主机的网卡。
假设我们要从master节点的“10.10.0.3”访问worker节点的“10.10.1.77”,因为master节点的“cni0”网桥管理的只是“10.10.0.0/24”这个网段,所以按照路由表,凡是“10.10.1.0/24”都要让flannel.1来处理,这样就进入了Flannel插件的工作流程。
然后Flannel就要来决定应该如何把数据发到另一个节点,在各种表里去查询。因为这个过程比较枯燥,我就不详细说了,你可以参考下面的示意图,用到的命令有 ip neighbor
、 bridge fdb
等等:
Flannel得到的结果就是要把数据发到“192.168.10.220”,也就是worker节点,所以它就会在原始网络包前面加上这些额外的信息,封装成VXLAN报文,用“ens160”网卡发出去,worker节点收到后再拆包,执行类似的反向处理,就可以把数据交给真正的目标Pod了。
# 2.4 使用 Calico 网络插件
看到这里,是不是感觉 Flannel 的 Overlay 处理流程非常复杂,接下来让我们看一下另一个 Route 模式的插件:Calico。
可以在 Calico 官网 https://www.tigera.io/project-calico/ (opens new window) 找到安装方式,这里不再介绍安装流程。
记得安装之前最好先把 Flannel 删掉。
安装之后我们来查看一下Calico的运行状态,注意它也是在“kube-system”名字空间:
我们仍然创建3个Nginx Pod来做实验:
kubectl create deploy ngx-dep --image=nginx:alpine --replicas=3
我们会看到master节点上有两个Pod,worker节点上有一个Pod,但它们的IP地址与刚才Flannel的明显不一样了,分别是“10.10.219.*”和“10.10.171.*”,这说明Calico的IP地址分配策略和Flannel是不同的:
然后我们来看看Pod里的网卡情况,你会发现虽然还是有虚拟网卡,但宿主机上的网卡名字变成了 calica17a7ab6ab@if4
,而且并没有连接到“cni0”网桥上:
这是不是很奇怪?
其实这是Calico的工作模式导致的正常现象。因为Calico不是Overlay模式,而是Route模式,所以它就没有用Flannel那一套,而是在宿主机上创建路由规则,让数据包不经过网桥直接“跳”到目标网卡去。
来看一下节点上的路由表就能明白:
假设Pod A“10.10.219.67”要访问Pod B“10.10.219.68”,那么查路由表,知道要走“cali051dd144e34”这个设备,而它恰好就在Pod B里,所以数据就会直接进Pod B的网卡,省去了网桥的中间步骤。
Calico的网络架构我也画了一张示意图,你可以再对比Flannel来学习:
至于在Calico里跨主机通信是如何路由的,你完全可以对照着路由表,一步步地“跳”到目标Pod去(提示:tunl0设备)。
# 2.5 小结
你可以看到,Kubernetes 的整个网络数据传输过程有大量的细节,非常多的环节都参与其中,想把它彻底弄明白还真不是件容易的事情。
不过好在CNI通过“依赖倒置”的原则把这些工作都交给插件去解决了,不管下层是什么样的环境,不管插件是怎么实现的,我们在Kubernetes集群里只会有一个干净、整洁的网络空间。
我来简单小结一下今天的内容:
- Kubernetes使用的是“IP-per-pod”网络模型,每个Pod都会有唯一的IP地址,所以简单易管理。
- CNI是Kubernetes定义的网络插件接口标准,按照实现方式可以分成“Overlay”“Route”和“Underlay”三种,常见的CNI插件有Flannel、Calico和Cilium。
- Flannel支持Overlay模式,它使用了cni0网桥和flannel.1设备,本机通信直接走cni0,跨主机通信会把原始数据包封装成VXLAN包再走宿主机网卡发送,有性能损失。
- Calico支持Route模式,它不使用cni0网桥,而是创建路由规则,把数据包直接发送到目标网卡,所以性能高。
课外小贴士:
- IP 地址网段通常用“ 网络码’来表示,也就是“/”后面的数字 (比如“/16”“/24”),因为 IPV4 是 32 位所以前面的位数就是网络号,后面的位数就是主机号网络号不同就不处于一个网络段。
- CNI 相关的可执行文件都存放在节点的 “/opt/cni/bin” 目录里,Flannel 的子网配置文件是 ”run/flannel/subnet.env”。
- eBPF (extended Berkeley Packet Filter) 是 在Linux 内核里运行的小程序,能够动态地扩展内核功能被广泛用于网络、安全、分析、监控等领域。
- 如果你有网络抓包的经验,可以尝试使用 tcp-dump/tshark,在 veth、cnio、flannel.1 等设备上抓包能够更清楚地看出网络数据的流向。