はじめに
Grafana Pyroscopeという継続的にプロファイル(Continuous Profiling)の情報を蓄積して活用するためのプロダクトがあります。このプロダクトにプロファイルの情報を蓄積する方法として以下の図にあるようにPyroscope SDK
とGrafana Agent
を用いる方法が提供されています。
ref https://grafana.com/docs/pyroscope/latest/configure-client/#sending-profiles-from-your-application
元々は単独でPyroscopeというプロジェクトでしたが、Grafana LabsのプロダクトとなったときにGrafana Phlareと統合されて現在のGrafana Pyrscopeとなりました。
Grafana Pyrscopeでプロファイル情報を集めるやり方としては専用のSDK(Pyroscope SDK)を利用するのが基本的な使い方になります。その際には対象のアプリケーションに変更を加える必要があります。
そこで、対象のアプリケーションのソースコードに変更を加えることなくGrafana Agentを用いてプロファイルを収集する機能(Auto-Instrumentation)が追加されました。
今回は対象のアプリケーションに対してソースコードの追加をせずにプロファイル情報を収集することができるGrafana Agentを用いた方法について説明していきます。
紹介する機能は現時点では少し実験的な印象を受ける部分もありますが、状況によっては使える場合もあるのかなと個人的には思っています。
Grafana Agentを用いたプロファイルの収集
- eBPF
- 収集対象はCPUプロファイリングのみ
-
BPF_PROG_TYPE_PERF_EVENT
が有効になっているkernel version >= 4.9
で利用可能 - Python、Ruby、JavaScriptのインタプリタ言語の情報も収集できるが、取得できる情報が限定的
- golang pull
- 収集対象はGo言語のみ
- Grafana Agentからpprofのエンドポイントへ接続できる場合に利用可能
- 取得対象はpprofの情報になるので、CPU以外にもメモリ等の情報も収集可能
この記事では以降でeBPF
を用いた場合の方法について説明していきます。
golang pull
についてはこちらの記事で紹介しています。
eBPFを用いたプロファイリングとそうじゃない場合の違い
eBPFを用いたプロファイルの他に各言語毎にプロファイルを取得する方法があります。
PyroscopeのBlogの中で違いについては以下のようにまとめられています。
ref https://pyroscope.io/blog/ebpf-profiling-pros-cons/#pros-and-cons-of-ebpf-and-non-ebpf-profiling
技術的な違いとしては以下について言及されているものが多いと思います。
- native-language profiling: User-space level で情報を取得する
- eBPF profiling: Kernel level で情報を取得する
ただ、実際にアプリケーションのデバッグをするためにプロファイルを見るというシナリオを考えた時に上記で書かれてる情報の取得方法の違いはあまりに気にならすに、以下の2点が利用者にとって特に気になる違いかなと思います。
- コンパイル言語について:CPUの情報は取得できるが、それ以外の情報が取得できない
- インタプリンタ言語について:CPUの情報についても不完全なものしか収集できない
違いの例
e.g. コンパイル言語
- Native Golang
- eBPF Golang
CPUのプロファイルについては後述するインタプリンタ言語のものと比べると差分が少なくある程度使えるのかなという気はします。ただ、実際のユースケースでCPUプロファイルだけじゃなく、メモリ等の他の情報と合わせて確認していくことになると思います。
アプリケーションのソースコードに何かを追加することなくこれだけの情報を取得できるというのは魅力的だと思いますが、CPUしか取得できなのでこのメリットに対して、eBPF用の設定をしたGrafana Agentを運用していく価値があるのかがポイントになると思います。
e.g. インタプリタ言語の例 (e.g. Python)
- Native Python
- eBPF Python
インタプリンタ言語に関してはeBPF profilerがランタイムのスタックトレースに上手くアクセスできなくて情報欠落が出てしまうため、現状ではあまり実用性がないです。
この辺りはこれからの技術かなと思います。ソースコードを追加せずに色々と情報を取得したいというニーズはあると思うので、研究テーマとしては魅力的だと思います。
Grafana Agentの設定方法
動作要件
- BPF_PROG_TYPE_PERF_EVENTが有効になっている kernel version >= 4.9
- securityContext
- privileged: true
- runAsGroup: 0
- runAsUser: 0
- hostPID: true
Grafana AgentでeBPF profilerを実行したい場合は上記が必要になります。
だいぶ強い権限を与える必要があるので、セキュリティ面では注意が必要かなと思います。
Kubernetes上で動かしてみる
eBPF profilerを用いる場合は、測定対象のアプリケーションと同じノードでGrafana Agentを実行する必要があります。そのため、DaemonSetでGrafana Agentを配置して使用するというのが想定されている使い方になります。
- 確認したバージョン
- Grafana Agent: v0.38.0
- Grafana Pyroscope: 1.2.0
- Grafana Agentの設定ファイル
eBPF profilerを動かす際に使用するGrafana Agentの設定を一部抜粋して記載します。
# スクレイプ対象の設定
# ここで Grafana Agent と同じノードで動作しているPodの情報を取得しています
discovery.kubernetes "local_pods" {
selectors {
field = "spec.nodeName=" + env("HOSTNAME")
role = "pod"
}
role = "pod"
}
# 書き込み先の Pyroscope のエンドポイント
pyroscope.write "endpoint" {
endpoint {
url = "http://pyroscope.observability.svc.cluster.local.:4040"
}
}
# スクレイプ対象と書き込み先のマッピング
pyroscope.ebpf "instance" {
forward_to = [pyroscope.write.endpoint.receiver]
targets = discovery.kubernetes.local_pods.targets
}
- Kubernetesのマニフェスト
apiVersion: apps/v1
kind: DaemonSet
metadata:
:
spec:
template:
metadata:
labels:
k8s-app: grafana-agent-ebpf
spec:
containers:
- args:
- run
- /etc/agent/config.river
- --storage.path=/tmp/agent
- --server.http.listen-addr=0.0.0.0:80
env:
- name: AGENT_MODE
value: flow
- name: HOSTNAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
image: docker.io/grafana/agent:v0.38.0
imagePullPolicy: IfNotPresent
name: grafana-agent
ports:
- containerPort: 80
name: http-metrics
readinessProbe:
httpGet:
path: /-/ready
port: 80
initialDelaySeconds: 10
timeoutSeconds: 1
securityContext: # 動作に必要な権限を追加
privileged: true
runAsGroup: 0
runAsUser: 0
volumeMounts:
- mountPath: /etc/agent
name: config
hostPID: true
volumes:
- configMap:
name: pyroscope-ebpf-grafana-agent
name: config
- 実際に起動させた際のGrafana Agentのログ
$ kubectl logs pyroscope-ebpf-grafana-agent-hzrbw
Defaulted container "grafana-agent" out of: grafana-agent, config-reloader
ts=2023-11-28T08:28:28.207599609Z level=info "boringcrypto enabled"=false
ts=2023-11-28T08:28:28.208643774Z level=info msg="running usage stats reporter"
ts=2023-11-28T08:28:28.209027373Z level=info msg="starting complete graph evaluation" controller_id="" trace_id=d658bd2d127bbc6c8bca244ab4637c9f
ts=2023-11-28T08:28:28.20921068Z level=info msg="applying non-TLS config to HTTP server" service=http
ts=2023-11-28T08:28:28.20931955Z level=info msg="finished node evaluation" controller_id="" trace_id=d658bd2d127bbc6c8bca244ab4637c9f node_id=http duration=246.393µs
ts=2023-11-28T08:28:28.20935043Z level=info msg="finished node evaluation" controller_id="" trace_id=d658bd2d127bbc6c8bca244ab4637c9f node_id=ui duration=1.612µs
ts=2023-11-28T08:28:28.209444333Z level=info msg="finished node evaluation" controller_id="" trace_id=d658bd2d127bbc6c8bca244ab4637c9f node_id=cluster duration=1.731µs
ts=2023-11-28T08:28:28.209963009Z level=info msg="finished node evaluation" controller_id="" trace_id=d658bd2d127bbc6c8bca244ab4637c9f node_id=pyroscope.write.endpoint duration=422.034µs
ts=2023-11-28T08:28:28.210473337Z level=info msg="Using pod service account via in-cluster config" component=discovery.kubernetes.local_pods
ts=2023-11-28T08:28:28.211212794Z level=info msg="finished node evaluation" controller_id="" trace_id=d658bd2d127bbc6c8bca244ab4637c9f node_id=discovery.kubernetes.local_pods duration=1.220205ms
ts=2023-11-28T08:28:28.211833145Z level=info msg="finished node evaluation" controller_id="" trace_id=d658bd2d127bbc6c8bca244ab4637c9f node_id=pyroscope.ebpf.instance duration=575.042µs
設定が正しく出来ていれば、エラー等に関するログが出力されずにpyroscope.write.endpoint
やdiscovery.kubernetes.local_pods
が正常に解決できたログが確認できます。
- Grafana PyroscopeでのCPUプロファイルの確認
Grafana Pyroscopeは独自のWebUIを持っているので、Grafana Loki等とは異なり、Grafanaとかを経由して参照しなくてもこんな形で情報を参照することができます。
所感
Goで書かれたアプリケーションのCPUプロファイルに関してなら、Grafana AgentのeBPF profilerは使えるかもしれないと思います。ただ、実際のユースケースではCPU以外の情報も参照したいということになると思うのでpprofを使って他の情報もみたいということになると思います。であれば、最初からpprofを使うことを考えた方が早いと思うので現実的なユースケースは現時点ではあまりないのかなと思っています。
Grafana Agent では他にgolang pull
というプロファイルの収集方法があり、個人的にはそちらの方が推しなので別の記事で紹介します。