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

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

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

懒猫微服这个 docker 还不太一样,一个有三个 Docker:

docker : 运行系统组件

pg-docker: 普通的 docker,让我们拿来玩

lzc-docker:运行懒猫商店的 docker

三套 Docker 引擎初探

我们先来看看这三套 docker 引擎跑了些什么,从 ps 看起:

docker ps
1
2
3
4
5
6
7
8
9
10
11
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
1
2
3
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
1
2
3
4
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 删除了,无论是从docker ps -a | grep auth还是可视化工具看来。这也很符合使用容器的习惯,不需要的时候就删除,随用随启动,但是数据仍然还在。

版本和运行时对比

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

1
2
3
4
5
6
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。

1
2
3
4
5
6
7
8
9
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 的版本都一样

1
2
3
4
5
6
7
8
9
10
# 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 的配置,好像也没啥差别。

1
2
3
4
5
6
7
8
9
10
11
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 都出奇的一致,到底是类似命名空间的隔离嘛?

1
2
3
4
5
6
7
8
9
10
11
12
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=...

1
2
3
4
5
6
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,这下子全都清楚了,有了这个就能在 docker 里使用宿主机的 Docker API 了。

daemon.json 配置详解

当然与之对应的还有 daemon.json,除了用来改代理之外,我们还能修改这些东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 sock 作为连接了,参考:

1
2
3
4
5
6
7
8
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
1
2
3
4
5
6
7
8
9
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 文件路径)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 默认的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、系统和商店隔离成三界,却又共用一套内核,很好玩。

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

https://xu-hardy.github.io/懒猫微服进阶心得(二):一台机器跑三套-docker?深入解析懒猫容器的共存机制(上)/

作者

Xu

发布于

2025-07-02

更新于

2025-07-01

许可协议

评论