如上图, K8s 目前有四种网络访问方式:
network namespace 是实现网络虚拟化的重要功能,它能创建多个隔离的网络空间,它们有独自的网络栈信息。不管是虚拟机还是容器,运行的时候仿佛自己就在独立的网络中。对于每个 network namespace 来说,它会有自己独立的网卡、路由表、arp表、iptables 等和网络相关的资源。相关的主要命令如下:
123 | ip netns add net1ip netns showip netns exec net1 ip addr |
---|
为了实现不同 network namespace 之间的网络通信,linux提供了 veth pair,可以将 veth pair 当做一对虚拟网卡,这对虚拟网卡连通着不同的网命名络空间(Docker 与宿主机之间也是这种模式)
如下, 创建两个网络空间, 通过 veth pair 联通:
12345678910111213141516171819202122 | // 创建2个网络命名空间sudo ip netns add net1sudo ip netns add net2// 创建veth pairsudo ip link add name veth1 type veth peer name veth2// 将veth pair的2个虚拟接口挪到新创建的2个网络命名空间中sudo ip link set veth1 netns net1sudo ip link set veth2 netns net2// 启动虚拟接口,并给veth pair配置ip地址,注意这里2个虚拟接口的ip必须是同网段的sudo ip netns exec net1 ip link set veth1 upsudo ip netns exec net2 ip link set veth2 upsudo ip netns exec net1 ip addr add 20.1.1.1/24 dev veth1sudo ip netns exec net2 ip addr add 20.1.1.2/24 dev veth2// veth pair接口配置的网络测试结果sudo ip netns exec net1 ping -c 3 20.1.1.2PING 20.1.1.2 (20.1.1.2) 56(84) bytes of data.64 bytes from 20.1.1.2: icmp_seq=1 ttl=64 time=0.033 ms64 bytes from 20.1.1.2: icmp_seq=2 ttl=64 time=0.028 ms |
---|
不同 namespace之间,两两之间直接通过veth pair相连,这种方式的缺点就是:当namespace很多时,veth pair数量呈指数增长。
通过linux bridge,相当于用一个交换机中转两个 namespace 的流量。这种方式就可以将veth pair的数量控制在n,n为网络命名空间的数量, 如:
在同一个Node上: 虚拟网桥与各个Pod间建立veth pair。各个Pod的IP是由虚拟网桥分配的,这些Pod都连在同一个网桥上,在同一个网段内,它们可以进行IP寻址和互通。
整体流程如下:
Docker/containerd
启动后, 创建默认的网桥 cbr0(custom bridge), 以连接当前 Node 上管理的所有容器Pod Sandbox
初始化基础网络时,调用 CNI bridge 插件创建 veth-pair(两张虚拟网卡),一张默认命名 eth0 (如果 hostNetwork = false,则后续调用 CNI ipam 插件分配 IP)。另一张放在 Host 的 Root Network Namespace
内,然后连接到 cbr0。当 Pod 在同一个 Node 内通信访问的时候,直接通过 cbr0 即可完成网桥转发通信。跨 Node 的 Pod 通信, 流程如下:
Service 共有四种网络类型, 用来应对内外的网络访问:
ClusterIP 表示在 K8s 集群内部通过 service.spec.clusterIP 进行访问,之后经过 kube-proxy 负载均衡到目标 Pod
当没有指定 service.type
的时候, 默认的 service 类型就是 ClusterIP
当业务需要从 K8s 集群外访问内部服务时,通过 NodePort 方式可以先将访问流量转发到对应的 Node IP,然后再通过 service.spec.ports[].nodePort
端口,通过 kube-proxy
负载均衡到目标 Pod:
12345678910111213 | apiVersion: v1kind: Servicemetadata: name: nodeport-servicespec: selector: app: nginx ports: - nodePort: 30800 port: 8080 protocol: TCP targetPort: 80 type: NodePort |
---|
上述 yaml 中各个 port 的区别:
NodePort/LoadBalancer
类型的 Service 在 Node 节点上动态(默认)或指定创建的端口,负责将节点上的流量转发到容器ClusterIP:port
进行集群内的流量转发。NodePort 本质上是靠 kube-proxy 监听集群每个节点上的静态端口(一般是 30000-32767), 然后将流量根据 service 的配置转发到对应 pod, 这种模式比较简单提供了集群外访问 pod 的能力
上面介绍的 NodePort 模式能够暴露集群外访问的能力, 不过有如下缺点:
LoadBalancer 模式可以解决以上问题, 创建了 LoadBalancer
的 Service 后, 会调用云厂商的能力创建一个 LB 与之关联。 相当于复用了云厂商 LB 的负载均衡能力:
12345678910111213141516171819 | apiVersion: v1kind: Servicemetadata: name: my-servicespec: selector: app.kubernetes.io/name: MyApp ports: - protocol: TCP nodePort: 30931 port: 80 targetPort: 9376 clusterIP: 10.0.171.239 type: LoadBalancerstatus: loadBalancer: -- 这里用的是 ingress 的实现 ingress: - ip: 10.1.163.12 |
---|
当业务需要从 K8s 内部访问外部服务的时候,可以通过 ExternalName 的方式实现:
12345678 | apiVersion: v1kind: Servicemetadata: name: my-service namespace: prodspec: type: ExternalName externalName: api.example.com |
---|
service.spec.externalName
字段值会被解析为 DNS 对应的 CNAME 记录,之后就可以访问到外部对应的服务了
在 K8s 中访问 Service 一般通过域名方式如 svc.namespace.svc.cluster.local
,从域名结构可以看出对应 Service 的 namespace + name,通过这样的域名方式可以大大简化集群内部 Service-Pod 之间的访问配置,并且域名屏蔽了后端 Pod IP 的变更。
该能力最早是由 kube-dns 组件实现的, 在 1.12 版本以后就由 coreDNS 方案来实现
CoreDNS 通过以 Pod 独立部署在集群中, 当 Kubernetes 集群中的应用程序或服务需要解析另一个服务或资源的名称时,它们会发送 DNS 查询请求到 CoreDNS, CoreDNS使用配置文件(如Corefile)来定义DNS解析规则和插件。这些规则指定了如何处理不同类型的DNS查询请求。 CoreDNS与Kubernetes API服务器集成,可以从Kubernetes API获取服务、端点和其他资源的信息
CoreDNS 机制原理:
我们从上面的章节可以看出 Service 最终负载均衡到后端的目标 Pods 是靠 kube-proxy
实现的。
随着 K8s 版本不断演进,kube-proxy 分别支持了多种工作模式:
在 K8s v1.2 版本之前的默认模式,这种模式下 Service 的请求会先从用户空间进入内核 iptables,然后再回到用户空间,由 kube-proxy 完成后端 Endpoints 的选择和代理工作。
这样流量从用户空间进出内核带来的性能损耗是不可接受的,因此从 v1.2 版本之后默认改为 iptables 模式
iptables 是 K8s 中当前默认的 kube-proxy 模式,核心逻辑是使用 iptables 中 PREROUTING 链 nat 表,实现 Service => Endpoints (Pod IP) 的负载均衡:
具体来说,访问 Service 的流量到达 Node 后,首先在 iptables PREROUTING 链中 KUBE-SERVICES 子链进行过滤:
1234 | iptables -t nat -nvL PREROUTINGChain PREROUTING (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 2907M 152G KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */ |
---|
接着,KUBE-SERVICES 链中会将所有 Service 建立对应的 KUBE-SVC-XXX 子链规则(KUBE-SVC-XXX 或其他 KUBE-XXX 相关的 Chain,后面的 XXX 是统一将部分字段(如 servicePortName + protocol)经过 Sum256 + encode 后取其前 16 位得到。 ),若 Service 类型是 NodePort,则命中最下面的 KUBE-NODEPORTS 子链:
12345678 | iptables -t nat -nvL KUBE-SERVICESChain KUBE-SERVICES (2 references) pkts bytes target prot opt in out source destination 0 0 KUBE-SVC-NPX46M4PTMTKRN6Y tcp -- * * 0.0.0.0/0 9.165.28.1 /* default/kubernetes:https cluster IP */ tcp dpt:443 0 0 KUBE-SVC-MYCX2RXADSOJAHT3 tcp -- * * 0.0.0.0/0 9.165.28.109 /* cloud-mt/rpc-client-api:rpc-client-api-http-port cluster IP */ tcp dpt:8080 0 0 KUBE-SVC-KA2XGWAJDV5QLLFK tcp -- * * 0.0.0.0/0 9.165.31.189 /* default/kubernetes-intranet:https cluster IP */ tcp dpt:443 0 0 KUBE-EXT-KA2XGWAJDV5QLLFK tcp -- * * 0.0.0.0/0 11.155.136.98 /* default/kubernetes-intranet:https loadbalancer IP */ tcp dpt:44323491 1217K KUBE-NODEPORTS all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL |
---|
接着,某个 Service 对应的 KUBE-SVC-XXX 子链的目的地 (target) 将指向 KUBE-SEP-XXX,表示 Service 对应的 Endpoints,一条 KUBE-SEP-XXX 子链代表一条后端 Endpoint IP。
1234 | iptables -t nat -nvL KUBE-SVC-MYCX2RXADSOJAHT3Chain KUBE-SVC-MYCX2RXADSOJAHT3 (2 references) pkts bytes target prot opt in out source destination 6822 355K KUBE-SEP-ZA4HDVB3YCYB4PTF all -- * * 0.0.0.0/0 0.0.0.0/0 /* cloud-mt/rpc-client-api:rpc-client-api-http-port -> 9.165.16.189:8080 */ |
---|
可以看到上面的包含一条 KUBE-SEP-xxx 子链, 代表这个 Service 后面就一个 Pod
继续查看某个 KUBE-SEP-XXX 子链的规则如下:
12345 | ptables -t nat -nvL KUBE-SEP-ZA4HDVB3YCYB4PTFChain KUBE-SEP-ZA4HDVB3YCYB4PTF (1 references) pkts bytes target prot opt in out source destination 0 0 KUBE-MARK-MASQ all -- * * 9.165.16.189 0.0.0.0/0 /* cloud-mt/rpc-client-api:rpc-client-api-http-port */ 7279 379K DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* cloud-mt/rpc-client-api:rpc-client-api-http-port */ tcp to:9.165.16.189:8080 |
---|
命中某个 KUBE-SEP-XXX 子链后,可以看到其目的地有两个:
ipvs (IP Virtual Server) 是 LVS (Linux Virtual Server) 内核模块的一个子模块,建立于 Netfilter 之上的高效四层负载均衡器,支持 TCP 和 UDP 协议,在这种模式下,kube-proxy 将规则插入到 ipvs 而非 iptables。
ipvs 具有优化的查找算法(哈希),复杂度为 O(1)。这意味着无论插入多少规则,它几乎都提供一致的性能。ipvs 支持多种负载均衡策略,如轮询 (rr)、加权轮询 (wrr)、最少连接 (lc)、源地址哈希 (sh)、目的地址哈希 (dh)等,K8s 中默认使用了 rr 策略。
尽管它有优势,但是 ipvs 可能不在所有 Linux 系统中都存在。与几乎每个 Linux 操作系统都有的 iptables 相比,ipvs 可能不是所有 Linux 系统的核心功能。如果集群中 Service 数量不太多,iptables 应该就够用了。