← 返回蜂巢洞察

如何将应用程序从AWS迁移到GKE [完整指南]

这个指导性实验将带你了解如何迁移一个小型支付应用。该应用包含使用React开发的用户界面以及运行在AWS EC2实例上的Express API,而数据库则仍保存在AWS上。 你需要将用户界面和API迁移到Google Kubernetes Engine上,但无需更改客户目前使用的公共URL地址,而是逐步将流量从AWS转移至GKE,每次转移的比例为几百分比。 完成整个迁移过程后,你将能够在本地运行该应用,在AWS上搭建基础设施,通过八个Terraform步骤在GKE上构建环境,测试新环境是否正常工作,然后将生产环境的流量全部转移到GKE上,完成切换流程,并拆除不再需要的资源。 下图展示了迁移过程

这个指导性实验将带你了解如何迁移一个小型支付应用。该应用包含使用React开发的用户界面以及运行在AWS EC2实例上的Express API,而数据库则仍保存在AWS上。

你需要将用户界面和API迁移到Google Kubernetes Engine上,但无需更改客户目前使用的公共URL地址,而是逐步将流量从AWS转移至GKE,每次转移的比例为几百分比。

完成整个迁移过程后,你将能够在本地运行该应用,在AWS上搭建基础设施,通过八个Terraform步骤在GKE上构建环境,测试新环境是否正常工作,然后将生产环境的流量全部转移到GKE上,完成切换流程,并拆除不再需要的资源。

下图展示了迁移过程中的中间状态:此时两种云服务都在正常运行,客户仍然使用同一个URL地址,数据库也依然保存在AWS上。

显示迁移过程中中间状态的图片,两种云服务都在运行中,客户使用同一URL地址,数据库位于AWS上。

在那个请求路径周围,还有另外三个系统在各自执行相应的功能。

展示请求路径及相关系统的图片

目录

在开始之前

需要安装的工具

请在相应章节中安装所需的工具。第一天并不需要安装所有工具。

章节 所需工具
1 Docker Desktop(或在Linux系统中使用Docker Engine与Compose),Git以及python3
4 一个AWS账户,AWS CLI(运行一次aws configure命令),Terraform 1.5或更高版本,rsync工具,以及一个SSH密钥
6 在您的笔记本电脑上安装Node.js 20或更高版本。scripts/目录中的检测脚本会使用node命令来解析JSON文件
10 一个已开启计费功能的GCP项目,gcloud CLI以及kubectl
14 一个GitHub账户,以及您自己在GitHub上复制的这个仓库
19 kustomize工具(在Mac系统上使用brew install kustomize进行安装;更多信息请访问kubectl/docs.kubernetes.io)

在Windows系统中,执行本书中的所有命令时请使用Git Bash或WSL。这些命令都是为Bash shell编写的。

所需技能

事先具备Kubernetes、Terraform、GCP或负载均衡相关的经验并非必需。无需购买域名。在AWS系统中,您只需使用https://加上Elastic IP地址即可。浏览器会提示证书为自签名证书,但点击确认后,您仍然可以使用与笔记本电脑上相同的ClearPay登录方式。在GCP系统中,您需要在/etc/hosts文件中添加一条记录,使https://api.clearpay.test指向负载均衡器;除非您选择了使用Cloud DNS。

成本估算

在AWS系统中,每日使用费用约为1.25美元。在GCP系统中,一旦环境搭建完成,每日费用约为1.60至3美元。如果整个流程只在周末进行,所需费用也会仅为几美元而已。

这两种开发环境都不应连续运行超过一个月。如果您停止使用它们超过几天,就需要分别执行以下命令来销毁这些环境:

./scripts/teardown-gcp.sh
cd infra/aws/terraform && terraform destroy

详细费用信息记录在两个文件中:infra/aws/COST.md以及infra/gcp/COST.md。本书仅推荐您参考这两个文件,其他文档无需查阅。

本书的使用方法

请克隆该仓库,然后从其根目录开始执行所有命令,除非有特别说明需要切换到其他目录:

git clone https://github.com/Osomudeya/migrate-api-aws-gke-devops.git

cd migrate-api-aws-gke-devops

该仓库中已经包含了应用程序代码、Terraform配置文件、Kubernetes相关资源、部署流程以及各种脚本。

你不需要编写新的代码。你的工作就是依次执行每一步操作,查看系统输出的结果,然后决定是否继续进行下一步。

这本书读起来就像一个故事:在第一周,你加入了一家小型支付公司,而每一章都描述了团队中某个人交给你的具体任务。

每一章都会以一个时间戳和相应的场景作为开头,随后会按照相同的步骤进行操作:

本书所遵循的流程图

最重要的规则

千万不要执行那些你无法理解的命令。如果这本书还没有解释其中的原因,请继续阅读,因为了解“为什么”才是关键所在。

这本书中的步骤分为两种类型。

第一种类型的步骤只是用来确认操作是否成功,比如文件是否被复制完毕,或者容器是否已经启动。如果操作成功,就继续下一步。

第二种类型的步骤则要求你仔细思考操作过程中发生了什么。在命令执行完成后,一定要阅读输出结果,弄清楚其含义,然后再进入下一章。

章节内容

请按顺序阅读这些章节,不要跳过任何一章。第一章并不会要求你安装Kubernetes或开启GCP,这些内容会在后面的章节中介绍。

>
部分 章节编号具体操作内容
1. 运行ClearPay并将其部署到AWS上 1–6章 你在笔记本电脑上运行该应用程序,然后将其部署到一台AWS服务器上,并记录其运行速度。
2. 规划后续迁移步骤 7–9章 你需要阅读相关说明,了解该应用程序依赖哪些组件,并研究GCP的设计方案。
3>使用Terraform构建Google Cloud环境 10–11章 你将通过八个步骤逐步使用Terraform来构建GCP环境。
4>将ClearPay部署到GKE测试环境中 12–15章 通过自动化流程将ClearPay部署到GKE测试环境中,并进行相应的测试。
5>使GKE环境准备好投入生产使用 16–18章 你需要检查安全设置,配置监控仪表盘,并故意引发一些问题以验证系统的稳定性。
6>将客户数据迁移至GKE环境中 19–23章 你需要完成生产环境的部署工作,并逐步将所有流量切换到GKE环境上。
7>完成整个迁移过程 24–28章 你需要验证迁移结果的准确性,关闭AWS环境,拆除所有的临时配置,最后整理相关文档以记录整个迁移过程。

如果出现问题怎么办

如果某个步骤出现了问题,请留在当前章节中,按照其中的“如果操作失败”部分提供的指导进行排查。千万不要跳过当前步骤继续前进,因为遗留下来的问题会变得越来越难以解决,因为下一章的内容会引入更多的复杂因素。

学习的目标并不是死记硬背Kubernetes或Terraform的相关知识,而是要掌握如何实际完成一次迁移任务:了解各个组件的运行情况,根据观察到的结果做出决策,验证最终的结果,以及知道在遇到问题时该采取什么措施。

整个过程需要多长时间

<这可能需要一天以上的时间来完成。如果你需要离开一段时间(超过一两天),请删除AWS和GCP上的相关资源,这样在它们处于闲置状态时就不会产生费用了。相关的操作命令可以在上方的“费用说明”部分找到。>

团队成员

在第一周,你将扮演ClearPay团队中的一名初级DevOps工程师Zee来开始这个实验。

  • Maya是工程经理,她会要求你提供相应的证据。

  • Daniel是一名资深工程师,他会与你一起负责服务器、Kubernetes以及系统容量相关的任务。

  • Amara负责维护支付API,她最关心的一点就是确保没有客户会被重复收费。

  • Chris负责处理客户支持工作,当转账出现故障时,他会是第一个收到通知的人。

你的任务是在不导致任何客户体验中断的情况下,将支付API从一台AWS EC2服务器迁移到GKE上,同时确保PostgreSQL数据库仍继续运行在AWS上。

第1部分:如何运行ClearPay并将其部署到AWS上

接下来该做什么?

任何迁移工作都需要一个可靠的起点。在这一部分中,你需要在自己的笔记本电脑上运行ClearPay程序,然后按照公司当前的实际运营方式将其部署到AWS上,并记录下其运行速度。在后续章节中,我们会将这些数据与GKE的运行表现进行对比。

第1章:在笔记本电脑上运行ClearPay

第一周,周一上午9:05

你的笔记本电脑是全新的,Wi-Fi密码写在了一张便签纸上。收件箱里的第一条消息来自工程经理Maya,内容很简单:

Maya:“首先在你的笔记本电脑上运行ClearPay程序。登录后先发送20美元的款项,然后再讨论如何将API迁移到GKE上。”

相关的代码存储在以下仓库中:api/、ui/和db/文件夹里。目前这些代码还没有被运行起来。

为什么这很重要

后续章节会需要参考你在笔记本电脑上成功运行ClearPay时的操作过程,以及成功发送20美元款项的实例。当AWS或GKE出现故障时,你可以将这些实际情况与之前的记录进行对比。

你即将运行的内容

ClearPay由三个部分组成,每个部分都有特定的功能。

  • 用户界面是客户能够看到的部分,包括登录页面、余额显示以及“发送款项”按钮等。UI代表“user interface”。

  • 应用程序编程接口则是负责执行具体操作的部分。它没有屏幕界面,用户界面会向它发送指令,比如“向Bob发送20美元”,API接收到指令后会执行相应的操作,并返回结果,例如“操作已完成”。API代表“application programming interface”。

  • 数据库用于存储所有数据,包括用户的账户信息、余额以及交易记录。ClearPay使用的是PostgreSQL作为数据库程序。

  • 服务器是一台全天候运行的计算机,它会等待来自用户端的请求。API程序就是运行在服务器上的。

咖啡馆的运作方式也与此类似:菜单就是用户界面,服务员则相当于应用程序编程接口,厨房及其记录则构成了数据库。顾客永远不会进入厨房,他们只需要告诉服务员自己想要什么就行了。

用于演示应用程序运行流程的图片

在本章中,你可以在笔记本电脑上同时运行这三部分内容。后续章节会将用户界面和API迁移到Google Cloud平台上,而数据库则继续保留在AWS上。

操作步骤

首先启动Docker Desktop。在本章中不需要安装Node.js或PostgreSQL,因为它们会运行在容器内部。

在包含docker-compose.yml文件的文件夹中,执行以下命令:

docker compose up --build

第一次构建过程需要几分钟时间。此时终端会持续输出日志信息,直到你按下Ctrl+C键才会停止输出。请让这个进程继续运行。

等待直到出现以下三行输出。左侧显示的名称可能会以你的文件夹名开头:

Container -postgres-1 Healthy
api-1       | {"message":"apistarted", ...}
ui-1        |   ➜  Local: http://localhost:5173/

Healthy表示Postgres数据库已经启动;api_started表示API服务已启动;而localhost:5173则表示用户界面已经可用。

在同一文件夹中打开另一个终端窗口,然后创建两个演示账户:

docker compose exec api node db/seed.js

这条命令会直接运行种子脚本。故意没有在API容器中安装npm,因为第14章中的安全扫描会检测到这一设置。

预期的输出结果如下:

seeded: alice@clearpay.dev (password: password123)
seeded: bob@clearpay.dev (password: password123)

如果看到skip (already exists): alice@clearpay.dev这样的提示,说明这些账户是在之前的运行过程中已经创建的,这属于正常情况。

目前系统运行的组件如下: 当前系统运行组件的流程图:浏览器、React用户界面、Express API以及Postgres数据库

验证运行状态

在第二个终端窗口中,执行以下命令:

curl localhost:4000/health

预期的输出结果中,instance字段会显示一个容器ID。

输出示例:

{"status":"ok","served_by":{"platform":"aws","version":"1.0.0","instance":"14b8d5d767ea","commit":"local","rollback_active":false}}

"status":"ok"表示系统运行正常;served_by字段指出了处理请求的平台。在你的笔记本电脑上,这个值为aws,因为docker-compose.yml文件中设置了PLATFORM: aws。后续当使用GKE时,该字段会显示gke,从而说明是哪个云平台处理了请求。

现在打开http://localhost:5173进行登录。由于系统已经为Alice创建好了账户,因此无需再次注册她。

  • 电子邮件:alice@clearpay.dev

  • 密码:password123

向bob@clearpay.dev转账20美元。这笔转账会以“已发送”为状态显示在“近期活动”栏目中,同时会附带一个简短的识别码,例如Sent · a1b2c3d4。

展示我们应用程序用户界面的图片

请关注这个控制面板上的两个区域。在本书的后续内容中,你还会经常用到这两个区域:

  1. “近期活动”栏目下的每一条记录都会以served by aws/1.0.0结尾。这一信息表示处理这笔转账的API所使用的平台及其版本。这些信息会与转账记录一起存储在数据库中。

  2. 当/health/ready命令执行成功时,顶部的黑色条会显示“所有系统均正常运行”。这意味着API进程已经启动,并且能够与Postgres数据库进行通信。不过这条信息与登录过程无关。如果输入的密码错误,表单上会显示红色错误提示,但黑色条仍然会显示“所有系统均正常运行”。

在阅读第5章之后,你可以在AWS地址上打开同一个控制面板。使用相同的登录信息,依然可以找到那两个需要关注的区域。

如果出现故障:

  • 如果出现“无法连接到docker API”或“docker.sock错误”,说明Docker Desktop没有运行。请先启动它,等待其准备好后再重新执行docker compose up --build命令。

  • password123。如果这个问题仍然存在,请再次执行seed命令。如果系统中显示“skip (already exists)”这一行,那就说明该账户确实已经存在。

  • docker compose up命令立即退出,请在另一个终端中执行docker compose logs api命令。在系统首次启动时,API进程可能会先于Postgres数据库准备好接受连接请求。此时请再次尝试执行docker compose up命令。

  • 4000或5173已被其他程序占用,请停止该程序,或者修改docker-compose.yml文件中的端口映射设置。

如何处理故障:

如果转账操作成功完成了,说明应用程序运行正常,可以继续下一步操作。

如果转账失败了,请在笔记本电脑上及时解决问题。因为在AWS或GKE平台上进行调试会相对困难,但在本地机器上,只需执行一条docker compose logs命令就能找到问题所在。

何时说明任务完成:

  • 当docker compose up命令能够无错误地运行时,说明任务已经完成。

  • 当seed工具报告系统中存在两个账户(无论是新创建的还是被跳过的账户)时,也说明任务完成。

  • 当你通过应用程序成功完成了20美元的转账操作,并且能在“近期活动”栏目中看到这笔转账记录时,也说明任务完成。

第2章. 阅读关键代码

星期一,上午11:30

20美元已经转到了账户里。你还在看着转账界面,这时一把椅子滚到了你的旁边。坐在隔壁办公桌上的高级工程师丹尼尔正喝着咖啡,他并不着急。

Daniel看了看你的屏幕,说道:“你不需要记住每一行代码。只要知道哪个文件负责做什么功能就行了,这样在迁移过程中就能快速打开相应的文件。”

为什么这很重要

后续的每一章都会涉及到这四个文件中的某个。现在花五分钟记住这些信息,以后在紧张的工作环境中就能节省大量的搜索时间。

操作步骤

按照以下顺序打开这些文件:

  1. api/src/index.js负责启动API并连接所有的路由路径,包括健康检查、/api/info接口、登录功能以及交易处理。

  2. api/src/servedBy.js这个文件只有17行代码。它会从环境变量中读取PLATFORM、APP_VERSION和ROLLBACK_ACTIVE这些值,然后 dashboard会使用这些信息来显示“由aws/1.0.0提供支持”这样的提示信息。

  3. api/src/routes/transactions.js用于处理资金转账操作。在文件的开头部分可以找到idempotency_key相关的检查逻辑,这个代码会在其他所有操作之前被执行。

  4. api/src/db.js负责将API与Postgres数据库连接起来。在这个文件中可以找到DB_POOL_SIZE || '10'这条配置信息。

Daniel在提到最后一个文件时停了下来,说道:

“数据库连接就相当于通往数据库的‘通道’。一个API进程最多可以同时建立10个这样的连接。请记住这个数字。”

你现在掌握了什么

index.js                 → 启动API并连接各个路由路径
servedBy.js              → 显示是由哪种云服务提供支持(aws或gke)
routes/transactions.js   → 处理资金转账操作,会先检查idempotency_key
db.js                    → 与Postgres数据库进行交互,每个进程最多可建立10个连接

servedBy.js负责在dashboard上显示“由aws/1.0.0提供支持”这样的提示信息。这表示是哪种云服务完成了请求处理,而不是哪位用户进行了登录操作。

目前只有一个API进程在运行,因此整个系统最多只能建立10个连接。而在GKE环境中,会同时运行多个API实例。Kubernetes将每个这样的实例称为一个“pod”,每个pod都会拥有自己的10个连接资源,而且所有pod都会共享同一个数据库。第22章会再次讨论这个相关内容。

什么时候算完成

  • 你不需要查看文件内容,就能立刻判断出是哪个文件设置了连接限制,以及哪个文件用于指定所使用的平台版本。

第3章:证明重试机制能够避免重复收费

星期一,下午2:10

午饭后,你收到了一条来自一个你还未曾见过面的人的消息。Amara负责维护支付相关的API系统,Maya知道你的笔记本电脑上安装了ClearPay软件,因此她没有在消息中添加问候语。

Amara说:“用同一个密钥分别发送两次20美元的转账请求。Alice的账户余额应该减少20美元,而不是40美元。”

为什么这很重要

Alice向Bob转了20美元,但她点击“发送”按钮后屏幕就卡住了。由于不确定交易是否成功,她又重新点击了一次“发送”按钮。

如果没有相应的保护机制,API会将这两个请求视为独立的操作,并因此收取两次费用。结果Alice反而损失了40美元。

idempotency_key这一机制可以防止这种情况的发生。“幂等”意味着重复执行某项操作也不会产生不同的结果:两次执行该操作会得到与一次执行相同的结果。
这个“标签”其实是应用程序为每笔交易指定的标识符,例如round-a。
  1. 第一个请求会使用标签round-a进行发送。API会收取20美元的费用,并将这个标签保存下来。

  2. 第二个请求也会使用相同的标签round-a。API再次看到这个标签后,会意识到这是同一笔交易,因此不会再收费,而是会返回之前的处理结果。

相同的标签意味着是同一笔交易。 在系统迁移过程中,这一点尤为重要。有时,第一次支付请求会发送到AWS服务器,而重试请求则会发送到GKE服务器。虽然这是两台不同的服务器,但它们使用的是同一个数据库。GKE服务器会在该数据库中查找round-a这个标签,从而避免重复收费。正是这个共享的数据库确保了客户在两种云环境中都能得到一致的服务。 在本章中,请使用curl命令来进行操作,而不是点击“汇款”按钮。因为用户界面会在每次点击时生成一个新的标识符,这意味着每次点击都会被视为一笔新的转账请求。 如果想更深入地了解幂等性的概念,可以参考PayFlow:一个用于学习真实DevOps技术的金融科技系统这篇文章。

操作步骤

请继续在第二个终端中,进入包含docker-compose.yml文件的文件夹。每次只运行一条命令,并在执行下一条命令之前先查看之前的输出结果。

以Alice的身份登录系统。系统会将她的登录令牌存储在一个名为TOKEN的shell变量中。这个令牌证明了Alice已经成功登录,后续的命令都会通过$TOKEN变量来传递这个令牌。

TOKEN=$(curl -s -X POST localhost:4000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@clearpay.dev","password":"password123"}' \
  | python3 -c "import sys, json; print(json.load(sys.stdin')[‘token’])")

如果操作成功,这条命令不会输出任何内容。你可以运行echo $TOKEN来确认令牌的值,应该会看到一串长字符。

接下来,查询Alice的账户信息,并记下balance_cents这个数值。

curl -s localhost:4000/api/accounts/me -H "Authorization: Bearer $TOKEN"
查询结果应该符合预期的格式。不过你的账户编号和id数值可能会有所不同:
{"account":{"id":"...","balance_cents":"94000","currency":"USD"},"served_by":{"platform":"aws","version":"1.0.0","instance":"...","commit":"local","rollback_active":false}}
“94000”表示940美元。如果你还没有进行过任何交易,那么查询结果中的balance_cents数值应该是“100000”,即1000美元。如果命令提示“missing_token”或“invalid_token”,请重新执行第一步操作。

现在,请选择一个你尚未使用过的标签:
LABEL=round-a
向Bob汇款20美元。2000表示20美元的金额,以分为单位。
curl -s -X POST localhost:4000/api/transactions \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "{\"to_email\":\"bob@clearpay.dev\",\"amount_cents\":2000,\"idempotency_key\":\"$LABEL\"}"

在回复中查找"replayed"这一字段:

  • "replayed":false表示这次交易花费了20美元,可以继续进行下一步操作。

  • "replayed":true表示这个标签之前已经被使用过,可能是在之前的某次交易中。因此不会再次收取费用。请将LABEL设置为round-b,然后重新发送请求,直到看到"replayed":false为止。

请再次发送完全相同的请求,不要更改LABEL的值。

curl -s -X POST localhost:4000/api/transactions \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "{\"to_email\":\"bob@clearpay.dev\",\"amount_cents\":2000,\"idempotency_key\":\"$LABEL\"}"

现在你应该会看到"replayed":true,而且这次交易的id应该与第一次发送时的ID相同。

再次查看你的账户信息:

curl -s localhost:4000/api/accounts/me -H "Authorization: Bearer $TOKEN"

检查结果:

balance_cents的值应该比你之前记录的数字少2000,而不是4000。如果你最初的账户余额是94000,那么现在应该看到92000。

如果操作失败:

  • 如果执行echo $TOKEN后没有输出任何内容,说明令牌无效,请重新运行登录命令。

  • 如果第二次发送请求时仍然显示"replayed":false,说明两次请求使用的LABEL不同。请先执行echo $LABEL来确认标签值是否正确,然后再重新发送请求。

  • 如果两次发送请求都显示"replayed":true,说明账户余额不会发生变化。此时请选择一个新的LABEL,然后重复步骤3和4。

操作完成的条件:

  • 当某次请求显示"replayed":false,而下一次请求显示"replayed":true时,说明操作成功。

  • 当balance_cents的值减少了2000而不是4000时,说明操作成功。

第4章:使用Terraform搭建AWS服务器

周二上午9:15

Maya在去开会的路上经过了你的办公桌。昨天你告诉她,ClearPay是在你的笔记本电脑上运行的,而且重复支付的操作只会收取一次费用。

Maya说:“很好。不过现在本地服务器并不能作为基准环境。我们需要把ClearPay部署到AWS上,证明客户可以在那里完成交易。之后我们再讨论是否需要移除AWS这个平台。Daniel会和你一起处理这个问题。”

Daniel从旁边的办公桌走过来。

Daniel说:“在开始之前有两条规则需要遵守:千万不要跳过terraform plan这一步。”

为什么这一点很重要

这本书介绍的是许多小型企业常用的系统架构:一台没有安装Docker的Linux服务器。

当服务器启动时,Linux的服务管理器systemd会自动启动Node API;nginx负责处理443端口上的HTTPS请求;PostgreSQL则运行在Amazon RDS这个托管型数据库服务上。

这套系统中并没有使用AWS负载均衡器。虽然负载均衡器可以将流量分散到多台服务器上,但在这种架构中添加负载均衡器并不会带来任何实际好处,反而会增加额外的费用。相关详细信息可以在infra/aws/COST.md文件中找到。

文件 `api/Dockerfile` 确实存在,但只有你的笔记本电脑在使用它。AWS 服务器从未运行过 Docker。

Daniel 说:“EC2 服务器上并不安装有 Docker。systemd 会在系统启动时同时启动 Node.js 和 Nginx。你即将使用的就是这种服务器配置。”

架构图显示 ClearPay 在 AWS 上的运行方式。用户通过 Elastic IP 连接到 EC2 服务器上的 Nginx,Nginx 负责提供前端文件,并将 API 请求转发到由 systemd 管理的 Node.js 进程,该进程运行在端口 4000 上。API 进程又会与 Amazon RDS 中的 PostgreSQL 16 数据库进行交互,而这个数据库目前并没有公共访问端点。

上图说明了客户请求是如何到达 ClearPay 在 AWS 上的服务器的。Elastic IP 为 EC2 服务器提供了固定的公共地址。Nginx 负责处理端口 443 上的 HTTPS 流量,提供前端文件,并将 API 请求转发到由 systemd 管理的 Node.js 进程,该进程运行在端口 4000 上。API 进程会与 Amazon RDS 中的 PostgreSQL 16 数据库进行交互,而这个数据库目前并没有公共访问端点。

在本章中会提到两个 AWS 相关的概念:

  • VPC 就是你的服务器所所在的私有网络。AWS 为每个账户默认提供一个 VPC。在这个实验中,我们直接使用默认的 VPC,而无需自己创建。

  • 安全组相当于 AWS 的防火墙,它决定了哪些端口可以访问服务器。在这个实验中,我们只允许来自你自己的 IP 地址的 SSH 连接,允许来自任何地方的 HTTPS 连接,同时只允许 API 服务器与 PostgreSQL 数据库进行交互。

操作步骤

首先确认你的笔记本电脑已经准备好。以下各项功能都必须能够正常使用:

docker --version          # 后面还会用到这个命令
aws sts get-caller-identity
terraform version         # 版本必须为 1.5 或更高
ssh-keygen -t ed25519 -f ~/.ssh/clearpay-deployer -N ""

aws sts get-caller-identity 命令会显示你的 AWS 账户信息。如果执行过程中出现错误,请使用具有创建 EC2、RDS、VPC 以及 CloudWatch 警报权限的 IAM 用户的访问密钥来运行 aws configure 命令。使用临时账户中的管理员用户也可以完成这个操作。

在仓库根目录下执行以下命令:

cd infra/aws/terraform
cp terraform.tfvars.example terraform.tfvars

打开文件 terraform.tfvars,并修改其中的两个值:

  • ssh_allowed_cidr:你的公共 IP 地址后加上 /32。你可以使用命令 curl ifconfig.me 来获取自己的 IP 地址。

  • ssh_public_key_path:你的密钥的 .pub 文件所在的路径。

接下来执行以下命令:

terraform init
terraform plan
terraform apply

在输入 yes 之前,请先仔细阅读生成的配置计划。首次运行 Terraform 时,系统只会创建 EC2 服务器、RDS 数据库以及安全组等资源。如果配置计划中提到了需要删除某些资源,请立即停止操作,不要继续执行。

在第五章之前,这个服务器环境实际上是空的,没有任何预先配置好的服务或数据。

AWS基础设施图示:一个Elastic IP地址指向默认VPC中的空EC2 Ubuntu服务器;该服务器的安全组允许来自指定IP地址的SSH连接,同时允许任何位置的用户通过端口80和443进行连接。CloudWatch用于监控该实例的状态;而RDS PostgreSQL 16数据库仅允许来自该EC2服务器安全组的连接,在端口5432上接收连接请求。

该图展示了Terraform创建的AWS基础设施:一台配置了Elastic IP的EC2 Ubuntu服务器,以及用于控制SSH和HTTPS访问的安全组;还有一个只能通过API服务器访问的RDS PostgreSQL数据库,同时还配置了CloudWatch监控功能。所有这些资源都位于默认的VPC环境中。目前这台EC2服务器还为空置状态,第5章会向其中部署ClearPay系统。

关于费用的问题:即使服务器处于运行状态,Elastic IP的费用也仅为每天约0.12美元,因为AWS会对每一个公共IPv4地址收取费用(详见AWS公共IPv4地址收费政策)。

检查结果:

terraform output instance_public_ip

这条命令会输出一个公共IPv4地址,这就是访问该服务器所需的地址。由于不需要使用域名,因此也不需要配置Route 53服务(manage_dns = false)。

如果出现错误:

  • Error: InvalidKeyPair.Duplicate:说明在这个AWS区域内,已经存在名为clearpay-deployer的密钥对。请在AWS控制台中删除该密钥对,或修改infra/aws/terraform/ec2.tf文件中的相关配置。

  • Error: UnauthorizedOperation:说明您的AWS账户缺乏访问某些资源的权限。错误信息会明确指出具体是哪个操作需要授权。只需为该操作添加相应的权限即可,无需申请完整的管理员权限。

下一步行动:

基础设施已经搭建完成,但服务器目前还为空置状态,因此还需要继续进行后续操作。请进入第5章继续学习。

何时算完成:

  • 当terraform apply命令执行完毕且没有出现任何错误时。

  • 当terraform output instance_public_ip命令输出一个有效的IP地址时。

第5章:在AWS服务器上部署ClearPay系统

周二下午1:40

在午餐之前,terraform apply命令就已经执行完毕。此时AWS系统中确实显示有一台服务器和一个数据库,但它们目前还无法为客户提供服务。

Terraform只是创建了一台空置的服务器和一个空的数据库,这些资源暂时还不能用于实际服务。

你打开终端准备开始部署过程,这时Daniel走过来坐在你旁边。

Daniel说:“仅仅因为Terraform执行完毕,并不意味着服务器已经可以正常使用了。我们还需要在服务器上安装应用程序,并发送一次真实的请求来测试系统是否能够正常运行。”

为什么这很重要:

如果服务器还是空置状态,那么它就无法作为迁移的起点。第6章会测试真实的客户请求在AWS环境中是否能够正常处理,而在这个问题得到解决之前,我们无法进行后续的测试工作。

操作步骤:

首先从仓库的根目录开始操作。如果你当前还在infra/aws/terraform目录中,需要先执行cd ../../..命令来切换到正确的目录。

接下来在服务器上安装Node.js、nginx和certbot软件:

cd infra/aws/deploy
INSTANCE_IP=$(terraform -chdir=../terraform output -raw instance_public_ip)
echo "$INSTANCE_IP"
# 记下这个IP地址,因为新的终端环境中可能没有这个变量。

ssh -o StrictHostKeyChecking=accept-new \
  -i ~/.ssh/clearpay-deployer ubuntu@$INSTANCE_IP 'bash -s' < bootstrap.sh

你应该会看到== ClearPay AWS bootstrap ==这一行,随后会是几分钟的apt-get命令执行输出。文件bootstrap.sh很短,建议仔细阅读它。

当输出内容以以下这几行结尾时,说明操作成功:

== Bootstrap完成 ==
Node: v20.20.2
自签名TLS证书位于/etc/letsencrypt/live/clearpay,可以使用curl -k命令进行访问。

你的Node版本可能与上述示例不同。但那些以debconf: unable to initialize frontend开头的行并不影响操作,它们出现的原因是SSH会话没有交互式界面。而那些包含+或*的行,则是OpenSSL在生成自签名证书时产生的。

如果完全没有输出信息,说明SSH连接失败了。可能是因为从第4章之后你的家庭IP地址发生了变化,导致安全组设置仍然允许旧的IP地址访问。你可以使用curl -s ifconfig.me; echo命令查看当前IP地址,然后将其与/32组合起来,保存到terraform.tfvars文件中的ssh_allowed_cidr字段里,接着运行terraform apply命令重新尝试连接。

接下来,需要编写API所需的.env文件并部署应用程序。数据库地址信息来自Terraform配置;JWT密钥用于生成登录令牌,且会每次使用时都重新生成:

DATABASE_URL=$(terraform -chdir=../terraform output -raw database_url)

cat > /tmp/clearpay.env <<EOF
DATABASE_URL=$DATABASE_URL
JWT_SECRET=$(openssl rand -hex 32)
PLATFORM=aws
APP_VERSION=1.0.0
GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo local)
ROLLBACK_ACTIVE=false
PORT=4000
EOF

./deploy.sh "$INSTANCE_IP" ~/.ssh/clearpay-deployer /tmp/clearpay.env
rm /tmp/clearpay.env

deploy.sh脚本会构建React用户界面并将其复制到/opt/clearpay-ui目录中,同时将API相关文件复制到/opt/clearpay-api目录并安装相关依赖包,生成.env配置文件,然后安装systemd服务及nginx配置文件,并重新启动这两个服务。

接下来需要加载数据库结构并初始化Alice和Bob账户。这些操作需要在服务器端执行,而不是在笔记本电脑上。由于RDS目前还没有公开访问接口(在infra/aws/terraform/rds.tf文件中设置了publicly_accessible = false),因此笔记本电脑无法直接访问RDS数据库。

笔记本电脑实际上是通过EC2服务器来间接访问RDS数据库的:

说明数据库访问规则的示意图。上图显示了笔记本电脑通过SSH连接到EC2服务器,而该服务器被允许在同一AWS私有网络内连接RDS数据库;下图则显示了笔记本电脑直接尝试连接RDS会被红色X标记阻止。

从示意图中可以看出,笔记本电脑之所以不能直接访问RDS数据库,是因为虽然允许通过SSH连接到EC2服务器,但该服务器也被允许在AWS私有网络内连接RDS数据库,因此笔记本电脑无法绕过这一中间环节直接访问数据库。你必须使用EC2服务器作为访问RDS数据库的入口点。

以下每个命令都以 `ssh ... ubuntu@$INSTANCE_IP "..."` 的形式开头。这意味着:需要登录到 EC2 服务器,然后运行括号中的命令。因此,`psql` 命令以及数据初始化操作都是在 EC2 上执行的,而 EC2 会自动与 RDS 进行交互。你可以在浏览器中看到执行结果:当你以 Alice 的身份登录时,EC2 会从 RDS 中读取她的账户信息。

scp -i ~/.ssh/clearpay-deployer ../../../db/schema.sql \
  ubuntu@$INSTANCE_IP:/tmp/schema.sql

ssh -i ~/.ssh/clearpay-deployer ubuntu@$INSTANCE_IP \
  "sudo apt-get install -y postgresql-client -qq && psql '$DATABASE_URL' -f /tmp/schema.sql"

ssh -i ~/.ssh/clearpay-deployer ubuntu@$INSTANCE_IP \
  "cd /opt/clearpay-api && sudo -u clearpay npm run seed"

这次使用的初始化脚本与第 1 章中的相同,只不过它会将数据写入 RDS 中。

检查

ssh -i ~/.ssh/clearpay-deployer ubuntu@$INSTANCE_IP 'sudo systemctl is-active clearpay-api'

预期的输出结果是:`active`。

然后,在你的笔记本电脑上执行以下命令:

curl -k https://$INSTANCE_IP/health

检查是否出现 `“status”: “ok”` 和 `“platform”: “aws”` 这些信息。`-k` 标志告诉 curl 接受 `bootstrap.sh` 文件生成的自我签名证书。

在浏览器中打开 `https://<你的实例 IP>`,忽略“不安全”的警告提示。这个警告是由于使用了自我签名证书导致的。你应该能看到与在笔记本电脑上相同的 ClearPay 登录界面,使用 `password123` 以 Alice 的身份登录。此时,控制面板会显示她的余额信息,以及 “由 aws/1.0.0 提供服务” 这一提示。

Daniel 说:“健康检查只能告诉我们应用程序是否正在运行。在确定基准数据之前,我需要先测试一下通过这个 IP 地址能否完成转账操作。”

你可以在浏览器中向 Bob 转账 20 美元。

如果出现故障:

  • 如果 `systemctl is-active` 的返回结果是 `failed`,那么请登录到 EC2 服务器,然后运行 `sudo journalctl -u clearpay-api -n 50` 命令。通常造成这种问题的原因是 `DATABASE_URL` 的值与 RDS 中设置的地址不一致,请将这两个值逐个字符进行对比。

  • 如果出现 `seed_failed` 并且 `SELF_SIGNED_CERT_INCHAIN` 的值为 `true`,说明数据库本身没有问题,可能是 Node.js 中的 `pg` 库版本较新,导致 `sslmode(require)` 参数被理解为需要进行全面的证书验证。请将当前的 `api/src/pgConfig.js`、`api/src/db.js` 和 `api/db/seed.js` 文件复制到服务器上,然后重新启动 `clearpay-api` 服务,再次执行数据初始化操作。注意不要再次运行 `schema.sql` 文件。

  • 如果 nginx 返回 `502 Bad Gateway` 的错误信息,说明 nginx 是正常运行的,但后面的 API 服务没有启动。请先检查前面的 systemd 状态信息。

  • 如果浏览器显示 “Cannot GET /” 这一错误,说明 nginx 仍然使用的是仅适用于 API 的配置文件。请运行 `./deploy.sh "$INSTANCE_IP" ~/.ssh/clearpay-deployer` 命令,不要添加第三个参数。这样就可以保留服务器上已有的 `.env` 配置文件了,无需再次执行数据初始化操作。

  • 如果 Alice 的登录信息无效,可能是浏览器自动填充了之前保存的密码。请清空密码输入框,重新输入 `password123`,然后尝试登录。

完成条件:

  • 运行 `systemctl is-active clearpay-api` 命令后,应显示 “active”。

  • 执行 `curl -k https://$INSTANCE_IP/health` 命令后,应返回 “status:”ok”。

  • 您需要通过浏览器访问 AWS 地址并支付 20 美元。

第 6章:记录 AWS 基线数据

星期三,上午 10:00

您给 Maya 发消息:“系统运行正常,Alice 已通过 Elastic IP 登录。”她不到一分钟就回复了。

Maya 说:“很好。现在用那个 IP 地址进行五项测试,请将终端输出结果发给我,不要截图。”

随后又发来一条消息:“然后再进行负载测试,并把 p95 值发给我。否则我无法将 GKE 的性能与 ‘正常运行’ 的标准进行比较。”

为什么这很重要

本章会为您提供两样非常重要的内容,在本书的后续部分中,您都会用到它们。

1. 通过“推广测试”这一关。 “推广测试”其实是一个简短的脚本,用于检测每个支付应用都必须满足的五项条件:应用程序正在运行中、能够连接数据库、用户可以正常登录、重复支付的操作只会执行一次、用户的余额会按照预期减少。如果这五项都符合要求,那么说明该应用程序是正常工作的。

在第 15 章中,您会在 GKE 上运行同样的测试脚本。如果 GKE 能够通过 AWS 所通过的那些测试,那就说明 GKE 也能完成相应的任务。

2. 一份评估报告。 负载测试会同时发送大量请求,模拟许多用户同时使用该应用程序的情况。然后系统会记录应用程序响应这些请求的速度以及有多少请求失败了。这样我们就能了解当前 AWS 系统的实际性能。之后,您会在 GKE 上再次进行同样的测试,并将结果与 AWS 的数据进行比较。

可以把这想象成在运动员更换鞋子之前测量他们的速度——如果没有第一次的测量数据,第二次的测量结果就毫无意义。本章的作用就是记录下这个“第一次测量”的数据。

本章中的脚本会使用您笔记本电脑上的 Node.js 来解析 JSON 数据。如果您还没有安装 Node.js 20 或更高版本,请现在就去安装吧。

操作步骤

从仓库的根目录开始操作。如果当前还在 `infra/aws/deploy` 目录中,先运行 `cd ../../..` 命令。

接下来对 AWS 进行“推广测试”:

export INSTANCE_IP=$(terraform -chdir=infra/aws/terraform output -raw instance_public_ip)

./scripts/promotion-gate.sh https://$INSTANCE_IP \
  alice@clearpay.dev password123 bob@clearpay.dev

请使用 `https://` 协议,因为如果使用 `http://`,Nginx 会进行重定向,导致脚本无法正常执行。

该测试会依次检查以下五项内容:

  1. 应用程序进程是否正在运行(通过检查 `/health` 端点)。

  2. 应用程序能否成功连接数据库(通过检查 `/health/ready` 端点)。

  3. Alice 是否能够正常登录。

  4. 重新使用的支付密钥是否会返回 “replayed=true” 的结果。

  5. Alice 的余额是否确实减少了一次(减少 100 美分),而不是两次。

  6. 预期的测试结果如下:

    PASS: health endpoint returns ok
    PASS: readiness endpoint confirms DB connectivity
    PASS: login succeeds and returns a token
    PASS: replaying an idempotency key returns replayed=true
    PASS: balance debited exactly once (100 cents), not twice
    
    5 passed, 0 failed
    
    GATE PASSED: safe to promote to production.

    最后一行提到了生产环境。目前还没有需要推广的生产环境版本。后来在GKE上运行的脚本中,这一行的含义也是一样的。

    现在来对AWS进行负载测试。该脚本会登录系统并访问/api/accounts/me以及/api/transactions这两个接口,而这些接口都会查询Postgres数据库。请不要对/health这个接口进行负载测试。因为API本身会立即回复/health的请求,而不会去询问数据库。但是,真实的客户请求才会触发数据库查询并需要等待响应,而这种等待时间通常占整个处理流程的大部分:

    /health                  笔记本电脑 → API → 回复                    (快速,无需查询数据库)
    /api/accounts/me         笔记本电脑 → API → 数据库 → API → 回复   (实际需要执行的操作)

    对/health这个接口进行负载测试时,实际上只测量了其中“快速响应”的那部分过程。可以把这想象成问店员“你们开门了吗?”和“你们有42码的这种鞋子吗?”的区别——只有后一个问题才会让店员去仓库查找。

    ./scripts/load-test.sh https://$INSTANCE_IP \
      alice@clearpay.dev password123 10 30

    最后两个数字分别表示并发请求的数量(10个请求同时进行)以及测试持续时间(30秒)。

    检查结果

    脚本会生成一份评分报告。以下是本次实验生成的报告内容:

    == 数据库负载测试结果: https://184.194.120.157  并发请求数=10  测试时间=30秒 ==
    测试接口:      GET /api/accounts/me  GET /api/transactions
    
    成功次数:       372    失败次数:     0
    
    本次测试的评分结果如下:
    
    p50延迟时间:        673毫秒
    p95延迟时间:        745毫秒
    p99延迟时间:        1326毫秒
    吞吐量:          12.4次/秒
    4xx错误率:         0.0%
    5xx错误率:         0.0%
    交易成功率:       100.0%

    你的评分结果可能会有所不同。因为你的IP地址、请求次数以及延迟时间都会受到你所处位置的影响。由于负载测试是在你的笔记本电脑上进行的,所以每个请求都需要从你的所在地传输到位于us-east-1区域的AWS服务器,然后再返回。

    如果测试地点靠近该区域,那么测试结果中的数值会比位于其他大陆的用户的测试结果要低一些。不过这两种结果都没有错。关键是要将GKE的测试结果与你自己在相同地点测得的评分结果进行对比。

    当看到失败次数=0、5xx错误率=0.0%以及交易成功率=100.0%时,说明测试通过了。

    • p95延迟时间表示有95%的请求在比这个数值更短的时间内完成。这一指标能够反映出那些响应速度较慢的客户所遇到的问题,而平均值往往无法体现这些细节。

    • 4xx错误表示客户端发送了错误的请求信息,例如缺少必要的认证令牌。

    • 5xx错误表示服务器出现故障,无法正常处理请求。

    将这份完整的评分报告复制到笔记文件中,然后把其中的相关内容发给Maya。她很快就回复说:“不要为了让数据看起来更整齐而四舍五入这些数值。我会直接用GKE的测试结果与这个报告进行对比。”

    现在就设定你的限制条件吧

    在评分报告下方,请写下以下这两行内容,并填写相应的数字:

    “中断”是指:5xx错误率连续超过2分钟,其数值高于1%;“故障”则指:p95错误率连续超过5分钟,其数值超过您所在AWS环境的p95阈值的3倍以上。

    失败阈值是根据你自己的AWS p95值来设定的,因为每个用户的基准情况都不相同。以这个实验中测得的745毫秒为基准,那么失败阈值大约就是2,235毫秒。这些只是示例数值,你可以自行选择合适的阈值,但必须现在就确定下来,因为在GKE出现之后再设置阈值的话,就会被迫去调整那些数值以使其符合新的标准了。

    用以下一行来开始记录成本信息:

    成本记录,条目1:AWS费用约为每天1.25美元 (infra/aws/COST.md)

    如果失败了:

    请一直等待,直到所有五项检查都通过为止。如果在迁移过程中存在问题,那么当涉及到两种云服务时,这些问题会变得更加难以排查。

    • 如果“准备就绪”这项检查失败,说明API无法连接到RDS。此时请返回到第5章中关于失败情况的处理方法。

    • 如果出现node: command not found这样的错误,请在您的笔记本电脑上安装Node.js。

    决策

    所有检查都通过了。这台EC2服务器就成为了基准值。本书中提到的所有与GKE相关的数值都会与这个基准值进行对比。

    一分钟后,Maya又添加了一条记录:“好了,这些数值现在就是我的标准了。”

    何时算完成:

    • 当系统显示“针对AWS IP地址,有5项检查通过,0项失败”时,说明迁移工作已经完成。

    • 当负载测试结果显示“失败次数为0”,并且所有的评估数据都已经被记录下来时,也表示迁移工作结束。

    • 当你把所有的中断时间和失败阈值都以具体的数值形式记录下来时,也就意味着迁移工作彻底完成了。

    第2部分:如何规划迁移计划

    23833564-68ec-469e-a404-cf2daca2d22c

    请告诉Maya迁移工作已经完成,并将评估结果发送给她。

    她回复说:“下一步的任务是部署GKE。我需要确保API能在GKE上正常运行,同时在整个迁移过程中,客户也需要继续支付相关费用。请把Postgres数据库留在AWS上,因为这个数据库并不属于本次迁移任务的范畴。”

    在这个阶段,不要对任何云服务进行任何修改。你需要仔细阅读相关资料,分析实际情况,然后做出决策。

    第7章:阅读迁移任务要求

    星期三下午4:00

    Maya在邮件开头写了一条提示:“在开始使用Terraform之前,请先仔细阅读所有内容。”

    任务要求:将ClearPay API从AWS EC2迁移至GKE。

    目前ClearPay运行在一台EC2服务器上。虽然它可以正常使用,但并没有自动扩展功能,也没有分阶段部署的机制,其部署流程完全依赖于某个人的记忆来执行。你在第5章中已经手动完成了这个部署过程,所以你应该很清楚它目前还缺少哪些功能。

    业务方希望将API部署在Kubernetes平台上:需要有一个测试环境,能够实现自动部署,并且系统还有进一步扩展的空间。再次强调,数据库并不属于本次迁移任务的范畴。

    当前架构

    目前ClearPay运行在一个AWS账户中,使用的是第4章中提到的默认VPC环境:其中包含一台EC2服务器和一个RDS数据库。

    不可改变的内容

    • 数据库。将Postgres迁移至其他平台是一个独立的项目,将在后续进行。

    • API接口规范。相同的路径会返回相同的JSON数据:/health、/api/auth/login、/api/transactions。在AWS上能够正常运行的浏览器或脚本,在GKE上同样可以无需任何修改即可正常使用。

    • 在整个迁移过程中,客户不会遇到任何持续性的服务中断情况。

    验收标准

    “完成迁移”意味着以下条件都必须得到满足:

    1. GKE将承担所有的生产环境流量,且这种状态会持续保持。错误率与延迟时间必须控制在第6章中规定的范围内。

    2. 只要AWS仍然存在,就可以随时将系统回滚到AWS环境中。

    3. 只有当GKE被证明能够稳定运行后,才能最终淘汰AWS。

    哪些内容会发生变化,哪些会保持不变

    迁移示意图:ClearPay的用户界面和API从AWS的EC2服务器迁移到Google Kubernetes Engine(GKE)上。GKE同时承载测试环境和生产环境;PostgreSQL数据库则仍留在AWS的Amazon RDS中,两个环境共享同一数据库。

    该示意图展示了从AWS到GCP的迁移过程:ClearPay的用户界面和API从EC2服务器迁移到Google Kubernetes Engine(GKE)上,而测试环境和生产环境仍然使用GKE;PostgreSQL数据库则继续留在AWS的Amazon RDS中,因此两个环境在迁移期间仍会使用同一个数据库。

    整本书所描述的技术路径如下:

    仅使用AWS → 同时使用AWS和GKE → 仅使用GKE,然后淘汰EC2

    中间阶段是整个迁移过程的核心:两种云环境都会运行相同的应用程序,都会访问同一个数据库,流量会逐步地在这两个环境之间进行转移。

    流量如何迁移

    目前,客户需要输入Elastic IP地址才能访问相关服务,所有请求都会被发送到AWS。

    1. 首先在前面添加一个Google负载均衡器,客户只需将访问地址更改为该负载均衡器的地址即可。在整个迁移过程中,他们都需要继续使用这个新的地址。

    2. 负载均衡器会将部分请求发送到AWS,其余的请求则发送到GKE。初始状态下,100%的流量都会被发送到AWS。

    3. 之后可以通过`scripts/shift-traffic.sh`脚本逐步增加发送到GKE的流量比例,每完成一步后都需要检查相关数据是否正常。

    4. 如果GKE环境中出现任何问题,可以立即使用`shift-traffic.sh 100 0`命令将100%的流量重新发送回AWS,系统恢复过程只需几秒钟即可完成。

    架构示意图:用户通过一个公共地址访问运行在AWS和Google Kubernetes Engine(GKE)上的ClearPay用户界面和API。两个环境都连接到了AWS中Amazon RDS上的同一个PostgreSQL数据库。

    上图展示了客户如何通过一个公共地址访问ClearPay;在迁移过程中,UI和API同时运行在AWS和GKE上。这两种环境都连接到亚马逊AWS中的同一台PostgreSQL数据库。将数据库放在同一个地方,可以避免在迁移应用程序的同时也迁移支付数据。

    为什么不用DNS来分配流量呢?DNS就是将域名转换为IP地址的机制,笔记本电脑和互联网服务提供商都会缓存这些查询结果。

    在更改DNS记录后,有些客户仍会在几分钟或几小时内继续访问旧地址。如果需要花费一小时才能完成回滚操作,那么这种回滚措施就毫无意义了;而负载均衡器可以在几秒钟内完成流量分配的调整。

    回滚要求

    在迁移的每一个阶段,迅速且安全地恢复到100%依赖AWS的状态是必须的。这不能通过重新部署系统来实现,也不能通过需要一小时才能生效的DNS设置变更来达成。第20章会在首次进行迁移之前,专门测试这些要求是否得到满足。

    这里的“零停机时间”意味着什么

    这并不意味着永远不会有任何请求失败。没有任何真正的系统能够保证这一点。

    它的意思是:没有任何客户会遇到持续的服务中断。只要失败的请求数量低于你在第6章中规定的限度,那么就是可以接受的。

    为什么先迁移API

    如果API和数据库同时进行迁移,而其中某个环节出现了问题,那么故障可能出在其中一个组件上,也可能出在它们之间的交互过程中。因此,只迁移API可以让问题变得更容易解决:这个API能够在GKE上正常运行,而不会影响到客户的体验吗?这是一个可以给出明确答案的问题。“能否安全地一次性完成所有迁移工作?”这个问题则要难回答得多,而且在实际操作中调试起来也会更加麻烦。

    为什么数据库仍留在AWS上

    第13章已经说明了跨云环境进行数据传输所涉及的技术细节。目前来说,在这个项目中,PostgreSQL数据库是不会被迁移的。

    未来会有一个单独的项目来处理数据库的迁移工作,很可能会使用谷歌的数据库迁移服务、持续复制技术以及专门的切换流程。而当前这个项目的目标仅仅是让API运行在GKE上,而数据库仍然留在AWS上。这就是我们的计划,并不是一个未完成的任务。

    为什么必须同时运行两个云平台

    回滚机制的实施就依赖于这一点。你可以先向GKE发送少量流量,同时确保AWS处于准备状态;如果GKE出现故障,AWS可以立即恢复所有服务。不过,这种方案只有在AWS仍然存在的情况下才能发挥作用。

    在开始使用GCP之前,先制定迁移预算

    现在就去做这件事吧,在阅读第9章之前。在你的笔记中写下四个数字,它们将决定这次迁移会让你花费多少钱。

    规划你的开支,就像规划旅行费用一样:

    1. 目前AWS的使用成本是多少?大约每天1.25美元,具体数据详见infra/aws/COST.md文件。

    2. 未来使用GCP每天会花费多少费用?这个数字你还不知道,现在先写上“待定”。第9章会为你计算出具体数值。根据infra/gcp/COST.md文件,GCP的每日费用大约在1.60美元到5美元之间。

    3. 在两个云平台同时运行的期间,这次迁移总共需要花费多少费用?

      在迁移过程中,你需要同时支付AWS和GCP的费用,也就是每天1.25美元加上GCP的每日使用成本。

    4. 你的预算是多少?

      这是你在整个迁移过程中最多可以花费的金额。在开始任何操作之前,先确定这个预算吧。

    本实验的相关数据:

    当前的AWS使用成本:每天1.25美元  
    预计的GCP使用成本:大约每天3美元(具体数值会在第9章中进行核实)  
    同时运行两者的总成本:1.25美元 + 3美元 = 每天4.25美元  
    迁移预算:5天 × 4.25美元 = 大约21美元,四舍五入后为25美元。

    在实验进行过程中,请随时记录累计成本。第19章到第25章会详细说明实际需要同时运行系统的天数。第28章会问一个关键问题:你的花费是否在预算范围内?如果迁移过程因某些原因而延长了,例如需要进行数据回滚,那么最终的成本就会反映出这一情况。

    迁移阶段

    流程图展示了12个连续的迁移阶段:调查、设计、配置资源、将应用容器化、在测试环境中部署、推广到生产环境、进行验证、逐步转移流量、处理可能出现的问题、完成系统切换、停止使用AWS服务以及进行最后的审查。箭头表示这些阶段在三个不同行中的对应关系。

    何时可以认为迁移完成:

    • 你能够用两句话向Maya解释:哪些部分需要被迁移,哪些部分可以保留不变,以及如何纠正那些出错的步骤。

    第8章:明确ClearPay所依赖的各个组件

    星期四上午9:00

    在开始部署GKE之前,团队需要一份书面清单,列出ClearPay所依赖的所有组件。自周二以来,这个系统一直是在手动模式下运行的,目前没有任何书面文件明确规定在迁移过程中哪些部分必须保持正常运行状态。

    Maya说:“在我们开始部署新的云平台之前,先把这个系统依赖的组件都记录下来吧。”

    为什么这很重要

    如果没有这份清单,迁移过程中就很可能会错误地移除某些重要的组件,或者遗漏一些会导致系统在切换后无法正常运行的关键因素。请务必根据仓库中的文档来回答下面的问题,而不要凭记忆作答。

    1. 该应用是否已经准备好被容器化部署?

    是的。api/Dockerfile文件从第1章开始就已经在项目中使用了。它的作用虽然有所变化,但在你的笔记本电脑上使用时它只是提供了一种便利;而在GKE环境中,它就成为了应用程序实际运行的基础。

    2>这些敏感信息保存在哪里?

    打开文件infra/aws/deploy/systemd/clearpay-api.service,会发现其中指定了EnvironmentFile=/opt/clearpay-api/.env这个配置文件。不过这个文件并不存储在Git仓库中,而是位于EC2服务器上,在第5章中是通过deploy.sh脚本将其复制到该位置的。

    API所需的全部敏感信息都保存在服务器上的一个纯文本文件中。请注意不要直接打开这个文件来查看其中的内容,因为GKE会从Google Secret Manager中读取这些信息。

    3>该应用会调用哪些外部系统?

    其中一个就是PostgreSQL数据库。连接请求是在AWS的默认VPC内部完成的,而且RDS并没有提供公共访问端点。也没有任何第三方API被使用到这个系统中。这些信息都可以通过查看api/src/目录来确认。

    4>服务器在处理不同请求时是否会记住用户信息?

    不需要。打开api/src/index.js和api/src/routes/transactions.js文件即可。每个请求都会携带一个JWT(经过签名的登录令牌),而所有账户数据都存储在Postgres数据库中。服务器本身不会在内存中保存任何这些数据。

    正因为如此,AWS和GKE才能在后续处理请求时进行分离。用户可以在AWS上登录,然后在GKE上进行资金转账操作,因为这两大云服务平台都会使用相同的令牌密钥,并且会从同一个数据库中获取信息。

    5. 重试操作会导致费用被收取两次吗?

    不会。第3章已经证明了“幂等性机制”是有效的。因此,即使第一次请求在AWS上处理,而重试请求在GKE上处理,这一机制仍然会正常发挥作用。

    整体架构图

    所需准备的资源:api/Dockerfile文件;EC2服务器上的一个.env配置文件;仅使用PostgreSQL数据库。  
    服务器运行原理:不依赖任何内存缓存,所有数据都存储在JWT和数据库中。  
    重试机制的安全性保障:通过“幂等性机制”来避免重复收费,这一机制已在第3章得到验证。

    实际上需要迁移哪些内容?

    只有用户界面和API需要从EC2服务器迁移到GKE上。Postgres数据库则继续留在AWS上。

    浏览器和脚本访问相同的URL后,仍然会得到相同的JSON响应数据。重试机制也必须确保不会导致费用被重复收取,这些规则是不会改变的。

    什么时候可以完成任务?

    • 需要将所有五个相关答案都记录下来,且每个答案都要对应到具体的文件。

    第9章:了解GCP的设计原理

    星期四下午2:30

    Terraform脚本针对Google Cloud平台已经编写完成。在你加入团队之前,已经有其他人做出了两个重要的设计决策。Maya希望知道你能否为这些决策提供合理的解释。她在团队频道中提出了这个问题。

    Maya写道:“请打开infra/gcp/COST.md和infra/gcp/terraform/03-gke文件,然后告诉我为什么这个代码仓库选择使用GKE Standard模式而不是Autopilot模式。我需要一个能够站得住脚的理由。”

    坐在隔壁桌位的Daniel正在查看infra/gcp/terraform/03-gke/main.tf文件。他指出了名为primary的配置块,这个配置块描述了节点池的信息——也就是GKE用于运行应用程序的那组虚拟机。其中包含了1到3台e2-medium型号的虚拟机。

    Daniel问道:“如果我们保留这些虚拟机,那么同时可以在上面运行多少个API实例呢?”

    需要注意的是,这一章并没有要求大家重新编写Terraform脚本,因为相关的文件已经准备好了。我们的任务是理解其中所包含的设计决策。

    Standard模式还是Autopilot模式?

    GKE是Google提供的托管型Kubernetes平台,它有两种运行模式:

    • Standard模式:用户需要自行选择虚拟机,并将它们组织成节点池。无论应用程序是否处于繁忙状态,Google都会对这些虚拟机收取费用。

    • Autopilot模式:Google会自动配置虚拟机资源。用户只需指定每个容器所需的CPU和内存资源,Google就会据此进行调度和计费。

    这个代码仓库选择使用Standard模式,原因并非出于成本考虑。实际上,这种配置方式能够让用户清楚地看到节点池的构成、虚拟机的规格以及自动扩展机制的运作原理,而这些正是本书所强调的内容。如果使用Autopilot模式,这些信息就会被隐藏起来。

    你自己计算一下这两项费用吧。标准机器的费用是固定的。e2-medium型机器如果全天运行,根据infra/gcp/COST.md中的数据,其费用约为1.44美元,此外还需要加上Cloud NAT的费用。

    Autopilot会根据每个Pod所消耗的CPU资源和内存资源来计费。k8s/base/deployment.yaml文件规定,每个API Pod会占用250m的CPU资源(相当于四分之一个CPU核心)和256Mi的内存。根据infra/gcp/COST.md中提供的Autopilot计费标准,三个API Pod的费用大约为:

    0.25 vCPU × 3 × $0.0445/小时 × 24 = 约0.80美元/天
    0.25 GiB 内存 × 3 × $0.0049/小时 × 24 = 约0.09美元/天
    

    所以,三个API Pod每天的总费用约为0.89美元,这还不包括UI Pod的费用。实际上,Autopilot带来的成本可能还低于使用一台标准机器的成本。不过这个实验室仍然选择使用标准机器,这样你们就可以看到节点池的配置情况了。

    把这两个数字以及相关的计算依据告诉Maya吧。

    需要多少个API进程?

    AWS会运行一个API进程。而在GKE上,HorizontalPodAutoscaler会根据CPU使用情况来自动增加或减少API Pod的数量。每个Pod都代表一个API进程,而且每个Pod都会拥有自己的10个数据库连接资源。

    这个实验室使用的集群是由三台小型e2-medium型机器组成的。在k8s/overlays/production/patch-hpa.yaml文件中配置的生产环境下的HPA机制,会根据需求运行1到3个API Pod,因为三台这种规模的机器无法与GKE自带的系统Pod们共同容纳更多的Pod。

    给Daniel的回答是:“在GKE上会运行1到3个API Pod,在AWS上也会有一个API Pod在运行。每个Pod都可以建立10个数据库连接。”

    如果一家公司的机器规模更大,那么它们可能会设置更高的上限,比如3到8个Pod。第22章会解释为什么在增加Pod数量之前,需要先检查这些上限是否与数据库的容量限制相符。

    精心设计的网络架构

    在AWS上,这个实验室使用了AWS默认提供的VPC。这个VPC并不是由任何人专门设计的。

    而在GCP上,infra/gcp/terraform/02-network文件会创建一个自定义的VPC,其中包含一个命名的子网以及Cloud NAT功能。集群中的机器们只能获得私有地址,但它们可以通过Cloud NAT访问互联网,进而连接到AWS上的数据库。所有这些机器都共享同一个公共IP地址。

    区域设置

    01-foundation/variables.tf文件将区域设置为us-east4,这个区域位于谷歌云平台中,距离AWS的us-east-1区域最近。从GKE发起的任何数据库查询都会在这两个云平台之间进行传输,因此选择距离较近的区域可以显著减少数据传输的时间。如果你的AWS环境并不位于us-east-1区域,请在开始构建任何系统之前先更改这个设置。

    整体架构图

    左侧显示的是当前已经存在的架构,右侧则展示了第三部分内容中将会添加的组件。

    迁移架构图:客户通过GCP中的单一公共负载均衡器将请求分发到运行nginx的AWS EC2服务器上,这些服务器同时负责处理UI界面和API请求;而在GKE上的集群中,也会运行处理UI和API请求的Pod,这些Pod部署在私有节点上。Cloud NAT为GKE提供了对外出连接的支持。两种环境最终都会连接到AWS上的同一个PostgreSQL 16数据库。请求会逐渐从AWS转移到GKE上。” height=

    上图展示了从AWS迁移到GCP的过程。客户使用一个由GCP负载均衡器管理的公共地址,该地址会将流量分配到现有的AWS EC2服务器和GKE之间。GKE在私有节点上运行用户界面和API,而Cloud NAT则负责提供出站互联网访问功能。这两种环境都会连接到AWS上的同一个PostgreSQL数据库。随着迁移的逐步进行,流量会逐渐从AWS转移到GKE上,而数据库本身则保持不变。

    在这个迁移过程中,您会遇到三个公共IP地址,它们的顺序如下:

    1. 弹性IP地址。这就是当前的EC2服务器所使用的IP地址,客户目前仍在使用它。

    2. GKE网关IP地址。负载均衡器会将分配给GKE的流量发送到这个地址上,但客户通常不需要手动输入这个地址。

    3. 来自08-edge区域的负载均衡器IP地址。客户只需将流量切换到这个地址上,后续的流量分配工作就会在背后自动完成。

    如果您需要暂停使用AWS……

    在第3部分中,并不需要让AWS一直处于运行状态。但在第11章(将数据库URL复制到Secret Manager中)和第13章(将数据库开放给GKE使用)时,还是需要使用AWS的。

    请不要关闭EC2服务器,同时要让RDS继续运行。RDS通常会占据AWS日常账单的大部分费用;即使停止运行,RDS也会在7天后自动重新启动,而弹性IP地址的费用则不会因此发生变化。

    如果您在一两天内就会继续使用AWS,请让它保持运行状态;但如果您要停用它几天,那么请执行以下命令来彻底删除相关资源:

    cd infra/aws/terraform && terraform destroy

    如果以后需要重新开始使用AWS,只需在该文件夹中运行terraform apply命令,然后按照第5章的步骤进行操作即可。您会得到一个新的弹性IP地址和新的数据库URL;而第6章中的各项指标仍然可以作为参考基准。

    完成迁移的条件:

    • 您已经向Maya说明了标准配置模式与自动配置模式的每日数据统计情况,以及为什么实验室选择使用标准配置模式。

    • 您也已经向Daniel解释了可以同时运行多少个API进程,以及每个进程能够建立多少个连接。

    第3部分:如何使用Terraform构建Google Cloud

    展示我们已经完成了哪些工作、目前处于什么阶段,以及下一步该做什么的图片。

    目前,AWS应用程序和相应的评估指标体系已经搭建完成,但Google Cloud的相关环境尚未配置完毕——此时还没有网络、集群或注册表等组件。

    后续,用户界面和API将会在那个GKE集群上运行;而Postgres数据库则仍然保留在AWS上。因此,正确的操作顺序是:首先在GCP中创建一个空白的基础设施环境,然后把应用程序部署到那里,最后让应用程序学会如何通过互联网连接到RDS数据库。

    目前这个阶段只是完成了基础设施环境的搭建,客户的数据和应用程序还没有被迁移过去。

    第10章:配置gcloud并选择您的项目

    周五上午8:45

    在开始任何操作之前,您的笔记本电脑需要知道应该使用哪个项目来进行配置,同时也需要确认您的身份信息。

    确保将 gcloud、kubectl 以及 Terraform 配置为指向正确的 Google 项目,并且 billing 功能已启用。

    操作步骤

    gcloud 是 Google Cloud 的命令行工具。请先安装它,然后关闭终端并打开一个新的终端。

    在 Mac 上:

    brew install --cask google-cloud-sdk

    在 Windows 上,请从 下载 Google Cloud CLI 安装程序,勾选将 gcloud 添加到 PATH 的选项,然后重新打开 Git Bash。在 Linux 上,请按照该页面上的 Linux 部分进行操作。执行 gcloud version 命令后,应该会显示版本信息。

    如果在使用 gcloud 后,kubectl version --client 命令无法正常运行,请安装 kubectl 以及用于登录 GKE 的插件:

    gcloud components install kubectl
    gcloud components install gke-gcloud-auth-plugin

    该插件必须被添加到你的 PATH 中。如果之后执行 kubectl get nodes 时出现 “gke-gcloud-auth-plugin not found” 的错误,请运行以下命令并重新打开终端:

    echo 'export PATH="$(gcloud info --format="value(installation.sdk_root)")/bin:$PATH"' >>> ~/.zshrc

    这条命令是针对 Mac 的默认 shell zsh 编写的。在使用 Bash 的 Linux 系统上,应将文件名改为 ~/.bashrc。在 Windows 上,安装程序通常会自动处理 PATH 的配置问题。安装完成后,请重新打开 Git Bash。

    现在请登录并选择相应的项目:

    gcloud auth login --update-adc
    gcloud projects list
    gcloud config set project YOURPROJECT_ID
    gcloud billing projects describe YOUR_PROJECT_ID
    terraform version
    kubectl version --client

    --update-adc 这个参数非常重要。仅使用 gcloud auth login 时,gcloud 只能使用你的账户信息;而 Terraform 是一个独立的工具,它会查找名为 “Application Default Credentials” 的文件。通过设置这个参数,Terraform 可以使用与浏览器登录相同的凭据进行操作。在 Google 页面上,请点击 “Allow” 按钮,不要提前关闭该标签页。

    应该使用哪个项目 ID

    执行 gcloud projects list 命令后,会看到两列内容,它们看起来很相似:

    • NAME 是项目的名称,很多项目都会使用默认的名称 “My First Project”。

    • PROJECT_ID 是每个项目的唯一标识符,例如 “my-first-project-123456”。

    请复制 PROJECT_ID,而不是项目名称。在所有需要输入 YOUR_PROJECT_ID 的地方,都使用这个 ID。

    如果你有多个项目,请选择那些已经启用了 billing 功能的项目。对于每个项目,分别执行 gcloud billing projects describe 命令,直到输出结果中包含 “billingEnabled: true” 这一行。

    你也可以在 Google Cloud Console 中查找项目 ID:点击页面顶部的项目名称,然后查看 ID 列。

    检查完成

    gcloud config get-value project

    这条命令会显示你的项目ID。如果输出的结果是[None],请再次运行gcloud config set project命令。

    如果操作失败:

    • 如果Terraform提示“无法找到默认凭据”,说明你在登录时没有使用相应的标志。请运行以下命令:
    gcloud auth application-default login
    • 如果该命令显示“需要cloud-platform权限,但尚未获得许可”,则可能是你在点击“允许”之前浏览器已经关闭了。请再次运行gcloud auth login --update-adc,然后再次点击“允许”。

    目前只需要进行一次gcloud登录操作。名称api.clearpay.test会在后续的第8阶段构建负载均衡器时出现,因此在此之前可以忽略它。

    操作完成的条件:

    • 运行gcloud config get-value project后,如果能够看到你的项目ID,说明操作成功。

    • 运行gcloud billing projects describe后,如果结果显示“billingEnabled: true”,也说明操作完成。

    • 运行kubectl version --client后,如果能看到版本信息,说明配置也已成功。

    消息提示:「gcloud已登录成功。项目配置已完成,计费功能也已开启。现在可以开始使用GCP了。」

    Maya提醒:“请一次只构建一个阶段。每个阶段完成后,请及时通知我。如果你一次性运行全部八个阶段,而第三个阶段出现了问题,你就无法判断是哪个环节出了故障。”

    第11章:通过八个Terraform阶段构建GCP

    时间安排:周五上午10:30,持续到第二周的周一

    GCP的构建过程分为八个Terraform阶段,每个阶段都会创建目标环境中的某一部分。

    请按顺序依次执行这些阶段。在每个阶段完成后,请先验证结果,确认一切正常后再进入下一阶段。

    需要构建的内容包括:API、网络架构、集群系统、镜像注册服务、身份认证机制、密钥管理功能、错误报警系统,以及公共负载均衡器等。

    为什么要分八个阶段进行构建?

    Terraform的相关配置文件被保存在八个文件夹中,分别是01-foundation到08-edge。每个文件夹都包含自己的状态文件,而且只会读取前几个阶段所需的配置信息。如果03-gke阶段出现故障,问题出在集群系统上,而不会出现在某个长达1,500行的配置文件中。

    这并不是说需要运行Terraform八次,而是指需要分别完成八个不同的构建任务,并且在开始下一个阶段之前,要确保前一个阶段的配置已经正确无误。

    对于个人用户来说,这些状态文件可以保存在本地笔记本电脑上;但如果是一个团队进行协作,建议将每个阶段的配置状态转移到Google Cloud Storage桶中,然后再由其他成员执行apply命令。

    GCP项目中八个Terraform阶段的构建流程图。第1阶段用于启用API;第2阶段创建网络和子网;第3阶段搭建私有GKE集群及Cloud NAT;第4阶段建立镜像注册服务;第5阶段配置身份认证信息;第6阶段管理密钥;第7阶段设置错误监控与报警机制;第8阶段创建公共负载均衡器。整个网络架构既支持互联网访问,也便于后续与AWS RDS的连接。

    上图展示了Terraform是如何分八个阶段来构建GCP基础设施的。它首先会启用所需的API,然后使用Cloud NAT创建网络及私有GKE集群,接着设置镜像注册表、身份认证机制、密钥管理配置、错误监控系统以及公共负载均衡器。Cloud NAT为私有节点提供了出站连接能力,在迁移过程中的后续步骤中,这些私有节点还能访问AWS RDS。

    各阶段的执行流程

    每个阶段都会在其对应的文件夹中使用相同的三个命令:

    terraform init
    terraform plan
    terraform apply
    

    在执行操作之前,请先查看相应的计划内容。下表列出了每个阶段预期会执行的操作数量,例如“计划:添加10个资源,修改0个资源,删除0个资源”。如果实际数量与预期不同,或者计划中包含删除资源的操作,在输入“yes”之前请务必仔细阅读相关内容。

    当执行`terraform apply`时,系统会询问“是否要执行这些操作?”请输入“yes”并按回车键。

    下面的文件夹路径都是从仓库根目录开始的。如果你已经处于`infra/gcp/terraform/0X-...`这个目录中,可以使用`cd ../0Y-...`来进入下一个阶段。

    对于那些需要在控制台进行的检查:请在页面顶部选择你的项目,并且当页面出现地区选择菜单时,将地区设置为`us-east4`。如果菜单列表为空,说明当前地区仍设置为`us-central1`。

    阶段1:启用Google APIs(01-foundation)

    在新创建的项目中,Google Cloud的各项服务默认都是处于关闭状态的。这个阶段会启用其中10项服务,包括GKE、Artifact Registry、Secret Manager、IAM以及日志记录功能等。如果你跳过这个阶段,后续的`03-gke`阶段就会因为权限问题而失败,而且这些权限错误信息并不会提供任何有用的线索。

    cd infra/gcp/terraform/01-foundation
    cp terraform.tfvars.example terraform.tfvars
    

    如果`terraform.tfvars`文件中已经存在你的项目ID,就可以直接跳过这个复制步骤。因为再次复制会覆盖你之前所做的修改。

    打开`terraform.tfvars`文件,将其中的`clearpay-migration-demo`替换为你的项目ID,然后运行以下命令:

    terraform init
    terraform plan
    terraform apply
    

    预期的执行结果应该是:会创建10个与Google项目服务相关的资源,但不会删除任何集群。

    计划:添加10个资源,修改0个资源,删除0个资源。
    输出参数的变化:
      + project_id = "YOUR_PROJECT_ID"
      + region     = "us-east4"
    

    如果执行结果中显示了`clearpay-migration-demo`这个选项,请先检查`terraform.tfvars`文件的内容是否正确。在应用配置的过程中,Secret Manager和服务网络相关设置可能会暂时显示为“Still creating”,这种情况属于正常现象,通常会在几分钟后自动完成。

    检查完成后,可以运行以下命令来验证各项服务是否已经成功启用:

    gcloud services list --enabled --filter='config.name:container.googleapis.com'
    
    名称                          标题
    container.googleapis.com    Kubernetes Engine API
    

    如果此时没有任何输出信息,说明 `gcloud` 所查询的项目对应的 API 尚未启用。请不要进入第二阶段,请先检查以下三个原因:

    1. gcloud 指向的是另一个项目。请运行命令 `gcloud config get-value project`,查看显示出的项目 ID 是否与你在 `terraform.tfvars` 中设置的项目 ID 一致。如果不一致,请运行 `gcloud config set project YOURPROJECT_ID`,然后再检查一次。

    2. `terraform apply` 操作尚未完成。请在 `01-foundation` 目录下再次运行 `terraform plan` 命令。如果操作已完成,屏幕上会显示 “No changes”;但如果系统仍提示需要添加资源,请运行 `terraform apply` 并输入 “yes” 以继续操作。

    3. Google 尚未更新相关配置。某些新启用的 API 可能需要一段时间才能在列表中显示出来。请等待一分钟后再重新检查。

    消息来自 Maya:“第一阶段已经完成,Kubernetes Engine 的 API 也已显示为已启用状态。”

    Maya:“很好,接下来进行网络配置设置。没有其他需要检查的内容了。”

    第二阶段:构建网络架构(02-network)

    VPC 是你 Google 项目中的私有网络,可以将其想象成集群所在的区域,而子网则相当于该区域内的具体街道。这一阶段会创建以下资源:

    • clearpay-vpc:私有网络本身。

    • clearpay-gke-subnet:集群中的机器使用的地址范围;另外还预留了两个子网范围,分别用于第三阶段的 `pods` 和 `services` 服务。

    • clearpay-router 和 clearpay-nat:云 NAT 设置相关资源。

    • clearpay-nat-ip:所有集群机器在对外发送请求时都会使用的公共 IP 地址。

    • 两条防火墙规则,用于确保内部机器之间能够相互通信,同时让 Google 的健康检查机制能够访问这些机器。

    该图展示了 GCP 的网络架构。私有机器位于子网内,没有公共 IP 地址;云 NAT 通过固定的公共 IP 提供互联网出站连接;该网络还能支持后续迁移过程中与 AWS RDS 数据库的连接。

    此文件夹中不存在 `terraform.tfvars` 文件,系统会从第一阶段的配置文件中读取项目 ID。

    cd infra/gcp/terraform/02-network

    接下来请运行以下命令:

    terraform init
    terraform plan
    terraform apply

    预期的执行结果如下:

    计划:需添加 7 个资源,无需修改任何资源,也无需删除任何资源。

    `terraform apply` 操作完成后的预期输出结果为:

    检查步骤:

    gcloud compute networks describe clearpay-vpc
    terraform output nat_ip_address
    

    需要查找以下内容:`name: clearpay-vpc`,以`clearpay-gke-subnet`结尾的`subnetworks`这一行,以及`autoCreateSubnetworks: false`这一项。最后这一行说明该网络是人工设计的,而非自动生成的。

    把NAT IP地址记下来。在第13章中,会使用这个地址来连接AWS数据库。

    在控制台中:VPC网络选项下可以看到`clearpay-vpc`;Cloud NAT选项下,`clearpay-router`对应的NAT IP地址是`clearpay-nat`;IP地址选项下也能找到`clearpay-nat-ip`。

    在GCP控制台上显示Cloud NAT的图片

    如果系统无法读取`../01-foundation/terraform.tfstate`文件,说明第1阶段的操作还没有完成。

    给Maya发消息:「`clearpay-vpc`和Cloud NAT已经创建好了。NAT IP地址记录在你的笔记里了。」

    Daniel会查看你的笔记,确认NAT IP地址是否已被记录下来。

    Daniel解释道:「我们所有的Pod与外部世界的通信都是通过这个IP地址进行的,包括与AWS数据库的连接。之后我们会设置AWS数据库只接受来自这个地址的连接。」

    这些操作会在第13章中进行。在那一章之前,请务必把NAT IP地址记清楚。

    第3阶段:创建GKE集群(`03-gke`)

    在这个阶段,我们会在`us-east4-a`区域创建一个名为`clearpay`的标准集群,并设置一个名为`primary`的节点池,该节点池由1到3台`e2-medium`型机器组成。这些机器只拥有私有IP地址,它们会通过Cloud NAT与外部进行通信。而用于管理集群的控制平面服务器则具有公共端点,因此你可以在笔记本电脑上使用`kubectl`来远程操作它。

    cd infra/gcp/terraform/03-gke
    

    接下来运行以下命令:

    terraform init
    terraform plan
    terraform apply
    

    预期的结果应该是创建一个集群以及名为`primary`的节点池。

    计划内容:需要添加2个资源,无需修改任何资源,也不需要删除任何资源。

    在执行`terraform apply`命令后,系统会显示“Still creating”状态,这个过程通常会持续7到10分钟。请不要提前按下Ctrl+C键终止操作。

    cluster_endpoint会输出<sensitive>这一信息,但你并不需要它。

    将kubectl指向新的集群:

    $(terraform output -raw get_credentials_command)

    如果操作成功,系统会输出“kubeconfig entry generated for clearpay”这一信息。

    检查一下:

    kubectl get nodes
    NAME                                 STATUS   ROLES    AGE   VERSION
    gke-clearpay-primary-........        Ready       2m    v1.xx

    Ready表示该机器已经启动并加入了集群,不过ClearPay应用程序尚未在该机器上运行。

    如果配置文件中提到了container.googleapis.com,请返回到第1阶段;如果提到了子网、pods或services的相关信息,请返回到第2阶段。切勿为了掩盖这些错误而修改03-gke文件。

    如果kubectl提示“gke-gcloud-auth-plugin not found”,说明集群本身没有问题,无需销毁它。请安装该插件,并调整终端的PATH环境变量:

    gcloud components install gke-gcloud-auth-plugin
    export PATH="$(gcloud info --format='value(installation.sdk_root)')/bin:$PATH"
    kubectl get nodes

    在控制台中,Kubernetes集群页面会显示clearpay集群正在运行中;而虚拟机实例页面则会显示名为gke-clearpay-primary-...的机器,该机器拥有内部IP地址,但没有外部IP地址。

    在控制台中创建的Kubernetes集群图像

    Maya发来消息:“集群已经启动,其中一个节点处于Ready状态,不过我们的应用程序还没有在该节点上运行。”

    整个配置应用过程耗时9分钟,你全程都见证了这一过程。

    第4阶段:创建镜像仓库(04-registry)

    容器镜像是应用程序的打包版本。GKE会从镜像仓库中下载这些镜像。在这个阶段,我们会在Artifact Registry中创建一个私有的Docker镜像仓库,其名称为clearpay,位置位于us-east4区域。在第14章中,当管道系统向该仓库推送数据时,这个仓库才会被使用到。

    cd infra/gcp/terraform/04-registry

    执行以下命令:

    terraform init
    terraform plan
    terraform apply

    需要创建的两个资源是一个镜像仓库,以及一条允许集群中的机器从该仓库中下载镜像的规则。

    计划操作:新增2个资源,无需修改任何资源,也不会删除任何资源。

    检查步骤:

    gcloud artifacts repositories describe clearpay --location=us-east4
    

    需要查找以下内容:`format: DOCKER`、以`/repositories/clearpay`结尾的`name`,以及`Repository Size: 0.000MB`。目前,一个空的注册表是正常的。你还会看到`SCANNING_DISABLED`这一字段。在第14章中,管道会使用Trivy来扫描图像。

    消息提示:“图像注册表存在,但它是空的。”

    阶段5:创建无需密钥文件的身份账户(05-identity)

    后续会有两项操作需要使用Google的权限:在GKE上运行的ClearPay pods,以及GitHub Actions在推送图像和进行部署时的操作。

    传统的方法是下载服务账户密钥文件。这种密钥文件实际上就是存储在磁盘上的密码,如果泄露的话,任何人都可以使用它。但在本实验中,我们不会采用这种方法。

    相反,在这个阶段会创建三个具有相应权限的Google服务账户,然后允许特定的调用者在短时间内借用这些账户:

    • clearpay-api-staging,由staging命名空间中的Pods借用

    • clearpay-api-production,由production命名空间中的Pods借用

    • clearpay-ci,通过名为clearpay-github-pool的工作负载身份池,由GitHub Actions借用

    工作负载身份是Google提供的一项功能,它允许将Kubernetes服务账户或GitHub仓库与Google服务账户关联起来,而无需使用密钥文件。

    可以把每个服务账户想象成一种“徽章”。现在还没有人会佩戴这种徽章,但后来Google会允许特定的Pod或仓库在短时间内使用这种“徽章”,而且根本不需要生成任何密钥文件。

    命名空间是一个集群内部的不同区域。staging和production命名空间共享同一台服务器,但它们的身份信息是相互独立的,因此staging命名空间中的Pod无法访问production命名空间中的敏感信息。在实际的企业环境中,通常会为不同的项目分别部署不同的集群。不过在本实验中,为了简化操作流程,我们使用同一个集群。

    示意图显示了一个名为某某的GKE集群,其中包含staging和production两个命名空间。staging命名空间使用某个服务账户,而production命名空间使用另一个服务账户。GitHub Actions在执行CI/CD操作时也会使用单独的服务账户。
    cd infra/gcp/terraform/05-identity
    cp terraform.tfvars.example terraform.tfvars
    

    该文件中唯一需要设置的值是`github_repo`。如果你已经拥有这个GitHub仓库的副本,那么现在就可以将其owner/name内容填写到这里,注意不要在后面加上`.git`后缀;如果没有这样的副本,那就保留`YOUR_GITHUB_USER/YOUR_REPO`这一格式。第14章会告诉你何时需要设置这个值,并且会说明如何重新执行这个阶段的操作。

    运行命令:

    terraform init
    terraform plan
    terraform apply
    

    预期计划:

    计划:增加10个资源,不修改任何资源,也不删除任何资源。

    这10个资源指的是3个服务账户、GitHub身份池及其提供者,以及它们之间的权限关联关系。