第 13 章 边缘计算与 IoT 云原生
本章导学
先打个比方:传统的云原生架构像"中央厨房",所有菜品(计算任务)都在中心机房做好再配送;而边缘计算像"社区前置仓 + 连锁便利店"——把一部分加工能力下沉到离用户最近的地方(工厂车间、路口摄像头、门店网关)。好处是时延低、省带宽;代价是这些"便利店"经常断网、没专人值守、硬件孱弱。
本章要解决的问题就是:当几千个"便利店"分布在弱网环境里且会离线时,如何用云原生的手段把它们管起来、让业务不中断、还能在边缘跑 AI?
graph TD
Cloud[云端控制面
K8s Master + 设备孪生]
EdgeHub[边缘节点 EdgeCore / SuperEdge]
Device[IoT 设备
传感器/摄像头/PLC]
AI[边缘 AI 推理
视频/异常检测]
Cloud --"云边通道(弱网/断网)"--> EdgeHub
EdgeHub --"MQTT/Modbus/OPC-UA"--> Device
EdgeHub --> AI
AI --"本地自治决策"--> Device
EdgeHub -.->|离线缓存
恢复后回传| Cloud面试要点:边缘计算 ≠ 把 K8s 直接装到树莓派上。核心差异是云边网络不可靠,所以必须解决"离线自治、断点续传、轻量运行时、设备协议接入"四类问题。下面 15 个子题就围绕这四点展开。
一、边缘节点管理与弱网/断网自治同步
13.1.1 当边缘节点离线超过 24h,如何设置 Pod 驱逐容忍避免业务中断?
白话直觉:K8s 默认节点失联约 5 分钟就会把上面的 Pod 驱逐、到其他节点重建。但边缘节点可能只是"网断了",机器本身还在跑业务。如果云端因为联系不上就把它上面的 Pod 干掉,业务就真断了。我们要告诉管控面:“它只是失联,别急着杀,先等等。”
做法是在云端对边缘节点设置更宽容的驱逐容忍(toleration)+ 调大节点 notReady 的宽限期。KubeEdge/SuperEdge 场景下,边缘节点上的业务由 EdgeCore 本地保活,云端只是"睁一只眼闭一只眼"。
# 业务 Pod 容忍边缘节点 NotReady/Tenant 状态,避免被误驱逐
apiVersion: v1
kind: Pod
metadata:
name: edge-camera-processor
namespace: edge-app
spec:
# 关键:容忍节点 NotReady 与 unreachable,且永不过期
tolerations:
- key: "node.kubernetes.io/not-ready" # 节点就绪探针失败
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 0 # 0 表示一直容忍,不驱逐
- key: "node.kubernetes.io/unreachable" # 节点不可达(典型断网)
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 0
containers:
- name: processor
image: registry.local/edge/processor:v1
resources:
requests:
memory: "128Mi"
cpu: "200m"
# 同时放宽云端对节点的宽限(kube-controller-manager 侧)
# --pod-eviction-timeout 默认 5m,边缘场景可放大到 1h 以上
kube-controller-manager \
--pod-eviction-timeout=1h \
--node-monitor-grace-period=40s \
--node-monitor-period=5s
注意:放宽驱逐会让"真死"的节点也赖着不走,所以通常配合 SuperEdge 的
application-grid或 KubeEdge 的nodeUpgrade做健康探测兜底。
13.1.2 请给出基于 EdgeMesh 实现 Service 跨云边调用的 iptables 规则。
白话直觉:云上有个 Service,边缘有个 Pod 想访问它。但边缘和云之间不是扁平网络(中间隔着公网/VPN),Pod 直接用 ClusterIP 是够不着的。EdgeMesh 的做法是:在边缘节点上劫持对某个"虚拟 IP"的流量,通过加密隧道打到云端,再由云端转发。
# EdgeMesh 在边缘节点通过 iptables 把访问云边互通 IP 的流量
# 重定向到本地的 edgemesh-agent(监听 172.16.0.0/16 的 DNAT 链)
iptables -t nat -N EDGE-MESH # 新建自定义链
iptables -t nat -A EDGE-MESH \
-d 172.16.0.0/16 \ # 云边互通的虚拟网段
-p tcp \
-j REDIRECT --to-ports 15001 # 重定向到 edgemesh-agent 监听端口
iptables -t nat -A PREROUTING -j EDGE-MESH
iptables -t nat -A OUTPUT -j EDGE-MESH
# 查看规则是否生效
iptables -t nat -L EDGE-MESH -n -v
sequenceDiagram
participant P as 边缘 Pod
participant A as edgemesh-agent(边缘)
participant T as 加密隧道
participant C as 云端 Service
P->>A: 访问 172.16.x.x:80 (iptables 劫持)
A->>T: 隧道封装转发
T->>C: 云端解封装并路由
C-->>T: 响应
T-->>A: 回包
A-->>P: 原路返回13.1.3 如何对 KubeEdge EdgeCore 做内存压缩并限制在 256MB 以内?
白话直觉:边缘盒子可能只有 1GB 内存,还要跑业务。EdgeCore 是边缘的"小管家",得把它自己压到极小。手段是:关掉用不上的模块、调小元数据的本地缓存、限制 Go 运行时内存、必要时上轻量的 lz4 压缩。
# /etc/kubeedge/config/edgecore.yaml 精简片段
modules:
edgecore:
edgeMesh:
enable: false # 不用跨云边 mesh 就关掉,省内存
metaManager:
metaServer:
enable: false # 不需要本地 API 缓存也关
edged:
cgroupDriver: cgroupfs
podSandboxImage: "" # 用系统自带 pause
imageGCHighThreshold: 70 # 镜像 GC 阈值调低,少占磁盘/内存
# 通过 systemd 限制进程内存上限
# 用 systemd 给 edgecore 套上 256MB 硬上限
mkdir -p /etc/systemd/system/kubeedge-edgecore.service.d
cat > /etc/systemd/system/kubeedge-edgecore.service.d/mem.conf <<'EOF'
[Service]
MemoryHigh=220M # 超过即开始回收
MemoryMax=256M # 硬上限,OOM 前先压
EOF
systemctl daemon-reload
systemctl restart kubeedge-edgecore
13.2.1 如何关闭 K3s 内置 Traefik 并替换为 Envoy Gateway?
白话直觉:K3s 开箱自带 Traefik 做入口网关,但边缘网关想要更细的协议控制(比如 gRPC、MQTT 透传),Envoy 更灵活。关掉 Traefik 就是启动时不加载那个 chart。
# 重装/启动 K3s 时禁用 Traefik(通过禁用 traefik 这个内置 chart)
curl -sfL https://get.k3s.io | sh -s - \
--disable traefik \
--disable servicelb # 顺手关掉 klipper-lb,避免和 Envoy 抢
# 改用 Envoy Gateway(精简示例)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: edge-gateway
namespace: envoy-gateway
spec:
gatewayClassName: envoy
listeners:
- name: mqtt
port: 1883
protocol: TCP # 边缘常用透传 MQTT,而不是 HTTP 路由
- name: http
port: 80
protocol: HTTP
13.2.2 请给出基于 K3s + SQLite 实现单节点高可用并自动备份的 Systemd Timer。
白话直觉:云上是 etcd 三副本保高可用;边缘单机没条件搞三节点,K3s 默认用 SQLite 存状态。单点怕丢,就定时把数据库文件打包备份到对象存储,真坏了还能拉回来。
# /usr/local/bin/k3s-sqlite-backup.sh —— 备份脚本
#!/bin/bash
set -euo pipefail
SRC=/var/lib/rancher/k3s/server/db/state.db
DST=/var/backups/k3s-$(date +%F-%H%M).db
# K3s 写时 SQLite 需停写,用 k3s 自带的 etcd快照/sqlite checkpoint 更稳
# 这里简化为先 checkpoint 再拷贝
sqlite3 "$SRC" "PRAGMA wal_checkpoint(TRUNCATE);"
cp "$SRC" "$DST"
# 上传到 OSS(断点续传由 rclone 保证)
rclone copy "$DST" oss:edge-backup/k3s/
echo "backup done: $DST"
# /etc/systemd/system/k3s-backup.timer + .service
[Unit]
Description=K3s SQLite 定时备份
[Timer]
OnCalendar=*-*-* 02:00:00 # 每天凌晨 2 点
Persistent=true
[Install]
WantedBy=timers.target
systemctl enable --now k3s-backup.timer
systemctl list-timers | grep k3s-backup
13.2.3 当边缘 CPU 架构混用 ARM/x86,如何构建多架构镜像并自动选择?
白话直觉:边缘盒子有的用 ARM(树莓派、飞腾),有的用 x86(工控机)。你不能给每个架构单独打一个镜像、再人工分发给对应机器。正确姿势是打"多架构镜像"——一个 tag 里塞进两种架构,节点拉取时自动挑自己能跑的那个。
# 用 buildx 一次构建并推送多架构镜像
docker buildx create --use
docker buildx build \
--platform linux/amd64,linux/arm64 \ # 同时产出 x86 与 ARM64
-t registry.local/edge/agent:v1 \
--push .
# 验证镜像里确实包含了多个架构
docker buildx imagetools inspect registry.local/edge/agent:v1
# 输出 Manifest List 会列出 amd64 / arm64 两个 digest
# 节点按架构打 label,调度时自动亲和
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: edge-agent
spec:
template:
spec:
# 不写 nodeSelector 也能跑:多架构镜像由容器运行时自动选
# 若需强制按架构分布,可加:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"]
13.5.1 使用 Rclone + OSS 实现断点续传,如何设置 chunk size 最大化带宽?
白话直觉:边缘往云上传大文件(比如一天的视频),链路经常抖。Rclone 可以把文件切成块传,哪块断了只重传那块。chunk 太大,断一次重传成本高;太小,握手开销大。经验值 5–64MB 之间按带宽调。
# 断点续传上传到阿里云 OSS
rclone copy /data/video_2022.mp4 oss:edge-bucket/ \
--transfers 4 \ # 并发传几个文件
--checkers 8 \
--chunk-size 32M \ # 单块 32MB,断点续传粒度
--ignore-size \ # 弱网下以校验和而非大小判断
--retries 10 \ # 弱网重试
--low-level-retries 20
13.5.2 请给出基于 Kubernetes CronJob + Syncthing 实现边缘到云增量同步的 Config。
白话直觉:Syncthing 是"文件双向同步"工具,像个去中心化的网盘。我们让它常驻边缘,把目录实时增量同步到云;再用 CronJob 定期做"对账",防止漏传。
# 边缘常驻 Syncthing,增量同步 /data 到云端
apiVersion: apps/v1
kind: Deployment
metadata:
name: syncthing-edge
namespace: sync
spec:
replicas: 1
selector:
matchLabels: { app: syncthing }
template:
metadata:
labels: { app: syncthing }
spec:
nodeSelector:
node-role.kubernetes.io/edge: "true"
containers:
- name: syncthing
image: syncthing/syncthing:latest
env:
- name: STGUIADDRESS
value: "0.0.0.0:8384"
volumeMounts:
- { name: data, mountPath: /var/syncthing/data }
volumes:
- name: data
hostPath: { path: /data } # 直接挂宿主边缘数据盘
---
# 定期对账的 CronJob(补漏传文件)
apiVersion: batch/v1
kind: CronJob
metadata:
name: sync-reconcile
namespace: sync
spec:
schedule: "*/30 * * * *" # 每 30 分钟
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: reconcile
image: rclone/rclone:latest
command: ["rclone", "sync", "/data", "oss:edge-bucket/incremental/", "--dry-run=false"]
volumeMounts:
- { name: data, mountPath: /data }
volumes:
- name: data
hostPath: { path: /data }
13.5.3 当回传链路带宽不足,如何基于 BBR 拥塞控制提升吞吐量?
白话直觉:边缘回传常是 4G/窄带,传统 Cubic 拥塞算法在丢包时会"过度缩窗",把本来就不多的带宽让出去。BBR 不盲目怕丢包,而是测真实带宽和时延来发数据,弱网下更扛造。
# 开启 BBR(边缘节点内核需 >= 4.9)
cat >> /etc/sysctl.conf <<'EOF'
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF
sysctl -p
# 验证已生效
sysctl net.ipv4.tcp_congestion_control # 应返回 bbr
lsmod | grep bbr
二、IoT 设备接入、边缘 AI 推理与运维
13.3.1 使用 EMQX Operator 时,如何设置 Session Persistence 避免 Pod 重启掉线?
白话直觉:MQTT 设备(传感器)连上 broker 后会订阅主题。如果 broker 的 Pod 重启,设备的"会话"丢了,就得全部重连、重订阅——百万设备同时重连叫"重连风暴"。Session Persistence(会话保持)让 broker 把会话状态外置到存储,Pod 重启后设备无缝续上。
# EMQX 自定义资源:开启会话持久化到 Redis/K8s PVC
apiVersion: apps.emqx.io/v2beta1
kind: EMQX
metadata:
name: edge-mqtt
namespace: iot
spec:
replicaStsTemplate:
spec:
replicas: 3
# 关键配置:会话持久化(避免 Pod 重启掉线)
config:
name: edge-mqtt-config
data:
# 会话过期间隔,设备断线后保留会话的时长
"mqtt.session_expiry_interval": "3600s"
# 持久化后端(示例:用 Redis 存会话)
"persistence.backend": "redis"
"persistence.redis.server": "redis.iot.svc:6379"
13.3.2 请给出基于 Kubernetes PVC + LocalPath 实现毫秒级故障切换的 YAML。
白话直觉:MQTT broker 的状态(会话、离线消息)要落盘。用 LocalPath 把宿主本地 SSD 直接挂进去,IO 路径最短,故障切换时新 Pod 调度到同节点、秒级接管数据。代价是不能跨节点漂移,边缘单机场景刚好合适。
# 用 LocalPath(不需额外 CSI 插件)提供本地高速盘
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: localpath-edge
provisioner: rancher.io/local-path
reclaimPolicy: Retain # 保留数据,故障时人工/脚本接管
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: emqx-data
namespace: iot
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: localpath-edge
resources:
requests: { storage: 20Gi }
13.3.3 当 MQTT 连接超过 100 万,如何基于 LVS + Keepalived 做负载均衡?
白话直觉:单台 EMQX 扛不住百万长连接,前面得挂一层四层负载均衡把连接摊到多台 broker。LVS(IPVS)在内核态转发、几乎不损耗;Keepalived 负责虚拟 IP 漂移,避免 LB 自己成为单点。
# 在 LVS Director 上用 IPVS 把 1883 摊到后端 EMQX 集群
ipvsadm -A -t 10.0.0.10:1883 -s rr # 虚拟 IP + 轮询
ipvsadm -a -t 10.0.0.10:1883 -r 10.0.0.21:1883 -m # 后端节点1
ipvsadm -a -t 10.0.0.10:1883 -r 10.0.0.22:1883 -m # 后端节点2
ipvsadm -a -t 10.0.0.10:1883 -r 10.0.0.23:1883 -m # 后端节点3
# Keepalived 配置:VIP 10.0.0.10 在主备间漂移
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 150
virtual_ipaddress {
10.0.0.10/24
}
}
13.4.1 使用 KubeEdge + Sedna 时,如何在边缘节点实现视频增量学习?
白话直觉:云端模型下发到边缘做推理。但边缘场景数据分布会变(新车型、新光照),全量回传训练太贵。Sedna 的思路是:边缘先做"难样本"筛选(只把模型不确定的帧挑出来),增量训练一个小模型,再和云端大模型协同——像"总部定大方向,分店补本地细节"。
# Sedna 联合学习任务(边缘增量学习视频模型)
apiVersion: sedna.io/v1alpha1
kind: FederatedLearningJob
metadata:
name: video-incremental
namespace: edge-ai
spec:
dataset:
name: edge-video-dataset
model:
name: base-yolo # 云端下发的基座模型
spec:
template:
edge:
# 边缘只训练"难样本",减少回传量
selector:
matchLabels: { node-role: edge }
hardExampleMining:
enabled: true
ratio: 0.1 # 仅 10% 难样本参与
13.4.2 请给出基于 GStreamer + TensorRT 实现 RTSP 拉流并推理的 Pod YAML。
白话直觉:摄像头吐 RTSP 视频流,边缘盒子用 GStreamer 拆帧,TensorRT 把帧喂给 GPU 加速的模型做目标检测,再把结果回传。整条链路在边缘 Pod 里闭环,只有"告警事件"才上云,省带宽。
apiVersion: v1
kind: Pod
metadata:
name: rtsp-infer
namespace: edge-ai
labels: { node-role: edge }
spec:
nodeSelector:
accelerator: gpu
containers:
- name: infer
image: registry.local/edge/gstreamer-tensorrt:v2
env:
- name: RTSP_URL
value: "rtsp://192.168.1.64/stream1"
- name: MODEL_PATH
value: "/models/yolo.trt"
# 把 GPU 与视频设备透传给容器
resources:
limits:
nvidia.com/gpu: 1
volumeDevices:
- name: video
devicePath: /dev/video0
volumes:
- name: video
hostPath: { path: /dev/video0 }
13.4.3 当边缘 GPU 温度过高,如何自动降频并迁移推理任务到云端?
白话直觉:边缘盒子散热差,夏天 GPU 容易过热降性能甚至宕机。要在"本地温度超阈值"时:先让边缘 GPU 降频保命,同时把推理任务切到云端(或邻近凉快节点),温度回落再切回来。
# 读取边缘 GPU 温度(NVIDIA 示例)
nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader
# 超过 80℃ 触发降频
if [ "$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader)" -gt 80 ]; then
nvidia-smi -pm 1
nvidia-smi -ac 2500,800 # 降显存/核心频率,降温
# 通知云端把本节点标记为"推理卸载",调度器把新任务导到云
kubectl label node edge-gpu-01 infer-capacity=off --overwrite
fi
graph LR
T[GPU 温度监控] -->|超阈值| D[本地降频]
T -->|超阈值| M[标记节点不可推理]
M --> C[云端接管推理任务]
T -->|回落| R[恢复本地推理]
R --> C自测题与动手练习
- 概念题:为什么边缘节点不能直接套用云端"节点失联 5 分钟就驱逐 Pod"的策略?请用 toleration 解释缓解思路。
- 对比题:EdgeMesh 和原生 Service Mesh(Istio)在跨云边调用上的核心差异是什么?为什么边缘更适合轻量劫持而非 Sidecar 全量注入?
- 实操题:在一台 ARM64 开发板上,用
buildx构建并推送一个支持amd64与arm64的多架构镜像,并用imagetools inspect验证。 - 排错题:百万 MQTT 设备凌晨集中重连导致 EMQX 雪崩,给出"会话持久化 + LVS 连接摊薄 + 重连抖动(jitter)“三步缓解方案。
- 设计题:画一张"边缘视频 AI 推理"的闭环图:RTSP 拉流 → 本地推理 → 难样本回传 → 云端增量训练 → 模型下发,并标注哪些环节只在边缘、哪些上云。
- 动手题:在边缘节点开启 BBR,用
iperf3对比开启前后跨弱网回传的吞吐量差异。
本章小结
- 边缘计算的本质是云边网络不可靠,由此派生出离线自治、断点续传、轻量运行时、设备协议接入四类核心问题。
- 节点管理上,用
toleration容忍 NotReady/Unreachable、用 EdgeMesh 做云边互通、用 K3s+SQLite 做单机高可用、用 buildx 多架构镜像适配 ARM/x86 混布。 - IoT 接入上,EMQX 的会话持久化避免重连风暴,LocalPath 提供毫秒级本地盘接管,LVS+Keepalived 摊百万长连接。
- 边缘 AI 上,KubeEdge+Sedna 做难样本增量学习,GStreamer+TensorRT 在边缘闭环推理,GPU 过热时降频并卸载到云端。
- 弱网同步上,Rclone 断点续传 + Syncthing 增量同步 + BBR 拥塞控制,三者组合把窄带利用率拉满。
- 面试一句话总结:边缘云原生的关键词是"容忍失联、本地自治、协议适配、轻装上阵”。
- 边缘 vs 云的核心差异:边缘节点不可靠(断电、断网、硬件孱弱),所以必须容忍失联、本地自治,不能依赖云端协调。
- 节点管理三板斧:toleration 容忍 NotReady + K3s+SQLite 单机高可用 + buildx 多架构镜像跨 ARM/x86 部署。
- 百万连接抗雪崩:EMQX 会话持久化 + LVS 连接摊薄 + jitter 重连抖动,三步组合避免凌晨集中重连撑爆 broker。
- 边缘 AI 闭环:难样本回传 → 云端增量训练 → 模型下发,这个循环是边缘智能的标配模式。
- 下一篇讲入口流量与网关——它是边缘和云端的交界处,处理千万级并发请求的稳定性和性能。