名称: pulumi-stacks 用户可调用: false 描述: 使用Pulumi堆栈管理多个环境,用于开发、测试和生产部署。 允许工具: [Bash, Read]
Pulumi 堆栈
使用Pulumi堆栈管理多个环境和配置,实现跨开发、测试和生产的一致基础设施。
概述
Pulumi堆栈是Pulumi程序的隔离、独立可配置实例。每个堆栈都有自己的状态、配置和资源,使您能够将相同的基础设施代码部署到多个环境。
堆栈基础
创建和选择堆栈
# 初始化新项目
pulumi new aws-typescript
# 创建新堆栈
pulumi stack init dev
# 列出所有堆栈
pulumi stack ls
# 选择堆栈
pulumi stack select dev
# 显示当前堆栈
pulumi stack
# 移除堆栈
pulumi stack rm dev
堆栈配置
# 设置配置值
pulumi config set aws:region us-east-1
pulumi config set instanceType t3.micro
# 设置秘密值(加密)
pulumi config set --secret dbPassword mySecurePassword123
# 获取配置值
pulumi config get aws:region
# 列出所有配置
pulumi config
# 移除配置
pulumi config rm instanceType
堆栈配置文件
Pulumi.yaml(项目文件)
name: my-infrastructure
user-invocable: false
runtime: nodejs
description: 多环境基础设施
config:
aws:region:
description: 部署的AWS区域
default: us-east-1
instanceType:
description: EC2实例类型
default: t3.micro
environment:
description: 环境名称
Pulumi.dev.yaml(堆栈配置)
config:
aws:region: us-east-1
my-infrastructure:instanceType: t3.micro
my-infrastructure:environment: 开发
my-infrastructure:minSize: "1"
my-infrastructure:maxSize: "3"
my-infrastructure:enableMonitoring: "false"
Pulumi.staging.yaml
config:
aws:region: us-east-1
my-infrastructure:instanceType: t3.small
my-infrastructure:environment: 测试
my-infrastructure:minSize: "2"
my-infrastructure:maxSize: "5"
my-infrastructure:enableMonitoring: "true"
Pulumi.prod.yaml
config:
aws:region: us-west-2
my-infrastructure:instanceType: t3.medium
my-infrastructure:environment: 生产
my-infrastructure:minSize: "3"
my-infrastructure:maxSize: "10"
my-infrastructure:enableMonitoring: "true"
my-infrastructure:backupRetention: "30"
在代码中读取配置
TypeScript配置
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// 获取配置
const config = new pulumi.Config();
const instanceType = config.get("instanceType") || "t3.micro";
const environment = config.require("environment");
const minSize = config.getNumber("minSize") || 1;
const maxSize = config.getNumber("maxSize") || 3;
const enableMonitoring = config.getBoolean("enableMonitoring") || false;
// 获取秘密
const dbPassword = config.requireSecret("dbPassword");
// 使用配置
const instance = new aws.ec2.Instance("web-server", {
instanceType: instanceType,
ami: "ami-0c55b159cbfafe1f0",
tags: {
Name: `web-server-${environment}`,
Environment: environment,
},
monitoring: enableMonitoring,
});
// 导出堆栈名称
export const stackName = pulumi.getStack();
export const instanceId = instance.id;
Python配置
import pulumi
import pulumi_aws as aws
# 获取配置
config = pulumi.Config()
instance_type = config.get("instanceType") or "t3.micro"
environment = config.require("environment")
min_size = config.get_int("minSize") or 1
max_size = config.get_int("maxSize") or 3
enable_monitoring = config.get_bool("enableMonitoring") or False
# 获取秘密
db_password = config.require_secret("dbPassword")
# 使用配置
instance = aws.ec2.Instance(
"web-server",
instance_type=instance_type,
ami="ami-0c55b159cbfafe1f0",
tags={
"Name": f"web-server-{environment}",
"Environment": environment,
},
monitoring=enable_monitoring,
)
# 导出输出
pulumi.export("stack_name", pulumi.get_stack())
pulumi.export("instance_id", instance.id)
环境特定资源
条件资源创建
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment");
const enableHighAvailability = config.getBoolean("enableHA") || false;
// 创建VPC
const vpc = new aws.ec2.Vpc("main", {
cidrBlock: "10.0.0.0/16",
tags: {
Name: `vpc-${environment}`,
Environment: environment,
},
});
// 生产环境获取多个可用区
const azCount = environment === "production" ? 3 : 1;
const subnets: aws.ec2.Subnet[] = [];
for (let i = 0; i < azCount; i++) {
const subnet = new aws.ec2.Subnet(`subnet-${i}`, {
vpcId: vpc.id,
cidrBlock: `10.0.${i}.0/24`,
availabilityZone: `us-east-1${String.fromCharCode(97 + i)}`,
tags: {
Name: `subnet-${environment}-${i}`,
Environment: environment,
},
});
subnets.push(subnet);
}
// 仅在生产环境创建NAT网关
let natGateway: aws.ec2.NatGateway | undefined;
if (environment === "production") {
const eip = new aws.ec2.Eip("nat-eip", {
vpc: true,
});
natGateway = new aws.ec2.NatGateway("nat", {
allocationId: eip.id,
subnetId: subnets[0].id,
tags: {
Name: `nat-${environment}`,
Environment: environment,
},
});
}
// 创建RDS,仅生产环境使用多AZ
const db = new aws.rds.Instance("database", {
engine: "postgres",
engineVersion: "14.7",
instanceClass: environment === "production" ? "db.t3.medium" : "db.t3.micro",
allocatedStorage: environment === "production" ? 100 : 20,
dbName: "myapp",
username: "admin",
password: config.requireSecret("dbPassword"),
multiAz: environment === "production",
backupRetentionPeriod: environment === "production" ? 30 : 7,
skipFinalSnapshot: environment !== "production",
tags: {
Name: `db-${environment}`,
Environment: environment,
},
});
export const vpcId = vpc.id;
export const subnetIds = subnets.map(s => s.id);
export const dbEndpoint = db.endpoint;
堆栈引用
跨堆栈引用
// 基础设施堆栈(infra/index.ts)
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const vpc = new aws.ec2.Vpc("shared-vpc", {
cidrBlock: "10.0.0.0/16",
tags: {
Name: "shared-vpc",
},
});
const subnet = new aws.ec2.Subnet("shared-subnet", {
vpcId: vpc.id,
cidrBlock: "10.0.1.0/24",
tags: {
Name: "shared-subnet",
},
});
// 为其他堆栈导出
export const vpcId = vpc.id;
export const subnetId = subnet.id;
export const vpcCidr = vpc.cidrBlock;
// 应用堆栈(app/index.ts)
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// 引用基础设施堆栈
const infraStack = new pulumi.StackReference("myorg/infra/prod");
// 从基础设施堆栈获取输出
const vpcId = infraStack.getOutput("vpcId");
const subnetId = infraStack.getOutput("subnetId");
// 使用引用值
const securityGroup = new aws.ec2.SecurityGroup("app-sg", {
vpcId: vpcId,
description: "应用的安全组",
ingress: [{
protocol: "tcp",
fromPort: 80,
toPort: 80,
cidrBlocks: ["0.0.0.0/0"],
}],
});
const instance = new aws.ec2.Instance("app-server", {
instanceType: "t3.micro",
ami: "ami-0c55b159cbfafe1f0",
subnetId: subnetId,
vpcSecurityGroupIds: [securityGroup.id],
tags: {
Name: "app-server",
},
});
export const instanceIp = instance.publicIp;
堆栈引用命令
# 先部署基础设施堆栈
cd infra
pulumi stack select prod
pulumi up
# 然后部署应用堆栈
cd ../app
pulumi stack select prod
pulumi up
# 查看引用堆栈的输出
pulumi stack output --stack myorg/infra/prod
堆栈输出
导出堆栈输出
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment");
// 创建资源
const vpc = new aws.ec2.Vpc("main", {
cidrBlock: "10.0.0.0/16",
});
const bucket = new aws.s3.Bucket("app-bucket", {
bucket: `myapp-${environment}-bucket`,
});
const db = new aws.rds.Instance("database", {
engine: "postgres",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
dbName: "myapp",
username: "admin",
password: config.requireSecret("dbPassword"),
skipFinalSnapshot: true,
});
// 导出输出
export const vpcId = vpc.id;
export const vpcCidr = vpc.cidrBlock;
export const bucketName = bucket.id;
export const bucketArn = bucket.arn;
export const dbEndpoint = db.endpoint;
export const dbPort = db.port;
// 导出计算值
export const dbConnectionString = pulumi.interpolate`postgresql://admin@${db.endpoint}/myapp`;
// 导出堆栈元数据
export const stackName = pulumi.getStack();
export const projectName = pulumi.getProject();
export const region = aws.getRegion().then(r => r.name);
访问堆栈输出
# 查看所有输出
pulumi stack output
# 获取特定输出
pulumi stack output vpcId
# 以JSON格式获取输出
pulumi stack output --json
# 在shell脚本中使用
VPC_ID=$(pulumi stack output vpcId)
echo "VPC ID: $VPC_ID"
# 导出到环境变量
export $(pulumi stack output --json | jq -r 'to_entries[] | "\(.key)=\(.value)"')
堆栈转换
全局资源转换
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment");
// 注册全局转换以添加标签
pulumi.runtime.registerStackTransformation((args) => {
if (args.type.startsWith("aws:")) {
args.props.tags = {
...args.props.tags,
Environment: environment,
ManagedBy: "Pulumi",
Stack: pulumi.getStack(),
};
}
return {
props: args.props,
opts: args.opts,
};
});
// 所有AWS资源自动获取标签
const vpc = new aws.ec2.Vpc("main", {
cidrBlock: "10.0.0.0/16",
// 标签将通过转换自动添加
});
const bucket = new aws.s3.Bucket("data", {
// 标签将通过转换自动添加
});
资源特定转换
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment");
// 转换以强制加密
const enforceEncryption = (args: pulumi.ResourceTransformationArgs) => {
if (args.type === "aws:s3/bucket:Bucket") {
args.props.serverSideEncryptionConfiguration = {
rule: {
applyServerSideEncryptionByDefault: {
sseAlgorithm: "AES256",
},
},
};
}
if (args.type === "aws:rds/instance:Instance") {
args.props.storageEncrypted = true;
}
return {
props: args.props,
opts: args.opts,
};
};
pulumi.runtime.registerStackTransformation(enforceEncryption);
// 资源将自动加密
const bucket = new aws.s3.Bucket("data");
const db = new aws.rds.Instance("database", {
engine: "postgres",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
});
堆栈标签和组织
标签策略
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment");
const project = pulumi.getProject();
const stack = pulumi.getStack();
// 定义常见标签
const commonTags = {
Project: project,
Environment: environment,
Stack: stack,
ManagedBy: "Pulumi",
CostCenter: config.get("costCenter") || "engineering",
Owner: config.get("owner") || "platform-team",
};
// 辅助函数合并标签
function mergeTags(resourceTags?: { [key: string]: string }): { [key: string]: string } {
return {
...commonTags,
...resourceTags,
};
}
// 使用一致标签
const vpc = new aws.ec2.Vpc("main", {
cidrBlock: "10.0.0.0/16",
tags: mergeTags({
Name: `vpc-${environment}`,
Type: "network",
}),
});
const bucket = new aws.s3.Bucket("data", {
tags: mergeTags({
Name: `data-${environment}`,
Type: "storage",
Compliance: "required",
}),
});
const db = new aws.rds.Instance("database", {
engine: "postgres",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
tags: mergeTags({
Name: `db-${environment}`,
Type: "database",
BackupRequired: "true",
}),
});
堆栈导入和导出
导出堆栈状态
# 将堆栈状态导出到JSON
pulumi stack export > stack-state.json
# 导出到文件
pulumi stack export --file stack-backup.json
# 导出秘密为明文(小心使用!)
pulumi stack export --show-secrets > stack-with-secrets.json
导入堆栈状态
# 导入堆栈状态
pulumi stack import --file stack-state.json
# 从标准输入导入
cat stack-state.json | pulumi stack import
堆栈迁移
# 从旧堆栈导出
pulumi stack select old-stack
pulumi stack export --file old-stack.json
# 创建并导入到新堆栈
pulumi stack init new-stack
pulumi stack import --file old-stack.json
# 验证资源
pulumi preview
多区域部署
区域特定堆栈
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const awsConfig = new pulumi.Config("aws");
const region = awsConfig.require("region");
const environment = config.require("environment");
// 创建区域特定资源
const vpc = new aws.ec2.Vpc(`vpc-${region}`, {
cidrBlock: "10.0.0.0/16",
tags: {
Name: `vpc-${environment}-${region}`,
Region: region,
Environment: environment,
},
});
// 在us-east-1创建CloudFront分发
const usEast1Provider = new aws.Provider("us-east-1", {
region: "us-east-1",
});
const certificate = new aws.acm.Certificate("cert", {
domainName: `${environment}.example.com`,
validationMethod: "DNS",
tags: {
Name: `cert-${environment}`,
Environment: environment,
},
}, { provider: usEast1Provider });
// 导出区域信息
export const deploymentRegion = region;
export const vpcId = vpc.id;
export const certificateArn = certificate.arn;
多区域堆栈配置
# Pulumi.us-east-1-prod.yaml
config:
aws:region: us-east-1
my-app:environment: 生产
my-app:isPrimaryRegion: "true"
# Pulumi.us-west-2-prod.yaml
config:
aws:region: us-west-2
my-app:environment: 生产
my-app:isPrimaryRegion: "false"
# Pulumi.eu-west-1-prod.yaml
config:
aws:region: eu-west-1
my-app:environment: 生产
my-app:isPrimaryRegion: "false"
堆栈策略
保护资源
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment");
// 保护生产数据库
const db = new aws.rds.Instance("database", {
engine: "postgres",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
tags: {
Name: `db-${environment}`,
},
}, {
protect: environment === "production",
});
// 保护生产存储
const bucket = new aws.s3.Bucket("data", {
tags: {
Name: `data-${environment}`,
},
}, {
protect: environment === "production",
});
保留资源
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment");
// 在堆栈删除时保留生产数据库
const db = new aws.rds.Instance("database", {
engine: "postgres",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
finalSnapshotIdentifier: environment === "production"
? `final-snapshot-${Date.now()}`
: undefined,
skipFinalSnapshot: environment !== "production",
}, {
retainOnDelete: environment === "production",
});
堆栈秘密管理
使用加密秘密
# 设置加密秘密
pulumi config set --secret dbPassword mySecurePassword123
pulumi config set --secret apiKey sk_live_abc123xyz789
# 查看配置(秘密已加密)
pulumi config
# 查看明文秘密(小心使用!)
pulumi config get dbPassword --show-secrets
代码中的秘密
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
// 获取秘密值
const dbPassword = config.requireSecret("dbPassword");
const apiKey = config.requireSecret("apiKey");
// 在资源中使用秘密
const db = new aws.rds.Instance("database", {
engine: "postgres",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
username: "admin",
password: dbPassword,
});
// 从秘密创建SSM参数
const dbPasswordParam = new aws.ssm.Parameter("db-password", {
name: "/app/database/password",
type: "SecureString",
value: dbPassword,
});
const apiKeyParam = new aws.ssm.Parameter("api-key", {
name: "/app/api/key",
type: "SecureString",
value: apiKey,
});
// 秘密在状态中加密
export const connectionString = pulumi.secret(
pulumi.interpolate`postgresql://admin:${dbPassword}@${db.endpoint}/myapp`
);
堆栈刷新和状态
刷新堆栈状态
# 刷新堆栈以匹配实际云状态
pulumi refresh
# 自动批准刷新
pulumi refresh --yes
# 刷新特定资源
pulumi refresh --target urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::my-bucket
# 刷新并显示差异
pulumi refresh --diff
堆栈状态管理
# 查看堆栈状态
pulumi stack --show-urns
# 查看特定资源
pulumi stack --show-urns | grep my-bucket
# 从状态移除资源(不删除云资源)
pulumi state delete 'urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::my-bucket'
# 在状态中重命名资源
pulumi state rename 'urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::old-name' \
'urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::new-name'
何时使用此技能
使用pulumi-stacks技能当您需要:
- 将基础设施部署到多个环境(开发、测试、生产)
- 管理环境特定配置
- 创建相同基础设施的隔离实例
- 在项目之间共享基础设施输出
- 实现多区域部署
- 分离基础设施关注点(网络、数据库、应用)
- 按环境管理秘密
- 按环境跟踪基础设施状态
- 实施渐进式部署策略
- 将复杂基础设施组织为可管理单元
- 应用环境特定策略和保护
- 跨环境维护一致基础设施
最佳实践
- 命名约定:使用一致堆栈命名,如
<env>或<region>-<env>(例如,prod,us-east-1-prod) - 配置文件:将堆栈配置文件保存在版本控制中(除秘密外)
- 环境隔离:切勿在环境之间共享状态;每个环境都有自己的堆栈
- 堆栈引用:使用堆栈引用而不是复制基础设施代码
- 秘密管理:始终对敏感值使用
--secret标志 - 渐进式部署:先部署到开发,然后测试,最后生产
- 状态备份:定期导出堆栈状态以进行灾难恢复
- 资源保护:为关键生产资源启用
protect选项 - 标签策略:在所有环境应用一致标签以进行成本跟踪
- 堆栈输出:导出其他堆栈或外部系统所需的所有值
- 配置验证:在创建资源前验证配置值
- 环境平等:尽可能保持环境相似,仅规模不同
- 自动化:使用CI/CD管道进行堆栈部署
- 文档:记录堆栈依赖关系和所需配置
- 状态加密:对敏感基础设施使用加密状态后端
常见陷阱
- 硬编码值:硬编码环境特定值而不是使用配置
- 共享状态:尝试在环境之间共享堆栈状态
- 缺失配置:部署到新堆栈而未设置所需配置
- 未加密秘密:将秘密存储为纯文本在配置中
- 不一致命名:在堆栈中使用不同命名约定
- 损坏引用:堆栈引用指向不存在堆栈或输出
- 缺失导出:未导出依赖堆栈所需的值
- 配置漂移:手动更改配置文件未反映在版本控制中
- 无资源保护:忘记保护关键生产资源
- 堆栈扩散:创建过多堆栈而无清晰组织
- 缺失验证:部署前未验证配置
- 循环依赖:创建循环堆栈引用
- 无备份策略:未导出堆栈状态以进行灾难恢复
- 环境差异:生产环境与其他环境显著不同
- 差秘密管理:将加密秘密检入公共仓库而无适当密钥管理