🔐 NLB × TCPリスナー × mTLS × EKS

エンドツーエンド暗号化を維持しながら gRPC マイクロサービスを安全にスケールさせる完全ガイド

NLB + TCPリスナー 相互TLS (mTLS) AWS LB Controller HPA + Cluster Autoscaler

📌 結論ファースト

mTLSでクライアント・サーバー間の双方向認証が必要な場合、NLB+TCPリスナーが唯一の正解です。 TCPリスナーはパケットを復号化せずにパススルーするため、TLSハンドシェイクがクライアント〜Pod間で直接行われ、 相互認証と暗号化が途切れません。

🔒

NLB + TCPリスナー

レイヤー4で動作。暗号化パケットを開封せずそのままPodへ転送。mTLSが正しく機能する

☸️

AWS LB Controller + IP mode

Pod IPに直接ルーティングすることで、kube-proxyを経由せず低レイテンシーな通信を実現

📈

HPA + Cluster Autoscaler

gRPC接続数やCPU使用率に応じてPod→ノードと二段階で自動スケール

🏛️ たとえ話:「外交機密便」の仕組み

mTLS + NLB の仕組みを「国家間の外交機密便ネットワーク」で理解しよう

👨‍💼
クライアント(gRPCアプリ)
↕ たとえると
外交官 — 封印付き外交パスポートで身元を証明
🏛️
バックエンドPod(gRPCサーバー)
↕ たとえると
大使館 — 国章の入った公印で大使館の正当性を証明
📦
TLS暗号化パケット
↕ たとえると
封蝋付き外交行嚢(ポーチ)— 中身は開封不可
🚏
NLB(TCPリスナー)
↕ たとえると
国際郵便の仕分けゲート — 宛先を見て転送するだけで中身は開けない
🎛️
AWS LB Controller
↕ たとえると
郵便システムの管理局 — ゲートのルール設定と大使館の住所登録を担当
📋
CA(認証局)
↕ たとえると
外務省 — 外交パスポートと公印を発行する信頼の根拠

📬 外交機密便の配達フロー = mTLS通信フロー

👨‍💼
外交官
外交パスポートを提示して身元を証明(=クライアント証明書)
🤝
相互確認
外交官と大使館が互いの証明書を確認(=mTLSハンドシェイク)
🚏
仕分けゲート
宛先だけ見て転送。封蝋は開けない(=NLB TCPパススルー)
🗺️
直接配達
大使館の部屋に直行(=IP modeでPodへ直接ルーティング)
🏛️
大使館
公印で自身を証明し封蝋を開封(=Podがサーバー証明書を返す)

🎯 5つの要件 → 各技術がどう解決するか

🔒

E2E暗号化

復号化なしで通信を維持

→ NLB TCPリスナー
📡

gRPC on 443

HTTP/2ベースのRPC通信

→ TCP:443 パススルー
🔥

数千の同時接続

高スループット処理

→ NLBの高性能L4処理
🤝

mTLS認証

双方向の証明書認証

→ TCPパススルー + Pod
📈

自動スケーリング

負荷に応じた伸縮

→ HPA + Cluster AS
✅ すべてを満たす構成 = NLB + TCPリスナー + AWS LB Controller(IP mode)+ HPA + Cluster Autoscaler
この組み合わせだけが5つの要件をすべて同時に満たすことができる
1

相互TLS(mTLS)のハンドシェイク

通常のTLSとの違い:サーバーだけでなくクライアントも証明書を提示

🔄 mTLS 7ステップ・ハンドシェイク

💻
クライアント
gRPCアプリ
1
ClientHello を送信(対応する暗号スイート一覧を含む)
2
ServerHello + サーバー証明書を返送
3
クライアントがサーバー証明書を検証(CAチェーンを確認)
🔍
4
📋
サーバーがCertificateRequestを送信(クライアント証明書を要求)
5
クライアントがクライアント証明書を送信
6
🔍
サーバーがクライアント証明書を検証(CAチェーンを確認)
7
✅ 双方の検証成功 → 暗号化通信チャネルが確立
🔐
🏛️
サーバー
gRPC Pod
⚠️
重要ポイント:mTLSのハンドシェイクはクライアントとサーバー間で直接行われる必要があります。 途中のロードバランサーがTLSを終端してしまうと、サーバーが受け取る証明書はLBのものになり、 クライアント証明書の検証ができなくなります。だからこそTCPパススルーが必要です。
2

gRPC プロトコルとは

離れたサーバーの関数を「ローカル関数のように呼べる」高速通信の仕組み

📡 gRPC = Google が開発した高速 RPC フレームワーク

gRPC は Remote Procedure Call(遠隔手続き呼び出し)の仕組みです。
離れた場所にあるサーバーの関数を、まるでローカルの関数を呼ぶように実行できます。

📻
外交機密便のたとえでは、gRPC は「外交官と大使館が使う共通言語・暗号通信回線」にあたります。 普通の手紙(REST API / HTTP/1.1)は1通ずつ送って返事を待ちますが、 gRPC の暗号通信回線(HTTP/2)は1本の回線で複数の会話を同時進行でき、 さらにバイナリ形式(protobuf)で高速にデータをやり取りします。
🚀
HTTP/2 上で動作
HTTP/2 の多重化(Multiplexing)により、1つの TCP 接続で複数のリクエスト・レスポンスを同時にやり取りできます。 これが「数千の同時接続」に強い理由です。
1本の接続 → 同時に複数の RPC を処理
= 接続のオーバーヘッドが劇的に減少
📦
Protocol Buffers
JSON のようなテキスト形式ではなく、バイナリ形式でデータをシリアライズします。 データサイズが小さく、シリアライズ/デシリアライズが高速で、低レイテンシーの通信に最適です。
JSON: {"name":"Alice","age":30} → 約30バイト
protobuf: 同じデータ → 約10バイト 🚀
🔄
双方向ストリーミング
クライアント→サーバー、サーバー→クライアントの両方向で連続的にデータをストリーム送受信できます。 リアルタイム通信やマイクロサービス間連携に最適です。
📱 ← ストリーム → 🏛️
リアルタイムで双方向にデータ交換
📮 REST API(HTTP/1.1)
データ形式 JSON(テキスト)— 人間が読みやすいが重い
通信方式 リクエスト→レスポンスの1往復ずつ
接続管理 リクエストごとに接続 or Keep-Alive
ストリーム ❌ 非対応(WebSocket は別途必要)
向いている ブラウザ向け API、シンプルな CRUD
VS
📡 gRPC(HTTP/2)
データ形式 Protocol Buffers(バイナリ)— 小さくて速い
通信方式 1接続で複数のRPCを同時多重化
接続管理 長時間接続を維持(Long-lived)
ストリーム ✅ 双方向ストリーミング標準サポート
向いている マイクロサービス間通信、低レイテンシー

🔌 HTTP/1.1 vs HTTP/2 — 接続の違いを図で理解する

HTTP/1.1(REST)— 順番待ち
Req 1
Res 1
Req 2
Res 2

⏳ 1つずつ順番に処理 → 待ち時間が発生

HTTP/2(gRPC)— 同時並行
RPC 1
RPC 2
RPC 3
RPC 4

⚡ すべて同時に処理 → 待ち時間なし

⚠️
今回の構成で重要なポイント:gRPC は HTTP/2 の長時間接続(Long-lived connection)を使います。 NLB の TCP リスナーは接続単位で負荷分散するため、一度確立された gRPC 接続は同じ Pod に固定されます。 これがトラブルシューティング(セクション8)で触れる「gRPC 接続の偏り」問題の根本原因であり、 クライアント側で定期的に再接続する設計が必要になる理由です。
3

ロードバランサー比較:なぜ NLB + TCP なのか

3つの選択肢を比較して、mTLSに最適な構成を理解する

NLB + TCPリスナー
レイヤー4 / パススルー
🔒 TLS終端しない — パケットをそのまま転送
🤝 mTLS対応 — クライアント⇔Pod間で直接ハンドシェイク
超低レイテンシー — 復号化/再暗号化なし
📡 gRPC対応 — TCP上でHTTP/2をそのまま通過
🔥 数百万接続 — 高スループットに最適化
✅ mTLSに最適
⚠️
NLB + TLSリスナー
レイヤー4 / TLS終端あり
🔓 NLBでTLS終端 — 一旦復号化される
mTLS不可 — クライアント証明書がNLBで途切れる
🔄 再暗号化可能 — バックエンドへの再暗号化は可能
📜 ACM統合 — AWS Certificate Managerで証明書管理
⚙️ TLSオフロードでバックエンドの負荷軽減
❌ mTLS不可
ALB + HTTPSリスナー
レイヤー7 / HTTPS終端
🔓 HTTPS終端必須 — コンテンツ検査のため復号化
mTLS不可 — クライアント証明書が届かない
🛣️ 高度なルーティング — パスベース/ホストベース
📡 gRPC対応 — ALBはgRPCをネイティブサポート
🔄 WebSocket、HTTP/2もサポート
❌ mTLS不可
💡
判定基準のまとめ:TLSリスナーやHTTPSリスナーは、ロードバランサー側でTLSを「終端」します。 すると、クライアント証明書の情報がPodまで届かないため、mTLSが成り立ちません。 TCPリスナーだけが「触らずに転送」できるため、mTLSの唯一の選択肢です。
4

AWS Load Balancer Controller とは

KubernetesリソースとAWSロードバランサーを自動連携させる「管理局」

🎛️ そもそも何をしてくれるもの?

AWS Load Balancer Controller は、Kubernetes 上で動作するコントローラーで、
YAML を書くだけで NLB/ALB の作成・設定・ターゲット管理をすべて自動化してくれます。

🏛️
外交機密便のたとえでは「郵便システムの管理局」にあたります。 仕分けゲート(NLB)のルール設定、大使館(Pod)の住所登録・削除を自動で行う管理者です。 管理局がなければ、ゲートの設置も大使館の住所変更もすべて手作業になってしまいます。
🏗️
役割 ❶ NLBの自動プロビジョニング
Service (type: LoadBalancer) → NLB 作成
Kubernetes の Service マニフェスト(YAML) にアノテーションを書くだけで、 AWS 側で NLB のプロビジョニング、リスナー設定、セキュリティグループの構成まですべて自動実行されます。 手動で AWS コンソールや CLI を操作する必要はありません。
📝 YAML を kubectl apply → 🎛️ Controller が検知 → 🚏 NLB が自動作成される
🎯
役割 ❷ Pod IP の自動ターゲット登録
IP mode: Pod IP を直接 NLB ターゲットに
HPA で Pod が増えたら即座に NLB のターゲットグループに追加し、 Pod が消えたら自動で除外します。kube-proxy を経由せず Pod に直接トラフィックが届くため、 低レイテンシーを実現できます。
📈 HPA が Pod を追加 → 🎛️ Controller が検知 → 🎯 NLB ターゲットに自動登録
Controller がない場合
1
AWS コンソールで NLB を手動作成
2
リスナー / ターゲットグループを手動設定
3
Pod が増減するたびに手動でターゲット登録・削除
4
HPA / CA と連携できず、スケール時にダウンタイム発生
5
Kubernetes と AWS の設定が乖離するリスク
Controller がある場合
1
YAML にアノテーションを書いて kubectl apply するだけ
2
NLB / リスナー / ターゲットグループが自動作成
3
Pod 増減時にターゲット登録・削除が自動
4
HPA / CA と完全連携し、シームレスにスケール
5
Kubernetes マニフェストが「唯一の信頼源」(Single Source of Truth)

🔄 Controller が裏側で行う自動連携フロー

📝
YAML apply
Service マニフェストを
クラスターに適用
🎛️
Controller 検知
アノテーションを読み取り
NLB 設定を決定
🚏
NLB 自動作成
AWS API を呼び出し
NLB + TCPリスナー構築
🎯
ターゲット登録
Pod IP を
ターゲットグループに登録
🔒
トラフィック開始
mTLS 暗号化トラフィックが
Pod へ直接到達
💡
mTLS 構成での重要性:Controller がいるからこそ「Pod が増える → 自動で NLB に登録 → すぐトラフィック受信」 という流れがシームレスに動きます。オートスケーリングとの連携が実現するのは、この Controller のおかげです。
5

全体アーキテクチャ図

クライアント → NLB → Pod の全経路と各コンポーネントの役割

🏗️ NLB + TCPリスナー + AWS LB Controller + EKS 構成図

外部
💻
gRPCクライアント
クライアント証明書を保持
⬇️ TCP:443 / TLS暗号化パケット(封蝋付き外交ポーチ)
AWS
ネットワーク
🚏
Network Load Balancer
TCPリスナー(ポート443)
パケットを復号化しない = パススルー
⬇️ 暗号化パケットそのまま転送(IP mode: Pod IPへ直接)
EKSクラスター
🎛️
AWS LB Controller
NLBのプロビジョニング
& TargetGroup管理
🏛️
gRPC Pod A
サーバー証明書 + mTLS処理
🏛️
gRPC Pod B
サーバー証明書 + mTLS処理
🏛️
gRPC Pod C
HPAによる自動追加
📊
HPA
Pod数を自動調整
🖥️
Cluster Autoscaler
ノード数を自動調整

🔐 暗号化状態の可視化:パケットはどこで復号化される?

💻
クライアント
TLSで暗号化して送信
🔒→
暗号化維持
🚏
NLB (TCP)
中身を見ない ⭐
🔒→
暗号化維持
🏛️
gRPC Pod
ここで初めて復号化
🔒 クライアント → NLB → Pod:全経路で暗号化が維持される(エンドツーエンド暗号化) 🔒
6

Kubernetes マニフェスト実装

AWS Load Balancer Controller のアノテーションで NLB + TCP を構成

service.yaml — NLB + TCPリスナーの定義 Kubernetes Service
apiVersion: v1 kind: Service metadata: name: grpc-mtls-service annotations: # ❶ NLBを指定 service.beta.kubernetes.io/aws-load-balancer-type: "external" service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip" # ❷ service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing" # ❸ TCPリスナーを使用(TLSを終端しない) service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "tcp" # ❹ クロスゾーン負荷分散 service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true" # ❺ ヘルスチェック設定 service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: "TCP" service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "443" spec: type: LoadBalancer loadBalancerClass: service.k8s.aws/nlb selector: app: grpc-server ports: - port: 443 targetPort: 8443 protocol: TCP # ← TCPを明示
❶ NLBタイプの指定

"external" を指定するとAWS LB Controllerが NLB を作成。"nlb-ip" 旧構文も可

❷ IP モード

Pod IPを直接ターゲットに登録。kube-proxyを経由せずPodに到達。低レイテンシーの鍵

❸ TCPプロトコル = パススルー

最重要設定。これによりNLBはTLSパケットを開封せずそのまま転送。mTLSが機能する根拠

❹ ヘルスチェック

TCPヘルスチェックはポートの応答のみ確認。TLS終端不要なのでHTTPSヘルスチェックは使わない

hpa.yaml — Horizontal Pod Autoscaler HPA
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: grpc-server-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: grpc-server minReplicas: 3 maxReplicas: 50 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70
7

二段階オートスケーリング

HPAがPodを増やし → Cluster AutoscalerがNodeを追加する連携プレー

📊

Horizontal Pod Autoscaler (HPA) 第1段階:Podレベルのスケール

CPU使用率 70% 超過 → Pod を追加

Pod
Pod
Pod
↓ スケールアウト ↓
Pod
Pod
Pod
+Pod
+Pod
監視メトリクス:CPU使用率、メモリ使用率、カスタムメトリクス(gRPC接続数など)。 閾値を超えるとPod数を増加し、負荷低下時は自動で縮小します。 AWS LB Controllerが新Podを検知してNLBのターゲットグループに自動登録します。
🖥️

Cluster Autoscaler (CA) 第2段階:ノードレベルのスケール

Podがスケジュール不可 → ノードを追加

🖥️ Node 1(満杯)
🖥️ Node 2(満杯)
↓ ノード追加 ↓
🖥️ Node 1
🖥️ Node 2
🖥️ +Node 3
トリガー条件:HPAが新しいPodを要求したが、既存ノードにリソース不足でスケジュールできない場合。 CAがAuto Scaling Groupを通じてEC2インスタンスを追加します。 低負荷時はノードを安全に削除してコストを最適化します。
💡 連携の流れ: 負荷増加 → HPA が Pod 追加を決定 → ノードの空きリソース不足を検知 → CA がノードを追加 → 新ノードで Pod がスケジュール → AWS LB Controller が NLB ターゲットに登録 → トラフィック受信開始
8

よくあるトラブルと解決策

mTLS + NLB + EKS環境で遭遇しやすい問題パターン

🔴
mTLSハンドシェイク失敗
原因:TLSリスナーを使用しているため、NLBがTLSを終端してしまい、クライアント証明書がPodに届かない
解決:TCPリスナーに変更。aws-load-balancer-backend-protocol: "tcp" を設定
🔴
NLBが作成されない
原因:AWS Load Balancer Controllerがインストールされていないか、IAMロールの権限不足
解決:Controller のデプロイ状況を確認し、IRSA(IAM Roles for Service Accounts)を正しく設定
🟡
新しいPodにトラフィックが来ない
原因:ターゲット登録のタイムラグ。NLBのヘルスチェックがまだ通過していない
解決:ヘルスチェック間隔を短縮(10秒)し、deregistration delay を適切に設定
🟡
gRPC接続の偏り
原因:gRPCはHTTP/2の長時間接続。NLBがコネクションレベルで分散するため既存接続は再分散されない
解決:クライアント側でコネクションプールを実装し、定期的に再接続する仕組みを導入
9

ベストプラクティス

本番運用で押さえるべきポイント

✅ 運用チェックリスト

🔐
証明書のローテーション:cert-manager等を活用し、クライアント/サーバー証明書の自動更新を設定。有効期限切れはサービス停止に直結
🌐
クロスゾーン分散:NLBのクロスゾーンロードバランシングを有効化し、AZ間の負荷偏りを防止
📊
カスタムメトリクス:gRPC接続数やリクエストレートをPrometheusで収集し、HPAのスケール指標に活用
🔄
Graceful Shutdown:Pod終了時にpreStopフックで接続をドレインし、クライアントの接続断を最小化
🛡️
SecurityGroupの適切な設定:NLBからのトラフィックのみPodに到達させ、不正アクセスをネットワークレベルで遮断
🧪
mTLS接続テスト:grpcurl--cert/--key オプションを付けて定期的にmTLS接続を検証

Created by SSuzuki1063

AWS SAP Learning Resources