はじめに
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を通るようになり従来のサイドカーを作成する方式と比べてオーバーヘッドが大幅に削減されます。
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で詳細な解析を行うことができます。
環境構築
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 Mesh | Cilium + Hubble |
---|---|---|
Service間の繋がり | o | o |
個々のパケットトレーシング | x | o |
DeploymentやPodの情報 | o | △ |
NodePoolの最低要件 | e2-standard-4 | e2-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との協調など残してしまった部分もあるので、今後も深掘りして行ければと思います。