通过 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 地址:
该地址应与 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 的连接。
转发器通过 /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
- 第一个命令应返回两条测试日志,记下各自的
insertId、timestamp和日志内容。如果暂时没有结果,稍等片刻后重试。 - 在第二个命令返回的
jsonPayload中找到对应的google_insert_id,确认http_status为 204,datakit_status为 2xx。失败时按reason和常见问题表排查。 - 在观测云的日志页面选择来源
gcp_pubsub和覆盖测试时间的范围,按google_insert_id分别查找记录。 - 确认两条记录的消息内容正确,且
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:
更新生效后,让应用产生一条新日志,用 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 便于关联,但它不会自动去重。
未被确认的消息可能在超过订阅保留期后过期。应监控订阅积压,并按需配置死信主题,处理反复投递失败的消息。