Skip to content

Commit

Permalink
Add otlp metrics export from Google Cloud Run Functions (#377)
Browse files Browse the repository at this point in the history
* Add otlp metrics export from Google Cloud Run Functions

* Update the metric name

* Update the README

* Add newline at EOF

* Update README

* Update the copyright year in license header

* Remove unused processors from the collector-config
  • Loading branch information
psx95 authored Oct 3, 2024
1 parent ff4949e commit 4181e2d
Show file tree
Hide file tree
Showing 7 changed files with 381 additions and 0 deletions.
105 changes: 105 additions & 0 deletions examples/otlpmetrics-function/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Exporting OTLP Metrics from Cloud Functions using OpenTelemetry Collector Sidecar

This example shows how to export OpenTelemetry metrics from a Google Cloud Run Function
to Google Managed Prometheus using OpenTelemetry Collector running as a sidecar.

*Google Cloud Functions were renamed to Google Cloud Run Functions. For more information on what this change entails, see [here](https://cloud.google.com/blog/products/serverless/google-cloud-functions-is-now-cloud-run-functions).*

Additional details on deploying functions on Cloud Run can be viewed [here](https://cloud.google.com/run/docs/deploy-functions).

##### Important Note
This example leverages the use of `always-allocated CPU` for Cloud Run Services, which may have different pricing implication compared to the default `CPU only allocated during request` option.
Please see the [pricing table](https://cloud.google.com/run/pricing#tables) for differences and additional details.

### Prerequisites

##### Get Google Cloud Credentials on your machine

```shell
gcloud auth application-default login
```

##### Export the Google Cloud Project ID to `GOOGLE_CLOUD_PROJECT` environment variable:

```shell
export GOOGLE_CLOUD_PROJECT="my-awesome-gcp-project-id"
```

### Deploying the function and collector sidecar

#### Prepare a docker image configured to run OpenTelemetry Collector

Create a docker image that runs OpenTelemetry container as a sidecar. This image would be pushed to Google Cloud Artifact Repository.

Follow these steps:

1. Create an artifact repository in your GCP project:
```shell
gcloud artifacts repositories create otlp-cloud-run --repository-format=docker --location=us-central1

gcloud auth configure-docker us-central1-docker.pkg.dev
```
2. Build & push the docker image with the collector:
```shell
# From the root of the repository
cd examples/otlpmetrics-function/collector-deployment
docker build . -t us-central1-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/otlp-cloud-run/otel-collector
docker push us-central1-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/otlp-cloud-run/otel-collector
```

#### Deploy & Run the Google Cloud Run Function:

#### Build the JAR to deploy

You first need to build the JAR that will be deployed as a function. To do so, run:

```shell
# From the examples-otlpmetrics-function directory
gradle clean build shadowJar
```
This command should generate a JAR named `hello-world-function.jar` in `out/deployment` directory.

#### Deploy the function
This example shows how to use the `gcloud` CLI to deploy the function along with the docker container:

##### Using gcloud command

```shell
# From the examples-otlpmetrics-function directory
gcloud beta run deploy cloud-func-helloworld \
--no-cpu-throttling \
--container app-function \
--function com.google.cloud.opentelemetry.examples.otlpmetricsfunction.HelloWorld \
--source=out/deployment \
--port=8080 \
--container otel-collector \
--image=us-central1-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/otlp-cloud-run/otel-collector:latest
```
*Note that even though you are running `gcloud run deploy` instead of `gcloud functions deploy`, the `--function` flags instructs Cloud Run to deploy this service as a function.*

After your Cloud Run Function has finished deploying, depending on your authentication setup, you can create a proxy to the deployed function on your localhost to facilitate testing:

```shell
# This will allow you to invoke your deployed function from http://localhost:8080
# Press Ctrl+C to interrupt the running proxy
gcloud beta run services proxy cloud-func-helloworld --port=8080
```

### Viewing exported metrics

This example is configured to export metrics via `debug` and `googlemanagedprometheus` exporters in the OpenTelemetry Collector.

- The exported metrics from the `debug` exporter can be viewed on standard out through the logs explorer in GCP.
- The exported metrics from `googlemanagedprometheus` can be viewed in [metrics explorer](https://cloud.google.com/monitoring/charts/metrics-selector). You can search for the metric named `function_counter_gmp` and it should be listed under the resource `Prometheus Target`.

### Cleanup

After you are done with the example you can follow these steps to clean up any Google Cloud Resources created when running this example:

```shell
# Delete the deployed function
gcloud run services delete cloud-func-helloworld

# Delete the artifact registry and all its contents
gcloud artifacts repositories delete otlp-cloud-run --location=us-central1
```
81 changes: 81 additions & 0 deletions examples/otlpmetrics-function/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
id 'java'
id "com.github.johnrengelman.shadow"
}

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

group 'org.example'

repositories {
mavenCentral()
}

configurations {
invoker
}

dependencies {
implementation 'com.google.cloud.functions:functions-framework-api:1.0.4'
implementation(libraries.opentelemetry_api)
implementation(libraries.opentelemetry_sdk_metrics)

// required by resource detection
implementation(libraries.opentelemetry_autoconfigure_spi)
// resource detection module
implementation(libraries.opentelemetry_gcp_resources)

implementation(libraries.opentelemetry_otlp_exporter)
implementation(libraries.opentelemetry_logging_exporter)

// For testing functions locally
invoker 'com.google.cloud.functions.invoker:java-function-invoker:1.1.1'
}

jar {
manifest {
doFirst {
attributes 'Class-Path': files(configurations.runtimeClasspath).asPath
}
}
}

tasks.named('shadowJar', ShadowJar) {
archivesBaseName = 'hello-world-function'
archiveClassifier = ''
archiveVersion = ''
destinationDirectory.set(file('out/deployment/'))
}

// Task only used to test the function locally
tasks.register('runFunction', JavaExec) {
mainClass = 'com.google.cloud.functions.invoker.runner.Invoker'
classpath(configurations.invoker)
inputs.files(configurations.runtimeClasspath, sourceSets.main.output)
args(
'--target', project.findProperty('run.functionTarget'),
'--port', project.findProperty('run.port') ?: 8080
)
doFirst {
args('--classpath', files(configurations.runtimeClasspath, sourceSets.main.output).asPath)
}
}

test {
useJUnitPlatform()
}
17 changes: 17 additions & 0 deletions examples/otlpmetrics-function/collector-deployment/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM otel/opentelemetry-collector-contrib:0.87.0

COPY collector-config.yaml /etc/otelcol-contrib/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

receivers:
otlp:
protocols:
grpc:
http:

processors:
resourcedetection:
detectors: [env, gcp]
timeout: 2s
override: false

exporters:
googlemanagedprometheus:
debug:
verbosity: detailed
sampling_initial: 5
sampling_thereafter: 200

extensions:
health_check:

service:
extensions: [health_check]
pipelines:
metrics:
receivers: [otlp]
processors: [resourcedetection]
exporters: [debug, googlemanagedprometheus]
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.cloud.opentelemetry.examples.otlpmetricsfunction;

import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import io.opentelemetry.api.metrics.LongCounter;
import java.util.Random;

public class HelloWorld implements HttpFunction {
private static final OpenTelemetryConfig openTelemetryConfig = OpenTelemetryConfig.getInstance();
private static final LongCounter counter =
openTelemetryConfig
.getMeterProvider()
.get("sample-function-library")
.counterBuilder("function_counter_gmp")
.setDescription("random counter")
.build();
private static final Random random = new Random();

public HelloWorld() {
super();
// Register a shutdown hook as soon as the function object is instantiated
Runtime.getRuntime()
.addShutdownHook(
new Thread(
() -> {
System.out.println("Closing OpenTelemetry SDK");
openTelemetryConfig.closeSdk();
System.out.println("OpenTelemetry SDK closed");
}));
}

@Override
public void service(HttpRequest request, HttpResponse response) throws Exception {
System.out.println("received request: " + request.toString());
counter.add(random.nextInt(100));
response.getWriter().write("Hello, World\n");
System.out.println("Function exited");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.cloud.opentelemetry.examples.otlpmetricsfunction;

import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.contrib.gcp.resource.GCPResourceProvider;
import io.opentelemetry.exporter.logging.LoggingMetricExporter;
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
import io.opentelemetry.sdk.resources.Resource;
import java.time.Duration;

/**
* A singleton class representing the OpenTelemetry instance that can be used to instrument the
* application.
*/
public final class OpenTelemetryConfig {
private static final OpenTelemetryConfig INSTANCE = new OpenTelemetryConfig();
private static final int METRIC_EXPORT_DURATION_MILLIS = 10000;
private final OpenTelemetrySdk openTelemetry;

// prevent object creation
private OpenTelemetryConfig() {
this.openTelemetry = initOpenTelemetry();
}

public static OpenTelemetryConfig getInstance() {
return INSTANCE;
}

public MeterProvider getMeterProvider() {
return this.openTelemetry.getMeterProvider();
}

/** Closes the OpenTelemetry SDK instance, exporting any pending metrics. */
public void closeSdk() {
openTelemetry.close();
}

private OpenTelemetrySdk initOpenTelemetry() {
// Enable proper resource detection within the application
// This is used for the logging exporter.
GCPResourceProvider resourceProvider = new GCPResourceProvider();
Resource resource = Resource.getDefault().merge(resourceProvider.createResource(null));

return OpenTelemetrySdk.builder()
.setMeterProvider(
SdkMeterProvider.builder()
.setResource(resource)
.registerMetricReader(
PeriodicMetricReader.builder(LoggingMetricExporter.create())
.setInterval(Duration.ofMillis(METRIC_EXPORT_DURATION_MILLIS))
.build())
.registerMetricReader(
PeriodicMetricReader.builder(OtlpGrpcMetricExporter.getDefault())
.setInterval(Duration.ofMillis(METRIC_EXPORT_DURATION_MILLIS))
.build())
.build())
.buildAndRegisterGlobal();
}
}
4 changes: 4 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ include ":exporter-trace"
include ":examples-trace"
include ":examples-otlp-spring"
include ":examples-otlptrace"
include ":examples-otlpmetrics-function"
include ":exporter-metrics"
include ":examples-metrics"
include ":exporter-auto"
Expand Down Expand Up @@ -92,3 +93,6 @@ project(':examples-otlp-spring').projectDir =

project(':examples-spring').projectDir =
"$rootDir/examples/spring" as File

project(':examples-otlpmetrics-function').projectDir =
"$rootDir/examples/otlpmetrics-function" as File

0 comments on commit 4181e2d

Please sign in to comment.