Grafana Beylaの出来るコト出来ないコト
この記事は、2024/6/28に登壇したJagu'e'r Jagu'e'r O11y-SRE × CloudNative コラボ Meetupのリマスターになります。
分散トレーシングの悩み
突然ですが皆さん、分散トレーシングを実装する際、一度はこんなことを考えた経験はありませんか?
特にクラウドインフラ出身の私は、意気揚々と分散トレーシングを実装しようとした時に、アプリケーションコードが書けずに全く歯が立たなかった苦い経験があります。。。
でも、、
ということで、本記事ではBeylaとは何者なのか、従来の分散トレーシングとは何が違うのかを解説していきます!💪
分散トレーシングとは?
分散トレーシング
分散トレーシングとは、各アプリケーションの一連の処理の流れを可視化出来るようにしたものを指します。
トレーシング情報は、従来のモニタリングでは判別出来なかったアプリケーションのパフォーマンスボトルネックの特定や、未知のエラーを調査する上で重要な情報となります。
トレーシング計装方法
トレーシング/ログ/メトリクスなどのテレメトリ収集ロジックを仕込むことを計装と呼びます。
計装には大きく3種類あると理解しています。
分散トレーシングとSRE
計装するために、多かれ少なかれアプリケーションコードを修正する必要があります。
インフラ畑出身のSREとしては、なかなか分散トレーシングを推進するのはしんどいところがあります。
もっと気軽に分散トレーシングを導入してみたいですよね。。
そこでBeylaの登場です。
Grafana Beyla
Grafana Beylaとは
Grafana BeylaはオープンソースのeBPFベースの自動計測ツールで、Go、C/C++、Rust、Python、Ruby、Java、NodeJS、.NETなどのアプリケーションのオブザーバビリティを簡単に観測することが可能です[1]。
コア技術となるeBPFは、Linux HTTP/SおよびgRPCサービスのREDメトリクス(Rate-Error-Duration)と基本的なトレーススパンを、アプリケーションコードやコンフィギュレーションに変更を加えることなくキャプチャするために使用されます。
eBPFとは
eBPF(Extended Berkeley Packet Filter)とは、LinuxのUserSpaceからカーネル上で実行されるコードをカーネルにロードし、カーネル上で任意の処理を行う技術のことを指します[2]。
KernelSpaceの様々なイベントにeBPFを安全にアタッチ出来、パケットフィルタリングに留まらずセキュリティやサービスメッシュ、プロファイリングなど多岐に渡り使用されています。
※勉強不足により「色々なイベント」とボヤかしていますm(_ _)m
検証
全体図
以下の環境でBelyaから取得したメトリクス/トレースを可視化します。
処理の流れ
- go app1はリクエストを受けてから1秒スリープしてgo app2へリクエストします。
- go app2はリクエストを受けてから0~3秒ランダムにスリープしてレスポンスを返します。
- go app1がgo app2からレスポンスを受け取ると、即クライアントへレスポンスを返します。
サンプルコード
以下にサンプルコードを示します。
アプリケーションのデプロイはSkaffoldで行います。
Skaffoldについてはこちらで詳細に解説していますので、是非見て頂けると幸いです。
フォルダ構成は以下の通りです。
├── beyla
│ └── values.yaml
├── otel-collector
│ └── values.yaml
├── backend1
│ ├── app
│ │ ├── Dockerfile
│ │ └── main.go
│ └── k8s
│ ├── pod.yaml
│ └── service.yaml
├── backend2
│ ├── app
│ │ ├── Dockerfile
│ │ └── main.go
│ └── k8s
│ ├── pod.yaml
│ └── service.yaml
└── skaffold.yaml
otel-collector
values.yaml
image:
repository: "otel/opentelemetry-collector-contrib"
mode: "deployment"
config:
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
processors:
resource:
attributes:
- key: "cluster"
value: "iwasaki-sample-cluster"
action: upsert
- key: "location"
value: "asia-northeast1-a"
action: upsert
batch:
send_batch_max_size: 200
send_batch_size: 200
timeout: 5s
memory_limiter:
check_interval: 1s
limit_percentage: 65
spike_limit_percentage: 20
exporters:
googlecloud:
project: "iwasaki-sample"
googlemanagedprometheus:
service:
pipelines:
metrics:
receivers:
- otlp
processors:
- resource
- memory_limiter
- batch
exporters:
# https://grafana.com/docs/beyla/latest/metrics/
- googlemanagedprometheus
traces:
receivers:
- otlp
exporters:
- googlecloud
以下のhelmコマンドでinstallします。
helm install otel-collector opentelemetry-helm/opentelemetry-collector --version 0.91.0 -f otel-collector/values.yaml -n beyla
beyla
values.yaml
config:
data:
otel_metrics_export:
endpoint: "http://otel-collector-opentelemetry-collector:4318"
exporter
otel_traces_export:
endpoint: "http://otel-collector-opentelemetry-collector:4318"
sampler:
name: always_on
discovery:
services:
- k8s_namespace: "beyla"
k8s_pod_name: "golang-backend1"
- k8s_namespace: "beyla"
k8s_pod_name: "golang-backend2"
以下のhelmコマンドでinstallします。
helm install beyla grafana/beyla --version 1.0.1 -f beyla/values.yaml -n beyla
go app1
main.go
package main
import (
"fmt"
"net/http"
"os"
"time"
)
func handler(w http.ResponseWriter, r *http.Request) {
time.Sleep(1 * time.Second)
host, exists := os.LookupEnv("URL")
url := fmt.Sprintf("http://%s:8081", host)
resp, err := http.Get(url)
if err != nil {
fmt.Println("Error making request:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response status:", resp.Status)
}
func main() {
http.HandleFunc("/", handler)
fmt.Println("Starting server on :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
fmt.Println("Failed to start server:", err)
}
}
Dockerfile
FROM golang:1.21-alpine
WORKDIR /app
COPY main.go .
ENTRYPOINT ["go","run","main.go"]
pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: golang-backend1
labels:
app: golang-backend1
spec:
containers:
- name: golang-backend1
image: <image名>
env:
- name: URL
value: "golang-backend2"
- name: OTEL_TRACES_EXPORTER
value: "otlp"
- name: OTEL_METRICS_EXPORTER
value: "none"
- name: OTEL_LOGS_EXPORTER
value: "none"
- name: OTEL_SERVICE_NAME
value: "golang-backend1-service"
service.yaml
apiVersion: v1
kind: Service
metadata:
name: golang-backend1
spec:
selector:
app: golang-backend1
ports:
- protocol: TCP
port: 8080
targetPort: 8080
go app2
main.go
package main
import (
"fmt"
"math/rand"
"net/http"
"time"
)
func handler(w http.ResponseWriter, r *http.Request) {
// 0~3秒をランダムに待機
rand.Seed(time.Now().UnixNano())
randomSleepTime := time.Duration(rand.Intn(4)) * time.Second
time.Sleep(randomSleepTime)
fmt.Println("Hello, World!")
fmt.Fprintf(w, "Hello, World!")
}
func main() {
http.HandleFunc("/", handler)
fmt.Println("Starting server on :8081")
if err := http.ListenAndServe(":8081", nil); err != nil {
fmt.Println("Failed to start server:", err)
}
}
Dockerfile
FROM golang:1.21-alpine
WORKDIR /app
COPY main.go .
ENTRYPOINT ["go","run","main.go"]
pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: golang-backend2
labels:
app: golang-backend2
spec:
containers:
- name: golang-backend2
image: <image名>
env:
- name: OTEL_TRACES_EXPORTER
value: "otlp"
- name: OTEL_METRICS_EXPORTER
value: "none"
- name: OTEL_LOGS_EXPORTER
value: "none"
- name: OTEL_SERVICE_NAME
value: "golang-backend2-service"
service.yaml
apiVersion: v1
kind: Service
metadata:
name: golang-backend2
spec:
selector:
app: golang-backend2
ports:
- protocol: TCP
port: 8081
targetPort: 8081
Skaffold
skaffold.yaml
apiVersion: skaffold/v4beta9
kind: Config
build:
platforms: ["linux/amd64"]
googleCloudBuild: {}
artifacts:
- image: <image名>
context: ./backend1/app
docker:
dockerfile: Dockerfile
- image: <image名>
context: ./backend2/app
docker:
dockerfile: Dockerfile
tagPolicy:
customTemplate:
template: "skaffold-{{.DIGEST}}"
components:
- name: DIGEST
inputDigest: {}
manifests:
rawYaml:
- backend1/k8s/*.yaml
- backend2/k8s/*.yaml
portForward:
- resourceType: service
resourceName: golang-backend1
namespace: beyla
port: 8080
localPort: 8080
以下のコマンドを実行し、アプリケーションをデプロイします。
skaffold dev
検証結果(トレース)
分散トレーシングがCloud Traceで確認出来ました👏
検証結果(メトリクス)
レスポンスタイムのヒストグラムを確認出来ました👏
Grafana Belyaで出来るコト出来ないコト
検証してみたBeylaですが、現段階では色々と制約がありそうです。
出来るコト👍
- アプリケーションコードを一切触らずに計装が出来る
出来ないコト👎
- 分散トレーシングはgolangしか対応していない[3]。
- アプリの計装と比べ、対応しているライブラリがかなり少ないため詳細なトレーシング情報を取得出来ない[4]。
- net/http
- Gorilla Mux
- Gin
- gRPC-go
- 各言語のhttp/https
Beylaの展望
初期リリースが2023/7と比較的若いOSSのため、機能的にはまだまだ感が否めない印象でした[5]。
ただ、issueを見てみるとredisかkafkaの計装など、どんどん対応ライブラリが増えていきそうな予感がします[6][7]。
GithubのStar数も着々と増えているので、数年後には計装手段の1選択肢として定着するかも、、?[8]。
Beylaのこれからに期待大ですね!
Discussion