feat: 新增 Squad 服务端 Docker 多实例部署方案
包含 docker-compose.yml(实例 2 用 profile 门控)、.env.example 配置模板、 以及基于实测数据的部署说明。 实测确认的关键点: - 镜像仅 134MB 不含游戏文件,约 80-100GB 服务端由 SteamCMD 运行时拉取 - 镜像声明 4 个端口,BEACONPORT=15000 官方 wiki 未提及,host 网络下多实例会冲突 - entry.sh 会自动重写 Rcon.cfg 端口,但 sed 只匹配字面量 21114, 首启后再改 RCONPORT 会静默失败 - entry.sh 每次启动无条件跑 app_update,故共享游戏文件方案有并发写入风险(未验证) - 发现两个未见于文档的环境变量:SERVER_NAME、MULTIHOME - RANDOM 环境变量在本镜像中无效(bash 内置特殊变量覆盖不了),已记录为已知问题 凭据一律使用占位符,.gitignore 挡住 .env / Rcon.cfg / License.cfg 等含密文件。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Squad 服务端部署配置模板
|
||||
#
|
||||
# 用法: cp .env.example .env 然后填入真实值
|
||||
# 注意: .env 已被 .gitignore 挡住,不要强制提交。
|
||||
# 本仓库是 public 仓库,任何真实凭据提交后即视为泄露。
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
# ── 镜像来源 ────────────────────────────────────────────────
|
||||
# 目标机所在网络无法访问 Docker Hub,必须走私有注册表。
|
||||
# 该镜像与 Docker Hub 上游 digest 一致:
|
||||
# sha256:8cba47f53df586c2767525a9f062c2996b36f9a34a1d771a35f67a1713c46893
|
||||
SQUAD_IMAGE=squaddocker.azurecr.cn/squad-server:v1
|
||||
|
||||
# ACR 需要先登录才能拉取(凭据不写在这里,见 README §5):
|
||||
# docker login squaddocker.azurecr.cn -u <用户名> --password-stdin
|
||||
|
||||
# ── 数据存放路径 ───────────────────────────────────────────
|
||||
# 游戏本体约 80-100GB,建议指向独立数据盘挂载点,不要放 OS 盘。
|
||||
# 目录 owner 必须是 uid 1000(镜像内以 steam 用户运行):
|
||||
# sudo mkdir -p /srv/squad/inst1 && sudo chown -R 1000:1000 /srv/squad
|
||||
SQUAD_DATA_ROOT=/srv/squad
|
||||
|
||||
# ── 实例 1 ─────────────────────────────────────────────────
|
||||
# 端口取镜像默认值(Squad 官方标准端口)。
|
||||
# 游戏端口与查询端口各占 N 和 N+1 两个,所以多实例间隔至少 10,不能只差 1。
|
||||
# BEACONPORT 容易被漏掉 —— 官方 wiki 没提,但 host 网络下会冲突。
|
||||
INST1_NAME=squad1
|
||||
INST1_PORT=7787
|
||||
INST1_QUERYPORT=27165
|
||||
INST1_BEACONPORT=15000
|
||||
INST1_RCONPORT=21114
|
||||
INST1_MAXPLAYERS=50
|
||||
INST1_TICKRATE=50
|
||||
|
||||
# 服务器名。entry.sh 会把它写入 Server.cfg 的 ServerName。
|
||||
# 该变量未见于镜像文档,是从 entry.sh 源码中确认的。
|
||||
INST1_SERVER_NAME=My Squad Server
|
||||
|
||||
# 容器内存上限。宿主机无 swap 时必须设,否则 OOM 会拖垮整机。
|
||||
# 单实例机器(7.7GB 内存)建议 6g;16GB 机器跑双实例建议各 7g。
|
||||
INST1_MEM_LIMIT=6g
|
||||
|
||||
# 可选:绑定指定网卡 IP。留空 / 0.0.0.0 / 127.0.0.1 时被忽略。
|
||||
# 多实例若各有独立 IP,可用它替代纯靠端口错开。
|
||||
# INST1_MULTIHOME=
|
||||
|
||||
# 可选:创意工坊 Mod。格式是 bash 数组字面量,注意括号:
|
||||
# INST1_MODS=(1959152751 2016356569)
|
||||
|
||||
# ── 实例 2(默认不启动)────────────────────────────────────
|
||||
# 启用前请确认硬件满足 README §2.2:2 个物理核 + 16GB 内存 + 160GB 磁盘。
|
||||
# 启用方式: docker compose --profile instance2 up -d
|
||||
INST2_NAME=squad2
|
||||
INST2_PORT=7797
|
||||
INST2_QUERYPORT=27175
|
||||
INST2_BEACONPORT=15010
|
||||
INST2_RCONPORT=21124
|
||||
INST2_MAXPLAYERS=50
|
||||
INST2_TICKRATE=50
|
||||
INST2_SERVER_NAME=My Squad Server 2
|
||||
INST2_MEM_LIMIT=6g
|
||||
# INST2_MULTIHOME=
|
||||
# INST2_MODS=()
|
||||
|
||||
# ── 可选:测试版分支(需 Offworld 提供的分支名与密码)─────────
|
||||
# STEAM_BETA_BRANCH=
|
||||
# STEAM_BETA_PASSWORD=
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 不要在此设置的项
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
#
|
||||
# RCON 密码:镜像没有 RCONPASSWORD 环境变量。entry.sh 只会重写
|
||||
# Rcon.cfg 里的端口,不管密码。密码必须在服务器上直接改
|
||||
# ServerConfig/Rcon.cfg,详见 README §6.4。
|
||||
#
|
||||
# RANDOM:此镜像中该变量无效。RANDOM 是 bash 内置特殊变量,
|
||||
# 环境变量覆盖不了它,entry.sh 读到的永远是随机整数。
|
||||
# 地图轮换请改用 MapRotation.cfg。详见 README §10。
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# ── 凭据:绝不入库 ──────────────────────────────────────────
|
||||
# 本仓库是 public,任何提交进来的密码都会公开且永久留在 git 历史里
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.secret
|
||||
secrets/
|
||||
|
||||
# Squad 服务端配置中含密码/授权的文件(应只存在于服务器本机)
|
||||
Rcon.cfg
|
||||
License.cfg
|
||||
Admins.cfg
|
||||
|
||||
# ── 运行时产物 ────────────────────────────────────────────
|
||||
data/
|
||||
logs/
|
||||
*.log
|
||||
@@ -0,0 +1,435 @@
|
||||
# Squad(战术小队)专用服务器 — Docker 部署
|
||||
|
||||
基于 `cm2network/squad` 镜像的 Squad 专用服务器部署方案,支持单实例与多实例。
|
||||
|
||||
标注 **[实测]** 的内容来自 2026-07-27 对目标环境和镜像的实际验证,非估算或推测。
|
||||
标注 **[未验证]** 的内容尚未在本环境证实,请勿直接用于生产。
|
||||
|
||||
---
|
||||
|
||||
## 1. 这套方案解决什么
|
||||
|
||||
Squad 服务端官方只提供 SteamCMD 裸装方式(配 `screen` 托管)。本方案用 Docker 替代:
|
||||
|
||||
- 进程守护与自动重启交给 Docker,不依赖 `screen` 会话
|
||||
- 游戏文件、配置、日志都在挂载卷里,容器可随意重建
|
||||
- 多实例通过环境变量声明端口,不用改启动脚本
|
||||
|
||||
**不解决的问题**:游戏本体下载量和硬件需求 —— 见下节。
|
||||
|
||||
---
|
||||
|
||||
## 2. 硬件要求 — 先读这一节
|
||||
|
||||
### 2.1 镜像不含游戏文件
|
||||
|
||||
**[实测]** `cm2network/squad` 镜像只有 **134.3 MB**(压缩后,6 层),内容是 Debian 基础层 + SteamCMD + `entry.sh`。
|
||||
|
||||
**游戏服务端本体(约 80–100 GB)是容器每次启动时由 SteamCMD 拉取的**,落在挂载卷里。
|
||||
|
||||
两个直接后果:
|
||||
|
||||
- 把镜像搬到私有注册表**不能**省掉这次下载
|
||||
- 每个独立安装的实例都要下一份,所以多实例基本上必须共享游戏文件(§7.2)
|
||||
|
||||
### 2.2 最低配置
|
||||
|
||||
| 资源 | 单实例 | 双实例 | 说明 |
|
||||
|---|---|---|---|
|
||||
| **物理核** | 1 | **2** | 看物理核,不是 vCPU。Squad 主 tick 重度单线程,一核跑两实例会双双掉 tick |
|
||||
| 内存 | 8 GB | **16 GB** | 无 swap 时更吃紧,务必设 `mem_limit` |
|
||||
| 磁盘(持久) | 120 GB | **160 GB**(共享游戏文件)<br>200+ GB(独立安装) | 另需留出 SteamCMD 更新的临时空间 |
|
||||
|
||||
> ⚠️ **Azure 用户注意**:`/mnt` 挂的资源盘是**临时盘**,VM deallocate 后清空,不能放游戏文件。
|
||||
> `df -hT` 看不出来,要查 `/etc/fstab` 是否有 `azure_resource` 和 `comment=cloudconfig` 字样。
|
||||
|
||||
### 2.3 首次下载耗时会远超预期
|
||||
|
||||
**[实测]** 目标环境到 Steam CDN 的速度为 **0.8–1.6 MB/s**(约 7–13 Mbit/s)。
|
||||
|
||||
按 80 GB 计算,**单次完整下载需要 14–28 小时**。
|
||||
|
||||
部署前确认:
|
||||
- 下载完成后磁盘仍有余量(建议留 20 GB 以上)
|
||||
- 下载期间尽量不重启容器 —— SteamCMD 支持续传,但会重新校验,很耗时
|
||||
|
||||
---
|
||||
|
||||
## 3. 前置条件
|
||||
|
||||
### 3.1 目标机器
|
||||
|
||||
- Linux x86_64,GLIBC ≥ 2.17
|
||||
- Docker Engine + `docker compose` 插件
|
||||
- 当前用户在 `docker` 组,或有 sudo
|
||||
|
||||
> Ubuntu 上**不要用 `apt install docker.io`**。**[实测]** 目标环境 `apt-cache policy docker.io` 显示
|
||||
> `Candidate: (none)`。请走 Docker 官方源(`download.docker.com`)。
|
||||
|
||||
### 3.2 Docker Hub 不可达时(中国大陆常见)
|
||||
|
||||
**[实测]** 目标环境 `registry-1.docker.io` / `index.docker.io` / `auth.docker.io` 全部超时
|
||||
(DNS 可解析,属网络层拦截)。
|
||||
|
||||
两种应对:
|
||||
|
||||
**A. 镜像搬运到私有注册表(本方案采用)** — 见 §5
|
||||
|
||||
**B. 配置 registry mirror** — `/etc/docker/daemon.json`:
|
||||
```json
|
||||
{ "registry-mirrors": ["https://docker.m.daocloud.io"] }
|
||||
```
|
||||
**[实测]** 该 mirror 返回 401 正常鉴权挑战,可用。但依赖第三方站点可用性。
|
||||
|
||||
### 3.3 端口放行
|
||||
|
||||
主机防火墙之外,**云平台安全组也要放行**。Azure NSG、阿里云安全组这类配置在机器外部,
|
||||
机内 `iptables` / `ufw` 查不到,是最容易漏的一环 —— 表现为服务端一切正常但玩家搜不到、连不上。
|
||||
|
||||
规则见 §4.2。
|
||||
|
||||
---
|
||||
|
||||
## 4. 端口规划
|
||||
|
||||
### 4.1 一个容易漏的端口
|
||||
|
||||
**[实测]** 镜像声明了 **4 个**端口:
|
||||
|
||||
```
|
||||
7787/udp 27165/udp 21114/tcp 15000/udp
|
||||
```
|
||||
|
||||
最后一个 **`15000/udp`(BEACONPORT)常被漏掉** —— 官方 wiki 服务器安装页完全没有提及。
|
||||
在 `network_mode: host` 下两个实例都会去绑 15000,导致第二个实例起不来或行为异常,
|
||||
而日志里不一定报得清楚。
|
||||
|
||||
### 4.2 多实例端口表
|
||||
|
||||
游戏端口与查询端口**各占 `N` 和 `N+1` 两个**,所以实例间至少间隔 10,不能只差 1。
|
||||
|
||||
| 用途 | 协议 | 实例 1 | 实例 2 |
|
||||
|---|---|---|---|
|
||||
| 游戏 | UDP | **7787**, 7788 | **7797**, 7798 |
|
||||
| Steam 查询 | UDP | **27165**, 27166 | **27175**, 27176 |
|
||||
| Beacon | UDP | **15000** | **15010** |
|
||||
| RCON | TCP | **21114** | **21124** |
|
||||
|
||||
安全组入站规则(实例 1):
|
||||
```
|
||||
UDP 7787-7788 允许
|
||||
UDP 27165-27166 允许
|
||||
UDP 15000 允许
|
||||
TCP 21114 允许 —— 建议限制来源 IP
|
||||
```
|
||||
|
||||
> RCON 是远程管理通道,**不应对全网开放**,建议只放行管理者 IP。
|
||||
|
||||
### 4.3 `network_mode: host` 是必需的
|
||||
|
||||
Squad 依赖 Steam 查询协议,用 `-p` 端口映射经常导致服务器在游戏内浏览器中搜不到。
|
||||
|
||||
代价:Docker 不再隔离端口,多实例端口必须手工错开。
|
||||
|
||||
若每个实例有独立 IP,也可以用 `MULTIHOME` 绑定(§8.2),比纯靠端口错开更清晰。
|
||||
|
||||
---
|
||||
|
||||
## 5. 镜像搬运到私有注册表
|
||||
|
||||
在能访问 Docker Hub 的机器上执行(通常是本地开发机)。
|
||||
|
||||
### 5.1 拉取、打标、推送
|
||||
|
||||
```bash
|
||||
# 该镜像只有 linux/amd64。在 Apple Silicon 上必须显式指定 --platform,
|
||||
# 否则会报找不到匹配平台。搬运不需要运行,所以架构不匹配无妨。
|
||||
docker pull --platform linux/amd64 cm2network/squad:latest
|
||||
|
||||
docker login <注册表地址> -u <用户名> --password-stdin # 密码走 stdin,不要用 -p
|
||||
docker tag cm2network/squad:latest <注册表地址>/squad-server:v1
|
||||
docker push <注册表地址>/squad-server:v1
|
||||
```
|
||||
|
||||
> 用 `--password-stdin` 而不是 `-p <密码>`:后者会把密码暴露在进程列表(`ps`)里。
|
||||
|
||||
### 5.2 核对 digest
|
||||
|
||||
推送后记录 digest,目标机拉取后比对,确认跑的是同一个镜像:
|
||||
|
||||
```bash
|
||||
docker image inspect --format '{{index .RepoDigests 0}}' <注册表地址>/squad-server:v1
|
||||
```
|
||||
|
||||
**[实测]** 本次搬运结果与 Docker Hub 上游 digest 完全一致:
|
||||
```
|
||||
sha256:8cba47f53df586c2767525a9f062c2996b36f9a34a1d771a35f67a1713c46893
|
||||
```
|
||||
若注册表无鉴权(任何人可写),这一步是必要的完整性检查,不是可选项。
|
||||
|
||||
### 5.3 HTTP 明文注册表的额外配置
|
||||
|
||||
如果私有注册表是 HTTP(非 TLS),Docker 默认拒绝,报
|
||||
`http: server gave HTTP response to HTTPS client`。需要:
|
||||
|
||||
**Linux** — `/etc/docker/daemon.json`:
|
||||
```json
|
||||
{ "insecure-registries": ["<地址:端口>"] }
|
||||
```
|
||||
改完 `sudo systemctl restart docker`。
|
||||
|
||||
**macOS(Docker Desktop)** — 写 `~/.docker/daemon.json` 后必须**完整退出并重启 Docker Desktop**。
|
||||
|
||||
> 踩坑记录:`docker desktop restart` 可能异步返回,导致后续命令连到的还是旧守护进程,
|
||||
> 配置看似没生效。另外用 `pkill` 强杀 Docker Desktop 会打断它的退出流程,
|
||||
> 使随后的 `open -a Docker` 失效。稳妥做法:`osascript -e 'quit app "Docker"'`
|
||||
> → 轮询等 socket 消失 → `open -a Docker` → 轮询等 `docker info` 成功 → 再验证:
|
||||
> ```bash
|
||||
> docker info --format '{{json .RegistryConfig.IndexConfigs}}'
|
||||
> ```
|
||||
> 确认目标地址出现且 `"Secure": false`。
|
||||
|
||||
**使用 ACR / Harbor 等有效 TLS 的注册表时,以上都不需要。**
|
||||
|
||||
### 5.4 拉取凭据的权限收敛
|
||||
|
||||
目标机 `docker login` 后凭据会以 base64 存进 `~/.docker/config.json`(**不是加密**)。
|
||||
|
||||
如果用的是注册表管理员凭据,那台机器一旦被入侵,攻击者就获得了**推送**权限,
|
||||
可以覆盖你的镜像 tag。生产环境建议改用**只读(pull-only)令牌**:
|
||||
|
||||
- Azure ACR:创建 scope map + token,只授 `content/read`
|
||||
- Harbor:创建 robot account,只勾 pull
|
||||
|
||||
---
|
||||
|
||||
## 6. 部署
|
||||
|
||||
### 6.1 准备数据目录
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /srv/squad/inst1
|
||||
# 镜像内以 uid 1000 (steam) 运行,必须给写权限
|
||||
sudo chown -R 1000:1000 /srv/squad
|
||||
```
|
||||
|
||||
### 6.2 配置
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
$EDITOR .env
|
||||
```
|
||||
|
||||
### 6.3 启动
|
||||
|
||||
```bash
|
||||
docker compose up -d squad1
|
||||
docker compose logs -f squad1
|
||||
```
|
||||
|
||||
首次启动进入 SteamCMD 下载阶段(§2.3,14–28 小时)。
|
||||
日志出现 `Success! App '403240' fully installed.` 表示下载完成。
|
||||
|
||||
### 6.4 编辑服务器配置
|
||||
|
||||
游戏文件下载完成后,配置文件出现在 `/srv/squad/inst1/SquadGame/ServerConfig/`:
|
||||
|
||||
| 文件 | 作用 |
|
||||
|---|---|
|
||||
| `Server.cfg` | 服务器名称、人数上限等主配置 |
|
||||
| `Rcon.cfg` | RCON 端口与**密码** |
|
||||
| `Admins.cfg` | 管理员与权限组 |
|
||||
| `MapRotation.cfg` | 地图轮换 |
|
||||
| `MOTD.cfg` | 每日消息 |
|
||||
| `ServerMessages.cfg` | 循环公告 |
|
||||
| `Bans.cfg` | 封禁列表 |
|
||||
| `License.cfg` | 服务器授权 |
|
||||
|
||||
**RCON 密码必须在这里改。** 镜像没有 `RCONPASSWORD` 环境变量,`entry.sh` 只重写端口、不管密码。
|
||||
默认密码不改等于把服务器管理权公开。
|
||||
|
||||
改完重启生效:
|
||||
```bash
|
||||
docker compose restart squad1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 多实例
|
||||
|
||||
### 7.1 RCON 端口:自动处理,但有个后续陷阱
|
||||
|
||||
**[实测]** `entry.sh` 会在启动时自动重写 `Rcon.cfg` 里的端口:
|
||||
|
||||
```bash
|
||||
sed -i -e 's/Port=21114/'"Port=${RCONPORT}"'/g' "${STEAMAPPDIR}/SquadGame/ServerConfig/Rcon.cfg"
|
||||
```
|
||||
|
||||
所以**新建实例时不需要手动改 `Rcon.cfg` 端口** —— 很多教程(包括官方 wiki)说必须手动改,
|
||||
对这个镜像而言是多余的。
|
||||
|
||||
**但这个 sed 只匹配字面量 `Port=21114`。** 因此:
|
||||
|
||||
> ⚠️ **首次启动之后再改 `RCONPORT`,Rcon.cfg 不会跟着变** ——
|
||||
> 此时 `Rcon.cfg` 里已经是旧的自定义端口,不再包含 `21114`,sed 匹配不上,静默失败。
|
||||
> 结果是启动参数与配置文件不一致,RCON 连不上。
|
||||
>
|
||||
> 改端口时必须**同时手工改 `Rcon.cfg`**,或者删掉 `Rcon.cfg` 让 SteamCMD 重新生成默认文件。
|
||||
|
||||
### 7.2 共享游戏文件 **[未验证]**
|
||||
|
||||
游戏本体约 80–100 GB,下载一次 14–28 小时,两个实例各下一份通常不现实。
|
||||
思路是游戏本体只留一份共享,每实例独立持有 `ServerConfig` 和日志。
|
||||
|
||||
> ⚠️ **本方案尚未实测,存在明确的已知风险。**
|
||||
>
|
||||
> **[实测]** `entry.sh` 在**每次容器启动时**都会无条件执行 `steamcmd +app_update`
|
||||
> (源码第 2–17 行,没有"文件已存在则跳过"的判断)。
|
||||
>
|
||||
> 两个容器共享同一游戏目录时,会**并发**对同一批文件跑 SteamCMD 更新,有文件损坏风险。
|
||||
>
|
||||
> 若要采用,稳妥流程是:停掉所有实例 → 单独跑一次更新 → 再一起启动;
|
||||
> 并且需要改造 `entry.sh` 或覆盖启动命令来跳过第二个实例的更新步骤。
|
||||
>
|
||||
> **在验证完成前,请勿在生产环境使用。**
|
||||
|
||||
### 7.3 启用实例 2
|
||||
|
||||
```bash
|
||||
docker compose --profile instance2 up -d
|
||||
```
|
||||
|
||||
启用前确认硬件满足 §2.2 的双实例要求。
|
||||
|
||||
---
|
||||
|
||||
## 8. 镜像中未见于文档的环境变量
|
||||
|
||||
以下变量**不在镜像的默认 `ENV` 列表里**,是从 `entry.sh` 源码中确认的 **[实测]**,
|
||||
但相当有用:
|
||||
|
||||
### 8.1 `SERVER_NAME`
|
||||
|
||||
设置后,`entry.sh` 会把它写入 `Server.cfg` 的 `ServerName`(源码第 22–25 行):
|
||||
|
||||
```bash
|
||||
sed -i -e "s/^ServerName=.*/ServerName=\"${SERVER_NAME}\"/" .../Server.cfg
|
||||
```
|
||||
|
||||
省去手工改配置文件。注意它每次启动都会覆盖 `ServerName`,
|
||||
所以如果你在 `Server.cfg` 里手改了服务器名,又设了这个变量,手改的会被覆盖。
|
||||
|
||||
### 8.2 `MULTIHOME`
|
||||
|
||||
绑定到指定网卡 IP(源码第 47–51 行)。留空 / `0.0.0.0` / `127.0.0.1` 时被忽略。
|
||||
|
||||
多实例若各有独立 IP,用它比纯靠端口错开更清晰,也能让每个实例都用标准端口。
|
||||
|
||||
---
|
||||
|
||||
## 9. 日常运维
|
||||
|
||||
### 更新服务端
|
||||
|
||||
`entry.sh` 每次启动都跑 SteamCMD 更新,所以游戏发新版本时:
|
||||
```bash
|
||||
docker compose restart squad1
|
||||
```
|
||||
|
||||
更新镜像本身:
|
||||
```bash
|
||||
docker compose pull && docker compose up -d
|
||||
```
|
||||
游戏文件在卷里,不会丢。
|
||||
|
||||
### 查看日志
|
||||
|
||||
```bash
|
||||
docker compose logs -f --tail=200 squad1
|
||||
```
|
||||
|
||||
服务端自身日志在 `/srv/squad/inst1/SquadGame/Saved/Logs/`。
|
||||
|
||||
容器日志轮转已在 `docker-compose.yml` 里配置(50MB × 3)。也可以在
|
||||
`/etc/docker/daemon.json` 里设为全局默认。
|
||||
|
||||
### 停止
|
||||
|
||||
```bash
|
||||
docker compose stop squad1
|
||||
```
|
||||
|
||||
> **[实测]** `entry.sh` 最后一行是 `bash SquadGameServer.sh ...` 而非 `exec`,
|
||||
> PID 1 是 bash 脚本。bash 在同步等待子进程时不转发 SIGTERM,
|
||||
> 所以优雅停止未必真的优雅。`docker-compose.yml` 里设了
|
||||
> `stop_grace_period: 120s` 尽量留出落盘时间,但不保证。
|
||||
> 重要操作前建议先在游戏内用 RCON 确认无人在线。
|
||||
|
||||
### 备份
|
||||
|
||||
只需备份配置,不含游戏本体:
|
||||
```bash
|
||||
tar czf squad-config-$(date +%F).tar.gz -C /srv/squad/inst1/SquadGame ServerConfig
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 已知问题与限制
|
||||
|
||||
诚实声明,避免误判。
|
||||
|
||||
### 10.1 `RANDOM` 环境变量在此镜像中无效 **[实测]**
|
||||
|
||||
镜像默认设了 `RANDOM=NONE`,`entry.sh` 第 61 行把 `${RANDOM}` 传给服务端。
|
||||
|
||||
但 **`RANDOM` 是 bash 内置特殊变量,环境变量覆盖不了它** —— 每次读取都返回随机整数。
|
||||
实测:
|
||||
|
||||
```bash
|
||||
$ env RANDOM=NONE bash -c 'echo ${RANDOM}'
|
||||
18575
|
||||
$ env RANDOM=NONE bash -c 'echo ${RANDOM}'
|
||||
2645
|
||||
```
|
||||
|
||||
所以传给 `SquadGameServer.sh` 的实际上是随机数字,不是 `NONE`。
|
||||
**设置这个变量没有任何效果**,不管设成什么。
|
||||
|
||||
地图轮换请改用 `MapRotation.cfg` 控制。
|
||||
|
||||
### 10.2 其他
|
||||
|
||||
- **§7.2 共享游戏文件方案未实测**,且已知有并发更新风险,不要直接用于生产
|
||||
- **免费服人数上限未经确认**。镜像默认 `FIXEDMAXPLAYERS=80`;
|
||||
更高人数是否需要 Offworld 授权(`License.cfg`),请向官方确认,本文不做断言
|
||||
- 镜像只有 `linux/amd64`,**无 arm64**。Apple Silicon 上只能搬运,无法本地运行验证
|
||||
- `MODS` 变量格式是 bash 数组字面量(如 `(1959152751 2016356569)`),
|
||||
不是逗号或空格分隔的普通字符串 —— 源码用 `declare -a MODS="${MODS}"` 解析
|
||||
- 宿主机 `ulimit -n` 若为默认 1024 偏低。已在 compose 中给容器单独调高,未改动宿主机
|
||||
|
||||
---
|
||||
|
||||
## 11. 排障
|
||||
|
||||
| 现象 | 原因 | 处理 |
|
||||
|---|---|---|
|
||||
| `http: server gave HTTP response to HTTPS client` | 未配 insecure-registries | §5.3 |
|
||||
| 服务器在游戏内搜不到 | 安全组未放行 UDP,或用了 `-p` 而非 host 网络 | §3.3 / §4.3 |
|
||||
| 第二实例起不来 | BEACONPORT 或 RCON 端口冲突 | §4.1 |
|
||||
| 改了 RCONPORT 后 RCON 连不上 | `Rcon.cfg` 没跟着变(sed 只匹配 21114) | §7.1 |
|
||||
| 容器反复重启 | 内存不足被 OOM kill | `dmesg \| grep -i oom` 确认;降人数或调 `mem_limit` |
|
||||
| 下载卡住不动 | Steam CDN 限速或中断 | 看日志;SteamCMD 会自动重试 |
|
||||
| 配置改了不生效 | 未重启容器 | `docker compose restart` |
|
||||
| 容器内无权限写 | 数据目录 owner 不是 uid 1000 | `sudo chown -R 1000:1000 <数据目录>` |
|
||||
| 设了 `RANDOM` 没效果 | 镜像 bug,该变量无效 | §10.1 |
|
||||
|
||||
---
|
||||
|
||||
## 12. 参考
|
||||
|
||||
- 官方 wiki(中文):https://squad.fandom.com/wiki/Server_Installation/zh
|
||||
- 镜像仓库:https://hub.docker.com/r/cm2network/squad/
|
||||
- Squad AppID:`403240`,创意工坊 ID:`393380`
|
||||
|
||||
> 官方 wiki 中文页用 6301 / 26301 作端口示例,那是部分托管商的习惯值,
|
||||
> **不是镜像默认值**。本文统一采用镜像默认的 Squad 官方标准端口 7787 / 27165。
|
||||
@@ -0,0 +1,92 @@
|
||||
# Squad 专用服务器 — 多实例编排
|
||||
#
|
||||
# 用法:
|
||||
# cp .env.example .env 然后按注释填写
|
||||
# docker compose up -d squad1 仅启动实例 1
|
||||
# docker compose --profile instance2 up -d 同时启动实例 2(需先满足硬件要求)
|
||||
#
|
||||
# 重要:必须用 network_mode: host。Squad 依赖 Steam 查询协议,
|
||||
# 用 -p 端口映射会导致服务器在游戏内浏览器中搜不到。
|
||||
# 代价是 Docker 不再隔离端口,多实例端口必须手工错开(见 README §4)。
|
||||
|
||||
x-squad-common: &squad-common
|
||||
image: ${SQUAD_IMAGE:?请在 .env 中设置 SQUAD_IMAGE}
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
|
||||
# entry.sh 以 `bash SquadGameServer.sh` 结尾而非 exec,信号不会完美转发,
|
||||
# 给足时间让服务端自行落盘。
|
||||
stop_grace_period: 120s
|
||||
|
||||
# PID 1 是 bash 脚本,用 init 回收僵尸进程
|
||||
init: true
|
||||
|
||||
# 宿主机 ulimit -n 默认 1024 偏低。在容器层面单独调高,
|
||||
# 不改宿主机配置(最小改动)。
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 65535
|
||||
hard: 65535
|
||||
|
||||
# Squad 日志量大,磁盘小的机器不轮转会被占满
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
|
||||
services:
|
||||
# ── 实例 1 ────────────────────────────────────────────────
|
||||
squad1:
|
||||
<<: *squad-common
|
||||
container_name: ${INST1_NAME:-squad1}
|
||||
environment:
|
||||
PORT: ${INST1_PORT:-7787}
|
||||
QUERYPORT: ${INST1_QUERYPORT:-27165}
|
||||
BEACONPORT: ${INST1_BEACONPORT:-15000}
|
||||
RCONPORT: ${INST1_RCONPORT:-21114}
|
||||
FIXEDMAXPLAYERS: ${INST1_MAXPLAYERS:-50}
|
||||
FIXEDMAXTICKRATE: ${INST1_TICKRATE:-50}
|
||||
# SERVER_NAME 会被 entry.sh 写入 Server.cfg 的 ServerName
|
||||
SERVER_NAME: ${INST1_SERVER_NAME:-}
|
||||
# MULTIHOME 绑定指定网卡 IP。多实例时可用它进一步隔离,
|
||||
# 留空 / 0.0.0.0 / 127.0.0.1 时 entry.sh 会忽略该参数。
|
||||
MULTIHOME: ${INST1_MULTIHOME:-}
|
||||
# 可选:创意工坊 Mod,格式为 bash 数组字面量,例如 (1959152751 2016356569)
|
||||
MODS: ${INST1_MODS:-()}
|
||||
# 可选:测试版分支
|
||||
STEAM_BETA_BRANCH: ${STEAM_BETA_BRANCH:-}
|
||||
STEAM_BETA_PASSWORD: ${STEAM_BETA_PASSWORD:-}
|
||||
# 注意:RANDOM 在此镜像中无效,不要设置。原因见 README §10
|
||||
volumes:
|
||||
- ${SQUAD_DATA_ROOT:?请在 .env 中设置 SQUAD_DATA_ROOT}/inst1:/home/steam/squad-dedicated
|
||||
# 内存上限。宿主机无 swap 时,不设上限会让容器 OOM 拖垮整机。
|
||||
mem_limit: ${INST1_MEM_LIMIT:-6g}
|
||||
|
||||
# ── 实例 2 ────────────────────────────────────────────────
|
||||
# 默认不启动。启用前请确认硬件满足 README §2.2 的双实例要求
|
||||
# (2 个物理核 + 16GB 内存 + 160GB 磁盘),否则两个实例会双双掉 tick。
|
||||
squad2:
|
||||
<<: *squad-common
|
||||
container_name: ${INST2_NAME:-squad2}
|
||||
profiles: ["instance2"]
|
||||
environment:
|
||||
PORT: ${INST2_PORT:-7797}
|
||||
QUERYPORT: ${INST2_QUERYPORT:-27175}
|
||||
BEACONPORT: ${INST2_BEACONPORT:-15010}
|
||||
RCONPORT: ${INST2_RCONPORT:-21124}
|
||||
FIXEDMAXPLAYERS: ${INST2_MAXPLAYERS:-50}
|
||||
FIXEDMAXTICKRATE: ${INST2_TICKRATE:-50}
|
||||
SERVER_NAME: ${INST2_SERVER_NAME:-}
|
||||
MULTIHOME: ${INST2_MULTIHOME:-}
|
||||
MODS: ${INST2_MODS:-()}
|
||||
STEAM_BETA_BRANCH: ${STEAM_BETA_BRANCH:-}
|
||||
STEAM_BETA_PASSWORD: ${STEAM_BETA_PASSWORD:-}
|
||||
volumes:
|
||||
# 独立安装:各自一份游戏文件(约 80-100GB × 2)
|
||||
- ${SQUAD_DATA_ROOT}/inst2:/home/steam/squad-dedicated
|
||||
#
|
||||
# 共享游戏文件方案见 README §7.2 —— 尚未实测,勿用于生产。
|
||||
# entry.sh 每次启动都会跑 SteamCMD app_update,两个容器共享同一
|
||||
# 游戏目录时会并发写入,有损坏风险。
|
||||
mem_limit: ${INST2_MEM_LIMIT:-6g}
|
||||
Reference in New Issue
Block a user