Cloud Run 服务中的 gcloud 命令行教程

在本教程中,您将使用 Cloud Run 服务中的 Google Cloud CLI 创建一系列 Cloud Run 服务。您可以先将在本教程学到的知识应用到现有的 Cloud 操作脚本或构建概念验证,再使用客户端库构建更稳健的服务。

在 Web 服务中,您可以像使用任何 Shell 脚本一样使用 gcloud CLI,例如,如 Shell 快速入门中所示。在 Cloud Run 上,这两个工具均会通过自动向 Cloud Run 服务身份进行身份验证来使用 Google Cloud服务。gcloud CLI 可使用为服务身份授予的所有权限。

gcloud CLI 能够广泛用于跨 Google Cloud 收集信息和管理资源,使得在 Web 服务中使用它的挑战可将调用者误用这些功能的风险降至最低。如果没有安全控制,则通过允许无意或有意的恶意活动会对在同一项目中运行的其他服务或资源造成风险。这些风险的示例包括:

  • 允许发现专用虚拟机的 IP 地址
  • 从同一项目的数据库启用对私有数据的访问
  • 允许删除其他正在运行的服务

本教程中的几个步骤展示了如何实施控制以最大限度地降低风险,例如指定要在代码中运行的 gcloud 命令,而不是打开以允许用户输入。

在 Cloud Run 服务中,使用命令行工具编写脚本类似于在本地使用命令行。主要区别在于应该围绕主要脚本逻辑添加的附加限制。

设置 gcloud 默认值

要配置您的 Cloud Run 服务的 gcloud 默认值,请执行以下操作:

  1. 设置默认项目:

    gcloud config set project PROJECT_ID

    PROJECT_ID 替换为您在本教程中创建的项目的名称。

  2. 为您选择的区域配置 gcloud:

    gcloud config set run/region REGION

    REGION 替换为您选择的受支持的 Cloud Run 区域

Cloud Run 位置

Cloud Run 是区域级的,这意味着运行 Cloud Run 服务的基础架构位于特定区域,并且由 Google 代管,以便在该区域内的所有可用区以冗余方式提供。

选择用于运行 Cloud Run 服务的区域时,主要考虑该区域能否满足您的延迟时间、可用性或耐用性要求。通常,您可以选择距离用户最近的区域,但除此之外,您还应该考虑 Cloud Run 服务使用的其他 Google Cloud产品的位置。跨多个位置使用 Google Cloud 产品可能会影响服务的延迟时间和费用。

Cloud Run 可在以下区域使用:

基于层级 1 价格

基于层级 2 价格

  • africa-south1(约翰内斯堡)
  • asia-east2(香港)
  • asia-northeast3(韩国首尔)
  • asia-southeast1(新加坡)
  • asia-southeast2 (雅加达)
  • asia-south2(印度德里)
  • australia-southeast1(悉尼)
  • australia-southeast2(墨尔本)
  • europe-central2(波兰,华沙)
  • europe-west10(柏林)
  • europe-west12(都灵)
  • europe-west2(英国伦敦) 叶形图标 二氧化碳排放量低
  • europe-west3(德国法兰克福)
  • europe-west6(瑞士苏黎世) 叶形图标 二氧化碳排放量低
  • me-central1(多哈)
  • me-central2(达曼)
  • northamerica-northeast1(蒙特利尔) 叶形图标 二氧化碳排放量低
  • northamerica-northeast2(多伦多) 叶形图标 二氧化碳排放量低
  • southamerica-east1(巴西圣保罗) 叶形图标 二氧化碳排放量低
  • southamerica-west1(智利圣地亚哥) 叶形图标 二氧化碳排放量低
  • us-west2(洛杉矶)
  • us-west3(盐湖城)
  • us-west4(拉斯维加斯)

如果您已创建 Cloud Run 服务,可在Google Cloud 控制台的 Cloud Run 信息中心内查看区域。

检索代码示例

如需检索可用的代码示例,请执行以下操作:

  1. 将示例应用代码库克隆到本地机器:

    git clone https://github.com/GoogleCloudPlatform/cloud-run-samples.git

    或者,您也可以下载该示例的 zip 文件并将其解压缩。

  2. 切换到包含 Cloud Run 示例代码的目录:

    cd cloud-run-samples/gcloud-report/

查看代码

本部分包含有关您检索到的代码示例的信息。

生成报告并将其上传到 Cloud Storage

此 Shell 脚本会在当前项目和区域中生成 Cloud Run 服务报告,并将结果上传到 Cloud Storage。它列出名称包含提供的字符串 search 参数的服务。

该脚本使用 gcloud run services list 命令、gcloud 高级格式选项gcloud 流式传输复制模式。

set -eo pipefail

# Check for required environment variables.
requireEnv() {
  test "${!1}" || (echo "gcloud-report: '$1' not found" >&2 && exit 1)
}
requireEnv GCLOUD_REPORT_BUCKET

# Prepare formatting: Default search term to include all services.
search=${1:-'.'}
limits='spec.template.spec.containers.resources.limits.flatten("", "", " ")'
format='table[box, title="Cloud Run Services"](name,status.url,metadata.annotations.[serving.knative.dev/creator],'${limits}')'

# Create a specific object name that will not be overridden in the future.
obj="gs://${GCLOUD_REPORT_BUCKET}/report-${search}-$(date +%s).txt"

# Write a report containing the service name, service URL, service account or user that
# deployed it, and any explicitly configured service "limits" such as CPU or Memory.
gcloud run services list \
  --format "${format}" \
  --filter "metadata.name~${search}" | gsutil -q cp -J - "${obj}"

# /dev/stderr is sent to Cloud Logging.
echo "gcloud-report: wrote to ${obj}" >&2
echo "Wrote report to ${obj}"

此脚本可以安全地作为服务运行,因为重复调用它会更新报告,而不会进一步增加代价高昂的流失。使用 gcloud CLI 的其他脚本在重复调用时费用可能较高(例如创建新的 Cloud 资源或执行费用高昂的任务)。幂等脚本会在重复调用中产生相同的结果,因此可以更安全地作为服务运行。

在 HTTP 请求中调用脚本

此 Go 代码将创建一个 Web 服务,该服务运行 shell 脚本以生成报告。由于搜索查询是用户输入,因此代码会进行验证以确保它仅包含字母、数字或连字符,以防止将恶意命令作为输入。这组字符集的范围足够小,可防止命令注入攻击

Web 服务将搜索参数作为参数传递给 Shell 脚本。


// Service gcloud-report is a Cloud Run shell-script-as-a-service.
package main

import (
	"log"
	"net/http"
	"os"
	"os/exec"
	"regexp"
)

func main() {
	http.HandleFunc("/", scriptHandler)

	// Determine port for HTTP service.
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
		log.Printf("defaulting to port %s", port)
	}

	// Start HTTP server.
	log.Printf("listening on port %s", port)
	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatal(err)
	}
}

func scriptHandler(w http.ResponseWriter, r *http.Request) {
	search := r.URL.Query().Get("search")
	re := regexp.MustCompile(`^[a-z]+[a-z0-9\-]*$`)
	if !re.MatchString(search) {
		log.Printf("invalid search criteria %q, using default", search)
		search = "."
	}

	cmd := exec.CommandContext(r.Context(), "/bin/bash", "script.sh", search)
	cmd.Stderr = os.Stderr
	out, err := cmd.Output()
	if err != nil {
		log.Printf("Command.Output: %v", err)
		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		return
	}
	w.Write(out)
}

go.mod 文件会在 go 模块中声明应用依赖项:

module github.com/GoogleCloudPlatform/cloud-run-samples/gcloud-report

go 1.19

定义容器环境

Dockerfile 定义如何合并环境以用于服务。它与 helloworld-shell 快速入门中的 Dockerfile 类似,不同之处在于最终容器映像基于 gcloud Google Cloud CLI 映像。这样,您的服务便可以使用 gcloud,而无需执行 Google Cloud CLI 的自定义安装和配置步骤。


# Use the official golang image to create a binary.
# This is based on Debian and sets the GOPATH to /go.
# https://hub.docker.com/_/golang
FROM golang:1.20-buster as builder

# Create and change to the app directory.
WORKDIR /app

# Retrieve application dependencies.
# This allows the container build to reuse cached dependencies.
# Expecting to copy go.mod and if present go.sum.
COPY go.* ./
RUN go mod download

# Copy local code to the container image.
COPY invoke.go ./

# Build the binary.
RUN go build -mod=readonly -v -o server

# Use a gcloud image based on debian:buster-slim for a lean production container.
# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
FROM gcr.io/google.com/cloudsdktool/cloud-sdk:slim

WORKDIR /app

# Copy the binary to the production image from the builder stage.
COPY --from=builder /app/server /app/server
COPY *.sh /app/
RUN chmod +x /app/*.sh

# Run the web service on container startup.
CMD ["/app/server"]

创建 Artifact Registry 标准制品库

创建 Artifact Registry 标准制品库以存储您的容器映像:

gcloud artifacts repositories create REPOSITORY \
    --repository-format=docker \
    --location=REGION

您需要进行如下替换:

  • REPOSITORY 替换为制品库的唯一名称。
  • REGION 替换为 Artifact Registry 的 Google Cloud 区域。

设置 Cloud Storage 存储桶

创建一个 Cloud Storage 存储桶以上传报告:

gcloud storage buckets create gs://REPORT_ARCHIVE_BUCKET

REPORT_ARCHIVE_BUCKET 替换为全局唯一的存储桶名称。

设置服务身份

为了限制服务对其他基础架构所拥有的权限,您需要创建服务身份并自定义执行工作所需的特定 IAM 权限。

在本示例中,必需的权限是读取 Cloud Run 服务以及读取和写入 Cloud Storage 存储桶的权限。

  1. 创建服务账号:

    gcloud iam service-accounts create gcloud-report-identity

  2. 向服务账号授予读取 Cloud Run 服务的权限:

    gcloud projects add-iam-policy-binding PROJECT_ID \
      --member=serviceAccount:gcloud-report-identity@PROJECT_ID.iam.gserviceaccount.com \
      --role roles/run.viewer
  3. 向服务账号授予读取和写入 Cloud Storage 存储桶的权限:

    gcloud storage buckets add-iam-policy-binding gs://REPORT_ARCHIVE_BUCKET \
      --member=serviceAccount:gcloud-report-identity@PROJECT_ID.iam.gserviceaccount.com \
      --role=roles/storage.objectUser

此自定义服务身份的有限访问权限不允许服务访问其他 Google Cloud 资源。

交付服务

发布代码包含三个步骤:

  • 使用 Cloud Build 构建容器映像
  • 将容器映像上传到 Artifact Registry
  • 将容器映像部署到 Cloud Run。

如需交付代码,请执行以下操作:

  1. 构建容器并将其发布到 Artifact Registry 上:

    gcloud builds submit --tag REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/gcloud-report

    您需要进行如下替换:

    • PROJECT_ID 替换为您的 Google Cloud 项目 ID。
    • REPOSITORY 替换为 Artifact Registry 制品库的名称。
    • REGION 替换为 Artifact Registry 的 Google Cloud 区域。

    gcloud-report 是服务的名称。

    成功完成后,“成功”消息会显示 ID、创建时间和映像名称。该映像存储在 Artifact Registry 中,并可根据需要重复使用。

  2. 运行以下命令来部署您的服务:

    gcloud run deploy gcloud-report \
       --image REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY/gcloud-report \
       --update-env-vars GCLOUD_REPORT_BUCKET=REPORT_ARCHIVE_BUCKET \
       --service-account gcloud-report-identity \
       --no-allow-unauthenticated

    您需要进行如下替换:

    • PROJECT_ID 替换为您的 Google Cloud 项目 ID。
    • REPOSITORY 替换为 Artifact Registry 制品库的名称。
    • REGION 替换为服务的 Google Cloud 区域。

    gcloud-report 是容器名称和服务名称的一部分。容器映像会部署到您之前在设置 gcloud 下配置的服务和区域 (Cloud Run)。

    --no-allow-unauthenticated 标志限制对服务的公开访问权限。通过将该服务保留为不公开状态,您可以依赖 Cloud Run 的内置身份验证来阻止未经授权的请求。如需详细了解基于 Identity and Access Management (IAM) 的身份验证,请参阅使用 IAM 管理访问权限

    等待部署完成。此过程可能需要半分钟左右的时间。成功完成时,命令行会显示服务网址。

  3. 如果要将代码更新部署到该服务,请重复执行上述步骤。向服务执行的每次部署都会创建一个新的修订版本,该修订版本准备就绪后会自动开始处理流量。

如需了解如何授予 Google Cloud 用户调用此服务的权限,请参阅使用 IAM 管理访问权限。项目编辑者和所有者自动拥有此权限。

生成报告

如需生成 Cloud Run 服务的报告,请执行以下操作:

  1. 使用 curl 发送经过身份验证的请求:

    curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" SERVICE_URL

    完成部署后,将 SERVICE_URL 替换为 Cloud Run 提供的网址。

    如果您创建了新项目并按照本教程进行操作,输出将类似于以下内容:

    Wrote report to gs://REPORT_ARCHIVE_BUCKET/report-.-DATE.txt

    文件名中的 . 是源代码中包含的默认搜索参数。

    如需使用搜索功能,请在请求中添加 search 参数:

    curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" SERVICE_URL?search=gcloud

    此查询将返回如下所示的输出:

    Wrote report to gs://REPORT_ARCHIVE_BUCKET/report-gcloud-DATE.txt
  2. 在本地使用 gcloud CLI 检索文件:

    gcloud storage cp gs://REPORT_FILE_NAME .

    此命令中的 . 表示当前工作目录。

    REPORT_FILE_NAME 替换为上一步中的 Cloud Storage 对象名称输出。

打开该文件即可查看报告。该属性应如下所示:

项目中 Cloud Run 服务列表的屏幕截图,其中包含四个服务特性的列。
会从服务说明中拉取这四个列。这些列包括服务的名称、在首次部署时分配的网址、服务的初始创建者以及服务的最大 CPU 和内存限制

提高稳健性以满足未来需要

如果您打算进一步开发此服务,请考虑采用更为完善的编程语言重写并使用 Cloud Run Admin APICloud Storage 客户端库

您可以通过将 --log-http 添加到 gcloud CLI 命令来检查要进行的 API 调用(以及查看某些身份验证详细信息)。

自动执行此操作

现在,Cloud Run 服务报告可由 HTTP 请求触发,您可以在需要时使用自动化功能自动生成: