Kubernetes 网络实现——Service网络

本文涉及的产品
云防火墙,500元 1000GB
公网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] 四层、七层负载均衡的区别

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
6天前
|
人工智能 弹性计算 运维
ACK Edge与IDC:高效容器网络通信新突破
本文介绍如何基于ACK Edge以及高效的容器网络插件管理IDC进行容器化。
|
3月前
|
Kubernetes 容器
K8S的Service的LoadBanlance之Metallb解决方案
本文介绍了如何在Kubernetes中使用MetalLB来实现Service的LoadBalancer功能,包括MetalLB的部署、配置、以及通过创建地址池和部署服务来测试MetalLB的过程。
150 1
K8S的Service的LoadBanlance之Metallb解决方案
|
4月前
|
Kubernetes 负载均衡 网络安全
Kubernetes 网络模型与实践
【8月更文第29天】Kubernetes(K8s)是当今容器编排领域的佼佼者,它提供了一种高效的方式来管理容器化应用的部署、扩展和运行。Kubernetes 的网络模型是其成功的关键因素之一,它支持服务发现、负载均衡和集群内外通信等功能。本文将深入探讨 Kubernetes 的网络模型,并通过实际代码示例来展示服务发现和服务网格的基本概念及其实现。
153 1
|
4月前
|
Kubernetes Devops 持续交付
DevOps实践:使用Docker和Kubernetes实现持续集成和部署网络安全的守护盾:加密技术与安全意识的重要性
【8月更文挑战第27天】本文将引导读者理解并应用DevOps的核心理念,通过Docker和Kubernetes的实战案例,深入探讨如何在现代软件开发中实现自动化的持续集成和部署。文章不仅提供理论知识,还结合真实示例,旨在帮助开发者提升效率,优化工作流程。
|
25天前
|
安全 Windows
【Azure Cloud Service】在Windows系统中抓取网络包 ( 不需要另外安全抓包工具)
通常,在生产环境中,为了保证系统环境的安全和纯粹,是不建议安装其它软件或排查工具(如果可以安装,也是需要走审批流程)。 本文将介绍一种,不用安装Wireshark / tcpdump 等工具,使用Windows系统自带的 netsh trace 命令来获取网络包的步骤
65 32
|
2月前
|
Kubernetes 网络协议 网络安全
k8s中网络连接问题
【10月更文挑战第3天】
194 7
|
2月前
|
Kubernetes 应用服务中间件 nginx
搭建Kubernetes v1.31.1服务器集群,采用Calico网络技术
在阿里云服务器上部署k8s集群,一、3台k8s服务器,1个Master节点,2个工作节点,采用Calico网络技术。二、部署nginx服务到k8s集群,并验证nginx服务运行状态。
876 1
|
3月前
|
Kubernetes 容器 Perl
Kubernetes网络插件体系及flannel基础
文章主要介绍了Kubernetes网络插件体系,特别是flannel网络模型的工作原理、配置和测试方法。
120 3
Kubernetes网络插件体系及flannel基础
|
2月前
|
Kubernetes 容器
基于Ubuntu-22.04安装K8s-v1.28.2实验(三)数据卷挂载NFS(网络文件系统)
基于Ubuntu-22.04安装K8s-v1.28.2实验(三)数据卷挂载NFS(网络文件系统)
151 0
|
4月前
|
Kubernetes Cloud Native 网络安全
云原生入门指南:Kubernetes和容器化技术云计算与网络安全:技术融合的新篇章
【8月更文挑战第30天】在云计算的浪潮中,云原生技术如Kubernetes已成为现代软件部署的核心。本文将引导读者理解云原生的基本概念,探索Kubernetes如何管理容器化应用,并展示如何通过实践加深理解。