Skip to content

懒猫微服进阶心得(二):一台机器跑三套 Docker?深入解析懒猫容器的共存机制(上)

本文仅代表个人视角对懒猫 Docker 的拆解分析,内容为基于现象的倒推推测,不代表懒猫官方实现方式。

拿到任何 NAS 的第一件事是开启 SSH 功能,第二步就是用 Docker 启动容器。

懒猫微服里的 Docker 环境不太一样,一台机器上有三套:

docker:运行系统组件

pg-docker:普通的 Docker 环境,供用户日常使用

lzc-docker:运行懒猫商店应用

三套 Docker 引擎初探 ​

先看看这三套 Docker 环境分别跑了什么,从 ps 看起:

docker ps ​
CONTAINER ID   IMAGE                                                                              COMMAND                  CREATED      STATUS                PORTS     NAMES
1838d4f379e5   registry.lazycat.cloud/lzc/lzcsys:latest                                           "/sspk/bin/pd-service"   9 days ago   Up 9 days                       lzc-runtime-peripheral-device-1
c2f6d791181b   registry.lazycat.cloud/lzc/lzcsys:latest                                           "/sspk/bin/lzc-ingre…"   9 days ago   Up 9 days (healthy)             lzc-runtime-ingress-control-1
9699c428d2b0   registry.lazycat.cloud/dexidp/dex:v2.42.0-alpine                                   "/usr/local/bin/dock…"   9 days ago   Up 9 days                       lzc-runtime-dex
57952c3e4ba5   registry.lazycat.cloud/lzc/lzcsys:latest                                           "/sspk/bin/lzc-apise…"   9 days ago   Up 9 days (healthy)             lzc-runtime-api-servers-1
cde0eba62fd2   registry.lazycat.cloud/lzc/lzcsys:latest                                           "/sspk/bin/lzc-pkgm"     9 days ago   Up 9 days (healthy)             lzc-runtime-pkgm-1
8e9c780c012c   registry.corp.lazycat.cloud/homecloud/lzc-registry-proxy:v0.0.0-2887-gd16c7f25.m   "/bin/sh -c /lzc-reg…"   9 days ago   Up 9 days             80/tcp    lzc-registry-proxy
59d3803ef304   registry.corp.lazycat.cloud/homecloud/lzc-installer:v0.0.0-2887-gd16c7f25.m        "/docker-entrypoint.…"   9 days ago   Up 9 days                       lzc-installer
c7192a7fd471   registry.corp.lazycat.cloud/homecloud/lzc-hal:v0.0.0-2887-gd16c7f25.m              "/bin/sh -c /sspk/bi…"   9 days ago   Up 9 days                       lzc-hal
1d194e975117   registry.corp.lazycat.cloud/homecloud/lzc-recovery:v0.0.0-2887-gd16c7f25.m         "/docker-entrypoint.…"   9 days ago   Up 9 days                       lzc-recovery
8338ce6a5c17   registry.corp.lazycat.cloud/homecloud/lzc-recovery:v0.0.0-2887-gd16c7f25.m         "/sspk/bin/entrypoin…"   9 days ago   Up 9 days

这里可以看到,系统级组件都跑在默认的 docker 下。

pg-docker ps ​
CONTAINER ID   IMAGE                                                                     COMMAND                  CREATED      STATUS        PORTS                                     NAMES
d0ae10b8fc8f   registry.lazycat.cloud/u04123229/qilinzhu/ql-play:fbf2e99a00ef9a7f        "sh /app/start.sh"       3 days ago   Up 26 hours                                             ql-play
0cb9ec655c16   registry.lazycat.cloud/u04123229/cloudsmithy/shuangpin:2a8ede2b23c38be8   "/docker-entrypoint.…"   6 days ago   Up 6 days     0.0.0.0:5004->80/tcp, [::]:5004->80/tcp   unruffled_lichterman

pg-docker 实际上就是日常部署、测试容器最常用的那套运行时环境,Dockge 默认连接的也是这里。它为了和系统 Docker 区分而改了名字,Playground 就是随便玩的意思。

image-20250520102656467

lzc-docker ps ​
lzc-docker ps
CONTAINER ID   IMAGE                                                                                                                              COMMAND                  CREATED        STATUS                  PORTS                                         NAMES
80c88ae6aa8b   registry.lazycat.cloud/app-tv-controller:1.0                                                                                       "/lzcinit/cloud.lazy…"   16 hours ago   Up 16 hours (healthy)                                                 cloudlazycatapplzctvcontroller-app-1
fdb2211b210e   registry.lazycat.cloud/lzc/tvos-release:v0.1.219                                                                                   "/home/tvos/run.sh"      16 hours ago   Up 16 hours             5500/tcp                                      cloudlazycatapplzctvcontroller-tvos-1

这个是懒猫商店的 Docker 环境。实测在客户端中停止应用时,会删除对应的容器;无论执行 docker ps -a | grep auth,还是通过可视化工具查看,结果都一样。这也符合容器的使用习惯:不需要时删除,使用时重新启动,但持久化数据仍然保留。

版本和运行时对比 ​

我们再来看一下版本,都还是一样的。所以这个就很有意思了。

bash
lzcbox-029c588e ~ # docker --version
Docker version 27.5.1, build 9f9e405
lzcbox-029c588e ~ # pg-docker --version
Docker version 27.5.1, build 9f9e405
lzcbox-029c588e ~ # lzc-docker --version
Docker version 27.5.1, build 9f9e405

再看看存储后端,有没有什么特殊改动?可以看出后端都是 containerd。

lzcbox-029c588e ~ # docker info | grep -i 'Runtimes\|Default Runtime'
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
lzcbox-029c588e ~ # pg-docker info | grep -i 'Runtimes\|Default Runtime'
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
lzcbox-029c588e ~ # lzc-docker info | grep -i 'Runtimes\|Default Runtime'
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc

甚至连 containerd 的版本都一样。

# docker info | grep -i "containerd"
 Runtimes: io.containerd.runc.v2 runc
 containerd version: bcc810d6b9066471b0b6fa75f557a15a1cbf31bb
lzcbox-029c588e ~ # pg-docker info | grep -i "containerd"
 Runtimes: io.containerd.runc.v2 runc
 containerd version: bcc810d6b9066471b0b6fa75f557a15a1cbf31bb
lzcbox-029c588e ~ # lzc-docker info | grep -i "containerd"
 Runtimes: io.containerd.runc.v2 runc
 containerd version: bcc810d6b9066471b0b6fa75f557a15a1cbf31bb
lzcbox-029c588e ~ #

一开始以为这里做了特殊修改,又看了下我的 Mac 上 OrbStack 的配置,好像也没什么差别。

docker info | grep -i 'Runtimes\|Default Runtime'
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc


docker info | grep -i "containerd"


 Runtimes: io.containerd.runc.v2 runc
 containerd version: 06b99ca80cdbfbc6cc8bd567021738c9af2b36ce

多引擎共存的实现方式 ​

DOCKER_HOST 的封装 ​

既然三个 Docker 客户端都出奇地一致,难道是通过类似命名空间的方式隔离吗?

lzcbox-029c588e ~ # which docker
/usr/bin/docker
lzcbox-029c588e ~ # which pg-docker
/lzcsys/bin/pg-docker
lzcbox-029c588e ~ # which lzc-docker
/lzcsys/bin/lzc-docker
lzcbox-029c588e ~ # file $(which docker)
/usr/bin/docker: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4a41bb12cfd0c306a6ede40f41cfc107b2045371, for GNU/Linux 3.2.0, with debug_info, not stripped
lzcbox-029c588e ~ # file $(which pg-docker)
/lzcsys/bin/pg-docker: Bourne-Again shell script, ASCII text executable
lzcbox-029c588e ~ # file $(which lzc-docker)
/lzcsys/bin/lzc-docker: Bourne-Again shell script, ASCII text executable

这就能发现问题了:docker 是原本的 Docker 客户端,而 pg-docker 和 lzc-docker 是封装脚本。来看一下:

所以可以得出一个关键点:懒猫并不是运行了三套完全独立的 Docker 服务,而是通过 shell 脚本封装,复用同一个 docker 客户端,切换不同的 socket 实现了“环境隔离”。这个脚本的作用相当于把 pg-docker 当成 docker 命令使用,还自动附带了环境变量 DOCKER_HOST=...。

bash
lzcbox-029c588e ~ # cat $(which pg-docker)
#!/bin/bash
set -e

export DOCKER_HOST=unix:///data/playground/docker.sock
exec docker "$@"

这设置了 DOCKER_HOST 环境变量,使得之后执行的 docker 命令会连接到 /data/playground/docker.sock 这个 Unix Socket,而不是默认的 /var/run/docker.sock。

exec 是一个 shell 内建命令,它会用新的进程替换当前脚本的进程。

"$@" 表示把脚本接收到的所有参数(比如 pg-docker ps -a)原样传递给 docker 命令。

之前想上架一个 Docker 可视化工具,但一直不知道需要映射哪个 docker.sock。这下全都清楚了:有了这个 Socket,就能在容器里使用宿主机的 Docker API。

daemon.json 配置详解 ​

与之对应的还有 daemon.json。除了用来修改代理之外,我们还能配置这些内容:

bash
lzcbox-029c588e /data/playground/data # cat /lzcsys/var/playground/daemon.json
{
  "bridge": "",
  "containerd-namespace": "playground-docker",
  "containerd-plugins-namespace": "playground-docker",
  "data-root": "/data/playground/data/docker",
  "default-address-pools": [

  ],
  "exec-root": "/data/playground/docker",
  "hosts": [
    "unix:///data/playground/docker.sock"
  ],
  "insecure-registries": [
    "registry.lazycat.cloud"
  ],
  "pidfile": "/data/playground/docker.pid"

这样多个 Docker 环境就能共存了,例如:

  • 系统默认的 /var/run/docker.sock
  • 一个沙箱环境 /data/playground/docker.sock

这么设置好之后可以快速切换上下文,而不用每次都手动设置 DOCKER_HOST。

在我开发的容器可视化面板中,已经可以指定 Docker Socket 作为连接地址,参考:

yml
services:
  containly:
    image: registry.lazycat.cloud/u04123229/cloudsmithy/containly:30e4e3279afe9a52
    ports:
      - 5003:5000
    volumes:
      - /data/playground/docker.sock:/var/run/docker.sock
    restart: unless-stopped

image-20250520104141112

三套 daemon.json 对比分析 ​

从实际查找结果来看,懒猫为三套 Docker 引擎配置了不同的 daemon.json 文件和运行时环境:

  • 系统组件专用(docker):/etc/docker/daemon.json
  • 用户 playground 环境(pg-docker):/lzcsys/var/playground/daemon.json
  • 懒猫商店环境(lzc-docker):/lzcsys/etc/docker/daemon.json
sudo find / -type f -name daemon.json 2>/dev/null

/etc/docker/daemon.json
/run/lzcsys/boot/lzc-os-init/var/playground/daemon.json
/run/lzcsys/boot/lzc-os-overlay/lowerdir/lzcsys/etc/docker/daemon.json
/run/lzcsys/boot/lzc-os-overlay/lowerdir/lzcsys/lzcsys/etc/docker/daemon.json
/lzcsys/etc/docker/daemon.json
/lzcsys/var/playground/daemon.json

每个配置文件中都指定了独立的:

  • data-root
  • exec-root
  • pidfile
  • hosts(即 sock 文件路径)
bash
# 默认的 Docker 引擎
lzcbox-029c588e ~ # cat /etc/docker/daemon.json
{
  "registry-mirrors": [
  ],
  "insecure-registries": [
    "registry.lazycat.cloud"
  ],
  "log-driver": "journald",
  "cgroup-parent": "sys_docker.slice"
}

# 商店的 Docker 引擎
lzcbox-029c588e ~ # cat /lzcsys/etc/docker/daemon.json
{
  "bridge": "none",
  "insecure-registries": [
    "registry.lazycat.cloud"
  ],
  "default-address-pools": [
  ],
  "ipv6": true,
  "hosts": [
    "unix:///lzcsys/run/lzc-docker/docker.sock"
  ],
  "containerd-namespace": "lzc-docker",
  "containerd-plugins-namespace": "lzc-docker-plugins",
  "exec-root": "/lzcsys/run/lzc-docker/docker",
  "pidfile": "/lzcsys/run/lzc-docker/docker.pid",
  "data-root": "/lzcsys/run/data/system/docker",
  "cgroup-parent": "lzc_docker.slice"
}

# Playground 的 Docker 引擎
cat /lzcsys/var/playground/daemon.json
{
  "bridge": "",
  "containerd-namespace": "playground-docker",
  "containerd-plugins-namespace": "playground-docker",
  "data-root": "/data/playground/data/docker",
  "default-address-pools": [],
  "exec-root": "/data/playground/docker",
  "hosts": [
    "unix:///data/playground/docker.sock"
  ],
  "insecure-registries": [
    "registry.lazycat.cloud"
  ],
  "pidfile": "/data/playground/docker.pid"

小结 ​

懒猫微服总会给我惊喜。除了极客风格的外壳和性能突出的硬件,里面的软件设计也同样有意思,这套设计让我对 Docker 有了更深的认识。

三套 Docker 环境共存不是表面上的改名,而是通过 containerd 的 namespace 配合脚本封装,在容器之上再抽象一层运行时,把 Playground、系统和商店隔离成三界,却又共用一套内核,很好玩。


忘机山人二维码

扫码关注「忘机山人」

❤️喜欢