欢迎回来
登录你的知识库账户
忘记密码?
还没有账户?立即注册
创建账户
注册你的专属知识库
已有账户?去登录
找回密码
输入注册邮箱获取验证码
返回登录
请输入图片中的验证码以继续注册
加载中...
取消
新建收藏
手动添加你喜欢的内容
取消
编辑头像与昵称
上传新头像或修改你的显示昵称
支持 JPG/PNG,最大 2MB
取消

问题反馈

notebasewww.notebase.cn
控制台
内容库
动态
管理
账户
U
用户
--
在线
v0.8.7 · 知识库
笔记
KnowledgeBase
网络无边,知识有迹。
0笔记
0工具
30推荐

分类导航

按主题直达

编辑精选

站内用户贡献 · 真实笔记

最新收录

每日更新
继续浏览全部内容 →
>
笔记
0
加载中...
工具
0
此页用于记录用户反馈问题后的每一次改进
笔记用法

“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。

md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。

要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。

工具用法

在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。

要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。

工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。

隐藏笔记

写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。

适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。

数据安全

你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。

技术

全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。

理念

这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。

原则

不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。

更多

产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。

举报

如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。

趋势
// 点击导航加载发现
归档
// 归档为空
最近浏览
// 暂无浏览记录
发布
// 加载中...
用户发布
// 加载中...
用户管理
// 加载中...
访问统计
// 加载中...
内容审核
// 加载中...
个人信息
// 加载中...
返回首页

我以为我懂容器,直到我亲手造了一个

2026/7/4操作系统

写在前面

这事儿得从一次翻车说起。

我刚刚高分通过了导师的 Docker 考试,那会儿我觉得自己已经彻底搞懂了容器——命名空间、cgroups、镜像层、PID 1、Kubernetes Pods,这些名词我张口就来。然后我敲下了第一个正经命令,Linux 就给我上了一课:知道名词和亲手造出来是两码事。

$ sudo unshare -p 1 test
unshare: failed to execute 1: No such file or directory

看到没,我连东西都还没开始造呢。只是把参数写错了,unshare 以为我要执行一个叫 1 的程序。这趟旅程注定不是“实现 Docker”,而是“让内核一个错误一个错误地修正我的自信”。


v1:命名空间,或者说 PID 1 第一次骗了我

第一个版本按理说应该很简单:在一个新的 PID 命名空间里跑一个进程,然后证明它把自己当成 PID 1。于是我按照自以为正确的方式敲了命令:

$ sudo unshare --pid bash
# echo $$
25184

这不是 PID 1。这简直丢人。我漏掉的关键规则其实很简单:PID 命名空间只对子进程生效。调用 unshare --pid 的进程并不会魔法般地变成 PID 1。你需要 fork。第一个诞生在新命名空间里的子进程才是 PID 1。

正确版本是这样的:

$ sudo unshare --pid --fork bash
# echo $$
1

就这一行命令,整个氛围都不一样了。我进入了一个不同的进程宇宙。shell 以为自己就是进程 1。信号的感觉变了。孤儿进程会来找它。

然后我运行了 ps,又被打回了原形:

# ps -o pid,ppid,comm
  PID  PPID  COMMAND
25310 25304 bash
25344 25310 ps

一开始我完全理解不了。我明明已经是 PID 1 了,但 ps 显示的却是宿主机的 PID。接下来的发现让我恍然大悟:ps 并不是向内核问一个纯粹的“有哪些进程存在?”问题,它读的是文件。如果 /proc 仍然指向宿主机的 procfs,你的工具就会告诉你宿主机上的故事。

所以我在命名空间内部重新挂载了 /proc:

# mount -t proc proc /proc
# ps -o pid,ppid,comm
  PID  PPID  COMMAND
    1     0 bash
    7     1 ps

那一刻我彻底明白了。命名空间在我的眼中变得“真实”,是在 /proc 和它达成一致之后。在那之前,我有隔离,但我的工具读的是旧的文件系统视图。

UTS 命名空间的意外实验

UTS 命名空间的教训就清爽多了。我无意中做了一次科学实验。在一个终端里,没有 UTS 命名空间:

$ hostname
ba149abae9bd

然后在新的 UTS 命名空间内部:

$ sudo unshare --uts bash
# hostname toybox
# hostname
toybox

回到 UTS 命名空间外面:

$ hostname
ba149abae9bd

这就是我的对照组和实验组。同一台机器,同一个内核,不同的主机名视图——而且“宿主机”本身已经是容器的主机名,这让容器嵌套容器的设置在输出里一目了然。没什么神秘的。只是一个隔离的内核数据结构在按照文档说的那样工作,只不过这次是我亲手验证的。


v2:pivot_root,真正的 Boss 战

命名空间搞定之后,我又开始过度自信了。下一个版本的目标是给进程一个自己的文件系统:一个微小的 rootfs、一个 shell、也许再加个 BusyBox。非常“容器范儿”。我的仓库里用的是 bash 脚本,不是从某个教程里抄来的编译好的运行时。

所以这次尝试的形态是 v2.sh、一个 rootfs、以及要在里面运行的命令。然后开局就是经典错误:

$ sudo ./v2.sh ./rootfs /bin/sh
exec /bin/sh: no such file or directory

行吧,在我说的位置没有 shell。我用 Alpine 自带的 BusyBox 修好了这个问题,然后撞上了更烦人的版本:文件存在,但内核仍然说无法执行:

$ ./rootfs/bin/busybox sh
bash: ./rootfs/bin/busybox: cannot execute: required file not found

这种错误会让你觉得是针对个人的——你能列出文件,你能看到符号链接,但计算机就是不干。真相来自 file 命令:

$ cd rootfs
$ file bin/busybox
bin/busybox: ELF 64-bit LSB pie executable, ARM aarch64, dynamically linked, interpreter /lib/ld-musl-aarch64.so.1, stripped

二进制文件在,但解释器在旧世界里找不到。Linux 不是在说“你的 BusyBox 文件不存在”,而是在说“从当前位置,我无法加载这个 ELF 需要的解释器”。同样的表面错误,不同的根本原因。

修复方法和我最初想的不一样。Alpine 的 BusyBox 不需要变成静态链接。一旦 Alpine 成为 /,它的 musl 加载器就会出现在 /lib/ld-musl-aarch64.so.1,Alpine 的 /bin/sh 就会正常工作。真正需要帮忙的是切换本身——我的 Ubuntu slim 镜像里甚至没有 pivot_root:

$ pivot_root . put_old
bash: pivot_root: command not found
$ file /bin/busybox
/bin/busybox: ELF 64-bit LSB executable, ARM aarch64, statically linked, stripped
$ /bin/busybox pivot_root . put_old

这才是更好的转折:旧世界无法完成交接,除非借用一个静态工具。busybox-static 不是我替换 Alpine 内部的 shell,它是在切换之前和切换期间都能运行的桥梁。

然后遇到了 Bash 哈希缓存

Alpine 现在是 / 了,但 Bash 仍然记得文件系统切换之前的命令路径。它去 /usr/bin/mount 找 mount,而那个世界刚刚被驱逐了:

/ # mount -t proc proc /proc
bash: /usr/bin/mount: No such file or directory
/ # hash -r
/ # mount -t proc proc /proc

我修好了文件系统,却还在调试 Bash 替我记住的一个旧决定。这种 bug 会让你想出去走走。

然后是 Mac 的问题

我的开发环境不是“普通 Linux 笔记本,本地 ext4 磁盘”。而是 Apple Silicon Mac → 特权 Ubuntu 容器 → 从 macOS 挂载的仓库。这意味着 virtiofs 不管我愿不愿意都参与了进来。症状在 pivot 之后出现,在 Alpine 内部,这让它更奇怪了。像 mount 和 ls 这样的 applet 符号链接在 Mac 共享挂载上会报 Permission denied,而直接调用 BusyBox 却正常工作。文件在,但通过符号链接执行就是不行:

/ # ls
sh: ls : Permission denied
/ # /bin/busybox ls
bin  dev  etc  lib  proc  put_old
/ # mount -t proc proc /proc
sh: mount: Permission denied
/ # /bin/busybox mount -t proc proc /proc

这就是我放弃让共享挂载表现得像普通 Linux 文件系统的地方。我把 rootfs 移到了一个容器原生路径,从那里重新跑了同样的想法。无聊的修复,正确的修复。

pivot_root 自己也有意见

pivot_root: invalid argument

这个错误也不光彩。新的根必须是一个挂载点。旧的根需要有个去处。我必须把新根绑定挂载到自身,创建 oldroot,调用 pivot_root(newroot, oldroot),chdir("/"),然后卸载旧根。

当它终于成功时,回报微小而完美:

/ # cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.24.1
PRETTY_NAME="Alpine Linux v3.24"

那个输出给我的感觉好得不正常。它只是 /etc/os-release,但这意味着进程现在活在我组装好的文件系统里。不是 Docker 魔法。只是挂载、一个 ELF 加载器问题、一个静态的 pivot_root applet、一次根切换,以及比任何干净图表都有用的错误信息。


v3:cgroups,或者说资源限制的真相

(原文在这里中断了,但按照这个系列的发展,v3 应该会深入 cgroups——控制组。cgroups 负责限制容器能使用的资源:CPU、内存、磁盘 I/O 等等。和命名空间不同,cgroups 不是“让你看到什么”,而是“限制你能用什么”。这是一个同样容易踩坑的领域,比如内存限制没生效、cgroup 版本不匹配、或者忘记把进程写进正确的控制组文件里。)


我学到的东西

回顾整个过程,最有价值的不是最终那个能工作的容器,而是那些错误。每个错误都揭示了我对 Linux 内核工作原理的一个误解:

  1. PID 命名空间不会自动让你变成 PID 1——你需要 --fork,让子进程成为新命名空间里的第一个进程。
  2. /proc 只是文件——命名空间隔离了内核视图,但你的工具读的是文件系统。不重新挂载 /proc,你就活在幻觉里。
  3. ELF 加载器是隐形的依赖——一个二进制文件“存在”不等于它能运行。它的解释器(动态链接器)必须在当前文件系统里找得到。
  4. pivot_root 不是随便能用的——新根必须是挂载点,旧根必须有个去处,而且你需要一个静态链接的工具来完成切换。
  5. Bash 会记住命令路径——hash -r 这种小东西在文件系统切换后能救你一命。
  6. 文件系统类型影响执行权限——virtiofs 和普通 Linux 文件系统的行为不一样,尤其是在符号链接执行上。

这些不是你在 Docker 教程里能学到的东西。你得亲手栽跟头,然后爬起来,看看内核到底在抱怨什么。现在我终于可以说:我理解了容器。不是因为我背下了名词,而是因为我亲手造了一个。

编写使用方法
Markdown 格式 · Ctrl+Enter 确定
新建笔记
预览
数据表格
点击单元格编辑 · Tab 移动
A1fx
Sheet1
BIH1H2≡🔗</>
隐私提醒

取消
编辑工具
取消