kubernetes网路 – 服务与DNS

kubernetes会为每个service对象创建dns记录,这个操作是由dns插件完成的,基本上所有集群都会使用coredns作为dns插件。 kubernetes中的服务是区分命名空间的,nginx这个域名在默认情况下是使用本pod所在namespace解析的,如pod所在的namespace为default,则默认解析为nginx.default。下列是一个示例pod的resolv文件配置 [root@master1 ~]# kubectl exec -it nginx-deployment-66b6c48dd5-95c7f -- cat /etc/resolv.confnameserver 172.17.0.10search default.svc.cluster.local svc.cluster.local cluster.localoptions ndots:5 服务对象分为普通服务和无头服务,无头服务是指spec.clusterIP=None的,其他服务则为普通服务。 如果service是一个普通服务对象,则会为其创建一个dns A记录(ipv6则为AAAA记录),该记录的ip地址是在kube-controller-manager的--service-cluster-ip-range参数指定的网段内,该网段和pod网段以及节点网段都不在一个网段内。 如果service是一个无头服务,则会创建一些列dns A记录,记录的ip地址是pod的ip。 k8s还会为服务对象的每个命名端口创建srv记录。如_my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster-domain.example。对于普通服务,解析到my-svc.my-namespace.svc.cluster-domain.exampl加端口号。对于无头服务会为每个pod创建一个类似的记录。auto-generated-name.my-svc.my-namespace.svc.cluster-domain.example加端口号。 通常情况下,会为所有pod创建如下类型的dns解析: pod-ip-address.my-namespace.pod.cluster-domain.example -> pod-ip kubectl run aaaa --image=nginx [root@master1 ~]# nslookup 10-248-5-2.default.pod.cluster.local 172.17.0.10 Server: 172.17.0.10 Address: 172.17.0.10#53 ​ Name: 10-248-5-2.default.pod.cluster.local Address: 10.248.5.2 通常情况下,会为deployment和daemonset的pod,且通过service进行暴露的话,则会创建下列dns解析: pod-ip-address.deployment.my-namespace.svc.cluster.local -> pod-ip 针对deployment进行测试 kubectl create deployment nginx --image=nginx kubectl expose deployment nginx --port 80 [root@master3 ~]# nslookup nginx.default.svc.cluster.local 172.17.0.10 Server: 172.17.0.10 Address: 172.17.0.10#53 ​…

kubernetes网络 – 服务概念

kubernetes中服务主要用来进行服务发现和负载均衡。 在集群中我们的工作负载是不稳定的,ip地址可能随着pod重启,升级和扩容等不断发生变化,为了能够让其他工作负载可以稳定访问到这个工作负载,我们需要为其创建一个服务对象。 服务对象也是一个api资源对象,和其他api对象定义方法相同,下面是一个服务对象的定义: apiVersion: v1 kind: Service metadata: name: nginx spec: selector: # 根据标签选择pod app: nginx # type: ClusterIP # clusterIP: 192.168.3.251 ports: # TCP, UDP, SCTP - protocol: TCP appProtocol: http name: nginx-http port: 80 targetPort: 80 # 支持多个端口配置 - name: https protocol: TCP port: 443 targetPort: 9377 externalTrafficPolicy: Cluster 这个服务对象会创建一个service资源,服务控制器会根据我们的selector选择对应的pod,创建endpoint后endpointslice资源关联到这个服务。服务控制器还会为我们的服务自动创建一个虚拟ip地址,其他工作负载使用这个虚拟ip地址访问这个服务。endpoint资源代表后端真正的服务实例,它关联到pod,访问到虚拟ip的流量会转到对应的后端pod对应的端口上。 通过selector可以让kunbernetes根据标签自动选择pod并创建endpoint关联到service。有些场景我们也可以不使用selector,例如: 引用已有服务的ip列表 apiVersion: v1 kind: Service metadata: name: my-service spec: ports: - protocol: TCP port: 443 targetPort: 6443…

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…

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

在上一篇API多版本初探中已经描述了API资源是如何注册到路由当中,并大概介绍了api请求如何根据优先版本存储到etcd中。本篇文章主要介绍apiserver如何将etcd中的数据按照按照用户指定的版本返回。在探究同一个api group下不同版本资源兼容问题之外,还会探究不同api group下的不同资源是如何做到兼容的。本文基于kubernetes-1.19.11版本。 本文关注的重点内容有: apiserver如何读取解析etcd中的数据 apiserver怎么返回用户指定的版本资源 不同api group怎么做到兼容,如netwoking.k8s.io/v1beta1下的Ingress资源和extensions/v1beta1下的Ingress资源 在阅读源码过程中在protobuf处理关键位置加入了打印堆栈日志,先找出整个函数调用链,再根据链路分析整个执行流程会更容易的多,具体修改和打印的栈信息可在后面的附录中查看。 首先看pkg/master/import_known_versions.go这个文件,该文件中有许多xxx/install的import。 import ( // These imports are the API groups the API server will support. _ "k8s.io/kubernetes/pkg/apis/admission/install" _ "k8s.io/kubernetes/pkg/apis/admissionregistration/install" _ "k8s.io/kubernetes/pkg/apis/apps/install" ... 来看下这个文件的作用。从文件的名称来看就知道是导入所有的所有已知的api版本,在install包的init函数中会将所有已定义的资源加载到runtime schema中,主要包含下面两类: 在pkg/apis/${group}/types.go中定义的类型 在k8s.io/api/${group}/${version}/types.go中定义的 第1类资源代表了内部版本的表示方法,是在etcd中读取的数据(或api请求数据)反序列化之后的表示类型。第2类资源为不同的group下特定version的类型,在写入etcd的(或api响应的)数据是根据特定group/version下的类型进行序列化的。 在install中有一个SetVersionPriority()方法,用来设置写入etcd是优先使用的版本。当然第2类资源在注册的时候还会加上一些设置默认值和转换到内部版本或从内部版本转换的方法来实现内部版本和外部版本之间的互相转换。这些方法的定义在pkg/apis/${group}/${version}下的default.go,conversion.go,zz_generated.xxx.go中定义的。 接下来以Ingress资源来说明转换的读取,转换,返回响应的流程,首先通过加日志查看当前runtime schema中可以看到有三个Ingress类型的定义。分别对应的是extensions/v1beta1,networking.k8s.io/v1beta1和内部版本,可以看到内部版本有两个,分别是extensions/__internal和networking.k8s.io/__internal,但是其他俩对应的实际类型都是pkg/apis/networking/types.go中定义的Ingress类型。 再来回顾一下加载api资源注册路由部分,有一个InstallAPIs()方法,该参数中的networkingrest.RESTStorageProvider{}来自pkg/registry/networking/rest/storage_settings.go中定义的storage接口,这个storage接口设置了ingresses资源对应ingressStorage。 func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) { storage := map[string]rest.Storage{} // ingresses ingressStorage, ingressStatusStorage, err := ingressstore.NewREST(restOptionsGetter) if err != nil { return storage, err } storage["ingresses"]…