容器目录挂载原理

前言

就我目前的对容器的了解, 使用namespace技术实现隔离, 使用cgroups技术实现资源限制. 但是具体是如何实现却从未深究过.

闲来无事, 挑其中的Mount Namespace来康康, 容器是如何实现目录隔离的.

目录隔离

耗子叔的这篇文章中对此技术进行了介绍.

c函数库中, 可通过如下方式实现目录的隔离:

int container_main(void* arg)
{
 // 调用 mount 方法, 触发目录隔离机制. 将根目录替换为 /root/tmp
 mount("/root/tmp", "/", "tmpfs", 0, "");
 // dosomething
 return 1;
}

void main(){
  // 调用 clone 创建子进程
    // 传递 CLONE_NEWNS 标识, 标明需要创建目录隔离
  clone(container_function, stack, CLONE_NEWNS | SIGCHLD , NULL)
}

如果想在命令行中测试目录隔离, 也可以如此操作:


# 创建用于挂载的临时目录
mkdir -p mount/bin
mkdir -p mount/lib64
mkdir -p mount/lib
# 将执行文件放进去
cp /bin/ls  mount/bin
# 将依赖的链接库放入 (依赖库可通过命令 ldd /bin/ls 查看)
cp /lib/x86_64-linux-gnu/libselinux.so.1 mount/lib
cp /lib/x86_64-linux-gnu/libc.so.6 mount/lib/
cp /lib/x86_64-linux-gnu/libpcre.so.3 mount/lib/
cp /lib/x86_64-linux-gnu/libdl.so.2 mount/lib/
cp /lib64/ld-linux-x86-64.so.2 mount/lib64/
cp /lib/x86_64-linux-gnu/libpthread.so.0 mount/lib/
# 替换运行进程的根目录
# 执行此命令时, chroot 命令会将 ls 命令的运行根目录替换为 ./mount 目录
# 可以尝试着执行 /bin/ls 命令查看
chroot ./mount /bin/ls

至此, 虽然举的例子很简单, 但依然足够我们理解目录隔离了. 容器启动后, 会将整个进程的根目录换掉, 甚至直接挂载整个操作系统的ISO. 这也就解释了为什么容器只是一个运行在宿主机上的进程, 却可以表现为不同的操作系统.

docker中, 容器和镜像的文件系统目录, 保存在宿主机的/var/lib/docker/overlay2.

你可以通过命令docker inspect <container_id>来查看容器的层级关系.

至于docker是如何将镜像的多层进行聚合, 最终展现给容器的, 简单说是通过UnionFS 技术, 将多个目录挂载到同一个目录下, 且可以设置优先级. 合并后的目录就是inspect命令结果中的MergedDir

exec

目录隔离使得容器启动后文件系统自成一派. 那么执行exec命令进入容器时, 又是如何做到的呢?

那必然是系统已经支持的啦. 在/proc/<pid>/ns目录下, 记录了命令空间隔离的数据.

image-20230520175409411

其中的mnt记录的就是目录隔离. 可以使用命令行nsenter -t <pid> -m来进入到特定进程的目录命名空间. (-m 表示要进入 mount namespace). 命令执行后, 再执行ls命令, 就会发现已经进入到容器的文件系统中了.

c的函数库中, 则使用setns函数来实现此功能. 函数具体使用不做赘述.


至此, 容器是如何做到目录隔离的, 有了一个大致模糊的印象. 收工.

订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请发表评论。x