API 网关 K8s 生产部署终极指南:万字总结 12 个避坑点 + 3 次线上事故复盘,从开发到上线一步到位!

API 网关 K8s 生产部署终极指南:万字总结 12 个避坑点 + 3 次线上事故复盘,从开发到上线一步到位!

从 Docker Compose 开发环境到 K8s 生产集群,这条路上坑比代码多。

本文是我在 3 个项目中将网关从开发→测试→预发→生产的完整记录,涵盖 12 个避坑点、5 套完整 YAML、3 次线上事故还原。

读完这篇文章,你能省下至少两周的踩坑时间。

目录

生产部署的基本盘

避坑 1:多副本 etcd 的正确姿势

避坑 2:滚动更新零中断

避坑 3:TLS 证书自动管理

避坑 4:配置管理优雅方案

避坑 5:HPA 自动扩缩容

避坑 6:健康检查配置

避坑 7:资源限制

避坑 8:日志与监控集成

避坑 9:网络策略与安全

避坑 10:灰度与金丝雀发布

事故复盘 x3

完整部署架构总览

一、生产部署的基本盘

1.1 从 Docker Compose 到 K8s 的思维转变

维度

Docker Compose

K8s 生产

存储

本地目录挂载

PVC / 云盘

网络

容器间直连

Service + Ingress

配置

文件映射

ConfigMap / Secret

扩容

手动改副本数

HPA 自动

证书

手动生成

cert-manager 自动

日志

docker logs

集中采集

1.2 生产环境拓扑

┌──────────────────┐

│ External LB │ (云厂商 CLB/ALB)

│ port: 443 │

└────────┬─────────┘

┌──────────────┴──────────────┐

│ K8s Ingress (Nginx) │

│ TLS 终结,转发到 Service │

└──────────────┬──────────────┘

┌──────────────┴──────────────┐

│ Service: api-gateway-svc │

│ ClusterIP, port: 9080 │

└──────────────┬──────────────┘

┌───────────────────┼───────────────────┐

▼ ▼ ▼

┌──────────┐ ┌──────────┐ ┌──────────┐

│Gateway-1 │ │Gateway-2 │ │Gateway-3 │

│ (Pod) │ │ (Pod) │ │ (Pod) │

│ Admin: │ │ Admin: │ │ Admin: │

│ 9180 │ │ 9180 │ │ 9180 │

└────┬─────┘ └────┬─────┘ └────┬─────┘

│ │ │

└──────────────────┼──────────────────┘

┌────────────┴────────────┐

│ Service: etcd-headless │

│ 指向 etcd StatefulSet │

└────────────┬────────────┘

┌─────────────────┼─────────────────┐

▼ ▼ ▼

┌──────────┐ ┌──────────┐ ┌──────────┐

│ etcd-0 │◄───►│ etcd-1 │◄─────►│ etcd-2 │

│ PVC:10Gi │ │ PVC:10Gi │ │ PVC:10Gi │

└──────────┘ └──────────┘ └──────────┘

二、避坑 1:高可用 etcd 集群

坑:单节点 etcd 是最大单点故障

很多人生产环境仍然 docker run etcd 单节点。一旦 etcd 挂了,网关无法读取路由配置、无法热更新,全站 503。

这个坑踩过的代价:全站宕机 45 分钟。

方案:StatefulSet 三节点 etcd

# k8s/etcd-statefulset.yaml

apiVersion: v1

kind: Service

metadata:

name: etcd-headless

namespace: demo-gateway

labels:

app: etcd

spec:

clusterIP: None

ports:

- name: client

port: 2379

targetPort: 2379

- name: peer

port: 2380

targetPort: 2380

selector:

app: etcd

---

apiVersion: apps/v1

kind: StatefulSet

metadata:

name: etcd

namespace: demo-gateway

spec:

serviceName: etcd-headless

replicas: 3

selector:

matchLabels:

app: etcd

podManagementPolicy: Parallel

template:

metadata:

labels:

app: etcd

spec:

affinity:

podAntiAffinity:

requiredDuringSchedulingIgnoredDuringExecution:

- labelSelector:

matchExpressions:

- key: app

operator: In

values:

- etcd

topologyKey: kubernetes.io/hostname

containers:

- name: etcd

image: bitnami/etcd:3.5

env:

- name: ALLOW_NONE_AUTHENTICATION

value: "yes"

- name: ETCD_NAME

valueFrom:

fieldRef:

fieldPath: metadata.name

- name: ETCD_ADVERTISE_CLIENT_URLS

value: "http://$(ETCD_NAME).etcd-headless.demo-gateway.svc.cluster.local:2379"

- name: ETCD_LISTEN_CLIENT_URLS

value: "http://0.0.0.0:2379"

- name: ETCD_INITIAL_ADVERTISE_PEER_URLS

value: "http://$(ETCD_NAME).etcd-headless.demo-gateway.svc.cluster.local:2380"

- name: ETCD_LISTEN_PEER_URLS

value: "http://0.0.0.0:2380"

- name: ETCD_INITIAL_CLUSTER_TOKEN

value: "demo-etcd-cluster"

- name: ETCD_INITIAL_CLUSTER

value: "etcd-0=http://etcd-0.etcd-headless.demo-gateway.svc.cluster.local:2380,etcd-1=http://etcd-1.etcd-headless.demo-gateway.svc.cluster.local:2380,etcd-2=http://etcd-2.etcd-headless.demo-gateway.svc.cluster.local:2380"

- name: ETCD_INITIAL_CLUSTER_STATE

value: "new"

ports:

- containerPort: 2379

name: client

- containerPort: 2380

name: peer

livenessProbe:

exec:

command:

- /bin/sh

- -c

- etcdctl endpoint health

initialDelaySeconds: 30

periodSeconds: 10

readinessProbe:

exec:

command:

- /bin/sh

- -c

- etcdctl endpoint health

initialDelaySeconds: 10

periodSeconds: 5

volumeMounts:

- name: data

mountPath: /bitnami/etcd

resources:

requests:

cpu: 200m

memory: 256Mi

limits:

cpu: 1000m

memory: 1Gi

volumeClaimTemplates:

- metadata:

name: data

spec:

accessModes: ["ReadWriteOnce"]

resources:

requests:

storage: 10Gi

关键点

配置项

作用

podAntiAffinity

确保三副本分布在不同物理节点

Parallel

并行启动,加速集群初始化

volumeClaimTemplates

每个节点独立存储,避免脑裂时数据冲突

ETCD_INITIAL_CLUSTER

预定义所有成员,启动时自动建集群

验证

# 检查集群健康

kubectl exec -n demo-gateway etcd-0 -- etcdctl endpoint health --cluster

# 预期: 三个节点都是 healthy

# 查看集群成员

kubectl exec -n demo-gateway etcd-0 -- etcdctl member list

三、避坑 2:滚动更新零中断

坑:默认滚动更新会导致连接断开

很多人以为 K8s 的 RollingUpdate 策略天然零中断。实际上,如果不配 preStop hook + terminationGracePeriodSeconds,新 Pod 还没就绪旧 Pod 就被杀了,导致流量黑洞。

方案:三要素保证零中断

# k8s/gateway-deployment.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

name: api-gateway

namespace: demo-gateway

labels:

app: api-gateway

spec:

replicas: 3

strategy:

type: RollingUpdate

rollingUpdate:

maxSurge: 1 # 最多多创建 1 个 Pod

maxUnavailable: 0 # 保证至少 3 个 Pod 始终可用

selector:

matchLabels:

app: api-gateway

template:

metadata:

labels:

app: api-gateway

spec:

# ⚠️ 关键1:给足优雅终止时间

terminationGracePeriodSeconds: 60

affinity:

podAntiAffinity:

preferredDuringSchedulingIgnoredDuringExecution:

- weight: 100

podAffinityTerm:

labelSelector:

matchExpressions:

- key: app

operator: In

values:

- api-gateway

topologyKey: kubernetes.io/hostname

containers:

- name: gateway

image: apache/apisix:3.10.0-centos

ports:

- containerPort: 9080

name: proxy

protocol: TCP

- containerPort: 9180

name: admin

protocol: TCP

- containerPort: 9443

name: tls

protocol: TCP

env:

- name: APISIX_STAND_ALONE

value: "false"

- name: APISIX_DEPLOYMENT_ETCD_HOST

value: "http://etcd-headless.demo-gateway.svc.cluster.local:2379"

# ⚠️ 关键2:生命周期钩子

lifecycle:

preStop:

exec:

command:

- /bin/sh

- -c

- |

echo "=== Preparing for graceful shutdown ==="

# 1. 将自己从负载均衡摘除(等 5 秒让 Service 端点更新)

sleep 5

# 2. 停止接收新请求(Nginx graceful shutdown)

apisix quit || true

# 3. 等待存量请求处理完毕

sleep 10

echo "=== Graceful shutdown completed ==="

# ⚠️ 关键3:就绪探针——旧 Pod 启动后再杀

readinessProbe:

httpGet:

path: /apisix/status

port: 9080

initialDelaySeconds: 15

periodSeconds: 5

failureThreshold: 3

livenessProbe:

httpGet:

path: /apisix/status

port: 9080

initialDelaySeconds: 30

periodSeconds: 15

failureThreshold: 5

resources:

requests:

cpu: 500m

memory: 512Mi

limits:

cpu: 2000m

memory: 2Gi

volumeMounts:

- name: gateway-config

mountPath: /usr/local/apisix/conf/config.yaml

subPath: config.yaml

volumes:

- name: gateway-config

configMap:

name: gateway-config

滚动更新全过程时间线

T+0s: kubectl apply 触发更新

T+0s: 创建新 Pod-1 (maxSurge=1,现 4 个 Pod)

T+15s: 新 Pod-1 就绪探针通过 → Service 端点加入

T+20s: 准备杀旧 Pod-A

T+20s: 旧 Pod-A 收到 SIGTERM

T+20s: preStop hook 执行: sleep 5 → apisix quit → sleep 10

T+35s: 旧 Pod-A 容器退出

T+35s: 创建新 Pod-2

... 重复

T+90s: 全部更新完成,全程 0 个 502

验证零中断

# 开一个终端持续压测

while true; do

curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" \

http://api-gateway-svc.demo-gateway.svc.cluster.local:9080/apisix/status

sleep 0.1

done

# 另一个终端触发更新

kubectl rollout restart deployment/api-gateway -n demo-gateway

# 观察终端 A:全程不应出现 5xx

四、避坑 3:TLS 证书自动管理

坑:手动管证书,过期了都不知道

手写 openssl 命令生成证书 → 文档里记到期日 → 忘了 → 某天一早用户反馈"网站被标不安全"。

方案:cert-manager 全自动

# 安装 cert-manager

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml

# k8s/cert-issuer.yaml

# 使用云厂商 DNS 验证的 ClusterIssuer

apiVersion: cert-manager.io/v1

kind: ClusterIssuer

metadata:

name: letsencrypt-prod

spec:

acme:

server: https://acme-v02.api.letsencrypt.org/directory

email: demo-admin@example.com

privateKeySecretRef:

name: letsencrypt-prod-key

solvers:

- http01:

ingress:

class: nginx

---

# 为网关域名申请证书

apiVersion: cert-manager.io/v1

kind: Certificate

metadata:

name: gateway-tls-cert

namespace: demo-gateway

spec:

secretName: gateway-tls-secret

duration: 2160h # 90 天

renewBefore: 360h # 提前 15 天续期

dnsNames:

- api.example.com

- admin-api.example.com

issuerRef:

name: letsencrypt-prod

kind: ClusterIssuer

---

# Ingress 配置 TLS

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: gateway-ingress

namespace: demo-gateway

annotations:

cert-manager.io/cluster-issuer: letsencrypt-prod

nginx.ingress.kubernetes.io/proxy-body-size: "10m"

nginx.ingress.kubernetes.io/ssl-redirect: "true"

spec:

ingressClassName: nginx

tls:

- hosts:

- api.example.com

secretName: gateway-tls-secret

rules:

- host: api.example.com

http:

paths:

- path: /

pathType: Prefix

backend:

service:

name: api-gateway-svc

port:

number: 9080

cert-manager 自动做的事情

步骤

说明

1

向 Let’s Encrypt 申请证书

2

通过 HTTP-01 验证域名归属

3

证书写入 Secret gateway-tls-secret

4

证书过期前 15 天自动续期

5

Ingress 自动挂载最新证书

五、避坑 4:配置管理(不要硬编码)

坑:把配置写在启动命令或镜像里

改一个路由超时时间 → 重新打镜像 → 走 CI/CD → 部署 → 半小时过去了。

方案:ConfigMap + 热加载

# k8s/gateway-config.yaml

apiVersion: v1

kind: ConfigMap

metadata:

name: gateway-config

namespace: demo-gateway

data:

config.yaml: |

apisix:

node_listen: 9080

enable_admin: true

enable_admin_cors: true

# etcd 集群地址

deployment:

role: traditional

role_traditional:

config_provider: etcd

etcd:

host:

- "http://etcd-0.etcd-headless.demo-gateway.svc.cluster.local:2379"

- "http://etcd-1.etcd-headless.demo-gateway.svc.cluster.local:2379"

- "http://etcd-2.etcd-headless.demo-gateway.svc.cluster.local:2379"

prefix: "/apisix"

timeout: 30

# Admin API 配置

admin_key:

- name: admin

key: xxxxxxxxxxxxxx # 生产环境用 Secret!

role: admin

# Prometheus 暴露

plugin_attr:

prometheus:

export_uri: /apisix/prometheus/metrics

enable_export_server: true

export_addr:

ip: 0.0.0.0

port: 9091

# 日志配置

nginx_config:

error_log: "/usr/local/apisix/logs/error.log"

error_log_level: "warn"

http_configuration_snippet: |

# 真实客户端 IP——网关前面有 LB/Ingress

real_ip_header X-Forwarded-For;

real_ip_recursive on;

set_real_ip_from 0.0.0.0/0;

# 日志格式——结构化 JSON,方便采集

log_format main escape=json

'{'

'"timestamp":"$time_iso8601",'

'"remote_addr":"$remote_addr",'

'"x_forwarded_for":"$http_x_forwarded_for",'

'"method":"$request_method",'

'"uri":"$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_host":"$http_host",'

'"http_user_agent":"$http_user_agent",'

'"route_id":"$http_x_apisix_route_id"'

'}';

access_log /usr/local/apisix/logs/access.log main buffer=16k flush=3s;

# k8s/gateway-secret.yaml

apiVersion: v1

kind: Secret

metadata:

name: gateway-admin-secret

namespace: demo-gateway

type: Opaque

stringData:

admin-key: "实际的生产密钥请用随机字符串生成"

关键点:敏感信息走 Secret,通用配置走 ConfigMap。

六、避坑 5:HPA 自动扩缩容(切忌拍脑袋)

坑:只配了 CPU 阈值

“CPU 超过 80% 就扩容”——然后某天突发流量,CPU 还没上来,内存先 OOM 了。

方案:多指标 HPA

# k8s/gateway-hpa.yaml

apiVersion: autoscaling/v2

kind: HorizontalPodAutoscaler

metadata:

name: api-gateway-hpa

namespace: demo-gateway

spec:

scaleTargetRef:

apiVersion: apps/v1

kind: Deployment

name: api-gateway

minReplicas: 3

maxReplicas: 20

behavior:

# 扩容策略:激进

scaleUp:

stabilizationWindowSeconds: 30

policies:

- type: Pods

value: 4 # 每 30s 最多扩容 4 个

periodSeconds: 30

- type: Percent

value: 50 # 或 50%

periodSeconds: 30

selectPolicy: Max

# 缩容策略:保守

scaleDown:

stabilizationWindowSeconds: 300 # 等 5 分钟再缩,防止抖动

policies:

- type: Pods

value: 1

periodSeconds: 60

metrics:

# 指标 1:CPU

- type: Resource

resource:

name: cpu

target:

type: Utilization

averageUtilization: 70

# 指标 2:内存

- type: Resource

resource:

name: memory

target:

type: Utilization

averageUtilization: 80

# 指标 3:自定义——QPS(需要 Prometheus Adapter)

- type: Pods

pods:

metric:

name: apisix_http_requests_per_second

target:

type: AverageValue

averageValue: "150" # 单 Pod 超过 150 QPS 就扩容

Prometheus Adapter 安装(用于自定义指标)

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts

helm install prometheus-adapter prometheus-community/prometheus-adapter \

--namespace demo-gateway \

--set prometheus.url=http://prometheus-server.demo-gateway.svc:9090

压测验证 HPA

# 使用 hey 压测

hey -z 120s -c 100 -q 200 \

http://api-gateway-svc.demo-gateway.svc.cluster.local:9080/demo/echo/hello

# 观察扩容

kubectl get hpa -n demo-gateway -w

# 预期: 副本数从 3 → 5 → 8 → ...

kubectl get pods -n demo-gateway -w

七、避坑 6:健康检查(配错等于没配)

坑:用 / 做健康检查路径

很多网关的 / 返回 404,这时 K8s 认为 Pod 不健康,一直重启——死循环。

# ❌ 错误配置

readinessProbe:

httpGet:

path: / # 这个路径可能 404!

port: 9080

# ✅ 正确配置

readinessProbe:

httpGet:

path: /apisix/status # 网关专用健康检查端点

port: 9080

三层探针的正确姿势

探针

作用

失败后果

典型配置

startupProbe

Pod 启动时专用

重启容器

initialDelay: 10s, failure: 30

readinessProbe

是否接收流量

从 Service 摘除

period: 5s, failure: 3

livenessProbe

是否活着

重启容器

period: 15s, failure: 5

startupProbe:

httpGet:

path: /apisix/status

port: 9080

initialDelaySeconds: 10

periodSeconds: 5

failureThreshold: 30 # 给足 2.5 分钟启动时间

readinessProbe:

httpGet:

path: /apisix/status

port: 9080

initialDelaySeconds: 5

periodSeconds: 5

failureThreshold: 3 # 15 秒没恢复就摘除

livenessProbe:

httpGet:

path: /apisix/status

port: 9080

initialDelaySeconds: 30

periodSeconds: 15

failureThreshold: 5 # 75 秒没恢复就重启

关键公式:

startupProbe 总容忍 = 30 × 5 = 150s(给 Pod 充足的启动时间)

readiness 总容忍 = 3 × 5 = 15s(快速摘除不健康的)

liveness 总容忍 = 5 × 15 = 75s(不到万不得已不重启)

八、避坑 7:资源限制——不要裸奔

坑:不设 limits

某个 Pod 的 Worker 进程悄悄泄漏内存,最后吃掉整个节点 90% 内存 → 节点 OOM → 驱逐所有 Pod → 雪崩。

方案:requests = limits = 稳定

resources:

requests:

cpu: "500m"

memory: "512Mi"

limits:

cpu: "2000m"

memory: "2Gi"

生产环境的经验值:

场景

requests.cpu

limits.cpu

requests.memory

limits.memory

轻流量 (< 500 QPS)

250m

1000m

256Mi

1Gi

中流量 (500-5000 QPS)

500m

2000m

512Mi

2Gi

高流量 (> 5000 QPS)

1000m

4000m

1Gi

4Gi

为什么不设 limits = 很大?

因为 K8s 的 QoS 机制:

requests == limits → Guaranteed(最高优先级,最后被杀)

requests < limits → Burstable(中优先级)

没设 → BestEffort(最低优先级,最先被驱逐)

网关是链路入口,必须 Guaranteed。

九、避坑 8:日志(进入生产就要换方案)

坑:仍然用 http-logger 推日志

http-logger 在开发环境够用,但生产环境有 3 个问题:

丢失:网关崩溃时未推送的日志消失

阻塞:Loki 慢了会阻塞网关线程

耦合:日志和业务共享一个 HTTP 连接池

方案:DaemonSet 采集器 + stdout 输出

# 配置网关日志输出到 stdout/文件

# 在 ConfigMap 中:

nginx_config:

access_log: "/usr/local/apisix/logs/access.log main buffer=64k flush=5s"

error_log: "/usr/local/apisix/logs/error.log warn"

# k8s/gateway-deployment.yaml 追加 sidecar

spec:

containers:

- name: gateway

# ... 主容器配置 ...

volumeMounts:

- name: shared-logs

mountPath: /usr/local/apisix/logs

# Sidecar——用 Filebeat/Promtail 采集日志

- name: log-shipper

image: grafana/promtail:2.9.0

args:

- -config.file=/etc/promtail/promtail.yaml

volumeMounts:

- name: shared-logs

mountPath: /var/log/gateway

- name: promtail-config

mountPath: /etc/promtail

resources:

requests:

cpu: 50m

memory: 64Mi

limits:

cpu: 200m

memory: 128Mi

volumes:

- name: shared-logs

emptyDir: {} # Pod 内共享的日志目录

- name: promtail-config

configMap:

name: promtail-config

# k8s/promtail-config.yaml

apiVersion: v1

kind: ConfigMap

metadata:

name: promtail-config

namespace: demo-gateway

data:

promtail.yaml: |

server:

http_listen_port: 9080

grpc_listen_port: 0

clients:

- url: http://loki.demo-gateway.svc.cluster.local:3100/loki/api/v1/push

positions:

filename: /tmp/positions.yaml

scrape_configs:

- job_name: gateway-access-log

static_configs:

- targets:

- localhost

labels:

app: api-gateway

log_type: access

__path__: /var/log/gateway/access.log

pipeline_stages:

- json:

expressions:

timestamp: timestamp

status: status

request_time: request_time

upstream_addr: upstream_addr

- labels:

status:

upstream_addr:

- job_name: gateway-error-log

static_configs:

- targets:

- localhost

labels:

app: api-gateway

log_type: error

__path__: /var/log/gateway/error.log

好处:日志采集和网关进程完全解耦,网关挂了日志也已写入磁盘并推送完毕。

十、避坑 9:网络策略

坑:Admin API 直接暴露

网关的 Admin API(端口 9180)默认监听 0.0.0.0,如果不限制网络策略,任何人只要知道 IP 就能增删路由。

方案:NetworkPolicy 最小权限

# k8s/gateway-networkpolicy.yaml

apiVersion: networking.k8s.io/v1

kind: NetworkPolicy

metadata:

name: gateway-network-policy

namespace: demo-gateway

spec:

podSelector:

matchLabels:

app: api-gateway

policyTypes:

- Ingress

- Egress

ingress:

# 允许外部流量访问网关代理端口

- from:

- namespaceSelector:

matchLabels:

name: ingress-nginx

ports:

- protocol: TCP

port: 9080

- protocol: TCP

port: 9443

# 只允许同 namespace 的监控组件访问 Admin API

- from:

- podSelector:

matchLabels:

app: prometheus

ports:

- protocol: TCP

port: 9180

# 只允许同 namespace 访问 metrics

- from:

- podSelector:

matchLabels:

app: prometheus

ports:

- protocol: TCP

port: 9091

egress:

# 网关需要访问 etcd

- to:

- podSelector:

matchLabels:

app: etcd

ports:

- protocol: TCP

port: 2379

- protocol: TCP

port: 2380

# 网关需要访问上游服务(按需放开)

- to:

- namespaceSelector: {}

podSelector: {}

ports:

- protocol: TCP

port: 8080

验证安全

# 从非允许的 Pod 访问 Admin API(应被拒绝)

kubectl run test-pod --rm -it --image=alpine/curl -- sh

curl http://api-gateway-svc.demo-gateway.svc.cluster.local:9180/admin-api/routes

# 预期: timeout / connection refused

十一、避坑 10:灰度发布(不是等比例切)

坑:用权重直接切流量

“新版本上线,先切 10% 流量试试”——但这 10% 里包含了一个超级 VIP 客户,出问题后投诉最猛。

方案:按请求头/用户粒度灰度

在网关层做灰度,而不是在 K8s Ingress 层:

# 在网关 Admin API 中配置金丝雀路由

# 1. 创建两个上游:stable 和 canary

curl -s http://127.0.0.1:9180/admin-api/upstreams/demo-svc-stable -X PUT -d '

{

"type": "roundrobin",

"nodes": {"demo-svc-stable.demo-gateway.svc.cluster.local:8080": 100}

}'

curl -s http://127.0.0.1:9180/admin-api/upstreams/demo-svc-canary -X PUT -d '

{

"type": "roundrobin",

"nodes": {"demo-svc-canary.demo-gateway.svc.cluster.local:8080": 100}

}'

# 2. 创建两条路由:canary 优先匹配

# Canary 路由——带特定 header

curl -s http://127.0.0.1:9180/admin-api/routes/demo-svc-canary -X PUT -d '

{

"uri": "/demo/*",

"name": "demo-svc-canary",

"priority": 100,

"vars": [

["http_x_canary", "==", "v2"]

],

"upstream_id": "demo-svc-canary",

"plugins": {

"traffic-split": {

"rules": [{

"weighted_upstreams": [{

"upstream_id": "demo-svc-canary",

"weight": 100

}]

}]

}

}

}'

# 主路由——默认

curl -s http://127.0.0.1:9180/admin-api/routes/demo-svc-stable -X PUT -d '

{

"uri": "/demo/*",

"name": "demo-svc-stable",

"priority": 10,

"upstream_id": "demo-svc-stable"

}'

灰度策略选择

策略

网关配置方式

适用场景

Header 匹配

vars: [["http_x_canary", "==", "v2"]]

内部测试人员验证

用户 ID 哈希

traffic-split + hash on userId

按百分比切,但同用户一致

地域灰度

vars: [["http_x_region", "==", "cn-south"]]

先在边缘地区验证

Agent 匹配

vars: [["http_user_agent", "~~", "内部测试客户端"]]

App 内测

十二、三起线上事故复盘

事故 1:etcd 磁盘满,网关全站 503

时间:某周五凌晨 2:00

现象:监控告警——所有路由 503,Prometheus 显示 etcd 连接失败。

原因:etcd 的 PVC 用完了所有磁盘空间(quota-backend-bytes 未设置上限)。

修复:

# 临时:手动压缩 etcd

kubectl exec etcd-0 -- etcdctl compact $(etcdctl endpoint status --write-out=json | jq '.[0].Status.header.revision')

kubectl exec etcd-0 -- etcdctl defrag

# 长期:配置自动压缩和配额

# 在 etcd StatefulSet 环境变量中加入

- name: ETCD_AUTO_COMPACTION_RETENTION

value: "1" # 保留 1 小时历史

- name: ETCD_QUOTA_BACKEND_BYTES

value: "8589934592" # 8GB 硬限制

教训:

etcd 磁盘必须监控(保留 30% 余量)

开启自动压缩(auto-compaction-retention)

PVC 配够大(生产建议 50Gi+)

事故 2:滚动更新导致 10 秒流量黑洞

时间:某周三下午 3:00

现象:发布新版本后,监控显示有 10 秒的 502 高峰。

原因:maxUnavailable: 1 + 没有 preStop hook → 旧 Pod 被直接 kill,新 Pod 还没就绪。

修复:改为本文第三章的配置(maxUnavailable: 0 + preStop + 就绪探针)。

教训:

maxUnavailable 生产环境必须设 0

preStop hook 的 sleep 时间要大于 Service 端点更新延迟(通常 5-10s)

事故 3:HPA 反复扩缩——“颤抖效应”

时间:某大促活动当天

现象:网关副本数在 3 ↔ 10 之间反复跳动,每 2 分钟一次。每次扩容都需要 30 秒,缩容后再来一波流量又不够,陷入死循环。

原因:scaleDown.stabilizationWindowSeconds 设成了 0(立即缩容)。

修复:

scaleDown:

stabilizationWindowSeconds: 300 # 等 5 分钟

policies:

- type: Percent

value: 10

periodSeconds: 60

把缩容窗口从 0 改为 300 秒后,"颤抖效应"消失。

教训:

扩容要快,缩容要慢

stabilizationWindowSeconds 至少 300 秒

大促前提前扩容避免冷启动延迟

十三、完整部署架构

13.1 最终部署清单

# 1. 创建 namespace

kubectl create namespace demo-gateway

# 2. 部署 etcd 集群

kubectl apply -f k8s/etcd-statefulset.yaml

# 3. 部署网关 + 配置

kubectl apply -f k8s/gateway-config.yaml

kubectl apply -f k8s/gateway-secret.yaml

kubectl apply -f k8s/gateway-deployment.yaml

# 4. 暴露服务

kubectl apply -f k8s/gateway-service.yaml

# 5. 配置 HPA

kubectl apply -f k8s/gateway-hpa.yaml

# 6. 配置 Ingress + TLS

kubectl apply -f k8s/cert-issuer.yaml

kubectl apply -f k8s/gateway-certificate.yaml

kubectl apply -f k8s/gateway-ingress.yaml

# 7. 部署监控

kubectl apply -f k8s/promtail-config.yaml

kubectl apply -f k8s/gateway-networkpolicy.yaml

# 8. 验证

kubectl get all -n demo-gateway

kubectl get hpa -n demo-gateway -w

kubectl describe certificate gateway-tls-cert -n demo-gateway

13.2 上线 CheckList

#

检查项

命令

1

etcd 三副本健康

kubectl exec etcd-0 -- etcdctl endpoint health --cluster

2

网关 Pod 分布在不同节点

kubectl get pods -n demo-gateway -o wide

3

HPA 正常工作

kubectl get hpa -n demo-gateway

4

证书有效期 > 30 天

kubectl get certificate -n demo-gateway

5

NetworkPolicy 生效

从 test-pod 验证 Admin API 不可访问

6

Prometheus 抓取正常

prometheus → Targets → api-gateway: UP

7

Loki 收到日志

Grafana → Explore → Loki

8

压测通过(2000 QPS 持续 5min)

hey -z 300s -c 100 ...

9

滚动更新零中断

kubectl rollout restart + 持续压测

10

告警规则可触发

手动停止上游节点,确认收到告警

13.3 最终文件结构

k8s/

├── etcd-statefulset.yaml # etcd 高可用集群

├── gateway-config.yaml # ConfigMap

├── gateway-secret.yaml # Admin Key

├── gateway-deployment.yaml # 网关 Deployment

├── gateway-service.yaml # Service

├── gateway-hpa.yaml # 自动扩缩容

├── gateway-ingress.yaml # Ingress + TLS

├── gateway-networkpolicy.yaml # 网络策略

├── gateway-certificate.yaml # cert-manager

├── cert-issuer.yaml # Let's Encrypt

├── promtail-config.yaml # 日志采集

└── verify.sh # 一键验证脚本

13.4 一键验证脚本

#!/bin/bash

# k8s/verify.sh

set -e

NS="demo-gateway"

RED='\033[0;31m'

GREEN='\033[0;32m'

NC='\033[0m'

check() {

if eval "$1" > /dev/null 2>&1; then

echo -e "${GREEN}[PASS]${NC} $2"

else

echo -e "${RED}[FAIL]${NC} $2"

fi

}

echo "=== API 网关生产部署验证 ==="

echo ""

check "kubectl get ns $NS" "Namespace 存在"

check "kubectl get pods -n $NS -l app=etcd --no-headers | grep Running | wc -l | grep 3" \

"etcd 三副本运行中"

check "kubectl exec -n $NS etcd-0 -- etcdctl endpoint health | grep 'is healthy' | wc -l | grep 3" \

"etcd 集群健康"

check "kubectl get deployment api-gateway -n $NS -o jsonpath='{.status.readyReplicas}' | grep -E '^[3-9]|^1[0-9]'" \

"网关 Deployment 运行中 (≥3 副本)"

check "kubectl get hpa api-gateway-hpa -n $NS" \

"HPA 配置生效"

check "kubectl get certificate gateway-tls-cert -n $NS -o jsonpath='{.status.conditions[0].status}' | grep True" \

"TLS 证书已就绪"

check "kubectl get ingress gateway-ingress -n $NS" \

"Ingress 配置正常"

echo ""

echo "=== 全部检查完成 ==="

写在系列最后

从第一篇文章的选择对比,到这篇生产部署指南,系列一"云原生 API 网关实战"共 10 篇文章,覆盖了:

序号

主题

核心价值

01

选型对比

选对工具,少走弯路

02

Docker 快速搭建

5 分钟跑起来

03

路由原理拆解

知道为什么快

04

手写插件

扩展能力

05

插件全景图

80% 场景全覆盖

06

负载均衡

怎么分发都不错

07

认证鉴权

安全可靠

08

AI Gateway

紧跟前沿

09

可观测性

看得见才能管得住

10

K8s 生产部署

从玩具到生产

如果你只记住三件事:

高可用 = 多副本 etcd + podAntiAffinity + readinessProbe + preStop hook

零中断 = maxUnavailable:0 + terminationGracePeriod + 就绪探针

可观测 = Prometheus + Loki + Grafana + 告警规则

这三件事做到了,你的网关就具备了生产级能力。

写在最后:部署只是开始,运维才是深渊

12 个避坑点 + 3 次事故复盘,这篇文章几乎是把我在 K8s 上踩的坑全倒出来了。

但坦白说,生产环境的挑战远不止"避坑"——

你可能还会面对这些场景:

🔸 流量突然翻 10 倍,网关怎么自动扩容?HPA + Cluster Autoscaler 怎么配?

🔸 Pod 起不来、流量不通、性能骤降——你能不能在 5 分钟内定位到根因?

🔸 单集群挂了怎么办?跨 region 多集群容灾架构怎么设计?

🔸 从 5000 QPS 压到 50000 QPS,网关参数怎么调?内核参数怎么改?

🔸 一个人运维网关,etcd 怎么备份?配置怎么版本管理?灾难恢复怎么演练?

这些问题的答案,都在我的付费专栏里。

🎯 专栏《云原生 API 网关从入门到生产》完整包含:

✅ 10 篇实战教程:

网关选型 / Docker 部署 / 动态路由原理 / 手写限流插件 / 鉴权全家桶 /

插件全景图 / 负载均衡 / 可观测性体系 / K8s 排查决策树 / 性能调优

✅ 完整代码仓库(一键 clone,所有 YAML / 脚本 / 配置直接能用)

✅ 一键极速部署脚本(./deploy.sh 一条命令拉起完整环境)

✅ 生产上线 Checklist(50 项 PDF,部署/安全/性能/监控四类,打印打勾用)

✅ 3 次线上事故复盘手册(根因分析 + 时间线 + 应急步骤 + 预防措施)

✅ K8s YAML 全集(可直接 kubectl apply 的完整生产级配置)

✅ 网关面试题精选 20 道(含答案解析)

✅ 付费读者优先答疑(48 小时内响应)

👉 点击订阅《云原生 API 网关从入门到生产》19.9 元

如果这篇万字长文帮到了你,点个赞 + 收藏,让更多人看到 🙏

// 相关文章

2026年美加墨世界杯德国队前瞻:重建中的传统豪门
dns该怎么填写(如何正确填写DNS)
菠菜365定位

dns该怎么填写(如何正确填写DNS)

⌛ 09-12 ⚠️ 8295
怎么样把微信和联系人同步
365bet足彩论坛

怎么样把微信和联系人同步

⌛ 10-29 ⚠️ 3807