当前位置: 首页 > article >正文

kube-proxy的iptables工作模式分析

系列文章目录

iptables基础知识


文章目录

  • 系列文章目录
  • 前言
  • 一、kube-proxy介绍
    • 1、kube-proxy三种工作模式
    • 2、iptables中k8s相关的链
  • 二、kube-proxy的iptables模式剖析
    • 1.集群内部通过clusterIP访问到pod的流程
      • 1.1.流程分析
    • 2.从外部访问内部service clusterIP后端pod的流程
      • 2.1. 流程分析
    • 3、外部通过nodeport访问后端pod的流程
      • 3.1.当externalTrafficPolicy默认为cluster配置时且从本机访问后端pod流程
      • 3.2.当externalTrafficPolicy为Local配置时且从本机访问后端pod流程
  • 三、iptables与ipvs区别


前言

在一个k8s集群中,微服务以 Pod形式 运行在我们的集群中,这些 Pod 的副本通过Service 服务暴露,当一个外部请求到达 Service 的虚拟 IP 时,如何将请求转发到其中一个底层 Pod?这便是本篇文章的核心思想。


一、kube-proxy介绍

在这里插入图片描述

kube-proxy是k8s网络代理核心组件,部署在每个Node节点上,主要维护节点上的网络规则。它是实现service的通信与负载均衡机制的重要组件
kube-proxy负责为pod创建代理服务,通过watch机制会根据 service 和 endpoints,node资源对象的改变来实时刷新iptables或者ipvs规则
使发往 Service 的流量(通过ClusterIP和NodePort)负载均衡到正确的后端Pod。

1、kube-proxy三种工作模式

userspace(目前已经弃用)
	请求是从用户态-->内核态-->用户态,转发是在用户态进行的,效率不高且容易丢包
iptables(默认工作模式)
	相比于userspace免去了一次内核态-->用户态的切换
	1、kube-proxy通过Api-server的watch接口实时监测service和endpoint对象的变化,当有service创建时,kube-proxy在iptables中追加新的规则
    2、在该模式下,kube-proxy为service后端的每个pod创建对应的iptables规则,直接将发向cluster ip的请求重定向到一个pod ip
    3、在该模式下,kube-proxy不承担四层代理的角色,只负责创建iptables规则
    补充:
		在iptables模式下,会根据service以及endpoints对象的改变来实时刷新规则,kube-proxy使用了iptables的filter表和nat表,
		并对 iptables 的链进行了扩充,自定义了 KUBE-SERVICES、KUBE-EXTERNAL-SERVICES、KUBE-NODEPORTS、KUBE-POSTROUTING、
			KUBE-MARK-MASQ、KUBE-MARK-DROP、KUBE-FORWARD 七条链。
		另外还新增了以“KUBE-SVC-xxx”和“KUBE-SEP-xxx”开头的数个链,除了创建自定义的链以外还将自定义链插入到已有链的后面以便劫持数据包。
ipvs
	1、kube-proxy ipvs模式是基于NAT实现的,对访问k8s service的请求进行虚拟ip到pod ip的转发
	工作原理:
		当创建一个svc后,ipvs模式的kube-proxy会做以下三件事:
			1、确保kube-ipvs0网卡的存在。因为ipvs的netfilter钩子挂载input链.需要把svc的访问IP绑定在该网卡上让内核觉得虚IP就是本机IP,从而进入input链。
			2、把svc的访问ip绑定在该网卡上
			3、通过socket调用,创建ipvs的虚拟服务和真实服务,分别对应svc和endpoints
	注意事项:
		ipvs用于流量转发,无法处理kube-proxy中的其他问题,例如把包过滤、SNAT等。因此在以下四种情况下kube-proxy依赖iptables:
			1、kube-proxy配置启动参数masquerade-all=true,即集群中所有经过kube-proxy的包都做一次SNAT;
			2、kube-proxy启动参数指定集群IP地址范围;
			3、支持loadbalance类型的服务
			4、支持nodeport类型的服务

2、iptables中k8s相关的链

名称含义
KUBE-SERVICES是服务数据包的入口点。它的作用是匹配 目标 IP:port 并将数据包分派到对应的 KUBE-SVC-* 链
KUBE-SVC-*充当负载均衡器,将数据包分发到 KUBE-SEP-* 链。KUBE-SEP-* 的数量等于后面的端点数量 服务。选择哪个 KUBE-SEP-* 是随机决定的
KUBE-SEP-*表示 Service EndPoint。它只是执行 DNAT,将 服务 IP:端口,以及 Pod 的端点 IP:端口
KUBE-MARK-MASQ将 Netfilter 标记添加到发往该服务的数据包中,该标记 源自集群网络之外。带有此标记的数据包将被更改 在 POSTROUTING 规则中使用源网络地址转换 (SNAT) 和 node 的 IP 地址作为其源 IP 地址
KUBE-MARK-DROP为没有目的地的数据包添加 Netfilter 标记 此时已启用 NAT。这些数据包将在 KUBE-FIREWALL 中被丢弃 链
KUBE-FW-*chain 在 service 部署时执行 LoadBalancer 类型,则 将目标 IP 与服务的负载均衡器 IP 匹配,并分配 数据包到对应的 KUBE-SVC-* 链或 KUBE-XLB-* 链的 KEY-XLB-* 链
KUBE-NODEPORTS发生在服务部署在 NodePort 和 LoadBalancer 的 LoadPort 共享 LoadPort 和 LoadBalancer 的有了它,外部源可以访问该服务 按 Node Port 的 NODE 端口。它匹配节点端口并分发 数据包到对应的 KUBE-SVC-* 链或 KUBE-XLB-* 链 的 KEY-XLB-* 链 的
KUBE-XLB-*在 externalTrafficPolicy 设置为 Local 时工作。有了这个 链式编程,如果节点没有相关端点,则数据包将被丢弃 保留

二、kube-proxy的iptables模式剖析

首先请查看顶部文章链接iptables基本知识,对出入栈、四表五链有个认知,然后再看下文
包含整改service模式的流程图
iptable工作流程图
只包含cluster IP、NodePort模式的流程图
在这里插入图片描述
流程图结合iptables示例图
在这里插入图片描述

结合上述流程图,主要分两部分对kube-proxy的iptables模式进行剖析

1.集群内部通过clusterIP访问到pod的流程

资源准备,如下图所示
在这里插入图片描述
需求: 当在集群pod1访问curl http://10.102.172.63:8088时,请求是怎么到达后端pod2的?

1.1.流程分析

确定kube-proxy的工作模式两种方式

[root@master ~]# curl http://127.0.0.1:10249/proxyMode
iptables

[root@master ~]# kubectl get configmaps -n kube-system kube-proxy -oyaml |grep mode
    mode: "" #默认为空就是iptables模式

a、进入到pod1,执行curl请求

[root@master ~]# kubectl exec -it -n xx xx-xx-6c57d9ddc6-2495v bash
bash-4.2$ curl http://10.102.172.63:8088

xshell多开一个窗口,登录到pod1宿主机,查看iptables规则
b、因为从上述iptables流程图可知,当请求从集群内部发出时,先进入的是NAT表中的OUTPUT链---->到达KUBE-SERVICE自定义链

那么在pod1宿主机中查看output链规则如下所示
[root@xmhl-std12 ~]# iptables -t nat -S |grep -i output
-P OUTPUT ACCEPT
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES   #此链就是入口链
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER

c、因为是clusterIP模式,因此在KUBE-SERVICES链查找到ClusterIP-10.102.172.63,然后根据结果将其转发到KUBE-SVC-*链

判断是否发生IP伪装(masquerade all)
从下面可以看到当数据包来自内部 Pod 时,即源 IP 仍然是pod ip。因此未发生转换,则直接到kube-svc-*链
bash-4.2$ curl -s 10.102.172.63:8088 | grep client
bash-4.2$ client_address=10.244.193.194
 
#查看kube-svc-*链是否存在  
[root@master ~]# iptables -nv -t nat -L KUBE-SERVICES |grep 'zz-trace'
    0     0 KUBE-SVC-LWBZJHXSVINAPFYY  tcp  --  *      *       0.0.0.0/0            10.102.172.63        /* xos/zz-trace-analyze:web cluster IP */ tcp dpt:8088

d、访问 KUBE-SVC*链 使用随机数负载均衡,将请求转发到kube-sep-*链中

#注意事项
	假如这个KUBE-SVC-LWBZJHXSVINAPFYY链里面定义了3条规则
	第一条规则有0.33333333349的概率匹配,也就是1/3的概率命中,
	第一条没命中的话第二条规则有1/2的概率命中,也就是2/3 * 1/2 = 1/3,
	第二条没命中的话就去第3条了。
	很明显,这里是在做负载均衡,那我们可以确认这3条规则后面的target就是这个service代理的3个pod相关的规则了

[root@master ~]# iptables -nv -t nat -L KUBE-SVC-LWBZJHXSVINAPFYY
Chain KUBE-SVC-LWBZJHXSVINAPFYY (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 KUBE-SEP-SU7QRQC2MJZZVHTR  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* xos/zz-trace-analyze:web */

e、每个 KUBE-SEP-* 链分别代表一个 Pod 或终端节点

重点
它包括两个操作:
	1) 从 Pod 转义的数据包是使用主机的 docker0 IP 进行源 NAT 处理的。 
	2) 进入 Pod 的数据包使用 Pod 的 IP 进行 DNAT 处理,然后路由到后端 Pod

查询如下所示: 从结果可以清晰的看到 pod1请求pod2的svc后,进入到pod2的数据包使用 Pod 的 IP 进行了 DNAT 处理,直接到达了pod2的目标pod

[root@master ~]# iptables -nv -t nat -L KUBE-SEP-SU7QRQC2MJZZVHTR
Chain KUBE-SEP-SU7QRQC2MJZZVHTR (1 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 KUBE-MARK-MASQ  all  --  *      *       10.244.8.182         0.0.0.0/0            /* xos/zz-trace-analyze:web */
    0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* xityos/zz-trace-analyze:web */ tcp to:10.244.8.182:18080

f、之后nat表的OUTPUT链中的规则结束,进入filter的OUTPUT链

[root@master ~]# iptables -t filter -S |grep OUTPUT
-P OUTPUT ACCEPT
-A OUTPUT -m conntrack --ctstate NEW -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -j KUBE-FIREWALL  #即nat表的OUTPUT链完成后进入到filter表中的OUTPUT链

g、可以看到所有新建的连接(ctstate NEW)都会匹配第一条规则KUBE-SERVICES,而当查看filter表中的KUBE-SERVICES链时,会发现这是一条空链。所以重点看第二条规则KUBE-FIREWALL

[root@master ~]# iptables -nv -t filter -L KUBE-FIREWALL
Chain KUBE-FIREWALL (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes firewall for dropping marked packets */ mark match 0x8000/0x8000
    0     0 DROP       all  --  *      *      !127.0.0.0/8          127.0.0.0/8          /* block incoming localnet connections */ ! ctstate RELATED,ESTABLISHED,DNAT

可以看到,所有被标记了0x8000/0x8000的数据包都会被直接DROP掉,而我们的数据包一路走过来没有被标记,所以不会被DROP。
这样一来filter的OUTPUT规则也走完了,终于进入了下一个阶段 -- POSTROUTRING链

h、POSTROUTING链涉及到2个表:mangle和nat,mangle表一般不使用,所以只需要关注nat表的POSTROUTING规则

[root@master ~]# iptables -nv -t nat -L |grep POSTROUTING
Chain POSTROUTING (policy ACCEPT 26679 packets, 2287K bytes)
  11M  982M KUBE-POSTROUTING  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes postrouting rules */
Chain KUBE-POSTROUTING (1 references)

i、先进入target,KUBE-POSTROUTING

[root@master ~]# iptables -nv -t nat -L KUBE-POSTROUTING
Chain KUBE-POSTROUTING (1 references)
 pkts bytes target     prot opt in     out     source               destination         
27635 2364K RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0            mark match ! 0x4000/0x4000
   47  2820 MARK       all  --  *      *       0.0.0.0/0            0.0.0.0/0            MARK xor 0x4000
   47  2820 MASQUERADE  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service traffic requiring SNAT */

此时会发现这条规则会把所有标记了0x4000/0x4000的数据包全部MASQUERADE SNAT,由于我们的数据包没有被标记过,所以不匹配这条规则。
当数据包匹配到MASQUERADE的话,当前表的剩余规则将不再匹配。

至此,集群内部通过clusterIP访问到pod的链路流程剖析完成,整理一下整个过程

数据包经历的链路:
	数据包 --> nat的OUTPUT
			 --> nat的KUBE-SERVICES 
			 	--> nat的KUBE-SVC-LWBZJHXSVINAPFYY
					--> nat的KUBE-SEP-SU7QRQC2MJZZVHTR(多选1,随机负载均衡,被DNAT)
						--> filter的OUTPUT 
							--> filter的KUBE-SERVICES 
								-->filter的KUBE-FIREWALL 
									--> nat的POSTROUTING 
										--> nat的KUBE-POSTROUTING 
											--> 没有被NAT
语言描述:
	0、当集群内部通过clusterIP访问到pod时,会发生以下过程
	1、对于进入 NAT表的OUTPUT 链的都转到 KUBE-SERVICES 链进行处理
	2、在 KUBE-SERVICES 链,对于访问 clusterIP 为 10.102.172.63 的转发到 KUBE-SVC-LWBZJHXSVINAPFYY
	3、访问 KUBE-SVC-LWBZJHXSVINAPFYY 的使用随机数负载均衡,并转发到 KUBE-SEP-SU7QRQC2MJZZVHTR上
	4、KUBE-SEP-SU7QRQC2MJZZVHTR对应 endpoint 中的 pod 10.244.8.182,设置 mark 标记,进行 DNAT 并转发到具体的 pod 上
	5、当nat表中的output链完成后,会进入filter表中OUTPUT链中的KUBE-FIREWALL自定义链,可以看到,
		所有被标记了0x8000/0x8000的数据包都会被直接DROP掉,反之则不会被drop
	6、最后进入到NAT表中的POSTROUTING链,此时会发现这条规则会把所有标记了0x4000/0x4000的数据包全部MASQUERADE SNAT,
		由于我们的数据包没有被标记过,所以不匹配这条规则。当数据包匹配到MASQUERADE的话,当前表的剩余规则将不再匹配
	7、至此,剖析结束

2.从外部访问内部service clusterIP后端pod的流程

2.1. 流程分析

需求: 从外部机器中访问http://10.102.172.63:8088 这个clusterip svc的后端pod
a、因为从上述iptables流程图可知,当请求从外部发出时,先进入的是NAT表中的PREROUTING链---->到达KUBE-SERVICE自定义链

[root@master ~]# iptables -t nat -S |grep PREROUTING
-P PREROUTING ACCEPT
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER

b、因为是clusterIP模式,因此在KUBE-SERVICES链查找到ClusterIP-10.102.172.63,然后根据结果将其转发到KUBE-SVC-*链

判断是否发生IP伪装(masquerade all)
从下面我们可以看到,当从外部(而不是从 pod)访问服务时,源 IP 替换为 nodeIP;
cactus@master01:~$ curl --interface 外部地址  -s 10.102.172.63:8088 | grep client
client_address=10.244.1.0 
[root@master ~]# conntrack -L -d 10.102.172.63
tcp      6 56 TIME_WAIT src=10.8.21.231 dst=10.102.172.63 sport=56688 dport=8088 src=10.244.8.182 dst=10.244.1.0 sport=18080 dport=50534 [ASSURED] mark=0 use=1

#查看kube-svc-*链是否存在
[root@xmhl-std24 ~]# iptables -nvL  -t nat |egrep -i 'kube-mark-masq'  |grep zz-trace
    0     0 KUBE-MARK-MASQ  all  --  *      *       10.244.8.182         0.0.0.0/0            /* cityos/zz-trace-analyze:web */
    
[root@master ~]# iptables -nv -t nat -L KUBE-SERVICES |grep 'zz-trace'
    0     0 KUBE-SVC-LWBZJHXSVINAPFYY  tcp  --  *      *       0.0.0.0/0            10.102.172.63        /* xos/zz-trace-analyze:web cluster IP */ tcp dpt:8088

接下来的分析步骤和集群内部通过clusterIP访问到pod的流程中c-i过程一致,在此不做过多描述
至此,集群内部通过clusterIP访问到pod的链路流程剖析完成,整理一下整个过程

数据包经历的链路:
	数据包 --> nat的PREROUTING
			 --> nat的KUBE-SERVICES 
			 	--> nat的KUBE-SVC-LWBZJHXSVINAPFYY
					--> nat的KUBE-SEP-SU7QRQC2MJZZVHTR(多选1,随机负载均衡,被DNAT)
						--> filter的OUTPUT 
							--> filter的KUBE-SERVICES 
								-->filter的KUBE-FIREWALL 
									--> nat的POSTROUTING 
										--> nat的KUBE-POSTROUTING 
											--> 没有被NAT
语言描述:
	0、当集群内部通过clusterIP访问到pod时,会发生以下过程
	1、对于进入 NAT表的PREROUTING 链的都转到 KUBE-SERVICES 链进行处理
	2、在 KUBE-SERVICES 链,对于访问 clusterIP 为 10.102.172.63 的转发到 KUBE-SVC-LWBZJHXSVINAPFYY
	3、访问 KUBE-SVC-LWBZJHXSVINAPFYY 的使用随机数负载均衡,并转发到 KUBE-SEP-SU7QRQC2MJZZVHTR上
	4、KUBE-SEP-SU7QRQC2MJZZVHTR对应 endpoint 中的 pod 10.244.8.182,设置 mark 标记,进行 DNAT 并转发到具体的 pod 上
	5、当nat表中的output链完成后,会进入filter表中OUTPUT链中的KUBE-FIREWALL自定义链,可以看到,
		所有被标记了0x8000/0x8000的数据包都会被直接DROP掉,反之则不会被drop
	6、最后进入到NAT表中的POSTROUTING链,此时会发现这条规则会把所有标记了0x4000/0x4000的数据包全部MASQUERADE SNAT,
		由于我们的数据包没有被标记过,所以不匹配这条规则。当数据包匹配到MASQUERADE的话,当前表的剩余规则将不再匹配
	7、至此,剖析结束

3、外部通过nodeport访问后端pod的流程

将根据上述图1分析两种类型的 NodePort 服务:
	1、默认服务 (externalTrafficPolicy: Cluster)
	2、externalTrafficPolicy:local

在这里插入图片描述

3.1.当externalTrafficPolicy默认为cluster配置时且从本机访问后端pod流程

a、从本机访问首先进入NAT表的OUTPUT链,然后进入到kube-nodeports链
所有访问端口 64438的数据包,首先要进行 SNAT 处理,然后才能进行kube-svc-*链进行负载均衡

[root@master ~]# iptables -t nat -S |grep OUTPUT
-P OUTPUT ACCEPT
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER

[root@master ~]# iptables -nv -t nat -L KUBE-NODEPORTS |grep 'xx-flow'
    0     0 KUBE-MARK-MASQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* xos/xx-flow:debug-port */ tcp dpt:64438
    0     0 KUBE-SVC-UFDICDG36KRBVVWD  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* xos/xx-flow:debug-port */ tcp dpt:64438

b、可以看到数据包先会命中第一条规则KUBE-MARK-MASQ,然后是第二条规则到达

[root@master ~]# iptables -t nat -L KUBE-MARK-MASQ
Chain KUBE-MARK-MASQ (165 references)
target     prot opt source               destination         
MARK       all  --  anywhere             anywhere             MARK or 0x4000

在这里数据包会被标记上0x4000标记,然后回到KUBE-NODEPORTS链中继续匹配下一条规则,下一条规则就是KUBE-SVC-UFDICDG36KRBVVWD

[root@master ~]# iptables -t nat -L KUBE-SVC-BCUGWLTE6RKBKCZT
Chain KUBE-SVC-BCUGWLTE6RKBKCZT (2 references)
target     prot opt source               destination         
KUBE-SEP-2SQRA2EPJ5ESOFTW  all  --  anywhere             anywhere             /* xos/xx-flow:web */ statistic mode random probability 0.50000000000
KUBE-SEP-QUKANZ2W6FTD6P23  all  --  anywhere             anywhere             /* xos/xx-flow:web */

c、接下来一路到nat的POSTROUTING为止都与ClusterIP模式相同,但在接下来的nat的KUBE-POSTROUTING阶段,由于我们的数据包在KUBE-MARK-MASQ被打上了0x4000标记,在这里会命中这条规则,从而被MASQUERADE(SNAT)

[root@xmhl-std11 ~]# iptables -nv -t nat -L KUBE-POSTROUTING
Chain KUBE-POSTROUTING (1 references)
 pkts bytes target     prot opt in     out     source               destination         
41065 3455K RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0            mark match ! 0x4000/0x4000
   22  1320 MARK       all  --  *      *       0.0.0.0/0            0.0.0.0/0            MARK xor 0x4000
   22  1320 MASQUERADE  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service traffic requiring SNAT */

至此,当externalTrafficPolicy默认为cluster配置时且从本机访问后端pod流程剖析完成

数据包经历的链路:
数据包 --> nat的OUTPUT 
		--> nat的KUBE-SERVICES 
			--> nat的KUBE-NODEPORTS 
				--> nat的KUBE-MARK-MASQ (被标记) 
					--> nat的KUBE-SVC-GKN7Y2BSGW4NJTYL
						--> nat的KUBE-SEP-ISPQE3VESBAFO225(3选1,被DNAT)
							--> filter的OUTPUT 
								--> filter的KUBE-SERVICES 
									-->filter的KUBE-FIREWALL 
										--> nat的POSTROUTING 
											--> nat的KUBE-POSTROUTING 
												--> 被SNAT

3.2.当externalTrafficPolicy为Local配置时且从本机访问后端pod流程

三、iptables与ipvs区别

在这里插入图片描述


http://www.kler.cn/a/419803.html

相关文章:

  • Qt—QLineEdit 使用总结
  • 【WEB开发.js】HTTP请求和相应报文的头字段:Content-Type (巨巨巨巨详细好懂的举例详解)
  • 5G CPE介绍(一)
  • 宏海科技募资额有所缩减,最大销售和采购都重度依赖美的集团
  • 5.1 MySQL 锁机制
  • Qt,如何从零开始入门!
  • 如何使用Python进行下载对应的视频地址
  • Python学习第十五天--魔术方法
  • Kong API Gateway 深度解析与实战指南
  • 【Linux内核】ashmem pin/unpin
  • Python毕业设计选题:基于django+vue的校园影院售票系统
  • CasaOS个人云存储系统使用Gopeed打造你的私人云端下载中心
  • Spring Boot自定义启动banner
  • 基于深度学习的甲状腺结节影像自动化诊断系统(PyQt5界面+数据集+训练代码)
  • 在 Ubuntu 使用 fonts-noto-cjk 设置 Matplotlib 支持中文的完整教程
  • Makefile 入门指南:构建自动化编译流程
  • java 反射 详解
  • Ubuntu 20.04 下 ROS 工作空间的详解与应用
  • rustdesk远程桌面使用
  • Milvus Cloud 2.5:易用性飞跃,助力用户高效管理向量数据库
  • 一款支持80+语言,包括:拉丁文、中文、阿拉伯文、梵文等开源OCR库
  • 【k8s深入学习之 event 记录】初步了解 k8s event 记录机制
  • 【ROS2】Ubuntu22.04安装ROS humble
  • 网络诊断指南:网络故障排查步骤与技巧
  • iOS——MVC、MVP、MVVM
  • leetcode——二分法