Kubernetes 网络实现——Service网络

本文涉及的产品
公网NAT网关,每月750个小时 15CU
简介: 本文介绍 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] 四层、七层负载均衡的区别

相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务&nbsp;ACK 容器服务&nbsp;Kubernetes&nbsp;版(简称&nbsp;ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情:&nbsp;https://www.aliyun.com/product/kubernetes
相关文章
|
6天前
|
运维 Kubernetes Cloud Native
探索Kubernetes的大二层网络:原理、优势与挑战🚀
在云原生领域,Kubernetes (K8s) 已经成为容器编排的事实标准☁️📦。为了支撑其灵活的服务发现和负载均衡🔍🔄,K8s采用了大二层网络的设计理念🕸️。本文将深入探讨大二层网络的工作原理、带来的好处✨,以及面临的挑战和解决方案❗🛠️。
探索Kubernetes的大二层网络:原理、优势与挑战🚀
|
6天前
|
存储 Kubernetes 调度
k8s常见的排错指南Node,svc,Pod等以及K8s网络不通问题
k8s常见的排错指南Node,svc,Pod等以及K8s网络不通问题
215 1
|
6天前
|
Kubernetes 应用服务中间件 Docker
Kubernetes学习-集群搭建篇(二) 部署Node服务,启动JNI网络插件
Kubernetes学习-集群搭建篇(二) 部署Node服务,启动JNI网络插件
|
6天前
|
运维 Kubernetes Linux
Kubernetes详解(七)——Service对象部署和应用
Kubernetes详解(七)——Service对象部署和应用
11 3
|
6天前
|
Kubernetes API 调度
|
6天前
|
JSON Kubernetes 网络架构
Kubernetes CNI 网络模型及常见开源组件
【4月更文挑战第13天】目前主流的容器网络模型是CoreOS 公司推出的 Container Network Interface(CNI)模型
|
6天前
|
Kubernetes 网络协议 应用服务中间件
kubernetes核心技术之Service知识点总结
kubernetes核心技术之Service知识点总结
22 0
|
6天前
|
Kubernetes 应用服务中间件 数据安全/隐私保护
k8s 网络策略揭秘:CKA认证必备的网络知识全解析
k8s 网络策略揭秘:CKA认证必备的网络知识全解析
27 0
|
6天前
|
Kubernetes 网络协议 Perl
K8s网络不通Calico网络不通flannel网络不通
K8s网络不通Calico网络不通flannel网络不通
41 4
|
6天前
|
Kubernetes Shell Docker
K8S核心插件-Flannel网络插件
K8S核心插件-Flannel网络插件
63 0