理解Docker(3):Docker 容器使用 Linux namespace 进行运行环境隔离

本系列文章将介绍Docker的有关知识:

(1)Docker 安装及基本用法

(2)Docker 镜像

(3)Docker 容器的隔离性 - 使用 namespace 进行环境隔离

(4)Docker 容器的隔离性 - 使用 cgroups 进行资源隔离

(4)Docker 容器的网络

(5)Docker 容器的存储

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 进程.

[email protected]:/home/sammy# ps -ef | grep python
root 2198 2179 0 00:06 ? 00:00:00 python app.py

[email protected]:/home/sammy# ps -ef | grep 2179
root 2179 765 0 00:06 ? 00:00:00 docker-containerd-shim 8b7dd09fbcae00373207f01e2acde45740871c9e3b98286b5458b4ea09f41b3e /var/run/docker/libcontainerd/8b7dd09fbcae00373207f01e2acde45740871c9e3b98286b5458b4ea09f41b3e docker-runc
root 2198 2179 0 00:06 ? 00:00:00 python app.py
root 2249 1692 0 00:06 pts/0 00:00:00 grep --color=auto 2179

[email protected]:/home/sammy# docker exec -it web31 ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 16:06 ? 00:00:00 python app.py

关于 containerd,containerd-shim 和 container 的关系,文章 中的下图可以说明:

简单来说,就是 containerd (Docker 守护进程)启动 containerd-shim,它再启动 runc,runc 再启动容器,在容器被创建后 runc 退出,shim 就作为容器的父进程。通过 shim 能实现容器的 deamonless,也就是不依赖于 docker deamon,也就是说在 docker deamon 死区后容器可以照常运行。[注:此部分待进一步研究,描述不一定正确]

这也能看出来,pid namespace 通过将 host 上 PID 映射为容器内的 PID, 使得容器内的进程看起来有个独立的 PID 空间。

2.2 UTS namespace

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

[email protected]:/home/sammy# hostname
devstack
[email protected]:/home/sammy# docker exec -it web31 hostname
8b7dd09fbcae

2.3 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 目录做任意修改:
[email protected]:/home/sammy# docker exec -ti web34 id
uid=0(root) gid=0(root) groups=0(root)
[email protected]:/home/sammy# id
uid=0(root) gid=0(root) groups=0(root)

而 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. 查看进程在容器内外的用户:
[email protected]:/home/sammy# ps -ef | grep python
231072    1726  1686  0 01:44 ?        00:00:00 python app.py

[email protected]:/home/sammy# 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:
[email protected]:/home/sammy# cat /etc/subuid
sammy:100000:65536
stack:165536:65536
dockremap:231072:65536

[email protected]:/home/sammy# cat /etc/subgid
sammy:100000:65536
stack:165536:65536
dockremap:231072:65536

  • 再看文件/proc/1726/uid_map,它表示了容器内外用户的映射关系,即将host 上的 231072 用户映射为容器内的 0 (即root)用户。
[email protected]:/home/sammy# cat /proc/1726/uid_map
         0     231072      65536
  • 现在,我们试图在容器内修改 host 上的 /bin 文件夹,就会提示权限不足了:
[email protected]:/host/bin# touch test2
touch: cannot touch ‘test2‘: Permission denied

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

其他的几个 namespace,比如 network,mnt 等,比较简单,这里就不多说了。总之,Docker 守护进程为每个容器都创建了六种namespace 的实例,使得容器中的进程都处于一种隔离的运行环境之中:

[email protected]:/proc/1726/ns# ls -l
total 0
lrwxrwxrwx 1 231072 231072 0 Sep 18 01:45 ipc -> ipc:[4026532210]
lrwxrwxrwx 1 231072 231072 0 Sep 18 01:45 mnt -> mnt:[4026532208]
lrwxrwxrwx 1 231072 231072 0 Sep 18 01:44 net -> net:[4026532213]
lrwxrwxrwx 1 231072 231072 0 Sep 18 01:45 pid -> pid:[4026532211]
lrwxrwxrwx 1 231072 231072 0 Sep 18 01:45 user -> user:[4026532207]
lrwxrwxrwx 1 231072 231072 0 Sep 18 01:45 uts -> uts:[4026532209]

3. Docker run 命令中 namespace 中相关参数

Docker run 命令有几个参数和 namespace 相关:

  • --ipc string IPC namespace to use
  • --pid string PID namespace to use
  • --userns string User namespace to use
  • --uts string UTS namespace to use

3.1 --userns

--userns:指定容器使用的 user namespace

  • ‘host‘: 使用 Docker host user namespace
  • ‘‘: 使用由 `--userns-remap‘ 指定的 Docker deamon user namespace

你可以在启用了 user namespace 的情况下,强制某个容器运行在 host user namespace 之中:

[email protected]:/proc/2835# docker run -d -v /bin:/host/bin --name web37 --userns host training/webapp python app.py
9c61e9a233abef7badefa364b683123742420c58d7a06520f14b26a547a9476c
[email protected]:/proc/2835# ps -ef | grep python
root      2962  2930  1 02:17 ?        00:00:00 python app.py

否则默认的话,就会运行在特定的 user namespace 之中了。

3.2 --pid

同样的,可以指定容器使用 Docker host pid namespace,这样,在容器中的进程,可以看到 host 上的所有进程。注意此时不能启用 user namespace。

[email protected]:/proc/2962# docker run -d -v /bin:/host/bin --name web38 --pid host --userns host training/webapp python app.py
f40f6702b61e3028a6708cdd7b167474ddf2a98e95b6793a1326811fc4aa161d
[email protected]:/proc/2962#
[email protected]:/proc/2962# docker exec -it web38 bash
[email protected]:/opt/webapp# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1  33480  2768 ?        Ss   17:40   0:01 /sbin/init
root         2  0.0  0.0      0     0 ?        S    17:40   0:00 [kthreadd]
root         3  0.0  0.0      0     0 ?        S    17:40   0:00 [ksoftirqd/0]
root         5  0.0  0.0      0     0 ?        S<   17:40   0:00 [kworker/0:0H]
root         6  0.0  0.0      0     0 ?        S    17:40   0:00 [kworker/u2:0]
root         7  0.0  0.0      0     0 ?        S    17:40   0:00 [rcu_sched]......

3.3 --uts

同样地,可以使容器使用 Docker host uts namespace。此时,最明显的是,容器的 hostname 和 Docker hostname 是相同的。

[email protected]:/proc/2962# docker run -d -v /bin:/host/bin --name web39 --uts host training/webapp python app.py
38e8b812e7020106bf8d3952b88085028fc87f4427af0c3b0a29b6a69c979221
[email protected]:/proc/2962# docker exec -it web39 bash
[email protected]:/opt/webapp# hostname
devstack

参考链接

时间: 2024-10-31 17:42:40

理解Docker(3):Docker 容器使用 Linux namespace 进行运行环境隔离的相关文章

《自己动手写Docker》书摘之一: Linux Namespace

Linux Namespace 介绍 我们经常听到说Docker 是一个使用了Linux Namespace 和 Cgroups 的虚拟化工具,但是什么是Linux Namespace 它在Docker内是怎么被使用的,说到这里很多人就会迷茫,下面我们就先介绍一下Linux Namespace 以及它们是如何在容器里面使用的. 概念 Linux Namespace 是kernel 的一个功能,它可以隔离一系列系统的资源,比如PID(Process ID),User ID, Network等等.一

理解Docker(3):Docker 使用 Linux namespace 隔离容器的运行环境

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 th

Docker之Linux Namespace

Linux Namespace 介绍 我们经常听到说Docker 是一个使用了Linux Namespace 和 Cgroups 的虚拟化工具,但是什么是Linux Namespace 它在Docker内是怎么被使用的,说到这里很多人就会迷茫,下面我们就先介绍一下Linux Namespace 以及它们是如何在容器里面使用的. 概念 Linux Namespace 是kernel 的一个功能,它可以隔离一系列系统的资源,比如PID(Process ID),User ID, Network等等.一

Docker深入浅出系列 | 容器初体验

Docker深入浅出系列 | 容器初体验 教程目标 Docker已经上市很多年,不是什么新鲜事物了,很多企业或者开发同学以前也不多不少有所接触,但是有实操经验的人不多,本系列教程主要偏重实战,尽量讲干货,会根据本人理解去做阐述,具体官方概念可以查阅官方文档,本章目标如下: 了解什么是Docker 了解Docker解决了什么 了解什么是镜像和容器 了解容器与虚拟机的区别 了解Vagrant与Docker的区别 了解Docker引擎和架构 了解Docker的镜像分层 了解VirturalBox和Do

Docker基础技术:Linux Namespace(上)

导读 时下最热的技术莫过于Docker了,很多人都觉得Docker是个新技术,其实不然,Docker除了其编程语言用go比较新外,其实它还真不是个新东西,也就是个新瓶装旧酒的东西,所谓的The New “Old Stuff”.Docker和Docker衍生的东西用到了很多很酷的技术,我会用几篇 文章来把这些技术给大家做个介绍,希望通过这些文章大家可以自己打造一个山寨版的docker.先从Linux Namespace开始. 简介 Linux Namespace是Linux提供的一种内核级别环境隔

Docker 基础技术:Linux Namespace(下)

导读 在Docker基础技术:Linux Namespace(上篇)中我们了解了,UTD.IPC.PID.Mount 四个namespace,我们模仿Docker做了一个相当相当山寨的镜像.在这一篇中,主要想向大家介绍Linux的User和Network的Namespace User Namespace User Namespace主要是用了CLONE_NEWUSER的参数,使用了这个参数后,内部看到的UID和GID已经与外部不同了.默认情况下容器没有的UID,系统自动设置上了最大的UID655

Docker基础技术:Linux Namespace(下)

在 Docker基础技术:Linux Namespace(上篇)中我们了解了,UTD.IPC.PID.Mount 四个namespace,我们模仿Docker做了一个相当相当山寨的镜像.在这一篇中,主要想向大家介绍Linux的User和Network的Namespace. 好,下面我们就介绍一下还剩下的这两个Namespace. User Namespace User Namespace主要是用了CLONE_NEWUSER的参数.使用了这个参数后,内部看到的UID和GID已经与外部不同了,默认显

Docker 基础技术之 Linux namespace 详解

Docker 是"新瓶装旧酒"的产物,依赖于 Linux 内核技术 chroot .namespace 和 cgroup.本篇先来看 namespace 技术. Docker 和虚拟机技术一样,从操作系统级上实现了资源的隔离,它本质上是宿主机上的进程(容器进程),所以资源隔离主要就是指进程资源的隔离.实现资源隔离的核心技术就是 Linux namespace.这技术和很多语言的命名空间的设计思想是一致的(如 C++ 的 namespace). 隔离意味着可以抽象出多个轻量级的内核(容器

Linux下正确修改Docker镜像和容器的默认存储位置,亲测有效

原文:Linux下正确修改Docker镜像和容器的默认存储位置,亲测有效 我们通过 yum 的方式安装完Docker环境后,它默认的存储位置是 /var/lib/docker,默认的 pid 存放位置是 /var/run/docker.pid. 如果仅仅是做测试,我们可能没有必要修改,但是当大量使用docker镜像的时候,我们可能就要默认存储的位置了. 具体操作方法如下: 1.停止docker: service docker stop 2.修改docker服务的service文件: vim /u