幻兽帕鲁服务器折腾记

1. 前言

这篇博客是记录下1月底给群友部署 Palworld 服务器时所经历的一些折腾。需要折腾的原因大概可以归结为如下两项:

  1. 首先,这个游戏的服务端实在太烂了。作为大型开放世界游戏,本身就比较吃内存;而开发组糟糕的代码又引入了非常严重的内存泄漏,这两个因素叠加的结果是惊人的内存占用。一开始,我认为事情不必搞得很复杂,于是使用闲置的旧笔记本,采用物理机部署的方式搭建服务器,而那台机器只有 16G 内存,很快我就发现运行一定时间后必定 OOM,此时玩家们就会卡成 ppt,唯一的解决办法就是重启。这期间最严重的一次 OOM 直接把系统搞挂了,由于物理部署的原因,在系统挂掉以后无法通过远程手段抢救,只能等晚上下班回家手动重启电脑,那个白天群友们就没法玩了。
  2. 此外,选择了在 linux 系统上部署服务,而 linux 上的 steam 命令行工具steamcmd不仅不太好用,而且没有游戏版本更新的推送机制。每次版本更新时我都无法立刻得知,只能等群友们发现连接服务器时报错“客户端和服务端版本不一致”,我才可以开始运维操作。而且彼时的运维方式是纯手工执行,要登到服务器上终止服务端程序,然后通过(非常难用的)steamcmd更新游戏,最后再次手动启动之,麻烦至极。

所以后来就开始了刻不容缓的容器化 + 一键运维改造,其中主要解决两个问题:

  1. 通过某种手段限制客户端的内存占用,容器化即可,这样就出现严重的内存泄漏也不至于把系统打挂
  2. 提供一种快捷的服务端重启方式,且把游戏的更新检查包含在其中,这样可以一条指令同时用于释放内存和更新客户端。

2. 整体方案

  • 内网穿透:用廉价的 ECS 做公网接入,用性能、内存充足的本地机器运行游戏服务端程序。ECS region 与物理机所在城市尽可能接近,从而减少在中转链路上的网络延时
  • 容器化部署:让游戏服务端运行在容器内,且对容器的内存和 swap 设置上限,保证系统稳定;设置容器自动拉起
  • 脚本化更新:把客户端更新、服务器启动等逻辑做到容器的启动脚本中,使重启容器就能自动更新版本,并开发一个远程命令执行的接口方便运维。

3. 环境

3.1. 公网接入的云服务器

3.2. 本地游戏服务器

  • 机器:机械师创物者Mini2(专门买了个迷你主机,有点败家了hhh后面再部署些自己的小玩具上去吧)
  • CPU:R5-6600H
  • 系统:Ubuntu 22.04
  • docker 25.0.1
  • 内网穿透工具:frp 0.51.2

4. 容器化改造

4.1. 问题分析

在创建容器时我们需要注意以下几个问题:

  1. 容器中运行的端口需要映射到宿主机上。帕鲁的游戏通信协议是基于 UDP 的,默认服务端口是 8211,为了直观起见直接映射到宿主的 8211 UDP 上。
  2. 既然是游戏服务器的容器,游戏存档是需要持久化的,不能随着容器的正常或意外关闭而丢失;帕鲁刚刚发布,还处于频繁的更新中,故其服务端程序的版本更新也是需要持久化的,不能每次容器重启以后都要重新安装版本补丁。因此,我们应当将游戏的服务端目录(连带其中的存档)存放在宿主机上,并用 Volume 机制使容器能够访问。
  3. 限制CPU核数、内存、SWAP等,并设置挂了以后自动拉起。不要给这个糟糕的服务端程序丝毫的信任

在这种设计下,容器本身只是作为一个程序在硬件资源上的“牢笼”,重要的文件都存放在宿主机上,容器本身的文件系统只用于存放客户端更新所需要的steamcmd

4.2. 操作

接下来就到了镜像构建的步骤,首先下载steamcmd的镜像作为我们的 base image,官方的是cm2network/steamcmd。没什么鼓捣 docker 经验的我为了方便 debug,又在里面下载了一些常用的 netstat、vim 等工具,重新打包成镜像steambase_with_cmd_tools

随后就涉及到了启动脚本的编写。这里为了偷懒,把脚本也放在了 Volume 的路径下,实际上是可以打包在镜像里的。思路很简单,就是记录下启动时间,通过 steamcmd 检查游戏更新,然后启动游戏服务端。

注意 steamcmd 对于游戏安装目录的修改是不会持久化的,每次使用时都必须加上参数 force_install_dir [path] 来修改,否则他会在默认的路径(大概是 /home/steam/ 吧?记不清了)重新下载一份完整的游戏,而且还会随着容器的关闭而丢失。

#!/bin/bash
# log restart time
file="$(dirname "$0")/output.log"
current_time=$(date +"%Y-%m-%d %H:%M:%S")
echo "$current_time" >> "$file"

# update palworld server app
/home/steam/steamcmd/steamcmd.sh +force_install_dir /games/palworld +login anonymous +app_update 2394010 validate +quit > /games/palworld/output.log 2>&1 &

# launch game server
/games/palworld/PalServer.sh -useperfthreads -NoAsyncLoadingThread -UseMultithreadForDS > /games/palworld/output.log 2>&1

脚本安排好以后写 dockerfile 就很简单了:

FROM steambase_with_cmd_tools
CMD ["/games/palworld/startserver.sh"]

完成镜像的打包以后,我们就可以启动的容器了,这里把容器命名为palworld,并需要设置 CPU 限制、内存 + swap 限制、端口映射、volume 配置和自动拉起,完整的命令是:

docker run -itd -p 8211:8211/udp --cpus=6 --memory=22g --memory-swap=26g --name palworld -v /home/cococat/files/volume/palworld:/games/palworld --restart always [image name]

这样一来,往后每次需要更新服务端版本时,只需要重启一次容器即可,即

sudo docker restart palworld

5. 参考

https://developer.valvesoftware.com/wiki/SteamCMD#Linux
https://tech.palworldgame.com/getting-started/deploy-dedicated-server

知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇