Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add otlp metrics export from Google Cloud Run Functions #377

Merged
merged 7 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 2023 Google LLC
psx95 marked this conversation as resolved.
Show resolved Hide resolved
#
# 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
psx95 marked this conversation as resolved.
Show resolved Hide resolved

COPY collector-config.yaml /etc/otelcol-contrib/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright 2023 Google LLC
psx95 marked this conversation as resolved.
Show resolved Hide resolved
#
# 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:
batch:
psx95 marked this conversation as resolved.
Show resolved Hide resolved
# batch metrics before sending to reduce API usage
send_batch_max_size: 200
send_batch_size: 200
timeout: 5s

memory_limiter:
psx95 marked this conversation as resolved.
Show resolved Hide resolved
# drop metrics if memory usage gets too high
check_interval: 1s
limit_percentage: 65
spike_limit_percentage: 20

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
dashpole marked this conversation as resolved.
Show resolved Hide resolved
dashpole marked this conversation as resolved.
Show resolved Hide resolved
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();
}
}
Loading
Loading