
前阵子帮团队把UE5.3像素流送项目从开发机搬到Linux云服务器本以为打包、拉镜像、跑起来就完事结果连续踩了好几天的坑浏览器里打开页面一直转圈、UE进程起一下就自动退出、容器里GPU明明可见但就是黑屏。把整个流程重新捋了一遍之后才把UE5.3 像素流送 Docker Linux这套组合彻底跑通。这篇就把完整思路和实操过程写出来。适合手里已经有UE5.3烘焙产物、想在云服务器上用Docker跑像素流送的开发者也适合正在评估像素流送上云方案的团队。内容会覆盖整体架构、前置环境、镜像构建、WebRTC网络问题和踩坑记录基本按从零搭建的顺序来。1. 像素流送上云的整体架构UE进程、信令服务器和浏览器三者怎么协作1.1 像素流送的基本链路浏览器当虚拟显示器像素流送的核心思路并不复杂UE引擎照常渲染画面但输出目标不是本地窗口而是通过插件把渲染画面编码成WebRTC视频流送给浏览器同时浏览器端的鼠标、键盘事件通过同样的链路反向传回UE实现远程交互。这条链路里有三个角色必须分清楚UE进程运行项目的实际渲染进程负责加载像素流送插件、编码音视频、接收输入事件。信令服务器Cirrus一个基于Node.js的服务负责在浏览器和UE进程之间交换WebRTC连接所需的SDP、ICE候选信息。它本身不传视频流。浏览器端播放器用户打开的页面通过Socket.IO连接信令服务器再与UE进程建立WebRTC媒体通道。理解这个结构之后部署目标就清晰了你需要在Linux服务器上同时跑起UE进程和信令服务器并让浏览器能通过公网访问到信令服务器。很多上云失败的情况本质上不是UE跑不起来而是这三个角色之间的地址、端口、网络路径没配到一块。让我用一个更直白的方式理解UE进程是主播浏览器是观众信令服务器是聊天室管理员。管理员只负责促成二人加上好友真正的视频通话是主播和观众之间直接建立的。这个比喻虽然简化但对排查问题方向很有帮助——信令通了不代表媒体流通了媒体流卡住又和信令服务器关系不大。1.2 为什么推荐Docker而不是直接裸机部署最早我也试过在云服务器上裸机部署装好依赖、编译依赖库、直接启动UE进程。能跑但维护起来很难受。裸机部署的问题主要集中在三点环境漂移。UE5.3在Linux上依赖一堆系统库今天能跑明天apt upgrade之后可能就起不来了。同一个项目要在开发机、测试机、正式服务器上保持完全一致手动维护几乎不可能。复现成本高。服务器上出问题团队里其他人很难在本地还原同样的环境。每次都要重新配一遍驱动、库、虚拟声卡时间都耗在环境上了。多实例扩展麻烦。像素流送最常见的生产形态是一台服务器上同时跑多个UE实例通过matchmaker把用户分配到不同实例。裸机部署时一旦要加实例就得手工处理端口、进程管理、资源隔离非常容易乱。Docker的价值在于把UE运行时、信令服务器、系统依赖完整打包成一个镜像。镜像一旦构建成功在任何安装了NVIDIA驱动的Linux服务器上都能以同样的方式启动。更关键的是容器天然就是进程隔离单元——每个实例一个容器资源限制用--cpus和--memory控制端口用Compose编排整体清晰很多。1.3 本文的部署拓扑后面所有配置都基于一个典型的单机拓扑一台带NVIDIA GPU的Linux云服务器操作系统Ubuntu 22.04。Docker环境已装好并通过nvidia-container-toolkit把GPU能力注入容器。一个包含UE5.3烘焙产物的目录类型是LinuxServer。一个官方信令服务器目录包含Cirrus和前端播放器页面。浏览器通过公网IP访问信令服务器UE进程在容器内通过本机地址和信令服务器通信。整个部署不涉及Kubernetes如果你后续要上K8s本文的镜像和启动参数仍然可以直接复用。2. Linux云服务器前置准备GPU、驱动和无头渲染环境2.1 选一台带NVIDIA GPU的Linux云服务器像素流送虽然理论上支持软件编码但生产环境我强烈建议选带NVIDIA GPU的实例。原因很简单像素流送的编码链路对GPU硬编码的依赖非常重纯CPU方案在高分辨率、高帧率场景下延迟和占用都扛不住。选型时有几个参数需要注意显存大小直接决定能跑多少实例。一个1080p、30帧的像素流送实例显存占用大约在2到4GB之间具体取决于场景复杂度。规划实例数时按这个数量级估算不要只算引擎包体大小。GPU型号影响NVENC编码能力。消费级显卡和部分专业卡在编码器并发路数上有差别建议以NVIDIA官网的NVENC矩阵为准。云厂商的GPU实例通常已经预装了驱动但这一点必须确认。如果你拿到的是不带驱动的镜像后面容器里怎么配都没用。2.2 宿主机驱动与nvidia-container-toolkit容器里到底装不装驱动这是新手最容易混淆的地方Docker容器里运行UE镜像里要不要装NVIDIA驱动答案是一般不用。驱动属于内核态和硬件层的东西应该由宿主机负责容器通过nvidia-container-toolkit把驱动动态挂载进去。宿主机上的准备工作分三步确认宿主机的NVIDIA驱动正常工作执行nvidia-smi能看到GPU信息和驱动版本。安装nvidia-container-toolkit。配置Docker使用该runtime。在Ubuntu 22.04上典型的安装命令如下sudo apt-get install -y nvidia-container-toolkit sudo nvidia-ctk runtime configure --runtimedocker sudo systemctl restart docker装完之后启动容器时加上--gpus all参数容器内就能看到GPU。可以用下面的命令快速验证docker run --rm --gpus all ubuntu:22.04 nvidia-smi如果这条命令能正常输出GPU信息说明宿主机的GPU已经成功注入容器后面UE进程才有机会渲染。注意nvidia-container-toolkit并不会把容器变成有完整驱动的环境它只是把宿主机的驱动库、工具链按需挂载进去。所以镜像不需要也不能装NVIDIA驱动否则容易和宿主机版本冲突。2.3 无头渲染的两个隐藏依赖虚拟显示与虚拟音频Linux服务器通常没有显示器、没有声卡但UE在渲染和音频初始化时默认会去找这些设备。这也是很多UE项目在容器里秒退或者启动就崩溃的原因之一——不是代码问题是它找不到显示设备和音频设备。像素流送本身支持无头模式但你需要给UE一个伪环境显示环境UE5.3在Linux上推荐使用Vulkan RHI像素流送插件可以通过-RenderOffScreen参数进行离屏渲染不需要真实显示器。但如果你的项目强制初始化X11窗口就需要考虑Xvfb这类虚拟显示方案。我的建议是能走OffScreen就不额外引入Xvfb少一个进程少一个故障点。音频环境UE启动时如果检测不到音频设备部分版本会出现初始化失败或输出大量错误日志。常见做法是加载内核虚拟声卡模块modprobe snd-dummy让容器内有一个无声卡来满足UE的音频初始化需求。实测下来这一步很容易被忽略建议在服务器初始化时一并完成。如果你是用阿里云、腾讯云这类平台的GPU实例需要在初始化脚本或运维文档里把modprobe snd-dummy和nvidia-ctk runtime configure一起固化下来避免下次重装系统后又要临时找方案。3. 构建UE5.3像素流送镜像从烘焙产物到可运行的容器3.1 基础镜像与系统依赖库基础镜像选择上我推荐直接用ubuntu:22.04不要用带CUDA的完整镜像。原因有两点一是UE5.3本身不依赖CUDA开发库像素流送编码走的是NVENC通过驱动映射就能用二是ubuntu:22.04镜像体积更小后续维护更简单。可选的做法是使用nvidia/cuda这类镜像但你会引入大量用不到的CUDA文件。如果团队对镜像体积敏感尽量保持精简。创建Dockerfile之前先列出UE5.3 Linux运行时常用到的系统依赖库。下面这份清单是在多次测试基础上整理的覆盖了X11、OpenGL/Vulkan、SDL、SSL等常见依赖FROM ubuntu:22.04 # 避免交互式安装 ENV DEBIAN_FRONTENDnoninteractive RUN apt-get update apt-get install -y --no-install-recommends \ ca-certificates \ curl \ libx11-6 \ libxext6 \ libxrandr2 \ libxfixes3 \ libxinerama1 \ libxcursor1 \ libasound2 \ libpulse0 \ libnss3 \ libssl3 \ libgl1 \ libegl1 \ libvulkan1 \ libsm6 \ libice6 \ nodejs \ npm \ rm -rf /var/lib/apt/lists/*这里nodejs和npm是为信令服务器准备的。如果你打算把信令服务器放到另一个容器里这部分可以去掉保持UE镜像更精简。依赖库宁多勿少。UE在Linux启动时会动态加载很多底层库缺一个就可能在启动早期静默退出。如果你启动时遇到error while loading shared libraries就按提示补装对应的libxxx。3.2 拷贝UE烘焙产物和官方信令服务器假设你的UE5.3项目已经完成了Linux Server平台的烘焙产物目录结构大致如下LinuxServer/ ├── MyProjectServer ├── Engine/ ├── MyProject/ └── ...其中MyProjectServer是启动脚本真正的二进制一般在MyProject/Binaries/Linux/下。把这个目录整体拷进镜像即可。信令服务器可以从UE引擎目录下找常见路径是Engine/Source/Programs/PixelStreaming/WebServers/SignallingWebServer/这个目录包含cirrus.js、www/前端播放器页面、package.json等。把它单独拷到镜像的/app/signalling目录下与UE产物放在一起后续通过一条启动命令同时拉起。如果你已经有一个npm版本的签信服务器目录也是一样的用法。前提是版本和UE5.3匹配最好直接用引擎自带的省得遇到协议版本不兼容的问题。3.3 启动脚本与像素流送参数设置镜像构建完还不够还需要一个启动脚本把UE进程和信令服务器进程一起拉起来。这是我实际使用的entrypoint.sh#!/bin/bash set -e # 启动信令服务器 cd /app/signalling npm install --omitdev node cirrus.js --httpPort80 # 启动UE像素流送进程 cd /app/LinuxServer/MyProject/Binaries/Linux ./MyProjectServer MyProject \ -UNATTENDED \ -RenderOffScreen \ -AllowPixelStreamingCommands \ -PixelStreamingIP127.0.0.1 \ -PixelStreamingPort8888 \ -AudioMixer \ -MaxFPS30 wait几个参数逐个说明-UNATTENDED表示无交互模式避免UE等待人工操作。-RenderOffScreen让UE使用离屏渲染这是无头环境的关键。-AllowPixelStreamingCommands是UE5.3新增的选项。默认情况下为避免安全风险浏览器端不能通过信令通道给UE发控制台命令开发调试时需要显式打开。-PixelStreamingIP127.0.0.1这个很容易出错。很多教程会让你填服务器公网IP但信令服务器和UE进程在同一个容器里用回环地址反而最稳定。填公网IP会让UE通过外网去回连自己的信令服务器一旦云平台安全组或防火墙策略有奇奇怪怪的拦截连接就建立不起来。-PixelStreamingPort8888是信令服务器监听的端口必须和cirrus配置一致。-MaxFPS30限制渲染帧率主要用于控制GPU占用按需调整。3.4 用Docker Compose编排UE进程和信令服务器虽然上面把信令服务器和UE进程放在同一个容器里但我更推荐用Docker Compose把它们拆成两个服务。好处是信令服务器可以独立升级、独立扩缩容日志也能分开收集。一个可用的docker-compose.yml示例如下version: 3.9 services: signalling: image: pixel-streaming:latest command: [node, /app/signalling/cirrus.js, --httpPort80] ports: - 80:80 restart: unless-stopped game: image: pixel-streaming:latest command: [/app/entrypoint.sh] runtime: nvidia environment: - NVIDIA_DRIVER_CAPABILITIESall ports: - 19301-19309:19301-19309/udp volumes: - ./LinuxServer:/app/LinuxServer restart: unless-stopped不过上面的command写法有点理想化其中一个服务还要负责启动UE进程。我把生产环境更好的做法留到后面第4章和第6章逐步展开先记住一个原则信令服务器是HTTP服务UE进程是WebRTC媒体源两者依赖的端口不同需要暴露给公网的范围也不同。4. 网络与WebRTC连接公网访问、端口规划和TURN的必要性4.1 哪些端口必须放开像素流送在Docker化部署后网络端口规划直接决定用户能不能连上。共包含三类流量流量类型协议默认端口用途前端页面TCP80浏览器加载播放器页面、与信令服务器通信信令与UE通信TCP8888信令服务器与UE进程交换连接信息WebRTC媒体流UDP19301-19309浏览器与UE进程之间的音视频、输入事件数据在云平台的安全组中至少要把80端口、8888端口和19301到19309的UDP端口全部放行。如果你用network_mode: host让容器直接使用宿主机网络则只需要在云控制台配置安全组如果使用端口映射桥接网络Docker层和云安全组两层都要放行。很多人只开了80端口页面能打开但画面一直卡在连接中大多数情况下就是UDP媒体端口没放行。4.2 信令服务器地址到底该填什么我见到最多的问题就是把-PixelStreamingIP填成服务器公网IP或云平台内网IP。在同一个容器内部署时最优解是127.0.0.1。原因是UE进程和信令服务器走回环网络通信完全绕开公网NAT和防火墙稳定性和延迟都更好。那浏览器怎么访问呢浏览器访问的是信令服务器的公网地址由前端页面在连接时指定。Cirrus默认监听所有网卡的80端口云服务器的公网IP加端口就能访问到。如果没有使用Nginx等反向代理直接访问http://你的服务器IP即可。如果配置了域名也需要把域名解析到服务器公网IP并在Cirrus配置里设置相应的publicIp或环境变量避免它向外广播内网地址。4.3 公网连接失败的排查顺序当用户反馈连不上时我一般按下面的顺序排查效率最高浏览器是否加载到了页面打不开页面先看80端口、安全组、Docker端口映射。信令服务器是否收到了连接请求看信令服务器的日志有没有浏览器接入记录。UE进程是否建立了WebRTC连接看UE进程日志有没有收到信令服务器转发过来的offer。信令服务器只是交换信息如果UE侧没有反应重点检查8888端口和-PixelStreamingIP。媒体流是否建立如果信令已经成功但没画面重点检查UDP端口、TURN中继、防火墙。这套排查顺序帮我解决过超过80%的连接类问题。切忌页面一打开转圈就直接怀疑GPU很多时候根本不是渲染问题而是信令或者UDP端口问题。5. 实测踩坑记录从黑屏到卡死的完整排查链路5.1 浏览器黑屏GPU设备没进容器有一次部署后页面能打开信令日志也显示连接成功了但浏览器画面全黑。第一反应是UE的Vulkan初始化失败但查看UE进程日志时发现渲染进程确实在运行帧率也在输出。后来排查到根本原因容器里实际上没有GPU设备。当时docker run命令少了--gpus all容器回退到了软件渲染。UE渲染正常但编码器无法工作推给浏览器的就是黑屏或花屏。验证方法很简单docker exec -it 容器名 nvidia-smi如果提示找不到nvidia-smi说明GPU没有映射进容器。检查docker run参数和nvidia-container-toolkit是否装好。5.2 UE进程启动即退出Vulkan ICD和共享内存问题UE进程崩溃退出通常有两类原因日志里表现不同。第一类是Vulkan初始化失败。容器里装了libvulkan1但NVIDIA的Vulkan ICD没有正确注入。nvidia-container-toolkit依靠NVIDIA_DRIVER_CAPABILITIES环境变量决定注入哪些能力。如果值设置不当Vulkan ICD文件就不会挂载进容器。在Compose中显式加入environment: - NVIDIA_DRIVER_CAPABILITIESall第二类是共享内存不足。UE是典型的多线程应用多进程架构下会创建共享内存做数据交互。容器默认的/dev/shm只有64MB很容易触发UE的共享内存分配失败表现为随机崩溃或启动后数秒内卡死。启动容器时加上--shm-size1g使用Compose则写为shm_size: 1g这个问题很隐蔽因为日志不一定每次都报同样的错误甚至可能表现成偶尔跑几分钟就挂了。5.3 音频设备缺失导致的启动报错容器里没有声卡时UE会打印类似无法打开音频设备的错误。严格说这不一定导致进程退出但会带出一连串初始化警告也会让后续排查变得困惑。我们在镜像里加装了libasound2和libpulse0并在宿主机执行modprobe snd-dummy让容器内存在一个虚拟声卡设备误报就消失了大半。如果用的是K8s或容器平台无法在宿主机加载内核模块也可以在容器内创建一个ALSA空设备配置把默认声卡指向null。效果没有modprobe snd-dummy干净但能规避崩溃问题。5.4 多实例并发时的显存与端口规划当一台服务器跑多个像素流送实例时端口规划就成了核心问题。一个UE实例需要一组独立的8888信令端口和WebRTC UDP端口区间。比如实例1占用8888和19301-19305实例2占用8889和19306-19310依次类推。显存也要提前划分。UE对显存的需求不是线性的多个实例同时启动时的峰值占用通常高于单实例的简单相加。我在4GB显存的GPU上跑两个1080p实例比较稳3个就开始偶发性丢帧。建议上线前压测一晚上记录显存峰会再决定实例数。6. 生产化建议自动重启、中继服务和HTTPS6.1 容器自动重启与健康检查UE进程崩溃是常态不是意外。生产环境必须让容器在崩溃后自动恢复。最简单的做法是设置restart: unless-stopped让Docker守护进程在异常退出后重新拉起容器。但重启UE容器会连信令服务器一起重启影响粒度比较粗。更精细的做法是在容器内用supervisor或s6管理多个子进程只重启挂掉的UE进程。对于大多数起步阶段Docker的restart策略已经够用。健康检查同样必要。Cirrus有一个/ready接口可用也可以直接用TCP端口检测。Docker的健康检查配置如下healthcheck: test: [CMD, curl, -f, http://127.0.0.1/] interval: 30s timeout: 10s retries: 3注意容器内要有curl否则健康检查永远失败。这个细节在构建镜像时就要带上。6.2 自建中继服务解决NAT后的媒体连接问题云服务器一般有公网IP按理说WebRTC直连就能通。但用户如果处于严格的NAT或防火墙环境下浏览器到UE进程的媒体流可能建立不起来表现就是信令正常、画面不出。遇到这种情况标准解法是部署一个TURN中继服务器。WebRTC的媒体流可以先经过TURN服务器中转绕开用户侧的NAT限制。coturn是常用的开源实现Docker化起来很快。部署coturn后在Cirrus的配置文件中填入TURN地址、端口、用户名和密码。这里要注意的是TURN只是兜底方案直连优先中继会引入额外延迟。在信令服务器的配置中把TURN的候选优先级调低即可。6.3 HTTPS/WSS和浏览器权限像素流送播放器页面涉及getUserMedia网页获取摄像头等设备能力时的接口限制、全屏等浏览器敏感API。在http://localhost下浏览器会放开但通过IP和域名访问时很多浏览器会限制这些能力。最干净的解法是给信令服务器加HTTPS使用Nginx反向代理或者直接让Cirrus启用SSL。启用HTTPS之后前端页面通过wss://连接信令服务器安全性问题一并解决。http://和https://混用会导致页面加载完成但自动播放、输入捕获异常这一点在给客户演示时最容易暴露。如果暂时没有域名和证书也可以在浏览器里先做特殊处理绕过限制但这只适合本地开发。生产环境一律建议上HTTPS成本很低收益却非常直接。最后再分享一个个人习惯每次部署完我都会在浏览器无痕窗口里完整走一遍打开页面、看到画面、操作键盘鼠标、关闭页面全流程同时盯着UE进程和信令服务器的日志。这套流程走通后再交给测试团队。像素流送出了问题80%集中在网络链路而非渲染本身先把端口、地址、TURN这些基础项全部确认完再回头看UE日志效率会高很多。