为什么选 TypeScript 写 Pulumi?
TypeScript 是 Pulumi 生态中最成熟的语言选择。Pulumi 的所有官方示例和大多数组件都优先提供 TypeScript 版本。TypeScript 的强类型系统与 Pulumi 的 Input<T>/Output<T> 类型系统完美配合,能在编译阶段就发现资源属性类型错误,而不必等到 pulumi up 失败后才发现。
项目初始化与 SDK 安装
# 创建 TypeScript + AWS 项目
mkdir my-ts-infra && cd my-ts-infra
pulumi new aws-typescript
# 手动安装 SDK(如果向已有项目添加云平台支持)
npm install @pulumi/pulumi @pulumi/aws
# 添加 Kubernetes 支持
npm install @pulumi/kubernetes
# 添加 GCP 支持
npm install @pulumi/gcp
# 编译 TypeScript(可选,pulumi up 会自动编译)
npx tsc --noEmit # 只做类型检查,不输出文件
tsconfig.json 推荐配置
{
"compilerOptions": {
"strict": true,
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "bin"
},
"include": ["*.ts"]
}
TypeScript 项目结构
my-ts-infra/
├── index.ts # 主入口:导出资源引用
├── network.ts # VPC、子网、安全组
├── compute.ts # EC2 实例
├── storage.ts # S3、RDS
├── Pulumi.yaml # 项目配置
├── Pulumi.dev.yaml # dev stack 配置
├── package.json
├── tsconfig.json
└── node_modules/
Input<T> 与 Output<T> 类型系统
类型定义
// Pulumi 类型系统的核心
type Input<T> = T | Promise<T> | Output<T>;
// Input<T> 可以是:
// - 普通值(直接传 string/number/boolean)
// - Promise(异步解析的值)
// - Output(另一个资源的输出)
interface InstanceArgs {
ami: Input<string>; // 接受字符串或 Output<string>
instanceType: Input<string>;
subnetId?: Input<string>; // 可选的 Input
vpcSecurityGroupIds?: Input<Input<string>[]>;
}
强类型资源属性的好处
import * as aws from "@pulumi/aws";
// 创建安全组
const sg = new aws.ec2.SecurityGroup("web-sg", {
ingress: [{
fromPort: 80,
toPort: 80,
protocol: "tcp",
cidrBlocks: ["0.0.0.0/0"],
// 错误示例:
// protocol: "invalid", // ❌ TypeScript 编译错误!
// fromPort: "80", // ❌ 类型错误,应该是 number
}],
egress: [{
fromPort: 0,
toPort: 0,
protocol: "-1",
cidrBlocks: ["0.0.0.0/0"],
}],
});
// 创建 EC2 实例(引用安全组 Output)
const instance = new aws.ec2.Instance("web-server", {
ami: "ami-0c02fb55956c7d316",
instanceType: aws.ec2.InstanceType.T3_Micro, // 枚举类型,IDE 自动补全!
vpcSecurityGroupIds: [sg.id], // Output<string> 自动被 Input<string> 接受
});
pulumi.all():合并多个 Output
import * as pulumi from "@pulumi/pulumi";
const db = new aws.rds.Instance("db", { /* ... */ });
const bucket = new aws.s3.BucketV2("assets");
// 合并多个 Output 为一个
const config = pulumi.all([db.endpoint, bucket.id, db.port])
.apply(([endpoint, bucketId, port]) => ({
databaseUrl: `postgres://user@${endpoint}:${port}/db`,
assetsBucket: bucketId,
}));
// pulumi.interpolate:模板字符串中使用 Output
const dbUrl = pulumi.interpolate
`postgres://admin:${dbPassword}@${db.endpoint}:${db.port}/mydb`;
// 导出复合配置
export const appConfig = config;
实战:VPC + EC2 + Security Group 完整网络架构
架构图
┌─────────────────────────────────────────────────────┐
│ AWS VPC (10.0.0.0/16) │
│ │
│ ┌─────────────────────┐ ┌────────────────────┐ │
│ │ Public Subnet │ │ Private Subnet │ │
│ │ 10.0.1.0/24 │ │ 10.0.2.0/24 │ │
│ │ │ │ │ │
│ │ ┌───────────────┐ │ │ ┌─────────────┐ │ │
│ │ │ EC2 Web │ │ │ │ RDS DB │ │ │
│ │ │ (port 80) │ │ │ │ (port 5432)│ │ │
│ │ └───────────────┘ │ │ └─────────────┘ │ │
│ └─────────────────────┘ └────────────────────┘ │
│ │ │
│ Internet Gateway │
└─────────────────────────────────────────────────────┘
network.ts — VPC 网络资源
// network.ts
import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";
// 1. 创建 VPC
export const vpc = new aws.ec2.Vpc("main", {
cidrBlock: "10.0.0.0/16",
enableDnsHostnames: true,
enableDnsSupport: true,
tags: { Name: "main-vpc" },
});
// 2. 创建 Internet Gateway
export const igw = new aws.ec2.InternetGateway("igw", {
vpcId: vpc.id,
tags: { Name: "main-igw" },
});
// 3. 创建公共子网(两个可用区,实现高可用)
const azs = ["us-east-1a", "us-east-1b"];
export const publicSubnets = azs.map((az, i) =>
new aws.ec2.Subnet(`public-${az}`, {
vpcId: vpc.id,
cidrBlock: `10.0.${i + 1}.0/24`,
availabilityZone: az,
mapPublicIpOnLaunch: true,
tags: { Name: `public-${az}`, Type: "public" },
})
);
// 4. 创建路由表(公共子网 → Internet Gateway)
const publicRt = new aws.ec2.RouteTable("public-rt", {
vpcId: vpc.id,
routes: [{
cidrBlock: "0.0.0.0/0",
gatewayId: igw.id,
}],
tags: { Name: "public-rt" },
});
// 5. 关联路由表到公共子网
publicSubnets.forEach((subnet, i) =>
new aws.ec2.RouteTableAssociation(`public-rta-${i}`, {
subnetId: subnet.id,
routeTableId: publicRt.id,
})
);
compute.ts — EC2 实例与安全组
// compute.ts
import * as aws from "@pulumi/aws";
import { vpc, publicSubnets } from "./network";
// 安全组:允许 HTTP + SSH
export const webSg = new aws.ec2.SecurityGroup("web-sg", {
vpcId: vpc.id,
description: "Allow HTTP and SSH",
ingress: [
{ fromPort: 80, toPort: 80, protocol: "tcp", cidrBlocks: ["0.0.0.0/0"] },
{ fromPort: 443, toPort: 443, protocol: "tcp", cidrBlocks: ["0.0.0.0/0"] },
{ fromPort: 22, toPort: 22, protocol: "tcp", cidrBlocks: ["10.0.0.0/8"] },
],
egress: [{
fromPort: 0, toPort: 0, protocol: "-1", cidrBlocks: ["0.0.0.0/0"],
}],
tags: { Name: "web-sg" },
});
// 查询最新 Amazon Linux 2 AMI
const ami = aws.ec2.getAmiOutput({
mostRecent: true,
owners: ["amazon"],
filters: [
{ name: "name", values: ["amzn2-ami-hvm-*-x86_64-gp2"] },
{ name: "virtualization-type", values: ["hvm"] },
],
});
// EC2 实例(user_data 安装 Nginx)
export const webServer = new aws.ec2.Instance("web-server", {
ami: ami.id,
instanceType: aws.ec2.InstanceType.T3_Micro,
subnetId: publicSubnets[0].id,
vpcSecurityGroupIds: [webSg.id],
userData: `#!/bin/bash
yum update -y
yum install -y nginx
systemctl start nginx
systemctl enable nginx`,
tags: { Name: "web-server", Role: "webserver" },
});
index.ts — 主入口与输出
// index.ts
import * as pulumi from "@pulumi/pulumi";
import { vpc, publicSubnets } from "./network";
import { webServer, webSg } from "./compute";
// 导出关键信息
export const vpcId = vpc.id;
export const publicSubnetIds = publicSubnets.map(s => s.id);
export const webServerPublicIp = webServer.publicIp;
export const webServerUrl = pulumi.interpolate`http://${webServer.publicIp}`;
export const securityGroupId = webSg.id;
# 部署
pulumi up
# 查看输出
pulumi stack output webServerPublicIp
pulumi stack output webServerUrl
# 查看所有输出(JSON 格式)
pulumi stack output --json
TypeScript 严格模式建议
在 tsconfig.json 中开启 "strict": true 可以获得最强的类型保护,Pulumi 的类型定义完全兼容严格模式。strict: true 会启用 noImplicitAny、strictNullChecks 等一系列检查,让你在 pulumi up 之前就能发现潜在的配置错误。
本章小结
本章核心要点
- TypeScript 是 Pulumi 最成熟的语言:官方示例最丰富,类型定义最完整,IDE 支持最好。
- Input<T> = T | Promise<T> | Output<T>:资源属性可以接受普通值、Promise 或另一个资源的 Output,Pulumi 自动处理异步依赖。
- 枚举类型:如
aws.ec2.InstanceType.T3_Micro,IDE 自动补全所有合法值,避免拼写错误。 - pulumi.all() + pulumi.interpolate:组合多个 Output 的两个利器,interpolate 适合字符串拼接,all() 适合复杂对象构建。
- 模块化:将网络、计算、存储拆分到不同 .ts 文件,通过 ES module import/export 组合,保持代码清晰。
- for 循环创建多个资源:用
azs.map(az => new aws.ec2.Subnet(...))批量创建,比 HCL 的 for_each 更直观强大。