这篇文章我们继续在之前部署的 Kind k8s 环境 中使用 Ingress,以学习熟悉 k8s 的 Ingress 特性。
什么是 Ingress
k8s Ingress 本质上是一个 API 对象,是一份 声明文件,用于定义 L7(七层,例如 HTTP/HTTPS)的 路由规则配置,定义 哪个域名/路径去哪家 Service。Ingress 通过 能够感知 7 层协议(Protocol-Aware)的配置机制(能够理解 URI、主机名、Path 等概念),将你的 HTTP(或 HTTPS)网络服务暴露出来。Ingress 允许你通过 Kubernetes API 定义规则,并根据这些规则将流量映射到不同的后端服务。
通过 Ingress,你可以实现如下功能:
- 为服务提供外部可访问 URL
- 负载均衡流量
- 终止 SSL/TLS
- 实现基于域名的虚拟主机
下图展示了如何通过 Ingress 配置的规则将流量发送到不同的后端服务:
需要注意,Ingress 本身只是一种配置资源,必须部署 Ingress Controller 才能让 Ingress 规则生效,仅创建 Ingress 资源无任何作用。Ingress Controller 才是配置规则的执行者 (网关实体):
- 一个部署在集群内的 Pod 应用(如 Nginx、Traefik、HAProxy),是 Ingress 规范的具体实现。
- 监听 Kubernetes API Server,获取所有 Ingress 资源的配置
- 根据这些配置,动态生成自己的配置文件(例如,Nginx Controller 会生成 nginx.conf)
- 作为集群的真正入口,接收所有外部请求,并严格按照 Ingress 定义的规则将请求负载均衡到后端的 Pod
如下展示了在使用 k8s Ingress 之后,流量进入集群的过程:
- 用户发起请求:访问
https://xxx/api - 到达 Ingress Controller:它是集群流量的入口
- 解析 Ingress 规则:Controller 查找 Ingress 定义的规则,发现
/api对应的是名为backend-service的Service - 将流量转发到对应的后端 Service,并最终达到业务 Pod
所以虽然使用了 Ingress/Ingress Controller,仍然还是需要有 Service,Ingress 只是在入口处做决策,Service 维护着 Pod 的状态映射(Endpoints),Ingress 必须通过 Service 才能找到这一组 Pod:
- Service 负责把后端的 Pod 聚合成一个整体
- Ingress Controller 负责根据 Ingress 配置规则将流量分发到 Service 上
- 在生产环境下,为了让外部流量能访问到你的
Ingress Controller(即那个负责转发流量的 Nginx/Traefik Pod),你通常需要为这个 Controller 创建一个type: LoadBalancer的 Service。
那什么时候需要使用 Ingress 呢,什么时候直接使用 Service 呢?Ingress 是绝大多数 Web 应用的首选,因为它工作在七层(HTTP/HTTPS),能够提供多域名/多路径路由、统一 SSL/TLS 管理、负载均衡、高级流量控制(灰度发布、黑白名单限制等等)等功能。额外解释下,通过 Ingress 也可以减少对云负载均衡器的依赖(对于 HTTP/HTTPS 应用):
- 没有 Ingress 时:如果你有 10 个 Web 微服务,为了让外界能访问,你可能需要创建 10 个 type: LoadBalancer 的 Service
- 有了 Ingress 后:你只需要创建一个
Ingress Controller,并定义一个 Ingress 规则,把这 10 个微服务都挂在这一个公网 IP 下,通过域名(如a.com,b.com)或路径(如/service1,/service2)来区分
Ingress 主要是为 HTTP/HTTPS 设计的,如果你要暴露的是数据库(MySQL, MongoDB)、邮件服务器(SMTP)、游戏后端(TCP/UDP 自定义协议)等,你应该直接使用 Service。另外,Ingress Controller 本身是一个代理层(Nginx/Envoy),会带来微小的性能损耗。
接下来我们就通过一些实际例子,来学习如何在 k8s 环境中使用 Ingress。
Ingress 示例环境部署
这里我们将使用 ingress-nginx 来作为 Ingress Controller 进行部署,除此之外还有非常多的 Ingress Controller 实现,具体可以参考这个文档。
部署业务应用
首先我们创建部署业务应用,它使用 echo 作为示例业务应用,通过 echo 服务返回的内容我们知道请求是否被不同的 Service 处理了:
- 后端业务应用 Service 配置文件
ingress/apps/echo-a.yaml
1 | # 第 1 部分: Deployment (创建 Pod) |
-
类似地,我们再创建
echo-b.yaml,配置文件,配置内容是类似的,只不过返回的响应内容不同,这样方便我们知道是不是对应的 Service 被访问了 -
应用配置文件
1 | kubectl apply -f ingress/apps/echo-a.yaml |
- 查看应用
1 | # kubectl get svc |
部署 ingress-nginx Controller
- 使用官方的 Manifest 来部署
ingress-nginx
1 | kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.15.1/deploy/static/provider/baremetal/deploy.yaml |
Manifest 包含的主要内容:
| 资源 | 作用 | 为什么需要 |
|---|---|---|
| Namespace | 隔离空间 | Controller 独立运行 |
| ServiceAccount | 身份标识 | Controller 访问 API 的身份 |
| ClusterRole | 集群级权限 | 监听所有 namespace 的 Ingress |
| Role | namespace 权限 | 操作 ingress-nginx namespace 资源 |
| ClusterRoleBinding | 绑定集群权限 | 让 ServiceAccount 有 ClusterRole 权限 |
| RoleBinding | 绑定 namespace 权限 | 让 ServiceAccount 有 Role 权限 |
| ConfigMap | NGINX 配置 | 存储全局配置 |
| Deployment | Controller Pod | 实际运行的 Controller |
| Service | NodePort | 暴露 Controller (默认随机端口) |
| IngressClass | Controller 标识 | 区分不同 Controller |
| ValidatingWebhook | 验证 Ingress | 创建 Ingress 时校验配置 |
- 等待 Controller Ready
1 | kubectl rollout status deployment/ingress-nginx-controller -n ingress-nginx |
- 配置
Ingress Resource (路由规则)
按照如下配置文件内容,创建 Ingress 路由规则:
1 | # ingress/nginx/04-ingress-path.yaml |
| 字段 | 值 | 解释 |
|---|---|---|
ingressClassName |
nginx | 使用 NGINX Controller 处理这个 Ingress |
host |
api.test | 域名规则,请求必须 Host header 匹配 |
path |
/v1 | URL 路径匹配规则 |
pathType |
Prefix | 前缀匹配,/v1 匹配 /v1 和 /v1/xxx |
backend.service.name |
echo-a | 后端 Service 名称,必须已存在 |
backend.service.port.number |
80 | Service 端口 |
这里 rewrite-target 的作用:
1 | 请求: /v1/users |
- 应用该 Ingress 规则
1 | kubectl apply -f ingress/nginx/04-ingress-path.yaml |
- 修改
ingress-nginx-controller的 NodePort:默认部署的ingress-nginx-controllerService 是个 NodePort 类型的 Service,这一点我们可以通过ingress-nginx的 manifest 得知:
1 | apiVersion: v1 |
由于其并没有显式指定 NodePort,因此会随机分配 NodePort,而我们的 Kind 环境 只预留了端口映射 30000-30002,这样就无法通过宿主机来访问随机分配的 NodePort 了。为了解决这个问题,我们对 ingress-nginx-controller Service 进行 patch 操作,让 Service 使用固定 NodePort(30001/30002),这样就能通过宿主机的端口来访问了:
1 | kubectl patch svc ingress-nginx-controller -n ingress-nginx -p '{ |
检查 ingress-nginx
查看 ingress-nginx 命名空间(因为是系统组件,一般会运行在单独的命名空间下)的所有资源:
1 | # kubectl get all -n ingress-nginx |
- 之前说过,
ingress-nginxController 本质上就是一个跑在 Pod 里的 Nginx 服务(运行在ingress-nginx命名空间中),并通过一个 Service 暴露给外界 service/ingress-nginx-controller就是核心的业务流量入口,它是 NodePort 类型的 Service,因此是通过节点IP:NodePort来访问的,这里 NodePort 是我们所指定的 30001/30002service/ingress-nginx-controller-admission这是给 K8s 内部使用的(Validating Webhook),用来校验 Ingress 配置的正确性
接下来查看 Ingress 路由规则(业务应用,default 命名空间):
1 | # kubectl get ingress -o wide |
- NAME:Ingress 名称
- CLASS:IngressClass 名称,这里就是 nginx
- HOSTS:域名规则
- ADDRESS:LoadBalancer IP(NodePort 模式显示节点 IP)
- PORTS:端口
- AGE:创建时间
注意这里尤其需要注意 ADDRESS,这是 NodePort 模式的特性,Ingress ADDRESS 显示的是:
- 如果是 LoadBalancer,显示 LB IP
- 如果是 NodePort,显示 Ingress Pod 运行节点的 IP(或所有节点 IP)
1 | # kubectl get pod -n ingress-nginx -o wide |
测试 Service 访问
我们可以直接通过节点的 NodePort 来访问所暴露的 http 服务:
1 | # curl --noproxy "*" -s http://172.19.0.3:30001/v1 -H "Host: api.test" | jq '.environment.ECHO_SERVER_ID' |
-
对于 NodePort 类型的 Service,我们可以直接访问节点
IP:NodePort来访问服务,这里可以看到针对不同的 URL,访问不通的后端 Service(app-a/app-b) -
对于 externalTrafficPolicy 为 Cluster(默认)的 NodePort 类型的 Service,流量达到任意的集群节点上,都可以正确地转发到实际运行的 Pod 上,因此我们访问
172.19.0.3:30001和172.19.0.4:30001,流量最终都能正确转发,并不一定说ingress-nginx-controller运行在172.19.0.4上,就只能通过172.19.0.4:30001来访问
由于 NodePort 的端口 30001 和 30002 已经是映射后的端口(参见 Kind 的 extraPortMappings 配置),因此我们也可以直接通过宿主机本身的 IP 来访问:
1 | $ curl --noproxy "*" -s http://10.9.33.133:30001/v1 -H "Host: api.test" | jq '.environment.ECHO_SERVER_ID' |
使用 LoadBalancer 的 Ingress 部署
接下来我们将使用 LoadBalancer 来暴露 Ingress Controller,以和 NodePort 的 Ingress Controller 模式进行对比:
1 | NodePort 模式: 外部 → 基于 Node IP:NodePort → Service → Pod |
上一篇文章我们已经学习过基于 MetalLB 来部署 LoadBalancer 类型的 Service,这里我们直接使用 MetalLB 来部署 Ingress Controller Service。
- 创建配置文件
ingress/nginx/03-service-lb.yaml,实现 LoadBalancer 类型的 Ingress Controller Service:
1 | apiVersion: v1 |
| 字段 | 值 | 说明 |
|---|---|---|
type |
LoadBalancer | MetalLB 会分配外部 IP |
selector |
app.kubernetes.io/name=ingress-nginx | 与 Deployment Pod 标签匹配 |
port |
80/443 | Service 端口 |
targetPort |
80/443 | Pod 端口 |
注意:这个 Service 使用相同的 selector 来指向同一个 Deployment(ingress-nginx-controller)。这样 NodePort 和 LoadBalancer 两个 Service 都可以暴露同一个 Controller。
- 应用配置:
1 | # kubectl apply -f ingress/nginx/03-service-lb.yaml |
- 确认 Service 状态
1 | # kubectl get svc -n ingress-nginx |
可以看到,新增了 ingress-nginx-controller-lb 这个 类型为 LoadBalancer 的 Service,并且 MetalLB 分配了外部 IP 172.19.0.101。
- 修改
ingress-nginx-controller deployment的启动参数,通过--publish-service参数来指定Ingress Controller需要监听哪个 Service 来获取 ADDRESS 信息。它去查看你指定的那个 Service(通常是ingress-nginx-controller service本身)的 External IP 或 Node IP,并将这个有效地址填入所有 Ingress 资源的 ADDRESS 栏。我们将其改成使用新创建的ingress-nginx-controller-lb这个 Service:
1 | kubectl patch deployment ingress-nginx-controller -n ingress-nginx --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--publish-service=$(POD_NAMESPACE)/ingress-nginx-controller-lb"}]' |
- 等待 rollout 完成
1 | # kubectl rollout status deployment/ingress-nginx-controller -n ingress-nginx --timeout=60s |
- 验证配置生效
1 | # kubectl get deployment ingress-nginx-controller -n ingress-nginx -o yaml | grep 'publish-service' |
- 当 Controller 重启完成之后,查看 Ingress 可以看到:Ingress 资源的 ADDRESS 已经变成了我们 LoadBalancer Service 的外部 IP:
1 | # kubectl get ingress |
- 基于
LoadBalancer类型的 Ingress 来访问业务:
1 | # curl --noproxy "*" -s http://172.19.0.101:80/v1 -H "Host: api.test" | jq '.environment.ECHO_SERVER_ID' |
小结
这篇文章我们学习了如何 k8s 的基本概念,包括 Ingress、Ingress Controller、Service 之间的关联和差异,并实际部署了 NodePort 和 LoadBalancer 两种 Serviced 类型的 ingress-nginx-controller,并通过它们来访问不同的后端服务。