Docker学习笔记(二)——Docker底层技术

本文详细介绍了Docker如何利用Linux namespace技术实现容器的环境隔离,包括PID、UTS、user、network等namespace的原理和Docker中的应用。此外,文章还探讨了Linux control groups(cgroups)在限制容器资源使用中的作用,以及AUFS和OverlayFS等联合文件系统在Docker存储层的角色。通过对这些核心技术的理解,读者可以更好地掌握Docker的基础工作原理。
摘要由CSDN通过智能技术生成

1. 基础知识:Linux namespace 的概念

    Linux 内核从版本 2.4.19 开始陆续引入了 namespace 的概念。其目的是将某个特定的全局系统资源(global system resource)通过抽象方法使得namespace 中的进程看起来拥有它们自己的隔离的全局系统资源实例(The purpose of each namespace is to wrap a particular global system resource in an abstraction that makes it appear to the processes within the namespace that they have their own isolated instance of the global resource. )。Linux 内核中实现了六种 namespace,按照引入的先后顺序,列表如下:

namespace 引入的相关内核版本 被隔离的全局系统资源 在容器语境下的隔离效果
Mount namespaces Linux 2.4.19 文件系统挂接点 每个容器能看到不同的文件系统层次结构
UTS namespaces Linux 2.6.19 nodename 和 domainname 每个容器可以有自己的 hostname 和 domainame
IPC namespaces Linux 2.6.19 特定的进程间通信资源,包括System V IPC 和  POSIX message queues 每个容器有其自己的 System V IPC 和 POSIX 消息队列文件系统,因此,只有在同一个 IPC namespace 的进程之间才能互相通信
PID namespaces Linux 2.6.24 进程 ID 数字空间 (process ID number space) 每个 PID namespace 中的进程可以有其独立的 PID; 每个容器可以有其 PID 为 1 的root 进程;也使得容器可以在不同的 host 之间迁移,因为 namespace 中的进程 ID 和 host 无关了。这也使得容器中的每个进程有两个PID:容器中的 PID 和 host 上的 PID。
Network namespaces 始于Linux 2.6.24 完成于 Linux 2.6.29 网络相关的系统资源 每个容器用有其独立的网络设备,IP 地址,IP 路由表,/proc/net 目录,端口号等等。这也使得一个 host 上多个容器内的同一个应用都绑定到各自容器的 80 端口上。
User namespaces 始于 Linux 2.6.23 完成于 Linux 3.8) 用户和组 ID 空间  在 user namespace 中的进程的用户和组 ID 可以和在 host 上不同; 每个 container 可以有不同的 user 和 group id;一个 host 上的非特权用户可以成为 user namespace 中的特权用户;

Linux namespace 的概念说简单也简单说复杂也复杂。简单来说,我们只要知道,处于某个 namespace 中的进程,能看到独立的它自己的隔离的某些特定系统资源;复杂来说,可以去看看 Linux 内核中实现 namespace 的原理,网络上也有大量的文档供参考,这里不再赘述。

2. Docker 容器使用 linux namespace 做运行环境隔离

当 Docker 创建一个容器时,它会创建新的以上六种 namespace 的实例,然后把容器中的所有进程放到这些 namespace 之中,使得Docker 容器中的进程只能看到隔离的系统资源。 

2.1 PID namespace

我们能看到同一个进程,在容器内外的 PID 是不同的:

  • 在容器内 PID 是 1,PPID 是 0。
  • 在容器外 PID 是 2198, PPID 是 2179 即 docker-containerd-shim 进程.
  • pid namespace 通过将 host 上 PID 映射为容器内的 PID, 使得容器内的进程看起来有个独立的 PID 空间。

2.2 UTS namespace

类似地,容器可以有自己的 hostname 和 domainname:

root@onfocus:/home/lee# hostname
onfocus
root@devstack:/home/lee# docker exec -it web hostname
8b7dd09fbcae

2.3 user namespace

2.3.1 Linux 内核中的 user namespace

 

老版本中,Linux 内核里面只有一个数据结构负责处理用户和组。内核从3.8 版本开始实现了 user namespace。通过在 clone() 系统调用中使用 CLONE_NEWUSER 标志,一个单独的 user namespace 就会被创建出来。在新的 user namespace 中,有一个虚拟的用户和用户组的集合。这些用户和用户组,从 uid/gid 0 开始,被映射到该 namespace 之外的 非 root 用户。

 

在现在的linux内核中,管理员可以创建成千上万的用户。这些用户可以被映射到每个 user namespace 中。通过使用 user namespace 功能,不同的容器可以有完全不同的 uid 和 gid 数字。容器 A 中的 User 500 可能被映射到容器外的 User 1500,而容器 B 中的 user 500 可能被映射到容器外的用户 2500.

 

为什么需要这么做呢?因为在容器中,提供 root 访问权限有其特殊用途。想象一下,容器 A 中的 root 用户 (uid 0) 被映射到宿主机上的 uid 1000,容器B 中的 root 被映射到 uid 2000.类似网络端口映射,这允许管理员在容器中创建 root 用户,而不需要在宿主机上创建。 

2.3.2 Docker 对 user namespace 的支持

在 Docker 1.10 版本之前,Docker 是不支持 user namespace。也就是说,默认地,容器内的进程的运行用户就是 host 上的 root 用户,这样的话,当 host 上的文件或者目录作为 volume 被映射到容器以后,容器内的进程其实是有 root 的几乎所有权限去修改这些 host 上的目录的,这会有很大的安全问题。

举例:

  • 启动一个容器: docker run -d -v /bin:/host/bin --name web34 training/webapp python app.py
  • 此时进程的用户在容器内和外都是root,它在容器内可以对 host 上的 /bin 目录做任意修改

而 Docker 1.10 中引入的 user namespace 就可以让容器有一个 “假”的  root 用户,它在容器内是 root,它被映射到容器外一个非 root 用户。也就是说,user namespace 实现了 host users 和 container users 之间的映射。

启用步骤:

  1. 修改 /etc/default/docker 文件,添加行  DOCKER_OPTS="--userns-remap=default"
  2. 重启 docker 服务,此时 dockerd 进程为 /usr/bin/dockerd --userns-remap=default --raw-logs
  3. 然后创建一个容器:docker run -d -v /bin:/host/bin --name web35 training/webapp python app.py
  4. 查看进程在容器内外的用户:
root@onfocus:/home/lee# ps -ef | grep python
231072    1726  1686  0 01:44 ?        00:00:00 python app.py

root@onfocus:/home/lee# docker exec web35 ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 17:44 ?        00:00:00 python app.py
  • 查看文件/etc/subuid 和 /etc/subgid,可以看到 dockermap 用户在host 上的 uid 和 gid 都是 231072:

 

root@onfocus:/home/lee# cat /etc/subuid
lee:100000:65536
stack:165536:65536
dockremap:231072:65536

root@onfocus:/home/lee# cat /etc/subgid
lee:100000:65536
stack:165536:65536
dockremap:231072:65536
  • 再看文件/proc/1726/uid_map,它表示了容器内外用户的映射关系,即将host 上的 231072 用户映射为容器内的 0 (即root)用户。
root@onfocus:/home/lee# cat /proc/1726/uid_map
         0     231072      65536
  •  现在,我们试图在容器内修改 host 上的 /bin 文件夹,就会提示权限不足了:
root@80993d821f7b:/host/bin# touch test2
touch: cannot touch 'test2': Permission denied

这说明通过使用 user namespace,使得容器内的进程运行在非 root 用户,我们就成功地限制了容器内进程的权限。

正是/proc/<pid>/uid_map 和 /proc/<pid>/gid_map 这两个文件, 把容器中的uid和真实系统的uid给映射在一起。这两个文件的格式为:

ID-inside-ns ID-outside-ns length 

其中:  

  • 第一个字段ID-inside-ns表示在容器显示的UID或GID,
  • 第二个字段ID-outside-ns表示容器外映射的真实的UID或GID。
  • 第三个字段表示映射的范围,一般填1,表示一一对应。

举个例子, 0 1000 256这条配置就表示父user namespace中的1000~1256映射到新user namespace中的0~256。

 比如,把真实的uid=1000映射成容器内的uid=0:

把namespace内部从0开始的uid映射到外部从0开始的uid,其最大范围是无符号32位整形:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值