容器化的概念很早就有了。2013 年 Docker 引擎 的出现使应用程序容器化变得更加容易。
根据 Stack Overflow 开发者调查-2020,Docker 是开发者 #1 最想要的平台、#2 最喜欢的平台,以及 #3 最流行的平台。
尽管 Docker 功能强大,但上手确并不容易。因此,本文将介绍从基础知识到更高层次容器化的的所有内容。读完本文之后,你应该能够:
- 容器化(几乎)任何应用程序
- 将自定义 Docker 镜像上传到在线仓库
- 使用 Docker Compose 处理多个容器
前提
- 熟悉 Linux 终端操作
- 熟悉 JavaScript(稍后的的演示项目用到了 JavaScript)
容器化和 Docker 简介
摘自 IBM,
容器化意味着封装或打包软件代码及其所有依赖项,以便它可以在任何基础架构上统一且一致地运行。
换句话说,容器化可以将软件及其所有依赖项打包在一个自包含的软件包中,这样就可以省略麻烦的配置,直接运行。
举一个现实生活的场景。假设你已经开发了一个很棒的图书管理应用程序,该应用程序可以存储所有图书的信息,还可以为别人提供图书借阅服务。
如果列出依赖项,如下所示:
- Node.js
- Express.js
- SQLite3
理论上应该是这样。但是实际上还要搞定其他一些事情。 Node.js 使用了 node-gyp
构建工具来构建原生加载项。根据 官方存储库 中的 安装说明,此构建工具需要 Python 2 或 3 和相应的的 C/C ++ 编译器工具链。
考虑到所有这些因素,最终的依赖关系列表如下:
- Node.js
- Express.js
- SQLite3
- Python 2 or 3
- C/C++ tool-chain
无论使用什么平台,安装 Python 2 或 3 都非常简单。在 Linux 上,设置 C/C ++ 工具链也非常容易,但是在 Windows 和 Mac 上,这是一项繁重的工作。
在 Windows 上,C++ 构建工具包有数 GB 之大,安装需要花费相当长的时间。在 Mac 上,可以安装庞大的 Xcode 应用程序,也可以安装小巧的 Xcode 命令行工具 包。
不管安装了哪一种,它都可能会在 OS 更新时中断。实际上,该问题非常普遍,甚至连官方仓库都专门提供了 macOS Catalina 的安装说明。
这里假设你已经解决了设置依赖项的所有麻烦,并且已经准备好开始。这是否意味着现在开始就一帆风顺了?当然不是。
如果你使用 Linux 而同事使用 Windows 该怎么办?现在,必须考虑如何处理这两个不同的操作系统不一致的路径,或诸如 nginx 之类的流行技术在 Windows 上未得到很好的优化的事实,以及诸如 Redis 之类的某些技术甚至都不是针对 Windows 预先构建的。
即使你完成了整个开发,如果负责管理服务器的人员部署流程搞错了,该怎么办?
所有这些问题都可以通过以下方式解决:
- 在与最终部署环境匹配的隔离环境(称为容器)中开发和运行应用程序。
- 将你的应用程序及其所有依赖项和必要的部署配置放入一个文件(称为镜像)中。
- 并通过具有适当授权的任何人都可以访问的中央服务器(称为仓库)共享该镜像。
然后,你的同事就可以从仓库中下载镜像,可以在没有平台冲突的隔离环境中运行应用,甚至可以直接在服务器上进行部署,因为该镜像也可以进行生产环境配置。
这就是容器化背后的想法:将应用程序放在一个独立的程序包中,使其在各种环境中都可移植且可回溯。
现在的问题是:Docker 在这里扮演什么角色?
正如我之前讲的,容器化是一种将一切统一放入盒子中来解决软件开发过程中的问题的思想。
这个想法有很多实现。Docker 就是这样的实现。这是一个开放源代码的容器化平台,可让你对应用程序进行容器化,使用公共或私有仓库共享它们,也可以编排它们。
目前,Docker 并不是市场上唯一的容器化工具,却是最受欢迎的容器化工具。我喜欢的另一个容器化引擎是 Red Hat 开发的 Podman。其他工具,例如 Google 的 Kaniko,CoreOS 的 rkt 都很棒,但和 Docker 还是有差距。
此外,如果你想了解容器的历史,可以阅读 A Brief History of Containers: From the 1970s Till Now,它描述了该技术的很多重要节点。
怎样安装 Docker
Docker 的安装因使用的操作系统而异。但这整个过程都非常简单。
Docker可在 Mac、Windows 和 Linux 这三个主要平台上完美运行。在这三者中,在 Mac 上的安装过程是最简单的,因此我们从这里开始。
怎样在 macOS 里安装 Docker
在 Mac 上,要做的就是跳转到官方的下载页面,然后单击Download for Mac(stable)按钮。
你会看到一个常规的 Apple Disk Image 文件,在该文件的内有 Docker 应用程序。所要做的就是将文件拖放到 Applications 目录中。
只需双击应用程序图标即可启动 Docker。应用程序启动后,将看到 Docker 图标出现在菜单栏上。
现在,打开终端并执行 docker --version
和 docker-compose --version
以验证是否安装成功。
怎样在 Windows 上安装 Docker
在 Windows 上,步骤几乎相同,当然还需要执行一些额外的操作。安装步骤如下:
- 跳转到此站点,然后按照说明在 Windows 10 上安装 WSL2。
- 然后跳转到官方下载页面 并单击 Download for Windows(stable) 按钮。
- 双击下载的安装程序,然后使用默认设置进行安装。
安装完成后,从开始菜单或桌面启动 Docker Desktop。Docker 图标应显示在任务栏上。
现在,打开 Ubuntu 或从 Microsoft Store 安装的任何发行版。执行 docker --version
和 docker-compose --version
命令以确保安装成功。
也可以从常规命令提示符或 PowerShell 访问 Docker,只是我更喜欢使用 WSL2。
怎样在 Linux 上安装 Docker
在 Linux 上安装 Docker 的过程有所不同,具体操作取决于你所使用的发行版,它们之间差异可能更大。但老实说,安装与其他两个平台一样容易(如果不能算更容易的话)。
Windows 或 Mac 上的 Docker Desktop 软件包是一系列工具的集合,例如Docker Engine
、Docker Compose
、Docker Dashboard
、Kubernetes
和其他一些好东西。
但是,在 Linux 上,没有得到这样的捆绑包。可以手动安装所需的所有必要工具。 不同发行版的安装过程如下:
- 如果你使用的是 Ubuntu,则可以遵循官方文档中的 在 Ubuntu 上安装 Docker 引擎 部分。
- 对于其他发行版,官方文档中提供了 不同发行版的安装指南。
- 如果你使用的发行版未在文档中列出,则可以参考从二进制文件安装 Docker 引擎指南。
- 无论参考什么程序,都必须完成一些非常重要的 Linux 的安装后续步骤。
- 完成 docker 安装后,必须安装另一个名为 Docker Compose 的工具。 可以参考官方文档中的 Install Docker Compose 指南。
安装完成后,打开终端并执行 docker --version
和 docker-compose --version
以确保安装成功。
尽管无论使用哪个平台,Docker 的性能都很好,但与其他平台相比,我更喜欢 Linux。在本文中,我将使用Ubuntu 20.10 或者 Fedora 33。
一开始就需要阐明的另一件事是,在本文中,我不会使用任何 GUI 工具操作 Docker。
我在各个平台用过很多不错的 GUI 工具,但是介绍常见的 docker 命令是本文的主要目标之一。
初识 Docker - 介绍 Docker 基本知识
已经在计算机上启动并运行了 Docker,现在该运行第一个容器了。打开终端并执行以下命令:
docker run hello-world
# Unable to find image 'hello-world:latest' locally
# latest: Pulling from library/hello-world
# 0e03bdcc26d7: Pull complete
# Digest: sha256:4cf9c47f86df71d48364001ede3a4fcd85ae80ce02ebad74156906caff5378bc
# Status: Downloaded newer image for hello-world:latest
#
# Hello from Docker!
# This message shows that your installation appears to be working correctly.
#
# To generate this message, Docker took the following steps:
# 1. The Docker client contacted the Docker daemon.
# 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
# (amd64)
# 3. The Docker daemon created a new container from that image which runs the
# executable that produces the output you are currently reading.
# 4. The Docker daemon streamed that output to the Docker client, which sent it
# to your terminal.
#
# To try something more ambitious, you can run an Ubuntu container with:
# $ docker run -it ubuntu bash
#
# Share images, automate workflows, and more with a free Docker ID:
# https://hub.docker.com/
#
# For more examples and ideas, visit:
# https://docs.docker.com/get-started/
hello-world 镜像是使用 Docker 进行最小化容器化的一个示例。它有一个从 hello.c 文件编译的程序,负责打印出终端看到的消息。
现在,在终端中,可以使用 docker ps -a
命令查看当前正在运行或过去运行的所有容器:
docker ps -a
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# 128ec8ceab71 hello-world "/hello" 14 seconds ago Exited (0) 13 seconds ago exciting_chebyshev
在输出中,使用 hello-world
镜像运行了名为 exciting_chebyshev
的容器,其容器标识为 128ec8ceab71
。它已经在 Exited (0) 13 seconds ago
,其中 (0)
退出代码表示在容器运行时未发生任何错误。
现在,为了了解背后发生的事情,必须熟悉 Docker 体系结构和三个非常基本的容器化概念,如下所示:
- 容器
- 镜像
- 仓库
我已经按字母顺序列出了这三个概念,并且将从列表中的第一个开始介绍。
什么是容器?
在容器化世界中,没有什么比容器的概念更基础的了。
官方 Docker resources 网站说 -
容器是应用程序层的抽象,可以将代码和依赖项打包在一起。容器不虚拟化整个物理机,仅虚拟化主机操作系统。
可以认为容器是下一代虚拟机。
就像虚拟机一样,容器是与主机系统是彼此之间完全隔离的环境。它也比传统虚拟机轻量得多,因此可以同时运行大量容器,而不会影响主机系统的性能。
容器和虚拟机实际上是虚拟化物理硬件的不同方法。两者之间的主要区别是虚拟化方式。
虚拟机通常由称为虚拟机监控器的程序创建和管理,例如 Oracle VM VirtualBox,VMware Workstation,KVM,Microsoft Hyper-V 等等。 该虚拟机监控程序通常位于主机操作系统和虚拟机之间,充当通信介质。
每个虚拟机都有自己的 guest 操作系统,该操作系统与主机操作系统一样消耗资源。
在虚拟机内部运行的应用程序与 guest 操作系统进行通信,该 guest 操作系统在与虚拟机监控器进行通信,后者随后又与主机操作系统进行通信,以将必要的资源从物理基础设施分配给正在运行的应用程序。
虚拟机内部运行的应用程序与物理基础设施之间存在很长的通信链。在虚拟机内部运行的应用程序可能只拥有少量资源,因为 guest 操作系统会占用很大的开销。
与虚拟机不同,容器以更智能的方式完成虚拟化工作。在容器内部没有完整的 guest 操作系统,它只是通过容器运行时使用主机操作系统,同时保持隔离 – 就像传统的虚拟机一样。
容器运行时(即 Docker)位于容器和主机操作系统之间,而不是虚拟机监控器中。容器与容器运行时进行通信,容器运行时再与主机操作系统进行通信,以从物理基础设施中获取必要的资源。
由于消除了整个主机操作系统层,因此与传统的虚拟机相比,容器的更轻量,资源占用更少。
为了说明这一点,请看下面的代码片段:
uname -a
# Linux alpha-centauri 5.8.0-22-generic #23-Ubuntu SMP Fri Oct 9 00:34:40 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
docker run alpine uname -a
# Linux f08dbbe9199b 5.8.0-22-generic #23-Ubuntu SMP Fri Oct 9 00:34:40 UTC 2020 x86_64 Linux
在上面的代码片段中,在主机操作系统上执行了 uname -a
命令以打印出内核详细信息。然后在下一行,我在运行 Alpine Linux 的容器内执行了相同的命令。
从输出中可以看到,该容器确实正在使用主机操作系统中的内核。这证明了容器虚拟化主机操作系统而不是拥有自己的操作系统这一点。
如果你使用的是 Windows 计算机,则会发现所有容器都使用 WSL2 内核。发生这种情况是因为 WSL2 充当了 Windows 上 Docker 的后端。在 macOS 上,默认后端是在 HyperKit 虚拟机管理程序上运行的 VM。
什么是 Docker 镜像?
镜像是分层的自包含文件,充当创建容器的模板。它们就像容器的冻结只读副本。 镜像可以通过仓库进行共享。
过去,不同的容器引擎具有不同的镜像格式。但是后来,开放式容器计划(OCI)定义了容器镜像的标准规范,该规范被主要的容器化引擎所遵循。这意味着使用 Docker 构建的映像可以与 Podman 等其他运行时一起使用,而不会有兼容性问题。
容器只是处于运行状态的镜像。当从互联网上获取镜像并使用该镜像运行容器时,实际上是在先前的只读层之上创建了另一个临时可写层。
在本文的后续部分中,这一概念将变得更加清晰。但就目前而言,请记住,镜像是分层只读文件,其中保留着应用程序所需的状态。
什么是仓库?
已经了解了这个难题的两个非常重要的部分,即 Containers 和 Images 。 最后一个是 Registry。
镜像仓库是一个集中式的位置,可以在其中上传镜像,也可以下载其他人创建的镜像。 Docker Hub 是 Docker 的默认公共仓库。另一个非常流行的镜像仓库是 Red Hat 的 Quay。
在本文中,我将使用 Docker Hub 作为首选仓库。
可以免费在 Docker Hub 上共享任意数量的公共镜像。供世界各地的人们下载免费使用。
除了 Docker Hub 或 Quay,还可以创建自己的镜像仓库来托管私有镜像。计算机中还运行着一个本地仓库,该仓库缓存从远程仓库提取的镜像。
Docker 架构概述
既然已经熟悉了有关容器化和 Docker 的大多数基本概念,那么现在是时候了解 Docker 作为软件的架构了。
该引擎包括三个主要组件:
-
Docker 守护程序: 守护程序(
dockerd
)是一个始终在后台运行并等待来自客户端的命令的进程。守护程序能够管理各种 Docker 对象。 -
Docker 客户端: 客户端(
docker
)是一个命令行界面程序,主要负责传输用户发出的命令。 - REST API: REST API 充当守护程序和客户端之间的桥梁。使用客户端发出的任何命令都将通过 API 传递,最终到达守护程序。
根据官方 文档,
“ Docker 使用客户端-服务器体系结构。Docker client 与 Docker daemon 对话,daemon 繁重地构建、运行和分发 Docker 容器”。
作为用户,通常将使用客户端组件执行命令。然后,客户端使用 REST API 来访问长期运行的守护程序并完成工作。
全景图
好吧,说的够多了。 现在是时候了解刚刚学习的所有这些知识如何和谐地工作了。在深入解释运行 docker run hello-world
命令时实际发生的情况之前,看一下下面的图片:
该图像是在官方文档中找到的图像的略微修改版本。 执行命令时发生的事件如下: