Kubernetes 网络实现——Service网络

简介: 本文介绍 Kubernetes 网络中 Service 网路部分的实现方案。

引言

本文介绍 Kubernetes 网络中 Service 网路部分的实现方案,更多关于 Kubernetes 的介绍均收录于<Kubernetes系列文章>中。

Service 网络

service network 比较特殊,每个新创建的 service 会被分配一个 service IP,它实际上只是一个存在于 iptables 中的路由规则,并没有对应的虚拟网卡。还记得我们在试玩环节创建的 kubia service 吗?我们只要在集群中的任意机器查看 iptables 就能发现端倪,这里需要声明的是我当时在创建 Kubernetes 集群时指定的 PodSubnet 为 192.168.128.0/18,serviceSubnet 为 192.168.192.0/18

我们知道,linux 系统中有 route,用来和其他网络连接。如果没有它,linux 就无法和外部通信了。

iptables 是一系列的规则,用来对内核中的数据进行处理。没有它,linux 可以正常工作,有了它,linux 可以对网络中的数据的操作更加多样化,可见 iptables 是一个辅助角色,可以让 linux 的网络系统更强大。

iptables 其实不是真正的防火墙,我们可以把它理解成一个客户端代理,用户通过 iptables 这个代理,将用户的安全设定执行到对应的"安全框架"中,这个"安全框架"才是真正的防火墙,这个框架的名字叫 netfilter。

netfilter 才是防火墙真正的安全框架(framework),netfilter位于内核空间。

iptables 其实是一个命令行工具,位于用户空间,我们用这个工具操作真正的框架。

Netfilter是Linux操作系统核心层内部的一个数据包处理模块,它具有如下功能:

  • 网络地址转换(Network Address Translate)
  • 数据包内容修改
  • 数据包过滤的防火墙功能

在 iptables 中 有两个概念:链和表,iptables 中定义有5条链,说白了就是5个钩子函数,它们是在数据包经过内核的过程中有五处关键地方,分别是 PREROUTING、INPUT、OUTPUT、FORWARD、POSTROUTING,因为每个钩子函数中可以定义多条规则,每当数据包到达一个钩子函数时,iptables 就会从钩子函数中第一条规则开始检查,看该数据包是否满足规则所定义的条件。如果满足,系统就会根据该条规则所定义的方法处理该数据包;否则 iptables 将继续检查下一条规则,如果该数据包不符合钩子函数中任一条规则,iptables 就会根据该函数预先定义的默认策略来处理数据包。iptables 中还有一个表的概念,每个表(tables)对应了一个特定的功能,有filter表(实现包过滤)、nat表(实现网络地址转换)、mangle表(实现包修改)、raw表(实现数据跟踪),不同的表中包含了不同的钩子函数,不同表的同一钩子函数具有一定的优先级:raw-->mangle-->nat-->filter。一条链上可定义不同功能的规则,检查数据包时将根据上面的优先级顺序检查。
table-and-chain.png
总结一下,数据包先经过 PREOUTING,由该链确定数据包的走向:

  1. 目的地址是本地,则发送到 INPUT,让INPUT决定是否接收下来送到用户空间,流程为①--->②;
  2. 若满足 PREROUTING 的 nat 表上的转发规则,则发送给 FORWARD,然后再经过 POSTROUTING 发送出去,流程为: ①--->③--->④--->⑥
  3. 主机发送数据包时,流程则是⑤--->⑥

iptables-summary
此处列出一些 iptables 常用的动作,在后面分析 iptables 时您将看到 Kubernetes 对各种动作的使用:

  • ACCEPT:允许数据包通过。
  • DROP:直接丢弃数据包,不给任何回应信息,这时候客户端会感觉自己的请求泥牛入海了,过了超时时间才会有反应。
  • REJECT:拒绝数据包通过,必要时会给数据发送端一个响应的信息,客户端刚请求就会收到拒绝的信息。
  • SNAT:源地址转换,解决内网用户用同一个公网地址上网的问题。
  • MASQUERADE:是SNAT的一种特殊形式,适用于动态的、临时会变的ip上。
  • DNAT:目标地址转换。
  • REDIRECT:在本机做端口映射。
  • MARK:打标记。

我们先回顾一下 kubia service 的端口和 service ip, 可以看到它的 service endpoint 是 192.168.199.234:8080,因为采用了 NodePort 类型的 service,所以我们也可以通过所有 pod 所处宿主机的 32681 端口访问该服务。

kubectl get service -o wide
#NAME         TYPE        CLUSTER-IP        EXTERNAL-IP   PORT(S)          AGE   SELECTOR
#kubia        NodePort    192.168.199.234   <none>        8080:32681/TCP   17h   app=kubia

然后我们先看一下 iptables 的 nat 表内容,因为它控制了网络地址转换

iptables -t nat -nL
#Chain PREROUTING (policy ACCEPT)
#target     prot opt source               destination
#KUBE-SERVICES  all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes service portals */
# 注入 KUBE-SERVICES 链
#
#Chain OUTPUT (policy ACCEPT)
#target     prot opt source               destination
#KUBE-SERVICES  all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes service portals */
# 注入 KUBE-SERVICES 链

在 PREROUTING 链和 OUTPUT 链中,Kubernetes 注入了自己的链,该链匹配所有 ip 的包。然后,我们看下 KUBE-SERVICES 链都做了什么。

#Chain KUBE-SERVICES (2 references)
#target     prot opt source               destination
#KUBE-MARK-MASQ  tcp  -- !192.168.128.0/18     192.168.199.234      /* default/kubia: cluster IP */ tcp dpt:8080
#KUBE-SVC-L5EAUEZ74VZL5GSC  tcp  --  0.0.0.0/0            192.168.199.234      /* default/kubia: cluster IP */ tcp dpt:8080
#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

在 KUBE-SERVICES 链中可以看到,当我们的 destination 为 192.168.199.234 tcp 8080 时,会先判断是不是 Pod 子网,如果不是则会先进入 KUBE-MARK-MASQ 链,然后才会进入 KUBE-SVC-L5EAUEZ74VZL5GSC 链,最后一条规则是 KUBE-NODEPORTS 匹配目标地址是 local 的流量。

接下来我们分别看一看 KUBE-SERVICES 下层的三个链分别是干什么用的。

#Chain KUBE-MARK-MASQ (25 references)
#target     prot opt source               destination
#MARK       all  --  0.0.0.0/0            0.0.0.0/0            MARK or 0x4000

KUBE-MARK-MASQ 链的作用,只是打上标记 MARK or 0x4000,该标记的作用一会儿再说。

#Chain KUBE-SVC-L5EAUEZ74VZL5GSC (2 references)
#target     prot opt source               destination
#KUBE-SEP-DHEI6HLVRKMGWFA5  all  --  0.0.0.0/0            0.0.0.0/0            statistic mode random probability 0.16667000018
#KUBE-SEP-U2WLSCWTWVRL43BI  all  --  0.0.0.0/0            0.0.0.0/0            statistic mode random probability 0.20000000019
#KUBE-SEP-T3I6FPLS4WOL5Q3I  all  --  0.0.0.0/0            0.0.0.0/0            statistic mode random probability 0.25000000000
#KUBE-SEP-SRYJEUDBHDCQM4KY  all  --  0.0.0.0/0            0.0.0.0/0            statistic mode random probability 0.33332999982
#KUBE-SEP-VVYACXWHEDETJZUB  all  --  0.0.0.0/0            0.0.0.0/0            statistic mode random probability 0.50000000000
#KUBE-SEP-5A4LPBRVHCEID4EO  all  --  0.0.0.0/0            0.0.0.0/0

在 KUBE-SVC-L5EAUEZ74VZL5GSC 链中就对应了我们 kubia 服务的的 6 个 pod,可以看到他在这一层做了一个负载均衡,第一个 KUBE-SEP 分配了 1/6 的份额,第二个 KUBE-SEP 分配了剩余份额中的 1/5 ,以此类推,最终达到的效果就是每个 KUBE-SEP 分配 1/Pod count 的份额,我们挑选第一个 pod 对应的 KUBE-SEP-DHEI6HLVRKMGWFA5 看一下。

#Chain KUBE-SEP-DHEI6HLVRKMGWFA5 (1 references)
#target     prot opt source               destination
#KUBE-MARK-MASQ  all  --  192.168.131.21       0.0.0.0/0
#DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp to:192.168.131.21:8080

如果源 ip 和 pod ip 地址相同的话,则同样打上 mark,然后将目标地址(DNAT)改为该 Pod 的 ip + 服务端口,to:192.168.131.21:8080,到这里就完成了路由工作。

#Chain KUBE-NODEPORTS (1 references)
#target     prot opt source               destination
#KUBE-MARK-MASQ  tcp  --  0.0.0.0/0            0.0.0.0/0            /* default/kubia: */ tcp dpt:32681
#KUBE-SVC-L5EAUEZ74VZL5GSC  tcp  --  0.0.0.0/0            0.0.0.0/0            /* default/kubia: */ tcp dpt:32681

最后,在KUBE-NODEPORTS 链中,如果目标端口和 kubia service 的 nodePort 匹配上的话,就会打上 mark 并进去之前的 service 链 KUBE-SVC-L5EAUEZ74VZL5GSC, 也就是说无论你访问集群内的任意一台机器的 32681 端口,流量均会均匀地转发到对应的 pod 中。

接下来我们看一下前面打的 MARK or 0x4000 都用来干什么了。

#Chain POSTROUTING (policy ACCEPT)
#target     prot opt source               destination
#KUBE-POSTROUTING  all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes postrouting rules */
#RETURN     all  --  192.168.128.0/18     192.168.128.0/18
#MASQUERADE  all  --  192.168.128.0/18    !224.0.0.0/4
#RETURN     all  -- !192.168.128.0/18     192.168.132.0/24
#MASQUERADE  all  -- !192.168.128.0/18     192.168.128.0/18
# 这里是在 POSTROUTING 中注入的 KUBE-POSTROUTING 链, 并使用 MASQUERADE 进行与 Pod subnet 相关的 SNAT,实际上这是将 service pod 的源 ip SNAT 为 service ip。后面会详细介绍这个过程。
#
#Chain KUBE-POSTROUTING (1 references)
#target     prot opt source               destination
#MASQUERADE  all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes service traffic requiring SNAT */ mark match 0x4000/0x4000

KUBE-POSTROUTING 中匹配了所有打了 mark 0x4000 的报文,并通过 MASQUERADE 地址伪装,它是 SNAT 的一种特殊形式,可以自动化的进行源地址转换 SNAT,这个自动进行 SNAT 转换的过程实际是将报文的源地址转化为 Pod Subnet 的 ip 域。

回顾一下前面哪些情况下加了 mark:

  1. KUBE-SERVICES: source ip 不在 pob subnet 域并且 destination 是 service ip + service port 时,实际的访问者在哪个集群主机上,就会使用该主机被分配的 pod subnet ip,即便是在 pod 中访问也会使用该 pod 所处的 host 被分配的 pod subnet ip(即 flannel 网卡 ip),而不是使用 pod 的 ip;
  2. KUBE-SEP-DHEI6HLVRKMGWFA5: source ip 和 destination ip 都是目标服务 pod 的 ip 时,会使用该 pod 所处 host 的 pod subnet ip(即 flannel 网卡 ip),而不是 pod 内的 ip;
  3. KUBE-NODEPORTS: 如果访问了 Service 使用的 NodePort,比如 Worker1 的 32681 端口,那么 SNAT 将改为 Worker1 的 pod subnet ip(即 flannel 网卡 ip);

另一个 mark 0x4000 的用途在 iptables 的 filter 表中,该表决定了数据报文的丢弃与放行的决策。

iptables -t filter -nL
#Chain INPUT (policy ACCEPT)
#target     prot opt source               destination
#KUBE-SERVICES  all  --  0.0.0.0/0            0.0.0.0/0            ctstate NEW /* kubernetes service portals */
#KUBE-EXTERNAL-SERVICES  all  --  0.0.0.0/0            0.0.0.0/0            ctstate NEW /* kubernetes externally-visible service portals */
# 允许创建任意 ip 地址的连接,因为 service 网络是不存在与任何宿主机网络中的,但是我们需要允许以 service ip 建立连接
#
#Chain OUTPUT (policy ACCEPT)
#target     prot opt source               destination
#KUBE-SERVICES  all  --  0.0.0.0/0            0.0.0.0/0            ctstate NEW /* kubernetes service portals */
# 同 INPUT,OUTPUT 链也放行以 service ip 相关的连接件建立
#Chain FORWARD (policy ACCEPT)
#ACCEPT     all  --  192.168.128.0/18     0.0.0.0/0
#ACCEPT     all  --  0.0.0.0/0            192.168.128.0/18
#KUBE-FORWARD  all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes forwarding rules */
# 在 FORWARD 链中注入了 KUBE-FORWARD,放行源 ip 或者目的 ip 为 pod subnet 的包
#
#Chain KUBE-FORWARD (1 references)
#target     prot opt source               destination
#DROP       all  --  0.0.0.0/0            0.0.0.0/0            ctstate INVALID
#ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes forwarding rules */ mark match 0x4000/0x4000
#ACCEPT     all  --  192.168.128.0/18     0.0.0.0/0            /* kubernetes forwarding conntrack pod source rule */ ctstate RELATED,ESTABLISHED
#ACCEPT     all  --  0.0.0.0/0            192.168.128.0/18     /* kubernetes forwarding conntrack pod destination rule */ ctstate RELATED,ESTABLISHED

ctstate description:

  • NEW新建的连接的包
  • ESTABLISHED已建立连接的后续包
  • RELATED相关连接的包, 如FTP控制和数据
  • INVALID没识别出来, 或者没有状态, 一般DROP此类的包

conntrack: 连接追踪表,记录了 SNAT 与 DNAT 的转换,可以通过 conntrack -L 查看

KUBE-FORWARD 中对打了 mark 0x4000 的包,源 ip 或者目的 ip 是 pod subnet 的包(状态:已建立连接或者存在相关连接)放行 Forward,对于其他非法状态的包则直接丢弃。

下图中所有黄色区域都为 Kubernetes 在 iptables 上做的改动。
Kubernetes-iptables
从上述 iptables 的分析中,我们可以知道,service network 实际上只是存在于 iptables 的路由规则,里面记录了一个指定 service ip 应该将流量路由到哪个 Pod ip 地址,以及收到的包应该如何进行 SNAT。下面两个动图描述了,从 Pod 发包给 Service,以及 service pod 回包的情况:
pod-to-service
我们可以看到,来自 pod1 的报文以某种形式流到了宿主机上(我们后面会详细介绍这部分的实现原理),然后要从宿主机的 eth0 设备发出,这时候触发了 iptables 的规则匹配,根据 iptables 中的规则,最终将请求的目标地址改成了 Pod ip 并发送出去。在这个过程中 Linux kernel 的 conntrack 会记住此时选择的 pod,并且在下次这个源 pod 要发送给该 service 时,仍路由到相同的 service pod。至此,iptables 就完成了集群内的负载均衡,剩下的传输过程就由 flannel network 负责了。
service-to-pod
当 service 响应 pod 的请求后,数据包被 eth0 网卡接收到,并再次进入 iptables 的匹配过程,还记得前面 iptables 中的 POSTROUTING 吗?

# 从 service pod 发来的回包,将 SNAT 恢复为 service ip 或者虚拟机的 ip
#MASQUERADE  all  --  192.168.128.0/18    !224.0.0.0/4
# 将对外部网络的访问恢复 SNAT
#MASQUERADE  all  -- !192.168.128.0/18     192.168.128.0/18

这里同样会使用到内核的 conntrack ,通过它可以将报文的源地址从 pod 的 ip 改为 service 的 ip,这下访问 service 的完整流程就走完了。如下是一个 conntrack 例子:

conntrack -L|grep 32681
#tcp      6 9 CLOSE src=<exeternal-ip> dst=<local-eth0-ip> sport=62142 dport=32681 src=192.168.132.73 dst=192.168.128.0 sport=8080 dport=62142 mark=0 use=1
#tcp      6 9 CLOSE src=<exeternal-ip> dst=<local-eth0-ip> sport=62790 dport=32681 src=192.168.132.72 dst=192.168.128.0 sport=8080 dport=62790 mark=0 use=1
conntrack -L|grep 192.168.192
#tcp      6 86377 ESTABLISHED src=192.168.128.2 dst=192.168.192.1 sport=38742 dport=443 src=<other-vm-ip> dst=<local-eth0-ip> sport=6443 dport=28222 [ASSURED] mark=0 use=1
#tcp      6 86394 ESTABLISHED src=<exeternal-ip> dst=192.168.192.1 sport=50800 dport=443 src=<local-eth0-ip> dst=<local-eth0-ip> sport=6443 dport=50800 [ASSURED] mark=0 use=1

文章说明

更多有价值的文章均收录于贝贝猫的文章目录

stun

版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

创作声明: 本文基于下列所有参考内容进行创作,其中可能涉及复制、修改或者转换,图片均来自网络,如有侵权请联系我,我会第一时间进行删除。

参考内容

[1] kubernetes GitHub 仓库
[2] Kubernetes 官方主页
[3] Kubernetes 官方 Demo
[4] 《Kubernetes in Action》
[5] 理解Kubernetes网络之Flannel网络
[6] Kubernetes Handbook
[7] iptables概念介绍及相关操作
[8] iptables超全详解
[9] 理解Docker容器网络之Linux Network Namespace
[10] A Guide to the Kubernetes Networking Model
[11] Kubernetes with Flannel — Understanding the Networking
[12] 四层、七层负载均衡的区别

相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。 &nbsp; &nbsp; 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
10月前
|
运维 Kubernetes 前端开发
如何用 eBPF 实现 Kubernetes 网络可观测性?实战指南
本文深入探讨了Kubernetes网络观测的挑战与eBPF技术的应用。首先分析了传统工具在数据碎片化、上下文缺失和性能瓶颈上的局限性,接着阐述了eBPF通过零拷贝观测、全链路关联和动态过滤等特性带来的优势。文章进一步解析了eBPF观测架构的设计与实现,包括关键数据结构、内核探针部署及生产环境拓扑。实战部分展示了如何构建全栈观测系统,并结合NetworkPolicy验证、跨节点流量分析等高级场景,提供具体代码示例。最后,通过典型案例分析和性能数据对比,验证了eBPF方案的有效性,并展望了未来演进方向,如智能诊断系统与Wasm集成。
479 1
|
人工智能 弹性计算 运维
ACK Edge与IDC:高效容器网络通信新突破
本文介绍如何基于ACK Edge以及高效的容器网络插件管理IDC进行容器化。
|
Kubernetes 容器
K8S的Service的LoadBanlance之Metallb解决方案
本文介绍了如何在Kubernetes中使用MetalLB来实现Service的LoadBalancer功能,包括MetalLB的部署、配置、以及通过创建地址池和部署服务来测试MetalLB的过程。
915 2
K8S的Service的LoadBanlance之Metallb解决方案
|
Kubernetes Shell Windows
【Azure K8S | AKS】在AKS的节点中抓取目标POD的网络包方法分享
在AKS中遇到复杂网络问题时,可通过以下步骤进入特定POD抓取网络包进行分析:1. 使用`kubectl get pods`确认Pod所在Node;2. 通过`kubectl node-shell`登录Node;3. 使用`crictl ps`找到Pod的Container ID;4. 获取PID并使用`nsenter`进入Pod的网络空间;5. 在`/var/tmp`目录下使用`tcpdump`抓包。完成后按Ctrl+C停止抓包。
484 12
|
安全 Windows
【Azure Cloud Service】在Windows系统中抓取网络包 ( 不需要另外安全抓包工具)
通常,在生产环境中,为了保证系统环境的安全和纯粹,是不建议安装其它软件或排查工具(如果可以安装,也是需要走审批流程)。 本文将介绍一种,不用安装Wireshark / tcpdump 等工具,使用Windows系统自带的 netsh trace 命令来获取网络包的步骤
406 32
|
运维 分布式计算 Kubernetes
ACK One多集群Service帮助大批量应用跨集群无缝迁移
ACK One多集群Service可以帮助您,在无需关注服务间的依赖,和最小化迁移风险的前提下,完成跨集群无缝迁移大批量应用。
|
Kubernetes 网络协议 应用服务中间件
Kubernetes Ingress:灵活的集群外部网络访问的利器
《Kubernetes Ingress:集群外部访问的利器-打造灵活的集群网络》介绍了如何通过Ingress实现Kubernetes集群的外部访问。前提条件是已拥有Kubernetes集群并安装了kubectl工具。文章详细讲解了Ingress的基本组成(Ingress Controller和资源对象),选择合适的版本,以及具体的安装步骤,如下载配置文件、部署Nginx Ingress Controller等。此外,还提供了常见问题的解决方案,例如镜像下载失败的应对措施。最后,通过部署示例应用展示了Ingress的实际使用方法。
902 2
|
Kubernetes 容器 Perl
Kubernetes网络插件体系及flannel基础
文章主要介绍了Kubernetes网络插件体系,特别是flannel网络模型的工作原理、配置和测试方法。
513 3
Kubernetes网络插件体系及flannel基础
|
Kubernetes 网络协议 网络安全
k8s中网络连接问题
【10月更文挑战第3天】
1226 7
|
Kubernetes 应用服务中间件 nginx
搭建Kubernetes v1.31.1服务器集群,采用Calico网络技术
在阿里云服务器上部署k8s集群,一、3台k8s服务器,1个Master节点,2个工作节点,采用Calico网络技术。二、部署nginx服务到k8s集群,并验证nginx服务运行状态。
4593 3

热门文章

最新文章

推荐镜像

更多