Skip to content

Commit b7910af

Browse files
dashpoleMrAlias
andauthored
Unify and improve GCP resource detection, second attempt (#2310)
* update GCP resource detection * Apply suggestions from code review Co-authored-by: Tyler Yahn <[email protected]> * return partial errors * gotidy * remove host.name from GKE, since GKE doesn't support it Co-authored-by: Tyler Yahn <[email protected]>
1 parent 520efe3 commit b7910af

File tree

10 files changed

+592
-0
lines changed

10 files changed

+592
-0
lines changed

detectors/gcp/README.md

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# GCP Resource detector
2+
3+
The GCP resource detector supports detecting resources on:
4+
5+
* Google Compute Engine (GCE)
6+
* Google Kubernetes Engine (GKE)
7+
* Google App Engine (GAE)
8+
* Cloud Run
9+
* Cloud Functions
10+
11+
## Usage
12+
13+
```golang
14+
ctx := context.Background()
15+
// Detect your resources
16+
res, err := resource.New(ctx,
17+
// Use the GCP resource detector!
18+
resource.WithDetectors(gcp.NewDetector()),
19+
// Keep the default detectors
20+
resource.WithTelemetrySDK(),
21+
// Add your own custom attributes to identify your application
22+
resource.WithAttributes(
23+
semconv.ServiceNameKey.String("my-application"),
24+
semconv.ServiceNamespaceKey.String("my-company-frontend-team"),
25+
),
26+
)
27+
if err != nil {
28+
// Handle err
29+
}
30+
// Use the resource in your tracerprovider (or meterprovider)
31+
tp := trace.NewTracerProvider(
32+
// ... other options
33+
trace.WithResource(res),
34+
)
35+
```
36+
37+
## Setting Kubernetes attributes
38+
39+
Previous iterations of GCP resource detection attempted to detect
40+
`container.name`, `k8s.pod.name` and `k8s.namespace.name`. When using this detector,
41+
you should use this in your Pod Spec to set these using
42+
`OTEL_RESOURCE_ATTRIBUTES`:
43+
44+
```yaml
45+
env:
46+
- name: POD_NAME
47+
valueFrom:
48+
fieldRef:
49+
fieldPath: metadata.name
50+
- name: NAMESPACE_NAME
51+
valueFrom:
52+
fieldRef:
53+
fieldPath: metadata.namespace
54+
- name: CONTAINER_NAME
55+
value: my-container-name
56+
- name: OTEL_RESOURCE_ATTRIBUTES
57+
value: k8s.pod.name=$(POD_NAME),k8s.namespace.name=$(NAMESPACE_NAME),k8s.container.name=$(CONTAINER_NAME)

detectors/gcp/cloud-function.go

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const (
2828
)
2929

3030
// NewCloudFunction will return a GCP Cloud Function resource detector.
31+
// Deprecated: Use gcp.NewDetector() instead, which sets the same resource attributes.
3132
func NewCloudFunction() resource.Detector {
3233
return &cloudFunction{
3334
cloudRun: NewCloudRun(),

detectors/gcp/cloud-run.go

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type metadataClient interface {
3838
}
3939

4040
// CloudRun collects resource information of Cloud Run instance.
41+
// Deprecated: Use gcp.NewDetector() instead. Note that it sets faas.* resource attributes instead of service.* attributes.
4142
type CloudRun struct {
4243
mc metadataClient
4344
onGCE func() bool
@@ -49,6 +50,7 @@ type CloudRun struct {
4950
var _ resource.Detector = (*CloudRun)(nil)
5051

5152
// NewCloudRun creates a CloudRun detector.
53+
// Deprecated: Use gcp.NewDetector() instead. Note that it sets faas.* resource attributes instead of service.* attributes.
5254
func NewCloudRun() *CloudRun {
5355
return &CloudRun{
5456
mc: metadata.NewClient(nil),

detectors/gcp/detector.go

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright The OpenTelemetry Authors
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 gcp // import "go.opentelemetry.io/contrib/detectors/gcp"
16+
17+
import (
18+
"context"
19+
"fmt"
20+
21+
"cloud.google.com/go/compute/metadata"
22+
"github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp"
23+
24+
"go.opentelemetry.io/otel/attribute"
25+
"go.opentelemetry.io/otel/sdk/resource"
26+
semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
27+
)
28+
29+
// NewDetector returns a resource detector which detects resource attributes on:
30+
// * Google Compute Engine (GCE).
31+
// * Google Kubernetes Engine (GKE).
32+
// * Google App Engine (GAE).
33+
// * Cloud Run.
34+
// * Cloud Functions.
35+
func NewDetector() resource.Detector {
36+
return &detector{detector: gcp.NewDetector()}
37+
}
38+
39+
type detector struct {
40+
detector gcpDetector
41+
}
42+
43+
// Detect detects associated resources when running on GCE, GKE, GAE,
44+
// Cloud Run, and Cloud functions.
45+
func (d *detector) Detect(ctx context.Context) (*resource.Resource, error) {
46+
if !metadata.OnGCE() {
47+
return nil, nil
48+
}
49+
b := &resourceBuilder{}
50+
b.attrs = append(b.attrs, semconv.CloudProviderGCP)
51+
b.add(semconv.CloudAccountIDKey, d.detector.ProjectID)
52+
53+
switch d.detector.CloudPlatform() {
54+
case gcp.GKE:
55+
b.attrs = append(b.attrs, semconv.CloudPlatformGCPKubernetesEngine)
56+
b.addZoneOrRegion(d.detector.GKEAvailabilityZoneOrRegion)
57+
b.add(semconv.K8SClusterNameKey, d.detector.GKEClusterName)
58+
b.add(semconv.HostIDKey, d.detector.GKEHostID)
59+
case gcp.CloudRun:
60+
b.attrs = append(b.attrs, semconv.CloudPlatformGCPCloudRun)
61+
b.add(semconv.FaaSNameKey, d.detector.FaaSName)
62+
b.add(semconv.FaaSVersionKey, d.detector.FaaSVersion)
63+
b.add(semconv.FaaSIDKey, d.detector.FaaSID)
64+
b.add(semconv.CloudRegionKey, d.detector.FaaSCloudRegion)
65+
case gcp.CloudFunctions:
66+
b.attrs = append(b.attrs, semconv.CloudPlatformGCPCloudFunctions)
67+
b.add(semconv.FaaSNameKey, d.detector.FaaSName)
68+
b.add(semconv.FaaSVersionKey, d.detector.FaaSVersion)
69+
b.add(semconv.FaaSIDKey, d.detector.FaaSID)
70+
b.add(semconv.CloudRegionKey, d.detector.FaaSCloudRegion)
71+
case gcp.AppEngine:
72+
b.attrs = append(b.attrs, semconv.CloudPlatformGCPAppEngine)
73+
b.addZoneAndRegion(d.detector.AppEngineAvailabilityZoneAndRegion)
74+
b.add(semconv.FaaSNameKey, d.detector.AppEngineServiceName)
75+
b.add(semconv.FaaSVersionKey, d.detector.AppEngineServiceVersion)
76+
b.add(semconv.FaaSIDKey, d.detector.AppEngineServiceInstance)
77+
case gcp.GCE:
78+
b.attrs = append(b.attrs, semconv.CloudPlatformGCPComputeEngine)
79+
b.addZoneAndRegion(d.detector.GCEAvailabilityZoneAndRegion)
80+
b.add(semconv.HostTypeKey, d.detector.GCEHostType)
81+
b.add(semconv.HostIDKey, d.detector.GCEHostID)
82+
b.add(semconv.HostNameKey, d.detector.GCEHostName)
83+
default:
84+
// We don't support this platform yet, so just return with what we have
85+
}
86+
return b.build()
87+
}
88+
89+
// resourceBuilder simplifies constructing resources using GCP detection
90+
// library functions.
91+
type resourceBuilder struct {
92+
errs []error
93+
attrs []attribute.KeyValue
94+
}
95+
96+
func (r *resourceBuilder) add(key attribute.Key, detect func() (string, error)) {
97+
if v, err := detect(); err == nil {
98+
r.attrs = append(r.attrs, key.String(v))
99+
} else {
100+
r.errs = append(r.errs, err)
101+
}
102+
}
103+
104+
// zoneAndRegion functions are expected to return zone, region, err.
105+
func (r *resourceBuilder) addZoneAndRegion(detect func() (string, string, error)) {
106+
if zone, region, err := detect(); err == nil {
107+
r.attrs = append(r.attrs, semconv.CloudAvailabilityZoneKey.String(zone))
108+
r.attrs = append(r.attrs, semconv.CloudRegionKey.String(region))
109+
} else {
110+
r.errs = append(r.errs, err)
111+
}
112+
}
113+
114+
func (r *resourceBuilder) addZoneOrRegion(detect func() (string, gcp.LocationType, error)) {
115+
if v, locType, err := detect(); err == nil {
116+
switch locType {
117+
case gcp.Zone:
118+
r.attrs = append(r.attrs, semconv.CloudAvailabilityZoneKey.String(v))
119+
case gcp.Region:
120+
r.attrs = append(r.attrs, semconv.CloudRegionKey.String(v))
121+
default:
122+
r.errs = append(r.errs, fmt.Errorf("location must be zone or region. Got %v", locType))
123+
}
124+
} else {
125+
r.errs = append(r.errs, err)
126+
}
127+
}
128+
129+
func (r *resourceBuilder) build() (*resource.Resource, error) {
130+
var err error
131+
if len(r.errs) > 0 {
132+
err = fmt.Errorf("%w: %s", resource.ErrPartialResource, r.errs)
133+
}
134+
return resource.NewWithAttributes(semconv.SchemaURL, r.attrs...), err
135+
}

0 commit comments

Comments
 (0)