惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Vulnerabilities – Threatpost
L
LINUX DO - 热门话题
F
Fox-IT International blog
C
Cisco Blogs
C
CERT Recently Published Vulnerability Notes
T
Tor Project blog
Malwarebytes
Malwarebytes
Latest news
Latest news
D
Darknet – Hacking Tools, Hacker News & Cyber Security
SecWiki News
SecWiki News
N
News and Events Feed by Topic
T
True Tiger Recordings
www.infosecurity-magazine.com
www.infosecurity-magazine.com
美团技术团队
P
Palo Alto Networks Blog
V
V2EX - 技术
AWS News Blog
AWS News Blog
A
About on SuperTechFans
Microsoft Azure Blog
Microsoft Azure Blog
量子位
博客园 - 【当耐特】
P
Proofpoint News Feed
N
News and Events Feed by Topic
博客园 - 司徒正美
U
Unit 42
G
Google Developers Blog
阮一峰的网络日志
阮一峰的网络日志
Schneier on Security
Schneier on Security
G
GRAHAM CLULEY
O
OpenAI News
T
The Blog of Author Tim Ferriss
F
Future of Privacy Forum
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
Blog — PlanetScale
Blog — PlanetScale
人人都是产品经理
人人都是产品经理
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
N
News | PayPal Newsroom
V
Visual Studio Blog
V
V2EX
Simon Willison's Weblog
Simon Willison's Weblog
Microsoft Security Blog
Microsoft Security Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Threat Research - Cisco Blogs
Spread Privacy
Spread Privacy
N
Netflix TechBlog - Medium
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
D
Docker
小众软件
小众软件
H
Hackread – Cybersecurity News, Data Breaches, AI and More
I
Intezer

郑文峰的博客

使用dify对接飞书多维表格 使用n8n对接飞书多维表格 服务启动时出现 OOM Bug 通缉令 一次服务升级时pg表DDL执行超时失败 Go语言高性能编程 Go语言高效IO缓冲技术详解 Go语言延迟初始化(Lazy Initialization)最佳实践 Go语言字符串拼接性能对比与优化指南 Go语言结构体内存对齐完全指南 Go语言空结构体:零内存消耗的高效编程 Go语言堆栈分配与逃逸分析深度解析 Go语言原子操作完全指南 Go语言内存预分配完全指南 Go语言不可变数据共享:无锁并发编程实践 Go语言零拷贝技术完全指南 Go语言遍历性能深度解析:从原理到优化实践 Go语言Interface Boxing原理与性能优化指南 Go协程池深度解析:原理、实现与最佳实践 使用etcd分布式锁导致的协程泄露与死锁问题 基于pre-commit的Python代码规范落地实践 初识 MCP Server pulsar阻塞导致logstash无法接入日志 django-prometheus使用及源码分析 kube-proxy源码分析 tcp缓存引起的日志丢失 django-apschedule定时任务异常停止 理解calico容器网络通信方案原理 理解flannel的三种容器网络方案原理 理解Linux IPIP隧道 理解VXLAN网络 理解Linux TunTap设备 快速了解iptables kafka中listener和advertised.listeners的作用 django rest_framework 分页 django后端服务、logstash和flink接入VictoriaMetrics指标监控 python中import原理 docker容器单机网络 手动实现docker容器bridge网络模型 mysql之MVCC原理 mysql之日志 使用java开发logstash的filter插件 使用python实现单例模式的三种方式 redis之缓存 redis之分片集群 redis之哨兵机制 redis之主从库同步 redis之持久化 redis之五种基本数据类型 go中如何处理error pod中将代码与运行环境分离 友链 ddt源码分析 python装饰器的使用方法 读书笔记:如何阅读一本书 使用ddt实现unittest的参数化测试 分布式锁 使用kubeadm安装k8s 优化gin表单的错误提示信息 gin中validator模块的源码分析 go简单使用grpc python简单使用grpc k8s之PV、PVC和StorageClass k8s之StatefulSet k8s之DaemonSet k8s之Job和CronJob k8s之ConfigMap和Secret k8s之Service k8s之Pod k8s之Deployment 容器的本质 docker容器 python迭代器与生成器 python元编程 python垃圾回收机制 python上下文管理器 django rest_framework使用jwt django rest_framework异常处理 django rest_framework 自定义文档 django压缩文件下载 django rest_framework使用pytest单元测试 django restframework choice 自定义输出数据 django Filtering 使用 django viewset 和 Router 配合使用时报的错 django model的序列化 django中使用AbStractUser django.core.exceptions.ImproperlyConfigured Application labels aren't unique, duplicates users django 中 media配置 django 外键引用自身和on_delete参数 django 警告 while time zone support is active Flask使用flask_socketio实现websocket flask结合mongo tornado 文件上传 tornado 使用jwt完成用户异步认证 tornado 用户密码 bcrypt加密 tornado 结合wtforms使用表单操作 tornado finish和write区别 tornado 使用peewee-async 完成异步orm数据库操作 pyspark streaming简介 和 消费 kafka示例 使用hue创建ozzie的pyspark action workflow
kubernetes service如何通过iptables转发
2024-01-18 · via 郑文峰的博客

# kubernetes service如何通过iptables转发

# 前言

本文主要是介绍kubernetes的service是如何利用iptables来进行流量的转发达到流量的负载均衡的,并会通过实践操作来更好的理解与验证其原理。

# 环境搭建

搭建环境如下图所示:

  1. 创建一个deploy,设置其副本数为3
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demoapp-deployment
  labels:
    app: demoapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demoapp
  template:
    metadata:
      labels:
        app: demoapp
    spec:
      containers:
        - name: ubuntu
          image: ikubernetes/demoapp:v1.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

  1. 创建service,为上面deployment的3个pod进行负载均衡访问
apiVersion: v1
kind: Service
metadata:
  name: demoapp-service
spec:
  selector:
    app: demoapp
  clusterIP: 192.44.140.73
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80

1
2
3
4
5
6
7
8
9
10
11
12
13

  1. 验证

创建完后,环境如下:

# kubectl get pods -n zwf -o wide
NAME                                  READY   STATUS    RESTARTS   AGE     IP
demoapp-deployment-64bdcb8666-4prbk   1/1     Running   0          7d17h   192.33.229.12   dmoc-fa163eee1e30
demoapp-deployment-64bdcb8666-cxmmz   1/1     Running   0          7d17h   192.33.73.139   dmoc-fa163e7188d6
demoapp-deployment-64bdcb8666-kkzsg   1/1     Running   0          7d17h   192.33.206.93   dmoc-fa163ee6bbe1
demoapp-pod                           1/1     Running   0          4d17h   192.33.73.172   dmoc-fa163e7188d6

# kubectl get svc -n zwf
NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
demoapp-service            ClusterIP   192.44.140.73    <none>        80/TCP         7s

1
2
3
4
5
6
7
8
9
10

可以直接在环境中请求service的clusterip地址,会随机访问到三个pod,输出的信息包含了请求IP和目的IP。

# curl 192.44.140.73
iKubernetes demoapp v1.0 !! ClientIP: 10.10.10.16, ServerName: demoapp-deployment-64bdcb8666-kkzsg, ServerIP: 192.33.206.93!

# curl 192.44.140.73
iKubernetes demoapp v1.0 !! ClientIP: 10.65.196.41, ServerName: demoapp-deployment-64bdcb8666-cxmmz, ServerIP: 192.33.73.139!

# curl 192.44.140.73
iKubernetes demoapp v1.0 !! ClientIP: 10.10.10.16, ServerName: demoapp-deployment-64bdcb8666-4prbk, ServerIP: 192.33.229.12!

1
2
3
4
5
6
7
8

# KUBE-SERVICES

PREROUTING​和OUTPUT​链的nat表中,都包含了KUBE-SERVICES​子链,该子链中就包含了serivce的iptables规则的配置。

# iptables -L PREROUTING -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination       
KUBE-SERVICES  all  --  anywhere             anywhere             /* kubernetes service portals */
cali-PREROUTING  all  --  anywhere             anywhere             /* cali:6gwbT8clXdHdC1b1 */

# iptables -L OUTPUT -t nat
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination       
KUBE-SERVICES  all  --  anywhere             anywhere             /* kubernetes service portals */
cali-OUTPUT  all  --  anywhere             anywhere             /* cali:tVnHkvAo15HuiPy0 */

1
2
3
4
5
6
7
8
9
10
11

为什么在PREROUTING和OUTPUT链中添加service规则?

service的作用是负载均衡,也就是需要将IP包进行DNAT操作,而这个操作需要在最靠近发送的位置。网卡收到的包进入到网络协议栈时,最先进入的就是PREROUTING链,也就是节点外部的流量进入的入口。OUTPUT是本地进程发出的包最先进入的链。所以需要在这两条链中添加KUBE-SERVICES,详情可参考快速了解iptables (opens new window)

为什么nat表?

因为要做DNAT操作,所以在nat表。

KUBE-SERVICES下面有许多KUBE-SVC-XXX的子链,每一条子链都是一个集群中一个service的配置,筛选一下

通过筛选demoapp-service​得到下面的子链,只有访问目的地址为192.44.140.73才能进入到该子链中。

# iptables -L KUBE-SERVICES -t nat | grep demoapp-service
KUBE-SVC-FIHIHDNRZEG5VQEQ  tcp  --  anywhere             192.44.140.73        /* zwf/demoapp-service:http cluster IP */ tcp dpt:http

1
2

查看UBE-SVC-FIHIHDNRZEG5VQEQ​链中内容,其中包含了四条子链。

# iptables -L KUBE-SVC-FIHIHDNRZEG5VQEQ -t nat -n
Chain KUBE-SVC-FIHIHDNRZEG5VQEQ (1 references)
target     prot opt source               destination       
KUBE-MARK-MASQ  tcp  -- !192.33.0.0/16        192.44.140.73        /* zwf/demoapp-service:http cluster IP */ tcp dpt:80
KUBE-SEP-FO7YK2K5DAKTCVSE  all  --  0.0.0.0/0            0.0.0.0/0            /* zwf/demoapp-service:http */ statistic mode random probability 0.33333333349
KUBE-SEP-IPKL7NSNTVGKI4T5  all  --  0.0.0.0/0            0.0.0.0/0            /* zwf/demoapp-service:http */ statistic mode random probability 0.50000000000
KUBE-SEP-BPER562ISF3UFYOQ  all  --  0.0.0.0/0            0.0.0.0/0            /* zwf/demoapp-service:http */

1
2
3
4
5
6
7

第一条链是,源IP不在192.33.0.0/16段、目的IP为192.44.140.73并且目的端口是80的数据包会匹配到该条子链进行跳转。其中192.33.0.0/16是pod的网段,如果不是集群内的流量,则会匹配上该条子链,会打上0x4000的标记。

# iptables -L KUBE-MARK-MASQ -t nat -n
Chain KUBE-MARK-MASQ (548 references)
target     prot opt source               destination       
MARK       all  --  0.0.0.0/0            0.0.0.0/0            MARK or 0x4000

1
2
3
4

后面的三条链是为了将流量随机的分配到任意一个pod中。第二条链代表着有1/3的概率会匹配到该链,而第三条链代表着1/2,最后一条链代表着百分百。

看第二条链中的规则,第一条规则是跳转到子链KUBE-MARK-MASQ​,源IP为192.33.206.93的数据包打上了0x4000标记,再将目的IP DNAT成192.33.206.93,说明如果自己访问自己,也会被打上标记。

这里的192.33.206.93是本身service所匹配上的pod IP地址,如果调用自己的service则会匹配上该链,打上0x4000标记。

而第二条规则是一个DNAT操作,会将目标地址改为192.33.206.93:80,也就是pod的地址。

 # iptables -L KUBE-SEP-FO7YK2K5DAKTCVSE -t nat
Chain KUBE-SEP-FO7YK2K5DAKTCVSE (1 references)
target     prot opt source               destination   
KUBE-MARK-MASQ  all  --  192.33.206.93        anywhere             /* zwf/demoapp-service:http */
DNAT       tcp  --  anywhere             anywhere             /* zwf/demoapp-service:http */ tcp to:192.33.206.93:80

1
2
3
4
5

剩下的第3、4条规则也是一样的操作,会将流量DNAT到对应的pod中。

# POSTROUTING

POSTROUTING中包含了一条KUBE-POSTROUTING子链,子链也是由kube-porxy来写入的。

# iptables -L POSTROUTING -t nat
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination       
KUBE-POSTROUTING  all  --  anywhere             anywhere             /* kubernetes postrouting rules */
cali-POSTROUTING  all  --  anywhere             anywhere             /* cali:O3lYWMrLQYEMJtB5 */

1
2
3
4
5

查看子链KUBE-POSTROUTING​内容:

  1. 第一条规则是没有标记为0x400的数据包会return,不执行下面的子链。还记得在KUBE-PREROUTING​中有两次打上0x4000标记,代表着访问是集群外部访问该service或者是源和目的一样,都会往下走。
  2. 第二条规则是对数据包的标记值进行异或,而这里是0x4000与0x4000进行异或,也就是0
  3. 第三条规则时对该数据包进行SNAT,random-fully是对源端口进行随机获取。
# iptables -L KUBE-POSTROUTING -t nat
Chain KUBE-POSTROUTING (1 references)
target     prot opt source               destination       
RETURN     all  --  anywhere             anywhere             mark match ! 0x4000/0x4000
MARK       all  --  anywhere             anywhere             MARK xor 0x4000
MASQUERADE  all  --  anywhere             anywhere             /* kubernetes service traffic requiring SNAT */ random-fully

1
2
3
4
5
6

这里有两种情况会被SNAT,说说为什么。

# SNAT

# 为什么集群外部访问该service需要被SNAT?

数据包需要回包,如果没有SNAT,那么回包的目的IP是客户端的IP,而源IP是Pod的IP,而客户端并不认该数据包,则会被丢弃。

如果有发生SNAT,那么回包的时候可以回到Node1的节点,再回到client。

但问题又来了,是怎么从Node1再回包到client的呢?

# conntrack

conntrack是一个追踪表,在每一个数据包进入到netfilter的时候,都会记录一条记录,当我们进行SNAT和DNAT的时候也都会更新该表中的记录。

所以在Node1回包给client时候,可以通过查询这个表中的记录,然后再次SNAT和DNAT恢复回去就可以进行正常的回包了。

可以看下conntrack里面的记录长什么样子。

先在某一台节点上curl service的ip,上图的client其实也是在Node1上的,因为Node1上有两张网卡,Node1和Node2的通信使用的网卡eth2 ip为10.10.10.16,因为转发到其他节点,所以会被SNAT。

# curl 192.44.140.73
iKubernetes demoapp v1.0 !! ClientIP: 10.10.10.16, ServerName: demoapp-deployment-64bdcb8666-4prbk, ServerIP: 192.33.229.12!

1
2

这里主要是看src和dst分别是client请求service的目的IP,然后后面又有一个src和dst它是由Node2回包给Node1的源和目的IP,也就是源是pod的IP,dst是Node1节点IP。

前面是发送包的方向,后面是回包的方向。通过查询该表,即可以从Node1回包给client

# conntrack -L | grep 192.33.229.12
tcp      6 107 TIME_WAIT src=10.65.196.41 dst=192.44.140.73 sport=33468 dport=80 src=192.33.229.12 dst=10.10.10.16 sport=80 dport=18623 [ASSURED] mark=0 use=1

1
2

# 源和目的IP相同

当pod访问service时,正好DNAT成自己的IP,那么就存在源和目的IP一样的情况。

这种数据包是很特殊的存在,网络设备可能会将这种数据包视为无效或者异常的包被丢弃,最好不要一样,所以会将源IP SNAT为主机的IP,解决该问题。

实验

当前节点的IP为:10.65.196.41,然后进入在当前节点的Pod内,当前pod的ip为:192.33.73.139

kubectl exec -it -n zwf demoapp-deployment-64bdcb8666-cxmmz -- sh

1
2

进行多次curl service ip,当某一次请求到本POD的IP时,查看其ClientIP,发现其源IP是:10.65.196.41,这个当前节点的IP

# curl 192.44.140.73
iKubernetes demoapp v1.0 !! ClientIP: 10.65.196.41, ServerName: demoapp-deployment-64bdcb8666-cxmmz, ServerIP: 192.33.73.139!

1
2

# NodePort

上面介绍的是serivi type=ClusterIP方式的,那么NodePort有什么区别呢?

# 搭建环境

先将clusterIP的service删了,防止干扰。

kubectl delete svc -n zwf demoapp-service

1

创建NodePort的service文件demo_service_nodeport.yaml

apiVersion: v1
kind: Service
metadata:
  name: demoapp-nodeport-service
spec:
  selector:
    app: demoapp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
  type: NodePort

1
2
3
4
5
6
7
8
9
10
11
12
13

创建Service

# kubectl apply -n zwf -f demo_service_nodeport.yaml 

# kubectl get svc -n zwf
NAME                       TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
demoapp-nodeport-service   NodePort   192.44.152.223   <none>        80:30337/TCP   64s

1
2
3
4
5

# 请求clusterip

通过clusterip+端口的方式请求service,原理和上面将是一样的。

在POSTROUTING和OUTPUT中添加了KUBE-SERVICES链,其中包含了新增的service对应的KUBE-SVC-XXX子链,当访问的是其clusterip时,则匹配上该链。在其中包含了从多个pod中随机选择一个进行DNAT操作,打上标记,在POSTROUTING中再SNAT。

# iptables -L PREROUTING -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination       
KUBE-SERVICES  all  --  anywhere             anywhere             /* kubernetes service portals */
cali-PREROUTING  all  --  anywhere             anywhere             /* cali:6gwbT8clXdHdC1b1 */


# iptables -L KUBE-SERVICES -t nat | grep zwf
KUBE-SVC-YSWRJGOK5VEB6HOG  tcp  --  anywhere             192.44.152.223       /* zwf/demoapp-nodeport-service:http cluster IP */ tcp dpt:http

# iptables -L KUBE-SVC-YSWRJGOK5VEB6HOG -t nat |grep zwf
KUBE-MARK-MASQ  tcp  -- !192.33.0.0/16        192.44.152.223       /* zwf/demoapp-nodeport-service:http cluster IP */ tcp dpt:http
KUBE-MARK-MASQ  tcp  --  anywhere             anywhere             /* zwf/demoapp-nodeport-service:http */ tcp dpt:30337
KUBE-SEP-NMMBRSZXI4OIWDPB  all  --  anywhere             anywhere             /* zwf/demoapp-nodeport-service:http */ statistic mode random probability 0.33333333349
KUBE-SEP-PXDORKZI3GD2RUMC  all  --  anywhere             anywhere             /* zwf/demoapp-nodeport-service:http */ statistic mode random probability 0.50000000000
KUBE-SEP-YLPOR67MTHPHIZAB  all  --  anywhere             anywhere             /* zwf/demoapp-nodeport-service:http */

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 请求节点IP

但是如果不是通过ClusterIP而是通过节点IP请求service呢,它是怎么做的?

因为是节点IP,所以无法匹配上该service的KUBE-SVC-XXX子链,但是有一条KUBE-NODEPORTS

# iptables -L KUBE-SERVICES  -t nat | grep KUBE-NODEPORTS
KUBE-NODEPORTS  all  --  anywhere             anywhere             /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL

1
2

在其中包含了一条子链KUBE-SVC-YSWRJGOK5VEB6HOG​,只要目的端口为30337就能匹配上,而这个端口正好就是demoapp-nodeport-service​的NodePort端口。

这条子链和上面匹配clusterIP时是同一个链,都是进行一个DNAT操作。

# # iptables -L KUBE-NODEPORTS -t nat | grep zwf
KUBE-SVC-YSWRJGOK5VEB6HOG  tcp  --  anywhere             anywhere             /* zwf/demoapp-nodeport-service:http */ tcp dpt:30337

1
2

# 参考链接