内容简介:本文将以带有两个 Linux 节点的标准 GKE 集群为例,通过跟踪 HTTP 请求被传送到集群服务的整个过程,深度拆解 Kubernetes 网络的复杂性。作者:Karen Bruner
本文将以带有两个 Linux 节点的标准 GKE 集群为例,通过跟踪 HTTP 请求被传送到集群服务的整个过程,深度拆解 Kubernetes 网络的复杂性。
作者:Karen Bruner
翻译:bot(才云)
校对:星空下的文仔(才云)
根据 CNCF 历年发布的 Kubernetes 生态报告,安全、存储、网络始终是开发者最关注的三大槽点。即便在虚拟网络和请求路由方面有丰富经验,很多开发者在处理 Kubernetes 集群网络时还是很混乱。
本文将以带有两个 Linux 节点的标准 Google Kubernetes Engine(GKE)集群为例,通过跟踪 HTTP 请求被传送到集群服务的整个过程,深度拆解 Kubernetes 网络的复杂性。
K8sMeetup
请求的旅程
当一个人在浏览网页时,他首先单击一个链接,发生了一些事,之后目标页面就被加载出来。这让人不免好奇,从单击链接到页面加载,中间到底发生了什么?
对于这个问题,我们可以这样理解。如下图所示,用户请求通过 Internet 被发送给一个非常大的云提供商,然后再被发送到该云提供商基础架构中托管的 Kubernetes 集群。
如果进一步放大 Kubernetes 集群,我们可以看到云提供商正向 Kubernetes
Service
资源(svc)发送请求,然后将请求路由到 Kubernetes ReplicaSet(rs)中的 Pod。
为了更直观,我们可以部署 YAML 来创建 Kubernetes Service 和 ReplicaSet:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: hello-world
labels:
app: hello-world
spec:
selector:
matchLabels:
app: hello-world
replicas: 2
template:
metadata:
labels:
app: hello-world
spec:
containers:
- name: hello-world
image: gcr.io/google-samples/node-hello:1.0
imagePullPolicy: Always
ports:
- containerPort: 8080
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: hello-world
spec:
selector:
app: hello-world
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: LoadBalancer
externalTrafficPolicy: Cluster
现在我们已经在
hello-world ReplicaSet
下创建了两个 Pod,还创建了一个带有负载均衡器的服务资源
hello-world
(如果云提供商和集群网络支持),以及一个在
host:port
中有两个条目的 Kubernetes
Endpoint
资源,每个 Pod 对应一个,以 Pod IP 作为主机值和端口 8080。
在 GKE 集群上,我们
kubectl
一下会返回以下内容:
集群 IP 网络信息:
-
Node - 10.138.15.0/24
-
Cluster - 10.16.0.0/14
-
Service - 10.19.240.0/20
已知服务在集群 CIDR 中的虚拟 IP 地址(VIP)是 10.19.240.1。现在,我们可以从负载均衡器开始,深入跟踪请求进入 Kubernetes 集群的整个“旅程”。
K8sMeetup
负载均衡器
Kubernetes 通过本地控制器和 Ingress 控制器提供了很多公开服务的方法,但这里我们还是使用
LoadBalancer
类型的标准
Service
资源。
我们的
hello-world
服务需要 GCP 网络负载均衡器。每个 GKE 集群都有一个云控制器,它在集群和 API 端点之间进行接口,以自动创建集群资源所需的 GCP 服务,包括我们的负载均衡器(不同云提供商的负载均衡器在类型、特性上都有不同)。
通过 从不同的角度观察集群 ,我们可以查看外部负载均衡器的位置:
K8sMeetup
kube-proxy
每个节点都有一个
kube-proxy
容器进程(在 Kubernetes 参考框架中,
kube-proxy
容器位于
kube-system
命名空间的 Pod 中),它负责把寻址到集群 Kubernetes 服务对象虚拟 IP 地址的流量转发到相应后端 Pod。
kube-proxy
当前支持三种不同的实现方式:
-
User space :即用户空间,服务路由是在用户进程空间的 kube-proxy 中进行的,而不是内核网络堆栈。这是
kube-proxy
的最初版本,较为稳定,但是效率不太高; -
iptables :这种方式采用 Linux 内核级 Netfilter 规则为 Kubernetes Services 配置所有路由,是大多数平台实现
kube-proxy
的默认模式。当对多个后端 Pod 进行负载均衡时,它使用未加权的循环调度; -
IPVS :IPVS 基于 Netfilter 框架构建,在 Linux 内核中实现了 L4 负载均衡,支持多种负载均衡算法,连接最少,预期延迟最短。它从 Kubernetes v1.11 中开始普遍可用,但需要 Linux 内核加载 IPVS 模块。它也不像 iptables 那样拥有各种 Kubernetes 网络项目的广泛支持。
在我们的 GKE 集群中,
kube-proxy
以 iptables 模式运行,所以我们后续主要研究该模式的工作方式。
如果查看创建好的
hello-world
服务,我们可以发现它已经被分配了一个节点端口
30510
。
节点网络上动态分配的端口允许其中托管的多个 Kubernetes 服务在其端点中使用相同的面向 Internet 的端口
。
如果服务已被部署到标准 Amazon EKS 集群,它将由 Elastic Load Balance 提供服务, 该服务会将传入的连接发送到相应 Pod 节点上我们服务的节点端口 。但是,Google Cloud Platform 网络负载均衡器只会将流量转发到与负载均衡器的传入端口位于同一端口的目标,例如,到负载均衡器上的端口 80 的流量会被发送到目标后端实例上的端口 80。
我们的
hello-world pods
绝对没有在节点的端口 80 上监听。所以如果在节点上运行
netstat
,我们可以看到没有进程正在监听该端口。
那么,通过负载均衡器的请求是如何成功建立连接的呢?如果
kube-proxy
在用户空间模式下运行,它实际上是将连接代理到后端 Pod。但是,在 iptables 模式下,
kube-proxy
配置了 Netfilter 链,因此该连接被节点的内核直接路由到了后端容器的端点。
K8sMeetup
iptables
在我们的 GKE 集群中,如果登录到其中一个节点并运行 iptables,我们可以看到这些规则。
根据规则注释,
我们可以获得与来自服务的负载均衡器到
hello-world
服务的传入连接相匹配的过滤器链的名称
,并遵循该链的规则(在没有规则注释的情况下,我们仍然可以将规则的源 IP 地址与服务的负载均衡器进行匹配)。
我们还可以可视化网络堆栈中用于评估和修改数据包的链和规则,查看我们在集群中创建的服务是怎么把流量定向到副本集成员的。
KUBE-FW-33X6KPGSXBPETFQV
链有三个规则,每个规则都添加了另一个链来处理数据包。
-
KUBE-MARK-MASQ
向发送到hello-world
服务的包(来自集群网络外部)添加一个 Netfilter 标记 。带有此标记的数据包将按照POSTROUTING
规则进行更改,以使用源网络地址转换(SNAT),并将节点 IP 地址作为其源 IP 地址; -
KUBE-SVC-33X6KPGSXBPETFQV
链适用于所有与hello-world
服务相关的流量(与源无关),并且对每个服务端点(在本例中为两个 Pod)提供规则 。使用哪个端点链是完全随机确定的: -
KUBE-SEP-ALRUKLHE5DT3R34X
:如果需要,KUBE-MARK-MASQ
会再次向数据包中添加一个 Netfilter 标记用以 SNAT;DNAT
规则使用 10.16.0.11:8080 端点作为目标来设置目标 NAT -
KUBE-SEP-X7DMMHFVFOT4JLHD
:如果需要,KUBE-MARK-MASQ
会再次为数据包添加一个 Netfilter 标记用以 SNAT;DNAT
规则使用 10.16.1.8:8080 端点作为目标来设置目标 NAT -
KUBE-MARK-DROP
向此时尚未启用目标 NAT 的数据包添加 Netfilter 标记 。这些数据包将在KUBE-FIREWALL
链中被丢弃。
需要注意的是,尽管我们的集群有两个节点,每个节点都有一个
hello-world
Pod,
但这种路由方法并不存在优先级
。如果我们将服务规范中的
externalTrafficPolicy
更改为
Local
,那么情况就会改变。假设此时存在请求,这个请求不仅会转到接收请求的节点上的 Pod,还会导致没有服务 Pod 的节点拒绝连接。
因此,
Local
策略通常需要与 Kubernetes daemon sets 一起使用,后者会在集群中的每个节点上调度一个 Pod。 虽然前者能明显降低请求的平均网络延迟,但它也可能导致服务 Pods 之间的负载不均衡
。
K8sMeetup
Pod 网络
本文不会深入介绍 Pod 网络,但是在我们的 GKE 集群中,Pod 网络有自己的 CIDR 块,与节点网络分开。 Kubernetes 网络模型要求集群中的所有 Pod 能够直接相互寻址(无视其主机节点) 。GKE 群集使用 kubenet CNI,它在每个节点上创建到 Pod 网络的网桥接口,为每个节点提供自己的 Pod IP 地址专用 CIDR 块,以简化分配和路由。Google Compute Engine(GCE)网络可以在 VM 之间路由该 Pod 网络流量。
K8sMeetup
请求
以下是是我们获取
HTTP 200
响应代码的方式:
本文提到了许多改变路由的方法,它们由不同 Kubernetes 平台提供,下面是一个简单的清单:
-
容器网络接口(CNI)插件 :每个云提供商默认使用与其 VM 网络模型兼容的 CNI 实现。本文以默认设置的 GKE 集群为例,但如果是 Amazon EKS,那会很不一样,因为 AWS VPC CNI 把容器直接放在节点的 VPC 网络上;
-
Kubernetes Network Policy :Calico 是实施网络策略最受欢迎的 CNI 插件之一,它在节点上为每个 Pod 创建一个虚拟网络接口,并使用 Netfilter 规则来实施其防火墙规则;
-
尽管大多数情况下仍然使用 Netfilter,但
kube-proxy
IPVS 路由模式 通常会把服务路由和 NAT 移出 Netfilter 规则; -
外部负载均衡器 或其他可以将流量直接发送到服务节点端口的源将匹配 iptables 中的不同链(
KUBE-NODEPORTS
); -
Kubernetes Ingress 控制器 可以通过多种方式更改边缘服务路由;
-
诸如 Istio 之类的服务网格 可能会绕过
kube-proxy
,直接连接服务容器之间的内部路由。
K8sMeetup
保护服务
Kubernetes 网络需要大量可移动部件,它非常复杂,但如果开发者对集群中发生的事有基本了解,这会有助于开发者更有效地监控、保护它。
第一, 对于 Kubernetes 服务资源创建的云负载均衡器,添加防火墙限制的通用方法是不存在的
。一些云提供商会支持服务规范中的
loadBalancerSourceRanges
字段,这个字段允许开发者提供可以连接到负载均衡器的 IP CIDR 块白名单。如果云提供商不支持此字段,它就会被忽略,因此开发者需要验证外部负载均衡器的网络配置。
而对于不支持
loadBalancerSourceRanges
字段的云提供商,除非已经在云提供商级别采取措施锁定了负载均衡器和运行它们的云网络,开发者还是应该假定负载均衡器上的服务端点是对全世界开放的。由于各种因素,云提供商负载均衡器产品的默认防火墙设置千差万别,一些云提供商可能还支持对
Service
对象的注释,以配置负载均衡器的安全性。
其次,请注意, 我们没有通过在 GKE 集群中启用 Kubernetes 网络策略支持来安装 Calico CNI,因为 Calico 创建了大量其他 iptables 规则
,
这给可视化跟踪到 Pod 的虚拟路由时增加了额外步骤
。尽管如此,我们还是建议开发者在生产集群中实现
NetworkPolicy
API 的 CNI,并创建限制 Pod 流量的策略。
第三, 启用
HostNetwork
属性创建的 Pod 将共享节点的网络空间
。虽然存在一些这样做的例子,但通常情况下,大多数 Pod 不需要在主机网络上,尤其是对于有 root 特权的 Pod,这可能会导致受攻击的容器可以查看网络流量。如果开发者需要在节点网络上公开容器端口,而使用 Kubernetes Service 节点端口无法满足需求,一个稳妥的选择是可以在 PodSpec 中为容器指定
hostPort
。
最后, 使用主机网络的 Pod 不应使用
NET_ADMIN
功能运行
,这将使它们能够读取和修改节点的防火墙规则。
参考文献
https://kubernetes.io/docs/concepts/services-networking/service/ https://kubernetes.io/docs/concepts/cluster-administration/networking/ https://twitter.com/thockin/status/1191766983735296000 https://kubernetes.io/blog/2018/07/09/ipvs-based-in-cluster-load-balancing-deep-dive/ https://netfilter.org/documentation/HOWTO/NAT-HOWTO-6.html
原文: https://www.stackrox.com/post/2020/01/kubernetes-networking-demystified/?utm_sq=gbie6n7pwh
推荐阅读:
在看点一下
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 解决DDD核心的复杂性
- 四招教你降低软件复杂性
- Kubernetes如何降低云的复杂性
- 降低软件复杂性的一般原则和方法
- Hadoop 气数已尽:逃离复杂性,拥抱云计算
- 算法复杂性“Bug-O”表示法 — Overreacted
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Producter 让产品从0到1
周楷雯 / 人民邮电出版社 / 2016-12-25 / CNY 69.00
这是一本以App Store首页推荐的成功App为例阐述如何完成一款App产品的设计、开发和营销的书。在这本书之后,作者的《一炷香》和《字里行间》两款产品也接连被App Store首页推荐。 《Producter 让产品从0到1》从产品的设计、产品的实现、产品的迭代、产品的营销、产品的进阶等几个角度,全面讲解了产品设计的基本原则、设计的重要性、设计的感觉、实用的设计工具、简单的iOS开发、产......一起来看看 《Producter 让产品从0到1》 这本书的介绍吧!
MD5 加密
MD5 加密工具
HSV CMYK 转换工具
HSV CMYK互换工具