客座撰稿人:Karen Bruner,StackRox技术专员。原文可以在这里找到。
https://www.stackrox.com/post/2020/01/kubernetes-networking-demystified/
Kubernetes集群网络可能会让人感到非常困惑,即使对于有使用虚拟网络和请求路由实践经验的工程师来说也是如此。在这篇文章中,我们将介绍Kubernetes网络的复杂性,通过跟踪HTTP请求到运行在基本Kubernetes集群上的服务过程。
我们将使用带有两个Linux节点的标准谷歌Kubernetes引擎(GKE)集群作为示例,并说明在其他平台上细节可能有所不同。
一个HTTP请求的旅程
以浏览网页的人为例。他们点击一个链接,发生了一些事情,页面就会加载。
我们需要把这些问号填一下。
在下一个图中,请求通过Internet发送到一个非常大的云提供商,然后发送到位于云提供商基础设施中的Kubernetes集群。
Kubernetes网络政策指南
当我们放大到Kubernetes集群时,我们看到云提供商负载均衡器向Kubernetes服务(Service)资源发送请求,然后将请求路由到Kubernetes副本集(ReplicaSet)中的pod。
我们可以部署以下YAML来创建Kubernetes服务(Service,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)资源,该资源在host:port表示法中有两个条目,每个Pod都有一个,其中Pod IP为主机值和端口8080。
在我们的GKE集群上,使用kubectl查询这些资源类型将返回以下内容:
作为参考,我们的集群具有以下IP网络:
我们的服务在群集CIDR块中具有10.19.240.1的虚拟IP地址(Virtual IP,VIP)。
现在,我们准备好从负载均衡器开始,按照请求进入Kubernetes集群的过程。
负载均衡器
虽然Kubernetes通过原生控制器和通过入口控制器提供了多种暴露服务的方法,但我们将使用LoadBalancer类型的标准Service资源。我们的hello-world服务需要GCP网络负载平衡器。每个GKE集群都有一个云控制器,该云控制器在集群和自动创建集群资源(包括我们的负载均衡器)所需的GCP服务的API端点之间进行连接。 (所有云提供商都提供具有不同选项和特性的不同类别的负载均衡器。)
要查看外部负载均衡器的位置,首先我们需要从另一个角度看待集群。
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服务在其端点中使用相同的面向Internet的端口。如果我们的服务已部署到标准的Amazon Elastic Kubernetes服务(EKS)集群,则将由Elastic Load Balancer提供服务,该服务会将传入的连接发送到具有服务pod的节点端口。但是,Google Cloud Platform(GCP)网络负载均衡器仅将流量转发到与负载均衡器上传入端口位于同一端口上的目标,也即是到负载均衡器上端口80的流量将发送到目标后端上的端口80实例。 hello-world pod绝对不会在节点的端口80上侦听。如果在节点上运行netstat,我们将看到没有进程在该端口上侦听。
那么,如何通过负载平衡器建立成功的连接请求?如果kube-proxy在用户空间模式下运行,则实际上是代理到后端Pod的连接。不过,在iptables模式下,kube-proxy配置了Netfilter链,因此该连接被节点的内核直接路由到后端容器的端点。
iptables
在我们的GKE集群中,如果我们登录到其中一个节点并运行iptables,则可以看到这些规则。
借助规则注释,我们可以获得与服务的负载平衡器到hello-world服务的传入连接匹配的筛选器链的名称,并遵循该链的规则。(在没有规则注释的情况下,我们仍然可以将规则的源IP地址与服务的负载均衡器进行匹配。)
我们还可以可视化网络堆栈中用于评估和修改数据包的链和规则,以查看我们在集群中创建的服务如何将流量定向到副本集成员。
KUBE-FW-33X6KPGSXBPETFQV链具有三个规则,每个规则都添加了另一个链来处理数据包。
请注意,即使我们的集群有两个节点,每个节点都有一个hello-world pod,但此路由方法并未显示优先选择路由到从云负载平衡器接收请求的节点上的Pod。但是,如果我们将服务规范中的externalTrafficPolicy更改为Local,那将会改变。如果存在请求,请求不仅会转到接收请求的节点上的Pod,而且这意味着没有服务Pod的节点将拒绝连接。因此,通常需要将Local策略与Kubernetes守护程序集一起使用,该守护程序集会在集群中的每个节点上调度一个Pod。尽管指定本地交付显然会减少请求的平均网络延迟,但可能导致服务Pod的负载不均衡。
Pod网络
这篇文章不会详细介绍Pod网络,但是在我们的GKE集群中,pod网络有自己的CIDR块,与节点的网络分开。Kubernetes网络模型要求集群中的所有Pod能够直接相互寻址,而不管其主机节点如何。GKE群集使用kubenet CNI,它在每个节点上创建到Pod网络的网桥接口,为每个节点提供自己的Pod IP地址专用CIDR块,以简化分配和路由。Google Compute Engine(GCE)网络可以在VM之间路由此pod网络流量。
HTTP请求
这就是我们获取HTTP 200响应代码的方式。
路由变量
这篇文章提到了各种Kubernetes平台提供的不同选项可以更改路由的一些方式。这是一个不全面的列表:
保护服务
Kubernetes网络需要大量的移动部件。它非常复杂,但是对集群中发生的事情有基本的了解将有助于您更有效地监视和保护它。
资料来源和进一步阅读