Kubernetes 网络解析:集群内部网络访问流程详解

2,994 阅读5分钟

在 Kubernetes 集群中,我们可能会部署多个工作负载,且希望不同的工作负载之间能够进行网络通信,但其中的通信具体流程是怎样的呢?

工作负载是在 Kubernetes 上运行的应用程序。 Ref: kubernetes.io/zh/docs/con…

在这篇文章中,让我们来一步步的看看 k8s 集群内互相访问时实际发生了什么,以及其中的详细流程:

Step 1. 从其他 Pod 访问到 Service Cluster IP

1dd450d8dc7dc996559f0a415cf00344.png

首先,由于 Pods 随着销毁和创建,IP 会动态分配,从而不断变化,故我们通常使用 Service 将 Pod 内的网络暴露到集群中。

  1. Pod 内需要访问一个 Service 时, 一般会对集群为 Service 的固定 Hostname 发起请求

在集群中,一个 Service 的 Hostname 通常为 <service-name>.<namespace-name>

  • 例如,访问默认命名空间中的 Service example 8080 端口的 http 服务可以在集群内其他 Pod 中通过如下方式访问
    • curl example.default:8080

Kubernetes DNS 在集群上调度 DNS Pod 和服务,并配置 kubelet 以告知各个容器 使用 DNS 服务的 IP 来解析 DNS 名称。

集群中定义的每个 Service (包括 DNS 服务器自身)都被赋予一个 DNS 名称。 默认情况下,客户端 Pod 的 DNS 搜索列表会包含 Pod 自身的名字空间和集群 的默认域。

Ref: kubernetes.io/zh/docs/con…

  1. 这时候,该 Pod 需要查询该 Hostname 对应的 IP,它会向集群内 DNS(如 CoreDNS/kube-dns 等)查询,获取到 Service Hostname 对应的 Cluster IP

一个集群下的 coredns:

NAME      READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES                          SELECTOR
coredns   1/1     1            1           26d   coredns      rancher/coredns-coredns:1.8.3   k8s-app=kube-dns
  1. 获取到 Service 对应的 Cluster IP 后,网络包被发往该 Cluster IP

Step 2. 从 Service Cluster IP 到 Pod IP

f09daea8ee87a01309e01c6a3268e8b3.png

  1. 网络数据包到达 Cluster IP 后,实际处理该包的是集群节点上的 kube-proxy
    • 如果该包发往的端口和 Service 配置中的 port 匹配,那么该包会被处理

Service 只是 kube-proxy 的配置

在 Kubernetes 集群中,每个 Node 运行一个 kube-proxy 进程。 kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式,而不是 ExternalName 的形式。

Ref: kubernetes.io/zh/docs/con…

默认情况下,用户空间模式下的 kube-proxy 通过轮转算法选择后端。

例如,对于下面的例子中的 Service 配置,如果请求到达 Cluster IP 的 80 端口,则会被处理

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
  1. kube-proxy 会根据 Service 配置的 selector 找到对应的一组 Pod
    • 在这里,我们把这组 Pod 叫做该 Service 的 Backend

这些 Pod 通常由同一个 Deployment 创建,通常为同一个工作负载的一个 ReplicaSet(副本集)

  1. kube-proxy 会根据配置的 mode 的规则,选择 Backend 中的一个

    • 例如,usermode 下,kube-proxy 采用 Round-Robin(轮流)方式选择,而在 iptables 模式下,则采用 Random(随机)方式选择
  2. kube-proxy 会将网络包转发到选中 Pod 的 IP,并根据 Service 的 targetPort 从原端口转发到 Pod IP 的 targetPort

    • 例如,对于第一步中的例子,打到 Cluster IP 80 端口的请求会被转发到 Backend Pod 的 9376 端口

Step 3: Pod IP 到 Container 内

92a2ea8faddc096db5f99f6522504198.png

在 Kubernetes 中,Pod 内部可能会有一系列的不同 Container (容器),Pod 配置中每个 Container 的 containerPort 定义了这个 Container 对 Pod 外开放的端口。

当网络包被转发到 Pod IP 后,如果该 Pod 内存在 Container 开放的端口 (containerPort) 与网络包发往的端口一致,则该网络包被发往该容器内,由容器内程序进行处理。

在同一个 Pod 内,不同 Container 使用相同的网络,也就是说,在一个 Container 内直接访问 localhost ,即可访问到同 Pod 内的其他 Container。

而 Container 向 Pod 外开放的端口,则需要由 containerPort 定义

每个 Pod 都在每个地址族中获得一个唯一的 IP 地址。 Pod 中的每个容器共享网络名字空间,包括 IP 地址和网络端口。 Pod 内 的容器可以使用 localhost 互相通信。 当 Pod 中的容器与 Pod 之外 的实体通信时,它们必须协调如何使用共享的网络资源 (例如端口)。

Ref: kubernetes.io/zh/docs/con…

例如,下面的配置中,我们在 Pod 中定义了两个 Container,一个暴露 80,443 端口,另一个暴露了 18080 端口:

  • 如发往 Pod IP 的数据包目标为 80 端口或 443 端口,则交由 service-1 对应的 Container 处理
  • 如发往 Pod IP 的数据包目标为 目标为 18080 端口,则交由 service-2 处理
apiVersion: v1
kind: Pod
metadata:
	name: example
spec:
	containers:
	- name: service-1
		image: service-1:latest
		ports:
			- containerPort: 80
			- containerPort: 443
	- name: service-2
		image: service-2:latest
			- containerPort: 18080

总结

总的来说,Kubernetes 集群内,工作负载间通常利用 Service 进行通讯,具体分为以下几步:

  • 向集群 DNS 查询 Service Hostname 对应的 Cluster IP,包发往 Serivce Cluster IP,实际发送端口应与 Service 中配置的 port 相同
  • 发往 Service Cluster IP 的包实际由 kube-proxy 处理,kube-proxy 根据 Selector 选择一组 Pod,根据自身 mode 选择其中一个 Pod,将包转发到 Pod IP,转发到 Service 中配置的 targetPort 的端口
  • Pod 收到该包后,若 Pod 内的容器有对该端口开发,也就是若有 containerPort 等于 targetPort 的容器,则将包发至该容器

92a2ea8faddc096db5f99f6522504198.png