K8s+Docker重构视频AI中台:GB28181/RTSP接入与算力池化实践

📅 发布时间:2026/10/16 4:10:07
K8s+Docker重构视频AI中台:GB28181/RTSP接入与算力池化实践
安防视频平台的演进这几年其实到了一个特别有意思的节点。早几年大家讨论的是“能不能跑通GB28181”“RTSP拉流卡不卡”现在都在聊“几十路、几百路视频同时接入后AI推理怎么调度”“一套平台怎么在不同客户的硬件上快速交付”。我去年参与了一套视频AI中台的架构改造核心就是标题里那句话——用K8s和Docker把GB28181/RTSP的视频接入和AI推理能力统一收编同时对GPU、NPU、CPU这些异构算力做池化管理。这篇文章就是那次改造的完整复盘包括选型逻辑、接入层容器化的细节、算力调度的核心配置以及一堆网上不太查得到的踩坑记录。如果你也在搞视频平台、AI中台或者正打算把安防项目容器化这篇应该能帮你省掉不少弯路。1. 视频AI中台的困境与破局思路1.1 三座大山接入杂、算力散、交付难先说清楚我们当时到底在扛什么问题。一套典型的视频AI中台下游接的是各种摄像头、NVR、视频平台上游要跑人脸识别、车辆结构化、烟火检测、区域入侵这类算法最终把结构化结果送到业务系统里去。听起来需求很清晰但真落到工程上三座大山压在头上。第一座是接入协议太杂。国内安防设备几乎没有统一标准老一点的海康、大华走RTSP国标设备走GB28181还有一部分私有SDK的或者ONVIF的。RTSP好歹还算通用GB28181光是信令交互那一套SIP流程就够做两周的兼容适配。每次接新项目都要为不同厂商的码流类型H.264、H.265、G.711音频等单独调参接入层的代码被if-else塞得没法看。第二座是算力资源散落在各个角落。有的客户机房有NVIDIA GPU有的客户指定用国产NPU还有的项目预算有限只能靠CPU硬扛。算法模型在不同硬件上的部署方式完全不一样推理服务的启动脚本、依赖库、运行环境各写一套。更头疼的是一个算法任务跑在GPU上可能要占用80%显存但代码里写死了一张卡只能跑一个模型资源利用率惨不忍睹。第三座是交付成本极高。每次到一个新现场都要现场装驱动、配环境、调依赖一套流程走下来起码两三天。如果客户临时说要扩容又得重新走一遍环境部署。项目一多运维同学基本被钉在客户现场回不来。1.2 为什么是K8s与Docker而不是其他组合面对这三座大山我们其实有过好几个备选方案——纯手工用docker-compose、用OpenStack虚拟机、以及K8s加Docker。最终选了K8s加Docker不是因为“大家都在用”而是因为这几个特性恰好命中痛点。Docker解决的是环境一致性问题。算法推理服务的依赖非常复杂pytorch版本、CUDA版本、OpenCV版本、各种系统库差一个版本可能就跑不起来。镜像把整个运行环境固化下来到哪个现场都是同样一份运行时什么“在我机器上能跑”的扯皮基本消失了。GB28181接入网关和流媒体处理服务也一样代码和依赖一起打镜像部署动作从“四五个小时的人工配置”变成“一条docker run”。K8s解决的是编排和资源调度问题。Docker只管单机层面的容器生命周期几十台服务器上的容器应该怎么放、怎么伸缩、挂了怎么恢复Docker自己管不了。K8s的调度器可以把不同算力类型的节点打上标签把GPU资源量化为可调度的资源再按任务优先级和资源诉求把Pod分配合适的节点上。面对异构算力这种“资源抽象能力”恰恰是我们最缺的。还有一个隐性原因是生态。K8s的Device Plugin机制可以自定义上报GPU、NPU等扩展资源Prometheus等监控体系跟K8s的集成也非常成熟。后来的实践也证明我们做的算力调度、弹性伸缩全部依赖K8s这套扩展生态才一步步落地。相比之下OpenStack太重应对这种业务导向的AI推理场景反而不灵活。1.3 异构算力到底在说什么异构算力这个词听起来很高大上说白了就是同一个AI推理任务可以在不同类型、不同厂商的计算硬件上执行。目前主流的基本是四大类。第一类是GPU。NVIDIA的A100、V100、T4、4090这些适合训练和重推理任务生态最成熟CUDA加持下几乎所有模型都能跑。第二类是NPU。国产华为昇腾、地平线等芯片专为AI推理设计功耗低、性价比高但目前对某些模型算子的支持不如GPU全面。第三类是CPU。用AVX512、OpenVINO等手段做推理加速性能弱但胜在部署门槛极低扛一些小模型和低并发足够了。第四类是VPU、FPGA这些边缘设备在视频编解码场景有独特优势但通用性差一些。这四类硬件混在一个集群里就带来了两个层面的问题一是怎么让上层业务不感知底层硬件差异二是怎么让不同任务按照最合适的算力类型去执行。前者靠容器运行时和镜像分层解决后者靠K8s的资源模型和调度策略解决。这套体系一旦建好客户说“这批货只能用国产卡”我们只需要把新节点加进集群算法任务自动往NPU上排业务侧代码一行不用改。2. GB28181与RTSP接入层的容器化改造2.1 两种协议的本质差异与接入难点开始动工之前我们先把GB28181和RTSP的技术路径又捋了一遍。RTSP是典型的“客户端拉流”模式平台主动向摄像头发RTSP请求设备返回SDP描述协商好传输参数后用RTP/UDP或RTP/TCP把码流推过来。GB28181则不一样它基于SIP信令设备上线时主动向SIP服务器注册之后平台通过SIP INVITE向设备发指令设备再把RTP流推到指定端口。简单说一个是你去取一个是设备送上门。这个差异直接导致了接入层的容器化难点。RTSP的问题主要在于KeepAlive和超时控制。RTSP会话是有状态的设备端如果长时间没有收到保活消息会自动断开。但容器化部署时Pod可能被重新调度、IP会变如果状态还留在内存里断线重连就无从谈起。所以我们把RTSP会话管理单独拆成一个带Redis持久化的模块所有会话信息落库Pod重启之后自动恢复。GB28181的问题还要更麻烦一点。SIP信令走UDP 5060端口设备注册、心跳、INVITE全都靠它。媒体流则需要接收端开放一个UDP端口范围比如30000到31000设备把RTP流推到这个范围内的某个端口上。这在容器网络里非常蛋疼因为K8s的NodePort端口范围默认是30000到32767而GB28181媒体端口通常要配置成一个相对固定的区间和NodePort混在一起容易冲突。2.2 接入网关的微服务拆分看清楚难点之后我们把原来的单体视频接入服务拆成了四个微服务全部容器化跑在K8s里。第一个是rtsp-gateway负责RTSP拉流、RTSP会话管理、流超时重连。它对外暴露RTSP端口对内通过内部API把码流交给处理链路。第二个是gb28181-sip负责SIP信令服务包括设备注册、心跳保活、INVITE请求收发。第三个是gb28181-media负责接收设备推上来的RTP流做RTP排序去重、解包、转封装。第四个是stream-analyzer负责把H.264/H.265裸流做解码、抽帧然后投递到下游AI处理队列。这四个服务拆开之后有一个特别大的好处可以独立扩容。比如某天某个片区突然接入了两三百路新设备只有gb28181-media需要扩容其他服务完全不用动。这在单体架构里是不敢想的原来一扩容就是把整个服务复制一份资源浪费非常夸张。拆服务还有一个隐形收益——故障隔离。之前信令处理有一个内存泄漏导致进程OOM整个视频接入全挂了。拆分之后最多SIP服务重启几秒钟RTSP流和媒体流不受影响。客户无感这个体验在交付时真的太重要了。注意拆分服务不是越多越好。我们踩过一次坑把音频解码单独拆了个服务结果因为内部网络传输延迟导致音频和视频不同步被迫做回滚合并。微分服务的粒度要基于业务边界而不是技术便利。2.3 流媒体处理链路与容器资源配额接入网关拆好了处理链路的性能调优也是重头戏。视频流从RTP包到AI推理任务中间的处理链路大致是这样RTP接收、组包排序、解复用、解码抽帧、缩放预处理、送入推理队列。这条链路的每一步都是CPU密集或内存密集操作给容器配资源配额时必须要算清楚。我们以一个标准1080P、25fps的H.264通道为例做了一次压力测试结论大概是组包排序和RTP解包单核CPU约能处理四路到六路取决于网络抖动程度。软解H.2641080P硬解CPU占用很低但如果客户环境没有硬解能力纯软解一路就要占掉两个到四个核。解码后的YUV抽帧转RGB内存带宽占用极高需要预留至少128MB到256MB内存做帧缓冲。综合下来我们把每个视频接入Pod的资源请求设置为“2核CPU、4GB内存”对应并发八路到十六路视频流。这个量级不是拍脑袋定的是真跑过压测、观察到CPU和内存曲线稳定后才定下来的。设置CPU request还有一层含义——K8s调度器判定Pod能不能放进某个节点时看的就是request值如果设置太低会被调度到资源不足的节点上结果容器起来了却频繁被抢占。2.4 接入层的高可用与优雅退出容器化的一个优势是故障自愈但不是“容器一重启就万事大吉”。视频接入服务是长连接型业务Pod在销毁、重启、迁移时如果不做优雅退出正在处理的视频流会直接断开设备端的重连风暴能把整个网关打穿。我们的做法是给所有接入类Pod配置lifecycle的preStop钩子在Pod终止前先向注册中心摘除自己停止接收新连接然后等待已有的视频流处理完或者达到宽限期上限。K8s默认的terminationGracePeriodSeconds是30秒RTSP和GB28181会话重置基本够用但如果你在调整转码参数或模型切换建议把这个值加长到60秒甚至90秒。另一个高可用细节是K8s的PodDisruptionBudgetPDB。我们在做节点维护、集群升级时如果没有配置PDB节点驱逐会把所有接入服务同时干掉。配置了PDB之后系统会保证至少有一定数量的副本存活驱逐过程变成滚动式业务基本无感知。这个配置看起来不起眼但真正经历一次半夜节点的强制重启你就知道它有多救命。3. 异构算力调度从“一卡一模型”到“资源池化”3.1 算力抽象层怎么落地接入层跑通了真正难啃的骨头是算力调度。前面说过客户的硬件五花八门GPU、NPU、CPU混在一个集群里上层算法任务要做的是“自动选择最合适的算力”。我们做的第一步是定义了一套算力抽象层把所有异构硬件分成两大类处理通用型算力和AI专型算力。通用型算力就是CPU任何节点都有默认兜底。AI专型算力包括GPU和NPU通过K8s的Device Plugin向外暴露扩展资源。举个具体例子。我们在部署GPU节点时用了NVIDIA官方提供的nvidia-device-plugin它会自动检测节点上的GPU型号和数量然后把GPU资源上报给K8s资源名类似nvidia.com/gpu。比如一台T4的节点上报资源就是nvidia.com/gpu: 1。在部署NPU节点时根据厂商SDK的Device Plugin实现做对接资源名定义为hislicon.com/npu: 1。上层算法服务的YAML里不再写死“必须跑在N卡”或“必须跑在昇腾上”而是写清楚资源诉求和约束条件。比如resources: requests: nvidia.com/gpu: 1 limits: nvidia.com/gpu: 1这个意思很明确这个任务需要一张GPU卡。具体是T4还是A100让调度器根据节点的用户态资源水位去决定。如果项目对算力类型有要求比如必须用NPU那就把资源名换成hislicon.com/npu同时给这条工作负载加一个nodeSelector绑定到对应算力类型的节点标签上。3.2 调度策略亲和性、优先级与碎片整理算力抽象只是开了个头调度策略才是决定资源利用率高低的关键。我们主要做了三件事。第一件是给节点打标签和污点。节点按算力类型、型号、显存大小打标签比如gpu-typet4、npu-typeascend、memory16g。需要跑在T4上的任务加nodeSelector匹配不上就不调度。如果某个节点要预留做独占任务就打上污点Taint普通任务调度不上去只有启用了对应容忍Toleration的任务才会分配上去。这就实现了“某些高优任务有专用算力低优任务只能抢剩余资源”的逻辑。第二件是设置Pod优先级。视频AI中台里的任务不是都平等的实时识别任务可能要求在几十毫秒内完成推理离线批量分析任务则可以容忍几分钟的排队。我们为推理任务设置了“实时任务”“准实时任务”“批量任务”三个优先级对应K8s的PriorityClass。调度时高优先级Pod可以抢占低优先级Pod确保实时业务不受影响。第三件是解决算力碎片问题。这是最容易忽略、也最坑的真实问题。做过GPU调度的都知道一张40GB显存的卡如果跑了一个需要32GB显存的模型剩余8GB往往没法被另一个需要8GB以上显存的任务利用这块资源就白白浪费了。我们做了两件事缓解碎片化一是把大模型切分到多卡推理把资源请求拆成多份二是对支持显存池化的硬件启用显存共享机制。每做完一次资源盘点都像在整理硬盘碎片效果非常直观。3.3 模型加载、冷启动与弹性伸缩算力调度里还有一个隐藏在调度之后的性能杀手——模型加载。一个大模型光加载就要十几秒甚至几十秒如果推理Pod每次扩容都要重新加载模型用户看到的延迟会非常难堪。我们的解法是引入模型预热和常驻机制。核心思想是推理Pod启动后立即加载模型加载完成并跑通健康检查后才对外提供服务。K8s的readinessProbe在这里起了关键作用探不到模型加载成功的返回值Pod就不会被接入Service流量扩容也不会造成请求超时问题。弹性伸缩方面我们混合使用了HPA。HPA根据指标把推理副本从2个拉到10个但受限于模型加载时间扩容生效可能需要30秒以上。为了平滑这个窗口我们又加了一层排队缓冲新来的视频帧先进入消息队列推理实例扩容完成后从队列里消费。这等于给“冷启动”垫了一层缓冲用户看到的结果就是——视频虽然有一瞬间轻微延迟但不会丢帧。关键体会异构算力调度不是“K8s装好就能自动均衡”它的核心是适配业务优先级、硬件差异、模型加载特性的综合调优。宁可前期花时间梳理任务优先级和资源画像不要等上了生产再在半夜被调度问题叫醒。3.4 推理任务的容器化封装与镜像分层算力调度还有一个基础前提就是算法服务的容器化封装必须规范。我们的算法团队一开始提交过来的Dockerfile千奇百怪有的把模型文件直接COPY进镜像有的把Python依赖和环境分开导致镜像有七八个GB拉取和部署都慢得不行。后来我们做了统一的算法镜像规范重点有三条第一基础镜像分层。CUDA运行环境、Python运行环境、算法依赖库、业务代码、模型文件每一层单独放。模型文件这种经常更新的层放在最上层基础环境层的镜像可以复用全团队共享一份镜像仓库占用率大幅下降。第二模型文件不打包进镜像。统一放在对象存储或共享文件系统里Pod启动时挂载对应存储卷。好处是模型更新不用重新打镜像、不用重新发布Pod直接替换存储里的模型文件再触发一次滚动重启就行。而且多副本Pod可以挂载同一个模型目录不用每个副本都copy一份容器占用内存和磁盘都省很多。第三推理服务必须支持多版本和灰度发布。容器化之后灰度发布变成两个版本的Pod同时运行通过Service流量权重做逐步切换。这个操作流程配合上算力调度很多时候连客户都没发现模型已经换了新的。4. 平台落地的关键配置与踩坑实录4.1 集群规划与容器运行时选型先聊一个被问得最多的基础问题K8s版本和容器运行时到底怎么选。我们在项目开始时用的是Kubernetes 1.20版本、containerd运行时后来逐步升级到1.24。为什么不用Docker运行时因为K8s从1.24开始就完全移除了对Docker的原生支持底层运行时换成了containerd。你现在如果还在用Docker引擎做运行时建议尽快换掉否则集群升级的时候会撞上不兼容问题。集群规模方面我们的规划是三组节点一组是通用计算节点池跑视频接入、转码、前端业务操作系统用通用Linux发行版即可一组是GPU节点池专门部署NVIDIA GPU算力一组是NPU节点池部署国产NPU算力。三组节点池通过标签和污点隔离各司其职互不干扰。网络用的Calico模式Pod网络IP不跨节点互通时走VXLAN隧道性能虽然有一点损耗但胜在管理简洁、兼容性好。4.2 存储与配置管理状态型服务的容器化关键视频AI中台里很多服务是有状态的GB28181的SIP设备注册表要持久化RTSP会话要做故障迁移恢复算法运行时的模型缓存和日志要落盘。这里如果沿用无状态应用思路去设计大概率会翻车。我们为状态型服务专门设计了PVCPersistentVolumeClaim方案。系统里的核心配置算法参数、接入网关白名单、调度策略统一放到K8s的ConfigMap和Secret里Pod启动时注入修改配置后滚动更新Pod即可生效。设备注册信息和会话状态存放在Redis集群里Redis本身通过StatefulSet部署配合Redis的持久化机制保证数据不丢。血泪教训一开始我们把所有服务都按无状态来设计结果GB28181的SIP服务Pod重启一次几百台设备全部重新上报信令风暴差点把SIP服务再次压垮。后来学乖了涉及状态的服务一律做好持久化和外部会话存储Pod只是“无状态的计算节点”可以随时替换。4.3 监控体系先做到看得见再谈调度优化算力和调度再怎么优化没有监控一切都白搭。事故发生时回看监控曲线往往比猜原因快十倍。我们的监控体系是标准的Prometheus加Grafana组合针对视频AI中台重点监控五类指标集群资源指标CPU、内存、磁盘、网络IO按节点和Pod维度分别看。算力指标GPU利用率、显存占用率、显存温度、NPU利用率。业务指标RTSP并发会话数、GB28181在线设备数、视频流处理帧率。推理指标请求入队数、出队数、平均推理延迟、P99延迟。队列指标消息队列堆积量、消费延迟。有了这五类指标调度问题和算法性能问题基本能一眼定位。比如有一次客户反馈视频拉流卡顿我们看监控发现某个RTSP网关Pod的网络吞吐已经跑到网卡上限而其他副本只有20%负载确定是流量入口没做负载均衡立刻调整Service的会话保持策略解决。4.4 六大常见问题排查速查表整个项目从开发到交付我们前前后后处理了不少问题。下面这个速查表是我觉得最有分享价值的都是网上不太容易查到的实战经验。现象可能原因排查思路解决方案GPU任务调度不上GPU节点未上报资源或调度的污点未容忍检查Device Plugin Pod状态、节点资源列表重启Device Plugin或调整Pod的NodeSelector视频流频繁断流RTSP会话超时设置过短查业务监控的断流时间和频率调长RTSP保活周期增加重连退避推理延迟突增模型加载与请求并发叠加看推理队列的堆积量和模型加载耗时启用模型预热机制扩容前预加载GB28181设备注册掉线SIP服务重启导致会话丢失查SIP服务Pod重启时间和设备注册日志状态外置到Redis服务重启不丢会话节点资源碎片严重大显存任务与多副本任务混部分析节点显存分配快照启用显存共享机制或用节点亲和性整理扩容后推理P99升高新Pod冷启动未就绪即接流量检查readinessProbe配置和流量入口策略配置就绪探针排队缓冲消化冷启动5. 架构演进的路线图建议5.1 从单体到中台的三个递进阶段如果你所在的团队也准备做类似改造我的建议是不要一上来就搞大而全的K8s中台分阶段演进会稳妥很多。第一阶段是单体容器化。先把视频接入和推理服务各打成镜像单机用docker或docker-compose跑起来把原有脚本部署平滑迁移到容器环境。这个阶段先不碰K8s重点是让团队适应镜像构建、依赖隔离和环境差异的消除。第二阶段是K8s托管。把容器化好的服务迁到K8s集群完成接入网关、存储、网络、监控的基础配置。在这个阶段重点做两件事一是状态型服务的外部化存储改造二是接入类型的标准化让GB28181、RTSP的接入流程都用同一套API。第三阶段才是异构算力统一调度。在K8s基础打好之后引入Device Plugin、节点池分类、优先级和弹性伸缩。越到后面的阶段越考验前阶段的基础是否扎实尤其是状态管理和资源配额在上算力调度前一定要理清楚。5.2 团队协同与DevOps配套技术架构演进到中台后开发组织的协同方式也必须跟着变。原来算法团队交付一个模型是一个docker镜像加一份README。现在需要一个标准化的模型包里面包含推理服务镜像、模型文件、资源诉求声明和调用的API定义。我们搭了一个简易的模型发布流水线算法训练完成后一键打包生成镜像并推送到镜像仓库同时在K8s里生成对应的推理工作负载。这倒逼算法团队把一些工程化习惯建立起来依赖锁定、多版本管理、回滚机制。我们也把部署运维的部分权限下放到项目组开发人员可以在测试环境自助发布Pod做验证但生产环境的变更必须走审批流。这套机制跑通以后交付节奏从原来的“一两周发一次版本”推进到“一天能发好几个版本”客户现场的问题响应速度也快了很多。5.3 后续可以继续耕耘的方向架构演进到这个程度视频AI中台已经算是跨过了最难的门槛。不过回过头看还有几个方向是被实践证明值得继续投入的。第一个是模型与算力的更智能匹配。现在算力调度还是靠人工定义资源名后续可以做一套“模型-算力画像”的映射关系分析每个模型在GPU和NPU上的推理耗时、功耗、费用调度时自动选择性价比最高的算力。这个做起来需要大量数据积累但一旦成熟算力成本的下降会很显著。第二个是流媒体处理的GPU卸载。GPU不仅在AI推理上能加速在视频编解码上也能用CUDA/NVDEC做硬件转码加速。把视频转码、抽帧这些重负载也迁到GPU上能大幅降低CPU压力提高单节点的接入密度。第三是更平滑的故障自愈和自愈演练。K8s虽然提供了Pod级别的自愈但业务层面的故障比如推理结果异常、算法模型失效它感知不到。后续值得做一套业务健康检查和自动回滚机制当算法指标连续异常时自动切换历史稳定版本避免长期带病运行。这次改造留给我的一个很深的体会是架构演进没有想象中那么多浪漫的技术创新更多是一个一个真实问题被解决、一个一个小坑被填平的过程。K8s和Docker不是银弹但配合上对业务细节的深耕确实能让视频AI中台在复杂多变的硬件环境下长出足够的韧性。如果你也正在这条路上摸索希望这篇从接入层到算力层再到交付层的完整复盘能帮你提前避开那些我们踩过的坑。