pulumi-stacks

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Pulumi Stacks

Pulumi Stacks

Manage multiple environments and configurations with Pulumi stacks for consistent infrastructure across development, staging, and production.
使用Pulumi stacks管理多环境与配置,确保开发、预发布和生产环境的基础设施一致性。

Overview

概述

Pulumi stacks are isolated, independently configurable instances of a Pulumi program. Each stack has its own state, configuration, and resources, enabling you to deploy the same infrastructure code to multiple environments.
Pulumi stacks是Pulumi程序的独立、可单独配置的实例。每个stack都有自己的状态、配置和资源,让你可以将同一基础设施代码部署到多个环境。

Stack Basics

Stack基础

Creating and Selecting Stacks

创建与选择Stacks

bash
undefined
bash
undefined

Initialize a new project

Initialize a new project

pulumi new aws-typescript
pulumi new aws-typescript

Create a new stack

Create a new stack

pulumi stack init dev
pulumi stack init dev

List all stacks

List all stacks

pulumi stack ls
pulumi stack ls

Select a stack

Select a stack

pulumi stack select dev
pulumi stack select dev

Show current stack

Show current stack

pulumi stack
pulumi stack

Remove a stack

Remove a stack

pulumi stack rm dev
undefined
pulumi stack rm dev
undefined

Stack Configuration

Stack配置

bash
undefined
bash
undefined

Set configuration values

Set configuration values

pulumi config set aws:region us-east-1 pulumi config set instanceType t3.micro
pulumi config set aws:region us-east-1 pulumi config set instanceType t3.micro

Set secret values (encrypted)

Set secret values (encrypted)

pulumi config set --secret dbPassword mySecurePassword123
pulumi config set --secret dbPassword mySecurePassword123

Get configuration values

Get configuration values

pulumi config get aws:region
pulumi config get aws:region

List all configuration

List all configuration

pulumi config
pulumi config

Remove configuration

Remove configuration

pulumi config rm instanceType
undefined
pulumi config rm instanceType
undefined

Stack Configuration Files

Stack配置文件

Pulumi.yaml (Project File)

Pulumi.yaml(项目文件)

yaml
name: my-infrastructure
user-invocable: false
runtime: nodejs
description: Multi-environment infrastructure

config:
  aws:region:
    description: AWS region for deployment
    default: us-east-1
  instanceType:
    description: EC2 instance type
    default: t3.micro
  environment:
    description: Environment name
yaml
name: my-infrastructure
user-invocable: false
runtime: nodejs
description: Multi-environment infrastructure

config:
  aws:region:
    description: AWS region for deployment
    default: us-east-1
  instanceType:
    description: EC2 instance type
    default: t3.micro
  environment:
    description: Environment name

Pulumi.dev.yaml (Stack Config)

Pulumi.dev.yaml(Stack配置)

yaml
config:
  aws:region: us-east-1
  my-infrastructure:instanceType: t3.micro
  my-infrastructure:environment: development
  my-infrastructure:minSize: "1"
  my-infrastructure:maxSize: "3"
  my-infrastructure:enableMonitoring: "false"
yaml
config:
  aws:region: us-east-1
  my-infrastructure:instanceType: t3.micro
  my-infrastructure:environment: development
  my-infrastructure:minSize: "1"
  my-infrastructure:maxSize: "3"
  my-infrastructure:enableMonitoring: "false"

Pulumi.staging.yaml

Pulumi.staging.yaml

yaml
config:
  aws:region: us-east-1
  my-infrastructure:instanceType: t3.small
  my-infrastructure:environment: staging
  my-infrastructure:minSize: "2"
  my-infrastructure:maxSize: "5"
  my-infrastructure:enableMonitoring: "true"
yaml
config:
  aws:region: us-east-1
  my-infrastructure:instanceType: t3.small
  my-infrastructure:environment: staging
  my-infrastructure:minSize: "2"
  my-infrastructure:maxSize: "5"
  my-infrastructure:enableMonitoring: "true"

Pulumi.prod.yaml

Pulumi.prod.yaml

yaml
config:
  aws:region: us-west-2
  my-infrastructure:instanceType: t3.medium
  my-infrastructure:environment: production
  my-infrastructure:minSize: "3"
  my-infrastructure:maxSize: "10"
  my-infrastructure:enableMonitoring: "true"
  my-infrastructure:backupRetention: "30"
yaml
config:
  aws:region: us-west-2
  my-infrastructure:instanceType: t3.medium
  my-infrastructure:environment: production
  my-infrastructure:minSize: "3"
  my-infrastructure:maxSize: "10"
  my-infrastructure:enableMonitoring: "true"
  my-infrastructure:backupRetention: "30"

Reading Configuration in Code

在代码中读取配置

TypeScript Configuration

TypeScript配置

typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Get configuration
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;

// Get secret
const dbPassword = config.requireSecret("dbPassword");

// Use configuration
const instance = new aws.ec2.Instance("web-server", {
    instanceType: instanceType,
    ami: "ami-0c55b159cbfafe1f0",
    tags: {
        Name: `web-server-${environment}`,
        Environment: environment,
    },
    monitoring: enableMonitoring,
});

// Export stack name
export const stackName = pulumi.getStack();
export const instanceId = instance.id;
typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Get configuration
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;

// Get secret
const dbPassword = config.requireSecret("dbPassword");

// Use configuration
const instance = new aws.ec2.Instance("web-server", {
    instanceType: instanceType,
    ami: "ami-0c55b159cbfafe1f0",
    tags: {
        Name: `web-server-${environment}`,
        Environment: environment,
    },
    monitoring: enableMonitoring,
});

// Export stack name
export const stackName = pulumi.getStack();
export const instanceId = instance.id;

Python Configuration

Python配置

python
import pulumi
import pulumi_aws as aws
python
import pulumi
import pulumi_aws as aws

Get configuration

Get configuration

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
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

Get secret

Get secret

db_password = config.require_secret("dbPassword")
db_password = config.require_secret("dbPassword")

Use configuration

Use configuration

instance = aws.ec2.Instance( "web-server", instance_type=instance_type, ami="ami-0c55b159cbfafe1f0", tags={ "Name": f"web-server-{environment}", "Environment": environment, }, monitoring=enable_monitoring, )
instance = aws.ec2.Instance( "web-server", instance_type=instance_type, ami="ami-0c55b159cbfafe1f0", tags={ "Name": f"web-server-{environment}", "Environment": environment, }, monitoring=enable_monitoring, )

Export outputs

Export outputs

pulumi.export("stack_name", pulumi.get_stack()) pulumi.export("instance_id", instance.id)
undefined
pulumi.export("stack_name", pulumi.get_stack()) pulumi.export("instance_id", instance.id)
undefined

Environment-Specific Resources

环境特定资源

Conditional Resource Creation

条件式资源创建

typescript
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;

// Create VPC
const vpc = new aws.ec2.Vpc("main", {
    cidrBlock: "10.0.0.0/16",
    tags: {
        Name: `vpc-${environment}`,
        Environment: environment,
    },
});

// Production gets multiple availability zones
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);
}

// Only create NAT gateway in production
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,
        },
    });
}

// Create RDS with multi-AZ only in production
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;
typescript
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;

// Create VPC
const vpc = new aws.ec2.Vpc("main", {
    cidrBlock: "10.0.0.0/16",
    tags: {
        Name: `vpc-${environment}`,
        Environment: environment,
    },
});

// Production gets multiple availability zones
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);
}

// Only create NAT gateway in production
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,
        },
    });
}

// Create RDS with multi-AZ only in production
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;

Stack References

Stack引用

Cross-Stack References

跨Stack引用

typescript
// Infrastructure stack (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 for other stacks
export const vpcId = vpc.id;
export const subnetId = subnet.id;
export const vpcCidr = vpc.cidrBlock;
typescript
// Application stack (app/index.ts)
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Reference infrastructure stack
const infraStack = new pulumi.StackReference("myorg/infra/prod");

// Get outputs from infrastructure stack
const vpcId = infraStack.getOutput("vpcId");
const subnetId = infraStack.getOutput("subnetId");

// Use referenced values
const securityGroup = new aws.ec2.SecurityGroup("app-sg", {
    vpcId: vpcId,
    description: "Security group for application",
    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;
typescript
// Infrastructure stack (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 for other stacks
export const vpcId = vpc.id;
export const subnetId = subnet.id;
export const vpcCidr = vpc.cidrBlock;
typescript
// Application stack (app/index.ts)
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Reference infrastructure stack
const infraStack = new pulumi.StackReference("myorg/infra/prod");

// Get outputs from infrastructure stack
const vpcId = infraStack.getOutput("vpcId");
const subnetId = infraStack.getOutput("subnetId");

// Use referenced values
const securityGroup = new aws.ec2.SecurityGroup("app-sg", {
    vpcId: vpcId,
    description: "Security group for application",
    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;

Stack Reference Commands

Stack引用命令

bash
undefined
bash
undefined

Deploy infrastructure stack first

Deploy infrastructure stack first

cd infra pulumi stack select prod pulumi up
cd infra pulumi stack select prod pulumi up

Then deploy application stack

Then deploy application stack

cd ../app pulumi stack select prod pulumi up
cd ../app pulumi stack select prod pulumi up

View outputs from referenced stack

View outputs from referenced stack

pulumi stack output --stack myorg/infra/prod
undefined
pulumi stack output --stack myorg/infra/prod
undefined

Stack Outputs

Stack输出

Exporting Stack Outputs

导出Stack输出

typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const environment = config.require("environment");

// Create resources
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 outputs
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 computed values
export const dbConnectionString = pulumi.interpolate`postgresql://admin@${db.endpoint}/myapp`;

// Export stack metadata
export const stackName = pulumi.getStack();
export const projectName = pulumi.getProject();
export const region = aws.getRegion().then(r => r.name);
typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const environment = config.require("environment");

// Create resources
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 outputs
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 computed values
export const dbConnectionString = pulumi.interpolate`postgresql://admin@${db.endpoint}/myapp`;

// Export stack metadata
export const stackName = pulumi.getStack();
export const projectName = pulumi.getProject();
export const region = aws.getRegion().then(r => r.name);

Accessing Stack Outputs

访问Stack输出

bash
undefined
bash
undefined

View all outputs

View all outputs

pulumi stack output
pulumi stack output

Get specific output

Get specific output

pulumi stack output vpcId
pulumi stack output vpcId

Get output as JSON

Get output as JSON

pulumi stack output --json
pulumi stack output --json

Use in shell scripts

Use in shell scripts

VPC_ID=$(pulumi stack output vpcId) echo "VPC ID: $VPC_ID"
VPC_ID=$(pulumi stack output vpcId) echo "VPC ID: $VPC_ID"

Export to environment variables

Export to environment variables

export $(pulumi stack output --json | jq -r 'to_entries[] | "(.key)=(.value)"')
undefined
export $(pulumi stack output --json | jq -r 'to_entries[] | "(.key)=(.value)"')
undefined

Stack Transformations

Stack转换

Global Resource Transformations

全局资源转换

typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const environment = config.require("environment");

// Register global transformation to add tags
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,
    };
});

// All AWS resources automatically get tags
const vpc = new aws.ec2.Vpc("main", {
    cidrBlock: "10.0.0.0/16",
    // tags will be automatically added by transformation
});

const bucket = new aws.s3.Bucket("data", {
    // tags will be automatically added by transformation
});
typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const environment = config.require("environment");

// Register global transformation to add tags
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,
    };
});

// All AWS resources automatically get tags
const vpc = new aws.ec2.Vpc("main", {
    cidrBlock: "10.0.0.0/16",
    // tags will be automatically added by transformation
});

const bucket = new aws.s3.Bucket("data", {
    // tags will be automatically added by transformation
});

Resource-Specific Transformations

特定资源转换

typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const environment = config.require("environment");

// Transformation to enforce encryption
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);

// Resources will be automatically encrypted
const bucket = new aws.s3.Bucket("data");
const db = new aws.rds.Instance("database", {
    engine: "postgres",
    instanceClass: "db.t3.micro",
    allocatedStorage: 20,
});
typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const environment = config.require("environment");

// Transformation to enforce encryption
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);

// Resources will be automatically encrypted
const bucket = new aws.s3.Bucket("data");
const db = new aws.rds.Instance("database", {
    engine: "postgres",
    instanceClass: "db.t3.micro",
    allocatedStorage: 20,
});

Stack Tags and Organization

Stack标签与组织

Tagging Strategy

标签策略

typescript
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();

// Define common tags
const commonTags = {
    Project: project,
    Environment: environment,
    Stack: stack,
    ManagedBy: "Pulumi",
    CostCenter: config.get("costCenter") || "engineering",
    Owner: config.get("owner") || "platform-team",
};

// Helper function to merge tags
function mergeTags(resourceTags?: { [key: string]: string }): { [key: string]: string } {
    return {
        ...commonTags,
        ...resourceTags,
    };
}

// Use consistent tagging
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",
    }),
});
typescript
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();

// Define common tags
const commonTags = {
    Project: project,
    Environment: environment,
    Stack: stack,
    ManagedBy: "Pulumi",
    CostCenter: config.get("costCenter") || "engineering",
    Owner: config.get("owner") || "platform-team",
};

// Helper function to merge tags
function mergeTags(resourceTags?: { [key: string]: string }): { [key: string]: string } {
    return {
        ...commonTags,
        ...resourceTags,
    };
}

// Use consistent tagging
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",
    }),
});

Stack Import and Export

Stack导入与导出

Exporting Stack State

导出Stack状态

bash
undefined
bash
undefined

Export stack state to JSON

Export stack state to JSON

pulumi stack export > stack-state.json
pulumi stack export > stack-state.json

Export to file

Export to file

pulumi stack export --file stack-backup.json
pulumi stack export --file stack-backup.json

Export with secrets in plaintext (use carefully!)

Export with secrets in plaintext (use carefully!)

pulumi stack export --show-secrets > stack-with-secrets.json
undefined
pulumi stack export --show-secrets > stack-with-secrets.json
undefined

Importing Stack State

导入Stack状态

bash
undefined
bash
undefined

Import stack state

Import stack state

pulumi stack import --file stack-state.json
pulumi stack import --file stack-state.json

Import from stdin

Import from stdin

cat stack-state.json | pulumi stack import
undefined
cat stack-state.json | pulumi stack import
undefined

Stack Migration

Stack迁移

bash
undefined
bash
undefined

Export from old stack

Export from old stack

pulumi stack select old-stack pulumi stack export --file old-stack.json
pulumi stack select old-stack pulumi stack export --file old-stack.json

Create and import to new stack

Create and import to new stack

pulumi stack init new-stack pulumi stack import --file old-stack.json
pulumi stack init new-stack pulumi stack import --file old-stack.json

Verify resources

Verify resources

pulumi preview
undefined
pulumi preview
undefined

Multi-Region Deployments

多区域部署

Region-Specific Stacks

区域特定Stack

typescript
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");

// Create region-specific resources
const vpc = new aws.ec2.Vpc(`vpc-${region}`, {
    cidrBlock: "10.0.0.0/16",
    tags: {
        Name: `vpc-${environment}-${region}`,
        Region: region,
        Environment: environment,
    },
});

// Create CloudFront distribution in us-east-1
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 region info
export const deploymentRegion = region;
export const vpcId = vpc.id;
export const certificateArn = certificate.arn;
typescript
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");

// Create region-specific resources
const vpc = new aws.ec2.Vpc(`vpc-${region}`, {
    cidrBlock: "10.0.0.0/16",
    tags: {
        Name: `vpc-${environment}-${region}`,
        Region: region,
        Environment: environment,
    },
});

// Create CloudFront distribution in us-east-1
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 region info
export const deploymentRegion = region;
export const vpcId = vpc.id;
export const certificateArn = certificate.arn;

Multi-Region Stack Configuration

多区域Stack配置

yaml
undefined
yaml
undefined

Pulumi.us-east-1-prod.yaml

Pulumi.us-east-1-prod.yaml

config: aws:region: us-east-1 my-app:environment: production my-app:isPrimaryRegion: "true"
config: aws:region: us-east-1 my-app:environment: production my-app:isPrimaryRegion: "true"

Pulumi.us-west-2-prod.yaml

Pulumi.us-west-2-prod.yaml

config: aws:region: us-west-2 my-app:environment: production my-app:isPrimaryRegion: "false"
config: aws:region: us-west-2 my-app:environment: production my-app:isPrimaryRegion: "false"

Pulumi.eu-west-1-prod.yaml

Pulumi.eu-west-1-prod.yaml

config: aws:region: eu-west-1 my-app:environment: production my-app:isPrimaryRegion: "false"
undefined
config: aws:region: eu-west-1 my-app:environment: production my-app:isPrimaryRegion: "false"
undefined

Stack Policies

Stack策略

Protect Resources

保护资源

typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const environment = config.require("environment");

// Protect production databases
const db = new aws.rds.Instance("database", {
    engine: "postgres",
    instanceClass: "db.t3.micro",
    allocatedStorage: 20,
    tags: {
        Name: `db-${environment}`,
    },
}, {
    protect: environment === "production",
});

// Protect production storage
const bucket = new aws.s3.Bucket("data", {
    tags: {
        Name: `data-${environment}`,
    },
}, {
    protect: environment === "production",
});
typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const environment = config.require("environment");

// Protect production databases
const db = new aws.rds.Instance("database", {
    engine: "postgres",
    instanceClass: "db.t3.micro",
    allocatedStorage: 20,
    tags: {
        Name: `db-${environment}`,
    },
}, {
    protect: environment === "production",
});

// Protect production storage
const bucket = new aws.s3.Bucket("data", {
    tags: {
        Name: `data-${environment}`,
    },
}, {
    protect: environment === "production",
});

Retain Resources

保留资源

typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const environment = config.require("environment");

// Retain production databases on stack deletion
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",
});
typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const environment = config.require("environment");

// Retain production databases on stack deletion
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",
});

Stack Secrets Management

Stack密钥管理

Using Encrypted Secrets

使用加密密钥

bash
undefined
bash
undefined

Set encrypted secrets

Set encrypted secrets

pulumi config set --secret dbPassword mySecurePassword123 pulumi config set --secret apiKey sk_live_abc123xyz789
pulumi config set --secret dbPassword mySecurePassword123 pulumi config set --secret apiKey sk_live_abc123xyz789

View config (secrets are encrypted)

View config (secrets are encrypted)

pulumi config
pulumi config

View secrets in plaintext (use carefully!)

View secrets in plaintext (use carefully!)

pulumi config get dbPassword --show-secrets
undefined
pulumi config get dbPassword --show-secrets
undefined

Secrets in Code

代码中的密钥

typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();

// Get secret values
const dbPassword = config.requireSecret("dbPassword");
const apiKey = config.requireSecret("apiKey");

// Use secrets in resources
const db = new aws.rds.Instance("database", {
    engine: "postgres",
    instanceClass: "db.t3.micro",
    allocatedStorage: 20,
    username: "admin",
    password: dbPassword,
});

// Create SSM parameters from secrets
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,
});

// Secrets are encrypted in state
export const connectionString = pulumi.secret(
    pulumi.interpolate`postgresql://admin:${dbPassword}@${db.endpoint}/myapp`
);
typescript
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();

// Get secret values
const dbPassword = config.requireSecret("dbPassword");
const apiKey = config.requireSecret("apiKey");

// Use secrets in resources
const db = new aws.rds.Instance("database", {
    engine: "postgres",
    instanceClass: "db.t3.micro",
    allocatedStorage: 20,
    username: "admin",
    password: dbPassword,
});

// Create SSM parameters from secrets
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,
});

// Secrets are encrypted in state
export const connectionString = pulumi.secret(
    pulumi.interpolate`postgresql://admin:${dbPassword}@${db.endpoint}/myapp`
);

Stack Refresh and State

Stack刷新与状态

Refresh Stack State

刷新Stack状态

bash
undefined
bash
undefined

Refresh stack to match actual cloud state

Refresh stack to match actual cloud state

pulumi refresh
pulumi refresh

Refresh with auto-approval

Refresh with auto-approval

pulumi refresh --yes
pulumi refresh --yes

Refresh specific resources

Refresh specific resources

pulumi refresh --target urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::my-bucket
pulumi refresh --target urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::my-bucket

Refresh and show diff

Refresh and show diff

pulumi refresh --diff
undefined
pulumi refresh --diff
undefined

Stack State Management

Stack状态管理

bash
undefined
bash
undefined

View stack state

View stack state

pulumi stack --show-urns
pulumi stack --show-urns

View specific resource

View specific resource

pulumi stack --show-urns | grep my-bucket
pulumi stack --show-urns | grep my-bucket

Remove resource from state (doesn't delete cloud resource)

Remove resource from state (doesn't delete cloud resource)

pulumi state delete 'urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::my-bucket'
pulumi state delete 'urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::my-bucket'

Rename resource in state

Rename resource in state

pulumi state rename 'urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::old-name'
'urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::new-name'
undefined
pulumi state rename 'urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::old-name'
'urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::new-name'
undefined

When to Use This Skill

何时使用该技能

Use the
pulumi-stacks
skill when you need to:
  • Deploy infrastructure to multiple environments (dev, staging, prod)
  • Manage environment-specific configurations
  • Create isolated instances of the same infrastructure
  • Share infrastructure outputs between projects
  • Implement multi-region deployments
  • Separate infrastructure concerns (networking, databases, applications)
  • Manage secrets per environment
  • Track infrastructure state per environment
  • Implement progressive deployment strategies
  • Organize complex infrastructure into manageable units
  • Apply environment-specific policies and protections
  • Maintain consistent infrastructure across environments
当你需要以下操作时,使用
pulumi-stacks
技能:
  • 将基础设施部署到多环境(开发、预发布、生产)
  • 管理环境特定配置
  • 创建同一基础设施的独立实例
  • 在项目间共享基础设施输出
  • 实现多区域部署
  • 分离基础设施关注点(网络、数据库、应用)
  • 按环境管理密钥
  • 按环境跟踪基础设施状态
  • 实现渐进式部署策略
  • 将复杂基础设施组织为可管理的单元
  • 应用环境特定的策略与保护措施
  • 保持多环境基础设施的一致性

Best Practices

最佳实践

  1. Naming Convention: Use consistent stack naming like
    <env>
    or
    <region>-<env>
    (e.g.,
    prod
    ,
    us-east-1-prod
    )
  2. Configuration Files: Keep stack config files in version control (except secrets)
  3. Environment Isolation: Never share state between environments; each environment gets its own stack
  4. Stack References: Use stack references instead of duplicating infrastructure code
  5. Secrets Management: Always use
    --secret
    flag for sensitive values
  6. Progressive Deployment: Deploy to dev first, then staging, finally production
  7. State Backups: Regularly export stack state for disaster recovery
  8. Resource Protection: Enable
    protect
    option for critical production resources
  9. Tagging Strategy: Apply consistent tags across all environments for cost tracking
  10. Stack Outputs: Export all values needed by other stacks or external systems
  11. Configuration Validation: Validate configuration values before creating resources
  12. Environment Parity: Keep environments as similar as possible, differing only in scale
  13. Automation: Use CI/CD pipelines for stack deployments
  14. Documentation: Document stack dependencies and required configuration
  15. State Encryption: Use encrypted state backends for sensitive infrastructure
  1. 命名规范:使用一致的Stack命名,如
    <env>
    <region>-<env>
    (例如:
    prod
    us-east-1-prod
  2. 配置文件:将Stack配置文件存入版本控制(密钥除外)
  3. 环境隔离:绝不在环境间共享状态;每个环境使用独立的Stack
  4. Stack引用:使用Stack引用而非重复基础设施代码
  5. 密钥管理:对敏感值始终使用
    --secret
    标志
  6. 渐进式部署:先部署到开发环境,再到预发布环境,最后到生产环境
  7. 状态备份:定期导出Stack状态以用于灾难恢复
  8. 资源保护:为关键生产资源启用
    protect
    选项
  9. 标签策略:在所有环境中应用一致的标签以进行成本跟踪
  10. Stack输出:导出所有其他Stack或外部系统所需的值
  11. 配置验证:在创建资源前验证配置值
  12. 环境一致性:保持环境尽可能相似,仅在规模上有所不同
  13. 自动化:使用CI/CD流水线进行Stack部署
  14. 文档化:记录Stack依赖关系与所需配置
  15. 状态加密:对敏感基础设施使用加密的状态后端

Common Pitfalls

常见误区

  1. Hardcoded Values: Hardcoding environment-specific values instead of using configuration
  2. Shared State: Attempting to share stack state between environments
  3. Missing Config: Deploying to new stack without setting required configuration
  4. Unencrypted Secrets: Storing secrets as plain text in configuration
  5. Inconsistent Naming: Using different naming conventions across stacks
  6. Broken References: Stack references that point to non-existent stacks or outputs
  7. Missing Exports: Not exporting values needed by dependent stacks
  8. Config Drift: Manual changes to config files not reflected in version control
  9. No Resource Protection: Forgetting to protect critical production resources
  10. Stack Sprawl: Creating too many stacks without clear organization
  11. Missing Validation: Not validating configuration before deployment
  12. Circular Dependencies: Creating circular stack references
  13. No Backup Strategy: Not exporting stack state for disaster recovery
  14. Environment Differences: Production significantly different from other environments
  15. Poor Secret Management: Checking encrypted secrets into public repositories without proper key management
  1. 硬编码值:使用硬编码的环境特定值而非配置
  2. 共享状态:尝试在环境间共享Stack状态
  3. 缺失配置:在未设置必要配置的情况下部署到新Stack
  4. 未加密密钥:将密钥以明文形式存储在配置中
  5. 命名不一致:在Stack间使用不同的命名规范
  6. 无效引用:指向不存在的Stack或输出的Stack引用
  7. 缺失导出:未导出依赖Stack所需的值
  8. 配置漂移:配置文件的手动更改未反映在版本控制中
  9. 未保护资源:忘记保护关键生产资源
  10. Stack泛滥:创建过多Stack而缺乏清晰的组织
  11. 缺失验证:部署前未验证配置
  12. 循环依赖:创建循环的Stack引用
  13. 无备份策略:未导出Stack状态以用于灾难恢复
  14. 环境差异过大:生产环境与其他环境差异显著
  15. 密钥管理不当:在未进行适当密钥管理的情况下将加密密钥存入公共仓库

Resources

参考资源