├── .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 | ![sample graphgen output](./graphgen/out.svg) 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 | 9 | 10 | 11 | 12 | 13 | 10.4.1.139 14 | 15 | 16 | productpage-v1-564d4686f-2jvmx 17 | 18 | 19 | 20 | 21 | 22 | 10.4.2.73 23 | 24 | 25 | details-v1-5f4d584748-4t97w 26 | 27 | 28 | 29 | 30 | 31 | 10.4.1.139->10.4.2.73 32 | 33 | 34 | 35 | 36 | 37 | 10.4.1.137 38 | 39 | 40 | reviews-v1-86896b7648-l2fnm 41 | 42 | 43 | 44 | 45 | 46 | 10.4.1.139->10.4.1.137 47 | 48 | 49 | 50 | 51 | 52 | 10.4.0.99 53 | 54 | 55 | reviews-v2-b7dcd98fb-f6j9b 56 | 57 | 58 | 59 | 60 | 61 | 10.4.1.139->10.4.0.99 62 | 63 | 64 | 65 | 66 | 67 | 10.4.1.138 68 | 69 | 70 | reviews-v3-5c5cc7b6d-fmw6k 71 | 72 | 73 | 74 | 75 | 76 | 10.4.1.139->10.4.1.138 77 | 78 | 79 | 80 | 81 | 82 | 10.4.0.98 83 | 84 | 85 | ratings-v1-686ccfb5d8-g2fbw 86 | 87 | 88 | 89 | 90 | 91 | 10.4.0.99->10.4.0.98 92 | 93 | 94 | 95 | 96 | 97 | 10.4.1.138->10.4.0.98 98 | 99 | 100 | 101 | 102 | 103 | 10.4.2.3 104 | 105 | 106 | collector-wlmwr 107 | 108 | 109 | 110 | 111 | 112 | 10.4.2.95 113 | 114 | 115 | otel-collector-687dd7fd85-j95bm 116 | 117 | 118 | 119 | 120 | 121 | 10.4.2.3->10.4.2.95 122 | 123 | 124 | 125 | 126 | 127 | 10.4.2.75 128 | 129 | 130 | oha-loadgen-84c68dbfbc-dwhck 131 | 132 | 133 | 134 | 135 | 136 | 10.4.2.75->10.4.1.139 137 | 138 | 139 | 140 | 141 | 142 | 10.4.1.174 143 | 144 | 145 | client-554d698b56-5gf9f 146 | 147 | 148 | 149 | 150 | 151 | 10.4.1.175 152 | 153 | 154 | server-68cd8cdf64-lrqjt 155 | 156 | 157 | 158 | 159 | 160 | 10.4.1.174->10.4.1.175 161 | 162 | 163 | 164 | 165 | 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 | ![Screen Shot 2022-10-07 at 4 37 05 PM](https://user-images.githubusercontent.com/3262098/194649254-e75c5313-07e4-44dc-a807-e136a52d30c5.png) 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 | ![Screen Shot 2022-10-07 at 4 37 05 PM](https://user-images.githubusercontent.com/3262098/194649254-e75c5313-07e4-44dc-a807-e136a52d30c5.png) 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 | --------------------------------------------------------------------------------