1. 简介
概览
为保护服务和应用的网络流量,许多组织使用 Google Cloud 上的虚拟私有云 (VCP) 网络,通过外围控件防止数据渗漏。VPC 网络是在 Google 的生产网络中实现的物理网络的虚拟版本。VPC 网络可为您的 Compute Engine 虚拟机 (VM) 实例提供连接,为内部应用负载平衡器提供原生内部直通式网络负载平衡器和代理系统,使用 Cloud VPN 隧道和适用于 Cloud Interconnect 的 VLAN 连接连接到本地网络,并将来自 Google Cloud 外部负载平衡器的流量分配到后端。
与虚拟机不同,Cloud Run 服务默认不与任何特定的 VPC 网络关联。此 Codelab 演示了如何更改入站(入站连接)设置,以便只有来自 VPC 的流量才能访问 Cloud Run 服务(例如后端服务)。此外,此 Codelab 将向您展示如何让第二项服务(例如前端服务)通过 VPC 访问 Cloud Run 后端服务,以及如何继续拥有公共互联网访问权限。
在此示例中,后端 Cloud Run 服务会返回 hello world。前端 Cloud Run 服务在界面中提供一个输入字段来收集网址。然后,前端服务将向该网址(例如后端服务)发出 GET 请求,从而使其成为服务到服务请求(而不是浏览器到服务请求)。当前端服务可以成功访问后端时,浏览器中会显示消息“hello world”。然后,了解如何调用 https://curlmyip.org 来检索前端服务的 IP 地址。
学习内容
- 如何仅允许从 VPC 传入 Cloud Run 服务的流量
- 如何在 Cloud Run 服务(例如前端)上配置出站流量,以与仅限内部入站流量的 Cloud Run 服务(例如后端)通信,同时保持前端服务的公共互联网访问权限。
2. 设置和要求
前提条件
- 您已登录 Cloud 控制台。
- 您之前已部署了第 2 代函数。例如,您可以按照《Cloud Functions(第 2 代)快速入门》中的说明开始部署。
激活 Cloud Shell
- 在 Cloud Console 中,点击激活 Cloud Shell
。
如果这是您第一次启动 Cloud Shell,系统会显示一个中间屏幕,说明它是什么。如果您看到中间屏幕,请点击继续。
预配和连接到 Cloud Shell 只需花几分钟时间。
这个虚拟机装有所需的所有开发工具。它提供了一个持久的 5 GB 主目录,并在 Google Cloud 中运行,大大增强了网络性能和身份验证功能。您在此 Codelab 中的大部分(即使不是全部)工作都可以通过浏览器完成。
在连接到 Cloud Shell 后,您应该会看到自己已通过身份验证,并且相关项目已设为您的项目 ID。
- 在 Cloud Shell 中运行以下命令以确认您已通过身份验证:
gcloud auth list
命令输出
Credentialed Accounts ACTIVE ACCOUNT * <my_account>@<my_domain.com> To set the active account, run: $ gcloud config set account `ACCOUNT`
- 在 Cloud Shell 中运行以下命令,以确认 gcloud 命令了解您的项目:
gcloud config list project
命令输出
[core] project = <PROJECT_ID>
如果不是上述结果,您可以使用以下命令进行设置:
gcloud config set project <PROJECT_ID>
命令输出
Updated property [core/project].
3. 创建 Cloud Run 服务
设置环境变量
您可以设置要在整个 Codelab 中使用的环境变量。
PROJECT_ID=<YOUR_PROJECT_ID> REGION=<YOUR_REGION, e.g. us-central1> FRONTEND=frontend-with-internet BACKEND=backend SUBNET_NAME=default
创建后端 Cloud Run 服务
首先,为源代码创建一个目录,然后通过 cd 命令进入该目录。
mkdir -p egress-private-codelab/frontend-w-internet egress-private-codelab/backend && cd egress-private-codelab/backend
然后,创建一个包含以下内容的“package.json”文件:
{ "name": "backend-service", "version": "1.0.0", "description": "", "scripts": { "start": "node index.js" }, "dependencies": { "express": "^4.18.1" } }
接下来,创建一个包含以下内容的 index.js
源文件。此文件包含服务的入口点以及应用的主要逻辑。
const express = require('express'); const app = express(); app.use(express.urlencoded({ extended: true })); app.get('/', function (req, res) { res.send("hello world"); }); const port = parseInt(process.env.PORT) || 8080; app.listen(port, () => { console.log(`helloworld: listening on port ${port}`); });
最后,运行以下命令部署 Cloud Run 服务。
gcloud run deploy $BACKEND --source . --allow-unauthenticated --region $REGION
创建前端 Cloud Run 服务
导航到前端目录
cd ../frontend-w-internet
然后,创建一个包含以下内容的 package.json
文件:
{ "name": "frontend", "version": "1.0.0", "description": "", "scripts": { "start": "node index.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "axios": "^1.6.6", "express": "^4.18.2", "htmx.org": "^1.9.10" } }
接下来,创建一个包含以下内容的 index.js
源文件。此文件包含服务的入口点以及应用的主要逻辑。
const express = require("express"); const app = express(); const port = 8080; const path = require('path'); const axios = require('axios'); // serve static content (index.html) using // built-in middleware function in Express app.use(express.static('public')); app.use(express.urlencoded({ extended: true })); // this endpoint receives a URL in the post body // and then makes a get request to that URL // results are sent back to the caller app.post('/callService', async (req, res) => { const url = req.body.url; let message = ""; try { console.log("url: ", url); const response = await axios.get(url); message = response.data; } catch (error) { message = error.message; console.error(error.message); } res.send(` ${message} <p> </p> `); }); app.listen(port, () => { console.log(`Example app listening on port ${port}`); });
为 index.html 文件创建一个公共目录
mkdir public touch public/index.html
更新 index.html
以包含以下内容:
<html> <script src="https://unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous" ></script> <body> <div style="margin-top: 100px; margin-left: 100px"> <h1>I'm the Request Tester service on the Internet</h1> <form hx-trigger="submit" hx-post="/callService" hx-target="#zen"> <label for="url"> URL:</label> <input style="width: 308px" type="text" id="url" name="url" placeholder="The backend service URL" required /> <button hx-indicator="#loading" type="submit">Submit</button> <p></p> <span class="htmx-indicator" id="loading"> Loading... </span> <div id="zen" style="white-space: pre-wrap"></div> <p></p> </form> </div> </body> </html>
最后,运行以下命令部署 Cloud Run 服务。
gcloud run deploy $FRONTEND --source . --allow-unauthenticated --region $REGION
调用后端服务
在本部分中,您将验证是否已成功部署两项 Cloud Run 服务。
在网络浏览器中打开前端服务的网址,例如https://frontend-your-hash-uc.a.run.app/
在文本框中,输入后端服务的网址。请注意,此请求会从前端 Cloud Run 实例路由到后端 Cloud Run 服务,而不是从浏览器路由。
您会看到“hello world”
4. 仅为内部入站流量设置后端服务
您可以运行以下 gcloud 命令,将 Cloud Run 服务整合到您的专用网络中。
gcloud run services update $BACKEND --ingress internal --region $REGION
如果您尝试从前端服务调用后端服务,则会收到 404 错误。前端 Cloud Run 服务传出连接(或出站流量)首先传出互联网,因此 Google Cloud 不知道请求的来源。
5. 配置前端服务以访问 VPC
在本部分中,您将配置前端 Cloud Run 服务,以通过 VPC 与后端服务通信。
为此,您需要将直接 VPC 出站流量添加到前端 Cloud Run 服务,以确保它可以访问 VPC 网络上的内部 IP 地址。然后,您将配置出站流量,以便仅将发送到专用 IP 的请求路由到 VPC。此配置将允许您的前端仍然连接公共互联网。如需了解详情,请参阅有关接收来自其他 Cloud Run 服务的请求的文档。
配置直接 VPC 出站流量
首先,运行以下命令以在前端服务上使用直接 VPC 出站流量:
gcloud beta run services update $FRONTEND \ --network=$SUBNET_NAME \ --subnet=$SUBNET_NAME \ --vpc-egress=private-ranges-only \ --region=$REGION
您现在可以确认前端服务可以访问 VPC:
gcloud beta run services describe $FRONTEND \ --region=$REGION
您应该会看到类似如下的输出:
VPC access: Network: default Subnet: default Egress: private-ranges-only
启用专用 Google 访问通道
接下来,您需要运行以下命令,在子网上启用专用 Google 访问通道:
gcloud compute networks subnets update $SUBNET_NAME \ --region=$REGION \ --enable-private-ip-google-access
您可以运行以下命令来验证专用 Google 访问通道是否已启用:
gcloud compute networks subnets describe $SUBNET_NAME \ --region=$REGION \ --format="get(privateIpGoogleAccess)"
为 run.app 网址创建 Cloud DNS 区域
最后,为 run.app 网址创建一个 Cloud DNS 区域,以便 Google Cloud 可以将它们视为内部 IP 地址。
在上一步中,您将直接 VPC 出站流量配置为 private-ranges-only。这意味着,仅当目标为内部 IP 时,来自前端服务的出站连接才会转到 VPC 网络。但是,您的后端服务使用解析为公共 IP 的 run.app 网址。
在此步骤中,您将为 run.app 网址创建一个 Cloud DNS 区域,以解析为 private.googleapis.com IP 地址范围(识别为内部 IP 地址)。现在,发送到这些范围的任何请求都将通过您的 VPC 网络路由。
您可以通过以下网址执行此操作:https://cloud.google.com/run/docs/securing/private-networking#from-other-services
# do not include the https:// in your DNS Name # for example: backend-<hash>-uc.a.run.app DNS_NAME=<your backend service URL without the https://> gcloud dns --project=$PROJECT_ID managed-zones create codelab-backend-service \ --description="" \ --dns-name="a.run.app." \ --visibility="private" \ --networks=$SUBNET_NAME gcloud dns --project=$PROJECT_ID record-sets create $DNS_NAME. \ --zone="codelab-backend-service" \ --type="A" \ --ttl="60" \ --rrdatas="199.36.153.8,199.36.153.9,199.36.153.10,199.36.153.11"
现在,当您尝试访问您网站的后端服务时,会看到“hello world”返回。
当您尝试使用 https://curlmyip.org/ 访问互联网时,会看到您的 IP 地址。
6. 问题排查
如果设置配置不正确,您可能会遇到以下错误消息。
- 如果您收到错误消息,
getaddrinfo ENOTFOUND backend-your-hash-uc.a.run.app
请确保您未添加“https://”添加到 DNS A 记录 - 如果您在配置可用区后尝试访问后端时收到 404 错误,则可以等待全局 run.app 记录中的缓存过期(例如 6 小时),也可以运行以下命令来创建新修订版本(从而清除缓存):
gcloud beta run services update $FRONTEND --network=$SUBNET_NAME --subnet=$SUBNET_NAME --vpc-egress=private-ranges-only --region=$REGION
7. 恭喜!
恭喜您完成此 Codelab!
建议您查看有关 Cloud Run 上的专用网络的文档。
所学内容
- 如何仅允许从 VPC 传入 Cloud Run 服务的流量
- 如何在 Cloud Run 服务(例如前端)上配置出站流量,以与仅限内部入站流量的 Cloud Run 服务(例如后端)通信,同时保持前端服务的公共互联网访问权限。
8. 清理
为避免产生意外费用(例如,如果此 Cloud Run 服务被意外调用的次数超过免费层级中的每月 Cloud Run 调用次数),您可以删除该 Cloud Run 服务或删除您在第 2 步中创建的项目。
如需删除 Cloud Run 服务,请前往 Cloud Run Cloud 控制台(网址为 https://console.cloud.google.com/functions/),并删除您在此 Codelab 中创建的 $FRONTEND 和 $BACKEND 服务。
如果您选择删除整个项目,可以前往 https://console.cloud.google.com/cloud-resource-manager,选择您在第 2 步中创建的项目,然后选择“删除”。如果删除项目,则需要在 Cloud SDK 中更改项目。您可以通过运行 gcloud projects list
来查看所有可用项目的列表。