GKE Observabilityツール – Cloud Service MeshとCiliumの比較

Sreake事業部

2024.9.9

はじめに

extended Berkley Packet Filter (eBPF) は、Linuxのカーネルに組み込まれた技術で、カーネルに直接変更を加えることなくプログラムを安全にカーネル内で実行することを可能にします。

CiliumはeBPFを利用し、サイドカーを必要としないサービスメッシュを構築するソフトウェアです。従来の方法では、各Podにサイドカーを追加する必要がありましたが、CiliumではeBPFを用いてこれを集約し、より軽快な通信処理を実現します。

今回の技術検証では、Google Kubernetes Engine (GKE) のDataplane V2を有効にしたクラスタ上で、Ciliumの機能を利用したデータ可視化ツールであるHubbleを使用し、従来の監視ツールであるCloud Service Meshと機能やパフォーマンスを比較しました。

自己紹介

石川

Sreake事業部・夏季インターン生の石川です。大学では情報理論と呼ばれる、情報の通信や圧縮に関わる理論的な研究を行っています。実務的な経験は少ないですがインフラやネットワークに興味があり今回のインターンに参加しました。

加藤

Sreake事業部・夏季インターン生の加藤です。普段は宅内2.5Gbpsやラックサーバでおもしろネットワークを構築してKubernetesで遊んでいます。最近は、クラウドネイティブに興味があり知識を蓄えることができればと思い参加しました。

前提知識

Google Kubernetes Engine (GKE)

Google Kubernetes Engine (GKE) は、Google Cloudが提供するマネージドKubernetesサービスです。
GKEの提供するネットワーク制御として、CalicoとDataplane V2の2種類が存在します。

Calicoはiptablesベースの制御であるのに対して、Dataplane V2はCilium・eBPFをベースとした制御です。
Dataplane V2では、後述するCiliumを用いたサイドカーレスなサービスメッシュの構築が簡単に行えます。

サービスメッシュ

マイクロサービスにおけるサービス間通信の処理を行う仕組みとしてサービスメッシュがあります。スケールに伴い増えるマイクロサービスを各サービスに依存せず監視・コントロールすることができます。

サイドカー

メインのアプリケーションコンテナと共に同じPod内で実行されるサポート用のコンテナです。特にマイクロサービス環境では、Podの数だけサイドカーが必要なため、それ自身が大きな負荷となることがあります。

https://kubernetes.io/ja/docs/concepts/workloads/pods/sidecar-containers/

前提条件

  • WSL: 5.15.153.1-microsoft-standard-WSL2
  • Ubuntu: 24.04
  • Google Cloud SDK: 489.0.0
  • kubectl: 1.30.2
  • gke: v1.29.7-gke.1104000

検証用のプロダクトとしてGoogle Cloudが公開しているmicroservices-demoを使用します。

https://github.com/GoogleCloudPlatform/microservices-demo/tree/main/terraform

GKE Cloud Service Meshを使った可視化

前提

Istio

Istioはサービスメッシュを構成するツールであり、各Podに対してサイドカーの形で導入されるため、プロダクトのコードを変更せずにサービスメッシュ機能を使用できます。今回はCilium + Hubbleの比較対象としてIstioベースのGKE Cloud Service Meshを使用します。

Cloud Service Mesh

GKEのCloud Service MeshはTraffic DitectorのコントロールプレーンとIstioベースのフルマネージドサービスメッシュであるAnthos Service Mesh (ASM)を組み合わせたサービス間の通信を監視することができるサービスです。対象のクラスタに対してFleetを登録し、サービスメッシュ機能を有効化することで簡単に利用可能です。

構築手順

サービスメッシュ機能を有効にするにはクラスタを建てたのち、そのクラスタに対して Fleet を登録します。
具体的にサンプルのデプロイを行う際は以下のような手順で実行します。

まず初めにGKEクラスタを構築します。
この際、マシンタイプはCloud Service Meshを使用する最低要件であるe2-standard-4 を指定します。

# 事前準備
export PROJECT_ID=<PROJECT_ID>
export CLUSTER_NAME=<CLUSTER_NAME>
export REGION=asia-northeast1

gcloud services enable container.googleapis.com \
  --project=${PROJECT_ID}

# GKEのクラスタを作成code-block
gcloud container clusters create ${CLUSTER_NAME} \
  --project=${PROJECT_ID} --region=${REGION} --machine-type=e2-standard-4

続いてクラスタにFleetを登録します。FleetはGKEクラスタを管理するのに便利な機能で、今回のCloud Service Meshを利用するのに必要です。

# プロジェクトのメッシュ機能とFleet APIを有効化
gcloud services enable mesh.googleapis.com --project ${PROJECT_ID}
gcloud container fleet mesh enable --project ${PROJECT_ID}

# クラスタにFleetを登録
gcloud container fleet memberships register ${CLUSTER_NAME} \
    --gke-cluster ${REGION}/${CLUSTER_NAME} \
    --enable-workload-identity

クラスタに対してメッシュ機能を有効化し、istioのsidecar-injectionを行います。

# クラスタにmesh_idを適用
FLEET_PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format 'value(projectNumber)')
gcloud container clusters update --project ${PROJECT_ID} ${CLUSTER_NAME} \
    --zone ${REGION} --update-labels="mesh_id=proj-$FLEET_PROJECT_NUMBER"
    
# クラスタのメッシュ機能を有効化
gcloud container fleet mesh update --project ${PROJECT_ID} \
    --management automatic \
    --memberships ${CLUSTER_NAME}

# asmcliをダウンロード/インストール
curl https://storage.googleapis.com/csm-artifacts/asm/asmcli_1.22 > asmcli
./asmcli install --project_id ${PROJECT_ID} --cluster_name ${CLUSTER_NAME} --cluster_location ${REGION} --fleet_id ${PROJECT_ID} --output_dir tmp --enable_all --ca mesh_ca

# istio-injectionラベルを設定
kubectl label namespace default istio-injection=enabled

クラスタの設定が終わったので実際に使用するmicroservices-demoをデプロイします。

# サンプルのリポジトリをクローン
git clone --depth 1 --branch v0 https://github.com/GoogleCloudPlatform/microservices-demo.git
cd microservices-demo/

ただし、microservices-demoはそのままデプロイしてもloadgeneratorがエラーで停止します。

これはistio-proxyが通信をブロックするためで設定を行い対策する必要があります。

💡 istio-proxyは、iptablesを使用してトラフィックをリダイレクトし、安全な通信を確保しますが、istio-proxyがPod内の他のコンテナと同時に起動されるため、起動初期段階のinitコンテナ(この場合はloadgenerator)は、外部との通信がブロックされる問題が発生します。これは、istio-proxyが完全に起動していない状態で通信を試みるためで、この問題の解決策として、initコンテナのUIDをistio-proxyが使用する1337に設定することがあります。この設定により、istioのiptablesルールから除外され、外部との通信が可能となります。

この設定を行うにはrelease/kubernetes-manifests.yamlを以下のように変更します。

@@ -132,30 +132,31 @@
                 echo "Frontend is reachable."
                 exit 0
             fi
             echo "Error: Could not reach frontend - Status code: ${STATUSCODE}"
             sleep $RETRY_INTERVAL
           done
           echo "Failed to reach frontend after $MAX_RETRIES attempts."
           exit 1
         name: frontend-check
         securityContext:
           allowPrivilegeEscalation: false
           capabilities:
             drop:
               - ALL
           privileged: false
+          runAsUser: 1337
           readOnlyRootFilesystem: true
         image: busybox:latest
         env:
         - name: FRONTEND_ADDR
           value: "frontend:80"

修正が終わったら、実際にデプロイし外部からアクセスできるか確かめます。

# microservices-demoのマニフェストを適用
kubectl apply -f ./release/kubernetes-manifests.yaml

# 外部からアクセス可能なIPアドレスを取得
kubectl get service frontend-external -n frontend

# EXTERNL_IP にアクセスしてアプリケーションを確認
http://<EXTERNAL_IP>

使ってみる

Google Cloudのコンソール画面から[サービスメッシュ]タブに移動することで登録されたクラスタの情報を確認することができます。トポロジビューでは各サービス間でどこからどこへ通信が行われているのかグラフィカルに表示できます。また、名前空間やラベル等によってソートすることもできます。

リストビューではそれぞれのサービスの状況を一覧で表示することができます。こちらで各サービスに対するトラフィック量やエラー率などより定量的な指標を確認することができます。

各サービスを選択することで該当のサービスについてより詳しい情報を確認することができます。

Cilium + Hubbleを使った可視化

前提

Ciliumとは

Ciliumは、オープンソースのネットワーキング、セキュリティ、オブザーバビリティを提供するソフトウェアです。メッシュ層をeBPFを用いてカーネルに直接統合することにより、全ての通信がCiliumを通るようになり従来のサイドカーを作成する方式と比べてオーバーヘッドが大幅に削減されます。

従来のサイドカーを使用したサービスメッシュとサイドカーレスのものとの比較 https://cilium.io/use-cases/service-mesh/

Pod内のリソースが減っていることからわかるように、各Podが独自のプロキシを持つ必要がなく、ネットワークトラフィックの処理がカーネルレベルで直接行われるため、リソース消費を低減することができます。

GKEでは、Dataplane V2を有効にすることでCiliumを使用することができます。Ciliumのkube-proxyの置き換えに関しては、過去のインターン生が検証していますので合わせてご覧ください。 (https://sreake.com/blog/cilium-kube-proxy-replacement/)

Hubbleとは

HubbleはCiliumに基づいて構築されたネットワークオブザーバビリティツールです。Ciliumを活用して、ネットワークフローやポリシーの決定、サービス依存関係などの詳細な可視化を提供します。リアルタイムのトラフィックフロー分析やトラヒック分析に長けており、ワークロードの通信と動作を詳細に把握するのに最適で、hubble-uiやhubble-cliで詳細な解析を行うことができます。

Hubble UIの例 https://cilium.io/use-cases/service-map/

環境構築

Ciliumの導入

GKEクラスタの作成時にAutopilotを有効にすると自動的にDataplane V2が有効になります。

microservices-demoの導入

監視を行う対象として、Google Cloudが提供しているmicroservices-demoを展開します。

https://github.com/GoogleCloudPlatform/microservices-demo/blob/main/README.md を参考に手順を進めます。

まず、コンテナクラスタの構築を行います。 PROJECT_IDにはGoogle CloudのプロジェクトIdを、CLUSTER_NAMEには任意の設定したいクラスタ名を設定します。日本国内での想定ですのでREGIONはasia-northeast1を使用していますが適宜変更してください。

export PROJECT_ID=<PROJECT_ID>
export CLUSTER_NAME=<CLUSTER_NAME>
export REGION=asia-northeast1

create-auto(Autopilot)でコンテナクラスタを作成します。 Autopilotが有効になると自動的にDataplane V2が有効になり、eBPFを用いたサイドカーレス構成のCiliumが有効になります。

gcloud services enable container.googleapis.com --project=${PROJECT_ID}
gcloud container clusters create-auto ${CLUSTER_NAME} --project=${PROJECT_ID} --region=${REGION} --enable-dataplane-v2-flow-observability

クラスタの構築が終わったので実際に使用するmicroservices-demoをデプロイします。

git clone --depth 1 --branch v0 <https://github.com/GoogleCloudPlatform/microservices-demo.git>
cd microservices-demo/
kubectl apply -f ./release/kubernetes-manifests.yaml

しばらく時間をおき、PodがRunnnigステートになったら完成です。

kubectl get pods

NAME                                     READY   STATUS    RESTARTS   AGE
adservice-7fc864d99f-z9nrd               1/1     Running   0          1h
cartservice-854bd558c4-blz59             1/1     Running   0          1h
checkoutservice-bcf5d57cc-q9p6b          1/1     Running   0          1h
currencyservice-54696f58d9-x96vx         1/1     Running   0          1h
emailservice-88f4786cc-cs5t9             1/1     Running   0          1h
frontend-6d4f7c7f68-z45pq                1/1     Running   0          1h
loadgenerator-67456c5675-sp595           1/1     Running   0          1h
paymentservice-5fd9c447bf-rmlwk          1/1     Running   0          1h
productcatalogservice-695d69bcf5-sb46p   1/1     Running   0          1h
recommendationservice-85c5459f67-dcv4r   1/1     Running   0          1h
redis-cart-5d68cb5ff4-vj8bp              1/1     Running   0          1h
shippingservice-5477876cd5-ghhx2         1/1     Running   0          1h

Hubbleの導入

その後、hubble-uiを展開します。

hubble-ui-128.yaml

# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: hubble-ui
  namespace: gke-managed-dpv2-observability
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: hubble-ui
  labels:
    app.kubernetes.io/part-of: cilium
rules:
  - apiGroups:
      - networking.k8s.io
    resources:
      - networkpolicies
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - componentstatuses
      - endpoints
      - namespaces
      - nodes
      - pods
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - apiextensions.k8s.io
    resources:
      - customresourcedefinitions
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - cilium.io
    resources:
      - "*"
    verbs:
      - get
      - list
      - watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: hubble-ui
  labels:
    app.kubernetes.io/part-of: cilium
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: hubble-ui
subjects:
  - kind: ServiceAccount
    name: hubble-ui
    namespace: gke-managed-dpv2-observability
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: hubble-ui-nginx
  namespace: gke-managed-dpv2-observability
data:
  nginx.conf: |
    server {
        listen       8081;
        # uncomment for IPv6
        # listen       [::]:8081;
        server_name  localhost;
        root /app;
        index index.html;
        client_max_body_size 1G;
        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            # CORS
            add_header Access-Control-Allow-Methods "GET, POST, PUT, HEAD, DELETE, OPTIONS";
            add_header Access-Control-Allow-Origin *;
            add_header Access-Control-Max-Age 1728000;
            add_header Access-Control-Expose-Headers content-length,grpc-status,grpc-message;
            add_header Access-Control-Allow-Headers range,keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout;
            if ($request_method = OPTIONS) {
                return 204;
            }
            # /CORS
            location /api {
                proxy_http_version 1.1;
                proxy_pass_request_headers on;
                proxy_hide_header Access-Control-Allow-Origin;
                proxy_pass http://127.0.0.1:8090;
            }
            location / {
                # double `/index.html` is required here
                try_files $uri $uri/ /index.html /index.html;
            }
        }
    }
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: hubble-ui
  namespace: gke-managed-dpv2-observability
  labels:
    k8s-app: hubble-ui
    app.kubernetes.io/name: hubble-ui
    app.kubernetes.io/part-of: cilium
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: hubble-ui
  template:
    metadata:
      labels:
        k8s-app: hubble-ui
        app.kubernetes.io/name: hubble-ui
        app.kubernetes.io/part-of: cilium
    spec:
      securityContext:
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault
      serviceAccount: hubble-ui
      serviceAccountName: hubble-ui
      containers:
        - name: frontend
          image: quay.io/cilium/hubble-ui:v0.13.1
          ports:
            - name: http
              containerPort: 8081
          volumeMounts:
            - name: hubble-ui-nginx-conf
              mountPath: /etc/nginx/conf.d/default.conf
              subPath: nginx.conf
            - name: tmp-dir
              mountPath: /tmp
          terminationMessagePolicy: FallbackToLogsOnError
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1000
            runAsGroup: 1000
            capabilities:
              drop:
                - all
        - name: backend
          image: quay.io/cilium/hubble-ui-backend:v0.13.1
          env:
            - name: EVENTS_SERVER_PORT
              value: "8090"
            - name: FLOWS_API_ADDR
              value: "hubble-relay.gke-managed-dpv2-observability.svc:443"
            - name: TLS_TO_RELAY_ENABLED
              value: "true"
            - name: TLS_RELAY_SERVER_NAME
              value: relay.gke-managed-dpv2-observability.svc.cluster.local
            - name: TLS_RELAY_CA_CERT_FILES
              value: /var/lib/hubble-ui/certs/hubble-relay-ca.crt
            - name: TLS_RELAY_CLIENT_CERT_FILE
              value: /var/lib/hubble-ui/certs/client.crt
            - name: TLS_RELAY_CLIENT_KEY_FILE
              value: /var/lib/hubble-ui/certs/client.key
          ports:
            - name: grpc
              containerPort: 8090
          volumeMounts:
            - name: hubble-ui-client-certs
              mountPath: /var/lib/hubble-ui/certs
              readOnly: true
          terminationMessagePolicy: FallbackToLogsOnError
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1000
            runAsGroup: 1000
            capabilities:
              drop:
                - all
      volumes:
        - configMap:
            defaultMode: 420
            name: hubble-ui-nginx
          name: hubble-ui-nginx-conf
        - emptyDir: {}
          name: tmp-dir
        - name: hubble-ui-client-certs
          projected:
            # note: the leading zero means this number is in octal representation: do not remove it
            defaultMode: 0400
            sources:
              - secret:
                  name: hubble-relay-client-certs
                  items:
                    - key: ca.crt
                      path: hubble-relay-ca.crt
                    - key: tls.crt
                      path: client.crt
                    - key: tls.key
                      path: client.key
---
kind: Service
apiVersion: v1
metadata:
  name: hubble-ui
  namespace: gke-managed-dpv2-observability
  labels:
    k8s-app: hubble-ui
    app.kubernetes.io/name: hubble-ui
    app.kubernetes.io/part-of: cilium
spec:
  type: ClusterIP
  selector:
    k8s-app: hubble-ui
  ports:
    - name: http
      port: 80
      targetPort: 8081
kubectl apply -f hubble-ui-128.yaml

5分程度で展開が完了し、以下のコマンドからhubble-uiのサービスをポート転送することでローカルで閲覧することができるようになります。

kubectl -n gke-managed-dpv2-observability port-forward service/hubble-ui 16100:80 --address='0.0.0.0'

使ってみる

先ほどのコマンドを実行したのち、http://127.0.0.1:16100へアクセスするとhubble-uiを見ることができます。

例えば、frontendのカードをクリックすると、frontendと通信しているPodが一覧で表示されます。

また、下部ではリアルタイムに通信の状況が表示されます。

GKE Cloud Service Mesh と Cilium + Hubble の比較

GKE Cloud Service Mesh はクラスタ全体の統計情報の可視化に強い一方で、Cilium + Hubbleでは Pod 間の個々のフローのトレーシングができるなどネットワークのリアルタイムな監視に特化している印象です。Cilium + HubbleでもGrafanaなどのツールを別途導入することで統計情報も可視化することができます。

https://docs.cilium.io/en/stable/observability/grafana/

比較軸Cloud Service MeshCilium + Hubble
Service間の繋がりoo
個々のパケットトレーシングxo
DeploymentやPodの情報o
NodePoolの最低要件e2-standard-4e2-medium
統計情報o別途必要

また、CSMは個々のPodに対してサイドカーを必要とする影響でノードに必要な性能が高いのに対し、 Cilium + Hubbleは比較的性能の低いノードでも動かすことができるため、コスト面で有利です。

CiliumはeBPFを基に構築されており、これによりネットワークのポリシー実施やトラフィックのリダイレクトがカーネルレベルで直接処理されます。これに対し、ASMではIstio CNIプラグインを用いたサイドカープロキシへのトラフィックリダイレクトを追加で設定する必要があります。

実際に、今回使用したmicroservices-demoではメインアプリケーションを編集する必要があり、設定に苦慮した点でもあります。その点、eBPFで実装されるCiliumではアプリケーションのコードを変更する必要がないため簡便に導入することができます。

今後の展望として、インターン期間で調査することのできなかったCilium + HubbleとGrafanaやPrometheusとの協調による統計的な情報の可視化を行いたいです。

おわりに

石川

インターン以前は Kubernetes をあまり触ったことがなく、eBPFや Cilium、Hubbleについてはこのインターンで初めて知りました。
今回の調査でそれらについて深く知ることができたと思いますし、手を動かして確認することの大切さと面白さを知ることができ大変有意義な時間になりました。特にeBPFについては非常に興味が湧いたので今後もキャッチアップしていきたいです。

加藤

2週間という短い期間でしたが、テーマ設定からアウトプットまでメンターの方による助けを借りて進められて良かったと感じています。
リソースの操作やカスタムリソースなどの作成に留まっていた私にとって、サービスメッシュの構築とeBPFを用いた通信の最適化を比較するこのインターンは、クラウドネイティブ技術の奥深さと面白さを再認識させる経験となりました。GarafanaやPrometheusとの協調など残してしまった部分もあるので、今後も深掘りして行ければと思います。

参考文献

ブログ一覧へ戻る

お気軽にお問い合わせください

SREの設計・技術支援から、
SRE運用内で使用する
ツールの導入など、
SRE全般についてご支援しています。

資料請求・お問い合わせ