Communitygithub.com

Pulumi 基礎設施即代碼

使用 TypeScript 原生方式,透過 Pulumi 實現基礎設施即代碼。

Pulumi 基礎設施即代碼 是什麼?

此技能提供了一套完整的 TypeScript 原生基礎設施即代碼 (IaC) 解決方案,基於 Pulumi 框架。它教導開發者如何利用 TypeScript 的型別安全特性來定義雲端基礎設施,重點包括使用 `ComponentResource` 封裝可重用基礎設施元件、使用 `pulumi.interpolate` 安全處理輸出值、使用 `Config.requireSecret()` 管理機密,以及避免在 `.apply()` 內部建立資源等關鍵模式。此技能涵蓋 AWS、GCP、Azure、Kubernetes 等多個平台,並深度探討資源管理、Outputs、Config/Secrets、StackReferences、Transforms、Automation API 和 Policy Packs 等高階主題。其核心價值在於提供一套嚴謹且可靠的最佳實踐,幫助團隊建立可維護、可重用的雲端基礎設施程式碼庫,避免常見陷阱,並確保資源追蹤與部署的可預測性。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/agents-inc/skills/tree/HEAD/src/skills/infra-iac-pulumi

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Pulumi 基礎設施即代碼 是做什麼的?

快速指南: 在 TypeScript 中以完整的型別安全性定義雲端基礎設施。使用 ComponentResource 封裝可重複使用的基礎設施模式。在元件內的所有子資源中傳遞 { parent: this }。使用 pulumi.interpolate 搭配 Outputs 來建構字串(而非字串串接)。永遠不要在 .apply() 內部建立資源。使用 Config.requireSecret() 處理敏感值。偏好使用 transforms 而非已棄用的 transformations。在元件建構子的結尾務必呼叫 this.registerOutputs()。


<critical_requirements>

關鍵:使用此技能前須知

所有程式碼必須遵循 CLAUDE.md 中的專案慣例(kebab-case、named exports、import 排序、import type、named constants)

(你必須在所有 ComponentResource 內部的子資源中傳遞 { parent: this } —— 省略它會破壞資源樹和狀態追蹤)

(你必須使用 pulumi.interpolate 搭配 Outputs 來建構字串 —— 字串串接會靜默地產生 [object Object])

(你絕對不能夠在 .apply() 內部建立資源 —— 它們不會出現在 pulumi preview 中,並會導致排序問題)

(你必須使用 Config.requireSecret() 處理敏感值 —— Config.require() 會將值以明文儲存在狀態中)

(你必須在每個 ComponentResource 建構子的結尾呼叫 this.registerOutputs() —— 省略它會妨礙輸出追蹤)

</critical_requirements>


詳細資源:

  • examples/core.md - 資源定義、元件資源、命名、Outputs、Config/Secrets
  • examples/advanced.md - Stack references、transforms、dynamic providers、Automation API、policy packs
  • reference.md - 決策框架、資源選項表、API 參考、CLI 命令

自動偵測: Pulumi, @pulumi/pulumi, @pulumi/aws, @pulumi/gcp, @pulumi/azure, @pulumi/kubernetes, pulumi.ComponentResource, pulumi.CustomResource, pulumi.Output, pulumi.Config, pulumi.interpolate, pulumi.all, registerOutputs, StackReference, ComponentResourceOptions, CustomResourceOptions, dynamic.Resource, dynamic.ResourceProvider, LocalWorkspace, InlineProgramArgs, Automation API, CrossGuard, PolicyPack

使用時機:

  • 使用 TypeScript 與型別安全的資源 API 定義雲端基礎設施
  • 使用 ComponentResource 建立可重複使用的基礎設施元件
  • 使用 stack references 管理多堆疊架構
  • 處理機密與環境特定設定
  • 使用 Automation API 建立自助式基礎設施平台
  • 使用 CrossGuard policy packs 撰寫合規政策

不應使用時機:

  • 一次性建立單一資源的 shell 腳本(請直接使用雲端 CLI)
  • 團隊沒有 TypeScript 經驗的專案(請考慮其他 IaC 語言選項)

涵蓋的主要模式:

  • 帶有型別輸入和自動命名的資源定義
  • ComponentResource 封裝(parent、naming、registerOutputs)
  • Outputs:apply、all、interpolate 和 lifting
  • Config 與 secrets 管理(Config.require、Config.requireSecret、pulumi.secret)
  • 用於跨堆疊資料共享的 Stack references
  • 資源選項(dependsOn、protect、aliases、ignoreChanges、transforms)
  • 用於自訂 CRUD 資源的 Dynamic providers
  • 用於程式化堆疊管理的 Automation API
  • 用於合規強制的 CrossGuard policy packs

哲學

Pulumi 將基礎設施視為真正的程式碼,而非設定檔案。TypeScript 為您提供型別安全、IDE 自動補全、重構工具以及完整的 Node.js 生態系。資源是物件,依賴關係是自動的,而重複使用是透過函式和類別來實現——而非自訂的模組語言。

核心原則:

  • 資源是物件:每個雲端資源都是一個具有型別輸入和輸出的 TypeScript 類別實例
  • 依賴關係是自動的:當您將一個資源的輸出作為另一個資源的輸入時,Pulumi 會推斷出依賴關係圖
  • 透過元件重複使用:ComponentResource 將多個資源封裝成一個具有自己輸入和輸出的單一邏輯單元
  • Outputs 是 promises:Output<T> 代表一個在部署後才可能知道的值——請使用 apply、all 或 interpolate 處理它們,切勿手動展開
  • 狀態是被管理的:Pulumi 會追蹤狀態中的每個資源——除非您使用 aliases,否則更改邏輯名稱或在檔案之間移動資源會觸發刪除並重新建立

核心模式

模式 1:資源定義

每個資源都接受一個邏輯名稱(用於狀態追蹤)、一個 args 包(型別輸入)以及可選的資源選項。

const bucket = new aws.s3.Bucket(
  "data-bucket",
  {
    versioning: { enabled: true },
    lifecycleRules: [
      { enabled: true, expiration: { days: BUCKET_EXPIRY_DAYS } },
    ],
  },
  { protect: true },
); // 防止意外刪除

要點: 邏輯名稱在堆疊內必須是每種類型唯一的。Pulumi 會自動附加一個隨機後綴到實體名稱以防止衝突。在關鍵資源上使用 protect: true。匯出輸出以供跨堆疊使用。

請參閱 examples/core.md 了解資源命名、自動命名設定和提供者選項。


模式 2:ComponentResource 封裝

將相關資源包裝在一個 ComponentResource 中,以建立可重複使用的基礎設施單元。

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

interface StaticSiteArgs {
  indexDocument?: string;
  errorDocument?: string;
}

class StaticSite extends pulumi.ComponentResource {
  public readonly bucketName: pulumi.Output<string>;
  public readonly websiteUrl: pulumi.Output<string>;

  constructor(
    name: string,
    args: StaticSiteArgs,
    opts?: pulumi.ComponentResourceOptions,
  ) {
    super("myinfra:web:StaticSite", name, args, opts);

    const bucket = new aws.s3.BucketV2(`${name}-bucket`, {}, { parent: this });

    const website = new aws.s3.BucketWebsiteConfigurationV2(
      `${name}-website`,
      {
        bucket: bucket.id,
        indexDocument: { suffix: args.indexDocument ?? "index.html" },
        errorDocument: { key: args.errorDocument ?? "error.html" },
      },
      { parent: this },
    );

    this.bucketName = bucket.id;
    this.websiteUrl = website.websiteEndpoint;
    this.registerOutputs({
      bucketName: this.bucketName,
      websiteUrl: this.websiteUrl,
    });
  }
}

為什麼好: 子資源使用 { parent: this },名稱從父資源前綴,呼叫了 registerOutputs,型別 token 遵循 pkg:module:Type 格式

請參閱 examples/core.md 了解包含提供者繼承和多元件資源的完整元件模式。


模式 3:使用 Outputs

Outputs 代表在部署後才解析的值。絕不要使用字串串接——請使用 interpolate、apply 或 all。

// interpolate -- 用於字串建構的標籤模板(首選)
const url = pulumi.interpolate`https://${bucket.bucketRegionalDomainName}/index.html`;

// apply -- 轉換單個輸出
const upper = bucket.id.apply((id) => id.toUpperCase());

// all -- 組合多個輸出
const endpoint = pulumi
  .all([lb.dnsName, listener.port])
  .apply(([dns, port]) => `http://${dns}:${port}`);

// Lifting -- 直接存取資源輸出上的屬性
const subnetId = vpc.subnets[0].id; // 已知屬性無需 apply

為什麼好: interpolate 透明地處理 Output 值,all 等待多個值,lifting 避免了不必要的 apply 呼叫

陷阱: 在 .apply() 內部建立的資源不會出現在 pulumi preview 中,並可能導致排序問題。務必將 Outputs 直接作為輸入傳遞給其他資源。

請參閱 examples/core.md 了解 Output 模式、pulumi.output() 包裝以及 apply 反模式。


模式 4:Config 與 Secrets

使用 pulumi.Config 處理堆疊特定的值。使用 requireSecret 處理敏感資料——它會將值加密在狀態中。

const config = new pulumi.Config();

// 純文字設定值
const region = config.require("region"); // 如果缺少則失敗
const nodeCount = config.getNumber("nodeCount"); // 如果缺少則回傳 undefined

// 秘密值 -- 在狀態中加密
const dbPassword = config.requireSecret("dbPassword");
const apiKey = config.getSecret("apiKey");

// 將程式化值標記為秘密
const connectionString = pulumi.interpolate`postgres://admin:${dbPassword}@${db.endpoint}/mydb`;
// connectionString 自動成為秘密,因為 dbPassword 是秘密

// 明確將值標記為秘密
const token = pulumi.secret(generateToken());

要點: 任何從秘密衍生出的 Output 都會自動標記為秘密。您無需重新標記衍生值。

請參閱 examples/core.md 了解命名空間 config、秘密輸出和 config set CLI 命令。


模式 5:資源選項

資源選項控制生命週期行為。最重要的選項如下:

const db = new aws.rds.Instance(
  "primary-db",
  {
    /* ... */
  },
  {
    protect: true, // 防止意外刪除
    dependsOn: [vpc, securityGroup], // 明確排序
    ignoreChanges: ["tags"], // 忽略特定屬性的漂移
    aliases: [{ name: "old-db-name" }], // 重新命名而不重新建立
    retainOnDelete: true, // 在 pulumi destroy 時保留雲端資源
    deleteBeforeReplace: true, // 對於必須唯一的資源
    replaceOnChanges: ["engine"], // 對特定變更強制替換
    provider: usEastProvider, // 明確的提供者(區域、帳戶)
  },
);

陷阱: 更改資源的邏輯名稱或父資源會導致 Pulumi 刪除並重新建立它。使用 aliases 以安全重新命名。

請參閱 reference.md 了解完整的資源選項表。


模式 6:Stack References

使用 StackReference 在堆疊之間共享輸出。

// 在網路堆疊中:匯出輸出
export const vpcId = vpc.id;
export const subnetIds = subnets.map((s) => s.id);

// 在應用程式堆疊中:使用輸出
const networkStack = new pulumi.StackReference("myorg/networking/prod");
const vpcId = networkStack.getOutput("vpcId");
const subnetIds = networkStack.getOutput("subnetIds");

// requireOutput 在輸出不存在時會失敗(比 getOutput 更安全)
const vpcIdRequired = networkStack.requireOutput("vpcId");

請參閱 examples/advanced.md 了解 stack reference 模式和 getOutputDetails。


模式 7:Transforms

對資源及其子資源套用轉換。使用 transforms(而非已棄用的 transformations)。Transforms 接收一個帶有 type、props 和 opts 的 args 物件,並回傳修改後的結果或 undefined 以跳過。

// 資源層級:將標籤套用至元件的所有可標籤子資源
const vpc = new MyVpcComponent(
  "vpc",
  {},
  {
    transforms: [
      (args) => {
        if (isTaggable(args.type)) {
          return {
            props: {
              ...args.props,
              tags: { ...args.props["tags"], ManagedBy: "pulumi" },
            },
            opts: args.opts,
          };
        }
        return undefined;
      },
    ],
  },
);

與棄用 transformations 的關鍵差異: transforms 支援修改打包的元件子資源(awsx、eks),支援非同步回呼,且不傳遞 Resource 物件。

請參閱 examples/advanced.md 了解堆疊層級 transforms、選項修改以及從 transformations 遷移。


模式 8:Automation API

無需 CLI 即可程式化執行 Pulumi——適用於自助平台、整合測試或自訂部署工具。

const stack = await LocalWorkspace.createOrSelectStack({
  stackName: "dev",
  projectName: "my-platform",
  program: async () => {
    const bucket = new aws.s3.Bucket("auto-bucket");
    return { bucketName: bucket.id };
  },
});
const upResult = await stack.up({ onOutput: console.log });

要點: Automation API 需要在系統上安裝 Pulumi CLI 並將其加入 PATH——它底層使用 CLI 的引擎。

請參閱 examples/advanced.md 了解 preview、destroy、本地程式模式、config 設置和堆疊輸出檢索。


<red_flags>

紅旗標誌

高優先級:

  • 在 .apply() 內部建立資源——它們不會出現在 pulumi preview 中,會導致排序問題,並破壞依賴關係圖。請將 Outputs 直接作為資源輸入傳遞。
  • ComponentResource 子資源遺漏 { parent: this }——資源會出現在狀態樹的根層級,破壞邏輯分組和元件刪除級聯。
  • 使用 Outputs 進行字串串接——"https://" + bucket.id 會產生 [object Object]。請改用 pulumi.interpolate。
  • 使用 Config.require() 處理密碼/金鑰——會將值以明文儲存在狀態中。請使用 Config.requireSecret() 來加密。
  • 忘記 this.registerOutputs()——元件的輸出將無法在狀態中正確追蹤,或無法透過 stack references 使用。
  • 在沒有 aliases 的情況下變更邏輯名稱——Pulumi 會刪除並重新建立資源。請使用 aliases: [{ name: "old-name" }] 以安全重新命名。

中優先級:

  • 在多區域設定中依賴預設提供者——請為每個區域使用明確的提供者。在 config 中設定 pulumi:disable-default-providers 以強制執行。
  • 從函式而不是 ComponentResource 開始——稍後遷移需要 aliases 或資源重新建立。請從一開始就使用元件。
  • 未對不明顯的依賴使用 dependsOn——Pulumi 會從 Input/Output 連線推斷依賴關係,但副作用(如 IAM 傳播)可能需要明確的 dependsOn。

</red_flags>


<anti_patterns>

反模式

字串串接反模式:

// ❌ 錯誤:產生 "[object Object]"
const url = "https://" + bucket.bucketRegionalDomainName + "/index.html";

// ✅ 正確:使用 pulumi.interpolate
const url = pulumi.interpolate`https://${bucket.bucketRegionalDomainName}/index.html`;

在 apply 中建立資源反模式:

// ❌ 錯誤:資源在 preview 中不可見
someOutput.apply((value) => {
  const bucket = new aws.s3.Bucket("dynamic-bucket"); // 隱藏資源!
});

// ✅ 正確:將 Output 傳遞給 resource input
const bucketName = pulumi.interpolate`bucket-${someOutput}`;
const bucket = new aws.s3.Bucket("static-bucket", {
  bucketName: bucketName
});

</anti_patterns>


版本的上下文: Pulumi 3.x 及之後版本。transforms 需要 @pulumi/pulumi >= 3.92.0。Automation API 需要 Pulumi CLI 並將其加入 PATH。CrossGuard 政策以效能為考量,在 TypeScript 中撰寫(不適用於 YAML 專案)。

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

相關技能