跳转至

通过 Pub/Sub 接入 Google Cloud 日志

使用 Cloud Logging Sink 筛选应用日志,再通过 Pub/Sub、Cloud Run 和 DataKit 发送到观测云。本文使用部署在私网主机上的独立 DataKit,不需要 GKE。转发器支持 jsonPayload 和 textPayload,不支持审计日志 protoPayload。

Cloud Logging Sink → Pub/Sub → Cloud Run 转发器 → 私网 DataKit → 观测云。

转发器源码和 README 已公开,采用 Apache License 2.0 许可。DataWay 地址和 token 只保存在 DataKit 中,转发器不需要这些凭据。

准备工作

  • 使用 Cloud Shell,或安装 gcloud 和 Git 并登录 Google Cloud。项目需要启用结算。
  • 当前账号需要启用 API、创建服务账号、读写日志、管理 Sink、Pub/Sub 和防火墙规则、部署 Cloud Run 及配置 IAM 的权限,还需要对下文创建的构建、运行和推送账号拥有 iam.serviceAccounts.actAs 权限。
  • 在 Cloud Run 所在区域准备一个 /26 或更大的 VPC 子网,确保它能通过私网访问 DataKit 主机。网络要求参见 Direct VPC egress。
  • 防火墙示例使用同一项目、同一 VPC 中的 Compute Engine 虚拟机。使用其他私网主机时,请先由网络管理员配置对应的路由和防火墙规则。

替换以下变量。所有 gcloud 命令在同一个终端执行,DataKit 的安装和配置在其主机上完成。示例在同一项目中创建新资源;请使用未占用的名称,或统一修改文中的资源名称。

PROJECT_ID="my-project"
REGION="us-central1"
NETWORK="my-vpc"
SUBNET="cloud-run-subnet"
DATAKIT_IPV4="10.0.1.10"
DATAKIT_VM="datakit-vm"
DATAKIT_ZONE="us-central1-a"

gcloud services enable run.googleapis.com pubsub.googleapis.com \
  logging.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com \
  compute.googleapis.com iam.googleapis.com --project "$PROJECT_ID"

PROJECT_NUMBER=$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')

1. 配置 DataKit

从观测云工作空间获取安装命令,在私网主机上安装 DataKit。主机需要能够访问工作空间对应的 DataWay 地址。

在 datakit.conf 中,将已有 [http_api] 的监听地址改为主机的私网 IPv4 地址:

[http_api]
  listen = "10.0.1.10:9529"

该地址应与 DATAKIT_IPV4 一致。保存后重启 DataKit。TCP 9529 仅允许 Cloud Run 子网及指定的管理来源访问,不要暴露到公网。

为 Compute Engine 虚拟机添加目标标签,并放行 Cloud Run 子网:

SUBNET_CIDR=$(gcloud compute networks subnets describe "$SUBNET" \
  --project "$PROJECT_ID" --region "$REGION" --format='value(ipCidrRange)')
gcloud compute instances add-tags "$DATAKIT_VM" \
  --project "$PROJECT_ID" --zone "$DATAKIT_ZONE" --tags pubsub-datakit
gcloud compute firewall-rules create allow-pubsub-datakit \
  --project "$PROJECT_ID" --network "$NETWORK" --direction INGRESS \
  --action ALLOW --rules tcp:9529 --target-tags pubsub-datakit \
  --source-ranges "${SUBNET_CIDR:?Subnet CIDR is required}"

如果启用了主机防火墙,也需要放行同一子网,并检查已有规则是否放行了其他来源。在 DataKit 主机上执行以下命令检查监听,请替换示例 IP。预期返回 HTTP 200;第 5 步会验证 Cloud Run 到 DataKit 的连接。

curl -i --fail --show-error --max-time 5 "http://10.0.1.10:9529/v1/ping"

转发器通过 /v1/write/logging 写入 JSON 记录,无需额外开启 logstreaming 采集器。目标地址必须位于 10.0.0.0/8、172.16.0.0/12 或 192.168.0.0/16 网段内。

2. 部署转发器

回到执行 gcloud 的终端,分别创建构建、运行和推送账号。仅向构建账号授予 roles/run.builder,并在部署时明确指定该账号。部署人员所需权限参见构建服务账号。

gcloud iam service-accounts create forwarder-build --project "$PROJECT_ID"
gcloud iam service-accounts create forwarder-run --project "$PROJECT_ID"
gcloud iam service-accounts create forwarder-push --project "$PROJECT_ID"

gcloud projects add-iam-policy-binding "$PROJECT_ID" \
  --member "serviceAccount:forwarder-build@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role roles/run.builder

git clone https://github.com/TrueWatchTech/google-cloud-log-forwarder.git
cd google-cloud-log-forwarder

cat > env.yaml <<EOF
DATAKIT_URL: "http://${DATAKIT_IPV4}:9529/v1/write/logging"
EXPECTED_PROJECT_ID: "${PROJECT_ID}"
EXPECTED_SUBSCRIPTION: "projects/${PROJECT_ID}/subscriptions/application-logs-push"
MEASUREMENT: "gcp_pubsub"
EOF

gcloud run deploy pubsub-log-forwarder \
  --source . --project "$PROJECT_ID" --region "$REGION" \
  --no-allow-unauthenticated \
  --build-service-account "projects/${PROJECT_ID}/serviceAccounts/forwarder-build@${PROJECT_ID}.iam.gserviceaccount.com" \
  --service-account "forwarder-run@${PROJECT_ID}.iam.gserviceaccount.com" \
  --network "$NETWORK" --subnet "$SUBNET" --vpc-egress private-ranges-only \
  --cpu 1 --memory 512Mi --min-instances 0 --max-instances 1 --concurrency 1 \
  --env-vars-file env.yaml

等待部署成功并显示服务 URL。如果刚授权就遇到权限错误,等待几分钟让 IAM 变更生效后,再执行部署命令。不要向 env.yaml 添加凭据。上述 CPU、内存和实例数仅作为起始配置,并非经过测量的生产最低规格,应根据实际负载测试后调整。

3. 配置认证推送

仅在此 Cloud Run 服务上授予 Invoker 权限。确保 Pub/Sub 服务代理已创建,允许它为推送账号创建 token,随后创建订阅:

SERVICE_URL=$(gcloud run services describe pubsub-log-forwarder \
  --project "$PROJECT_ID" --region "$REGION" --format='value(status.url)')

gcloud run services add-iam-policy-binding pubsub-log-forwarder \
  --project "$PROJECT_ID" --region "$REGION" \
  --member "serviceAccount:forwarder-push@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role roles/run.invoker

gcloud pubsub topics create application-logs --project "$PROJECT_ID"
gcloud beta services identity create \
  --service pubsub.googleapis.com --project "$PROJECT_ID"
gcloud iam service-accounts add-iam-policy-binding \
  "forwarder-push@${PROJECT_ID}.iam.gserviceaccount.com" \
  --project "$PROJECT_ID" \
  --member "serviceAccount:service-${PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com" \
  --role roles/iam.serviceAccountTokenCreator

gcloud pubsub subscriptions create application-logs-push \
  --project "$PROJECT_ID" --topic application-logs --ack-deadline 60 \
  --push-endpoint "${SERVICE_URL}/pubsub" \
  --push-auth-service-account "forwarder-push@${PROJECT_ID}.iam.gserviceaccount.com" \
  --push-auth-token-audience "$SERVICE_URL"

保持 payload unwrapping 关闭,转发器需要完整的 Pub/Sub JSON 消息封装。OIDC token 由 Cloud Run 校验,需保持身份认证和 Invoker IAM 检查开启。详见认证推送要求。

4. 创建测试 Sink

先只接入专用日志 ID pubsub-guide-demo,避免测试时转发无关日志:

gcloud logging sinks create application-logs \
  "pubsub.googleapis.com/projects/${PROJECT_ID}/topics/application-logs" \
  --project "$PROJECT_ID" \
  --log-filter="logName=\"projects/${PROJECT_ID}/logs/pubsub-guide-demo\" AND (jsonPayload:* OR textPayload:*)"

SINK_WRITER=$(gcloud logging sinks describe application-logs \
  --project "$PROJECT_ID" --format='value(writerIdentity)')

gcloud pubsub topics add-iam-policy-binding application-logs \
  --project "$PROJECT_ID" --member "$SINK_WRITER" --role roles/pubsub.publisher

Sink 配置完成后会发送符合条件的新日志,不会回放历史日志。先完成下面的样例验证,再选择实际应用日志。详情参见日志路由。

5. 验证日志入库

向测试日志分别写入一条 JSON 和一条纯文本日志:

TEST_ID="pubsub-$(date -u +%Y%m%dT%H%M%SZ)"
gcloud logging write pubsub-guide-demo \
  "{\"message\":\"${TEST_ID}-json\"}" \
  --payload-type=json --severity=INFO --project "$PROJECT_ID"
gcloud logging write pubsub-guide-demo "${TEST_ID}-text" \
  --payload-type=text --severity=INFO --project "$PROJECT_ID"

读取测试日志和转发器的处理记录:

gcloud logging read \
  "logName=\"projects/${PROJECT_ID}/logs/pubsub-guide-demo\" AND SEARCH(\"${TEST_ID}\")" \
  --project "$PROJECT_ID" --freshness=1h --limit=10 --format=json
gcloud logging read \
  "resource.type=\"cloud_run_revision\" AND resource.labels.service_name=\"pubsub-log-forwarder\" AND resource.labels.location=\"${REGION}\" AND jsonPayload.receipt:*" \
  --project "$PROJECT_ID" --freshness=1h --limit=20 --format=json
  1. 第一个命令应返回两条测试日志,记下各自的 insertId、timestamp 和日志内容。如果暂时没有结果,稍等片刻后重试。
  2. 在第二个命令返回的 jsonPayload 中找到对应的 google_insert_id,确认 http_status 为 204,datakit_status 为 2xx。失败时按 reason 和常见问题表排查。
  3. 在观测云的日志页面选择来源 gcp_pubsub 和覆盖测试时间的范围,按 google_insert_id 分别查找记录。
  4. 确认两条记录的消息内容正确,且 log_timestamp 与原始时间一致。JSON 日志的 message 是一个 JSON 字符串,纯文本日志保留原文。

Cloud Run 返回 204 或 DataKit 返回 2xx 不代表已经入库,需在观测云中查到两条日志。用于生产前,仍需按实际负载测试吞吐、可靠性、资源规格和应用开销。

6. 切换到实际应用日志

确认应用日志已能在 Cloud Logging 中查询到,再替换测试筛选条件。下面的 GKE 示例仅接入一个应用容器的 stdout 和 stderr。请替换集群、命名空间和容器名,只选择业务容器,排除 DataKit;k8s_container 条件也会排除 Cloud Run 转发器。

APP_FILTER=$(cat <<'EOF'
resource.type="k8s_container"
resource.labels.cluster_name="my-cluster"
resource.labels.namespace_name="my-namespace"
resource.labels.container_name="my-app"
(log_id("stdout") OR log_id("stderr"))
(jsonPayload:* OR textPayload:*)
EOF
)
gcloud logging read "$APP_FILTER" \
  --project "$PROJECT_ID" --freshness=1h --limit=5 --format=json

确认查询结果仅包含预期的应用日志。如果没有结果,先让应用产生一条日志,并在 Logs Explorer 中检查筛选条件。非 GKE 应用需改用对应的资源类型和应用标签。确认后更新 Sink:

gcloud logging sinks update application-logs \
  --project "$PROJECT_ID" --log-filter="$APP_FILTER"

更新生效后,让应用产生一条新日志,用 APP_FILTER 在 Cloud Logging 中查到它,再按第 5 步与观测云中的记录核对标识、时间和消息内容。此时 pubsub-guide-demo 测试日志已不再匹配 Sink。筛选范围始终应排除转发器和 DataKit 自身日志,避免循环转发。

字段映射

每条 Cloud Logging 日志转换为一条 DataKit 日志。默认来源为 gcp_pubsub,由 MEASUREMENT 设置。

Cloud Logging 内容 DataKit 内容
jsonPayload 或 textPayload message:紧凑 JSON 字符串或原始文本
timestamp Unix 纳秒记录时间,并在 log_timestamp 中保留原始字符串
receiveTimestamp 存在时写入 receive_timestamp
insertId 标签 google_insert_id
logName 中的项目 ID 标签 project,必须与 EXPECTED_PROJECT_ID 一致
logName 中的日志 ID 标签 log_name,经过 URL 解码
resource.type 标签 resource_type,缺失时为 unknown
severity 标签 severity 和字段 status

status 中,DEFAULT 和 INFO 转为 info,EMERGENCY 转为 emerg,其他级别转为小写。JSON 内容不会自动展开成独立字段,resource labels 也不会映射。

常见问题

现象 检查项
Cloud Run 部署失败 构建权限、子网区域和剩余地址
推送返回 401 或 403 推送服务账号、服务级 Invoker 权限、OIDC audience 和 token 创建权限;转发器返回 403 时,还需检查 reason、EXPECTED_PROJECT_ID 和 EXPECTED_SUBSCRIPTION
转发器返回 400 或 413 日志类型和大小,必需的 insertId、logName、timestamp,以及 payload unwrapping 设置
转发器返回 502 DataKit 监听地址、VPC 路由、TCP 9529 防火墙规则和 DataKit 写入响应
工作空间中没有日志 Sink 筛选条件和发布权限、订阅投递状态、DataKit 到 DataWay 的连接、工作空间和查询时间范围

推送请求体上限为 1 MiB,转换后的记录上限为 512 KiB。Pub/Sub 采用至少一次投递,重试可能产生重复记录。保留 google_insert_id 便于关联,但它不会自动去重。

未被确认的消息可能在超过订阅保留期后过期。应监控订阅积压,并按需配置死信主题,处理反复投递失败的消息。

文档评价

文档内容是否对您有帮助?