├── .github
├── CODEOWNERS
├── header-checker-lint.yml
└── workflows
│ ├── config
│ └── yamllint.yaml
│ └── yamllint.yml
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── collector-config.yaml
├── instrumentation.yaml
├── recipes
├── README.md
├── beyla-golden-signals
│ ├── gce
│ │ ├── README.md
│ │ ├── beyla-config.yaml
│ │ ├── google-cloud-ops-agent
│ │ │ └── config.yaml
│ │ ├── install-beyla.sh
│ │ └── run-sample-app.sh
│ └── gke
│ │ ├── README.md
│ │ ├── beyla-daemonset.yaml
│ │ ├── collector-config.yaml
│ │ └── rbac.yaml
├── beyla-service-graph
│ ├── README.md
│ ├── beyla-daemonset.yaml
│ ├── collector-config.yaml
│ ├── graphgen
│ │ ├── go.mod
│ │ ├── go.sum
│ │ ├── internal
│ │ │ ├── graph.go
│ │ │ ├── graph_test.go
│ │ │ ├── query.go
│ │ │ └── query_test.go
│ │ ├── main.go
│ │ └── out.svg
│ ├── rbac-beyla.yaml
│ └── rbac-otel.yaml
├── cloud-trace
│ ├── README.md
│ └── collector-config.yaml
├── daemonset-and-deployment
│ ├── README.md
│ ├── daemonset-collector-config.yaml
│ └── deployment-collector-config.yaml
├── host-and-kubelet-metrics
│ ├── README.md
│ └── collector-config.yaml
├── resource-detection
│ ├── README.md
│ └── collector-config.yaml
├── self-managed-otlp-ingest
│ ├── README.md
│ ├── instrumentation.yaml
│ ├── k8s
│ │ ├── kustomization.yaml
│ │ ├── quickstart-app.yaml
│ │ └── quickstart-traffic.yaml
│ ├── setup-application.sh
│ └── traffic
│ │ ├── cloudbuild-hey.yaml
│ │ └── hey.Dockerfile
├── trace-enhancements
│ ├── README.md
│ ├── collector-config-resource-detection.yaml
│ └── collector-config.yaml
├── trace-filtering
│ ├── README.md
│ └── collector-config.yaml
├── trace-remote-sampling
│ ├── README.md
│ ├── collector-config.yaml
│ ├── instrumentation.yaml
│ └── remote-sampling-config.yaml
└── trace-sampling
│ ├── README.md
│ └── instrumentation.yaml
└── sample-apps
├── README.md
├── go
├── Makefile
├── README.md
├── app
│ ├── Dockerfile
│ ├── go.mod
│ └── main.go
├── k8s
│ ├── app.yaml
│ └── service.yaml
└── server
│ ├── Dockerfile
│ ├── go.mod
│ └── main.go
├── java
├── .gitignore
├── Makefile
├── README.md
├── app
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── google
│ │ └── example
│ │ └── app
│ │ └── App.java
├── buildSrc
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── kotlin
│ │ ├── com.google.example.java-application-conventions.gradle.kts
│ │ ├── com.google.example.java-common-conventions.gradle.kts
│ │ ├── com.google.example.java-inst-application-conventions.gradle.kts
│ │ ├── com.google.example.java-library-conventions.gradle.kts
│ │ └── com.google.example.java-spring-application-conventions.gradle.kts
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── k8s
│ ├── app.yaml
│ └── service.yaml
├── service
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── java
│ │ └── com
│ │ │ └── google
│ │ │ └── example
│ │ │ └── service
│ │ │ └── Main.java
│ │ └── resources
│ │ └── application.properties
├── settings.gradle.kts
└── utilities
│ ├── build.gradle.kts
│ └── src
│ └── main
│ ├── java
│ └── com
│ │ └── google
│ │ └── example
│ │ └── utilities
│ │ ├── AttachTraceLogFilter.java
│ │ └── Logging.java
│ └── resources
│ └── logback.xml
├── nodejs-java
├── Makefile
├── README.md
└── k8s
│ ├── app.yaml
│ └── service.yaml
├── nodejs
├── Dockerfile
├── Makefile
├── README.md
├── app.js
├── k8s
│ ├── app.yaml
│ └── service.yaml
└── server.js
├── python
├── .gitignore
├── Dockerfile
├── Makefile
├── README.md
├── app.py
├── k8s
│ ├── app.yaml
│ └── service.yaml
├── requirements.txt
└── server.py
└── troubleshooting.md
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # Global owners
16 | * @GoogleCloudPlatform/opentelemetry-ops
17 |
--------------------------------------------------------------------------------
/.github/header-checker-lint.yml:
--------------------------------------------------------------------------------
1 | allowedCopyrightHolders:
2 | - Google LLC
3 | - OpenTelemetry Authors
4 | allowedLicenses:
5 | - Apache-2.0
6 | sourceFileExtensions:
7 | - go
8 | - sh
9 | - js
10 | - py
11 | - java
12 | - yaml
13 | - Makefile
14 | - Dockerfile
15 | ignoreFiles:
16 | - .github/**/*
17 |
--------------------------------------------------------------------------------
/.github/workflows/config/yamllint.yaml:
--------------------------------------------------------------------------------
1 | extends: default
2 |
3 | rules:
4 | # 80 chars should be enough, but don't fail if a line is longer
5 | line-length:
6 | max: 80
7 | level: warning
8 |
9 | indentation:
10 | indent-sequences: whatever
11 |
--------------------------------------------------------------------------------
/.github/workflows/yamllint.yml:
--------------------------------------------------------------------------------
1 | name: "YAML lint"
2 |
3 | on:
4 | workflow_dispatch:
5 | pull_request:
6 |
7 | permissions:
8 | contents: read
9 | pull-requests: read
10 |
11 | jobs:
12 | yamllint:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: 'Checkout'
16 | uses: actions/checkout@master
17 | - name: Yamllint Github Action
18 | uses: karancode/yamllint-github-action@v2.0.0
19 | with:
20 | yamllint_config_filepath: ./.github/workflows/config/yamllint.yaml
21 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code Reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows [Google's Open Source Community
28 | Guidelines](https://opensource.google/conduct/).
29 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | CONTAINER_REGISTRY ?= otel-operator
16 | REGISTRY_LOCATION ?= us-central1
17 | GCLOUD_PROJECT ?= $(shell gcloud config get project)
18 |
19 | .PHONY: setup
20 | setup:
21 | gcloud artifacts repositories create ${CONTAINER_REGISTRY} --repository-format=docker --location=${REGISTRY_LOCATION} --description="OpenTelemetry Operator sample registry"
22 |
23 | .PHONY: sample-replace
24 | sample-replace:
25 | sed -i "s/%GCLOUD_PROJECT%/${GCLOUD_PROJECT}/g" k8s/app.yaml
26 | sed -i "s/%CONTAINER_REGISTRY%/${CONTAINER_REGISTRY}/g" k8s/app.yaml
27 | sed -i "s/%REGISTRY_LOCATION%/${REGISTRY_LOCATION}/g" k8s/app.yaml
28 | sed -i "s/%GCLOUD_PROJECT%/${GCLOUD_PROJECT}/g" k8s/service.yaml
29 | sed -i "s/%CONTAINER_REGISTRY%/${CONTAINER_REGISTRY}/g" k8s/service.yaml
30 | sed -i "s/%REGISTRY_LOCATION%/${REGISTRY_LOCATION}/g" k8s/service.yaml
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OpenTelemetry Operator Sample
2 |
3 | This repo hosts samples for working with the OpenTelemetry Operator on GCP.
4 |
5 | * [Running the Operator](#running-the-operator)
6 | * [Prerequisites](#prerequisites)
7 | * [Installing the OpenTelemetry Operator](#installing-the-opentelemetry-operator)
8 | * [Starting the Collector](#starting-the-collector)
9 | * [Auto-instrumenting Applications](#auto-instrumenting-applications)
10 | * [Sample Applications](#sample-applications)
11 | * [Recipes](#recipes)
12 | * [Contributing](#contributing)
13 | * [License](#license)
14 |
15 | ## Running the Operator
16 |
17 | ### Prerequisites
18 |
19 | * A running GKE cluster
20 | * [Helm](https://helm.sh) (for GKE Autopilot)
21 | * `cert-manager` installed in your cluster
22 | * [Instructions here](https://cert-manager.io/docs/installation/)
23 | * (For private clusters): set up the [firewall rules](#firewall-rules) for cert-manager
24 |
25 | For GKE Autopilot, install `cert-manager` with the following [Helm](https://helm.sh) commands:
26 | ```
27 | helm repo add jetstack https://charts.jetstack.io
28 | helm repo update
29 | helm install \
30 | --create-namespace \
31 | --namespace cert-manager \
32 | --set installCRDs=true \
33 | --set global.leaderElection.namespace=cert-manager \
34 | --set extraArgs={--issuer-ambient-credentials=true} \
35 | cert-manager jetstack/cert-manager
36 | ```
37 |
38 | #### Firewall rules
39 |
40 | By default, private GKE clusters may not allow the necessary ports for
41 | cert-manager to work, resulting in an error like the following:
42 |
43 | ```
44 | Error from server (InternalError): error when creating "collector-config.yaml": Internal error occurred: failed calling webhook "mopentelemetrycollector.kb.io": failed to call webhook: Post "https://opentelemetry-operator-webhook-service.opentelemetry-operator-system.svc:443/mutate-opentelemetry-io-v1alpha1-opentelemetrycollector?timeout=10s": context deadline exceeded
45 | ```
46 |
47 | To fix this, [create a firewall
48 | rule](https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters#add_firewall_rules)
49 | for your cluster with the following command:
50 |
51 | ```
52 | gcloud compute firewall-rules create cert-manager-9443 \
53 | --source-ranges ${GKE_MASTER_CIDR} \
54 | --target-tags ${GKE_MASTER_TAG} \
55 | --allow TCP:9443
56 | ```
57 |
58 | `$GKE_MASTER_CIDR` and `$GKE_MASTER_TAG` can be found by following the steps in
59 | the [firewall
60 | docs](https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters#add_firewall_rules)
61 | listed above.
62 |
63 | ### Installing the OpenTelemetry Operator
64 |
65 | Install the Operator with:
66 |
67 | ```
68 | kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/download/v0.116.0/opentelemetry-operator.yaml
69 | ```
70 |
71 | ### Starting the Collector
72 |
73 | Set up an instance of the OpenTelemetry Collector by creating an `OpenTelemetryCollector` object.
74 | The one in this repo sets up a basic OTLP receiver and logging exporter:
75 |
76 | ```
77 | kubectl apply -f collector-config.yaml
78 | ```
79 |
80 | ### Auto-instrumenting Applications
81 |
82 | The Operator offers [auto-instrumentation of application pods](https://github.com/open-telemetry/opentelemetry-operator#opentelemetry-auto-instrumentation-injection)
83 | by adding an annotation to the Pod spec.
84 |
85 | First, create an `Instrumentation` [Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)
86 | that contains the settings for the instrumentation. We have provided a sample resource
87 | in [`instrumentation.yaml`](instrumentation.yaml):
88 |
89 | ```
90 | kubectl apply -f instrumentation.yaml
91 | ```
92 |
93 | With a Collector and auto-instrumentation set up, you can experiment with it using one of the [sample applications](sample-apps),
94 | or skip right to the [recipes](recipes) if you already have an application running.
95 |
96 | ## Sample Applications
97 |
98 | The [`sample-apps/`](sample-apps/) folder contains basic apps to demonstrate collecting traces with
99 | the operator in various languages:
100 |
101 | * [NodeJS](sample-apps/nodejs)
102 | * [Java](sample-apps/java)
103 | * [Python](sample-apps/python)
104 | * DotNET (coming soon)
105 | * [Go](sample-apps/go)
106 | * [NodeJS + Java](sample-apps/nodejs-java)
107 |
108 | Each of these sample apps works well with the [recipes](recipes) listed below.
109 |
110 | ## Recipes
111 |
112 | The [`recipes`](recipes/) directory holds different sample use cases for working with the
113 | operator and auto-instrumentation along with setup guides for each recipe. Currently, there are:
114 |
115 | * [Trace sampling configuration](recipes/trace-sampling)
116 | * [Trace remote sampling config](recipes/trace-remote-sampling)
117 | * [Trace filtering](recipes/trace-filtering)
118 | * [Trace enhancements](recipes/trace-enhancements)
119 | * [Cloud Trace integration](recipes/cloud-trace)
120 | * [Resource detection](recipes/resource-detection)
121 | * [Daemonset and Deployment](recipes/daemonset-and-deployment)
122 | * [eBPF HTTP Golden Signals with Beyla](recipes/beyla-golden-signals)
123 | * [eBPF HTTP Service Graph with Beyla](recipes/beyla-service-graph)
124 |
125 | ## Contributing
126 |
127 | See [`CONTRIBUTING.md`](CONTRIBUTING.md) for details.
128 |
129 | ## License
130 |
131 | Apache 2.0; see [`LICENSE`](LICENSE) for details.
132 |
--------------------------------------------------------------------------------
/collector-config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: OpenTelemetryCollector
17 | metadata:
18 | name: otel
19 | spec:
20 | config: |
21 | receivers:
22 | otlp:
23 | protocols:
24 | grpc:
25 | endpoint: 0.0.0.0:4317
26 | http:
27 | endpoint: 0.0.0.0:4318
28 | processors:
29 |
30 | exporters:
31 | debug:
32 | verbosity: detailed
33 |
34 | service:
35 | pipelines:
36 | traces:
37 | receivers: [otlp]
38 | processors: []
39 | exporters: [debug]
40 |
--------------------------------------------------------------------------------
/instrumentation.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: Instrumentation
17 | metadata:
18 | name: my-instrumentation
19 | spec:
20 | exporter:
21 | endpoint: http://otel-collector:4317
22 | propagators:
23 | - tracecontext
24 | - baggage
25 | - b3
26 | sampler:
27 | type: parentbased_traceidratio
28 | argument: "0.25"
29 |
30 | python:
31 | env:
32 | # Needed until https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1361
33 | # is fixed
34 | - name: OTEL_METRICS_EXPORTER
35 | value: none
36 | # Python autoinstrumentation only supports OTLP/HTTP which the collector runs on port 4318,
37 | # see https://github.com/open-telemetry/opentelemetry-operator/issues/924
38 | - name: OTEL_EXPORTER_OTLP_ENDPOINT
39 | value: http://otel-collector:4318
40 |
--------------------------------------------------------------------------------
/recipes/README.md:
--------------------------------------------------------------------------------
1 | # Recipes
2 |
3 | This directory holds different "recipe" configurations for the
4 | operator and auto-instrumentation. See below to get started:
5 |
6 | * [Trace sampling config](trace-sampling)
7 | * [Trace remote sampling config](trace-remote-sampling)
8 | * [Trace filtering](trace-filtering)
9 | * [Trace enhancements](trace-enhancements)
10 | * [Cloud trace integration](cloud-trace)
11 | * [Resource detection](resource-detection)
12 | * [Daemonset and Deployment](daemonset-and-deployment)
13 | * [eBPF HTTP Golden Signals with Beyla](beyla-golden-signals)
14 | * [eBPF HTTP Service Graph with Beyla](beyla-service-graph)
15 |
--------------------------------------------------------------------------------
/recipes/beyla-golden-signals/gce/README.md:
--------------------------------------------------------------------------------
1 | # eBPF Golden Signals with Beyla on GCE
2 |
3 | This recipe demonstrates how to configure the Google Cloud Ops Agent (installed on your Google Compute Engine instance) to produce golden signal metrics from [Beyla](https://github.com/grafana/beyla) http
4 | metrics and send those metrics to [Google Managed Service for
5 | Prometheus](https://cloud.google.com/stackdriver/docs/managed-prometheus).
6 |
7 | In this recipe, Beyla is configured to generate http metrics from an application running on a GCE instance without introducing any code changes. The sample application used for this recipe is a simple Java server-client application located in the [sample-apps/java](../../../sample-apps/java/) directory of this repository.
8 |
9 | This recipe is based on applying an [Google Cloud Ops Agent](https://cloud.google.com/stackdriver/docs/solutions/agents/ops-agent) configuration that enables the agent to receive and export telemetry generated by Beyla to [Google Managed Service for Prometheus](https://cloud.google.com/stackdriver/docs/managed-prometheus).
10 |
11 | Beyla allows [two modes of exporting data](https://grafana.com/docs/beyla/latest/configure/export-modes/#beyla-export-modes). This sample relies on the *Direct Mode* which allows Beyla to push OTLP data to a specified endpoint.
12 |
13 | # Prerequisites
14 |
15 | * Cloud Monitoring API enabled in your GCP project
16 | * The `roles/monitoring.metricWriter`
17 | [IAM permissions](https://cloud.google.com/trace/docs/iam#roles) for your cluster's service
18 | account (or Workload Identity setup as shown below).
19 | * A GCE instance running Debian Linux and [Google Cloud Ops Agent](https://cloud.google.com/stackdriver/docs/solutions/agents/ops-agent) installed.
20 | * For various installation methods see the [installation instructions](https://cloud.google.com/stackdriver/docs/solutions/agents/ops-agent/install-index).
21 | * If you're creating the GCE instance from Google Cloud Console, you can simply check the `Install Ops Agent for Monitoring and Logging` checkbox during VM creation.
22 | * Ability to SSH into the created GCE instance.
23 | * The files in this recipe, locally on the created VM.
24 | * You can `git clone` this repository directly on the VM and `cd` into this recipe's folder. *You may need to install `git` on the VM.*
25 |
26 | ## Running
27 |
28 | From the location of this recipe *(beyla-golden-sigals/gce)*, execute the following:
29 |
30 | ### Configure the VM instance
31 |
32 | #### Install and configure Beyla on the VM:
33 |
34 | ```sh
35 | # Install beyla on VM and configure Ops Agent
36 | ./install-beyla.sh
37 | ```
38 |
39 | #### Check if Google Cloud Ops Agent is still running (Optional):
40 |
41 | ```sh
42 | # Logging Agent and Metrics Agent should be running
43 | sudo systemctl status "google-cloud-ops-agent*"
44 | ```
45 |
46 | #### Start Beyla process
47 |
48 | > [!WARNING]
49 | > This will start the Beyla process in the background as root.
50 |
51 | ```sh
52 | # Start beyla in the background
53 | sudo beyla --config=beyla-config.yaml 2>&1 > logs_beyla.txt &
54 | ```
55 |
56 | ## Build & run the sample Java server app
57 |
58 | ### Build the server and client app and start server in the background.
59 |
60 | > [!TIP]
61 | > If for whatever reason you need to kill the currently running server app, run the following command `fuser -k 8080/tcp`.
62 |
63 | ```sh
64 | # This will start the built server application in the background, listening on port 8080
65 | ./run-sample-app.sh
66 | ```
67 |
68 | ### Make requests running to the server from the client application.
69 |
70 | ```sh
71 | # The sample app requires a URI to connect to
72 | export SERVICE_NAME=http://localhost:8080
73 | # Run the executable JAR for the app
74 | java -jar ../../../sample-apps/java/app/build/libs/app-standalone.jar
75 | ```
76 |
77 | Or, issue multiple requests to generate a good amount of metric data
78 |
79 | ```sh
80 | export SERVICE_NAME=http://localhost:8080
81 | # Issues 20 requests sequentially to the server
82 | for i in {1..20}; do java -jar ../../../sample-apps/java/app/build/libs/app-standalone.jar; done;
83 | ```
84 |
85 | ## Viewing the generated metrics
86 |
87 | To view the metrics generated by beyla:
88 | - Metrics will be visible in the [Metrics Explorer](https://cloud.google.com/monitoring/charts/metrics-selector).
89 | - The generated metrics will be located under *Prometheus Target* → *Http*.
90 | Metrics to look for:
91 | - `http_server_request_body_size`
92 | - `http_server_request_duration`
93 |
--------------------------------------------------------------------------------
/recipes/beyla-golden-signals/gce/beyla-config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2024 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # Beyla can also be configured globally using environment variables.
16 | # Environment variables have priority over the properties in the config file.
17 | # Beyla can be configured to listen to applications running on multiple ports.
18 | # This configures Beyla to execute all the processes owning one of the ports in
19 | # the specified range.
20 | # Limit the range of ports/specify ports if you want Beyla to execute only specific
21 | # applications. Having a large range might increase resource consumption by Beyla.
22 | open_port: 1-65536
23 | trace_printer: text
24 | protocol: grpc
25 |
26 | otel_metrics_export:
27 | endpoint: http://0.0.0.0:4317
28 |
29 | otel_traces_export:
30 | # Uncomment the below line to enable trace generation by Beyla
31 | # endpoint: http://0.0.0.0:4317
32 |
33 | # This section describes how you can discover various services
34 | # running on your system and assign them different names and
35 | # namespaces based on the exe_path.
36 | # This feature is not available when configuring through environment variables.
37 | discovery:
38 | services:
39 | - exe_path: java
40 | name: "my-java-service"
41 | namespace: beyla-gce-java-apps
42 | # This configuration is not relevant to this sample
43 | # since we are only running a java application.
44 | - exe_path: node
45 | name: "my-node-service"
46 | namespace: beyla-gce-node-apps
47 |
--------------------------------------------------------------------------------
/recipes/beyla-golden-signals/gce/google-cloud-ops-agent/config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2024 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # <== Enter custom agent configurations in this file.
16 | # See https://cloud.google.com/stackdriver/docs/solutions/agents/ops-agent/configuration
17 | # for more details.
18 | #
19 |
20 | combined:
21 | receivers:
22 | otlp:
23 | type: otlp
24 | grpc_endpoint: 0.0.0.0:4317
25 | # Switch metrics mode to 'googlecloudmonitoring' to use Cloud Monitoring
26 | # instead of Google managed service for prometheus
27 | metrics_mode: googlemanagedprometheus
28 | metrics:
29 | service:
30 | pipelines:
31 | otlp:
32 | receivers: [otlp]
33 | # Add OTLP receiver to the following to export traces.
34 | # The service for traces must exist when using 'combined' for configuration to be valid.
35 | traces:
36 | service:
37 | pipelines:
38 | otlp:
39 | receivers: []
40 |
--------------------------------------------------------------------------------
/recipes/beyla-golden-signals/gce/install-beyla.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2024 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | # This script installs beyla on to the current GCE VM instance while installing necessary dependencies.
18 | # The script also configures the installed google-cloud-ops-agent so that it can collect metrics emitted by Beyla.
19 |
20 | # Update dependencies
21 | sudo apt update
22 |
23 | # Install Java
24 | echo "Installing Java 17..."
25 | sudo apt install -y openjdk-17-jdk
26 |
27 | # Download Beyla
28 | echo "Downloading Beyla..."
29 | BEYLA_V1_7_RELEASE=https://github.com/grafana/beyla/releases/download/v1.8.6/beyla-linux-amd64-v1.8.6.tar.gz
30 | curl -Lo beyla.tar.gz $BEYLA_V1_7_RELEASE
31 | mkdir -p beyla-installation/
32 | tar -xzf beyla.tar.gz -C beyla-installation/
33 | # Move beyla executable to /usr/local/bin
34 | # /usr/local/bin is the path on a default GCE instance running Debian
35 | sudo cp beyla-installation/beyla /usr/local/bin
36 |
37 | # Apply the custom configuration to installed Google Cloud Ops Agent
38 | echo "Configuring the Google Cloud Ops Agent..."
39 | sudo cp ./google-cloud-ops-agent/config.yaml /etc/google-cloud-ops-agent/config.yaml
40 | sudo systemctl restart google-cloud-ops-agent
41 | echo "Google Cloud Ops Agent restarted..."
42 |
--------------------------------------------------------------------------------
/recipes/beyla-golden-signals/gce/run-sample-app.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2024 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | # Build the server and client sample apps
18 | SAMPLE_APP_JAVA_DIR=../../../sample-apps/java
19 | SERVICE_JAR_FILE=service.jar
20 |
21 | # Build client and server Java apps
22 | pushd $SAMPLE_APP_JAVA_DIR
23 | ./gradlew :service:build
24 | ./gradlew :app:build
25 | popd
26 |
27 | # Run the server application
28 | java -jar $SAMPLE_APP_JAVA_DIR/service/build/libs/$SERVICE_JAR_FILE 2>&1 > logs_service.txt &
29 |
--------------------------------------------------------------------------------
/recipes/beyla-golden-signals/gke/README.md:
--------------------------------------------------------------------------------
1 | # eBPF HTTP Golden Signals with Beyla
2 |
3 | This recipe demonstrates how to configure the OpenTelemetry Collector (as deployed by the
4 | Operator) to produce golden signal metrics from [Beyla](https://github.com/grafana/beyla) http
5 | metrics and send those metrics to [Google Managed Service for
6 | Prometheus](https://cloud.google.com/stackdriver/docs/managed-prometheus).
7 |
8 | In this recipe, Beyla is configured to generate http metrics from all
9 | workloads in the cluster without any code changes. Beyla has other features,
10 | such as auto-instrumentation for Go applications, but this sample does not use
11 | it for that purpose.
12 |
13 | This recipe is based on applying a Collector config that enables the Google Cloud exporter.
14 | It provides an `OpenTelemetryCollector` object that, when created, instructs the Operator to
15 | create a new instance of the Collector with that config. If overwriting an existing `OpenTelemetryCollector`
16 | object (i.e., you already have a running Collector through the Operator such as the one from the
17 | [main README](../../README.md#starting-the-collector)), the Operator will update that existing
18 | Collector with the new config.
19 |
20 | ## Prerequisites
21 |
22 | > [!WARNING]
23 | > Beyla only works with GKE standard clusters, because GKE Auto restricts the use of privileged pods.
24 |
25 | * Cloud Monitoring API enabled in your GCP project
26 | * The `roles/monitoring.metricWriter`
27 | [IAM permissions](https://cloud.google.com/trace/docs/iam#roles) for your cluster's service
28 | account (or Workload Identity setup as shown below).
29 | * A running (non-autopilot) GKE cluster
30 | * The OpenTelemetry Operator installed in your cluster
31 | * A Collector deployed with the Operator (recommended) or a ServiceAccount that can be used by the new Collector.
32 | * Note: This recipe assumes that you already have a Collector ServiceAccount named `otel-collector`,
33 | which is created by the operator when deploying an `OpenTelemetryCollector` object such as the
34 | one [in this repo](../../collector-config.yaml).
35 | * An application already deployed that makes or serves HTTP requests:
36 | * Note: Beyla does not currently support HTTPS or gRPC
37 | * [One of the sample apps](../../sample-apps) from this repo, without auto-instrumentation enabled.
38 |
39 | Note that the `OpenTelemetryCollector` object needs to be in the same namespace as your sample
40 | app, or the Collector endpoint needs to be updated to point to the correct service address.
41 |
42 | ## Running
43 |
44 | ### Deploying the Recipe
45 |
46 | Apply the role-based access control (RBAC) configuration:
47 |
48 | ```
49 | kubectl apply -f rbac.yaml
50 | ```
51 |
52 | This allows the Beyla agent to read additional metadata from the Kubernetes API to enrich the
53 | telemetry. See [Configuring Kubernetes metadata
54 | decoration](https://grafana.com/docs/beyla/latest/setup/kubernetes/#configuring-kubernetes-metadata-decoration)
55 | for more information. If you are deploying the Beyla Daemonset in a namespace other than
56 | `default`, make sure to update the `namespace` property of the ServiceAccount in
57 | [`rbac.yaml`](rbac.yaml#L38).
58 |
59 | Apply the `OpenTelemetryCollector` object from this recipe:
60 |
61 | ```
62 | kubectl apply -f collector-config.yaml
63 | ```
64 |
65 | (This will overwrite any existing collector config, or create a new one if none exists.)
66 |
67 | Once the Collector restarts, apply the Beyla Daemonset:
68 |
69 | ```
70 | kubectl apply -f beyla-daemonset.yaml
71 | ```
72 |
73 | This will begin creating metrics for all http traffic on each node, and exporting them to
74 | Google Managed Service for Prometheus.
75 |
76 | ### Workload Identity Setup
77 |
78 | If you have Workload Identity enabled, you'll see permissions errors after deploying the recipe.
79 | You need to set up a GCP service account with permission to write traces to Cloud Trace, and allow
80 | the Collector's Kubernetes service account to act as your GCP service account. You can do this with
81 | the following commands:
82 |
83 | ```
84 | export GCLOUD_PROJECT=
85 | gcloud iam service-accounts create otel-collector --project=${GCLOUD_PROJECT}
86 | ```
87 |
88 | Then give that service account permission to write traces and metrics:
89 |
90 | ```
91 | gcloud projects add-iam-policy-binding $GCLOUD_PROJECT \
92 | --member "serviceAccount:otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com" \
93 | --role "roles/cloudtrace.agent"
94 | ```
95 |
96 | ```
97 | gcloud projects add-iam-policy-binding $GCLOUD_PROJECT \
98 | --member "serviceAccount:otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com" \
99 | --role "roles/monitoring.metricWriter"
100 | ```
101 |
102 | Then bind the GCP service account to the Kubernetes ServiceAccount that is used by the Collector
103 | you deployed in the prerequisites (note: set `$COLLECTOR_NAMESPACE` to the namespace you installed
104 | the Collector in):
105 |
106 | ```
107 | export COLLECTOR_NAMESPACE=default
108 | gcloud iam service-accounts add-iam-policy-binding "otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com" \
109 | --role roles/iam.workloadIdentityUser \
110 | --member "serviceAccount:${GCLOUD_PROJECT}.svc.id.goog[${COLLECTOR_NAMESPACE}/otel-collector]"
111 | ```
112 |
113 | **(Optional):** If you don't already have a ServiceAccount for the Collector (such as the one provided
114 | when deploying a prior OpenTelemetryCollector object), create it with `kubectl create serviceaccount otel-collector`.
115 |
116 | Finally, annotate the OpenTelemetryCollector Object to allow the Collector's ServiceAccount to use Workload Identity:
117 |
118 | ```
119 | kubectl annotate opentelemetrycollector otel \
120 | --namespace $COLLECTOR_NAMESPACE \
121 | iam.gke.io/gcp-service-account=otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com
122 | ```
123 |
124 | ## View Application Metrics
125 |
126 | Follow the the instructions to [view application metrics in
127 | GKE](https://cloud.google.com/stackdriver/docs/managed-prometheus/exporters/server/http#view-dashboard).
128 | You can comment out the `name != "http.server.duration"` filter clause in
129 | [collector-config.yaml](collector-config.yaml) to send [all metrics generated by
130 | Beyla](https://grafana.com/docs/beyla/latest/metrics/), which can be viewed in the Metrics
131 | Explorer.
132 |
133 | ## Troubleshooting
134 |
135 | ### rpc error: code = PermissionDenied
136 |
137 | An error such as the following:
138 |
139 | ```
140 | 2022/10/21 13:41:11 failed to export to Google Cloud Trace: rpc error: code = PermissionDenied desc = The caller does not have permission
141 | ```
142 |
143 | This indicates that your Collector is unable to export spans, likely due to misconfigured IAM. Things to check:
144 |
145 | #### GKE (cluster-side) config issues
146 |
147 | With some configurations it's possible that the Operator could overwrite an existing ServiceAccount when deploying
148 | a new Collector. Ensure that the Collector's service account has the `iam.gke.io/gcp-service-account` annotation after
149 | running the `kubectl apply...` command in [Deploying the Recipe](#deploying-the-recipe). If this is missing, re-run the
150 | `kubectl annotate` command to add it to the ServiceAccount and restart the Collector Pod by deleting it (`kubectl delete pod/otel-collector-xxx..`).
151 |
152 | #### GCP (project-side) config issues
153 |
154 | Double check that IAM is properly configured for Cloud Monitoring access. This includes:
155 |
156 | * Verify the `otel-collector` service account exists in your GCP project
157 | * That service account must have `roles/monitoring.metricWriter` permissions
158 | * The `serviceAccount:${GCLOUD_PROJECT}.svc.id.goog[${COLLECTOR_NAMESPACE}/otel-collector]` member must also be bound
159 | to the `roles/iam.workloadIdentityUser` role (this identifies the Kubernetes ServiceAccount as able to use Workload Identity)
160 |
--------------------------------------------------------------------------------
/recipes/beyla-golden-signals/gke/beyla-daemonset.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2024 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: apps/v1
16 | kind: DaemonSet
17 | metadata:
18 | name: beyla-agent
19 | labels:
20 | app: beyla
21 | spec:
22 | selector:
23 | matchLabels:
24 | app: beyla
25 | template:
26 | metadata:
27 | labels:
28 | app: beyla
29 | annotations:
30 | # allow beyla to write to /sys/fs/bpf by setting the
31 | # apparmor policy to unconfined.
32 | container.apparmor.security.beta.kubernetes.io/beyla: "unconfined"
33 | spec:
34 | serviceAccountName: beyla
35 | hostPID: true
36 | initContainers:
37 | - name: mount-bpf-fs
38 | image: grafana/beyla:1.8.6
39 | args:
40 | # Create the directory using the Pod UID, and mount the BPF filesystem.
41 | - 'mkdir -p /sys/fs/bpf/$BEYLA_BPF_FS_PATH && mount -t bpf bpf /sys/fs/bpf/$BEYLA_BPF_FS_PATH'
42 | command:
43 | - /bin/bash
44 | - -c
45 | - --
46 | securityContext:
47 | # The init container is privileged so that it can use bidirectional mount propagation
48 | privileged: true
49 | volumeMounts:
50 | - name: bpffs
51 | mountPath: /sys/fs/bpf
52 | # Make sure the mount is propagated back to the host so it can be used by the Beyla container
53 | mountPropagation: Bidirectional
54 | env:
55 | - name: KUBE_NAMESPACE
56 | valueFrom:
57 | fieldRef:
58 | fieldPath: metadata.namespace
59 | - name: BEYLA_BPF_FS_PATH
60 | value: beyla-$(KUBE_NAMESPACE)
61 | containers:
62 | - name: beyla
63 | resources:
64 | requests:
65 | cpu: 10m
66 | memory: 100Mi
67 | image: grafana/beyla:1.8.6
68 | securityContext:
69 | seccompProfile:
70 | type: RuntimeDefault
71 | runAsUser: 0
72 | readOnlyRootFilesystem: true
73 | capabilities:
74 | add:
75 | - BPF
76 | - SYS_PTRACE
77 | - NET_RAW
78 | - CHECKPOINT_RESTORE
79 | - DAC_READ_SEARCH
80 | - PERFMON
81 | drop:
82 | - ALL
83 | env:
84 | - name: BEYLA_CONFIG_PATH
85 | value: "/config/beyla-config.yml"
86 | - name: KUBE_NAMESPACE
87 | valueFrom:
88 | fieldRef:
89 | fieldPath: metadata.namespace
90 | - name: BEYLA_BPF_FS_PATH
91 | value: beyla-$(KUBE_NAMESPACE)
92 | volumeMounts:
93 | - name: bpffs
94 | mountPath: /sys/fs/bpf
95 | # Use HostToContainer to propagate the mount from the init container to the Beyla container
96 | mountPropagation: HostToContainer
97 | - name: beyla-config
98 | mountPath: /config
99 | volumes:
100 | - name: bpffs
101 | hostPath:
102 | path: /sys/fs/bpf
103 | - name: beyla-config
104 | configMap:
105 | name: beyla-config
106 |
107 | ---
108 | apiVersion: v1
109 | kind: ConfigMap
110 | metadata:
111 | name: beyla-config
112 | data:
113 | beyla-config.yml: |
114 | discovery:
115 | services:
116 | # only gather metrics from workloads running as a pod
117 | - k8s_pod_name: .+
118 | skip_go_specific_tracers: true
119 | otel_metrics_export:
120 | endpoint: http://otel-collector:4317
121 | interval: 30s
122 | attributes:
123 | kubernetes:
124 | enable: true
125 | # drop_external only collects golden signal metrics for kubernetes entities (e.g. pods), which reduces resource usage.
126 | drop_external: true
127 | # disable_informers prevents Beyla from watching k8s resources, and reduces the load on the kubernetes API Server.
128 | disable_informers: [replicaset, service, node]
129 | routes:
130 | unmatched: wildcard
131 | ebpf:
132 | bpf_fs_base_dir: /sys/fs/bpf
133 |
--------------------------------------------------------------------------------
/recipes/beyla-golden-signals/gke/collector-config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2024 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: OpenTelemetryCollector
17 | metadata:
18 | name: otel
19 | spec:
20 | image: otel/opentelemetry-collector-contrib:0.112.0
21 | config: |
22 | receivers:
23 | # receive OTLP metrics from Beyla
24 | otlp:
25 | protocols:
26 | grpc:
27 | endpoint: 0.0.0.0:4317
28 | http:
29 | endpoint: 0.0.0.0:4318
30 | processors:
31 | # detect gke resource attributes
32 | resourcedetection:
33 | detectors: [env, gcp]
34 | timeout: 2s
35 | override: false
36 | exporters:
37 | googlemanagedprometheus:
38 | service:
39 | pipelines:
40 | metrics:
41 | receivers: [otlp]
42 | processors: [resourcedetection]
43 | exporters: [googlemanagedprometheus]
44 |
--------------------------------------------------------------------------------
/recipes/beyla-golden-signals/gke/rbac.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2024 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # See https://grafana.com/docs/beyla/latest/setup/kubernetes/#configuring-kubernetes-metadata-decoration
16 |
17 | apiVersion: v1
18 | kind: ServiceAccount
19 | metadata:
20 | name: beyla
21 | ---
22 | apiVersion: rbac.authorization.k8s.io/v1
23 | kind: ClusterRole
24 | metadata:
25 | name: beyla
26 | rules:
27 | - apiGroups: ["apps"]
28 | resources: ["replicasets"]
29 | verbs: ["list", "watch"]
30 | - apiGroups: [""]
31 | resources: ["pods"]
32 | verbs: ["list", "watch"]
33 | ---
34 | apiVersion: rbac.authorization.k8s.io/v1
35 | kind: ClusterRoleBinding
36 | metadata:
37 | name: beyla
38 | subjects:
39 | - kind: ServiceAccount
40 | name: beyla
41 | namespace: default
42 | roleRef:
43 | apiGroup: rbac.authorization.k8s.io
44 | kind: ClusterRole
45 | name: beyla
46 |
--------------------------------------------------------------------------------
/recipes/beyla-service-graph/README.md:
--------------------------------------------------------------------------------
1 | # eBPF HTTP Service Graph with Beyla
2 |
3 | This recipe demonstrates how to configure the OpenTelemetry Collector (as deployed by the
4 | Operator) to produce service graph metrics from [Beyla](https://github.com/grafana/beyla) spans
5 | and send those metrics to [Google Managed Service for
6 | Prometheus](https://cloud.google.com/stackdriver/docs/managed-prometheus). You can use the
7 | `graphgen` script to visualize the service graph from the metrics in your GCP project.
8 |
9 | In this recipe, Beyla is configured to collect http traces from all
10 | workloads in the cluster without any code changes. Beyla has other features,
11 | such as auto-instrumentation for Go applications, but this sample does not use
12 | it for that purpose.
13 |
14 | This recipe is based on applying a Collector config that enables the Google Cloud exporter.
15 | It provides an `OpenTelemetryCollector` object that, when created, instructs the Operator to
16 | create a new instance of the Collector with that config. If overwriting an existing `OpenTelemetryCollector`
17 | object (i.e., you already have a running Collector through the Operator such as the one from the
18 | [main README](../../README.md#starting-the-collector)), the Operator will update that existing
19 | Collector with the new config.
20 |
21 | ## Prerequisites
22 |
23 | > [!WARNING]
24 | > Beyla only works with GKE standard clusters, because GKE Auto restricts the use of privileged pods.
25 |
26 | * Cloud Monitoring API enabled in your GCP project
27 | * The `roles/monitoring.metricWriter`
28 | [IAM permissions](https://cloud.google.com/trace/docs/iam#roles) for your cluster's service
29 | account (or Workload Identity setup as shown below).
30 | * A running (non-autopilot) GKE cluster
31 | * The OpenTelemetry Operator installed in your cluster
32 | * A Collector deployed with the Operator (recommended) or a ServiceAccount that can be used by the new Collector.
33 | * Note: This recipe assumes that you already have a Collector ServiceAccount named `otel-collector`,
34 | which is created by the operator when deploying an `OpenTelemetryCollector` object such as the
35 | one [in this repo](../../collector-config.yaml).
36 | * An application already deployed that makes or serves HTTP requests:
37 | * Note: Beyla does not currently support HTTPS or gRPC
38 | * [One of the sample apps](../../sample-apps) from this repo, without auto-instrumentation enabled.
39 |
40 | Note that the `OpenTelemetryCollector` object needs to be in the same namespace as your sample
41 | app, or the Collector endpoint needs to be updated to point to the correct service address.
42 |
43 | ## Running
44 |
45 | ### Deploying the Recipe
46 |
47 | Apply the role-based access control (RBAC) configurations:
48 |
49 | ```
50 | kubectl apply -f rbac-beyla.yaml -f rbac-otel.yaml
51 | ```
52 |
53 | This allows the Collector and Beyla to read additional metadata from the
54 | Kubernetes API to enrich the telemetry. See [`k8sattributes`
55 | documentation](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/v0.90.0/processor/k8sattributesprocessor/README.md#role-based-access-control)
56 | for more information. If you are deploying the `OpenTelemetryCollector` object in a namespace
57 | other than `default`, make sure to update the `namespace` property of the ServiceAccounts.
58 |
59 | Apply the `OpenTelemetryCollector` object from this recipe:
60 |
61 | ```
62 | kubectl apply -f collector-config.yaml
63 | ```
64 |
65 | (This will overwrite any existing collector config, or create a new one if none exists.)
66 |
67 | Once the Collector restarts, apply the Beyla Daemonset:
68 |
69 | ```
70 | kubectl apply -f beyla-daemonset.yaml
71 | ```
72 |
73 | This will begin creating metrics for all http traffic on each node, and exporting them to
74 | Google Managed Service for Prometheus.
75 |
76 | ### Workload Identity Setup
77 |
78 | If you have Workload Identity enabled, you'll see permissions errors after deploying the recipe.
79 | You need to set up a GCP service account with permission to write traces to Cloud Trace, and allow
80 | the Collector's Kubernetes service account to act as your GCP service account. You can do this with
81 | the following commands:
82 |
83 | ```
84 | export GCLOUD_PROJECT=
85 | gcloud iam service-accounts create otel-collector --project=${GCLOUD_PROJECT}
86 | ```
87 |
88 | Then give that service account permission to write traces and metrics:
89 |
90 | ```
91 | gcloud projects add-iam-policy-binding $GCLOUD_PROJECT \
92 | --member "serviceAccount:otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com" \
93 | --role "roles/cloudtrace.agent"
94 | ```
95 |
96 | ```
97 | gcloud projects add-iam-policy-binding $GCLOUD_PROJECT \
98 | --member "serviceAccount:otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com" \
99 | --role "roles/monitoring.metricWriter"
100 | ```
101 |
102 | Then bind the GCP service account to the Kubernetes ServiceAccount that is used by the Collector
103 | you deployed in the prerequisites (note: set `$COLLECTOR_NAMESPACE` to the namespace you installed
104 | the Collector in):
105 |
106 | ```
107 | export COLLECTOR_NAMESPACE=default
108 | gcloud iam service-accounts add-iam-policy-binding "otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com" \
109 | --role roles/iam.workloadIdentityUser \
110 | --member "serviceAccount:${GCLOUD_PROJECT}.svc.id.goog[${COLLECTOR_NAMESPACE}/otel-collector]"
111 | ```
112 |
113 | **(Optional):** If you don't already have a ServiceAccount for the Collector (such as the one provided
114 | when deploying a prior OpenTelemetryCollector object), create it with `kubectl create serviceaccount otel-collector`.
115 |
116 | Finally, annotate the OpenTelemetryCollector Object to allow the Collector's ServiceAccount to use Workload Identity:
117 |
118 | ```
119 | kubectl annotate opentelemetrycollector otel \
120 | --namespace $COLLECTOR_NAMESPACE \
121 | iam.gke.io/gcp-service-account=otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com
122 | ```
123 |
124 | ## View your Service Graph
125 |
126 | Navigate to the Metrics explorer and look for the `traces_service_graph_request_total` metric. This
127 | metric counts the number of calls between each pod. To visualize this metric, run the
128 | `graphgen` script which queries the metric from your project and outputs an SVG:
129 |
130 | ```sh
131 | cd graphgen
132 | go run main.go -projectId="$GCLOUD_PROJECT" > graph.svg
133 | # optionally filter on cluster and namespace
134 | go run main.go -projectId="$GCLOUD_PROJECT" -cluster=cluster-foo -namespace=default > graph.svg
135 | ```
136 |
137 | 
138 |
139 | ## Troubleshooting
140 |
141 | ### rpc error: code = PermissionDenied
142 |
143 | An error such as the following:
144 |
145 | ```
146 | 2022/10/21 13:41:11 failed to export to Google Cloud Trace: rpc error: code = PermissionDenied desc = The caller does not have permission
147 | ```
148 |
149 | This indicates that your Collector is unable to export spans, likely due to misconfigured IAM. Things to check:
150 |
151 | #### GKE (cluster-side) config issues
152 |
153 | With some configurations it's possible that the Operator could overwrite an existing ServiceAccount when deploying
154 | a new Collector. Ensure that the Collector's service account has the `iam.gke.io/gcp-service-account` annotation after
155 | running the `kubectl apply...` command in [Deploying the Recipe](#deploying-the-recipe). If this is missing, re-run the
156 | `kubectl annotate` command to add it to the ServiceAccount and restart the Collector Pod by deleting it (`kubectl delete pod/otel-collector-xxx..`).
157 |
158 | #### GCP (project-side) config issues
159 |
160 | Double check that IAM is properly configured for Cloud Monitoring access. This includes:
161 |
162 | * Verify the `otel-collector` service account exists in your GCP project
163 | * That service account must have `roles/monitoring.metricWriter` permissions
164 | * The `serviceAccount:${GCLOUD_PROJECT}.svc.id.goog[${COLLECTOR_NAMESPACE}/otel-collector]` member must also be bound
165 | to the `roles/iam.workloadIdentityUser` role (this identifies the Kubernetes ServiceAccount as able to use Workload Identity)
166 |
--------------------------------------------------------------------------------
/recipes/beyla-service-graph/beyla-daemonset.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: apps/v1
16 | kind: DaemonSet
17 | metadata:
18 | name: beyla-agent
19 | labels:
20 | app: beyla
21 | spec:
22 | selector:
23 | matchLabels:
24 | app: beyla
25 | template:
26 | metadata:
27 | labels:
28 | app: beyla
29 | annotations:
30 | # allow beyla to write to /sys/fs/bpf by setting the
31 | # apparmor policy to unconfined.
32 | container.apparmor.security.beta.kubernetes.io/beyla: "unconfined"
33 | spec:
34 | serviceAccountName: beyla
35 | hostPID: true
36 | initContainers:
37 | - name: mount-bpf-fs
38 | image: grafana/beyla:1.8.6
39 | args:
40 | # Create the directory using the Pod UID, and mount the BPF filesystem.
41 | - 'mkdir -p /sys/fs/bpf/$BEYLA_BPF_FS_PATH && mount -t bpf bpf /sys/fs/bpf/$BEYLA_BPF_FS_PATH'
42 | command:
43 | - /bin/bash
44 | - -c
45 | - --
46 | securityContext:
47 | # The init container is privileged so that it can use bidirectional mount propagation
48 | privileged: true
49 | volumeMounts:
50 | - name: bpffs
51 | mountPath: /sys/fs/bpf
52 | # Make sure the mount is propagated back to the host so it can be used by the Beyla container
53 | mountPropagation: Bidirectional
54 | env:
55 | - name: KUBE_NAMESPACE
56 | valueFrom:
57 | fieldRef:
58 | fieldPath: metadata.namespace
59 | - name: BEYLA_BPF_FS_PATH
60 | value: beyla-$(KUBE_NAMESPACE)
61 | containers:
62 | - name: beyla
63 | resources:
64 | requests:
65 | cpu: 10m
66 | memory: 100Mi
67 | image: grafana/beyla:1.8.6
68 | securityContext:
69 | seccompProfile:
70 | type: RuntimeDefault
71 | runAsUser: 0
72 | readOnlyRootFilesystem: true
73 | capabilities:
74 | add:
75 | - BPF
76 | - SYS_PTRACE
77 | - NET_RAW
78 | - CHECKPOINT_RESTORE
79 | - DAC_READ_SEARCH
80 | - PERFMON
81 | drop:
82 | - ALL
83 | env:
84 | - name: BEYLA_CONFIG_PATH
85 | value: "/config/beyla-config.yml"
86 | - name: KUBE_NAMESPACE
87 | valueFrom:
88 | fieldRef:
89 | fieldPath: metadata.namespace
90 | - name: BEYLA_BPF_FS_PATH
91 | value: beyla-$(KUBE_NAMESPACE)
92 | volumeMounts:
93 | - name: bpffs
94 | mountPath: /sys/fs/bpf
95 | # Use HostToContainer to propagate the mount from the init container to the Beyla container
96 | mountPropagation: HostToContainer
97 | - name: beyla-config
98 | mountPath: /config
99 | volumes:
100 | - name: bpffs
101 | hostPath:
102 | path: /sys/fs/bpf
103 | - name: beyla-config
104 | configMap:
105 | name: beyla-config
106 |
107 | ---
108 | apiVersion: v1
109 | kind: ConfigMap
110 | metadata:
111 | name: beyla-config
112 | data:
113 | beyla-config.yml: |
114 | discovery:
115 | services:
116 | # only gather metrics from workloads running as a pod
117 | - k8s_pod_name: .+
118 | skip_go_specific_tracers: true
119 | otel_metrics_export:
120 | features: []
121 | otel_traces_export:
122 | endpoint: http://otel-collector:4317
123 | interval: 30s
124 | attributes:
125 | instance_id:
126 | dns: false
127 | kubernetes:
128 | enable: true
129 | # drop_external only collects spans for kubernetes entities (e.g. pods), which reduces resource usage.
130 | drop_external: true
131 | # disable_informers prevents Beyla from watching k8s resources, and reduces the load on the kubernetes API Server.
132 | disable_informers: [replicaset, service, node]
133 | routes:
134 | unmatched: wildcard
135 | ebpf:
136 | bpf_fs_base_dir: /sys/fs/bpf
137 |
--------------------------------------------------------------------------------
/recipes/beyla-service-graph/collector-config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: OpenTelemetryCollector
17 | metadata:
18 | name: otel
19 | spec:
20 | image: otel/opentelemetry-collector-contrib:0.112.0
21 | config: |
22 | receivers:
23 | # receive OTLP spans from Beyla
24 | otlp:
25 | protocols:
26 | grpc:
27 | endpoint: 0.0.0.0:4317
28 | http:
29 | endpoint: 0.0.0.0:4318
30 | connectors:
31 | # convert spans into a calls metric
32 | spanmetrics/servicegraph:
33 | histogram:
34 | disable: true
35 | dimensions:
36 | - name: server.address
37 | - name: client.address
38 | exclude_dimensions:
39 | - 'status.code'
40 | - 'span.kind'
41 | - 'span.name'
42 | - 'service.name'
43 | - 'rpc.method'
44 | - 'rpc.system'
45 | - 'rpc.grpc.status_code'
46 | - 'server.port'
47 | processors:
48 | # filter down to only non-local http server spans
49 | filter/serveronly:
50 | error_mode: ignore
51 | traces:
52 | span:
53 | - attributes["http.request.method"] == nil and attributes["rpc.method"] == nil
54 | - kind.string != "Server"
55 | - attributes["server.address"] == "127.0.0.1"
56 | # detect gke resource attributes
57 | resourcedetection:
58 | detectors: [env, gcp]
59 | timeout: 2s
60 | override: false
61 | # Move server and client address from metric attributes to resource attributes so we can
62 | # use it to get k8s.pod.name for the server and client pods
63 | groupbyattrs:
64 | keys:
65 | - server.address
66 | - client.address
67 | # Metrics are generated from server spans only, so k8s.pod.name already contains the server pod
68 | transform/k8spodname_to_server:
69 | metric_statements:
70 | - context: resource
71 | statements:
72 | - set(attributes["server"], attributes["k8s.pod.name"])
73 | - delete_key(attributes, "k8s.pod.name")
74 | # Put the client address into k8s.pod.ip for k8sattributes processor to detect the client pod name
75 | - set(attributes["k8s.pod.ip"], attributes["client.address"])
76 | # Add k8s.pod.name
77 | k8sattributes:
78 | extract:
79 | metadata:
80 | - k8s.pod.name
81 | pod_association:
82 | - sources:
83 | - from: resource_attribute
84 | name: k8s.pod.ip
85 | # Moves annotated k8s.pod.name to client.address
86 | transform/k8spodname_to_client:
87 | metric_statements:
88 | - context: resource
89 | statements:
90 | - set(attributes["client"], attributes["k8s.pod.name"])
91 | - delete_key(attributes, "k8s.pod.name")
92 | - delete_key(attributes, "k8s.pod.ip")
93 | transform/servicegraphconventions:
94 | metric_statements:
95 | - context: datapoint
96 | statements:
97 | # GMP metrics are expected to be double
98 | - set(value_double, Double(value_int))
99 | # Rename metric to match the servicegraphconnector's output
100 | # https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/servicegraphconnector#how-it-works
101 | - context: metric
102 | statements:
103 | - set(name, "traces_service_graph_request") where name == "traces.span.metrics.calls"
104 | resource/podinstance:
105 | attributes:
106 | - key: pod
107 | from_attribute: k8s.pod.name
108 | action: upsert
109 | - key: service.instance.id
110 | from_attribute: k8s.pod.uid
111 | action: upsert
112 | exporters:
113 | googlemanagedprometheus:
114 | metric:
115 | resource_filters:
116 | - regex: 'pod'
117 | - regex: client
118 | - regex: server
119 | service:
120 | pipelines:
121 | traces:
122 | receivers: [otlp]
123 | processors: [filter/serveronly]
124 | exporters: [spanmetrics/servicegraph]
125 | metrics:
126 | receivers: [spanmetrics/servicegraph]
127 | processors:
128 | - groupbyattrs
129 | - transform/k8spodname_to_server
130 | - k8sattributes
131 | - transform/k8spodname_to_client
132 | - transform/servicegraphconventions
133 | - resourcedetection
134 | exporters: [googlemanagedprometheus]
135 |
--------------------------------------------------------------------------------
/recipes/beyla-service-graph/graphgen/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/GoogleCloudPlatform/opentelemetry-operator-sample/recipes/beyla/graphgen
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/goccy/go-graphviz v0.1.2
7 | github.com/prometheus/client_golang v1.17.0
8 | github.com/prometheus/common v0.44.0
9 | github.com/stretchr/testify v1.8.4
10 | google.golang.org/api v0.154.0
11 | )
12 |
13 | require (
14 | cloud.google.com/go/compute v1.23.3 // indirect
15 | cloud.google.com/go/compute/metadata v0.2.3 // indirect
16 | github.com/davecgh/go-spew v1.1.1 // indirect
17 | github.com/felixge/httpsnoop v1.0.4 // indirect
18 | github.com/fogleman/gg v1.3.0 // indirect
19 | github.com/go-logr/logr v1.3.0 // indirect
20 | github.com/go-logr/stdr v1.2.2 // indirect
21 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
22 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
23 | github.com/golang/protobuf v1.5.3 // indirect
24 | github.com/google/s2a-go v0.1.7 // indirect
25 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
26 | github.com/json-iterator/go v1.1.12 // indirect
27 | github.com/kr/text v0.2.0 // indirect
28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
29 | github.com/modern-go/reflect2 v1.0.2 // indirect
30 | github.com/pkg/errors v0.9.1 // indirect
31 | github.com/pmezard/go-difflib v1.0.0 // indirect
32 | go.opencensus.io v0.24.0 // indirect
33 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
34 | go.opentelemetry.io/otel v1.21.0 // indirect
35 | go.opentelemetry.io/otel/metric v1.21.0 // indirect
36 | go.opentelemetry.io/otel/trace v1.21.0 // indirect
37 | golang.org/x/crypto v0.16.0 // indirect
38 | golang.org/x/image v0.14.0 // indirect
39 | golang.org/x/net v0.19.0 // indirect
40 | golang.org/x/oauth2 v0.15.0 // indirect
41 | golang.org/x/sys v0.15.0 // indirect
42 | golang.org/x/text v0.14.0 // indirect
43 | google.golang.org/appengine v1.6.7 // indirect
44 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect
45 | google.golang.org/grpc v1.59.0 // indirect
46 | google.golang.org/protobuf v1.31.0 // indirect
47 | gopkg.in/yaml.v3 v3.0.1 // indirect
48 | )
49 |
--------------------------------------------------------------------------------
/recipes/beyla-service-graph/graphgen/internal/graph.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package internal
16 |
17 | import (
18 | "io"
19 | "log/slog"
20 |
21 | "github.com/goccy/go-graphviz"
22 | "github.com/goccy/go-graphviz/cgraph"
23 | )
24 |
25 | type Node struct {
26 | Ip string
27 | Name string
28 | }
29 |
30 | type Graph struct {
31 | // Adjacency list where nodes are IP address strings and values are the nodes. Each key called the value.
32 | adjacencies map[string][]string
33 | nodes map[string]*Node
34 | }
35 |
36 | func NewGraph() *Graph {
37 | return &Graph{
38 | adjacencies: make(map[string][]string),
39 | nodes: make(map[string]*Node),
40 | }
41 | }
42 |
43 | func (g Graph) AddEdge(client, server *Node) {
44 | g.nodes[client.Ip] = client
45 | g.nodes[server.Ip] = server
46 | g.adjacencies[client.Ip] = append(g.adjacencies[client.Ip], server.Ip)
47 | }
48 |
49 | func (g Graph) Render(writer io.Writer) error {
50 | gv := graphviz.New()
51 | graph, err := gv.Graph()
52 | if err != nil {
53 | return err
54 | }
55 | defer func() {
56 | if err := graph.Close(); err != nil {
57 | slog.Error("error closing graph", "err", err)
58 | }
59 | gv.Close()
60 | }()
61 |
62 | nodes := map[*Node]*cgraph.Node{}
63 |
64 | // add nodes and edges
65 | for clientIp, servers := range g.adjacencies {
66 | clientNode, err := getCNode(g.nodes[clientIp], graph, nodes)
67 | if err != nil {
68 | return err
69 | }
70 | for _, serverIp := range servers {
71 | serverNode, err := getCNode(g.nodes[serverIp], graph, nodes)
72 | if err != nil {
73 | return err
74 | }
75 |
76 | _, err = graph.CreateEdge("", clientNode, serverNode)
77 | // TODO: set weight in label
78 | // e.SetLabel("e")
79 | if err != nil {
80 | return err
81 | }
82 | }
83 | }
84 |
85 | if err := gv.Render(graph, graphviz.SVG, writer); err != nil {
86 | return err
87 | }
88 | return nil
89 | }
90 |
91 | func getCNode(node *Node, graph *cgraph.Graph, nodes map[*Node]*cgraph.Node) (*cgraph.Node, error) {
92 | if node, ok := nodes[node]; ok {
93 | return node, nil
94 | }
95 |
96 | cnode, err := graph.CreateNode(node.Ip)
97 | if err != nil {
98 | return nil, err
99 | }
100 | cnode.SetTooltip(node.Ip)
101 | if node.Name != "" {
102 | cnode.SetLabel(node.Name)
103 | }
104 | cnode.SetOrdering(cgraph.OutOrdering)
105 |
106 | nodes[node] = cnode
107 | return cnode, nil
108 | }
109 |
--------------------------------------------------------------------------------
/recipes/beyla-service-graph/graphgen/internal/graph_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package internal_test
16 |
17 | import (
18 | "bytes"
19 | "testing"
20 |
21 | "github.com/GoogleCloudPlatform/opentelemetry-operator-sample/recipes/beyla/graphgen/internal"
22 | "github.com/stretchr/testify/assert"
23 | )
24 |
25 | func TestGraph_Render(t *testing.T) {
26 | g := internal.NewGraph()
27 | g.AddEdge(
28 | &internal.Node{Ip: "1.1.1.1", Name: "source"},
29 | &internal.Node{Ip: "1.1.1.2", Name: "dest"},
30 | )
31 |
32 | var buf bytes.Buffer
33 | assert.NoError(t, g.Render(&buf))
34 | assert.NotEmpty(t, buf.String())
35 | }
36 |
--------------------------------------------------------------------------------
/recipes/beyla-service-graph/graphgen/internal/query.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package internal
16 |
17 | import (
18 | "context"
19 | "fmt"
20 | "log/slog"
21 | "strings"
22 | "time"
23 |
24 | "github.com/prometheus/client_golang/api"
25 | v1 "github.com/prometheus/client_golang/api/prometheus/v1"
26 | "github.com/prometheus/common/model"
27 | )
28 |
29 | const (
30 | clientIpKey = "client_address"
31 | clientKey = "client"
32 | serverIpKey = "server_address"
33 | serverKey = "server"
34 | )
35 |
36 | type QueryArgs struct {
37 | QueryWindow time.Duration
38 | Cluster string
39 | Namespace string
40 | }
41 |
42 | func QueryPrometheus(
43 | ctx context.Context,
44 | client api.Client,
45 | queryArgs QueryArgs,
46 | ) (*Graph, error) {
47 | promApi := v1.NewAPI(client)
48 | query := getQuery(queryArgs)
49 | slog.InfoContext(ctx, "logging", "query", query)
50 | res, warnings, err := promApi.Query(ctx, query, time.Now())
51 | if err != nil {
52 | return nil, err
53 | }
54 |
55 | if len(warnings) != 0 {
56 | slog.WarnContext(ctx, "Warnings from promQL query", "warnings", warnings)
57 | }
58 | slog.InfoContext(ctx, "Got metrics", "metrics", res)
59 | slog.InfoContext(ctx, "type", "type", res.Type())
60 |
61 | vec, ok := res.(model.Vector)
62 | if !ok {
63 | return nil, fmt.Errorf("couldn't cast %v to vector", res)
64 | }
65 |
66 | graph := NewGraph()
67 | for _, sample := range vec {
68 | labels := sample.Metric
69 | client := &Node{Ip: string(labels[clientIpKey]), Name: string(labels[clientKey])}
70 | server := &Node{Ip: string(labels[serverIpKey]), Name: string(labels[serverKey])}
71 | graph.AddEdge(client, server)
72 | }
73 | return graph, nil
74 | }
75 |
76 | func getQuery(queryArgs QueryArgs) string {
77 | filters := addFilter(nil, "cluster", queryArgs.Cluster)
78 | filters = addFilter(filters, "namespace", queryArgs.Namespace)
79 | return fmt.Sprintf(
80 | `sum by (
81 | server, client_address, client, server_address
82 | ) (
83 | rate(traces_service_graph_request_total{%s}[%s]) != 0
84 | )`,
85 | strings.Join(filters, ","),
86 | queryArgs.QueryWindow,
87 | )
88 | }
89 |
90 | func addFilter(filters []string, key, value string) []string {
91 | if value == "" {
92 | return filters
93 | }
94 | return append(filters, fmt.Sprintf(`%s="%s"`, key, value))
95 | }
96 |
--------------------------------------------------------------------------------
/recipes/beyla-service-graph/graphgen/internal/query_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package internal
16 |
17 | import (
18 | "context"
19 | "io"
20 | "net/http"
21 | "net/http/httptest"
22 | "testing"
23 | "time"
24 |
25 | "github.com/prometheus/client_golang/api"
26 | "github.com/stretchr/testify/assert"
27 | )
28 |
29 | const testJson = `{
30 | "status": "success",
31 | "data": {
32 | "resultType": "vector",
33 | "result": [
34 | {
35 | "metric": {
36 | "client": "root",
37 | "client_address": "10.4.2.54",
38 | "server": "child1",
39 | "server_address": "10.4.1.85"
40 | },
41 | "value": [1709790055.51, "9.7341795580096"]
42 | },
43 | {
44 | "metric": {
45 | "client": "root",
46 | "client_address": "10.4.2.54",
47 | "server": "leaf1",
48 | "server_address": "10.4.1.86"
49 | },
50 | "value": [1709790055.51, "9.7341795580096"]
51 | },
52 | {
53 | "metric": {
54 | "client": "child1",
55 | "client_address": "10.4.1.85",
56 | "server": "leaf2",
57 | "server_address": "10.112.0.1"
58 | },
59 | "value": [1709790055.51, "9.7341795580096"]
60 | }
61 | ]
62 | }
63 | }`
64 |
65 | func TestQueryPrometheus(t *testing.T) {
66 | ctx := context.Background()
67 | ts := httptest.NewServer(
68 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
69 | io.WriteString(w, testJson)
70 | }),
71 | )
72 | defer ts.Close()
73 |
74 | client, err := api.NewClient(api.Config{
75 | Address: ts.URL,
76 | Client: ts.Client(),
77 | })
78 | assert.NoError(t, err)
79 |
80 | graph, err := QueryPrometheus(
81 | ctx,
82 | client,
83 | QueryArgs{
84 | QueryWindow: time.Second * 5,
85 | Cluster: "cluster",
86 | },
87 | )
88 | assert.NoError(t, err)
89 |
90 | assert.Equal(
91 | t,
92 | graph.nodes,
93 | map[string]*Node{
94 | "10.4.2.54": {Ip: "10.4.2.54", Name: "root"},
95 | "10.4.1.85": {Ip: "10.4.1.85", Name: "child1"},
96 | "10.4.1.86": {Ip: "10.4.1.86", Name: "leaf1"},
97 | "10.112.0.1": {Ip: "10.112.0.1", Name: "leaf2"},
98 | },
99 | )
100 | assert.Equal(
101 | t,
102 | graph.adjacencies,
103 | map[string][]string{
104 | "10.4.1.85": {"10.112.0.1"},
105 | "10.4.2.54": {"10.4.1.85", "10.4.1.86"},
106 | },
107 | )
108 | }
109 |
--------------------------------------------------------------------------------
/recipes/beyla-service-graph/graphgen/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "context"
19 | "flag"
20 | "fmt"
21 | "log/slog"
22 | "net/http"
23 | "os"
24 | "time"
25 |
26 | "github.com/GoogleCloudPlatform/opentelemetry-operator-sample/recipes/beyla/graphgen/internal"
27 | "github.com/prometheus/client_golang/api"
28 | "google.golang.org/api/option"
29 | apihttp "google.golang.org/api/transport/http"
30 | )
31 |
32 | var (
33 | projectId = flag.String("projectId", "", "Required. The projectID to query.")
34 | cluster = flag.String("cluster", "", "The GKE cluster to filter in the query. If not set, does no filtering.")
35 | namespace = flag.String("namespace", "", "The k8s namespace to filter in the query. If not set, does no filtering.")
36 | queryWindow = flag.Duration("queryWindow", time.Minute*5, "Query window for service graph metrics.")
37 | )
38 |
39 | func main() {
40 | flag.Parse()
41 | if *projectId == "" {
42 | fmt.Println("You must set the -projectId flag!")
43 | os.Exit(1)
44 | }
45 |
46 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
47 | defer cancel()
48 |
49 | client, err := createPromApiClient(ctx)
50 | if err != nil {
51 | slog.ErrorContext(ctx, "Got error creating prometheus api client")
52 | panic(err)
53 | }
54 |
55 | graph, err := internal.QueryPrometheus(
56 | ctx,
57 | client,
58 | internal.QueryArgs{
59 | QueryWindow: *queryWindow,
60 | Cluster: *cluster,
61 | Namespace: *namespace,
62 | },
63 | )
64 | if err != nil {
65 | slog.ErrorContext(ctx, "Got error querying prometheus", "err", err)
66 | panic(err)
67 | }
68 | slog.InfoContext(ctx, "Got graph list", "graph", graph)
69 |
70 | err = graph.Render(os.Stdout)
71 | if err != nil {
72 | slog.ErrorContext(ctx, "Got error rendering graph", "err", err)
73 | panic(err)
74 | }
75 | }
76 |
77 | func createPromApiClient(ctx context.Context) (api.Client, error) {
78 | roundTripper, err := apihttp.NewTransport(
79 | ctx,
80 | http.DefaultTransport,
81 | option.WithScopes("https://www.googleapis.com/auth/monitoring.read"),
82 | )
83 | if err != nil {
84 | return nil, err
85 | }
86 | return api.NewClient(api.Config{
87 | Address: fmt.Sprintf("https://monitoring.googleapis.com/v1/projects/%v/location/global/prometheus", *projectId),
88 | RoundTripper: roundTripper,
89 | })
90 | }
91 |
--------------------------------------------------------------------------------
/recipes/beyla-service-graph/graphgen/out.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
166 |
--------------------------------------------------------------------------------
/recipes/beyla-service-graph/rbac-beyla.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2024 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # See https://grafana.com/docs/beyla/latest/setup/kubernetes/#configuring-kubernetes-metadata-decoration
16 |
17 | apiVersion: v1
18 | kind: ServiceAccount
19 | metadata:
20 | name: beyla
21 | ---
22 | apiVersion: rbac.authorization.k8s.io/v1
23 | kind: ClusterRole
24 | metadata:
25 | name: beyla
26 | rules:
27 | - apiGroups: ["apps"]
28 | resources: ["replicasets"]
29 | verbs: ["list", "watch"]
30 | - apiGroups: [""]
31 | resources: ["pods"]
32 | verbs: ["list", "watch"]
33 | ---
34 | apiVersion: rbac.authorization.k8s.io/v1
35 | kind: ClusterRoleBinding
36 | metadata:
37 | name: beyla
38 | subjects:
39 | - kind: ServiceAccount
40 | name: beyla
41 | namespace: default
42 | roleRef:
43 | apiGroup: rbac.authorization.k8s.io
44 | kind: ClusterRole
45 | name: beyla
46 |
--------------------------------------------------------------------------------
/recipes/beyla-service-graph/rbac-otel.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: rbac.authorization.k8s.io/v1
16 | kind: ClusterRole
17 | metadata:
18 | name: otel-collector
19 | rules:
20 | - apiGroups: [""]
21 | resources: ["pods", "namespaces", "nodes"]
22 | verbs: ["get", "watch", "list"]
23 | - apiGroups: ["apps"]
24 | resources: ["replicasets"]
25 | verbs: ["get", "list", "watch"]
26 | - apiGroups: ["extensions"]
27 | resources: ["replicasets"]
28 | verbs: ["get", "list", "watch"]
29 |
30 | ---
31 | apiVersion: rbac.authorization.k8s.io/v1
32 | kind: ClusterRoleBinding
33 | metadata:
34 | name: otel-collector
35 | subjects:
36 | - kind: ServiceAccount
37 | name: otel-collector
38 | namespace: default
39 | roleRef:
40 | kind: ClusterRole
41 | name: otel-collector
42 | apiGroup: rbac.authorization.k8s.io
43 |
--------------------------------------------------------------------------------
/recipes/cloud-trace/README.md:
--------------------------------------------------------------------------------
1 | # Cloud Trace integration
2 |
3 | This recipe demonstrates how to configure the OpenTelemetry Collector
4 | (as deployed by the Operator) to send trace data to GCP [Cloud Trace](https://cloud.google.com/trace).
5 |
6 | This recipe is based on applying a Collector config that enables the Google Cloud exporter.
7 | It provides an `OpenTelemetryCollector` object that, when created, instructs the Operator to
8 | create a new instance of the Collector with that config. If overwriting an existing `OpenTelemetryCollector`
9 | object (i.e., you already have a running Collector through the Operator such as the one from the
10 | [main README](../../README.md#starting-the-collector)), the Operator will update that existing
11 | Collector with the new config.
12 |
13 |
14 | ## Prerequisites
15 |
16 | * Cloud Trace API enabled in your GCP project
17 | * The `roles/cloudtrace.agent` [IAM permission](https://cloud.google.com/trace/docs/iam#roles)
18 | for your cluster's service account (or Workload Identity setup as shown below).
19 | * A running GKE cluster
20 | * The OpenTelemetry Operator installed in your cluster
21 | * A Collector deployed with the Operator (recommended) or a ServiceAccount that can be used by the new Collector.
22 | * Note: This recipe assumes that you already have a Collector ServiceAccount named `otel-collector`,
23 | which is created by the operator when deploying an `OpenTelemetryCollector` object such as the
24 | one [in this repo](../../collector-config.yaml).
25 | * An application already deployed that is either:
26 | * Instrumented to send traces to the Collector
27 | * Auto-instrumented by the Operator
28 | * [One of the sample apps](../../sample-apps) from this repo
29 |
30 | Note that the `OpenTelemetryCollector` object needs to be in the same namespace as your sample
31 | app, or the Collector endpoint needs to be updated to point to the correct service address.
32 |
33 | ## Running
34 |
35 | ### Deploying the Recipe
36 |
37 | Apply the `OpenTelemetryCollector` object from this recipe:
38 |
39 | ```
40 | kubectl apply -f collector-config.yaml
41 | ```
42 |
43 | (This will overwrite any existing collector config, or create a new one if none exists.)
44 |
45 | Once the Collector restarts, you should see traces from your application
46 |
47 | ### Workload Identity Setup
48 |
49 | If you have Workload Identity enabled, you'll see permissions errors after deploying the recipe.
50 | You need to set up a GCP service account with permission to write traces to Cloud Trace, and allow
51 | the Collector's Kubernetes service account to act as your GCP service account. You can do this with
52 | the following commands:
53 |
54 | ```
55 | export GCLOUD_PROJECT=
56 | gcloud iam service-accounts create otel-collector --project=${GCLOUD_PROJECT}
57 | ```
58 |
59 | Then give that service account permission to write traces:
60 |
61 | ```
62 | gcloud projects add-iam-policy-binding $GCLOUD_PROJECT \
63 | --member "serviceAccount:otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com" \
64 | --role "roles/cloudtrace.agent"
65 | ```
66 |
67 | Then bind the GCP service account to the Kubernetes ServiceAccount that is used by the Collector
68 | you deployed in the prerequisites (note: set `$COLLECTOR_NAMESPACE` to the namespace you installed
69 | the Collector in):
70 |
71 | ```
72 | export COLLECTOR_NAMESPACE=default
73 | gcloud iam service-accounts add-iam-policy-binding "otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com" \
74 | --role roles/iam.workloadIdentityUser \
75 | --member "serviceAccount:${GCLOUD_PROJECT}.svc.id.goog[${COLLECTOR_NAMESPACE}/otel-collector]"
76 | ```
77 |
78 | **(Optional):** If you don't already have a ServiceAccount for the Collector (such as the one provided
79 | when deploying a prior OpenTelemetryCollector object), create it with `kubectl create serviceaccount otel-collector`.
80 |
81 | Finally, annotate the OpenTelemetryCollector Object to allow the Collector's ServiceAccount to use Workload Identity:
82 |
83 | ```
84 | kubectl annotate opentelemetrycollector otel \
85 | --namespace $COLLECTOR_NAMESPACE \
86 | iam.gke.io/gcp-service-account=otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com
87 | ```
88 |
89 | ## View your Spans
90 |
91 | Navigate to https://console.cloud.google.com/traces/list, and click on one of
92 | the traces to see its details. Make sure you are looking at the right GCP project.
93 | If you don't see any traces right away, enable auto-reload in the top-right to
94 | have the graph periodically refreshed.
95 |
96 | The NodeJS example app trace will look something like:
97 |
98 | 
99 |
100 | ## Troubleshooting
101 |
102 | ### rpc error: code = PermissionDenied
103 |
104 | An error such as the following:
105 |
106 | ```
107 | 2022/10/21 13:41:11 failed to export to Google Cloud Trace: rpc error: code = PermissionDenied desc = The caller does not have permission
108 | ```
109 |
110 | This indicates that your Collector is unable to export spans, likely due to misconfigured IAM. Things to check:
111 |
112 | #### GKE (cluster-side) config issues
113 |
114 | With some configurations it's possible that the Operator could overwrite an existing ServiceAccount when deploying
115 | a new Collector. Ensure that the Collector's service account has the `iam.gke.io/gcp-service-account` annotation after
116 | running the `kubectl apply...` command in [Deploying the Recipe](#deploying-the-recipe). If this is missing, re-run the
117 | `kubectl annotate` command to add it to the ServiceAccount and restart the Collector Pod by deleting it (`kubectl delete pod/otel-collector-xxx..`).
118 |
119 | #### GCP (project-side) config issues
120 |
121 | Double check that IAM is properly configured for Cloud Trace access. This includes:
122 |
123 | * Verify the `otel-collector` service account exists in your GCP project
124 | * That service account must have `roles/cloudtrace.agent` permissions
125 | * The `serviceAccount:${GCLOUD_PROJECT}.svc.id.goog[${COLLECTOR_NAMESPACE}/otel-collector]` member must also be bound
126 | to the `roles/iam.workloadIdentityUser` role (this identifies the Kubernetes ServiceAccount as able to use Workload Identity)
127 |
--------------------------------------------------------------------------------
/recipes/cloud-trace/collector-config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: OpenTelemetryCollector
17 | metadata:
18 | name: otel
19 | spec:
20 | image: otel/opentelemetry-collector-contrib:0.112.0
21 | config: |
22 | receivers:
23 | otlp:
24 | protocols:
25 | grpc:
26 | endpoint: 0.0.0.0:4317
27 | http:
28 | endpoint: 0.0.0.0:4318
29 | processors:
30 |
31 | exporters:
32 | googlecloud:
33 | debug:
34 | verbosity: detailed
35 |
36 | service:
37 | pipelines:
38 | traces:
39 | receivers: [otlp]
40 | processors: []
41 | exporters: [debug, googlecloud]
42 |
--------------------------------------------------------------------------------
/recipes/daemonset-and-deployment/README.md:
--------------------------------------------------------------------------------
1 | # Daemonset and Deployment
2 |
3 | This recipe demonstrates how to configure the OpenTelemetry Collector
4 | (as deployed by the Operator) to run as a daemonset and deployment.
5 |
6 | The daemonset is configured to send to the deployment collector by setting
7 | `endpoint: otel-deployment-collector:4317` in the OTLP exporter. It runs one
8 | collector on each Kubernetes Node, and is configured with the `memory_limiter`
9 | processor to ensure it doesn't run into its memory limits and get OOM Killed.
10 |
11 | The deployment is configured with a persistent queue to enable it to buffer
12 | telemetry during a network outage. To do this, it includes an `emptyDir` volume
13 | and requests `1Gi` of `ephemeral-storage` space to ensure it has guaranteed access
14 | to disk space for buffering. It configures the `file_storage` extension to make
15 | that disk space available to exporters in the collector, and configures the `googlecloud`
16 | exporter's `sending_queue` to use that storage for storing items in the queue.
17 |
18 | If overwriting an existing `OpenTelemetryCollector` object (i.e., you already have a running
19 | Collector through the Operator such as the one from the
20 | [main README](../../README.md#starting-the-collector)), the Operator will update that existing
21 | Collector with the new config. The [main instrumentation.yaml](../../instrumentation.yaml)
22 | is configured to send to the daemonset to demonstrate forwarding metrics from a
23 | local daemonset pod to a deployment.
24 |
25 | ## Why both?
26 |
27 | Why would you want to run both a daemonset and deployment?
28 |
29 | The answer is simple: You need a daemonset because you're observing data local to each kubernetes node
30 | but you'd like the *scalability* of a deployment.
31 |
32 | By using both, you can gain the flexibility you need to scale your observability:
33 |
34 | - Local node observability can be limited to the daemonset, e.g.
35 | - `hostmetrics` receiver
36 | - container logs (via [`filelog` receiver](https://opentelemetry.io/docs/kubernetes/collector/components/#filelog-receiver))
37 | - Instrumentation can still point at the deployment, ensuring you can scale horizontally with load.
38 | - The `memory_limiter` on the daemonset helps drop o11y to remain within scaling limits.
39 | - The persistent queue on the deployment helps deal with network outages when sending telemetry out of the cluster.
40 |
41 | ## Prerequisites
42 |
43 | * Cloud Trace API enabled in your GCP project
44 | * The `roles/cloudtrace.agent` [IAM permission](https://cloud.google.com/trace/docs/iam#roles)
45 | for your cluster's service account (or Workload Identity setup as shown below).
46 | * A running GKE cluster
47 | * The OpenTelemetry Operator installed in your cluster
48 | * A Collector deployed with the Operator (recommended) or a ServiceAccount that can be used by the new Collector.
49 | * Note: This recipe assumes that you already have a Collector ServiceAccount named `otel-collector`,
50 | which is created by the operator when deploying an `OpenTelemetryCollector` object such as the
51 | one [in this repo](../../collector-config.yaml).
52 | * An application already deployed that is either:
53 | * Instrumented to send traces to the Collector
54 | * Auto-instrumented by the Operator
55 | * [One of the sample apps](../../sample-apps) from this repo
56 |
57 | Note that the `OpenTelemetryCollector` object needs to be in the same namespace as your sample
58 | app, or the Collector endpoint needs to be updated to point to the correct service address.
59 |
60 | ## Running
61 |
62 | ### Deploying the Recipe
63 |
64 | Apply the `OpenTelemetryCollector` objects from this recipe:
65 |
66 | ```
67 | kubectl apply -f daemonset-collector-config.yaml
68 | kubectl apply -f deployment-collector-config.yaml
69 | ```
70 |
71 | (This will overwrite any existing collector config, or create a new one if none exists.)
72 |
73 | Once the Collector restarts, you should see traces from your application
74 |
75 | ### Workload Identity Setup
76 |
77 | If you have Workload Identity enabled, you'll see permissions errors after deploying the recipe.
78 | You need to set up a GCP service account with permission to write traces to Cloud Trace, and allow
79 | the Collector's Kubernetes service account to act as your GCP service account. You can do this with
80 | the following commands:
81 |
82 | ```
83 | export GCLOUD_PROJECT=
84 | gcloud iam service-accounts create otel-collector --project=${GCLOUD_PROJECT}
85 | ```
86 |
87 | Then give that service account permission to write traces:
88 |
89 | ```
90 | gcloud projects add-iam-policy-binding $GCLOUD_PROJECT \
91 | --member "serviceAccount:otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com" \
92 | --role "roles/cloudtrace.agent"
93 | ```
94 |
95 | Then bind the GCP service account to the Kubernetes ServiceAccount that is used by the Collector
96 | you deployed in the prerequisites (note: set `$COLLECTOR_NAMESPACE` to the namespace you installed
97 | the Collector in):
98 |
99 | ```
100 | export COLLECTOR_NAMESPACE=default
101 | gcloud iam service-accounts add-iam-policy-binding "otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com" \
102 | --role roles/iam.workloadIdentityUser \
103 | --member "serviceAccount:${GCLOUD_PROJECT}.svc.id.goog[${COLLECTOR_NAMESPACE}/otel-collector]"
104 | ```
105 |
106 | **(Optional):** If you don't already have a ServiceAccount for the Collector (such as the one provided
107 | when deploying a prior OpenTelemetryCollector object), create it with `kubectl create serviceaccount otel-collector`.
108 |
109 | Finally, annotate the OpenTelemetryCollector Object to allow the Collector's ServiceAccount to use Workload Identity:
110 |
111 | ```
112 | kubectl annotate opentelemetrycollector otel \
113 | --namespace $COLLECTOR_NAMESPACE \
114 | iam.gke.io/gcp-service-account=otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com
115 | ```
116 |
117 | ## View your Spans
118 |
119 | Navigate to https://console.cloud.google.com/traces/list, and click on one of
120 | the traces to see its details. Make sure you are looking at the right GCP project.
121 | If you don't see any traces right away, enable auto-reload in the top-right to
122 | have the graph periodically refreshed.
123 |
124 | The NodeJS example app trace will look something like:
125 |
126 | 
127 |
128 | ## Troubleshooting
129 |
130 | ### rpc error: code = PermissionDenied
131 |
132 | An error such as the following:
133 |
134 | ```
135 | 2022/10/21 13:41:11 failed to export to Google Cloud Trace: rpc error: code = PermissionDenied desc = The caller does not have permission
136 | ```
137 |
138 | This indicates that your Collector is unable to export spans, likely due to misconfigured IAM. Things to check:
139 |
140 | #### GKE (cluster-side) config issues
141 |
142 | With some configurations it's possible that the Operator could overwrite an existing ServiceAccount when deploying
143 | a new Collector. Ensure that the Collector's service account has the `iam.gke.io/gcp-service-account` annotation after
144 | running the `kubectl apply...` command in [Deploying the Recipe](#deploying-the-recipe). If this is missing, re-run the
145 | `kubectl annotate` command to add it to the ServiceAccount and restart the Collector Pod by deleting it (`kubectl delete pod/otel-collector-xxx..`).
146 |
147 | #### GCP (project-side) config issues
148 |
149 | Double check that IAM is properly configured for Cloud Trace access. This includes:
150 |
151 | * Verify the `otel-collector` service account exists in your GCP project
152 | * That service account must have `roles/cloudtrace.agent` permissions
153 | * The `serviceAccount:${GCLOUD_PROJECT}.svc.id.goog[${COLLECTOR_NAMESPACE}/otel-collector]` member must also be bound
154 | to the `roles/iam.workloadIdentityUser` role (this identifies the Kubernetes ServiceAccount as able to use Workload Identity)
155 |
--------------------------------------------------------------------------------
/recipes/daemonset-and-deployment/daemonset-collector-config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: OpenTelemetryCollector
17 | metadata:
18 | name: otel
19 | spec:
20 | image: otel/opentelemetry-collector-contrib:0.112.0
21 | mode: daemonset
22 | resources:
23 | requests:
24 | cpu: 10m
25 | memory: 60Mi
26 | limits:
27 | memory: 100Mi
28 | config: |
29 | receivers:
30 | otlp:
31 | protocols:
32 | grpc:
33 | endpoint: 0.0.0.0:4317
34 | http:
35 | endpoint: 0.0.0.0:4318
36 | processors:
37 | memory_limiter:
38 | check_interval: 1s
39 | limit_percentage: 65
40 | spike_limit_percentage: 20
41 |
42 | exporters:
43 | otlp:
44 | endpoint: otel-deployment-collector:4317
45 | tls:
46 | insecure: true
47 |
48 | service:
49 | pipelines:
50 | traces:
51 | receivers: [otlp]
52 | processors: [memory_limiter]
53 | exporters: [otlp]
54 |
--------------------------------------------------------------------------------
/recipes/daemonset-and-deployment/deployment-collector-config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: OpenTelemetryCollector
17 | metadata:
18 | name: otel-deployment
19 | spec:
20 | image: otel/opentelemetry-collector-contrib:0.112.0
21 | resources:
22 | requests:
23 | cpu: 50m
24 | memory: 100Mi
25 | ephemeral-storage: 1Gi
26 | limits:
27 | memory: 140Mi
28 | volumeMounts:
29 | - mountPath: /var/lib/buffered-telemetry
30 | name: buffered-telemetry
31 | volumes:
32 | - emptyDir: {}
33 | name: buffered-telemetry
34 | config: |
35 | receivers:
36 | otlp:
37 | protocols:
38 | grpc:
39 | endpoint: 0.0.0.0:4317
40 | http:
41 | endpoint: 0.0.0.0:4318
42 | extensions:
43 | file_storage:
44 | directory: /var/lib/buffered-telemetry
45 | timeout: 10s
46 |
47 | exporters:
48 | googlecloud:
49 | sending_queue:
50 | storage: file_storage
51 |
52 | service:
53 | extensions: [file_storage]
54 | pipelines:
55 | traces:
56 | receivers: [otlp]
57 | processors: []
58 | exporters: [googlecloud]
59 |
--------------------------------------------------------------------------------
/recipes/host-and-kubelet-metrics/README.md:
--------------------------------------------------------------------------------
1 | # Host and kubelet metrics
2 |
3 | This recipe demonstrates how to configure the OpenTelemetry Collector
4 | (as deployed by the Operator) to send host and pod-level resource metrics
5 | [Google Managed Service for Prometheus](https://cloud.google.com/stackdriver/docs/managed-prometheus).
6 |
7 | This recipe is based on applying a Collector config that enables the Google Managed Prometheus exporter.
8 | It provides an `OpenTelemetryCollector` object that, when created, instructs the Operator to
9 | create a new instance of the Collector with that config. If overwriting an existing `OpenTelemetryCollector`
10 | object (i.e., you already have a running Collector through the Operator such as the one from the
11 | [main README](../../README.md#starting-the-collector)), the Operator will update that existing
12 | Collector with the new config.
13 |
14 | ## Prerequisites
15 |
16 | * Cloud Monitoring API enabled in your GCP project
17 | * The `roles/monitoring.metricWriter`
18 | [IAM permissions](https://cloud.google.com/trace/docs/iam#roles) for your cluster's service
19 | account (or Workload Identity setup as shown below).
20 | * A running GKE cluster
21 | * The OpenTelemetry Operator installed in your cluster
22 | * A Collector deployed with the Operator (recommended) or a ServiceAccount that can be used by the new Collector.
23 | * Note: This recipe assumes that you already have a Collector ServiceAccount named `otel-collector`,
24 | which is created by the operator when deploying an `OpenTelemetryCollector` object such as the
25 | one [in this repo](../../collector-config.yaml).
26 |
27 | ## Running
28 |
29 | ### Deploying the Recipe
30 |
31 | Apply the `OpenTelemetryCollector` object from this recipe:
32 |
33 | ```
34 | kubectl apply -f collector-config.yaml
35 | ```
36 |
37 | (This will overwrite any existing collector config, or create a new one if none exists.)
38 |
39 | Once the Collector restarts, you should see traces from your application
40 |
41 | ### Workload Identity Setup
42 |
43 | If you have Workload Identity enabled, you'll see permissions errors after deploying the recipe.
44 | You need to set up a GCP service account with permission to write traces to Cloud Trace, and allow
45 | the Collector's Kubernetes service account to act as your GCP service account. You can do this with
46 | the following commands:
47 |
48 | ```
49 | export GCLOUD_PROJECT=
50 | gcloud iam service-accounts create otel-collector --project=${GCLOUD_PROJECT}
51 | ```
52 |
53 | Then give that service account permission to write traces:
54 |
55 | ```
56 | gcloud projects add-iam-policy-binding $GCLOUD_PROJECT \
57 | --member "serviceAccount:otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com" \
58 | --role "roles/monitoring.metricWriter"
59 | ```
60 |
61 | Then bind the GCP service account to the Kubernetes ServiceAccount that is used by the Collector
62 | you deployed in the prerequisites (note: set `$COLLECTOR_NAMESPACE` to the namespace you installed
63 | the Collector in):
64 |
65 | ```
66 | export COLLECTOR_NAMESPACE=default
67 | gcloud iam service-accounts add-iam-policy-binding "otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com" \
68 | --role roles/iam.workloadIdentityUser \
69 | --member "serviceAccount:${GCLOUD_PROJECT}.svc.id.goog[${COLLECTOR_NAMESPACE}/otel-collector]"
70 | ```
71 |
72 | **(Optional):** If you don't already have a ServiceAccount for the Collector (such as the one provided
73 | when deploying a prior OpenTelemetryCollector object), create it with `kubectl create serviceaccount otel-collector`.
74 |
75 | Finally, annotate the OpenTelemetryCollector Object to allow the Collector's ServiceAccount to use Workload Identity:
76 |
77 | ```
78 | kubectl annotate opentelemetrycollector otel \
79 | --namespace $COLLECTOR_NAMESPACE \
80 | iam.gke.io/gcp-service-account=otel-collector@${GCLOUD_PROJECT}.iam.gserviceaccount.com
81 | ```
82 |
83 | ## View your Metrics
84 |
85 | Navigate to console.cloud.google.com/monitoring/metrics-explorer, and in the
86 | "Select a metric" dropdown, search for "prometheus/k8s" to see kubelet metrics
87 | or "prometheus/system" to see available host metrics.
88 |
89 | ## Troubleshooting
90 |
91 | ### rpc error: code = PermissionDenied
92 |
93 | An error such as the following:
94 |
95 | ```
96 | 2022/10/21 13:41:11 failed to export to Google Cloud Trace: rpc error: code = PermissionDenied desc = The caller does not have permission
97 | ```
98 |
99 | This indicates that your Collector is unable to export spans, likely due to misconfigured IAM. Things to check:
100 |
101 | #### GKE (cluster-side) config issues
102 |
103 | With some configurations it's possible that the Operator could overwrite an existing ServiceAccount when deploying
104 | a new Collector. Ensure that the Collector's service account has the `iam.gke.io/gcp-service-account` annotation after
105 | running the `kubectl apply...` command in [Deploying the Recipe](#deploying-the-recipe). If this is missing, re-run the
106 | `kubectl annotate` command to add it to the ServiceAccount and restart the Collector Pod by deleting it (`kubectl delete pod/otel-collector-xxx..`).
107 |
108 | #### GCP (project-side) config issues
109 |
110 | Double check that IAM is properly configured for Cloud Trace access. This includes:
111 |
112 | * Verify the `otel-collector` service account exists in your GCP project
113 | * That service account must have `roles/cloudtrace.agent` permissions
114 | * The `serviceAccount:${GCLOUD_PROJECT}.svc.id.goog[${COLLECTOR_NAMESPACE}/otel-collector]` member must also be bound
115 | to the `roles/iam.workloadIdentityUser` role (this identifies the Kubernetes ServiceAccount as able to use Workload Identity)
116 |
--------------------------------------------------------------------------------
/recipes/host-and-kubelet-metrics/collector-config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2024 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: OpenTelemetryCollector
17 | metadata:
18 | name: otel
19 | spec:
20 | image: otel/opentelemetry-collector-contrib:0.112.0
21 | mode: daemonset
22 | env:
23 | - name: K8S_NODE_NAME
24 | valueFrom:
25 | fieldRef:
26 | fieldPath: spec.nodeName
27 | volumes:
28 | - name: hostfs
29 | hostPath:
30 | path: /
31 | volumeMounts:
32 | - mountPath: /hostfs
33 | name: hostfs
34 | config: |
35 | receivers:
36 | hostmetrics:
37 | collection_interval: 10s
38 | root_path: /hostfs
39 | scrapers:
40 | cpu:
41 | disk:
42 | load:
43 | memory:
44 | network:
45 | paging:
46 | processes:
47 | filesystem:
48 | exclude_mount_points:
49 | match_type: regexp
50 | mount_points:
51 | - /var/lib/kubelet/*
52 | kubeletstats:
53 | collection_interval: 10s
54 | auth_type: "none"
55 | endpoint: "http://${env:K8S_NODE_NAME}:10255"
56 | insecure_skip_verify: true
57 |
58 | processors:
59 | resourcedetection:
60 | detectors: [gcp]
61 | timeout: 10s
62 |
63 | resource:
64 | attributes:
65 | - action: insert
66 | key: k8s.node.name
67 | value: ${K8S_NODE_NAME}
68 |
69 | transform:
70 | # "location", "cluster", "namespace", "job", "instance", and "project_id" are reserved, and
71 | # metrics containing these labels will be rejected. Prefix them with exported_ to prevent this.
72 | metric_statements:
73 | - context: datapoint
74 | statements:
75 | - set(attributes["exported_location"], attributes["location"])
76 | - delete_key(attributes, "location")
77 | - set(attributes["exported_cluster"], attributes["cluster"])
78 | - delete_key(attributes, "cluster")
79 | - set(attributes["exported_namespace"], attributes["namespace"])
80 | - delete_key(attributes, "namespace")
81 | - set(attributes["exported_job"], attributes["job"])
82 | - delete_key(attributes, "job")
83 | - set(attributes["exported_instance"], attributes["instance"])
84 | - delete_key(attributes, "instance")
85 | - set(attributes["exported_project_id"], attributes["project_id"])
86 | - delete_key(attributes, "project_id")
87 |
88 | batch:
89 | # batch metrics before sending to reduce API usage
90 | send_batch_max_size: 200
91 | send_batch_size: 200
92 | timeout: 5s
93 |
94 | memory_limiter:
95 | # drop metrics if memory usage gets too high
96 | check_interval: 1s
97 | limit_percentage: 65
98 | spike_limit_percentage: 20
99 |
100 | exporters:
101 | debug:
102 | verbosity: detailed
103 | googlemanagedprometheus:
104 | metric:
105 | extra_metrics_config:
106 | enable_target_info: false
107 | resource_filters:
108 | - regex: "k8s.*"
109 |
110 | service:
111 | pipelines:
112 | metrics:
113 | receivers: [hostmetrics, kubeletstats]
114 | processors: [batch, memory_limiter, resourcedetection, transform, resource]
115 | exporters: [googlemanagedprometheus, debug]
116 |
--------------------------------------------------------------------------------
/recipes/resource-detection/README.md:
--------------------------------------------------------------------------------
1 | # GCE/GKE Resource Detection
2 |
3 | This recipe demonstrates GKE and GCE resource detection in a collector config,
4 | deployed with the operator. These resource detectors add the following metadata:
5 |
6 | GKE:
7 | ```
8 | * cloud.provider ("gcp")
9 | * cloud.platform ("gcp_gke")
10 | * k8s.cluster.name (name of the GKE cluster)
11 | ```
12 |
13 | GCE:
14 | ```
15 | * cloud.platform ("gcp_compute_engine")
16 | * cloud.account.id
17 | * cloud.region
18 | * cloud.availability_zone
19 | * host.id
20 | * host.image.id
21 | * host.type
22 | ```
23 |
24 | ## Prerequisites
25 |
26 | * OpenTelemetry Operator installed in your cluster
27 | * Running un-instrumented application (such as one of the [sample apps](../../sample-apps)).
28 | * An `Instrumentation` object already created such as the one from the main [README](../../README.md#auto-instrumenting-applications)
29 |
30 | # Running
31 |
32 | Apply the `OpenTelemetryCollector` object from [`collector-config.yaml`](collector-config.yaml):
33 |
34 | ```
35 | kubectl apply -f collector-config.yaml
36 | ```
37 |
38 | ### Checking the modified spans
39 |
40 | To stream logs from the otel-collector, run:
41 | ```
42 | kubectl logs deployment/otel-collector -f
43 | ```
44 |
45 | In these, you should see resource attributes such as the following:
46 |
47 | ```
48 | -> cloud.provider: STRING(gcp)
49 | -> cloud.platform: STRING(gcp_kubernetes_engine)
50 | -> cloud.region: STRING(us-central1)
51 | -> k8s.cluster.name: STRING(autopilot-cluster-1)
52 | ```
53 |
--------------------------------------------------------------------------------
/recipes/resource-detection/collector-config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: OpenTelemetryCollector
17 | metadata:
18 | name: otel
19 | spec:
20 | image: otel/opentelemetry-collector-contrib:0.112.0
21 | config: |
22 | receivers:
23 | otlp:
24 | protocols:
25 | grpc:
26 | endpoint: 0.0.0.0:4317
27 | http:
28 | endpoint: 0.0.0.0:4318
29 |
30 | processors:
31 | resourcedetection:
32 | detectors: [env, gcp]
33 | timeout: 2s
34 | override: false
35 |
36 | exporters:
37 | debug:
38 | verbosity: detailed
39 |
40 | service:
41 | pipelines:
42 | traces:
43 | receivers: [otlp]
44 | processors: [resourcedetection]
45 | exporters: [debug]
46 |
--------------------------------------------------------------------------------
/recipes/self-managed-otlp-ingest/instrumentation.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2025 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: Instrumentation
17 | metadata:
18 | namespace: default
19 | name: sample-java-auto-instrumentation
20 | spec:
21 | exporter:
22 | endpoint: http://opentelemetry-collector.opentelemetry.svc.cluster.local:4317
23 | sampler:
24 | type: parentbased_traceidratio
25 | argument: "0.01"
26 |
27 | java:
28 | env:
29 | - name: OTEL_EXPORTER_OTLP_PROTOCOL
30 | value: grpc
31 | - name: OTEL_LOGS_EXPORTER
32 | value: none
33 |
--------------------------------------------------------------------------------
/recipes/self-managed-otlp-ingest/k8s/kustomization.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2025 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: kustomize.config.k8s.io/v1beta1
16 | kind: Kustomization
17 | resources:
18 | - quickstart-app.yaml
19 | - quickstart-traffic.yaml
20 |
--------------------------------------------------------------------------------
/recipes/self-managed-otlp-ingest/k8s/quickstart-app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2025 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: v1
16 | kind: Service
17 | metadata:
18 | name: quickstart-app
19 | labels:
20 | app: quickstart-app
21 | spec:
22 | ports:
23 | - port: 8080
24 | targetPort: 8080
25 | name: quickstart-app
26 | selector:
27 | app: quickstart-app
28 | ---
29 | apiVersion: apps/v1
30 | kind: Deployment
31 | metadata:
32 | name: quickstart-app
33 | labels:
34 | app: quickstart-app
35 | spec:
36 | replicas: 2
37 | selector:
38 | matchLabels:
39 | app: quickstart-app
40 | template:
41 | metadata:
42 | labels:
43 | app: quickstart-app
44 | spec:
45 | containers:
46 | - name: quickstart-app
47 | image: ${REGISTRY_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${CONTAINER_REGISTRY}/java-quickstart:latest
48 | ports:
49 | - containerPort: 8080
50 | name: quickstart-app
51 |
--------------------------------------------------------------------------------
/recipes/self-managed-otlp-ingest/k8s/quickstart-traffic.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2025 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | name: traffic-simulator
19 | spec:
20 | replicas: 1
21 | selector:
22 | matchLabels:
23 | app: traffic-simulator
24 | template:
25 | metadata:
26 | labels:
27 | app: traffic-simulator
28 | spec:
29 | containers:
30 | - name: traffic-simulator
31 | image: ${REGISTRY_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${CONTAINER_REGISTRY}/hey:latest
32 | args:
33 | - -c=2
34 | - -q=1
35 | - -z=1h
36 | - http://quickstart-app:8080/multi
37 |
--------------------------------------------------------------------------------
/recipes/self-managed-otlp-ingest/setup-application.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright 2025 Google LLC
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # https://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | UNSET_WARNING="Environment variable not set, please set the environment variable"
17 |
18 | # Verify necessary environment variables are set
19 | echo "${PROJECT_ID:?${UNSET_WARNING}}"
20 | echo "${CLUSTER_NAME:?${UNSET_WARNING}}"
21 | echo "${CLUSTER_REGION:?${UNSET_WARNING}}"
22 | echo "${CONTAINER_REGISTRY:?${UNSET_WARNING}}"
23 | echo "${REGISTRY_LOCATION:?${UNSET_WARNING}}"
24 |
25 | echo "ENVIRONMENT VARIABLES VERIFIED"
26 |
27 | echo "CREATING CLUSTER WITH NAME ${CLUSTER_NAME} in ${CLUSTER_REGION}"
28 | gcloud beta container --project "${PROJECT_ID}" clusters create-auto "${CLUSTER_NAME}" --region "${CLUSTER_REGION}"
29 | echo "CLUSTER CREATED SUCCESSFULLY"
30 |
31 | echo "PULLING SAMPLE APPLICATION REPOSITORY"
32 | echo "BUILDING SAMPLE APPLICATION IMAGE"
33 | git clone https://github.com/GoogleCloudPlatform/opentelemetry-operations-java.git
34 | pushd opentelemetry-operations-java/examples/instrumentation-quickstart && \
35 | DOCKER_BUILDKIT=1 docker build -f uninstrumented.Dockerfile -t java-quickstart . && \
36 | popd && \
37 | rm -rf opentelemetry-operations-java
38 | echo "APPLICATION IMAGE BUILT"
39 |
40 | echo "CREATING CLOUD ARTIFACT REGISTRY"
41 | gcloud artifacts repositories create ${CONTAINER_REGISTRY} --repository-format=docker --location=${REGISTRY_LOCATION} --description="Sample applications to auto-instrument using OTel operator"
42 | echo "CREATED ${CONTAINER_REGISTRY} in ${REGISTRY_LOCATION}"
43 |
44 | echo "PUSHING THE SAMPLE APPLICATION IMAGE TO THE REGISTRY"
45 | docker tag java-quickstart:latest ${REGISTRY_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${CONTAINER_REGISTRY}/java-quickstart:latest
46 | docker push ${REGISTRY_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${CONTAINER_REGISTRY}/java-quickstart:latest
47 | echo "APPLICATION IMAGE PUSHED TO ARTIFACT REGISTRY"
48 |
49 | echo "BUILDING TRAFFIC SIMULATOR IMAGE"
50 | pushd traffic && \
51 | DOCKER_BUILDKIT=1 docker build -f hey.Dockerfile -t ${REGISTRY_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${CONTAINER_REGISTRY}/hey:latest . && \
52 | docker push ${REGISTRY_LOCATION}-docker.pkg.dev/${PROJECT_ID}/${CONTAINER_REGISTRY}/hey:latest && \
53 | popd
54 | echo "TRAFFIC SIMULATOR IMAGE BUILT & PUSHED TO ARTIFACT REGISTRY"
55 |
56 | echo "DEPLOYING APPLICATION ON ${CLUSTER_NAME}"
57 | kubectl kustomize k8s | envsubst | kubectl apply -f -
58 | echo "SAMPLE APPLICATION DEPLOYED"
59 |
--------------------------------------------------------------------------------
/recipes/self-managed-otlp-ingest/traffic/cloudbuild-hey.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2025 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # Configuration to build the 'hey' utility using Google Cloud Build. The
16 | # configuration also pushes the built application image to Google Artifact
17 | # Registry. The 'hey' utility issues a steady stram of requests to a configured
18 | # endpoint.
19 | # REGISTRY_LOCATION, GOOGLE_CLOUD_PROJECT, ARTIFACT_REGISTRY environment variables must be
20 | # substituted in this file.
21 | #
22 | # Using gCloud CLI:
23 | # gcloud builds submit --config <(envsubst < cloudbuild-hey.yaml) .
24 | steps:
25 | - name: 'gcr.io/cloud-builders/docker'
26 | env:
27 | - 'DOCKER_BUILDKIT=1'
28 | args: ['build', '-t', '${REGISTRY_LOCATION}-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/${ARTIFACT_REGISTRY}/hey:latest', '-f', 'hey.Dockerfile', '.']
29 | - name: 'gcr.io/cloud-builders/docker'
30 | args: ['push', '${REGISTRY_LOCATION}-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/${ARTIFACT_REGISTRY}/hey:latest']
31 | images: ['${REGISTRY_LOCATION}-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/${ARTIFACT_REGISTRY}/hey:latest']
32 |
--------------------------------------------------------------------------------
/recipes/self-managed-otlp-ingest/traffic/hey.Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright 2025 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | FROM golang:1.21-alpine3.19 AS build
16 |
17 | RUN apk add --no-cache wget
18 | # The link for the binary is specified in library's README
19 | # https://github.com/rakyll/hey/blob/master/README.md
20 | RUN wget -O /hey https://hey-release.s3.us-east-2.amazonaws.com/hey_linux_amd64
21 | RUN chmod +x /hey
22 |
23 | FROM scratch
24 | COPY --from=build /hey /hey
25 |
26 | ENTRYPOINT ["/hey"]
27 |
--------------------------------------------------------------------------------
/recipes/trace-enhancements/README.md:
--------------------------------------------------------------------------------
1 | # Trace enhancements
2 |
3 | This recipe shows how to do basic trace enhancement by inserting values with the
4 | [attributes processor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/attributesprocessor).
5 |
6 | ## Prerequisites
7 |
8 | * OpenTelemetry Operator installed in your cluster
9 | * Running un-instrumented application (such as one of the [sample apps](../../sample-apps)).
10 | * An `Instrumentation` object already created such as the one from the main [README](../../README.md#auto-instrumenting-applications)
11 |
12 | # Running
13 |
14 | Apply the `OpenTelemetryCollector` object from [`collector-config.yaml`](collector-config.yaml):
15 |
16 | ```
17 | kubectl apply -f collector-config.yaml
18 | ```
19 |
20 | # Combined with Resource Detection
21 |
22 | Similar to the attributes processor, you can use the
23 | [resource processor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/resourceprocessor)
24 | to parse resource attributes.
25 |
26 | Combine the resource processor with the
27 | [GCE/GKE resource detection recipe](../resource-detection)
28 | to auto-populate a new attribute dynamically based on where the app is running.
29 |
30 | The config file [`collector-config-resource-detection.yaml`](collector-config-resource-detection.yaml)
31 | creates a new `location` attribute that is parsed from the cloud zone or region the pod is running in.
32 |
33 | Create this config with:
34 |
35 | ```
36 | kubectl apply -f collector-config-resource-detection.yaml
37 | ```
38 |
39 | (Optionally, also enable [Cloud Trace integration](../cloud-trace) to see the traces in
40 | your GCP dashboard.)
41 |
42 | The regex in this config parses the first section of your node's zone or region to
43 | its own `location` attribute, for example `us-central1` becomes `us` and `asia-south1-b`
44 | becomes `asia`.
45 |
--------------------------------------------------------------------------------
/recipes/trace-enhancements/collector-config-resource-detection.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: OpenTelemetryCollector
17 | metadata:
18 | name: otel
19 | spec:
20 | image: otel/opentelemetry-collector-contrib:0.112.0
21 | config: |
22 | receivers:
23 | otlp:
24 | protocols:
25 | grpc:
26 | endpoint: 0.0.0.0:4317
27 | http:
28 | endpoint: 0.0.0.0:4318
29 |
30 | processors:
31 | resourcedetection:
32 | detectors: [env, gcp]
33 | timeout: 2s
34 | override: false
35 | resource:
36 | attributes:
37 | - key: "cloud.availability_zone"
38 | pattern: ^(?P.*)-.*
39 | action: extract
40 | - key: "cloud.region"
41 | pattern: ^(?P.*)-.*
42 | action: extract
43 |
44 | exporters:
45 | debug:
46 | verbosity: detailed
47 |
48 | service:
49 | pipelines:
50 | traces:
51 | receivers: [otlp]
52 | processors: [resourcedetection,resource]
53 | exporters: [debug]
54 |
--------------------------------------------------------------------------------
/recipes/trace-enhancements/collector-config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: OpenTelemetryCollector
17 | metadata:
18 | name: otel
19 | spec:
20 | image: otel/opentelemetry-collector-contrib:0.112.0
21 | config: |
22 | receivers:
23 | otlp:
24 | protocols:
25 | grpc:
26 | endpoint: 0.0.0.0:4317
27 | http:
28 | endpoint: 0.0.0.0:4318
29 |
30 | processors:
31 | attributes:
32 | actions:
33 | - key: "environment"
34 | value: "QA"
35 | action: insert
36 |
37 | exporters:
38 | debug:
39 | verbosity: detailed
40 |
41 | service:
42 | pipelines:
43 | traces:
44 | receivers: [otlp]
45 | processors: [attributes]
46 | exporters: [debug]
47 |
--------------------------------------------------------------------------------
/recipes/trace-filtering/README.md:
--------------------------------------------------------------------------------
1 | # Filtering out spans
2 |
3 | This recipe demonstrates how to configure the OpenTelemetry Collector
4 | to filter out spans.
5 |
6 | This recipe is based on applying a Collector config that includes the `filter` processor.
7 | It provides an `OpenTelemetryCollector` object that, when created, instructs the Operator to
8 | create a new instance of the Collector with that config. If overwriting an existing `OpenTelemetryCollector`
9 | object (i.e., you already have a running Collector through the Operator such as the one from the
10 | [main README](../../README.md#starting-the-collector)), the Operator will update that existing
11 | Collector with the new config.
12 |
13 |
14 | ## Prerequisites
15 |
16 | * A running Kubernetes cluster
17 | * The OpenTelemetry Operator installed in your cluster
18 | * A Collector deployed with the Operator (recommended)
19 | * [One of the sample apps](../../sample-apps) from this repo installed in the cluster
20 |
21 | Note that the `OpenTelemetryCollector` object needs to be in the same namespace as your sample
22 | app, or the Collector endpoint needs to be updated to point to the correct service address.
23 |
24 | ## Running
25 |
26 | ### Deploying the Recipe
27 |
28 | Apply the `OpenTelemetryCollector` object from this recipe:
29 |
30 | ```
31 | kubectl apply -f collector-config.yaml
32 | ```
33 |
34 | (This will overwrite any existing collector config, or create a new one if none exists.)
35 |
36 | ### Checking the filtered Spans
37 |
38 | To stream logs from the otel-collector, run:
39 | ```
40 | kubectl logs deployment/otel-collector -f
41 | ```
42 |
43 | You should see only client spans, where `service.name` is `-app`. You should not see any spans from the server, where `service.name` is `-service`, as those are filtered out.
44 |
45 | ## Learn More
46 |
47 | The filter processor can filter on more than just service name! For a complete description of its capabilities, see the upstream [README](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/filterprocessor#filter-processor).
--------------------------------------------------------------------------------
/recipes/trace-filtering/collector-config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: OpenTelemetryCollector
17 | metadata:
18 | name: otel
19 | spec:
20 | image: otel/opentelemetry-collector-contrib:0.112.0
21 | config: |
22 | receivers:
23 | otlp:
24 | protocols:
25 | grpc:
26 | endpoint: 0.0.0.0:4317
27 | http:
28 | endpoint: 0.0.0.0:4318
29 |
30 | processors:
31 | filter:
32 | spans:
33 | include:
34 | match_type: regexp
35 | # Keep the NodeJS, Python, and Java example client spans
36 | services:
37 | - .*-app.*
38 | exclude:
39 | match_type: regexp
40 | # Filter out the NodeJS, Python, and Java example server spans
41 | services:
42 | - .*-service
43 |
44 | exporters:
45 | debug:
46 | verbosity: detailed
47 |
48 | service:
49 | pipelines:
50 | traces:
51 | receivers: [otlp]
52 | processors: [filter]
53 | exporters: [debug]
54 |
--------------------------------------------------------------------------------
/recipes/trace-remote-sampling/README.md:
--------------------------------------------------------------------------------
1 | # Trace Remote Sampling Configuration Recipe
2 |
3 | This recipe shows how to use the Jaeger Remote Sampling
4 | protocol to provide fine-grained control of trace sampling.
5 |
6 |
7 | This recipe provides three main pieces of configuration:
8 |
9 | - A ConfigMap that provides fine-grained trace sampling controls for the entire cluster.
10 | - An OpenTelemetryCollector deployment that will serve the control protocol to instrumentation.
11 | - An Instrumentation configuration that will leverage the OpenTelemetryCollector deployment to look up trace sampling information.
12 |
13 | ## Prerequisites
14 |
15 | * OpenTelemetry Operator installed in your cluster
16 | * Running un-instrumented application (such as one of the [sample apps](../../sample-apps)).
17 |
18 | ## Running
19 |
20 | Apply the `ConfigMap` object from [`remote-sampling-config.yaml`](remote-sampling-config.yaml)
21 |
22 | ```
23 | kubectl apply -f remote-sampling-config.yaml
24 | ```
25 |
26 | This creates the configuration for trace sampling. At any point, we can modify the `remote-sampling-config.yaml` file and reperform this step to adjust sampling on the cluster in the future (see [Tuning](#tuning))
27 |
28 |
29 | Apply the `OpenTelemetryCollector` object from [`collector-config.yaml`](collector-config.yaml)
30 |
31 | ```
32 | kubectl apply -f collector-config.yaml
33 | ```
34 |
35 | Next, create the `Instrumentation` object from [`instrumentation.yaml`](instrumentation.yaml) that will use the remote sampling service:
36 |
37 | ```
38 | kubectl apply -f instrumentation.yaml
39 | ```
40 |
41 | This creates a new object named `instrumentation/trace-remote-sampling` in the current namespace.
42 |
43 | > Note that `jaeger_remote` sampler configuration is
44 | > only available in Java and Go as of 2023-10-18.
45 |
46 | Annotate your application pods to use these settings by editing the `instrumentation.opentelemetry.io`
47 | annotation using one of the following commands:
48 |
49 | > Note that if the app is a standalone Pod you can
50 | >`kubectl annotate` directly on the Pod, but if it is owned by a Deployment or other replica controller
51 | > you must patch the metadata of the Pod template.
52 |
53 | * **Java:**
54 |
55 | Pod:
56 | ```
57 | kubectl annotate pod/ instrumentation.opentelemetry.io/inject-java="trace-remote-sampling"
58 | ```
59 | Deployment:
60 | ```
61 | kubectl patch deployment.apps/ -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-java": "trace-remote-sampling"}}}}}'
62 | ```
63 |
64 | # Tuning
65 |
66 | To tune sampling in the cluster, simply update the `remote-sampling-config.yaml` and reapply:
67 |
68 | ```
69 | kubectl apply -f remote-sampling-config.yaml
70 | ```
71 |
72 | The OpenTelemetryCollector deployment should pick up changes within a few minutes of the ConfigMap rollout, and clients will further pull in those changes within a few minutes.
73 |
74 | This can help, e.g. when needing to increase the sampling rate of a service for better observability "on the fly" and turn it back down after collecting enough data.
75 |
76 | The format of the remote sampling configuration is [documented here](https://www.jaegertracing.io/docs/1.28/sampling/#collector-sampling-configuration).
77 |
78 | An example configuration which disables tracing prometheus metrics and health checks would look as follows:
79 |
80 | ```json
81 | {
82 | "default_strategy": {
83 | "type": "probabilistic",
84 | "param": 0.5,
85 | "operation_strategies": [
86 | {
87 | "operation": "GET /health",
88 | "type": "probabilistic",
89 | "param": 0.0
90 | },
91 | {
92 | "operation": "GET /metrics",
93 | "type": "probabilistic",
94 | "param": 0.0
95 | }
96 | ]
97 | }
98 | }
99 | ```
--------------------------------------------------------------------------------
/recipes/trace-remote-sampling/collector-config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: OpenTelemetryCollector
17 | metadata:
18 | name: otel
19 | spec:
20 | image: otel/opentelemetry-collector-contrib:0.112.0
21 | # We need to specify ports so remote sampler is exposed.
22 | ports:
23 | - port: 4317
24 | name: otlp
25 | - port: 4318
26 | name: otlp-grpc
27 | - port: 5778
28 | name: jaeger
29 | - port: 14250
30 | name: jaeger-grpc
31 | # Connect the config-map w/ our jaeger sampler config so we can update it quickly.
32 | volumes:
33 | - name: sampling-config-volume
34 | configMap:
35 | name: remote-sampling-config
36 | volumeMounts:
37 | - name: sampling-config-volume
38 | mountPath: /etc/otel/sampling
39 | readOnly: true
40 | # Ensure the jaeger remote sampler config is enabled as an extension.
41 | config: |
42 | receivers:
43 | otlp:
44 | protocols:
45 | grpc:
46 | endpoint: 0.0.0.0:4317
47 | http:
48 | endpoint: 0.0.0.0:4318
49 | exporters:
50 | debug:
51 | extensions:
52 | jaegerremotesampling:
53 | source:
54 | reload_interval: 60s
55 | file: /etc/otel/sampling/sampling.json
56 | service:
57 | extensions: [jaegerremotesampling]
58 | pipelines:
59 | traces:
60 | receivers: [otlp]
61 | processors: []
62 | exporters: [debug]
63 |
--------------------------------------------------------------------------------
/recipes/trace-remote-sampling/instrumentation.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: Instrumentation
17 | metadata:
18 | name: trace-remote-sampling
19 | spec:
20 |
21 | # Default exporting OTLP to the gRPC interface of a collector.
22 | exporter:
23 | endpoint: http://otel-collector:4317
24 |
25 | # Use w3c `traceparent` and `baggage` for propgation of distributed values.
26 | propagators:
27 | - tracecontext
28 | - baggage
29 |
30 | # Use the Jaeger remote sampling config from the gateway
31 | #
32 | # pollingInterval determines how often clients will refresh sampling configuration.
33 | # initialSamplingRate determines sampling in the event a client cannot connect to the
34 | # the sampling service.
35 | sampler:
36 | type: jaeger_remote
37 | argument: "endpoint=http://otel-collector:14250,pollingIntervalMs=5000,initialSamplingRate=0.25"
38 |
39 | # Note: Python currently supports HTTP not GRPC endpoint
40 | python:
41 | env:
42 | - name: OTEL_EXPORTER_OTLP_ENDPOINT
43 | value: http://otel-collector:4318
44 |
--------------------------------------------------------------------------------
/recipes/trace-remote-sampling/remote-sampling-config.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: v1
16 | kind: ConfigMap
17 | metadata:
18 | name: remote-sampling-config
19 | data:
20 | sampling.json: |
21 | {
22 | "default_strategy": {
23 | "type":"probabilistic",
24 | "param": 0.5
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/recipes/trace-sampling/README.md:
--------------------------------------------------------------------------------
1 | # Trace Sampling Configuration Recipe
2 |
3 | This recipe shows how to set basic trace sampling configuration on auto-instrumented applications.
4 |
5 | ## Prerequisites
6 |
7 | * OpenTelemetry Operator installed in your cluster
8 | * Running un-instrumented application (such as one of the [sample apps](../../sample-apps)).
9 |
10 | ## Running
11 |
12 | Create the `Instrumentation` object from [`instrumentation.yaml`](instrumentation.yaml):
13 |
14 | ```
15 | kubectl apply -f instrumentation.yaml
16 | ```
17 |
18 | This creates a new object named `instrumentation/trace-sampling` in the current namespace.
19 | Edit the `spec.sampler` section of this file to update settings for
20 | [trace sampling](https://opentelemetry.io/docs/reference/specification/trace/tracestate-probability-sampling/),
21 | such as the [type of sampler](https://opentelemetry.io/docs/reference/specification/trace/sdk/#built-in-samplers)
22 | and sampling ratio.
23 |
24 | Annotate your application pods to use these settings by editing the `instrumentation.opentelemetry.io`
25 | annotation using one of the following commands:
26 |
27 | > Note that if the app is a standalone Pod you can
28 | >`kubectl annotate` directly on the Pod, but if it is owned by a Deployment or other replica controller
29 | > you must patch the metadata of the Pod template.
30 |
31 | * **NodeJS:**
32 |
33 | Pod:
34 | ```
35 | kubectl annotate pod/ instrumentation.opentelemetry.io/inject-nodejs="trace-sampling"
36 | ```
37 | Deployment:
38 | ```
39 | kubectl patch deployment.apps/ -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-nodejs": "trace-sampling"}}}}}'
40 | ```
41 |
42 | * **Java:**
43 |
44 | Pod:
45 | ```
46 | kubectl annotate pod/ instrumentation.opentelemetry.io/inject-java="trace-sampling"
47 | ```
48 | Deployment:
49 | ```
50 | kubectl patch deployment.apps/ -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-java": "trace-sampling"}}}}}'
51 | ```
52 |
53 | * **Python:**
54 |
55 | Pod:
56 | ```
57 | kubectl annotate pod/ instrumentation.opentelemetry.io/inject-python="trace-sampling"
58 | ```
59 | Deployment:
60 | ```
61 | kubectl patch deployment.apps/ -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-python": "trace-sampling"}}}}}'
62 | ```
63 |
64 | * **DotNET:**
65 |
66 | Pod:
67 | ```
68 | kubectl annotate pod/ instrumentation.opentelemetry.io/inject-python="trace-sampling"
69 | ```
70 | Deployment:
71 | ```
72 | kubectl patch deployment.apps/ -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-dotnet": "trace-sampling"}}}}}'
73 | ```
74 |
--------------------------------------------------------------------------------
/recipes/trace-sampling/instrumentation.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: opentelemetry.io/v1alpha1
16 | kind: Instrumentation
17 | metadata:
18 | name: trace-sampling
19 | spec:
20 | exporter:
21 | endpoint: http://otel-collector:4317
22 | propagators:
23 | - tracecontext
24 | - baggage
25 | - b3
26 | sampler:
27 | type: parentbased_traceidratio
28 | argument: "0.25"
29 |
--------------------------------------------------------------------------------
/sample-apps/README.md:
--------------------------------------------------------------------------------
1 | # Sample apps
2 |
3 | This directory holds sample apps in various languages for working with
4 | the operator and auto-instrumentation. See below to get started:
5 |
6 | * [NodeJS](nodejs)
7 | * [Java](java)
8 | * [Python](python)
9 | * DotNET
10 | * [Go](go)
11 | * [NodeJS + Java](nodejs-java)
12 |
13 | ## Setup
14 |
15 | Each sample app can be built as a container and deployed in a GKE cluster with a few
16 | commands. First, run `make setup` to create an Artifact Registry if you don't already
17 | have one:
18 |
19 | ```
20 | export REGISTRY_LOCATION=us-central1
21 | export CONTAINER_REGISTRY=otel-operator
22 | make setup
23 | ```
24 |
25 | The build and deploy commands for each app use the environment variables from above to run
26 | the container image from your registry, along with `GCLOUD_PROJECT`.
27 |
28 | Make sure to set the `GCLOUD_PROJECT` environment variable before running these:
29 |
30 | ```
31 | export GCLOUD_PROJECT=my-gke-project
32 | ```
33 |
34 | ## Troubleshooting
35 |
36 | See [troubleshooting.md](troubleshooting.md) if you encounter issues with any of these apps.
37 |
--------------------------------------------------------------------------------
/sample-apps/go/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | include ../../Makefile
16 |
17 | .PHONY: build
18 | build: sample-replace
19 | docker build -t ${REGISTRY_LOCATION}-docker.pkg.dev/${GCLOUD_PROJECT}/${CONTAINER_REGISTRY}/go-sample-app app
20 | docker build -t ${REGISTRY_LOCATION}-docker.pkg.dev/${GCLOUD_PROJECT}/${CONTAINER_REGISTRY}/go-sample-server server
21 |
22 | .PHONY: push
23 | push:
24 | docker push ${REGISTRY_LOCATION}-docker.pkg.dev/${GCLOUD_PROJECT}/${CONTAINER_REGISTRY}/go-sample-app:latest
25 | docker push ${REGISTRY_LOCATION}-docker.pkg.dev/${GCLOUD_PROJECT}/${CONTAINER_REGISTRY}/go-sample-server:latest
26 |
27 |
--------------------------------------------------------------------------------
/sample-apps/go/README.md:
--------------------------------------------------------------------------------
1 | # Go sample app
2 |
3 | This is a sample app consisting of a basic client and server written in Go. The
4 | server listens for requests which the client makes on a timed loop.
5 |
6 | ## Prerequisites
7 |
8 | * OpenTelemetry Operator installed in your cluster
9 | * Artifact Registry set up in your GCP project (see the
10 | [main README.md](../../README.md#sample-applications))
11 | * An `OpenTelemetryCollector` object already created in the current namespace,
12 | such as [the sample `collector-config.yaml`](../../README.md#starting-the-Collector)
13 | from the main [README](../../README.md)
14 | * An `Instrumentation` object already created in the current namespace,
15 | such as [the sample `instrumentation.yaml`](../../README.md#auto-instrumenting-applications)
16 | from the main [README](../../README.md)
17 |
18 | ## Running
19 |
20 | 1. Build the sample app:
21 | ```
22 | make build
23 | ```
24 | This command will also update the local [manifests](k8s)
25 | to refer to your image location.
26 |
27 | 2. Push the local image to the Artifact Registry you created
28 | in the setup steps (if you did not create one, or are using an already created registry,
29 | make sure to set the `REGISTRY_LOCATION` and `CONTAINER_REGISTRY` variables):
30 | ```
31 | make push
32 | ```
33 |
34 | 3. Deploy the app in your cluster:
35 | ```
36 | kubectl apply -f k8s/.
37 | ```
38 | If you want to run the sample app in a specific namespace, pass `-n `.
39 |
40 | 4. Run the following commands to patch the `app` and `server` deployments for auto-instrumentation:
41 | ```
42 | kubectl patch deployment.apps/goshowcase-app -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-go": "true", "instrumentation.opentelemetry.io/otel-go-auto-target-exe": "/app/main"}}}}}'
43 | kubectl patch deployment.apps/goshowcase-server -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-go": "true", "instrumentation.opentelemetry.io/otel-go-auto-target-exe": "/server/main"}}}}}'
44 | ```
45 | These commands will use the `Instrumentation` created as part of the Prerequisites.
46 |
47 | ## View your Spans
48 |
49 | To stream logs from the otel-collector, which will include spans from this sample application, run:
50 | ```
51 | kubectl logs deployment/otel-collector -f
52 | ```
53 |
54 | Alternatively, follow the [cloud-trace recipe](../../recipes/cloud-trace/) to view your spans in Google Cloud Trace.
55 |
--------------------------------------------------------------------------------
/sample-apps/go/app/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | FROM golang:1.19
16 | WORKDIR /app
17 | COPY . .
18 | RUN go build -o main
19 |
--------------------------------------------------------------------------------
/sample-apps/go/app/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/GoogleCloudPlatform/opentelemetry-operator-sample/sample-apps/go/app
2 |
3 | go 1.20
4 |
--------------------------------------------------------------------------------
/sample-apps/go/app/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "io"
19 | "log"
20 | "net/http"
21 | "time"
22 | )
23 |
24 | func main() {
25 | for {
26 | resp, err := http.Get("http://goshowcase-server/hello")
27 | if err != nil {
28 | log.Fatal(err)
29 | }
30 | body, err := io.ReadAll(resp.Body)
31 | if err != nil {
32 | log.Fatal(err)
33 | }
34 |
35 | log.Printf("Body: %s\n", string(body))
36 | err = resp.Body.Close()
37 | if err != nil {
38 | log.Fatal(err)
39 | }
40 | time.Sleep(5 * time.Second)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/sample-apps/go/k8s/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | name: goshowcase-app
19 | spec:
20 | selector:
21 | matchLabels:
22 | app: goshowcase-app
23 | template:
24 | metadata:
25 | name: goshowcase-app
26 | labels:
27 | app: goshowcase-app
28 | spec:
29 | containers:
30 | - name: goshowcase-app
31 | image: "%REGISTRY_LOCATION%-docker.pkg.dev/%GCLOUD_PROJECT%/%CONTAINER_REGISTRY%/go-sample-app:latest"
32 | command: ['/app/main']
33 | resources:
34 | requests:
35 | memory: "256Mi"
36 | cpu: "250m"
37 | limits:
38 | memory: "256Mi"
39 | cpu: "250m"
40 | restartPolicy: Always
41 |
--------------------------------------------------------------------------------
/sample-apps/go/k8s/service.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: v1
16 | kind: Service
17 | metadata:
18 | name: goshowcase-server
19 | spec:
20 | selector:
21 | app: goshowcase-server
22 | ports:
23 | - port: 80
24 | targetPort: 8080
25 | ---
26 | apiVersion: apps/v1
27 | kind: Deployment
28 | metadata:
29 | name: goshowcase-server
30 | spec:
31 | selector:
32 | matchLabels:
33 | app: goshowcase-server
34 | template:
35 | metadata:
36 | labels:
37 | app: goshowcase-server
38 | spec:
39 | containers:
40 | - name: goshowcase-server
41 | image: "%REGISTRY_LOCATION%-docker.pkg.dev/%GCLOUD_PROJECT%/%CONTAINER_REGISTRY%/go-sample-server:latest"
42 | command: ['/server/main']
43 | ports:
44 | - containerPort: 8080
45 | resources:
46 | requests:
47 | memory: "256Mi"
48 | cpu: "250m"
49 | limits:
50 | memory: "256Mi"
51 | cpu: "250m"
52 |
--------------------------------------------------------------------------------
/sample-apps/go/server/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | FROM golang:1.19
16 | WORKDIR /server
17 | COPY . .
18 | RUN go build -o main
19 |
--------------------------------------------------------------------------------
/sample-apps/go/server/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/GoogleCloudPlatform/opentelemetry-operator-sample/sample-apps/go/server
2 |
3 | go 1.20
4 |
--------------------------------------------------------------------------------
/sample-apps/go/server/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "fmt"
19 | "io"
20 | "log"
21 | "net/http"
22 | "os"
23 | )
24 |
25 | func remoteHello(nextServer string) string {
26 | resp, err := http.Get(nextServer)
27 | if err != nil {
28 | log.Fatal(err)
29 | }
30 | body, err := io.ReadAll(resp.Body)
31 | if err != nil {
32 | log.Fatal(err)
33 | }
34 | err = resp.Body.Close()
35 | if err != nil {
36 | log.Fatal(err)
37 | }
38 | return string(body)
39 | }
40 |
41 | func hello(w http.ResponseWriter, _ *http.Request) {
42 | url, exists := os.LookupEnv("NEXT_SERVER")
43 | if !exists {
44 | fmt.Fprintf(w, "hello\n")
45 | } else {
46 | fmt.Fprintf(w, "%s\n", remoteHello(url))
47 | }
48 | }
49 |
50 | func main() {
51 | http.HandleFunc("/hello", hello)
52 | err := http.ListenAndServe(":8080", nil)
53 | if err != nil {
54 | log.Fatal(err)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/sample-apps/java/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle/
2 | build
3 | .idea/
4 |
--------------------------------------------------------------------------------
/sample-apps/java/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | include ../../Makefile
16 |
17 | .PHONY: build
18 | build: sample-replace
19 | ./gradlew :service:jib --image=${REGISTRY_LOCATION}-docker.pkg.dev/${GCLOUD_PROJECT}/${CONTAINER_REGISTRY}/java-sample-service
20 | ./gradlew :app:jib --image=${REGISTRY_LOCATION}-docker.pkg.dev/${GCLOUD_PROJECT}/${CONTAINER_REGISTRY}/java-sample-app
21 |
--------------------------------------------------------------------------------
/sample-apps/java/README.md:
--------------------------------------------------------------------------------
1 | # Java sample app
2 |
3 | This sample app runs a server "service" that listens for requests from a client "app"
4 | running as a CronJob.
5 |
6 | ## Prerequisites
7 |
8 | * OpenTelemetry Operator installed in your cluster
9 | * Artifact Registry set up in your GCP project (see the
10 | [main README.md](../../README.md#sample-applications))
11 | * An `OpenTelemetryCollector` object already created in the current namespace,
12 | such as [the sample `collector-config.yaml`](../../README.md#starting-the-Collector)
13 | from the main [README](../../README.md)
14 | * An `Instrumentation` object already created in the current namespace,
15 | such as [the sample `instrumentation.yaml`](../../README.md#auto-instrumenting-applications)
16 | from the main [README](../../README.md)
17 |
18 | ## Running
19 |
20 | 1. Build the sample app and service:
21 | ```
22 | make build
23 | ```
24 | This command will also push the app's images to the Artifact Registry you set
25 | up in the Prerequisites. It also updates the local [manifests](k8s) to refer to
26 | your image registry.
27 |
28 | 2. Deploy the apps in your cluster:
29 | ```
30 | kubectl apply -f k8s/.
31 | ```
32 | If you want to run the sample app in a specific namespace, pass `-n `.
33 |
34 | 3. Run the following commands to patch the app CronJob and service Deployment for auto-instrumentation:
35 | ```
36 | kubectl patch deployment.apps/javashowcase-service -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-java": "true"}}}}}'
37 | kubectl patch cronjob.batch/javashowcase-app -p '{"spec":{"jobTemplate":{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-java": "true"}}}}}}}'
38 | ```
39 | These commands will use the `Instrumentation` created as part of the Prerequisites.
40 |
41 | ## View your Spans
42 |
43 | To stream logs from the otel-collector, which will include spans from this sample application, run:
44 | ```
45 | kubectl logs deployment/otel-collector -f
46 | ```
47 |
48 | Alternatively, follow the [cloud-trace recipe](../../recipes/cloud-trace/) to view your spans in Google Cloud Trace.
49 |
--------------------------------------------------------------------------------
/sample-apps/java/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * This file was generated by the Gradle 'init' task.
3 | */
4 |
5 | plugins {
6 | id("com.google.example.java-application-conventions")
7 | }
8 |
9 | dependencies {
10 | implementation(project(":utilities"))
11 | implementation("ch.qos.logback:logback-classic")
12 | implementation("org.slf4j:slf4j-api")
13 | implementation("org.slf4j:jul-to-slf4j")
14 | }
15 |
16 | val mainClassName = "com.google.example.app.App"
17 |
18 | application {
19 | // Define the main class for the application.
20 | mainClass.set(mainClassName)
21 | }
22 |
23 | /**
24 | * Create a Fat Jar with all dependencies for easier execution
25 | */
26 | val fatJar = task("fatJar", type = Jar::class) {
27 | dependsOn("compileJava")
28 | archiveClassifier.set("standalone")
29 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE
30 | manifest {
31 | attributes(Pair("Main-Class", mainClassName))
32 | }
33 | val sourcesMain = sourceSets.main.get()
34 | val contents = configurations.runtimeClasspath.get()
35 | .map { if (it.isDirectory) it else zipTree(it) } + sourcesMain.output
36 | from(contents)
37 | dependsOn(":utilities:jar", ":app:processResources")
38 | }
39 |
40 | tasks.build {
41 | dependsOn(fatJar)
42 | }
43 |
--------------------------------------------------------------------------------
/sample-apps/java/app/src/main/java/com/google/example/app/App.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Java source file was generated by the Gradle 'init' task.
3 | */
4 | package com.google.example.app;
5 |
6 | import java.util.logging.Logger;
7 | import java.net.http.HttpClient;
8 | import java.net.http.HttpResponse;
9 | import java.net.http.HttpResponse.BodyHandlers;
10 |
11 | public class App {
12 | private static final Logger log = Logger.getLogger(App.class.getName());
13 |
14 | public static void main(String[] args) throws java.io.IOException, InterruptedException {
15 | com.google.example.utilities.Logging.initializeLogging();
16 | log.info("Performing batch job work.");
17 |
18 | String service = System.getenv("SERVICE_NAME");
19 |
20 | HttpClient httpClient = HttpClient.newHttpClient();
21 | HttpResponse response = httpClient.send(java.net.http.HttpRequest.newBuilder()
22 | .uri(java.net.URI.create(service))
23 | .timeout(java.time.Duration.ofMinutes(2))
24 | .GET()
25 | .build(), BodyHandlers.ofString());
26 | log.info("Processing complete, status: " + response.statusCode());
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/sample-apps/java/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | // Support convention plugins written in Kotlin. Convention plugins are build scripts in 'src/main' that automatically become available as plugins in the main build.
3 | `kotlin-dsl`
4 | }
5 | repositories {
6 | mavenCentral()
7 | gradlePluginPortal()
8 | }
9 |
10 | dependencies {
11 | implementation("gradle.plugin.com.google.cloud.tools:jib-gradle-plugin:3.1.4")
12 | // version 3.x of the spring boot plugin requires a minimum Java 17 version
13 | implementation("org.springframework.boot:spring-boot-gradle-plugin:2.7.14")
14 | }
15 |
--------------------------------------------------------------------------------
/sample-apps/java/buildSrc/src/main/kotlin/com.google.example.java-application-conventions.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | // Apply the common convention plugin for shared build configuration between library and application projects.
3 | id("com.google.example.java-common-conventions")
4 |
5 | // Apply the application plugin to add support for building a CLI application in Java.
6 | application
7 | id("com.google.cloud.tools.jib")
8 | }
9 |
10 | jib {
11 | containerizingMode = "packaged"
12 | from.image = "gcr.io/distroless/java-debian10:11"
13 | }
14 |
--------------------------------------------------------------------------------
/sample-apps/java/buildSrc/src/main/kotlin/com.google.example.java-common-conventions.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | // Apply the java Plugin to add support for Java.
3 | java
4 | }
5 |
6 | repositories {
7 | // Use Maven Central for resolving dependencies.
8 | mavenCentral()
9 | }
10 |
11 | dependencies {
12 | constraints {
13 | // Define dependency versions as constraints
14 | implementation("com.google.cloud:google-cloud-core:2.0.5")
15 | implementation("io.opentelemetry:opentelemetry-api:1.9.0")
16 | implementation("ch.qos.logback:logback-core:1.2.6")
17 | implementation("ch.qos.logback:logback-classic:1.2.2")
18 | // Allow j.u.l to pass through SLF4J into logback.
19 | implementation("org.slf4j:slf4j-api:1.7.32")
20 | implementation("org.slf4j:jul-to-slf4j:1.7.32")
21 | }
22 |
23 | // Use JUnit Jupiter for testing.
24 | testImplementation("org.junit.jupiter:junit-jupiter:5.7.2")
25 | }
26 |
27 | tasks.test {
28 | // Use JUnit Platform for unit tests.
29 | useJUnitPlatform()
30 | }
31 |
32 | java {
33 | sourceCompatibility = JavaVersion.VERSION_11
34 | targetCompatibility = JavaVersion.VERSION_11
35 | }
36 |
--------------------------------------------------------------------------------
/sample-apps/java/buildSrc/src/main/kotlin/com.google.example.java-inst-application-conventions.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.gradle.api.tasks.Copy
2 | import org.gradle.kotlin.dsl.*
3 |
4 | plugins {
5 | // Apply the common convention plugin for shared build configuration between library and application projects.
6 | id("com.google.example.java-application-conventions")
7 |
8 | // Apply the application plugin to add support for building a CLI application in Java.
9 | application
10 | id("com.google.cloud.tools.jib")
11 | }
12 |
13 | val agent by configurations.creating
14 | val agentOutputDir = layout.buildDirectory.dir("otelagent").forUseAtConfigurationTime().get()
15 |
16 | tasks.register("copyAgent") {
17 | from (agent) {
18 | rename("exporter-auto(.*).jar", "gcp_ext.jar")
19 | rename("opentelemetry-javaagent(.*).jar", "otel_agent.jar")
20 | }
21 | into(agentOutputDir)
22 | }
23 |
24 | // TODO: Figure out how to share this across all three tasks.
25 | tasks.named("jib") {
26 | dependsOn("copyAgent")
27 | }
28 | tasks.named("jibDockerBuild") {
29 | dependsOn("copyAgent")
30 | }
31 | tasks.named("jibBuildTar") {
32 | dependsOn("copyAgent")
33 | }
34 |
35 |
36 | jib {
37 | container.jvmFlags = mutableListOf(
38 | // Use the downloaded java agent.
39 | "-javaagent:/otelagent/otel_agent.jar",
40 | // Export every 5 minutes
41 | "-Dotel.metric.export.interval=5m",
42 | // Use the GCP exporter extensions.
43 | "-Dotel.javaagent.extensions=/otelagent/gcp_ext.jar",
44 | // Configure auto instrumentation.
45 | "-Dotel.traces.exporter=google_cloud_trace",
46 | "-Dotel.metrics.exporter=google_cloud_monitoring")
47 | extraDirectories {
48 | paths {
49 | path {
50 | into = "/otelagent"
51 | setFrom(agentOutputDir.asFile.toPath())
52 | }
53 | }
54 | }
55 | }
56 |
57 | dependencies {
58 | agent("io.opentelemetry.javaagent:opentelemetry-javaagent:1.13.1")
59 | agent("com.google.cloud.opentelemetry:exporter-auto:0.21.0-alpha")
60 | }
61 |
--------------------------------------------------------------------------------
/sample-apps/java/buildSrc/src/main/kotlin/com.google.example.java-library-conventions.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * This file was generated by the Gradle 'init' task.
3 | */
4 |
5 | plugins {
6 | // Apply the common convention plugin for shared build configuration between library and application projects.
7 | id("com.google.example.java-common-conventions")
8 |
9 | // Apply the java-library plugin for API and implementation separation.
10 | `java-library`
11 | }
12 |
--------------------------------------------------------------------------------
/sample-apps/java/buildSrc/src/main/kotlin/com.google.example.java-spring-application-conventions.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | // Apply the common convention plugin for shared build configuration between library and application projects.
3 | id("com.google.example.java-common-conventions")
4 |
5 | // Apply the spring boot application plugin
6 | id("org.springframework.boot")
7 | id("io.spring.dependency-management")
8 | }
9 |
10 | dependencies {
11 | constraints {
12 | implementation("org.springframework.boot:spring-boot-starter-web:2.4.5")
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/sample-apps/java/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleCloudPlatform/opentelemetry-operator-sample/fc5937dcfa25c419aa6182093048a240abb98bde/sample-apps/java/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/sample-apps/java/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
4 | networkTimeout=10000
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/sample-apps/java/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/sample-apps/java/k8s/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: batch/v1
16 | kind: CronJob
17 | metadata:
18 | name: javashowcase-app
19 | spec:
20 | schedule: "*/2 * * * *"
21 | jobTemplate:
22 | spec:
23 | template:
24 | metadata:
25 | name: javashowcase-app
26 | spec:
27 | containers:
28 | - name: javashowcase-app
29 | image: "%REGISTRY_LOCATION%-docker.pkg.dev/%GCLOUD_PROJECT%/%CONTAINER_REGISTRY%/java-sample-app:latest"
30 | env:
31 | - name: "SERVICE_NAME"
32 | value: "http://javashowcase-service/"
33 | resources:
34 | requests:
35 | memory: "256Mi"
36 | cpu: "250m"
37 | limits:
38 | memory: "256Mi"
39 | cpu: "250m"
40 | # Do not restart containers after they exit
41 | restartPolicy: Never
42 |
--------------------------------------------------------------------------------
/sample-apps/java/k8s/service.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: v1
16 | kind: Service
17 | metadata:
18 | name: javashowcase-service
19 | spec:
20 | selector:
21 | app: javashowcase-service
22 | ports:
23 | - port: 80
24 | targetPort: 8080
25 | ---
26 | apiVersion: apps/v1
27 | kind: Deployment
28 | metadata:
29 | name: javashowcase-service
30 | spec:
31 | selector:
32 | matchLabels:
33 | app: javashowcase-service
34 | template:
35 | metadata:
36 | labels:
37 | app: javashowcase-service
38 | spec:
39 | containers:
40 | - name: javashowcase-service
41 | image: "%REGISTRY_LOCATION%-docker.pkg.dev/%GCLOUD_PROJECT%/%CONTAINER_REGISTRY%/java-sample-service:latest"
42 | ports:
43 | - containerPort: 8080
44 | resources:
45 | requests:
46 | memory: "256Mi"
47 | cpu: "250m"
48 | limits:
49 | memory: "256Mi"
50 | cpu: "250m"
51 |
--------------------------------------------------------------------------------
/sample-apps/java/service/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * This file was generated by the Gradle 'init' task.
3 | */
4 |
5 | plugins {
6 | id("com.google.example.java-application-conventions")
7 | id("com.google.example.java-spring-application-conventions")
8 | }
9 |
10 | dependencies {
11 | implementation(project(":utilities"))
12 | implementation("org.springframework.boot:spring-boot-starter-web")
13 | }
14 |
15 | application {
16 | // Define the main class for the application.
17 | mainClass.set("com.google.example.service.Main")
18 | }
19 |
20 | jib {
21 | container.ports = listOf("8080")
22 | }
23 |
--------------------------------------------------------------------------------
/sample-apps/java/service/src/main/java/com/google/example/service/Main.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.google.example.service;
17 |
18 | import org.springframework.boot.SpringApplication;
19 | import org.springframework.boot.autoconfigure.SpringBootApplication;
20 | import org.springframework.web.bind.annotation.GetMapping;
21 | import org.springframework.web.bind.annotation.RestController;
22 |
23 | import java.io.IOException;
24 | import java.net.http.HttpClient;
25 | import java.net.http.HttpResponse;
26 | import java.net.http.HttpResponse.BodyHandlers;
27 | import java.util.logging.Logger;
28 | import java.util.logging.Level;
29 |
30 | @RestController
31 | @SpringBootApplication
32 | public class Main {
33 | private static Logger logger = Logger.getLogger("Main");
34 |
35 | public static void main(String[] args) throws IOException {
36 | com.google.example.utilities.Logging.initializeLogging();
37 | SpringApplication.run(Main.class, args);
38 | }
39 |
40 | @GetMapping("/")
41 | public String home() {
42 | String service = System.getenv("NEXT_SERVER");
43 | if (service != null) {
44 | return remoteHelloWorld(service);
45 | }
46 | return helloWorld();
47 | }
48 |
49 | private String remoteHelloWorld(String nextServer) {
50 | try {
51 | HttpClient httpClient = HttpClient.newHttpClient();
52 | HttpResponse response =
53 | httpClient.send(java.net.http.HttpRequest.newBuilder()
54 | .uri(java.net.URI.create(nextServer))
55 | .timeout(java.time.Duration.ofMinutes(2))
56 | .GET()
57 | .build(), BodyHandlers.ofString());
58 | return response.body();
59 | } catch (Exception e) {
60 | logger.log(Level.SEVERE, "failed to connect to next server", e);
61 | return "{\"response\":\"Failure\"}";
62 | }
63 | }
64 |
65 | private String helloWorld() {
66 | return "{\"response\":\"Hello World\"}";
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/sample-apps/java/service/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | logging.level.org.springframework.web.servlet.DispatcherServlet=DEBUG
2 | spring.application.name=gcp-javashowcase-service
3 | spring.main.banner-mode=off
4 |
--------------------------------------------------------------------------------
/sample-apps/java/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "java-showcase"
2 | include("app", "service", "utilities")
3 |
--------------------------------------------------------------------------------
/sample-apps/java/utilities/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.google.example.java-library-conventions")
3 | }
4 |
5 | dependencies {
6 | implementation("com.google.cloud:google-cloud-core")
7 | implementation("io.opentelemetry:opentelemetry-api")
8 | implementation("ch.qos.logback:logback-core")
9 | implementation("ch.qos.logback:logback-classic")
10 | implementation("org.slf4j:slf4j-api")
11 | implementation("org.slf4j:jul-to-slf4j")
12 | }
13 |
--------------------------------------------------------------------------------
/sample-apps/java/utilities/src/main/java/com/google/example/utilities/AttachTraceLogFilter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.google.example.utilities;
17 |
18 | import ch.qos.logback.classic.spi.ILoggingEvent;
19 | import ch.qos.logback.core.filter.Filter;
20 | import com.google.cloud.ServiceOptions;
21 | import io.opentelemetry.api.trace.Span;
22 | import io.opentelemetry.api.trace.SpanContext;
23 | import io.opentelemetry.context.Context;
24 |
25 | /** This is a "filter" for logback that simple appends opencensus trace context to the MDC. */
26 | public class AttachTraceLogFilter extends Filter {
27 | private static final String TRACE_ID = "gcp.trace_id";
28 | private static final String SPAN_ID = "gcp.span_id";
29 | private static final String SAMPLED = "gcp.trace_sampled";
30 |
31 | private final String projectId;
32 | private final String tracePrefix;
33 |
34 | public AttachTraceLogFilter() {
35 | this.projectId = lookUpProjectId();
36 | this.tracePrefix = "projects/" + (projectId == null ? "" : projectId) + "/traces/";
37 | }
38 |
39 | @Override
40 | public ch.qos.logback.core.spi.FilterReply decide(
41 | ch.qos.logback.classic.spi.ILoggingEvent event) {
42 | SpanContext context = Span.fromContext(Context.current()).getSpanContext();
43 | if (context.isValid()) {
44 | org.slf4j.MDC.put(TRACE_ID, tracePrefix + context.getTraceId());
45 | org.slf4j.MDC.put(SPAN_ID, context.getSpanId());
46 | org.slf4j.MDC.put(SAMPLED, Boolean.toString(context.isSampled()));
47 | }
48 | return ch.qos.logback.core.spi.FilterReply.ACCEPT;
49 | }
50 |
51 | private static String lookUpProjectId() {
52 | return ServiceOptions.getDefaultProjectId();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/sample-apps/java/utilities/src/main/java/com/google/example/utilities/Logging.java:
--------------------------------------------------------------------------------
1 | package com.google.example.utilities;
2 |
3 | /**
4 | * Utilities for configuring logging.
5 | */
6 | public final class Logging {
7 | private Logging() {}
8 |
9 | /** Initialize the java.util.logging -> slf4j redirect so logback gets baked-in logs. */
10 | public static void initializeLogging() {
11 | org.slf4j.bridge.SLF4JBridgeHandler.removeHandlersForRootLogger();
12 | org.slf4j.bridge.SLF4JBridgeHandler.install();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/sample-apps/java/utilities/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {"severity":"%-5p","message":"%logger:%L %m %ex","sourceLocation":"%logger:%L","request_id":"%X{request_id}","logging.googleapis.com/trace": "%X{gcp.trace_id}","logging.googleapis.com/spanId":"%X{gcp.span_id}","logging.googleapis.com/trace_sampled":"%X{gcp.trace_sampled}"}%n
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/sample-apps/nodejs-java/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | include ../../Makefile
16 |
--------------------------------------------------------------------------------
/sample-apps/nodejs-java/README.md:
--------------------------------------------------------------------------------
1 | # NodeJS+Java sample app
2 |
3 | The purpose of this sample app is to demonstrate basic use of the operator across multi-lingual
4 | microservice applications. It combines the [NodeJS sample app](../nodejs) with the
5 | [Java sample service](../java), where the NodeJS app periodically calls to the Java service.
6 |
7 | ## Prerequisites
8 |
9 | * OpenTelemetry Operator installed in your cluster
10 | * Artifact Registry set up in your GCP project (see the
11 | [main README.md](../../README.md#sample-applications))
12 | * An `OpenTelemetryCollector` object already created in the current namespace,
13 | such as [the sample `collector-config.yaml`](../../README.md#starting-the-Collector)
14 | from the main [README](../../README.md)
15 | * An `Instrumentation` object already created in the current namespace,
16 | such as [the sample `instrumentation.yaml`](../../README.md#auto-instrumenting-applications)
17 | from the main [README](../../README.md)
18 |
19 | ## Running
20 |
21 | 1. Build and push the [Java sample app](../java):
22 | ```
23 | pushd ../java
24 | make build
25 | popd
26 | ```
27 |
28 | 2. Build and push the [NodeJS sample app](../nodejs):
29 | ```
30 | pushd ../nodejs
31 | make build
32 | make push
33 | popd
34 | ```
35 |
36 | 3. Update the [manifests](k8s) in this directory to point to your newly-pushed images:
37 | ```
38 | make sample-replace
39 | ```
40 |
41 | 4. Deploy the apps in your cluster:
42 | ```
43 | kubectl apply -f k8s/.
44 | ```
45 |
46 | 5. Run the following commands to patch the app CronJob and service Deployment for auto-instrumentation:
47 | ```
48 | kubectl patch deployment.apps/nodeshowcase-app -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-nodejs": "true"}}}}}'
49 | kubectl patch deployment.apps/javashowcase-service -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-java": "true"}}}}}'
50 | ```
51 | These commands will use the `Instrumentation` created as part of the Prerequisites.
52 |
53 | ## View your Spans
54 |
55 | To stream logs from the otel-collector, which will include spans from this sample application, run:
56 | ```
57 | kubectl logs deployment/otel-collector -f
58 | ```
59 |
60 | Alternatively, follow the [cloud-trace recipe](../../recipes/cloud-trace/) to view your spans in Google Cloud Trace.
61 |
--------------------------------------------------------------------------------
/sample-apps/nodejs-java/k8s/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | # Unique key of the Job instance
19 | name: nodeshowcase-app
20 | spec:
21 | selector:
22 | matchLabels:
23 | app: nodeshowcase-app
24 | template:
25 | metadata:
26 | name: nodeshowcase-app
27 | labels:
28 | app: nodeshowcase-app
29 | spec:
30 | containers:
31 | - name: nodeshowcase-app
32 | image: "%REGISTRY_LOCATION%-docker.pkg.dev/%GCLOUD_PROJECT%/%CONTAINER_REGISTRY%/nodejs-sample:latest"
33 | command: ['node', 'app.js']
34 | env:
35 | - name: "SERVICE_NAME"
36 | value: "http://javashowcase-service/"
37 | resources:
38 | requests:
39 | memory: "256Mi"
40 | cpu: "250m"
41 | limits:
42 | memory: "256Mi"
43 | cpu: "250m"
44 | restartPolicy: Always
45 |
--------------------------------------------------------------------------------
/sample-apps/nodejs-java/k8s/service.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: v1
16 | kind: Service
17 | metadata:
18 | name: javashowcase-service
19 | spec:
20 | selector:
21 | app: javashowcase-service
22 | ports:
23 | - port: 80
24 | targetPort: 8080
25 | ---
26 | apiVersion: apps/v1
27 | kind: Deployment
28 | metadata:
29 | name: javashowcase-service
30 | spec:
31 | selector:
32 | matchLabels:
33 | app: javashowcase-service
34 | template:
35 | metadata:
36 | labels:
37 | app: javashowcase-service
38 | spec:
39 | containers:
40 | - name: javashowcase-service
41 | image: "%REGISTRY_LOCATION%-docker.pkg.dev/%GCLOUD_PROJECT%/%CONTAINER_REGISTRY%/java-sample-service:latest"
42 | ports:
43 | - containerPort: 8080
44 | resources:
45 | requests:
46 | memory: "256Mi"
47 | cpu: "250m"
48 | limits:
49 | memory: "256Mi"
50 | cpu: "250m"
51 |
--------------------------------------------------------------------------------
/sample-apps/nodejs/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | FROM node:latest
16 |
17 | WORKDIR /usr/src/app
18 |
19 | COPY server.js .
20 | COPY app.js .
21 |
22 | EXPOSE 80
23 |
24 | CMD [ "node", "server.js" ]
25 |
--------------------------------------------------------------------------------
/sample-apps/nodejs/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | include ../../Makefile
16 |
17 | .PHONY: build
18 | build: sample-replace
19 | docker build -t ${REGISTRY_LOCATION}-docker.pkg.dev/${GCLOUD_PROJECT}/${CONTAINER_REGISTRY}/nodejs-sample .
20 |
21 | .PHONY: push
22 | push:
23 | docker push ${REGISTRY_LOCATION}-docker.pkg.dev/${GCLOUD_PROJECT}/${CONTAINER_REGISTRY}/nodejs-sample:latest
24 |
25 |
--------------------------------------------------------------------------------
/sample-apps/nodejs/README.md:
--------------------------------------------------------------------------------
1 | # NodeJS sample app
2 |
3 | This is a sample app consisting of a basic client and server written in NodeJS. The
4 | server listens for requests which the client makes on a timed loop.
5 |
6 | ## Prerequisites
7 |
8 | * OpenTelemetry Operator installed in your cluster
9 | * Artifact Registry set up in your GCP project (see the
10 | [main README.md](../../README.md#sample-applications))
11 | * An `OpenTelemetryCollector` object already created in the current namespace,
12 | such as [the sample `collector-config.yaml`](../../README.md#starting-the-Collector)
13 | from the main [README](../../README.md)
14 | * An `Instrumentation` object already created in the current namespace,
15 | such as [the sample `instrumentation.yaml`](../../README.md#auto-instrumenting-applications)
16 | from the main [README](../../README.md)
17 |
18 | ## Running
19 |
20 | 1. Build the sample app:
21 | ```
22 | make build
23 | ```
24 | This command will also update the local [manifests](k8s)
25 | to refer to your image location.
26 |
27 | 2. Push the local image to the Artifact Registry you created
28 | in the setup steps (if you did not create one, or are using an already created registry,
29 | make sure to set the `REGISTRY_LOCATION` and `CONTAINER_REGISTRY` variables):
30 | ```
31 | make push
32 | ```
33 |
34 | 3. Deploy the app in your cluster:
35 | ```
36 | kubectl apply -f k8s/.
37 | ```
38 | If you want to run the sample app in a specific namespace, pass `-n `.
39 |
40 | 4. Run the following commands to patch the `app` and `server` deployments for auto-instrumentation:
41 | ```
42 | kubectl patch deployment.apps/nodeshowcase-app -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-nodejs": "true"}}}}}'
43 | kubectl patch deployment.apps/nodeshowcase-service -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-nodejs": "true"}}}}}'
44 | ```
45 | These commands will use the `Instrumentation` created as part of the Prerequisites.
46 |
47 | ## View your Spans
48 |
49 | To stream logs from the otel-collector, which will include spans from this sample application, run:
50 | ```
51 | kubectl logs deployment/otel-collector -f
52 | ```
53 |
54 | Alternatively, follow the [cloud-trace recipe](../../recipes/cloud-trace/) to view your spans in Google Cloud Trace.
55 |
--------------------------------------------------------------------------------
/sample-apps/nodejs/app.js:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | const http = require('http');
16 |
17 | var serviceURL = process.env.SERVICE_NAME
18 |
19 | console.log("starting app for service at "+serviceURL)
20 |
21 | setInterval(function() {
22 | http.get(serviceURL, resp => {
23 | let data = ''
24 | resp.on('data', d => {
25 | data += d
26 | })
27 | resp.on('end', () => {
28 | console.log(JSON.parse(data))
29 | })
30 | }).on('error', err => {
31 | console.log(err)
32 | });
33 | }, 1000)
34 |
--------------------------------------------------------------------------------
/sample-apps/nodejs/k8s/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | # Unique key of the Job instance
19 | name: nodeshowcase-app
20 | spec:
21 | selector:
22 | matchLabels:
23 | app: nodeshowcase-app
24 | template:
25 | metadata:
26 | name: nodeshowcase-app
27 | labels:
28 | app: nodeshowcase-app
29 | spec:
30 | containers:
31 | - name: nodeshowcase-app
32 | image: "%REGISTRY_LOCATION%-docker.pkg.dev/%GCLOUD_PROJECT%/%CONTAINER_REGISTRY%/nodejs-sample:latest"
33 | command: ['node', 'app.js']
34 | env:
35 | - name: "SERVICE_NAME"
36 | value: "http://nodeshowcase-service/"
37 | resources:
38 | requests:
39 | memory: "256Mi"
40 | cpu: "250m"
41 | limits:
42 | memory: "256Mi"
43 | cpu: "250m"
44 | restartPolicy: Always
45 |
--------------------------------------------------------------------------------
/sample-apps/nodejs/k8s/service.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: v1
16 | kind: Service
17 | metadata:
18 | name: nodeshowcase-service
19 | spec:
20 | selector:
21 | app: nodeshowcase-service
22 | ports:
23 | - port: 80
24 | targetPort: 8080
25 | ---
26 | apiVersion: apps/v1
27 | kind: Deployment
28 | metadata:
29 | name: nodeshowcase-service
30 | spec:
31 | selector:
32 | matchLabels:
33 | app: nodeshowcase-service
34 | template:
35 | metadata:
36 | labels:
37 | app: nodeshowcase-service
38 | spec:
39 | containers:
40 | - name: nodeshowcase-service
41 | image: "%REGISTRY_LOCATION%-docker.pkg.dev/%GCLOUD_PROJECT%/%CONTAINER_REGISTRY%/nodejs-sample:latest"
42 | ports:
43 | - containerPort: 8080
44 | resources:
45 | requests:
46 | memory: "256Mi"
47 | cpu: "250m"
48 | limits:
49 | memory: "256Mi"
50 | cpu: "250m"
51 |
--------------------------------------------------------------------------------
/sample-apps/nodejs/server.js:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | var http = require("http");
16 |
17 | console.log("serving on port 8080...")
18 |
19 | var serviceURL = process.env.NEXT_SERVER
20 |
21 | var listener = function (req, res) {
22 |
23 | if (serviceURL) {
24 | http.get(serviceURL, resp => {
25 | let data = ''
26 | resp.on('data', d => { data += d });
27 | resp.on('end', () => {
28 | res.writeHead(200);
29 | res.end(data);
30 | });
31 | });
32 | } else {
33 | res.writeHead(200);
34 | res.end(JSON.stringify({data: "Hello world"}));
35 | }
36 | };
37 | var server = http.createServer(listener);
38 |
39 | server.listen(8080);
40 |
--------------------------------------------------------------------------------
/sample-apps/python/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/#use-with-ide
110 | .pdm.toml
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Cython debug symbols
153 | cython_debug/
154 |
155 | # PyCharm
156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 | # and can be added to the global gitignore or merged into this file. For a more nuclear
159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 | #.idea/
161 |
--------------------------------------------------------------------------------
/sample-apps/python/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | FROM python:3.10-alpine
16 |
17 | WORKDIR /usr/src/app
18 | COPY server.py app.py requirements.txt ./
19 | RUN pip install -r requirements.txt
20 |
21 | CMD [ "gunicorn", "server:app", "-b", "0.0.0.0:8080", "--access-logfile", "-" ]
22 |
--------------------------------------------------------------------------------
/sample-apps/python/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | include ../../Makefile
16 |
17 | .PHONY: build
18 | build: sample-replace
19 | docker build -t ${REGISTRY_LOCATION}-docker.pkg.dev/${GCLOUD_PROJECT}/${CONTAINER_REGISTRY}/python-sample .
20 |
21 | .PHONY: push
22 | push:
23 | docker push ${REGISTRY_LOCATION}-docker.pkg.dev/${GCLOUD_PROJECT}/${CONTAINER_REGISTRY}/python-sample:latest
24 |
25 |
--------------------------------------------------------------------------------
/sample-apps/python/README.md:
--------------------------------------------------------------------------------
1 | # Python sample app
2 |
3 | This is a sample app consisting of a basic server written in Python. The
4 | server listens for requests which the client makes on a timed loop.
5 |
6 | ## Prerequisites
7 |
8 | * OpenTelemetry Operator installed in your cluster
9 | * Artifact Registry set up in your GCP project (see the
10 | [main README.md](../../README.md#sample-applications))
11 | * An `OpenTelemetryCollector` object already created in the current namespace,
12 | such as [the sample `collector-config.yaml`](../../README.md#starting-the-Collector)
13 | from the main [README](../../README.md)
14 | * An `Instrumentation` object already created in the current namespace,
15 | such as [the sample `instrumentation.yaml`](../../README.md#auto-instrumenting-applications)
16 | from the main [README](../../README.md)
17 |
18 | ## Running
19 |
20 | 1. Build the sample app:
21 | ```
22 | make build
23 | ```
24 | This command will also update the local [manifests](k8s)
25 | to refer to your image location.
26 |
27 | 2. Push the local image to the Artifact Registry you created
28 | in the setup steps (if you did not create one, or are using an already created registry,
29 | make sure to set the `REGISTRY_LOCATION` and `CONTAINER_REGISTRY` variables):
30 | ```
31 | make push
32 | ```
33 |
34 | 3. Deploy the app in your cluster:
35 | ```
36 | kubectl apply -f k8s/.
37 | ```
38 | If you want to run the sample app in a specific namespace, pass `-n `.
39 |
40 | 4. Run the following commands to patch the `app` and `server` deployments for auto-instrumentation:
41 | ```
42 | kubectl patch deployment.apps/pythonshowcase-app -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-python": "true"}}}}}'
43 | kubectl patch deployment.apps/pythonshowcase-service -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-python": "true"}}}}}'
44 | ```
45 | These commands will use the `Instrumentation` created as part of the Prerequisites.
46 |
47 | ## View your Spans
48 |
49 | To stream logs from the otel-collector, which will include spans from this sample application, run:
50 | ```
51 | kubectl logs deployment/otel-collector -f
52 | ```
53 |
54 | Alternatively, follow the [cloud-trace recipe](../../recipes/cloud-trace/) to view your spans in Google Cloud Trace.
55 |
--------------------------------------------------------------------------------
/sample-apps/python/app.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | import time
17 | import requests
18 |
19 | service_url = os.environ['SERVICE_NAME']
20 | print(f"starting app for service at {service_url}")
21 |
22 |
23 | while True:
24 | time.sleep(1)
25 | try:
26 | json = requests.get(service_url, timeout=10).json()
27 | print(json)
28 | except requests.RequestException as e:
29 | print(e)
30 |
--------------------------------------------------------------------------------
/sample-apps/python/k8s/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: apps/v1
16 | kind: Deployment
17 | metadata:
18 | # Unique key of the Job instance
19 | name: pythonshowcase-app
20 | spec:
21 | selector:
22 | matchLabels:
23 | app: pythonshowcase-app
24 | template:
25 | metadata:
26 | name: pythonshowcase-app
27 | labels:
28 | app: pythonshowcase-app
29 | spec:
30 | containers:
31 | - name: pythonshowcase-app
32 | image: "%REGISTRY_LOCATION%-docker.pkg.dev/%GCLOUD_PROJECT%/%CONTAINER_REGISTRY%/python-sample:latest"
33 | command: ['python', 'app.py']
34 | env:
35 | - name: "SERVICE_NAME"
36 | value: "http://pythonshowcase-service/"
37 | - name: PYTHONUNBUFFERED
38 | value: "1"
39 | resources:
40 | requests:
41 | memory: "256Mi"
42 | cpu: "250m"
43 | limits:
44 | memory: "256Mi"
45 | cpu: "250m"
46 | restartPolicy: Always
47 |
--------------------------------------------------------------------------------
/sample-apps/python/k8s/service.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | apiVersion: v1
16 | kind: Service
17 | metadata:
18 | name: pythonshowcase-service
19 | spec:
20 | selector:
21 | app: pythonshowcase-service
22 | ports:
23 | - port: 80
24 | targetPort: 8080
25 | ---
26 | apiVersion: apps/v1
27 | kind: Deployment
28 | metadata:
29 | name: pythonshowcase-service
30 | spec:
31 | selector:
32 | matchLabels:
33 | app: pythonshowcase-service
34 | template:
35 | metadata:
36 | labels:
37 | app: pythonshowcase-service
38 | spec:
39 | containers:
40 | - name: pythonshowcase-service
41 | image: "%REGISTRY_LOCATION%-docker.pkg.dev/%GCLOUD_PROJECT%/%CONTAINER_REGISTRY%/python-sample:latest"
42 | ports:
43 | - containerPort: 8080
44 | env:
45 | - name: PYTHONUNBUFFERED
46 | value: "1"
47 | resources:
48 | requests:
49 | memory: "256Mi"
50 | cpu: "250m"
51 | limits:
52 | memory: "256Mi"
53 | cpu: "250m"
54 |
--------------------------------------------------------------------------------
/sample-apps/python/requirements.txt:
--------------------------------------------------------------------------------
1 | blinker==1.6.2
2 | click==8.1.7
3 | Flask==2.3.3
4 | gunicorn==22.0.0
5 | itsdangerous==2.1.2
6 | Jinja2==3.1.3
7 | MarkupSafe==2.1.3
8 | packaging==23.2
9 | Werkzeug==2.3.7
10 | requests==2.32.4
11 |
--------------------------------------------------------------------------------
/sample-apps/python/server.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import os
16 | from flask import Flask
17 | from flask.json import jsonify
18 | import requests
19 |
20 | app = Flask(__name__)
21 |
22 | print(os.environ)
23 |
24 | @app.route("/")
25 | def hello_world():
26 | if 'NEXT_SERVER' in os.environ.keys():
27 | try:
28 | json = requests.get(os.environ['NEXT_SERVER'], timeout=10).json()
29 | return json
30 | except requests.RequestException as e:
31 | return jsonify({"error": e})
32 | else:
33 | return jsonify({"data": "Hello world"})
34 |
35 |
--------------------------------------------------------------------------------
/sample-apps/troubleshooting.md:
--------------------------------------------------------------------------------
1 | # Troubleshooting
2 |
3 | ## Node scale up failed: Pod is at risk of not being scheduled
4 |
5 | If this occurs on GKE Autopilot, see
6 | [this GCP doc](https://cloud.google.com/kubernetes-engine/docs/troubleshooting/troubleshooting-autopilot-clusters#scale-up-failed-serial-port-logging)
7 | which explains that you need to enable serial port logging either in your
8 | organization or project, which can be done by running:
9 |
10 | ```
11 | gcloud compute project-info add-metadata \
12 | --metadata serial-port-logging-enable=true
13 | ```
14 |
--------------------------------------------------------------------------------