Kubernetes 中的 Ingress:将应用公开到世界
1. 概述
当您的 cluster 内已有 Service 运行,但希望外部用户通过漂亮的域名(如 api.nonladev.com)访问,而不是混乱的 IP:port,这时就需要 Ingress。本文介绍 Ingress 是什么、与 Service type LoadBalancer 有何不同、如何安装 Ingress Controller 以及如何为常见场景编写 Ingress resource:基于路径的路由、基于主机的路由、TLS。阅读完毕后,您就能自己通过域名和 HTTPS 将 Spring Boot 应用公开到外部。
2. 为什么需要 Ingress
2.1. Service 的问题
Service type NodePort 或 LoadBalancer 虽然能用,但有局限性:
LoadBalancer:每个 Service 需要一个独立的外部 IP → 如果有很多 service 会很浪费(每个云 LB 都单独收费)。NodePort:暴露奇怪的端口(30000-32767),没有 TLS termination,无法按域名/路径路由。
假设您有 5 个微服务需要对外公开,使用 LoadBalancer 意味着需要 5 个云 LB、5 个外部 IP。Ingress 通过使用一个入口点将流量路由到内部的多个 Service,基于主机名或路径来做出决定。
2.2. 什么是 Ingress
Ingress 是一个 API object,定义了从外部路由 HTTP/HTTPS 流量到 cluster 内 Service 的规则。Ingress 本身不做任何事情 — 它需要Ingress Controller(运行反向代理 Pod,如 NGINX、Traefik、HAProxy)来读取规则并执行。
Client → Ingress Controller (LoadBalancer/NodePort) → Ingress rules → Service → Pod
**注意:**如果 cluster 还没有安装 Ingress Controller,创建 Ingress resource 也没有作用 — 它只是配置文件“躺在那里等待”controller 读取。
3. 安装 Ingress Controller
最常见的是 ingress-nginx。用 Helm 安装:
<!-- 不适用 — 使用 Helm/kubectl,而不是 Maven dependency -->
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace
此命令创建 namespace ingress-nginx,部署 Ingress Controller 并创建一个 type LoadBalancer 的 Service 以接收来自外部的流量。检查:
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
NAME TYPE EXTERNAL-IP PORT(S)
ingress-nginx-controller LoadBalancer 34.123.45.67 80:31234/TCP,443:31235/TCP
这个外部 IP 正是 cluster 中所有 Ingress 的唯一入口点。
4. Deployment 和 Service 示例
在编写 Ingress 之前,需要有 Service 来路由到。假设应用 order-service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 2
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: nonladev/order-service:1.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
Service order-service 暴露端口 80,转发到容器端口 8080。注意此 Service 不需要 type LoadBalancer,使用 ClusterIP(默认)就足够了,因为 Ingress Controller 会通过内部网络调用 Service。
5. 基础 Ingress resource — 基于主机的路由
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: order-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: api.nonladev.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: order-service
port:
number: 80
逐部分解释:
ingressClassName: nginx— 指定哪个 controller 处理这个规则(当 cluster 有多个 controller 时很重要)。rules.host— 只有具有 headerHost: api.nonladev.com的流量才会匹配这个规则。path+pathType: Prefix— 所有以/开头的请求(即全部)都会转发到order-service:80。- annotation
rewrite-target— 特定于 ingress-nginx,用于在转发前重写 URL(不同 controller 的 annotation 不同)。
应用并测试:
kubectl apply -f order-ingress.yaml
curl -H "Host: api.nonladev.com" http://34.123.45.67/
{"service":"order-service","status":"UP"}
在生产环境中,您需要将 DNS 记录 api.nonladev.com 指向 Ingress Controller 的外部 IP,不需要像上面那样假装 Host header。
6. 基于路径的路由 — 多个 service 共享域名
常见场景:一个域名,多个 service 对应不同的路径。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-gateway-ingress
spec:
ingressClassName: nginx
rules:
- host: api.nonladev.com
http:
paths:
- path: /orders
pathType: Prefix
backend:
service:
name: order-service
port:
number: 80
- path: /customers
pathType: Prefix
backend:
service:
name: customer-service
port:
number: 80
请求到 api.nonladev.com/orders/123 会路由到 order-service,而 /customers/55 会路由到 customer-service。pathType: Prefix 表示按前缀匹配 — 不同于 Exact(必须完全匹配)和 ImplementationSpecific(由 controller 自行解释,通常用于 regex)。
**注意:**在 Ingress v1 中,路径的顺序不影响匹配 — controller 会自动选择最长的匹配路径优先。但编写清晰的路径、避免重叠,可以防止难以调试的 bug。
7. TLS — Ingress 的 HTTPS
要使用 HTTPS,需要一个包含证书和密钥的 Secret,然后在 Ingress 中引用它。
kubectl create secret tls nonladev-tls \
--cert=tls.crt --key=tls.key
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: order-ingress-tls
spec:
ingressClassName: nginx
tls:
- hosts:
- api.nonladev.com
secretName: nonladev-tls
rules:
- host: api.nonladev.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: order-service
port:
number: 80
spec.tls 声明哪个主机使用哪个证书。Controller 会自动进行 TLS termination — 即在 Ingress 处解密 HTTPS,然后将纯 HTTP 转发到内部的 Service。您的 Pod 不需要知道任何关于 TLS 的事情。
在实际应用中,您通常使用 cert-manager 来自动获取 Let’s Encrypt 证书,而不是手动创建 Secret:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: order-ingress-tls
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- api.nonladev.com
secretName: nonladev-tls
rules:
- host: api.nonladev.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: order-service
port:
number: 80
annotation cluster-issuer 要求 cert-manager 自动创建和续期 Secret nonladev-tls — 不需要手动做任何事情。
8. 编写测试以验证 expose 后的端点
Ingress 启动后,您可以编写简单的集成测试来验证 controller class 正确处理路径(在 Spring Boot 应用层测试,不直接测试 YAML):
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderControllerIT {
@Autowired
private TestRestTemplate restTemplate;
@Test
void shouldReturnOrderWhenCalledWithOrdersPrefix() {
ResponseEntity<String> response =
restTemplate.getForEntity("/orders/123", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).contains("orderId");
}
}
此测试确保 Spring Boot 应用正确响应 Ingress rewrite 的路径 /orders/123 — 将后端代码错误与 Ingress 配置错误分开。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
9. 常见错误
- 遗漏
ingressClassName:如果 cluster 有多个 Ingress Controller(例如 nginx 和 traefik),Ingress 不清楚哪个 controller 处理它 → 可能无法工作或被两个都错误处理。 - Service type 错误:后端 Service 设置为
LoadBalancer没有必要,会浪费资源 — 只需ClusterIP。 pathType不符合预期:当需要Prefix时使用Exact,导致子请求(/orders/123)返回 404,因为只有/orders完全匹配。- DNS 未指向:在配置真实 DNS 之前,使用
curl -H "Host: ..."测试,避免浪费时间等待 DNS 传播。 - Rewrite annotation 用于错误的 controller:ingress-nginx 的 annotation 不适用于 Traefik 或 AWS ALB Ingress Controller — 每个 controller 有自己的 annotation 集,要仔细阅读相应文档。
**何时不应使用 Ingress:**如果应用使用非 HTTP/HTTPS 协议(例如原始 TCP、复杂的 gRPC streaming 或 UDP),标准 Ingress 不支持很好 — 需要查看
Gateway API或直接用 Service typeLoadBalancer来处理 TCP/UDP。
10. 总结
Ingress 是集中的 HTTP/HTTPS 路由层,帮助您通过单一入口点将多个 Service 公开到外部,节省 LoadBalancer 成本,并且便于管理域名/路径/TLS。关键要点:Ingress resource 只是规则,需要 Ingress Controller 来执行;选择正确的 ingressClassName 和 pathType;使用 cert-manager 来自动化 TLS。对于非纯 HTTP 的应用,考虑使用 Gateway API 而不是强行使用 Ingress。