Kubernetes Service iptables 网络通信验证

标签: Knowledge k8s iptables kube-proxy service | 发表时间:2022-01-25 07:01 | 作者:
出处:https://lotabout.me/

Kubernetes gives Pods their own IP addresses and a single DNS name for a setof Pods, and can load-balance across them.

K8s Service会为每个 Pod 都设置一个它自己的 IP,并为一组 Pod 提供一个统一的 DNS 域名,还可以提供在它们间做负载均衡的能力。这篇文章会对 kube-proxy 的 iptables 模式内部的机制做一个验证。大体上涉及的内容如下:

实验配置

创建一个 Service,配置如下:

apiVersion: v1     
kind: Service
metadata:
creationTimestamp: "2022-01-23T02:32:38Z"
name: spring-test
namespace: default
resourceVersion: "94418"
uid: cdaab6bc-a518-4235-a161-a4cae6f564cf
spec:
clusterIP: 10.1.68.7
clusterIPs:
- 10.1.68.7
externalTrafficPolicy: Cluster
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- nodePort: 31080
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: spring-test
sessionAffinity: None
type: NodePort
status:
loadBalancer: {}

创建后的 service 如下:

$ k get svc -o wide -A     
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
default kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 2d22h <none>
default sender NodePort 10.1.177.169 <none> 8081:31081/TCP 46h app=sender
default spring-test NodePort 10.1.68.7 <none> 8080:31080/TCP 2d4h app=spring-test
kube-system kube-dns ClusterIP 10.1.0.10 <none> 53/UDP,53/TCP,9153/TCP 2d22h k8s-app=kube-dns

注意其中的 spring-test 和 kube-dns 两项,后面会用到。另外 service 对应的 podIP 如下:

$ k get ep     
NAME ENDPOINTS AGE
kubernetes 192.168.50.48:6443 2d23h
sender 10.244.1.7:8080,10.244.2.7:8080 47h
spring-test 10.244.1.3:8080,10.244.2.3:8080 2d4h

DNS

K8s 会为 Service 创建一个 DNS域名,格式为 <svc>.<namespace>.svc.<cluster-domain>,例如我们创建的 spring-test Service 则会有 spring-test.default.svc.cluster.local [1] 域名。

我们首先进入 pod,看一下 /etc/resolv.conf 文件,关于域名解析的配置:

nameserver 10.1.0.10     
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
  • 这里的 10.1.0.10 是 kube-dns service 的 cluster IP

  • 文件中配置了多个 search 域,因此我们写 spring-testspring-test.defaultspring-test.default.svc 都是可以解析的,另外注意解析后的 IP 也不是具体哪个 POD 的地址,而是为 Service 创建的虚拟地址ClusterIP。

    [email protected]:/# nslookup spring-test       
    Server: 10.1.0.10
    Address: 10.1.0.10#53

    Name: spring-test.default.svc.cluster.local
    Address: 10.1.68.7

    [email protected]:/# nslookup spring-test.default
    Server: 10.1.0.10
    Address: 10.1.0.10#53

    Name: spring-test.default.svc.cluster.local
    Address: 10.1.68.7

    [email protected]:/# nslookup spring-test.default.svc
    Server: 10.1.0.10
    Address: 10.1.0.10#53

    Name: spring-test.default.svc.cluster.local
    Address: 10.1.68.7
  • ndots:5 指的是如果域名中的 . 大于等于 5 个,则不走 search 域,目的是减少常规域名的解析次数 [2]

iptables 转发

DNS 里创建的记录解决了域名到 ClusterIP 的转换问题,发送到 ClusterIP 的请求,如何转发到对应的 POD 里呢?K8s Service 有几种实现方式,这里验证的是 iptables 的实现方式:kube-proxy 会监听 etcd 中关于 k8s 的事件,并动态地对 iptables 做配置,最终由 iptables 来完成转发。先看看跟这个 Service 相关的规则如下:

0.  -A PREROUTING -j KUBE-SERVICES     
1. -A KUBE-NODEPORTS -p tcp -m tcp --dport 31080 -j KUBE-SVC-S
2. -A KUBE-SEP-A -s 10.244.2.3/32 -j KUBE-MARK-MASQ
3. -A KUBE-SEP-A -p tcp -m tcp -j DNAT --to-destination 10.244.2.3:8080
4. -A KUBE-SEP-B -s 10.244.1.3/32 -j KUBE-MARK-MASQ
5. -A KUBE-SEP-B -p tcp -m tcp -j DNAT --to-destination 10.244.1.3:8080
6. -A KUBE-SERVICES -d 10.1.68.7/32 -p tcp -m tcp --dport 8080 -j KUBE-SVC-S
7. -A KUBE-SVC-S ! -s 10.244.0.0/16 -d 10.1.68.7/32 -p tcp -m tcp --dport 8080 -j KUBE-MARK-MASQ
8. -A KUBE-SVC-S -p tcp -m tcp --dport 31080 -j KUBE-MARK-MASQ
9. -A KUBE-SVC-S -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-B
10. -A KUBE-SVC-S -j KUBE-SEP-A

我们先用 iptables-save 打印出所有的规则,筛选出和 spring-test service相关的规则,删除了一些 comment,并对名字做了简化。可以看到有这么几类:

  • KUBE-NODEPORTS,这类规则用来将发送到 NodePort 的报文转到 KUBE-SVC-*
  • KUBE-SERVICES:是识别目标地址为 ClusterIP( 10.1.68.7),命中的报文转到 KUBE-SVC-* 做处理
  • KUBE-SVC 的作用是做负载均衡,将请求分配到 KUBE-SEP
  • KUBE-SEP 通过 DNAT 替换目标地址为 Pod IP,转发到具体的 POD 中

另外经常看到 -j KUBE-MARK-MASQ,它的作用是在请求里加上 mark,在 POSTROUTING 规则中做 SNAT,这点后面再细说。

我们开启 iptables 的 trace 模式 [3],并在其中一个 pod 发送一个请求,检查 TRACE 中规则的命中情况(由于输出特别多,这里挑选了重要的输出并做了精简):

0:  nat:PREROUTING    IN=cni0 OUT=           SRC=10.244.1.7 DST=10.1.68.7  DPT=8080     
6: nat:KUBE-SERVICES IN=cni0 OUT= SRC=10.244.1.7 DST=10.1.68.7 DPT=8080
10: nat:KUBE-SVC-S IN=cni0 OUT= SRC=10.244.1.7 DST=10.1.68.7 DPT=8080
3: nat:KUBE-SEP-A IN=cni0 OUT= SRC=10.244.1.7 DST=10.1.68.7 DPT=8080
mangle:FORWARD IN=cni0 OUT=flannel.1 SRC=10.244.1.7 DST=10.244.2.3 DPT=8080
  • PREROUTING 时,进入第 6 条进判定
  • KUBE-SERVICES 判断目标地址为 10.1.68.7 且目标端口为 8080,于是跳转进入 KUBE-SVC-S 链的判断
  • KUBE-SVC-S 有多条规则,从日志看最终是从第 10 条退出,进入 KUBE-SEP-A
  • KUBE-SEP-A 最终命中第 3 条规则退出,但此时会进行 DNAT 转换目标地址
  • 下一条日志显示, DST 目标地址已经变成 pod 地址 10.244.2.3

类似的,如果我们是通过 NodePort 来访问 Service,则 Trace 日志如下:

0: nat:PREROUTING:      IN=eth0 OUT=     SRC=192.168.50.135 DST=192.168.50.238 DPT=31080     
6: nat:KUBE-SERVICES: IN=eth0 OUT= SRC=192.168.50.135 DST=192.168.50.238 DPT=31080
1: nat:KUBE-NODEPORTS: IN=eth0 OUT= SRC=192.168.50.135 DST=192.168.50.238 DPT=31080
9: nat:KUBE-SVC-S: IN=eth0 OUT= SRC=192.168.50.135 DST=192.168.50.238 DPT=31080
9: nat:KUBE-SVC-S: IN=eth0 OUT= SRC=192.168.50.135 DST=192.168.50.238 DPT=31080
5: nat:KUBE-SEP-A: IN=eth0 OUT= SRC=192.168.50.135 DST=192.168.50.238 DPT=31080
mangle:FORWARD: IN=eth0 OUT=cni0 SRC=192.168.50.135 DST=10.244.1.3 DPT=8080

iptables 负载均衡

上一节我们比较关注 iptables 转发的内容,那么如何做负载均衡?这部分是比较纯粹的iptables 知识 [4]:

首先:iptables 对于规则的解析是严格顺序的,所以如果只是单纯列出两个条目,则会永远命中第一条:

-A KUBE-SVC-S -j KUBE-SEP-A     
-A KUBE-SVC-S -j KUBE-SEP-B

于是,我们需要第一条规则在某些条件下不命中。这样 iptables 就有机会执行后面的规则。iptables 提供了两种方法,第一种是有随机数,也是上一节我们看到的:

-A KUBE-SVC-S -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-B     

这条规则在执行时,iptables 会随机生成一个数,并以 probability 的概率命中当前规则。换句话说,第一条命中的概率是 p,则第二条规则就是 1-p。如果有 3 个副本,则会类似下面这样的规则,大家可以计算下最后三个 Pod 是不是平均分配:

-A KUBE-SVC-S --mode random --probability 0.33333333349 -j KUBE-SEP-A     
-A KUBE-SVC-S --mode random --probability 0.50000000000 -j KUBE-SEP-B
-A KUBE-SVC-S -j KUBE-SEP-C

另外一种模式是 round-robin,但是 kubernetes 的 iptables 模式不支持,这里就不细说了。猜想 kubernetes iptables 模式下不支持的原因是虽然单机 iptables 能支持round-robin,但多机模式下,无法做到全局的 round-robin。

SNAT

前面我们提到 KUBE 系列的规则经常看到 -j KUBE-MARK-MASQ,和它相关的规则有这些:

-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000     
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE

首先 KUBE-MARK-MASQ 的作用是把报文打上 0x4000/0x4000 的标记,在 KUBE-POSTROUTING 时,如果报文中包含这个标记,会执行 -j MASQUERADE 操作,而这个操作的作用就是做源地址转换(SNAT)。那 SNAT 是什么,为什么要做 SNAT 呢?

这里引用 这篇文章里的图做说明:

如果没有 SNAT,被转发到 POD 的请求返回时,会尝试把请求直接返回给 Client,我们知道一个 TCP 连接的依据是(src_ip, src_port, dst_ip, dst_port),现在client 在等待 eIP/NP 返回的报文,等到的却是 pod IP 的返回,client 不认这个报文。换句话说,经过 proxy 的流量都正常情况下都应该原路返回才能工作。

在一些情况下可能希望关闭 SNAT,K8S 提供 externalTrafficPolicy: Local 的配置项,但流量的流转也会发生变化,这里不深入。

小结

这篇文章和上一篇 Flannel 网络通信验证类似,都是尝试搭建环境,在学习 kube-proxy 工作机制的同时,对 kube-proxy 的产出iptables 做一些验证。文章中验证了这些内容:

  1. 验证了 service ClusterIP 和 domain 的创建,及 pod 中 /etc/resolv.conf 中搜索域的设置
  2. 验证了 kube-proxy 生成的 iptables 规则,并验证请求在这些规则中的流转
  3. 学习了 iptables 负载均衡的工作机制
  4. 了解了 SNAT 是什么,kube-proxy 需要做 SNAT 的原因

这篇文章的信息量不大,希望读者也撸起袖子,实打实地做一些验证,能让我们对kube-proxy 涉及的 iptables 的操作有更深刻的理解。

参考


  1. cluster.local 是可以改的,但是比较麻烦,参考: https://stackoverflow.com/a/66106716

  2. 参考 https://hansedong.github.io/2018/11/20/9/

  3. https://www.opensourcerers.org/2016/05/27/how-to-trace-iptables-in-rhel7-centos7/

  4. Turning IPTables into a TCP load balancer for fun and profit

相关 [kubernetes service iptables] 推荐:

Kubernetes Service iptables 网络通信验证

- - 三点水
Kubernetes gives Pods their own IP addresses and a single DNS name for a setof Pods, and can load-balance across them.. K8s Service会为每个 Pod 都设置一个它自己的 IP,并为一组 Pod 提供一个统一的 DNS 域名,还可以提供在它们间做负载均衡的能力.

Kubernetes学习笔记之kube-proxy service实现原理 – 运维派

- -
我们生产k8s对外暴露服务有多种方式,其中一种使用. external-ips clusterip service ClusterIP Service方式对外暴露服务,kube-proxy使用iptables mode. 这样external ips可以指定固定几台worker节点的IP地址(worker节点服务已经被驱逐,作为流量转发节点不作为计算节点),并作为lvs vip下的rs来负载均衡.

kubernetes的Service Account_码农崛起-CSDN博客

- -
kubernetes的Service Account. Service account作用. Service account是为了方便Pod里面的进程调用Kubernetes API或其他外部服务. Service account使用场景. 运行在pod里的进程需要调用Kubernetes API以及非Kubernetes API的其它服务.

iptables NAT 学习

- - BlogJava-首页技术区
为了搞清楚iptables NAT的过程,做了这个实验. 使用了1台双网卡服务器和1台单网卡服务器,2个网段. 1.       为了看到调度服务器上的数据转发过程,首先在调度服务器上分出内核的debug日志:. l 在/etc/rsyslog.conf最后增加:kern.debug /var/log/iptables.log.

iptables 小结

- - CSDN博客系统运维推荐文章
        最近工作上一个作业用到了iptables命令,主要进行端口映射,在网上查了好多资料,尽管有很多例子,但还是整了好几天才整明白. (有一些是从网络中总结的,不断完善中...). (1) iptables简介.            iptables是一个Linux下优秀的nat+防火墙工具,iptables操作的是2.4以上内核的netfilter,所以需要linux的内核在2.4以上.

iptables 详解

- - 行业应用 - ITeye博客
以下文章转载于:http://blog.chinaunix.net/uid-26495963-id-3279216.html. 防火墙,其实说白了讲,就是用于实现Linux下访问控制的功能的,它分为硬件的或者软件的防火墙两种. 无论是在哪个网络中,防火墙工作的地方一定是在网络的边缘. 而我们的任务就是需要去定义到底防火墙如何工作,这就是防火墙的策略,规则,以达到让它对出入网络的IP、数据进行检测.

iptables原理说明

- - CSDN博客互联网推荐文章
1.iptables的前身叫ipfirewall (内核1.x时代),这是一个作者从freeBSD上移植过来的,能够工作在内核当中的,对数据包进行检测的一款简易访问控制工具. 2. 作者一共在内核空间中选择了5个位置,.     1).内核空间中:从一个网络接口进来,到另一个网络接口去的.     2).数据包从内核流入用户空间的.

iptables 学习总结

- - 小火箭
参考文章: 朱双印 iptables. 主机防火墙:对单个主机进行防护. 网络防火墙:通常处于网络的入口/出口,服务于其背后的局域网. 硬件防火墙:在硬件级别实现部分防火墙功能,另一部分功能基于软件实现,性能高,成本高. 软件防火墙:应用软件处理逻辑运行于通用硬件平台之上的防火墙,性能低,成本低.

SPRING BOOT OAUTH2 + KEYCLOAK - service to service call

- - BlogJava-首页技术区
employee-service调用department-service,如果要按OAUTH2.0流程,只需要提供client-id和client-secrect即可. 在KEYCLOAK中引入service-account,即配置该employee-service时,取消standard-flow,同时激活service-account.

Kubernetes & Microservice

- - 午夜咖啡
这是前一段时间在一个微服务的 meetup 上的分享,整理成文章发布出来. 谈微服务之前,先澄清一下概念. 微服务这个词的准确定义很难,不同的人有不同的人的看法. 比如一个朋友是『微服务原教旨主义者』,坚持微服务一定是无状态的 http API 服务,其他的都是『邪魔歪道』,它和 SOA,RPC,分布式系统之间有明显的分界.