Skip to content

Commit

Permalink
Unify and improve GCP resource detection, second attempt (#2310)
Browse files Browse the repository at this point in the history
* 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
dashpole and MrAlias authored Jun 1, 2022
1 parent 520efe3 commit b7910af
Show file tree
Hide file tree
Showing 10 changed files with 592 additions and 0 deletions.
57 changes: 57 additions & 0 deletions detectors/gcp/README.md
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)
1 change: 1 addition & 0 deletions detectors/gcp/cloud-function.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (
)

// NewCloudFunction will return a GCP Cloud Function resource detector.
// Deprecated: Use gcp.NewDetector() instead, which sets the same resource attributes.
func NewCloudFunction() resource.Detector {
return &cloudFunction{
cloudRun: NewCloudRun(),
Expand Down
2 changes: 2 additions & 0 deletions detectors/gcp/cloud-run.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type metadataClient interface {
}

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

// NewCloudRun creates a CloudRun detector.
// Deprecated: Use gcp.NewDetector() instead. Note that it sets faas.* resource attributes instead of service.* attributes.
func NewCloudRun() *CloudRun {
return &CloudRun{
mc: metadata.NewClient(nil),
Expand Down
135 changes: 135 additions & 0 deletions detectors/gcp/detector.go
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
}
Loading

0 comments on commit b7910af

Please sign in to comment.