折腾笔记

折腾笔记

Redroid 安卓容器导致宿主机开机卡死:排查与处理

4
2026-10-08

一、现象

x86 NAS(飞牛 fnOS,基于 Debian 12)部署 Redroid(Android-in-Docker)容器后:

  • 系统反复重启,间隔约 20 分钟
  • 无法完成引导,卡在 systemd 启动阶段
  • 禁用 Docker 服务后,系统可正常引导

二、环境信息

项目 值
操作系统 飞牛 fnOS(基于 Debian 12)
内核 6.18.18.c1126-trim(x86_64)
Docker 数据根目录 /vol1/docker(由 /etc/docker/daemon.json 的 data-root 指定)
容器 octop-mobile-android,镜像 redroid/redroid:13.0.0-latest
存储 LVM + Btrfs
网络 OVS 网桥

三、排查过程

3.1 内核与硬件层检查

sudo sh -c 'zcat -f /var/log/syslog* /var/log/kern.log* | \
  grep -aiE "kernel panic|Oops|BUG: |segfault|oom-kill|Hardware Error|hung task"'
检查项 结果
kernel panic / Oops / BUG 无
OOM / oom-kill 无
Hardware Error / MCE 无
磁盘 I/O 错误 无
/sys/fs/pstore 空
/var/crash 仅历史遗留目录
systemd-coredump 无 core 文件
温度 硬盘 32–35 °C,GPU 41 °C

内核层无崩溃、内存耗尽、硬件故障与掉电记录。

3.2 重启来源分析

每次重启前,syslog 中存在完整的服务停止序列:

每次重启前的服务停止序列

崩溃或掉电会造成日志中断,不会输出该序列,因此关机流程均正常执行。

比对 shell 历史(HISTFILE 带时间戳)与 syslog:

shell 历史时间戳与 syslog 关机时刻比对

时间戳 1791435437 对应 2026-10-08 12:57:17,与 syslog 中 Stopping session-1.scope(12:57:17.258)一致。

auth.log 中的操作记录:

sudo: <user> : TTY=tty1 ; COMMAND=/usr/sbin/reboot

另有一条 systemd[1]: Received SIGINT,对应物理键盘的 Ctrl+Alt+Del 事件。

3.3 Docker 服务配置

禁用 Docker 后系统可正常引导,问题范围收敛至 Docker 启动流程。

/etc/systemd/system/docker.service:

[Unit]
Wants=ovsdb-server.service ovs-vswitchd.service
Requires=containerd.service
After=containerd.service ovsdb-server.service ovs-vswitchd.service ...

[Service]
Type=simple
TimeoutStartSec=0
Restart=on-failure
ExecStart=/usr/bin/dockerd ...

TimeoutStartSec=0 表示 systemd 不对该服务设置超时。

/etc/docker/daemon.json:

{
  "data-root": "/vol1/docker",
  "live-restore": true,
  "log-driver": "json-file"
}

live-restore: true 使 dockerd 启动时尝试恢复上次运行的容器。

3.4 容器运行参数

hostconfig.json:

"Privileged": true,
"NetworkMode": "host",
"SecurityOpt": ["label=disable"],
"RestartPolicy": {"Name": "unless-stopped"},
"Binds": ["/dev/binderfs:/dev/binderfs"]

参数含义:

  • Privileged: true:容器内进程拥有宿主机全部能力
  • NetworkMode: host:容器与宿主机共享网络命名空间,容器内 Android 系统的 netd 可直接修改宿主机路由表与 iptables 规则
  • RestartPolicy: unless-stopped:dockerd 启动时自动拉起该容器

3.5 开机卡死的事件链

  1. 开机后 systemd 启动 containerd,随后启动 docker.service
  2. dockerd 因 live-restore 尝试恢复上次运行的容器
  3. 该容器因 unless-stopped 策略被拉起
  4. 容器 bind 挂载源 /dev/binderfs 不存在:/dev 为 tmpfs,每次开机清空,系统中没有任何单元负责挂载 binderfs
  5. 容器启动失败,dockerd 反复重试
  6. TimeoutStartSec=0 使 systemd 持续等待 docker.service,引导流程无法完成

binderfs 状态与内核支持:

$ ls -la /dev/binderfs
ls: cannot access '/dev/binderfs': No such file or directory

$ grep -iE 'BINDER' /boot/config-$(uname -r)
CONFIG_ANDROID_BINDER_IPC=y
CONFIG_ANDROID_BINDERFS=y
CONFIG_ANDROID_BINDER_DEVICES="binder,hwbinder,vndbinder"

内核已内置 binderfs 支持(功能编译进内核而非模块,故 lsmod 中无 binder 条目),但没有任何单元在开机时挂载它。

四、处理步骤

以下操作在 Docker 停止状态下执行。

4.1 修改容器元数据

hostconfig.json:

"RestartPolicy": {"Name": "no", "MaximumRetryCount": 0}

config.v2.json:

"State": {"Running": false, "Pid": 0, ...}

使用 Python 修改并校验 JSON:

python3 -c '
import json
p="/vol1/docker/containers/<ID>/hostconfig.json"
d=json.load(open(p))
d["RestartPolicy"]={"Name":"no","MaximumRetryCount":0}
open(p,"w").write(json.dumps(d,separators=(",",":")))
'

修改前备份原始配置;修改后确认文件属主与权限(-rw------- root root)未变化。

4.2 部署网络守护脚本

#!/bin/bash
LOG=/root/net-guard.log
GW=<网关地址>
for i in $(seq 1 16); do
  bad=0
  ip route | grep -q "^default via ${GW} dev <主网卡>" || bad=1
  ping -c 2 -W 2 "${GW}" >/dev/null 2>&1 || bad=1
  if [ "$bad" != "0" ]; then
    systemctl stop docker >> "$LOG" 2>&1
    exit 1
  fi
  sleep 30
done

注册为独立定时任务,不依赖 SSH 会话:

systemd-run --unit=net-guard --on-active=25 /root/net-guard.sh

4.3 启动 Docker 并验证容器状态

timeout 150 systemctl start docker

dockerd 3 秒内就绪。

docker inspect -f '{{.Name}} Running={{.State.Running}} Status={{.State.Status}} RestartPolicy={{.HostConfig.RestartPolicy.Name}}' octop-mobile-android
/octop-mobile-android Running=false Status=exited RestartPolicy=no

dockerd 未拉起该容器,路由表与操作前一致。

4.4 删除容器与镜像

docker rm octop-mobile-android
docker rmi redroid/redroid:13.0.0-latest
Untagged: redroid/redroid:13.0.0-latest
Untagged: redroid/redroid@sha256:41e5f0c1ff27...
Deleted:  sha256:439b3b1f65db...
Deleted:  sha256:ec5b33f9a2b3...

/vol1/docker 占用由 14 GB 降至 12 GB。

4.5 清理应用数据

数据目录占用 2.2 GB:

子目录 大小
.cache(uv 1.3 GB + pip 214 MB) 1.5 GB
.local 405 MB
.octop 296 MB
.octop-browser 23 MB
.android 3.6 MB

清理前备份配置文件并生成文件清单。

应用本体及其 3 个 systemd 单元一并卸载。该应用的 sudoers 配置为:

Defaults:<user> !requiretty
<user> ALL=(ALL) NOPASSWD: ALL

该文件一并移除,并通过 visudo -c 校验 sudo 配置。

五、容器挂载源审计

校验全部容器的 bind 挂载源是否存在:

  • 读取 config.v2.json 的 MountPoints[].Source
  • 读取 hostconfig.json 的 Binds

结果:20 条 MountPoints 与 19 条 Binds 的宿主机源路径全部存在;无容器引用 /dev/binderfs。

MountPoints 的 key 是容器内目标路径,宿主机路径是 Source 字段。

六、重启验证

重启前 boot_id : 940764e6-55bf-47a1-8e35-e60bd66b3c4d
重启后 boot_id : b831d887-4315-457b-836c-b1a350438e3d
Startup finished in 17.111s (firmware) + 9.500s (loader) + 3.728s (kernel)
                 + 28.284s (userspace) = 58.724s
graphical.target reached after 28.258s in userspace.

重启后校验结果

检查项 结果
运行容器数 13
默认路由数 2
安卓 / redroid 残留 0
failed 单元 仅 nut-server

七、根因

  1. 日志中不存在内核 panic、OOM 与硬件错误记录
  2. 引导阻塞由 docker.service 的 TimeoutStartSec=0、容器 unless-stopped 重启策略、live-restore 机制共同造成
  3. 容器使用 Privileged: true 与 NetworkMode: host,可修改宿主机路由表与 iptables 规则
  4. 阻断路径:在 Docker 停止状态下将容器 RestartPolicy 置为 no,使 dockerd 启动时不拉起该容器

附录:相关命令

# 崩溃关键字全量扫描(含归档)
sudo sh -c 'zcat -f /var/log/syslog* /var/log/kern.log* | \
  grep -aiE "kernel panic|Oops|BUG: |segfault|oom-kill|Hardware Error|hung task"'

# 段错误统计
sudo grep -ac segfault /var/log/kern.log

# 重启与关机历史
last -x -F reboot shutdown

# 关机触发来源
sudo grep -aE "Received SIG" /var/log/syslog

# 重启命令审计
sudo grep -aE "COMMAND=.*(reboot|poweroff)" /var/log/auth.log

# 查看容器完整运行参数
docker inspect -f '{{json .HostConfig}}' <容器名> | python3 -m json.tool

# 校验容器挂载源
docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' <容器名>