【テクニカル・上級編】Pulumiでマルチクラウド(AWS & GCP)インフラを統合管理する実践アーキテクチャ – インフラ構成管理(IaC)活用バイブル

マルチクラウドの深淵:PulumiによるAWS & GCP統合管理の実践アーキテクチャ

インフラストラクチャ・アズ・コード(IaC)の領域において、TerraformのHclやCloudFormationの静的なYAML/JSON地獄に疲弊したシニアエンジニアたちよ、目を覚ませ。真の自動化とは、プログラミング言語の表現力と型安全性をインフラのライフサイクルに持ち込むことにある。

特にAWSとGCPを跨ぐマルチクラウド環境の構築において、プロバイダの壁を越えたリソース連携と、複雑な依存関係の解決は、多くのOpsチームを夜中に叩き起こすアラートの温床となってきた。

本稿では、TypeScript(またはPython)を用い、同一の実行コンテキスト内でAWSとGCPのリソースを完全に協調動作させ、ゼロ・ドリフトを達成するPulumiの極限アーキテクチャを解説する。机上の空論ではない。明日から直ちにプロダクションへ投入可能なレベルの設計知見とコードパターンを提示しよう。

—

1. マルチクラウド運用の課題とPulumiの優位性

マルチクラウドの現実解として、多くの企業がAWSの堅牢なストレージ/コンピュート基盤(S3, EKSなど)と、GCPの圧倒的なデータ分析・AI基盤(BigQuery, Vertex AIなど)を組み合わせて使っている。

しかし、ここで従来のIaCツールを使うとどうなるか?

  • Terraformの場合: `aws`プロバイダと`google`プロバイダを別々のStateファイルで管理するか、単一の巨大なStateに押し込む必要がある。クロスプラットフォームでの変数受渡しは `terraform_remote_state` や強引なモジュール出力のバケツリレーになり、CI/CDパイプラインは極めて脆弱になる。
  • Pulumiの優位性: ひとつのプログラム(グラフ)の中に、AWSリソースとGCPリソースを同時に定義できる。例えば、「AWS S3バケットを定義し、そのARNをGCP側のIAMサービスアカウントにバインドし、さらにその認証情報を生成してAWS側へセキュアに渡す」といった処理を、JavaScript/TypeScriptの強烈な型システムとPromise(非同期処理)の制御下で安全に実行できるのだ。

—

2. 同一プロジェクト内でAWSとGCPのリソースを連携させるコード例

百聞は一見にしかず。以下に、「AWSのS3に配置されたデータレイクに対し、GCPのBigQuery(あるいはCloud Run)から安全にアクセスするためのクロスプラットフォーム連携アーキテクチャ」のTypeScriptコードを示す。

このコードでは、AWS側でIAM RoleとS3バケットを作り、GCP側でWorkload Identity Poolを設定して、鍵(JSONキー)のファイル出力を完全に排除したモダンなOIDC(OpenID Connect)連携を構築する。

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

// ———————————————————————
_configの読み込み_
// ———————————————————————
const config = new pulumi.Config();
const environment = pulumi.getStack(); // 例: prod, staging
const awsRegion = config.require(“awsRegion”);
const gcpProject = config.require(“gcpProject”);
const gcpRegion = config.require(“gcpRegion”);

// ———————————————————————
1. AWS側リソースの構築: データレイク用S3バケットとアクセスログ用
// ———————————————————————
const dataBucket = new aws.s3.Bucket(`datalake-${environment}`, {
bucket: `enterprise-datalake-${environment}-${awsRegion}`,
forceDestroy: false,
serverSideEncryptionConfiguration: {
rule: {
applyServerSideEncryptionByDefault: {
sseAlgorithm: “AES256”,
},
},
},
});

// パブリックアクセスブロックの徹底
const bucketPublicAccessBlock = new aws.s3.BucketPublicAccessBlock(`datalake-block-${environment}`, {
bucket: dataBucket.id,
blockPublicAcls: true,
blockPublicPolicy: true,
ignorePublicAcls: true,
restrictPublicBuckets: true,
});

// ———————————————————————
2. GCP側リソースの構築: Workload Identity Poolの作成
// ———————————————————————
// GCP側でAWSを外部アイデンティティプロバイダとして信頼させる
const awsPool = new gcp.iam.WorkloadIdentityPool(`aws-pool-${environment}`, {
workloadIdentityPoolId: `aws-pool-${environment}`,
displayName: `AWS Workload Identity Pool for ${environment}`,
description: “Identity pool for cross-cloud access from AWS to GCP”,
disabled: false,
});

const awsPoolProvider = new gcp.iam.WorkloadIdentityPoolProvider(`aws-provider-${environment}`, {
workloadIdentityPoolId: awsPool.workloadIdentityPoolId,
workloadIdentityPoolProviderId: `aws-provider-${environment}`,
displayName: `AWS Provider for ${environment}`,
aws: {
// AWSアカウントIDをハードコードせず、AWSプロバイダから動的に取得
accountId: aws.getCallerIdentity().then(id => id.accountId),
},
});

// ———————————————————————
3. 連携の要: GCP Service AccountとAWS IAM Roleのクロスバインディング
// ———————————————————————
// GCP側のサービスアカウント(例: BigQuery操作用)
const gcpServiceAccount = new gcp.serviceaccount.Account(`bq-operator-${environment}`, {
accountId: `bq-operator-${environment}`,
displayName: `Service Account for AWS DataOps`,
});

// GCPのサービスアカウントに、AWSの特定のIAMロールからのAssumeRoleを許可するIAM Binding
const saBinding = new gcp.serviceaccount.IAMBinding(`sa-workload-identity-binding-${environment}`, {
serviceAccountId: gcpServiceAccount.name,
role: “roles/iam.workloadIdentityUser”,
members: [
awsPoolProvider.name.apply(providerName =>
`principalSet://iam.googleapis.com/${providerName}/attribute.aws_role/arn:aws:iam::${aws.getCallerIdentity().then(id => id.accountId)}:role/DataPipelineRole-${environment}`
),
],
});

// エクスポート:両クラウドの接続情報をCLIやCI/CDの次ステップへ渡す
export const awsBucketName = dataBucket.bucket;
export const gcpServiceAccountEmail = gcpServiceAccount.email;
export const gcpWorkloadIdentityPoolProviderName = awsPoolProvider.name;

このコードの美しさは、`aws.getCallerIdentity()` や `awsPoolProvider.name` という非同期のOutput(Promiseのラッパー)を、プロバイダの境界を越えてそのまま文字列結合・加工できる点にある。静的設定ファイルでは絶対に実現できない、プログラムによる動的なリソースグラフ構築の真骨頂である。

—

3. 共通設定と環境ごとのパラメータ管理(Stackの活用)

マルチクラウド環境をスケールさせる際、最大の悪夢は環境間(Staging / Production)でのパラメータ漏れや、リージョン誤設定によるリソースのデプロイミスだ。

Pulumiの Stack(スタック) 機構と、型安全な構成管理システムを組み合わせることで、この課題を完全に無力化する。

構成管理のベストプラクティス:型付きConfigモジュール

単に `config.require(“key”)` をコードのあちこちに散りばめるのは素人のやり方だ。設定値をパース・検証する専用のモジュールを用意せよ。

// config.ts
import as pulumi from “@pulumi/pulumi”;

interface ConfigSchema {
awsRegion: string;
gcpProject: string;
gcpRegion: string;
instanceType: string;
}

const pulumiConfig = new pulumi.Config();

export const config: ConfigSchema = {
awsRegion: pulumiConfig.require(“awsRegion”),
gcpProject: pulumiConfig.require(“gcpProject”),
gcpRegion: pulumiConfig.require(“gcpRegion”),
instanceType: pulumiConfig.get(“instanceType”) ?? “t3.medium”, // デフォルト値の安全なフォールバック
};

各スタックの設定ファイル(例: `Pulumi.production.yaml`)では、暗号化が必要な機密情報(DBパスワードやAPIトークンなど)を `pulumi config secret` を用いてシームレスにKMS暗号化して保存する。

Pulumi.production.yaml
config:
my-multicloud-project:awsRegion: ap-northeast-1
my-multicloud-project:gcpProject: my-company-prod-123456
my-multicloud-project:gcpRegion: asia-northeast1
my-multicloud-project:dbPassword:
fn::secret: b64:AAAB… (暗号化されたシークレット)

—

4. 障害に強いインフラ構成のコード設計とパフォーマンス最適化

数千のリソースを単一のPulumiプログラムで管理し始めると、エンジニアは「パフォーマンス」と「状態の整合性(State Consistency)」の壁に直面する。最高峰のSREとして、以下のハックを頭に叩き込んでおけ。

① 依存関係の明示的制御(`dependsOn` の極意)

マルチクラウド間では、APIの伝播遅延(Eventual Consistency)がしばしば発生する。特にAWS側で作成したIAMリソースがGCP側から即座に見えないケースがある。
Pulumiでは、暗黙的な参照だけでなく、`dependsOn` を明示的に指定して実行順序をハードニングする。

const gcpResource = new gcp.storage.Bucket(“my-bucket”, {
// …
}, {
dependsOn: [awsPoolProvider] // AWS側のプロバイダが完全に確立されてからGCP側を作成
});

② 内部アーキテクチャの最適化:並行度(Concurrency)のチューニング

Pulumiエンジンはデフォルトでグラフの依存関係に基づき、可能な限り多くのリソースを並行して作成(プロビジョニング)する。
しかし、AWSやGCPのAPIレートリミット(スロットリング)に抵触し、`429 Too Many Requests` が発生することがある。これを回避するため、環境変数で並行度を制御せよ。

CI/CDパイプラインの実行スクリプト(GitHub Actionsなど)で以下のように設定する。

同時リソース作成数を制限し、レートリミットエラーを防ぐ
export PULUMI_CONCURRENCY=10
pulumi up –non-interactive –yes

③ メモリ消費とガベージコレクションの対策

TypeScript(Node.jsランタイム)で数千のリソースをメモリ上に展開すると、V8エンジンのヒープメモリが枯渇することがある。
巨大なインフラストラクチャをコード化する場合は、「プロジェクトの分割(Project Stack Composition)」 を行い、ドメインごと(例: ネットワーク層、データ層、アプリケーション層)にPulumiプロジェクトを細分化せよ。

スタック間のデータ共有には `pulumi.StackReference` を用いる。

// 別プロジェクト(例: networking-prod)の出力値を安全に参照する
const networkingStack = new pulumi.StackReference(“my-org/networking/production”);
const vpcId = networkingStack.requireOutput(“vpcId”);

これによって、メモリ効率が劇的に改善されるだけでなく、Blast Radius(障害の影響範囲)を最小限に抑えることができる。

—

結び:コードこそが唯一の真実である

マルチクラウドの複雑さは、インフラを「手動」や「断片的なスクリプト」で管理しようとするから牙をむく。
Pulumiを用いて、AWSもGCPもひとつの強固な型付きプログラムとして抽象化し、GitOpsのパイプラインに組み込んだ瞬間、インフラは完全にあなたの手のひらの上で支配される。

妥協のない設計、美しく洗練されたコード、そして揺るぎない自動化。それらを極めた者だけが到達できる「静謐なるインフラストラクチャ」の境地へ、今すぐ踏み出せ。

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