Azure Event Hubsのプライマリ/セカンダリ接続文字列で無停止キー・ローテーションを実現する方法|Kafka互換で重複配信を正しく理解する

Azure Event Hubs を Kafka 互換プロトコルで利用しながら、プライマリ/セカンダリ接続文字列を併用して「無停止でキーをローテーションしたい」。この要件は多くの現場で求められます。ここでは、重複配信の正しい理解、鍵の役割、ゼロダウンタイム運用手順、そして高可用性設計の勘所を、実践的な検証観点とともに整理します。

目次

前提と構成の整理

想定構成は次のとおりです。

  • 入力用 Event Hub(以降「入力EH」)から Kafka 互換プロトコルを使って、出力用 Event Hub(以降「出力EH」)へルーティング。
  • 入力側アプリ(プロデューサ/コネクタ)が bootstrap.servers に同一のホストを指定し、プライマリ接続文字列とセカンダリ接続文字列の “2本” を持つ。

接続文字列と Shared Access Policy の関係

Event Hubs の「プライマリ/セカンダリ接続文字列」は、同一の Shared Access Policy(SAS ポリシー)にぶら下がる、2組の鍵(SharedAccessKey)です。認可主体(SharedAccessKeyName)は共通で、鍵だけが 2本用意されています。

項目例意味
Namespacecontoso-ehns.servicebus.windows.netEvent Hubs 名前空間
EntityPathinput-hubEvent Hub 名(省略可)
SharedAccessKeyNamesend-policyポリシー(認可主体)名
Primary/Secondary Key長い Base64 文字列同一ポリシーに対する2本の鍵
Connection StringEndpoint=...;SharedAccessKeyName=...;SharedAccessKey=...クライアントがパスワードとして使う文字列

よくある疑問への結論

「両キーで同時送信すれば2重に届くはず」— なのに1件しか届かない理由

同じ bootstrap.servers(= 同一名前空間の Kafka 互換エンドポイント)に対し、同一ポリシーのプライマリ/セカンダリを「併用」しても、多くの Kafka クライアントは実装上、実際に使う資格情報は1系統に収束します。たとえば設定の最後に読み込まれた片方が有効になったり、同一クライアントインスタンスに複数の JAAS を同時適用できなかったりします。そのため、“2回送ったつもり”でも物理的には1回しか送っていないケースが大半です。

重要なのは、Event Hubs 側で重複除去をしているわけではないという点です。送信が1回しか実行されていないので1件しか届かない、というのが観測の正体です。

「Event Hubs が重複検出して落としている?」— いいえ、基本はしません

Event Hubs はブローカー側での自動重複排除(exactly-once の保証)を前提にしていません。リトライやネットワーク揺らぎ等で重複が起き得る(at-least-once)前提のサービスです。Kafka 互換エンドポイントであっても、ブローカー実装は Kafka と同一ではないため、サービス側の重複排除を期待しない設計が基本です。

「高可用性の観点でこの構成は正しい?」

“2本の鍵で同時送信” は高可用性には寄与しません。プライマリ/セカンダリは冗長回線ではなく、無停止のキー・ローテーション(片方ずつ更新)と障害時の切替のために用意されています。可用性を高めるのは、クライアント冗長化(多インスタンス・多ゾーン)やバックオフ&リトライ、監視/自己回復ロジックです。

観測パターンと結果の早見表

クライアント構成資格情報挙動結果
単一クライアントに両キーを「同時設定」同一ポリシーの Primary/Secondary実装上どちらか一方だけが有効送信は1回、重複せず
別プロセス/別クライアントを2つ起動片方が Primary、もう片方が Secondaryそれぞれ独立送信重複が発生(当然)
出力側でシンク(プロデューサ)を2系統鍵の違いに関係なし両方から送信重複が発生(当然)
片方の鍵を更新しながらもう片方で運用同一ポリシー切替時のみ接続再確立無停止ローテーション

鍵の役割と正しいメンタルモデル

  • プライマリ/セカンダリは「同一ポリシーの2本の鍵」。どちらを使っても認可主体(SharedAccessKeyName)は同じです。
  • 2本がある理由は、片方ずつ再生成(ローテーション)するため。両方同時に更新すると当然ながら全接続が無効になります。
  • 「別回線」「別プロデューサ ID」にはなりません。鍵を増やしても論理的な分離は生まれないと理解してください。

ゼロダウンタイムで実施するキー・ローテーション手順

現場で安定して機能する“ブルー/グリーン鍵ローテーション”の具体手順です。

手順アクション目的ポイント
1アプリに両方の接続文字列を設定(Primary/Secondary)切替準備設定は環境変数・Key Vault 参照など外出し。起動時は Primary を優先利用、障害時に Secondary へフォールバック。
2Primary Key を再生成鍵の更新稼働中の接続は Secondary で継続。無停止。
3アプリ設定を更新して新しい Primary を使用新鍵への移行ロール再起動やホットリロードで切替。メトリクスでドロップ/スロットリングを監視。
4Secondary Key を再生成残鍵の更新最後にバックアップ側を更新して完了。両鍵を同時に更新しない。

ローテーションの実務 Tips

  • 鍵の保管は Key Vault 等で管理し、秘密情報のデプロイなしで差し替えできるようにする。
  • アプリ側は再認証リトライ(401/403 相当のエラー時)と指数バックオフを実装しておく。
  • 監視は Incoming Requests、Throttled Requests、Server Errors、Oldest Event Age を重点確認。
  • 切替の健全性は「パーティション別スループット」「遅延」「エラー率」で判定。

Kafka クライアント設定例(PLAIN 認証 + 接続文字列)

# producer.properties
bootstrap.servers=contoso-ehns.servicebus.windows.net:9093
security.protocol=SASL_SSL
sasl.mechanism=PLAIN
# JAAS 内の username は固定で "$ConnectionString"
sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \
  username="$ConnectionString" \
  password="Endpoint=sb://contoso-ehns.servicebus.windows.net/;SharedAccessKeyName=send-policy;SharedAccessKey=${EH_PRIMARY_KEY};EntityPath=input-hub";
# 運用上推奨
acks=all
retries=2147483647
delivery.timeout.ms=120000
max.in.flight.requests.per.connection=1
linger.ms=20
batch.size=131072
client.id=producer-app-a

フォールバックのために、起動時に Primary が不可なら Secondary で再試行する簡易ロジックを入れておくと切替が安全です。

// 疑似コード(例:Java)
Properties p = loadBaseProps();
try {
  p.put("sasl.jaas.config", jaasWith(primaryConnStr));
  KafkaProducer<K, V> producer = new KafkaProducer<>(p);
  healthCheck(producer);
  return producer;
} catch (AuthException e) {
  log.warn("Primary 認証失敗、Secondary へフォールバック");
  p.put("sasl.jaas.config", jaasWith(secondaryConnStr));
  KafkaProducer<K, V> producer = new KafkaProducer<>(p);
  healthCheck(producer);
  return producer;
}

Azure CLI での鍵再生成(参考)

# Primary Key を更新(Secondary で運用継続中)
az eventhubs namespace authorization-rule keys renew \
  --resource-group rg-contoso \
  --namespace-name contoso-ehns \
  --name send-policy \
  --key PrimaryKey

# 切替後、Secondary Key を更新

az eventhubs namespace authorization-rule keys renew 
--resource-group rg-contoso 
--namespace-name contoso-ehns 
--name send-policy 
--key SecondaryKey 

重複を「意図的に」発生させたい/検証したい場合

鍵を2本使うだけでは論理的な送信主体は変わりません。重複の検証は次のいずれかで行います。

  • 別プロセスのプロデューサを2つ立ち上げ、それぞれに別の鍵(あるいは同じ鍵)を割り当てる。
  • 異なる Shared Access Policy を作成し(例:send-a と send-b)、クライアントを分ける。
  • 別パーティションまたは別の Partition Key を用いて送信経路を分離する。

いずれも「クライアントが2つある」ことが本質であり、鍵の違い自体は重複の原因ではありません。

出力側で重複が発生した理由の整理

出力EHに対し、シンク(プロデューサ)を2系統で構成すると、それぞれ独立したクライアントとして送信するので重複は当然発生します。ここでも「鍵の違い」は本質ではありません。クライアントが2つあるかどうかが重複の有無を決めます。

Event Hubs と重複制御の実務設計

アプリケーションは at-least-once を前提に冪等(べきとう)性を確保しましょう。

戦略方法ポイント
メッセージに一意の IDmessageId、ソースのオフセット、ハッシュシンク側で「既処理 ID」を記録し二重適用を防止
Upsert(同一キーの更新は上書き)データストアで MERGE/UPSERT二重書き込みでも最終結果が1回分に収束
順序付け + シーケンスパーティションキー + 連番欠番や逆順を検出しやすくする
再処理に耐える設計副作用を外部化/トランザクション境界の最小化リトライやバッチ再投入に強くなる

高可用性を高める設計パターン

  • クライアント冗長化:プロデューサ/コンシューマを複数インスタンス化し、可用性ゾーンをまたいで配置。
  • 障害時フェイルオーバー:認証失敗や接続ドロップ時に Secondary に自動切替。回復後に Primary へフェールバック。
  • リトライ・バックオフ:スロットリング(429)や一時障害に対して指数バックオフ。ジャムを作らない。
  • 構成の外部化:接続文字列は環境変数/Key Vault。ロール再起動のみで鍵差し替えが完了する形に。
  • 監視と自動復旧:エラー率増加でヘルスチェックを落とし、オーケストレータが再起動/別ノードに移す。

運用チェックリスト

タイミングチェック合格基準
事前両鍵の所在・有効性、Key Vault 参照権限、再起動手順ドキュメント化済み/自動化済み
切替直前Secondary での接続テスト(ステージングで可)送信・消費が成功、遅延の増悪なし
切替中ドロップ・スロットリング・再接続回数短時間で収束、データ欠損なし
切替後受信レイテンシ/バースト、Oldest Event Ageベースラインに復帰

よくある落とし穴

  • 両鍵を同時に再生成:全接続が失効してダウン。片方ずつ、順番に。
  • 長期キャッシュされた SAS トークン:SAS Token を自前生成して長時間キャッシュすると切替に追従しない。原則「接続文字列→ライブラリ任せ」で。
  • JAAS の取り違え:username は $ConnectionString 固定、password に接続文字列を入れるのが定石。
  • EntityPath の混同:接続文字列に EntityPath を入れる場合と入れない場合でトピック指定の仕方が変わる。運用で統一する。

質問への明快な回答(要点)

  • 疑問1:「両キーで同時送信すれば2重に届くはず」→ 実際はクライアントが片方の資格情報しか使っておらず送信は1回。重複除去が働いたわけではない。
  • 疑問2:Event Hubs が重複を落としている? → いいえ。サービス側で自動重複排除を前提にしない。重複は設計で吸収する。
  • 疑問3:この構成は高可用? → 鍵の併用は HA にならない。プライマリ/セカンダリは無停止ローテーションとフェイルオーバーのための“バックアップ鍵”である。

運用例(実践ステップ)

手順目的ポイント
1セカンダリキーでアプリを待機運用両方の接続文字列を設定しておくと切替が容易
2プライマリキーを再生成セカンダリで通信継続のため無停止
3アプリ設定を更新し新しいプライマリへ動作確認後フェールバックも容易
4セカンダリキーを再生成両鍵の更新が完了しローテーション終了

要点:プライマリ/セカンダリは「鍵のバックアップ」であり「別回線」ではありません。ゼロダウンタイムでローテーションするには、片方ずつ鍵を更新し、常に少なくとも1本は有効に保つこと。

トラブルシューティングの実践手順

  1. クライアントの送信実績を確認:アプリログで「どの JAAS(どの鍵)で接続したか」「送信カウント」を突合。
  2. ネットワーク/認証エラーの分解:TLS 失敗、SASL 失敗、スロットリングを分離して把握。
  3. 重複検知の仕掛けを一時有効化:出力先で messageId の重複率を計測し、切替時の挙動を可視化。
  4. 再現テスト:別プロセスのダミープロデューサを 2 本立てて重複が生じることを確認(鍵の差ではなくクライアント数が要因だと体感する)。

セキュリティと将来拡張

  • 最小権限のポリシー:送信専用には Send のみを付与したポリシー、受信専用には Listen のみ。
  • ポリシー分割:チーム/ワークロードごとにポリシーを分けると、鍵漏えい時の影響半径を限定できる。
  • 認証方式の抽象化:将来の AAD(OAuth)移行やマネージド ID への切替を視野に、アプリから認証機構を疎結合に。

まとめ

  • プライマリ/セカンダリは同一ポリシーの2本の鍵。冗長回線ではない。
  • 「両キー同時送信」ではふつう1回しか送られない。それはクライアント実装の挙動であり、Event Hubs が重複を捨てているわけではない。
  • ゼロダウンタイムの鍵更新は片方ずつ更新し、常に1本は有効に保つ。
  • 高可用性はクライアント冗長化・リトライ・監視で担保する。鍵の併用は HA ではない。
  • 重複を扱うのはアプリの責務。冪等性を確保し、再処理に強いパイプラインを設計する。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次