Chapter 09

State 管理与 CI/CD 集成

掌握状态文件管理,构建基础设施的 GitOps 工作流

State 管理概述

什么是 Pulumi State?

Pulumi State 是一个 JSON 文件,记录了 Pulumi 当前管理的所有云资源的快照:包括每个资源的类型、名称、属性和云平台上的实际 ID。每次 pulumi up 后,State 都会更新。

State 是 Pulumi 计算"增量变更"的依据:目标状态(你的代码)减去当前状态(State 文件)= 需要执行的操作。没有 State 文件,Pulumi 就无法知道哪些资源已经存在,会试图重新创建所有资源。

State 后端选择

后端配置优点适用场景
Pulumi Cloud pulumi login 免费、免运维、内置 secrets 加密、Web UI 大多数团队的首选
AWS S3 pulumi login s3://bucket 完全自控、无 SaaS 依赖 安全合规要求高
Azure Blob pulumi login azblob://container Azure 生态集成 以 Azure 为主的团队
GCS pulumi login gs://bucket GCP 生态集成 以 GCP 为主的团队
本地文件 pulumi login --local 简单快速 本地开发测试
# 切换到 S3 后端
pulumi login s3://my-pulumi-state-bucket/prefix

# 切换到 Azure Blob 后端
pulumi login azblob://my-state-container

# 从 Pulumi Cloud 迁移到 S3
pulumi login  # 先登录 Cloud
pulumi stack export > stack-state.json  # 导出状态
pulumi login s3://my-pulumi-state-bucket  # 切换后端
pulumi stack import < stack-state.json    # 导入状态

pulumi refresh:同步漂移

配置漂移(State Drift)是指云资源被 Pulumi 之外的方式修改(手动在控制台改配置、其他工具修改等),导致 Pulumi State 与云上实际状态不一致。

# pulumi refresh:将云上实际状态同步到 State 文件
# (更新 State,不修改代码)
pulumi refresh

# refresh 后再 plan,会显示漂移产生的差异
pulumi preview

# 自动接受 refresh(CI/CD 中)
pulumi refresh --yes
refresh 的注意事项

pulumi refresh 将 State 更新为云上实际状态,可能会"接受"一些手动改动。如果你随后运行 pulumi up,Pulumi 会尝试将这些漂移"修正"回代码描述的目标状态。要保留手动改动,应该先更新代码,再 up。

pulumi import:导入现有资源

pulumi import 将已有的云资源纳入 Pulumi 管理,而不需要销毁再重建。适合"将已有基础设施迁移到 Pulumi"的场景。

# 导入一个 AWS S3 Bucket(格式:resource_type resource_name cloud_id)
pulumi import aws:s3/bucketV2:BucketV2 my-bucket my-existing-bucket-name

# 导入 EC2 实例
pulumi import aws:ec2/instance:Instance web-server i-1234567890abcdef0

# 导入 RDS 实例
pulumi import aws:rds/instance:Instance prod-db my-database-identifier

# 导入 IAM Role
pulumi import aws:iam/role:Role my-role my-existing-role-name

# pulumi import 会输出对应的 Python/TypeScript 代码供参考
# 你需要将这段代码添加到你的程序中
# 批量导入(使用 import 文件,Pulumi v3.55+)
# 创建 import.json
# {
#   "resources": [
#     {"type": "aws:s3/bucketV2:BucketV2", "name": "logs-bucket", "id": "my-logs-bucket"},
#     {"type": "aws:ec2/vpc:Vpc", "name": "main-vpc", "id": "vpc-0a1b2c3d4e5f"}
#   ]
# }

pulumi import --file import.json

GitHub Actions CI/CD 集成

基本工作流:Push to main 自动部署

# .github/workflows/deploy.yml
name: Pulumi Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: |
          python -m venv venv
          source venv/bin/activate
          pip install -r requirements.txt

      - name: Pulumi up
        uses: pulumi/actions@v5
        with:
          command: up
          stack-name: myorg/my-infra/prod
        env:
          PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

PR Preview:预览基础设施变更

# .github/workflows/preview.yml — PR 时预览变更
name: Pulumi Preview

on:
  pull_request:
    branches: [main]

jobs:
  preview:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write    # 需要写 PR 评论的权限
    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: |
          python -m venv venv
          source venv/bin/activate
          pip install -r requirements.txt

      - name: Pulumi Preview
        uses: pulumi/actions@v5
        with:
          command: preview
          stack-name: myorg/my-infra/prod
          comment-on-pr: true        # 将 preview 结果评论到 PR
          github-token: ${{ secrets.GITHUB_TOKEN }}
        env:
          PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

多环境部署流水线

# .github/workflows/multi-env.yml
name: Multi-Env Deploy

on:
  push:
    branches: [main]

jobs:
  deploy-dev:
    runs-on: ubuntu-latest
    environment: dev
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install -r requirements.txt
      - uses: pulumi/actions@v5
        with:
          command: up
          stack-name: myorg/my-infra/dev
        env:
          PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

  deploy-staging:
    needs: deploy-dev       # dev 成功后才部署 staging
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install -r requirements.txt
      - uses: pulumi/actions@v5
        with:
          command: up
          stack-name: myorg/my-infra/staging
        env:
          PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

  deploy-prod:
    needs: deploy-staging   # 需要手动审批(在 GitHub environment 中配置)
    runs-on: ubuntu-latest
    environment: production  # 配置了 required reviewers
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install -r requirements.txt
      - uses: pulumi/actions@v5
        with:
          command: up
          stack-name: myorg/my-infra/prod
        env:
          PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
          AWS_ACCESS_KEY_ID: ${{ secrets.PROD_AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.PROD_AWS_SECRET_ACCESS_KEY }}

Secrets 加密方案

Pulumi Cloud(默认)
登录 Pulumi Cloud 后,secrets 由 Pulumi 托管的密钥加密(每个 Stack 独立密钥)。密文存储在 Pulumi.{stack}.yaml 中,可安全提交 Git。
passphrase
使用环境变量 PULUMI_CONFIG_PASSPHRASE 作为加密密钥。Self-Hosted 后端的标准选择,适合 CI/CD 环境。密钥必须在所有能运行 pulumi 的机器上一致。
AWS KMS
pulumi stack init prod --secrets-provider="awskms://alias/pulumi-secrets"。使用 AWS KMS 密钥加密,权限由 IAM 控制,适合高安全要求场景。
HashiCorp Vault
与企业级 Vault 集成:--secrets-provider="vault://vault.example.com/v1/transit/keys/pulumi"
# CI/CD 中使用 passphrase 加密

# 1. 创建 Stack 时指定 passphrase provider
PULUMI_CONFIG_PASSPHRASE="super-secret-phrase" \
  pulumi stack init prod --secrets-provider=passphrase

# 2. 设置 secret 配置
PULUMI_CONFIG_PASSPHRASE="super-secret-phrase" \
  pulumi config set --secret dbPassword "my-db-password"

# 3. GitHub Actions 中设置为 Repository Secret
# PULUMI_CONFIG_PASSPHRASE: "super-secret-phrase"(在 GitHub Secrets 中)

# 4. CI 中运行 up
# env:
#   PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
获取 PULUMI_ACCESS_TOKEN

在 Pulumi Cloud(app.pulumi.com)中,点击头像 → Settings → Access Tokens → Create Token,生成 Personal Access Token,然后添加到 GitHub Repository Secrets(名称:PULUMI_ACCESS_TOKEN)。每个 CI/CD 环境(dev/staging/prod)建议使用不同的 Token 以便审计追踪。

本章小结

本章核心要点