実践Dapr:ステート管理とPub/SubによるKubernetes上のマイクロサービス疎結合化

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

マイクロサービスにおける「統合の負債」

マイクロサービスの構築において、実際の機能開発よりも「配管作業」に多くの時間を費やしていると感じることは少なくありません。分散システムを開始すると、コードはすぐにインフラ固有のロジックで肥大化してしまいます。セッションを保存するためにRedis SDKを導入し、メッセージを送信するためにKafkaクライアントを組み込む。気づけば、ビジネスロジックは15種類もの外部ライブラリとその固有のリトライポリシーの下に埋もれてしまいます。

統合の負債が最も重くのしかかるのは、インフラを拡張・変更する必要が生じた時です。例えば、チームがAWS SQSからAzure Service Busへの移行を決定したとしましょう。突然、20以上のサービスにわたる統合コードの書き換えに直面することになります。この密結合は、機敏なアーキテクチャを依存関係の固まりである「硬直したモノリス」に変えてしまいます。Dapr(Distributed Application Runtime)は、サービスのためのユニバーサルな抽象化レイヤーとして機能することで、この問題を解決します。

サイドカーパターンがいかに開発を簡素化するか

Daprは、分散システムの重労働を処理する「ビルディングブロック」を提供します。アプリがデータベースと直接通信する代わりに、ポート3500のシンプルなHTTPまたはgRPC APIを介してDaprと通信します。Daprは、そのリクエストを接続されている任意のインフラストラクチャに合わせて変換します。

その中核となるメカニズムがサイドカーパターン(Sidecar Pattern)です。Kubernetes上では、Daprはアプリケーションと同じPod内で小さなコンテナとして実行されます。アプリがlocalhost:3500にアクセスすると、サイドカーがネットワーク、セキュリティ、およびリトライを管理します。私が以前担当した本番環境への導入では、このアプローチによってボイラープレートな統合コードを約30%削減でき、チームはスプリントごとに機能を2日早くリリースできるようになりました。

本ガイドで扱う主なビルディングブロック

  • ステート管理(State Management):DBドライバーを使用せずに、CRUD形式のAPIでデータを保存・取得します。
  • パブリッシュ/サブスクライブ(Pub/Sub):サービス同士を完全に匿名に保ちながら、イベントをブロードキャストするための標準的な手法です。

クラスターの初期化

Kubernetesクラスター(Minikubeなど)とDapr CLIが必要です。まず、Daprのコントロールプレーンを初期化します。

dapr init -k

これにより、Daprオペレーターとサイドカーインジェクターがdapr-systemネームスペースにデプロイされます。次のコマンドを実行して、5つのコアサービスすべてが正常であることを確認してください:

kubectl get pods -n dapr-system

ステート管理の実装

Daprはコンポーネント(Component)モデルを採用しています。YAMLファイルで一度データベースを定義すれば、コード側がその実体を知る必要はありません。今回はRedisを使用します。これをstatestore.yamlとして保存してください:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: my-statestore
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: redis-master.default.svc.cluster.local:6379
  - name: redisPassword
    secretKeyRef:
      name: redis
      key: redis-password

これをKubernetesに適用すると、アプリはシンプルなPOSTリクエストでデータを保存できるようになります。SDKは不要です:

# HTTP経由で注文を保存
curl -X POST http://localhost:3500/v1.0/state/my-statestore \
     -H "Content-Type: application/json" \
     -d '[{"key": "order_99", "value": {"sku": "A100", "qty": 1}}]'

後でRedisをMongoDBに切り替えますか?その場合はYAMLファイルを更新するだけです。Go、Python、Node.jsのコードを一行も書き換える必要はありません。

Pub/Subによる非同期通信

イベント駆動型システムに複雑なブローカー設定は不要であるべきです。Daprでは、メッセージングレイヤーを処理するためにpubsub.yamlコンポーネントを定義します。

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: my-pubsub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: redis-master.default.svc.cluster.local:6379

イベントの発行

メッセージを発行するには、サービスがローカルのサイドカーにリクエストを送ります。標準的なシェルコマンドでは以下のようになります:

curl -X POST http://localhost:3500/v1.0/publish/my-pubsub/orders \
     -H "Content-Type: application/json" \
     -d '{"orderId": "99", "status": "paid"}'

サブスクリプションの処理

サブスクライブ(購読)はさらに簡単です。アプリがGET /dapr/subscribeエンドポイントを公開して、Daprに必要な情報を伝えるだけです。「orders」トピックにメッセージが届くと、Daprは自動的にPOSTリクエストを介して/process-orderルートに配信します。

// アプリのサブスクリプション設定
[
  {
    "pubsubname": "my-pubsub",
    "topic": "orders",
    "route": "/process-order"
  }
]

Kubernetesへのデプロイ

これらを連携させるには、デプロイメントに3つの特定の注釈(アノテーション)を追加します。これにより、Kubernetesが自動的にDaprサイドカーを注入します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  template:
    metadata:
      annotations:
        dapr.io/enabled: "true"
        dapr.io/app-id: "order-processor"
        dapr.io/app-port: "8080"
    spec:
      containers:
      - name: order-service
        image: myregistry/order-service:v1.2

app-idは極めて重要です。これはDaprがサービス間の発見やmTLSセキュリティに使用する一意の識別子です。

アーキテクチャの標準化

Daprへの移行は戦略的な転換です。それは、特定のクラウドプロバイダーのSDKに縛られない、ポータブルなアプリケーションを構築することを意味します。私の経験上、初期のセットアップに要する時間は、スケールアップやサービスの切り替えが必要になった瞬間に十分に報われます。まずはローカルのステート管理を置き換えることから始めてみてください。サイドカーが動作するのを確認できたら、Secrets Management(秘密情報管理)やDistributed Tracing(分散トレーシング)へと拡張し、クラスターの可視性をすぐに手に入れることができます。

Share: