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

各个网络步骤说明如下:
- 用户发出请求,经 DNS 解析后到达服务网关(可能是某个 Load Balance 组件)。步是网络的固定解析路线,我们暂时不讨论。 重点关心流量到达 K8s 集群后,具体是如何流流转到
app-deployment中的,以下的步骤: - Load Balance 通过转发规则将流量转发到 K8s 的集群中 ,由某一个 Ingress 进行接收处理。
- Ingress 接收到流量后,通过配置的转发规则将请求转发到具体的 service, 例如上图中的
app-service - 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 来接收请求, 所以这个网络流程图变成下面这样子:

那么问题变为:
- Load Balance 如何将请求转发到
ingress-svc - 对于部署成 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 如何接收到流量的问题。

所以如果使用的是第三方的 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 所在的节点。这就需要相关前提:
- 节点 IP 可以公网访问
- Ingress Controller 需要保证会发到指定的节点上
- 通过 NodePort 的方式配置 service 是的节点可以直接接收流量
- 节点上尽量不要再发布其它服务(否则可能会影响到 Ingress)
第一点无需赘述, 第 2,3 需要在部署 Ingress Controller 时配置污点/容忍度或者节点亲和性来实现,简单地以污点和容忍度配置说明:
-
对指定节点添加标签如:
node-ingress-controllerkubectl lable nodes <node> dedlicated=node-ingress-controller kubectl taint nodes <node> dedlicated=node-ingress-controller:NoSchedule -
在 deployment 中配置
nodeSelector和tolerations
至此我们能够保证 Ingress Controller 程序会部到指定节点上,此时就可以通过 NodePort 的方式暴露节点的端口来接收流量,在 DNS 解析中配置任意节点的 IP 中,或者配置轮询解析。NotePort 方式的 Service 会自动将流量负载均衡到所有的 Pod 实例中

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:30080或https://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

那么这个 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) 的情况下,文章最开始那条路线便走通了, 更详细的线路图应该是这样:

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

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
参考内容:
- yarp/samples/KubernetesIngress.Sample/README.md at 95cafb5479370532af69584a815f491d3820122e · dotnet/yarp
- 网络概述_容器服务 Kubernetes 版 ACK(ACK)-阿里云帮助中心
- Service 资源 | Kubernetes