|
| 1 | +--- |
| 2 | +title: "Red Hat build of OpenTelemetryで遊んでみた" |
| 3 | +date: 2024-05-12T00:00:00+09:00 |
| 4 | +draft: false |
| 5 | +categories: |
| 6 | + - openshift |
| 7 | +tags: |
| 8 | + - openshift |
| 9 | + - kubernetes |
| 10 | + - opentelemetry |
| 11 | +image: telescope.jpg |
| 12 | +slug: otel-openshift |
| 13 | +--- |
| 14 | + |
| 15 | +Header Photo by [Matthew Ansley](https://unsplash.com/ja/@ansleycreative) on [Unsplash](https://unsplash.com/photos/black-telescope-during-day-time-8SjeH5pZbjw) |
| 16 | + |
| 17 | +## OpenTelemetry |
| 18 | + |
| 19 | +[OpenTelemetry](https://opentelemetry.io/docs/what-is-opentelemetry/)(OTEL)は、CNCFのプロジェクトの1つです。Observabilityの重要性が語られ始めて久しい昨今ですが、OTELはクラウドネイティブなソフトウェアにおいてObsevabilityを実現するためのAPI、SDK、ツール群です。GitHubにたくさんの[関連リポジトリ](https://github.com/orgs/open-telemetry/repositories)があります。 |
| 20 | + |
| 21 | +メトリクス、ログ、トレースを合わせてテレメトリデータと呼んでいます。OpenTelemetryを使ソフトウェアを計装し、テレメトリの生成、収集、エクスポートが行えます。オープンソースかつベンダー(vendor neutral)に依存しない点も特長です。 |
| 22 | + |
| 23 | +### OpenTelemetry Collector |
| 24 | + |
| 25 | +>  |
| 26 | +> https://opentelemetry.io/docs/collector/ より引用 |
| 27 | +
|
| 28 | +OpenTelemetry Collectorは、さまざまなテレメトリデータを受信、処理、エクスポートするためのコンポーネントです。以下の図のように、Jeager、Prometheusなどからテレメトリデータを受信し、処理した後、バックエンドにデータを送信できます。これにより複数のエージェント/コレクターを管理・運用する必要がなくなります。 |
| 29 | + |
| 30 | +## Red Hat build of OpenTelemetry |
| 31 | + |
| 32 | +UpstreamのOpenTelemetryプロジェクトに基づいた製品で、OpenShift上におけるOpenTelemetry Collector のデプロイと管理、さらにワークロードの計装の簡素化をサポートします。つまり、OTEL Collectorの導入や自動計装などを、OpenShiftクラスタおよびその上で動くワークロードに提供します。 |
| 33 | + |
| 34 | +OpenShiftでは、(OpenShiftらしく、)Operatorを使ってOpenTelemetry Collectorをデプロイします。具体的には、OperatorHubからRed Hat build of OpenTelemetry Operatorをインストールした後、`OpenTelemetryCollector` CRを作成します。`OpenTelemetryCollector` CRの `spec.onfig`には、主に以下の項目を設定します。 |
| 35 | + |
| 36 | +- **Receiver: データを取り込む。OTLP Receiver、Jaeger Receiver、Prometheus Receiver (Tech Preview)などのプロトコルがある** |
| 37 | +- **Processor:** データが受信されてからエクスポートされるまでの間のデータ処理。Batch Processor、Memory Limiter processorなどがある |
| 38 | +- **Exporter:** データを1つ以上のバックエンドや宛先に送信する。**OTLP exporter、OTLP HTTP exporter、Debug exporter、Prometheus exporterなどのプロトコルがサポートされる** |
| 39 | + |
| 40 | +詳細は、[ドキュメント](https://docs.openshift.com/container-platform/4.14/observability/otel/otel-configuration-of-otel-collector.html#otel-collector-config-options_otel-configuration-of-otel-collector)を参照してください。 |
| 41 | + |
| 42 | +## Red Hat build of OpenTelemetryで遊ぶ |
| 43 | + |
| 44 | +Red Hat build of OpenTelemetryおよびOTELがなにもわからなかったので、[Sidecar injectionを用いてOTEL Collectorをワークロードに挿入する方法](https://docs.openshift.com/container-platform/4.14/observability/otel/otel-sending-traces-and-metrics-to-otel-collector.html)を試してみました。サンプルアプリ(Go)とYAMLマニフェストたちは、[こちら](https://github.com/nishipy/go-otel-openshift)に置いておきます。今回使った環境は以下の通りです。 |
| 45 | + |
| 46 | +- OpenShift: v4.14.20 |
| 47 | +- OpenTelemetry Operator: v0.93.0-3 |
| 48 | + |
| 49 | +### Red Hat build of OpenTelemetryのインストール |
| 50 | + |
| 51 | +前述の通り、OperatorHubからRed Hat build of OpenTelemetry (OpenTelemetry Operator)をインストールします。[ドキュメント](https://docs.openshift.com/container-platform/4.14/observability/otel/otel-installing.html#installing-otel-by-using-the-cli_install-otel)に従って実行すればOKです。 |
| 52 | + |
| 53 | +``` |
| 54 | +$ oc version |
| 55 | +Client Version: 4.12.10 |
| 56 | +Kustomize Version: v4.5.7 |
| 57 | +Server Version: 4.14.20 |
| 58 | +Kubernetes Version: v1.27.11+ec42b99 |
| 59 | +
|
| 60 | +$ oc get csv | grep opentelemetry-operator |
| 61 | +opentelemetry-operator.v0.93.0-3 Red Hat build of OpenTelemetry 0.93.0-3 opentelemetry-operator.v0.93.0-2 Succeeded |
| 62 | +``` |
| 63 | + |
| 64 | +### サンプルワークロード用コンテナイメージ作成 |
| 65 | + |
| 66 | +今回テレメトリデータをCollectorに送信するため、コンテナイメージを作成します。[こんな感じ](https://github.com/nishipy/go-otel-openshift/blob/main/cmd/sampleapp/main.go)でGoでOTELライブラリを使い計装してみました。 |
| 67 | + |
| 68 | +```go |
| 69 | +package main |
| 70 | + |
| 71 | +import ( |
| 72 | + "context" |
| 73 | + "log" |
| 74 | + "net/http" |
| 75 | + "time" |
| 76 | + |
| 77 | + "go.opentelemetry.io/otel" |
| 78 | + "go.opentelemetry.io/otel/codes" |
| 79 | + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" |
| 80 | + "go.opentelemetry.io/otel/sdk/trace" |
| 81 | +) |
| 82 | + |
| 83 | +func main() { |
| 84 | + ctx := context.Background() |
| 85 | + exp, err := otlptracegrpc.New( |
| 86 | + ctx, |
| 87 | + otlptracegrpc.WithInsecure(), |
| 88 | + ) |
| 89 | + if err != nil { |
| 90 | + panic(err) |
| 91 | + } |
| 92 | + |
| 93 | + tracerProvider := trace.NewTracerProvider(trace.WithBatcher(exp)) |
| 94 | + defer func() { |
| 95 | + if err := tracerProvider.Shutdown(ctx); err != nil { |
| 96 | + panic(err) |
| 97 | + } |
| 98 | + }() |
| 99 | + otel.SetTracerProvider(tracerProvider) |
| 100 | + |
| 101 | + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
| 102 | + tr := otel.Tracer("http-server") |
| 103 | + _, span := tr.Start(r.Context(), "handleRequest") |
| 104 | + defer span.End() |
| 105 | + |
| 106 | + // Simulate some work |
| 107 | + time.Sleep(100 * time.Millisecond) |
| 108 | + span.SetStatus(codes.Ok, "Status is OK.") |
| 109 | + |
| 110 | + w.Write([]byte("Hello, world!\n")) |
| 111 | + }) |
| 112 | + |
| 113 | + // Start the HTTP server |
| 114 | + port := ":8080" |
| 115 | + log.Printf("Starting server on port %s...", port) |
| 116 | + if err := http.ListenAndServe(port, nil); err != nil { |
| 117 | + log.Fatalf("failed to start server: %v", err) |
| 118 | + } |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +これを以下のようなDockerfileを使ってコンテナイメージをビルドします。ビルドしたものは、`ghcr.io/nishipy/go-otel-sample:latest` にも置いてあります。 |
| 123 | + |
| 124 | +```dockerfile |
| 125 | +FROM golang:1.21.3-alpine as builder |
| 126 | +WORKDIR /app |
| 127 | +COPY go.mod go.sum ./ |
| 128 | +RUN go mod download go.opentelemetry.io/otel |
| 129 | +COPY ./cmd/sampleapp/*.go ./ |
| 130 | +RUN go build -trimpath -ldflags="-w -s" -o "otel-sample" |
| 131 | + |
| 132 | +FROM gcr.io/distroless/static-debian11 |
| 133 | +COPY --from=builder /app/otel-sample /otel-sample |
| 134 | +CMD ["/otel-sample"] |
| 135 | +``` |
| 136 | + |
| 137 | +### サンプルワークロードのデプロイ |
| 138 | + |
| 139 | +ビルドしたコンテナイメージを使って、ワークロードをデプロイし、`Route` を用いて公開してみましょう。まず、Deploymentを作成します。現時点では何の意味もないですが`sidecar.opentelemetry.io/inject: "true"` というアノテーションがポイントです。次節で作成する `OpenTelemetryCollector` CRによって追加されるAdmission Webhookがこのアノテーションを検知し、OTEL Collectorコンテナを挿入してくれます。 |
| 140 | + |
| 141 | +```yaml |
| 142 | +--- |
| 143 | +apiVersion: v1 |
| 144 | +kind: Namespace |
| 145 | +metadata: |
| 146 | + name: otel-sample |
| 147 | +--- |
| 148 | +apiVersion: apps/v1 |
| 149 | +kind: Deployment |
| 150 | +metadata: |
| 151 | + name: otel-sample-deployment |
| 152 | + namespace: otel-sample |
| 153 | +spec: |
| 154 | + serviceAccount: otel-collector-sidecar |
| 155 | + replicas: 1 |
| 156 | + selector: |
| 157 | + matchLabels: |
| 158 | + app: otel-sample |
| 159 | + template: |
| 160 | + metadata: |
| 161 | + labels: |
| 162 | + app: otel-sample |
| 163 | + annotations: |
| 164 | + sidecar.opentelemetry.io/inject: "true" |
| 165 | + spec: |
| 166 | + containers: |
| 167 | + - name: otel-sample |
| 168 | + image: ghcr.io/nishipy/go-otel-sample:latest |
| 169 | + ports: |
| 170 | + - containerPort: 8080 |
| 171 | +``` |
| 172 | +
|
| 173 | +作成したPodはRouteで公開します。 |
| 174 | +```yaml |
| 175 | +--- |
| 176 | +apiVersion: v1 |
| 177 | +kind: Service |
| 178 | +metadata: |
| 179 | + name: otel-sample-service |
| 180 | + namespace: otel-sample |
| 181 | +spec: |
| 182 | + selector: |
| 183 | + app: otel-sample |
| 184 | + ports: |
| 185 | + - protocol: TCP |
| 186 | + port: 8080 |
| 187 | + targetPort: 8080 |
| 188 | + type: ClusterIP |
| 189 | +--- |
| 190 | +apiVersion: route.openshift.io/v1 |
| 191 | +kind: Route |
| 192 | +metadata: |
| 193 | + name: otel-sample-route |
| 194 | + namespace: otel-sample |
| 195 | +spec: |
| 196 | + to: |
| 197 | + kind: Service |
| 198 | + name: otel-sample-service |
| 199 | + weight: 100 |
| 200 | + port: |
| 201 | + targetPort: 8080 |
| 202 | +``` |
| 203 | +
|
| 204 | +これらのYAMLマニフェストを適用すると、以下のようにPodやRouteが作成されるはずです。 |
| 205 | +
|
| 206 | +``` |
| 207 | +$ oc get pod |
| 208 | +NAME READY STATUS RESTARTS AGE |
| 209 | +otel-sample-deployment-b8696df8d-7xkll 1/1 Running 0 39s |
| 210 | +$ oc get route |
| 211 | +NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD |
| 212 | +otel-sample-route otel-sample-route-otel-sample.apps.test.lab.local otel-sample-service 8080 None |
| 213 | +$ curl -I otel-sample-route-otel-sample.apps.test.lab.local |
| 214 | +HTTP/1.1 200 OK |
| 215 | +date: Mon, 06 May 2024 14:34:48 GMT |
| 216 | +content-length: 14 |
| 217 | +content-type: text/plain; charset=utf-8 |
| 218 | +set-cookie: 27c7a5203dbedf674a50081de44f4d21=f3ceec06cdf162006c6cd294f3279af3; path=/; HttpOnly |
| 219 | +``` |
| 220 | +
|
| 221 | +## OpenTelemetry Collector の設定 |
| 222 | +
|
| 223 | +[ドキュメント](https://docs.openshift.com/container-platform/4.14/observability/otel/otel-sending-traces-and-metrics-to-otel-collector.html#sending-traces-and-metrics-to-otel-collector-with-sidecar_otel-sending-traces-and-metrics-to-otel-collector)に従って、OpenTelemetry Collectorをサイドカーコンテナとして挿入しましょう。まず前段として、OTEL Collectorサイドカーコンテナのために必要な`ServiceAccount`および`ClusterRole`、`ClusterRoleBinding`を作成します。 |
| 224 | + |
| 225 | +```yaml |
| 226 | +--- |
| 227 | +apiVersion: v1 |
| 228 | +kind: ServiceAccount |
| 229 | +metadata: |
| 230 | + name: otel-collector-sidecar |
| 231 | + namespace: otel-sample |
| 232 | +--- |
| 233 | +apiVersion: rbac.authorization.k8s.io/v1 |
| 234 | +kind: ClusterRole |
| 235 | +metadata: |
| 236 | + name: otel-collector |
| 237 | +rules: |
| 238 | +- apiGroups: ["", "config.openshift.io"] |
| 239 | + resources: ["pods", "namespaces", "infrastructures", "infrastructures/status"] |
| 240 | + verbs: ["get", "watch", "list"] |
| 241 | +--- |
| 242 | +apiVersion: rbac.authorization.k8s.io/v1 |
| 243 | +kind: ClusterRoleBinding |
| 244 | +metadata: |
| 245 | + name: otel-collector |
| 246 | +subjects: |
| 247 | +- kind: ServiceAccount |
| 248 | + name: otel-collector-sidecar |
| 249 | + namespace: otel-sample |
| 250 | +roleRef: |
| 251 | + kind: ClusterRole |
| 252 | + name: otel-collector |
| 253 | + apiGroup: rbac.authorization.k8s.io |
| 254 | +``` |
| 255 | + |
| 256 | +次に、`OpenTelemetryCollector` CRを作成します。Export先にTempoのインスタンスを用意するのは骨が折れそうなので、今回は以下のようにします。(ちなみにドキュメントの設定をコピペしても、`service.pipelines` の設定が間違っていたり、serviceAccountを指定するインデントが間違っていたりして、エラーになると思うのでご注意ください) |
| 257 | + |
| 258 | +```yaml |
| 259 | +apiVersion: opentelemetry.io/v1alpha1 |
| 260 | +kind: OpenTelemetryCollector |
| 261 | +metadata: |
| 262 | + name: otel |
| 263 | + namespace: otel-sample |
| 264 | +spec: |
| 265 | + serviceAccount: otel-collector-sidecar |
| 266 | + mode: sidecar |
| 267 | + config: | |
| 268 | + receivers: |
| 269 | + otlp: |
| 270 | + protocols: |
| 271 | + grpc: |
| 272 | + http: |
| 273 | + processors: |
| 274 | + batch: |
| 275 | + memory_limiter: |
| 276 | + check_interval: 1s |
| 277 | + limit_percentage: 50 |
| 278 | + spike_limit_percentage: 30 |
| 279 | + resourcedetection: |
| 280 | + detectors: [openshift] |
| 281 | + timeout: 2s |
| 282 | + exporters: |
| 283 | + debug: |
| 284 | + verbosity: detailed |
| 285 | + service: |
| 286 | + pipelines: |
| 287 | + traces: |
| 288 | + receivers: [otlp] |
| 289 | + processors: [memory_limiter, resourcedetection, batch] |
| 290 | + exporters: [debug] |
| 291 | + metrics: |
| 292 | + receivers: [otlp] |
| 293 | + processors: [memory_limiter, resourcedetection, batch] |
| 294 | + exporters: [debug] |
| 295 | +``` |
| 296 | + |
| 297 | +`spec.mode: sidecar` を設定することにより、OTEL Collectorサイドカーコンテナを、`sidecar.opentelemetry.io/inject: "true"` アノテーションを持つPodに挿入します。 |
| 298 | + |
| 299 | +Receiverとしては、[OTLP Receiver](https://docs.openshift.com/container-platform/4.14/observability/otel/otel-configuration-of-otel-collector.html#otlp-receiver_otel-configuration-of-otel-collector)を使っています。これはGoで書いたサンプルコード内で、OTLPプロトコル(over gRPC)を使ってトレースを生成しているためです。また、Exporterには、[Debug Exporter](https://docs.openshift.com/container-platform/4.14/observability/otel/otel-troubleshooting.html#debug-exporter-to-stdout_otel-troubleshoot)を使います。その名の通り、デバッグ用にテレメトリデータを標準出力にエクスポートします。 |
| 300 | + |
| 301 | +YAMLマニフェストを適用すると、以下のように `mutating webhook` が見えるはずです。 |
| 302 | + |
| 303 | +``` |
| 304 | +$ oc get mutatingwebhookconfigurations.admissionregistration.k8s.io -l olm.owner.namespace=openshift-opentelemetry-operator |
| 305 | +NAME WEBHOOKS AGE |
| 306 | +minstrumentation.kb.io-ddjq4 1 20d |
| 307 | +mopampbridge.kb.io-5hkkl 1 20d |
| 308 | +mopentelemetrycollector.kb.io-4bfwl 1 20d |
| 309 | +mpod.kb.io-7dmnz 1 20d |
| 310 | +``` |
| 311 | +
|
| 312 | +## OpenTelemetry Collector sidecar の挿入 |
| 313 | +
|
| 314 | +`mutating webhook` も用意できたので、実験用にデプロイしていたPodを再作成しましょう。Admission Webhookにより、OpenTelemetry Collectorがサイドカーコンテナとして追加されるはずです。Podを一旦削除すると再作成され、READY列が `2/2` になり、サイドカーコンテナが挿入されたことがわかります。準備完了です。 |
| 315 | +
|
| 316 | +``` |
| 317 | +$ oc get pod |
| 318 | +NAME READY STATUS RESTARTS AGE |
| 319 | +otel-sample-deployment-b8696df8d-7xkll 1/1 Running 0 8m31s |
| 320 | +$ oc delete pod otel-sample-deployment-b8696df8d-7xkll |
| 321 | +pod "otel-sample-deployment-b8696df8d-7xkll" deleted |
| 322 | +$ oc get pod |
| 323 | +NAME READY STATUS RESTARTS AGE |
| 324 | +otel-sample-deployment-b8696df8d-gc6hg 2/2 Running 0 37s |
| 325 | +``` |
| 326 | +
|
| 327 | +試しにRoute経由でPodにcurlでアクセスした後、OpenTelemetry Collectorのサイドカーコンテナ(`otc-container`)を覗いてみると、テレメトリデータが表示されているのがわかります。 |
| 328 | +
|
| 329 | +``` |
| 330 | +$ curl -I otel-sample-route-otel-sample.apps.test.lab.local |
| 331 | +$ oc logs otel-sample-deployment-b8696df8d-gc6hg otc-container |
| 332 | +2024-05-06T14:45:02.667Z info TracesExporter {"kind": "exporter", "data_type": "traces", "name": "debug", "resource spans": 1, "spans": 1} |
| 333 | +2024-05-06T14:45:02.667Z info ResourceSpans #0 |
| 334 | +Resource SchemaURL: https://opentelemetry.io/schemas/1.24.0 |
| 335 | +Resource attributes: |
| 336 | + -> service.name: Str(unknown_service:otel-sample) |
| 337 | + -> telemetry.sdk.language: Str(go) |
| 338 | + -> telemetry.sdk.name: Str(opentelemetry) |
| 339 | + -> telemetry.sdk.version: Str(1.26.0) |
| 340 | +ScopeSpans #0 |
| 341 | +ScopeSpans SchemaURL: |
| 342 | +InstrumentationScope http-server |
| 343 | +Span #0 |
| 344 | + Trace ID : a1964374ea412edf92ee5d4f843071ab |
| 345 | + Parent ID : |
| 346 | + ID : 8fbada7e64abb4bf |
| 347 | + Name : handleRequest |
| 348 | + Kind : Internal |
| 349 | + Start time : 2024-05-06 14:45:01.285150513 +0000 UTC |
| 350 | + End time : 2024-05-06 14:45:01.385644776 +0000 UTC |
| 351 | + Status code : Ok |
| 352 | + Status message : |
| 353 | + {"kind": "exporter", "data_type": "traces", "name": "debug"} |
| 354 | +... |
| 355 | +``` |
0 commit comments