很久之前, 参加过公司的一个内部培训用Terraform实现了一个云资源管理平台。好久没 用,基本上把他的语法都忘完了。最近开始用 Pulumi 管理云资源,它和 Terraform 的思路完全不同 —— 直接用真正的编程语言(TypeScript/Python/Go)写基础设施代 码,而不是学新的 DSL。所以记录基础概念和常见用法,方便以后快速回顾。
核心概念
| 概念 | 说明 | 类比 |
|---|---|---|
| Resource | 云资源(VM、S3、数据库) | 对象实例 |
| Stack | 独立环境(dev/staging/prod) | 环境变量 |
| State | 资源当前状态(实际 vs 期望) | 数据库快照 |
| Output | 异步值(资源 ID、IP 等) | Promise |
Resource:云资源
Resource 是你要创建的云资源,比如 S3 bucket、EC2 实例、数据库等。每个 Resource 有三个参数:
new aws.s3.Bucket(
"my-bucket", // 1. 名称(Pulumi 内部标识)
{ acl: "private" }, // 2. 配置(传给云 API)
{ protect: true } // 3. 选项(可选,防止误删等)
);关键:代码只是声明,运行 pulumi up 才会真正调用云 API 创建资源。
Stack:环境隔离
Stack 是同一份代码部署到不同环境的实例。比如同一份代码,用 dev Stack 部署到开发环境,用 prod Stack 部署到生产环境。
pulumi stack init dev # 创建 dev Stack
pulumi stack select dev # 切换到 dev
pulumi up # 部署到 dev 环境每个 Stack 可以有不同的配置(实例规格、副本数等):
const config = new pulumi.Config();
const instanceType = config.require("instanceType");
const server = new aws.ec2.Instance("web", {
instanceType, // dev 用 t2.micro,prod 用 t3.large
});State:状态管理
State 是 Pulumi 记录的资源快照,用于对比「代码里写的」和「云上实际的」是否一致。如果不一致,Pulumi 会帮你修改云资源。
常用操作:
pulumi refresh # 同步 State(有人手动改了云资源)
pulumi import aws:s3/bucket:Bucket my-bucket existing-id # 导入已存在的资源Output:异步值
资源的属性(如 ID、IP)在创建前不存在,所以不能直接用,必须用 Output<T> 类型处理。
const bucket = new aws.s3.Bucket("data");
bucket.id // Output<string>,不是 string
// ✅ 用 apply
bucket.id.apply(id => console.log(id));
// ✅ 用 interpolate 拼接字符串
const url = pulumi.interpolate`https://${bucket.id}.s3.amazonaws.com`;
// ✅ 传给其他资源(Pulumi 自动处理 Output)
new aws.s3.BucketObject("file", { bucket: bucket.id });多环境配置
一套代码部署多个环境,用 Stack 区分。每个 Stack 有独立的配置文件 Pulumi.<stack>.yaml,这样就能用同一份代码、不同配置部署到 dev/staging/prod 环境。
工作原理:
Pulumi 入口文件 (index.ts) → 读取配置 → 创建云资源
↑
└─ 根据当前 Stack 读取对应的配置文件
说明:
index.ts是 Pulumi 项目的入口文件(定义基础设施的代码,不是业务代码)- 文件名不一定要叫
index.ts,可以在Pulumi.yaml中通过main字段指定其他名字 - 约定俗成通常用
index.ts(Node.js)或__main__.py(Python)
完整示例:三环境部署
下面演示如何用一份代码管理三个环境。
项目结构
my-app/
├── index.ts # Pulumi 入口文件(定义基础设施资源)
├── Pulumi.yaml # 项目配置
├── Pulumi.dev.yaml # Dev 环境配置
├── Pulumi.staging.yaml # Staging 环境配置
└── Pulumi.prod.yaml # Prod 环境配置
项目配置(所有环境共享)
Pulumi.yaml 定义项目名和运行时:
name: my-app
runtime: nodejs
# main: src/infra.ts # 可选:指定其他入口文件(默认是 index.ts)环境配置(每个环境独立)
每个环境有独立的配置文件,定义实例规格、副本数等。配置格式是 <项目名>:<配置名>。
Pulumi.dev.yaml:
config:
aws:region: us-east-1
my-app:instanceType: t2.micro
my-app:dbSize: "20"
my-app:replicas: "1"Pulumi.prod.yaml:
config:
aws:region: us-west-2
my-app:instanceType: t3.large
my-app:dbSize: "200"
my-app:replicas: "5"
my-app:dbPassword:
secure: AAABAxxxxxxxx # 加密后的值代码(读取配置)
代码通过 config.require() 读取配置,Pulumi 会根据当前 Stack 自动读取对应的配置文件。
index.ts(Pulumi 入口文件):
const config = new pulumi.Config();
const stack = pulumi.getStack();
// 读取配置(从 Pulumi.<stack>.yaml)
const instanceType = config.require("instanceType");
const replicas = config.requireNumber("replicas");
// 创建云资源(EC2 服务器)
const servers = Array.from({ length: replicas }, (_, i) =>
new aws.ec2.Instance(`web-${i}`, {
instanceType, // dev 用 t2.micro,prod 用 t3.large
tags: { Environment: stack },
})
);
// 导出 Output(部署后会显示在终端,可通过 pulumi stack output 查看)
export const serverIps = servers.map(s => s.publicIp);export 的作用:
- 部署完成后,Pulumi 会在终端显示这些值
- 可以用
pulumi stack output serverIps命令查看 - 其他 Pulumi 项目可以引用这些值(StackReference)
注意:这不是你的应用业务代码(如 Express、NestJS),而是定义云资源的代码(创建 EC2、S3、数据库等)。
部署流程
第一步是创建 Stack 和配置,后续只需切换 Stack 并 pulumi up 即可。
# 第一次:创建 Stack
pulumi stack init dev
pulumi stack init prod
# 配置(或手动编辑 Pulumi.<stack>.yaml)
pulumi stack select dev
pulumi config set instanceType t2.micro
pulumi config set replicas 1
pulumi stack select prod
pulumi config set instanceType t3.large
pulumi config set replicas 5
pulumi config set dbPassword my-secret --secret # 自动加密
# 部署到 Dev
pulumi stack select dev
pulumi up # 读取 Pulumi.dev.yaml,部署到 Dev
# 部署完成后会显示 Output:
# Outputs:
# serverIps: ["54.123.45.67"]
# 查看 Output
pulumi stack output serverIps # 显示服务器 IP 列表
pulumi stack output # 显示所有 Output关键点:
pulumi stack select <name>切换环境- 切换后,所有操作(
up/preview/destroy)都针对当前 Stack - 每个 Stack 有独立的 State,互不影响
- 配置文件自动匹配:
pulumi stack select dev→ 读取Pulumi.dev.yaml
简化版:代码里硬编码配置
如果觉得配置文件麻烦,可以直接在代码里判断 Stack 名称:
const stack = pulumi.getStack();
const envConfig = {
dev: { instanceType: "t2.micro", replicas: 1 },
prod: { instanceType: "t3.large", replicas: 5 },
}[stack];
const server = new aws.ec2.Instance("web", {
instanceType: envConfig.instanceType,
});部署:
pulumi stack init dev && pulumi stack init prod
pulumi stack select dev && pulumi up
pulumi stack select prod && pulumi up注意:这种方式配置写死在代码里,修改配置需要改代码。敏感信息(密码)建议还是用配置文件加密存储。
- 切换后,所有操作(
up/preview/destroy)都针对当前 Stack - 每个 Stack 有独立的 State,互不影响
- 配置文件自动匹配:
pulumi stack select dev→ 读取Pulumi.dev.yaml
简化版:代码里硬编码配置
不想用配置文件,可以直接在代码里判断:
const stack = pulumi.getStack();
const envConfig = {
dev: { instanceType: "t2.micro", replicas: 1 },
prod: { instanceType: "t3.large", replicas: 5 },
}[stack];
const server = new aws.ec2.Instance("web", {
instanceType: envConfig.instanceType,
});部署:
pulumi stack init dev && pulumi stack init prod
pulumi stack select dev && pulumi up
pulumi stack select prod && pulumi up依赖与复用
自动依赖
Pulumi 会根据资源之间的引用关系自动建立依赖顺序。比如 Subnet 引用了 VPC 的 ID,Pulumi 就知道要先创建 VPC。
const vpc = new aws.ec2.Vpc("vpc", { cidrBlock: "10.0.0.0/16" });
const subnet = new aws.ec2.Subnet("subnet", {
vpcId: vpc.id, // 自动依赖:subnet 等 vpc 创建完
cidrBlock: "10.0.1.0/24",
});组件复用
把常用的资源组合封装成 Component,可以重复使用。比如把”安全组 + EC2 + 公网 IP”封装成一个 WebServer 组件。
class WebServer extends pulumi.ComponentResource {
public url: pulumi.Output<string>;
constructor(name: string, port: number) {
super("custom:WebServer", name);
const sg = new aws.ec2.SecurityGroup(`${name}-sg`, {
ingress: [{ fromPort: port, toPort: port, cidrBlocks: ["0.0.0.0/0"] }],
}, { parent: this });
const server = new aws.ec2.Instance(`${name}-vm`, {
instanceType: "t2.micro",
vpcSecurityGroupIds: [sg.id],
}, { parent: this });
this.url = pulumi.interpolate`http://${server.publicIp}:${port}`;
}
}
// 使用封装好的组件
const web = new WebServer("app", 80);
export const url = web.url;Pulumi vs Terraform
| 维度 | Pulumi | Terraform |
|---|---|---|
| 语言 | 真实编程语言(TS/Python/Go) | HCL(专用 DSL) |
| 抽象 | 用函数、类封装逻辑 | 用 module |
| 测试 | 单元测试(Jest/pytest) | 集成测试为主 |
| 学习曲线 | 会编程就能上手 | 需学 HCL 语法 |
| 生态 | 较新,社区小 | 成熟,资源多 |
选择建议:
- 团队已熟悉某门语言 → Pulumi
- 需要复杂逻辑(循环、条件、算法) → Pulumi
- 追求稳定、成熟方案 → Terraform
常用操作
日常使用 Pulumi 的常见命令:
# 预览变更(看看会改什么,但不执行)
pulumi preview
# 部署(真正执行变更)
pulumi up
# 删除所有资源
pulumi destroy
# 刷新 State(同步云上实际状态)
pulumi refresh
# 导入已存在资源到 Pulumi 管理
pulumi import aws:s3/bucket:Bucket my-bucket existing-bucket-name
# 使用本地 State(不用登录云端)
pulumi login --localResource Options(资源选项):
一些常用的资源行为控制:
// 阻止误删(生产数据库)
new aws.rds.Instance("prod-db", {...}, { protect: true });
// 忽略外部变更(自动伸缩的字段)
new aws.autoscaling.Group("asg", {...}, {
ignoreChanges: ["desiredCapacity"]
});参考: