一篇文章带你吃透 Docker 原理

一篇文章带你吃透 Docker 原理

目录

容器的实现原理
容器的隔离:Namespace
容器的限制:Cgroups
容器的文件系统:容器镜像 - rootfs
Overlay2
容器与虚拟机技术的对比
解决的问题
参考

Top
容器的实现原理
从本质上,容器其实就是一种沙盒技术。就好像把应用隔离在一个盒子内,使其运行。因为有了盒子边界的存在,应用于应用之间不会相互干扰。并且像W B W a集装箱一样,拿来就走,随处运行。其实这就是 PaaS 的理想状态。

实现容器X + J ! 3 I的核心,就是要生成限制应用运行时的边界。我们知道,编译后的可执行代码加上数据,叫做程序。而把程序运行起来后,就变成了进程,也就q 2 K r是所谓的应用。如果能在应用启动时,给其加上一个边界,这样不就能实现期待的沙盒吗?

在 Linux 中,实现容器的边界,主要有两种技术Cgroups和NamespaZ p V ; 1 A A ? Vce.Cgroups用于对运行的容器进行资源的限. } ~ W 6 Z制,Namespace则会将容器隔离起来,实现边界。

这样看来e v u,容器只是一种被限制的了特殊进程而已b A * 1 # Q

Top
容器的隔离:Namespace
在介绍Namespace前y m D U U,先看一个实验:

使用 python3.6.8 的官方镜n F I H像,建立了一个运行 django 的环境

进入该容器后,使用 ps 命令,查看运行的进程

root@86 = W B G ~729260f784a:/src# ps -A
PID TTn 0 Q x 5Y TIME CMD

1 ?        00:01:22 gunicorn

22 ? 00:01:20 gunicorn
23 ? 00:01:24 gunico7 - Irn
2a e ! # 2 h5 ? 00:01:30 gunicorn+ + u ? Y 0 7 @
27 ? 00:01:16 gunicorn
41 pts/0 00:00:00 bash
55 p9 P 6 * o X sts/0 00:00:00 ps
可以看到,容器内 PID =1 的进程,是 gunicorn 启动的 django 应用。熟悉 Linux 的同学都知道,PID =1 的进程是系统启动时的第一个进程,也称 init 进程。其他的进程,都是由它管理产生的。而此时,PID=1 确B + | ~实是 django 进程。

接着,退出容器,在宿主机执行 ps 命令[ T g

环境为 Centos7

[root@lb g Jocalhost ~]# ps -ef | grep gunicoV k grn
root 9623 8409 0 21:29 pts/0 00:00:00 grepn s S * / v Q G --color=auto gunicorn
root 30828 30804 0 May28 ? 00:01:22 /uF s 5 D s ; G asr/local/biV f D |n/python /usr/local/bin/g1 d 5 | e L a 2 0unicorn -c gunicorn_config.py ctg.wsgi
root 31171 30828 0 May28 ? 00:01:20 /usr/local/bin/python /usr/local/bin/gun) / f o uicorC @ u | Q Hn -c gunicorn_config.py ctg.wsgi
root 31172 30828 0 May28 ? 00:01:24 /usr/loU U p : 3 Z m f /cal/bin/s 3 ] 6 pO O ( L ~ . @ #ython /usr/local6 3 ;/biv ` H K fn/g- . P ; ) Q Runicorn -c gunicorn_config.py ctg.wsgi
root 31174 30828 0 May28 ? 00:01:30 /usr/local/bin/pytw 3 p w q ahon /usr/local/bin/gunicorn -c gunicorn_config.py ctg.wsgi
root 31176 30828 0 May28 ? 00:01:1r / m g a m s ~ C6 /usr/local/bin/python /usr/local/bin/gunicorn -c gunicorn_config.py ctg.wsgi
如果以宿主机的视角,发现 django 进程 PIDp & ` D 变成了 30828. 这也就不难证I 3 6 ~明,在容器中,确实做了一些处理。把明明是 30828 的进程,变S Q 8 ) @ f成了容器内的第一号进程,同时在容器还看不到宿主机的其他进程。这也说明容器内的环境确实是被隔离了。

这种处理,其实就是 Linux 的Namesp0 ? G ? hacep $ c % r D .机制。比如,上述将 PID 变成 1 的方法就是通过PID Namespace。在 Linux 中创建线程的方法是clone, 在其中指定CLONE_NEWPID参数,这样新创建的进程,就会看u w B G $ 9 b g到一个全新的进程空间。而此时这个新的进程,也就变成了 P$ f b 0 F ! J 8ID=1 的进程。

int pid = clone(main_function, stack_size, CLONE_NEWPID |? N , SIGCHLD, NULL);
在 Linux 类似于 PID Namespace 的参数还有很多,比如:

Top
容器的限制:Cgroups
通过 Namespace 技术,我们实现了容器和容器间,容器9 8 L与宿主机之间的隔离。 I 7 U d L但这还不够,想象这样一种场景,宿主机上运行着两个容器。虽然在8 I k | $ * L容器间相互隔离,但以宿主机的视角来看的话,其实两个容器就是两个 Z 4 R - w N ;特殊的进程,而S z N } + h n / 9进程之间自然存在e l j ^ a s +着竞争关系,自然就可以将系统的资源吃光。当然,我们不能允许这么做的。

Cgroupsn G ( ? }就是 Linux 内核中用来为进程设置资源的一个技术。

Linux Cgroups 全称是 Linux Control Group,主要I ^ S ] V m的作用就是限制进程组使用的资源A O % v =f 1 Q k d . K c g限,包括 CPU,内存,磁盘,网络带宽。

还可以对进程进行优先级设置,审计,挂起和恢复等操作。

在之前的版本中,s | = $ 4 ] $ ( H可通过libcgroup tools来管理 cgroup, 在 RedHat7 后,已经改为通过s, ? ( ;ystemctl来管理。

我们知道,systemd在 Linux 中的功能就是管理系统的资源。而为了管理的方便,衍生出了一个叫Unit的概念,比如一个unit可以有比较宽泛的定义,比如可以表示抽象的服务,网络的资源,设备,挂载的文件系统等。为了更好的区分,Linux 将Unit的类型主要分为 12 种。

类型 作用
.autom( % n .ounte ( | R { 用于自动挂载配置的挂载点
.swap 描述系统的交换区,反映了设备或文b o !件的路径
.tark l x p _get 在系统启动或者改变状态时,为其他 unit 提供同步点
.path 定义的文件路径,用于激活。
.service 一个服务或g ] Y F者一个应用,具体定义在配置文件中。
.socket 一7 + g F H 3 w P E个网络或者 IPC socket,FIFO buffeZ m J f -r.
.device 描述一个需要被systemdudev或sy8 d dsfs文K n Y ) I ` D K j件系统管理的设备
.mount 定义的挂载点
.timer 定时: 7 + 0
.snapshot 被systemctl snapshot命k ~ R ! )令自动创建的单元
.slice 用于关联 Linuxo w C $ & u h Control Group 节点,根据关联的 slice 来限制进程。一个管理单元# Y 7 z , 0 ; L的组。Slice 并不包含任何进程,仅仅管理由 service 和 scope 组成的层级结构。
.scope systemd 从 bus 接口收到消息后自动创建。Scope 封装了任意进程通过fov q = f ? Prk()函数开启或停止的进程,并且在systemd运行时注册。例如:用户 sessions,容器和虚拟机。
在Cgroup中,主要使用的是slice,scopeandservice这三种类型。

如创建一个/ . | _ S临时 cgroup, 然0 ; @ 1 G { P Z @后对其启动的进程进行资源限制:

# 创建一个叫 toptest 的服务,在名为 test 的 slice 中运行
[root@localhost ~]# systemd-run --unit=tO . i 5 7optest --slice=test top -b
Running as unit toptest.service.
现在 toptest 的服务已经运行在后台了

通过 systemd-cgls 来查看 Cgroup 的信息

[root@localhost ~]# systemd-cgls
├─1 /us+ Q B Z u a ;r/lib/systemd/systemd --switched-rootg c i U N - S d --systh , 5 mem --deserialize 22
├─test.slice
Z = j C 1 W Z P j └─toptest.service
│ └─6490 /usr/bin/top -b

通过 systemctl status 查看服务的状态

[root@localhost ~]# systemc8 f * 6 v - k * stl status toptest
● toptest.service - /usr/biq w ) y u =n/top -b
Loaded: loa# - 0 n g Aded (/run/systemd/system/toptest.service; static; vendo} K t R _ ,r preseC s &t: disab, q S Vled)
Drop-In: /run/systemd/system/t] g M : m ; # ` optest.s~ ; { N [ X I L gervice.d

       └─50-Description.conf, 50-ExecStart.conf, 50-Slice.conf

Active: active (running) since Tue 2020-06-02 14:01:01 CST; 3min 50s ago
Main PID: 6490 (top)
CGroup: /test.slice/tF * = j + ` E s /optest.service

       └─6490 /usr/bin/top -b

现在] 3 B @ + 0 ; =对运行的 toptest 服务进行资源的限制。

H h o X看下,没有被限制前A q & X的 Cgroup 的信息, 6490 为进程 PID

[root@localhost ~]# cat /proc/6490/cgroup
11:ps Y Uids:/test.slice
10:blkiof G i L:/test.slice
9:hugetlb:/
8:cpuset:/
7:memory:/test.slice
6:devices:/test.slice
5:net_prio,net_cls:/
4:perf_event:/
3:freezer:/
2:- b ( I Acpuacct,cpu:| % S B/test.slice
1:name=systemd:/test.slice/toptest.service

对其- v S y _ d / u使用的 CPU 和 内存进行限制g B 3 G

systemctll T D v n % set-property toptest.service CPUShares=600 MemoR = Z s + J y E 0ryLimit=500M

再次查看 Cgroup 的信息,发现在 cpu 和 memory 追加了一些内容。

[root@localhost ~]# cat /procA 8 s [ T r 8 //6490/cgroup
11:pids:9 ) x/test.slice
10:blkio:/test.slice
9:hugetlb:/
8:cpuset:/
7:memory:/test.slice/toptest.service
6:devices:/test.slice
5:net_prio,net_cls:/
4:perG C B t * Q W 3 ;f_event:/
3:fred B X 7 o E 4 r 9ezer:/
2:cpuacct,cpu:/test.slice/toptt O /est.service
1:name=systemd:/test.slice/toptest.service
这时可以在/sys/fs/cgroup/memory/test.slice和/sys/fs/cgroup/cpu/test.slice目录& A T $ c b l X n下,多出了一个叫 toptest.service 的目录。

在其目录下cat toP c R G } g * optest.service/cpu.shares可以发现,里面的/ k a y R h w CPU 被限制了600.

回到 DockC F G 2 /er,其实 d= . z W j o 7ocker 和我们上面做的操作基本一致,具体需要限制哪些资源就是在doP J 7cker r! G V uun里指定:

$ docker run -it --cpu-period=100000 --cpu-q2 M Puota=20000 ubuntu /bin/bash
关于 docker 具体的限制,可以在sys/fs/cgroup/cpu/docekr/等文件夹来查看。

Top
容器的文件系统:容器镜像 - rootfs
现在我们知道,容器技术的核心就是通过Namespace限制了容器看到的视野,通过Cgroup限制了容器可访问的资源。 但关于Mount Namespace还有一些特殊的地方,需要着重关注^ ; u下。

Mount Namespace特殊之处在于,除了在修改时需要进程对文件系统挂载点的认证,还需要显式声明需要挂载那些目录。o d x j l在 Linux 系统中,有一个叫chroot的命令,可以改变进程的根目录到指定的位置。而Mount Namespace正是基于 chroot 的基础上发展出来的。

在容器内,应该看到完全独立的文件系统,而且不会受到宿主机以及其他U V 容器的影响。这个独立的文件系统,就叫做容器镜像。它还有一个更专业的名字叫rootfs.rootfs中包含了一个操作系统所需要的文件,配置和v 9 p ^目录,但并不包含系统内核。因为在 Linux 中,文件和内核是分开存放的,操作系统只有在开启启动时才会加载指定的内核。这也就意味着,所有的容器都会共享宿主机上操作系统的内核。

在 PaaS 时代,由于云端和本地的环境不同,应用打包的过程,一直是比较痛苦的过程。但有了rootfs,这个问题就被很好的解决了。因为在镜像内,打包的不仅仅是应用,还有所需要的依赖,都被封装在一起。这就解决了无论是在哪,应用都可以很好的运行的原因。

不光这o } d % n D g 6 8样,rootfs还解决了可重用性的问题,想象这个场景,你通过rootfs打包了一z L _ h W个包含 java 环, O o 4 q w境的 centosG E t t 镜像,别人需要在容器内跑一个apache的服务,那么他是否需要从头开始搭建 java 环境呢?docker 在解决这个问题时,引入了一个叫层的概念,每次针对rootfs的修改,都只保存增量的内容,而不是 fork 一个新镜像。

层级的想法,同样来自于 Linux,一个叫 union file system (联合文件系统)。它最主要的功能就是将不同位置的目录联合挂载到同一个目录下。对应在 Docker 里面,不同的环境则使[ Y h . * ` l @用了不同的联合文件系统O ; & W k [ i。比如 centos7 下最新的版本使用的是overlay2,而 Ubuntu 16.04 和 Docker CE 18.05 使用的是 AuFS.

可以通过docker info来查询1 u T使用的存储驱动,我这里的是/ | H 5 .ov& 1 3 % nerlay2。

[root@localhost ~]# docker info
C! U I e X t ^lient:
Debug Mode: fal# I ~ W m a y w 5se

Server:
Containers: 4
Running: 4
Paused: 0
Stopped: 0
Images: 4
Server Version1 { s 2 8 F k: 19.03.8
Storage Driver: overlay2
接着我们来了解下,Overlay2 的文件系统在 docker 中是如何使用的?

Overlay2
在 Linux 的主机上,OvS 9 & + MerlayFS一般有两个目录,但在显示时具体会P U M 2 ^显示为一个目录。这两个目录被称为层,联合在一起的过程称为unioL s a y ^ fn mount. 在其下层的目录称为lowerdir, 上层的目录称为upperdiq # r X Or. 两者联合后,暴露出来的视图称为view. 听起来有点抽象,先看下整体] & W结构:

可以看到,lowerdir其实对应的就是镜像层,upperdir对应的就是容器器。而merged对应M ^ d D M ~ h的就是h v U % 3 v Q D两者联合挂载之后的内容。而且我们发现,当镜像层和容器层拥有相同的文件时,会以容器层的文件为准(最上层的文件为准)。通常来说,overlay2支持最多 128 lower 层。Y # Q

下面实际K { + Q ` Y 9 .看下容器层和镜像具体的体现,我这台 linux 主机上,运行着 4 个 container, ; j P , Y X )

Docker 一般的存储位置在/var/lib/docker,先看下a s N s y里面的结a , R构:

[root@V ` z .localhost docker]# ls -l /var/lib/docker
total 16
drwx------. 2 root root 24 Mar 4 03:39 builder
drwx--x--x. 4 root root 92 Mar 4 03:39 buildkit
drwx------. 7 root root 4096 Jun 1 10:36 containers
drwx--[ { Q ( h l $----. 3 root root 22 Mar 4 03:39 image
drwxr-x---. 3 root root 19 Mar 4 03:39 network
drwx------. 69 root root 8192 Jur A 2 | ] P T :n 1 15:01 overlay2
drwx------. 4 roh F ^ not root 32 Mar 4 03:39 plugins
drwx------. 2 root root 6 Jun 1 15:00 runtimes
drwx------. 2 root root 6 Mar 4 03:z q ,39 swarm
drwx------. 2 root root 6 Jun 1 15:01 tmp
drwx----c 5 6 B 3 6--. 2 root root 6 Mar 4* F C % P ^ A ~ 03:39 trust
drwx------. 3K q + root root 45 May 18 10:28 volumes
需要着重关注的是container,image,overlay2这几个文件夹。

container:这个不j ( @ O @ V 1用多说,正在运行或创建的容器会在这个目录下。
image:对应记录的就是镜像。
overlay2:记录的是每个镜像下包含的lowerrdir.
之前提到,unionfs 的实现可能有多种,比如overlay2,aufs,devicemappe6 P +r{ E ( ^ b 9 @等。那么自然在 image 文件夹下,就会存在多种驱动的文件夹,:p Y 0 R n = u

image/
└── overlay2

├── distribution
├── imagedb
│ ├── content
│ └── metadata
├── layerdb
│ ├── mounts
│ ├── sha256
│ └── tmp
└── repH | 0 Oositories.json

这里的ima) } $gedb和layerdb, 就是存储元数据的地方。之前我们了解到,容器的文件系统构成就是通过 image 层 和 containeD i 3 Ir 层联合构成的,而每个 image 可能是由多个层构$ 9 w成。这就意味着,每个层可能会被多个 image 引用。那么之间是如何关联的呢?答案就在imagedb这个文件下。

这里我以z 9 ! n x w P Pmysql镜像为例:

查看 mysql 的镜像 id

[root@localhost dock7 u # Q G Mer]# doL n . / 8 l # pcker image ls
R. 7 ) % y h E nEPOSITORY TAG IMAGE ID CREATED SIZE
ctg/mysql 5.7.29 84164b03fa2e 3 months ago 456MB

进入到 imagedb/ Q x f f X s 7/content/sha256 目录, 可以找 j 6 9 , 2到对应的镜像 id

[rooc p q A n D S ? qt@localhost docker]# ls -l image/overlay2/imagedb/content/sha256/
...
-rw-------. 1 root root: J ) 5 L 6995 Apr 27 02:45 84164b03fa2ecb33r 2 b ) s +e8b4c1f2636ec3286e9078684 r N y 8 ]19faa4d1c103ae147824196a

接着看p : 0 n下里面记录的g d v F x S内容, 这里截取有用的部分

cat image/overlaY H 2 Uy2/imagedb/content/sha256/84164b03fa2ecb33e8bd Q a4c1f26u u v y q @ O } ,36ec3286e90786819faa4d1c103ae147824196a
{
.........
j Q N [ ="os": "linux",
"rootfs": {

"type": "layeJ _ 4 ^ Nrs",
"diff_ids": [
"sha256:f2cb0ecef392f2a630fa1205b874ab2e2aedf96de04d0b8838e4e728m Y w 4eS P w28142da",
"sha256:a9f6b7c7101b86ffaa53dc29638e577dabf5b24% } : `150577a59199d8554d7ce2921",
"sha256:0c615b40cc37ed667e9cbaf33b726fe986d23e5b2588b7acbd9288c92b8716b6",
"sha256:ad160f341db9317284bba805a3fe9112d868b272041933552df5ea14647ec54a",
"shd  fa256:1ea6ef84dc3af6506c? ? 326753e9e2cf7c0d6c1c743102b# R k 5 E * i85ebd3ee5e3$ v y57d7e9bc4",
"sha256:6fce4d95d4af3777f3e3452e5d17612b7396a36b+ z 3 O !f0cb588ba2ae1b71d139bab9",
"sha256:6de3946ea0137e75dcc43a3a081d10dda2fec0d065627I a Ya03800a99e4abe2ede4",
"sha256:a35a4bacba4d5402b8l ` { m5ee6e898b95cc71462bc071078941cbe/ e & E z ~ | _ e8c77a6ce2fca62",
"sha256:1ff9500bdff4455fa89a808685622b69 X , V4790c321da101da ^ | / 1 Q G n27c17b710f7be2e0e7e",
R Q %"sha256:1cf663d0cb7$ 9 f b ` 3 sa52a3a33a/ g 7c8` ) D N d ; f 2 f4ff5290b80966921ee8d3cb11592da332b4a9e016",
"shX G N { 3 ha256:bcb387cbc5bcbc83 r W $ p qb5c33fbfadbce42875A 1 c (22719db43d3e3a286da74492b7d6eca"
]

}
}
可以看到C ? d = C N / &mysql镜像由 11 层组成,其中i 9 G v f _ k kf2cb是最低层,bcb3是最上层。

接着,我们看下layerdb的内容:

[root@localhost docker]# ls -l image/overlay2/layerdb/
tot0 v ( N {al 8
drwxr-xr-x. 6 root root 4096 May 13n % Y P k } 13:38 mounts
drwxr-xr-x. 39 root root 4096 Apr 27 02:51 sha256
drwxr-xr-x. 2 root root 6 Apr 27 02:51 tmp; V ^

首先看下 sha256{ Y = L # ( m 目录下的内容

[root@localhos1 M y R N ut dockK 2 8 ` ~ 8 ;er]# ls -l image/t : } Doverlay2/layerdb/sha256/
total 0
....
drwx------. 2 root root 71 Apr 27 02:45 bbb9cccab59a16cb6da78f8879e9d07a19e3a8d^ C M { 8 6 +49010ab9c98a2c348fa116c87
drwx------. 2 root root 71 Apr 27 02:45 f2cb0ecef392f2a630fa1205b874ab2e2aedf96de04d0b8838e4e728e28142da
....
可以发现,在这里仅能找到最底层的层 ID,原因在于层之间的关联是通过 chainID 的方式保存的,简单来说就是通过 sha256 算法后能计算出一层a : { B 2 c T w的容器 id.

比如这里,最底层 id 是f2cb0ecef392f2a630faM Y ^ G ? 2 91205b0 c 8874ab2e2aedf96de04d0b8{ 6 A i U y E838e4e728e281` ` U U42da, 上一层 id 是a9f6b7c7101b86ffaa53dc2963# ! K (8e577dabf5bx D q ) ( J2G 0 c q4150577a5919W f S , 3 k / (9d8554d7ce2921, 那么对应在 sha256 目录下的. ^ N M $ @ J下一层 id 的计算方法就是:

[root@localhost docker]# echo -n "sha256:f2cb0ecef392f2a630fa1205b874ab2e2aedf96de04d0b8838e4e728e28142da shY @ Wa256:a9f6b7c7101b86ffaa53dc29638ep ) ` ? s )577dabf5b24150577a59199d8554d7ce2921" | sha256sum
bbb9cccab59a16cb6da78f8879e9d07; [ 1 2 0 O F ha19e3a8d49010ab9c98a2c348fa116c87 -
接着我们可以在sha256目录下,找到bbb9..这层的内容。

OK,现在我们已经把镜像和层关联起来_ s a,但之前说过,image 下村的都是元数据。真实的 rootfs 其实在另一个地方 -/L ? =docker/overlay2下。

通过查询 cache-id,得到就是真实的 r/ S ] gootfs 层

[root@localhost dock$ | s f B ^ @er]# cat image/oveV Q Y P W = 8rlay2/layerdb/sha256/f2cb0ecef392f2ay H Q630fa1205b874ab2e2aeE : _ t 3 Xdf96de04d0b8838e4e728e28142da/cache-id
2996b24990e75cbd304093139e665a45d96df8d7e49334527827d. 1 d pcff82L 6 m ! B % s0dbf16[
进入到/docker/overlay2下查看:

[root@localhost docker]# ls -l overlay2/
total 4
...
drwx------. 3 root rI N M N [ M .oot 47 Apr 27 02:45 2996b24990e75cbd304093139e665a45d96df8d7e49334527827dcff820dbf16
...
drwx------. 2 root root 4096 May 13 13:38 l
这样真实的] Q q U ! A [ rootfs 层也被找到了。

这里重新梳理下,我们先是在mage/overlay2/imagedb/content/sha256/u , v $,根据 image ic e Y a k c }d 查看该 image 具有所有的层ID,然后根据最底层ID和上层ID通过 sha256 计算得到,引用的上一层 ID, 依次类推,关联所有的层。最后通过每一层的cache-id,将元数据和真实的 rootfs 层数据对应起来了。

最后总结一下,rootfs 的构成。

每个 rootfs 由镜像层(lowerdir)和 容器层(upperdir)构成,其中镜像层只能只读,而容器层则能读写。而m b S r ( q U ,且镜像层可有最多128层构成。

其实,rootfs 构成还有另外一层,但由于在进行提交或编译时,不会把这层加进去,所以就没* / I b n B把这层算在rootfs里面,但实际上存在的。

在之前我们查看ls -l /var/lib/docker/overlay$ L Y 1 * # b c2/下镜像层,会看到好几个以-init结尾的目录,而且数量恰好等于容器的数量。这层夹在镜像层之上,容器层之下。是由 docker 内部单独生成的一层,专门用于存放etc/hosts、/etc/resolv.conf等配置信息。存/ o g E . _在的目的,是由于用户在容器启动时,需要配置一些特定的值,比如 hostname,dns 等,但这些配置仅对当前容器有效,放到其他环境下自然有别的配置,所以这层被单独拿出来,在提交镜像时,忽略这一层。

Top
容器与虚拟机技术的对比
下面这张图是 docker 官方中,截取下来的,基于上面我们学习的内容,重新分析下 docker 和 传统 VM 的区别:

u g E { / O }移性和性能:

传统 VM: 需要基于 Hyc d Cpervisor 的硬件虚拟化技术,模拟出 CPU@ n V 4 r s . I,内存等硬件。然后在其上搭建一套完整的操作系统,自然; f ^ m = F Y在性能上会有很大的损失。迁移自然更不用说,传统的 ova 导q J t出后就是一个完整的操作系统。
Docker:Docker 将 Hypervisx 9 o 3or 的位置换成自己的 Docekr Engine. 然后运行的容器仅仅是一个特殊的F , i ! o f -进程,自然性能不会有太* D ) o * G T O v大的损失。并且可以应用Z V ` x ~ q ] ^和其所需要的系统文件打包成镜像,} ? ! 0 % |无论在哪读可以正常运行,而且相对于 ova 来说体积也小了更多。(需要内核支持)
一般来说,运行着 CentOS 的 KVM,启动后,在不做优化的前提下,需要占用 100~200 M 内存。在加上用户对宿主机的调用,需要通过虚拟化软件拦截和处理,又是一层性x ! 8 y能损耗,特别是对计算资源,网络A 1 | T b和磁盘I/O等。

隔离性:

传统 VM:由于是虚拟化出一套完整的操作系统,所以隔离性非常好。比如微软的 Azure 平台,就是在 Windows 服务器上,虚拟出大量的 Linux 虚拟机[ : Q H U u
Docker:在隔离性上相差就很多了,因为本身上容器就? k Y S f y 5 y #是一种进~ 6 / e l N F = W程,而所有的进程都需要共享一个系统内核。
这就意味着,在 Windows 上运行 Linux 容器,或者 Linux 宿主机运行高版本内核的容器就无法实现。
在 Linux 内核中,有许多资源和对象N N ; L M L K不能 Namespaceb W A n b 8 化,如时间,比如通过settimeofday(2) 系统调用修改时间,整个宿主机的实际都会被修改。
安全的问题,共享宿主机内核的事实,容器暴露出的攻击面更大。
资源的限制:

传统 VM:非常便于管理,控制资源的使用,依赖于虚拟的操作系统。
Docker:由于 docker 内资源的限制通过 Cgroup 实现,而 Cgroup 有很多不完善的地方,比如
对 /proc 的处理问题。进入容器后,执行top命令,看G / h O 6 m 9 ! $到的信息和宿主机是一样的,而不是配置后的容器的数据。(可以通过 lxcfs 修正)。
在运行 java 程序时5 s ! q = 0 f # Z,给容器内设置的内存为 4g,使用默认的 jvm 配置。而默认的 jvm 读取的内存是宿主机(可能大于 4g),这样就会出现 OOM 的情况。
Top
解决的问题
容器是如何进行隔离的?
在创建新进程时,通过 Namespace 技术,如 PID namespaces 等,实现隔离性。让运行后的容器仅能看到本身的内容。

比如,在容器运行时,会默认加上 PID, UTS, network, user, mount, IPC, cgr6 w J i zoup 等 Namespace.

容器是如何进行资源限制的?
通过 Linux Cgroup 技术,可% 0 3 7 [ 2 5为每个进程设定限制的 CPU,Memory 等资源,进而设置进程访q L 3 &问资源的上限。

简述下 docker 的: X 0 ` s文件系统- M J k y F M F ]
docker 的文件系统称为 roW * .otfs,它的实现的想法来自与 Linux unionFS 。将不同的目录,挂载到一起,形成一个独立的视图。并且 docker 在此基础上引入了层的概念) b f $ i _ 4 ) S,解决了可重用性的问题。

在具体实现上,: $ + u b q Q arootfs 的存储区分根据 linux 内核和 docker 本身的版本,分为i N 5overlay2,overlay,aufs,devicemapper等。rootfs(镜像)其实就是多个层的叠加,当f O b + X % L多层. 5 b K存在相同的文件时,上层的文件会将下层的文7 Q 4 U E / u Y件覆盖掉。

容器的启动过程?
指定 Linux Na! 2 u M Z $mespace 配置
设置指定8 n d L的 Cgroups 参数
切换进程的根目录
容器内运行多个应用的问题?
首先更正一个概念,我们都说容器是一个单进程的应用,其实这里的单进程不是指在容器中只允许着一个进程,而是指只有一个进程时可控的。在容器内当然可以使用 ping,ssh 等进程,但这些进程时不受 docker 控制的。

容器内的主进程,也就是 pid =1 的进程,一1 n n : ^ 4 $ m !般是通过 DockerFild ~ ( : De 中 ENTRYPOINT 或者 CMD 指定的。如果在一个容器内如果存在着多个服务(进程),就可能出现主进程正常运行,但是子进程退出挂掉的问7 w题,而对于 dockero f | E y 来说,仅仅控制主进程,无法对这种意外的情况作出处理,也就会出现,容器明明正常运行,但是服务已经挂掉的情况,这时编排系统就变得非常困难。而且多个服务,在也不容易进行排障和管理。: x C # q i M ^ k

所以如果真的想要在容器内运行多个服务,一般会通过带有systemd或者sN H Hupervisord这类工具进行管理,或者通过--init方法。其实这些方法的本质就是让多个服务的进程拥有同一个父进程。

但考虑到容器本身的设计,就是希望容器和服务能够同生命周期。所以这样做,有点背道而驰的意味。

控制(回收S ! w Z b ^ _ R和生命周期的管理)

Top
参考
Cgroup 介绍

Overlay2介绍

d7 { C W R ,ocker 官网

在一个容器内搭建多个服务

原文地址https://l w T ` { r Awww.cnblogs.com/michael9/p/13039700.html