Docker 容器简介
容器是镜像的运行时实例。正如从虚拟机模板上启动 VM 一样,用户也同样可以从单个镜像上启动一个或多个容器。虚拟机和容器最大的区别是容器更快并且更轻量级——与虚拟机运行在完整的操作系统之上相比,容器会共享其所在主机的操作系统/内核。
下图为使用单个 Docker 镜像启动多个容器的示意图:
启动容器的简便方式是使用 docker container run
命令。该命令可以携带很多参数,在其基础的格式 docker container run
中,指定了启动所需的镜像以及要运行的应用。docker container run -it ubuntu /bin/bash
则会启动某个 Ubuntu Linux
容器,并运行 Bash Shell
作为其应用;如果想启动 PowerShell
并运行一个应用,则可以使用命令 docker container run -it microsoft- /powershell:nanoserver pwsh.exe
。
-it
参数可以将当前终端连接到容器的 Shell
终端之上。
容器随着其中运行应用的退出而终止。在上面两个示例中,Linux
容器会在 Bash Shell
退出后终止,而 Windows
容器会在 PowerShell
进程终止后退出。
一个简单的验证方法就是启动新的容器,并运行 sleep
命令休眠 10s。容器会启动,然后运行休眠命令,在 10s 后退出。如果你在 Linux
主机(或者在 Linux
容器模式下的 Windows
主机上)运行 docker container run alpine:latest sleep 10
命令,Shell
会连接到容器 Shell
10s 的时间,然后退出;也可以在 Windows
容器上运行 docker container run microsoft/powershell:nanoserver Start-Sleep -s 10
来验证这一点。
读者可以使用 docker container stop
命令手动停止容器运行,并且使用 docker container start
再次启动该容器。如果再也不需要该容器,则使用 docker container rm
命令来删除容器。
容器 vs 虚拟机
容器和虚拟机都依赖于宿主机才能运行。宿主机可以是笔记本,是数据中心的物理服务器,也可以是公有云的某个实例。在下面的示例中,假设宿主机是一台需要运行 4 个业务应用的物理服务器。
在虚拟机模型中,首先要开启物理机并启动 Hypervisor
引导程序(本课程跳过了 BIOS
和 Bootloader
代码等)。一旦 Hypervisor
启动,就会占有机器上的全部物理资源,如 CPU
、RAM
、存储和 NIC
。Hypervisor
接下来就会将这些物理资源划分为虚拟资源,并且看起来与真实物理资源完全一致。然后 Hypervisor
会将这些资源打包进一个叫作虚拟机(VM
)的软件结构当中。这样用户就可以使用这些虚拟机,并在其中安装操作系统和应用。前面提到需要在物理机上运行 4 个应用,所以在 Hypervisor
之上需要创建 4 个虚拟机并安装 4 个操作系统,然后安装 4 个应用。当操作完成后,结构如下图所示:
而容器模型则略有不同。
服务器启动之后,所选择的操作系统会启动。在 Docker
世界中可以选择 Linux
,或者内核支持的容器原语的新版本 Windows
。与虚拟机模型相同,OS
也占用了全部硬件资源。在 OS
层之上,需要安装容器引擎(如 Docker
)。容器引擎可以获取系统资源,比如进程树、文件系统以及网络栈,接着将资源分割为安全的互相隔离的资源结构,称之为容器。每个容器看起来就像一个真实的操作系统,在其内部可以运行应用。按照前面的假设,需要在物理机上运行 4 个应用。因此,需要划分出 4 个容器并在每个容器中运行一个应用,如下图所示:
从更高层面上来讲,Hypervisor
是硬件虚拟化(Hardware Virtualization
)——Hypervisor
将硬件物理资源划分为虚拟资源;另外,容器是操作系统虚拟化(OS Virtualization
)——容器将系统资源划分为虚拟资源。
虚拟机的额外开销
首先我们的目标是在一台物理机上运行 4 个业务相关应用。每种模型示例中都安装了一个操作系统或者 Hypervisor
(一种针对虚拟机高度优化后的操作系统)。截至目前,两个模型还很相似,但是也就到此为止了。
虚拟机模型将底层硬件资源划分到虚拟机当中。每个虚拟机都是包含了虚拟 CPU
、虚拟 RAM
、虚拟磁盘等资源的一种软件结构。因此,每个虚拟机都需要有自己的操作系统来声明、初始化并管理这些虚拟资源。但不幸的是,操作系统本身是有其额外开销的。例如,每个操作系统都消耗一点 CPU、一点 RAM、一点存储空间等。每个操作系统都需要独立的许可证,并且都需要打补丁升级,每个操作系统也都面临被攻击的风险。通常将这种现象称作 OS Tax
或者 VM Tax
,每个操作系统都占用一定的资源。
容器模型具有在宿主机操作系统中运行的单个内核。在一台主机上运行数十个甚至数百个容器都是可能的——容器共享一个操作系统/内核。这意味着只有一个操作系统消耗 CPU
、RAM
和存储资源,只有一个操作系统需要授权,只有一个操作系统需要升级和打补丁。同时,只有一个操作系统面临被攻击的风险。简言之,就是只有一份 OS
损耗!
在上述单台机器上只需要运行 4 个业务应用的场景中,也许问题尚不明显。但当需要运行成百上千应用的时候,就会引起质的变化。
另一个值得考虑的事情是启动时间。因为容器并不是完整的操作系统,所以其启动要远比虚拟机快。切记,在容器内部并不需要内核,也就没有定位、解压以及初始化的过程——更不用提在内核启动过程中对硬件的遍历和初始化了。这些在容器启动的过程中统统都不需要!唯一需要的是位于下层操作系统的共享内核是启动了的!最终结果就是,容器可以在 1s 内启动。唯一对容器启动时间有影响的就是容器内应用启动所花费的时间。
这就是容器模型要比虚拟机模型简洁并且高效的原因了。使用容器可以在更少的资源上运行更多的应用,启动更快,并且支付更少的授权和管理费用,同时面对未知攻击的风险也更小。
检查 Docker daemon
为了完成下面的示例,需要一个运行 Docker
的主机。对于大多数命令来说,无论是 Linux
还是 Windows
都没有差别。
通常登录 Docker
主机后的第一件事情是检查 Docker
是否正在运行:
[root@bogon ~]# docker version
Client: Docker Engine - Community
Version: 20.10.1
API version: 1.41
Go version: go1.13.15
Git commit: 831ebea
Built: Tue Dec 15 04:37:17 2020
OS/Arch: linux/amd64
Context: default
Experimental: true
Server: Docker Engine - Community
Engine:
Version: 20.10.1
API version: 1.41 (minimum version 1.12)
Go version: go1.13.15
Git commit: f001486
Built: Tue Dec 15 04:35:42 2020
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.4.3
GitCommit: 269548fa27e0089a8b8278fc4fc781d7f65a939b
runc:
Version: 1.0.0-rc92
GitCommit: ff819c7e9184c13b7c2607fe6c30ae19403a7aff
docker-init:
Version: 0.19.0
GitCommit: de40ad0
当命令输出中包含 Client
和 Server
的内容时,可以继续下面的示例。如果在 Server
部分中包含了错误码,这表示 Docker daemon
很可能没有运行,或者当前用户没有权限访问。
如果在 Linux
中遇到无权限访问的问题,需要确认当前用户是否属于本地 Docker UNIX
组。如果不是,可以通过 usermod -aG docker
来添加,然后退出并重新登录 Shell
,改动即可生效。
此外,可以使用如下命令查看 Docker
服务的状态:
[root@bogon ~]# sudo service docker status
Redirecting to /bin/systemctl status docker.service
● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
Active: active (running) since 一 2020-12-21 16:02:33 CST; 1h 46min ago
Docs: https://docs.docker.com
Main PID: 5617 (dockerd)
Tasks: 10
Memory: 163.6M
CGroup: /system.slice/docker.service
└─5617 /usr/bin/dockerd -H fd:// --containerd=/run/