说明:本文由 Codex 根据作者提供的主题、素材与公开资料辅助生成,属于 AI 生成/整理内容,非作者原创。请读者自行甄别并交叉验证。
Pulumi 解决什么问题#
Pulumi 是一套 Infrastructure as Code 工具。
IaC 的核心目标是:
用代码描述基础设施的期望状态,
再由工具把真实云资源推进到这个状态。传统方式里,创建云资源可能依赖:
- 控制台手动点击。
- 临时脚本。
- 运维文档。
- 零散的 CLI 命令。
这些方式的问题是:
- 很难复现。
- 很难审查变更。
- 很难知道当前环境和预期环境是否一致。
- 很难在开发、测试、生产环境之间保持结构一致。
Pulumi 的思路是:
基础设施也应该像应用代码一样被编写、复用、审查、测试和部署。它和 Terraform、CloudFormation、ARM/Bicep 等工具属于同一类问题空间,但 Pulumi 的明显特点是使用通用编程语言来描述资源。
Pulumi 的特点#
Pulumi 支持 TypeScript、JavaScript、Python、Go、.NET、Java、YAML 等语言。
这意味着可以用普通代码组织基础设施逻辑:
- 使用函数封装重复资源。
- 使用类或组件抽象一组资源。
- 使用条件、循环和类型系统表达复杂配置。
- 使用包管理器复用已有库。
- 在同一套工程体系中做测试、格式化和代码审查。
例如 TypeScript 中可以写:
import * as aws from "@pulumi/aws";
const bucket = new aws.s3.Bucket("assets");
export const bucketName = bucket.bucket;这段代码不是普通业务逻辑,而是在向 Pulumi 注册一个云资源:
希望当前 Stack 里存在一个名为 assets 的 S3 Bucket。Pulumi 后续会比较当前状态和期望状态,决定创建、更新、替换还是删除资源。
Pulumi 由哪些部分组成#
从使用者角度看,Pulumi 至少包括几层。
Pulumi Program#
Pulumi Program 是描述基础设施的代码。
它可以是:
index.tsmain.pymain.goProgram.csPulumi.yaml
这个程序运行时会创建资源对象。
注意,这里的“创建资源对象”不等于立刻调用云厂商 API 创建真实资源。
更准确地说:
Pulumi Program 执行后,会向 Pulumi 引擎注册资源声明。Language Host#
Language Host 负责运行 Pulumi Program。
例如:
- TypeScript/JavaScript 使用 Node 语言宿主。
- Python 使用 Python 语言宿主。
- Go 使用 Go 语言宿主。
Language Host 的工作是:
运行用户代码;
捕获代码里注册的资源;
把资源注册请求发给 Pulumi 部署引擎。Deployment Engine#
Deployment Engine 是 Pulumi 的核心调度者。
它负责比较:
当前 Stack 的状态
vs
Pulumi Program 本次表达的期望状态然后决定每个资源应该执行什么操作:
createupdatereplacedeletesame
它还会根据资源依赖关系决定执行顺序。
如果两个资源之间没有依赖,Pulumi 可以并行处理它们;如果一个资源依赖另一个资源的输出,则必须按依赖顺序执行。
Resource Provider#
Resource Provider 负责和具体平台交互。
例如:
- AWS Provider 调 AWS API。
- Azure Provider 调 Azure API。
- Kubernetes Provider 调 Kubernetes API。
- Cloudflare Provider 调 Cloudflare API。
Pulumi 引擎本身不直接实现每个云资源的创建逻辑,而是通过 Provider 插件完成。
这也解释了为什么同样是 Pulumi,不同云厂商资源的行为和耗时可能差异很大:
Pulumi 负责调度和状态;
Provider 负责具体 API;
云平台决定底层资源的实际生命周期。State#
State 是 Pulumi 理解当前基础设施的关键。
Pulumi 会记录每个 Stack 的资源状态,例如:
- 资源 URN。
- 资源 ID。
- 输入参数。
- 输出结果。
- 依赖关系。
- Secret 标记。
有了 State,Pulumi 才知道:
这次代码里的资源,和上次已经创建过的真实资源,是不是同一个资源。没有 State,Pulumi 就无法可靠判断应该创建新资源还是更新旧资源。
Backend#
Backend 是保存 State 的地方。
常见选择包括:
- Pulumi Cloud。
- 本地文件。
- S3、Azure Blob、Google Cloud Storage 等对象存储。
Backend 不只是一个文件位置,还承担并发更新协调、状态读写、加密配置等职责。
生产环境通常不建议随意使用本地状态,因为本地状态更容易丢失,也不利于多人协作。
Stack#
Stack 是 Pulumi Program 的一个独立实例。
常见 Stack:
dev
staging
prod同一套代码可以部署到多个 Stack:
同一个 Pulumi Program
-> dev Stack
-> staging Stack
-> prod Stack每个 Stack 有自己的:
- 配置。
- Secret。
- State。
- 资源集合。
因此 Stack 是环境隔离的核心单位。
Pulumi 的执行流程#
执行:
pulumi up大致会发生这些事:
- Pulumi CLI 读取当前 Project 和 Stack。
- CLI 启动对应语言的 Language Host。
- Language Host 执行 Pulumi Program。
- 程序中的资源声明被发送给 Deployment Engine。
- Engine 读取当前 Stack 的 State。
- Engine 计算 diff,生成计划。
- Provider 根据计划调用云平台 API。
- 操作完成后,Engine 更新 State。
这个过程可以简化成:
代码 -> 资源声明 -> diff -> 云 API -> 新状态Preview 和 Up 的区别#
pulumi preview 只计算将要发生什么。
pulumi preview它用于回答:
如果现在执行,会创建、修改、替换、删除哪些资源?pulumi up 则会真正执行。
pulumi up实际使用中,建议先 preview,再 up。
这和应用开发里的代码审查类似:
先看 diff,再合并。Pulumi 如何实现幂等#
IaC 工具常说的幂等,不是说“每次执行都什么也不做”,而是说:
重复执行同一份期望状态,最终应该收敛到同一个基础设施状态。Pulumi 的幂等依赖几个条件。
资源逻辑名称稳定#
Pulumi 资源通常有一个逻辑名称:
const bucket = new aws.s3.Bucket("assets");这里的 "assets" 是 Pulumi 资源名的一部分。
如果它每次运行都随机变化:
const bucket = new aws.s3.Bucket(`assets-${Date.now()}`);Pulumi 会认为这是一个新资源。
结果可能是:
- 旧资源从本次程序中消失,被计划删除。
- 新资源被计划创建。
- 某些资源因云平台唯一名称冲突而失败。
所以资源逻辑名称应该稳定。
输入参数稳定#
如果资源输入每次运行都变化,Pulumi 就会认为期望状态发生了变化。
例如:
tags: {
buildTime: new Date().toISOString(),
}这种值会导致每次 pulumi up 都出现 diff。
如果这个字段不是基础设施真实需要的一部分,就不应该放进资源输入。
Provider 行为稳定#
Pulumi Engine 会根据 Provider 返回的信息判断资源差异。
如果某个 Provider 对某些字段处理不稳定,或者云平台 API 返回值本身会漂移,也可能导致反复 diff。
这种情况下通常需要:
- 检查资源字段是否真的需要托管。
- 使用
ignoreChanges忽略外部系统会改写的字段。 - 使用稳定的输入配置。
- 通过
pulumi refresh同步状态。
随机资源 ID 会破坏幂等吗#
要区分两类 ID。
云平台生成的物理 ID#
很多云资源创建后会得到云平台返回的 ID。
例如:
vpc-xxxx
subnet-xxxx
lb-xxxx这些 ID 是资源创建后才知道的。
只要 Pulumi State 保存了它们,后续运行就可以通过 State 找回对应资源。
这不会破坏幂等。
程序每次生成的逻辑名称或输入#
如果代码每次执行都生成随机资源名、随机配置或随机业务 ID,就会破坏 Pulumi 对“同一个资源”的识别。
例如:
const name = `worker-${Math.random()}`;
new cloud.ProviderResource(name, { name });这类写法会让 Pulumi 每次看到一个新资源。
更好的方式是:
- 逻辑名称使用稳定值。
- 需要随机但长期稳定的值时,使用专门的随机资源并让它进入 State。
- 业务侧唯一后缀通过配置传入,而不是每次运行动态生成。
Pulumi 的自动命名#
Pulumi 对很多资源支持自动命名。
例如逻辑名是:
assets真实云资源名可能带有自动后缀:
assets-3f2a91b这种后缀由 Pulumi 管理,目的是减少命名冲突,并帮助资源替换时先创建新资源再删除旧资源。
如果手动指定云资源的物理名称,替换时可能遇到名称冲突。
这也是为什么一些资源如果禁用自动命名或固定物理名,变更策略会更敏感。
Automation API 是什么#
常规使用方式是命令行:
pulumi preview
pulumi up
pulumi destroyAutomation API 则是把这些能力变成 SDK。
也就是说,可以在自己的程序里调用 Pulumi:
创建 Stack;
设置配置;
执行 preview;
执行 up;
读取输出;
执行 destroy。它适合这些场景:
- 在自定义平台里提供基础设施创建能力。
- 在 CI/CD 中更细粒度地控制部署流程。
- 做集成测试时动态创建和销毁环境。
- 构建内部 CLI 或 Web 控制台。
需要注意的是,Automation API 仍然是驱动 Pulumi 引擎,不是绕过 Pulumi 直接调云平台 API。
Pulumi 和 Terraform 的关键差异#
这里不展开完整对比,只抓最关键的差异。
| 维度 | Pulumi | Terraform |
|---|---|---|
| 配置语言 | 通用编程语言或 YAML | HCL |
| 抽象方式 | 函数、类、包、组件 | module、表达式 |
| 类型系统 | 依赖宿主语言 | HCL 类型系统 |
| 执行方式 | 运行程序,注册资源 | 解析配置,构建计划 |
| 状态 | 每个 Stack 独立 State | 每个 Workspace/State 后端管理 |
| 生态重点 | 多语言 SDK 与组件抽象 | 声明式配置与 Provider 生态 |
Pulumi 的优势在于:
- 对开发者友好,尤其是熟悉 TypeScript/Python/Go 的团队。
- 复杂逻辑表达能力强。
- 容易复用语言生态里的测试、包管理和工程工具。
- 适合构建平台化的 IaC 能力。
它的代价是:
- 代码自由度更高,也更容易写出难以审查的动态逻辑。
- 团队需要约束资源命名、随机值、组件边界和配置方式。
- 对 IaC 初学者来说,语言运行时和 Pulumi 运行时容易混淆。
使用 Pulumi 的实践建议#
1. 保持资源逻辑名称稳定#
不要把时间戳、随机数、临时输入拼进资源逻辑名。
// 好
new aws.s3.Bucket("assets");
// 不好
new aws.s3.Bucket(`assets-${Date.now()}`);2. 把环境差异放进 Stack 配置#
例如:
pulumi config set instanceType t3.small
pulumi config set replicas 3不要在代码里到处写:
if (stack === "prod") {
...
}必要的环境分支可以有,但最好集中管理。
3. 先 preview 再 up#
对生产环境尤其重要。
pulumi preview
pulumi up关注是否有意外的 delete 或 replace。
4. 不要手工修改 State#
State 是 Pulumi 的核心账本。
除非非常明确知道后果,否则不要直接改 State 文件。
如果真实环境和 State 不一致,优先考虑:
pulumi refresh5. 谨慎处理 Secret#
敏感配置应该使用 Pulumi 的 Secret 机制:
pulumi config set --secret dbPassword ...不要把密码、Token、私钥写进代码或普通配置。
常见误解#
误解一:Pulumi 程序执行就是创建资源#
不准确。
Pulumi 程序执行时主要是在注册资源声明。真正的创建、更新和删除由 Deployment Engine 和 Provider 根据 diff 执行。
误解二:用了 TypeScript 就可以随便写动态逻辑#
不建议。
IaC 代码最重要的是可预测、可审查、可重复执行。
过度动态会降低可维护性。
误解三:Stack 名称不同就一定完全隔离#
Pulumi State 是隔离的,但云平台账号、区域、命名空间、VPC、Kubernetes 集群等外部资源仍可能共享。
是否真正隔离,取决于资源本身如何配置。
误解四:随机物理 ID 一定破坏幂等#
云平台创建后返回的物理 ID 不会破坏幂等,因为 Pulumi 会记录在 State 中。
真正危险的是程序每次运行都生成新的逻辑名称或输入参数。
小结#
Pulumi 可以概括为:
用通用编程语言描述基础设施,
用 Stack 隔离环境,
用 State 记录现实世界,
用 Engine 计算差异,
用 Provider 调云平台 API,
最终让基础设施收敛到代码表达的期望状态。理解 Pulumi 的关键不是背命令,而是理解这条链路:
Program -> Engine -> Provider -> Cloud API -> State只要资源命名稳定、配置边界清晰、State 管理可靠,Pulumi 就能成为一套比较自然的 IaC 工程化工具。