K8s 中的网络配置解析

K8s 中的网络配置解析

zeee 67 2025-04-06

总览

对于部署到 K8s 的服务 (Deployment) 来说,一个典型的网络过程是这样的:

image-20250426150648469

各个网络步骤说明如下:

  1. 用户发出请求,经 DNS 解析后到达服务网关(可能是某个 Load Balance 组件)。步是网络的固定解析路线,我们暂时不讨论。 重点关心流量到达 K8s 集群后,具体是如何流流转到 app-deployment 中的,以下的步骤:
  2. Load Balance 通过转发规则将流量转发到 K8s 的集群中 ,由某一个 Ingress 进行接收处理。
  3. Ingress 接收到流量后,通过配置的转发规则将请求转发到具体的 service, 例如上图中的 app-service
  4. Service 将流量转发到关联的 Deployment 中,具体来说是转发到 Deployment 的 Pod 实例中

上面各个步骤中的粗体部分,即是网络转发的关键新,我们最终要弄明白流量如何发到 Ingress, Ingress 如何处理请求转发到 Service , Service 转发到 Pod 时是否有什么策略。

1~2:User → Ingress

虽然最新版的 K8s 中, Ingress 组件已经被更加强大的 ApiGateway 代替, 但对于典型的 http 应用来说, Ingress 组件因为其简单的架构和配置,依旧是不错的选择。

Ingress 本质上也是一组 WebApi 服务, 实际处理流量的程序叫做 Ingress Controller , 在 K8s 中其是当作 Deployment 部署, 同样地通过一个 svc 来接收请求, 所以这个网络流程图变成下面这样子:

image-20250426160506405

那么问题变为:

  1. Load Balance 如何将请求转发到 ingress-svc
  2. 对于部署成 deployment 的程序来说,如何在 K8s 集群内标识它是一个 Ingress Controller , 而不是普通的后端服务。这样它才能接收并转发流量,与 ingress-svc 一道作为 Ingress 来工作,转发请求到后端服务 (app) 中. 更具体地说,只有这样它才能识别 ingress.yaml

为了方便说明,我们使用微软的反向代理工具 yarp 来作为 Ingress 进行后续说明

问题1

Service

​ 对于一些企业级应用例如阿里云ACK来说,创建 service 资源时可以指定资源类型为 Load Balance, 并指定的 Load Balance 实例(如 CLB/NLB) , 此时阿里云会在 CLB 中配置虚拟服务器组,将流量转发指定到 ingress-svc 中, 解决 svc 如何接收到流量的问题。

image-20250426172541051

所以如果使用的是第三方的 K8s 平台 并希望自己部署自己的 Ingress Controller 或者使用已经支持 LoadBalancer 的本地集群时,可以将 ingress-svc 的服务类型设置为 LoadBalancer , 并绑定到对应的 负载均衡设施, 云厂商会自动分配公网 IP 给负载均衡设施, 而负载均衡设施能自动将流量转发到 ingress-svc.

apiVersion: v1
kind: Service
metadata:
  name: ingress-svc
  namespace: yarp
spec:
  type: LoadBalancer  # 云厂商会自动分配公网 IP
  selector:
    app: ingress-controller
  ports:
    - name: http
      port: 80
      targetPort: 80
      nodePort: 30080  # 节点上的端口
    - name: https
      port: 443
      targetPort: 443
      nodePort: 30443

而如果没有相关的负载均衡设施, 那么就需要手动配置将流量转发到 Ingress Controller 所在的节点。这就需要相关前提:

  1. 节点 IP 可以公网访问
  2. Ingress Controller 需要保证会发到指定的节点上
  3. 通过 NodePort 的方式配置 service 是的节点可以直接接收流量
  4. 节点上尽量不要再发布其它服务(否则可能会影响到 Ingress)

第一点无需赘述, 第 2,3 需要在部署 Ingress Controller 时配置污点/容忍度或者节点亲和性来实现,简单地以污点和容忍度配置说明:

  1. 对指定节点添加标签如:node-ingress-controller

    kubectl lable nodes <node> dedlicated=node-ingress-controller
    kubectl taint nodes <node> dedlicated=node-ingress-controller:NoSchedule
    
  2. 在 deployment 中配置 nodeSelectortolerations

至此我们能够保证 Ingress Controller 程序会部到指定节点上,此时就可以通过 NodePort 的方式暴露节点的端口来接收流量,在 DNS 解析中配置任意节点的 IP 中,或者配置轮询解析。NotePort 方式的 Service 会自动将流量负载均衡到所有的 Pod 实例中

image-20250426182802581

service 的配置大概如下

apiVersion: v1
kind: Service
metadata:
  name: ingress-svc
  namespace: yarp
spec:
  type: NodePort
  selector:
    app: ingress-controller
  ports:
    - name: http
      port: 80
      targetPort: 80
      nodePort: 30080  # 节点上的端口
    - name: https
      port: 443
      targetPort: 443
      nodePort: 30443
 

此方法有一个问题,由于节点暴露的端口是 30080/30443, 所以请求时实际会是:http://example.com:30080https://example.com:30443 而如果需要通过 80 代理,还是得需要通过 LoadBalancer 来代理

Deployment

上面说到,对于 Ingress Controller 程序来说,需要通过污点和容忍度来进行部署,以确保只会发到指定节点上以及独占节点,据此只需配置节点选择器和容忍度即可:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: ingress-controller
  name: ingress-controller
  namespace: yarp
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: ingress-controller
    spec:
      nodeSelector: # 选择带有 node-ingress-controller 的节点进行部署
        dedicated: node-ingress-controller
      tolerations: # 如已经部署其它 Pod 则不部署,实现独占性
      - key: "dedicated"
        operator: "Equal"
        value: "node-ingress-controller"
        effect: "NoSchedule"
      
      containers:
#        serviceAccountName: yarp-serviceaccount
      - name: ingress-controller-image
        imagePullPolicy: IfNotPresent
        image: <yarp controller 的镜像地址>
        ports:
        - containerPort: 80
          name: http
          protocol: TCP
        - containerPort: 443
          name: https
          protocol: TCP
        env:
        - name: ASPNETCORE_URLS
          value: http://*:80;https://*:443
#        volumeMounts:
#          - name: config
#            mountPath: /app/config
#      volumes:
#        - name: config
#          configMap:
#            name: yarp-config

至此我们成功把 Ingress Controller 程序部署上去并且能接收到请求了, 接下来就是第二个问题:如何把这个 Deployment 标识为 Ingress Controller 资源来进行工作呢?

问题2

首先对于 K8s 来说,如果需要使用一个 Ingress , 则必须要有一个 Ingress Controller 实例在工作,即我们上面部署好的服务。 同时需要通过 IngressClass 类声明来指定 Ingress Controller 。 同 Service 一样, Ingress 是一种逻辑资源,用于表示由哪些控制器实现了该类。 所以在 Ingress d 这个架构图内部,实际上会有个一个虚拟的 IngressClass 资源来指定由哪个 IngressController 来实现该 Class

image-20250426190852131

那么这个 IngressClass 是如何指定 Controller 的呢? 我们看一下 IngressClass 的配置文件:

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: yarp-ingress-class
  annotations:
    #ingressclass.kubernetes.io/is-default-class: "true"
spec:
  controller: microsoft.com/ingress-yarp

这里 .spec.controller 字段即指定了由哪个 controller 来实现这个 IngressClass, 那么只要 Ingress Controller 来响应这个 microsoft.com/ingress-yarp 便可以。 我们翻一下 yarp 的源码可以找到下面这段代码

if (!string.Equals(_options.ControllerClass, ingressClass.Spec.Controller, StringComparison.OrdinalIgnoreCase))
{
    _logger.LogInformation(
        "Ignoring {IngressClassNamespace}/{IngressClassName} as the spec.controller is not the same as this ingress",
        ingressClass.Metadata.NamespaceProperty,
        ingressClass.Metadata.Name);
    return;
}

因此通过 _options.ControllerClass 和 IngressClass 配置文件中的 .spec.Controller 便可实现二者的绑定。 而 _options.ControllerClass 其实就是程序的配置文件,例如appsettings.json 中的配置项。

注意到上面的 deployment.yaml 中最后注释了一段 ConfigMap 的配置。 如果把注释打开便可以通过 ConfigMap 来实现 _options.ControllerClass 的动态配置, 只需要配置一个 ConfigMap 资源如下:

当然这个只是 yarp 这个程序的实现,不同的 IngressController 实现所需要的配置同,具体情况具体分析

apiVersion: v1
kind: ConfigMap
metadata:
  name: yarp-config
  namespace: yarp
data:
  yarp.json: |
    {
      "Yarp": {
        "ControllerClass": "microsoft.com/ingress-yarp",  // 这里指定了控制器实现的 IngressClass
        "ServerCertificates": false,
        "DefaultSslCertificate": "yarp/yarp-ingress-tls",
        "ControllerServiceName": "ingress-controller",
        "ControllerServiceNamespace": "yarp"
      }
    }

3: Ingress → App Service

以上步骤配置完成后, 请求会发到 Ingress Controller 进行处理, Ingress Controller 根据 ControllerClass 字段来决定实现哪个 IngressClass ,以明确自己应该处理哪些 Ingress 资源, 而通过 Ingress 资源,Ingress Controller 就知道如何将流量转发到后端服务(app-service) 中了。

所以 只要解决 IngressClass 如何指定 Ingress 资源的问题, 就能将文章最开头那条网络路线走通了。 其实说到这里答案已经呼之欲出:只需在 Ingress 资源定义中指定对应的 Ingress 资源即可

一个简单的 Ingress 资源定义如下:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-service-ingress
spec:
  ingressClassName: yarp-ingress-class
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app-service
            port:
              number: 80

这个资源说明该 Ingress 指定 IngressClass 为 yarp-ingress-class, 并将所有请求转发到 app-service 中。 如此,在不考虑最后一步(service->pod) 的情况下,文章最开始那条路线便走通了, 更详细的线路图应该是这样:

image-20250426195825298

多个服务使用不同的 Ingress:

上面展示的是一个服务的情况,但实际应用中 K8s 集群内都会部署不同的服务, 一般而言,不同的服务需要不同的 Ingress 来处理网络请求, 那么一个可行的配置 (包括命名)应该是这样的:

image-20250426201028155

4: Service -> Deployment (Pod)

默认情况下 service 会根据 selector 关联自己的 Deployment , 并将流量平均地转发到所有 Deployment 的所有 Pod 实例中。

其它配置 (权限)

由于 Ingress Controller 需要访问 K8s 的很多资源, 例如 service, 节点,namespace 等等才能正确地处理和转发请求。 所以一般建议单独为之配置 ServiceAccount 并分配权限,以及将其部署到单独的命名空间中。

注意到 上面的 deploymen.yaml 中注释掉了 container 的 serviceAccount 字段。 事实上是建议打开的。 在打开之前应该创建哈对应的资源:

Namespace

kind: Namespace
apiVersion: v1
metadata:
  name: yarp

ServiceAccount

apiVersion: v1
kind: ServiceAccount
metadata:
  name: yarp-serviceaccount
  namespace: yarp	

ClusterRole

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: yarp-ingress-clusterrole
  namespace: yarp
rules:
- apiGroups:
  - ""
  resources:
  - endpoints
  - nodes
  - pods
  - secrets
  - namespaces
  verbs:
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - get
- apiGroups:
  - ""
  resources:
  - services
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - services/status
  verbs:
  - get
- apiGroups:
  - networking.k8s.io
  - extensions
  - networking.internal.knative.dev
  resources:
  - ingresses
  - ingressclasses
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - networking.k8s.io
  resources:
  - events
  verbs:
  - create
  - patch
- apiGroups:
  - networking.k8s.io
  - extensions
  - networking.internal.knative.dev
  resources:
  - ingresses/status
  verbs:
  - get
  - update

ClusterRoleBinding

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: yarp-ingress-clusterrole-nisa-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: yarp-ingress-clusterrole
subjects:
- kind: ServiceAccount
  name: yarp-serviceaccount
  namespace: yarp

参考内容: