1. 概览
本实验演示了一些特性和功能,这些特性和功能旨在简化在容器化环境中开发 NodeJS 应用的软件工程师的开发工作流。典型的容器开发要求用户了解容器和容器构建流程的详细信息。此外,开发者通常需要中断他们的流程,离开 IDE,以在远程环境中测试和调试其应用。借助本教程中提到的工具和技术,开发者无需离开 IDE 即可高效使用容器化应用。
学习内容
在本实验中,您将学习在 GCP 中使用容器进行开发的方法,包括:
- 创建起始 Nodejs 应用
- 配置 Nodejs 应用以进行容器开发
- 编写简单的 CRUD REST 服务
- 部署到 GKE
- 调试错误状态
- 利用断点 / 日志
- 将更改热部署回 GKE
- 可选:集成 CloudSQL 以实现后端持久性
2. 设置和要求
自定进度的环境设置
- 登录 Google Cloud 控制台,然后创建一个新项目或重复使用现有项目。如果您还没有 Gmail 或 Google Workspace 账号,则必须创建一个。
- 项目名称是此项目参与者的显示名称。它是 Google API 尚未使用的字符串,您可以随时对其进行更新。
- 项目 ID 在所有 Google Cloud 项目中必须是唯一的,并且不可变(一经设置便无法更改)。Cloud Console 会自动生成一个唯一字符串;通常情况下,您无需关注该字符串。在大多数 Codelab 中,您都需要引用项目 ID(它通常标识为
PROJECT_ID
),因此如果您不喜欢某个 ID,请再生成一个随机 ID,还可以尝试自己创建一个,并确认是否可用。然后,项目创建后,ID 会处于“冻结”状态。 - 第三个值是一些 API 使用的项目编号。如需详细了解所有这三个值,请参阅文档。
- 接下来,您需要在 Cloud Console 中启用结算功能,才能使用 Cloud 资源/API。运行此 Codelab 应该不会产生太多的费用(如果有费用的话)。要关闭资源以避免产生超出本教程范围的费用,请按照此 Codelab 末尾提供的任何“清理”说明操作。Google Cloud 的新用户符合参与 $300 USD 免费试用计划的条件。
启动 Cloudshell 编辑器
本实验旨在与 Google Cloud Shell Editor 搭配使用,并经过测试。要访问该编辑器,请按以下步骤操作:
- 通过 https://console.cloud.google.com 访问您的 Google 项目。
- 点击右上角的 Cloud Shell 编辑器图标
- 窗口底部会打开一个新窗格
- 点击“打开编辑器”按钮
- 编辑器将打开,右侧为探索器,中心区域为编辑器
- 屏幕底部还应提供一个终端窗格
- 如果终端未打开,请使用 `ctrl+` 的组合键打开新的终端窗口
设置 gcloud
在 Cloud Shell 中,设置项目 ID 以及要将应用部署到的区域。将它们保存为 PROJECT_ID
和 REGION
变量。
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')
设置 GKE 集群和数据库
- 下载设置脚本并使其可执行。
wget https://raw.githubusercontent.com/GoogleCloudPlatform/container-developer-workshop/main/labs/nodejs/setup.sh
chmod +x setup.sh
预配本实验中使用的基础架构
在本实验中,您会将代码部署到 GKE,并访问存储在 Spanner 数据库中的数据。下面的设置脚本会为您准备此基础架构。
- 打开
setup.sh
文件,然后修改当前设为“CHANGEME”的密码的值 - 运行设置脚本,以建立您将在本实验中使用的 GKE 集群和 CloudSQL 数据库
./setup.sh
- 在 Cloud Shell 中,创建一个名为
mynodejsapp
的新目录
mkdir mynodejsapp
- 切换到此目录并将其作为工作区打开。此操作会在新创建的文件夹中创建工作区配置,从而重新加载编辑器。
cd mynodejsapp && cloudshell workspace .
- 使用 NVM 安装 Node 和 NPM。
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
# This loads nvm bash_completion
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
nvm install stable
nvm alias default stable
3. 创建新的起始应用
- 初始化应用
通过运行以下命令创建 package.json
文件
npm init
Choose the entry point: (index.js) src/index.js and default values for the rest of the parameters. This will create the file with following contents
{ "name": "mynodejsapp", "version": "1.0.0", "description": "", "main": "src/index.js",, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" }
- 添加入口点
修改此文件,以在脚本 "start": "node src/index.js",
中添加启动命令。更改后,脚本应如以下代码段所示:
"scripts": {
"start": "node src/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
- 添加极速依赖项
我们要添加的代码也使用 express
,因此我们将该依赖项添加到此 package.json
文件中。因此,完成所有更改后,package.json
文件应如下所示。
{ "name": "mynodejsapp", "version": "1.0.0", "description": "", "main": "src/index.js", "scripts": { "start": "node src/index.js", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Your Name", "license": "ISC", "dependencies": { "express": "^4.16.4" } }
- 创建 index.js 文件
创建一个名为 src 的源目录
使用以下代码创建 src/index.js
const express = require('express');
const app = express();
const PORT = 8080;
app.get('/', (req, res) => {
var message="Greetings from Node";
res.send({ message: message });
});
app.listen(PORT, () => {
console.log(`Server running at: http://localhost:${PORT}/`);
});
请注意,PORT 已设置为 8080
值
生成清单
Skaffold 提供集成工具以简化容器开发。在此步骤中,您将初始化 skaffold,它会自动创建基础 Kubernetes YAML 文件。执行以下命令以开始该过程。
在终端中执行以下命令
skaffold init --generate-manifests
当系统提示时:
- 输入 8080 作为端口
- 输入 y 以保存配置
向工作区可视化图表添加了 skaffold.yaml
和 deployment.yaml
两个文件
更新应用名称
配置中包含的默认值目前与应用的名称不匹配。更新文件以引用您的应用名称,而不是默认值。
- 更改 Skaffold 配置中的条目
- 打开
skaffold.yaml
- 选择当前设为“
package-json-image
”的映像名称 - 右键点击并选择“更改所有出现次数”
- 输入新名称
mynodejsapp
- 更改 Kubernetes 配置中的条目
- 打开
deployment.yaml
文件 - 选择当前设为“
package-json-image
”的映像名称 - 右键点击并选择“更改所有出现次数”
- 输入新名称
mynodejsapp
请注意,在 skaffold.yaml
文件中,build
部分使用 buildpacks
将应用容器化。此代码不包含 Dockerfile,并且开发者无需具备任何 Docker 知识就能将此应用容器化。
此外,通过此 Skaffold 配置,会自动在编辑器和正在运行的容器之间启用热同步。无需其他配置即可启用热同步。
4. 开发过程介绍
在本部分中,您将逐步完成使用 Cloud Code 插件的几个步骤,了解基本流程并验证起始应用的配置和设置。
Cloud Code 与 Skaffold 集成,可简化您的开发流程。当您按以下步骤部署到 GKE 时,Cloud Code 和 Skaffold 会自动构建容器映像,将其推送到 Container Registry,然后将应用部署到 GKE。这是在后台将细节从开发者流程中提取出来的。Cloud Code 还可以为基于容器的开发提供传统的调试和热同步功能,从而增强您的开发流程。
将容器部署到 Kubernetes
- 在 Cloud Shell Editor 底部的窗格中,选择 Cloud Code 
- 在顶部显示的面板中,选择 Run on Kubernetes。如果出现提示,请选择“Yes”以使用当前的 Kubernetes 上下文。
- 首次运行此命令时,屏幕顶部会显示一条提示,询问您是否需要当前的 Kubernetes 上下文,请选择“是”接受并使用当前上下文。
- 接下来,系统会显示一条提示,询问要使用哪个容器注册表。按 Enter 键可接受提供的默认值
- 选择下部窗格中的“Output”(输出)标签页可查看进度和通知
- 选择“Kubernetes:运行/调试 - 详细”查看右侧渠道下拉菜单中的 其他详细信息和实时从容器流式传输的日志
- 选择“Kubernetes: Run/Debug”返回简化的视图下拉菜单
- 构建和测试完成后,“Output”(输出)标签页会显示
Resource deployment/mynodejsapp status completed successfully
,并列出了一个网址:“Forwarded 网址 from service demo-app: http://localhost:8080” - 在 Cloud Code 终端中,将鼠标悬停在输出中的网址 (http://localhost:8080) 上,然后在显示的工具提示中选择“打开网页预览”。
响应将是:
{"message":"Greetings from Node"}
热重载
- 前往
src/index.js
。将问候消息的代码修改为'Hello from Node'
请注意,在 Output
窗口的 Kubernetes: Run/Debug
视图中,Watcher 将更新后的文件与 Kubernetes 中的容器同步
Update initiated File sync started for 1 files for gcr.io/myproject/mynodejsapp:latest@sha256:f554756b3b4d6c301c4b26ef96102227cfa2833270db56241248ae42baa1971a File sync succeeded for 1 files for gcr.io/myproject/mynodejsapp:latest@sha256:f554756b3b4d6c301c4b26ef96102227cfa2833270db56241248ae42baa1971a Update succeeded
- 如果切换到
Kubernetes: Run/Debug - Detailed
视图,您会注意到它可以识别文件更改并重启节点
files modified: [src/index.js] Copying files:map[src/index.js:[/workspace/src/index.js]]togcr.io/myproject/mynodejsapp:latest@sha256:f554756b3b4d6c301c4b26ef96102227cfa2833270db56241248ae42baa1971a Syncing 1 files for gcr.io/myproject/mynodejsapp:latest@sha256:f554756b3b4d6c301c4b26ef96102227cfa2833270db56241248ae42baa1971a Watching for changes... [mynodejsapp] [mynodejsapp]> mynodejsapp@1.0.0 start /workspace [mynodejsapp]> node src/index.js [mynodejsapp] [mynodejsapp]Server running at: http://localhost:8080/
- 刷新浏览器以查看更新后的结果。
调试
- 转到“调试”视图并停止当前线程
。
- 点击底部菜单中的
Cloud Code
,然后选择Debug on Kubernetes
以在debug
模式下运行应用。
- 在
Output
窗口的Kubernetes Run/Debug - Detailed
视图中,请注意 Skaffold 将在调试模式下部署此应用。 - 构建和部署应用需要几分钟的时间。这次您会发现连接了调试程序。
Port forwarding pod/mynodejsapp-6bbcf847cd-vqr6v in namespace default, remote port 9229 -> http://127.0.0.1:9229 [mynodejsapp]Debugger attached.
- 底部状态栏的颜色从蓝色变为橙色,表示它处于调试模式。
- 在
Kubernetes Run/Debug
视图中,请注意启动了一个 Debuggable 容器
**************URLs***************** Forwarded URL from service mynodejsapp-service: http://localhost:8080 Debuggable container started pod/mynodejsapp-deployment-6bc7598798-xl9kj:mynodejsapp (default) Update succeeded ***********************************
利用断点
- 打开
src/index.js
- 找到显示
var message="Greetings from Node";
的语句 - 点击行号左侧的空白处,为该行添加断点。系统会显示一个红色指示器,指明断点已设置
- 重新加载浏览器,并注意调试程序会在断点停止进程,并允许您调查在 GKE 中远程运行的应用变量和状态
- 点击“变量”部分,直到找到
"message"
变量。 - 按“Step over
”执行该行
- 观察
"message"
变量的当前值更改为"Greetings from Node"
- 双击变量名称“target”在弹出式窗口中,将值更改为其他值,例如
"Hello from Node"
- 点击调试控制台中的“继续”按钮
- 在浏览器中查看响应,浏览器现在会显示您刚刚输入的更新值。
- 停止“调试”进入模式,然后再次点击停止按钮
以移除该断点。
5. 开发简单的 CRUD 静态服务
至此,您的应用已完全针对容器化开发进行了配置,并且您已完成 Cloud Code 的基本开发工作流。在以下部分中,您将通过添加连接到 Google Cloud 中的代管式数据库的 REST 服务端点,练习所学知识。
配置依赖项
应用代码使用数据库来保留其余服务数据。通过在 package.json
文件中添加以下代码,确保依赖项可用
- 将另外两个依赖项
pg
和sequelize
添加到package.json
文件,以构建 CRUD 应用 Postgres。发布更改后,依赖项部分将如下所示。
"dependencies": {
"express": "^4.16.4",
"pg": "^8.7.3",
"sequelize": "^6.17.0"
}
编写 REST 服务代码
- 将 CRUD 应用代码添加到此应用
wget -O app.zip https://github.com/GoogleCloudPlatform/container-developer-workshop/raw/main/labs/nodejs/app.zip
unzip app.zip
此代码包含
- models 文件夹,其中包含
item
的实体模型 - controllers 文件夹,包含执行 CRUD 操作的代码
- routes 文件夹,用于将特定网址格式路由到不同的调用
- 包含数据库连接详情的 config 文件夹
- 请注意,
db.config.js
文件中的数据库配置是指连接到数据库时需要提供的环境变量。此外,您还需要解析传入的网址编码请求。 - 在
src/index.js
中添加以下代码段,以便能够从主 JavaScript 文件连接到 CRUD 代码,就在以app.listen(PORT, () => {
开头的最后一部分之前
const bodyParser = require('body-parser')
app.use(bodyParser.json())
app.use(
bodyParser.urlencoded({
extended: true,
})
)
const db = require("../app/models");
db.sequelize.sync();
require("../app/routes/item.routes")(app);
- 修改
deployment.yaml
文件中的部署,以添加环境变量来提供数据库连接信息。
更新文件末尾的规范条目,以与以下定义匹配
spec:
containers:
- name: mynodejsapp
image: mynodejsapp
env:
- name: DB_HOST
value: ${DB_INSTANCE_IP}
- name: DB_PORT
value: "5432"
- name: DB_USER
valueFrom:
secretKeyRef:
name: gke-cloud-sql-secrets
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: gke-cloud-sql-secrets
key: password
- name: DB_NAME
valueFrom:
secretKeyRef:
name: gke-cloud-sql-secrets
key: database
- 将 DB_HOST 值替换为您的数据库地址
export DB_INSTANCE_IP=$(gcloud sql instances describe mytest-instance \
--format=json | jq \
--raw-output ".ipAddresses[].ipAddress")
envsubst < deployment.yaml > deployment.new && mv deployment.new deployment.yaml
部署并验证应用
- 在 Cloud Shell 编辑器底部的窗格中,选择
Cloud Code
,然后选择屏幕顶部的Debug on Kubernetes
。 - 构建和测试完成后,“Output”(输出)标签页会显示
Resource deployment/mynodejsapp status completed successfully
,并列出了一个网址:“Forwarded 网址 from service mynodejsapp: http://localhost:8080” - 添加几项内容。
从 cloudshell 终端运行以下命令
URL=localhost:8080
curl -X POST $URL/items -d '{"itemName":"Body Spray", "itemPrice":3.2}' -H "Content-Type: application/json"
curl -X POST $URL/items -d '{"itemName":"Nail Cutter", "itemPrice":2.5}' -H "Content-Type: application/json"
- 在浏览器中运行 $网址/items 以测试 GET。您也可以从命令行运行 curl 命令
curl -X GET $URL/items
- Test Delete:现在尝试通过运行来删除商品。根据需要更改 item-id 的值。
curl -X DELETE $URL/items/1
This throws an error message
{"message":"Could not delete Item with id=[object Object]"}
找出并解决问题
- 在调试模式下重启应用,然后找出问题。请参考以下提示:
- 我们知道 DELETE 出错了,因为它没有返回所需的结果。因此,您需要在
itemcontroller.js
->exports.delete
方法中设置断点。 - 逐步执行执行,并观察每一步的变量,观察左侧窗口中局部变量的值。
- 如需观察特定值(如
request.params
),请将此变量添加到“Watch”窗口。
- 请注意,分配给
id
的值为undefined
。更改代码以解决问题。
修复后的代码段将如下所示。
// Delete a Item with the specified id in the request exports.delete = (req, res) => { const id = req.params.id;
- 重启应用后,通过尝试删除应用再次测试。
- 点击调试工具栏
中的红色方块,停止调试会话
6. 清理
恭喜!在本实验中,您从头开始创建了一个新的 Nodejs 应用,并将其配置为采用容器热部署模式。然后,您按照传统应用堆栈中的相同开发者流程,将应用部署并调试到远程 GKE 集群。
在完成实验后进行清理:
- 删除实验中使用的文件
cd ~ && rm -rf mynodejsapp && rm -f setup.sh
- 删除项目以移除所有相关基础架构和资源