1. 简介
为什么要从单体式应用迁移到微服务架构?将应用分解为微服务具有以下优势,而大部分优势基于微服务是松散耦合的这一性质。
- 微服务可以独立测试和部署。部署单元越小,部署就越简单。
- 微服务能够以不同的语言和框架实现。对于每个微服务,您可以随意选择最适合特定用例的技术。
- 微服务可以由不同团队管理。微服务之间具有边界,因此您可以更轻松地为一个或多个微服务安排专属团队。
- 通过迁移到微服务,您可以解除团队之间的依赖。每个团队只需关注其所依赖的微服务的 API 即可。团队无需考虑这些微服务的实现方式以及发布周期等方面。
- 您可以更轻松地设计故障预防机制。在服务之间划分明确边界,有助于确定服务出现故障时的应对策略。
与单体式应用相比,微服务具有以下缺点:
- 由于基于微服务的应用是由不同服务组成的网络,而且这些服务通常以不明显的方式进行交互,因此系统的整体复杂性往往会增加。
- 与单体式应用的内部通信不同,微服务通过网络进行通信。在某些情况下,这可能会被视为安全问题。Istio 通过自动加密微服务之间的流量来解决此问题。
- 由于服务之间存在延迟,可能难以达到与单体式应用方法相同水平的性能。
- 系统的行为不是由单个服务引起的,而是由许多服务及服务交互引起的。因此,了解您的系统在生产环境中的行为方式(即可观测性)变得更加困难。Istio 也可以解决这一问题。
在本实验中,我们将在 Google Kubernetes Engine (GKE) 中运行微服务。Kubernetes 是一个用于管理、托管、扩缩和部署容器的平台。容器是一种可移植的代码封装和运行方式。它们非常适合微服务模式,使用容器时,每个微服务可以在各自的容器中运行。
在本实验中,我们将把现有的单体式应用部署到 Google Kubernetes Engine 集群,然后将其分解为微服务!
微服务的架构图
我们将首先将单体式应用分解为三个微服务,一次分解一个。这三个微服务包括 Orders、Products 和 Frontend。我们使用 Cloud Build 为每个微服务构建 Docker 映像,并从 Cloud Shell 中触发 Cloud Build。然后,我们将使用 Kubernetes 服务类型 LoadBalancer 在 Google Kubernetes Engine (GKE) 上部署并公开微服务。我们将为每项服务执行此操作,同时将它们从单体式应用中重构出来。在此过程中,单体式应用和微服务将同时运行,直至您最后能够删除单体式应用。

学习内容
- 如何将单体式应用分解为微服务
- 如何创建 Google Kubernetes Engine 集群
- 如何创建 Docker 映像
- 如何将 Docker 映像部署到 Kubernetes
前提条件
- 一个 Google Cloud Platform 账号,具有创建项目的管理员权限或具有 Project Owner 角色
- 对 Docker 和 Kubernetes 有基本了解
2. 环境设置
自定进度的环境设置
如果您还没有 Google 账号(Gmail 或 Google Apps),则必须创建一个。登录 Google Cloud Platform Console ( console.cloud.google.com) 并创建一个新项目:


请记住项目 ID,它在所有 Google Cloud 项目中都是唯一名称(很抱歉,上述名称已被占用,您无法使用!)。它稍后将在此 Codelab 中被称为 PROJECT_ID。
接下来,您需要在 Developers Console 中启用结算功能,以便使用 Google Cloud 资源并启用 Container Engine API。
在此 Codelab 中运行仅花费几美元,但是如果您决定使用更多资源或继续让它们运行,费用可能更高(请参阅本文档末尾的“清理”部分)。Google Kubernetes Engine 价格已记录在此处。
Google Cloud Platform 的新用户有资格获享$300 免费试用。
Google Cloud Shell
虽然 Google Cloud 和 Kubernetes 可以从笔记本电脑远程操作,但在此 Codelab 中,我们将使用 Google Cloud Shell,这是一个在云端运行的命令行环境。
基于 Debian 的这个虚拟机已加载了您需要的所有开发工具。它提供了一个持久的 5GB 主目录,并且在 Google Cloud 中运行,大大增强了网络性能和身份验证。这意味着在本 Codelab 中,您只需要一个浏览器(没错,它适用于 Chromebook)。
- 如需从 Cloud Console 激活 Cloud Shell,只需点击激活 Cloud Shell
(预配和连接到环境仅需花费一些时间)。
在连接到 Cloud Shell 后,您应该会看到自己已通过身份验证,并且相关项目已设置为您的 PROJECT_ID。
gcloud auth list
命令输出
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
命令输出
[core] project = <PROJECT_ID>
如果出于某种原因未设置项目,只需发出以下命令即可:
gcloud config set project <PROJECT_ID>
正在查找您的 PROJECT_ID?检查您在设置步骤中使用的 ID,或在 Cloud Console 信息中心查找该 ID:
默认情况下,Cloud Shell 还会设置一些环境变量,这对您日后运行命令可能会很有用。
echo $GOOGLE_CLOUD_PROJECT
命令输出
<PROJECT_ID>
- 最后,设置默认可用区和项目配置。
gcloud config set compute/zone us-central1-f
您可以选择各种不同的可用区。如需了解详情,请参阅区域和可用区。
3. 克隆源代码库
我们使用一个现有的单体式应用,该应用是一个虚构的电子商务网站,其中包含一个简单的欢迎页面、一个产品页面和一个订单历史记录页面。我们只需从 Git 代码库克隆源代码,以便专注于将其分解为微服务并部署到 Google Kubernetes Engine (GKE)。
运行以下命令,将 Git 代码库克隆到 Cloud Shell 实例,并切换到相应的目录。我们还将安装 NodeJS 依赖项,以便在部署之前测试单体式应用。此脚本可能需要几分钟才能运行完毕。
cd ~ git clone https://github.com/googlecodelabs/monolith-to-microservices.git cd ~/monolith-to-microservices ./setup.sh
此命令将克隆我们的 GitHub 代码库,切换到相应目录,并安装在本地运行应用所需的依赖项。此脚本可能需要几分钟才能运行完毕。
4. 创建 GKE 集群
现在您已经有了可用的开发者环境,接下来需要一个 Kubernetes 集群来部署单体式应用,最终还要部署微服务!在创建集群之前,我们需要确保已启用正确的 API。运行以下命令以启用 Containers API,以便使用 Google Kubernetes Engine:
gcloud services enable container.googleapis.com
现在,我们已准备好创建集群!运行以下命令,创建一个名为 fancy-cluster 且包含 3 个节点的 GKE 集群。
gcloud container clusters create fancy-cluster --num-nodes 3
集群可能需要几分钟才能完成创建。命令完成后,运行以下命令并查看集群的三个工作器虚拟机实例:
gcloud compute instances list
输出:
NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS gke-fancy-cluster-default-pool-ad92506d-1ng3 us-east4-a n1-standard-1 10.150.0.7 XX.XX.XX.XX RUNNING gke-fancy-cluster-default-pool-ad92506d-4fvq us-east4-a n1-standard-1 10.150.0.5 XX.XX.XX.XX RUNNING gke-fancy-cluster-default-pool-ad92506d-4zs3 us-east4-a n1-standard-1 10.150.0.6 XX.XX.XX.XX RUNNING
您还可以在 Google Cloud 控制台中查看 Kubernetes 集群和相关信息。点击左上角的菜单按钮,向下滚动到“Kubernetes Engine”,然后点击“集群”。此时应该会看到名为 fancy-cluster 的集群。


恭喜!您刚刚创建了第一个 Kubernetes 集群!
5. 部署现有的单体式应用
由于本实验的重点是逐步将单体式应用分解为微服务,因此我们需要先让单体式应用正常运行。运行以下脚本,将单体式应用部署到我们的 GKE 集群,以用于本实验:
cd ~/monolith-to-microservices ./deploy-monolith.sh
访问巨石
如需查找单体式应用的外部 IP 地址,请运行以下命令。
kubectl get service monolith
您应看到类似于以下内容的输出:
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE monolith 10.3.251.122 203.0.113.0 80:30877/TCP 3d
注意:需要为此预配外部负载平衡器和 IP,因此这需要一些时间。如果输出将外部 IP 列为
<pending> 请等待几分钟,然后重试。
确定单体式应用的外部 IP 地址后,请复制该 IP 地址。将浏览器指向此网址(例如 http://203.0.113.0)以检查您的单体式应用是否可访问。

您应该会看到单体式网站的欢迎页面,如上图所示。欢迎页面是一个静态页面,稍后将由 Frontend 微服务提供。现在,您的单体式网站已完全在 Kubernetes 上运行!
6. 将 Orders 迁移到微服务
现在,GKE 上已经在运行现有的单体式网站,接下来要将每个服务分解为微服务。具体分解哪些服务,通常需要提前做好计划,一般围绕应用的具体部分(如业务领域)进行拆分。为了演示,我们创建了一个简单的示例,并围绕业务领域(订单、产品和前端)分解各项服务。代码已迁移完毕,我们将专注于在 Google Kubernetes Engine (GKE) 上构建和部署服务。
创建新的 Orders 微服务
首先要分解的是 Orders 服务。我们将使用提供的单独代码库,并为此服务创建一个单独的 Docker 容器。
使用 Google Cloud Build 创建 Docker 容器
由于我们已为您迁移代码库,因此第一步是使用 Google Cloud Build 创建 Orders 服务的 Docker 容器。
通常,您需要分两步完成该操作。先构建 Docker 容器,然后将其推送到注册表,以便存储映像供 GKE 拉取。不过,我们可以让生活更轻松,只需一个命令即可使用 Google Cloud Build 构建 Docker 容器,并将映像放入 Google Cloud Container Registry!这样一来,我们只需发出一个命令即可构建映像并将其移至容器注册表。如需查看创建 Docker 文件和推送文件的手动过程,请点击此处。
Google Cloud Build 会压缩目录中的文件,并将它们移至 Google Cloud Storage 存储桶。然后,构建流程会从存储桶中提取所有文件,并使用 Dockerfile 运行 Docker 构建流程。由于我们为 Docker 映像指定了 --tag 标志,并将主机设置为 gcr.io,因此生成的 Docker 映像将推送到 Google Cloud Container Registry。
运行以下命令来构建 Docker 容器并将其推送到 Google Container Registry:
cd ~/monolith-to-microservices/microservices/src/orders
gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/orders:1.0.0 .
此过程需要几分钟时间,完成后,终端中将显示类似以下内容的输出:
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ID CREATE_TIME DURATION SOURCE IMAGES STATUS 1ae295d9-63cb-482c-959b-bc52e9644d53 2019-08-29T01:56:35+00:00 33S gs://<PROJECT_ID>_cloudbuild/source/1567043793.94-abfd382011724422bf49af1558b894aa.tgz gcr.io/<PROJECT_ID>/orders:1.0.0 SUCCESS
如需查看构建历史记录或实时监控构建过程,您可以前往 Google Cloud 控制台。点击左上角的菜单按钮,向下滚动到“工具”→“Cloud Build”,然后点击“历史记录”。在这里,您可以看到之前所有 build 的列表;其中应该只有您刚刚创建的 build。

点击 build ID,即可查看 build 的所有详细信息,包括日志输出。
在“build 详情”页面中,您可以点击“build 信息”部分中的映像名称,查看创建的容器映像。

将容器部署到 GKE
现在,我们已经将网站容器化并将容器推送到了 Google Container Registry,接下来就可以部署到 Kubernetes 了!
Kubernetes 将应用表示为 Pod,这是表示一个容器(或一组紧密耦合的容器)的单元。Pod 是 Kubernetes 中最小的可部署单元。在本教程中,每个 Pod 仅包含您的微服务容器。
如需在 GKE 集群上部署和管理应用,您必须与 Kubernetes 集群管理系统进行通信。您通常使用 Cloud Shell 中的 kubectl 命令行工具执行此操作。
首先,我们将创建一个 Deployment 资源。Deployment 管理应用的多个运行实例(称为副本),并安排这些副本在集群中的各个节点上运行。在本例中,Deployment 将只运行应用的一个 Pod。Deployment 通过创建 ReplicaSet 来确保这一点。ReplicaSet 负责确保指定数量的副本始终处于运行状态。
下面的 kubectl create deployment 命令会让 Kubernetes 在您的集群中创建一个名为 orders 的 Deployment,其中包含 1 个副本。
运行以下命令以部署应用:
kubectl create deployment orders --image=gcr.io/${GOOGLE_CLOUD_PROJECT}/orders:1.0.0
验证部署
如需验证 Deployment 是否已成功创建,请运行以下命令。Pod 状态可能需要过一会儿才会变为“Running”:
kubectl get all
输出:
NAME READY STATUS RESTARTS AGE pod/monolith-779c8d95f5-dxnzl 1/1 Running 0 15h pod/orders-5bc6969d76-kdxkk 1/1 Running 0 21s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.39.240.1 <none> 443/TCP 19d service/monolith LoadBalancer 10.39.241.130 34.74.209.57 80:30412/TCP 15h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/monolith 1/1 1 1 15h deployment.apps/orders 1/1 1 1 21s NAME DESIRED CURRENT READY AGE replicaset.apps/monolith-779c8d95f5 1 1 1 15h replicaset.apps/orders-5bc6969d76 1 1 1 21s
此输出显示了以下几项内容:我们可以看到 Deployment 已处于最新状态、ReplicaSet 设定的期望 Pod 数量为 1,并且 Pod 正在运行。看来一切都已成功创建!
公开 GKE 容器
我们已在 GKE 上部署了应用,但无法从集群外部访问该应用。默认情况下,您在 GKE 上运行的容器无法通过互联网访问,因为这些容器没有外部 IP 地址。您必须通过 Service 资源明确公开应用,以便允许来自互联网的流量访问。Service 为应用的 Pod 提供网络和 IP 支持。GKE 会为您的应用创建外部 IP 地址和负载平衡器(需要付费)。
部署 Orders 服务时,我们通过 Kubernetes Deployment 在内部将该服务公开在端口 8081 上。为了从外部公开该服务,我们需要创建一个类型为 LoadBalancer 的 Kubernetes 服务,以将外部端口 80 的流量路由到 Orders 服务的内部端口 8081。运行以下命令,将您的网站公开到互联网:
kubectl expose deployment orders --type=LoadBalancer --port 80 --target-port 8081
访问本服务
GKE 会将外部 IP 地址分配给 Service 资源,而不是 Deployment。如果要查找 GKE 为应用预配的外部 IP,可以使用 kubectl get service 命令检查 Service:
kubectl get service orders
输出:
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE orders 10.3.251.122 203.0.113.0 80:30877/TCP 3d
确定应用的外部 IP 地址后,请复制该 IP 地址。保存此信息,以便在下一步修改单体式应用,使其指向新的 Orders 服务时使用!
重新配置单体式应用
由于我们已从单体式应用中移除 Orders 服务,因此必须修改单体式应用,使其指向新的外部 Orders 微服务。
在分解单体式应用时,我们需要将代码段从单个代码库中移除,然后将其分别部署到多个微服务中。由于微服务在不同的服务器上运行,我们不能再将服务网址作为绝对路径引用,而是需要将其路由到 Orders 微服务的新服务器地址。请注意,这需要单体式服务停用一段时间,以便更新已分解出的每个服务的网址。在微服务迁移过程中,当规划将微服务和单体式应用迁移到生产环境时,应考虑到这一点。
我们需要更新单体式应用中的配置文件,以指向新的 Orders 微服务的 IP 地址。使用 nano 编辑器将本地网址替换为新 Orders 微服务的 IP 地址。运行以下命令以修改
cd ~/monolith-to-microservices/react-app nano .env.monolith
编辑器打开后,您的文件应如下所示:
REACT_APP_ORDERS_URL=/service/orders REACT_APP_PRODUCTS_URL=/service/products
将 REACT_APP_ORDERS_URL 替换为新格式,同时替换为您的 Orders 微服务的 IP 地址,使其如下所示:
REACT_APP_ORDERS_URL=http://<ORDERS_IP_ADDRESS>/api/orders REACT_APP_PRODUCTS_URL=/service/products
按 CTRL+O,再按 ENTER,然后按 CTRL+X,即可在 nano 编辑器中保存文件。
您可以访问刚刚在此文件中设置的网址,以此测试新的微服务。网页应返回来自 Orders 微服务的 JSON 响应。
接下来,我们需要重建单体式应用前端,并重复构建流程来构建单体式应用的容器,然后将其重新部署到 GKE 集群。运行以下命令来完成这些步骤:
重建单体式应用配置文件
npm run build:monolith
使用 Google Cloud Build 创建 Docker 容器
cd ~/monolith-to-microservices/monolith
gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/monolith:2.0.0 .
将容器部署到 GKE
kubectl set image deployment/monolith monolith=gcr.io/${GOOGLE_CLOUD_PROJECT}/monolith:2.0.0
在浏览器中前往单体式应用,并导航到“Orders”(订单)页面,验证应用是否已接入新的 Orders 微服务。所有订单 ID 都应以 -MICROSERVICE 为后缀,如下所示:

7. 将 Products 迁移到微服务
创建新的 Products 微服务
接下来迁移 Products 服务,继续分解服务。我们将按照与上一步相同的流程操作。运行以下命令来构建 Docker 容器、部署容器,并通过 Kubernetes 服务公开容器。
使用 Google Cloud Build 创建 Docker 容器
cd ~/monolith-to-microservices/microservices/src/products
gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/products:1.0.0 .
将容器部署到 GKE
kubectl create deployment products --image=gcr.io/${GOOGLE_CLOUD_PROJECT}/products:1.0.0
公开 GKE 容器
kubectl expose deployment products --type=LoadBalancer --port 80 --target-port 8082
按照查找 Orders 服务公共 IP 地址的方式,使用以下命令查找 Products 服务的公共 IP 地址:
kubectl get service products
输出:
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE products 10.3.251.122 203.0.113.0 80:30877/TCP 3d
保存该 IP 地址,以便在下一步中重新配置单体式应用,使其指向新的 Products 微服务。
重新配置单体式应用
使用 nano 编辑器将本地网址替换为新 Products 微服务的 IP 地址:
cd ~/monolith-to-microservices/react-app nano .env.monolith
编辑器打开后,您的文件应如下所示:
REACT_APP_ORDERS_URL=http://<ORDERS_IP_ADDRESS>/api/orders REACT_APP_PRODUCTS_URL=/service/products
将 REACT_APP_PRODUCTS_URL 替换为新格式,同时替换为您的 Product 微服务的 IP 地址,使其如下所示:
REACT_APP_ORDERS_URL=http://<ORDERS_IP_ADDRESS>/api/orders REACT_APP_PRODUCTS_URL=http://<PRODUCTS_IP_ADDRESS>/api/products
按 CTRL+O,再按 ENTER,然后按 CTRL+X,即可在 nano 编辑器中保存文件。
您可以访问刚刚在此文件中设置的网址,以此测试新的微服务。网页应返回来自 Products 微服务的 JSON 响应。
接下来,我们需要重建单体式应用前端,并重复构建流程来构建单体式应用的容器,然后将其重新部署到 GKE 集群。运行以下命令来完成这些步骤:
重建单体式应用配置文件
npm run build:monolith
使用 Google Cloud Build 创建 Docker 容器
cd ~/monolith-to-microservices/monolith
gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/monolith:3.0.0 .
将容器部署到 GKE
kubectl set image deployment/monolith monolith=gcr.io/${GOOGLE_CLOUD_PROJECT}/monolith:3.0.0
在浏览器中前往单体式应用,并导航到“Products”(产品)页面,验证应用是否已接入新的 Products 微服务。所有产品名称都应以 MS- 为前缀,如下所示:

8. 将 Frontend 迁移到微服务
迁移过程的最后一步是将 Frontend 代码移至微服务并关闭单体式应用。完成此步骤后,我们就成功将单体式应用迁移到了微服务架构。
创建新的前端微服务
按照与前两个步骤相同的流程,创建新的 Frontend 微服务。
之前,我们在重建单体式应用时更新了配置,以指向单体式应用,但现在我们需要为 Frontend 微服务使用相同的配置。运行以下命令,将微服务网址配置文件复制到 Frontend 微服务代码库:
cd ~/monolith-to-microservices/react-app cp .env.monolith .env npm run build
完成后,我们将按照与之前步骤相同的流程操作。运行以下命令来构建 Docker 容器、部署容器,并通过 Kubernetes 服务公开容器。
使用 Google Cloud Build 创建 Docker 容器
cd ~/monolith-to-microservices/microservices/src/frontend
gcloud builds submit --tag gcr.io/${GOOGLE_CLOUD_PROJECT}/frontend:1.0.0 .
将容器部署到 GKE
kubectl create deployment frontend --image=gcr.io/${GOOGLE_CLOUD_PROJECT}/frontend:1.0.0
公开 GKE 容器
kubectl expose deployment frontend --type=LoadBalancer --port 80 --target-port 8080
删除单体式应用
现在所有服务都作为微服务运行,我们可以删除单体式应用了!请注意,在实际迁移中,您还需要进行 DNS 更改等操作,以使现有域名指向应用的新前端微服务。运行以下命令以删除单体式应用:
kubectl delete deployment monolith kubectl delete service monolith
测试您的工作
要验证一切是否正常运作,您的单体式服务旧 IP 地址应无法访问,而前端服务的新 IP 地址上应运行新应用。如需查看所有服务和 IP 地址的列表,请使用以下命令:
kubectl get services
您的输出应如下所示:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE frontend LoadBalancer 10.39.246.135 35.227.21.154 80:32663/TCP 12m kubernetes ClusterIP 10.39.240.1 <none> 443/TCP 18d orders LoadBalancer 10.39.243.42 35.243.173.255 80:32714/TCP 31m products LoadBalancer 10.39.250.16 35.243.180.23 80:32335/TCP 21m
确定 Frontend 微服务的外部 IP 地址后,请复制该 IP 地址。将浏览器指向此网址(例如 http://203.0.113.0)以检查您的 Frontend 微服务是否可访问。您的网站应该与将单体式应用分解为微服务之前的网站相同!
9. 清理
准备就绪后,清理所有已执行活动的最简单方法是删除项目。删除项目会删除本 Codelab 中创建的所有资源,以确保不会产生意外的重复费用。在 Cloud Shell 中执行以下命令,其中 PROJECT_ID 是完整的项目 ID,而不仅仅是项目名称。
gcloud projects delete [PROJECT_ID]
出现提示时,输入“Y”以确认删除。
10. 恭喜!
您已成功将单体式应用分解为微服务,并将其部署到 Google Kubernetes Engine 上!
后续步骤
您可以查看以下 Codelab,详细了解 Kubernetes:
- 在 Google Kubernetes Engine 上部署、扩缩容和更新网站
- 在 Kubernetes 上使用 Node.js 构建 Slack 机器人
- 使用 Spinnaker 向 Kubernetes 持续交付
- 将 Java 应用部署到 Google Kubernetes Engine 上的 Kubernetes
其他资源
- Docker - https://docs.docker.com/
- Kubernetes - https://kubernetes.io/docs/home/
- Google Kubernetes Engine (GKE) - https://cloud.google.com/kubernetes-engine/docs/
- Google Cloud Build - https://cloud.google.com/cloud-build/docs/
- Google Container Registry - https://cloud.google.com/container-registry/docs/
- 将单体式应用迁移到微服务 - https://cloud.google.com/solutions/migrating-a-monolithic-app-to-microservices-gke