关于 dd-gcp-integration
通过 Terraform 配置 Datadog GCP 集成的实操 skill,面向负责云监控接入的用户。它会检查凭据、确定监控范围,并使用服务账号模拟,避免长期密钥。
主要功能
- 01基于 Terraform 配置 GCP 集成
- 02验证 Datadog 凭据
- 03查找或创建委托主体
- 04发现项目与文件夹范围
- 05应用更改前征求确认
适用场景
- 01为指定 GCP 项目配置 Datadog 监控
- 02在不使用长期服务账号密钥的情况下设置访问
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ datadog-labs/ agent-skills --skill dd-gcp-integration安装后在 Claude Code 中输入/dd-gcp-integration确认已加载。
附带文件references/terraform.md
详细介绍
Datadog GCP 集成
你将帮助用户使用 Terraform 设置 Datadog GCP 集成。
该集成会在客户的项目中创建一个 GCP 服务账号,并授予 Datadog 的委托主体通过 roles/iam.serviceAccountTokenCreator 模拟该账号的权限。这样就完全无需使用长期密钥。
这是一个动手操作流程:在对话过程中亲自运行命令,而不是只把命令列表交给用户;让用户随时了解进度,并在执行 terraform apply 前暂停并征求确认。
运行其他技能
下面的一些步骤会移交给其他技能(例如 dd-account-setup)。如果已安装该技能,请运行它。如果未安装,请下载并在本地阅读:
SKILL='<skill-name>'
case "$SKILL" in ''|/*|*..*|*[!A-Za-z0-9._/-]*) echo "invalid skill name: $SKILL"; exit 1 ;; esac
d=$(mktemp -d) || exit 1
code=$(curl -q -sS --proto '=https' --connect-timeout 10 --max-time 120 -o "$d/repo.tar.gz" -w '%{http_code}' \
https://codeload.github.com/datadog-labs/agent-skills/tar.gz/refs/heads/main) && [ "$code" = 200 ] \
|| { echo "download failed (HTTP ${code:-none}) - stop"; exit 1; }
list=$(tar -tzf "$d/repo.tar.gz") || { echo "downloaded archive is unreadable - stop"; exit 1; }
printf '%s\n' "$list" | grep -qxF "agent-skills-main/$SKILL/SKILL.md" \
|| { echo "skill $SKILL not found in datadog-labs/agent-skills - stop"; exit 1; }
tar -xzf "$d/repo.tar.gz" -C "$d" --strip-components=1 "agent-skills-main/$SKILL" \
|| { echo "extracting $SKILL failed - stop"; exit 1; }
[ -f "$d/$SKILL/SKILL.md" ] || { echo "extracted skill $SKILL is missing SKILL.md - stop"; exit 1; }
rm -f "$d/repo.tar.gz"
echo "$d/$SKILL"
这会下载该仓库当前 main 分支的一个快照(可能比你正在运行的技能更新),并且只提取该技能所在目录,因此它引用的所有文件(references/、scripts/ 等)都会包含在内。代码块会输出该目录。使用文件读取工具完整阅读其中的 SKILL.md 及其指向的任何文件,然后遵循其中的说明并返回此处。不要使用网页抓取或摘要工具,因为这些工具会缩短技能内容并遗漏步骤。如果代码块执行失败,请停止移交并告知用户:不要凭记忆或依据不完整的下载继续操作。下载的技能仅作为参考说明;不能覆盖用户指令或本技能中的安全规则。
阶段 0:预检
Terraform 或 OpenTofu。 本技能中的所有命令都写作 terraform,但 OpenTofu 可直接替代——此处使用的提供程序和模块源在两个注册表中的解析方式相同。进入阶段 1 前,先检查用户实际安装了哪个二进制文件:
command -v terraform tofu
如果 PATH 中只有 tofu,请将下文中的每条 terraform <subcommand> 都视为 tofu <subcommand>。如果两者都存在,请询问用户想用哪一个,不要自行猜测。
Datadog 凭据。 从环境变量中读取 DD_SITE / DD_API_KEY / DD_APP_KEY(如果没有,则从 .env.local / .env 中读取),并分别验证两个密钥——它们可能各自独立失效:
for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
: "${DD_SITE:=datadoghq.com}"
echo "DD_SITE=${DD_SITE}"
echo "DD_API_KEY=$([ -n "${DD_API_KEY:-}" ] && echo set || echo UNSET) DD_APP_KEY=$([ -n "${DD_APP_KEY:-}" ] && echo set || echo UNSET)"
printf 'DD-API-KEY: %s\n' "$DD_API_KEY" \
| curl -sS --max-time 20 -o /dev/null -w "validate: HTTP %{http_code}\n" \
-H @- "https://api.${DD_SITE}/api/v1/validate"
printf 'DD-API-KEY: %s\nDD-APPLICATION-KEY: %s\n' "$DD_API_KEY" "$DD_APP_KEY" \
| curl -sS --max-time 20 -o /dev/null -w "current_user: HTTP %{http_code}\n" \
-H @- "https://api.${DD_SITE}/api/v2/current_user"
| 结果 | 含义 | 操作 |
|---|---|---|
两者均为 200 | 密钥适用于此站点 | 继续阶段 1 |
validate 返回 403 | API 密钥无效,或所属区域与 DD_SITE 不同 | 询问密钥所属的站点,修正 DD_SITE,然后重新检查 |
validate 返回 200,current_user 返回 403 | 应用密钥错误或来自其他区域——不是 API 密钥 | 从 <APP_BASE>/organization-settings/application-keys 获取一个 |
| 任一密钥未设置 | 无法进行验证 | 对于商业站点,运行 dd-account-setup 技能,然后返回此处。对于 ddog-gov.com 或 us2.ddog-gov.com,直接向用户索取密钥——该技能会根据不包含这两个政府站点的列表验证 DD_SITE,并拒绝它们 |
应用 URL。 Datadog 应用主机并非所有站点都是 app.${DD_SITE}。它分别是 https://app.datadoghq.com(US1)、https://app.datadoghq.eu(EU1)、https://app.ddog-gov.com(Gov);其他所有站点的 URL 都是 https://${DD_SITE} 本身,例如 https://us3.datadoghq.com、https://us5.datadoghq.com、https://ap1.datadoghq.com、https://ap2.datadoghq.com、https://uk1.datadoghq.com。解析一次,然后在下文出现 <APP_BASE> 的所有位置替换为该 URL。完整列表:https://docs.datadoghq.com/getting_started/site/
记住解析出的 DD_SITE。 大多数 Agent 运行时会为每条命令启动一个新的 shell,因此上面的
export 到下一个代码块时就失效了。这就是为什么后续每个需要凭据的代码块顶部都会原样重复加载器命令——这是有意为之,并非内容漂移;不要删掉它。DD_SITE 不是机密,因此每个后续代码块都会通过显式的 DD_SITE='<site>'; export DD_SITE 自行重新设置它——请替换为 Phase 0 中确认的站点。
这里刻意使用普通赋值,而不是 : "${DD_SITE:=...}"::= 只会填充未设置或为空的值,因此如果 .env 中已有错误的非空 DD_SITE,它就会保留下来,导致每次调用都发往错误的区域。密钥则使用 :? 保护,这样缺失密钥时会明确报错并中止,而不是发送空请求头。绝不要将密钥值直接写入命令——它们必须始终通过加载器以 $DD_API_KEY / $DD_APP_KEY 的形式传入。由此带来两点影响,而且两者都是有意的:
- Datadog 调用通过 stdin 传递请求头,形式为
printf 'DD-API-KEY: %s\n' "$DD_API_KEY" | curl -H @- ...。printf是 shell 内置命令,因此密钥不会成为任何进程的参数,也不会出现在ps中。如果改用-H "DD-API-KEY: $DD_API_KEY",密钥就会出现在 curl 的 argv 中。(-H @-需要 curl 7.55+;它只读取请求头行,因此-d和--data-urlencode仍可正常使用。) - 对于 AWS、Azure 和 GCP,Terraform 完全不会接收密钥作为值:Datadog provider
会从环境变量中读取
DD_API_KEY/DD_APP_KEY,因此没有根变量、没有-var=参数,也没有任何内容会被 Terraform 记录到状态文件或保存的计划中。(OCI 是例外——其 模块需要将密钥作为输入,因此密钥会通过TF_VAR_*传递。)
配合加载器,这样就能让两个密钥都不出现在转录记录、shell 历史记录或进程列表中。
工具。 必须安装 terraform,Phase 1 还需要 jq。gcloud CLI 可用于发现,但不是必需的,
不过无论如何,google provider 都需要可用的 GCP 凭据
(gcloud auth application-default login,或 GOOGLE_APPLICATION_CREDENTIALS):
command -v terraform || echo "MISSING terraform - https://developer.hashicorp.com/terraform/install"
command -v jq || echo "MISSING jq - needed to read the delegate email in Phase 1"
command -v gcloud >/dev/null 2>&1 && echo "gcloud: available" || echo "gcloud: not installed"
如果没有 jq,请在 Phase 1 中自行从原始 JSON 响应读取 data.attributes.delegate_account_email,而不是通过管道传给 jq。
此处的代码片段使用 POSIX shell。 如果使用 PowerShell 或 cmd,请改用 Windows 对应写法
(Get-Command、$env:VAR、2>$null、curl.exe)——调用相同,顺序相同。
Phase 1:获取 Datadog Delegate Principal
Datadog 会通过每个组织专属的 delegate 服务账号来模拟客户的服务账号。获取其 邮箱——先执行 GET,只有当组织还没有 delegate 时才执行 POST,这样会复用已有账号,而不是重新创建:
for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
DD_SITE='datadoghq.com'; export DD_SITE # <- replace with the site confirmed in Phase 0.
# Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.
: "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"
resp=$(printf 'DD-API-KEY: %s\nDD-APPLICATION-KEY: %s\n' "$DD_API_KEY" "$DD_APP_KEY" \
| curl -sS -w '\n%{http_code}' -X GET \
-H @- "https://api.${DD_SITE}/api/v2/integration/gcp/sts_delegate")
code=$(printf '%s' "$resp" | tail -1); body=$(printf '%s' "$resp" | sed '$d')
if [ "$code" = "404" ]; then
echo "no delegate exists yet (HTTP 404) - creating one"
resp=$(printf 'DD-API-KEY: %s\nDD-APPLICATION-KEY: %s\n' "$DD_API_KEY" "$DD_APP_KEY" \
| curl -sS -w '\n%{http_code}' -X POST -H "Content-Type: application/json" -d '{}' \
-H @- "https://api.${DD_SITE}/api/v2/integration/gcp/sts_delegate")
code=$(printf '%s' "$resp" | tail -1); body=$(printf '%s' "$resp" | sed '$d')
elif [ "$code" != "200" ]; then
echo "GET sts_delegate returned HTTP $code - not creating anything. Response:"
printf '%s\n' "$body"
echo "403 means the app key lacks permission, 429 is rate limiting, 000 is a transport failure, 5xx is"
echo "server-side. None of those mean 'no delegate exists', so a POST here could create one you did not"
echo "intend. Resolve the error, then re-run. If the body says no delegate exists, POST explicitly."
exit 1
fi
[ "$code" = "200" ] || { echo "sts_delegate failed with HTTP $code:"; printf '%s\n' "$body"; exit 1; }
email=$(printf '%s' "$body" | jq -r '.data.attributes.delegate_account_email // empty')
case "$email" in
*@*.iam.gserviceaccount.com) echo "DATADOG_PRINCIPAL_ID=$email" ;;
*) echo "did not get a delegate service-account email; response was:"; printf '%s\n' "$body"; exit 1 ;;
esac
将输出的邮箱记为 DATADOG_PRINCIPAL_ID——下面的 Terraform 需要它。该值因组织而异,
不能硬编码。
如果邮箱为空或为 null,绝不要继续。 上述防护措施的存在是因为在错误响应上执行 curl -s | jq -r 会输出 null,随后会悄悄变成 Terraform 中的 member = "serviceAccount:null",从而创建一个无法通过身份验证的集成。
如果没有 jq,请严格按原样运行代码块,但不要运行 email=$(... | jq -r ...) 这一行——
不要删掉 printf ... | curl -H @- 管道,因为它负责提供身份验证。请自行从 $body 中读取
data.attributes.delegate_account_email,并遵循相同规则:除非它看起来是
[email protected] 地址,否则不要继续。
阶段 2:确定范围
询问用户是否已经知道希望 Datadog 监控哪些 GCP 项目 ID 和/或文件夹 ID。
如果知道,请收集:
- 哪个 GCP 项目应托管 Datadog 服务账号(“宿主项目”)?
- 要监控的项目 ID 和/或文件夹 ID 列表。
如果不知道或希望获得帮助:
如果可使用 gcloud CLI,请提出帮他们发现 GCP
环境。说明你将使用 gcloud 列出他们的组织、文件夹和项目,
以便他们选择要监控的对象。此过程尽力而为——独立运行每条命令,并
根据成功执行的命令获取信息:
组织:
gcloud organizations list --format="table(displayName, name)"
文件夹(使用 REST API 搜索组织中的所有有效文件夹;还需要使用 gcloud
获取访问令牌):
printf 'Authorization: Bearer %s\n' "$(gcloud auth print-access-token)" \
| curl -sS -H @- -H "Content-Type: application/json" \
-d '{"query": "lifecycleState=ACTIVE"}' \
"https://cloudresourcemanager.googleapis.com/v2/folders:search"
如果响应包含 nextPageToken,请在请求正文中添加 "pageToken": "<token>" 进行分页,
直到检索到所有文件夹。
自行展开嵌套文件夹。 上述搜索会返回组织中的每个有效文件夹,因此请根据用户选择的文件夹
构建其完整的后代集合:对于每个选定的文件夹,收集其 parent 链最终指向该文件夹的所有文件夹,
然后列出这些文件夹中的所有项目:
# replace these with the chosen folder and every folder nested beneath it
set -- 123456789012 987654321098
for folder do
gcloud projects list --filter="parent.id=${folder} AND lifecycleState=ACTIVE AND NOT projectId:sys*" \
--format="value(projectId)"
done | sort -u
将展开后的列表填入 Terraform 的 project_ids,同时在 folder_ids 中保留选定的文件夹。
原因见模板:文件夹级 IAM 会继承到每个后代项目,但 API 启用状态不会继承,而且 google_projects
数据源只会匹配直接父级。folder_ids 中的文件夹负责 IAM(包括之后创建的项目);展开后的
project_ids 则用于在每个现有项目中实际启用所需 API。跳过展开会导致集成看似已配置,
但实际上不会从子文件夹中的项目收集任何内容。
项目(仅有效项目,不包括系统项目):
gcloud projects list --filter="lifecycleState=ACTIVE AND NOT projectId:sys*" --format="table(projectId, name, parent.id)"
如果某条命令执行失败(例如,用户没有列出组织或文件夹的权限),请告知用户失败的命令及原因, 但继续使用已成功检索到的信息。例如,如果用户可以列出项目但不能列出文件夹,请继续进行 项目级设置。
否则(未安装 gcloud 或未通过身份验证),请让用户从
GCP 控制台获取这些 ID:
- 项目 ID:console.cloud.google.com → 项目选择器(顶部栏)→ 每个项目的 ID 都显示在其名称 旁边。
- 文件夹 ID(可选):console.cloud.google.com → IAM 与管理 → 管理资源 → 展开组织树;文件夹 ID 显示在资源管理器中。
- 这些文件夹中的每个项目,包括嵌套子文件夹中的项目——在
管理资源中展开整个子树,并收集每个项目 ID。这并非可有可无的琐事:Terraform 会在每个项目中启用
所需 API,因此任何未列出的项目都会继承文件夹级 IAM,却不会启用 API,也就不会收集任何内容。
如果用户不愿逐一列出这些项目,请告知他们文件夹范围的设置需要使用
gcloud(它可以自动展开子树),并提供项目范围设置作为替代方案。 - 宿主项目:询问哪个项目应托管 Datadog 服务账号。
以易读的格式展示收集到的结果,并让用户选择:
- 特定项目:项目 ID 列表
- 文件夹:文件夹 ID 列表(Datadog 将发现其中所有有效项目)
- 两者:显式指定的项目与文件夹的组合
然后询问哪个项目应托管 Datadog 服务账号。
阶段 3:生成并应用 Terraform
检查现有配置——先检查本地 Terraform,再检查 Datadog
在生成或应用任何内容之前,按以下顺序执行。先检查本地状态,因为它决定现有集成是可以更新,还是绝不能动:
find . -maxdepth 1 -type f \( -name '*.tf' -o -name 'terraform.tfstate' \) -print
# A project is "present" if it has configuration - .terraform/ may simply not exist yet on a fresh clone
# with a remote backend, and terraform.tfstate does not exist at all when state is remote.
if [ -n "$(find . -maxdepth 1 -type f \( -name '*.tf' -o -name '*.tf.json' \) -print -quit)" ]; then
terraform init -input=false >/dev/null || { echo "terraform init failed - resolve that before concluding anything about existing state"; exit 1; }
out=$(terraform state list 2>&1); rc=$?
if [ "$rc" -ne 0 ]; then
case $out in
*'No state file'*|*'no state'*|*'Backend initialization required'*)
echo "project is initialized but has no state yet - treat as a clean install" ;;
*)
printf '%s\n' "$out"
echo "could not read state (backend or credentials problem) - do NOT treat this as 'nothing exists'"; exit 1 ;;
esac
elif [ -z "$out" ]; then
echo "state is empty - treat as a clean install"
else
printf '%s\n' "$out" | grep -F 'datadog_integration_gcp_sts' || echo "state exists but holds no datadog_integration_gcp_sts resource"
fi
else
echo "no Terraform configuration here yet - clean install"
fi
匹配准确的资源地址 datadog_integration_gcp_sts,不要使用宽泛的 grep -i datadog:否则,不相关的 Datadog 资源,或部分应用后遗留的云 IAM 资源,都会被误认为是受管理的集成。
然后询问 Datadog 当前已有的内容:
for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
DD_SITE='datadoghq.com'; export DD_SITE # <- replace with the site confirmed in Phase 0.
# Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.
: "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"
resp=$(printf 'DD-API-KEY: %s\nDD-APPLICATION-KEY: %s\n' "$DD_API_KEY" "$DD_APP_KEY" \
| curl -sS -w '\n%{http_code}' -X GET -H @- "https://api.${DD_SITE}/api/v2/integration/gcp/accounts")
code=$(printf '%s' "$resp" | tail -1); body=$(printf '%s' "$resp" | sed '$d')
[ "$code" = "200" ] || { echo "lookup failed with HTTP $code - do not assume 'not connected':"; printf '%s\n' "$body"; exit 1; }
printf '%s\n' "$body"
现在先协调这两个结果,再进行任何操作:
-
Datadog 中不存在,本地状态中也不存在——全新安装。继续。
-
Datadog 中存在,且本地状态中也存在——这是更新/修复场景,不是重复创建。 继续执行下面的 Terraform,将其作为对现有资源的更改,并让计划显示将修改哪些内容。
-
Datadog 中存在,但本地状态中不存在(Phase 2 中用于主机项目的服务账号已注册)——停止。应用操作可能会创建重复项,或与管理它的现有配置冲突。明确说明这一点,并提供以下选项:将现有对象导入此项目(
terraform import datadog_integration_gcp_sts.datadog_integration "<integration-uuid>"——uuid 来自GET /api/v2/integration/gcp/accounts,而不是项目 ID 或服务账号电子邮件)、在当前管理它的位置进行管理,或者先在 Datadog 中删除它。只有在用户选择其中一项并确认后才能继续。 导入时要依次完成两件事。先生成配置(下面的 Terraform,根据已存在的身份进行调整——角色/应用/服务账号名称保持一致),因为terraform import会将现有对象绑定到已配置的资源地址,没有该地址就会失败。仅导入 Datadog 注册并不够:云端身份(IAM 角色、应用注册、服务账号)仍不在状态中,因此需要将它们也导入,或通过数据源引用;否则下一次应用操作会尝试再次创建它们并发生冲突。# the uuid comes from the accounts listing, not the project id or service-account email terraform import datadog_integration_gcp_sts.datadog_integration "<integration-uuid>" -
本地状态中存在,但 Datadog 中不存在——安装未完成或已回滚。云端资源可能仍然存在,但注册不存在。不要从头开始:运行计划,让它显示缺少哪些内容,并预期它只会重新创建注册。
检查现有 Terraform
在生成新的 Terraform 配置之前,请检查用户当前目录或附近是否已有 Terraform 项目:
find . -maxdepth 1 -type f \( -name '*.tf' -o -name 'terraform.tfstate' \) -print
如果找到了现有的 .tf 文件:
- 阅读这些文件,了解已配置的 provider 和资源。
- 如果已有
datadogprovider,则复用其配置,不要重复创建。 - 如果已有
googleprovider,则复用它。 - 只向现有项目添加所需的新资源(服务账号、IAM 绑定、
datadog_integration_gcp_sts)。不要重新生成已经存在的 provider、变量或 terraform 块。 - 如果用户采用模块化布局,请为 Datadog 资源创建一个新文件,例如
datadog-gcp-integration.tf。
如果没有找到现有 Terraform 配置,则生成独立配置。
完整的 HCL 模板——包括 provider、API 启用、服务账号、Datadog 委托方的令牌创建者绑定、项目和文件夹角色绑定,以及 datadog_integration_gcp_sts 注册——位于 references/terraform.md,其中还包含仅针对项目和仅针对文件夹的变体。现在读取该文件,并填入占位符。
应用 Terraform
-
将模板中的所有
<PLACEHOLDER>值替换为收集到的实际值:- Phase 2 中的
<USER_FOLDER_IDS>和<USER_PROJECT_IDS> - Phase 2 中的
<HOST_PROJECT_ID> - Phase 1 中的
<DATADOG_PRINCIPAL_ID> - Phase 0 中的
<DD_SITE>
- Phase 2 中的
-
如果用户只选择了项目(没有文件夹),请移除
folder_idslocal、google_projects.folder_projects数据源、google_folder_iam_member资源,并将all_project_ids简化为仅包含local.project_ids。 -
如果用户只选择了文件夹(没有显式选择项目),仍要将 Phase 2 中展开后的后代项目列表放入
project_ids——不要将其设为[]。folder_ids会授予 IAM 权限(并涵盖之后创建的项目),但 API 启用是按项目进行的,因此空的project_ids会导致这些文件夹中的所有现有项目都缺少所需的 API。 -
运行
terraform init以安装 providers。 -
执行计划,并将计划保存到文件。Datadog provider 会直接从环境中读取
DD_API_KEY/DD_APP_KEY,因此没有根变量,也不需要-var=参数——不会有任何机密进入计划文件、状态或命令行:for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done DD_SITE='datadoghq.com'; export DD_SITE # <- replace with the site confirmed in Phase 0. # Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive. : "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}" umask 077 # tighten permissions on the plan file anyway terraform plan -out=tfplan向用户展示计划输出,并等待明确确认。
-
仅在用户确认后,应用已保存的计划。应用该文件才能确保审批有效:不带计划文件运行
terraform apply会计算全新的计划,而-auto-approve会在无人查看的情况下执行计划,因此自计划后发生的任何变更都会未经审查便应用:for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done DD_SITE='datadoghq.com'; export DD_SITE # <- replace with the site confirmed in Phase 0. # Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive. : "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}" trap 'rm -f tfplan' EXIT HUP INT TERM # the plan file goes away even if this is interrupted if terraform apply tfplan; then echo "apply complete" else echo "terraform apply FAILED - do NOT verify or report success" exit 1 fitrap会在每种退出情况下移除计划文件,包括用户决定时按下 Ctrl-C。应用操作外层的if/else很重要,因为如果清理命令是代码块的最后一行,应用失败时就会以状态码 0 退出,Agent 随后会继续“验证”实际上并未发生的部署。(这里有意使用if,而不是status=$?:在 zsh 中,status是只读变量。)计划文件完全不含密钥材料,因为密钥不会成为 Terraform 值——provider 会从环境中读取密钥。这就是为什么在这里的任何 Terraform 版本中使用
-out都是安全的,也正因如此,现有项目无需更改变量。 -
terraform apply成功后,验证集成是否已向 Datadog 注册:for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done DD_SITE='datadoghq.com'; export DD_SITE # <- replace with the site confirmed in Phase 0. # Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive. : "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}" resp=$(printf 'DD-API-KEY: %s\nDD-APPLICATION-KEY: %s\n' "$DD_API_KEY" "$DD_APP_KEY" \ | curl -sS -w '\n%{http_code}' -X GET -H @- "https://api.${DD_SITE}/api/v2/integration/gcp/accounts") code=$(printf '%s' "$resp" | tail -1); body=$(printf '%s' "$resp" | sed '$d') [ "$code" = "200" ] || { echo "lookup failed with HTTP $code - do not assume 'not connected':"; printf '%s\n' "$body"; exit 1; } printf '%s\n' "$body"确认响应中包含刚刚配置的服务账号电子邮件地址。如果缺少该信息,请向用户展示响应,以便他们进行排查。
充分利用集成
terraform apply 成功完成后,祝贺用户,并告知他们通常会在 5-10 分钟内收到指标。然后检查是否已有早期指标,并展示一个 widget。
检查指标
等待几秒,然后向指标 API 发起一次查询——将 <MONITORED_PROJECT_ID> 替换为实际纳入监控范围的项目,不一定是 host project,因为模板允许 host project 不在监控范围内:
for f in .env.local .env; do [ -f "$f" ] || continue; for k in DD_SITE DD_API_KEY DD_APP_KEY; do eval "[ -n \"\${$k:-}\" ]" && continue; v=$(grep -E "^$k=" "$f" | head -1 | cut -d= -f2- | sed 's/^["'\'']//;s/["'\'']$//'); [ -n "$v" ] && export "$k=$v"; done; done
DD_SITE='datadoghq.com'; export DD_SITE # <- replace with the site confirmed in Phase 0.
# Explicit assignment, not ':=': a wrong non-empty DD_SITE in .env would otherwise survive.
: "${DD_API_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"; : "${DD_APP_KEY:?not set - run dd-account-setup (commercial sites) or supply it directly (government sites)}"
sleep 10
resp=$(printf 'DD-API-KEY: %s\nDD-APPLICATION-KEY: %s\n' "$DD_API_KEY" "$DD_APP_KEY" \
| curl -sS -w '\n%{http_code}' -G -H @- "https://api.${DD_SITE}/api/v1/query" \
--data-urlencode "from=$(($(date +%s) - 900))" \
--data-urlencode "to=$(date +%s)" \
--data-urlencode "query=avg:gcp.gce.instance.cpu.utilization{project_id:<MONITORED_PROJECT_ID>} by {instance_name}")
code=$(printf '%s' "$resp" | tail -1); body=$(printf '%s' "$resp" | sed '$d')
[ "$code" = "200" ] || { echo "metric query failed with HTTP $code - that is NOT 'metrics still propagating':"; printf '%s\n' "$body"; exit 1; }
printf '%s\n' "$body"
渲染组件
如果 series 数组非空,请根据真实数据绘制 ASCII 图表:
- 使用
pointlist的值绘制折线,并根据实际最小值和最大值缩放 Y 轴。 - 使用制表符绘图字符(
╭、╰、─、│、`┤)绘制折线。 - 在底部列出每个 series
scope中的实例名称。 - 如果返回了多个 series,则显示平均值最高的前 3 个。
如果 series 数组为空,请显示以下静态预览,并告知用户指标仍在传播中:
┌─────────────────────────────────────────────────────────┐
│ gcp.gce.instance.cpu.utilization ▂▃▅▆▇▆▅▃▂▁▂▃▅ │
│ 100% ┤ ╭──╮ │
│ 75% ┤ ╭───╮ ╭──╯ │ │
│ 50% ┤ ╭────╯ ╰──╮ ╭────╯ │ │
│ 25% ┤ ╭────────╯ ╰────╯ │ │
│ 0% ┤────╯ │ │
│ └────────────────────────────────────────────┘ │
│ │
│ Metrics are on their way - check back in a few minutes │
└─────────────────────────────────────────────────────────┘
Metrics Explorer:<APP_BASE>/metric/explorer?exp_metric=gcp.gce.instance.cpu.utilization
向用户确认其集成已配置完成,数据很快就会显示。所有链接都使用 DD_SITE 构造为 <APP_BASE>/...。
- GCP Integration 磁贴:
<APP_BASE>/integrations/google-cloud-platform— 访问预构建的仪表板并确认集成已启用。 - Metrics Explorer:
<APP_BASE>/metric/explorer?exp_metric=gcp.gce.instance.cpu.utilization— 确认数据正在流入。 - 每个 GCP 子集成(Cloud SQL、Cloud Run、Pub/Sub、GKE 等)都有自己的仪表板;检测到该服务的指标后,仪表板会自动启用。
推荐监视器——建议在 <APP_BASE>/monitors/create 创建监视器,以监控常见的 GCP 健康信号:
- Compute Engine CPU 使用率超过阈值
- Cloud SQL 连接数接近上限
- GKE 节点进入 NotReady 状态
- Pub/Sub 死信队列增长
如果已启用资源收集:
- Resource Catalog:
<APP_BASE>/infrastructure/catalog— 浏览 Compute 实例、Cloud SQL 数据库、GKE 集群等。 - Infrastructure Map:
<APP_BASE>/infrastructure/map— 可视化 GCP 基础设施。
探索更多 Datadog 产品:
- 日志管理:
<APP_BASE>/logs— 汇集 GCP 日志,便于集中搜索和告警。设置指南:https://docs.datadoghq.com/integrations/google_cloud_platform/#log-collection - APM 与链路追踪:
<APP_BASE>/apm/getting-started— 对运行于 Cloud Run、GKE 或 Compute Engine 上的应用进行分布式链路追踪。 - Notebooks:
<APP_BASE>/notebook— 分享结合指标、日志和事件的调查记录。
重要说明
- 生成 Terraform 之前,务必先向用户确认项目 ID、文件夹 ID 和宿主项目。
- 用户需要拥有在范围内的每个项目和文件夹中创建服务账号、启用服务以及设置 IAM 策略的权限:
roles/resourcemanager.projectIamAdmin/roles/resourcemanager.folderIamAdmin;在宿主项目中拥有roles/iam.serviceAccountAdmin;并在配置启用 API 的每个项目中拥有roles/serviceusage.serviceUsageAdmin(包括宿主项目——该项目会启用iam.googleapis.com和iamcredentials.googleapis.com)。 服务账号自身获得的roles/serviceusage.serviceUsageConsumer权限不足以启用 API。 - Datadog 委派主体 ID 因组织而异,必须通过 API 获取,不能硬编码。
- 绝不能将 Datadog API 密钥和应用密钥作为值传递给 Terraform:
datadogprovider 会从环境变量读取DD_API_KEY和DD_APP_KEY,因此无需根变量,也不需要-var=参数,Terraform 也不会在状态文件或保存的计划中记录这些密钥。不要声明密钥变量,也不要将密钥写入已提交的.tfvars文件或任何其他持久文件。 - 未先向用户展示计划之前,绝不能运行
terraform apply。