【テクニカル・上級編】PulumiでTerraformモジュールを直接読み込んで再利用する方法:tf2pulumiの限界を超えるPulumi Terraform Bridgeの高度な応用テクニック – インフラ構成管理(IaC)活用バイブル

既存Terraform資産の完全調伏:Pulumi Terraform Bridgeがもたらす「真のIaC融合」の深淵

インフラストラクチャ・作為(IaC)の歴史において、TerraformのHCL(HashiCorp Configuration Language)が築いてきたモジュール資産の山は、移行期にある組織にとって巨大な遺産であり、同時に足枷でもある。

「Pulumiへ移行したい。しかし、何百とある社内標準のTerraformモジュールを書き直す余裕などない」

この絶望的なジレンマに対する安易な回答が、かつて存在した `tf2pulumi` だ。あれはHCLを一度限りの使い捨てコードに変換するだけの代物に過ぎず、生成されたコードの保守性は地獄であり、上流のTerraformモジュールが更新された瞬間に破綻する。過去の遺物をその場しのぎで翻訳するアプローチは、SREの美学に反する。

我々が求めるべきは、翻訳ではない。「統合」だ。
そして、その鍵を握るのが Pulumi Terraform Bridge である。

本稿では、Pulumi Terraform Bridgeを用いて既存のTerraformプロバイダやモジュールを動的にラップし、TypeScript/Python/Goといった汎用プログラミング言語の型安全性と表現力の中に、Terraformエコシステムを完全に封じ込める高度な応用テクニックを解説する。

—

1. 内部アーキテクチャの理解:なぜ `tf2pulumi` ではダメで、Bridgeなのか?

`tf2pulumi` が静的なコード変換ツールであるのに対し、Pulumi Terraform Bridgeは、TerraformのGo言語製プロバイダ(Provider)のバイナリをラップし、PulumiのRPC(gRPC)プロトコルを話す動的なリソースサーバとして再定義する仕組みだ。

+————————————+
| Pulumi Program (TS / Python / Go) |
+————————————+
| gRPC (Pulumi Engine Protocol)
v
+————————————+
| Pulumi Terraform Bridge |
| – State Mapping |
| – Schema Translation |
+————————————+
| Internal Terraform Plugin SDK
v
+————————————+
| Terraform Provider Binary |
+————————————+

このアーキテクチャの真の強みは、Terraformの状態管理(State)とリソースライフサイクル(Create, Read, Update, Delete)のセマンティクスを完全に維持したまま、実行時のみPulumiのグラフエンジンに組み込める点にある。

これにより、以下の極限的なメリットがもたらされる:

  • HCLの貧弱な制御構造(`for_each` のハックや複雑な `dynamic` ブロック)から解放され、強靭なプログラミング言語のループ、条件分岐、関数型処理でリソースを構築できる。
  • 既存の社内製Terraformプロバイダを、わずか数時間のブリッジング作業で、型安全なPulumiパッケージとしてネイティブに近い形で再利用できる。

—

2. Pulumi Terraform Bridgeを用いた外部プロバイダのラップと型安全なラッパー作成

独自のTerraformプロバイダ、あるいは非公開のTerraformモジュール群をPulumiからシームレスに呼び出すためのカスタムプロバイダパッケージの構築手順を追う。

ここでは、既存のTerraformモジュールをラップするための `pulumi-tf-provider-bridging` の実践的な設定を行う。

ステップ 1: Bridge設定ファイル (`provider/resources.go`) の極限チューニング

Pulumi Bridge SDKを使用し、TerraformプロバイダをPulumiパッケージに変換するGoコードの核心部だ。ここでリソースのマッピングや型変換の挙動を制御する。

package customprovider

import (
“path/filepath”

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

// ProviderInfo はPulumiエンジンとTerraformプロバイダを仲介するメタデータを定義する。
func Provider() tfbridge.ProviderInfo {
prov := tfbridge.ProviderInfo{
P: shimv2.NewProvider(custom.Provider()),
Name: “custom”,
// 組織名とモジュール名のマッピング
Publisher: “YourOrg”,
LogoURL: “https://raw.githubusercontent.com/your-org/pulumi-custom/main/logo.png”,
PluginDownloadURL: “github://api.github.com/your-org/pulumi-custom”,
Description: “A Pulumi package for safely bridging internal custom Terraform providers.”,
Keywords: []string{“pulumi”, “custom”, “bridge”},
License: “Apache-2.0”,
Homepage: “https://www.your-org.io”,
Repository: “https://github.com/your-org/pulumi-custom”,
Config: map[string]tfbridge.SchemaInfo{
// 機密情報の環境変数マッピングを厳格に制御
“api_token”: {
Default: &tfbridge.DefaultInfo{
EnvVars: []string{“CUSTOM_API_TOKEN”},
},
},
},
Resources: map[string]tfbridge.ResourceInfo{
“custom_app_deployment”: {
Tok: tfbridge.MakeResource(“custom”, “index”, “AppDeployment”),
// Terraformの不完全なスキーマをPulumi側で型安全に補正
Fields: map[string]tfbridge.SchemaInfo{
“environment_variables”: {
MaxItems: 100,
},
},
},
},
DataSources: map[string]tfbridge.DataSourceInfo{
“custom_cluster_info”: {
Tok: tfbridge.MakeDataSource(“custom”, “index”, “getClusterInfo”),
},
},
Golang: &tfbridge.GolangInfo{
ImportBasePath: filepath.Join(
“github.com/your-org/pulumi-custom/sdk”,
tfbridge.GetModuleMajorVersion(“v1.0.0”),
“go”,
“custom”,
),
GenerateResourceContainerTypes: true,
},
}

// 自動補完とドキュメントの適用
prov.SetAutonaming(255, “-“)

return prov
}

このコードをコンパイルし、Pulumiスキーマジェネレータを実行することで、TypeScriptやPython向けの完全な型付きSDK(Software Development Kit)が自動生成される。

—

3. 移行期におけるハイブリッド運用:Stateの調停とベストプラクティス

TerraformからPulumiへの移行期において、最大のリスクは「Stateの競合」と「リソースの二重管理(あるいは消失)」である。
両者を安全に共存させるためのアーキテクチャ上の鉄則を提示する。

鉄則 1: S3/GCSバックエンドのストレージ分離とRead-Onlyインポートの活用

Terraformが管理している既存インフラストラクチャをPulumiへ安全に移行するには、強引な `pulumi import` ではなく、既存Terraform Stateからのデータ参照と段階的リソース乗っ取りを行う。

ハイブリッド運用時におけるディレクトリ構造のベストプラクティス:

infra-root/
├── terraform/ # 既存のTerraformモジュール群(徐々に縮退)
│ ├── modules/
│ │ └── secure-vpc/ # 社内標準VPCモジュール
│ └── main.tf
└── pulumi-stacks/ # 新規構築および移行先Pulumiコード
├── index.ts
└── package.json

Pulumi側から、既存のTerraformが管理するS3バックエンドやTerraform CloudのOutputsを直接読み込み、Pulumi側の入力値として流し込むことで、ダウンタイムゼロの移行を実現する。

import as pulumi from “@pulumi/pulumi”;
import as aws from “@pulumi/pulumi-aws”;

// 1. 既存Terraformが管理するS3ステートバケットからOutputsを安全に参照する
// (Terraformのremote_stateデータソースに相当する操作をPulumiのStackReferenceで実現)
const tfCoreInfra = new pulumi.StackReference(“org/core-infra/prod”);

// Terraform側で生成されたVPC IDを取得し、Pulumi側のリソースの親とする
const vpcId = tfCoreInfra.requireOutput(“vpc_id”);
const privateSubnetIds = tfCoreInfra.requireOutput(“private_subnet_ids”);

// 2. Pulumi側で管理する新規マイクロサービス群のデプロイ
// ここではTerraformモジュールをブリッジしたカスタムプロバイダを使用
import as custom from “@your-org/pulumi-custom”;

const appDeployment = new custom.AppDeployment(“my-microservice”, {
vpcId: vpcId,
subnetIds: privateSubnetIds,
replicas: 3,
environmentVariables: {
“LOG_LEVEL”: “debug”,
“FEATURE_FLAG_NEW_ROUTING”: “true”,
},
});

export const deploymentEndpoint = appDeployment.endpointUrl;

鉄則 2: ライフサイクル管理の排他制御(Mutexパターン)

同一のクラウドインフラストラクチャに対して、TerraformとPulumiの双方が同時に `apply` を実行可能な状態にしておくことは、競状態(Race Condition)を引き起こし、クラウドAPIのレートリミット超過やStateの破損を招く。

移行期間中は、CI/CDパイプライン(GitHub Actions等)レベルで排他制御を実装せよ。

GitHub Actions Workflowでの排他制御例
concurrency:
group: infra-production-environment
cancel-in-progress: false # 実行中のデプロイを中断させない

さらに、Terraform側のリソースをPulumiへ移行する瞬間は、以下の手順を厳守する:
1. Terraform側で該当リソースを `terraform state rm` でステートから切り離す(リソース自体は削除しない)。
2. Pulumi側で同一のリソース定義を記述し、`pulumi import` または `import` オプションを付与してコードと実体を紐付ける。

// Pulumiのインポート機能を用いた安全な引き継ぎ
const migratedResource = new aws.s3.Bucket(“legacy-bucket”, {
bucket: “my-production-data-bucket”,
}, {
// 既存の実体をそのままPulumiの管理下に置く
import: “my-production-data-bucket”,
});

—

4. エキスパートの知見:パフォーマンスとメモリ消費の最適化ハック

Pulumi Terraform Bridgeを用いた大規模構成(数千リソースを超える環境)の運用において、エンジニアが直面する最大の壁は 「メモリリークとgRPCプロセスのオーバヘッド」 である。

TerraformプロバイダはGo言語のプラグインとして別プロセス(あるいはプラグインプロセス)として起動し、Pulumiエンジンと通信する。リソース数が膨大になると、以下のボトルネックが発生する。

対策 1: プラグインのライフサイクルと並行度の調整

Pulumiはデフォルトで並行してリソースをプロビジョニングする。しかし、ブリッジされたTerraformプロバイダの内部SDKが並行リクエストに対応しきれず、デッドロックやメモリ急増を引き起こす場合がある。

`Pulumi.yaml` または環境変数で並行度を制御せよ。

同時実行数を制限し、メモリ消費とAPIスロットリングを抑制する
export PULUMI_PARALLEL=10

対策 2: 巨大なStateの差分計算(Refresh)の高速化

Terraformプロバイダをブリッジした場合、Pulumiの `refresh` コマンド実行時にすべてのリソースに対してTerraformの `Read` が走り、ネットワークI/Oとメモリを大量に消費する。

変更のない安定したリソースグループに対しては、明示的に `–refresh=false` を指定するか、リソースオプションで `protect: true` を設定し、不要なAPIコールを極限まで排除せよ。

const stableDatabase = new custom.Database(“core-db”, {
instanceSize: “db.r6g.xlarge”,
}, {
protect: true, // 誤削除および不要なリフレッシュからの防護
additionalSecretOutputs: [“masterPassword”],
});

—

結び:IaCの境界線を越えろ

HCLの限界に縛られる時代は終わった。
Pulumi Terraform Bridgeを使いこなし、既存のTerraformモジュール資産という「重力」を味方につけながら、現代的なプログラミング言語の表現力と型安全性をインフラ構築の現場に導入すること。それこそが、複雑化するクラウドインフラを圧倒的な速度と堅牢性で制圧する、SRE/インフラエンジニアの到達点である。

コードを書け。インフラをコードの海へ沈めろ。そして、完全に自動化された静寂の世界を支配せよ。

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