使用 Ingress 路由 Kubernetes 流量

KubernetesBeginner
立即练习

简介

Service 为集群内的应用提供稳定的访问端点。Ingress 在 Service 前面增加 HTTP 路由规则,让多个应用可以通过主机名或 URL 路径共享同一个入口。

你将检查已准备好的 ingress-nginx 控制器,部署两个后端应用,添加基于主机和路径的路由,并跟踪请求经过 Ingress、Service 和 Pod 的完整链路。

在整个实验过程中,请牢记请求路径:客户端 → ingress-nginx 入口 → Ingress 规则 → Service → 就绪 Pod。当请求失败时,按这个顺序检查各个环节,比把 Ingress 当成一个无法拆解的黑盒更容易定位问题。

检查 Ingress 控制器

本步骤将检查 CKA 环境已经提供的控制器,而不是再次安装一个副本。

Ingress 对象只是配置,不是代理进程。只有兼容的控制器持续监视它时,Ingress 才会生效。首先确认这两部分基础设施都存在:名为 nginx 的 IngressClass,以及一个可用的 ingress-nginx 控制器 Deployment。

cd /home/labex/project/ingress-routing
kubectl get ingressclasses
kubectl get deployments,pods,services -n ingress-nginx
kubectl wait --for=condition=Available deployment/ingress-nginx-controller -n ingress-nginx --timeout=60s

Ingress 资源保存路由意图。ingress-nginx 控制器会监视 ingressClassName 为 nginx 的资源,并据此配置实际的代理。

在这个本地集群中,控制器 Service 通过 NodePort 暴露代理。稍后你将查询该端口,而不是把端口号硬编码到命令中。

准备好的环境应显示一个 nginx 类别、一个可用的控制器,以及一个 NodePort Service:

NAME    CONTROLLER
nginx   k8s.io/ingress-nginx

deployment.apps/ingress-nginx-controller   1/1   ...
service/ingress-nginx-controller           NodePort   ...   80:.../TCP,443:.../TCP

你可能还会看到两个状态为 Completed 的 admission Pod。它们是已经执行完毕的安装 Job,并不是失败的控制器副本。

部署两个 Service 后端

本步骤将通过一个多文档文件,创建两个单副本 Web 工作负载及其对应的 ClusterIP Service。

这些后端使用了 Kubernetes 入门课程中的概念,因此本步骤的重点是它们之间的路由关系,而不是 Deployment 语法。该文件包含由 --- 分隔的五个 YAML 文档:一个 Namespace,以及每个应用各自的 Deployment 和 Service。请将每个 Service.spec.selector 与对应 Pod 的标签放在一起检查;在 Ingress 能够访问后端之前,两者必须匹配。

cd /home/labex/project/ingress-routing
cat <<'EOF' > backends.yaml
apiVersion: v1
kind: Namespace
metadata: {name: practice-ingress}
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: storefront, namespace: practice-ingress}
spec:
  replicas: 1
  selector: {matchLabels: {app: storefront}}
  template:
    metadata: {labels: {app: storefront}}
    spec:
      containers: [{name: web, image: nginx:1.27-alpine, imagePullPolicy: IfNotPresent, ports: [{name: http, containerPort: 80}]}]
---
apiVersion: v1
kind: Service
metadata: {name: storefront, namespace: practice-ingress}
spec: {selector: {app: storefront}, ports: [{name: http, port: 80, targetPort: http}]}
---
apiVersion: apps/v1
kind: Deployment
metadata: {name: reports, namespace: practice-ingress}
spec:
  replicas: 1
  selector: {matchLabels: {app: reports}}
  template:
    metadata: {labels: {app: reports}}
    spec:
      containers: [{name: web, image: httpd:2.4-alpine, imagePullPolicy: IfNotPresent, ports: [{name: http, containerPort: 80}]}]
---
apiVersion: v1
kind: Service
metadata: {name: reports, namespace: practice-ingress}
spec: {selector: {app: reports}, ports: [{name: http, port: 80, targetPort: http}]}
EOF

一次性应用全部五个文档,然后等待两个 Deployment 完成滚动发布。分开等待可以清楚地显示具体是哪个后端出现问题,例如镜像或选择器配置错误:

kubectl apply -f backends.yaml
kubectl rollout status deployment/storefront -n practice-ingress --timeout=60s
kubectl rollout status deployment/reports -n practice-ingress --timeout=60s

Ingress 只能将流量路由到 Service,因此每个后端都必须已经拥有就绪端点。

在创建路由规则前,先明确确认这一前置条件:

kubectl get services,endpointslices -n practice-ingress

每个 Service 都应对应一个包含一个就绪地址的 EndpointSlice。

service/reports      ClusterIP   ...   80/TCP
service/storefront   ClusterIP   ...   80/TCP

endpointslice.../reports-...      IPv4   80   10.244.104...
endpointslice.../storefront-...   IPv4   80   10.244.104...

生成的后缀和地址会有所不同,但每个 Service 名称都应对应一个非空的 EndpointSlice。Ingress 无法修复没有后端的 Service;这项检查会先验证底层服务是否正常。

添加基于主机的路由

本步骤将 store.practice.test 路由到 storefront Service。

Ingress 规则包含三个路由决策:host、path 和后端 Service 端口。ingressClassName: nginx 用于选择控制器。重写注解是控制器专用配置,它会将发往后端的请求路径修改为 /,这样两个演示 Web 服务器都能返回页面。

cd /home/labex/project/ingress-routing
cat <<'EOF' > ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: practice-routing
  namespace: practice-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: store.practice.test
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: storefront
                port: {name: http}
EOF

应用规则,然后使用 describe 查看控制器对规则的解析结果,其中包括已经解析出的后端端点:

kubectl apply -f ingress.yaml
kubectl describe ingress practice-routing -n practice-ingress

pathType: Prefix 会匹配 / 及其子路径。重写注解会将选中的请求发送到后端的 /,演示 Web 服务器在该路径提供页面。主机名在这里充当 HTTP 路由键;对于这个本地测试,不需要配置公共 DNS。

describe 的输出应将规则完整解析到某个 Pod 地址:

Rules:
  Host                 Path  Backends
  store.practice.test
                       /   storefront:http (10.244.104...:80)

在创建后的本地环境中,顶层 Address 暂时为空并不表示失败。已经解析出的 storefront:http 后端及其 Pod 地址才是关键证据。

添加路径路由并测试请求

本步骤将添加 /reports 路由,并通过节点入口测试两条规则。

JSON Patch 路径以 /- 结尾。对于数组来说,- 表示追加新项目,而不是替换现有的 / 路由。新的后端通过名为 http 的端口指向 reports Service。

请只运行一次这个追加补丁。与 kubectl apply 不同,向 /- 追加操作不是幂等的:重复执行会再添加一个完全相同的 /reports 项目,而不会识别之前已经添加过的项目。

下面两个赋值语句会自动发现入口地址,而不是将地址硬编码到命令中。minikube ip -p labex-v135 返回指定 Minikube 配置文件的节点 IP,命令替换 $(...) 会将每条命令的输出保存到 Shell 变量中。JSONPath 查询会选取名为 http 的 Service 端口,并提取其 NodePort。

kubectl patch ingress practice-routing -n practice-ingress --type='json' -p='[{"op":"add","path":"/spec/rules/0/http/paths/-","value":{"path":"/reports","pathType":"Prefix","backend":{"service":{"name":"reports","port":{"name":"http"}}}}}]'
NODE_IP=$(minikube ip -p labex-v135)
HTTP_NODE_PORT=$(kubectl get service ingress-nginx-controller -n ingress-nginx -o jsonpath='{.spec.ports[?(@.name=="http")].nodePort}')
echo "Ingress endpoint: ${NODE_IP}:${HTTP_NODE_PORT}"

这两个变量避免了在操作说明中嵌入特定环境的地址。现在,使用规则所要求的 Host 请求头发送请求。对于每条 curl 命令,-s 会隐藏进度信息,--retry 5 会重试暂时性失败,--retry-connrefused 会在连接最初被拒绝时也进行重试,--retry-delay 2 会在两次尝试之间等待两秒。-H 用于设置 HTTP Host 请求头,管道则将响应发送给 grep,因此只显示用于识别页面的文本。

curl -s --retry 5 --retry-connrefused --retry-delay 2 -H 'Host: store.practice.test' "http://${NODE_IP}:${HTTP_NODE_PORT}/" | grep 'Welcome to nginx'
curl -s --retry 5 --retry-connrefused --retry-delay 2 -H 'Host: store.practice.test' "http://${NODE_IP}:${HTTP_NODE_PORT}/reports" | grep 'It works'

这两个响应证明了同一个入口可以将请求路由到不同的 Service。

第一个响应来自 NGINX,第二个响应来自 Apache httpd。页面文本不同是有意设计的,用于证明路径规则确实选择了不同的后端,而不只是证明代理返回了 HTTP 200。

预期匹配的行如下:

<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
<title>It works! Apache httpd</title>
<p>It works!</p>

这个结果也说明了自定义 Host 请求头的重要性:两个请求都访问同一个节点 IP 和 NodePort,真正决定规则和后端的是主机名与路径。

总结

你复用了已准备好的 ingress-nginx 控制器,将 Ingress 规则连接到具有就绪端点的 Service,并通过一个入口测试了基于主机和路径的路由。