Skip to content

Commit 4181e2d

Browse files
authored
Add otlp metrics export from Google Cloud Run Functions (#377)
* 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
1 parent ff4949e commit 4181e2d

File tree

7 files changed

+381
-0
lines changed

7 files changed

+381
-0
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Exporting OTLP Metrics from Cloud Functions using OpenTelemetry Collector Sidecar
2+
3+
This example shows how to export OpenTelemetry metrics from a Google Cloud Run Function
4+
to Google Managed Prometheus using OpenTelemetry Collector running as a sidecar.
5+
6+
*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).*
7+
8+
Additional details on deploying functions on Cloud Run can be viewed [here](https://cloud.google.com/run/docs/deploy-functions).
9+
10+
##### Important Note
11+
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.
12+
Please see the [pricing table](https://cloud.google.com/run/pricing#tables) for differences and additional details.
13+
14+
### Prerequisites
15+
16+
##### Get Google Cloud Credentials on your machine
17+
18+
```shell
19+
gcloud auth application-default login
20+
```
21+
22+
##### Export the Google Cloud Project ID to `GOOGLE_CLOUD_PROJECT` environment variable:
23+
24+
```shell
25+
export GOOGLE_CLOUD_PROJECT="my-awesome-gcp-project-id"
26+
```
27+
28+
### Deploying the function and collector sidecar
29+
30+
#### Prepare a docker image configured to run OpenTelemetry Collector
31+
32+
Create a docker image that runs OpenTelemetry container as a sidecar. This image would be pushed to Google Cloud Artifact Repository.
33+
34+
Follow these steps:
35+
36+
1. Create an artifact repository in your GCP project:
37+
```shell
38+
gcloud artifacts repositories create otlp-cloud-run --repository-format=docker --location=us-central1
39+
40+
gcloud auth configure-docker us-central1-docker.pkg.dev
41+
```
42+
2. Build & push the docker image with the collector:
43+
```shell
44+
# From the root of the repository
45+
cd examples/otlpmetrics-function/collector-deployment
46+
docker build . -t us-central1-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/otlp-cloud-run/otel-collector
47+
docker push us-central1-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/otlp-cloud-run/otel-collector
48+
```
49+
50+
#### Deploy & Run the Google Cloud Run Function:
51+
52+
#### Build the JAR to deploy
53+
54+
You first need to build the JAR that will be deployed as a function. To do so, run:
55+
56+
```shell
57+
# From the examples-otlpmetrics-function directory
58+
gradle clean build shadowJar
59+
```
60+
This command should generate a JAR named `hello-world-function.jar` in `out/deployment` directory.
61+
62+
#### Deploy the function
63+
This example shows how to use the `gcloud` CLI to deploy the function along with the docker container:
64+
65+
##### Using gcloud command
66+
67+
```shell
68+
# From the examples-otlpmetrics-function directory
69+
gcloud beta run deploy cloud-func-helloworld \
70+
--no-cpu-throttling \
71+
--container app-function \
72+
--function com.google.cloud.opentelemetry.examples.otlpmetricsfunction.HelloWorld \
73+
--source=out/deployment \
74+
--port=8080 \
75+
--container otel-collector \
76+
--image=us-central1-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/otlp-cloud-run/otel-collector:latest
77+
```
78+
*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.*
79+
80+
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:
81+
82+
```shell
83+
# This will allow you to invoke your deployed function from http://localhost:8080
84+
# Press Ctrl+C to interrupt the running proxy
85+
gcloud beta run services proxy cloud-func-helloworld --port=8080
86+
```
87+
88+
### Viewing exported metrics
89+
90+
This example is configured to export metrics via `debug` and `googlemanagedprometheus` exporters in the OpenTelemetry Collector.
91+
92+
- The exported metrics from the `debug` exporter can be viewed on standard out through the logs explorer in GCP.
93+
- 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`.
94+
95+
### Cleanup
96+
97+
After you are done with the example you can follow these steps to clean up any Google Cloud Resources created when running this example:
98+
99+
```shell
100+
# Delete the deployed function
101+
gcloud run services delete cloud-func-helloworld
102+
103+
# Delete the artifact registry and all its contents
104+
gcloud artifacts repositories delete otlp-cloud-run --location=us-central1
105+
```
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2024 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+
* 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+
plugins {
17+
id 'java'
18+
id "com.github.johnrengelman.shadow"
19+
}
20+
21+
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
22+
23+
group 'org.example'
24+
25+
repositories {
26+
mavenCentral()
27+
}
28+
29+
configurations {
30+
invoker
31+
}
32+
33+
dependencies {
34+
implementation 'com.google.cloud.functions:functions-framework-api:1.0.4'
35+
implementation(libraries.opentelemetry_api)
36+
implementation(libraries.opentelemetry_sdk_metrics)
37+
38+
// required by resource detection
39+
implementation(libraries.opentelemetry_autoconfigure_spi)
40+
// resource detection module
41+
implementation(libraries.opentelemetry_gcp_resources)
42+
43+
implementation(libraries.opentelemetry_otlp_exporter)
44+
implementation(libraries.opentelemetry_logging_exporter)
45+
46+
// For testing functions locally
47+
invoker 'com.google.cloud.functions.invoker:java-function-invoker:1.1.1'
48+
}
49+
50+
jar {
51+
manifest {
52+
doFirst {
53+
attributes 'Class-Path': files(configurations.runtimeClasspath).asPath
54+
}
55+
}
56+
}
57+
58+
tasks.named('shadowJar', ShadowJar) {
59+
archivesBaseName = 'hello-world-function'
60+
archiveClassifier = ''
61+
archiveVersion = ''
62+
destinationDirectory.set(file('out/deployment/'))
63+
}
64+
65+
// Task only used to test the function locally
66+
tasks.register('runFunction', JavaExec) {
67+
mainClass = 'com.google.cloud.functions.invoker.runner.Invoker'
68+
classpath(configurations.invoker)
69+
inputs.files(configurations.runtimeClasspath, sourceSets.main.output)
70+
args(
71+
'--target', project.findProperty('run.functionTarget'),
72+
'--port', project.findProperty('run.port') ?: 8080
73+
)
74+
doFirst {
75+
args('--classpath', files(configurations.runtimeClasspath, sourceSets.main.output).asPath)
76+
}
77+
}
78+
79+
test {
80+
useJUnitPlatform()
81+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
FROM otel/opentelemetry-collector-contrib:0.87.0
16+
17+
COPY collector-config.yaml /etc/otelcol-contrib/config.yaml
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
receivers:
16+
otlp:
17+
protocols:
18+
grpc:
19+
http:
20+
21+
processors:
22+
resourcedetection:
23+
detectors: [env, gcp]
24+
timeout: 2s
25+
override: false
26+
27+
exporters:
28+
googlemanagedprometheus:
29+
debug:
30+
verbosity: detailed
31+
sampling_initial: 5
32+
sampling_thereafter: 200
33+
34+
extensions:
35+
health_check:
36+
37+
service:
38+
extensions: [health_check]
39+
pipelines:
40+
metrics:
41+
receivers: [otlp]
42+
processors: [resourcedetection]
43+
exporters: [debug, googlemanagedprometheus]
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2024 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+
* 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.cloud.opentelemetry.examples.otlpmetricsfunction;
17+
18+
import com.google.cloud.functions.HttpFunction;
19+
import com.google.cloud.functions.HttpRequest;
20+
import com.google.cloud.functions.HttpResponse;
21+
import io.opentelemetry.api.metrics.LongCounter;
22+
import java.util.Random;
23+
24+
public class HelloWorld implements HttpFunction {
25+
private static final OpenTelemetryConfig openTelemetryConfig = OpenTelemetryConfig.getInstance();
26+
private static final LongCounter counter =
27+
openTelemetryConfig
28+
.getMeterProvider()
29+
.get("sample-function-library")
30+
.counterBuilder("function_counter_gmp")
31+
.setDescription("random counter")
32+
.build();
33+
private static final Random random = new Random();
34+
35+
public HelloWorld() {
36+
super();
37+
// Register a shutdown hook as soon as the function object is instantiated
38+
Runtime.getRuntime()
39+
.addShutdownHook(
40+
new Thread(
41+
() -> {
42+
System.out.println("Closing OpenTelemetry SDK");
43+
openTelemetryConfig.closeSdk();
44+
System.out.println("OpenTelemetry SDK closed");
45+
}));
46+
}
47+
48+
@Override
49+
public void service(HttpRequest request, HttpResponse response) throws Exception {
50+
System.out.println("received request: " + request.toString());
51+
counter.add(random.nextInt(100));
52+
response.getWriter().write("Hello, World\n");
53+
System.out.println("Function exited");
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2024 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+
* 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.cloud.opentelemetry.examples.otlpmetricsfunction;
17+
18+
import io.opentelemetry.api.metrics.MeterProvider;
19+
import io.opentelemetry.contrib.gcp.resource.GCPResourceProvider;
20+
import io.opentelemetry.exporter.logging.LoggingMetricExporter;
21+
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
22+
import io.opentelemetry.sdk.OpenTelemetrySdk;
23+
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
24+
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
25+
import io.opentelemetry.sdk.resources.Resource;
26+
import java.time.Duration;
27+
28+
/**
29+
* A singleton class representing the OpenTelemetry instance that can be used to instrument the
30+
* application.
31+
*/
32+
public final class OpenTelemetryConfig {
33+
private static final OpenTelemetryConfig INSTANCE = new OpenTelemetryConfig();
34+
private static final int METRIC_EXPORT_DURATION_MILLIS = 10000;
35+
private final OpenTelemetrySdk openTelemetry;
36+
37+
// prevent object creation
38+
private OpenTelemetryConfig() {
39+
this.openTelemetry = initOpenTelemetry();
40+
}
41+
42+
public static OpenTelemetryConfig getInstance() {
43+
return INSTANCE;
44+
}
45+
46+
public MeterProvider getMeterProvider() {
47+
return this.openTelemetry.getMeterProvider();
48+
}
49+
50+
/** Closes the OpenTelemetry SDK instance, exporting any pending metrics. */
51+
public void closeSdk() {
52+
openTelemetry.close();
53+
}
54+
55+
private OpenTelemetrySdk initOpenTelemetry() {
56+
// Enable proper resource detection within the application
57+
// This is used for the logging exporter.
58+
GCPResourceProvider resourceProvider = new GCPResourceProvider();
59+
Resource resource = Resource.getDefault().merge(resourceProvider.createResource(null));
60+
61+
return OpenTelemetrySdk.builder()
62+
.setMeterProvider(
63+
SdkMeterProvider.builder()
64+
.setResource(resource)
65+
.registerMetricReader(
66+
PeriodicMetricReader.builder(LoggingMetricExporter.create())
67+
.setInterval(Duration.ofMillis(METRIC_EXPORT_DURATION_MILLIS))
68+
.build())
69+
.registerMetricReader(
70+
PeriodicMetricReader.builder(OtlpGrpcMetricExporter.getDefault())
71+
.setInterval(Duration.ofMillis(METRIC_EXPORT_DURATION_MILLIS))
72+
.build())
73+
.build())
74+
.buildAndRegisterGlobal();
75+
}
76+
}

settings.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ include ":exporter-trace"
2828
include ":examples-trace"
2929
include ":examples-otlp-spring"
3030
include ":examples-otlptrace"
31+
include ":examples-otlpmetrics-function"
3132
include ":exporter-metrics"
3233
include ":examples-metrics"
3334
include ":exporter-auto"
@@ -92,3 +93,6 @@ project(':examples-otlp-spring').projectDir =
9293

9394
project(':examples-spring').projectDir =
9495
"$rootDir/examples/spring" as File
96+
97+
project(':examples-otlpmetrics-function').projectDir =
98+
"$rootDir/examples/otlpmetrics-function" as File

0 commit comments

Comments
 (0)