Ingress:轻松拿捏集群流量管理
一、引言
通过我们对Service学习,我们知道了Service 对象,它是 Kubernetes 内置的负载均衡机制,使用静态 IP 地址代理动态变化的 Pod,支持域名访问和服务发现,它本质上就是一个由 kubeproxy 控制的四层负载均衡,在 TCP/IP 协议栈上转发流量。但在四层上的负载均衡功能还是太有限了,只能够依据 IP 地址和端口号做一些简单的判断和组合,而我们现在的绝大多数应用都是跑在七层的 HTTP/HTTPS 协议上的,有更多的高级路由条件,比如主机名、URI、请求头、证书等等,而这些在 TCP/IP 网络栈里是根本看不见的,它比较适合代理集群内部的服务。这就导致了一种很无奈的局面:我们的服务空有一身本领,却没有合适的机会走出去大展拳脚。Kubernetes 引入一个新的 API 对象,它就是我们今天的主角:Ingress,接下来我们就来揭开它的神秘面纱。
二、Ingress是什么
我们都知道Service 本身是没有服务能力的,它只是一些 iptables 规则,真正配置、应用这些规则的实际上是节点里的 kube-proxy 组件。如果没有 kube-proxy,Service 定义得再完善也没有用。
同样的,Ingress 也只是一些 HTTP 路由规则的集合,相当于一份静态的描述文件,真正要把这些规则在集群里实施运行,还需要有另外一个东西,这就是 Ingress Controller,它的作用就相当于 Service 的 kube-proxy,能够读取、应用 Ingress 规则,处理、调度流量。
Ingress 作为 K8s 中的关键组件,就像是一个智能的流量调度员,专门负责管理外部对集群内服务的访问,尤其是 HTTP 和 HTTPS 流量 。在 实际生产环境中,我们可能会有多个服务运行在 Kubernetes 集群内,比如 Web 应用、API 服务等,需 要一种高效的方式将外部流量引入到这些服务中。
举个例子,假设你有一个电商系统,包含商品展示服务、用户订单服务和支付服务等,Ingress 就可以将来自互联网的用户请求,根据不同的规则,准确地路由到对应的服务上,而无需为每个服务都创建复杂的 LoadBalancer,也不用将每个服务都直接暴露在节点上。它允许我们通过定义一系列规则,灵 活地控制流量的走向,为集群内的服务提供统一的外部访问入口。
在明白了Ingress是什么之后,接下来我们看看他和Service的区别在哪里。
三、Ingress 和 Service 的区别
在 K8s 体系中,Service 同样是非常重要的概念,那它和 Ingress 有什么区别呢?从功能定位上来看,-Service 主要是为了解决 K8s 集群内部服务发现和负载均衡问题,为一组 Pod 提供稳定的访问入口,实 现简单的负载均衡 。而 Ingress 专注于 HTTP/HTTPS 流量的管理,通过定义规则来实现更复杂的路由 。
从访问范围来说,Service 不仅能用于集群内部通信,也能通过 NodePort、LoadBalancer 等方式实现 有限的外部暴露 。而 Ingress 则是专门用于将外部的 HTTP/HTTPS 流量引入到集群内的服务。从实现 方式上,Service 是 K8s 核心内置资源,通过 kube-proxy 和 iptables/IPVS 规则就能实现负载均衡。而 Ingress 本身只是规则定义,需要依赖 Ingress Controller,像 Nginx Ingress Controller、Traefik 等来 实现流量转发。
简单来说,如果把 K8s 集群比作一个小区,Service 就像是小区内每个单元楼的门牌号,方便小区内居民(内部服务)互相访问;而 Ingress 则像是小区的大门,负责管理外来访客(外部流量)进入小区后 ,该去哪个单元楼。
四、Ingress 的工作原理
4.1 Ingress 资源定义
在 Kubernetes 中,Ingress 资源是通过 YAML 文件来定义的,通过这些配置,我们可以清晰地告诉 Kubernetes 如何处理外部流量的路由 。下面是一个简单的 Ingress 资源对象的 YAML 示例:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: example-ingressannotations:nginx.ingress.kubernetes.io/rewrite-target: /
spec:rules:- host: myapp.example.comhttp:paths:- path: /myapppathType: Prefixbackend:service:name: myapp-serviceport:number: 80
在这个示例中,关键配置项含义如下:
- metadata:元数据部分,name 定义了 Ingress 的名称为 example - ingress,annotations 则是一些注解,这里的 nginx.ingress.kubernetes.io/rewrite - target: / 表示将请求路径重写为根路径 。
- spec.rules:规则部分,定义了流量的路由规则。host 指定了主机名myapp.example.com,意味着只有访问这个域名的请求才会被这个 Ingress 规则处理。
- spec.rules.http.paths:HTTP 路径规则,path 指定了路径为 /myapp,pathType 为 Prefix 表示前缀匹配,即只要请求路径以 /myapp 开头就会匹配这个规则 。
- spec.rules.http.paths.backend:后端服务配置,service.name 指定了后端服务的名称为 myapp - service,port.number 指定了服务的端口为 80,这表明匹配到规则的请求会被转发到 myapp - service 服务的 80 端口 。
通过上面的介绍我们知道了Ingress 只是一些 HTTP 路由规则的集合,相当于一份静态的描述文件,真正要把 这些规则在集群里实施运行,还需要有另外一个东西,这就是 Ingress Controller,它的 作用就相当于 Service 的 kube-proxy,能够读取、应用 Ingress 规则,处理、调度流量。接下来我们就一起来看看Ingress Controller。
4.2 Ingress Controller
Ingress Controller是实现 Ingress 资源中定义规则的关键组件,它就像是 Ingress 规则的执行者。常见的 Ingress 控制器有 Nginx Ingress Controller、Traefik、HAProxy Ingress Controller 等 。
按理来说,Kubernetes 应该把 Ingress Controller 内置实现,作为基础设施的一部分,就像 kube-proxy 一样。 不过 Ingress Controller 要做的事情太多,与上层业务联系太密切,所以 Kubernetes 把 Ingress Controller 的实现交给了社区,任何人都可以开发 Ingress Controller,只要遵守 Ingress 规则就好。 这就造成了 Ingress Controller“百花齐放”的盛况。
这些实现中最著名的,就是老牌的反向代理和负载均衡软件 Nginx 了。从 Ingress Controller 的描述上我们也可以看到,HTTP 层面的流量管理、安全控制等功能其实就是经典的反向代理,而 Nginx 则是其中稳定性最好、性能最高的产品,所以它也理所当然成为了 Kubernetes 里应用得最广泛的 Ingress Controller。
下面的这张图就来自 Nginx 官网,比较清楚地展示了 Ingress Controller 在 Kubernetes 集 群里的地位:
4.3 IngressClass
那么到现在,有了 Ingress 和 Ingress Controller,我们是不是就可以完美地管理集群的进出流 量了呢?
在一个K8S集群里有一个 Ingress Controller,再给它配上许多不同 的 Ingress 规则,应该就可以解决请求的路由和分发问题了。
但随着 Ingress 在实践中的大量应用,很多用户发现这种用法会带来一些问题,比如:
- 由于某些原因,项目组需要引入不同的 Ingress Controller,但 Kubernetes 不允许这样做;
- Ingress 规则太多,都交给一个 Ingress Controller 处理会让它不堪重负;
- 多个 Ingress 对象没有很好的逻辑分组方式,管理和维护成本很高;
- 集群里有不同的租户,他们对 Ingress 的需求差异很大甚至有冲突,无法部署在同一个 Ingress Controller 上。
所以,Kubernetes 就又提出了一个 Ingress Class 的概念,让它插在 Ingress 和 Ingress Controller 中间,作为流量规则和控制器的协调人,解除了 Ingress 和 Ingress Controller 的强绑定关系。
现在,Kubernetes 用户可以转向管理 Ingress Class,用它来定义不同的业务逻辑分组,简化 Ingress 规则的复杂度。比如说,我们可以用 Class A 处理博客流量、Class B 处理短视频流量、Class C 处理购物流量。
这些 Ingress 和 Ingress Controller 彼此独立,不会发生冲突,所以上面的那些问题也就随着 Ingress Class 的引入迎刃而解了。
4.4 使用 YAML 描述 Ingress Class
其实 Ingress Class 本身并没有什么实际的功能,只是起到联系 Ingress 和 Ingress Controller 的作用,所以它的定义非常简单,在“spec”里只有一个必需的字段“controller”,表示要使用 哪个 Ingress Controller,具体的名字就要看实现文档了。
比如,如果我要用 Nginx 开发的 Ingress Controller,那么就要用名字“nginx.org/ingress controller”:
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:name: ngx-ink
spec:controller: nginx.org/ingress-controller
接下来我们使用一张图来展示Ingress 和 Service、Ingress Class 的关系。
五、在 Kubernetes 里使用 Ingress Controller
准备好了 Ingress 和 Ingress Class,接下来我们就需要部署真正处理路由规则的 Ingress Controller。
Ingress Controller 的 YAML 大概是这个样子:
apiVersion: apps/v1
kind: Deployment
metadata:name: nginx-ingress-controller-f5namespace: nginx-ingress
spec:replicas: 2selector:matchLabels:app: nginx-ingress-f5template:metadata:labels:app: nginx-ingress-f5spec:containers:- name: nginx-ingress-controllerimage: nginx/nginx-ingress:3.2.0 # F5 官方镜像args:- --ingress-class=ngx-ink # 关联的 IngressClass 名称- --controller-class=nginx.org/ingress-controller # 与 IngressClass 的 controller 字段对应ports:- name: httpcontainerPort: 80- name: httpscontainerPort: 443
有了 Ingress Controller,这些 API 对象的关联就更复杂了,我们可以用下面的这张图来看出它们是如何使用对象名字联系起来的:
在创建完Ingress Controller之后,还有最后一道工序,因为 Ingress Controller 本身也是一个 Pod,想要向外提供服务还是 要依赖于 Service 对象。所以你至少还要再为它定义一个 Service,使用 NodePort 或者 LoadBalancer 暴露端口,才能真正把集群的内外流量打通。
我们在 curl 发测试请求的时候需要注意,因为 Ingress 的路由规则是 HTTP 协议,所以就不能 用 IP 地址的方式访问,必须要用域名、URI。
可以修改 /etc/hosts 来手工添加域名解析,也可以使用 --resolve 参数,指定域名的 解析规则,比如在这里我就把“ngx.test”强制解析到“127.0.0.1”,也就是被 kubectl port-forward 转发的本地地址:
curl --resolve ngx.test:8080:127.0.0.1 http://ngx.test:8080
通过我们上述操作就可以解决掉我们在测试的时候使用IP出现的问题,真正将集群的内外流量打通。
六、Ingress 的高级应用
6.1 灰度发布
在软件开发与部署过程中,灰度发布是一种非常重要的策略,它允许我们在生产环境中逐步将新版本 的服务暴露给部分用户,从而降低发布风险 。基于 Ingress-nginx,我们可以实现灵活的灰度发布。
其原理主要是通过 Ingress-nginx 的注解(annotations)来实现流量切分 。比如,当我们有一个新版本的服务需要上线时,通过配置特定的注解,Ingress-nginx 可以根据请求的某些特征,将部分流量导 向新版本服务,其余流量仍然访问旧版本服务 。 下面是基于不同方式进行流量切分的配置示例:
1. 基于请求头(Header)的流量切分
假设我们有一个应用,希望将新版本服务灰度给内部测试人 员 。我们可以通过请求头来标识这些内部测试人员的请求 。首先,创建指向旧版本服务的主 Ingress :
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: main-ingressannotations:kubernetes.io/ingress.class: "nginx"
spec:rules:- host: example.comhttp:paths:- path: /pathType: Prefixbackend:service:name: old-version-serviceport:number: 80
然后,创建指向新版本服务的 Canary Ingress,并通过注解配置基于请求头的流量切分:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: canary-ingressannotations:kubernetes.io/ingress.class: "nginx"nginx.ingress.kubernetes.io/canary: "true"nginx.ingress.kubernetes.io/canary-by-header: "X-Internal-User"nginx.ingress.kubernetes.io/canary-by-header-value: "true"
spec:rules:- host: example.comhttp:paths:- path: /pathType: Prefixbackend:service:name: new-version-serviceport:number: 80
在这个配置中,当请求头中包含X-Internal-User: true时,请求会被路由到新版本服务new-version-service;否则,会被路由到旧版本服务old-version-service 。
6.2 基于权重的流量切分
假设我们要将 10% 的流量导向新版本服务 。创建如下 Canary Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: canary-ingressannotations:kubernetes.io/ingress.class: "nginx"nginx.ingress.kubernetes.io/canary: "true"nginx.ingress.kubernetes.io/canary-weight: "10"
spec:rules:- host: example.comhttp:paths:- path: /pathType: Prefixbackend:service:name: new-version-serviceport:number: 80
这样,大约 10% 的流量会被随机分配到新版本服务,其余 90% 的流量访问旧版本服务 。
6.3 基于 Cookie 的流量切
假设我们希望通过设置 Cookie,将特定用户路由到新版本服务 。创建如下 Canary Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: canary-ingressannotations:kubernetes.io/ingress.class: "nginx"nginx.ingress.kubernetes.io/canary: "true"nginx.ingress.kubernetes.io/canary-by-cookie: "new-version-cookie"
spec:rules:- host: example.comhttp:paths:- path: /pathType: Prefixbackend:service:name: new-version-serviceport:number: 80
当用户请求中携带new-version-cookie=always的 Cookie 时,请求会被路由到新版本服务;当携带new-version-cookie=never时,会被路由到旧版本服务 。
6.2 性能优化
在实际生产环境中,随着业务量的增长,对 Ingress 性能的要求也越来越高 。以下是一些性能优化建议:
1. 调整 Ingress 控制器参数
以 Nginx Ingress Controller 为例,我们可以通过修改其配置参数来提升性能 。比如,调整worker_processes参数,该参数决定了 Nginx 工作进程的数量,一般建议设置为与服务器 CPU 核心数相同,以充分利用 CPU 资源 。可以通过修改 Nginx Ingress Controller 的 ConfigMap 来实现:
kubectl edit configmap nginx-configuration -n ingress-nginx
在 ConfigMap 中添加或修改worker_processes参数:
apiVersion: v1
kind: ConfigMap
data:worker_processes: "4" # 根据实际CPU核心数调整
另外,还可以调整keepalive_timeout参数,它决定了 Nginx 保持与客户端连接的时间 。适当增大这个值,可以减少建立和关闭连接的开销 。在 ConfigMap 中添加或修改该参数:
apiVersion: v1
kind: ConfigMap
data:keepalive_timeout: "65" # 根据实际情况调整
2. 优化后端服务负载均衡策略
Ingress-nginx 支持多种负载均衡策略,如轮询(Round Robin)、IP 哈希(IP Hash)等 。根据业务特点选择合适的负载均衡策略可以提升性能 。比如,如果业务对会话粘性有要求,即希望同一客户端的请求始终路由到同一后端 Pod,可以使用 IP 哈希策略 。在 Ingress 资源配置中添加注解来指定负载均衡策略:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: myapp-ingressannotations:kubernetes.io/ingress.class: "nginx"nginx.ingress.kubernetes.io/load-balance: "ip-hash"
spec:rules:- host: myapp.example.comhttp:paths:- path: /pathType: Prefixbackend:service:name: myapp-serviceport:number: 80
此外,合理调整后端服务的副本数量,根据业务负载情况进行动态扩缩容,也能有效提升整体性能 。可以使用 Kubernetes 的 Horizontal Pod Autoscaler(HPA)来实现后端服务的自动扩缩容 。例如,根据 CPU 使用率来自动调整后端服务的 Pod 数量:
kubectl autoscale deployment myapp-deployment --cpu-percent=80 --min=2 --max=10
这条命令表示当myapp-deployment的 CPU 使用率达到 80% 时,自动增加 Pod 数量,最小保持 2 个 Pod,最大扩展到 10 个 Pod 。
七、总结
Ingress 作为 Kubernetes 集群中管理 HTTP 和 HTTPS 流量的关键组件,为我们提供了高效、灵活的外部流量管理方式 。通过本文的介绍,我们了解了 Ingress 的概念、与 Service 的区别、工作原理、丰富的使用场景,以及详细的实战搭建步骤和高级应用优化技巧 。
Tips: 为了大家快速高效的学习,已经将文章提交到了git仓库,涵盖后端大部分技术,以及后端学习路线,仓库内容会持续更新,建议 Star 收藏 以便随时查看https://gitee.com/bxlj/java-article。