MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

剖析 Kubernetes 的网络模型与实践应用

2021-07-174.0k 阅读

Kubernetes 网络模型基础概念

在深入探讨 Kubernetes 的网络模型之前,我们先来了解一些基本概念。

Pod 网络

在 Kubernetes 中,Pod 是最小的可部署和可管理的计算单元。一个 Pod 可以包含一个或多个紧密相关的容器。从网络角度看,Pod 内的所有容器共享同一个网络命名空间,这意味着它们共享相同的 IP 地址和端口空间。这种设计使得 Pod 内的容器之间可以通过 localhost 进行高效通信。

例如,假设我们有一个包含两个容器的 Pod,一个是 Web 服务器容器,另一个是数据库客户端容器。Web 服务器容器在本地端口 8080 上运行,数据库客户端容器要连接这个 Web 服务器,它只需要连接 localhost:8080 即可,因为它们处于同一个网络命名空间。

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - name: web-server
    image: nginx
    ports:
    - containerPort: 8080
  - name: db-client
    image: some-db-client

服务(Service)网络

虽然 Pod 是 Kubernetes 中应用运行的基本单元,但 Pod 具有临时性的特点。当 Pod 被销毁或重新调度时,其 IP 地址会发生变化。为了提供一个稳定的网络端点来访问一组 Pod,Kubernetes 引入了服务(Service)的概念。

服务是一个抽象的概念,它定义了一组 Pod 的逻辑集合以及访问这些 Pod 的策略。Kubernetes 支持多种类型的服务,如 ClusterIP、NodePort 和 LoadBalancer。

ClusterIP 服务:这是默认的服务类型,它为服务分配一个仅在集群内部可访问的虚拟 IP 地址。集群内的其他 Pod 可以通过这个 ClusterIP 来访问对应的服务。

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: ClusterIP

在上述示例中,my-service 服务通过 selector 标签选择了具有 app: my-app 标签的 Pod。它将集群内部的 80 端口映射到被选中 Pod 的 8080 端口。

NodePort 服务:这种服务类型在每个节点上打开一个特定的端口(NodePort),通过这个 NodePort 可以从集群外部访问服务。所有发送到 NodePort 的流量都会被转发到对应的 ClusterIP 服务,然后再转发到后端的 Pod。

apiVersion: v1
kind: Service
metadata:
  name: my-nodeport-service
spec:
  selector:
    app: my-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
    nodePort: 30080
  type: NodePort

这里,my-nodeport-service 服务通过 nodePort 字段指定了 30080 作为节点端口。外部客户端可以通过 <节点 IP>:30080 来访问这个服务。

LoadBalancer 服务:适用于云环境,它会在集群外部创建一个负载均衡器,将外部流量路由到后端的 Pod。云提供商(如 AWS、GCP 等)会负责提供和管理这个负载均衡器。

apiVersion: v1
kind: Service
metadata:
  name: my-loadbalancer-service
spec:
  selector:
    app: my-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: LoadBalancer

网络策略(NetworkPolicy)

网络策略用于定义 Pod 之间的网络访问规则。它基于标签选择器来指定允许或拒绝哪些 Pod 之间的通信。网络策略是基于命名空间的,默认情况下,所有 Pod 之间可以相互通信。

例如,以下网络策略允许具有 role: frontend 标签的 Pod 访问具有 role: backend 标签的 Pod 的 80 端口:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-to-backend
  namespace: my-namespace
spec:
  podSelector:
    matchLabels:
      role: backend
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 80

Kubernetes 网络模型原理剖析

容器间通信原理

在 Pod 内部,容器之间的通信基于 Linux 的网络命名空间技术。每个 Pod 有一个独立的网络命名空间,其中包含网络接口、路由表等网络资源。当容器被创建时,它们的网络接口被添加到 Pod 的网络命名空间中。

例如,在创建一个包含两个容器的 Pod 时,Kubernetes 会先创建一个网络命名空间,然后为每个容器创建虚拟网络接口(veth pair),并将其中一端添加到 Pod 的网络命名空间,另一端留在容器的网络命名空间。这样,容器之间就可以通过 Pod 网络命名空间内的网络接口进行通信。

从 IP 地址分配来看,Pod 内的所有容器共享一个 IP 地址,这个 IP 地址是在 Pod 被创建时从集群的 Pod 网络 CIDR 中分配的。每个 Pod 都有一个唯一的 IP 地址,这使得不同 Pod 之间可以通过 IP 地址进行通信。

Pod 与外部通信原理

Pod 要与外部通信,主要涉及到源地址转换(SNAT)和路由等机制。当 Pod 发起对外的网络请求时,请求首先会经过节点的网络栈。由于 Pod 的 IP 地址属于集群内部网络,为了让外部网络能够正确响应,节点会对请求数据包的源 IP 地址进行 SNAT,将其转换为节点的 IP 地址。

例如,假设 Pod 的 IP 地址是 10.244.0.10,节点的 IP 地址是 192.168.1.100。当 Pod 向外部服务器 8.8.8.8 发送请求时,数据包的源 IP 地址会从 10.244.0.10 转换为 192.168.1.100。外部服务器响应时,会将数据包发送到节点的 IP 地址 192.168.1.100,然后节点再根据目的 IP 地址(Pod 的 IP 地址)将数据包转发给对应的 Pod。

在路由方面,集群内的节点需要知道如何将数据包路由到其他节点上的 Pod。这通常通过在节点上配置路由表来实现。例如,每个节点会有一条指向 Pod 网络 CIDR 的路由,将发往 Pod 网络的数据包转发到对应的节点。

服务通信原理

ClusterIP 服务通信:当一个 Pod 要访问 ClusterIP 类型的服务时,Kubernetes 通过 iptables 或 IPVS 规则来实现流量转发。在 iptables 模式下,Kubernetes 会在节点上创建一系列 iptables 规则,将发往 ClusterIP 的流量随机转发到后端的某个 Pod。

例如,假设 my-service 的 ClusterIP 是 10.96.0.10,后端有两个 Pod,IP 地址分别是 10.244.0.1010.244.0.11。当一个 Pod 向 10.96.0.10:80 发送请求时,iptables 规则会随机选择一个后端 Pod(比如 10.244.0.10),将请求转发到该 Pod 的 80 端口。

在 IPVS 模式下,Kubernetes 利用 IPVS 提供的高性能负载均衡功能。IPVS 基于内核态的 Netfilter 框架,通过哈希表等数据结构来快速查找和转发流量。相比 iptables,IPVS 在处理大规模流量时具有更低的延迟和更高的吞吐量。

NodePort 服务通信:NodePort 服务是在 ClusterIP 服务的基础上实现的。当一个外部请求发送到节点的 NodePort 时,节点上的 iptables 规则会将流量转发到对应的 ClusterIP 服务,然后再由 ClusterIP 服务转发到后端的 Pod。

例如,假设 my-nodeport-service 的 NodePort 是 30080,ClusterIP 是 10.96.0.10。当外部请求发送到 192.168.1.100:30080 时,节点上的 iptables 规则会将流量转发到 10.96.0.10:80,然后再转发到后端的 Pod。

LoadBalancer 服务通信:在云环境中,当创建一个 LoadBalancer 类型的服务时,云提供商会创建一个外部负载均衡器(如 AWS 的 Elastic Load Balancing)。这个负载均衡器会将外部流量转发到集群内的节点。节点再通过 NodePort 或 ClusterIP 服务将流量转发到后端的 Pod。

例如,在 AWS 上创建一个 LoadBalancer 服务,AWS 会分配一个弹性 IP 地址给负载均衡器。外部用户通过这个弹性 IP 地址访问服务,负载均衡器将流量转发到集群内的节点,然后节点将流量转发到对应的 Pod。

Kubernetes 网络模型实践应用

部署简单的 Web 应用

我们以一个简单的 Node.js Web 应用为例,来展示 Kubernetes 网络模型的实际应用。

首先,创建一个包含 Node.js Web 应用的 Docker 镜像。假设我们的 Node.js 代码如下:

const http = require('http');

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello, Kubernetes!\n');
});

const port = 3000;
server.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

将上述代码打包成 Docker 镜像,并推送到镜像仓库。

然后,创建一个 Deployment 来管理 Pod:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-web-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-web-app
  template:
    metadata:
      labels:
        app: my-web-app
    spec:
      containers:
      - name: my-web-app
        image: your-image-repo/your-image:tag
        ports:
        - containerPort: 3000

接下来,创建一个 ClusterIP 服务来暴露这个应用:

apiVersion: v1
kind: Service
metadata:
  name: my-web-app-service
spec:
  selector:
    app: my-web-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 3000
  type: ClusterIP

此时,集群内的其他 Pod 可以通过 my-web-app-service:80 来访问这个 Web 应用。

多服务应用部署

假设我们有一个更复杂的应用,包含一个前端 Web 应用和一个后端 API 服务。

前端 Web 应用使用 React 开发,后端 API 服务使用 Python Flask 开发。

首先,为前端创建 Docker 镜像并推送。然后创建前端的 Deployment 和 Service:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: your-frontend-image-repo/your-frontend-image:tag
        ports:
        - containerPort: 3000
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  selector:
    app: frontend
  ports:
  - protocol: TCP
    port: 80
    targetPort: 3000
  type: NodePort

对于后端 API 服务,同样创建 Docker 镜像、Deployment 和 Service:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: backend
        image: your-backend-image-repo/your-backend-image:tag
        ports:
        - containerPort: 5000
apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  selector:
    app: backend
  ports:
  - protocol: TCP
    port: 80
    targetPort: 5000
  type: ClusterIP

在前端代码中,通过 backend-service:80 来访问后端 API 服务。外部用户可以通过 <节点 IP>:<NodePort> 来访问前端应用,前端应用再通过内部的 backend-service 来调用后端 API。

网络策略实践

假设我们有一个电商应用,包含用户服务、订单服务和支付服务。我们希望用户服务只能访问订单服务的特定端口,订单服务可以访问支付服务。

首先,为每个服务创建对应的 Deployment 和 Service,并为 Pod 添加合适的标签:

# 用户服务 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service
        image: your-user-service-image-repo/your-user-service-image:tag
        ports:
        - containerPort: 8080
# 用户服务 Service
apiVersion: v1
kind: Service
metadata:
  name: user-service-service
spec:
  selector:
    app: user-service
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: ClusterIP

类似地创建订单服务和支付服务的 Deployment 和 Service。

然后,创建网络策略:

# 用户服务到订单服务的网络策略
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: user-to-order
  namespace: e-commerce
spec:
  podSelector:
    matchLabels:
      app: order-service
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: user-service
    ports:
    - protocol: TCP
      port: 8081
# 订单服务到支付服务的网络策略
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: order-to-payment
  namespace: e-commerce
spec:
  podSelector:
    matchLabels:
      app: payment-service
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: order-service
    ports:
    - protocol: TCP
      port: 8082

这样,通过网络策略,我们限制了不同服务之间的网络访问,提高了应用的安全性。

网络模型优化与故障排查

网络性能优化

选择合适的网络插件:Kubernetes 支持多种网络插件,如 Flannel、Calico、Weave Net 等。不同的插件在性能和功能上有所差异。例如,Calico 基于 BGP 协议,在大规模集群中具有较好的网络扩展性和性能;而 Flannel 相对简单,适合小型集群。根据集群的规模和性能需求选择合适的网络插件可以提升网络性能。

优化网络拓扑:合理规划集群内节点的网络拓扑结构,减少网络跳数和带宽瓶颈。例如,在数据中心内部,可以采用高速网络交换机连接节点,确保节点之间有足够的带宽。同时,对于需要大量数据传输的 Pod,可以将它们调度到同一节点或相邻节点,减少跨节点的网络传输。

使用高性能网络设备:在硬件层面,选择高性能的网卡、交换机等网络设备。例如,使用 10Gbps 或更高带宽的网卡,可以显著提升节点的网络吞吐量。此外,一些支持 SR-IOV(Single Root I/O Virtualization)技术的网卡可以为容器提供更接近物理网卡性能的网络接口。

网络故障排查

检查 Pod 网络配置:当 Pod 出现网络问题时,首先检查 Pod 的网络配置是否正确。可以通过 kubectl describe pod <pod-name> 命令查看 Pod 的详细信息,包括 IP 地址分配、网络接口状态等。如果发现 Pod 的 IP 地址未分配或网络接口异常,可能是网络插件配置错误或节点网络故障。

验证服务配置:对于服务相关的网络问题,检查服务的配置是否正确。例如,确认 Service 的 selector 标签是否正确匹配后端的 Pod,端口映射是否正确。可以通过 kubectl describe service <service-name> 命令查看服务的详细信息。同时,检查 iptables 或 IPVS 规则是否正确配置,确保流量能够正确转发到后端 Pod。

排查网络策略问题:如果应用了网络策略,检查网络策略是否阻止了预期的网络通信。可以通过 kubectl describe networkpolicy <network-policy-name> 命令查看网络策略的详细信息,确认是否允许或拒绝了相应的 Pod 之间的通信。注意网络策略的优先级和匹配规则,确保策略配置符合应用的网络需求。

使用网络诊断工具:Kubernetes 提供了一些网络诊断工具,如 kubectl exec 可以在 Pod 内部执行命令,用于测试网络连通性。例如,可以在 Pod 内使用 pingtelnet 等命令测试与其他 Pod 或服务的连通性。此外,kubectl proxy 命令可以创建一个代理,通过浏览器访问 Kubernetes API 服务器,查看集群的网络状态和配置信息,辅助故障排查。

通过对 Kubernetes 网络模型的深入理解和实践应用,以及掌握网络优化和故障排查的方法,我们能够构建稳定、高效的容器化应用集群,充分发挥 Kubernetes 在容器编排方面的强大功能。在实际应用中,还需要根据具体的业务需求和环境特点,不断调整和优化网络配置,以满足应用的网络性能和安全要求。