作者: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):
代码语言:javascript复制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目前支持三种不同的操作模式:
- User space: 此模式之所以得名,是因为服务路由发生在用户进程空间的kube-proxy中,而不是内核网络堆栈中。因为它运行缓慢且过时,并不常用。
- iptables: 该模式使用Linux内核级Netfilter规则来配置Kubernetes Services的所有路由。在大多数平台上,此模式是kube-proxy的默认模式。在为多个后端容器进行负载均衡时,它使用未加权的轮询调度模式。
- IPVS (IP Virtual Server): IPVS基于Netfilter框架构建,在Linux内核中实现了四层的负载均衡,支持多种负载均衡算法,包括最少连接数和最短的预期延迟。这种kube-proxy模式在Kubernetes 1.11中开始变得普遍可用,但是它需要Linux内核中加载IPVS模块。在各种Kubernetes网络项目中它也没有iptables模式支持的广泛。
在我们的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
- 如果需要,KUBE-MARK-MASQ会再次在数据包中添加一个用于SNAT的Netfilter标记。
- DNAT规则使用10.16.0.11:8080 endpoint作为目的地来设置目标NAT。
2)KUBE-SEP-X7DMMHFVFOT4JLHD
- 如果需要,KUBE-MARK-MASQ会再次在数据包中添加一个用于SNAT的Netfilter标记。
- DNAT规则使用10.16.0.8:8080 endpoint作为目的地来设置目标NAT。
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平台提供的可以更改路由的一些方式。这是一个不全面的列表:
- 容器网络接口(CNI)插件:每个云提供商默认使用与其VM网络模型兼容的CNI实现方式。本文以默认设置的GKE集群为例。Amazon EKS中的示例看起来会有很大不同,因为AWS VPC CNI将Pod直接放置在节点的VPC网络上。
- Kubernetes网络策略:Calico是实施网络策略最受欢迎的CNI插件之一,它在节点上为每个Pod创建一个虚拟网络接口,并使用Netfilter规则来实施其防火墙规则。
- 尽管大多数情况下仍使用Netfilter, 但kube-proxy IPVS路由模式在大多数情况下将服务路由和NAT移出了Netfilter规则。
- 可以直接将流量发送到服务节点端口的外部负载均衡器或其他来源,将与iptables中的其他链(KUBE-NODEPORTS)匹配。
- Kubernetes Ingress控制器可以通过多种方式更改边缘服务路由。
- 诸如Istio之类的服务网格可能会绕过kube-proxy,并直接连接服务Pods之间的内部路由。
7
安全防护服务
- 没有通用的方法可以为Kubernetes Service资源创建的云负载均衡器添加防火墙限制。一些云提供商会遵循服务规范中的[loadBalancerSourceRanges](https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/)字段,该字段可让您提供允许连接到负载均衡器的IP CIDR块列表。如果云厂商不遵守此字段,则它将被默认忽略,因此请务必验证外部负载均衡器的网络配置。对于不支持loadBalancerSourceRanges 字段的提供商,除非您在云提供商级别采取措施来锁定负载均衡器和运行它们的云网络,否则应假定负载均衡器上的服务endpoint将对外界开放。云提供商的负载均衡产品默认的防火墙设置千差万别,取决于许多因素。一些云提供商还可能支持对Service对象的注释以配置负载均衡器的安全性。
- 请注意,我们没有通过在GKE集群中启用Kubernetes网络策略支持来安装Calico CNI, 因为Calico会创建大量的其他iptables规则,从而在可视化跟踪到Pod的虚拟路由时添加了额外的步骤。但是,我们强烈建议您使用在生产集群中实现NetworkPolicy API的CNI,并创建限制Pod流量的策略。
- 启用了HostNetwork属性创建的Pod将共享节点的网络空间。尽管存在一些有效的用例,但通常大多数Pod不需要在主机网络上,尤其是对于具有root权限运行的Pod, 这可能会使受损的容器监听网络流量。如果您需要在节点的网络上公开容器端口,而使用Kubernetes Service节点端口无法满足您的需求,则可以选择在PodSpec中为容器指定hostPort。
- 使用主机网络的Pod不应使用NET_ADMIN功能运行,这个功能将使它们能够读取和修改节点的防火墙规则。
Kubernetes网络需要大量的移动部件。它非常复杂,但是对集群中发生的事情有基本的了解将有助于您更有效地监控,做好安全防护。
原文链接: https://www.stackrox.com/post/2020/01/kubernetes-networking-demystified/