【実務・中級編】Pulumiカスタムプロバイダ開発入門:Bridge技術を使って独自APIや社内システムをIaC化する – インフラ構成管理(IaC)活用バイブル

Pulumiカスタムプロバイダ開発入門:Bridge技術とSchema駆動で社内レガシー・独自APIを完全IaC化する

テックリードの君なら、こんな絶望を味わったことが一度はあるはずだ。
「AWSもKubernetesも綺麗にPulumiでコード化できた。しかし、なぜ社内の自製プロビジョニングAPIや、古のオンプレ連携基盤、ドキュメントすらない社内REST APIだけは、いまだに手動のCurlスクリプトやスパゲッティ状態のシェル芸で叩いているのか?」

世の中のすべてのインフラストラクチャが、最初から綺麗にAWS ProviderやKubernetes Providerで完結しているわけではない。独自のドメインロジクを持つ社内システム、認証基盤、クラウド上の特異な自製コンポーネント――これらを既存のIaCツールに組み込もうとした瞬間、多くのエンジニアは諦め、シェルスクリプトに逃げる。

だが、待ってほしい。君の手元にはPulumiがある。
Pulumiの真髄は、TypeScript, Python, Goといった汎用プログラミング言語でインフラを記述できることだけではない。「あらゆるAPIを、強靭な型安全性を持つIaCリソースへと昇華させる拡張性」にこそ、その本質がある。

今回は、既存のTerraformプロバイダを再利用するPulumi Bridge(ブリッジ)技術と、JSON Schemaから完全にゼロベースでプロバイダを錬成するPulumi Schema駆動開発の奥義を、実務の現場ですぐに使えるコードと共に伝授しよう。

—

1. なぜ「自作プロバイダ」なのか?(Terraform Bridge vs Schema駆動)

社内APIや独自システムをIaC化するアプローチには、大きく分けて2つの道がある。

1. Terraform Provider Bridge:
すでに社内でTerraformのカスタムプロバイダ(Go製)が存在する場合、あるいはゼロから作る場合でも、Terraformのプラグインエコシステム(CRUDのライフサイクル管理やステート管理)をそのまま流用し、数行のラッパー設定でPulumiプロバイダへ変換する手法。
2. Pulumi Schema駆動開発:
Terraformの影を踏まず、純粋なPulumi Schema(JSON/YAML)を定義し、それをベースにSDK(TypeScript/Python/Go等)を自動生成する手法。独自REST APIのラッパーを極めてクリーンに実装したい場合に最適。

今回は、この両者のアプローチを実務の文脈に落とし込んで解説する。

—

2. 開発スピードを劇的に高める「プロの環境構築」

カスタムプロバイダ開発において、毎回のコンパイルとバイナリ配置の往復は開発者の寿命を縮める。以下のツールチェーンとショートカットを仕込み、フィードバックループを限界まで短縮せよ。

絶対に入れるべき神ツール & プラグイン

  • `pulumi-language-go` / `pulumi-tfgen`: ブリッジ開発の要。
  • Direnv: ディレクトリ移動時にローカルビルドしたプロバイダバイナリのパス(`PATH`)を自動で切り替える。
  • jq + httpie: デバッグ時のAPIレスポンス検証の必須セット。

開発効率を爆上げするVS Code / GoLand設定

カスタムプロバイダはGo言語で記述することが多いため、保存時の自動コード整形(`goimports`)と、Pulumiリソースのスキーマ検証を厳密に行う設定が必須だ。

// .vscode/settings.json のベストプラクティス
{
“editor.formatOnSave”: true,
“[go]”: {
“editor.codeActionsOnSave”: {
“source.organizeImports”: true
}
},
“files.associations”: {
“pulumi.json”: “jsonc”
}
}

—

3. 実践:Terraform Bridgeを使った既存APIのPulumi化

すでに社内でTerraformのカスタムプロバイダ(例:社内ユーザー管理APIを叩く `terraform-provider-internalapp`)があるとする。これをPulumiにブリッジする手順は、驚くほどエレガントだ。

Step 1: ブリッジプロジェクトの構造化

`pulumi-tf-provider-boilerplate` をベースに、以下のようなディレクトリ構成を構築する。

pulumi-internalapp/
├── provider/
│ ├── cmd/
│ │ └── pulumi-resource-internalapp/
│ │ └── main.go # エントリーポイント
│ └── resources.go # TerraformリソースとPulumiの紐付け
├── sdk/ # 自動生成される多言語SDK
└── go.mod

Step 2: `resources.go` でのブリッジ定義

ここで、Terraformのリソース名をPulumiの世界へとマッピングする。型安全性を担保するための肝となるコードだ。

// provider/resources.go
package internalapp

import (
“fmt”
“path/filepath”

“github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge”
shimv2 “github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim/sdk-v2”
internalapp “github.com/your-org/terraform-provider-internalapp/internalapp”
“github.com/your-org/pulumi-internalapp/provider/pkg/version”
)

const (
mainPkg = “internalapp”
mainMod = “index”
)

// Provider は Pulumi プロバイダのメタデータを返します。
func Provider() tfbridge.ProviderInfo {
// 既存のTerraformプロバイダのShimを取得
p := shimv2.NewProvider(internalapp.Provider())

prov := tfbridge.ProviderInfo{
P: p,
Name: “internalapp”,
Description: “A Pulumi provider for interacting with Internal Corp Legacy APIs.”,
Keywords: []string{“pulumi”, “internalapp”, “category/utility”},
License: “Apache-2.0”,
Homepage: “https://internal.company.com/docs/pulumi-internalapp”,
Repository: “https://github.com/your-org/pulumi-internalapp”,
GitHubOrg: “your-org”,
Config: map[string]tfbridge.SchemaInfo{
// APIトークンなどの機密設定はSecretとして扱う
“api_token”: {
Secret: tfbridge.Bool(true),
},
},
Resources: map[string]tfbridge.ResourceInfo{
“internalapp_user”: {
Tok: tfbridge.MakeResource(mainPkg, mainMod, “User”),
Fields: map[string]tfbridge.SchemaInfo{
“email”: {
// スキーマのバリデーションや変換ルールをここに記述
CSharpName: “UserEmail”,
},
},
},
},
DataSources: map[string]tfbridge.DataSourceInfo{
“internalapp_user”: {
Tok: tfbridge.MakeDataSource(mainPkg, mainMod, “getUser”),
},
},
Java: &tfbridge.JavaInfo{
BasePackage: “com.internal.pulumi”,
},
}

prov.SetAutonaming(255, “-“)
return prov
}

このコードをコンパイルし、`pulumi-resource-internalapp` バイナリを生成するだけで、TypeScriptやPythonから `new internalapp.User(…)` として社内APIを叩けるようになる。これがブリッジの魔力だ。

—

4. 応用:Pulumi Schema駆動による「ゼロからのカスタムプロバイダ開発」

Terraformの遺産がない、あるいは純粋にREST APIを綺麗にラップしたい場合は、Pulumi Schema (JSON) を直接定義するアプローチを取る。

完全な `schema.json` のベストプラクティス

以下は、社内の「仮想マシン自動承認システム(Approval Gate API)」を制御するためのスキーマ定義だ。

{
“name”: “approvalgate”,
“version”: “0.1.0”,
“displayName”: “Approval Gate Provider”,
“description”: “A Pulumi provider for managing corporate deployment approvals via internal REST API.”,
“language”: {
“nodejs”: {
“packageName”: “@internal/pulumi-approvalgate”
},
“python”: {
“packageName”: “pulumi_approvalgate”
},
“go”: {
“importBasePath”: “github.com/your-org/pulumi-approvalgate/sdk/go/approvalgate”
}
},
“config”: {
“properties”: {
“endpoint”: {
“type”: “string”,
“description”: “The base URL of the Approval Gate API.”
},
“hmacSecret”: {
“type”: “string”,
“secret”: true,
“description”: “HMAC secret for signing API requests.”
}
},
“required”: [“endpoint”, “hmacSecret”]
},
“resources”: {
“approvalgate:index:Gate”: {
“properties”: {
“serviceName”: {
“type”: “string”,
“description”: “Name of the service requesting deployment.”
},
“approverGroup”: {
“type”: “string”,
“description”: “LDAP group required to approve.”
},
“status”: {
“type”: “string”,
“description”: “Current status of the gate (PENDING, APPROVED, REJECTED).”
}
},
“required”: [“serviceName”, “approverGroup”],
“stateInputs”: true
}
}
}

プロバイダサーバーの実装(Go)

Schemaから生成されたコードを基盤に、実際のHTTPクライアントを叩くプロバイダサーバーを実装する。

// main.go (Schema駆動プロバイダの実装骨子)
package main

import (
“context”
“fmt”
“github.com/pulumi/pulumi/sdk/v3/go/common/resource”
p “github.com/pulumi/pulumi/sdk/v3/go/pulumi/provider”
)

type GateProvider struct {
p.UnimplementedProvider
}

// Create メソッドで社内REST APIの POST を実行する
func (e GateProvider) Create(ctx context.Context, req p.CreateRequest) (p.CreateResponse, error) {
inputs := req.Plan
serviceName := inputs[“serviceName”].StringValue()
approverGroup := inputs[“approverGroup”].StringValue()

// TODO: 社内APIへのHTTP POSTリクエスト実装
// resp, err := httpClient.Post(…)

outState := map[string]interface{}{
“serviceName”: serviceName,
“approverGroup”: approverGroup,
“status”: “PENDING”, // 初期状態
}

id := fmt.Sprintf(“gate-%s”, serviceName)
return &p.CreateResponse{
Id: id,
Properties: resource.NewPropertyMapFromMap(outState),
}, nil
}

—

5. チーム開発で役立つ設定の共有化ルールと冪等性の担保

カスタムプロバイダを組織に導入する際、最も重要なのは「冪等性(Idempotency)」の担保と設定のガバナンスだ。

1. 冪等性の罠:APIの挙動をPulumiに正しく調停させる

レガシーな社内APIは、「すでに存在するリソースに対してPOSTを投げるとエラーになる」「更新(PUT)と作成(POST)のエンドポイントが分かれている」といった行儀の悪い設計が多々ある。
Pulumiのカスタムプロバイダ側(あるいはブリッジ元のTerraform側)で、必ず以下のハンドリングを実装せよ。

  • Read(GET)時の404ハンドリング: リソースが外部で削除されていた場合、Pulumiのステート側で `Id: “”` を返し、自動的に再作成(Create)を誘発させること。
  • リトライロジック: 社内基盤の気まぐれな一時エラー(503やタイムアウト)に対しては、指数バックオフ(Exponential Backoff)によるリトライをプロバイダ内部に必ず埋め込むこと。

2. 設定ファイル(YAML)のベストプラクティス構成

複数環境(Staging / Production)でカスタムプロバイダを安全に共有するため、PulumiのConfig(`Pulumi..yaml`)は以下のように構造化せよ。

Pulumi.production.yaml
config:
aws:region: ap-northeast-1
# カスタムプロバイダの設定
approvalgate:endpoint: https://gate.internal.company.net/v1
approvalgate:hmacSecret:
secure: AAABAHvK… (環境変数 PULUMI_CONFIG_PASSPHRASE または KMS で暗号化)

これをチームメンバー全員が `pulumi config set` で手動設定するのではなく、リポジトリ内の `Pulumi.yaml` と共に管理し、CI/CDパイプライン(GitHub Actions等)で以下のように環境変数経由でセキュアに注入するルールを徹底する。

.github/workflows/deploy.yml のスニペット

  • name: Run Pulumi

uses: pulumi/action@v5
with:
command: up
stack-name: production
env:
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
APPROVALGATE_HMAC_SECRET: ${{ secrets.PROD_HMAC_SECRET }}

—

最後に:インフラエンジニアの境界線を突破せよ

多くのエンジニアは、「AWSの範囲内」「Kubernetesの範囲内」という見えない檻の中でインフラを書いている。しかし、PulumiのBridge技術とSchema駆動開発をマスターした君には、もはやその檻は存在しない。

社内の誰もが「めんどくさい」「手動でやるしかない」と諦めていたレガシーシステムや独自APIを、洗練されたTypeScriptやGoのコードで宣言的に記述し、チーム全体のデプロイメントスピードを圧倒的な高みへと引き上げる――それこそが、現代の卓越したSRE/インフラエンジニアの姿だ。

さあ、エディターを開き、君だけのプロバイダをビルドしよう。世界は、君のコードによる自動化を待っている。

タイトルとURLをコピーしました