在 Docker for Mac 上使文件不可变(immutable)后容器无法删除的 workaround
昨天,学长 @zzh1996 发现在 Docker for Mac 的容器中对文件 chattr +i
之后,容器就无法被正常删除。我在自己的电脑上复现了这个问题,并且,嗯,最后成功删掉了。在这里记录一下这个 workaround。
(PS:Linux 上的 Docker 未见此问题)
复现问题
版本:Docker Desktop 2.0.0.2 (30215),Engine: 18.09.1
跑一个 Ubuntu 的容器。设置 --privileged=true
以使 chattr +i
成功执行。
docker container run --privileged=true -it ubuntu bash
创建 immutable 的文件。
root@1865cedf0ef0:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@1865cedf0ef0:/# touch test
root@1865cedf0ef0:/# chattr +i test # now `test` is immutable
root@1865cedf0ef0:/# rm test # cannot remove when you're root
rm: cannot remove 'test': Operation not permitted
root@1865cedf0ef0:/# rm -f test # even `-f` won't work
rm: cannot remove 'test': Operation not permitted
root@1865cedf0ef0:/# exit
在退出之后看一下现有的容器。
(base) ➜ ~ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1865cedf0ef0 ubuntu "bash" 2 minutes ago Exited (1) 2 minutes ago ecstatic_hawking
# 省略其他容器
然后……删不掉。
(base) ➜ ~ docker container rm 186
Error response from daemon: container 1865cedf0ef0e58303a0361b8e19cd3a882a1bc741990603fb446e153656589f: driver "overlay2" failed to remove root filesystem: remove /var/lib/docker/overlay2/e000b8a86824f48cb824ab643ce7d035e7ec4f3114bad897a2235cf922bba972/diff/test: operation not permitted
同样,加上 -f
也没有任何作用。可以注意到,我们创建的文件阻止了容器的正常删除。
Workaround
那么,返回中指向的路径在哪里呢?很明显这不是我们主机上的路径。
由 Docker 的工作原理我们知道,Docker for Mac 需要虚拟一个 Linux 内核才能够在 macOS 上正常运行,而这个最外层的虚拟机就是我们的目标:我们需要在其上把 test
文件删掉。
Docker for Mac 选择的虚拟机环境是 Xhyve VM。根据这条链接的内容,我们可以进入这个虚拟机环境。
screen ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/tty
但是……
linuxkit-025000000001:/var/lib/docker/overlay2/e000b8a86824f48cb824ab643ce7d035e7ec4f3114bad897a2235cf922bba972/diff# ls
test
linuxkit-025000000001:/var/lib/docker/overlay2/e000b8a86824f48cb824ab643ce7d035e7ec4f3114bad897a2235cf922bba972/diff# chattr
-sh: chattr: not found
linuxkit-025000000001:/var/lib/docker/overlay2/e000b8a86824f48cb824ab643ce7d035e7ec4f3114bad897a2235cf922bba972/diff#
这个环境没有 chattr
……虽然有个包管理器 apk
,但是……
linuxkit-025000000001:/var/lib# apk update
ERROR: Unable to lock database: Read-only file system
ERROR: Failed to open apk database: Read-only file system
外层虚拟机的系统部分是只读的,装不了什么东西。虽然缺 chattr
,但是一些基础的东西还是比较齐的——甚至有 wget
。如果去运行的话会发现这些工具都来自于 BusyBox。但是,BusyBox 里面应该是有 chattr
的啊1。
这就尴尬了。
所幸的是,在 BusyBox 的网站上可以直接下载到编译好的二进制文件。
wget https://busybox.net/downloads/binaries/1.30.0-i686/busybox_CHATTR
然后,
linuxkit-025000000001:/var/lib/docker/overlay2/e000b8a86824f48cb824ab643ce7d035e7ec4f3114bad897a2235cf922bba972/diff# chmod +x ./busybox_CHATTR
linuxkit-025000000001:/var/lib/docker/overlay2/e000b8a86824f48cb824ab643ce7d035e7ec4f3114bad897a2235cf922bba972/diff# ./busybox_CHATTR -i test
linuxkit-025000000001:/var/lib/docker/overlay2/e000b8a86824f48cb824ab643ce7d035e7ec4f3114bad897a2235cf922bba972/diff# rm test
之后退出虚拟机环境,再删一次就行了。
附录 1: 能不能直接从其他 Linux 机器上复制一份 chattr
过来?
答案是:(大部分情况下)不行。
实话讲,这是我想到的第一个办法。但最终证明是行不通的。为什么?动态链接。
root@tao-kali:~# uname -a
Linux tao-kali 4.19.0-kali1-amd64 #1 SMP Debian 4.19.13-1kali1 (2019-01-03) x86_64 GNU/Linux
root@tao-kali:~# ldd /usr/bin/chattr
linux-vdso.so.1 (0x00007ffe607c1000)
libe2p.so.2 => /lib/x86_64-linux-gnu/libe2p.so.2 (0x00007ff779d4e000)
libcom_err.so.2 => /lib/x86_64-linux-gnu/libcom_err.so.2 (0x00007ff779d48000)
libblkid.so.1 => /lib/x86_64-linux-gnu/libblkid.so.1 (0x00007ff779cf3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff779b32000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007ff779b11000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff779d86000)
libuuid.so.1 => /lib/x86_64-linux-gnu/libuuid.so.1 (0x00007ff779b08000)
可以注意到,这份 chattr
依赖于 7 个动态链接库。而这个精简的虚拟环境很难满足这些需求。
而下载得到的 chattr
是静态链接的。
(base) ➜ Downloads file busybox_CHATTR
busybox_CHATTR: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped
所以说……用于 rescue 的程序最好还是静态链接。
附录 2: chattr +i
到底做了什么?
简单地说,”immutable” 等实际是文件系统提供的一种特性。所以要对不支持此特性的文件系统(比如 FAT32)上的文件运行 chattr
是行不通的。
从 BusyBox 的源码中可以找到 chattr.c
,该文件属于 e2fsprogs
。分析代码可以发现在解码完参数之后,调用了宏 fsetflags(name, flags)
(实际指向函数 fgetsetflags(name, NULL, flags)
),通过 ioctl
对文件的 flags
进行了修改。
目前,Docker 使用 OverlayFS 文件系统。它是一种 union filesystem,可以让不同文件系统的文件挂载在同一个挂载点上。Docker 以这种方式实现分层镜像2。而在虚拟环境中 df -hT
可以看到,/dev/sda1
(虚拟磁盘)是以 ext4
挂在 /var/lib
上的。
Comments