深入理解Kubernetes中Service的实现

iptables 是 Kubernetes 中 Service 的默认实现,了解 iptables 是理解 Service 工作原理不可缺少的一步。iptables 的构成在对包处理的过程中,内核添加了对包自定义处理的操作,这些自定义操作以链的形式存在,而链由表组成;每条链由不同的表组成,每个表由若

iptables 是 Kubernetes 中 Service 的默认实现,了解 iptables 是理解 Service 工作原理不可缺少的一步。

iptables 的构成

在对包处理的过程中,内核添加了对包自定义处理的操作,这些自定义操作以的形式存在,而组成;

每条链由不同的表组成,每个表由若干条规则构成;

每条链中同名的表相互独立,表名用来区分规则的作用。

5 条链

PREROUTING 接收包时执行的第一个链,执行完后进行路由选择

INPUT 路由选择后发现是本机的包时,会执行该链

FORWARD 接收到数据包,经 PREROUTING 处理并进行路由选择后,发现非本机数据包,进行转发处理时,执行此链

OUTPUT 发送数据包,进行路由选择之后,遇到的第一个链

POSTROUTING 紧跟 OUTPUT 链执行

4 张表

raw目的:将命中规则的包,跳过其它表的处理,它的优先级最高。 位置:由于其目的是跳过其它表,所以只需要在接收和发送两大过程的最开头处把关,所以只需要用到 PREROUTINGOUTPUT 两个钩子。

mangle目的:根据规则修改数据包的一些标志位,比如 TTL 位置:有可能会在任意位置都有可能会修改网络包,所以它是用到了全部的钩子位置。

nat目的:实现网络地址转换 位置:分为 SNAT(Source NAT)和 DNAT(Destination NAT)两种,可能会工作在 PREROUTINGINPUT、OUTPUT、POSTROUTING 四个位置。

filter目的:过滤某些包,这是防火墙工作的基础 位置:只在 INPUTOUTPUTFORWARD 这三步中工作就够了

链表之间的关系

hG9KjU

iptables 的工作场景

接收网络包

PREROUTING链 -> 路由判断(是本机)-> INPUT链 -> ...

转发网络包

PREROUTING链 -> 路由判断(不是本设备,找到下一跳) -> FORWARD链 -> POSTROUTING链 -> ...

发送网络包

路由选择 -> OUTPUT链 -> POSTROUTING链 -> ...

iptables 的工作流程与契机

简化的包处理流程

数据接收过程走的是 1 和 2

发送过程走的是 4 、5

转发过程是 1、3、5

PDkV4g

完整的包处理流程

完整的包流量走向图

包处理的流程图

wcEZ10

iptables 的常用命令

常用参数

源地址:-s 192.168.1.0/24

目标地址:-d 192.168.1.11

协议:-p tcp|udp|icmp

从哪个网卡进来:-i eth0|lo

从哪个网卡出去:-o eth0|lo

目标端口(必须制定协议):-p tcp|udp --dport 8080

源端口(必须制定协议):-p tcp|udp --sport 8080

iptables

iptables -t ${表名}  ${Commands} ${链名}  ${链中的规则号} ${匹配条件} ${目标动作}

表名:4张表,filternatmangleraw

Commands:尾部追加-A、检查-C、删除-D、头部插入-I、替换-R、查看全部-L、清空-F、新建-N、默认规则-P(默认为ACCEPT)

链名:5条链,PREROUTINGINPUTFORWOARDOUTPUTPOSTROUTING

匹配条件-p协议、-4 IPv4协议包-6 IPv6 协议包-s 源地址-d 目标地址-i/-o 进/出网络接口名--dport/--sport 目标/源端口

目标动作:拒绝访问-j REJECT、允许通过-j ACCEPT、丢弃-j DROP、记录日志 -j LOG、源地址转换-j snat、目标地址转换-j dnat、还有RETURNQUEUE

示例:

iptables -t nat -A POSTROUTING -s 192.168.0.0/24 ! -o br0 -j MASQUERADE

SNAT 源地址替换

iptables -t nat -A PREROUTING  ! -i br0 -p tcp -m tcp --dport 8088 -j DNAT --to-destination 192.168.0.2:80

DNAT 目的地址转换

iptables -I INPUT -s 121.0.0.0/8 -j DROP

封禁

iptables -I INPUT -s 121.0.0.0/8 -j DROP

解封

iptables -t filter -I INPUT -s 1.2.3.4 -p tcp --dport 22 -j ACCEPT

iptables -t filter -I INPUT -p tcp --dport 22 -j DROP

Kubernetes Service 的原理

相关网段分别为

  • Pod CIDR:10.244.0.0/16
  • Service CIDR:10.96.0.0/12

创建一个测试 Deployment 和 Service,

# 镜像为 ARM 架构
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: http-server
  name: http-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: http-server
  template:
    metadata:
      labels:
        app: http-server
    spec:
      containers:
      - image: ghcr.io/joengjyu/simple-http-server:20221009
        name: simple-http-server
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: http-server
  name: http-svc
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: http-server
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: http-server
  name: http-svc-node-port
spec:
  type: NodePort
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: http-server
EOF

结果如下

$ kubectl get pod,svc
NAME                                         READY   STATUS    RESTARTS   AGE   IP
pod/http-server-7b59bbfffc-bxcvs   1/1     Running   0          3m12s   10.244.2.9
pod/http-server-7b59bbfffc-f7cgt   1/1     Running   0          3m12s   10.244.1.7
pod/http-server-7b59bbfffc-wpfb2   1/1     Running   0          3m12s   10.244.2.8

NAME                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)
service/http-svc             ClusterIP   10.107.38.142   <none>        80/TCP
service/http-svc-node-port   NodePort    10.108.51.223   <none>        80:32054/TCP

然后通过 iptables-save 命令得到所有链、表上的规则,相关规则可概括为:

KUBE-SERVICES 或者 KUBE-NODEPORTS 规则对应的 Service 的⼊⼝链,这个规则应该与 VIP 和 Service 端⼝⼀⼀对应;

KUBE-SEP-(hash) 规则对应的 DNAT 链,这些规则应该与 Endpoints ⼀⼀对应;

KUBE-SVC-(hash) 规则对应的负载均衡链,这些规则的数⽬应该与 Endpoints 数⽬⼀致;

如果是 NodePort 模式的话,还有 POSTROUTING 处的 SNAT 链。

Cluster IP 类型

其中与 Service 相关的规则如下:

  1. PREROUTINGOUTPUT 链中的 NAT 表中其他规则处理完之后,跳转到自定义链 KUBE-SERVICES 上;
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
  1. 自定义链 KUBE-SERVICES 上有对 Service http-svc 的 Cluster IP 做处理,如果目标 IP 是 10.107.38.142/32 且端口为80,则交给自定义链 KUBE-SVC-GM62ZXCOIF6ZCRWU 处理;
-A KUBE-SERVICES -d 10.107.38.142/32 -p tcp -m comment --comment "default/http-svc cluster IP" -m tcp --dport 80 -j KUBE-SVC-GM62ZXCOIF6ZCRWU
-A KUBE-SERVICES -d 10.108.51.223/32 -p tcp -m comment --comment "default/http-svc-node-port cluster IP" -m tcp --dport 80 -j KUBE-SVC-C3VEVZBK3X2BH5IZ
  1. 由于存在两个 Pod,因此在负载均衡链 KUBE-SVC-ZV76JU53N5UCJJE6 上可以看到两条主要的规则,其中第一条以30%的概率执行,第二条是50%的概率执行,第三条100%执行(由于这两条 iptables 规则存在先后顺序,且如果第一条执行后,第二条就不会执行,依次类推)。
-A KUBE-SVC-GM62ZXCOIF6ZCRWU ! -s 10.244.0.0/16 -d 10.107.38.142/32 -p tcp -m comment --comment "default/http-svc cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-GM62ZXCOIF6ZCRWU -m comment --comment "default/http-svc -> 10.244.1.7:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-C6IB5RPVIEGEDGUL
-A KUBE-SVC-GM62ZXCOIF6ZCRWU -m comment --comment "default/http-svc -> 10.244.2.8:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-NVFETS636V4CPTUN
-A KUBE-SVC-GM62ZXCOIF6ZCRWU -m comment --comment "default/http-svc -> 10.244.2.9:80" -j KUBE-SEP-IHI45R5HV5QHYZU6
  1. 在 DNAT 链上,可以看到将目标地址换成了对应 Pod 的 IP 地址。
-A KUBE-SEP-C6IB5RPVIEGEDGUL -s 10.244.1.7/32 -m comment --comment "default/http-svc" -j KUBE-MARK-MASQ
-A KUBE-SEP-C6IB5RPVIEGEDGUL -p tcp -m comment --comment "default/http-svc" -m tcp -j DNAT --to-destination 10.244.1.7:80

-A KUBE-SEP-NVFETS636V4CPTUN -s 10.244.2.8/32 -m comment --comment "default/http-svc" -j KUBE-MARK-MASQ
-A KUBE-SEP-NVFETS636V4CPTUN -p tcp -m comment --comment "default/http-svc" -m tcp -j DNAT --to-destination 10.244.2.8:80

-A KUBE-SEP-IHI45R5HV5QHYZU6 -s 10.244.2.9/32 -m comment --comment "default/http-svc" -j KUBE-MARK-MASQ
-A KUBE-SEP-IHI45R5HV5QHYZU6 -p tcp -m comment --comment "default/http-svc" -m tcp -j DNAT --to-destination 10.244.2.9:80

NodePort 类型

跳转到 NodePort 链 KUBE-NODEPORTS 的时机:

  • 本机从网卡中接受数据包
  • 做为 KUBE-SERVICES 链条的守门员
-A INPUT -m comment --comment "kubernetes health check service ports" -j KUBE-NODEPORTS
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS

KUBE-NODEPORTS 链条详情

-A KUBE-NODEPORTS -p tcp -m comment --comment "default/http-svc-node-port" -m tcp --dport 32054 -j KUBE-EXT-C3VEVZBK3X2BH5IZ

-A KUBE-EXT-C3VEVZBK3X2BH5IZ -m comment --comment "masquerade traffic for default/http-svc-node-port external destinations" -j KUBE-MARK-MASQ
-A KUBE-EXT-C3VEVZBK3X2BH5IZ -j KUBE-SVC-C3VEVZBK3X2BH5IZ

跳转到 KUBE-SVC-C3VEVZBK3X2BH5IZ 链后,接下来的处理方式与 http-svc-node-port 的 ClusterIP 方式一致。

Reference

Read more

容器镜像(4):镜像的常用工具箱

容器镜像(4):镜像的常用工具箱

前几篇在讲多架构镜像时已经用过 skopeo 和 crane 做镜像复制,这篇系统整理这两个工具的完整能力,同时介绍几个日常操作镜像时同样好用的工具。 一、skopeo:不依赖 Daemon 的镜像瑞士军刀 skopeo 的核心价值是绕过 Docker daemon,直接与 Registry API 交互。上一篇用它做镜像复制和离线传输,但它的能力远不止于此。 1.1 安装 # Ubuntu / Debian sudo apt install -y skopeo skopeo --version # skopeo version 1.15.1 1.2 inspect:免拉取检查镜像元数据 docker inspect 需要先把镜像拉到本地,skopeo inspect 直接向 Registry

容器镜像(3):多架构镜像构建

容器镜像(3):多架构镜像构建

一、什么是多架构镜像 1.1 OCI Image Index 上一篇介绍了单平台镜像的结构:一个 Manifest 指向 Config 和若干 Layer blob。多架构镜像在此之上多了一层——OCI Image Index(也叫 Manifest List),是一个轻量的索引文件,把多个单平台 Manifest 组织在一起: $ docker manifest inspect golang:1.22-alpine { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests&

容器镜像(2):containerd 视角下的镜像

容器镜像(2):containerd 视角下的镜像

一、为什么需要了解 containerd 如果你只用 docker run 跑容器,从来不关心底层,那可以不了解 containerd。但如果你在用 Kubernetes,或者想真正理解"容器运行时"是什么,containerd 是绕不开的。 事实上,当你执行 docker run 的时候,containerd 早就在后台悄悄工作了——Docker 从 1.11 版本开始,就把核心运行时剥离出来交给 containerd 负责。 1.1 Docker 的架构演变 早期的 Docker(1.10 及之前)是一个"大一统"的单体程序:一个 dockerd