1.3 容器

1.3 容器 #

可以使用 docker pull 命令,拉取一个新的镜像——操作系统 Alpine:

docker pull alpine

然后使用 docker run 命令运行它的 Shell 程序:

docker run -it alpine sh

-it参数,可以离开当前的操作系统,进入容器内部。

容器,就是一个特殊的隔离环境,它能够让进程只看到这个环境里的有限信息,不能对外界环境施加影响。

1.3.1 为什么需要隔离 #

对于操作系统来说,一个不受任何限制的应用程序是十分危险的。这个进程能够看到系统里所有的文件、所有的进程、所有的网络流量,访问内存里的任何数据,那么恶意程序很容易就会把系统搞瘫痪,正常程序也可能会因为无意的 Bug 导致信息泄漏或者其他安全事故。

使用容器技术,就可以让应用程序运行在一个有严密防护的 “沙盒”(Sandbox)环境之内,它可以在这个环境里自由活动,但绝不允许 “越界”,从而保证了容器外系统的安全。

在计算机里有各种各样的资源,CPU、内存、硬盘、网卡,虽然目前的高性能服务器都是几十核 CPU、上百 GB 的内存、数 TB 的硬盘、万兆网卡,但这些资源终究是有限的,而且考虑到成本,也不允许某个应用程序无限制地占用。容器技术的另一个本领就是为应用程序加上资源隔离,在系统里切分出一部分资源,让它只能使用指定的配额,比如只能使用一个 CPU,只能使用 1GB 内存等等,这样就可以避免容器内进程的过度系统消耗,充分利用计算机硬件,让有限的资源能够提供稳定可靠的服务。

1.3.2 容器和虚拟机的区别 #

容器和虚拟机面对的都是相同的问题,使用的也都是虚拟化技术,只是所在的层次不同。

容器和虚拟机的目的都是隔离资源,保证系统安全,尽量提高资源的利用率。

从实现的角度来看,虚拟机虚拟化出来的是硬件,需要在上面再安装一个操作系统后才能够运行应用程序,而硬件虚拟化和操作系统都比较 “重”,会消耗大量的 CPU、内存、硬盘等系统资源,但这些消耗其实并没有带来什么价值,属于 “重复劳动” 和 “无用功”,不过好处就是隔离程度非常高,每个虚拟机之间可以做到完全无干扰。

容器直接利用了下层的计算机硬件和操作系统,因为比虚拟机少了一层,所以自然就会节约 CPU 和内存,显得非常轻量级,能够更高效地利用硬件资源。不过,因为多个容器共用操作系统内核,应用程序的隔离程度就没有虚拟机那么高。

1.3.3 隔离的实现 #

Linux 操作系统内核为资源隔离提供了三种技术:namespace、cgroup、chroot,虽然这三种技术的初衷并不是为了实现容器,但它们三个结合在一起就会发生奇妙的 “化学反应”。

namespace 是 2002 年从 Linux 2.4.19 开始出现的,和编程语言里的 namespace 有点类似,它可以创建出独立的文件系统、主机名、进程号、网络等资源空间,相当于给进程盖了一间小板房,这样就实现了系统全局资源和进程局部资源的隔离。

cgroup 是 2008 年从 Linux 2.6.24 开始出现的,它的全称是 Linux Control Group,用来实现对进程的 CPU、内存等资源的优先级和配额限制,相当于给进程的小板房加了一个天花板。

chroot 的历史则要比前面的 namespace、cgroup 要古老得多,早在 1979 年的 UNIX V7 就已经出现了,它可以更改进程的根目录,也就是限制访问文件系统,相当于给进程的小板房铺上了地砖。目前的容器基本不再使用过于古老的 chroot 了,而是改用 pivot_root。

综合运用这三种技术,一个四四方方、具有完善的隔离特性的容器就此出现了,进程就可以搬进这个小房间,过它的 “快乐生活” 了。

1.3.4 常见容器操作 #

基本的格式是 “docker run 设置参数”,再跟上 “镜像名或 ID”,后面可能还会有附加的 “运行命令”。

docker run -h srv alpine hostname

这里的 -h srv 就是容器的运行参数,alpine 是镜像名,它后面的 hostname 表示要在容器里运行的 “hostname” 这个程序,输出主机名。

docker run 是最复杂的一个容器操作命令,有非常多的额外参数用来调整容器的运行状态,可以加上 –help 来看它的帮助信息。

  • -it 表示开启一个交互式操作的 Shell,这样可以直接进入容器内部,就好像是登录虚拟机一样,这个命令实际上是-i-t两个参数的组合形式。

  • -d 表示让容器在后台运行,这在启动例入 Nginx、Redis 等服务器程序的时候非常有用。

  • --name 可以为容器起一个名字,方便查看,不过它不是必须的,如果不用这个参数,Docker 会分配一个随机的名字。

  • --rm 可以让容器运行结束后自动删除。

对于正在运行中的容器,可以使用 docker exec 命令在里面执行另一个程序,效果和 docker run 很类似,但因为容器已经存在,所以不会创建新的容器。最常见的用法是使用 -it 参数打开一个 Shell,从而进入容器内部,例如:

docker exec -it 容器名称/容器ID sh/bash

参考 #