作者:Karen Bruner
译者:毛艳清
关于译者
毛艳清,富士康工业互联网科技服务事业群运维中心主管,现负责公有云和私有云的运维工作,聚焦在云计算和云原生领域,主要关注企业迁云的策略与业务价值、云计算解决方案、云计算实施与运维管理,以及云原生技术的布道和落地实践。
Kubernetes 集群网络可能会让人感到困惑,甚至对于那些有处理虚拟网络和请求路由实际经验的工程师来说也是如此。在这篇文章中,我们将通过跟踪HTTP请求到运行在基本的Kubernetes集群上的服务来介绍Kubernetes网络的复杂性。我们将使用由两个Linux节点组成的一个标准的Google Kubernetes Engine(GKE)集群作为示例,并说明与其他平台上可能不同的细节。
1
一个请求的旅程
以一个人浏览网页作为典型的例子,他们点击一个链接,就会发生一些事情,然后页面就会加载。
我们需要把那些问号填一下。
在下图中,请求通过Internet发送到非常大的云服务提供商,然后再发送到云服务提供商基础架构中托管的Kubernetes集群。
随着我们近距离观察Kubernetes集群,我们看到了一个云供应商的负载均衡器提供给Kubernetes Service资源,该资源随后将请求路由到Kubernetes ReplicaSet中的Pod.
我们可以部署下面的YAML文件来创建Kubernetes服务(svc)和ReplicaSet(rs):
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服务资源。它还会创建一个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块中有一个10.19.240.1的虚拟IP地址(VIP).
现在,我们准备按照请求进入Kubernetes集群的过程,从负载均衡器开始说明。
2
负载均衡器
尽管Kubernetes通过本地控制器和Ingress控制器提供了多种暴露服务的方法,但我们将使用LoadBalancer 类型的标准Service资源。我们的hello-world服务需要一个GCP网络负载均衡器。每个GKE集群有一个云控制器,该控制器在集群和需要自动创建集群资源(包括我们的负载均衡器)的GCP服务的API endpoints 之间建立接口。(所有云提供商都提供具有不同选项和特性的不同类别的负载均衡器。)
要查看外部负载均衡器适合的位置,首先我们需要从另一个角度来观察集群。
3
kube-proxy
每个节点都有一个kube-proxy容器进程。(在Kubernetes参考框架中,该kube-proxy容器位于kube-system命名空间的Pod中)。kube-proxy将寻址到集群中Kubernetes服务对象的虚拟IP地址(VIP)的流量转发到适当的后端Pod中。kube-proxy目前支持三种不同的操作模式:
在我们的GKE集群中的kube-proxy, 在iptables模式下运行,因此我们将研究该模式的工作原理。
如果我们查看创建的hello-world服务,我们可以看到该服务已经被分配了30510的节点端口(该节点IP地址的网络端口)。节点网络上动态分配的端口允许集群中托管的多个Kubernetes服务在其endpoint中使用相同的面向Internet的端口。如果我们的服务已部署到标准的Amazon Elastic Kubernetes服务(EKS)集群,则将由Elastic Load Balancer提供服务,该服务会将进来的连接发送到具有实时服务Pod的节点上我们服务的节点端口。然而,Google Cloud Platform(GCP)网络负载均衡器仅将流量转发到与负载均衡器上传入端口位于同一端口上的目标实例,即,到负载均衡器上端口80的流量将发送到目标后端实例上的80端口。Hello-World Pods 绝对没有侦听节点上的80端口. 如果在节点上运行netstat, 我们将看到在该端口上没有进程在侦听。
那么,如何通过负载均衡器建立成功的连接请求?如果kube-proxy在用户空间模式下运行,它实际上通过代理连接到后端的Pod。不过,在iptables模式,kube-proxy配置了Netfliter链,因此该连接被节点的内核直接路由到后端容器的endpoint。
4
iptables
在我们的GKE集群中,如果我们登录到其中一个节点并运行iptables命令,则可以看到这些规则。
借助规则注释,我们可以获得与服务的负载均衡器到hello-world服务的传入连接匹配的过滤链名称,并遵循该链的规则。(在没有规则注释的情况下,我们仍然可以将规则的源IP地址与服务的负载均衡器进行匹配。)
我们还可以可视化网络堆栈中用于评估和修改数据包的链和规则,以查看我们在集群中创建的服务如何将流量定向到副本集成员。
KUBE-FW-33X6KPGSXBPETFQV 链有三条规则,每条规则都添加了另一个链来处理数据包。
1. UBE-MARK-MASQ将Netfilter标记添加到发往集群外部网络的用于hello-world服务的数据包。带有此标记的数据包将按照POSTROUTING规则进行更改,以使用源IP地址作为节点IP地址的源网络地址转换(SNAT)。
2. KUBE-SVC-33X6KPGSXBPETFQV链适用于为我们的hello-world服务绑定的所有流量,无论其来源如何,每个服务endpoint(在本例中有两个Pod)都有规则。使用哪个endpoint链是完全随机确定的。
1)KUBE-SEP-ALRUKLHE5DT3R34X
2)KUBE-SEP-X7DMMHFVFOT4JLHD
3.KUBE-MARK-DROP向此点未启用目标NAT的数据包添加Netfilter标记。这些数据包将在KUBE-FIREWALL链中被丢弃。
请注意,即使我们的集群有两个节点,每个节点有一个hello-world的Pod, 但此路由方法并未显示优先选择路由到从云负载均衡器接收请求的节点上的Pod。但是,如果我们将服务规范中的externalTrafficPolicy更改为Local, 那将会改变。如果存在请求,请求不仅会转到接收请求的节点上的Pod, 而且还意味着没有服务Pod的节点将拒绝此连接。因此,通常需要将Local策略与Kubernetes守护程序集一起使用,该守护程序集会在集群中的每个节点上调度一个Pod。虽然指定本地交付明显会减少请求的平均网络延迟,但可能导致服务Pod的负载不均衡。
5
Pod 网络
这篇文章不会详细介绍Pod网络,但是在我们的GKE集群中,Pod网络有自己的CIDR块,与节点的网络分开。
Kubernetes网络模型要求集群中的所有Pod能够直接相互寻址,而不管其主机节点如何。GKE集群使用Kubernetes CNI,它在每个节点上创建到Pod网络的网桥接口,为每个节点提供自己的Pod IP地址专用CIDR块,以简化分配和路由。Google Compute Engine (GCE) 网络可以在VM之间路由该Pod的网络流量。
6
请求
这就是我们获取HTTP 200 响应代码的方式。
路由变量
这篇文章提到了各种Kubernetes平台提供的可以更改路由的一些方式。这是一个不全面的列表:
7
安全防护服务
Kubernetes网络需要大量的移动部件。它非常复杂,但是对集群中发生的事情有基本的了解将有助于您更有效地监控,做好安全防护。
原文链接: https://www.stackrox.com/post/2020/01/kubernetes-networking-demystified/