kubernetes网络 – 集群网络原理分析

vxlan网络 vxlan是一种基于3层网络实现的2层overlay网络。vxlan网络可以使用VNI划分出多个彼此隔离的网络,可应用于多租户场景。 vxlan可以结合bridge一起来使用,相当于使用vxlan将多个主机上的bridge连接起来形成一个大的逻辑上的bridge设备。也可以不使用bridge,而是结合路由使用。下图是结合路由或bridge使用的场景示例: 在kubernetes环境中,可以使用简单的路由方式。内核在vxlan数据包解包后将原始二层数据给到了vxlan设备去接收,vxlan设备将数据包送入到上层网络协议栈。网络协议栈根据主机上的路由表配置,将包发给相应pod的网卡。 在这种路由模式下,节点上不需要知道其他所有pod的mac地址,只需要节点上对应的网关的地址的mac。数据包通过vxlan设备发送和接收的大致流程如下: 数据发送: 数据包从pod1发出,进入peer网卡的接收队列,然后到达node1的协议栈,经过路由判决,走node1的forward链。 forward时,要去的pod2的IP为172.16.2.1,主机路由匹配到应该走vxlan设备,下一跳为172.16.2.0。 数据包到达vxlan设备,查询路由表得知网关地址为172.16.2.0,于是通过arp协议查找网关的mac地址,在arp表中找到了匹配的记录后完成mac头封装,准备发送。 vxlan设备发送数据包并不是提交到发送队列,而是调用udp隧道相关接口将数据包封装成一个udp数据包,接着会查询fdb表查找要将数据从那个VTEP接口发出,经过查找后得到的VTEP接口为192.168.56.2,从而将数据包发送node2节点 。 数据接收: node2接收后,走主机协议栈,判断这是发往本机的udp包,于是走INPUT方向,最终发到UDP隧道层处理。 当创建vxlan创建udp隧道时,会将其接收数据包方法覆盖为vxlan的接收方法,所以在收到vxlan的UDP数据包后,进行对vxlan包解包,之后调用网卡的接收数据包方法进行接收原始二层数据包,此时如果vxlan设备被加入到了bridge设备,那么这个数据包会由bridge设备收到,bridge设备决定进行二层抓发或是送入上层网络协议栈。如果没有加入bridge中,那么如果目的mac地址是自己的也会送入上层网络协议栈。 进入上层网络协议栈后,就会根据主机的路由判断是入站还是转发,发现目标地址是172.16.2.1,经过路由判决时,发现不是本机地址,进行转发,找到相应的路由通过veth-pairhost端发出,从而进入pod。 下面,我们通过一些列命令手动创建和管理vxlan设备,进行测试vxlan相关功能,此处我们采用纯路由方式。 首先在第一台主机上先创建vxlan设备,配置路由: set -ux sysctl -w net.ipv4.ip_forward=1 sysctl -w net.ipv4.conf.all.proxy_arp=1 sysctl -w net.ipv4.conf.all.rp_filter=0 iptables -A FORWARD -d 172.16.0.0/16 -j ACCEPT iptables -A FORWARD -s 172.16.0.0/16 -j ACCEPT ​ ip link add vxlan.1 type vxlan vni 1 ip link set vxlan.1 up ip addr add 172.16.1.0/32 dev vxlan.1 ip route add 172.16.2.0/24 via 172.16.2.0 dev vxlan.1…

kubernetes网络 – kube-proxy详解

kube-proxy是kubernetes中网络核心组件,实现了服务暴露和转发等网络功能。如前面章节讲到,kube-proxy支持用户空间模式,ipvs和iptables三种代理模式。用户空间模式性能问题较严重,基本不再使用,应用最多的是iptables和ipvs模式。如果启动时不指定具体使用哪种模式,当前版本kube-proxy会优先使用iptables模式,如果探测到系统不支持则回滚到用户空间模式。当然,如果指定了ipvs模式,但是系统不支持的话也会回滚到iptables模式。 kube-proxy通过apiserver监听service和endpoint资源的变化,并动态更新网络配置规则。当前iptables应该是应用较多的模式,因此本节主要针对iptables规则进行分析。对于ipvs模式来说也并不是不需要iptables规则,在下列情况的时候还是会依赖iptables的规则实现相关功能:参考这里。在本节对iptables规则进行分析之后,可以按照这种方式再去分析ipvs相关规则配置,在分析ipvs规则时也需要要求我们熟悉ipvs和ipset等。 在kubernetes网络中,还有其他网络插件实现了和kube-proxy相同的功能,若kube-router,cilium,他们都支持kube-proxy实现的功能,因此在使用上述网络插件时,集群里可以不部署kube-proxy。 kube-proxy在同步iptables规则时是通过iptables-save方式获取当前的规则,然后根据集群内的service状态动态对已有规则进行更新,最后使用iptables-save将规则在配置到系统上。kube-proxy重启不会影响到新建链接和已有链接的状态。在删除已有service,kube-proxy还会释放相关的链接,以及对不同协议的额外处理操作,这里不做太多分析。 下面是一个固定nginx的service的来进行分析iptables规则。 [root@master1 ~]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 172.17.0.1 <none> 443/TCP 79d nginx LoadBalancer 172.17.45.122 192.168.64.1,4.3.2.1 80:30935/TCP 3d5h [root@master1 ~]# kubectl edit svc nginx Edit cancelled, no changes made. [root@master1 ~]# kubectl get svc nginx -oyaml apiVersion: v1 kind: Service metadata: creationTimestamp: "2021-10-16T05:55:09Z" labels: app: nginx name: nginx namespace: default resourceVersion: "619828" uid: 58ad25b5-6ee0-4a10-8c60-3a4371db1bbc spec: clusterIP: 172.17.45.122 clusterIPs: - 172.17.45.122…

kubernetes网络 – iptables讲解

iptables命令用来管理ip数据包的过滤。iptables由命令行工具和一系列内核模块组成,iptables命令行工具用来管理规则配置,内核模块负责执行具体的规则。iptables内核模块依托于linux的netfilter网络框架,netfilter框架提供了一系列的网络钩子,ip数据包在经过网络协议栈的时候会触发这些钩子以执行注册到这些钩子上的函数。iptables内核模块会在这些钩子上注册处理函数来实现对ip数据包的过滤等。 netfilter中提供了ip钩子,ipv6钩子,arp钩子和bridge钩子等,都有对应的命令行工具和内核模块,如ip6tables,ebtables等,可以为不同网络层提供过滤功能,关于这些不同的功能,可以参考对应的文档查看。我们主要关心iptables,来看下netfilter中包含的ip钩子: NF_IP_PRE_ROUTING ip数据包达到网络协议栈后最先触发的是这个钩子,在还未执行路由决策之前触发。 NF_IP_LOCAL_IN 在linux对ip数据包执行了路由之后,如果目的地是给本地系统时触发。 NF_IP_FORWARD 在linux对ip数据包执行了路由之后,如果需要转发给其他主机时触发。 NF_IP_LOCAL_OUT 本地数据包将要从本地发出时触发。 NF_IP_POST_ROUTING 在任何本地发出的或本地进行转发的数据包将要在网络设备发发出时触发。 ip数据包在netfilter框架中会经过上面的钩子进行处理,iptables内核模块在这些钩子上注册了不同的处理函数,可以对数据包进行处理。iptables中包含这几种对数据包的处理功能: 包过滤,主要用来实现防火墙功能 包修改,修改数据包中的信息 网络地址转换,实现网络地址转换功能 表、链和目标 iptables中有不同类型的表:默认的filter表,nat表,mangle表raw表,每张表里存放不同类型的规则。 不同的表中还包含不同的链,可以是内置的链或用户自定义的链,内置的链包括:INPUT,PREROUTING,FORWARD,POSTROUTING,OUTPUT,这些是和netfilter中的钩子相对应的,是执行我们规则的入口。 每个链中又包含不同的规则,规则定义如何对数据包进行匹配,以及匹配之后要执行的目标动作,这些动作可以是跳转到用户的自定义链或者是内置的ACCEPT,DROP,RETURN,还可以是扩展模块中的,如REJECT,MARK等。这些目标可以分为终结和非终结型,终结型规则在执行完之后不会再匹配接下来规则,如ACCEPT,DROP,REJECT,MASQUERADE(nat的都是),而非终结类型再执行完当前规则后还会继续匹配后面的规则,如MARK,LOG。链中的规则匹配是按照顺序机型匹配执行的,如果当前规则不匹配则会一直匹配下一条规则,直到终结或匹配结束。 ACCEPT是允许数据包通过。DROP是丢弃数据包。RETURN是从当前链返回到调用该链的上层链中的下一条继续匹配。如果是内置链,则当匹配结束,或有规则使用了RETURN作为目标动作时,则使用这个内置链的策略决定是否允许通过。 关注Network Layer,以及注意图中包的路径,表的先后顺序。 命令行工具 iptables规则表,链和规则的关系为:table <-> chain -> rule。 指定表 -t, --table table 指定要操作的表,如果不指定这个参数则操作默认的filter表,针对所有命令生效。 默认策略 -P, --policy chain target 指定内置表的默认策略,target必须是ACCEPT或DROP。 链操作 -L, --list [chain]列出指定链内的规则,如果chain未指定,则列出该表下的所有规则。可以和-n选项结合使用,来讲ip地址和端口以数字形式显示。可以和--line-numbers结合使用,显示行号。 -S, --list-rules [chain]列出指定链内的规则,如果chain未指定,则列出该表下的所有规则。该命令以定义规则所使用的命令方式展示。 -F, --flush [chain]清空指定链内的规则(链仍然保留),如果chain未指定,则清空该表下的所有规则。 -N, --new-chain chain创建新的链。 -X, --delete-chain [chain]删除用户自定义链。被删除的链不能有其他链引用,被删除的链必须为空,即里面没有规则。如果不指定chain的话将会尝试删除所有的用户自定义链。 -E, --rename-chain old-chain new-chain修改链的名称。 规则操作 -A, --append chain rule-specification添加规则到链中。 -C, --check chain rule-specification测试指定规则是否存在 -D,…

kubernetes基础 – 使用kubeadm部署kubernetes集群

环境信息 ​ 主机名 内网ip 公网ip 负载均衡器 master1 192.168.3.29 x.x.x.x 192.168.3.33:6443 *:6443 master2 192.168.3.30 n/a master3 192.168.3.31 n/a node1 192.168.3.27 n/a n/a node2 192.168.3.32 n/a n/a node3 192.168.3.28 n/a n/a 主机操作系统版本为CentOS 7.9。 配置基础环境 cat /etc/hosts ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ​ 192.168.3.29 master1 192.168.3.30 master2 192.168.3.31 master3 192.168.3.27 node1 192.168.3.32 node2 192.168.3.28 node3 # 生成known_hosts ssh-keyscan -t rsa -f /etc/hosts >> ~/.ssh/known_hosts 配置master1节点到其他节点的免密登录 ssh-keygen yum install -y…

kubernetes网络 – linux网络基础设施

在介绍之前,先来熟悉一下linux给我们提供的一些基础设备。回顾一下之前cni与pod网络中的测试用例。 [root@localhost ~]# ip link add name testbr type bridge [root@localhost ~]# ip link add name veth1 type veth peer name br-veth1 [root@localhost ~]# ip link add name veth2 type veth peer name br-veth2 [root@localhost ~]# ip netns add test1 [root@localhost ~]# ip netns add test2 [root@localhost ~]# ip link set netns test1 dev veth1 [root@localhost ~]# ip link set netns test2 dev veth2 [root@localhost ~]# ip link set master…

kubernetes网络 – cni与pod网络

pod网络 kubernetes中每个pod都拥有自己的ip地址,pod内的所有容器实例都共享同一个网络命名空间,因此容器实例之间可以通过localhost地址进行通信,并且他们都可以看到同样的ip地址和mac地址。 kubernetes对于pod的网络定制了下列三个要求,所有网络插件支持这几个要求即可: 所有节点上所有的pod可以互相通信,并且不依赖NAT 节点上的进程可以和该节点上的所有pod通信 运行在主机网络空间下的pod可以和所有节点上的所有pod互相通信,并且不依赖NAT 网络插件 cni代表容器网络接口,是CNCF社区下的一个项目,定义了一系列的接口格式,来规范容器的网络接口的配置。 kubernetes支持两种网络插件配置模式,分别为 cni kubenet kubenet 其中kubenet是一个非常简单的网络插件,并且只支持linux。本身不提供跨节点的网络通信以及网络策略功能。通常是要和云控制器结合起来去使用的。kubenet模式下,kubelet会创建一个叫做cbr0的网桥设备,并未每个pod创建一个veth-pair(虚拟接口对),一端在容器内,一端在容器外,在容器外的接口加入到这个网桥(二层)中。节点上所有的pod都通过主机上的接口连接到网桥,因此针对单个节点上的pod是可以互相通信的。 可以通过以下示例进行简单测试验证: echo 1 > /proc/sys/net/ipv4/conf/all/accept_local echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter echo 1 > /proc/sys/net/ipv4/conf/default/accept_local echo 0 > /proc/sys/net/ipv4/conf/default/rp_filter echo 0 > /proc/sys/net/bridge/bridge-nf-call-iptables ip link add testbr type bridge ip link set testbr promisc on ip link set testbr up ip netns add test1 ip netns add test2 ip link add veth1 type veth peer br-veth1 ip link add…

client-go代码分析之informer和sharedInformer分析

在茫茫代码中,很容易迷失。经过一段时间的艰苦探索,终于对client-go有了一定的了解,能够从整体上把相关功能串起来。本文主要是informer相关的内容,因为这是client-go比较核心的功能。后面还会发一篇关于client-go编程使用的文章,会从kubernetes.Interface,dynamic,discovery,workequeue和informer工厂函数等方面介绍,再结合一个operator作为实例进行分析(虽然大部分operator都使用kube-builder等类似的工具构建,但是本着求根问底的精神还是要分析一下)。 相关存储结构 在开始介绍之前,先介绍一下client-go中提供存储相关的接口,这些接口用来缓存api对象。Store是最基础的接口,提供增删改查等,还有一些继承于Store的接口,比Store可能会多实现一些方法,可以针对不同的场景进行使用。 Store ExpirationStore UndeltaStore Queue FIFO DeltaFIFO Indexer cache 不同Store的引用 Store接口的具体定义可以看vendor/k8s.io/client-go/tools/cache/store.go这个文件。在IDE的帮助下,找到了所有实现了这个Store的相关实现。本文主要关心client-go,所以主要跟进client-go下面的相关实现。 delta_fifo.go NewDeltaFIFO() NewDeltaFIFOWithOptions() informer和sharedinformer使用到了 vendor/k8s.io/client-go/tools/cache/controller.go:NewInformer() vendor/k8s.io/client-go/tools/cache/shared_informer.go:sharedIndexInformer.Run() expiration_cache.go NewTTLStore() pkg/kubelet/util/cache/object_cache.go:NewObjectCache() NewExpirationStore() pkg/credentialprovider/aws/aws_credentials.go:newECRProvider() pkg/credentialprovider/azure/azure_credentials.go:NewACRProvider() fifo.go 该文件中定义了Queue接口,继承了Store,并且在该文件中通过FIFO实现了这个queue,另外一个实现是在delta_fifo.go中的DeltaFIFO NewFIFO() 代码中未找到该引用 index.go 这个文件定义了Indexer接口,继承了Store,在store.go中通过cache实现了该接口 store.go NewStore() staging/src/k8s.io/client-go/tools/cache/controller.go:NewInformer() staging/src/k8s.io/client-go/tools/cache/undelta_store.go:NewUndeltaStore() NewIndexer() staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache.go:newWatchCache() staging/src/k8s.io/client-go/tools/cache/controller.go:NewIndexerInformer() staging/src/k8s.io/client-go/tools/cache/reflector.go:NewNamespaceKeyedIndexerAndReflector() 该函数在IDE中显示未被使用 staging/src/k8s.io/client-go/tools/cache/shared_informer.go:NewSharedIndexInformer() undelata_store.go NewUndeltaStore() pkg/kubelet/config/apiserver.go:newSourceApiserverFromLW() informer简要介绍 client-go中提供了普通informer和sharedInformer两种informer给我们使用。使用informer可以快速的构建各种资源的控制器,来对k8s进行扩展。informer提供了资源变化时执行回调的功能,可以在新增资源,修改资源和是删除资源时执行相应的控制器逻辑。 使用sharedInformer可以对同一个资源注册多个控制器,每个控制器独立执行自己的业务逻辑,互不干扰。多个控制器之间底层公用一个存储和一个到apiserver的连接,可以减少apiserver的压力的和本地内存的占用。informer则不具备这些能力,对于简单控制器可以使用informer,对于较复杂的控制器,会有针对同一个资源多个控制逻辑的推荐使用sharedInformer。 这两个informer在实现的复杂度上有很大差异,sharedInformer相对于普通informer来说要复杂的多。因此本文先从普通informer开始来进行分析,普通informer还分两种,一种是普通的informer,另外一种indexerinfomer。两种informer的区别在于前者使用Store作为对象存储,而后者采用Indexer作为对象存储,通过索引可以加快搜索。 普通informer分析 接下来我们以普通的informer进行分析,关于informer的使用可以参考这里。代码位于k8s.io/client-go/tools/cache/controller.go文件中,该文件中定义了Controller接口,并通过controller进行了实现。 controller分析 先看下接口定义以及controller包含的关键数据结构: // 这两个函数用来创建informer实例 func NewInformer(lw ListerWatcher, objType runtime.Object, resyncPeriod time.Duration, h ResourceEventHandler,) (Store, Controller) {} func NewIndexerInformer(lw ListerWatcher, objType…

flannel源码简要分析

启动运行流程 启动流程 寻找要使用的主机网卡,该网卡会在创建flannel.1设备时作为dev参数使用,表示flannel.1设备绑定的网卡为该网卡,vxlan的出站和入站数据都会经过这个网卡。 接下来是创建网络管理器,网络管理器用来获取网络配置,本机的pod网络等。 创建后端插件,图中实例的是vxlan后端,类型是Backend,有一个RegisterNetwork方法,在vxlan后端中在这个方法中创建vxlan设备以及设置其ip地址,并返回一个backend.Network结构。 设置iptables的伪装和转发规则,其中的-A POSTROUTING ! -s 10.244.0.0/16 -d 10.244.0.0/16 -j MASQUERADE这条iptables规则导致host到容器网络的访问源地址使用flannel.1的ip。 运行backend.Networ.Run()方法持续不断的监听所有网络变更的事件,根据事件刷新相关的route,arp和fdb表到主机上。 定时对本机的租约进行续租(etcd时才用)。 功能组件 SubnetManager 网络管理器用来管理当前主机和集群主机的网络配置。 接口说明 GetNetworkConfig(ctx context.Context) (*Config, error) 获取当前节点的网络配置信息: type Config struct { Network ip.IP4Net SubnetMin ip.IP4 SubnetMax ip.IP4 SubnetLen uint BackendType string `json:"-"` Backend json.RawMessage `json:",omitempty"` } // 该配置由下列函数生成 func ParseConfig(s string) (*Config, error) { cfg := new(Config) err := json.Unmarshal([]byte(s), cfg) if err != nil { return nil, err } if cfg.SubnetLen…

k8s调研学习方向

网络 flannel/calico 组网和calico的ipam的实现 cilium 全面了解这个网络插件 metallb 全面了解这个网络插件,底层实现 kube-proxy 主要关注iptables规则,ipset和ipvs的使用 存储 csi 关注这个接口的具体定义实现 juicefs 关注源码层面的原理 glusterfs 试用与了解 minio 源码级研究 etcd 源码级研究 核心组件 kube-apiserver 主要关注存储的实现,性能与横向扩展 kubelet 主要关注底层创建pod的完整流程,包括cgroup,存储,cri,网络配置等 kube-scheduler 主要看调度的整体流程,以及基于批的调度实现 扩展组件 client-go 关注list-watch机制,informer原理,apiserver端的实现 operator和controller-runtime库,以及自己实现一个operator,并支持api多版本…

kube-apiserver代码分析 – API多版本初探

kube-apiserver是k8s中最为核心的组件,对外暴露restful接口,实现对集群中各种资源的增删改查操作。kubelet,kube-*组件都通过apiserver获取自己感兴趣的资源做处理,系统中所有的组件都只负责自己的部分,最终会促使各种资源到达期望的状态。 apiserver中的api资源是由组构成的,叫做ApiGroup,如apps,extensions,每个资源组下面又有不同类型的资源,称为Kind,如Deployment。每个分组下还会有不同的版本,在相同分组的不同版本下面相同的Kind的资源可能会有字段的增删等变更。因此apiserver需要能够正确处理不同版本资源之间的兼容性处理。从废弃策略的规则#2中可以了解到,不同版本之间的资源可以互相转换,并且不丢失任何信息,接下来分析apiserver是具体怎么实现的。本文基于k8s的1.8.0版本版本的代码。 首先看下apiserver的启动流程,下面是关键的函数调用链,去掉了不相关代码和条件判断等。 main() cmd/kube-apiserver/apiserver.go:31 NewAPIServerCommand() cmd/kube-apiserver/app/server.go:99 Run() cmd/kube-apiserver/app/server.go:151 CreateServerChain() cmd/kube-apiserver/app/server.go:169 # 返回的config.ExtraConfig中保存了 CreateKubeAPIServerConfig() cmd/kube-apiserver/app/server.go:273 buildGenericConfig() cmd/kube-apiserver/app/server.go:417 # 加载当前版本默认api资源配置 genericConfig.MergedResourceConfig = master.DefaultAPIResourceConfigSource() cmd/kube-apiserver/app/server.go:431 # 将参数配置中的--runtime-config应用到默认api资源配置 s.APIEnablement.ApplyTo(genericConfig, master.DefaultAPIResourceConfigSource(), legacyscheme.Scheme) cmd/kube-apiserver/app/server.go:449 # 设置存储端后端对应的api操作接口 storageFactoryConfig.APIResourceConfig = genericConfig.MergedResourceConfig # 将apiserver和storage的配置返回了,就是说apiserver和storage都是使用的用户自定义的配置对默认配置进行覆盖后的配置 # 返回的 genericConfig.MergedResourceConfig保存了api启用配置,类型为*storage.ResourceConfig # 返回的storageFactory.APIResourceConfig也保存了api启用配置,类型也为*storage.ResourceConfig CreateKubeAPIServer() cmd/kube-apiserver/app/server.go:191 kubeAPIServerConfig.Complete().New() cmd/kube-apiserver/app/server.go:219 # 启用legacy api(/api/v1) m.InstallLegacyAPI() pkg/master/master.go:405 # 启用api(/apis/{groupn}) m.InstallAPIs() m.GenericAPIServer.InstallAPIGroups() pkg/master/master.go:553 `` 其中m.InstallLegacyAPI()是安装旧版接口,及/api/v1接口路径下的接口。我们主要看m.InstallAPIS这部分,这部分安装的接口的都在/apis/{group}路径下。 现在深入到InstallAPIGroups()这个函数中以及看后面的详细调用链。 // Exposes given api groups in the API. func (s *GenericAPIServer) InstallAPIGroups(apiGroupInfos…