文章链接:https://blog.csdn.net/qq_43201350/article/details/163477946
从 Docker Compose 开发环境到 K8s 生产集群,这条路上坑比代码多。 本文是我在 3 个项目中将网关从开发→测试→预发→生产的完整记录,涵盖 12 个避坑点、5 套完整 YAML、3 次线上事故还原。 读完这篇文章,你能省下至少两周的踩坑时间。
目录
一、生产部署的基本盘
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: 9080cert-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:网络策略
坑:AdminAPI直接暴露
网关的 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-gateway13.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 元
如果这篇万字长文帮到了你,点个赞 + 收藏,让更多人看到 🙏