第 10 章:部署 FlixTube

本章内容概览:

  • 复习已掌握的工具
  • 采用统一代码库开发微服务
  • 探索 FlixTube 的架构、结构及主要代码逻辑
  • 在开发环境中构建、运行及测试 FlixTube
  • 构建 FlixTube 的持续部署流程

在本书的第 10 章,我们终于到达了一个重要的里程碑。至此,我们已经利用了多种工具来构建、测试和部署微服务。在本章,我们将通过 FlixTube 这一示例应用,展示这些工具的实战运用。

你将全面了解 FlixTube 的运行机制,并将接触到一些新的微服务。同时,我们还将在一个完整的微服务应用的背景下,复习和巩固所学的技能。

首先,我们将从构建 FlixTube 开始,并在开发环境中运行。之后,我们将依据第 9 章的内容对其进行测试。最终,我们将把 FlixTube 部署到生产级的 Kubernetes 集群,并演示其持续部署(CD)流程。

10.1 熟悉的工具

恭喜你!你已经掌握了构建微服务应用所需的所有关键工具。下表列出了本章将复习的工具。

表 10.1 第 10 章中复习的关键工具

工具版本用途
Node.js18.17.1运行单个微服务(本章以 metadata 微服务为例)。
Docker24.0.5打包和发布 FlixTube 的微服务。
Docker ComposeDocker 包含在开发和测试环境中运行 FlixTube。
Kubernetes1.25.12部署 FlixTube 至本地 Kubernetes 实例(运行在 Docker Desktop 上)及 Azure 云上的生产 Kubernetes 实例。
Kubectl1.27.2将 FlixTube 部署到 Kubernetes。
GitHub ActionsN/A创建 FlixTube 的持续集成/持续部署(CI/CD)流程。
Jest29.6.4测试 metadata 微服务。
Playwright1.37.1测试 FlixTube 前端。

每种工具都有更深入的学习空间,同时还有许多其他有用的工具可供探索。未来可能会出现新的工具。但目前,你已经具备了构建微服务产品所需的基本工具。随着你深入项目开发,将会遇到特定问题,这时就需要进一步研究和解决。将来,你可能需要对 Docker、Kubernetes、Terraform 和 GitHub Actions 有更深入的了解。现在,让我们开始 FlixTube 的第一版构建。

10.2 获取代码

要跟随本章内容,你需要获取代码:

  • 从此处下载代码的 zip 文件: http://mng.bz/p1Ww

  • 使用 Git 克隆代码库:

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

第 2 章有关安装和使用 Git 的详细帮助。如果在使用代码时遇到问题,请在 GitHub 的代码库中提出 issue。

10.3 重温基本技能

在构建 FlixTube 示例的过程中,我们将运用所学的基本技能来构建、运行、测试和部署微服务。回顾这些技能,你会意识到我们已经覆盖了多么广泛的主题!

  • 使用 Node.js 运行微服务(第 2 章)
  • 使用 Docker 打包和发布我们的微服务(第 3 章和第 6 章)
  • 使用 Docker Compose 在开发中构建和运行我们的应用程序(第 4 章和第 5 章)
  • 使用数据库存储和检索数据(第 4 章)
  • 使用外部文件存储存储和检索文件(第 4 章)
  • 通过 HTTP 请求和 RabbitMQ 消息在微服务之间通信(第 5 章)
  • 使用 Jest 测试单个微服务(第 9 章)
  • 使用 Playwright 测试整个应用程序(第 9 章)
  • 使用 kubectl 将应用程序部署到 Kubernetes 集群(第 6 章)
  • 使用 GitHub Actions 创建 CD 流程(第 8 章)

图 10.1 展示了我们将复习的技能,并展示了它们在整体中的上下文。为了充分利用本章内容,请跟随示例操作。你应该自己运行 FlixTube,以便研究它并了解其工作原理。为了测试和提高你的理解,你应该尝试进行自己的修改。实践是将这些技能牢牢记在脑海中的最佳方式。

图 10.1 本章中重温的基本技能
图 10.1 本章中重温的基本技能

10.4 FlixTube 概述

本章的代码仅包括一个示例:完整的 FlixTube 项目。你可以在 chapter-10 代码库中找到它。让我们从鸟瞰其结构开始。图 10.2 显示了 FlixTube 的最新版本。

图 10.2 完整的 FlixTube 示例应用程序概述
图 10.2 完整的 FlixTube 示例应用程序概述

10.4.1 FlixTube 微服务概览

如图 10.2 所示,本章将介绍你已经熟悉的几个微服务,包括:

  • 视频流(首次介绍于第 2 章)
  • Azure 存储(第 4 章讨论)
  • 历史记录(第 5 章介绍)
  • 元数据(第 9 章探讨)

此外,我们还将引入一些新的微服务,如网关和视频上传功能。表 10.2 列出了各个微服务的具体用途。

表 10.2 FlixTube 微服务详细

微服务用途
网关作为应用程序的入口点,提供前端服务及 REST API
视频流负责从存储中将视频流式传输至用户
历史记录记录用户的观看历史
元数据记录视频的详细信息及相关元数据
视频上传协调视频文件上传至云存储
Azure 存储在外部云服务中存储和检索视频
模拟存储存储微服务的替代选项,使用本地文件系统存储视频。你将了解到如何通过它简化开发和测试流程。

10.4.2 微服务项目结构

在探讨整个应用程序的项目结构之前,我们先回顾一个单独的 Node.js 微服务项目结构。你可以在 chapter-10 代码库中找到 metadata 微服务的相关文件夹,进行详细查看。

以 metadata 微服务为例,图 10.3 展示了其项目布局。这是一个标准的 Node.js 项目结构,FlixTube 中的所有微服务几乎都采用了这种布局。

图 10.3 Node.js 微服务项目的结构(metadata 微服务)
图 10.3 Node.js 微服务项目的结构(metadata 微服务)

10.4.3 FlixTube 单一代码库

现在,让我们来看一下 FlixTube 的单一代码库结构。所谓单一代码库(monorepo),是指一个包含多个项目的单一代码仓库。与每个微服务拥有各自独立的代码库相比,我们将所有微服务合并至一个 FlixTube 单一代码库中,其布局如图 10.4 所示。你可以打开 chapter-10 文件夹亲自查看。

图 10.4 FlixTube 单一代码库的结构
图 10.4 FlixTube 单一代码库的结构

通过使用单一代码库,FlixTube 项目的管理变得更加简洁,减少了复杂性,同时使微服务的初始设置更加便捷。这种方式非常适合构建规模较小的微服务应用。而且,这也为你提供了一个完整的微服务示例,方便你进行实验和学习。

尽管如此,在生产环境中,微服务通常不会被放置在同一个代码库中。这是因为,至少在过去,使用单一代码库可能会削弱微服务独立部署的主要优势。然而,随着 GitHub Actions 这样的现代工具的出现,我们现在可以为单一代码库中的各个子目录创建独立的持续部署管道。我们将在本章后面看到这种做法的实际应用,它使我们能够享受单一代码库的简便性,同时又不失微服务的独立部署优势。但即便如此,如果你的微服务应用规模足够大,难以管理,最终你可能还是会选择将其拆分为多个代码库。我们将在第 12 章进一步探讨这一主题。

10.5 在开发环境中运行 FlixTube

我们首先需要在开发环境中启动 FlixTube。图 10.5 展示了 FlixTube 在开发中的运行状态。请注意,为了方便开发,我们使用了 mock-storage 微服务来替代 azure-storage 微服务,后续章节将会解释这样做的原因。

图 10.5 FlixTube 在开发中的样子
图 10.5 FlixTube 在开发中的样子

10.5.1 启动单个微服务

在启动整个应用程序之前,我们先回顾如何启动一个单独的微服务。当我们需要开发新微服务或专注于现有微服务的修改时,通常只需单独运行该微服务,而无需启动整个应用程序。下面是启动 metadata 微服务的步骤。首先,切换到项目目录:

cd chapter-10/metadata

我们使用 Node.js 来运行我们的微服务。如果你已经按照第 2 章和第 9 章的指导安装了 Node.js,可以直接继续。如果还未安装 Node.js,请参考第 2 章的安装指南。运行 Node.js 项目前,需要先安装依赖项:

npm install

使用以下命令启动 Node.js 项目:

npm start

这会执行 package.json 文件中指定的命令。FlixTube 的所有微服务都遵循这一通用的 Node.js 启动约定,这意味着你可以轻松地启动任何一个微服务,并在生产模式下独立运行它们。

启动微服务时,系统可能会显示错误消息,提示需要设置一些环境变量。在尝试再次启动微服务前,请确保设置了所有必需的环境变量。

对于 metadata 微服务,需要设置的环境变量包括:

  • PORT——Web 服务器监听的端口号,通常在本地测试时设置为 3000。
  • DBHOST——MongoDB 数据库服务器的连接字符串。你可以选择在本地安装 MongoDB 或在 Docker 容器中运行它。
  • DBNAME——此微服务使用的数据库名称,如 metadata-dev
  • RABBIT——RabbitMQ 服务器的连接字符串。

在开发模式下运行微服务可以启用实时重新加载功能,允许你在代码编辑后自动重启微服务。我们使用以下命令以开发模式启动任何一个 FlixTube 微服务:

npm run start:dev

有关生产模式、开发模式和实时重新加载的更多信息,请参考第 2 章的相关内容。

如果你已经设置了上述环境变量,你会注意到,像 metadata 这样的微服务现在有许多依赖项,它们很难单独启动。这些依赖项通常需要一个数据库和一个 RabbitMQ 服务器,有时两者都需要。我们可以通过以下方式解决这个问题:

  • 在开发计算机上安装 MongoDB 和 RabbitMQ。这可能初看起来麻烦,但从长远看是有益的。
  • 使用 Docker 或 Docker Compose 来运行 MongoDB 和 RabbitMQ 的容器。这是一种方便、有效且简洁的方法。
  • 使用模拟库来代替 MongoDB、RabbitMQ 和其他依赖项。这类似于我们在第 9 章进行的自动化测试。

10.5.2 启动整个 FlixTube 应用程序

现在,让我们通过 Docker Compose 启动整个 FlixTube 应用程序,这是我们从第 4 章开始就一直依赖的强大工具。在日常开发中,我们需要频繁地构建和重启应用程序,Docker Compose 让这一过程变得简单。虽然我们经常只关注单个微服务的开发,但仍需要定期测试整个应用程序。

以下是 Docker Compose 文件(docker-compose.yaml)的示例结构,展示了我们如何使用它来在开发环境中启动 FlixTube:

示例 Docker Compose 文件:启动 FlixTube

version: '3'
services:
  db:
    image: mongo:5.0.9
    container_name: db
    # 启动 MongoDB 数据库的容器

  rabbit:
    image: rabbitmq:3.9.21-management
    container_name: rabbit
    # 启动 RabbitMQ 服务器的容器

  db-fixture-rest-api:
    image: db-fixture-rest-api
    build:
      context: ./db-fixture-rest-api
      dockerfile: Dockerfile
    container_name: db-fixture-rest-api
    # 启动用于加载数据库夹具的 REST API 的容器

  video-streaming:
    image: video-streaming
    build:
      context: ./video-streaming
      dockerfile: Dockerfile-dev
    container_name: video-streaming
    # 构建并启动视频流微服务

  # 其他所有 FlixTube 微服务配置在此。

这个 Docker Compose 文件包含了我们熟悉的视频流微服务、数据库设置和 RabbitMQ,以及在自动化测试中使用的数据库夹具 REST API。

现在,运行以下命令在 Docker Compose 环境中构建并启动 FlixTube:

cd chapter-10
docker compose up --build

如果这是你第一次执行此操作,构建和启动过程可能需要一些时间。等待终端显示网关微服务的“online”消息。

现在 FlixTube 已在运行,你可以打开浏览器并访问 http://localhost:4000 来查看 FlixTube 的主界面。花点时间探索 FlixTube,包括上传视频和查看播放历史。

开发结束后,为了释放资源,请关闭 FlixTube:

docker compose down --volumes

10.6 在开发环境中测试 FlixTube

在开发过程中,测试扮演着至关重要的角色。虽然我们可以进行手动测试,但自动化测试的效率、可靠性和可重复性远胜人工测试。

在第 9 章中,我们已经介绍了使用 Jest 和 Playwright 进行多种类型测试的方法。在本章中,我们将复习这些测试技术。这些测试已在第 10 章的代码库中重新使用,我们现在将它们应用于完成的 FlixTube 示例。

需要指出的是,任何真实的应用程序都需要比本示例中展示的更全面的测试。本章仅展示了基础测试方法,我们未能实现完整的测试覆盖率。请按照后续的操作步骤尝试运行这些测试。

10.6.1 使用 Jest 对微服务进行测试

FlixTube 中的 metadata 微服务包含了第 9 章介绍的 Jest 单元测试。在执行测试前,你需要安装必要的依赖项:

cd chapter-10/metadata
npm install

接下来,运行常规的 npm 测试脚本:

npm test

这将执行我们在第 9 章中配置好的 metadata 微服务的 package.json 文件中指定的命令。图 10.6 展示了测试成功执行的结果。

我们也可以在实时重新加载模式下运行测试,这样可以在代码编辑后自动重启测试。通过另一个 npm 脚本 test:watch(个人设定)来实现:

npm run test:watch

要了解更多关于 Jest 的信息,请回顾第 9 章的内容。关于 npm 和 Jest 的实时重新加载配置,详见第 9 章。

图 10.6 使用 Jest 成功运行 metadata 微服务的自动化测试
图 10.6 使用 Jest 成功运行 metadata 微服务的自动化测试

10.6.2 使用 Playwright 对应用程序进行测试

我们还可以使用第 9 章介绍的 Playwright 工具对 FlixTube 应用程序进行端到端测试。之前,我们在一个简化版本的 FlixTube 上运行了测试,现在,我们将这些测试应用于完整的应用程序。首先,安装 FlixTube 单一代码库的依赖项:

cd chapter-10
npm install

确保整个 FlixTube 应用程序已经启动(如果尚未启动):

docker compose up --build

等待终端显示网关微服务上线的消息,这是开始运行测试的先决条件。

现在,在一个新的终端窗口中运行常规的 npm 测试脚本,这里配置为调用 Playwright:

cd chapter-10
npm test

这将在终端中运行 Playwright 测试,你应该可以看到类似于图 10.7 的结果。请注意,在不同的项目中 npm test 可能指代不同的行为。在 metadata 微服务项目中,它指的是运行 Jest,在更广泛的 FlixTube 单一代码库中,它指的是运行 Playwright。这种设定让我们可以针对各个项目运行自动化测试,而无需记住特定测试框架的具体细节。

图 10.7 使用 Playwright 成功运行 FlixTube UI 的自动化测试
图 10.7 使用 Playwright 成功运行 FlixTube UI 的自动化测试

10.7 FlixTube 深入探讨

至此,你应该对 FlixTube 有了全面的了解。你知道每个微服务的基本功能,并了解如何在开发环境中构建、运行和测试应用程序。在我们把 FlixTube 部署到生产环境之前,让我们进一步了解一些详细的技术细节。本节将探讨 FlixTube 的以下方面:

  • 数据库夹具
  • 模拟存储微服务
  • 网关
  • FlixTube 用户界面
  • 视频流
  • 视频上传

10.7.1 数据库夹具的应用

在第 9 章中我们初次讨论了数据库夹具,这些是在执行自动化测试前用来填充数据库的具有代表性数据的工具。数据库夹具的作用不限于自动化测试,它们对手动测试甚至是产品演示同样重要。应用启动后立即可用的真实数据极大地便利了展示和测试过程。

在使用 Jest 进行单元测试时,我们不需要任何数据库夹具,因为我们通过模拟 MongoDB 实例来替换实际的数据库交互,使用假数据进行操作。然而,在集成测试中,我们通过 Jest 直接与 MongoDB 进行交互,这时可以在测试代码中直接嵌入测试数据,而无需创建独立的数据文件,这提供了极大的便利。

为了简化操作,在使用 Playwright 进行端到端测试时,我们通过一个自行开发的数据库夹具 REST API 来管理数据库夹具的加载。这个 REST API 的结构与本书中你已见过的其他微服务类似。我们这里不深入其代码细节,但你若感兴趣,可以在第 9 章的代码库中找到,也已复制到第 10 章的代码库中,以便在测试 FlixTube 时使用。此外,其源代码也可在 GitHub 上查看,链接为: https://github.com/ashleydavis/db-fixture-rest-api 。在之前的清单 10.1 中你可以看到关于 REST API 容器的配置。

要查看一个具体的数据库夹具示例,请参考清单 10.2(位于 chapter-10/fixtures/two-videos/videos.js)。我们的数据库夹具存储于 chapter-10 代码库的 fixtures 子目录下。FlixTube 使用的一个夹具文件为 videos.js(如清单 10.2 所展示的)。文件名反映了数据将被加载到的数据库集合。此夹具中的数据将加载到 videos 集合中。

清单 10.2 FlixTube 的数据库夹具示例

const { ObjectId } = require("mongodb");
// 导入 MongoDB 库以便创建数据库 ID

module.exports = [
  {
    _id: new ObjectId("5ea234a1c34230004592eb32"),
    name: "SampleVideo_1280x720_1mb.mp4"
    // 设置视频的文件名
  },
  {
    _id: new ObjectId("5ea234a5c34230004592eb33"),
    name: "Another video.mp4"
    // 为新记录创建数据库 ID
  }
];
// 导出用于插入到 metadata 数据库 videos 集合中的数据

夹具文件所在的目录名 two-videos 为此夹具命名,因为它主要用于加载两个视频的元数据到数据库中。通常,我们应为数据库夹具选择更具描述性的名称,以便我们可以轻松记住其用途。

每个数据库夹具可以包含多个文件。虽然在这里我们只有一个文件用于 two-videos 夹具,但它可以包含更多的文件以设置数据库中其他集合的内容。

如果你已经按照第 10.6.2 节的指示运行了 Playwright 测试,那么你已经使用了这个数据库夹具!注意,清单 10.2 中的夹具实际上是一个 JavaScript 文件。我们可以选择使用 JSON 格式或 JavaScript 来定义这些数据库夹具。JSON 适合静态数据,而 JavaScript 则是生成动态数据的良好选择,这为我们生成测试数据提供了极大的灵活性。在清单 10.2 中,我们展示了如何使用 MongoDB 库为测试数据生成数据库 ID。

10.7.2 模拟存储微服务

为了简化开发过程,我们使用了一个模拟版本的存储微服务来替代真实的 Azure 存储微服务。这种模拟方法不仅适用于函数、对象和库(如我们在第 9 章中所见),而且也可用于完整的微服务。图 10.8 展示了在开发期间如何利用模拟存储微服务来替换 azure-storage。

图 10.8 用模拟微服务替换云存储,以便在开发期间更方便和高效地使用
图 10.8 用模拟微服务替换云存储,以便在开发期间更方便和高效地使用

我们的模拟存储微服务并非纯粹的“假”微服务。它确实执行存储任务,但与其使用云存储,不如说它将视频保存在本地文件系统中。主要原因不仅是测试便利性,也因为这样做提升了性能,使整个应用程序能够仅在开发者的机器上运行。

在开发阶段,我们希望尽可能地减少外部依赖,如云存储服务的连接。将存储限制在本地文件系统不仅简化了开发配置,也提高了性能,因为文件直接存储在本地,而非远程传输至云端。除了存储方式的改变,FlixTube 的其他微服务并不知道 azure-storage 已被替换为模拟版本。

这种用简化版本替换复杂微服务的做法不仅方便,而且可能在未来变得必要。虽然目前 FlixTube 是一个较小的应用程序,但你可以想象,随着它逐渐发展成为一个全球级的流媒体服务,它的规模可能会变得过于庞大,不再适合在单台机器上运行。

那时,我们将需要利用各种技巧来适配开发计算机的资源限制。这包括去除不必要的微服务。例如,如果我们不需要测试历史记录微服务,就可以从 Docker Compose 配置中移除它。历史记录微服务不是 FlixTube 运行的关键部分,因此,如果需要减小应用规模,移除它不会造成负面影响。

注意:删除或替换复杂的微服务,甚至可能是整个微服务组,是一个减少应用规模以适应单机运行的有效策略。

清单 10.3(摘自 chapter-10/docker-compose.yaml)展示了在 Docker Compose 文件中模拟存储微服务的配置。它的设置与 azure-storage 微服务的类似,不同之处在于它共享了主操作系统和容器之间的存储子目录,这是上传视频的存储位置。这种共享方式意味着我们可以在主机上检查上传的视频,以确认模拟存储微服务是否正常工作。

清单 10.3 Docker Compose 文件中的模拟存储微服务配置

video-storage:
  image: mock-storage
  build:
    context: ./mock-storage
    dockerfile: Dockerfile-dev
  container_name: video-storage
  volumes:
    - /tmp/mock-storage/npm-cache:/root/.npm:z
    - ./mock-storage/src:/usr/src/app/src:z
    - ./mock-storage/storage:/usr/src/app/storage:z
  ports:
    - "4005:80"
  environment:
    - PORT=80
  restart: "no"

在开发中能够用模拟版本替换真实微服务是一个极好的策略,它使开发过程更加简便。然而,有时我们也需要关注微服务的真实版本,即我们需要在真实环境中进行测试而不是在模拟环境中。在这种情况下,我们可以简单地在 Docker Compose 文件中将模拟版本替换为真实版本。如果你感兴趣,可以尝试这种切换,体验两者之间的差异。

清单 10.4(源自 chapter-10/docker-compose.yaml)展示了一个真实 azure-storage 微服务的注释配置。你只需取消注释此部分配置,并将模拟版本的配置加以注释。然后,在终端中设置环境变量 STORAGE_ACCOUNT_NAMESTORAGE_ACCESS_KEY,使用你的 Azure Storage 账户认证详情(详情参见第 4 章第 4.4.1 节,了解如何获取这些信息)。现在,重新构建并重启 FlixTube,即可在开发环境中测试真实的 azure-storage 微服务。

清单 10.4 真实 azure-storage 微服务的注释部分

# video-storage:
#   image: azure-storage
#   build:
#     context: ./azure-storage
#     dockerfile: Dockerfile-dev
#   container_name: video-storage
#   --snip--
#   environment:
#     - PORT=80
#     - STORAGE_ACCOUNT_NAME=${STORAGE_ACCOUNT_NAME}
#     - STORAGE_ACCESS_KEY=${STORAGE_ACCESS_KEY}
#   restart: "no"
# 从环境变量中获取 Azure Storage 账户的连接信息
# (这样可以避免在代码中添加敏感信息)

# 取消注释此部分,以在开发过程中包含 azure-storage 微服务。要使其正常工作,必须注释掉模拟存储微服务
# (如清单 10.3 所示),实际替换为真实版本。

清单 10.5(源自 chapter-10/mock-storage/src/index.js)展示了模拟存储微服务的代码实现。这一模拟版本将真实存储微服务的 /video/upload 路由替换为使用本地文件系统。模拟微服务作为即插即用的替代方案,其 REST API 与真实 azure-storage 微服务接口兼容。

清单 10.5 模拟存储微服务代码

--snip--

const storagePath = path.join(__dirname, "../storage"); // 在本地文件系统中设定存储视频的路径

const app = express();

app.get("/video", (req, res) => { // HTTP GET 路由处理程序,从模拟存储中流式传输视频
    const videoId = req.query.id;
    const localFilePath = path.join(storagePath, videoId);
    res.sendFile(localFilePath); // 将本地文件直接作为响应发送
});

app.post("/upload", (req, res) => { // HTTP POST 路由处理程序,用于将视频上传至模拟存储
    const videoId = req.headers.id;
    const localFilePath = path.join(storagePath, videoId);
    const fileWriteStream = fs.createWriteStream(localFilePath);
    
    req.pipe(fileWriteStream) // 将 HTTP 请求的主体(上传的文件)流式传输至本地文件
        .on("error", err => {
            console.error("Upload failed. 上传失败。");
            console.error(err && err.stack || err);
        })
        .on("finish", () => {
            res.sendStatus(200);
        });
});

--snip--

10.7.3 网关

FlixTube 设有一个统一的网关微服务。它被称为Gateway,因为它充当了用户访问应用程序的主入口。在当前的 FlixTube 版本中,这是整个应用程序的唯一入口点。网关提供了前端用户界面,使用户能够通过其网页浏览器与 FlixTube 互动。它还提供了一个 REST API,以便前端可以与后端进行通信。

目前的 FlixTube 尚不支持任何形式的身份验证,但未来我们可能会升级网关以验证用户身份。在这种情况下,用户需要先在网关登录后,才能与后端的任何其他微服务进行交互。

图 10.9 展示了未来可能拥有多个网关的 FlixTube 模样,这是一种被称为 Backend for Frontend 的著名模式。每个前端都有自己的网关:一个网关服务于网页浏览器,另一个服务于移动应用,还有一个用于 FlixTube 管理门户。

尽管我们更倾向于保持简单,只维护一个网关,但共用一个网关来服务多种类型的前端是完全可行的。然而,如果我们发现不同前端(如网页与移动应用之间的身份验证差异,或网页与管理门户之间的安全考虑)有不同的需求,那么 Backend for Frontend 模式将能提供帮助。

图 10.9 拥有多个网关的 FlixTube 模样
图 10.9 拥有多个网关的 FlixTube 模样

如果我们实际扩展到拥有多个网关,那么我们将需要通过不同的主机名或子域名来访问它们。例如,浏览器主要网关可能使用 flixtube.com,移动网关使用 mobile.flixtube.com,管理员门户使用 admin.flixtube.com。要为你的应用程序分配域名,你需要通过 DNS 提供商购买域名,并将每个域名配置为指向特定网关微服务的 IP 地址。

网关微服务的主要任务之一是将 HTTP 请求转发至集群。我们将在后续章节中看到相关代码示例。一个更高级的网关(目前 FlixTube 尚未实现)将具备 REST API 路由功能,能够向多个内部微服务发起请求,并将来自这些服务的多个响应集成后返回给前端。

例如,假设有一个 REST API 用于查询单个用户的历史记录。这可能需要向用户账户微服务(FlixTube 目前尚未拥有)和历史记录微服务发送 HTTP 请求,然后整合这些响应,并将结果发送至前端。在这个理论示例中,网关合并了两个 HTTP 请求的响应。

10.7.4 FlixTube 用户界面

如果你还未有机会体验 FlixTube 的用户界面,现在就是一个好时机。根据第 10.5.2 节的指导,构建并启动应用程序,然后在你的网页浏览器中访问 http://localhost:4000。尝试上传一两个视频体验一下。

图 10.10 展示了上传视频后的 FlixTube 主页面(视频列表)。你可以点击任何一个视频开始观看。在导航栏中,你还可以点击视频、上传和历史记录,以在主页面之间切换。

图 10.10 FlixTube 用户界面的主页面显示已上传的视频列表
图 10.10 FlixTube 用户界面的主页面显示已上传的视频列表

FlixTube 是一个采用传统服务器渲染网页技术而非现代浏览器渲染的单页应用程序(SPA)。展示的 FlixTube 前端是一个多页应用程序(MPA)。若此为一个商业应用,我可能会选择使用 React、Angular 或 Vue 将其开发为 SPA 而非 MPA。但这并不意味着 SPA 一定优于 MPA;它们各有所长,新方法(如构建 SPA)并不总是比传统方法(如构建 MPA)更优。

为何不采用流行的现代 SPA 框架呢?简单来说,这超出了本书的讨论范围。我们的重点不是用户界面设计,这也是为什么我们尽量保持前端的简单性。(此外,我不想在 SPA 框架信徒之间引发争议,虽然大家都知道 React 很受欢迎,不是吗?)

FlixTube 使用 Express 和 Handlebars 模板引擎进行服务器端渲染,前端使用原生 JavaScript。这意味着 FlixTube 的前端完全由 HTML、CSS 和 JavaScript 组成,没有采用任何复杂的现代框架。

清单 10.6(来自 chapter-10/gateway/src/index.js)展示了网关微服务的主要代码片段。它展示了用于渲染主页面的 HTTP GET 路由。主页面显示上传的视频列表。该路由处理程序首先发出 HTTP GET 请求(通过 Axios)以从 metadata 微服务获取数据,然后使用 video-list 模板渲染网页,并将视频列表作为模板数据传递。

清单 10.6 渲染 video-list 网页的网关代码

app.get("/", async (req, res) => { // 定义一个 HTTP GET 路由处理程序,用于检索主页面并显示上传的视频列表
    const videosResponse = await axios.get("http://metadata/videos"); // 向 metadata 服务发送 HTTP 请求以获取视频列表

    res.render("video-list", { // 使用 video-list 模板渲染页面(清单 10.7 显示了模板)
        videos: videosResponse.data.videos // 将视频数组作为模板数据传递以在模板中显示
    });
});

尽管没有使用 JavaScript 框架,我确实采用了 CSS 框架(Tailwind CSS),以便在不直接处理 CSS 细节的情况下创建一个美观的用户界面。

清单 10.7(chapter-10/gateway/src/views/video-list.hbs)展示了 FlixTube 的主页面。这是一个在 Handlebars 模板中包含的 HTML 文档。Handlebars 是一个简单且强大的模板库,我们可以用它基于数据生成网页。如果你回顾一下清单 10.6,你会看到从 metadata 微服务检索到的视频列表作为模板数据进行了传递。数据与模板的结合渲染出了将在用户的网页浏览器中显示的 HTML 页面。

清单 10.7 video-list 网页的 Handlebars 模板

<!doctype html> <!-- HTML5 文档 -->
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>FlixTube: Home</title>
    --snip-- <!-- 包括各种 CSS 文件,其中包含 Tailwind CSS 框架 -->
</head>
<body>
    <div class="flex flex-col">
        <div class="border-b-2 bg-gray-100">
            --snip-- <!-- 渲染页面顶部的导航栏 -->
        </div>
    </div>

    <div class="m-4"> <!-- 页面的主要内容 -->
        <h1>Videos</h1>
        <div id="video-list" class="m-4"> <!-- 视频列表的容器 -->
            {{#if videos}} <!-- 使用 Handlebars 语法从数据渲染模板 -->
                {{#each videos}} <!-- 为每个视频重复渲染此元素 -->
                    <div class="mt-1">
                        <a href="/video?id={{this._id}}"> <!-- 根据模板数据渲染指向视频的链接 -->
                            {{this.name}}
                        </a>
                    </div>
                {{/each}}
            {{else}}
                No videos uploaded yet. <!-- 如果还没有视频上传,则显示此消息 -->
            {{/if}}
        </div>
    </div>
</body>
</html>

10.7.5 视频流

视频流是 FlixTube 的核心功能。我们从第 2 章开始探讨这个主题,并在整本书中持续讨论。现在,让我们详细了解 FlixTube 示例应用程序中视频流的实际工作原理。虽然这部分内容部分是对前几章的回顾,但在我们已经介绍了网关微服务和用户界面的情况下,理解其在更广泛背景下的运作显得尤为重要。

图 10.11 展示了一个视频流的完整路径:从左侧的外部云存储开始,一直传输到右侧用户的网页浏览器中。在这个传输过程中,视频依次流经了三个微服务。现在,让我们通过代码来追踪这一过程。

图 10.11 FlixTube 中流媒体视频的路径
图 10.11 FlixTube 中流媒体视频的路径

清单 10.8(来自 chapter-10/azure-storage/src/index.js)展示了从 Azure 存储中检索并流式传输视频的过程。HTTP GET /video 路由负责从 Azure 存储中检索视频并将其流式传输到 HTTP 响应。具体的工作原理尽管复杂,但详细内容可以参见第 4 章第 4.4.1 节。

清单 10.8 从 Azure 存储流媒体视频

app.get("/video", async (req, res) => { 
    // HTTP GET 路由处理程序,用于从 azure-storage 微服务检索流媒体视频
    const videoId = req.query.id; 
    // 通过 HTTP 查询参数输入要检索的视频 ID

    const blobService = createBlobService();
    const containerClient = 
        blobService.getContainerClient(STORAGE_CONTAINER_NAME);
    const blobClient = containerClient.getBlobClient(videoId);
    const properties = await blobClient.getProperties();

    res.writeHead(200, { 
        "Content-Length": properties.contentLength, 
        "Content-Type": "video/mp4" 
    }); 
    // 向响应中写入 HTTP 头部信息

    const response = await blobClient.download();
    response.readableStreamBody.pipe(res); 
    // 将视频从 Azure 存储流式传输至 HTTP 响应
});

清单 10.9(来自 chapter-10/video-streaming/src/index.js)展示了 HTTP GET /video 路由如何利用 Node.js 流,将视频从 azure-storage 流式传输到其自身的 HTTP 响应。视频流微服务还负责向应用程序中的其他微服务广播“视频已观看”的消息。这种事件驱动的编程方式允许将来添加对这些事件响应的微服务,而无需更改现有代码。

正如你在第 5 章第 5.8 节中看到的,历史记录微服务接收这些消息并用它来记录用户的观看历史。间接消息传递的使用不仅使视频流和历史记录微服务解耦,还体现了基于微服务的应用程序如何灵活和可扩展。

清单 10.9 通过视频流微服务转发视频

app.get("/video", async (req, res) => {
    // 定义一个 HTTP GET 路由处理程序,用于从视频流微服务检索流媒体视频
    const videoId = req.query.id;

    const response = await axios({
        method: "GET",
        url: `http://video-storage/video?id=${videoId}`,
        data: req,
        responseType: "stream",
    });
    // 将 HTTP GET 请求转发至视频存储微服务,不关心其与 azure-storage 还是 mock-storage 微服务对话

    response.data.pipe(res);
    // 使用 Node.js 流将视频从视频存储微服务传输至此请求的响应

    broadcastViewedMessage(messageChannel, videoId);
    // 广播“视频已观看”消息,通知其他微服务用户正在观看视频
});

我们的视频流旅程最终在用户界面结束。你可以在清单 10.11(来自 chapter-10/gateway/src/views/play-video.hbs)中看到 HTML 视频元素的使用。source 元素及其 src 属性通过触发到网关的 HTTP GET 请求来启动对视频流的请求,进而触发对视频存储的请求。流媒体视频随后从 azure-storage 通过视频流、网关,最终流向前端,在用户的网页浏览器中显示。

清单 10.11 使用 HTML 视频元素在前端播放视频

<video controls autoplay muted> <!-- 在前端使用 HTML 视频元素展示流媒体视频 -->
  <source src={{video.url}} type="video/mp4"> <!-- 视频链接指向网关微服务中的 /api/video 路由,用于检索并展示流媒体视频 -->
  Your browser does not support the video tag.
</video>

10.7.6 视频上传

视频流是 FlixTube 的重要组成部分,但视频上传同样关键,它是我们将视频添加到 FlixTube 的初始步骤。尽管本书尚未详细介绍视频上传的具体操作,其工作原理与视频流类似,因此你应该能够轻松掌握。

图 10.12 描述了视频上传在应用程序中的路径。用户在前端选择并上传视频文件。上传的视频首先到达网关微服务,然后通过视频上传微服务转发到 azure-storage 微服务。最终,视频被安全存储在外部云存储中。接下来,我们将通过代码来追踪这一过程。

图 10.12 上传视频在 FlixTube 中的路径
图 10.12 上传视频在 FlixTube 中的路径

图 10.13 是 FlixTube 上传界面的截图。如果你按照第 10.5.2 节的步骤操作,你将看到这个界面并尝试上传视频。用户点击选择文件按钮并选择一个视频文件上传。上传完成后,界面会更新以显示上传成功的反馈。如果上传过程中出现错误,则会显示错误信息。

图 10.13 FlixTube 上传视频的用户界面
图 10.13 FlixTube 上传视频的用户界面

清单 10.12(摘自 chapter-10/gateway/public/js/upload.js)是一个前端代码片段,用于将视频上传到后端。代码使用 fetch 函数通过 HTTP POST 请求上传视频。通常情况下,我们可能会在前端使用如 Axios 这样的库。但为了保持 FlixTube 前端的简洁,这里使用的是无需构建过程的原生 JavaScript。由于没有构建过程,我们无法将如 Axios 这样的 npm 包打包进网页。在有构建过程的现代 Web 应用程序中,我们肯定会使用 Axios。

清单 10.12 使用 fetch 在前端上传视频

fetch("/api/upload", {
    body: file, // 设置要上传的文件为请求体
    method: "POST", // 使用 POST 方法
    headers: {
        "File-Name": file.name,
        "Content-Type": file.type, // 在请求头中设置文件名和 MIME 类型
    },
})
.then(() => {
    // 上传成功后的 UI 更新操作
})
.catch((err) => {
    // 处理上传失败的错误
});

从 Web 浏览器上传后,HTTP POST 请求被网关通过下列代码中的 /api/upload 路由处理(摘自 chapter-10/gateway/src/index.js)。我们看到请求如何被转发到视频上传微服务。

清单 10.13 网关将 HTTP POST 请求转发至视频上传服务

app.post("/api/upload", async (req, res) => {
    const response = await axios({
        method: "POST",
        url: "http://video-upload/upload",
        data: req,
        responseType: "stream",
        headers: {
            "content-type": req.headers["content-type"],
            "file-name": req.headers["file-name"],
        },
    });
    response.data.pipe(res); // 将视频上传服务的响应转发回原请求
});

清单 10.14(摘自 chapter-10/video-upload/src/index.js)展示了视频上传微服务如何处理传入的视频。视频通过创建 MongoDB 的 ObjectId 实例获得唯一 ID。然后请求被转发到视频存储微服务。

上传成功后,系统会广播“视频已上传”的消息,通知其他微服务系统中添加了新视频。metadata 微服务接收此消息并在其数据库中记录新视频。

清单 10.14 视频上传微服务处理视频上传

app.post("/upload", async (req, res) => {
    const fileName = req.headers["file-name"]; // 提取文件名
    const videoId = new mongodb.ObjectId(); // 为视频生成唯一 ID
    const response = await axios({
        method: "POST",
        url: "http://video-storage/upload",
        data: req,
        headers: {
            "content-type": req.headers["content-type"],
            "id": videoId,
        },
    });
    response.data.pipe(res); // 将请求流式传输至视频存储服务

    broadcastVideoUploadedMessage({
        id: videoId,
        name: fileName,
    }); // 广播视频上传成功的消息
});

最后,上传的视频到达 azure-storage 微服务,如清单 10.15(摘自 chapter-10/azure-storage/src/index.js)所示。从这里,视频被保存至 Azure Storage。完成整个链条后,我们成功地保存了用户上传的视频副本。如果想更深入了解如何将文件添加到 Azure Storage,请查看 azure-storage 微服务的完整代码。

清单 10.15 将视频从 HTTP POST 请求流式传输至 Azure Storage

app.post("/upload", async (req, res) => {
    const videoId = req.headers.id; // 从请求头获取视频 ID
    const contentType = req.headers["content-type"];

    const blobService = createBlobService();
    const containerClient = blobService.getContainerClient(STORAGE_CONTAINER_NAME);
    await containerClient.createIfNotExists(); // 确保存储容器存在

    const blockBlobClient = containerClient.getBlockBlobClient(videoId);
    await blockBlobClient.uploadStream(req); // 将视频流式上传到 Azure Storage
    await blockBlobClient.setHTTPHeaders({
        blobContentType: contentType, // 设置内容类型
    });
    res.sendStatus(200); // 发送上传成功状态码
});

在尝试将 FlixTube 部署到云端的生产 Kubernetes 集群之前,先在 Docker Desktop 自带的本地 Kubernetes 实例上进行部署练习是非常有帮助的。通常,我不建议在开发或测试阶段使用 Kubernetes,因为相比之下,使用 Docker Compose 来启动、重启和关闭整个应用程序更为简单。然而,为了确保生产部署的顺利进行,我们还是需要在本地 Kubernetes 上进行一些部署实践。

如果你急于将 FlixTube 部署到生产环境,可以直接跳到下一节。但是,需要注意,将 FlixTube 部署到本地 Kubernetes 实例通常比部署到云端的生产 Kubernetes 集群要简单,因此如果在后者上遇到问题,最好回到这一节,在更简单的环境中进行练习。

图 10.14 展示了 FlixTube 在本地 Kubernetes 实例上的运行情况。注意,在这个示例中,我们使用了 mock-storage 微服务替代了 azure-storage 微服务,这是为了避免连接到 Azure Storage 服务的复杂性。

图 10.14 将 FlixTube 部署到本地 Kubernetes 实例
图 10.14 将 FlixTube 部署到本地 Kubernetes 实例

10.8 在本地 Kubernetes 部署 FlixTube

在我们尝试将 FlixTube 部署到云中的生产 Kubernetes 集群之前,我们首先应该在 Docker Desktop 自带的本地 Kubernetes 实例上练习部署。就个人而言,我通常不会这样运行 FlixTube 进行开发或测试,因为使用 Docker Compose 启动、重启和关闭整个应用程序要简单得多,而不是使用 Kubernetes。但在我们开始完整的生产部署之前,使用我们的本地 Kubernetes 实例进行部署练习是值得的。

如果你迫不及待想要把 FlixTube 推向生产环境,可以跳到下一节。但请记住,将应用部署到本地 Kubernetes 实例比部署到云中的生产 Kubernetes 集群要简单一些,所以如果你在那里遇到问题,你应该回到这一节,在更简单的环境中练习你的部署。

图 10.14 展示了 FlixTube 在本地 Kubernetes 实例中运行的样子。请注意,我们已经将 azure-storage 微服务替换为 mock-storage 微服务,以避免连接 FlixTube 到 Azure 存储服务的复杂性。

图 10.14 在本地 Kubernetes 部署 FlixTube
图 10.14 在本地 Kubernetes 部署 FlixTube

10.8.1 本地部署的前提条件

在开始本地 Kubernetes 部署前,你需要安装 Docker Desktop 并启用其中的 Kubernetes 功能(详见第 6 章第 6.5 节)。这通常会自动为你提供 kubectl 命令行界面(CLI)工具,如果没有自动安装,你则需要手动安装它(详见第 6 章第 6.6 节)。如果你刚启用 Kubernetes,kubectl 应已连接到你的本地实例并可用。若需检查连接或重新配置 kubectl 与本地 Kubernetes 的连接,请参考第 6 章第 6.8.4 节。进行本地部署时,你不需要设置容器仓库。

10.8.2 本地部署操作

在 chapter-10 代码库中,我们提供了一个 shell 脚本用于在本地 Kubernetes 上进行部署。你可以这样调用它:

cd chapter-10/scripts/local-kub
./deploy.sh

Windows 用户需要使用 WSL2 Linux 终端执行这些 shell 脚本,因为它们在常规 Windows 终端下可能不兼容。

该 shell 脚本被设计为构建并部署 FlixTube 的所有微服务,尽管存在许多重复,但这样做是为了保持简单,便于你阅读和理解脚本的作用。

你可以在 VS Code 中打开 shell 脚本,查看其如何完成以下工作:

  • 使用 Docker 的 build 命令构建每个微服务(参见第 3 章第 3.8.2 节和第 6 章第 6.8.1 节)
  • 使用 kubectl 的 apply 命令将每个微服务部署到 Kubernetes(参见第 6 章第 6.8.5 节)
  • 通过各种 YAML 文件向 kubectl 提供每个微服务的部署配置,比如打开 gateway.yaml 文件查看其配置详情

10.8.3 测试本地部署

部署 FlixTube 到本地 Kubernetes 实例后,应检查其是否正常运作。可以使用 kubectl get podskubectl get deploykubectl get services 命令查看为 FlixTube 创建的 Kubernetes 资源是否已创建并运行正常。

kubectl get services 的输出中找到网关微服务的端口号(通常是一个 NodePort,端口号在 30000 或以上)。在浏览器中访问该端口:http://localhost:<the-port-number>,这样你就可以测试 FlixTube 前端是否正常工作。更多关于测试本地部署的详细信息,可以参见第 6 章第 6.8.5 节和第 6.8.6 节。

10.8.4 删除本地部署

完成测试后,可以使用以下提供的 shell 脚本删除部署:

./delete.sh

该 shell 脚本调用 kubectl delete 命令以从 Kubernetes 中删除每个微服务。或者,如果你想彻底清理本地 Kubernetes 实例,可以按照第 6 章第 6.5 节中的指南简单重置它。

完成这些操作后,别忘了禁用本地 Kubernetes 实例,这样可以节省系统资源和延长电池寿命(如果你使用的是笔记本电脑)。

10.9 手动将 FlixTube 部署到生产环境

随着我们逐步建立 CI/CD 流程,使得 FlixTube 在每次代码提交时都能自动部署到生产环境,这一目标已触手可及。然而,在我们启动自动化之前,我们首先需要确保能够手动完成部署流程(否则自动化的基础又将建立在何处?)。这个步骤相比在本地 Kubernetes 集群部署应用程序要复杂得多,因此如果你在此遇到任何困难,请返回第 10.8 节加强练习后再继续。

图 10.15 展示了我们如何使用 Docker 来构建和发布我们的镜像,并通过 kubectl 命令将容器部署到运行在 Azure 上的 Kubernetes 集群中。

图 10.15 将 FlixTube 手动部署到生产环境
图 10.15 将 FlixTube 手动部署到生产环境

10.9.1 生产部署的前提条件

在我们能够部署 FlixTube 之前,需要准备好以下几点:

  • 一个用于发布我们微服务 Docker 镜像的容器仓库。你可以依照第 3 章第 3.9.1 节的步骤,在 Azure 门户 UI 中创建你的仓库,或者依据第 7 章第 7.9.2 节的指南通过 Terraform 创建。记住要记录下容器仓库的 URL、用户名和密码;详情请见第 3 章第 3.9.1 节。
  • 一个用于部署我们微服务的 Kubernetes 集群。你可以根据第 6 章第 6.9 节的指南在 Azure 门户 UI 中创建一个集群,或者根据第 7 章第 7.11 节的说明通过 Terraform 创建。
  • 别忘了像第 6 章第 6.11.3 节中那样,将你的容器仓库与 Kubernetes 集群关联起来,这样集群才能从仓库中拉取镜像。
  • 你需要安装 kubectl(参见第 6 章第 6.6 节),并依照第 6 章第 6.10.3 节的说明对你的 Kubernetes 集群进行身份验证。

10.9.2 生产部署

在将微服务镜像发布到容器仓库之前,我们首先需要登录仓库并配置一些环境变量,这些环境变量会在部署脚本中使用。

打开一个终端,并设置以下环境变量:

export CONTAINER_REGISTRY=<url-to-your-container-registry>

提醒 Windows 用户,你需要在 WSL2 的 Linux 终端中执行这些命令,以使用这些 shell 脚本。

这个环境变量在部署脚本中被引用,同时也用于仓库的登录认证:

docker login $CONTAINER_REGISTRY

接着输入你的仓库用户名和密码。

完成仓库身份验证后,在同一个终端中运行生产部署脚本:

cd chapter-10/scripts/production-kub
./deploy.sh

这个 shell 脚本是硬编码的,旨在构建并部署 FlixTube 的所有微服务。虽然存在一些冗余,但这有助于你理解部署过程。你可以在 VS Code 中打开这个 shell 脚本,并将其与本地部署脚本(见第 10.8.2 节)进行比较,探究它们的区别。

你会发现,生产部署脚本将镜像推送到我们的容器仓库。它还使用 envsubst 命令(我们在第 8 章第 8.9.2 节中已学过)来填充每个微服务的模板配置,将 CONTAINER_REGISTRY 环境变量嵌入必要的地方。扩展后的模板配置通过管道传递给 kubectl,以部署每个微服务到 Kubernetes。

10.9.3 测试生产部署

将 FlixTube 部署到生产 Kubernetes 集群后,我们现在需要检验它是否正常运行。再次使用 kubectl get podskubectl get deploykubectl get services 命令查看为 FlixTube 创建的 Kubernetes 资源。更多关于测试已部署微服务的信息,请参阅第 6 章第 6.11.5 节和第 6.11.6 节。

通过 kubectl get services 命令的输出,我们可以找到网关微服务的 IP 地址。在浏览器中导航到该 IP 地址对应的网页:http://<the-ip-address>。现在,你应该可以测试 FlixTube 的前端。更多关于测试本地部署的详细信息,请查阅第 6 章第 6.8.5 节和第 6.8.6 节。

10.9.4 清理生产环境部署

在完成测试和试验后,你可以通过下述 shell 脚本来撤销部署:

./delete.sh

这个 shell 脚本利用 kubectl delete 命令来清除 Kubernetes 中的各个微服务,实现全面的撤销。

10.10 持续部署到生产环境

经过对手动将 FlixTube 部署到生产环境的测试和实践,我们已经准备好上线持续部署(CD)流程。你可以按照操作进行,但需注意,这一章节较前面章节将更具挑战性。如果遇到任何问题,可能需要回溯至本地或手动部署(如第 10.8 和 10.9 节所述)以识别问题源。

正如我们在第 8 章所做的那样,我们将利用 GitHub Actions 来搭建我们的 CD 流程。你可以轻松地将此流程迁移到其他任何 CD 平台。正如第 8 章所提到的,CD 流程本质上是以更高级的方式执行 shell 脚本,即使是那些提供精美 UI 的服务商也是如此。图 10.16 展示了 FlixTube 的 CD 流程架构。

图 10.16 FlixTube 的 CD 管道
图 10.16 FlixTube 的 CD 管道

10.10.1 持续部署的前提条件

要参与本节操作,你需要拥有一个 GitHub 账户。如果你已经按照第 8 章的步骤操作,你应该已经注册了账户。如果没有,请在 https://github.com/ 注册一个免费账户。

你还需要在第 10.9.1 节中提到的前提条件(容器仓库和 Kubernetes 集群)。此外,你需要用 Base64 编码的 Kubernetes 集群认证信息;具体操作请参见第 8 章第 8.9.5 节。

10.10.2 设置你自己的代码库

为了运行 FlixTube 的 CD 流程,你需要 fork chapter-10 代码库,并按照第 8 章第 8.9.9 节中的指南创建 secrets。你需要将以下 secrets 添加到你 fork 的代码库中:

  • CONTAINER_REGISTRY—你的容器仓库 URL
  • REGISTRY_UN—仓库的用户名
  • REGISTRY_PW—仓库的密码
  • KUBE_CONFIG—Base64 编码的配置,用于验证 kubectl 与集群的连接(见第 8 章第 8.9.5 节)

10.10.3 部署基础设施

在我们开始使用 CD 部署 FlixTube 前,我们应当先部署应用依赖的基础设施。这意味着需要手动将 MongoDB 和 RabbitMQ 部署到我们的 Kubernetes 集群。由于这些组件不频繁更改,我们无需为它们设置 CD;我们仅在初次设置时部署它们一次。

在 chapter-10 代码库中有一个 shell 脚本可用于执行此操作:

cd chapter-10
./scripts/cd/infrastructure.sh

请自行查看这个 shell 脚本。它非常简单,调用 kubectl apply 来部署 MongoDB 和 RabbitMQ 配置。

在第 4 章中,我曾建议我们的 Kubernetes 集群应保持无状态,即把持久性 MongoDB 数据库移出集群,并将数据存储于外部(和托管的)数据库中。我在这里建议直接将 MongoDB 加入你的集群,仅仅是为了方便。将 MongoDB 部署到你的集群可能比创建外部数据库并连接到集群更为简单。但对于实际的生产应用程序,我建议你使用托管的 MongoDB 数据库,将数据库提取出集群(搜索“托管 MongoDB”,你会找到多个选项)。返回第 4 章第 4.4.5 节和第 4.5.3 节,查看我为何这样建议的完整论据。

10.10.4 每个微服务一个 CD 管道

在 chapter-10 代码库下,浏览目录 .github/workflows,你会发现每个微服务都有独立的 GitHub Actions 工作流文件(YAML 文件)。

以网关微服务的 CD 流程配置为例;完整工作流见清单 10.16(chapter-10/.github/workflows/gateway.yaml)。注意,大部分工作由环境变量配置的 shell 脚本执行。

清单 10.16 启用网关的独立 CD 管道

name: Deploy gateway
# 命名 CD 管道

on:
  push:
    branches:
      - main
      # 在推送到 main 分支时运行工作流
    paths:
      - gateway/**
      # 限定 CD 管道在网关子目录内运行。此工作流仅在网关微服务代码变更时运行,这是在单一代码库中实现微服务独立 CD 管道的方法。

  workflow_dispatch:
    # 允许从 GitHub Actions UI 触发 CD 管道

jobs:
  deploy:
    runs-on: ubuntu-latest

    env:
      VERSION: ${{ github.sha }}
      CONTAINER_REGISTRY: ${{ secrets.CONTAINER_REGISTRY }}
      REGISTRY_UN: ${{ secrets.REGISTRY_UN }}
      REGISTRY_PW: ${{ secrets.REGISTRY_PW }}
      NAME: gateway
      DIRECTORY: gateway
      # 定义 shell 脚本和部署配置中使用的环境变量

    steps:
      - uses: actions/checkout@v3

      - name: Build
        run: ./scripts/cd/build-image.sh
        # 构建 Docker 镜像

      - name: Publish
        run: ./scripts/cd/push-image.sh
        # 将 Docker 镜像发布到容器仓库

      - uses: tale/kubectl-action@v1
        with:
          base64-kube-config: ${{ secrets.KUBE_CONFIG }}
          kubectl-version: v1.24.2
        # 安装并配置 kubectl

      - name: Deploy
        run: ./scripts/cd/deploy.sh
        # 展开配置模板并部署微服务

清单 10.16 显示了如何将 CD 管道限定在网关子目录内。这种方法确保了 CD 流程只与该子目录下的网关微服务代码相关。此工作流仅在网关微服务代码更改时被调用。

这种“范围限定”的功能允许我们将每个工作流专注于一个独立的微服务,从而允许每个微服务拥有独立的部署计划。这实现了微服务真正的力量(即它们是独立的),即使出于方便考虑,我们将所有 FlixTube 微服务放在一个代码库中(FlixTube 单一代码库)。

如果你想查看每个 shell 脚本的内容,请在 VS Code 中打开 chapter-10 代码库进行探索。你会看到我们现在应该已经熟悉的命令:使用 Docker 构建和发布镜像,使用 envsubst(见第 8 章第 8.9.2 节)展开模板配置,并使用 kubectl 部署我们的微服务。

10.10.5 测试 CD 管道

现在我们已经准备好测试我们的持续部署(CD)流程。首先,确保你已经将 chapter-10 代码库 fork 到你的 GitHub 账户,并根据第 10.10.2 节的指南配置了必要的 GitHub secrets。

你有两种方法可以触发 CD 流程。首先,尝试对 main 分支做出代码更改。可以进行一些简单的更改,例如在网关微服务的某个文件中添加 console.log("Hello world");。提交并推送这些更改应该会触发 CD 流程,随之微服务会被构建、发布并部署到 Kubernetes。你也可以通过 GitHub Actions UI 手动启动 CD 流程,这在第 8 章第 8.7.10 节有具体描述。

如果一切顺利,你应该能够为每个微服务独立触发 CD 流程,并将整个 FlixTube 应用程序部署到生产环境。部署完成后,使用第 10.9.3 节中描述的 kubectl 命令检查 FlixTube 是否正常运行,并获取连接到前端的 IP 地址。

现在 FlixTube 的部署已经实现自动化。随着我们对 FlixTube 代码进行功能添加和修改,各个微服务会自动部署到生产环境,随时准备供用户使用,这使得我们可以专注于开发面向客户的功能,减少基础设施和部署的干扰。

10.11 FlixTube 的未来

恭喜你!如果你已按照本章的步骤操作,FlixTube 现在应该已在生产环境中顺利运行,并且你已准备好继续其开发和迭代。你可以对代码进行更改,本地测试这些更改,然后将它们部署到生产环境。

FlixTube 未来的发展方向有哪些可能?这完全取决于你的创意和规划!在第 12 章中,我们将探讨 FlixTube 未来可能采用的技术路线:

  • 我们如何扩展系统以满足不断增长的用户需求?
  • 随着应用程序的扩大和开发团队规模的增长,我们如何扩展开发和部署流程?

目前,只需设想你希望在未来为 FlixTube 添加哪些类型的新微服务。图 10.17 展示了一些可能的未来发展方向。

图 10.17 FlixTube 未来可能的样子
图 10.17 FlixTube 未来可能的样子

10.12 继续学习

在本章中,我们探讨了 FlixTube 示例应用的结构和布局,并在开发过程中构建、运行和测试了它。接着,我们通过持续部署(CD)流程将其部署到生产环境。

你已经让 FlixTube 运行起来了,接下来怎么做呢?阅读书籍只能帮助你到一定程度。维持技能的关键在于不断的练习。尝试进行代码实验,添加功能,引入新的微服务,甚至尝试破坏 FlixTube 看会发生什么。持续的实践是提升开发艺术的关键。

开发是充满挑战的。它实质上是一个不断解决问题和寻找解决方案的过程。当你在使用任何工具或技术时遇到问题,回顾本书相关章节可能会找到答案。如果不够,你可能需要深入探索其他资源。

本书的后几章提供了一些指导,帮助你在未来的微服务开发旅程中导航。每章末尾的参考资料会帮助你继续学习之旅。但记住,持续实践是保持成功和技能掌握的关键。

以下是一些关于 UI 开发和微服务开发的推荐书籍:

  • 《React Quickly, 2nd ed.》,作者 Morten Barklund 和 Azat Mardan(Manning,2023)
  • 《Angular in Action》,作者 Jeremy Wilken(Manning,2018)
  • 《Getting MEAN with Mongo, Express, Angular, and Node, 2nd ed.》,作者 Simon D. Holmes 和 Clive Harber(Manning,2019)
  • 《Micro Frontends in Action》,作者 Michael Geers(Manning,2020)

关于微服务开发,你也可以参考以下书籍:

  • 《Designing Microservices》,作者 S. Ramesh(Manning,预计 2024 年春季)
  • 《Microservices: A Practical Guide, 2nd ed.》,作者 Eberhard Wolff(Manning,2019)
  • 《Microservices in Action》,作者 Morgan Bruce 和 Paulo A. Pereira(Manning,2018)
  • 《Microservices Patterns》,作者 Chris Richardson(Manning,2018)
  • 《The Tao of Microservices》,作者 Richard Rodger(Manning,2017)
  • 《Microservices in .NET, 2nd ed.》,作者 Christian Horsdal Gammelgaard(Manning,2021)
  • 《Microservice APIs: Using Python, Flask, FastAPI, OpenAPI and More》,作者 José Haro Peralta(Manning,2022)

总结

  • 我们可以使用 Node.js 开发、运行和测试单个微服务。
  • 我们可以使用 Docker Compose 在开发计算机上开发、运行和测试整个微服务应用程序。
  • 测试框架如 Jest 和 Playwright 可以用于对我们的代码库、微服务,甚至整个微服务应用程序运行自动化测试。
  • FlixTube 的主要功能是上传和流媒体播放视频。多个微服务共同提供这些功能。
  • 数据库基准可以用于将测试数据集加载到我们的数据库中。这不仅对自动化测试有用,对手动测试和产品演示也有帮助。
  • 为了减少本地开发和测试的微服务应用程序的大小,或者只是为了隔离它的一部分,我们可以用模拟版本替换整个微服务或一组微服务。例如,在本章中,为了方便测试和部署,我们使用 mock-storage 微服务代替了真实的 azure-storage 微服务。
  • 网关微服务是客户进入 FlixTube 应用程序的入口。它提供了前端,客户可以使用它与 FlixTube 互动。
  • FlixTube 通过一个基于 GitHub Actions 构建的持续部署(CD)管道部署到 Kubernetes。它将工作委托给几个构建、发布和部署微服务的 shell 脚本。
  • 使用命令 envsubst 展开模板化配置,该命令从环境变量中获取值,并将这些值替换到 Kubernetes 部署配置文件中。

文章导航

独立页面

这是书籍中的独立页面。

书籍首页

评论区