如何配置 Cloud Run 服务以使用直接 VPC 出站流量访问内部 Cloud Run 服务

1. 简介

概览

为保护服务和应用的网络流量,许多组织使用 Google Cloud 上的虚拟私有云 (VPC) 网络,通过外围控件防止数据渗漏。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”。

学习内容

  • 如何仅允许从 VPC 传入 Cloud Run 服务的流量
  • 如何在 Cloud Run 服务上配置出站流量,以与仅限内部入站流量的 Cloud Run 服务通信

2. 设置和要求

前提条件

激活 Cloud Shell

  1. 在 Cloud Console 中,点击激活 Cloud Shelld1264ca30785e435.png

cb81e7c8e34bc8d.png

如果这是您第一次启动 Cloud Shell,系统会显示一个中间屏幕,说明它是什么。如果您看到中间屏幕,请点击继续

d95252b003979716.png

预配和连接到 Cloud Shell 只需花几分钟时间。

7833d5e1c5d18f54

这个虚拟机装有所需的所有开发工具。它提供了一个持久的 5 GB 主目录,并在 Google Cloud 中运行,大大增强了网络性能和身份验证功能。您在此 Codelab 中的大部分(即使不是全部)工作都可以通过浏览器完成。

在连接到 Cloud Shell 后,您应该会看到自己已通过身份验证,并且相关项目已设为您的项目 ID。

  1. 在 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`
  1. 在 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 中使用的环境变量。

REGION=<YOUR_REGION, e.g. us-central1>
FRONTEND=frontend
BACKEND=backend

创建后端 Cloud Run 服务

首先,为源代码创建一个目录,然后通过 cd 命令进入该目录。

mkdir -p internal-codelab/frontend internal-codelab/backend && cd internal-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

然后,创建一个包含以下内容的 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"
  }
}

接下来,创建一个包含以下内容的 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 Frontend service on the Internet</h1>
      <form hx-trigger="submit" hx-post="/callService" hx-target="#message">
        <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="message" 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 服务。

在网络浏览器中打开前端服务的网址。

在文本框中,输入后端服务的网址。请注意,此请求会从前端 Cloud Run 实例路由到后端 Cloud Run 服务,而不是从浏览器路由。

您会看到“hello world”。

4. 仅为内部入站流量设置后端服务

运行以下 gcloud 命令,以仅允许来自您的 VPC 网络的流量访问您的后端服务。

gcloud run services update $BACKEND --ingress internal --region $REGION

如需确认您的后端服务只能接收来自 VPC 的流量,请再次尝试从前端服务调用后端服务。

这次您会看到“请求失败,状态代码 404”

您收到此错误是因为前端 Cloud Run 服务出站请求(即出站流量)首先传输到互联网,因此 Google Cloud 不知道请求的来源。

在下一部分中,您将配置前端服务以访问 VPC,以便 Google Cloud 知道请求来自被识别为内部来源的 VPC。

5. 配置前端服务以访问 VPC

在本部分中,您将配置前端 Cloud Run 服务,以通过 VPC 与后端服务通信。

为此,您需要将直接 VPC 出站流量添加到前端 Cloud Run 实例,为您的服务提供可在 VPC 中使用的内部 IP 地址。然后,您将配置出站流量,以使来自前端服务的所有出站连接都转到 VPC。

首先,运行以下命令以启用直接 VPC 出站流量:

gcloud beta run services update $FRONTEND \
--network=default \
--subnet=default \
--vpc-egress=all-traffic \
--region=$REGION

您现在可以确认前端服务可以访问 VPC:

gcloud beta run services describe $FRONTEND \
--region=$REGION

您应该会看到类似如下的输出:

VPC access:
    Network:         default
    Subnet:          default
    Egress:          all-traffic

现在再次尝试从前端服务调用后端服务。

这次您会看到“hello world”。

注意:您的前端服务将无法访问互联网,因为所有出站流量都已路由到 VPC。例如,如果您的前端服务尝试访问 https://curlmyip.org/,就会超时。

6. 恭喜!

恭喜您完成此 Codelab!

建议您查看 Cloud Run 文档以及如何为 Cloud Run 服务配置专用网络

所学内容

  • 如何仅允许从 VPC 传入 Cloud Run 服务的流量
  • 如何在 Cloud Run 服务上配置出站流量,以与仅限内部入站流量的 Cloud Run 服务通信

7. 清理

为避免产生意外费用(例如,如果意外调用 Cloud Run 服务的次数超过免费层级中的每月 Cloud Run 调用次数),您可以删除 Cloud Run 或删除在第 2 步中创建的项目。

如需删除 Cloud Run 服务,请前往 https://console.cloud.google.com/run 前往 Cloud Run Cloud 控制台,然后删除 $FRONTEND 和 $BACKEND 服务。

如果您选择删除整个项目,可以前往 https://console.cloud.google.com/cloud-resource-manager,选择您在第 2 步中创建的项目,然后选择“删除”。如果删除项目,则需要在 Cloud SDK 中更改项目。您可以通过运行 gcloud projects list 来查看所有可用项目的列表。