概述

Kubernetes Service 提供四层负载均衡,但在生产环境中,绝大多数 Web 应用需要七层路由能力:基于域名的虚拟主机、基于路径的路由、TLS 终止、灰度发布。Ingress 就是 K8s 对七层路由的抽象,而 Ingress Controller 则是这一抽象的具体实现。

选择 Ingress Controller 不是一个小决策——它处于所有外部流量的入口位置,一旦选错或配置不当,影响的是整个集群的服务可用性。本文对比主流 Ingress Controller 的优劣,并给出生产环境配置实践。

本文基于 Kubernetes v1.30。参考 Kubernetes Ingress 文档

Ingress 原理

数据流路径

客户端 → 负载均衡器(云LB/MetalLB) → Ingress Controller Pod → Service → Pod
                                    Ingress 资源
                                    (路由规则)

Ingress Controller 本质上是一个运行在集群中的 Pod(通常是 Deployment 或 DaemonSet),它:

  1. 监听 K8s API 中的 Ingress 资源变化
  2. 将 Ingress 规则翻译成自身配置(如 nginx.conf)
  3. 热加载配置,处理外部请求并路由到对应 Service

Ingress 资源结构

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  namespace: production
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - api.example.com
    secretName: api-tls
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /v1
        pathType: Prefix
        backend:
          service:
            name: api-v1-service
            port:
              number: 8080
      - path: /v2
        pathType: Prefix
        backend:
          service:
            name: api-v2-service
            port:
              number: 8080

pathType 三种模式

pathType匹配规则示例
Exact精确匹配/health 只匹配 /health
Prefix前缀匹配/api 匹配 /api/api/v1/api/v2/users
ImplementationSpecific由 Ingress Controller 决定Nginx 用正则,Traefik 用自己的规则

注意Prefix 匹配 /api 时也会匹配 /apixxx,因为它是前缀匹配而非路径分段匹配。如果需要按路径分段匹配,使用 Nginx 的 rewrite-target 注解或正则路径。

主流 Ingress Controller 对比

四大主流方案

特性Nginx IngressTraefikHAProxy IngressEnvoy Gateway
底层引擎Nginx/OpenResty自研 GoHAProxyEnvoy
配置方式注解 + ConfigMapCRD + 注解注解 + ConfigMapCRD + xDS
动态配置需要 reload(社区版)热加载热加载热加载
性能中高极高极高
社区活跃度最高高(CNCF)
学习曲线低(熟悉 Nginx)
生态丰富度高(Service Mesh)
适用规模中小到中大型中小型大型大型/Service Mesh

各方案详解

Nginx Ingress Controller

最广泛使用的 Ingress Controller,分为社区版(kubernetes/ingress-nginx)和 F5 版(nginxinc/kubernetes-ingress)。

优势:社区最大,文档丰富,踩坑资料多;基于 Nginx,运维人员熟悉;注解丰富,支持复杂路由逻辑。

劣势:社区版配置变更需要 reload(有毫秒级中断);注解太多导致配置碎片化,难以管理;对高级流量管理(金丝雀、A/B 测试)支持不够优雅。

Traefik

Go 语言编写的现代反向代理,自动服务发现。

优势:配置热加载无中断;原生支持 Let’s Encrypt 自动证书;Dashboard 直观;CRD(IngressRoute)功能强大。

劣势:性能不如 Nginx/HAProxy;高级路由依赖 CRD,与标准 Ingress 兼容性一般;社区版功能有限,企业版收费。

HAProxy Ingress

基于工业级 LB HAProxy。

优势:性能极高,连接复用优秀;统计报表强大;适合高并发场景。

劣势:社区较小,文档不如 Nginx 丰富;注解风格与 Nginx 不同,迁移成本高。

Envoy Gateway

基于 Envoy 的下一代网关,CNCF 项目。

优势:极高性能和可观测性;原生支持 HTTP/2、gRPC;与 Service Mesh(Istio)无缝衔接;xDS 动态配置无中断。

劣势:学习曲线陡峭;生态仍在发展中;配置复杂度高。

选型建议

场景推荐原因
通用 Web 应用,团队熟悉 NginxNginx Ingress生态成熟,资料丰富
快速原型/中小规模,需自动证书Traefik配置简单,Let’s Encrypt 集成
超高并发,性能优先HAProxy Ingress性能最优
Service Mesh / gRPC 密集Envoy Gateway原生支持,与 Istio 集成
多团队共享集群Nginx Ingress多 IngressClass 隔离

Nginx Ingress Controller 部署

安装

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.service.type=LoadBalancer \
  --set controller.config.proxy-body-size=50m \
  --set controller.config.proxy-read-timeout=3600 \
  --set controller.config.proxy-send-timeout=3600

生产级 Helm 配置

# values.yaml
controller:
  replicaCount: 2

  resources:
    requests:
      cpu: 100m
      memory: 128Mi
    limits:
      cpu: 1000m
      memory: 512Mi

  nodeSelector:
    node-role: ingress

  tolerations:
  - key: "ingress-only"
    operator: "Equal"
    value: "true"
    effect: "NoSchedule"

  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            app.kubernetes.io/name: ingress-nginx
        topologyKey: kubernetes.io/hostname

  config:
    proxy-body-size: "50m"
    proxy-read-timeout: "3600"
    proxy-send-timeout: "3600"
    proxy-connect-timeout: "10"
    keep-alive: "120"
    keep-alive-requests: "10000"
    worker-processes: "auto"
    max-worker-connections: "65535"
    enable-brotli: "true"
    brotli-types: "text/xml text/plain text/css application/javascript application/json image/svg+xml"
    use-gzip: "true"
    gzip-types: "text/plain text/css application/javascript application/json"
    ssl-protocols: "TLSv1.2 TLSv1.3"
    ssl-ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"
    ssl-session-cache: "shared:SSL:10m"
    ssl-session-timeout: "10m"
    log-format-escape-json: "true"
    log-format-upstream: '{"time":"$time_iso8601","remote_addr":"$remote_addr","x_forwarded_for":"$proxy_add_x_forwarded_for","request":"$request","status":$status,"body_bytes_sent":$body_bytes_sent,"request_time":$request_time,"upstream_response_time":"$upstream_response_time","upstream_addr":"$upstream_addr","upstream_status":"$upstream_status","http_referer":"$http_referer","http_user_agent":"$http_user_agent","request_id":"$req_id"}'

  podSecurityContext:
    runAsNonRoot: true
    runAsUser: 101
    fsGroup: 101

  containerSecurityContext:
    allowPrivilegeEscalation: false
    readOnlyRootFilesystem: true
    capabilities:
      drop:
      - ALL
      add:
      - NET_BIND_SERVICE

  healthCheckPath: /healthz

  metrics:
    enabled: true
    serviceMonitor:
      enabled: true

  admissionWebhooks:
    enabled: true
    failurePolicy: Fail

defaultBackend:
  enabled: true
  replicaCount: 2

TLS 终止

证书管理方案

方案适用场景特点
手动创建 TLS Secret内部服务/自签证书简单,需手动续期
cert-manager + Let’s Encrypt公网域名自动签发和续期
cert-manager + 自建 CA内部服务统一管理内部证书
云厂商证书管理云上 K8s与云 LB 集成

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
# Let's Encrypt ClusterIssuer
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-key
    solvers:
    - http01:
        ingress:
          class: nginx
# Ingress 自动签发证书
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
  - hosts:
    - api.example.com
    secretName: api-tls  # cert-manager 会自动创建
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 8080

TLS 版本与加密套件加固

# ConfigMap TLS 加固
data:
  ssl-protocols: "TLSv1.2 TLSv1.3"           # 禁用 TLS 1.0/1.1
  ssl-ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384"
  ssl-prefer-server-ciphers: "true"
  ssl-session-cache: "shared:SSL:10m"
  ssl-session-timeout: "10m"
  ssl-session-tickets: "false"                # 禁用 session tickets
  hsts: "true"                                 # 启用 HSTS
  hsts-max-age: "31536000"
  hsts-include-subdomains: "true"
  hsts-preload: "true"

HTTP 强制跳转 HTTPS

# 全局跳转(ConfigMap)
data:
  ssl-redirect: "true"

# 单个 Ingress 注解
annotations:
  nginx.ingress.kubernetes.io/ssl-redirect: "true"

路径路由

基于路径的多服务路由

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      # /api/v1/* -> api-v1-service/*
      - path: /api/v1(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: api-v1-service
            port:
              number: 8080
      # /api/v2/* -> api-v2-service/*
      - path: /api/v2(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: api-v2-service
            port:
              number: 8080
      # /static/* -> static-service/*(不重写路径)
      - path: /static/
        pathType: Prefix
        backend:
          service:
            name: static-service
            port:
              number: 80
      # / -> frontend-service
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80

WebSocket 支持

annotations:
  nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
  nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
  nginx.ingress.kubernetes.io/upstream-hash-by: "$remote_addr"  # 会话保持

Nginx Ingress Controller 默认支持 WebSocket,只需确保超时时间足够长。

gRPC 路由

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: grpc-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
spec:
  tls:
  - hosts:
    - grpc.example.com
    secretName: grpc-tls
  rules:
  - host: grpc.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: grpc-service
            port:
              number: 9090

灰度发布

Canary 注解方式

Nginx Ingress Controller 通过注解支持金丝雀发布:

# 主 Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-main
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-stable
            port:
              number: 8080
# Canary Ingress —— 按权重灰度
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-canary
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"   # 10%流量到新版本
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-canary
            port:
              number: 8080

灰度策略对比

策略注解适用场景
按权重canary-weight: "10"10%流量到新版本
按 Headercanary-by-header: "x-canary"指定 Header 值路由到新版本
按 Cookiecanary-by-cookie: "canary"指定 Cookie 路由到新版本
# 按 Header 灰度
annotations:
  nginx.ingress.kubernetes.io/canary: "true"
  nginx.ingress.kubernetes.io/canary-by-header: "x-canary"
  nginx.ingress.kubernetes.io/canary-by-header-value: "true"
# 组合使用:10%流量 + 特定Header全量
annotations:
  nginx.ingress.kubernetes.io/canary: "true"
  nginx.ingress.kubernetes.io/canary-weight: "10"
  nginx.ingress.kubernetes.io/canary-by-header: "x-canary"
  nginx.ingress.kubernetes.io/canary-by-header-value: "always"

注意:Canary Ingress 不会单独处理请求,它只是在主 Ingress 的基础上添加路由规则。两个 Ingress 必须使用相同的 host 和 path。

性能调优

Nginx Worker 参数调优

# ConfigMap 调优
data:
  worker-processes: "auto"             # 自动匹配CPU核数
  worker-cpu-affinity: "auto"          # CPU 亲和性
  max-worker-connections: "65535"      # 单 worker 最大连接数
  max-worker-open-files: "65535"       # 单 worker 最大文件描述符
  worker-shutdown-timeout: "240s"      # 优雅关闭等待时间
  worker-processes: "4"                # 手动指定(auto不准时)

连接与超时调优

data:
  keep-alive: "120"                    # 客户端 keep-alive 秒数
  keep-alive-requests: "10000"         # keep-alive 最大请求数
  upstream-keepalive-connections: "320" # 到后端的 keepalive 连接数
  upstream-keepalive-timeout: "60"     # 到后端的 keepalive 超时
  upstream-keepalive-requests: "10000" # 到后端的 keepalive 请求数
  proxy-connect-timeout: "10"          # 连接后端超时
  proxy-read-timeout: "60"             # 读后端超时
  proxy-send-timeout: "60"             # 发后端超时
  proxy-next-upstream: "error timeout" # 失败后重试下一个后端
  proxy-next-upstream-tries: "3"       # 最多重试3次

压缩优化

data:
  use-gzip: "true"
  gzip-types: "text/plain text/css application/javascript application/json application/xml image/svg+xml"
  gzip-min-length: "1024"              # 大于1KB才压缩
  gzip-comp-level: "5"                 # 压缩级别1-9
  enable-brotli: "true"                # Brotli 压缩(比gzip好)
  brotli-types: "text/plain text/css application/javascript application/json application/xml image/svg+xml"
  brotli-min-length: "1024"

缓冲区调优

data:
  proxy-buffer-size: "16k"             # 代理缓冲区大小
  proxy-buffers: "4 16k"               # 代理缓冲区数量和大小
  proxy-busy-buffers-size: "32k"       # 繁忙缓冲区大小
  client-header-buffer-size: "4k"      # 客户端请求头缓冲区
  large-client-header-buffers: "4 16k" # 大请求头缓冲区

负载均衡策略

# 默认:轮询(round-robin)
# 注解方式切换
annotations:
  nginx.ingress.kubernetes.io/load-balance: "least_conn"    # 最少连接
  # nginx.ingress.kubernetes.io/load-balance: "ip_hash"     # IP哈希(会话保持)
  # nginx.ingress.kubernetes.io/upstream-hash-by: "$request_uri"  # URI哈希
策略说明适用场景
round_robin(默认)轮询通用
least_conn最少连接长连接、请求耗时差异大
ip_hash客户端 IP 哈希需要会话保持
consistent_hash一致性哈希缓存场景

可观测性

日志配置

# JSON 格式日志(推荐)
data:
  log-format-escape-json: "true"
  log-format-upstream: >-
    {"time":"$time_iso8601","remote_addr":"$remote_addr","x_forwarded_for":"$proxy_add_x_forwarded_for","request":"$request","method":"$request_method","uri":"$request_uri","status":$status,"body_bytes_sent":$body_bytes_sent,"request_time":$request_time,"upstream_response_time":"$upstream_response_time","upstream_addr":"$upstream_addr","upstream_status":"$upstream_status","http_referer":"$http_referer","http_user_agent":"$http_user_agent","request_id":"$req_id","host":"$host","server_name":"$server_name"}    

Prometheus 指标

# 启用 metrics
controller:
  metrics:
    enabled: true
    serviceMonitor:
      enabled: true
      additionalLabels:
        release: prometheus

关键指标:

指标含义告警阈值
nginx_ingress_controller_requests请求总数/QPS根据容量规划
nginx_ingress_controller_response_duration_seconds响应延迟 P99> 1s
nginx_ingress_controller_nginx_process_connections当前连接数> max-worker-connections 的 80%
nginx_ingress_controller_upstream_latency_seconds后端延迟> 500ms
nginx_ingress_controller_errors错误数持续增长

Grafana Dashboard

推荐使用官方 Dashboard ID:9614(Nginx Ingress Controller)和 14314(Nginx 详细监控)。

生产实践

多 IngressClass 隔离

# 内网 IngressClass
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: nginx-internal
  annotations:
    ingressclass.kubernetes.io/is-default-class: "false"
spec:
  controller: k8s.io/ingress-nginx
  parameters:
    apiGroup: k8s.example.com
    kind: IngressParameters
    name: internal-params
# 外网 IngressClass
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: nginx-external
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
spec:
  controller: k8s.io/ingress-nginx

节点亲和性部署

# 将 Ingress Controller 调度到专用节点
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ingress-nginx-controller
spec:
  template:
    spec:
      nodeSelector:
        node-role.kubernetes.io/ingress: "true"
      tolerations:
      - key: "ingress-only"
        operator: "Exists"
        effect: "NoSchedule"
      # 反亲和性确保副本分布在不同节点
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app.kubernetes.io/name: ingress-nginx
            topologyKey: kubernetes.io/hostname

速率限制

annotations:
  nginx.ingress.kubernetes.io/limit-connections: "100"      # 最大并发连接
  nginx.ingress.kubernetes.io/limit-rps: "50"               # 每秒请求数
  nginx.ingress.kubernetes.io/limit-burst: "100"            # 突发请求数
  nginx.ingress.kubernetes.io/limit-retry-after-time: "60"  # 429响应Retry-After

基础认证

# 创建 htpasswd Secret
kubectl create secret generic basic-auth \
  --from-file=auth \
  -n production

# Ingress 注解
annotations:
  nginx.ingress.kubernetes.io/auth-type: basic
  nginx.ingress.kubernetes.io/auth-secret: basic-auth
  nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"

CORS 配置

annotations:
  nginx.ingress.kubernetes.io/enable-cors: "true"
  nginx.ingress.kubernetes.io/cors-allow-origin: "https://example.com,https://app.example.com"
  nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
  nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"
  nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
  nginx.ingress.kubernetes.io/cors-max-age: "86400"

常见问题排查

502 Bad Gateway

# 原因:后端 Service 没有就绪或 Pod 不可达
# 排查步骤:
kubectl get endpoints <service-name> -n <namespace>  # 检查 Endpoints
kubectl get pods -l app=<app-name> -n <namespace>    # 检查 Pod 状态
kubectl logs <ingress-pod> -n ingress-nginx | grep <domain>  # 检查 Ingress 日志

504 Gateway Timeout

# 原因:后端响应超时
# 解决:调整超时配置
annotations:
  nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
  nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"

配置不生效

# 检查 Ingress 是否被正确识别
kubectl get ingress -A
kubectl describe ingress <name> -n <namespace>

# 检查 IngressClass 是否匹配
kubectl get ingressclass

# 检查 Nginx 配置是否生成
kubectl exec -it <ingress-pod> -n ingress-nginx -- cat /etc/nginx/nginx.conf | grep <domain>

# 强制 reload(不推荐,仅排障)
kubectl exec -it <ingress-pod> -n ingress-nginx -- /nginx-ingress-controller --publish-service 2>&1 | head

SSL 证书不生效

# 检查证书 Secret 是否存在
kubectl get secret <tls-secret-name> -n <namespace>
kubectl get certificate -n <namespace>  # cert-manager

# 检查证书内容
kubectl get secret <tls-secret-name> -n <namespace> -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout

总结

Ingress Controller 是 K8s 集群的流量入口,选型和配置直接影响服务可用性。核心要点:

  1. 选型看场景:Nginx Ingress 是最安全的选择,生态成熟、资料丰富。只有在有明确需求(极高性能、Service Mesh 集成)时才考虑其他方案。
  2. TLS 必须加固:禁用 TLS 1.0/1.1,启用 HSTS,使用 cert-manager 自动管理证书。
  3. 灰度发布用 Canary 注解:Nginx Ingress 的 canary 注解能满足大部分灰度需求,复杂的流量管理考虑 Istio/Envoy。
  4. 性能调优抓关键参数max-worker-connectionsupstream-keepalive-connectionskeep-alive 是三个最关键的性能参数。
  5. 可观测性不能少:JSON 日志 + Prometheus 指标是标配,没有可观测性的网关等于盲飞。
  6. 多副本 + 反亲和性:Ingress Controller 至少 2 副本,分布在不同节点,避免单点故障。

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. Kubernetes Ingress 文档 — Kubernetes 官方,参考了Kubernetes Ingress 文档相关内容