Skip to content
Published on

HubbleとClusterMesh — Ciliumの可観測性とマルチクラスタ運用

Authors

はじめに

ネットワークポリシーの強制を始めると、すぐに新しい質問が押し寄せます。「さっき決済APIがなぜ切れた?」「このドロップはポリシーのせいかルーティングのせいか?」「2つのクラスタにまたがるサービスはどうフェイルオーバーする?」データパスがカーネルの中に入った以上、観測手段も同じ深さで提供されなければなりません。

HubbleはCiliumのeBPFデータパスから直接フローを抽出する可観測性レイヤーであり、ClusterMeshは複数クラスタのデータパスを1つのメッシュに束ねるマルチクラスタレイヤーです。どちらも追加のエージェントやサイドカーなしに、既存のCiliumの上で動作する点が核心です。本記事ではHubbleのアーキテクチャと実践クエリ、メトリクス/アラート構成、フローの長期保管、そしてClusterMeshの構成とグローバルサービス、運用上の問題、サービスメッシュとの関係まで扱います。

Hubbleアーキテクチャ

+------------------- ノード1 ------------------+
|  [eBPFデータパス]                             |
|       | perfリングバッファ (フローイベント)    |
|       v                                      |
|  [cilium-agent内のHubbleサーバ]               |
|   - ノードローカルのリングバッファにフロー保管 |
|   - gRPC API (unixソケット / 4244)           |
+------------------+---------------------------+
                   |
+------------------- ノード2 ... N -------------+
|  (同一構造)       |                            |
+------------------+---------------------------+
                   |
                   v  (全ノードの4244へ接続)
            [hubble-relay]  :4245
             - クラスタ全体のフローを単一APIに集約
                   |
        +----------+-----------+
        v                      v
   [hubble CLI]           [hubble-ui]
   (運用/デバッグ)         (サービスマップ可視化)

この構造から導かれる運用特性が3つ重要です。

  1. フローはノードローカルのリングバッファに一時的にしか存在しません。 デフォルト設定ではノードあたり一定数(デフォルト4095件)のフローのみメモリに保持されるため、長期分析が必要なら必ずexportを構成すべきです。
  2. relayは集約者であって保存庫ではありません。 relayが落ちてもデータパスやノードローカルの観測には影響がありません。
  3. フローデータモデルはL3/L4/L7メタデータ(送信元/宛先のidentityとラベル、verdict、ドロップ理由、HTTP/DNS情報)を持つ構造化イベントです。パケットのペイロードは保存しません。

hubble CLI実践

CLIはcilium-cliと同じチャネルで配布され、ローカルからrelayへポートフォワードして使うパターンが一般的です。

cilium hubble enable --ui          # Hubble + relay + UI を有効化
cilium hubble port-forward &       # localhost:4245 でrelayに接続
hubble status                      # フロー受信状態の確認

ドロップ追跡 — 最もよく使うクエリ

# クラスタ全体でドロップされたフローをリアルタイム観察
hubble observe --verdict DROPPED -f

# 特定名前空間のドロップ + 理由まで
hubble observe --verdict DROPPED --namespace payments \
  --output json | jq -r '.flow.drop_reason_desc' | sort | uniq -c

# ポリシー拒否だけを抽出 (ドロップ理由フィルタ)
hubble observe --verdict DROPPED --drop-reason-desc POLICY_DENIED

# 特定Podから出るトラフィックのうち拒否されたもの
hubble observe --from-pod payments/pg-gateway --verdict DROPPED

# 特定の2ワークロード間のすべてのフロー (方向不問)
hubble observe --pod shop/frontend --pod shop/backend

サービス間依存関係の把握

# order-apiが呼び出す先の整理 (egress依存関係)
hubble observe --from-pod shop/order-api --type trace:to-endpoint \
  --output json | jq -r '.flow.destination.namespace + "/" + (.flow.destination.labels | join(","))' \
  | sort | uniq -c | sort -rn

# DNSクエリ履歴 (どの外部ドメインを探しているか)
hubble observe --from-namespace payments --protocol dns \
  --output json | jq -r '.flow.l7.dns.query' | sort | uniq -c

# L7 HTTP観察 (L7ポリシーまたは可視性アノテーションが必要)
hubble observe --namespace shop --protocol http \
  --output json | jq -r '[.flow.l7.http.method, .flow.l7.http.url, (.flow.l7.http.code|tostring)] | join(" ")'

# 時間範囲 + ラベルセレクタの組み合わせ
hubble observe --since 30m --label app=checkout --verdict FORWARDED

verdictの種類にはFORWARDED(許可)、DROPPED(遮断)、AUDIT(監査モードで遮断されたはずのトラフィック)、ERRORなどがあり、ポリシー導入段階ではAUDITフィルタが中心的なツールになります。

Hubble UI — サービスマップ

hubble-uiは名前空間単位のサービスマップを描いてくれます。ワークロード間の矢印(L4/L7の区別)、失敗率、DNS宛先まで表示されるため、「ポリシーを書く前の現状の通信構造の把握」と「障害時にどの区間が切れたかの即時確認」に有用です。ただしUIはリアルタイムビュー中心のため、事後分析は後述するexportデータで行うべきです。

メトリクス — Prometheus連携

Hubbleはフローから派生したメトリクスをPrometheus形式で公開します。helm値でどのメトリクスを生成するか選択します。

hubble:
  enabled: true
  metrics:
    enableOpenMetrics: true
    enabled:
      - dns:query;labelsContext=source_namespace,destination_namespace
      - drop:labelsContext=source_namespace,destination_namespace;sourceContext=workload-name
      - tcp
      - flow
      - port-distribution
      - httpV2:exemplars=true;labelsContext=source_namespace,source_workload,destination_namespace,destination_workload

HTTPゴールデンシグナル

httpV2メトリクスにより、コード修正なしでサービス別のリクエストレート、エラー率、レイテンシ分位数が得られます。

# リクエストレート (RPS)
sum(rate(hubble_http_requests_total{destination_namespace="shop"}[5m]))
  by (destination_workload)

# 5xxエラー率
sum(rate(hubble_http_requests_total{status=~"5.."}[5m]))
  / sum(rate(hubble_http_requests_total[5m]))

# p99レイテンシ
histogram_quantile(0.99,
  sum(rate(hubble_http_request_duration_seconds_bucket[5m])) by (le, destination_workload))

ポリシードロップのアラートルール

groups:
  - name: cilium-policy
    rules:
      - alert: PolicyDropSpike
        expr: |
          sum(rate(hubble_drop_total{reason="POLICY_DENIED"}[5m]))
            by (source_namespace, destination_namespace) > 1
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: 'Policy drops between namespaces exceeded threshold'
      - alert: CiliumAgentDown
        expr: up{job="cilium-agent"} == 0
        for: 5m
        labels:
          severity: critical

ポリシー強制の直後はドロップアラートの閾値を意図的に低く設定して不足している許可ルールを早く見つけ、安定後に運用レベルへ引き上げる2段階の運用を推奨します。

フローログの長期保管 — exportとSIEM連携

リングバッファは分単位で回転するため、監査証跡と事後分析にはファイルexportが必須です。

hubble:
  export:
    fileMaxSizeMb: 50
    fileMaxBackups: 10
    dynamic:
      enabled: true
      config:
        content:
          - name: security-events
            filePath: /var/run/cilium/hubble/security.log
            includeFilters:
              - verdict: ["DROPPED", "AUDIT"]
          - name: dns-all
            filePath: /var/run/cilium/hubble/dns.log
            includeFilters:
              - protocol: ["dns"]

exportファイルは行単位JSONなので、標準的なログパイプラインにそのまま載せられます。

ノード: hubble exportファイル (JSON lines)
   -> ログ収集器 (Fluent Bit / Vector / OTel Collector)
   -> バッファ (Kafkaなど、任意)
   -> 保存/分析 (Elasticsearch, Loki, S3+Athena, SIEM)

SIEM連携で有用なマッピング: verdictとdrop_reason_descはセキュリティイベント分類へ、source/destinationのidentityラベルは資産タグ付けへ、l7.dns.queryは脅威インテリジェンス(悪性ドメインとの照合)への入力として使います。全フローを送るとコストが爆発するため、上記の例のようにセキュリティ関連(DROPPED/AUDIT/DNS)のみ選別してexportするのが一般的です。

ClusterMeshアーキテクチャ

ClusterMeshは複数のCiliumクラスタを束ね、クラスタ間のサービスディスカバリ、ロードバランシング、ポリシーを提供します。

+--------- クラスタA (id=1) -----------+      +--------- クラスタB (id=2) -----------+
|                                      |      |                                      |
|  [cilium-agent x N]                  |      |  [cilium-agent x N]                  |
|       |  (読み取り)                   |      |       |  (読み取り)                  |
|       v                              |      |       v                              |
|  [clustermesh-apiserver]  <----------+------+--- エージェントが相手クラスタの      |
|   - 自クラスタのサービス/identity/    |      |    apiserverに接続し状態をwatch      |
|     エンドポイントをetcd形式で公開    |      |                                      |
|   - LoadBalancer/NodePortで公開      |      |  [clustermesh-apiserver]             |
|                                      |      |                                      |
|  PodCIDR: 10.1.0.0/16 (重複禁止!)    |      |  PodCIDR: 10.2.0.0/16 (重複禁止!)    |
+--------------------------------------+      +--------------------------------------+

同期されるもの: サービス(グローバル)、identity、ipcache(リモートPod IP -> identity)
同期後: Pod間トラフィックは両クラスタのデータパスが直接ルーティング (中間ゲートウェイなし)

設計上の重要な点:

  • identityがクラスタ境界を越えて意味を保ちます。 クラスタBのapp=backend PodのidentityがクラスタAのipcacheにも伝播されるため、マルチクラスタポリシーが同じモデルで動作します。
  • データプレーンに追加ホップがありません。 コントロールプレーン(clustermesh-apiserver)だけが追加され、パケットは既存のルーティングモード(トンネル/ネイティブ)で直接流れます。
  • コントロールプレーン障害は新規変更の伝播だけを止めます。 apiserverが落ちても、すでに同期済みのサービス/identityでトラフィックは流れ続けます。

ClusterMesh構成の実践

前提条件

  • すべてのクラスタのPodCIDR/ノードIPが重複しないこと(最もよくある設計ミス)。
  • すべてのクラスタが同じCAで発行された証明書を使うこと(mTLS相互信頼)。
  • クラスタ間でノード同士が直接通信できること(VPCピアリング、専用線、VPNなど)。
  • クラスタごとに一意のnameとid(1〜255)が必要です。
# クラスタA helm values
cluster:
  name: cluster-a
  id: 1
clustermesh:
  useAPIServer: true
  apiserver:
    service:
      type: LoadBalancer

cilium-cliで接続

# 1) クラスタAのCAをクラスタBにコピー (同じCAを共有)
kubectl --context cluster-a -n kube-system get secret cilium-ca -o yaml \
  | kubectl --context cluster-b apply -f -

# 2) 両側でClusterMeshを有効化
cilium clustermesh enable --context cluster-a
cilium clustermesh enable --context cluster-b

# 3) 相互接続 (双方向ピアリングを自動構成)
cilium clustermesh connect --context cluster-a --destination-context cluster-b

# 4) 状態と接続性の検証
cilium clustermesh status --context cluster-a --wait
cilium connectivity test --context cluster-a --multi-cluster cluster-b

グローバルサービス — アフィニティとフェイルオーバー

同じ名前/名前空間のサービスにアノテーションを付けると、両クラスタのバックエンドが1つの仮想サービスに統合されます。

# 両クラスタに同一に配備
apiVersion: v1
kind: Service
metadata:
  name: checkout
  namespace: shop
  annotations:
    service.cilium.io/global: "true"
    service.cilium.io/affinity: "local"   # ローカルバックエンド優先、なければリモート
spec:
  selector:
    app: checkout
  ports:
    - port: 80
      targetPort: 8080
アノテーション動作
service.cilium.io/globaltrueクラスタ間でバックエンドを統合
service.cilium.io/affinitylocalローカルの正常バックエンド優先、全滅時にリモートへ
service.cilium.io/affinityremoteリモート優先(ドレイン/カナリアのシナリオ)
service.cilium.io/affinitynone全クラスタへ均等分散
service.cilium.io/global-sync-endpoint-slicestrueリモートエンドポイントをEndpointSliceとして同期

affinity=localの構成が事実上の標準です。平常時はクラスタ内部で処理してレイテンシを節約し、ローカルバックエンドがすべてunhealthyになると自動的にリモートクラスタへ切り替わるフェイルオーバーが得られます。このとき「unhealthy判定」はreadinessベースなので、アプリケーションのreadinessProbeが正確でなければフェイルオーバーも正確になりません。

マルチクラスタポリシー

CNPではクラスタを条件として使えます。

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-cross-cluster-checkout
  namespace: shop
spec:
  endpointSelector:
    matchLabels:
      app: checkout-db
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: checkout
            io.cilium.k8s.policy.cluster: cluster-a   # 特定クラスタからのみ

クラスタラベルを指定しないと、両クラスタのapp=checkoutの両方にマッチします。「DBは自クラスタのアプリからのみアクセス可能」のように、クラスタ境界をポリシーで強制したい場合にclusterラベルが必要です。

暗号化 — WireGuardとIPsec

クラスタ間トラフィックが信頼できないネットワークを通るなら、転送暗号化を有効にします。同じ機能はクラスタ内部のノード間にも適用されます。

項目WireGuardIPsec
設定難易度非常に低い(鍵の自動管理)鍵の生成/ローテーションを手動管理
カーネル要件5.6以上広範にサポート
性能一般的に優秀、マルチキューを活用アルゴリズム/ハードウェアオフロード依存
鍵ローテーション自動手動手順(シークレット交換)が必要
規制適合性アルゴリズム固定(ChaCha20)FIPS要求環境で好まれる
# WireGuardの有効化 (helm)
helm upgrade cilium cilium/cilium -n kube-system \
  --reuse-values --set encryption.enabled=true --set encryption.type=wireguard

# 暗号化状態の確認
kubectl -n kube-system exec ds/cilium -- cilium encrypt status

特別な規制要件(FIPSなど)がなければ、WireGuardの方が運用上は圧倒的にシンプルです。暗号化オーバーヘッドの分だけMTU計算をやり直す必要がある点を忘れないでください。

運用上の問題 — クラスタの追加/削除、バージョンスキュー、モニタリング

クラスタの追加/削除

# 追加: CA共有 -> enable -> connect (前述の手順と同じ)

# 削除: グローバルサービスの依存から整理が必要
# 1) 削除対象クラスタへのaffinity/トラフィックを先に排除
# 2) 接続解除
cilium clustermesh disconnect --context cluster-a --destination-context cluster-c
# 3) 残りのクラスタで状態確認
cilium clustermesh status --context cluster-a

削除するとグローバルサービスのリモートバックエンドが消えるため、affinity=noneでリモートに依存していたサービスがないか先に点検すべきです。

バージョンスキュー

ClusterMeshで接続されたクラスタ間のCiliumバージョンは、公式にはマイナー1段階の差までしかサポートされません。アップグレードは「1クラスタずつ、全クラスタが同じバージョンになるまで次のマイナーへ進まない」が原則です。Kubernetesのバージョンスキューとは別軸なので、両方をマトリクスで管理すべきです。

接続モニタリング

# メッシュ状態の中心コマンド
cilium clustermesh status --context cluster-a

# エージェント視点のリモートクラスタ同期状態
kubectl -n kube-system exec ds/cilium -- cilium status --verbose | grep -A10 ClusterMesh

Prometheusでは、clustermesh関連のエージェントメトリクス(リモートクラスタの準備状態、同期キュー)に加え、グローバルサービスのリモートバックエンド数が0になる状況をアラートにしておくと、「静かなメッシュ分断」を早く発見できます。

サービスメッシュとの関係 — Istioとの重なりと違い

「Ciliumがあれば Istioは不要か?」は最もよく受ける質問です。重なる部分と異なる部分を分けて見るべきです。

能力Cilium (+Hubble/ClusterMesh)Istio
L3/L4ポリシー、ネットワーク隔離中核領域副次的
L7ポリシー(HTTPパス/メソッド)可能(Envoyへ選別リダイレクト)可能(全トラフィックをプロキシ)
mTLS(ワークロード間暗号化)転送層(WireGuard/IPsec)中心アプリ層mTLS + ID証明
トラフィック分割(カナリア重み付け)限定的中核領域(VirtualService)
リトライ/タイムアウト/サーキットブレーカー未提供中核領域
マルチクラスタサービスClusterMeshマルチプライマリ/リモート構成
可観測性ネットワーク中心(フロー、verdict)リクエスト中心(トレーシング、アプリメトリクス)
オーバーヘッド低い(カーネル内処理中心)プロキシ経由コスト(ambientで削減可能)

実務上のまとめ: ネットワークのセキュリティ/隔離/観測が目的ならCiliumだけで十分なことが多く、カナリアデプロイ・リクエスト単位のリトライ・細かいトラフィック制御が必要ならサービスメッシュを上に載せます。実際、CiliumをCNIとして敷き、その上にIstio ambientを載せる組み合わせも一般的で、その場合は両者のmTLS/L7機能が重複しないよう役割分担(例: 暗号化はWireGuard、L7ルーティングはIstio)を明示的に決めるべきです。

運用チェックリスト

  • Hubble relay/UIが有効でhubble statusが正常か
  • DROPPED/AUDIT verdictベースのアラートが構成されているか
  • HTTPゴールデンシグナルメトリクス(httpV2)がダッシュボードに接続されているか
  • セキュリティイベント(ドロップ/DNS)のexportと長期保管パイプラインがあるか
  • 全クラスタのPodCIDR/ノードIP帯が重複しないことを文書で確認したか
  • クラスタ間で同じCAの共有が構成されているか
  • cluster id(1〜255)とnameが全クラスタで一意か
  • グローバルサービスのaffinity戦略(local推奨)とreadinessProbeを点検したか
  • クラスタ間のバージョンスキューがマイナー1段階以内に管理されているか
  • clustermesh statusとリモートバックエンド数がモニタリングに接続されているか
  • クラスタ削除手順(トラフィック排除 → disconnect)がランブックにあるか
  • 暗号化(WireGuard/IPsec)の選択とMTU再計算が完了しているか

おわりに

HubbleとClusterMeshは別々の機能に見えますが、どちらも「identityがカーネルまで降りたデータパス」という同じ基盤から生まれています。フローにidentityが付いているから観測が意味を持ち、identityがクラスタを越えて同期されるから、マルチクラスタポリシーが単一クラスタと同じモデルで動作します。運用の視点での結論はシンプルです。ポリシーを強制する前にまずHubbleを有効にし、クラスタを増やす前にCIDRとCAの設計を先に終わらせること。この順序を守れば、Ciliumスタックは単一クラスタからマルチクラスタまで同じ運用モデルで拡張できます。

参考資料