Kubernetes Ingress 深度实战——Nginx Ingress 路由规则、SSL、限流配置
Kubernetes Ingress 深度实战——Nginx Ingress 路由规则、SSL、限流配置
适读人群:需要在 K8s 集群里管理 HTTP 路由的工程师 | 阅读时长:约 16 分钟 | 核心价值:Nginx Ingress 的完整实用配置,包括路由、HTTPS、限流、跨域
说起 Ingress,我有一段不太愉快的回忆。
公司的 K8s 集群跑了半年,某一天接到业务方投诉:他们的 API 服务频繁被第三方爬虫刷接口,QPS 能到 3000+,直接把我们的数据库连接池打满。
我当时的第一反应是在应用里加限流。后来想了想,这种事从网关层解决更合适,于是翻出了 Nginx Ingress 的限流配置文档,搞了两个小时,把爬虫的 IP 段直接限死了。
这件事让我重新认真学了一遍 Nginx Ingress,发现它比我之前认知的强大很多。
Ingress 和 Ingress Controller 的关系
很多人搞不清楚这两个概念:
- Ingress:K8s 里的一种资源对象(就像 Deployment、Service 一样),定义了"HTTP 路由规则"
- Ingress Controller:真正干活的控制器,它监听 Ingress 资源的变化,把规则翻译成实际的负载均衡配置
K8s 不内置 Ingress Controller,你需要自己安装。最常用的是 Nginx Ingress Controller(ingress-nginx,CNCF 维护的那个),此外还有 Traefik、HAProxy Ingress 等。
安装 Nginx Ingress Controller
# 使用 Helm 安装(推荐)
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 \
--set controller.replicaCount=2 \
--set controller.nodeSelector."kubernetes\.io/os"=linux \
--set controller.resources.requests.cpu=100m \
--set controller.resources.requests.memory=256Mi \
--set controller.resources.limits.cpu=1 \
--set controller.resources.limits.memory=512Mi
# 确认安装成功
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
# 记录 EXTERNAL-IP(LoadBalancer 的公网 IP,指向这个)基础路由配置
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-gateway
namespace: production
annotations:
kubernetes.io/ingress.class: "nginx"
# 超时配置
nginx.ingress.kubernetes.io/proxy-connect-timeout: "30"
nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
# Body 大小限制
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
spec:
rules:
# 主域名路由
- host: api.example.com
http:
paths:
- path: /v1/users
pathType: Prefix
backend:
service:
name: user-service
port:
number: 8080
- path: /v1/orders
pathType: Prefix
backend:
service:
name: order-service
port:
number: 8080
- path: /v1/payments
pathType: Prefix
backend:
service:
name: payment-service
port:
number: 8080
# 默认路由(兜底)
- path: /
pathType: Prefix
backend:
service:
name: gateway-service
port:
number: 8080
# 内部管理接口(不同域名,限制访问)
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-service
port:
number: 8080pathType 有三个值:
Exact:精确匹配/foo,不匹配/foo/或/foo/barPrefix:前缀匹配,/foo匹配/foo、/foo/bar等ImplementationSpecific:由 Controller 决定
HTTPS 和 TLS 配置
方案一:手动管理证书
# 创建 TLS Secret
kubectl create secret tls api-tls-secret \
--cert=api.example.com.crt \
--key=api.example.com.key \
-n productionapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-gateway-tls
namespace: production
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
tls:
- hosts:
- api.example.com
secretName: api-tls-secret
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gateway-service
port:
number: 8080方案二:cert-manager 自动签发(强烈推荐)
# 安装 cert-manager
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set installCRDs=true
# 创建 ClusterIssuer(Let's Encrypt 免费证书)
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
EOF然后在 Ingress 里加 annotation,cert-manager 会自动申请和续期证书:
metadata:
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- api.example.com
secretName: api-tls-secret # cert-manager 会自动创建这个 Secret限流配置
这是我处理那次爬虫事件用的方案:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-rate-limited
namespace: production
annotations:
# 基于 IP 的限流(每秒请求数)
nginx.ingress.kubernetes.io/limit-rps: "50"
# 基于连接数限流
nginx.ingress.kubernetes.io/limit-connections: "20"
# 限流白名单(白名单 IP 不限流)
nginx.ingress.kubernetes.io/limit-whitelist: "10.0.0.0/8,172.16.0.0/12"
# 超出限流后的状态码(默认 503,改成 429 更标准)
nginx.ingress.kubernetes.io/limit-req-status-code: "429"针对特定路径更严格的限流(需要配合 Nginx ConfigMap):
# nginx-configmap(全局配置)
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
# 全局日志格式,加上限流相关字段
log-format-upstream: '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_length $request_time [$proxy_upstream_name] [$proxy_alternative_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status $req_id'
# 限流请求带 Retry-After 头
limit-req-status-code: "429"
# 主动开启 client 请求体缓冲
proxy-buffering: "on"踩坑实录一:限流配置只对部分节点生效
现象:配置了 limit-rps: 50,但实际上有时候 QPS 能超过 50 很多。
原因:Nginx Ingress Controller 有多个副本(我们是 3 个),每个副本有独立的限流计数器,不共享。所以实际的全局 QPS 上限是 50 × 3 = 150。
解法:
方案一:把限流值除以副本数(50/3 ≈ 17)
方案二:使用 Redis 共享限流状态(需要 Nginx Plus 或自定义模块)
方案三:在接入层(云厂商 WAF 或 API Gateway)做全局限流,Nginx Ingress 做应用层补充限流
跨域(CORS)配置
metadata:
annotations:
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "https://app.example.com,https://admin.example.com"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-headers: "Authorization,Content-Type,X-Request-ID"
nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
nginx.ingress.kubernetes.io/cors-max-age: "3600"路径重写
有时候后端服务的路径和 Ingress 暴露的路径不一致,需要重写:
metadata:
annotations:
# 把 /api/v1/(.+) 重写成 /$1
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- host: api.example.com
http:
paths:
# 访问 /api/v1/users → 后端收到 /users
- path: /api/v1(/|$)(.*)
pathType: Prefix
backend:
service:
name: user-service
port:
number: 8080踩坑实录二:Ingress 更新后部分请求还是走旧路由
现象:更新了 Ingress 规则,新加了一个路径 /v2/products,但有时候请求还是 404,刷新一下又好了。
原因:Nginx Ingress Controller 需要重新加载配置(reload nginx),这个过程有几秒钟的延迟。在多副本情况下,不同副本的 reload 时间不同步,导致短暂的不一致。
解法:这是正常现象,无法完全避免,但可以减少 reload 频率:
- 批量修改 Ingress 规则,不要频繁小改
- 调整 Controller 的
--sync-period参数
踩坑实录三:WebSocket 连接被 Nginx 断开
现象:应用里有 WebSocket 功能,连接总是在 60s 后断开,客户端不停重连,服务端日志里有大量连接断开日志。
原因:Nginx 默认的 proxy_read_timeout 是 60s,WebSocket 的长连接如果超过这个时间没有数据传输,就会被 Nginx 主动断开。
解法:
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# WebSocket 升级头
nginx.ingress.kubernetes.io/proxy-set-headers: "ws-headers"
---
# ws-headers ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: ws-headers
namespace: production
data:
Upgrade: $http_upgrade
Connection: "upgrade"Nginx Ingress 是一个功能非常丰富的工具,annotation 就有几百个,一篇文章不可能覆盖所有情况。但把路由、HTTPS、限流这三个核心功能配置好,能解决大部分日常需求。
