3. 漂移检测原理

漂移检测是堆栈复核流程的核心前置环节。Pulumi 通过 Refresh 机制将云商实际资源状态拉取到本地状态文件,再与期望的 IaC 代码进行比对,从而发现非代码变更导致的差异。理解 Refresh 的内部原理、与 Preview 的区别以及触发时机,是构建可靠复核流程的基础。

3.1 Pulumi Refresh 机制

Refresh 的本质是 状态同步 操作。它不会修改云上资源,而是用云商返回的最新属性覆盖 Pulumi 状态文件(pulumi.state)中的对应字段。其核心流程如下:

  1. 读取状态文件:Pulumi 加载当前堆栈的 .pulumi/stacks/<stack-name>.json,获取所有托管资源的 URN 和 ID。
  2. 调用云商 API:对每个资源调用对应的 Read 操作(如 AWS DescribeInstances、Azure GetVM)。
  3. 比对并更新状态:将 API 返回的属性与状态文件中的属性逐字段比对,若不一致则用最新值更新状态文件。
  4. 输出差异摘要:在终端或日志中列出所有发生变化的属性,但不会生成 IaC 代码层面的变更计划。
关键认知:Refresh 只更新状态文件,不修改 index.ts 或 Pulumi.yaml 等代码文件。漂移检测的最终结论需要将 Refresh 后的状态与代码进行 pulumi preview 比对才能得出。

以下是一个 Refresh 操作的典型输出示例:

$ pulumi refresh --yes
Previewing refresh (dev):

     Type                 Name               Plan       Info
     pulumi:pulumi:Stack  infra-dev           running...
 ~   aws:s3:Bucket       my-app-bucket       update     [diff: +tags, -versioning]
 ~   aws:ec2:Instance    web-server          update     [diff: ~instanceType, +userData]
     aws:iam:Role        app-role             no change

Resources:
    ~ 2 to update
    1 no change
    3 resources refreshed

Duration: 4.2s

上述输出中,~ 表示状态文件中的属性被更新,+ 表示新增属性,- 表示移除属性。这些变化仅反映状态文件与云商实际资源之间的差异,并非代码与云商之间的差异。

3.2 Refresh vs Preview:核心差异

初学者常混淆 Refresh 和 Preview。两者虽然都涉及比对,但目的、操作对象和结果完全不同。下表从多个维度进行对比:

对比维度 pulumi refresh pulumi preview
操作对象 状态文件 vs 云商实际资源 状态文件 vs IaC 代码(index.ts 等)
是否修改状态 是,用云商最新属性覆盖状态文件 否,只读操作,不修改任何文件
是否修改云资源 否,只读取不写入 否,只计算不执行
输出内容 状态文件与云商资源的差异 代码与状态文件的差异(即变更计划)
典型用途 漂移检测前的状态校准 代码变更影响评估、CI 审批
执行速度 较慢,需调用云商 API 读取所有资源 较快,仅基于本地状态和代码计算

在漂移复核流程中,两者的关系是 先 Refresh 后 Preview:

  • Step 1:执行 pulumi refresh,将状态文件与云商实际资源对齐。
  • Step 2:执行 pulumi preview,此时状态文件已反映真实云环境,Preview 输出的差异即为 代码与真实环境之间的漂移。
常见误区:直接执行 pulumi preview 而不先 Refresh,得到的差异是代码与旧状态文件的差异,而非代码与真实云环境的差异。如果云上资源已被手动修改,这种 Preview 会漏报漂移。

3.3 漂移检测的触发时机

漂移检测并非每次操作都自动执行,需要根据场景选择合适的触发时机。以下是生产环境中推荐的四种触发模式:

  1. 定时触发(推荐):通过 CI/CD 定时任务(如 CronJob、GitHub Actions Schedule)每 6 小时或每天执行一次 pulumi refresh && pulumi preview,适用于合规性要求高的生产环境。
  2. 事件触发:当云商发生资源变更事件(如 AWS CloudTrail、Azure Monitor 告警)时,自动触发漂移检测。适合需要快速响应的动态环境。
  3. 人工触发:运维人员在排查故障或进行变更前,手动执行漂移检测命令。适用于临时性复核。
  4. 变更前触发:在每次 pulumi up 之前自动执行 Refresh,确保部署基于最新的真实状态。Pulumi 默认在 up 前会询问是否 Refresh,可通过 --skip-preview 跳过,但不建议在生产环境跳过。

下图展示了漂移检测在堆栈复核流程中的完整触发链路:

漂移检测触发链路与 Refresh + Preview 协作流程 触发源 • 定时 CronJob (6h/12h/24h) • 云商事件 (CloudTrail) • 人工 CLI 命令 • pulumi up 前置钩子 Refresh 阶段 1. 读取状态文件 2. 调用云商 Read API 3. 更新状态文件属性 4. 输出 Refresh 差异 Preview 阶段 1. 读取更新后状态 2. 比对 IaC 代码 3. 生成变更计划 4. 输出漂移报告 漂移报告 • 新增资源漂移 • 修改属性漂移 • 删除资源漂移 核心原则:先 Refresh 校准状态,再 Preview 检测漂移 • 若跳过 Refresh,Preview 基于旧状态计算,可能漏报云上手动变更 • 若只 Refresh 不 Preview,仅知状态变化,不知代码是否合规 • 两者组合使用,才能得到完整的漂移检测结论

3.4 漂移检测的自动化脚本示例

以下是一个适用于 CI/CD 环境的漂移检测脚本,集成了 Refresh 和 Preview 两个步骤,并输出结构化结果:

#!/bin/bash
# drift-detection.sh - 堆栈漂移检测脚本
# 用法: ./drift-detection.sh <stack-name> [--json]

set -euo pipefail

STACK_NAME="${1:?请提供堆栈名称}"
OUTPUT_FORMAT="${2:--normal}"

echo "=== 步骤 1: 执行 Refresh 同步状态 ==="
if [ "$OUTPUT_FORMAT" == "--json" ]; then
  pulumi refresh --stack "$STACK_NAME" --yes --json > refresh-result.json 2>&1
else
  pulumi refresh --stack "$STACK_NAME" --yes
fi

echo "=== 步骤 2: 执行 Preview 检测漂移 ==="
if [ "$OUTPUT_FORMAT" == "--json" ]; then
  pulumi preview --stack "$STACK_NAME" --json > preview-result.json 2>&1
  echo "漂移报告已保存至 preview-result.json"
else
  pulumi preview --stack "$STACK_NAME"
fi

echo "=== 漂移检测完成 ==="
生产建议:在 CI 流水线中,建议将 --json 输出解析为结构化数据,通过 Webhook 发送到告警平台(如 PagerDuty、Slack),实现漂移事件的自动通知与工单创建。

3.5 漂移检测的触发时机对比表

下表总结了四种触发时机的适用场景、频率和优缺点,帮助你在实际项目中做出选择:

触发方式 频率 适用场景 优点 缺点
定时触发 每 6h / 12h / 24h 生产环境合规巡检 自动化、可预测、审计友好 无法实时发现漂移
事件触发 实时(秒级) 动态环境、安全敏感场景 响应快、覆盖手动变更 依赖云商事件服务、成本较高
人工触发 按需 故障排查、变更前复核 灵活、无额外成本 依赖人员主动性、可能遗漏
变更前触发 每次 pulumi up 前 CI/CD 部署流水线 确保部署基于最新状态 增加部署耗时(约 10-30s)

在实际的堆栈复核流程中,建议采用 定时触发 + 变更前触发 的组合策略:定时任务负责周期性巡检,变更前触发确保每次部署的准确性。对于安全等级较高的环境,可额外叠加事件触发,实现近乎实时的漂移监控。