01 容器,到底是怎么一回事儿?


容器技术的核心功能

  1. 容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”
  2. 对于 Docker 等大多数 Linux 容器来说,Cgroups 技术是用来制造约束的主要手段,而 Namespace 技术则是用来修改进程视图的主要方法。

进程的静态表现和动态表现

  1. 对于进程来说,它的静态表现就是程序,平常都安安静静地待在磁盘上;
  2. 而一旦运行起来,它就变成了计算机里的数据和状态的总和,这就是它的动态表现。

docker 的边界是如何实现的

1
2
3
4
5
[root@k8s-master1 ~]# docker run -it --rm --name test busybox sh
/ # ps
PID USER TIME COMMAND
1 root 0:00 sh
6 root 0:00 ps
  1. Docker 里最开始执行的 sh 就是这个容器内部的第 1 号进程(PID=1),而这个容器里一共只有两个进程在运行。
  2. 这就意味着,前面执行的 sh,以及我们刚刚执行的 ps,已经被 Docker 隔离在了一个跟宿主机完全不同的世界当中。
  3. 原本 每当我们在宿主机上运行了一个 /bin/sh 程序,操作系统都会给它分配一个进程编号,比如 PID=100
  4. 这个编号是进程的唯一标识,就像员工的工牌一样。所以 PID=100,可以粗略地理解为这个 /bin/sh 是我们公司里的第 100 号员工,而第 1 号员工就自然是比尔 · 盖茨这样统领全局的人物。
  5. 而现在,我们要通过 Docker 把这个 /bin/sh 程序运行在一个容器当中。这时候,Docker 就会在这个第 100 号员工入职时给他施一个“障眼法”,让他永远看不到前面的其他 99 个员工,更看不到比尔 · 盖茨。
  6. 这样,他就会错误地以为自己就是公司里的第 1 号员工。
  7. 这种机制,其实就是对被隔离应用的进程空间做了手脚,使得这些进程只能看到重新计算过的进程编号,比如 PID=1。
  8. 可实际上,他们在宿主机的操作系统里,还是原来的第 100 号进程。这种技术,就是 Linux 里面的 Namespace 机制。

Linux namespace

  1. Linux namespace 实现了 6 项资源隔离,基本上涵盖了一个小型操作系统的运行要素,包括主机名、用户权限、文件系统、网络、进程号、进程间通信。

Namespace 的使用机制

  1. 这 6 项资源隔离分别对应 6 种系统调用,通过传入上表中的参数,调用 clone() 函数来完成。
  2. 它其实只是 Linux 创建新进程的一个可选参数
  3. 在 Linux 系统中创建线程的系统调用是 clone(),比如:
1
int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);
  1. 创建的这个进程将会“看到”一个全新的进程空间,在这个进程空间里,它的 PID 是 1。之所以说“看到”,
  2. 是因为这只是一个“障眼法”,在宿主机真实的进程空间里,这个进程的 PID 还是真实的数值,比如 100。

  3. Docker 容器这个听起来玄而又玄的概念,实际上是在创建容器进程时,指定了这个进程所需要启用的一组 Namespace 参数。

  4. 这样,容器就只能“看”到当前 Namespace 所限定的资源、文件、设备、状态,或者配置。而对于宿主机以及其他不相关的程序,它就完全看不到了。
  5. 所以说,容器,其实是一种特殊的进程而已。
1
2
3
4
5
6
7
8
9
10
[root@k8s-master1 ~]# ps -ef|grep busy
root 2379 1073 0 09:53 pts/0 00:00:00 docker run -it --rm --name test busybox sh
[root@k8s-master1 ~]# ls -l /proc/2379/ns
total 0
lrwxrwxrwx 1 root root 0 Nov 10 10:10 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Nov 10 10:10 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Nov 10 10:10 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Nov 10 10:10 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Nov 10 10:10 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Nov 10 10:10 uts -> uts:[4026531838]

虚拟机和容器

虚拟机的工作原理

  1. Hypervisor 的软件是虚拟机最主要的部分。它通过硬件虚拟化功能,模拟出了运行一个操作系统需要的各种硬件,比如 CPU、内存、I/O 设备等等。
  2. 然后,它在这些虚拟的硬件上安装了一个新的操作系统,即 Guest OS。
  3. 用户的应用进程就可以运行在这个虚拟的机器中,它能看到的自然也只有 Guest OS 的文件和目录,以及这个机器里的虚拟设备。
  4. 这就是为什么虚拟机也能起到将不同的应用进程相互隔离的作用。

容器的工作原理

  1. 跟真实存在的虚拟机不同,在使用 Docker 的时候,并没有一个真正的“Docker 容器”运行在宿主机里面。Docker 项目帮助用户启动的,还是原来的应用进程。
  2. 只不过在创建这些进程时,Docker 为它们加上了各种各样的 Namespace 参数。
  3. 这些进程就会觉得自己是各自 PID Namespace 里的第 1 号进程,只能看到各自 Mount Namespace 里挂载的目录和文件,只能访问到各自 Network Namespace 里的网络设备,就仿佛运行在一个个“容器”里面,与世隔绝。

隔离与限制

  1. Namespace 技术实际上修改了应用进程看待整个计算机“视图”,即它的“视线”被操作系统做了限制,只能“看到”某些指定的内容。但对于宿主机来说,这些被“隔离”了的进程跟其他进程并没有太大区别。
  2. 不应该把 Docker Engine 或者任何容器管理工具放在跟 Hypervisor 相同的位置,因为它们并不像 Hypervisor 那样对应用进程的隔离环境负责,也不会创建任何实体的“容器”,真正对隔离环境负责的是宿主机操作系统本身
  3. 用户运行在容器里的应用进程,跟宿主机上的其他进程一样,都由宿主机操作系统统一管理,只不过这些被隔离的进程拥有额外设置过的 Namespace 参数。而 Docker 项目在这里扮演的角色,更多的是旁路式的辅助和管理工作。

  1. 使用虚拟化技术作为应用沙盒,就必须要由 Hypervisor 来负责创建虚拟机,这个虚拟机是真实存在的,并且它里面必须运行一个完整的 Guest OS 才能执行用户的应用进程。这就不可避免地带来了额外的资源消耗和占用。
  2. 容器化后的用户应用,却依然还是一个宿主机上的普通进程,这就意味着这些因为虚拟化而带来的性能损耗都是不存在的;而另一方面,使用 Namespace 作为隔离手段的容器并不需要单独的 Guest OS,这就使得容器额外的资源占用几乎可以忽略不计。
  3. 所以说,“敏捷”和“高性能”是容器相较于虚拟机最大的优势,也是它能够在 PaaS 这种更细粒度的资源管理平台上大行其道的重要原因。
  4. 基于 Linux Namespace 的隔离机制相比于虚拟化技术也有很多不足之处,其中最主要的问题就是:隔离得不彻底。

哪些地方没有进行隔离

  1. 首先,既然容器只是运行在宿主机上的一种特殊的进程,那么多个容器之间使用的就还是同一个宿主机的操作系统内核。
  2. 这意味着,如果你要在 Windows 宿主机上运行 Linux 容器,或者在低版本的 Linux 宿主机上运行高版本的 Linux 容器,都是行不通的。

  3. 在 Linux 内核中,有很多资源和对象是不能被 Namespace 化的,最典型的例子就是:时间。

  4. 在生产环境中,没有人敢把运行在物理机上的 Linux 容器直接暴露到公网上。

容器的限制

  1. 虽然容器内的第 1 号进程在“障眼法”的干扰下只能看到容器里的情况,但是宿主机上,它作为第 100 号进程与其他所有进程之间依然是平等的竞争关系。
  2. 这就意味着,虽然第 100 号进程表面上被隔离了起来,但是它所能够使用到的资源(比如 CPU、内存),却是可以随时被宿主机上的其他进程(或者其他容器)占用的。
  3. 当然,这个 100 号进程自己也可能把所有资源吃光。这些情况,显然都不是一个“沙盒”应该表现出来的合理行为。
  4. 而 Linux Cgroups 就是 Linux 内核中用来为进程设置资源限制的一个重要功能。

Linux Cgroups

  1. Linux Cgroups 的全称是 Linux Control Group。
  2. 它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。
  3. 在 Linux 中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。
  4. 它的输出结果,是一系列文件系统目录
1
2
3
4
5
6
7
8
9
10
11
12
[root@k8s-master1 containers]# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
  1. 在 /sys/fs/cgroup 下面有很多诸如 cpuset、cpu、 memory 这样的子目录,也叫子系统。这些都是我这台机器当前可以被 Cgroups 进行限制的资源种类
  2. 子系统对应的资源种类下,你就可以看到该类资源具体可以被限制的方法。比如,对 CPU 子系统来说,我们就可以看到如下几个配置文件,这个指令是:
1
2
3
[root@k8s-master1 containers]# ls /sys/fs/cgroup/cpu
cgroup.clone_children cgroup.procs cpuacct.stat cpuacct.usage_percpu cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat notify_on_release system.slice user.slice
cgroup.event_control cgroup.sane_behavior cpuacct.usage cpu.cfs_period_us cpu.rt_period_us cpu.shares docker release_agent tasks

限制一个docker容器的资源使用

  1. Linux Cgroups 的设计还是比较易用的,简单粗暴地理解呢,它就是一个子系统目录加上一组资源限制文件的组合。
  2. 而对于 Docker 等 Linux 容器项目来说,它们只需要在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录),
  3. 然后在启动容器进程之后,把这个进程的 PID 填写到对应控制组的 tasks 文件中就可以了。
  4. 而至于在这些控制组下面的资源文件里填上什么值,就靠用户执行 docker run 时的参数指定了,比如这样一条命令:
1
2
3
# docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash
[root@k8s-master1 ~]# docker run -it -d --name test --cpus=".5" busybox
98dad5852fc5e03f7dcb1241b52e24ade1c425deb68bf12a3687e89ea730515e
1
2
3
4
5
6
7
8
9
1. 它意味着在每 100 ms 的时间里,被该控制组限制的进程只能使用 50 ms 的 CPU 时间,也就是说这个进程只能使用到 50% 的 CPU 带宽。

[root@k8s-master1 ~]# cat /sys/fs/cgroup/cpu/docker/98dad5852fc5e03f7dcb1241b52e24ade1c425deb68bf12a3687e89ea730515e/cpu.cfs_period_us
100000

[root@k8s-master1 ~]# cat /sys/fs/cgroup/cpu/docker/98dad5852fc5e03f7dcb1241b52e24ade1c425deb68bf12a3687e89ea730515e/cpu.cfs_quota_us
50000

# 这个 Docker 容器,只能使用到 50% 的 CPU 带宽
1
2
3
# 容器里跑一个死循环 吃死CPU 再去查看使用率
[root@k8s-master1 ~]# docker exec -it test sh
/ # while : ; do : ; done &

1
2
3
4
5
# 被限制的进程的 PID 写入 container 组里的 tasks 文件
[root@k8s-master1 containers]# cat /sys/fs/cgroup/cpu/docker/98dad5852fc5e03f7dcb1241b52e24ade1c425deb68bf12a3687e89ea730515e/tasks
13589
13659
13676

总结

  1. 一个正在运行的 Docker 容器,其实就是一个启用了多个 Linux Namespace 的应用进程,而这个进程能够使用的资源量,则受 Cgroups 配置的限制。
  2. 使用namespace 在docker进程启动的时候做隔离,让容器就只能“看”到当前 Namespace 所限定的资源、文件、设备、状态,或者配置
  3. 然后使用 cgroup 为每个容器创建一个控制组 在启动容器进程之后,把这个进程的 PID 填写到对应控制组的 tasks 文件中 控制他们的资源使用率。
  4. 这也是容器技术中一个非常重要的概念,即:容器是一个“单进程”模型。
1
2
3
1. 由于一个容器的本质就是一个进程,用户的应用进程实际上就是容器里 PID=1 的进程,也是其他后续创建的所有进程的父进程。
2. 这就意味着,在一个容器中,你没办法同时运行两个不同的应用,除非你能事先找到一个公共的 PID=1 的程序来充当两个不同应用的父进程,
3. 这也是为什么很多人都会用 systemd 或者 supervisord 这样的软件来代替应用本身作为容器的启动进程。
1
2
3
1. 但是,在后面分享容器设计模式时,我还会推荐其他更好的解决办法。
2. 这是因为容器本身的设计,就是希望容器和应用能够同生命周期,这个概念对后续的容器编排非常重要。
3. 否则,一旦出现类似于“容器是正常运行的,但是里面的应用早已经挂了”的情况,编排系统处理起来就非常麻烦了。
  1. 另外,跟 Namespace 的情况类似,Cgroups 对资源的限制能力也有很多不完善的地方,被提及最多的自然是 /proc 文件系统的问题。
  2. 众所周知,Linux 下的 /proc 目录存储的是记录当前内核运行状态的一系列特殊文件,用户可以通过访问这些文件,查看系统以及当前正在运行的进程的信息,
  3. 比如 CPU 使用情况、内存占用率等,这些文件也是 top 指令查看系统信息的主要数据来源。
  4. 但是,你如果在容器里执行 top 指令,就会发现,它显示的信息居然是宿主机的 CPU 和内存数据,而不是当前容器的数据。
  5. 造成这个问题的原因就是,/proc 文件系统并不知道用户通过 Cgroups 给这个容器做了什么样的资源限制,即:/proc 文件系统不了解 Cgroups 限制的存在。
  6. 生产环境中,这个问题必须进行修正,否则应用程序在容器里读取到的 CPU 核数、可用内存等信息都是宿主机上的数据,这会给应用的运行带来非常大的困惑和风险。
  7. 这也是在企业中,容器化应用碰到的一个常见问题,也是容器相较于虚拟机另一个不尽如人意的地方。

问题

  1. 如何修复容器中的 top 指令以及 /proc 文件系统中的信息

深入理解容器镜像

  1. Docker 在镜像的设计中,引入了层(layer)的概念。也就是说,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量 rootfs。
  2. 用到了一种叫作联合文件系统(Union File System)的能力。
  3. Union File System 也叫 UnionFS,最主要的功能是将多个不同位置的目录联合挂载(union mount)到同一个目录下。
  4. centos:7使用的UnionFS 为 overlay2
1
2
[root@k8s-master1 docker]# docker info
Storage Driver: overlay2
  1. Docker 镜像使用的 rootfs,往往由多个“层”组成
  2. 通过结合使用 Mount Namespace 和 rootfs,容器就能够为进程构建出一个完善的文件系统隔离环境。
  3. 当然,这个功能的实现还必须感谢 chroot 和 pivot_root 这两个系统调用切换进程根目录的能力。
  4. 而在 rootfs 的基础上,Docker 公司创新性地提出了使用多个增量 rootfs 联合挂载一个完整 rootfs 的方案,这就是容器镜像中“层”的概念。

Docker 容器的本质

Flask小例子

  1. 使用 Flask 框架启动了一个 Web 服务器,功能是:
  2. 如果当前环境中有“NAME”这个环境变量,就把它打印在“Hello”后,否则就打印“Hello world”,最后再打印出当前环境的 hostname。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@k8s-master1 runtime]# vim app.py

from flask import Flask
import socket
import os

app = Flask(__name__)

@app.route('/')
def hello():
html = "<h3>Hello {name}!</h3>" \
"<b>Hostname:</b> {hostname}<br/>"
return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname())

if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)
1
2
3
# python依赖
[root@k8s-master1 runtime]# vim requirements.txt
Flask

应用容器化的第一步,制作容器镜像

  1. 制作镜像有两个方法,手动构建 和 Dockerfile 构建
  2. 手动构建就是基于base镜像一点一点的制作容器,最后提交为镜像
1
2
3
4
5
6
7
8
9
10
# 提交本地镜像
# commit -m "centos7-ssh" # 添加注释
# mycentos # 容器名
# test/mycentos-7-ssh # 仓库名称/镜像:版本 不加版本号就是latest 最后的版本
[root@linux-node1 tools]# docker commit -m "centos7-ssh" mycentos test/mycentos-7-ssh

[root@linux-node1 tools]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test/mycentos-7-ssh latest db3993e49556 About a minute ago 278MB
...
  1. 使用Dockfile 制作镜像
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@k8s-master1 runtime]# vim Dockerfile

# 使用官方提供的Python开发镜像作为基础镜像
FROM python:2.7-slim

# 将工作目录切换为/app
WORKDIR /app

# 将当前目录下的所有内容复制到/app下
ADD . /app

# 使用pip命令安装这个应用所需要的依赖
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# 允许外界访问容器的80端口
EXPOSE 80

# 设置环境变量
ENV NAME World

# 设置容器进程为:python app.py,即:这个Python应用的启动命令
CMD ["python", "app.py"]
  1. Dockerfile 的设计思想,是使用一些标准的原语(即大写高亮的词语),描述我们所要构建的 Docker 镜像。并且这些原语,都是按顺序处理的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 比如 FROM 原语,指定了“python:2.7-slim”这个官方维护的基础镜像,从而免去了安装 Python 等语言环境的操作。否则,这一段我们就得这么写了:

FROM ubuntu:latest
RUN apt-get update -yRUN apt-get install -y python-pip python-dev build-essential
...

2. RUN 原语就是在容器里执行 shell 命令的意思。
3. WORKDIR,意思是在这一句之后,Dockerfile 后面的操作都以这一句指定的 /app 目录作为当前目录。
4. ENTRYPOINT。实际上,它和 CMD 都是 Docker 容器进程启动所必需的参数,完整执行格式是:“ENTRYPOINT CMD”。
5. CMD 的内容就是 ENTRYPOINT 的参数,Docker 容器的启动进程为 ENTRYPOINT,而不是 CMD。
6. 默认情况下,Docker 会为你提供一个隐含的 ENTRYPOINT,即:/bin/sh -c
7. 在不指定 ENTRYPOINT 时,比如在我们这个例子里,实际上运行在容器里的完整进程是:/bin/sh -c “python app.py”
8. Dockerfile 里的原语并不都是指对容器内部的操作。
9. 就比如 ADD,它指的是把当前目录(即 Dockerfile 所在的目录)里的文件,复制到指定容器内的目录当中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[root@k8s-master1 runtime]# ls -l
total 12
-rw-r--r-- 1 root root 352 Nov 10 16:04 app.py
-rw-r--r-- 1 root root 506 Nov 10 16:09 Dockerfile
-rw-r--r-- 1 root root 6 Nov 10 16:07 requirements.txt

# 制作镜像
[root@k8s-master1 runtime]# docker build -t helloworld .

[root@k8s-master1 runtime]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
helloworld latest 605b63c39db7 About a minute ago 148MB

[root@k8s-master1 overlay2]# docker inspect helloworld

"RootFS": {
"Type": "layers",
"Layers": [
"sha256:b67d19e65ef653823ed62a5835399c610a40e8205c16f839c5cc567954fcf594",
"sha256:bb8510b5f5989e37e21b983be7a4936f6dab0ae7e0f634e592c6ae74990b0629",
"sha256:3e27dc6a2fe0c3aa9db3238038379d895bd990e5fd152f924277378f28a64f2c",
"sha256:6fb7ce0ece77af2e66fe961545b743ecf12bfd1df549b1cccf55dfda1f3fc770",
"sha256:afd237bb63dc3d749b32fc5913c914255bc87c821db1d7779f1b8917abb0dc16",
"sha256:365f769a43650e437a2777204ad5d7459d8bf687acd57b9214fb82dcbeae42b1",
"sha256:e79da1f2be17dee958c0b2cb67b5ce2d08dc4856ab2fe8e50fb5f689e4cb1a51"
]
},



1. -t 的作用是给这个镜像加一个 Tag,即:起一个好听的名字。
2. docker build 会自动加载当前目录下的 Dockerfile 文件,然后按照顺序,执行文件中的原语。
3. 而这个过程,实际上可以等同于 Docker 使用基础镜像启动了一个容器,然后在容器中依次执行 Dockerfile 中的原语。
  1. 需要注意的是,Dockerfile 中的每个原语执行后,都会生成一个对应的镜像层
  2. 即使原语本身并没有明显地修改文件的操作(比如,ENV 原语),它对应的层也会存在。只不过在外界看来,这个层是空的。

docker run 命令启动容器

1
2
3
4
5
[root@k8s-master1 docker]# docker run -d --name hello -p 4000:80 helloworld

在这一句命令中,镜像名 helloworld 后面,我什么都不用写,因为在 Dockerfile 中已经指定了 CMD。否则,我就得把进程的启动命令加在后面:

$ docker run -p 4000:80 helloworld python app.py

docker exec 是怎么做到进入容器里的呢?

  1. Linux Namespace 创建的隔离空间虽然看不见摸不着,但一个进程的 Namespace 信息在宿主机上是确确实实存在的,并且是以一个文件的方式存在。
  2. Docker 容器的进程号(PID)
1
2
[root@k8s-master1 docker]# docker inspect --format '{{.State.Pid }}' hello
14883
1
2
3
4
5
6
7
8
9
10
11
12
13
[root@k8s-master1 docker]# ls -l /proc/14883/ns
total 0
lrwxrwxrwx 1 root root 0 Nov 10 16:27 ipc -> ipc:[4026532223]
lrwxrwxrwx 1 root root 0 Nov 10 16:27 mnt -> mnt:[4026532221]
lrwxrwxrwx 1 root root 0 Nov 10 16:22 net -> net:[4026532226]
lrwxrwxrwx 1 root root 0 Nov 10 16:27 pid -> pid:[4026532224]
lrwxrwxrwx 1 root root 0 Nov 10 16:27 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Nov 10 16:27 uts -> uts:[4026532222]

1. 可以看到,一个进程的每种 Linux Namespace,都在它对应的 /proc/[进程号]/ns 下有一个对应的虚拟文件,
2. 并且链接到一个真实的 Namespace 文件上。
3. 有了这样一个可以“hold 住”所有 Linux Namespace 的文件,我们就可以对 Namespace 做一些很有意义事情了,
4. 比如:加入到一个已经存在的 Namespace 当中。
  1. 这也就意味着:一个进程,可以选择加入到某个进程已有的 Namespace 当中,从而达到“进入”这个进程所在容器的目的,这正是 docker exec 的实现原理。
  2. Docker 还专门提供了一个参数,可以让你启动一个容器并“加入”到另一个容器的 Network Namespace 里,这个参数就是 -net,比如:
  3. docker commit,实际上就是在容器运行起来后,把最上层的“可读写层”,加上原先容器镜像的只读层,打包组成了一个新的镜像。

Volume(数据卷)

  1. 容器里进程新建的文件,怎么才能让宿主机获取到?
  2. 宿主机上的文件和目录,怎么才能让容器里的进程访问到?
  3. 这正是 Docker Volume 要解决的问题:Volume 机制,允许你将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作。
  4. 在 Docker 项目里,它支持两种 Volume 声明方式,可以把宿主机目录挂载进容器的 /test 目录当中:
1
2
$ docker run -v /test ...
$ docker run -v /home:/test ...
  1. 而这两种声明方式的本质,实际上是相同的:都是把一个宿主机的目录挂载进了容器的 /test 目录。
  2. 只不过,在第一种情况下,由于你并没有显示声明宿主机目录,那么 Docker 就会默认在宿主机上创建一个临时目录 /var/lib/docker/volumes/[VOLUME_ID]/_data,然后把它挂载到容器的 /test 目录上。
  3. 而在第二种情况下,Docker 就直接把宿主机的 /home 目录挂载到容器的 /test 目录上。

总结