家里有台吃灰的 CM311-1A,运营商送的那种电视盒子。参数在今天是妥妥的电子垃圾:
项 | 规格 |
|---|---|
SoC | Amlogic S905L3A |
CPU | 4× Cortex-A55 |
内存 | 2 GB |
存储 | 16 GB eMMC |
网络 | 家宽 |
刷了 Armbian 之后,我把它当成一台小型服务器用,现在跑着 7 个容器服务。
这篇文章不复述安装步骤,只讲三个在低配上必然遇到、但搜索很难搜到答案的问题。
浏览器
→ Cloudflare 边缘(SSL: Full)
→ cloudflared(apt 原生部署,非 Docker)
→ OpenResty(host 网络,:80)
→ 站点目录 / 各服务端口隧道这一层的调参结论很反直觉:默认参数(QUIC + 双栈)是唯一可用的组合。加 --protocol http2 会因为 TCP 7844 不通直接断隧道,加 --edge-ip-version 4 会掐掉唯一可用的 IPv6 通路。
低配设备上,Docker 有三处会失控,按危害排序:
① 悬空镜像
docker compose pull 拉新版本时,旧镜像不会消失,只是从「有标签」变成 <none>:<none>。来回升级几次,几个 G 就没了。
docker image prune -f # 只删悬空镜像,安全注意不要加 -a。docker system prune -a 会删掉所有没有容器在用的镜像,容器一停就再也起不来。
② 容器日志
Docker 默认 json-file 驱动没有大小上限。在 /etc/docker/daemon.json 里加:
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "2" }
}改完 systemctl restart docker。但有两个坑:
daemon.json 的 log-opts,面板会自己生成 HostConfig所以必须逐个确认:
docker inspect 容器名 --format '{{.HostConfig.LogConfig.Type}} {{.HostConfig.LogConfig.Config}}'输出 map[] 就是没上限。
已经写大的日志不要 rm——容器还开着句柄,空间不会释放。用截断:
truncate -s 0 /var/lib/docker/containers/<ID>/<ID>-json.log③ 构建缓存
docker system df 里单独一行 Build Cache,经常比镜像本身还大。docker builder prune -f 清掉。
现象是最迷惑的一种:
$ docker ps
CONTAINER ID IMAGE STATUS
a1b2c3d4e5f6 xxx:latest Restarting (137) 3 seconds ago容器不是起不来,是起来几秒就被杀。docker inspect 看到 ExitCode: 137,dmesg 里能看到:
Out of memory: Killed process 12345 (java) total-vm:2891234kB137 = 128 + 9,就是被 SIGKILL 干掉,最常见的原因就是内核 OOM Killer。
一开始我以为是服务太重,甚至去看了换 4G 内存的板子。后来发现真正的原因跟服务本身无关,是运行时按宿主机内存算默认堆:
运行时 | 默认上限 | 依据 |
|---|---|---|
JVM(Java 8) | 物理内存的 1/4 | 完全不看 cgroup,容器里也一样 |
JVM(Java 10+) | 容器 limit 的 1/4 | 有 UseContainerSupport,能感知 cgroup |
Node.js | 约 2G(64 位) | 与机器内存无关,写死在 V8 |
问题在于 Java 8 那个版本不感知 cgroup。你在 compose 里写了 mem_limit: 256m,JVM 依然按宿主机的 2G 算出 512M 的堆,一启动就超限被杀。而且 7 个容器各自独立计算,谁也不让谁。
确认办法:
docker exec 容器名 java -XX:+PrintFlagsFinal -version | grep -Ei 'maxheapsize|maxram'如果 MaxHeapSize 明显大于你设的 limit,就中了。
三条修法,按优先级:
① 显式钉死堆上限(Java 8 必做)
environment:
- JAVA_OPTS=-Xms128m -Xmx256m -XX:MaxMetaspaceSize=128m② Java 10+ 用百分比,跟着容器 limit 走
environment:
- JAVA_OPTS=-XX:MaxRAMPercentage=60 -XX:InitialRAMPercentage=30③ Node 服务加参数
command: ["node", "--max-old-space-size=256", "app.js"]然后给容器本身加上限:
services:
app:
mem_limit: 384m
memswap_limit: 384m
memswap_limit等于mem_limit时,容器就没有 swap 可用。低配机器上这点很重要——有 swap 的时候 OOM 不会立刻杀进程,而是让整机开始疯狂换页,SSH 都连不上,比直接杀掉难受得多。
验证要看到真实占用:
docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}'7 个容器加起来稳定在 1.4G 左右,留 600M 余量,才算配置到位。
还有一个隐蔽的坑:docker stats 里的 MEM USAGE 只算匿名内存,页缓存不算,所以它比内核里看到的实际占用偏低。真要核对得看 cgroup:
# cgroup v1
cat /sys/fs/cgroup/memory/docker/<容器ID>/memory.usage_in_bytes
# cgroup v2(新版 Armbian 默认是这个)
cat /sys/fs/cgroup/system.slice/docker-<容器ID>.scope/memory.current两个数字对不上是正常的,别以为统计错了。
青龙面板里跑脚本,日志结尾是:
⚠️ 青龙通知模块 notify.py 导入失败,已跳过通知:No module named 'notify'脚本跑完了,只是通知没发出去。notify.py 确实在 /ql/data/scripts/ 下。所以问题只能是那个目录不在 sys.path 里。
两次失败尝试
PYTHONPATH=/ql/data/scripts —— 无效。青龙执行任务时会自己重写 PYTHONPATH。dep_cache/.../site-packages/ 放一个 sitecustomize.py,内容是 sys.path.append(...) —— 单独 python3 -c "import notify" 成功,任务里照样失败。真因
看 otask.sh,关键是这一行(只对 .py / .pyc 任务生效):
export PYTHONPATH="${dir_preload}:${dir_config}:${PYTHONPATH}"dir_preload = /ql/shell/preload。这个目录里有青龙官方的 sitecustomize.py,最后一行是模块级的 import task_before。
于是链条是:otask.sh 把 preload 排最前 → site.py 找到的是官方那份 → Python 模块按名字缓存,同名只导入一个 → 放在 dep_cache 里的那份永远不执行,且不报错。
修法(两条一起做)
A. 走官方钩子,把路径追加进去:
# /ql/data/config/task_before.py
import sys
_scripts_dir = "/ql/data/scripts"
if _scripts_dir not in sys.path:
sys.path.append(_scripts_dir)一定要
append,不要insert(0)。仓库里几百个脚本,随便哪个叫os.py、json.py的文件都会顶掉标准库。
B. 把 dep_cache 里那份改名,让两个钩子共存:
cd /ql/data/dep_cache/python3/lib/python3.11/site-packages/
mv sitecustomize.py usercustomize.pysite.py 的加载顺序是先 sitecustomize 再 usercustomize,两个名字不同,可以共存。
唯一可信的验证方式
docker exec 容器名 bash -c 'cd /ql/data/scripts && QL_DIR=/ql task 仓库名/目录名/脚本名.py'这条命令等价于「在面板上点运行」。
不要用 python3 -E 或裸 shell 里 import notify 的结果当结论——那些进程没经过 otask.sh,PYTHONPATH 完全不同。我在这上面浪费了一整轮。
compose pull && up -d机器还在跑,欢迎交流。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。