こんにちは!クラウドインフラの世界へようこそ。
日々、膨大なインフラコード(IaC)を書いていると、似たような構成の繰り返しにうんざりすることはありませんか?
「またVPCとパブリックサブネット、NATゲートウェイのセットを書いている……」
「開発チームごとに、S3バケットのセキュリティ設定のボイラープレート(お決まりのコード)をコピペしている……」
そんな毎日の単調な作業からあなたを解放し、インフラコードを美しく、再利用可能な「ソフトウェア」へと昇華させるための最強の武器が、Pulumi Component Resources(カスタムコンポーネント)です。
これをマスターすれば、複雑なインフラ構成をカプセル化し、チームメンバーはたった数行の直感的なコードを書くだけで、セキュアでベストプラクティスに準拠したクラウド環境を爆速で構築できるようになります。
今日は、その極意を一緒に紐解いていきましょう。
—
1. コンポーネントリソースとは何か(再利用性の向上)
従来のIaCにおける「コピペの呪縛」
Terraformのモジュールや、従来のIaCツールでも再利用の試みはされてきました。しかし、多くの場合それは「ただの文字列のテンプレート」に近く、型安全性が欠けていたり、内部の複雑さがそのまま呼び出し側に漏れ出したりしていました。
Pulumiがもたらすパラダイムシフト
Pulumiの最大の特徴は、TypeScriptやPythonといった「本物のプログラミング言語」でインフラを書ける点です。
そして、その強みを極限まで活かしたのが「Component Resources(カスタムコンポーネント)」です。
カスタムコンポーネントとは、複数のクラウド・リソース(例:VPC、サブネット、ルートテーブル、インターネットゲートウェイ)を1つに束ね、「独自の大きなリソース」として定義し直す仕組みです。
オブジェクト指向プログラミングで言うところの「クラス」や「カプセル化」に非常に近いです。内部の実装詳細(何個のサブネットを作ったかなど)を隠蔽し、外側からはクリーンなインターフェース(入力と出力)だけを提供します。
—
2. TypeScriptを用いたカスタムコンポーネントの実装ハンズオン
百聞は一見にしかず。実際に手を動かして、セキュアなS3バケットを簡単に作れるカスタムコンポーネントを作ってみましょう。
このコンポーネントは、以下の「ベストプラクティス」を自動で適用します:
1. パブリックアクセスを完全ブロックする
2. デフォルトでサーバーサイド暗号化(AES256)を有効にする
3. バージョニングを有効にする
前提条件
手元にNode.jsとPulumi CLIがインストールされている環境を想定しています。
ステップ1: プロジェクトの初期化
適当なディレクトリを作成し、Pulumiプロジェクトを初期化します。
mkdir pulumi-component-demo
cd pulumi-component-demo
pulumi new aws-typescript –dir . –force –yes
ステップ2: カスタムコンポーネントの作成
プロジェクト内に `components` ディレクトリを作り、その中に `secureBucket.ts` というファイルを作成します。
import as aws from “@pulumi/aws”;
import as pulumi from “@pulumi/pulumi”;
// 1. コンポーネントに入力するプロパティ(引数)の型定義
export interface SecureBucketArgs {
bucketName: string;
environment: “dev” | “prod”;
}
// 2. カスタムコンポーネントクラスの定義
export class SecureBucket extends pulumi.ComponentResource {
// 外部に公開する出力プロパティ
public readonly bucket: aws.s3.Bucket;
public readonly bucketArn: pulumi.Output
constructor(name: string, args: SecureBucketArgs, opts?: pulumi.ComponentResourceOptions) {
// 親クラス(pulumi.ComponentResource)のコンストラクタを呼び出す
// 第1引数には、リソースタイプの名前空間を指定します(例: “custom:storage:SecureBucket”)
super(“custom:storage:SecureBucket”, name, {}, opts);
// コンポーネント内でのリソース作成には、optsに { parent: this } を渡すのが鉄則です!
// これにより、Pulumiのグラフ上で「このS3バケットはこのコンポーネントの子である」と認識されます。
// S3バケット本体の作成
this.bucket = new aws.s3.Bucket(`${name}-bucket`, {
bucket: args.bucketName,
tags: {
Environment: args.environment,
ManagedBy: “Pulumi”,
},
}, { parent: this });
// バージョニングの有効化
new aws.s3.BucketVersioningV2(`${name}-versioning`, {
bucket: this.bucket.id,
versioningConfiguration: {
status: “Enabled”,
},
}, { parent: this });
// パブリックアクセスの完全ブロック
new aws.s3.BucketPublicAccessBlock(`${name}-pab`, {
bucket: this.bucket.id,
blockPublicAcls: true,
blockPublicPolicy: true,
ignorePublicAcls: true,
restrictPublicBuckets: true,
}, { parent: this });
// 外部にARNを公開
this.bucketArn = this.bucket.arn;
// このコンポーネントが管理するリソースの登録を完了する
this.registerOutputs({
bucketArn: this.bucketArn,
});
}
}
> 💡 先輩エンジニアからの極意:
> コード内の `{ parent: this }` に注目してください。これを忘れると、Pulumiのリソース依存関係グラフが正しく構築されず、プレビューやデプロイの挙動がおかしくなります。カスタムコンポーネントを作る際は、子リソースに必ず `parent` を渡すことを忘れないでくださいね。
ステップ3: 実際に使ってみる(`index.ts` の書き換え)
それでは、先ほど作ったカスタムコンポーネントを `index.ts` から呼び出してみましょう。
import as pulumi from “@pulumi/pulumi”;
import { SecureBucket } from “./components/secureBucket”;
// たったこれだけの記述で、セキュアなS3バケットが爆誕します!
const myAppStorage = new SecureBucket(“app-storage”, {
bucketName: `my-super-unique-company-bucket-${pulumi.getStack()}`,
environment: “dev”,
});
// 出力をエクスポート
export const bucketArn = myAppStorage.bucketArn;
たったこれだけです!従来のIaCなら数十行に及ぶ設定が、わずか数行の意図が明確なコードに凝縮されました。
—
3. 組織内でインフラのベストプラクティスをモジュール化するコツ
個人や小規模なチームであれば上記のようにファイルを分けるだけで十分ですが、組織全体(複数チーム)でインフラの標準化を図る場合は、さらに一歩進める必要があります。
1. NPMパッケージとして切り出す
作成したカスタムコンポーネント群を、社内プライベートNPMレジストリ(GitHub PackagesやAWS CodeArtifactなど)にパブリッシュします。
これにより、インフラチームは以下のような美しいパッケージを提供できるようになります。
import { SecureVpc } from “@company/pulumi-aws-secure-vpc”;
import { CompliantDatabase } from “@company/pulumi-aws-compliant-db”;
開発チームのエンジニアは、AWSの複雑なIAMやセキュリティグループの細かい仕様を意識することなく、パッケージをインストールして使うだけで「社内基準を100%満たした安全なインフラ」を手に入れることができます。
2. 命名規則とタグ付けの強制
カスタムコンポーネントの内部で、リソース名やタグ(CostCenter, Ownerなど)の付与を自動化・強制します。これにより、「野良リソース」の発生を根絶し、クラウドガバナンスを強固に保つことができます。
—
4. ユニットテストの書き方
「インフラのテストなんてどうせ結合テストで実際にAWSを作るんでしょ?」と思ったそこのあなた。
Pulumiの大きな強みの一つが、「クラウドプロバイダに接続しなくても、高速にユニットテストが書けること」です。
Jestなどのテストフレームワークを使い、カスタムコンポーネントが正しくリソースを生成しているかをモックして検証できます。
試しに、Jestを使った簡単なテストコードを見てみましょう。
import as pulumi from “@pulumi/pulumi”;
// Pulumiのモック機能を有効化
pulumi.runtime.setMocks({
newResource: (_args: pulumi.runtime.MockResourceArgs): { id: string, state: Record
return {
id: `${_args.name}-id`,
state: _args.inputs,
};
},
call: (_args: pulumi.runtime.MockCallArgs) => {
return {};
},
});
import { SecureBucket } from “./components/secureBucket”;
describe(“SecureBucket Component”, () => {
it(“should create a bucket with public access blocked”, async () => {
// テスト対象のコンポーネントをインスタンス化
const bucketComponent = new SecureBucket(“test-bucket”, {
bucketName: “my-test-bucket”,
environment: “dev”,
});
// Pulumiの出力値が解決されるのを待って検証
// (実際にはAWSへリクエストを送らず、モックされた状態を検証します)
await new Promise
pulumi.all([bucketComponent.bucket.bucket]).apply(([bucketName]) => {
expect(bucketName).toBe(“my-test-bucket”);
resolve();
});
});
});
});
このように、インフラのコードも「通常のアプリケーションコードと同じようにテスト駆動で書く」ことが可能になります。これが、IaCの品質を劇的に高める秘訣です。
—
まとめ
今回は、Pulumi Component Resourcesを使ったカスタムコンポーネントの作り方と設計パターンについて解説しました。
- コンポーネントリソースで複雑なインフラをカプセル化する
- TypeScriptの型安全性とオブジェクト指向の恩恵をフルに受ける
- 組織のベストプラクティスをコードとして共有・強制する
- ユニットテストでCI/CDパイプラインの品質を担保する
これをマスターすれば、毎日のインフラ作業が驚くほど楽になり、より創造的で価値のあるアーキテクチャ設計に集中できるようになりますよ。
さあ、今日の業務から、あなたのチームのための「最高のカスタムコンポーネント」を書き始めてみませんか?