Skip to content

Add GCP resource detector #383

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

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
52 changes: 52 additions & 0 deletions detectors/gcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# GCP Resource detector

Note: This is still a work in progress. Use the detector in opentelemetry-go-contrib for now: https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/detectors/gcp.

## Usage

```golang
ctx := context.Background()
// detect your resources
resource, err := resource.New(ctx,
resource.WithDetectors(
// Use this GCP resource detector!
gcp.NewDetector(),
// Add other, default resource detectors.
resource.WithFromEnv(),
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"),
),
)
// use the resource in your tracerprovider (or meterprovider)
tp := trace.NewTracerProvider(
// ... other options
trace.WithResource(resource),
)
```

## 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:
- name: OTEL_RESOURCE_ATTRIBUTES
value: k8s.pod.name=$(POD_NAME),k8s.namespace.name=$(NAMESPACE_NAME),k8s.container.name=$(CONTAINER_NAME)
```
48 changes: 48 additions & 0 deletions detectors/gcp/app_engine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2022 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
//
// 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.

package gcp

import (
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
)

func (d *detector) onAppEngine() bool {
_, found := d.os.LookupEnv("GAE_SERVICE")
return found
}

// appEngineAttributes detects attributes available in app-engine environments:
// https://cloud.google.com/appengine/docs/flexible/python/migrating#modules
// This includes: faas name, faas version, faas id, and cloud region
func (d *detector) appEngineAttributes() ([]attribute.KeyValue, []string) {
attributes, errs := d.zoneAndRegionAttributes()
if serviceName, found := d.os.LookupEnv("GAE_SERVICE"); !found {
errs = append(errs, "envvar GAE_SERVICE not found.")
} else {
attributes = append(attributes, semconv.FaaSNameKey.String(serviceName))
}
if serviceVersion, found := d.os.LookupEnv("GAE_VERSION"); !found {
errs = append(errs, "envvar GAE_VERSION contains empty string.")
} else {
attributes = append(attributes, semconv.FaaSVersionKey.String(serviceVersion))
}
if serviceInstance, found := d.os.LookupEnv("GAE_INSTANCE"); !found {
errs = append(errs, "envvar GAE_INSTANCE contains empty string.")
} else {
attributes = append(attributes, semconv.FaaSIDKey.String(serviceInstance))
}
return attributes, errs
}
119 changes: 119 additions & 0 deletions detectors/gcp/detector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2022 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
//
// 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.

package gcp

import (
"context"
"fmt"
"os"

"cloud.google.com/go/compute/metadata"

"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 performs OpenTelemetry resource
// detection for GKE, Cloud Run, Cloud Functions, App Engine, and Compute Engine.
func NewDetector() resource.Detector {
return &detector{metadata: metadata.NewClient(nil), os: realOSProvider{}}
}

func (d *detector) Detect(ctx context.Context) (*resource.Resource, error) {
if !metadata.OnGCE() {
return nil, nil
}
attributes := []attribute.KeyValue{semconv.CloudProviderGCP}
var errors []string

// Detect project attributes on all platforms
attrs, errs := d.projectAttributes()
attributes = append(attributes, attrs...)
errors = append(errors, errs...)

// Detect different resources depending on which platform we are on
switch {
case d.onGKE():
attributes = append(attributes, semconv.CloudPlatformGCPKubernetesEngine)
attrs, errs = d.gkeAttributes()
attributes = append(attributes, attrs...)
errors = append(errors, errs...)
case d.onCloudRun():
attributes = append(attributes, semconv.CloudPlatformGCPCloudRun)
attrs, errs = d.faasAttributes()
attributes = append(attributes, attrs...)
errors = append(errors, errs...)
case d.onCloudFunctions():
attributes = append(attributes, semconv.CloudPlatformGCPCloudFunctions)
attrs, errs = d.faasAttributes()
attributes = append(attributes, attrs...)
errors = append(errors, errs...)
case d.onAppEngine():
attributes = append(attributes, semconv.CloudPlatformGCPAppEngine)
attrs, errs = d.appEngineAttributes()
attributes = append(attributes, attrs...)
errors = append(errors, errs...)
default:
attributes = append(attributes, semconv.CloudPlatformGCPComputeEngine)
attrs, errs = d.gceAttributes()
attributes = append(attributes, attrs...)
errors = append(errors, errs...)
}
var aggregatedErr error
if len(errors) > 0 {
aggregatedErr = fmt.Errorf("detecting GCP resources: %s", errors)
}

return resource.NewWithAttributes(semconv.SchemaURL, attributes...), aggregatedErr
}

func (d *detector) projectAttributes() (attributes []attribute.KeyValue, errs []string) {
if projectID, err := d.metadata.ProjectID(); err != nil {
errs = append(errs, err.Error())
} else if projectID != "" {
attributes = append(attributes, semconv.CloudAccountIDKey.String(projectID))
}
return
}

// detector collects resource information for all GCP platforms
type detector struct {
metadata metadataProvider
os osProvider
}

// metadataProvider contains the subset of the metadata.Client functions used
// by this resource detector to allow testing with a fake implementation.
type metadataProvider interface {
ProjectID() (string, error)
InstanceID() (string, error)
Get(string) (string, error)
InstanceName() (string, error)
Zone() (string, error)
InstanceAttributeValue(string) (string, error)
}

// osProvider contains the subset of the os package functions used by
type osProvider interface {
LookupEnv(string) (string, bool)
}

// realOSProvider uses the os package to lookup env vars
type realOSProvider struct{}

func (realOSProvider) LookupEnv(env string) (string, bool) {
return os.LookupEnv(env)
}
Loading