[译] Packet 在 Kubernetes 的生命周期-第三部分

原文地址: https://dramasamy.medium.com/life-of-a-packet-in-kubernetes-part-3-dd881476da0f

这是系列文章“Packet 在 Kubernetes 的生命周期”的第3部分。我们将解剖 Kubernetes 的 kube-proxy 组件如何使用 iptables 来控制流量。了解 kube-proxy 在 Kubernetes 环境中的作用以及它如何使用 iptables 控制流量非常重要。

注意:还有许多其他的插件/工具可以控制流量,但在本文中我们将重点关注 kube-proxy + iptables 的组合。

我们将从 Kubernetes 提供的各种通信模型及其实现开始。如果您已经熟悉 “Service”、“ClusterIP” 和 “NodePort” 这些词的概念,请直接跳到 kube-proxy/iptables 部分。

Pod 间通信

kube-proxy 不参与 Pod 间的通信,因为 CNI 会为 Node 和 Pod 配置所需的路由。所有容器都可以与所有其他容器进行通信,不需要 NAT。所有节点都可以与所有容器(反之亦然)通信,不需要 NAT。

注意:Pod 的 IP 地址不是静态的(有方法可以获取静态 IP,但是默认配置不能保证 IP 地址静态)。CNI 会在 Pod 重启时分配新的 IP 地址,因为 CNI 没有维护 IP 地址和 Pod 的映射。Pod 的名称在 Deployment 中也不是静态的。

从实际角度来看,Deployment 中的 Pod 应该使用负载均衡类型的实体来暴露应用程序,因为该应用程序是无状态的,并且会有多个 Pod 托管该应用程序。在 Kubernetes 中,负载均衡类型的实体称为“Service”。

Pod 到外部的通信

对于从 Pod 流向外部地址的流量,Kubernetes 使用 SNAT。它所做的就是用主机的 IP:端口替换 Pod 的内部源 IP:端口。当返回数据包回到主机时,它将 Pod 的 IP:端口重写为目标,并将其发送回原始 Pod。整个过程对于原始 Pod 来说是透明的,它不知道地址转换。

Pod 到 Service 的通信

ClusterIP

Kubernetes 有一个称为“Service”的概念,它只是一个位于 Pod 前面的 L4 负载均衡器。有几种不同类型的 Service。最基本的类型称为 ClusterIP。这种类型的 Service 有一个唯一的 VIP 地址,该地址只在集群内可路由。

仅使用 Pod IP 来发送流量到特定应用程序并不容易。Kubernetes 集群的动态特性意味着 Pod 可以被移动、重启、升级或扩展。此外,一些服务将有许多副本,所以我们需要某种方式在它们之间进行负载均衡。

Kubernetes 通过 Service 来解决这个问题。Service 是一个 API 对象,它将一组 Pod IP 映射到一个虚拟 IP (VIP)。此外,Kubernetes 为每个服务的名称和虚拟 IP 提供了 DNS 条目,以便可以轻松地通过名称寻址服务。

节点上的 kube-proxy 进程协调了集群内虚拟 IP 到 Pod IP 的映射。该流程设置 iptables 或 IPVS 以自动将 VIP 转换为 Pod IP,然后再将数据包发送到集群网络。跟踪了各个连接,以便在返回时可以适当地取消数据包的翻译。IPVS 和 iptables 可以对单个服务虚拟 IP 进行负载均衡到多个 Pod IP,尽管 IPVS 在负载均衡算法方面有更大的灵活性。虚拟 IP 实际上并不存在于系统接口中;它存在于 iptable 中。

从 Kubernetes 文档中对“Service”的定义 —— 以一种抽象的方式将在一组 Pod 上运行的应用程序作为为网络服务公开。使用 Kubernetes, 不需要修改应用程序以使用不熟悉的服务发现机制。Kubernetes 为 Pod 提供独立的 IP 地址, 将多个 Pod 定义成一组并提供单个 DNS 名称, 并可以在它们之间进行负载均衡。

前端部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
labels:
app: webapp
spec:
replicas: 2
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: nginx
image: nginx:1.14.2
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80

后端部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth
labels:
app: auth
spec:
replicas: 2
selector:
matchLabels:
app: auth
template:
metadata:
labels:
app: auth
spec:
containers:
- name: nginx
image: nginx:1.14.2
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80

服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
---
apiVersion: v1
kind: Service
metadata:
name: frontend
labels:
app: frontend
spec:
ports:
- port: 80
protocol: TCP
type: ClusterIP
selector:
app: webapp
---
apiVersion: v1
kind: Service
metadata:
name: backend
labels:
app: backend
spec:
ports:
- port: 80
protocol: TCP
type: ClusterIP
selector:
app: auth

现在,前端的 Pod 可以通过 Kubernetes 添加的 ClusterIP 或 DNS 条目连接到后端。诸如 CoreDNS 之类的支持集群的 DNS 服务器会监视 Kubernetes API 以获取新服务并为每个服务创建一组 DNS 记录。如果在整个集群中启用了 DNS,则所有 Pod 应该可以通过其 DNS 名称自动解析服务。

NodePort(外部到 Pod 的通信)

现在我们有了可以在集群内的服务之间进行通信的 DNS。然而,外部请求无法访问集群内的服务,因为 IP 地址是虚拟的和专用的。

让我们试图从外部服务器访问 frontEnd Pod IP 地址。(注意:此时还没有为 FrontEnd 服务创建服务)

无法访问 Pod IP,因为它是一个不可路由的专用 IP 地址。

让我们创建一个 NodePort 服务将 FrontEnd 服务暴露给外部世界。如果将 type 字段设置为 NodePort,Kubernetes 控制平面会从 --service-node-port-range 标志指定的范围内分配一个端口(默认值:30000-32767)。每个节点都会将该端口(每个节点上的相同端口号)代理到您的服务中。您的服务会在其 .spec.ports[*].nodePort 字段中展示分配的端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
type: NodePort
selector:
app: webapp
ports:
# By default and for convenience, the `targetPort` is set to the same value as the `port` field.
- port: 80
targetPort: 80
# Optional field
# By default and for convenience, the Kubernetes control plane will allocate a port from a range (default: 30000-32767)
nodePort: 31380
...


现在我们可以通过 <anyClusterNode>:<nodePort> 访问前端服务。如果您想指定一个特定的端口号,可以在 nodePort 字段中指定一个值。控制层将为您分配该端口,或者报告 API 事务失败。需要注意可能的端口冲突, 设置的端口需要在 NodePort 使用的范围内。

外部流量策略

ExternalTrafficPolicy 配置了服务外部流量的路由方式, 是到节点本地还是集群范围的端点。“Local” 保留客户端源 IP 并避免 NodePort 类型服务的第二次跳转,但有可能造成流量传播不平衡。而“Cluster” 会掩盖客户端源 IP 并可能需要二次跳转到另一个节点的上,但应具有整体良好的负载平衡效果。

集群流量策略

这是 Kubernetes 服务的默认外部流量策略。这里的假设是,您始终希望将流量路由到运行服务的所有 Pod(跨所有节点),并进行均等分配。

使用此策略的一个警告是,当您导入外部流量时,您可能会在节点之间看到不必要的网络跳转。例如,如果您通过 NodePort 接收外部流量,则 NodePort SVC 可能会(随机)将流量路由到另一个主机上的 Pod,而本可以将流量路由到同一主机上的 Pod,避免额外的网络跳转。

Cluster 流量策略中,数据包流如下:

  • 客户端将数据包发送到 node2:31380
  • node2 用自己的 IP 地址替换数据包中的源 IP 地址(SNAT)
  • node2 将数据包上的目标 IP 替换为 pod IP
  • 数据包被路由到节点 1 或 3,然后到端点
  • pod 的回复被路由回 node2
  • pod 的回复被发送回客户端

本地流量策略

如果在服务上设置 externalTrafficPolicy: Local,Kubernetes API 将要求您使用 LoadBalancer 或 NodePort 类型。这是因为“Local”外部流量策略仅与外部流量相关,该流量仅适用于这两种类型。

当使用这个流量策略时,kube-proxy 只会在 Pod 所在的节点上相应的 NodePort(30000-32767) 端口上添加代理规则, 并且不会将流量转发到其他节点。这种方法可以保留原始源 IP 地址。如果没有本地端点,发送到节点的数据包将被丢弃,所以您可以依赖任何数据包处理规则中正确的源 IP,这些规则可能应用于使数据包到达端点的数据包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
---
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
type: NodePort
externalTrafficPolicy: Local
selector:
app: webapp
ports:
# By default and for convenience, the `targetPort` is set to the same value as the `port` field.
- port: 80
targetPort: 80
# Optional field
# By default and for convenience, the Kubernetes control plane will allocate a port from a range (default: 30000-32767)
nodePort: 31380
...

本地流量策略中的数据包流如下:

  • 客户端将数据包发送到 node1:31380,其中确实端点
  • node1 使用正确的源 IP 将数据包路由到端点
  • node1 不会将数据包路由到 node3,因为策略是 Local
  • 客户端向 node2:31380 发送数据包,其中没有任何端点
  • 数据包被丢弃


负载均衡器服务类型下的本地流量策略

如果您在 Google Kubernetes Engine/GCE 上运行,将 service.spec.externalTrafficPolicy 字段设置为 Local 会强制没有服务端点的节点通过故意失败运行状况检查来从负载均衡流量的候选节点列表中删除自己。所以不会有任何流量下降。对于大量导入外部流量的应用程序来说,这种模型很好,并避免了网络上的不必要跳转以减少延迟。我们还可以保留真实的客户端 IP,因为我们不再需要从代理节点 SNAT 流量!然而,Kubernetes 文档中提到的使用“本地”外部流量策略的最大缺点是,流量到您的应用程序可能不平衡。

Kube-Proxy(iptables 模式)

在 Kubernetes 中实现“服务”的组件称为 kube-proxy。它位于每个节点上,并编程复杂的 iptables 规则来过滤 Pod 和服务之间的所有类型的过滤和 NAT。如果您登录 Kubernetes 节点并输入 iptables-save,您会看到 Kubernetes 或其他程序插入的规则。最重要的链是 KUBE-SERVICESKUBE-SVC-*KUBE-SEP-*

  • KUBE-SERVICES 是服务数据包的入口点。它所做的就是匹配目标 IP:端口 并将数据包分发到相应的 KUBE-SVC-* 链。
  • KUBE-SVC-* 链充当负载均衡器,并将数据包平均分发到 KUBE-SEP-*链。每个 KUBE-SVC-* 都有与其后面的端点数量一样多的 KUBE-SEP-* 链。
  • KUBE-SEP-* 链表示一个 Service EndPoint。它简单地执行 DNAT,用 pod 的端点 IP:Port 替换服务 IP:port。

对于 DNAT,连接跟踪会启动并使用状态机跟踪连接状态。需要状态是因为它需要记住它改变的目标地址,并在返回的数据包返回时将其改回。Iptables 还可以依赖 conntrack 状态(ctstate)来决定数据包的命运。那4个 conntrack 状态特别重要:

  • NEW:conntrack 对此数据包一无所知,这发生在接收到 SYN 数据包时。
  • ESTABLISHED:conntrack 知道数据包属于已建立的连接,这发生在握手完成后。
  • RELATED:数据包不属于任何连接,但它与另一个连接相关联,这对于像 FTP 这样的协议特别有用。
  • INVALID:数据包有问题,conntrack 不知道如何处理它。此状态在此 Kubernetes 问题中起着核心作用。

这就是 Pod 和服务之间的 TCP 连接的工作方式;事件序列如下:

  • 客户端 Pod 从左边发送一个数据包到服务:2.2.2.10:80
  • 数据包通过客户端节点中的 iptables 规则,并将目标更改为 pod IP,1.1.1.20:80
  • 服务器 Pod 处理数据包并发送回一个目标地址为 1.1.1.10 的数据包
  • 数据包回到客户端节点,conntrack 识别数据包并将源地址重写回 2.2.2.10:80
  • 客户端 Pod 收到响应数据包

iptables

在 Linux 操作系统中,使用 netfilter 进行防火墙。这是一个内核模块,它决定允许哪些数据包进入或退出。iptables 只是 netfilter 的接口。这两者经常被认为是一回事。更好的角度是把它看作是一个后端(netfilter)和一个前端(iptables)。

每个链负责一个特定的任务:

  • PREROUTING:这个链决定数据包一到达网络接口要发生什么。我们有不同的选择,比如改变数据包(可能是为了NAT)、丢弃数据包,或者完全不做任何事情并让它通过别的方式继续处理。

  • INPUT:这是最流行的链之一,因为它几乎总是包含严格的规则,以避免互联网上的一些歹徒伤害我们的计算机。如果您想打开/阻止一个端口,这里是您要做的地方。

  • FORWARD:此链负责数据包转发。这正是其名称所暗示的。我们可能希望将计算机视为路由器,此处可能会应用一些规则来完成此任务。

  • OUTPUT:此链负责您的所有网页浏览等许多其他内容。在此链允许的情况下,您无法发送单个数据包。您有很多选择,无论您是否希望允许端口进行通信。如果您不确定每个应用程序正在通过哪个端口进行通信,那么这是限制出站流量的最佳位置。

  • POSTROUTING:在离开我们的计算机之前,数据包在此链中留下最后的痕迹。这用于路由等许多其他任务,只是为了确保按我们希望的方式处理数据包。

FORWARD 链仅在 Linux 服务器中启用 ip_forward 时才能工作,这就是在设置和调试 Kubernetes 集群时以下命令很重要的原因。

1
2
3
4
node-1# sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1
node-1# cat /proc/sys/net/ipv4/ip_forward
1

上述更改不是持久的。要在 Linux 系统上永久启用 IP 转发,请编辑 /etc/sysctl.conf 并添加以下行:

1
net.ipv4.ip_forward = 1

我们将重点关注 NAT 表,但以下是可用表。

  • Filter:这是默认表。在此表中,将决定数据包是否允许进入/离开计算机。如果您想阻止端口以停止接收任何内容,这就是所要操作的表。

  • Nat:这是第二个最流行的表,负责创建新连接。这是网络地址转换(Network Address Translation)的简称。如果您不熟悉该术语,不用担心。我会在下面给你一个例子。

  • Mangle:仅用于专门的数据包。此表用于在传入或离开之前更改数据包中的某些内容。

  • Raw:如其名所示,此表处理原始数据包。主要用于跟踪连接状态。当我们想允许来自 SSH 连接的成功数据包时,下面会看到此表的示例。

  • Security:它负责在过滤表之后保护您的计算机。其中包含 SELinux。如果您不熟悉该术语,它是现代 Linux 发行版上的一个强大安全工具。

请阅读 THIS 文章以获取有关 iptables 的更详细信息。

Kubernetes 中的 iptable 配置

让我们在 minikube 中 设置 replica count 为 2 部署一个 Nginx 应用程序并输出 iptable 规则。

ServiceType:NodePort

1
2
3
4
5
6
7
master # kubectl get svc webapp
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
webapp NodePort 10.103.46.104 <none> 80:31380/TCP 3d13h
master # kubectl get ep webapp
NAME ENDPOINTS AGE
webapp 10.244.120.102:80,10.244.120.103:80 3d13h
master #

ClusterIP 在任何地方都不存在,它是一个存在于 iptable 中的虚拟 IP,Kubernetes 在 CoreDNS 中添加了一个 DNS 条目。

为了挂钩数据包过滤和 NAT,Kubernetes 将从 iptables 创建一个自定义链 KUBE-SERVICES;它将所有 PREROUTING 和 OUTPUT 流量重定向到自定义链 KUBE-SERVICES, 如下所示

1
2
3
4
5
6
$ sudo iptables -t nat -L PREROUTING | column -t
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
cali-PREROUTING all -- anywhere anywhere /* cali:6gwbT8clXdHdC1b1 */
KUBE-SERVICES all -- anywhere anywhere /* kubernetes service portals */
DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL

在使用 KUBE-SERVICES 链钩入数据包过滤和 NAT 之后,Kubernetes 可以检查其服务的流量并相应地应用 SNAT/DNAT。在 KUBE-SERVICES 链的末尾,它将设置另一个自定义链 KUBE-NODEPORTS 来处理特定服务类型 NodePort 的流量。

如果流量是为 ClusterIP,那么 KUBE-SVC-2IRACUALRELARSND 链将处理该流量;否则,下一个链将处理该流量,即 KUBE-NODEPORTS

1
2
3
4
5
6
$ sudo iptables -t nat -L KUBE-SERVICES | column -t
Chain KUBE-SERVICES (2 references)
target prot opt source destination
KUBE-MARK-MASQ tcp -- !10.244.0.0/16 10.103.46.104 /* default/webapp cluster IP */ tcp dpt:www
KUBE-SVC-2IRACUALRELARSND tcp -- anywhere 10.103.46.104 /* default/webapp cluster IP */ tcp dpt:www
KUBE-NODEPORTS all -- anywhere anywhere /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL

我们检查 KUBE-NODEPORTS 的链部分,

1
2
3
4
5
$ sudo iptables -t nat -L KUBE-NODEPORTS | column -t
Chain KUBE-NODEPORTS (1 references)
target prot opt source destination
KUBE-MARK-MASQ tcp -- anywhere anywhere /* default/webapp */ tcp dpt:31380
KUBE-SVC-2IRACUALRELARSND tcp -- anywhere anywhere /* default/webapp */ tcp dpt:31380

从这一点开始,ClusterIP 和 NodePort 的处理是相同的。请查看以下 iptables 流程图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# statistic  mode  random -> Random load-balancing between endpoints.
$ sudo iptables -t nat -L KUBE-SVC-2IRACUALRELARSND | column -t
Chain KUBE-SVC-2IRACUALRELARSND (2 references)
target prot opt source destination
KUBE-SEP-AO6KYGU752IZFEZ4 all -- anywhere anywhere /* default/webapp */ statistic mode random probability 0.50000000000
KUBE-SEP-PJFBSHHDX4VZAOXM all -- anywhere anywhere /* default/webapp */

$ sudo iptables -t nat -L KUBE-SEP-AO6KYGU752IZFEZ4 | column -t
Chain KUBE-SEP-AO6KYGU752IZFEZ4 (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 10.244.120.102 anywhere /* default/webapp */
DNAT tcp -- anywhere anywhere /* default/webapp */ tcp to:10.244.120.102:80

$ sudo iptables -t nat -L KUBE-SEP-PJFBSHHDX4VZAOXM | column -t
Chain KUBE-SEP-PJFBSHHDX4VZAOXM (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 10.244.120.103 anywhere /* default/webapp */
DNAT tcp -- anywhere anywhere /* default/webapp */ tcp to:10.244.120.103:80

$ sudo iptables -t nat -L KUBE-MARK-MASQ | column -t
Chain KUBE-MARK-MASQ (24 references)
target prot opt source destination
MARK all -- anywhere anywhere MARK or 0x4000

注:截取输出以仅显示阅读所需的规则。

ClusterIP:

KUBE-SERVICES → KUBE-SVC-XXX → KUBE-SEP-XXX

NodePort:

KUBE-SERVICES → KUBE-NODEPORTS → KUBE-SVC-XXX → KUBE-SEP-XXX

注意:NodePort 服务将分配一个 ClusterIP 来处理内部和外部流量。

上述 iptables 规则的可视化表示,

外部流量策略:Local

正如之前讨论的,使用“externalTrafficPolicy: Local”将保留源IP,并丢弃来自没有本地端点的代理节点的数据包。让我们看一下没有本地端点的节点中的iptable规则。

1
2
3
4
master # kubectl get nodes
NAME STATUS ROLES AGE VERSION
minikube Ready master 6d1h v1.19.2
minikube-m02 Ready <none> 85m v1.19.2

部署配置 externalTrafficPolicy Local 的Nginx

1
2
3
master # kubectl get pods nginx-deployment-7759cc5c66-p45tz -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-7759cc5c66-p45tz 1/1 Running 0 29m 10.244.120.111 minikube <none> <none>

检查 externalTrafficPolicy,
1
2
master # kubectl get svc webapp -o wide -o jsonpath={.spec.externalTrafficPolicy}
Local

获得服务信息
1
2
3
master # kubectl get svc webapp -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
webapp NodePort 10.111.243.62 <none> 80:30080/TCP 29m app=webserver

让我们检查节点 minikube-m02 中的iptable规则; 应该有一个DROP规则来丢弃数据包,因为没有本地端点。
1
2
3
4
5
$ sudo iptables -t nat -L KUBE-NODEPORTS
Chain KUBE-NODEPORTS (1 references)
target prot opt source destination
KUBE-MARK-MASQ tcp — 127.0.0.0/8 anywhere /* default/webapp */ tcp dpt:30080
KUBE-XLB-2IRACUALRELARSND tcp — anywhere anywhere /* default/webapp */ tcp dpt:30080

检查 KUBE-XLB-2IRACUALRELARSND

$ sudo iptables -t nat -L KUBE-XLB-2IRACUALRELARSND

Chain KUBE-XLB-2IRACUALRELARSND (1 references)

target prot opt source destination

KUBE-SVC-2IRACUALRELARSND all — 10.244.0.0/16 anywhere / 重定向试图访问外部负载均衡器VIP的Pod到集群IP /

KUBE-MARK-MASQ all — anywhere anywhere / 为默认/webapp LB IP伪装本地流量 / ADDRTYPE match src-type LOCAL

KUBE-SVC-2IRACUALRELARSND all — anywhere anywhere / 将默认/webapp LB IP的本地流量路由到服务链 / ADDRTYPE match src-type LOCAL

KUBE-MARK-DROP all — anywhere anywhere / 默认/webapp没有本地端点 /

如果你仔细看,集群层面的流量没有问题;只有nodePort流量会在这个节点上被丢弃。

minikube’ 节点iptable规则,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ sudo iptables -t nat -L KUBE-NODEPORTS
Chain KUBE-NODEPORTS (1 references)
target prot opt source destination
KUBE-MARK-MASQ tcp — 127.0.0.0/8 anywhere /* default/webapp */ tcp dpt:30080
KUBE-XLB-2IRACUALRELARSND tcp — anywhere anywhere /* default/webapp */ tcp dpt:30080

$ sudo iptables -t nat -L KUBE-XLB-2IRACUALRELARSND
Chain KUBE-XLB-2IRACUALRELARSND (1 references)
target prot opt source destination
KUBE-SVC-2IRACUALRELARSND all — 10.244.0.0/16 anywhere /* Redirect pods trying to reach external loadbalancer VIP to clusterIP */
KUBE-MARK-MASQ all — anywhere anywhere /* masquerade LOCAL traffic for default/webapp LB IP */ ADDRTYPE match src-type LOCAL
KUBE-SVC-2IRACUALRELARSND all — anywhere anywhere /* route LOCAL traffic for default/webapp LB IP to service chain */ ADDRTYPE match src-type LOCAL
KUBE-SEP-5T4S2ILYSXWY3R2J all — anywhere anywhere /* Balancing rule 0 for default/webapp */

$ sudo iptables -t nat -L KUBE-SVC-2IRACUALRELARSND
Chain KUBE-SVC-2IRACUALRELARSND (3 references)
target prot opt source destination
KUBE-SEP-5T4S2ILYSXWY3R2J all — anywhere anywhere /* default/webapp */

无头服务 ( 这部分不太懂 )

-来自Kubernetes文档的摘录-

有时您不需要负载均衡和单个服务IP。在这种情况下,您可以通过为集群IP(.spec.clusterIP)明确指定"None"来创建所谓的“无头”服务。

您可以使用无头服务与其他服务发现机制接口,而不会被绑定到Kubernetes的实现。

对于无头Services,不会分配集群IP,kube-proxy不处理这些Services,并且平台不进行负载均衡或代理。 DNS的自动配置情况取决于Service是否定义了选择器:

配置了选择器(selector)

对于定义选择器的无头服务,endpoints控制器在API中创建Endpoints记录,并修改DNS配置以返回记录(地址),这些记录直接指向ServicePods

1
2
3
4
5
6
master # kubectl get svc webapp-hs
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
webapp-hs ClusterIP None <none> 80/TCP 24s
master # kubectl get ep webapp-hs
NAME ENDPOINTS AGE
webapp-hs 10.244.120.109:80,10.244.120.110:80 31s

没有配置选择器(selector)

对于不定义选择器的无头服务,endpoints 控制器不会创建 Endpoints 记录。然而,DNS 系统会查找并配置:

  • 对于 ExternalName 类型的服务,配置CNAME记录。
  • 对于与 Service 共享名称的所有其他类型的任何 Endpoints,配置 A 记录。

如果有外部 IP 可以路由到一个或多个集群节点,则可以在这些 externalIPs 上公开 Kubernetes 服务。以外部 IP 为目标 IP 的 Service 端口所接收的流量将被路由到 Service 端点之一。externalIPs 不由 Kubernetes 管理,而是由集群管理员负责。

网络策略

到目前为止,您可能已经对 Kubernetes 中的网络策略实现有了概念。是的,iptables 又来了;这次,CNI 负责实现网络策略,而不是 kube-proxy。本节应该添加到 Calico(第 2 部分);然而,我觉得这里是详细说明网络策略的正确位置。

让我们创建三个服务 —— frontend、backend 和 db。

默认情况下,pod 是非隔离的;它们接受来自任何源的流量。

然而,应该有一个流量策略来隔离DB Pod与前端Pod,以避免他们之间的任何流量流动。

我建议您阅读这篇文章以了解网络策略配置。本节将重点关注网络策略在Kubernetes中的实现,而不是配置深度探讨。

我已经应用了一个网络策略来隔离 db 和 frontend pod;这导致frontend和db pod之间没有连接。

注意:上图显示“服务”符号而不是“pod”符号是为了更容易理解,因为给定服务中可以有许多pod。但是实际规则是针对每个Pod应用的。

1
2
3
4
5
master # kubectl exec -it frontend-8b474f47-zdqdv -- /bin/sh
# curl backend
backend-867fd6dff-mjf92
# curl db
curl: (7) Failed to connect to db port 80: Connection timed out

然而,backend可以毫无问题地访问db服务。

1
2
3
master # kubectl exec -it backend-867fd6dff-mjf92 -- /bin/sh
# curl db
db-8d66ff5f7-bp6kf

让我们看一下网络策略——如果设置了标签 allow-db-access 值为 true ,则允许从服务进入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-db-access
spec:
podSelector:
matchLabels:
app: "db"
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
networking/allow-db-access: "true"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
labels:
app: backend
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
networking/allow-db-access: "true"
spec:
volumes:
- name: workdir
emptyDir: {}
containers:
- name: nginx
image: nginx:1.14.2
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts:
- name: workdir
mountPath: /usr/share/nginx/html
initContainers:
- name: install
image: busybox
imagePullPolicy: IfNotPresent
command: ['sh', '-c', "echo $HOSTNAME > /work-dir/index.html"]
volumeMounts:
- name: workdir
mountPath: "/work-dir"
...

Calico将Kubernetes网络策略转换为Calico的本地格式,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
master # calicoctl get networkPolicy --output yaml
apiVersion: projectcalico.org/v3
items:
- apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
creationTimestamp: "2020-11-05T05:26:27Z"
name: knp.default.allow-db-access
namespace: default
resourceVersion: /53872
uid: 1b3eb093-b1a8-4429-a77d-a9a054a6ae90
spec:
ingress:
- action: Allow
destination: {}
source:
selector: projectcalico.org/orchestrator == 'k8s' && networking/allow-db-access
== 'true'
order: 1000
selector: projectcalico.org/orchestrator == 'k8s' && app == 'db'
types:
- Ingress
kind: NetworkPolicyList
metadata:
resourceVersion: 56821/56821

iptables规则通过使用“filter”表发挥着执行策略的重要作用。由于Calico使用像ipset这样的高级概念,所以逆向工程很难。从iptables规则中,我看到只有当数据包来自后端时才允许进入db pod,这正是我们的网络策略。

从calicoctl获取工作负载端点详细信息。

1
2
3
4
5
master # calicoctl get workloadEndpoint
WORKLOAD NODE NETWORKS INTERFACE
backend-867fd6dff-mjf92 minikube 10.88.0.27/32 cali2b1490aa46a
db-8d66ff5f7-bp6kf minikube 10.88.0.26/32 cali95aa86cbb2a
frontend-8b474f47-zdqdv minikube 10.88.0.24/32 cali505cfbeac50

cali95aa86cbb2a — 这是veth对的主机端,正在被db pod使用。

让我们获取与此接口相关的iptables规则。

$ sudo iptables-save | grep cali95aa86cbb2a

:cali-fw-cali95aa86cbb2a - [0:0]

:cali-tw-cali95aa86cbb2a - [0:0]

-A cali-from-wl-dispatch -i cali95aa86cbb2a -m comment —comment “cali:R489GtivXlno-SCP” -g cali-fw-cali95aa86cbb2a

-A cali-fw-cali95aa86cbb2a -m comment —comment “cali:3XN24uu3MS3PMvfM” -m conntrack —ctstate RELATED,ESTABLISHED -j ACCEPT

-A cali-fw-cali95aa86cbb2a -m comment —comment “cali:xyfc0rlfldUi6JAS” -m conntrack —ctstate INVALID -j DROP

-A cali-fw-cali95aa86cbb2a -m comment —comment “cali:wG4_76ot8e_QgXek” -j MARK —set-xmark 0x0/0x10000

-A cali-fw-cali95aa86cbb2a -p udp -m comment —comment “cali:Ze6pH1ZM5N1pe76G” -m comment —comment “Drop VXLAN encapped packets originating in pods” -m multiport —dports 4789 -j DROP

-A cali-fw-cali95aa86cbb2a -p ipencap -m comment —comment “cali:3bjax7tRUEJ2Uzew” -m comment —comment “Drop IPinIP encapped packets originating in pods” -j DROP

-A cali-fw-cali95aa86cbb2a -m comment —comment “cali:0pCFB_VsKq1qUOGl” -j cali-pro-kns.default

-A cali-fw-cali95aa86cbb2a -m comment —comment “cali:mbgUOxlInVlwb2Ie” -m comment —comment “Return if profile accepted” -m mark —mark 0x10000/0x10000 -j RETURN

-A cali-fw-cali95aa86cbb2a -m comment —comment “cali:I7GVOQegh6Wd9EMv” -j cali-pro-ksa.default.default

-A cali-fw-cali95aa86cbb2a -m comment —comment “cali:g5ViWVLiyVrKX91C” -m comment —comment “Return if profile accepted” -m mark —mark 0x10000/0x10000 -j RETURN

-A cali-fw-cali95aa86cbb2a -m comment —comment “cali:RBmQDo38EoPmxJ0I” -m comment —comment “Drop if no profiles matched” -j DROP

-A cali-to-wl-dispatch -o cali95aa86cbb2a -m comment —comment “cali:v3sEoNToLYUOg7M6” -g cali-tw-cali95aa86cbb2a

-A cali-tw-cali95aa86cbb2a -m comment —comment “cali:eCrqwxNk3cKw9Eq6” -m conntrack —ctstate RELATED,ESTABLISHED -j ACCEPT

-A cali-tw-cali95aa86cbb2a -m comment —comment “cali:_krp5nzavhAu5avJ” -m conntrack —ctstate INVALID -j DROP

-A cali-tw-cali95aa86cbb2a -m comment —comment “cali:Cu-tVtfKKu413YTT” -j MARK —set-xmark 0x0/0x10000

-A cali-tw-cali95aa86cbb2a -m comment —comment “cali:leBL64hpAXM9y4nk” -m comment —comment “Start of policies” -j MARK —set-xmark 0x0/0x20000

-A cali-tw-cali95aa86cbb2a -m comment —comment “cali:pm-LK-c1ra31tRwz” -m mark —mark 0x0/0x20000 -j cali-pi-_tTE-E7yY40ogArNVgKt

-A cali-tw-cali95aa86cbb2a -m comment —comment “cali:q_zG8dAujKUIBe0Q” -m comment —comment “Return if policy accepted” -m mark —mark 0x10000/0x10000 -j RETURN

-A cali-tw-cali95aa86cbb2a -m comment —comment “cali:FUDVBYh1Yr6tVRgq” -m comment —comment “Drop if no policies passed packet” -m mark —mark 0x0/0x20000 -j DROP

-A cali-tw-cali95aa86cbb2a -m comment —comment “cali:X19Z-Pa0qidaNsMH” -j cali-pri-kns.default

-A cali-tw-cali95aa86cbb2a -m comment —comment “cali:Ljj0xNidsduxDGUb” -m comment —comment “Return if profile accepted” -m mark —mark 0x10000/0x10000 -j RETURN

-A cali-tw-cali95aa86cbb2a -m comment —comment “cali:0z9RRvvZI9Gud0Wv” -j cali-pri-ksa.default.default

-A cali-tw-cali95aa86cbb2a -m comment —comment “cali:pNCpK-SOYelSULC1” -m comment —comment “Return if profile accepted” -m mark —mark 0x10000/0x10000 -j RETURN

-A cali-tw-cali95aa86cbb2a -m comment —comment “cali:sMkvrxvxj13WlTMK” -m comment —comment “Drop if no profiles matched” -j DROP

$ sudo iptables-save -t filter | grep cali-pi-_tTE-E7yY40ogArNVgKt

:cali-pi-_tTE-E7yY40ogArNVgKt - [0:0]

-A cali-pi-_tTE-E7yY40ogArNVgKt -m comment —comment “cali:M4Und37HGrw6jUk8” -m set —match-set cali40s:LrVD8vMIGQDyv8Y7sPFB1Ge src -j MARK —set-xmark 0x10000/0x10000

-A cali-pi-_tTE-E7yY40ogArNVgKt -m comment —comment “cali:sEnlfZagUFRSPRoe” -m mark —mark 0x10000/0x10000 -j RETURN

通过检查ipset,可以清楚地看到,仅允许来自后端pod IP 10.88.0.27 的流量进入db pod

1
2
3
4
5
6
7
8
9
10
[root@minikube /]# ipset list
Name: cali40s:LrVD8vMIGQDyv8Y7sPFB1Ge
Type: hash:net
Revision: 6
Header: family inet hashsize 1024 maxelem 1048576
Size in memory: 408
References: 3
Number of entries: 1
Members:
10.88.0.27

我将在本系列的第2部分更新更详细的步骤来解释 calico iptables 规则。

引用

  1. https://kubernetes.io
  2. https://www.projectcalico.org/
  3. https://rancher.com/
  4. http://www.netfilter.org/
Author: Sean
Link: https://blog.whileaway.io/posts/ab025878/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.