第 3 章:发布微服务

本章内容概览:

  • 镜像与容器的基本区别
  • 如何在开发环境中利用 Docker
  • 将你的微服务封装并发布为 Docker 镜像
  • 构建私有容器仓库
  • 在容器环境中部署你的微服务

到本书的最后,我们将学习如何将多个微服务部署到生产环境中的 Kubernetes 集群。然而,在部署整个微服务应用之前,我们需要先掌握如何封装和发布单个微服务。本章将指导你把第 2 章中开发的视频流微服务发布出去,为部署到集群做好准备。

要在云中运行的集群中部署微服务,我们需要先将微服务发布到集群能够访问的地方。这需要我们将代码、资产和依赖项打包成一个整体。接下来,我们需要在云中为这些包寻找一个托管位置。为此,我们会建立一个容器仓库。如果你对容器还不熟悉,别担心,我们会很快解释这一概念。

在本书中,我们将模拟为公司构建专用应用程序的过程。出于安全性和隐私性的考虑,我们选择建立一个私有的容器仓库,而不使用公共仓库。我们将在 Microsoft Azure 上手动创建此容器仓库,并在第 7 章教你如何通过编程方式构建自己的仓库。本章结束时,我们将验证是否能从远程容器仓库直接将已发布的微服务实例化到开发计算机上。

3.1 Docker:一种新工具

本章将引入 Docker(详见表 3.1),这是一个关键的新工具。从此章开始,Docker 将被广泛使用,你需要掌握一些基本技能来理解其工作原理,并在问题出现时进行问题排查。

表 3.1 介绍的新工具

工具版本描述
Docker24.0.5用于封装、发布和测试微服务的主要工具。

Docker 支持 Linux、macOS 和 Windows 10+。如果你在 Windows 上使用,需要首先安装 WSL2(Windows Subsystem for Linux),具体安装方法请见第 3.7.1 节。

3.2 获取代码

本章继续使用第 2 章的示例 2,即视频流微服务。要跟进本章内容,你需要下载或克隆相应的代码库。

代码的 ZIP 文件可从以下链接下载: http://mng.bz/XqEp 。你也可以通过以下 Git 命令克隆代码库:

git clone https://github.com/bootstrapping-microservices-2nd-edition/chapter-3

关于安装和使用 Git 的更多帮助,请回顾第 2 章。如遇到代码问题,请在 GitHub 仓库中提交问题。

3.3 容器是什么?

简而言之,容器就像其名字所示的那样,是用来装东西的。具体来说,在本场景中,它用于封装(或托管)一个微服务。

定义:容器是一种虚拟化服务器的技术。

更具体地说,容器提供了一种虚拟化操作系统和硬件的方法。这允许我们抽象(或 虚拟化 )所需的微服务资源。容器能够在一台机器上划分资源,让我们能够在众多此类服务中共享这些资源,是现代技术中帮助实现微服务成本效益的关键工具之一。

容器常与虚拟机(VM)相比较。无论是 VM 还是容器,都允许我们隔离微服务,防止它们之间相互干扰。在容器被发明之前,我们使用 VM 来运行服务,如果条件允许,现在仍可以选择这样做。图 3.1 对比了 VM 和容器的差异,帮助你直观理解两者之间的区别。

图 3.1 比较虚拟机和容器
图 3.1 比较虚拟机和容器

正如图 3.1 所示,VM 比容器更重,因为它包含了一份完整的操作系统副本,并在完全虚拟化的硬件上运行。相比之下,容器虚拟化了操作系统和硬件,因此体积更小,负载更轻,这使我们能够更有效地利用计算资源。

最终,我们的目标是在 Kubernetes 集群上运行多个此类容器。但目前,我们的焦点是实例化一个单独的容器,来托管我们在上一章中创建的视频流微服务。

3.4 什么是镜像?

众所周知,镜像指的是事物的快照。这个词在多个场景中都有应用,可以是一张照片的镜像,也可以是虚拟机硬盘的镜像快照。在本书中,我们特指 Docker 镜像。

定义:镜像是服务器(本例中为微服务)的一个可启动快照,包含了运行该服务所需的所有代码、依赖和资产。

在本章的示例中,我们创建了视频流微服务的镜像。镜像具有不可变性,这意味着一旦创建后,就无法更改。与之相对的是,容器是可变的。镜像一旦实例化为容器后,其文件系统的内容就可以被修改。我们将在第 5 章利用这一点来优化开发体验。

认识到镜像的不可变性非常重要。我们可能已经对镜像进行了测试和安全检查。由于镜像不可被篡改,我们可以确信之前的测试和安全检查仍然有效。

你可以将镜像视为微服务的休眠状态,它是存储微服务待运行状态的一种方式,随时准备被实例化到应用程序中并启动为容器。

图 3.2 展示了如何从镜像启动容器。镜像本身包含了启动容器所需的一切:微服务的代码、依赖项以及其他执行任务所需的资产和资源。

图 3.2 在云中运行我们的微服务,我们将实例化其 Docker 镜像到一个容器中。
图 3.2 在云中运行我们的微服务,我们将实例化其 Docker 镜像到一个容器中。

不久后,我们将构建我们的微服务的镜像并将其部署为容器运行。在此之前,让我们更深入地了解 Docker。

3.5 为什么选择 Docker?

我相信你已经听说过 Docker,这可能也是你购买本书的原因之一。目前,几乎所有构建云应用的开发者都在使用或希望使用 Docker。让我们探讨一下其背后的原因。

Docker 在软件行业已几乎无处不在,尽管有替代品,但作为一种打包和部署容器的技术,Docker 已赢得了主流认可和广泛支持。

Docker 甚至正在扩展到其他领域。例如,我了解到有人正在使用 Docker 将应用部署到物联网(IoT)设备上。那么,Docker 究竟为我们提供了哪些便利?简而言之,Docker 是我们用来打包和发布微服务的工具。

我倾向于将 Docker 视为通用包管理器——一个统治所有的包管理器!通常你可能不会这样看待 Docker,但这种看法其实非常合理。“包管理器"这一点显而易见,我们使用 Docker 来打包和发布我们的工作成果。而我说它是"通用的”,是因为它支持多种不同的技术栈。Docker 是开源的,你可以在这里找到命令行界面(CLI)工具的代码: https://github.com/docker/cli ,你还可以在这里看到 Docker 公司的其他开源项目: www.docker.com/community/open-source

标准化你的开发环境

Docker 也非常适用于标准化开发环境,确保所有开发者都在使用相同的环境。反过来,这也与生产环境保持一致,最大限度地确保开发中的代码在生产中也能正常运行,为开发者提供更好的机会在代码部署前发现并修复问题。

3.6 为何选择使用 Docker?

我们来详细探讨使用 Docker 的理由。通过 Docker,我们可以实现以下关键任务:

  • 将微服务打包为 Docker 镜像
  • 将这些镜像发布到私有容器仓库
  • 在容器中运行微服务

这些任务中,最关键的是能够在生产环境中运行微服务,这需要我们先将其打包并发布。当前我们的微服务还未准备好部署到生产环境,因此我们会专注于学习必要的 Docker 命令,以便在开发环境中打包、发布和测试我们的镜像。

图 3.3 展示了我们需要进行的操作。我们会将视频流微服务的 Node.js 项目(如图中左侧所示)打包成 Docker 镜像,并将其发布到我们的私有容器仓库。之后,这些服务将部署到 Kubernetes 集群中,具体操作将在第 6 章介绍。

图 3.3 将 Docker 镜像发布到我们的私有容器仓库的云中
图 3.3 将 Docker 镜像发布到我们的私有容器仓库的云中

3.7 在开发环境中整合 Docker

在我们开始使用 Docker 之前,需要先升级我们的开发环境。本节将指导你在本地计算机上安装 Docker 并确保它准备就绪。

图 3.4 显示了安装 Docker 后的开发环境样貌。尽管我们的 Node.js 微服务将在 Docker 下运行,但我们并非总是需要这样做。在测试单个微服务时,我们仍可选择在主操作系统上直接运行它,正如我们在第 2 章中所做的那样。

图 3.4 扩展我们的开发环境以在容器中运行我们的微服务
图 3.4 扩展我们的开发环境以在容器中运行我们的微服务

使用 Docker 打包微服务非常有用,因为它允许我们在本地测试微服务的运行情况,无论是在发布前还是发布后。对于在 Kubernetes 上表现不佳的任何微服务,这种测试能力都是非常有价值的。我们将在第 11 章进一步讨论这个问题。

3.7.1 安装 Docker

要安装 Docker,请访问 Docker 官网 https://docs.docker.com 。在该网站上,找到下载/安装链接,并按照指示安装适用于你平台的 Docker Desktop。

在 Windows 平台上,你需要先安装 WSL2(Windows Subsystem for Linux)。根据表 3.2 中的说明操作。

表 3.2 Docker 支持的平台

平台描述
Linux/macOS Windows访问 Docker 官网 https://docs.docker.com ,点击下载/安装链接,并按照说明在你的系统上安装 Docker Desktop。
Windows在安装 Docker 前,必须先安装 WSL2。安装 WSL2 的指南,请访问 https://docs.microsoft.com/en-us/windows/wsl/install 。安装 WSL2 后,你可以根据 https://docs.docker.com/desktop/windows/install/ 的指南安装 Docker。

Windows 用户:Linux 终端还是 Windows 终端?

作为 Windows 用户,你需要安装 WSL2 和虚拟 Linux 才能使用 Docker。安装完 WSL2 和 Docker 后,你将能够通过 Windows 终端来使用 Docker。实际上,我大部分工作都是在 Windows 上使用 Windows 终端完成的。(你可以在 Microsoft Store 免费安装 Windows 终端。)

虽然我们书中介绍的许多命令都可以通过 Windows 终端运行,但书中后面的某些命令示例只能在 Unix 风格的终端中执行。例如,在第 8 章中,我们将运行各种 shell 脚本,这些脚本无法在 Windows 终端中执行。这些命令应在你的虚拟 Linux 终端中运行,该终端由 WSL2 支持。

现在不必过分担心这些细节。如果你偏好使用 Windows 终端,可以继续使用。如果你希望更多地使用 WSL2 下的虚拟 Linux,那也完全没有问题。稍后在本书中,我将指出哪些示例需要使用 Linux 执行。

3.7.2 检查 Docker 安装情况

安装 Docker 后,你可以通过终端检查其是否正常安装,方法是查询版本信息:

docker --version

如果你安装的版本与我撰写此书时的版本一致,输出应如下所示:

Docker version 24.0.5, build ced0996

如果你使用的是 Docker 的更新版本,也无需担心。它很可能是向后兼容的。

3.8 打包微服务

安装 Docker 之后,我们可以开始使用它来打包我们的微服务以便于部署。最终目标是将微服务部署到生产环境,但在此之前,我们需要先完成打包和准备工作。打包微服务的步骤包括:

  1. 为微服务创建一个 Dockerfile。
  2. 将微服务打包为 Docker 镜像。
  3. 通过启动容器来测试已发布的镜像。

3.8.1 创建 Dockerfile

为每个想要创建的 Docker 镜像,我们都需要编写一个 Dockerfile。你可以在示例 1 项目中找到这个文件,如图 3.5 所示。

example-1
├── .dockerignore
├── Dockerfile
├── package-lock.json
├── package.json
├── README.MD
├── node_modules
└── src
    └── index.js
    └── videos
        └── SampleVideo_1280x720_1mb.mp4
图 3.5 显示 Dockerfile 位置的 example-1 项目布局
图 3.5 显示 Dockerfile 位置的 example-1 项目布局

Dockerfile 是 Docker 创建镜像的规范文件。我倾向于将 Dockerfile 视为一个脚本,它包含了构建镜像的具体指令。你可以在图 3.6 中看到这一点。

图 3.6 Dockerfile 是一个脚本,指定如何构建我们的 Docker 镜像。
图 3.6 Dockerfile 是一个脚本,指定如何构建我们的 Docker 镜像。

请注意,在图 3.6 中,我们正在把示例视频复制到我们的镜像中。虽然我们不会在最终生产版本中这样做,但在这个示例中,这样做是有帮助的——因为我们暂时没有其他方法来存储这个视频。

如果只有一个视频,那么视频流应用程序可能会显得单调,但我们将在第 4 章中解决这个问题。现在,这可以作为一个很好的示例,说明我们的镜像中不仅可以包含代码。将其他类型的资产复制到镜像中对 Docker 来说完全没有问题!

清单 3.1(chapter-3/example-1/Dockerfile)展示了我们视频流微服务的 Dockerfile。这里的内容并不多,但它是一个很好的 Node.js 应用程序的 Dockerfile 示例。请仔细阅读,并想象每一行代码如何将文件复制到最终的镜像中。

FROM node:18.17.1             # 设置我们新镜像的基础镜像,这允许我们基于现有镜像产生新镜像

WORKDIR /usr/src/app          # 设置镜像中的工作目录。其他操作的路径都相对于此目录。
COPY package*.json ./         # 将 package.json 和 package-lock.json 复制到镜像中
RUN npm ci --omit=dev         # 仅安装生产环境所需的依赖
COPY ./src ./src              # 复制此微服务的源代码
COPY ./videos ./videos        # 复制我们的示例视频

CMD npm start                 # 使用“npm start”命令启动微服务,该命令在上一章介绍过

在清单 3.1 中,FROM 指令指定了我们的基础镜像,我们选择了 node:18.17.1 作为起点,它包含了 Node.js 版本 18.17.1 的运行环境。

如果你使用的是其他语言或框架,你会选择不同的基础镜像。选择一个与你的技术栈相匹配的镜像至关重要。

选择合适的基础镜像非常有用,你可以从 Docker Hub( https://hub.docker.com )上选择众多公开镜像,或者创建自己的自定义基础镜像。这意味着我们可以重用现有镜像,在本书的后续章节中,我们还会看到如何重用第三方镜像的示例。

清单 3.1 中的 COPY 指令用于将文件复制到镜像中。你可以看到 package.jsonpackage-lock.json、源代码及示例视频都被复制进了镜像。

RUN 指令同样重要,它允许我们在构建过程中在镜像内运行命令,以进行更改、安装依赖项和执行其他设置任务。在这个示例中,我们使用 RUN 来安装 npm 依赖项,将它们固化到镜像中。

最后,CMD 指令设置了容器实例化时要调用的命令。这是我们指定使用我们在第 2 章中添加到 package.json 文件的 npm start 脚本来运行 Node.js 应用的方式。

.dockerignore 文件

你可能注意到示例 1 项目中有一个 .dockerignore 文件,它在图 3.5 中被提到。包含这个文件在你的 Docker 项目中是可选的,但非常有用,因为你可以在这里指定 Docker 在构建过程中应该忽略的文件和目录。如果你发现 Docker 构建过程异常缓慢,可能是因为它处理了过多的文件。

通常,我们应该将 .git.githubnode_modules 等目录添加到 .dockerignore 文件中。这些目录可能非常庞大,并且不是构建过程所必需的(node_modules 通常在构建过程中在镜像内重新生成)。如果你忘记让 Docker 忽略这些目录,随着项目的增大,Docker 构建的速度会越来越慢。

3.8.2 打包并检查 Docker 镜像

创建了 Dockerfile 后,我们可以开始将微服务打包成一个 随时可运行 的镜像。我们将使用 docker build 命令来构建镜像,该命令以我们的 Dockerfile 为输入,Dockerfile 中包含了构建镜像所需的指令。图 3.7 展示了这个过程。

图 3.7 <strong>docker build</strong> 命令根据我们的 Dockerfile 生成一个 Docker 镜像。
图 3.7 docker build 命令根据我们的 Dockerfile 生成一个 Docker 镜像。

注意 在我们可以将微服务部署到生产环境之前,我们必须能够将其打包到 Docker 镜像中。

cd chapter-3/example-1
docker build -t video-streaming --file Dockerfile .

现在来到有趣的部分,是时候从我们的微服务创建一个镜像了。要跟进,请确保你有一个像清单 3.1 中显示的 Dockerfile 和一个 Node.js 项目。你可以创建自己的或使用 GitHub 上第 3 章代码仓库中的 example-1(参见第 3.2 节)。

准备好后,打开一个终端,并更改到 chapter-3/example-1 目录(或包含你的代码和 Dockerfile 的任何目录)。现在调用 docker build,如下所示:

cd chapter-3/example-1
docker build -t video-streaming --file Dockerfile .

不要忘记在该命令的末尾添加句点(小数点)!这很容易遗漏,但这是告诉命令在当前目录操作的。

让我们了解这个命令的各个部分:

docker build -t video-streaming --file Dockerfile .

当你调用 docker build 时,你将看到基础镜像的各个部分被下载。这种下载只发生在第一次;随后,你将在计算机上缓存基础镜像,因此不会再次下载(至少直到你稍后在第 3.9.3 节中删除所有本地镜像为止)。完成后,你应该在输出的末尾看到类似这样的内容:

[+] Building 2.0s (11/11) FINISHED
--snip--
 => => writing image sha256:2c68c7c4e2989f9aaeacb30abaedf...
 => => naming to docker.io/library/video-streaming

这告诉你镜像已成功构建。输出显示你的镜像的唯一 ID,并显示你为其设置的标签。

注意 当你为自己调用这个命令时,你将看到不同的输出,因为分配给你的镜像的 ID 将与分配给我的镜像的 ID 不同。

因为它是唯一的 ID,所以每次你创建新镜像时都会不同。如果你愿意,可以记下这个 ID,并在以后的 Docker 命令中使用它来引用镜像。然而,你真正不需要这样做,因为我们用有意义的名称(video-streaming)标记了它,我们可以使用这个名称而不是 ID。

还要注意输出中的版本自动设置为 latest,因为我们没有为其指定任何内容。在第 8 章中,我们将自动设置此版本,作为我们持续部署管道的一部分。这将区分我们生产的每个新版本的镜像,因为我们迭代更新我们的代码并构建新镜像。

需要注意的其他几点如下:

  • -t 参数允许我们标记或命名我们的镜像。你会希望这样做;否则,你将不得不通过其唯一 ID 引用你的镜像,即你在前面输出中看到的那串大丑陋的数字。拥有一个我们可以更容易记住的标签比依赖 ID 要好。
  • –file 参数指定使用的 Dockerfile 的名称。从技术上讲,这是不必要的,因为它默认为名为 Dockerfile 的文件。我明确包含这个,以便你了解它,并且它是我们将在第 5 章中利用的东西。在那一章中,我们将分离我们的 Dockerfile,以便为开发和生产拥有不同的版本。
  • 不要忘记最后的句点!容易遗漏。句点告诉构建命令对当前目录进行操作。这意味着 Dockerfile 中的任何指令都是相对于当前工作目录的。更改这个目录使我们可以将 Dockerfile 存储在与项目资产不同的目录中。这有时可能很有用,但我们现在不需要这个功能。

这是 docker build 命令的通用格式:

docker build -t <你为镜像命名的名称> --file <Dockerfile 的路径>
➥ <项目路径>

我们可以插入我们的微服务的特定名称作为镜像名称,Dockerfile 的路径,以及项目文件夹的路径。构建我们的镜像后,我们现在应该检查它是否正常。我们可以使用此命令列出我们的本地镜像:

docker image list

这会列出我们本地计算机上的镜像。如果我们前一节中的 docker build 命令成功完成,我们应该看到我们刚刚构建的镜像:

REPOSITORY        TAG       IMAGE ID       CREATED         SIZE
video-streaming   latest    bffcbcc3f39c   6 seconds ago   1.1GB

如果你已经在使用 Docker 创建其他镜像或探索 Docker Hub(稍后在本章中的“探索其他容器”边栏中看到)上的许多公开可用的镜像,你可能会在此列表中看到其他镜像。

注意前面输出中的列。在 REPOSITORY 列下,你可以看到 video-streaming,其中 video-streaming 是我们刚刚创建的微服务的镜像。

TAG 是下一列,通常显示镜像的版本号。因为我们没有为我们的 video-streaming 镜像特别选择一个版本,所以它自动分配了版本 latest。

下一列是 IMAGE ID,显示每个镜像的唯一 ID。请注意,我们的 video-streaming 镜像的 ID 是从之前的 build 命令输出中的相同 ID 的缩写版本。再次,请期望你的镜像的唯一 ID 与你在这里看到的不同。此输出的其他列包括 CREATED,告诉你镜像是何时创建的,以及 SIZE,显示你镜像的大小。

3.8.3 在容器中启动微服务

在我们发布新创建的 Docker 镜像之前,我们应在开发计算机上进行测试,以确保一切正常。一旦将微服务打包为 Docker 镜像,我们可以使用 docker run 命令将其实例化为容器,如图 3.8 所示。这样我们就能在本地创建视频流微服务的实例,并通过网络浏览器进行测试。

图 3.8 docker run 命令生成在容器中运行的微服务实例。
图 3.8 docker run 命令生成在容器中运行的微服务实例。

准备好后,执行以下命令从镜像实例化你的微服务:

cd chapter-3/example-1
docker run -d -p 3000:3000 -e PORT=3000 video-streaming

这里有许多操作,让我们解释一下该命令的各个部分:

docker run -d -p 3000:3000 -e PORT=3000 video-streaming
  • -d 参数使容器在分离模式(后台运行)下运行,我们无法直接看到它的日志。如果我们省略这个参数,容器将在前台运行,我们将直接看到它的输出,这有时候是有用的,但也会占用我们的终端。
  • -p 参数绑定宿主操作系统的端口与容器的端口。这是一种端口转发;发送到开发计算机上端口 3000 的网络流量将被转发到容器内的端口 3000。我们这样设置是因为配置了微服务在端口 3000 上监听。
  • -e 参数设置环境变量 PORT 为 3000,配置微服务在该端口上运行。虽然这里使用 3000 并不是必须的,我们几乎可以使用任何数字,但 3000 是开发/测试 HTTP 服务器时的常用端口。
  • 最后一个参数 video-streaming 是我们给镜像起的名字,用于指定将哪个镜像(如果有多个的话)实例化。这与我们之前在 docker build 命令中使用 -t 参数设置的名称相对应。

运行该命令后,你应该会看到输出的容器唯一 ID。这是我运行命令时的输出:

460a199466896e02dd1ed601f9f6b132dd9ad9b42bbd3df351460e5eeacbe6ce

如果你看到了类似的输出,这意味着你的微服务已成功启动。运行此命令时,你将看到不同的输出,因为你的容器将具有不同的唯一 ID。你将需要这个 ID 来执行未来与容器相关的 Docker 命令。

检查容器

我们现在有了一个正在运行的容器,让我们检查一下它是否正常运行。要显示你拥有的容器,请执行此命令:

docker container list

这是输出的简化版本:

CONTAINER ID  IMAGE            STATUS         PORTS
460a19946689  video-streaming  Up 20 seconds  0.0.0.0:3000->3000/tcp

你的输出会有所不同,因为我删除了 COMMAND、CREATED 和 NAMES 列以使其适合。但你可以执行完整命令自己查看这些。

注意 CONTAINER ID 列。这显示了容器的唯一 ID。它是我们在 docker run 命令中得到的更长 ID 的简化版本。这两个都是你容器的唯一标识,我们将使用这个 ID 来标识容器,执行 Docker 命令时用到。

检查我们的微服务

我们已经成功从镜像中实例化了一个容器并检查它是否在运行。但我们如何知道容器内的微服务是否功能正常?它可能正在产生各种错误,我们还不知道。让我们检查微服务的日志,看看它在告诉我们什么:

docker logs 460a19946689

等一下,别急!你不能直接使用我的容器唯一 ID 执行这个命令。记住,ID 会因为你计算机上创建的容器而异。如果你按照上面的方式精确调用它,你将得到一个错误。因此,请注意你自己容器的 ID,并像这样执行命令,插入你自己的容器 ID:

docker logs <container-id>

现在你应该看到你的微服务的日志。如果你运行的是第 3 章代码仓库中的 example-1 代码,你应该看到类似这样的内容:

Microservice listening on port 3000, point your browser at http://localhost:3000/video

成功了!我们构建了一个镜像,将其实例化为一个容器,并验证了我们的微服务是可操作的。

现在,让我们在网络浏览器中测试这一点。将你的浏览器指向 http://localhost:3000/video 。你应该可以看到流视频,效果应该与我们在第 2 章中测试的相同。

这是因为我们使用了 docker run 命令的 -p 参数将开发计算机上的端口 3000(假设此端口尚未被占用)转发到容器中的端口 3000。我们的微服务正在监听端口 3000 并做出了响应!

显然,我们还可以做更多的测试以验证我们的代码,但我们将在以后的章节中讨论。在第 9 章中,我们将看到如何应用自动化、代码驱动的测试到我们的微服务上。然后,在第 11 章中,我们将了解如何监控我们的微服务,如何在发现问题时进行调试,以及我们可以使用的构建容错系统的技术。但现在,我们已经准备好发布我们的镜像了!

探索其他容器

你知道吗,你可以使用 docker run 命令轻松运行任何公开的镜像吗?我们稍后在本书中将使用的两个镜像是 mongodbrabbitmq。尝试为自己运行以下命令,获取一个 即时数据库,可在 localhost:27017 上使用:

docker run -p 27017:27017 mongo:latest

有许多公开可用的镜像在线上,你可以不需要任何账户就能访问它们。在 https://hub.docker.com 上搜索 Docker Hub 找到更多。

3.8.4 调试容器

当我们在本地运行容器时,尤其是出现问题的容器,进入容器内部进行检查可能非常有用。这可以帮助我们了解容器内部的情况。我们可以使用以下命令打开一个 shell 并进入我们的容器:

docker exec -it <container-id> bash

请确保使用你自己容器的 container ID,这个 ID 可以从我们在第 3.8.3 节中启动容器时的输出中找到。在容器内,你可以使用常见的 Linux 命令,如 cdlsps,来检查容器内的文件系统和进程。这是一个非常有价值的调试技巧,可以帮助你深入了解容器内部的情况,因此请花时间探索你的容器。

3.8.5 停止容器

以分离模式(使用 -d 参数)启动的容器意味着容器在后台运行,并将继续运行直到我们明确指示它停止。为了停止我们的容器,我们需要知道它的 ID,这个 ID 是在我们之前启动容器时在终端中显示的(参见第 3.8.3 节)。如果我们需要再次找到容器 ID,我们可以运行 docker container list 并从列表中找到我们的容器 ID。

要停止容器而不删除它,请执行:

docker stop <container-id>

当容器停止但未删除时,我们仍然可以像上一节所述进入容器内部进行检查和调试。

请注意,停止容器后,它将不再出现在 docker container list 的输出中,因为默认情况下不显示已停止的容器。然而,如果你添加 --all 参数,即 docker container list --all,输出也将显示任何已停止的容器。在停止我们的容器后,我们可以通过以下命令删除它:

docker rm <container-id>

3.9 发布微服务

现在我们的第一个微服务已经准备好进行生产部署。我们已经将其打包成 Docker 镜像,但该镜像目前仅存储在我们的开发计算机上。这对我们自己的测试和实验是足够的,但我们还需要将镜像发布到某个地方,以便稍后将其部署到我们的 Kubernetes 集群。图 3.9 说明了我们现在将如何将我们的镜像发布到云中托管的私有容器仓库。

我们通过以下步骤发布我们的微服务:

  1. 在 Azure 上创建我们自己的私有容器仓库。我们只需要在第一次发布镜像时这样做。稍后,当我们发布新版本的镜像和其他微服务的镜像时,我们将简单地重用这个相同的仓库。
  2. 发布前,我们必须使用 docker login 命令进行身份验证。
  3. 使用 docker push 命令将我们的镜像上传到仓库。这是发布我们的微服务的关键步骤。
  4. 再次使用 docker run 来检查我们是否可以从发布的镜像启动我们的微服务。
图 3.9 将我们的 Docker 镜像发布到云中的私有容器仓库
图 3.9 将我们的 Docker 镜像发布到云中的私有容器仓库

3.9.1 创建私有容器仓库

创建私有容器仓库其实非常简单。我们计划在 Azure 上创建我们的仓库,但几乎所有主要的云平台,如 Amazon Web Services(AWS;使用 Elastic Container Registry [ECR])、Google Cloud Platform(GCP)等,都支持创建容器仓库。为何选择发布到私有仓库?因为本书主要探讨如何为私人公司构建专有应用程序,所以选择将我们的镜像发布到私有仓库,而非公共仓库(如 Docker Hub),是符合逻辑的。

我选择在本书中使用 Azure,是因为它是最简单的云平台之一,非常适合初学者学习构建云原生应用程序。Azure 为新用户提供了优惠,允许你在第一个月内免费获得一定的额度。这让你有机会在不产生费用的情况下尝试本书演示的云基础设施。

然而,确保在使用完这些免费额度后及时销毁你的资源,以避免额度用尽后产生意外费用。此外,选择 Azure 的另一个理由是 Microsoft 已经简化了查找和销毁云资源的流程,这有助于防止我们忘记某些资源最终导致支付未使用的基础设施费用(AWS 在这方面的操作相对复杂)。现在,我们将通过 Azure 门户 UI 手动创建我们的容器仓库。不过,在第 7 章,我们将学习如何通过使用 Terraform 代码化地创建容器仓库。

首先,访问 Azure 网站( https://azure.microsoft.com )。按照步骤注册并登录到 Azure 门户( https://portal.azure.com )。

登录后,点击左侧菜单中的“创建资源”按钮。在搜索框中输入 “container registry” 后点击 Enter。你将看到如图 3.10 所示的搜索结果。选择 Microsoft 的 Container Registry 选项,然后点击“Create > Container Registry”。

图 3.10 在 Azure 门户中创建新的私有容器仓库
图 3.10 在 Azure 门户中创建新的私有容器仓库

现在你将看到一个页面,上面详细介绍了 Microsoft Container Registry。如果你愿意,可以阅读相关信息,然后点击“Create”按钮。

接下来,我们需要填写一些关于我们即将创建的仓库的详细信息。如图 3.11 所示,首先需要设置一个名称。名称非常重要,因为它会生成我们稍后用来与仓库通信的 URL。我为我的仓库选择的名称是 bmdk1,这生成了以下 URL:bmdk1.azurecr.io。

由于仓库的名称会生成 URL,因此它必须是全球唯一的。换言之,你不能选择其他人已经使用的名称——你必须选择一个独特的名称。记下这个 URL,因为当你使用 Docker 命令时,你很快就会需要它。

在点击“查看 + 创建”按钮之前,我们还需要选择或创建一个 资源组。资源组是 Azure 中的一种功能,它允许你将云资源组织到一起以便管理。

图 3.11 填写我们新的私有容器仓库的详细信息
图 3.11 填写我们新的私有容器仓库的详细信息

如图 3.12 所示,我正在为名为 bmdk1 的新仓库创建一个新的资源组。要创建新的资源组,请点击“创建新的”,输入名称,然后点击“确定”。

图 3.12 创建一个新的资源组来包含私有容器仓库
图 3.12 创建一个新的资源组来包含私有容器仓库

资源组的名称不必与容器仓库的名称相同,也不需要是全球唯一的。只需确保你给它起一个对你有意义的名称,这样当你以后看到它时能够记起它的用途。

现在点击“查看 + 创建”按钮。在下一个页面上,点击“创建”按钮以创建你的仓库。

要跟踪我们仓库的创建状态,我们需要查看 Azure 门户中的通知。点击通知图标打开通知侧栏并观察我们部署的进度。这可能需要一些时间,但完成后,我们将在侧边栏中看到“部署成功”的通知,如图 3.13 所示。

图 3.13 我们新的容器仓库的部署成功!
图 3.13 我们新的容器仓库的部署成功!

从“部署成功”的通知中,我们可以点击“转到资源”查看新仓库的详细信息。否则,如果我们稍后需要再次找到我们的仓库,可以点击左侧菜单中的“所有资源”。如图 3.14 所示,这将列出我们所有的资源(如果我们已创建其他资源的话),包括我们的新容器仓库。

图 3.14 你可以在“所有资源”列表中找到你的容器仓库。在这个阶段,你只有一个资源,即仓库本身。
图 3.14 你可以在“所有资源”列表中找到你的容器仓库。在这个阶段,你只有一个资源,即仓库本身。

点击列表中的容器仓库以深入了解其详细信息,然后点击左侧菜单中的“访问密钥”。你可以在这里看到仓库的 URL。

注意 启用“管理员用户”选项非常重要,以便在推送和拉取镜像时进行身份验证。

现在记下你仓库的用户名和密码(你只需要第一个密码)。不要费心记下图 3.15 中显示的那

些。这些是我的仓库的详细信息,而当你阅读这本书时,它将不存在。

图 3.15 查看新的私有容器仓库的身份验证详细信息
图 3.15 查看新的私有容器仓库的身份验证详细信息

恭喜!如果你按照这些说明操作,你现在应该拥有了自己的私有容器仓库。你可以将你的镜像推送到这个仓库,并从那里将它们部署到生产环境。现在,让我们发布我们的第一个镜像!

公共与私有

在本书中,我们主要关注发布私有 Docker 镜像。但你可能对发布公共镜像感兴趣,例如,如果你创建了一个开源微服务。你可以创建一个 Docker 镜像,并将其公开发布到 Docker Hub,这可以帮助你的用户快速运行它!

要发布到 Docker Hub,你需要在 https://hub.docker.com 上注册。然后你可以使用 docker push 命令将你的镜像推送到 Docker Hub。

Docker Hub 也允许你发布私有镜像,尽管要发布多个私有镜像,你需要升级到付费账户。

3.9.2 将微服务推送到仓库

现在我们有了一个私有容器仓库,就有了一个可以发布我们第一个微服务的地方。我们将通过执行 docker push 命令来发布我们的镜像,如图 3.16 所示。

图 3.16 <strong>docker push</strong> 命令将我们的 Docker 镜像上传到我们的私有容器仓库。
图 3.16 docker push 命令将我们的 Docker 镜像上传到我们的私有容器仓库。

使用仓库进行身份验证

在我们可以推送到我们的仓库之前,我们必须先登录。我们启用了身份验证,因为我们不希望任何人都能发布镜像到我们的仓库。

在上一节中,你创建了你的私有容器仓库并记下了其详细信息。要与仓库通信,你必须知道其 URL。要推送和拉取镜像,你需要用户名和密码。如果你不记得这些,请参考第 3.9.1 节在 Azure 门户中找到你的仓库并回忆这些详细信息。为了进行身份验证,我们将使用下图中显示的 docker login 命令。

我本可以向你展示我使用的完整命令,其中包括我自己仓库的 URL、用户名和密码。但那不适合放在页面上!此外,这对你也没有帮助,因为此时,你必须使用你自己仓库的详细信息。当你执行 docker login 时,请务必使用你自己的 URL、用户名和密码。完成身份验证后,你现在可以对你的仓库执行其他 Docker 命令。

增补图片
增补图片

假设你的登录信息正确,你应该会看到一个警告和一个成功消息:

WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Login Succeeded

不要太担心那个警告。你目前只是在学习,所以安全问题还不是问题。但稍后,在第 8 章中,我们将正确处理这个问题,这样我们就不会得到那个警告。

标记我们的镜像

在我们可以将我们的镜像发布到仓库之前,我们必须告诉 Docker 镜像将被推送到哪里。我们通过执行 docker tag 命令来标记镜像,如下所示:

增补图片
增补图片

当然,你不能只是照字面意思输入那个命令。你必须使用你自己仓库的 URL!

docker tag 命令有以下通用格式:

docker tag <existing-image> <registry-url>/<image-name>:<version>

我们设置一个现有镜像的名称以进行标记,然后应用新标签。我们在这种情况下进行标记,只是因为我们想要将这个镜像推送到我们的仓库。出于这个原因,我们在标签中包括了仓库的 URL。

我们可以通过执行 docker image list 来检查我们的新标签是否已应用。在应用新标签后尝试这样做。你应该在表格中看到一个新条目,用于新标签。请注意,Docker 没有创建一个新镜像;它只是对现有镜像应用了一个新标签。我们可以通过检查镜像的唯一 ID 来确认这一点,我们会看到它与两个标记版本的 ID 相同。

将我们的镜像推送到仓库

最后,我们准备好将我们的镜像发布到仓库了。为此,我们将执行 docker push 命令:

增补图片
增补图片

再次确保你在此处使用自己仓库的 URL;否则,这个命令将无法为你工作。

docker push 的通用格式如下:

docker push <registry-url>/<image-name>:<version>

命令中 docker push 之后的部分标识了要推送的镜像和要推送到的仓库。

如果你觉得这过程有些繁琐,我同意你的看法。我认为应该有一个一步到位的流程来将现有的镜像推送到仓库,而不必经历先前的标记过程,但这就是目前完成它的方式。开始上传镜像后,请耐心等待其完成。

检查我们的镜像是否到达仓库

在我们将镜像推送到仓库后,我们现在想要确认它是否顺利到达。我们怎么知道它是否成功呢?第一个线索是在输出中。它应该表明推送成功,我们可以相信那是正确的。但出于好奇,让我们回到 Azure 门户中的仓库,看看现在的情况如何。

在 Azure 门户中,导航到所有资源,找到你的仓库并点击它。从左侧菜单中点击存储库。如图 3.17 所示,你应该能够在存储库列表中看到你的视频流镜像。如果你查看存储库(在图 3.17 的右侧),你可以在这里看到版本列表。目前只有一个版本(标记为“latest”),但将来,在你推送此镜像的更新后,你可以返回这里并看到列出的其他版本。

你可以通过点击“latest”标签深入了解镜像的详细信息,包括其文件清单。我鼓励你探索这个界面,看看你可以了解到关于你刚刚发布的镜像的什么信息。

图 3.17 通过 Azure 门户查看推送到容器仓库的镜像
图 3.17 通过 Azure 门户查看推送到容器仓库的镜像

上传新版本

我们现在有了一个手动管道,我们可以使用它来发布镜像。通常,我们不会标记“最新”这个词,而是用实际的版本号进行标记。每次我们更改微服务中的代码时,我们将再次构建、标记并推送它。

如果你愿意,也可以尝试这样做。尝试进行一次小的代码更改,再次调用 docker build(第 3.8.2 节),然后用版本“1”进行标记,如下所示:

docker tag video-streaming bmdk1.azurecr.io/video-streaming:1

现在将版本 1 推送到你的容器仓库:

docker push bmdk1.azurecr.io/video-streaming:1

请记住用你的容器仓库的 URL 替换这些命令中的仓库 URL。

现在尝试再次更改代码,将版本提升到 2,并再次推送它。这个练习说明了我们如何发布我们的微服务的新版本,因为我们发展其代码。这只是为了看看它是如何工作的;最终,我们不想手动构建和发布我们的微服务的镜像(如果这已经让你感到厌烦了,想象一下,手动构建、发布甚至部署不止一个微服务会有多烦人)。在第 8 章中,你将学习如何为你的微服务自动化这个过程。

3.9.3 从仓库启动微服务

恭喜你,你刚刚将你的第一个镜像发布到了你自己的私有仓库。你现在可以将此镜像部署到你的生产环境中,但我们将在第 6 章中保存这一步,届时你将学习如何将微服务部署到 Kubernetes。在那之前,你仍有工作要做和事情要学。

在继续之前,我们应该确认我们发布的镜像是否有效。我的意思是我们应该能够直接从云中的仓库实例化镜像为容器。只是因为我们还没有生产环境并不意味着我们不能在我们的开发计算机上模拟部署。这并不困难,也不比你在本章中已经学到的东西有什么不同。

从镜像运行容器大致相同,无论镜像是我们本地构建的还是在远程仓库可用的。所以,让我们返回到 docker run 命令来测试我们发布的镜像,如图 3.18 所示。

图 3.18 我们可以通过在开发计算机上运行它来测试我们发布的镜像;在这种情况下,docker run 命令必须首先从仓库拉取镜像。
图 3.18 我们可以通过在开发计算机上运行它来测试我们发布的镜像;在这种情况下,docker run 命令必须首先从仓库拉取镜像。

清理

在测试我们的镜像前,要先做一件事:删掉本地版本的镜像。不然,调用 docker run 时会从本地已有镜像启动容器,不删就无法测试发布的镜像!

我们想测试能否从远程仓库拉取镜像,若本地已缓存镜像版本就无需拉取远程的。所以,现在要确保删掉本地的容器和镜像。

容器不会自动消失,创建长期运行服务器的容器通常会停留!完成后得关闭,不然会消耗系统资源。没删容器(在第 3.8.5 节中)的,现在必须先删除容器才能删镜像。

注意 在我们可以删除镜像之前,必须先删除从中实例化的任何容器。尝试删除具有正在运行容器的镜像将导致错误。

我们将从终端调用 docker container list --all--all 参数能使其显示正在运行和已停止的容器。如果你在容器列表中看到你的视频流微服务,那就是你要删除的容器。记下这个容器 ID。你要记得自己的容器的 ID 是 460a19946689。当然,你的会有所不同,所以不要期望在你的容器列表中看到那个特定的 ID。我用以下命令删除了我的容器:

docker stop 460a19946689
docker rm 460a19946689

记住使用你的容器 ID。这是通用格式:

docker stop <your-container-id>
docker rm <your-container-id>

删除容器后,我们可以再次调用 docker ps 并检查容器是否不再在列表中。在删除任何容器后,我们现在可以继续删除镜像。

调用 docker image list。我们至少可以在列表中看到三个镜像。你应该看到我们的视频流微服务的两个标记版本。我们只需要删除我们的微服务的镜像。

请注意,我们的镜像的两个标记版本具有相同的镜像 ID,这些只是同一个镜像的多次引用。我们可以通过调用带有 --force 参数的 docker rmi 命令将这两者都删除,如下所示:

docker rmi 9c475d6b1dc8 --force

当然,需要使用你的特定镜像 ID 运行此命令(你可以从 docker image list 的输出中找到)。通用格式是

docker rmi <your-image-id> --force

我们在这里使用 --force,因为否则我们将因为镜像在多个存储库中被引用而被停止,显示类似 Image is referenced in multiple repositories 的错误消息。我们可以使用 --force 来确保这些都被删除。删除镜像后,再次调用 docker image list 来检查这是否正确操作,并确保我们的视频流镜像不再在列表中。

一次性清理所有东西

需要一种方法一次性清除所有 Docker 容器和镜像吗?要做到这一点需要几个步骤。以下命令适用于 macOS、Linux 和 Windows 下的 WSL2 Linux 终端(但不适用于 Windows 终端)。

首先,停止并删除所有容器:

docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)

然后,使用以下选项运行 prune 命令以删除其他所有内容:

docker system prune --volumes --all

这将删除你计算机上的所有缓存镜像。准备好等待所有后续 Docker 构建更长的时间,因为它将再次下载所有基础镜像到你的本地缓存中。

直接从仓库运行容器

清理了本地容器和镜像后,我们现在可以直接从远程仓库的镜像实例化新容器了。我们将再次使用 docker run,如下所示:

docker run -d -p 3000:3000 -e PORT=3000 bmdk1.azurecr.io/video-streaming:latest

请确保使用你自己仓库的 URL,而不是上面显示的我的 URL。这是通用格式:

docker run -d -p <host-port>:<container-port> -e <name>=<value> <registry-url>/<image-name>:<version>

你应该看到来自 Docker 的输出。这说明 Docker 正在从仓库下载镜像。这是我们第一次不使用本地缓存。一旦镜像下载完成,你的新容器应该会启动并开始运行。如果你在上一节中没有出现错误,并且你的容器启动并运行,则你知道你的仓库正在正常工作,你已经成功将镜像发布到它。

请检查你的容器是否已成功启动,如下所示:

docker container list

如果你看到你的容器正在运行,请使用此命令检查其日志:

docker logs <your-container-id>

一切正常的话,你的微服务应该正在运行,就像我们之前在开发环境中测试的那样。你可以通过打开网络浏览器并导航到 http://localhost:3000/video 来测试一下。

3.9.4 删除你的容器仓库

在本章结束时,你可以通过 Azure 门户删除你的容器仓库,或者你可以让它继续运行,因为你在第 6 章还会用到它。

不过,第 6 章离现在还有一段时间,你可能不想让容器仓库在此期间消耗你的 Azure 额度。如果是这种情况,请打开浏览器访问 Azure 门户 ( https://portal.azure.com/ ),通过“所有资源”页面找到你的仓库。然后,点击工具栏中的删除按钮。

3.10 Docker 回顾

哇!真是一次旅程。Docker 看起来很简单,直到你尝试在一个章节里解释它!我们刚刚做了以下事情:

  • 我们为我们的微服务创建了一个 Dockerfile,指示 Docker 如何为其构建镜像。
  • 我们调用 docker build 将我们的微服务打包成一个镜像。
  • 在 Azure 上创建我们的私有容器仓库后,我们调用了 docker tagdocker logindocker push 来发布我们的镜像。
  • 最后,我们使用 docker run 测试运行了我们发布的镜像。

我们拼凑在一起的完整流水线如图 3.19 所示。仔细阅读这个图表,欣赏你迄今为止所学到的知识。

图 3.19 显示“构建”、“推送”和“运行”在流程中所处位置的完整 Docker 构建流水线
图 3.19 显示“构建”、“推送”和“运行”在流程中所处位置的完整 Docker 构建流水线

在继续之前,让我们快速回顾一下本章中我们添加到工具带中的命令,如表 3.3 所列。

表 3.3 Docker 命令回顾

命令描述
docker --version检查 Docker 是否安装并打印版本号
docker container list列出正在运行的容器
docker container list -a列出所有容器(包括运行和停止的)
docker image list列出本地镜像
docker build -t <tag> --file <docker-file> .根据 docker-file 中的指令,从当前目录中的资产构建镜像。-t 参数为镜像指定一个标签。
docker run -d -p <host-port>:<container-port> -e <name>=<value> <tag>从镜像实例化一个容器。如果本地没有该镜像,可以从远程仓库中拉取(假设标签指定了仓库的 URL)。
-d 参数以分离模式运行容器,这样它就不会绑定到终端,你也看不到输出。省略此参数可以直接看到输出,但这也会锁定你的终端。
-p 参数允许你将主机上的端口绑定到容器中的端口。
-e 参数允许你设置环境变量。
docker logs <container-id>检索特定容器的输出。你需要这个命令来查看以分离模式运行的容器的输出。
docker login <url> --username <username> --password <password>认证到你的私有 Docker 仓库,以便你可以对其运行其他命令
docker tag <existing-tag> <new-tag>为现有镜像添加新标签。要将镜像推送到你的私有容器仓库,你必须首先用仓库的 URL 为其打标签。
docker push <tag>将一个适当打标签的镜像推送到你的私有 Docker 仓库。镜像应该用你的仓库的 URL 打标签。
docker exec -it <container-id> sh进入特定容器内部进行检查和调试
docker stop <container-id>本地停止特定容器
docker rm <container-id>本地删除特定容器(必须先停止)
docker rmi <image-id> --force本地删除特定镜像(必须先删除所有容器)。--force 参数即使镜像有多个标签也会将其删除。

3.11 继续学习

本章进展很快。目的是给你提供启动应用程序所需的最少知识,但你可以学习的 Docker 内容还有很多。这里有一些其他书籍的参考资料,可以帮助你深入了解 Docker:

  • Learn Docker in a Month of Lunches,作者 Elton Stoneman (Manning, 2020)
  • Docker in Practice, Second Edition,作者 Aidan Hobson Sayers 和 Ian Miell (Manning, 2019)
  • Docker in Action, Second Edition,作者 Jeff Nickoloff 和 Stephen Kuenzli (Manning, 2019)

Docker 也有很好的在线文档。值得浏览一下: http://mng.bz/yZVy

在本章中,我们探讨了如何使用 Docker 构建和发布单个微服务。我们将在未来的章节中继续扩展这些技能,推出更多的微服务并创建我们的应用程序。在下一章中,我们将扩展到多个微服务,并学习如何在开发计算机上轻松运行多个基于 Docker 的微服务。

总结

  • Docker 是用于打包、发布和运行容器的普遍工具。
  • 容器是虚拟化的服务器,例如微服务。
  • 镜像是准备实例化为服务器的容器快照。
  • 容器仓库是我们可以发布微服务镜像的地方(在我们的例子中是私有的),以便为部署到我们的 Kubernetes 集群做好准备。
  • Dockerfile 是用于创建镜像的脚本。它指定要包含在微服务中的代码和资产。
  • Docker Hub 提供了许多免费的镜像,我们可以轻松启动这些镜像来托管我们自己的服务器。在边栏“探索其他容器”中,我们尝试了 MongoDB,我们将在未来的章节中再次使用它,但还有许多其他软件包可供使用。
  • docker build 命令根据 Dockerfile 创建镜像。
  • docker run 命令从镜像实例化一个容器,从而实例化一个微服务。
  • docker push 命令将镜像发布到容器仓库。
  • docker tag 命令用于将镜像标记为容器仓库的名称,以便将其推送到仓库。

文章导航

独立页面

这是书籍中的独立页面。

书籍首页

评论区