云原生入口流量与网关:Ingress、DNS/Anycast、CDN 与 DSR
面试定位:本章聚焦"电商大促千万级并发入口流量接入"这一真实场景。面试官想听的不是你会不会配 YAML,而是你能不能在大流量、多活、容灾、降级的综合约束下,把流量这条"主动脉"稳住。下面所有内容都围绕一个核心问题展开:当每秒百万级请求砸向入口,网关、DNS、CDN、四层负载如何各司其职、互不拖垮?
一、基于 Kubernetes 的 Ingress-NGINX 性能调优
(一)白话直觉:Ingress-NGINX 是一个"收费站"
把 Ingress-NGINX 想象成高速路上的主收费站:每辆车(请求)都要先在这里被"抬杆"(TLS 卸载、路由匹配、限流),再放行到后端服务。收费站有几个关键资源:
- 收费员数量(worker-processes):一个进程一个收费员,太少则排队。
- 每个收费员手里的车道数(worker-connections):一个收费员同时能抬几根杆。
- 待抬杆的缓冲区(listen 队列 / accept 队列):车太多时先停在缓冲区,溢出就丢车。
单 Pod 的 QPS 上限,本质上就是"收费员数 × 每人能并发处理的车道数 × 每车道平均处理速度"。调优不是玄学,而是把这三个旋钮拧到与机器资源匹配。
(二)worker-processes 与 worker-connections 怎么设
Ingress-NGINX 的 worker 由 ConfigMap 与 nginx.ingress.kubernetes.io 注解控制,但底层终究是 NGINX。核心配置在 nginx.conf 的 events 块:
# 这一段由 ingress-nginx 的 controller 自动生成,但我们可以通过
# values.yaml / command 参数覆盖。这里展示"最终生效"的样子。
worker_processes auto; # 关键1:设成 auto,让 NGINX 按 CPU 核数起进程
worker_rlimit_nofile 1048576; # 关键2:每个 worker 能打开的最大文件句柄(连接≈fd)
events {
worker_connections 65535; # 关键3:单 worker 最大并发连接数
use epoll; # Linux 下必用 epoll 多路复用
multi_accept on; # 一次事件循环尽可能多 accept
}
面试要点(如何最大化单 Pod QPS):
worker_processes auto:必须等于 Pod 能用的 CPU 核数,不能超配(超了反而争抢)。在 K8s 里给 Ingress Pod 设resources.limits.cpu为整数核(如 8),并让auto取到 8。worker_connections与nofile的匹配关系:单 Pod 最大并发连接 ≈ worker_processes × worker_connections。但每个连接至少要一个 fd,所以worker_rlimit_nofile必须 ≥worker_processes × worker_connections(再留余量给日志、临时文件)。- 内核侧配合(
initContainer里 sysctl):
# 让 listen 队列能容纳突发连接,避免握手阶段就丢包
sysctl -w net.core.somaxconn=65535
sysctl -w net.ipv4.tcp_max_syn_backlog=65535
# 允许进程突破默认 1024 文件句柄限制(与 worker_rlimit_nofile 呼应)
ulimit -n 1048576
- 反向直觉:QPS 不是连接数的平方。短连接(HTTP/1.1 每次建连)受
worker_connections和建连速率双重制约;开 HTTP/2 后多路复用让单连接承载多请求,此时瓶颈从"连接数"转移到"worker 的 CPU 与内存"。
(三)开启 HTTP/2 后大量 502 的定位与解决
现象:开了 nginx.ingress.kubernetes.io/backend-protocol: "GRPC" 或启用了 HTTP/2 到后端,突然大量 502。
根因(面试常考点),按出现频率排序:
- 后端不支持 / 错误协商 HTTP/2:Ingress 用 HTTP/2 连后端,但后端(如老版本 Spring Boot、某些 sidecar)只认 HTTP/1.1,握手即断 → 502。
- keepalive 超时不一致:NGINX upstream 的
keepalive_timeout比后端长,NGINX 复用一个已被后端关掉的空闲连接发请求 → 502(recv() failed (104: Connection reset by peer))。 - 每连接最大请求数(keepalive_requests)耗尽:HTTP/2 多路复用单连接跑很久,超过后端
max_requests被强制断开,NGINX 复用旧连接即 502。
定位三板斧:
# 1. 看 ingress-nginx 的错误日志,关键词 502 与 upstream
kubectl exec -n ingress-nginx <controller-pod> -- \
tail -f /var/log/nginx/error.log | grep -i '502\|upstream\|reset'
# 2. 抓到具体错误后,临时把后端协议改回 HTTP/1.1 验证(排除协商问题)
kubectl annotate ingress <name> nginx.ingress.kubernetes.io/backend-protocol=HTTP
# 3. 观察后端真实响应:用 kubectl 直接 port-forward 打后端,确认它是否吐 502
kubectl port-forward svc/<backend> 8080:8080 &
curl -v http://127.0.0.1:8080/healthz
解决配置(ConfigMap 层面统一收敛 keepalive,避免"半开连接"):
# ingress-nginx 的 ConfigMap,统一 upstream keepalive 行为
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
# upstream 空闲连接最大存活,必须 <= 后端 keepalive 超时,留 5s 余量
keep-alive: "55"
# 单连接最大请求数,避免后端主动断开后 NGINX 复用
keep-alive-requests: "1000"
# 开启 proxy_next_upstream 自动重试(对非幂等要谨慎)
proxy-next-upstream: "error timeout http_502 http_503"
# 启用 upstream 连接池
upstream-keepalive-connections: "512"
upstream-keepalive-timeout: "55"
upstream-keepalive-keepalive-requests: "1000"
经验值:后端(Tomcat/Jetty)keepalive 常是 60s,Ingress 侧设 55s;后端
maxKeepAliveRequests默认常是 100,这里设 1000 反而不能大于后端,需对齐后端。面试时说"要对齐上下游超时与请求数"比背数字更得分。
(四)通过 eBPF 采集 Ingress-NGINX 连接队列长度的完整命令链
为什么用 eBPF:ss -lnt 只能看到"瞬时值",大促时队列在毫秒级抖动,用户态采样抓不到。eBPF 在内核态直接读 struct sock 的 sk_ack_backlog(已入 accept 队列、等待 accept() 的连接数),零拷贝、低开销。
完整命令链(假设已装 bpftrace 与 bcc):
# 步骤1:确认用户态已经出现 listen 队列溢出迹象(先快速体检)
ss -lnt | grep -E ':80|:443' # Recv-Q 持续逼近 Send-Q 即积压
netstat -s | grep -i 'listen queue of a socket overflowed' # 有数值=已丢 SYN
# 步骤2:用 bpftrace 在内核态实时采样 accept 队列长度
# tcp_v4_syn_recv_sock 的第一个参数就是"监听 socket"本身
sudo bpftrace -e '
#include <linux/tcp.h>
#include <net/sock.h>
kprobe:tcp_v4_syn_recv_sock {
$sk = (struct sock *)arg0;
@ack = $sk->sk_ack_backlog; // 当前 accept 队列长度
@max = $sk->sk_max_ack_backlog; // 队列上限(= listen backlog)
}
interval:s:5 {
printf("accept_queue=%d / max=%d\n", @ack, @max);
clear(@ack); clear(@max);
}'
# 步骤3(可选):统计 SYN 被丢弃的次数,定位是否真溢出
sudo bpftrace -e '
kprobe:tcp_drop {
$skb = (struct sk_buff *)arg0;
@drop = count();
}
interval:s:10 { print(@drop); clear(@drop); }'
读法:步骤2 中若 accept_queue 长期贴近 max,且步骤3 的 drop 持续增长,说明 Ingress Pod 的 accept 速度跟不上入队速度(典型是 worker 被 CPU 限流或业务 accept() 慢),应扩容 Ingress Pod 或调大 somaxconn。
(五)一图总览 Ingress-NGINX 调优关系
flowchart LR
A[客户端 HTTPS] --> B[Ingress-NGINX Pod]
B -->|worker_processes=CPU核数| C{多 worker 进程}
C -->|每个 worker| D[worker_connections=65535]
D --> E[epoll 多路复用]
E -->|accept 队列| F[net.core.somaxconn 兜底]
F --> G[后端 Service]
G -->|HTTP/2 多路复用| H[keepalive 对齐防 502]
style B fill:#fef3c7,stroke:#d97706
style H fill:#dbeafe,stroke:#2563eb二、全球多活 DNS 与 Anycast IP 流量调度
(一)白话直觉:DNS 是"全球交通警察",Anycast 是"就近入口"
- DNS 权重 + 健康检查 = 交警按"各路口车流量配比 + 哪个路口堵了就改道"来分流。
- BGP Anycast = 同一个 IP 地址在全球多个机房同时广播,运营商路由自然把用户引到物理最近的那个入口。就像"同一个电话号,你打时自动接到离你最近的营业厅"。
(二)阿里云云解析 + AWS Route53 混合:权重 + 健康检查实现秒级容灾
场景:国内用户走阿里云,海外走 Route53,但两者都要能在对方故障时"接管"。
设计要点:
- 主域名交给 Route53(顶级容灾兜底),用
Failover策略;国内子域cn.example.com用阿里云"线路类型=默认/国内"做权重调度。 - 健康检查 ≠ DNS 轮询:DNS 本身 TTL 长,秒级切换靠的是"权威 DNS 在被探活失败后立即收回该记录",而不是客户端重新解析。
阿里云云解析(权重 + 健康检查)示例思路:
# 阿里云云解析控制台 / OpenAPI 配置(伪配置,表达意图)
记录类型: A
主机记录: cn
线路类型: 默认
记录值: 10.0.1.10 权重: 100 # 主 AZ
记录值: 10.0.2.10 权重: 0 # 备 AZ,平时 0 权重不接流量
健康检查: 开启,URL=/healthz,间隔 2s,连续 3 次失败则"暂停"该记录
# 当主 AZ 探活失败 -> 自动把备 AZ 权重调为 100(秒级生效,因 TTL 设 1s)
Route53 侧 Failover(容灾兜底):
{
"Name": "example.com",
"Type": "A",
"SetIdentifier": "primary-aliyun",
"Failover": "PRIMARY",
"HealthCheckId": "hc-aliyun-cn",
"TTL": 1,
"ResourceRecords": [{ "Value": "10.0.1.10" }]
},
{
"Name": "example.com",
"Type": "A",
"SetIdentifier": "secondary-route53",
"Failover": "SECONDARY",
"TTL": 1,
"ResourceRecords": [{ "Value": "52.1.2.3" }]
}
关键点:把 TTL 压到 1 秒,Health Check 间隔 2s + 连续失败 3 次 ≈ 6s 内完成切换,满足"秒级容灾"的面试叙事。但面试官可能反问"TTL=1s 会不会把权威 DNS 打爆"——答:用递归 DNS 的缓存 + 健康检查驱动记录启停,真实生效的是权威侧,递归侧 TTL 短只是配合。
(三)BGP Anycast 防 TCP 长连接被重置
问题:Anycast 同一个 IP 在多节点广播。某节点故障重启,BGP 收敛期间,原本连到它的 TCP 长连接(如 WebSocket、gRPC 流)会被路由到另一个节点,而新节点没有这条连接的会话状态 → 对端收到 RST,长连接断开。
解法(面试三层回答):
- BGP 优雅撤退(Graceful Shutdown):节点下线前先
withdraw路由前缀,等已建立的连接自然 drained 再重启,避免"路由突变"。 - 会话同步 / 无状态入口:让 Anycast 节点只做 L4 透传,真正的有状态连接在后端集群(共享会话存储),任一 Anycast 节点挂掉,连接重路由到另一节点也能从后端恢复上下文。
- 连接保持(Connection Draining):入口层在 withdraw 后维持
FIN优雅关闭,配合客户端自动重连 + 断点续传逻辑,把"连接重置"转化为"可恢复的瞬时抖动"。
(四)CoreDNS Split-Horizon 实现国内/海外分流示例 YAML
Split-Horizon(分视界)让"同一域名、不同来源客户端解析到不同 IP"。CoreDNS 原生 forward 不支持按客户端分流,可用 template 插件的 policy client_ip_range 实现,或用 view 插件。下面给出可直接落地的 template 方案:
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
# 国内段客户端(Pod CIDR 10.0.0.0/8)走阿里云内网 DNS,解析到内网地址
template ANY ANY {
policy client_ip_range 10.0.0.0/8
upstream 100.100.2.136 100.100.2.138
}
# 其余(海外/办公网段)走公共解析,命中 Route53 / 公网 VIP
template ANY ANY {
policy client_ip_range 0.0.0.0/0
upstream 8.8.8.8 1.1.1.1
}
# 兜底:集群内部 Service 域名仍由 kubernetes 插件解析
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
}
cache 30
errors
health
}
注意:
template插件主职是"合成记录",用它做分流转发在生产中常用于简单场景;更标准的 Split-Horizon 是部署两套 CoreDNS(分别 bind 不同接口 / 不同forward),或用社区view插件。面试时说出"两种实现路径 + 各自取舍"更显深度。
(五)容灾切换时序图
sequenceDiagram
participant U as 用户
participant R as 递归 DNS
participant A as 阿里云解析(主)
participant B as Route53(备)
participant H as 健康检查
H->>A: 每2s探 /healthz
Note over A: 连续3次失败
A-->>R: 暂停主记录(权重置0)
A-->>R: 启用备记录(权重100)
U->>R: 解析 example.com
R->>B: TTL=1s 命中备
B-->>R: 52.1.2.3
R-->>U: 返回备 IP
Note over U,B: 秒级切换完成,用户无感三、CDN 边缘缓存预热与回源策略
(一)白话直觉:CDN 是"把货提前铺到小区便利店"
大促前,用户会集中点击"爆款商品图"。如果等用户来才去源头仓库(源站)取,仓库瞬间被挤爆。预热就是大促前把热门图片提前"铺货"到全国边缘节点;回源就是边缘没货时回仓库取,取完顺便自己存一份。
(二)千万级商品图片预热:Kubernetes Job + SLS 批量提交不触发 QPS 限速
难点:直接循环调 CDN 预热 API 会被限流(如 100 QPS);千万 URL 提交要分批、可重试、可观测。
方案:用 K8s Job 起多个 worker,每个 worker 从对象存储/SLS(日志服务,这里指"URL 清单存储")拉一批 URL,按令牌桶匀速调用 CDN 预热接口。
apiVersion: batch/v1
kind: Job
metadata:
name: cdn-prewarm
spec:
parallelism: 20 # 20 个 Pod 并行,总吞吐 = 20 × 单Pod限速
completions: 20
backoffLimit: 3
template:
spec:
restartPolicy: Never
containers:
- name: prewarm
image: registry.example.com/cdn-prewarm:1.0
env:
- name: SLS_ENDPOINT # URL 清单所在(日志服务/对象存储)
value: "https://sls.example.com/urls/shard-${POD_INDEX}"
- name: CDN_API_QPS # 单 Pod 限速,远小于全局阈值,避免触发风控
value: "5"
- name: BATCH_SIZE
value: "200" # 每批 200 个 URL 合并一次预热请求
command: ["/bin/sh","-c"]
args:
- |
# 1. 从 SLS 拉取本分片 URL 清单
# 2. 令牌桶限速调用 CDN 预热 API(伪代码逻辑)
python3 prewarm.py \
--qps $CDN_API_QPS \
--batch $BATCH_SIZE \
--source $SLS_ENDPOINT
不触发限速的关键:每个 Pod 把 QPS 压到单账号阈值的 1/总并发以下(如全局 1000 QPS ÷ 20 Pod = 50,这里再保守取 5),且预热是"提前离线"操作,不与大促实时流量争抢 API 配额。
(三)源站返回 Cache-Control: private 时强制 CDN 缓存
问题:源站(如某些框架默认)吐 Cache-Control: private, max-age=0,语义上"禁止共享缓存(CDN)存储"。但商品图明明可缓存,被这个头卡住 → 每次回源。
解法:在 CDN 边缘(或回源层 OpenResty)重写响应头,去掉 private 并补 public + 长 max-age。
# 在 CDN 回源节点 / 边缘的 OpenResty 中
location ~* \.(jpg|png|webp)$ {
# 强制忽略源站的 private 指令,改为可公共缓存 1 天
proxy_ignore_headers Cache-Control Expires Set-Cookie;
add_header Cache-Control "public, max-age=86400, immutable";
proxy_cache my_static;
proxy_cache_valid 200 86400s;
proxy_pass http://origin;
}
注意:对含用户隐私的图片(如头像)不能这么做,否则会跨用户泄露。面试强调"只对静态、可公共缓存资源强制覆盖,敏感资源保持 private"。
(四)OpenResty + Lua 回源失败降级到对象存储
场景:边缘回源源站 5xx/超时,与其让用户看到裂图,不如直接从对象存储(OSS/S3)取一份兜底图片。
# OpenResty 边缘节点配置:回源失败 -> Lua 兜底对象存储
location /img/ {
proxy_pass http://origin-backend;
proxy_intercept_errors on; # 拦截后端错误,交给 error_page 处理
error_page 502 503 504 = @oss_fallback;
}
location @oss_fallback {
# 用 lua-resty-http 从 OSS 拉兜底图,避免再走一次失败回源
content_by_lua_block {
local http = require "resty.http"
local ngx = ngx
local path = ngx.var.uri -- /img/xxx.jpg
local oss_host = "https://oss-fallback.example.com"
local httpc = http.new()
httpc:set_timeout(2000) -- 2s 超时,快速失败
local res, err = httpc:request_uri(oss_host .. path, {
method = "GET",
ssl_verify = false,
})
if not res or res.status ~= 200 then
ngx.status = 502
ngx.say("image unavailable")
return
end
ngx.status = 200
ngx.header["Content-Type"] = res.headers["Content-Type"] or "image/jpeg"
ngx.header["X-Served-By"] = "oss-fallback" -- 标记降级来源,便于监控
ngx.print(res.body)
}
}
(五)CDN 预热与降级流程
flowchart TD
A[大促前 Job 批预热] -->|匀速令牌桶| B[CDN 边缘节点]
B -->|命中| C[直接返回商品图]
B -->|未命中| D[回源源站]
D -->|200| E[缓存并返回]
D -->|502/503/504| F[Lua 兜底 OSS]
F -->|200| G[返回兜底图 标记 X-Served-By]
F -->|失败| H[返回 502 裂图告警]
style A fill:#dcfce7,stroke:#16a34a
style F fill:#fef9c3,stroke:#ca8a04四、四层负载均衡 DSR(Direct Server Return)
(一)白话直觉:DSR 是"去的时候经过调度器,回来的时候抄近路"
普通 L4 负载(如 NAT 模式):请求和响应都经过负载均衡器,响应往往比请求大得多(图片、视频),均衡器成了带宽瓶颈。
DSR(直接返回):请求经负载均衡器转发到后端 Pod,但响应由后端 Pod 直接发给客户端,不再绕回均衡器。就像你网购:快递员(请求)把包裹送到你家(后端),你退换货(响应)直接寄回商家(客户端),不用再经过快递中转站。
代价:后端 Pod 必须"假装"自己就是 VIP(因为客户端源目 IP 是 客户端→VIP),否则客户端收到来自"真实 Pod IP"的包会丢弃。
(二)Kubernetes 中为 Pod 配置 VIP 与 loopback 使 DSR 闭环
DSR 要求后端 Pod 在 loopback 上绑定 VIP,这样它发出的响应源 IP = VIP,客户端才认。
# 思路:通过 initContainer + 主机网络/特权,在 Pod 的 lo 上添加 VIP
# 实际生产多用 IPVS + DSR 模式的 Service,这里给出"手动闭环"演示
apiVersion: v1
kind: Pod
metadata:
name: dsr-backend
annotations:
# 告知 L4 调度器(如 LVS/IPVS)该 Pod 参与 DSR
l4dsr.vip: "192.168.100.100"
spec:
# DSR 通常需要 hostNetwork 或在 lo 上配 VIP;演示用 hostNetwork 简化
hostNetwork: true
initContainers:
- name: bind-vip
image: busybox
command: ["/bin/sh","-c"]
args:
- |
# 在 loopback 上绑定 VIP,使本机发出的响应源 IP 为 VIP
ip addr add 192.168.100.100/32 dev lo
# 关键:抑制 ARP 应答,避免本 Pod 抢答 VIP 的 ARP 导致同网段冲突
echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
securityContext:
capabilities:
add: ["NET_ADMIN"]
containers:
- name: app
image: nginx
ports:
- containerPort: 80
hostPort: 80
生产环境更推荐用 Cilium / kube-proxy IPVS 模式 + DSR(
--ipvs-scheduler+--ipvs-dr),让 kube-proxy 自动完成 VIP 与 loopback 闭环,而不是手写 initContainer。手写方案用于讲清原理。
(三)IPVS 与 DPDK 在 DSR 性能差异的根本原因
一句话根因:IPVS 跑在内核协议栈里,数据包要过内核网络栈(软中断、skb 分配、路由查找);DPDK 是用户态轮询驱动(PMD),绕开内核栈,直接把包从网卡 DMA 到用户态内存,靠"忙等轮询"换掉中断开销。
| 维度 | IPVS (内核态) | DPDK (用户态) |
|---|---|---|
| 数据路径 | 网卡 → 内核栈 → IPVS → 转发 | 网卡 → 用户态 PMD 直接处理 |
| 中断模型 | 中断驱动,高 PPS 下软中断打满 CPU | 轮询驱动,无中断抖动 |
| 典型瓶颈 | 内核 skb 分配 / 锁竞争 | 需要独占核,CPU 利用率"看起来空"实则忙轮询 |
| 适用 | 通用 K8s Service 负载 | 超高性能网关(千万 PPS) |
根本原因:差异不在"DSR 算法"本身,而在转发路径是否经过内核协议栈。DSR 只是改了响应走向,IPVS 仍需内核处理每个包;DPDK 直接绕开内核,所以同硬件下 DPDK 的 PPS/时延上限显著更高,代价是占用整核、生态重。
(四)DSR 防后端 Pod 看到客户端真实 IP 导致反向路由不对称
问题:DSR 下,请求包目的 IP 是 VIP,但源 IP 是客户端真实 IP。后端 Pod 收到后,若它想"主动回包给客户端"(实际应走 DSR 直返),或者它的反向路由把去往客户端真实 IP 的包发给了默认网关而非原路返回,就会出现非对称路由——响应没走 DSR 直返,反而绕回调度器或被丢弃,客户端收不到回包。
防护三招:
- 关闭后端对客户端网段的独立路由:后端 Pod 不学习客户端真实 IP 的明细路由,所有"回客户端"的流量统一由 DSR 机制(源 IP=VIP + lo 绑定)直发,避免反向路由表指错路。
- rp_filter 严格模式:
net.ipv4.conf.all.rp_filter=1,防止后端因"收到源 IP 不在本地路由"的包而触发反向路径过滤丢包。 - 源 IP 保留但响应强制走 lo/VIP 出口:确保响应包的出口接口与入包一致,配合上面 initContainer 的 ARP 抑制,彻底闭环。
(五)DSR 流量闭环图
flowchart LR
C[客户端] -->|目的IP=VIP| L[L4 调度器 IPVS/DPDK]
L -->|改写MAC,目的IP仍为VIP| P[后端 Pod]
P -->|响应源IP=VIP 直发| C
subgraph Pod内部
P -->|lo 绑定 VIP| V[(loopback: VIP)]
end
style L fill:#fee2e2,stroke:#dc2626
style C fill:#dbeafe,stroke:#2563eb五、入口流量镜像与灰度审计
(一)白话直觉:流量镜像是"平行世界的影子测试"
你想验证新版本网关/风控规则,又不敢真拿用户流量试。于是把一份一模一样的副本悄悄发给影子环境,真实用户走原链路,影子环境随便折腾。但影子不是免费的——镜像放大会让下游(数据库、第三方计费 API)收到双倍甚至多倍请求。
(二)Istio TrafficMirror 避免镜像流量放大导致下游计费飙升
陷阱:TrafficMirror 若配置成"按比例镜像 + 多层级联",流量可能在多个服务间被反复镜像,呈指数放大,打到按调用量计费的第三方(如短信、支付风控)直接爆账单。
正确姿势:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: product-mirror
spec:
hosts: ["product.example.svc.cluster.local"]
http:
- route:
- destination:
host: product.example.svc.cluster.local # 100% 真实流量走原服务
weight: 100
mirror:
host: product-shadow.example.svc.cluster.local # 镜像到影子服务
mirrorPercentage:
value: 10 # 只镜像 10%,控制放大倍数
# 关键:给镜像流量打标,下游据此短路"真实计费"逻辑
headers:
request:
set:
x-shadow: "true"
防计费飙升的三道闸:
mirrorPercentage控制在低位(如 5%~10%),放大倍数 = 1 + 比例,可预算。- 影子链路不调用真实计费/短信:下游服务读
x-shadow: true头后,走 mock 或只写影子库,绝不触达计费 API。 - 镜像流量不二次镜像:影子服务禁用
TrafficMirror,防止级联放大。
(三)基于 eBPF + pcap 收集镜像流量并实时脱敏的架构图
目标:在入口网卡用 eBPF 抓包(比 tcpdump 用户态更轻),pcap 拿到原始流后,在用户态用 eBPF map / 程序做敏感字段(手机号、身份证、token)脱敏,再落盘供审计,避免明文敏感数据落库。
flowchart TD
A[入口网卡] -->|eBPF tc 挂载| B[eBPF 抓包程序]
B -->|perf/ringbuf| C[pcap 消费者]
C --> D{脱敏引擎}
D -->|识别手机号/身份证/token| E[掩码替换]
E --> F[(审计存储 已脱敏)]
D -->|命中敏感字段计数| G[metrics: 脱敏命中率]
style B fill:#ede9fe,stroke:#7c3aed
style E fill:#fef9c3,stroke:#ca8a04# 用 bpftool + tc 加载 eBPF 抓包(架构图中"eBPF 抓包程序"的落地)
# 1. 在入口网卡 ingress 挂载 eBPF 程序(假设已编译 mirror_capture.o)
sudo tc qdisc add dev eth0 clsact
sudo tc filter add dev eth0 ingress bpf da obj mirror_capture.o sec ingress
# 2. 用户态用 pcap 从 perf buffer 读包并脱敏(伪代码调用)
sudo ./pcap-desensitize \
--ebpf-map /sys/fs/bpf/tc/eth0/ingress/mirror_map \
--rules phone,idcard,token \
--out /audit/mirrored.pcap
(四)镜像流量延迟 > 5 秒:快速定位是 Sidecar 还是网络设备
决策树思路:
flowchart TD
Q[镜像延迟>5s] --> A{真实链路延迟正常?}
A -->|正常,仅镜像慢| B{Sidecar CPU/队列}
A -->|真实也慢| C[先查网络设备/链路]
B -->|Sidecar CPU 打满| D[扩容 Sidecar / 降 mirrorPercentage]
B -->|Sidecar 队列堆积| E[检查 envoy 的 mirror cluster 连接池]
C -->|交换机/专线抖动| F[查网络设备丢包与时延]
C -->|DNAT/iptables 规则多| G[迁 Cilium eBPF 替代 kube-proxy]落地命令:
# 1. 先看真实与镜像两路延迟差(在 Pod 内)
kubectl exec <pod> -c istio-proxy -- \
curl -s http://localhost:15000/stats | grep -E 'mirror|upstream_rq_time'
# 2. 看 Sidecar 是否被 CPU 限流(延迟>5s 常见元凶)
kubectl top pod <pod> -c istio-proxy
kubectl exec <pod> -c istio-proxy -- \
curl -s localhost:15000/stats | grep 'listener_manager.workers'
# 3. 排除网络设备:在节点上测到镜像目标的裸 RTT
ping -c 20 <shadow-service-ip>
# 若裸 RTT 就 > 5s,说明是网络/链路,不是 Sidecar
面试总结句:延迟>5s 先二分——真实链路正常则锅在 Sidecar(CPU/连接池),真实链路也慢则锅在网络设备。
自测题与动手练习
自测题(口述)
worker_processes auto配合 K8s 的limits.cpu为什么要设成整数核?小数核会导致什么问题?- 开 HTTP/2 后大量 502,请按"协商 / keepalive 超时 / keepalive_requests"三条线分别给出定位命令与修复配置。
- BGP Anycast 节点故障重启,为什么已有 TCP 长连接会被 RST?给出三层防御手段。
- DSR 下后端 Pod 为什么要在 loopback 绑 VIP?如果不抑制 ARP 会怎样?
- Istio
TrafficMirror的"计费飙升"风险根因是什么?三道闸分别挡住什么?
动手练习
- 在测试集群用
bpftrace采样一次 Ingress-NGINX 的sk_ack_backlog,记录峰值并判断是否需要扩容。 - 手写一个 CoreDNS
template的 Split-Horizon ConfigMap,让10.0.0.0/8走内网 DNS,其余走公网,并kubectl apply后dig验证。 - 用 OpenResty + Lua 写一个最小回源降级脚本,故意让
proxy_pass指向不存在的地址,确认@oss_fallback能返回兜底内容且带X-Served-By头。 - 在本地用
ipvsadm建一条 DR 模式虚拟服务,观察响应是否不经调度器直返客户端(tcpdump抓调度器网卡验证)。
本章小结
本章围绕"千万级并发入口流量接入"这一主线,串起四道关口:
- Ingress-NGINX:调优本质是
worker 数 × 连接数 × 单连接效率,HTTP/2 的 502 多源于上下游 keepalive 超时/请求数不对齐,eBPF 能在内核态精准采样 accept 队列。 - DNS / Anycast:权重 + 健康检查 + TTL=1s 实现秒级容灾;Anycast 靠优雅撤退与无状态入口化解 TCP 长连接重置;CoreDNS Split-Horizon 按客户端网段分流。
- CDN:预热用 Job + 令牌桶匀速避限速;
private头可在边缘强制覆盖(敏感资源除外);回源失败用 Lua 降级对象存储。 - DSR:响应直返破解带宽瓶颈,代价是后端需 lo 绑 VIP + 抑制 ARP;IPVS 与 DPDK 的性能差根本在"是否过内核栈";反向路由不对称靠 rp_filter 与闭环出口规避。
- 流量镜像:影子测试要控比例、禁级联、下游短路计费;延迟>5s 用"真实 vs 镜像"二分法定 Sidecar 还是网络设备。
面试时记住一个贯穿全局的视角:**入口层的所有技术,都是在"性能、成本、可用性、安全"四角之间做权衡",能说清"为什么这么选、代价是什么",比背配置更值钱。
- Ingress-NGINX 调优核心公式:单 Pod QPS ≈ worker_processes × worker_connections × 单连接吞吐量;HTTP/2 多路复用后瓶颈从连接数转到 CPU。
- Anycast 优雅撤退:TCP 长连接在 BGP 撤销时强制断开,靠"渐进式权重下调 + 健康检查"避免瞬时丢包。
- CDN 预热防限流:大文件用 Job + 令牌桶匀速预热,避免一次性大量回源触发源站限速。
- DSR 反路由不对称:响应不经调度器直返客户端,需要后端节点在 lo 绑定 VIP 并抑制 ARP 通告。
- 下一篇讲未来架构趋势——Serverless、边云协同、AI Native 等前沿方向。
排查步骤:
1. 查看 Ingress-NGINX 日志中的 upstream_timeout 错误
2. 对比 upstream_keepalive_timeout 和后端服务的 keepalive 配置
修复方案:确保上游 keepalive 超时 ≥ Ingress 的超时设置,或在 Ingress-NGINX 中设置
proxy-nextProtoNegotiation 等参数。面试加分点:提到可以用
bpftrace 在内核态采样 sk_ack_backlog,看 accept 队列是否真的满了,而不是盲目加 worker 数量。