使用 NodeJS 进行 InnerLoop 开发

1. 概览

本实验将演示旨在简化软件工程师在容器化环境中开发 NodeJS 应用的开发工作流程的功能和特性。典型的容器开发需要用户了解容器和容器构建流程的详细信息。此外,开发者通常必须中断工作流程,离开 IDE 以在远程环境中测试和调试应用。借助本教程中提到的工具和技术,开发者无需离开 IDE 即可高效处理容器化应用。

学习内容

在本实验中,您将学习在 Google Cloud 中使用容器进行开发的方法,包括:

  • 创建起始 Node.js 应用
  • 为容器开发配置 Node.js 应用
  • 编写简单的 CRUD REST 服务
  • 部署到 GKE
  • 调试错误状态
  • 利用断点 / 日志
  • 将更改热部署回 GKE
  • 可选:集成 CloudSQL 以实现后端持久性

2. 设置和要求

自定进度的环境设置

  1. 登录 Google Cloud 控制台,然后创建一个新项目或重复使用现有项目。如果您还没有 Gmail 或 Google Workspace 账号,则必须创建一个。

b35bf95b8bf3d5d8.pnga99b7ace416376c4.pngbd84a6d3004737c5.png

  • 项目名称是此项目参与者的显示名称。它是 Google API 尚未使用的字符串,您可以随时对其进行更新。
  • 项目 ID 在所有 Google Cloud 项目中必须是唯一的,并且不可变(一经设置便无法更改)。Cloud Console 会自动生成一个唯一字符串;通常情况下,您无需关注该字符串。在大多数 Codelab 中,您都需要引用项目 ID(它通常标识为 PROJECT_ID),因此如果您不喜欢某个 ID,请再生成一个随机 ID,还可以尝试自己创建一个,并确认是否可用。然后,项目创建后,ID 会处于“冻结”状态。
  • 第三个值是一些 API 使用的项目编号。如需详细了解所有这三个值,请参阅文档。
  1. 接下来,您需要在 Cloud Console 中启用结算功能,才能使用 Cloud 资源/API。运行此 Codelab 应该不会产生太多的费用(如果有费用的话)。要关闭资源以避免产生超出本教程范围的费用,请按照此 Codelab 末尾提供的任何“清理”说明操作。Google Cloud 的新用户符合参与 $300 USD 免费试用计划的条件。

启动 Cloudshell Editor

本实验经过设计和测试,可与 Google Cloud Shell 编辑器搭配使用。如需访问编辑器,请执行以下操作:

  1. 访问您的 Google 项目:https://console.cloud.google.com。
  2. 点击右上角的 Cloud Shell 编辑器图标

8560cc8d45e8c112.png

  1. 系统会在窗口底部打开一个新窗格
  2. 点击“打开编辑器”按钮

9e504cb98a6a8005.png

  1. 编辑器将打开,右侧显示资源管理器,中央区域显示编辑器
  2. 屏幕底部还应显示一个终端窗格
  3. 如果终端未打开,请使用 `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 集群和数据库

  1. 下载设置脚本并使其可执行。
wget https://raw.githubusercontent.com/GoogleCloudPlatform/container-developer-workshop/main/labs/nodejs/setup.sh
chmod +x setup.sh

预配本实验中使用的基础设施

在本实验中,您将把代码部署到 GKE,并访问存储在 Spanner 数据库中的数据。下面的设置脚本会为您准备此基础设施。

  1. 打开 setup.sh 文件,并修改当前设置为 CHANGEME 的密码值
  2. 运行设置脚本以启动您将在本实验中使用的 GKE 集群和 CloudSQL 数据库
./setup.sh
  1. 在 Cloud Shell 中,创建一个名为 mynodejsapp 的新目录
mkdir mynodejsapp
  1. 切换到该目录,然后将其作为工作区打开。这将在新创建的文件夹中创建工作区配置,从而重新加载编辑器。
cd mynodejsapp && cloudshell workspace .
  1. 使用 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. 创建新的启动方式

  1. 初始化应用

通过运行以下命令创建 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"
}
  1. 添加入口点

修改此文件,以在脚本 "start": "node src/index.js", 中添加启动命令。更改后,脚本应如下面的代码段所示:

"scripts": {
    "start": "node src/index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  1. 添加 Express 依赖项

我们将要添加的代码也使用 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"
  }
}
  1. 创建 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

更新应用名称

配置中包含的默认值目前与您的应用名称不一致。更新文件以引用您的应用名称,而不是默认值。

  1. 更改 Skaffold 配置中的条目
  • 打开“skaffold.yaml”
  • 选择当前设置为 package-json-image 的图片名称
  • 右键点击,然后选择“更改所有匹配项”
  • 以 mynodejsapp 格式输入新名称
  1. 更改 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

  1. 在 Cloud Shell Editor 底部的窗格中,选择 Cloud Code 

fdc797a769040839.png

  1. 在顶部显示的面板中,选择在 Kubernetes 上运行。如果系统提示您,请选择“是”以使用当前的 Kubernetes 上下文。

cfce0d11ef307087.png

  1. 首次运行该命令时,屏幕顶部会显示一个提示,询问您是否需要当前 Kubernetes 上下文,请选择“是”以接受并使用当前上下文。

817ee33b5b412ff8.png

  1. 接下来,系统会显示一个提示,询问要使用哪个容器注册表。按 Enter 键接受提供的默认值

eb4469aed97a25f6.png

  1. 选择下部窗格中的“输出”标签页,查看进度和通知

f95b620569ba96c5.png

  1. 在右侧的渠道下拉菜单中选择“Kubernetes:运行/调试 - 详细”,以查看其他详细信息和从容器实时传输的日志

94acdcdda6d2108.png

  1. 从下拉菜单中选择“Kubernetes:运行/调试”,返回到简化视图
  2. 构建和测试完成后,“输出”标签页会显示 Resource deployment/mynodejsapp status completed successfully,并列出网址:“Forwarded 网址 from service demo-app: http://localhost: 8080”(来自服务 demo-app 的转发网址:http://localhost:8080)
  3. 在 Cloud Code 终端中,将光标悬停在输出中的网址 (http://localhost:8080) 上,然后在显示的工具提示中选择“打开网页预览”。

响应内容如下:

{"message":"Greetings from Node"}

热重载

  1. 前往 src/index.js。修改问候消息的代码'Hello from Node'

请注意,在 Output 窗口的 Kubernetes: Run/Debug 视图中,监视器会立即将更新后的文件与 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
  1. 如果您切换到 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/
  1. 刷新浏览器即可查看更新后的结果。

调试

  1. 前往“调试”视图,然后停止当前线程 647213126d7a4c7b.png。
  2. 点击底部菜单中的 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.
  1. 底部状态栏的颜色从蓝色变为橙色,表示处于调试模式。
  2. 在 Kubernetes Run/Debug 视图中,请注意,可调试容器已启动
**************URLs*****************
Forwarded URL from service mynodejsapp-service: http://localhost:8080
Debuggable container started pod/mynodejsapp-deployment-6bc7598798-xl9kj:mynodejsapp (default)
Update succeeded
***********************************

利用断点

  1. 打开 src/index.js
  2. 找到显示 var message="Greetings from Node"; 的语句
  3. 点击行号左侧的空白处,为该行添加一个断点。系统会显示一个红色指示器,表明已设置断点
  4. 重新加载浏览器,并注意调试器会在断点处停止进程,以便您调查在 GKE 中远程运行的应用的变量和状态
  5. 向下点击进入变量部分,直到找到 "message" 变量。
  6. 按“Step over”7cfdee4fd6ef5c3a.png 执行该行
  7. 观察 "message" 变量的当前值是否更改为 "Greetings from Node"
  8. 双击变量名称“target”,然后在弹出式窗口中将值更改为其他值,例如 "Hello from Node"
  9. 点击调试控制面板中的“继续”按钮
  10. 在浏览器中查看响应,其中现在显示了您刚刚输入的更新后的值。
  11. 按停止按钮 647213126d7a4c7b.png 停止“调试”模式,然后再次点击断点以移除断点。

5. 开发简单的 CRUD REST 服务

至此,您的应用已完全配置好,可以进行容器化开发,并且您已通过 Cloud Code 完成了基本开发工作流程。在以下部分中,您将通过添加连接到 Google Cloud 中托管式数据库的 REST 服务端点来练习所学知识。

配置依赖项

应用代码使用数据库来持久保留 REST 服务数据。通过在 package.json 文件中添加以下内容来确保依赖项可用

  1. 向 package.json 文件添加另外两个依赖项 pg 和 sequelize,以构建 CRUD 应用 Postgres。发布更改后的依赖项部分如下所示。
    "dependencies": {
    "express": "^4.16.4",
    "pg": "^8.7.3",
    "sequelize": "^6.17.0"
  }

对 REST 服务进行编码

  1. 向此应用添加 CRUD 应用代码
wget -O app.zip https://github.com/GoogleCloudPlatform/container-developer-workshop/raw/main/labs/nodejs/app.zip

unzip app.zip

此代码具有

  • 模型文件夹,其中包含 item 的实体模型
  • 包含执行 CRUD 操作的代码的 controllers 文件夹
  • 路由文件夹,用于将特定网址格式路由到不同的调用
  • 包含数据库连接详情的 config 文件夹
  1. 请注意,db.config.js 文件中的数据库配置是指连接到数据库时需要提供的环境变量。您还需要解析传入的请求以进行网址编码。
  2. 在 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);
  1. 修改 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
  1. 将 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

部署和验证应用

  1. 在 Cloud Shell 编辑器底部的窗格中,选择 Cloud Code,然后选择屏幕顶部的 Debug on Kubernetes。
  2. 构建和测试完成后,“输出”标签页将显示 Resource deployment/mynodejsapp status completed successfully,并列出网址:“Forwarded URL from service mynodejsapp: http://localhost: 8080”(来自服务 mynodejsapp 的转发网址:http://localhost:8080)
  3. 添加几项内容。

在 Cloud Shell 终端中,运行以下命令

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"
  1. 在浏览器中运行 $URL/items,测试 GET。您还可以通过命令行运行 curl
curl -X GET $URL/items
  1. 测试删除:现在,尝试运行以下命令来删除某项内容。根据需要更改 item-id 的值。
curl -X DELETE $URL/items/1
    This throws an error message
{"message":"Could not delete Item with id=[object Object]"}

找出并解决问题

  1. 在调试模式下重启应用,然后找到问题。请参考以下提示:
  • 我们知道 DELETE 存在问题,因为它未返回所需的结果。因此,您需要在 itemcontroller.js->exports.delete 方法中设置断点。
  • 逐步执行并观察每个步骤中的变量,以查看左侧窗口中的局部变量值。
  • 如需观察特定值(例如 request.params),请将此变量添加到“监视”窗口。
  1. 请注意,分配给 id 的值为 undefined。更改代码以解决问题。

修正后的代码段如下所示。

// Delete a Item with the specified id in the request
exports.delete = (req, res) => {
    const id = req.params.id;
  1. 重启应用后,再次尝试删除,以进行测试。
  2. 点击调试工具栏中的红色方块,停止调试会话 647213126d7a4c7b.png

6. 清理

恭喜!在本实验中,您从头开始创建了一个新的 Nodejs 应用,并将其配置为在热部署模式下与容器搭配使用。然后,您按照传统应用堆栈中相同的开发者流程,将应用部署到远程 GKE 集群并进行调试。

如需在完成实验后进行清理,请执行以下操作:

  1. 删除实验中使用的文件
cd ~ && rm -rf mynodejsapp && rm -f setup.sh
  1. 删除项目以移除所有相关基础架构和资源