Azure Container Apps × Dapr × Pulumi:プロダクショングレードのマイクロサービス基盤をコードで極める
こんにちは。テックリードの私だ。
これまで数々のクラウドインフラを構築・運用してきたが、Kubernetesの複雑性(Cognitive Load)にチームが疲弊し、かといって純粋なPaaSでは拡張性に縛られるというジレンマに直面したことはないだろうか?
その答えの一つが、Azure Container Apps (ACA) と Dapr (Distributed Application Runtime) の組み合わせだ。K8sのパワーを抽象化しつつ、サービストレージ、pub/sub、mTLSによるセキュアなサービス間通信をサイドカーとして標準装備する。
そして、このモダンなアーキテクチャを「完全な冪等性」と「型安全」を持ってデプロイするために選ぶべきIaCツールが Pulumi だ。TypeScript/Python等を用い、TerraformのHCL地獄から解放された真のソフトウェアエンジニアリングとしてのインフラ構築を実践しよう。
今回は、実務で即座にチームの生産性を爆上げするための実践知とコードの全貌を伝授する。
—
1. 開発スピードを加速させるPulumi実践テクニック
まずは、日常の開発体験を極限まで高めるためのツールチェーンと設定の共有化ルールから入る。ここを押さえているか否かで、チームのデプロイ速度は3倍変わる。
必須のVS Code拡張機能
1. Pulumi (公式): リソースのホバープレビューや補完に必須。
2. Azure Account / Container Apps: リソースの状態をIDE内で直感的に確認。
チーム開発における設定共有化ルール
Pulumiのスタック設定(`Pulumi.
- 暗号化: 標準の `pulumi config set –secret` を用い、Azure Key Vaultを秘密鍵のKMSバックエンドとして指定する。
- 構成の分離: インフラの共通定義(VNet、Log Analytics)と、アプリケーション固有のコンテナ定義をプロジェクトレベルで分離せよ。モノリスなPulumiコードは、マイクロサービスの独立性を殺す。
—
2. アーキテクチャ概要
今回構築するインフラストラクチャの構成は以下の通りだ。
1. Resource Group & Log Analytics: 観測性の基盤。
2. Container Apps Environment: Daprコンポーネントを内包するVNet統合環境。
3. Container App (Frontend / Backend): Daprサイドカーを有効化し、gRPC/HTTPでセキュアに通信するマイクロサービス。
4. Dapr Pub/Sub (Azure Service Bus): サービス間の非同期メッセージング基盤。
—
3. 実践コード:TypeScriptによる完全自動化
それでは、実際のPulumiコード(TypeScript)を見ていこう。型安全性とモジュール化を極限まで高めた実装だ。
プロジェクト構造
.
├── Pulumi.yaml
Pulumi.dev.yaml
├── index.ts
└── package.json
`index.ts` の実装(プロダクション仕様)
import as pulumi from “@pulumi/pulumi”;
import as resources from “@pulumi/azure-native/resources”;
import as operationalinsights from “@pulumi/azure-native/operationalinsights”;
import as app from “@pulumi/azure-native/app”;
import as servicebus from “@pulumi/azure-native/servicebus”;
// 1. リソースグループの作成
const rg = new resources.ResourceGroup(“rg-aca-dapr-prod”, {
location: “japaneast”,
});
// 2. 観測性(Log Analytics Workspace)の作成
const law = new operationalinsights.Workspace(“law-aca”, {
resourceGroupName: rg.name,
location: rg.location,
sku: {
name: “PerGB2018”,
},
retentionInDays: 30,
});
// 3. Container Apps Managed Environment の作成(Dapr有効化の肝)
const environment = new app.ManagedEnvironment(“aca-env”, {
resourceGroupName: rg.name,
location: rg.location,
appLogsConfiguration: {
destination: “log-analytics”,
logAnalyticsConfiguration: {
customerId: law.customerId,
sharedKey: law.primarySharedKey,
},
},
// VNetインジェクションを行う場合はここでworkloadProfilesやvnetConfigurationを設定する
});
// 4. Dapr用バックエンド:Azure Service Bus のプロビジョニング
const sbNamespace = new servicebus.Namespace(“sb-dapr-ns”, {
resourceGroupName: rg.name,
location: rg.location,
sku: {
name: “Standard”,
tier: “Standard”,
},
});
const sbTopic = new servicebus.Topic(“sb-topic-orders”, {
resourceGroupName: rg.name,
namespaceName: sbNamespace.name,
});
// 5. Dapr Component の定義 (Pub/Sub via Service Bus)
const daprPubSubComponent = new app.ManagedEnvironmentComponent(“dapr-pubsub”, {
resourceGroupName: rg.name,
environmentName: environment.name,
componentType: “pubsub.azure.servicebus.topics”,
version: “v1”,
metadata: [
{ name: “connectionString”, value: sbNamespace.name.apply(async nsName => {
// 実際にはListAuthorizationRuleKeys等で接続取得するロジックを挟む
return “Endpoint=sb://…;”; // 簡略化のためプレースホルダー
})},
{ name: “consumerID”, value: “order-consumer-group” },
],
scopes: [“backend-app”], // このコンポーネントにアクセス可能なアプリを指定
});
// 6. バックエンド・マイクロサービスの定義(Daprサイドカー統合)
const backendApp = new app.ContainerApp(“backend-app”, {
resourceGroupName: rg.name,
environmentId: environment.id,
configuration: {
ingress: {
external: false, // 内部通信のみ
targetPort: 8080,
transport: “http”,
},
dapr: {
enabled: true,
appId: “backend-app”,
appPort: 8080,
appProtocol: “http”,
},
},
template: {
containers: [
{
name: “backend”,
image: “mcr.microsoft.com/azuredocs/aci-helloworld:latest”, // 実運用では ACR のイメージを指定
resources: {
cpu: 0.5,
memory: “1.0Gi”,
},
},
],
},
});
// 7. フロントエンド・マイクロサービスの定義
const frontendApp = new app.ContainerApp(“frontend-app”, {
resourceGroupName: rg.name,
environmentId: environment.id,
configuration: {
ingress: {
external: true, // 外部からのアクセスを許可
targetPort: 3000,
transport: “http”,
},
dapr: {
enabled: true,
appId: “frontend-app”,
appPort: 3000,
appProtocol: “http”,
},
},
template: {
containers: [
{
name: “frontend”,
image: “mcr.microsoft.com/azuredocs/aci-helloworld:latest”,
resources: {
cpu: 0.5,
memory: “1.0Gi”,
},
},
],
},
});
// エンドポイントのエクスポート
export const frontendUrl = frontendApp.configuration.apply(c => `https://${c.ingress?.fqdn}`);
—
4. 現場で役立つ設計の勘所(Best Practices)
このコードをベースラインとして、さらにエンタープライズレベルへ昇華させるための知見を共有する。
1. ゼロトラストネットワークの徹底
Container Appsのイングレス設定において、サービス間通信(FrontendからBackendへ)は `external: false` を厳守すること。Daprのサイドカー(mTLS)が自動的に通信を暗号化・認証するため、アプリケーションコード側で証明書の管理や複雑な認証ロジックを書く必要が一切なくなる。これがDaprを採用する最大のROIだ。
2. シークレットの動的バインディング
データベースの接続文字列やAPIキーをPulumiのコード内にハードコードしてはならない。Azure Key Vaultと連携し、Container Appsのsecretsプロパティ経由で環境変数にインジェクトする構成を標準化しよう。
3. 観測性(Distributed Tracing)の強制
DaprはZipkinやApplication Insightsへのトレース自動送信をサポートしている。`ManagedEnvironment` の段階でテレメトリーのエンドポイントを構成し、すべてのマイクロサービス間でW3C Trace Contextが伝播するように徹底せよ。障害発生時の原因特定スピードが圧倒的に変わる。
—
5. おわりに
PulumiとAzure Container Apps、そしてDaprの組み合わせは、Kubernetesの学習コストと運用地獄をバイパスしつつ、モダンなマイクロサービスに必要な要件(レジリエンス、セキュアな通信、非同期メッセージング)をすべてコードベースで手に入れられる究極の選択肢だ。
「インフラをコードとして書く」のではなく、「アプリケーションの拡張としてインフラを定義する」。
このパラダイムシフトを、ぜひあなたのチームでも実践してほしい。圧倒的な開発スピードとシステムの堅牢性が、あなたを待っている。