Skip to content
Published on

eBPFベースのゼロインストルメンテーションKubernetesオブザーバビリティ:Cilium HubbleとGrafana Beyla実践ガイド

Authors
  • Name
    Twitter
eBPFオブザーバビリティ

はじめに:なぜeBPFベースのゼロインストルメンテーションか

Kubernetes環境でオブザーバビリティを実現する従来のアプローチは2つあります。第一に、アプリケーションコードにSDKを組み込んでメトリクスやトレースを生成する明示的インストルメンテーション。第二に、各PodにEnvoyベースのサイドカープロキシを注入してL7トラフィックを観測するサービスメッシュアプローチです。両方のアプローチは本番環境で実績がありますが、根本的な限界があります。

SDKベースのインストルメンテーションは、すべてのサービスにライブラリを追加しインストルメンテーションコードを書く必要があります。数百のマイクロサービスを持つ大規模クラスターでこれを一貫して維持するには膨大なエンジニアリングコストがかかります。サイドカープロキシアプローチはコード変更なしでL7の可視性を得られますが、Podごとに追加コンテナをデプロイするためノードあたりのメモリ使用量が数百MB増加し、ネットワークホップの追加によりP99レイテンシが通常2~5ms上昇します。

eBPF(extended Berkeley Packet Filter)は両方のアプローチの限界を同時に解決します。Linuxカーネル内でサンドボックス化されたプログラムを実行することで、アプリケーションコードの変更なしにネットワークフロー、HTTP/gRPCリクエスト、DNSクエリ、TCP接続状態をカーネルレベルで観測できます。サイドカーコンテナが不要なためリソースオーバーヘッドが極めて低く、データをカーネルから直接収集するためアプリケーション性能への影響もほとんどありません。

本記事ではeBPFベースオブザーバビリティの中核ツールであるCilium HubbleとGrafana Beylaに焦点を当て、Kubernetesクラスターにおけるコード変更ゼロでのネットワーク可視性とアプリケーションパフォーマンスモニタリング(APM)構築の詳細な実践運用ガイドを提供します。

eBPF技術概要:カーネルレベルインストルメンテーションの原理

eBPFプログラムの実行フロー

eBPFはLinuxカーネル4.xから本格的に導入された技術で、カーネルモジュールを直接変更することなくカーネル空間でプログラムを実行できます。eBPFプログラムはユーザー空間で記述され、カーネルのVerifierを通過した後、JIT(Just-In-Time)コンパイラによりネイティブコードに変換されます。Verifierは無限ループの防止、境界外メモリアクセスのブロック、許可されたヘルパー関数呼び出しのみの許可によってカーネルの安定性を確保します。

eBPFプログラムをアタッチできるフックポイントは非常に多様です。ネットワーキングにはTC(Traffic Control)、XDP(eXpress Data Path)、ソケットレベルのフックがあります。システム全体の観測にはkprobes(カーネル関数エントリ/エグジット)、tracepoints(事前定義された観測ポイント)、uprobes(ユーザー空間関数トレーシング)が使用されます。Cilium Hubbleは主にTCとソケットレベルのフックを使用してネットワークフローを観測し、Grafana Beylaはuprobsとkprobesを活用してHTTP/gRPCリクエストからRED(Rate、Error、Duration)メトリクスを自動抽出します。

カーネルバージョン別のeBPF機能サポート

eBPFの機能サポートはカーネルバージョンによって異なります。Cilium Hubbleの完全な機能にはカーネル4.19以上が必要で、Grafana Beylaの自動インストルメンテーション機能はカーネル5.8以上で安定動作します。本番環境ではカーネル5.15 LTS以上を推奨します。具体的には、BTF(BPF Type Format)サポートが有効なカーネルでは、eBPFプログラムをCO-RE(Compile Once, Run Everywhere)モードでデプロイでき、多様なノード環境間の互換性が確保されます。

# 現在のノードのカーネルバージョンとBTFサポートを確認
uname -r
# 出力例: 5.15.0-91-generic

# BTFサポートの確認
ls /sys/kernel/btf/vmlinux
# ファイルが存在すればBTFサポートが有効

# eBPF機能プローブ(bpftool使用)
bpftool feature probe kernel | grep -E "map_type|program_type|helper"

# Kubernetesノード全体のカーネルバージョンを一括確認
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.kernelVersion}{"\n"}{end}'

Cilium Hubbleアーキテクチャ:ネットワークオブザーバビリティの中核

Hubbleの内部構造

Cilium HubbleはCilium CNIのオブザーバビリティレイヤーで、3つのコアコンポーネントで構成されています。第一に、Hubble Serverは各ノード上でCilium Agentに組み込まれて動作し、eBPFデータプレーンからネットワークイベントを収集します。第二に、Hubble Relayはクラスター全体のHubble Serverからイベントを集約し、単一のAPIエンドポイントで提供します。第三に、Hubble UIはサービスマップとネットワークフローを視覚的に表現します。

Hubbleが観測できるネットワークイベントの範囲は以下の通りです。L3/L4レベルでは、TCP/UDP/ICMPパケットのソース/デスティネーションIP、ポート、プロトコル情報とパケットドロップ理由を提供します。L7レベルでは、HTTPリクエスト/レスポンスのメソッド、パス、ステータスコード、DNSクエリとレスポンスのドメイン、タイプ、レスポンスコード、KafkaメッセージのトピックとAPIキーを解析します。これらの情報はすべてKubernetesアイデンティティ(名前空間、Pod、サービスラベル)に自動マッピングされ、サービス間の通信パターンを明確に把握できます。

サービスマップとフローの可視性

Hubbleの最も強力な機能の一つは自動生成されるサービスマップです。従来のサービスメッシュでは、EnvoyサイドカーがPodのインバウンド/アウトバウンドトラフィックをプロキシしてサービストポロジーを構築しますが、HubbleはカーネルeBPFプログラムが収集したネットワークフローデータからサービス依存関係グラフを自動構築します。これにより、サービスメッシュをデプロイすることなく、どのサービスがどのサービスと通信し、その通信成功率やレイテンシがどの程度かをリアルタイムで確認できます。

Hubbleのインストールと設定:Helmチャートベースのデプロイ

Cilium + Hubble統合インストール

HubbleはCilium CNIの一部としてデプロイされます。既にCiliumを使用している場合はHubble機能を有効にするだけです。新規クラスターの場合はCiliumインストール時にHubbleを有効にします。以下は本番環境に適したHelmバリュー設定です。

# cilium-hubble-values.yaml
# Cilium + Hubble本番デプロイ用Helmバリュー
hubble:
  enabled: true
  relay:
    enabled: true
    replicas: 3 # 高可用性のためのRelayレプリカ数
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 500m
        memory: 512Mi
    retryTimeout: 30s
    sortBufferLenMax: 5000
    sortBufferDrainTimeout: 1s
  ui:
    enabled: true
    replicas: 2
    ingress:
      enabled: true
      annotations:
        kubernetes.io/ingress.class: nginx
        cert-manager.io/cluster-issuer: letsencrypt-prod
      hosts:
        - hubble.internal.example.com
      tls:
        - secretName: hubble-ui-tls
          hosts:
            - hubble.internal.example.com
  metrics:
    enableOpenMetrics: true
    enabled:
      - dns
      - drop
      - tcp
      - flow
      - port-distribution
      - icmp
      - httpV2:exemplars=true;labelsContext=source_ip,source_namespace,source_workload,destination_ip,destination_namespace,destination_workload,traffic_direction
    serviceMonitor:
      enabled: true # Prometheus Operator ServiceMonitorの自動作成
      interval: 15s
  tls:
    enabled: true # Hubble ServerとRelay間のmTLSを有効化
    auto:
      enabled: true
      method: certmanager
      certManagerIssuerRef:
        group: cert-manager.io
        kind: ClusterIssuer
        name: hubble-issuer

# Cilium Agent最適化設定
operator:
  replicas: 2
  resources:
    requests:
      cpu: 100m
      memory: 128Mi

# モニターバッファサイズ(ノードごとのイベントキャッシュ)
bpf:
  monitorAggregation: medium # none/low/medium/high - 集約レベル
  monitorInterval: 5s
  monitorFlags: all
  mapDynamicSizeRatio: 0.0025
# Cilium + Hubbleのインストール(Helm)
helm repo add cilium https://helm.cilium.io
helm repo update

helm upgrade --install cilium cilium/cilium \
  --version 1.16.5 \
  --namespace kube-system \
  --values cilium-hubble-values.yaml \
  --wait

# インストールの確認
cilium status --wait
cilium hubble port-forward &

# Hubble CLIでクラスター全体のフローを確認
hubble observe --since 1m --output compact

# 特定の名前空間のHTTPトラフィックのみフィルタリング
hubble observe \
  --namespace production \
  --protocol http \
  --http-status 500-599 \
  --output json | jq '{
    source: .flow.source.labels,
    destination: .flow.destination.labels,
    http: .flow.l7.http
  }'

# サービス間のドロップされたパケットを分析
hubble observe \
  --verdict DROPPED \
  --namespace production \
  --output table

HubbleメトリクスベースのPromQLクエリ

Hubbleが生成したメトリクスがPrometheusに収集されると、さまざまなネットワークレベルのSLI(Service Level Indicators)を定義できます。以下は本番環境でよく使用されるPromQLクエリ集です。

# サービスごとのHTTPリクエスト成功率(SLI)
(
  sum(rate(hubble_http_requests_total{http_status_code=~"2.."}[5m])) by (destination_workload, destination_namespace)
  /
  sum(rate(hubble_http_requests_total[5m])) by (destination_workload, destination_namespace)
) * 100

# ネットワークポリシーによるパケットドロップ率
sum(rate(hubble_drop_total{reason="POLICY_DENIED"}[5m])) by (source_workload, destination_workload)
/ on(source_workload) group_left
sum(rate(hubble_flows_processed_total[5m])) by (source_workload)

# TCP接続確立P99レイテンシ(サービスごと)
histogram_quantile(0.99,
  sum(rate(hubble_tcp_connect_duration_seconds_bucket[5m])) by (le, destination_workload, destination_namespace)
)

# DNSクエリ失敗率TOP10ワークロード
topk(10,
  sum(rate(hubble_dns_responses_total{rcode!="No Error"}[5m])) by (source_workload, source_namespace, qtypes, rcode)
)

# 名前空間間トラフィック量マトリクス
sum(rate(hubble_flows_processed_total[5m])) by (source_namespace, destination_namespace)

Grafana Beyla自動インストルメンテーション:ゼロコードAPM

Beylaの動作原理

Grafana BeylaはeBPFを使用した自動インストルメンテーションツールで、アプリケーションコードの変更なしにHTTP/HTTPS、gRPC、SQLリクエストのRED(Rate、Error、Duration)メトリクスと分散トレースを自動生成します。Beylaは各ノードにDaemonSetとしてデプロイされ、uprobsを通じてGo、Java、Python、Node.js、Rust、.NETなど各種ランタイムのHTTPハンドラーとgRPCサーバー関数を自動検出してフックします。

Beylaが生成するコアメトリクスは以下の通りです。http.server.request.durationはHTTPサーバーリクエスト処理時間をヒストグラムとして提供し、http.client.request.durationはHTTPクライアントの外部呼び出し時間を追跡します。rpc.server.durationrpc.client.durationはgRPCサーバーとクライアントの呼び出し時間をそれぞれ測定します。これらのメトリクスはOpenTelemetryのセマンティック規約に準拠しており、OTLPプロトコルで直接エクスポートするか、Prometheus Remote Writeで送信できます。

Hubbleがネットワーク層(L3/L4/L7)の可視性を提供するのに対し、Beylaはアプリケーション層のパフォーマンスメトリクスとトレースで補完します。両ツールを併用することで、インフラネットワーキングからアプリケーションパフォーマンスまで、コード変更なしで観測できる完全なオブザーバビリティスタックが構築されます。

Beylaの自動サービス検出

Beylaはプロセスを自動検出してインストルメンテーション対象を特定します。検出は2つの方法で行われます。第一に、実行バイナリのシンボルテーブルを分析し、HTTPサーバーやgRPCサーバー関連の関数が含まれているかを確認します。Goバイナリの場合、net/http.(*Server).Servegoogle.golang.org/grpc.(*Server).Serveなどのシンボルを検出します。第二に、リスニングソケットを監視し、特定ポートでTCP接続を受け付けているプロセスを特定します。これら2つの方法を組み合わせることで、どの言語で書かれたサービスでも自動的にインストルメンテーション対象に含められます。

Beylaのデプロイと設定:Kubernetes DaemonSet構成

DaemonSetベースのデプロイ

KubernetesにBeylaをデプロイする最も一般的な方法はDaemonSetです。各ノードでホストPID名前空間にアクセスし、ノード上のすべてのプロセスを検出してインストルメンテーションします。

# beyla-daemonset.yaml
# Grafana Beyla DaemonSetデプロイマニフェスト
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: beyla
  namespace: monitoring
  labels:
    app.kubernetes.io/name: beyla
    app.kubernetes.io/component: auto-instrumentation
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: beyla
  template:
    metadata:
      labels:
        app.kubernetes.io/name: beyla
      annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port: '9090'
        prometheus.io/path: '/metrics'
    spec:
      serviceAccountName: beyla
      hostPID: true # ホストPID名前空間へのアクセスが必要
      tolerations:
        - operator: Exists # すべてのノードにデプロイ(masterを含む)
      containers:
        - name: beyla
          image: grafana/beyla:1.8.2
          securityContext:
            privileged: true # eBPFプログラムのロードに必要
            runAsUser: 0
          ports:
            - containerPort: 9090
              name: metrics
              protocol: TCP
          env:
            - name: BEYLA_OPEN_PORT
              value: '80,443,8080,8443,3000,5000,9090'
            - name: BEYLA_SERVICE_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: BEYLA_KUBE_METADATA_ENABLE
              value: 'autodetect'
          volumeMounts:
            - name: beyla-config
              mountPath: /config
            - name: sys-kernel-security
              mountPath: /sys/kernel/security
              readOnly: true
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
      volumes:
        - name: beyla-config
          configMap:
            name: beyla-config
        - name: sys-kernel-security
          hostPath:
            path: /sys/kernel/security
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: beyla-config
  namespace: monitoring
data:
  beyla-config.yml: |
    # Beylaメイン設定ファイル
    log_level: info

    # サービス自動検出設定
    discovery:
      services:
        - k8s_namespace: "production|staging"
          k8s_pod_labels:
            app.kubernetes.io/part-of: ".*"
        - k8s_namespace: ".*"
          k8s_deployment_name: ".*"

    # メトリクスエクスポート設定
    otel_metrics_export:
      endpoint: http://mimir-distributor.monitoring:4317
      protocol: grpc
      interval: 15s
      features:
        - application
        - application_process
        - application_service_graph
      histograms:
        - explicit:
            boundaries:
              - 0.005
              - 0.01
              - 0.025
              - 0.05
              - 0.1
              - 0.25
              - 0.5
              - 1
              - 2.5
              - 5
              - 10

    # トレースエクスポート設定
    otel_traces_export:
      endpoint: http://tempo-distributor.monitoring:4317
      protocol: grpc
      sampler:
        name: parentbased_traceidratio
        arg: "0.1"                       # 10%サンプリング

    # Prometheusエンドポイント設定
    prometheus_export:
      port: 9090
      path: /metrics
      features:
        - application
        - application_process
        - application_service_graph

    # ネットワークメトリクス(実験的機能)
    network:
      enable: true
      cidrs:
        - 10.0.0.0/8
        - 172.16.0.0/12

    # 属性設定
    attributes:
      kubernetes:
        enable: true
      host_id:
        fetch_timeout: 5s
      select:
        beyla_network_flow_bytes:
          include:
            - k8s.src.namespace
            - k8s.dst.namespace
            - direction
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: beyla
  namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: beyla
rules:
  - apiGroups: ['']
    resources: ['pods', 'nodes', 'services', 'replicationcontrollers']
    verbs: ['get', 'list', 'watch']
  - apiGroups: ['apps']
    resources: ['deployments', 'replicasets', 'statefulsets', 'daemonsets']
    verbs: ['get', 'list', 'watch']
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: beyla
subjects:
  - kind: ServiceAccount
    name: beyla
    namespace: monitoring
roleRef:
  kind: ClusterRole
  name: beyla
  apiGroup: rbac.authorization.k8s.io
# Beyla DaemonSetのデプロイ
kubectl apply -f beyla-daemonset.yaml

# デプロイ状態の確認
kubectl rollout status daemonset/beyla -n monitoring

# Beylaログで自動検出されたサービスを確認
kubectl logs -n monitoring -l app.kubernetes.io/name=beyla --tail=50 | grep "instrumenting"
# 出力例:
# msg="instrumenting process" pid=12345 service=frontend-deployment
# msg="instrumenting process" pid=12346 service=api-server-deployment
# msg="instrumenting process" pid=12347 service=payment-service-deployment

# Beylaが生成したメトリクスを確認
kubectl exec -n monitoring $(kubectl get pod -n monitoring -l app.kubernetes.io/name=beyla -o jsonpath='{.items[0].metadata.name}') \
  -- wget -qO- http://localhost:9090/metrics | head -40

# eBPFプログラムのロード状態を確認
kubectl exec -n monitoring $(kubectl get pod -n monitoring -l app.kubernetes.io/name=beyla -o jsonpath='{.items[0].metadata.name}') \
  -- bpftool prog list | grep beyla

Grafanaスタック統合:Tempo、Mimir、Loki

統合アーキテクチャ

Cilium HubbleとGrafana Beylaが生成するテレメトリデータをGrafana LGTM(Loki、Grafana、Tempo、Mimir)スタックと統合することで、ゼロインストルメンテーションに基づく完全なオブザーバビリティプラットフォームが構築されます。データフローは以下の通りです。

HubbleメトリクスはPrometheus ServiceMonitor経由で収集され、Mimirに長期保存されます。Hubbleのネットワークフローログは FluentdまたはVectorを通じてLokiに送信されます。Beylaが生成するREDメトリクスはOTLPプロトコル経由でMimir Distributorに直接送信されるか、Prometheus Remote Writeで保存されます。Beylaの分散トレースはOTLP経由でTempo Distributorに送信されます。

GrafanaダッシュボードではMimirメトリクス(Hubbleネットワーク + Beyla APM)、Tempoトレース(Beyla自動生成)、Lokiログ(Hubbleフローログ)を単一ビューで相関分析できます。トレースからメトリクス、メトリクスからログへと自由にナビゲートできることで、インシデント時の根本原因分析(RCA)時間が大幅に短縮されます。

Grafanaデータソースとダッシュボードの設定

# grafana-datasources.yaml
# Grafanaデータソース ConfigMap(Hubble + Beyla統合)
apiVersion: v1
kind: ConfigMap
metadata:
  name: grafana-datasources
  namespace: monitoring
  labels:
    grafana_datasource: '1'
data:
  datasources.yaml: |
    apiVersion: 1
    datasources:
      # Mimir - HubbleネットワークメトリクスとBeyla APMメトリクスの統合保存先
      - name: Mimir
        type: prometheus
        url: http://mimir-query-frontend.monitoring:8080/prometheus
        access: proxy
        isDefault: true
        jsonData:
          timeInterval: 15s
          exemplarTraceIdDestinations:
            - name: traceID
              datasourceUid: tempo
              urlDisplayLabel: View Trace
          httpMethod: POST
          prometheusType: Mimir

      # Tempo - Beyla自動生成トレースの保存先
      - name: Tempo
        type: tempo
        uid: tempo
        url: http://tempo-query-frontend.monitoring:3200
        access: proxy
        jsonData:
          tracesToMetrics:
            datasourceUid: mimir
            spanStartTimeShift: "-1h"
            spanEndTimeShift: "1h"
            tags:
              - key: service.name
                value: service
              - key: http.method
                value: method
            queries:
              - name: Request Rate
                query: "sum(rate(http_server_request_duration_seconds_count{service=\"${__span.tags.service.name}\"}[5m]))"
              - name: Error Rate
                query: "sum(rate(http_server_request_duration_seconds_count{service=\"${__span.tags.service.name}\",http_status_code=~\"5..\"}[5m]))"
          tracesToLogs:
            datasourceUid: loki
            spanStartTimeShift: "-5m"
            spanEndTimeShift: "5m"
            filterByTraceID: true
            filterBySpanID: false
            tags:
              - key: k8s.namespace.name
                value: namespace
              - key: k8s.pod.name
                value: pod
          serviceMap:
            datasourceUid: mimir
          nodeGraph:
            enabled: true
          search:
            hide: false
          lokiSearch:
            datasourceUid: loki

      # Loki - Hubbleフローログ + アプリケーションログ
      - name: Loki
        type: loki
        uid: loki
        url: http://loki-read.monitoring:3100
        access: proxy
        jsonData:
          derivedFields:
            - datasourceUid: tempo
              matcherRegex: "traceID=(\\w+)"
              name: TraceID
              url: "$${__value.raw}"

eBPF vs サイドカー vs エージェント比較

eBPFベースのオブザーバビリティ、サイドカープロキシベースのサービスメッシュ、従来のエージェントベースの監視を運用観点から比較します。

比較項目eBPF(Hubble + Beyla)サイドカー(Istio/Envoy)エージェント(SDKインストルメンテーション)
コード変更の要否不要不要(自動注入)SDK統合が必要
Podごとの追加コンテナなし1(Envoy)なし
ノードあたりのメモリオーバーヘッド約300MB(Cilium+Beyla)約100MB x Pod数約50MB(エージェント)
P99レイテンシへの影響0.1ms未満2~5ms0.5~1ms
L3/L4ネットワーク可視性非常に詳細限定的なし
L7 HTTP/gRPC可視性BeylaによるREDメトリクス非常に詳細SDK依存
分散トレーシングBeyla自動(コンテキスト伝播に制限)自動(完全な伝播)完全制御
DNS観測Hubble組み込みEnvoy DNSプロキシ別途設定が必要
カーネルバージョン依存性5.8以上推奨なしなし
セキュリティ権限要件privilegedまたはCAP_BPFNET_ADMIN一般ユーザー
サービスメッシュ機能(mTLS、トラフィック制御)Cilium経由で部分サポート完全サポートなし
運用の複雑さ低(ただしSDK管理コストが高い)
多言語サポート言語非依存言語非依存言語別SDKが必要

選択ガイド

eBPFベースのアプローチは以下の場合に適しています。数百のマイクロサービスに一貫した監視を適用する必要があるが、各サービスにSDKを統合するエンジニアリングリソースが不足している場合。サイドカープロキシのリソースオーバーヘッドを吸収しにくい高密度ノードクラスター。レガシーサービスを含めてコード変更なしで監視を実現する必要がある移行期。DNS、TCP接続状態、パケットドロップなどきめ細かなネットワークレベルの可視性が重要なインフラ中心の運用。

一方、サイドカープロキシの方が適している場合もあります。mTLS、トラフィック分割、サーキットブレーカーなどサービスメッシュのトラフィック制御機能が必要な場合。完全な分散トレースのコンテキスト伝播が不可欠な場合。カーネル5.8未満のノードで運用する必要がある環境。eBPFのprivilegedコンテナ権限を許可できないセキュリティポリシーを持つクラスター。

運用上の考慮事項

カーネルバージョンとディストリビューションの互換性

eBPFベースツールの運用で最も一般的な障害原因はカーネルバージョンの不一致です。Cilium Hubbleの基本機能はカーネル4.19以上で動作しますが、L7プロトコル解析と高度なメトリクスにはカーネル5.4以上が必要です。Grafana Beylaのuprobsベースの自動インストルメンテーションはカーネル5.8以上でのみ安定動作し、BPF ring bufferを活用する機能は特に5.8を最低要件とします。

Amazon EKSの場合、AL2023 AMIがデフォルトでカーネル6.1を提供するため問題ありません。ただし、AL2 AMIはカーネル5.10を使用しており、ほとんどの機能をサポートしますが一部の高度な機能には制限がある場合があります。GKEのContainer-Optimized OS(COS)はカーネル5.15以上を提供し、Ubuntuノードイメージも5.15以上です。AKSはUbuntu 22.04ベースのノードでカーネル5.15を使用しています。

セキュリティに関する考慮事項

BeylaはeBPFプログラムをカーネルにロードするため、CAP_SYS_ADMINまたはCAP_BPF + CAP_PERFMON権限が必要です。最もシンプルな方法はprivilegedコンテナとして実行することですが、本番環境では最小権限の原則に従い必要なcapabilitiesのみを付与することを推奨します。

# 最小権限セキュリティコンテキスト(privilegedの代わり)
securityContext:
  privileged: false
  runAsUser: 0
  capabilities:
    add:
      - BPF # eBPFプログラムのロード
      - PERFMON # perfイベントアクセス
      - NET_RAW # rawソケットアクセス(ネットワーク観測)
      - SYS_PTRACE # プロセストレーシング(uprobes)
      - DAC_READ_SEARCH # ファイルシステムトラバーサル
    drop:
      - ALL

ただし、一部のKubernetesセキュリティポリシー(Pod Security Standards Restrictedプロファイル)ではこれらのcapabilitiesの追加自体が拒否される場合があります。その場合、Beyla DaemonSet用の個別の例外ポリシーを設定する必要があります。また、eBPFプログラムはカーネル空間で実行されるため、Beylaイメージのソースと完全性を検証する必要があります。署名付きイメージを使用し、イメージプルポリシーをAlwaysに設定して改ざんされたイメージのデプロイを防止してください。

リソース管理とスケーリング

大規模クラスター(500ノード以上)でHubbleとBeylaを運用する場合、リソース使用量を注意深く監視する必要があります。Hubble Relayはクラスター内の全ノードからイベントを集約するため、メモリとCPU使用量はノード数に比例して増加します。100ノードあたりRelayインスタンス1つをベースラインとし、500ノード以上のクラスターではRelayを5インスタンス以上に水平スケールしてロードバランシングを設定してください。

BeylaのメモリUsageはノードあたりのインストルメンテーション対象プロセス数に応じて変動します。プロセスあたり約2~5MBの追加メモリが必要なため、Pod密度の高いノード(50+ Pod)ではBeylaのメモリ上限を1Gi以上に引き上げる必要がある場合があります。また、不要なリソース消費を防ぐため、discovery設定でインストルメンテーション対象を特定の名前空間やラベルに限定することを推奨します。

障害事例と復旧手順

事例1:eBPFプログラムロード失敗

最も一般的な障害タイプは、カーネルバージョンの不一致によるeBPFプログラムのロード失敗です。ノードアップグレード過程で一部のノードだけが新しいカーネルに移行した場合に容易に発生します。

症状:Beyla PodがCrashLoopBackOff状態に陥り、ログにfailed to load eBPF program: invalid argumentまたはkernel doesn't support bpf_ringbufエラーが記録される。

診断手順:まず対象ノードのカーネルバージョンを確認します。kubectl get node <node-name> -o jsonpath='{.status.nodeInfo.kernelVersion}'でカーネルバージョンを照会し、Beylaの最低要件バージョン(5.8以上)と比較します。BTFサポートも確認が必要です。ノードにアクセスして/sys/kernel/btf/vmlinuxの存在を確認してください。

復旧方法:nodeSelectorまたはnodeAffinityを使用して、古いカーネルのノードにBeyla DaemonSetがスケジュールされないようにします。長期的には、該当ノードのOSイメージをアップグレードしてください。

事例2:Hubble Relayのメモリ不足(OOMKilled)

大量のネットワークフローを処理中にHubble Relayがメモリ制限を超過した場合に発生します。

症状:Hubble Relay Podが繰り返し再起動し、kubectl describe podOOMKilledの終了理由が確認される。Hubble CLIとUIで接続が断続的に切断される。

診断手順:kubectl top pod -n kube-system -l k8s-app=hubble-relayで現在のメモリ使用量を確認します。また、hubble_relay_received_flows_totalメトリクスでRelayが処理している毎秒イベント量を把握します。

復旧方法:短期的にはRelayのメモリ上限を引き上げます(512Miから1Gi以上)。中期的にはCiliumのbpf.monitorAggregation設定をmediumまたはhighに調整してイベント量を削減します。長期的にはRelayインスタンスを水平スケールします。

事例3:Beylaメトリクスの欠落

Beylaは正常に動作しているが、特定サービスのメトリクスが収集されない。

症状:特定サービスのREDメトリクスがGrafanaダッシュボードに表示されない。Beylaログに該当サービスのinstrumentationメッセージが出現しない。

診断手順:サービスのバイナリ形式を確認します。Goサービスが-ldflags="-s -w"オプションでシンボルをストリップしてビルドされている場合、Beylaが関数シンボルを検出できない可能性があります。非標準ポートやカスタムHTTPフレームワークを使用している場合も自動検出に失敗する可能性があります。

復旧方法:Goバイナリの場合、-ldflags="-s -w"を使用せずシンボルを保持するか、Beylaの設定でBEYLA_OPEN_PORT環境変数に該当サービスのポートを明示的に追加します。非標準フレームワークの場合、BeylaのジェネリックHTTPトレーシングモードを有効にしてソケットレベルでHTTPパターンを検出させます。

事例4:HubbleとBeylaのメトリクス不一致

HubbleのネットワークレベルHTTPメトリクスとBeylaのアプリケーションレベルHTTPメトリクスで異なる数値が表示される事例。

症状:Hubbleが報告するHTTPリクエスト数がBeylaの報告値より多いまたは少ない。

根本原因分析:HubbleはネットワークパケットレベルでHTTPを解析するため、ヘルスチェック、Kubernetesプローブ、サイドカー間通信を含むすべてのHTTPトラフィックをキャプチャします。一方、BeylaはアプリケーションプロセスのFunction Callをトレースするため、アプリケーションが実際に処理したリクエストのみをインストルメンテーションします。この不一致は正常な動作であり、両方のメトリクスを合わせて分析することでインフラトラフィックとアプリケーショントラフィックを区別するのに有用です。

運用チェックリスト

eBPFベースのオブザーバビリティを本番環境に導入する際に確認すべき項目一覧です。

デプロイ前の確認事項:

  • すべてのノードのカーネルバージョンが5.8以上であることを確認
  • BTF(BPF Type Format)サポートが有効であることを確認(/sys/kernel/btf/vmlinuxが存在)
  • ノードのセキュリティポリシー(Pod Security Standards)がprivilegedまたはCAP_BPFを許可することを確認
  • マネージドKubernetes(EKS、GKE、AKS)の場合、CNIプラグイン置換の実現可能性を確認
  • Ciliumが既存CNIと競合しないことを検証(kube-proxy置換モードを含む)

デプロイ後の確認事項:

  • Cilium Agentが全ノードでReadyであることを確認(cilium status
  • Hubble Relayが全ノードのHubble Serverに接続されていることを確認
  • ログでBeylaが期待されるサービスを自動検出していることを確認
  • HubbleとBeylaのメトリクスがMimir/Prometheusに正常に収集されていることを確認
  • BeylaのトレースがTempoに正常に送信されていることを確認

監視アラート:

  • Hubble Relayのメモリ使用率が80%を超えた場合にアラート
  • Beyla DaemonSetのReady Pod数がノード数と一致していることを確認
  • eBPFプログラムのロード失敗イベントを検出
  • Hubbleメトリクス収集遅延(scrape duration)を監視
  • BeylaのOTLPエクスポート失敗率を監視

定期メンテナンス:

  • 月次:カーネルセキュリティパッチ後のeBPFプログラム互換性を確認
  • 四半期:Cilium/Beylaバージョンアップグレードのレビューとテスト
  • 半期:カーネルバージョンアップグレードの計画(LTSカーネルの追跡)

参考文献

クイズ

Q1: 「eBPFベースのゼロインストルメンテーションKubernetesオブザーバビリティ:Cilium HubbleとGrafana Beyla実践ガイド」の主なトピックは何ですか?

eBPFベースのCilium Hubbleによるネットワークオブザーバビリティと、Grafana Beylaの自動インストルメンテーションを活用した、コード変更不要のKubernetes監視アーキテクチャの実践運用ガイドです。

Q2: Cilium Hubbleアーキテクチャ:ネットワークオブザーバビリティの中核について説明してください。

Hubbleの内部構造 Cilium HubbleはCilium CNIのオブザーバビリティレイヤーで、3つのコアコンポーネントで構成されています。第一に、Hubble Serverは各ノード上でCilium Agentに組み込まれて動作し、eBPFデータプレーンからネットワークイベントを収集します。第二に、Hubble Relayはクラスター全体のHubble Serverからイベントを集約し、単一のAPIエンドポイントで提供します。第三に、Hubble UIはサービスマップとネットワークフローを視覚的に表現します。

Q3: Hubbleのインストールと設定:Helmチャートベースのデプロイの主な手順は何ですか?

Cilium + Hubble統合インストール HubbleはCilium CNIの一部としてデプロイされます。既にCiliumを使用している場合はHubble機能を有効にするだけです。新規クラスターの場合はCiliumインストール時にHubbleを有効にします。以下は本番環境に適したHelmバリュー設定です。 HubbleメトリクスベースのPromQLクエリ Hubbleが生成したメトリクスがPrometheusに収集されると、さまざまなネットワークレベルのSLI(Service Level Indicators)を定義できます。

Q4: Grafana Beyla自動インストルメンテーション:ゼロコードAPMの主な特徴は何ですか?

Beylaの動作原理 Grafana BeylaはeBPFを使用した自動インストルメンテーションツールで、アプリケーションコードの変更なしにHTTP/HTTPS、gRPC、SQLリクエストのRED(Rate、Error、Duration)メトリクスと分散トレースを自動生成します。Beylaは各ノードにDaemonSetとしてデプロイされ、uprobsを通じてGo、Java、Python、Node.js、Rust、.NETなど各種ランタイムのHTTPハンドラーとgRPCサーバー関数を自動検出してフックします。

Q5: Beylaのデプロイと設定:Kubernetes DaemonSet構成の主な手順は何ですか? DaemonSetベースのデプロイ KubernetesにBeylaをデプロイする最も一般的な方法はDaemonSetです。各ノードでホストPID名前空間にアクセスし、ノード上のすべてのプロセスを検出してインストルメンテーションします。