K8s 流量入口选 Ingress Controller 是搭集群必经的题。本文对比 4 个主流选项。

一句话

  • Nginx Ingress:稳,文档全,默认选项
  • Traefik:现代,配置友好,中小规模
  • APISIX:性能强,插件多,国产
  • Istio Gateway:要 service mesh 就选

对比

维度 Nginx Traefik APISIX Istio
性能 最高
配置易用 中(annotation 多)
插件 极多
WebSocket / gRPC 支持 支持 支持 支持
动态配置 需 reload
社区
学习曲线

Nginx Ingress:默认选

helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.replicaCount=3 \
  --set controller.resources.requests.cpu=200m \
  --set controller.resources.requests.memory=256Mi

优点:
- 最广泛使用,遇到问题 Google 都有答案
- annotations 几乎覆盖 nginx.conf 所有指令
- 生产稳定性最佳

缺点:
- 改 backend 要 reload nginx(短暂中断)
- annotations 写多了乱

适合:中大规模、保守业务。

Traefik:现代友好

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: api
spec:
  routes:
  - match: Host(`api.example.com`)
    kind: Rule
    services:
    - name: api
      port: 8080
    middlewares:
    - name: rate-limit

优点:
- 配置语法清晰(IngressRoute CRD)
- 自动发现 Docker / K8s 服务
- 自带 Let's Encrypt 证书签发

缺点:
- 性能比 Nginx 略低
- 生产场景没 Nginx 久经考验

适合:小到中规模、新项目。

APISIX:性能党

基于 OpenResty (Nginx + Lua),性能极致。

helm install apisix apisix/apisix \
  --namespace apisix --create-namespace \
  --set gateway.type=LoadBalancer

优点:
- 性能最高(QPS 比 Nginx Ingress 高 30%+)
- 插件市场丰富(限流、鉴权、灰度、AB 测试)
- 全动态配置,零 reload
- Dashboard UI 友好

缺点:
- 国内文档好、国外社区相对小
- 故障排查需要懂一些 Lua

适合:高 QPS、网关有复杂逻辑(流量染色、灰度发布)。

Istio Gateway:上 mesh 就用

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: api-gw
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    hosts:
    - "*.example.com"

只在你已经/准备用 Istio service mesh 时才考虑,单纯做入口太重。

选型决策树

要 service mesh?
  └ Yes → Istio Gateway
  └ No → 业务规模?
          └ 小 → Traefik
          └ 中大 → 网关有复杂逻辑(限流/鉴权/灰度)?
                    └ Yes → APISIX
                    └ No → Nginx Ingress

教训:选 Ingress Controller 主要看团队熟悉度——你能 5 分钟定位故障的那个,就是最好的。

现象

一次 OSD 扩容后,集群有几十个 PG 长期处于 active+remapped+backfilling 状态,ceph -s 显示 recovery 速度只有几 MB/s,按这个速度回填几天都跑不完。本文按我自己的排查路径走一遍。

第一步:先确认是不是"真的慢"

ceph -s
ceph osd pool ls detail
ceph pg dump | awk '/active\+remapped\+backfilling/ {print $1, $15}' | head

重点看几件事:

  • 回填速度ceph -s 里的 recovery: X MiB/s 是否远低于磁盘能力
  • PG 总数 vs OSD 数:单个 OSD 上的 PG 数是否超过 200(默认 mon_max_pg_per_osd=250),上限会卡 PG 创建
  • 是否触发 backfillfull:单个 OSD 接近 90% 会停掉回填

第二步:看是不是被限流卡住了

Ceph 默认对 recovery 限流非常保守,生产经常调:

ceph config get osd osd_max_backfills
ceph config get osd osd_recovery_max_active
ceph config get osd osd_recovery_sleep_hdd

典型现象:HDD 集群 osd_recovery_sleep_hdd 默认是 0.1,意思是每个 IO 之间 sleep 100ms。临时拉高(恢复完记得改回去!):

ceph config set osd osd_max_backfills 4
ceph config set osd osd_recovery_max_active 8
ceph config set osd osd_recovery_sleep_hdd 0

NVMe/SSD 集群可以更激进,HDD 别开太高,会拖业务 IO。

第三步:是不是某几个 OSD 在拖后腿

ceph osd perf
ceph daemon osd.X dump_historic_slow_ops

一两块盘 latency 飙到几百 ms,要么 SMART 看坏块、要么这块就是慢盘,临时 ceph osd out X 把它隔离掉,让回填走完再处理。

第四步:PG 数量配比有没有问题

ceph osd df tree

如果某些 OSD 上 PG 数明显高于平均(差 30% 以上),说明 CRUSH 分布不均,可以:

  • 启用 upmap 平衡器:ceph balancer mode upmap && ceph balancer on
  • 或手动 reweight:ceph osd reweight-by-utilization

真正的坑:mClock 调度器

Reef 之后默认调度器从 WPQ 换成了 mClock,它会按 IOPS 配额自动限速。如果你发现怎么调上面的参数 recovery 都不动,多半是 mClock 在卡:

ceph config get osd osd_op_queue
# 临时切回 WPQ(需重启 OSD):
ceph config set osd osd_op_queue wpq

或者用 mClock 的 profile:

ceph config set osd osd_mclock_profile high_recovery_ops

收尾

回填完成后务必把限流改回默认,否则下次掉盘时业务 IO 会被 recovery 抢光:

ceph config rm osd osd_max_backfills
ceph config rm osd osd_recovery_max_active
ceph config rm osd osd_recovery_sleep_hdd

教训一句话:Ceph 的 recovery 速度=慢盘短板 + 限流参数 + 调度器策略,三个维度都得看。

默认 K8s 任意 Pod 间互通,零安全。NetworkPolicy 是命名空间级别的 L3/L4 防火墙,本文给一套生产可用的策略集。

前提

NetworkPolicy 是声明式,但真正生效要 CNI 支持

  • Calico ✅
  • Cilium ✅
  • WeaveNet ✅
  • Flannel ❌(默认不支持,要加 Canal)
kubectl get pods -n kube-system | grep -E "calico|cilium"  # 确认 CNI

第一条:默认拒绝

每个 namespace 都先加一条 default deny:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

应用后整个 ns 所有 Pod 收发都断。然后按需放开。

放通同 namespace 内部

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-same-namespace
  namespace: production
spec:
  podSelector: {}
  ingress:
  - from:
    - podSelector: {}

放通 DNS

90% 的应用都要 DNS,必须放:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53

跨 namespace 调用(前端 → 后端)

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-frontend
  namespace: backend
spec:
  podSelector:
    matchLabels:
      app: api
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: frontend
      podSelector:
        matchLabels:
          app: web
    ports:
    - protocol: TCP
      port: 8080

注意 ingress 是作用在被访问方——这条 policy 部署在 backend ns。

调试技巧

策略加错了 Pod 突然连不上,怎么定位?

# 1. 看哪些 policy 作用在这个 Pod
kubectl get networkpolicy -A -o jsonpath='{.items[*].spec.podSelector}' | grep <label>

# 2. 用 cilium hubble 看实际流量
hubble observe --from-pod production/api --to-pod production/db

# 3. 临时关掉所有 policy 看是否就通了
kubectl delete networkpolicy --all -n production  # 危险

常见坑

  1. labels 改了 policy 失效:podSelector 是按 label 匹配,重命名了 label 要同步改
  2. namespaceSelector 需要 ns 有对应 label:默认 ns 没 label,K8s 1.22+ 自动加 kubernetes.io/metadata.name
  3. Egress 拒绝把 healthcheck 也封了:kubelet 探测来自 host network,需要放通节点 IP
  4. CNI 不支持 Egress:老版本 Calico 默认只支持 Ingress

教训:NetworkPolicy 先在 staging 跑两周,盯监控有没有断流,再推生产。

K8s 所有状态都在 etcd 里。etcd 坏了集群就死了,但 etcd 备份恢复其实不难。本文是完整流程。

备份

ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%F-%H%M).db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

校验:

ETCDCTL_API=3 etcdctl snapshot status /backup/etcd-xxx.db -w table

自动化(crontab)

0 2 * * * /usr/local/bin/etcd-backup.sh

脚本:

#!/bin/bash
BACKUP_DIR=/backup/etcd
mkdir -p $BACKUP_DIR
FILE=$BACKUP_DIR/etcd-$(date +%F-%H%M).db
ETCDCTL_API=3 etcdctl snapshot save $FILE \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

# 保留 7 天
find $BACKUP_DIR -name "etcd-*.db" -mtime +7 -delete

# rsync 异地
rsync -av $FILE backup@<backup-server>:/etcd-backups/

恢复(单节点 etcd)

# 1. 停 kube-apiserver 和 etcd
systemctl stop kube-apiserver
mv /etc/kubernetes/manifests/etcd.yaml /tmp/   # 如果是 kubeadm

# 2. 清理 etcd 数据
mv /var/lib/etcd /var/lib/etcd.bak

# 3. 恢复
ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-xxx.db \
  --data-dir /var/lib/etcd \
  --name <node-name> \
  --initial-cluster <node-name>=https://<node-ip>:2380 \
  --initial-advertise-peer-urls https://<node-ip>:2380

# 4. 起服务
mv /tmp/etcd.yaml /etc/kubernetes/manifests/
systemctl start kube-apiserver

恢复(HA etcd 集群)

3 节点 etcd 恢复要在所有节点同时做:

# 在每个节点:
ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-xxx.db \
  --data-dir /var/lib/etcd \
  --name <本节点名> \
  --initial-cluster node1=https://<n1ip>:2380,node2=https://<n2ip>:2380,node3=https://<n3ip>:2380 \
  --initial-advertise-peer-urls https://<本节点ip>:2380 \
  --initial-cluster-token <token>

# 同时启动所有 etcd

--initial-cluster-token 必须三个节点都一样且和原集群不同(避免脑裂)。

配合 Velero 做应用级备份

etcd 备份只保了 K8s 对象,PV 数据没保。生产再加 Velero:

velero install \
  --provider aws --bucket k8s-backup \
  --backup-location-config region=us-east-1 \
  --use-volume-snapshots=true

velero backup create daily-backup --include-namespaces production

Velero + etcd 双备份,定期演练恢复,是 K8s 生产标配。

教训:备份脚本写好后至少做一次完整恢复演练,没演练过的备份等同于没有。

OpenStack 自带监控生态有 Ceilometer/Gnocchi/Aodh,新生代有 Prometheus exporter。怎么选?

老栈:Ceilometer 全家桶

  • Ceilometer:采集
  • Gnocchi:时序存储
  • Aodh:告警
  • Panko(已弃):事件存储

特点:
- 深度集成 OpenStack,支持计费用例
- 配置复杂,运维难
- Gnocchi 性能瓶颈明显(大集群跑不动)

新栈:Prometheus

  • openstack-exporter:抓 OpenStack API
  • libvirt-exporter:抓 hypervisor
  • node-exporter:抓物理机
  • Alertmanager:告警
  • Grafana:可视化

特点:
- 性能强、生态广
- 不支持计费场景(只看实时状态)
- 配置简单

我的选型

场景
公有云、要计费 Ceilometer + Gnocchi
私有云、只要监控告警 Prometheus
大规模(>500 节点) Prometheus
小规模 + 简单需求 Prometheus

Prometheus 部署关键

scrape_configs:
  - job_name: 'openstack'
    static_configs:
      - targets: ['exporter-host:9180']
    scrape_interval: 60s
    scrape_timeout: 30s

  - job_name: 'libvirt'
    static_configs:
      - targets:
          - 'compute1:9177'
          - 'compute2:9177'
    scrape_interval: 30s

openstack-exporter 默认抓很多指标,scrape_interval 别小于 60s,否则把 Keystone 打挂。

必装的几个告警

groups:
- name: openstack
  rules:
  - alert: NovaComputeDown
    expr: openstack_nova_agent_state{service="nova-compute"} == 0
    for: 5m
  - alert: NeutronAgentDown
    expr: openstack_neutron_agent_state == 0
    for: 5m
  - alert: HypervisorMemoryHigh
    expr: openstack_nova_used_memory_bytes / openstack_nova_memory_bytes > 0.85
    for: 10m
  - alert: VolumeStuck
    expr: openstack_cinder_volumes{status=~"creating|deleting|error"} > 0
    for: 30m

Grafana dashboard

Grafana 官方 dashboard 库搜 "OpenStack",推荐 ID 9701(openstack-exporter 配套)。

教训:Ceilometer 链路长(采集→存→查),任何一环挂都看不到数据;Prometheus 简单粗暴 pull,定位故障容易 10 倍。