-
Notifications
You must be signed in to change notification settings - Fork 581
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Unify and improve GCP resource detection, second attempt (#2310)
* update GCP resource detection * Apply suggestions from code review Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com> * return partial errors * gotidy * remove host.name from GKE, since GKE doesn't support it Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
- Loading branch information
Showing
10 changed files
with
592 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# GCP Resource detector | ||
|
||
The GCP resource detector supports detecting resources on: | ||
|
||
* Google Compute Engine (GCE) | ||
* Google Kubernetes Engine (GKE) | ||
* Google App Engine (GAE) | ||
* Cloud Run | ||
* Cloud Functions | ||
|
||
## Usage | ||
|
||
```golang | ||
ctx := context.Background() | ||
// Detect your resources | ||
res, err := resource.New(ctx, | ||
// Use the GCP resource detector! | ||
resource.WithDetectors(gcp.NewDetector()), | ||
// Keep the default detectors | ||
resource.WithTelemetrySDK(), | ||
// Add your own custom attributes to identify your application | ||
resource.WithAttributes( | ||
semconv.ServiceNameKey.String("my-application"), | ||
semconv.ServiceNamespaceKey.String("my-company-frontend-team"), | ||
), | ||
) | ||
if err != nil { | ||
// Handle err | ||
} | ||
// Use the resource in your tracerprovider (or meterprovider) | ||
tp := trace.NewTracerProvider( | ||
// ... other options | ||
trace.WithResource(res), | ||
) | ||
``` | ||
|
||
## Setting Kubernetes attributes | ||
|
||
Previous iterations of GCP resource detection attempted to detect | ||
`container.name`, `k8s.pod.name` and `k8s.namespace.name`. When using this detector, | ||
you should use this in your Pod Spec to set these using | ||
`OTEL_RESOURCE_ATTRIBUTES`: | ||
|
||
```yaml | ||
env: | ||
- name: POD_NAME | ||
valueFrom: | ||
fieldRef: | ||
fieldPath: metadata.name | ||
- name: NAMESPACE_NAME | ||
valueFrom: | ||
fieldRef: | ||
fieldPath: metadata.namespace | ||
- name: CONTAINER_NAME | ||
value: my-container-name | ||
- name: OTEL_RESOURCE_ATTRIBUTES | ||
value: k8s.pod.name=$(POD_NAME),k8s.namespace.name=$(NAMESPACE_NAME),k8s.container.name=$(CONTAINER_NAME) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// | ||
// 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 gcp // import "go.opentelemetry.io/contrib/detectors/gcp" | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"cloud.google.com/go/compute/metadata" | ||
"github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp" | ||
|
||
"go.opentelemetry.io/otel/attribute" | ||
"go.opentelemetry.io/otel/sdk/resource" | ||
semconv "go.opentelemetry.io/otel/semconv/v1.10.0" | ||
) | ||
|
||
// NewDetector returns a resource detector which detects resource attributes on: | ||
// * Google Compute Engine (GCE). | ||
// * Google Kubernetes Engine (GKE). | ||
// * Google App Engine (GAE). | ||
// * Cloud Run. | ||
// * Cloud Functions. | ||
func NewDetector() resource.Detector { | ||
return &detector{detector: gcp.NewDetector()} | ||
} | ||
|
||
type detector struct { | ||
detector gcpDetector | ||
} | ||
|
||
// Detect detects associated resources when running on GCE, GKE, GAE, | ||
// Cloud Run, and Cloud functions. | ||
func (d *detector) Detect(ctx context.Context) (*resource.Resource, error) { | ||
if !metadata.OnGCE() { | ||
return nil, nil | ||
} | ||
b := &resourceBuilder{} | ||
b.attrs = append(b.attrs, semconv.CloudProviderGCP) | ||
b.add(semconv.CloudAccountIDKey, d.detector.ProjectID) | ||
|
||
switch d.detector.CloudPlatform() { | ||
case gcp.GKE: | ||
b.attrs = append(b.attrs, semconv.CloudPlatformGCPKubernetesEngine) | ||
b.addZoneOrRegion(d.detector.GKEAvailabilityZoneOrRegion) | ||
b.add(semconv.K8SClusterNameKey, d.detector.GKEClusterName) | ||
b.add(semconv.HostIDKey, d.detector.GKEHostID) | ||
case gcp.CloudRun: | ||
b.attrs = append(b.attrs, semconv.CloudPlatformGCPCloudRun) | ||
b.add(semconv.FaaSNameKey, d.detector.FaaSName) | ||
b.add(semconv.FaaSVersionKey, d.detector.FaaSVersion) | ||
b.add(semconv.FaaSIDKey, d.detector.FaaSID) | ||
b.add(semconv.CloudRegionKey, d.detector.FaaSCloudRegion) | ||
case gcp.CloudFunctions: | ||
b.attrs = append(b.attrs, semconv.CloudPlatformGCPCloudFunctions) | ||
b.add(semconv.FaaSNameKey, d.detector.FaaSName) | ||
b.add(semconv.FaaSVersionKey, d.detector.FaaSVersion) | ||
b.add(semconv.FaaSIDKey, d.detector.FaaSID) | ||
b.add(semconv.CloudRegionKey, d.detector.FaaSCloudRegion) | ||
case gcp.AppEngine: | ||
b.attrs = append(b.attrs, semconv.CloudPlatformGCPAppEngine) | ||
b.addZoneAndRegion(d.detector.AppEngineAvailabilityZoneAndRegion) | ||
b.add(semconv.FaaSNameKey, d.detector.AppEngineServiceName) | ||
b.add(semconv.FaaSVersionKey, d.detector.AppEngineServiceVersion) | ||
b.add(semconv.FaaSIDKey, d.detector.AppEngineServiceInstance) | ||
case gcp.GCE: | ||
b.attrs = append(b.attrs, semconv.CloudPlatformGCPComputeEngine) | ||
b.addZoneAndRegion(d.detector.GCEAvailabilityZoneAndRegion) | ||
b.add(semconv.HostTypeKey, d.detector.GCEHostType) | ||
b.add(semconv.HostIDKey, d.detector.GCEHostID) | ||
b.add(semconv.HostNameKey, d.detector.GCEHostName) | ||
default: | ||
// We don't support this platform yet, so just return with what we have | ||
} | ||
return b.build() | ||
} | ||
|
||
// resourceBuilder simplifies constructing resources using GCP detection | ||
// library functions. | ||
type resourceBuilder struct { | ||
errs []error | ||
attrs []attribute.KeyValue | ||
} | ||
|
||
func (r *resourceBuilder) add(key attribute.Key, detect func() (string, error)) { | ||
if v, err := detect(); err == nil { | ||
r.attrs = append(r.attrs, key.String(v)) | ||
} else { | ||
r.errs = append(r.errs, err) | ||
} | ||
} | ||
|
||
// zoneAndRegion functions are expected to return zone, region, err. | ||
func (r *resourceBuilder) addZoneAndRegion(detect func() (string, string, error)) { | ||
if zone, region, err := detect(); err == nil { | ||
r.attrs = append(r.attrs, semconv.CloudAvailabilityZoneKey.String(zone)) | ||
r.attrs = append(r.attrs, semconv.CloudRegionKey.String(region)) | ||
} else { | ||
r.errs = append(r.errs, err) | ||
} | ||
} | ||
|
||
func (r *resourceBuilder) addZoneOrRegion(detect func() (string, gcp.LocationType, error)) { | ||
if v, locType, err := detect(); err == nil { | ||
switch locType { | ||
case gcp.Zone: | ||
r.attrs = append(r.attrs, semconv.CloudAvailabilityZoneKey.String(v)) | ||
case gcp.Region: | ||
r.attrs = append(r.attrs, semconv.CloudRegionKey.String(v)) | ||
default: | ||
r.errs = append(r.errs, fmt.Errorf("location must be zone or region. Got %v", locType)) | ||
} | ||
} else { | ||
r.errs = append(r.errs, err) | ||
} | ||
} | ||
|
||
func (r *resourceBuilder) build() (*resource.Resource, error) { | ||
var err error | ||
if len(r.errs) > 0 { | ||
err = fmt.Errorf("%w: %s", resource.ErrPartialResource, r.errs) | ||
} | ||
return resource.NewWithAttributes(semconv.SchemaURL, r.attrs...), err | ||
} |
Oops, something went wrong.