From f33f5c6701fd5aa52ee00e6e378e27f6117d49eb Mon Sep 17 00:00:00 2001 From: Ridwan Sharif Date: Mon, 23 Oct 2023 00:22:59 +0000 Subject: [PATCH] confgenerator: add support for RunMonitoring configs This change does the following: - Adds confgenerator for `RunMonitoring` - Adds all structs required for the config - Adds golden test files for unit tests - Configures the entrypoint so it creates the config for the collector subprocess - Use secret manager for storing the configs Notes for reviewers: - Sorry this PR is long again - I wanted to add the skeleton for the config generation. The actual labels we will add by default and what should be used in the monitored resource are not yet solidified and will be done in a seperate PR based on discussion in go/run-gmp-config. In this PR, ignore what we're setting as labels. - We discussed this offline, this change has some duplication with the Ops Agent's confgenerator and prometheus-engine's PodMonitoring structs. We accept this added tech debt because the Ops Agents packages aren't super well suited to use as libraries for this sidecar (yet, not much work is needed to make this possible but its not a blocker), and the prometheus libraries used by the prometheus-engine have different interfaces from the ones we forked. We will prioritize fixing this up and simplifying the confgenerator after the MVP Change-Id: Ie57f82761544ebfd0612f8347ac5ab07a4a22edf --- Dockerfile | 1 - README.md | 19 +- clean-up-cloud-run.sh | 27 ++ cloudbuild-single-req.yaml | 19 +- cloudbuild.yaml | 21 +- collector-config.yaml | 50 -- collector/service/components.go | 2 + confgenerator/agentmetrics.go | 89 ++++ confgenerator/confgenerator.go | 75 +++ confgenerator/confgenerator_test.go | 172 +++++++ confgenerator/config.go | 427 ++++++++++++++++++ confgenerator/otel/modular.go | 127 ++++++ confgenerator/otel/processors.go | 173 +++++++ .../add-metadata-labels/golden/otel.yaml | 147 ++++++ .../testdata/add-metadata-labels/input.yaml | 27 ++ .../testdata/builtin/golden/otel.yaml | 154 +++++++ confgenerator/testdata/builtin/input.yaml | 13 + .../testdata/relabel-labels/golden/otel.yaml | 158 +++++++ .../testdata/relabel-labels/input.yaml | 30 ++ .../simple-app-scrape/golden/otel.yaml | 154 +++++++ .../testdata/simple-app-scrape/input.yaml | 22 + confgenerator/util.go | 132 ++++++ create-service-account.sh | 10 + default-config.yaml | 15 + entrypoint.go | 37 +- go.mod | 30 +- go.sum | 47 +- run-service.yaml | 25 +- sample-apps/simple-app/main.go | 4 +- sample-apps/single-req-app/main.go | 4 +- 30 files changed, 2091 insertions(+), 120 deletions(-) create mode 100755 clean-up-cloud-run.sh delete mode 100644 collector-config.yaml create mode 100644 confgenerator/agentmetrics.go create mode 100644 confgenerator/confgenerator.go create mode 100644 confgenerator/confgenerator_test.go create mode 100644 confgenerator/config.go create mode 100644 confgenerator/otel/modular.go create mode 100644 confgenerator/otel/processors.go create mode 100644 confgenerator/testdata/add-metadata-labels/golden/otel.yaml create mode 100644 confgenerator/testdata/add-metadata-labels/input.yaml create mode 100644 confgenerator/testdata/builtin/golden/otel.yaml create mode 100644 confgenerator/testdata/builtin/input.yaml create mode 100644 confgenerator/testdata/relabel-labels/golden/otel.yaml create mode 100644 confgenerator/testdata/relabel-labels/input.yaml create mode 100644 confgenerator/testdata/simple-app-scrape/golden/otel.yaml create mode 100644 confgenerator/testdata/simple-app-scrape/input.yaml create mode 100644 confgenerator/util.go create mode 100644 default-config.yaml diff --git a/Dockerfile b/Dockerfile index 2f23a6c..c8a0206 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,5 @@ FROM alpine:3 RUN apk add --no-cache ca-certificates COPY --from=builder /sidecar/bin/rungmpcol /rungmpcol COPY --from=builder /sidecar/bin/run-gmp-entrypoint /run-gmp-entrypoint -COPY collector-config.yaml /etc/rungmp/config.yml ENTRYPOINT ["/run-gmp-entrypoint"] \ No newline at end of file diff --git a/README.md b/README.md index 2423aef..585106f 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Because this sample requires `docker` or similar container build system for Linu gcloud services enable cloudbuild.googleapis.com --quiet ``` -The bundled configuration file for Cloud Build (`cloudbuild.yaml`) requires a new servcie account with the following roles or stronger: +The bundled configuration file for Cloud Build (`cloudbuild.yaml`) requires a new service account with the following roles or stronger: * `roles/iam.serviceAccountUser` * `roles/storage.objectViewer` @@ -82,6 +82,7 @@ commands: ``` export GCP_PROJECT= +export RUN_GMP_CONFIG=run-gmp-config gcloud artifacts repositories create run-gmp \ --repository-format=docker \ --location=us-east1 @@ -116,6 +117,15 @@ docker build -t us-east1-docker.pkg.dev/$GCP_PROJECT/run-gmp/collector . docker push us-east1-docker.pkg.dev/$GCP_PROJECT/run-gmp/collector ``` +#### Create RunMonitoring config and store as a secret + +Create a `RunMonitoring` config and store it in secret manager. In this example, we use +`run-gmp-config` as the secret name. + +``` +gcloud secrets create ${RUN_GMP_CONFIG} --data-file=default-config.yaml +``` + ##### Create the Cloud Run Service The `run-service.yaml` file defines a multicontainer Cloud Run Service with the @@ -127,6 +137,8 @@ Replace the `%SAMPLE_APP_IMAGE%` and `%OTELCOL_IMAGE%` placeholders in ``` sed -i s@%OTELCOL_IMAGE%@us-east1-docker.pkg.dev/${GCP_PROJECT}/run-gmp/collector@g run-service.yaml sed -i s@%SAMPLE_APP_IMAGE%@us-east1-docker.pkg.dev/${GCP_PROJECT}/run-gmp/sample-app@g run-service.yaml +sed -i s@%PROJECT%@${GCP_PROJECT}@g run-service.yaml +sed -i s@%SECRET%@${RUN_GMP_CONFIG}@g run-service.yaml ``` Create the Service with the following command: @@ -166,8 +178,5 @@ User request received! After running the demo, please make sure to clean up your project so that you don't consume unexpected resources and get charged. ```console -gcloud run services delete run-gmp-sidecar-service --region us-east1 --quiet -gcloud artifacts repositories delete run-gmp \ - --location=us-east1 \ - --quiet +./clean-up-cloud-run.sh ``` diff --git a/clean-up-cloud-run.sh b/clean-up-cloud-run.sh new file mode 100755 index 0000000..4035cd5 --- /dev/null +++ b/clean-up-cloud-run.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Copyright 2023 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 +# +# 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. + +set -ex + +PROJECT_ID=$(gcloud config get-value project) +SA_NAME="run-gmp-sa" +REGION="us-east1" + +gcloud run services delete run-gmp-sidecar-service --region ${REGION} --quiet +gcloud secrets delete run-gmp-config +gcloud artifacts repositories delete run-gmp \ + --location=${REGION} \ + --quiet +gcloud iam service-accounts delete ${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com \ No newline at end of file diff --git a/cloudbuild-single-req.yaml b/cloudbuild-single-req.yaml index b2f5043..745c8a9 100644 --- a/cloudbuild-single-req.yaml +++ b/cloudbuild-single-req.yaml @@ -35,15 +35,26 @@ steps: waitFor: - BUILD_COLLECTOR + - name: "gcr.io/cloud-builders/gcloud" + args: ["secrets", "create", "${_RUN_GMP_CONFIG}", "--data-file=default-config.yaml"] + id: CREATE_SECRET + waitFor: + - PUSH_COLLECTOR + - name: "ubuntu" env: - "IMAGE_APP=${_IMAGE_APP}" - "IMAGE_COLLECTOR=${_IMAGE_COLLECTOR}" + - "SECRET=${_RUN_GMP_CONFIG}" + - "PROJECT=${_GCP_PROJECT}" script: | sed -i s@%OTELCOL_IMAGE%@${IMAGE_COLLECTOR}@g run-service.yaml sed -i s@%SAMPLE_APP_IMAGE%@${IMAGE_APP}@g run-service.yaml + sed -i s@%SECRET%@${SECRET}@g run-service.yaml + sed -i s@%PROJECT%@${PROJECT}@g run-service.yaml id: REPLACE_YAML_VALUE - waitFor: ["-"] + waitFor: + - CREATE_SECRET - name: "gcr.io/google.com/cloudsdktool/cloud-sdk:slim" entrypoint: gcloud @@ -81,9 +92,11 @@ steps: substitutions: _REGION: us-east1 - _REGISTRY: ${_REGION}-docker.pkg.dev/${PROJECT_ID}/run-gmp + _GCP_PROJECT: ${PROJECT_ID} + _REGISTRY: ${_REGION}-docker.pkg.dev/${_GCP_PROJECT}/run-gmp _IMAGE_APP: ${_REGISTRY}/sample-app _IMAGE_COLLECTOR: ${_REGISTRY}/collector + _RUN_GMP_CONFIG: run-gmp-config _SA_NAME: run-gmp-sa images: @@ -97,7 +110,7 @@ images: # * roles/logging.logWriter # * roles/artifactregistry.createOnPushWriter # * roles/run.admin -serviceAccount: "projects/${PROJECT_ID}/serviceAccounts/${_SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" +serviceAccount: "projects/${_GCP_PROJECT}/serviceAccounts/${_SA_NAME}@${_GCP_PROJECT}.iam.gserviceaccount.com" options: dynamic_substitutions: true diff --git a/cloudbuild.yaml b/cloudbuild.yaml index f0a7940..4784203 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -35,15 +35,26 @@ steps: waitFor: - BUILD_COLLECTOR + - name: "gcr.io/cloud-builders/gcloud" + args: ["secrets", "create", "${_RUN_GMP_CONFIG}", "--data-file=default-config.yaml"] + id: CREATE_SECRET + waitFor: + - PUSH_COLLECTOR + - name: "ubuntu" env: - "IMAGE_APP=${_IMAGE_APP}" - "IMAGE_COLLECTOR=${_IMAGE_COLLECTOR}" + - "SECRET=${_RUN_GMP_CONFIG}" + - "PROJECT=${_GCP_PROJECT}" script: | sed -i s@%OTELCOL_IMAGE%@${IMAGE_COLLECTOR}@g run-service.yaml sed -i s@%SAMPLE_APP_IMAGE%@${IMAGE_APP}@g run-service.yaml + sed -i s@%SECRET%@${SECRET}@g run-service.yaml + sed -i s@%PROJECT%@${PROJECT}@g run-service.yaml id: REPLACE_YAML_VALUE - waitFor: ["-"] + waitFor: + - CREATE_SECRET - name: "gcr.io/google.com/cloudsdktool/cloud-sdk:slim" entrypoint: gcloud @@ -81,9 +92,11 @@ steps: substitutions: _REGION: us-east1 - _REGISTRY: ${_REGION}-docker.pkg.dev/${PROJECT_ID}/run-gmp + _GCP_PROJECT: ${PROJECT_ID} + _REGISTRY: ${_REGION}-docker.pkg.dev/${_GCP_PROJECT}/run-gmp _IMAGE_APP: ${_REGISTRY}/sample-app _IMAGE_COLLECTOR: ${_REGISTRY}/collector + _RUN_GMP_CONFIG: run-gmp-config _SA_NAME: run-gmp-sa images: @@ -97,8 +110,8 @@ images: # * roles/logging.logWriter # * roles/artifactregistry.createOnPushWriter # * roles/run.admin -serviceAccount: "projects/${PROJECT_ID}/serviceAccounts/${_SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" +serviceAccount: "projects/${_GCP_PROJECT}/serviceAccounts/${_SA_NAME}@${_GCP_PROJECT}.iam.gserviceaccount.com" options: dynamic_substitutions: true - logging: CLOUD_LOGGING_ONLY + logging: CLOUD_LOGGING_ONLY \ No newline at end of file diff --git a/collector-config.yaml b/collector-config.yaml deleted file mode 100644 index bfc07fe..0000000 --- a/collector-config.yaml +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2023 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. - -receivers: - prometheus: - use_start_time_metric: true - use_collector_start_time_fallback: true - allow_cumulative_resets: true - config: - scrape_configs: - - job_name: 'run-gmp-sidecar' - scrape_interval: 10s - static_configs: - - targets: ['0.0.0.0:8000'] - -processors: - resourcedetection: - detectors: [env, gcp] - timeout: 2s - override: false - - resource: - attributes: - - key: service.name - value: ${env:K_SERVICE} - action: upsert - - key: service.instance.id - from_attribute: faas.id - action: insert - -exporters: - googlemanagedprometheus: - -service: - pipelines: - metrics: - receivers: [prometheus] - processors: [resourcedetection, resource] - exporters: [googlemanagedprometheus] diff --git a/collector/service/components.go b/collector/service/components.go index beccac0..b0f9332 100644 --- a/collector/service/components.go +++ b/collector/service/components.go @@ -19,6 +19,7 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/googlecloudexporter" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstransformprocessor" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourceprocessor" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor" @@ -84,6 +85,7 @@ func components() (otelcol.Factories, error) { processors := []processor.Factory{ filterprocessor.NewFactory(), resourcedetectionprocessor.NewFactory(), + metricstransformprocessor.NewFactory(), resourceprocessor.NewFactory(), transformprocessor.NewFactory(), groupbyattrsprocessor.NewFactory(), diff --git a/confgenerator/agentmetrics.go b/confgenerator/agentmetrics.go new file mode 100644 index 0000000..6ec0c94 --- /dev/null +++ b/confgenerator/agentmetrics.go @@ -0,0 +1,89 @@ +// Copyright 2023 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 +// +// 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 confgenerator + +import ( + "fmt" + + "github.com/GoogleCloudPlatform/run-gmp-sidecar/confgenerator/otel" +) + +type AgentSelfMetrics struct { + Version string + Port int +} + +func (r AgentSelfMetrics) OTelReceiverPipeline() otel.ReceiverPipeline { + return otel.ReceiverPipeline{ + Receiver: otel.Component{ + Type: "prometheus", + Config: map[string]interface{}{ + "config": map[string]interface{}{ + "scrape_configs": []map[string]interface{}{{ + "job_name": "run-gmp-sidecar", + "scrape_interval": "1m", + "static_configs": []map[string]interface{}{{ + // TODO(b/196990135): Customization for the port number + "targets": []string{fmt.Sprintf("0.0.0.0:%d", r.Port)}, + }}, + }}, + }, + }, + }, + Processors: []otel.Component{ + otel.MetricsFilter( + "include", + "strict", + "otelcol_process_uptime", + "otelcol_process_memory_rss", + "otelcol_grpc_io_client_completed_rpcs", + "otelcol_googlecloudmonitoring_point_count", + ), + otel.MetricsTransform( + otel.RenameMetric("otelcol_process_uptime", "agent/uptime", + // change data type from double -> int64 + otel.ToggleScalarDataType, + otel.AddLabel("version", r.Version), + // remove service.version label + otel.AggregateLabels("sum", "version"), + ), + otel.RenameMetric("otelcol_process_memory_rss", "agent/memory_usage", + // remove service.version label + otel.AggregateLabels("sum"), + ), + otel.RenameMetric("otelcol_grpc_io_client_completed_rpcs", "agent/api_request_count", + // change data type from double -> int64 + otel.ToggleScalarDataType, + // TODO: below is proposed new configuration for the metrics transform processor + // ignore any non "google.monitoring" RPCs (note there won't be any other RPCs for now) + // - action: select_label_values + // label: grpc_client_method + // value_regexp: ^google\.monitoring + otel.RenameLabel("grpc_client_status", "state"), + // delete grpc_client_method dimension & service.version label, retaining only state + otel.AggregateLabels("sum", "state"), + ), + otel.RenameMetric("otelcol_googlecloudmonitoring_point_count", "agent/monitoring/point_count", + // change data type from double -> int64 + otel.ToggleScalarDataType, + // Remove service.version label + otel.AggregateLabels("sum", "status"), + ), + ), + }, + } +} + +// intentionally not registered as a component because this is not created by users diff --git a/confgenerator/confgenerator.go b/confgenerator/confgenerator.go new file mode 100644 index 0000000..2032c80 --- /dev/null +++ b/confgenerator/confgenerator.go @@ -0,0 +1,75 @@ +// Copyright 2023 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 +// +// 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 confgenerator + +import ( + "context" + "log" + + "github.com/GoogleCloudPlatform/run-gmp-sidecar/confgenerator/otel" +) + +// GenerateOtelConfig generates the complete collector config including the agent self metrics. +func (rc *RunMonitoringConfig) GenerateOtelConfig(ctx context.Context) (string, error) { + userAgent, _ := UserAgent("Google-Cloud-Run-GMP-Sidecar", "run-gmp", Version) + metricVersionLabel, _ := VersionLabel("run-gmp-sidecar") + receiverPipelines := make(map[string]otel.ReceiverPipeline) + sidecarPipeline, err := rc.OTelReceiverPipeline() + if err != nil { + return "", err + } + receiverPipelines["application-metrics"] = *sidecarPipeline + + // Find a free port for self metrics. + var selfMetricsPort = rc.SelfMetricsPort + if selfMetricsPort == 0 { + selfMetricsPort, err = getFreePort() + if err != nil { + return "", err + } + } + log.Printf("confgenerator: using port %d for self metrics", selfMetricsPort) + + receiverPipelines["run-gmp-self-metrics"] = AgentSelfMetrics{ + Version: metricVersionLabel, + Port: selfMetricsPort, + }.OTelReceiverPipeline() + + otelConfig, err := otel.ModularConfig{ + ReceiverPipelines: receiverPipelines, + Exporter: googleManagedPrometheusExporter(userAgent), + SelfMetricsPort: selfMetricsPort, + }.Generate() + if err != nil { + return "", err + } + return otelConfig, nil +} + +func googleManagedPrometheusExporter(userAgent string) otel.Component { + return otel.Component{ + Type: "googlemanagedprometheus", + Config: map[string]interface{}{ + // (b/233372619) Due to a constraint in the Monarch API for retrying successful data points, + // leaving this enabled is causing adverse effects for some customers. Google OpenTelemetry team + // recommends disabling this. + "retry_on_failure": map[string]interface{}{ + "enabled": false, + }, + "user_agent": userAgent, + "untyped_double_export": true, + }, + } +} diff --git a/confgenerator/confgenerator_test.go b/confgenerator/confgenerator_test.go new file mode 100644 index 0000000..ac5727d --- /dev/null +++ b/confgenerator/confgenerator_test.go @@ -0,0 +1,172 @@ +// Copyright 2023 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 +// +// 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 confgenerator_test + +import ( + "context" + "errors" + "io/fs" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/GoogleCloudPlatform/run-gmp-sidecar/confgenerator" + "gotest.tools/v3/assert" + "gotest.tools/v3/golden" +) + +const ( + builtinTestdataDirName = "builtin" + goldenDir = "golden" + errorGolden = goldenDir + "/error" + inputFileName = "input.yaml" +) + +func testMetadata() *confgenerator.CloudRunEnvironment { + return &confgenerator.CloudRunEnvironment{ + Service: "test_service", + Revision: "test_revision", + Configuration: "test_configuration", + } +} + +func TestGoldens(t *testing.T) { + t.Parallel() + testNames := getTestsInDir(t) + + for _, testName := range testNames { + // https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables + testName := testName + t.Run(testName, func(t *testing.T) { + t.Parallel() + t.Run(testName, func(t *testing.T) { + testDir := filepath.Join(testName) + got, err := generateConfigs(testDir) + if strings.HasPrefix(testName, "invalid-") { + assert.Assert(t, err != nil, "expected test config to be invalid, but was successful") + } + if err := testGeneratedFiles(t, got, filepath.Join(testDir, goldenDir)); err != nil { + t.Errorf("Failed to check generated configs: %v", err) + } + }) + }) + } +} + +func getTestsInDir(t *testing.T) []string { + t.Helper() + + testdataDir := filepath.Join("testdata") + testDirEntries, err := os.ReadDir(testdataDir) + if os.IsNotExist(err) { + // No tests for this combination. + return nil + } + assert.NilError(t, err, "couldn't read directory %s: %v", testdataDir, err) + testNames := []string{} + for _, testDirEntry := range testDirEntries { + if !testDirEntry.IsDir() { + continue + } + userSpecifiedConfPath := filepath.Join(testdataDir, testDirEntry.Name(), inputFileName) + if _, err := os.Stat(userSpecifiedConfPath + ".missing"); err == nil { + // Intentionally missing + } else if _, err := os.Stat(userSpecifiedConfPath); errors.Is(err, os.ErrNotExist) { + // Empty directory; probably a leftover with backup files. + continue + } + testNames = append(testNames, testDirEntry.Name()) + } + return testNames +} + +func generateConfigs(testDir string) (got map[string]string, err error) { + ctx := context.Background() + + got = make(map[string]string) + defer func() { + if err != nil { + got["error"] = err.Error() + } + }() + + c, err := confgenerator.ReadConfigFromFile(ctx, filepath.Join("testdata", testDir, inputFileName)) + if err != nil { + return + } + + // Use deterministic metadata and self metrics port for tests + c.Env = testMetadata() + c.SelfMetricsPort = 42 + + // Otel configs + otelGeneratedConfig, err := c.GenerateOtelConfig(ctx) + if err != nil { + return + } + got["otel.yaml"] = otelGeneratedConfig + return +} + +func testGeneratedFiles(t *testing.T, generatedFiles map[string]string, testDir string) error { + // Find all files currently in this test directory + existingFiles := map[string]struct{}{} + goldenPath := filepath.Join("testdata", testDir) + err := filepath.Walk( + goldenPath, + func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + if info.Mode().IsRegular() { + existingFiles[info.Name()] = struct{}{} + } + return nil + }, + ) + if golden.FlagUpdate() && os.IsNotExist(err) { + if err := os.Mkdir(goldenPath, 0777); err != nil { + return err + } + } else if err != nil { + return err + } + + // Assert the goldens of all the generated files. Either the generated file + // matches a file already present in the directory, or the file is new. + // If the file is new, the test will fail if not currently doing a golden + // update (`-update` flag). + for file, content := range generatedFiles { + golden.Assert(t, content, filepath.Join(testDir, file)) + delete(existingFiles, file) + } + + // If there are any files left in the existing file map, then that means the + // test generated new files and we're currently in an update run. We now need + // to clean up the existing lua files left aren't being generated anymore. + for file := range existingFiles { + if golden.FlagUpdate() { + err := os.Remove(filepath.Join("testdata", testDir, file)) + if err != nil { + return err + } + } else { + t.Errorf("unexpected existing file: %q", file) + } + } + + return nil +} diff --git a/confgenerator/config.go b/confgenerator/config.go new file mode 100644 index 0000000..3040236 --- /dev/null +++ b/confgenerator/config.go @@ -0,0 +1,427 @@ +// Copyright 2023 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 +// +// 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 confgenerator + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/GoogleCloudPlatform/run-gmp-sidecar/confgenerator/otel" + "github.com/prometheus/prometheus/discovery" + "github.com/prometheus/prometheus/discovery/targetgroup" + "github.com/prometheus/prometheus/model/relabel" + + yaml "github.com/goccy/go-yaml" + prommodel "github.com/prometheus/common/model" + promconfig "github.com/prometheus/prometheus/config" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type RunMonitoringConfig struct { + metav1.TypeMeta `yaml:",inline"` + metav1.ObjectMeta `yaml:"metadata,omitempty"` + Spec RunMonitoringSpec `yaml:"spec"` + + Env *CloudRunEnvironment + SelfMetricsPort int +} + +// RunMonitoringSpec contains specification parameters for RunMonitoring. +type RunMonitoringSpec struct { + // The endpoints to scrape on the selected pods. + Endpoints []ScrapeEndpoint `yaml:"endpoints"` + // Labels to add to the Prometheus target for discovered endpoints. + TargetLabels RunTargetLabels `yaml:"targetLabels,omitempty"` + // Limits to apply at scrape time. + Limits *ScrapeLimits `yaml:"limits,omitempty"` +} + +// RunTargetLabels specifies the additional metadata about the target +// users can add to their metric. Allowed options are {service, revision +// , configuration}. If not specified, the sidecar defaults to adding all +// of them to every metric. +type RunTargetLabels struct { + Metadata *[]string `yaml:"metadata,omitempty"` +} + +// ScrapeEndpoint specifies a Prometheus metrics endpoint to scrape. +type ScrapeEndpoint struct { + // Name or number of the port to scrape. + Port string `yaml:"port"` + // Protocol scheme to use to scrape. + Scheme string `yaml:"scheme,omitempty"` + // HTTP path to scrape metrics from. Defaults to "/metrics". + Path string `yaml:"path,omitempty"` + // HTTP GET params to use when scraping. + Params map[string][]string `yaml:"params,omitempty"` + // Proxy URL to scrape through. Encoded passwords are not supported. + ProxyURL string `yaml:"proxyUrl,omitempty"` + // Interval at which to scrape metrics. Must be a valid Prometheus duration. + Interval string `yaml:"interval,omitempty"` + // Timeout for metrics scrapes. Must be a valid Prometheus duration. + // Must not be larger then the scrape interval. + Timeout string `yaml:"timeout,omitempty"` + // Relabeling rules for metrics scraped from this endpoint. Relabeling rules + // that override protected target labels (project_id, location, cluster, + // namespace, job, cloud_run_instance, or __address__) are not permitted. + MetricRelabeling []RelabelingRule `yaml:"metricRelabeling,omitempty"` +} + +type RelabelingRule struct { + // The source labels select values from existing labels. Their content is concatenated + // using the configured separator and matched against the configured regular expression + // for the replace, keep, and drop actions. + SourceLabels []string `yaml:"sourceLabels,omitempty"` + // Separator placed between concatenated source label values. Defaults to ';'. + Separator string `yaml:"separator,omitempty"` + // Label to which the resulting value is written in a replace action. + // It is mandatory for replace actions. Regex capture groups are available. + TargetLabel string `yaml:"targetLabel,omitempty"` + // Regular expression against which the extracted value is matched. Defaults to '(.*)'. + Regex string `yaml:"regex,omitempty"` + // Modulus to take of the hash of the source label values. + Modulus uint64 `yaml:"modulus,omitempty"` + // Replacement value against which a regex replace is performed if the + // regular expression matches. Regex capture groups are available. Defaults to '$1'. + Replacement string `yaml:"replacement,omitempty"` + // Action to perform based on regex matching. Defaults to 'replace'. + Action string `yaml:"action,omitempty"` +} + +type ScrapeLimits struct { + // Maximum number of samples accepted within a single scrape. + // Uses Prometheus default if left unspecified. + Samples uint64 `yaml:"samples,omitempty"` + // Maximum number of labels accepted for a single sample. + // Uses Prometheus default if left unspecified. + Labels uint64 `yaml:"labels,omitempty"` + // Maximum label name length. + // Uses Prometheus default if left unspecified. + LabelNameLength uint64 `yaml:"labelNameLength,omitempty"` + // Maximum label value length. + // Uses Prometheus default if left unspecified. + LabelValueLength uint64 `yaml:"labelValueLength,omitempty"` +} + +var allowedTargetMetadata = []string{"revision", "service", "configuration"} + +// DefaultRunMonitoringConfig creates a config that will be used by default if +// no user config (or an empty one) is found. It scrapes the default location of +// 0.0.0.0:8080/metrics for prometheus metrics. +func DefaultRunMonitoringConfig() *RunMonitoringConfig { + + return &RunMonitoringConfig{ + metav1.TypeMeta{ + Kind: "RunMonitoring", + APIVersion: "monitoring.googleapis.com/v1beta", + }, + metav1.ObjectMeta{ + Name: "run-gmp-sidecar", + }, + RunMonitoringSpec{ + Endpoints: []ScrapeEndpoint{ + { + Port: "8080", + Path: "/metrics", + Interval: "60s", + }, + }, + TargetLabels: RunTargetLabels{Metadata: &allowedTargetMetadata}, + }, + nil, + 0, /* dynamic port selection for self metrics */ + } +} + +// ReadConfigFromFile reads the user config file and returns a RunMonitoringConfig. +// If the user config file does not exist, or is empty - it returns the default +// RunMonitoringConfig. +func ReadConfigFromFile(ctx context.Context, path string) (*RunMonitoringConfig, error) { + config := DefaultRunMonitoringConfig() + + // Fetch metadata from the available environment variables. + config.Env = fetchMetadata() + + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + return config, nil + } + return nil, fmt.Errorf("failed to retrieve the user config file %q: %w", path, err) + } + + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + // Unmarshal the user config over the default config. If some options are unspecified + // the collector uses the default settings for those options. For example, if not specified + // targetLabels is set to {"revision", "service", "configuration"} + if err := yaml.UnmarshalContext(ctx, data, config, yaml.Strict()); err != nil { + return nil, err + } + + return config, nil +} + +// OTelReceiverPipeline creates the appropriate OTel pipeline translated from the +// RunMonitoringConfig. +func (rc *RunMonitoringConfig) OTelReceiverPipeline() (*otel.ReceiverPipeline, error) { + scrapeConfig, err := rc.scrapeConfigs() + if err != nil { + return nil, err + } + return &otel.ReceiverPipeline{ + Receiver: otel.Component{ + Type: "prometheus", + Config: map[string]interface{}{ + "preserve_untyped": true, + "use_start_time_metric": true, + "use_collector_start_time_fallback": true, + "allow_cumulative_resets": true, + "config": map[string]interface{}{ + "scrape_configs": scrapeConfig, + }, + }, + }, + Processors: []otel.Component{otel.GroupByGMPAttrs()}, + }, nil +} + +// scrapeConfigs converts the given RunMonitoringConfig to an equivalent set of Prometheus ScrapeConfigs. +func (rc *RunMonitoringConfig) scrapeConfigs() (res []*promconfig.ScrapeConfig, err error) { + for i := range rc.Spec.Endpoints { + c, err := rc.endpointScrapeConfig(i) + if err != nil { + return nil, fmt.Errorf("invalid definition for endpoint with index %d: %w", i, err) + } + res = append(res, c) + } + return res, nil +} + +// endpointScrapeConfig creates a scrape config for the endpoint specified. +func (rc *RunMonitoringConfig) endpointScrapeConfig(index int) (*promconfig.ScrapeConfig, error) { + metadataLabels := map[string]struct{}{} + if rc.Spec.TargetLabels.Metadata != nil { + for _, l := range *rc.Spec.TargetLabels.Metadata { + if !contains(allowedTargetMetadata, l) { + return nil, fmt.Errorf("metadata label %q not allowed, must be one of %v", l, allowedTargetMetadata) + } + metadataLabels[l] = struct{}{} + } + } + relabelCfgs := relabelingsForMetadata(metadataLabels, rc.Env) + return endpointScrapeConfig( + fmt.Sprintf("RunMonitoring/%s", rc.Name), + rc.Spec.Endpoints[index], + relabelCfgs, + rc.Spec.Limits, + rc.Env, + ) +} + +func relabelingsForMetadata(keys map[string]struct{}, env *CloudRunEnvironment) (res []*relabel.Config) { + if env == nil { + return + } + + if _, ok := keys["service"]; ok { + res = append(res, &relabel.Config{ + Action: relabel.Replace, + SourceLabels: prommodel.LabelNames{"__address__"}, + Replacement: env.Service, + TargetLabel: "cloud_run_service", + }) + } + if _, ok := keys["revision"]; ok { + res = append(res, &relabel.Config{ + Action: relabel.Replace, + SourceLabels: prommodel.LabelNames{"__address__"}, + Replacement: env.Revision, + TargetLabel: "cloud_run_revision", + }) + } + if _, ok := keys["configuration"]; ok { + res = append(res, &relabel.Config{ + Action: relabel.Replace, + SourceLabels: prommodel.LabelNames{"__address__"}, + Replacement: env.Configuration, + TargetLabel: "cloud_run_configuration", + }) + } + return res +} + +func endpointScrapeConfig(id string, ep ScrapeEndpoint, relabelCfgs []*relabel.Config, limits *ScrapeLimits, env *CloudRunEnvironment) (*promconfig.ScrapeConfig, error) { + if env == nil { + return nil, fmt.Errorf("metadata from Cloud Run was not found") + } + labelSet := make(map[prommodel.LabelName]prommodel.LabelValue) + labelSet[prommodel.AddressLabel] = prommodel.LabelValue("0.0.0.0:" + ep.Port) + discoveryCfgs := discovery.Configs{ + discovery.StaticConfig{ + &targetgroup.Group{Targets: []prommodel.LabelSet{labelSet}}, + }, + } + relabelCfgs = append(relabelCfgs, + &relabel.Config{ + Action: relabel.Replace, + SourceLabels: prommodel.LabelNames{"__address__"}, + TargetLabel: "cluster", + Replacement: "__run__", + }, + &relabel.Config{ + Action: relabel.Replace, + SourceLabels: prommodel.LabelNames{"__address__"}, + TargetLabel: "namespace", + Replacement: env.Service, + }, + &relabel.Config{ + Action: relabel.Replace, + SourceLabels: prommodel.LabelNames{"__address__"}, + TargetLabel: "instance", + Replacement: env.Revision + ":" + ep.Port, + }, + ) + + interval, err := prommodel.ParseDuration(ep.Interval) + if err != nil { + return nil, fmt.Errorf("invalid scrape interval: %w", err) + } + timeout := interval + if ep.Timeout != "" { + timeout, err = prommodel.ParseDuration(ep.Timeout) + if err != nil { + return nil, fmt.Errorf("invalid scrape timeout: %w", err) + } + if timeout > interval { + return nil, fmt.Errorf("scrape timeout %v must not be greater than scrape interval %v", timeout, interval) + } + } + + metricsPath := "/metrics" + if ep.Path != "" { + metricsPath = ep.Path + } + + var metricRelabelCfgs []*relabel.Config + for _, r := range ep.MetricRelabeling { + rcfg, err := convertRelabelingRule(r) + if err != nil { + return nil, err + } + metricRelabelCfgs = append(metricRelabelCfgs, rcfg) + } + + scrapeCfg := &promconfig.ScrapeConfig{ + // Generate a job name to make it easy to track what generated the scrape configuration. + // The actual job label attached to its metrics is overwritten via relabeling. + JobName: fmt.Sprintf("%s/%s", id, ep.Port), + ServiceDiscoveryConfigs: discoveryCfgs, + MetricsPath: metricsPath, + Scheme: ep.Scheme, + Params: ep.Params, + ScrapeInterval: interval, + ScrapeTimeout: timeout, + RelabelConfigs: relabelCfgs, + MetricRelabelConfigs: metricRelabelCfgs, + } + if limits != nil { + scrapeCfg.SampleLimit = uint(limits.Samples) + scrapeCfg.LabelLimit = uint(limits.Labels) + scrapeCfg.LabelNameLengthLimit = uint(limits.LabelNameLength) + scrapeCfg.LabelValueLengthLimit = uint(limits.LabelValueLength) + } + // The Prometheus configuration structs do not generally have validation methods and embed their + // validation logic in the UnmarshalYAML methods. To keep things reasonable we don't re-validate + // everything and simply do a final marshal-unmarshal cycle at the end to run all validation + // upstream provides at the end of this method. + b, err := yaml.Marshal(scrapeCfg) + if err != nil { + return nil, fmt.Errorf("scrape config cannot be marshalled: %w", err) + } + var scrapeCfgCopy promconfig.ScrapeConfig + if err := yaml.Unmarshal(b, &scrapeCfgCopy); err != nil { + return nil, fmt.Errorf("invalid scrape configuration: %w", err) + } + return scrapeCfg, nil +} + +// convertRelabelingRule converts the rule to a relabel configuration. An error is returned +// if the rule would modify one of the protected labels. +func convertRelabelingRule(r RelabelingRule) (*relabel.Config, error) { + rcfg := &relabel.Config{ + // Upstream applies ToLower when digesting the config, so we allow the same. + Action: relabel.Action(strings.ToLower(r.Action)), + TargetLabel: r.TargetLabel, + Separator: r.Separator, + Replacement: r.Replacement, + Modulus: r.Modulus, + } + for _, n := range r.SourceLabels { + rcfg.SourceLabels = append(rcfg.SourceLabels, prommodel.LabelName(n)) + } + // Instantiate the default regex Prometheus uses so that the checks below can be run + // if no explicit value is provided. + re := relabel.MustNewRegexp(`(.*)`) + + // We must only set the regex if its not empty. Like in other cases, the Prometheus code does + // not setup the structs correctly and this would default to the string "null" when marshalled, + // which is then interpreted as a regex again when read by Prometheus. + if r.Regex != "" { + var err error + re, err = relabel.NewRegexp(r.Regex) + if err != nil { + return nil, fmt.Errorf("invalid regex %q: %w", r.Regex, err) + } + rcfg.Regex = re + } + + // Validate that the protected target labels are not mutated by the provided relabeling rules. + switch rcfg.Action { + // Default action is "replace" per https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config. + case relabel.Replace, relabel.HashMod, "": + // These actions write into the target label and it must not be a protected one. + if isProtectedLabel(r.TargetLabel) { + return nil, fmt.Errorf("cannot relabel with action %q onto protected label %q", r.Action, r.TargetLabel) + } + case relabel.LabelDrop: + if matchesAnyProtectedLabel(re) { + return nil, fmt.Errorf("regex %s would drop at least one of the protected labels %s", r.Regex, strings.Join(protectedLabels, ", ")) + } + case relabel.LabelKeep: + // Keep drops all labels that don't match the regex. So all protected labels must + // match keep. + if !matchesAllProtectedLabels(re) { + return nil, fmt.Errorf("regex %s would drop at least one of the protected labels %s", r.Regex, strings.Join(protectedLabels, ", ")) + } + case relabel.LabelMap: + // It is difficult to prove for certain that labelmap does not override a protected label. + // Thus we just prohibit its use for now. + // The most feasible way to support this would probably be store all protected labels + // in __tmp_protected_ via a replace rule, then apply labelmap, then replace the + // __tmp label back onto the protected label. + return nil, fmt.Errorf("relabeling with action %q not allowed", r.Action) + case relabel.Keep, relabel.Drop: + // These actions don't modify a series and are OK. + default: + return nil, fmt.Errorf("unknown relabeling action %q", r.Action) + } + return rcfg, nil +} diff --git a/confgenerator/otel/modular.go b/confgenerator/otel/modular.go new file mode 100644 index 0000000..66cb31a --- /dev/null +++ b/confgenerator/otel/modular.go @@ -0,0 +1,127 @@ +// Copyright 2023 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 +// +// 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 otel provides data structures to represent and generate otel configuration. +package otel + +import ( + "fmt" + + "github.com/mitchellh/mapstructure" + "gopkg.in/yaml.v2" +) + +// ReceiverPipeline represents a single OT receiver and zero or more processors that must be chained after that receiver. +type ReceiverPipeline struct { + Receiver Component + Processors []Component +} + +// Component represents a single OT component (receiver, processor, exporter, etc.) +type Component struct { + // Type is the string type needed to instantiate the OT component. + Type string + // Config is an object which can be serialized by mapstructure into the configuration for the component. + // This can either be a map[string]interface{} or a Config struct from OT. + Config interface{} +} + +func (c Component) name(suffix string) string { + if suffix != "" { + return fmt.Sprintf("%s/%s", c.Type, suffix) + } + return c.Type +} + +type ModularConfig struct { + LogLevel string + ReceiverPipelines map[string]ReceiverPipeline + Exporter Component + SelfMetricsPort int +} + +func (c ModularConfig) Generate() (string, error) { + receivers := map[string]interface{}{} + processors := map[string]interface{}{} + exporters := map[string]interface{}{} + pipelines := map[string]interface{}{} + service := map[string]map[string]interface{}{ + "pipelines": pipelines, + "telemetry": { + "metrics": map[string]interface{}{ + "address": fmt.Sprintf("0.0.0.0:%d", c.SelfMetricsPort), + }, + }, + } + + configMap := map[string]interface{}{ + "receivers": receivers, + "processors": processors, + "exporters": exporters, + "service": service, + } + + for key, receiverPipeline := range c.ReceiverPipelines { + receiverName := receiverPipeline.Receiver.name(key) + var receiverProcessorNames []string + for i, processor := range receiverPipeline.Processors { + name := processor.name(fmt.Sprintf("%s_%d", key, i)) + receiverProcessorNames = append(receiverProcessorNames, name) + processors[name] = processor.Config + } + receivers[receiverName] = receiverPipeline.Receiver.Config + + // Keep track of all the processors we're adding to the config. + var processorNames []string + processorNames = append(processorNames, receiverProcessorNames...) + + // Add the resource detector + processors["resourcedetection"] = GCPResourceDetector().Config + processorNames = append(processorNames, "resourcedetection") + + // Add serverless ID detector + processors["resource/serverless"] = AddResourceAttr("service.instance.id", "faas.id").Config + processorNames = append(processorNames, "resource/serverless") + + // Add the serverless instance id as a metric label + transformProcessor := TransformationMetrics(FlattenResourceAttribute("faas.id", "cloud_run_instance")) + processors["transform/instance"] = transformProcessor.Config + processorNames = append(processorNames, "transform/instance") + + exporters["googlemanagedprometheus"] = c.Exporter.Config + pipelines["metrics/"+key] = map[string]interface{}{ + "receivers": []string{receiverName}, + "processors": processorNames, + "exporters": []string{"googlemanagedprometheus"}, + } + } + + out, err := configToYaml(configMap) + // TODO: Return []byte + if err != nil { + return "", err + } + return string(out), nil +} + +// configToYaml converts a tree of structs into a YAML file. +// To match OT's built-in config parsing, we use mapstructure to convert the tree of structs into a tree of maps. +// This allows the direct use of OT's config types at any level of the hierarchy. +func configToYaml(config interface{}) ([]byte, error) { + outMap := make(map[string]interface{}) + if err := mapstructure.Decode(config, &outMap); err != nil { + return nil, err + } + return yaml.Marshal(outMap) +} diff --git a/confgenerator/otel/processors.go b/confgenerator/otel/processors.go new file mode 100644 index 0000000..cd83939 --- /dev/null +++ b/confgenerator/otel/processors.go @@ -0,0 +1,173 @@ +// Copyright 2023 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 +// +// 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 otel + +import "fmt" + +// Helper functions to easily build up processor configs. + +// MetricsFilter returns a Component that filters metrics. +// polarity should be "include" or "exclude". +// matchType should be "strict" or "regexp". +func MetricsFilter(polarity, matchType string, metricNames ...string) Component { + return Component{ + Type: "filter", + Config: map[string]interface{}{ + "metrics": map[string]interface{}{ + polarity: map[string]interface{}{ + "match_type": matchType, + "metric_names": metricNames, + }, + }, + }, + } +} + +// MetricsTransform returns a Component that performs the transformations specified as arguments. +func MetricsTransform(metrics ...map[string]interface{}) Component { + return Component{ + Type: "metricstransform", + Config: map[string]interface{}{ + "transforms": metrics, + }, + } +} + +// RenameMetric returns a config snippet that renames old to new, applying zero or more transformations. +func RenameMetric(old, new string, operations ...map[string]interface{}) map[string]interface{} { + out := map[string]interface{}{ + "include": old, + "action": "update", + "new_name": new, + } + if len(operations) > 0 { + out["operations"] = operations + } + return out +} + +// CombineMetrics returns a config snippet that renames metrics matching the regex old to new, applying zero or more transformations. +func CombineMetrics(old, new string, operations ...map[string]interface{}) map[string]interface{} { + out := map[string]interface{}{ + "include": old, + "match_type": "regexp", + "action": "combine", + "new_name": new, + "submatch_case": "lower", + } + if len(operations) > 0 { + out["operations"] = operations + } + return out +} + +// ToggleScalarDataType transforms int -> double and double -> int. +var ToggleScalarDataType = map[string]interface{}{"action": "toggle_scalar_data_type"} + +// AddLabel adds a label with a fixed value. +func AddLabel(key, value string) map[string]interface{} { + return map[string]interface{}{ + "action": "add_label", + "new_label": key, + "new_value": value, + } +} + +// RenameLabel renames old to new +func RenameLabel(old, new string) map[string]interface{} { + return map[string]interface{}{ + "action": "update_label", + "label": old, + "new_label": new, + } +} + +// AggregateLabels removes all labels except those in the passed list, aggregating values using aggregationType. +func AggregateLabels(aggregationType string, labels ...string) map[string]interface{} { + return map[string]interface{}{ + "action": "aggregate_labels", + "label_set": labels, + "aggregation_type": aggregationType, + } +} + +// GroupByGMPAttrs moves the "namespace", "cluster", and "location" +// metric attributes to resource attributes. The +// googlemanagedprometheus exporter will use these resource attributes +// to populate metric labels. +func GroupByGMPAttrs() Component { + return Component{ + Type: "groupbyattrs", + Config: map[string]interface{}{ + "keys": []string{"namespace", "cluster", "location"}, + }, + } +} + +// GCPResourceDetector returns a resourcedetection processor configured for only GCP. +func GCPResourceDetector() Component { + config := map[string]interface{}{ + "detectors": []string{"gcp", "env"}, + } + + return Component{ + Type: "resourcedetection", + Config: config, + } +} + +// AddResourceAttr adds a resource attribute using a processor. +func AddResourceAttr(key, from string) Component { + attributeConfig := map[string]interface{}{ + "key": key, + "from_attribute": from, + "action": "insert", + } + config := map[string]interface{}{ + "attributes": attributeConfig, + } + + return Component{ + Type: "resource", + Config: config, + } +} + +// TransformationMetrics returns a transform processor object that contains all the queries passed into it. +func TransformationMetrics(queries ...TransformQuery) Component { + queryStrings := []string{} + for _, q := range queries { + queryStrings = append(queryStrings, string(q)) + } + return Component{ + Type: "transform", + Config: map[string]map[string]interface{}{ + "metric_statements": { + "context": "datapoint", + "statements": queryStrings, + }, + }, + } +} + +// TransformQuery is a type wrapper for query expressions supported by the transform +// processor found here: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/transformprocessor +type TransformQuery string + +// FlattenResourceAttribute returns an expression that brings down a resource attribute to a +// metric attribute. +func FlattenResourceAttribute(resourceAttribute, metricAttribute string) TransformQuery { + return TransformQuery(fmt.Sprintf(`set(attributes["%s"], resource.attributes["%s"])`, metricAttribute, resourceAttribute)) +} diff --git a/confgenerator/testdata/add-metadata-labels/golden/otel.yaml b/confgenerator/testdata/add-metadata-labels/golden/otel.yaml new file mode 100644 index 0000000..be8202b --- /dev/null +++ b/confgenerator/testdata/add-metadata-labels/golden/otel.yaml @@ -0,0 +1,147 @@ +exporters: + googlemanagedprometheus: + retry_on_failure: + enabled: false + untyped_double_export: true + user_agent: Google-Cloud-Run-GMP-Sidecar/latest; ShortName=run-gmp;ShortVersion=latest +processors: + filter/run-gmp-self-metrics_0: + metrics: + include: + match_type: strict + metric_names: + - otelcol_process_uptime + - otelcol_process_memory_rss + - otelcol_grpc_io_client_completed_rpcs + - otelcol_googlecloudmonitoring_point_count + groupbyattrs/application-metrics_0: + keys: + - namespace + - cluster + - location + metricstransform/run-gmp-self-metrics_1: + transforms: + - action: update + include: otelcol_process_uptime + new_name: agent/uptime + operations: + - action: toggle_scalar_data_type + - action: add_label + new_label: version + new_value: run-gmp-sidecar@latest + - action: aggregate_labels + aggregation_type: sum + label_set: + - version + - action: update + include: otelcol_process_memory_rss + new_name: agent/memory_usage + operations: + - action: aggregate_labels + aggregation_type: sum + label_set: [] + - action: update + include: otelcol_grpc_io_client_completed_rpcs + new_name: agent/api_request_count + operations: + - action: toggle_scalar_data_type + - action: update_label + label: grpc_client_status + new_label: state + - action: aggregate_labels + aggregation_type: sum + label_set: + - state + - action: update + include: otelcol_googlecloudmonitoring_point_count + new_name: agent/monitoring/point_count + operations: + - action: toggle_scalar_data_type + - action: aggregate_labels + aggregation_type: sum + label_set: + - status + resource/serverless: + attributes: + action: insert + from_attribute: faas.id + key: service.instance.id + resourcedetection: + detectors: + - gcp + - env + transform/instance: + metric_statements: + context: datapoint + statements: + - set(attributes["cloud_run_instance"], resource.attributes["faas.id"]) +receivers: + prometheus/application-metrics: + allow_cumulative_resets: true + config: + scrape_configs: + - job_name: RunMonitoring/run-run-run/8080 + honor_timestamps: false + scrape_interval: 1m + scrape_timeout: 1m + metrics_path: /metrics + sample_limit: 1000 + follow_redirects: false + enable_http2: false + relabel_configs: + - source_labels: [__address__] + target_label: cloud_run_service + replacement: test_service + action: replace + - source_labels: [__address__] + target_label: cluster + replacement: __run__ + action: replace + - source_labels: [__address__] + target_label: namespace + replacement: test_service + action: replace + - source_labels: [__address__] + target_label: instance + replacement: test_revision:8080 + action: replace + static_configs: + - targets: + - 0.0.0.0:8080 + preserve_untyped: true + use_collector_start_time_fallback: true + use_start_time_metric: true + prometheus/run-gmp-self-metrics: + config: + scrape_configs: + - job_name: run-gmp-sidecar + scrape_interval: 1m + static_configs: + - targets: + - 0.0.0.0:42 +service: + pipelines: + metrics/application-metrics: + exporters: + - googlemanagedprometheus + processors: + - groupbyattrs/application-metrics_0 + - resourcedetection + - resource/serverless + - transform/instance + receivers: + - prometheus/application-metrics + metrics/run-gmp-self-metrics: + exporters: + - googlemanagedprometheus + processors: + - filter/run-gmp-self-metrics_0 + - metricstransform/run-gmp-self-metrics_1 + - resourcedetection + - resource/serverless + - transform/instance + receivers: + - prometheus/run-gmp-self-metrics + telemetry: + metrics: + address: 0.0.0.0:42 diff --git a/confgenerator/testdata/add-metadata-labels/input.yaml b/confgenerator/testdata/add-metadata-labels/input.yaml new file mode 100644 index 0000000..c7e974c --- /dev/null +++ b/confgenerator/testdata/add-metadata-labels/input.yaml @@ -0,0 +1,27 @@ +# Copyright 2023 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 +# +# 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. + +apiVersion: monitoring.googleapis.com/v1beta +kind: RunMonitoring +metadata: + name: run-run-run +spec: + endpoints: + - port: 8080 + interval: 60s + targetLabels: + metadata: + - service + limits: + samples: 1000 \ No newline at end of file diff --git a/confgenerator/testdata/builtin/golden/otel.yaml b/confgenerator/testdata/builtin/golden/otel.yaml new file mode 100644 index 0000000..4ea7f21 --- /dev/null +++ b/confgenerator/testdata/builtin/golden/otel.yaml @@ -0,0 +1,154 @@ +exporters: + googlemanagedprometheus: + retry_on_failure: + enabled: false + untyped_double_export: true + user_agent: Google-Cloud-Run-GMP-Sidecar/latest; ShortName=run-gmp;ShortVersion=latest +processors: + filter/run-gmp-self-metrics_0: + metrics: + include: + match_type: strict + metric_names: + - otelcol_process_uptime + - otelcol_process_memory_rss + - otelcol_grpc_io_client_completed_rpcs + - otelcol_googlecloudmonitoring_point_count + groupbyattrs/application-metrics_0: + keys: + - namespace + - cluster + - location + metricstransform/run-gmp-self-metrics_1: + transforms: + - action: update + include: otelcol_process_uptime + new_name: agent/uptime + operations: + - action: toggle_scalar_data_type + - action: add_label + new_label: version + new_value: run-gmp-sidecar@latest + - action: aggregate_labels + aggregation_type: sum + label_set: + - version + - action: update + include: otelcol_process_memory_rss + new_name: agent/memory_usage + operations: + - action: aggregate_labels + aggregation_type: sum + label_set: [] + - action: update + include: otelcol_grpc_io_client_completed_rpcs + new_name: agent/api_request_count + operations: + - action: toggle_scalar_data_type + - action: update_label + label: grpc_client_status + new_label: state + - action: aggregate_labels + aggregation_type: sum + label_set: + - state + - action: update + include: otelcol_googlecloudmonitoring_point_count + new_name: agent/monitoring/point_count + operations: + - action: toggle_scalar_data_type + - action: aggregate_labels + aggregation_type: sum + label_set: + - status + resource/serverless: + attributes: + action: insert + from_attribute: faas.id + key: service.instance.id + resourcedetection: + detectors: + - gcp + - env + transform/instance: + metric_statements: + context: datapoint + statements: + - set(attributes["cloud_run_instance"], resource.attributes["faas.id"]) +receivers: + prometheus/application-metrics: + allow_cumulative_resets: true + config: + scrape_configs: + - job_name: RunMonitoring/run-gmp-sidecar/8080 + honor_timestamps: false + scrape_interval: 1m + scrape_timeout: 1m + metrics_path: /metrics + follow_redirects: false + enable_http2: false + relabel_configs: + - source_labels: [__address__] + target_label: cloud_run_service + replacement: test_service + action: replace + - source_labels: [__address__] + target_label: cloud_run_revision + replacement: test_revision + action: replace + - source_labels: [__address__] + target_label: cloud_run_configuration + replacement: test_configuration + action: replace + - source_labels: [__address__] + target_label: cluster + replacement: __run__ + action: replace + - source_labels: [__address__] + target_label: namespace + replacement: test_service + action: replace + - source_labels: [__address__] + target_label: instance + replacement: test_revision:8080 + action: replace + static_configs: + - targets: + - 0.0.0.0:8080 + preserve_untyped: true + use_collector_start_time_fallback: true + use_start_time_metric: true + prometheus/run-gmp-self-metrics: + config: + scrape_configs: + - job_name: run-gmp-sidecar + scrape_interval: 1m + static_configs: + - targets: + - 0.0.0.0:42 +service: + pipelines: + metrics/application-metrics: + exporters: + - googlemanagedprometheus + processors: + - groupbyattrs/application-metrics_0 + - resourcedetection + - resource/serverless + - transform/instance + receivers: + - prometheus/application-metrics + metrics/run-gmp-self-metrics: + exporters: + - googlemanagedprometheus + processors: + - filter/run-gmp-self-metrics_0 + - metricstransform/run-gmp-self-metrics_1 + - resourcedetection + - resource/serverless + - transform/instance + receivers: + - prometheus/run-gmp-self-metrics + telemetry: + metrics: + address: 0.0.0.0:42 diff --git a/confgenerator/testdata/builtin/input.yaml b/confgenerator/testdata/builtin/input.yaml new file mode 100644 index 0000000..7ba50f9 --- /dev/null +++ b/confgenerator/testdata/builtin/input.yaml @@ -0,0 +1,13 @@ +# Copyright 2023 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 +# +# 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. diff --git a/confgenerator/testdata/relabel-labels/golden/otel.yaml b/confgenerator/testdata/relabel-labels/golden/otel.yaml new file mode 100644 index 0000000..09024fb --- /dev/null +++ b/confgenerator/testdata/relabel-labels/golden/otel.yaml @@ -0,0 +1,158 @@ +exporters: + googlemanagedprometheus: + retry_on_failure: + enabled: false + untyped_double_export: true + user_agent: Google-Cloud-Run-GMP-Sidecar/latest; ShortName=run-gmp;ShortVersion=latest +processors: + filter/run-gmp-self-metrics_0: + metrics: + include: + match_type: strict + metric_names: + - otelcol_process_uptime + - otelcol_process_memory_rss + - otelcol_grpc_io_client_completed_rpcs + - otelcol_googlecloudmonitoring_point_count + groupbyattrs/application-metrics_0: + keys: + - namespace + - cluster + - location + metricstransform/run-gmp-self-metrics_1: + transforms: + - action: update + include: otelcol_process_uptime + new_name: agent/uptime + operations: + - action: toggle_scalar_data_type + - action: add_label + new_label: version + new_value: run-gmp-sidecar@latest + - action: aggregate_labels + aggregation_type: sum + label_set: + - version + - action: update + include: otelcol_process_memory_rss + new_name: agent/memory_usage + operations: + - action: aggregate_labels + aggregation_type: sum + label_set: [] + - action: update + include: otelcol_grpc_io_client_completed_rpcs + new_name: agent/api_request_count + operations: + - action: toggle_scalar_data_type + - action: update_label + label: grpc_client_status + new_label: state + - action: aggregate_labels + aggregation_type: sum + label_set: + - state + - action: update + include: otelcol_googlecloudmonitoring_point_count + new_name: agent/monitoring/point_count + operations: + - action: toggle_scalar_data_type + - action: aggregate_labels + aggregation_type: sum + label_set: + - status + resource/serverless: + attributes: + action: insert + from_attribute: faas.id + key: service.instance.id + resourcedetection: + detectors: + - gcp + - env + transform/instance: + metric_statements: + context: datapoint + statements: + - set(attributes["cloud_run_instance"], resource.attributes["faas.id"]) +receivers: + prometheus/application-metrics: + allow_cumulative_resets: true + config: + scrape_configs: + - job_name: RunMonitoring/mycollector/8080 + honor_timestamps: false + scrape_interval: 10s + scrape_timeout: 10s + metrics_path: /metrics + follow_redirects: false + enable_http2: false + relabel_configs: + - source_labels: [__address__] + target_label: cloud_run_service + replacement: test_service + action: replace + - source_labels: [__address__] + target_label: cloud_run_revision + replacement: test_revision + action: replace + - source_labels: [__address__] + target_label: cloud_run_configuration + replacement: test_configuration + action: replace + - source_labels: [__address__] + target_label: cluster + replacement: __run__ + action: replace + - source_labels: [__address__] + target_label: namespace + replacement: test_service + action: replace + - source_labels: [__address__] + target_label: instance + replacement: test_revision:8080 + action: replace + metric_relabel_configs: + - source_labels: [some_label] + target_label: instance + action: replace + static_configs: + - targets: + - 0.0.0.0:8080 + preserve_untyped: true + use_collector_start_time_fallback: true + use_start_time_metric: true + prometheus/run-gmp-self-metrics: + config: + scrape_configs: + - job_name: run-gmp-sidecar + scrape_interval: 1m + static_configs: + - targets: + - 0.0.0.0:42 +service: + pipelines: + metrics/application-metrics: + exporters: + - googlemanagedprometheus + processors: + - groupbyattrs/application-metrics_0 + - resourcedetection + - resource/serverless + - transform/instance + receivers: + - prometheus/application-metrics + metrics/run-gmp-self-metrics: + exporters: + - googlemanagedprometheus + processors: + - filter/run-gmp-self-metrics_0 + - metricstransform/run-gmp-self-metrics_1 + - resourcedetection + - resource/serverless + - transform/instance + receivers: + - prometheus/run-gmp-self-metrics + telemetry: + metrics: + address: 0.0.0.0:42 diff --git a/confgenerator/testdata/relabel-labels/input.yaml b/confgenerator/testdata/relabel-labels/input.yaml new file mode 100644 index 0000000..ea7481c --- /dev/null +++ b/confgenerator/testdata/relabel-labels/input.yaml @@ -0,0 +1,30 @@ +# Copyright 2023 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 +# +# 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. + +apiVersion: monitoring.googleapis.com/v1beta +kind: RunMonitoring +metadata: + name: mycollector + labels: + run-app: mycollector + type: mytype +spec: + endpoints: + - port: 8080 + interval: 10s + metricRelabeling: + - action: replace + sourceLabels: + - some_label + targetLabel: instance \ No newline at end of file diff --git a/confgenerator/testdata/simple-app-scrape/golden/otel.yaml b/confgenerator/testdata/simple-app-scrape/golden/otel.yaml new file mode 100644 index 0000000..f290c2c --- /dev/null +++ b/confgenerator/testdata/simple-app-scrape/golden/otel.yaml @@ -0,0 +1,154 @@ +exporters: + googlemanagedprometheus: + retry_on_failure: + enabled: false + untyped_double_export: true + user_agent: Google-Cloud-Run-GMP-Sidecar/latest; ShortName=run-gmp;ShortVersion=latest +processors: + filter/run-gmp-self-metrics_0: + metrics: + include: + match_type: strict + metric_names: + - otelcol_process_uptime + - otelcol_process_memory_rss + - otelcol_grpc_io_client_completed_rpcs + - otelcol_googlecloudmonitoring_point_count + groupbyattrs/application-metrics_0: + keys: + - namespace + - cluster + - location + metricstransform/run-gmp-self-metrics_1: + transforms: + - action: update + include: otelcol_process_uptime + new_name: agent/uptime + operations: + - action: toggle_scalar_data_type + - action: add_label + new_label: version + new_value: run-gmp-sidecar@latest + - action: aggregate_labels + aggregation_type: sum + label_set: + - version + - action: update + include: otelcol_process_memory_rss + new_name: agent/memory_usage + operations: + - action: aggregate_labels + aggregation_type: sum + label_set: [] + - action: update + include: otelcol_grpc_io_client_completed_rpcs + new_name: agent/api_request_count + operations: + - action: toggle_scalar_data_type + - action: update_label + label: grpc_client_status + new_label: state + - action: aggregate_labels + aggregation_type: sum + label_set: + - state + - action: update + include: otelcol_googlecloudmonitoring_point_count + new_name: agent/monitoring/point_count + operations: + - action: toggle_scalar_data_type + - action: aggregate_labels + aggregation_type: sum + label_set: + - status + resource/serverless: + attributes: + action: insert + from_attribute: faas.id + key: service.instance.id + resourcedetection: + detectors: + - gcp + - env + transform/instance: + metric_statements: + context: datapoint + statements: + - set(attributes["cloud_run_instance"], resource.attributes["faas.id"]) +receivers: + prometheus/application-metrics: + allow_cumulative_resets: true + config: + scrape_configs: + - job_name: RunMonitoring/run-run-run/8080 + honor_timestamps: false + scrape_interval: 1m + scrape_timeout: 1m + metrics_path: /metrics + follow_redirects: false + enable_http2: false + relabel_configs: + - source_labels: [__address__] + target_label: cloud_run_service + replacement: test_service + action: replace + - source_labels: [__address__] + target_label: cloud_run_revision + replacement: test_revision + action: replace + - source_labels: [__address__] + target_label: cloud_run_configuration + replacement: test_configuration + action: replace + - source_labels: [__address__] + target_label: cluster + replacement: __run__ + action: replace + - source_labels: [__address__] + target_label: namespace + replacement: test_service + action: replace + - source_labels: [__address__] + target_label: instance + replacement: test_revision:8080 + action: replace + static_configs: + - targets: + - 0.0.0.0:8080 + preserve_untyped: true + use_collector_start_time_fallback: true + use_start_time_metric: true + prometheus/run-gmp-self-metrics: + config: + scrape_configs: + - job_name: run-gmp-sidecar + scrape_interval: 1m + static_configs: + - targets: + - 0.0.0.0:42 +service: + pipelines: + metrics/application-metrics: + exporters: + - googlemanagedprometheus + processors: + - groupbyattrs/application-metrics_0 + - resourcedetection + - resource/serverless + - transform/instance + receivers: + - prometheus/application-metrics + metrics/run-gmp-self-metrics: + exporters: + - googlemanagedprometheus + processors: + - filter/run-gmp-self-metrics_0 + - metricstransform/run-gmp-self-metrics_1 + - resourcedetection + - resource/serverless + - transform/instance + receivers: + - prometheus/run-gmp-self-metrics + telemetry: + metrics: + address: 0.0.0.0:42 diff --git a/confgenerator/testdata/simple-app-scrape/input.yaml b/confgenerator/testdata/simple-app-scrape/input.yaml new file mode 100644 index 0000000..310c8cd --- /dev/null +++ b/confgenerator/testdata/simple-app-scrape/input.yaml @@ -0,0 +1,22 @@ +# Copyright 2023 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 +# +# 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. + +apiVersion: monitoring.googleapis.com/v1beta +kind: RunMonitoring +metadata: + name: run-run-run +spec: + endpoints: + - port: 8080 + interval: 60s \ No newline at end of file diff --git a/confgenerator/util.go b/confgenerator/util.go new file mode 100644 index 0000000..4f4120c --- /dev/null +++ b/confgenerator/util.go @@ -0,0 +1,132 @@ +// Copyright 2023 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 +// +// 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 confgenerator represents the Ops Agent configuration and provides functions to generate subagents configuration from unified agent. +package confgenerator + +import ( + "fmt" + "net" + "os" + "strings" + "text/template" + + "github.com/prometheus/prometheus/model/relabel" +) + +// CloudRunEnvironment captures some environment metadata that is captures using environment variables. +// See https://cloud.google.com/run/docs/container-contract#services-env-vars for more information. +// Note that PORT is not made available to sidecar containers, and so is omitted from this struct. +type CloudRunEnvironment struct { + Service string + Revision string + Configuration string +} + +func fetchMetadata() *CloudRunEnvironment { + return &CloudRunEnvironment{ + Service: os.Getenv("K_SERVICE"), + Revision: os.Getenv("K_REVISION"), + Configuration: os.Getenv("K_CONFIGURATION"), + } +} + +var versionLabelTemplate = template.Must(template.New("versionlabel").Parse(`{{.Prefix}}@{{.AgentVersion}}`)) +var userAgentTemplate = template.Must(template.New("useragent").Parse(`{{.Prefix}}/{{.AgentVersion}}; ShortName={{.ShortName}};ShortVersion={{.ShortVersion}}`)) + +var Version = "latest" + +func expandTemplate(t *template.Template, prefix string, extraParams map[string]string) (string, error) { + params := map[string]string{ + "Prefix": prefix, + "AgentVersion": Version, + } + for k, v := range extraParams { + params[k] = v + } + var b strings.Builder + if err := t.Execute(&b, params); err != nil { + fmt.Println(err.Error()) + return "", err + } + return b.String(), nil +} + +func VersionLabel(prefix string) (string, error) { + return expandTemplate(versionLabelTemplate, prefix, nil) +} + +func UserAgent(prefix, shortName, shortVersion string) (string, error) { + extraParams := map[string]string{ + "ShortName": shortName, + "ShortVersion": shortVersion, + } + return expandTemplate(userAgentTemplate, prefix, extraParams) +} + +func contains(s []string, str string) bool { + for _, v := range s { + if v == str { + return true + } + } + + return false +} + +var protectedLabels = []string{ + "project_id", + "location", + "cluster", + "namespace", + "job", + "cloud_run_instance", + "__address__", +} + +func isProtectedLabel(s string) bool { + return contains(protectedLabels, s) +} + +func matchesAnyProtectedLabel(re relabel.Regexp) bool { + for _, pl := range protectedLabels { + if re.MatchString(pl) { + return true + } + } + return false +} + +func matchesAllProtectedLabels(re relabel.Regexp) bool { + for _, pl := range protectedLabels { + if !re.MatchString(pl) { + return false + } + } + return true +} + +func getFreePort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return 0, err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + defer l.Close() + return l.Addr().(*net.TCPAddr).Port, nil +} diff --git a/create-service-account.sh b/create-service-account.sh index 722df66..ed949e4 100755 --- a/create-service-account.sh +++ b/create-service-account.sh @@ -45,6 +45,16 @@ gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ --role="roles/artifactregistry.createOnPushWriter" \ --quiet +gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ + --member="serviceAccount:${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \ + --role="roles/secretmanager.admin" \ + --quiet + +gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ + --member="serviceAccount:${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \ + --role="roles/secretmanager.secretAccessor" \ + --quiet + # In order to change policy of the run service, it requires 'run.services.setIamPolicy', # which is contained in run.admin role gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ diff --git a/default-config.yaml b/default-config.yaml new file mode 100644 index 0000000..39e1718 --- /dev/null +++ b/default-config.yaml @@ -0,0 +1,15 @@ +# Copyright 2023 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 +# +# 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. + +# <== Enter custom agent configurations in this file. \ No newline at end of file diff --git a/entrypoint.go b/entrypoint.go index 1cb013e..2387aa7 100644 --- a/entrypoint.go +++ b/entrypoint.go @@ -15,38 +15,59 @@ package main import ( + "context" + "io/ioutil" "log" "os" "os/signal" + "path/filepath" "syscall" + + "github.com/GoogleCloudPlatform/run-gmp-sidecar/confgenerator" ) // Create channel to listen for signals. var signalChan chan (os.Signal) = make(chan os.Signal, 1) +var userConfigFile = "/etc/rungmp/config.yml" +var otelConfigFile = "/run/rungmp/otel.yml" func main() { // SIGINT handles Ctrl+C locally. // SIGTERM handles Cloud Run termination signal. signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) + ctx := context.Background() - // 1. Pick up RunMonitoring configuration from mounted volume that - // is tied to secret manager. - // TODO(b/293137197) + // Pick up RunMonitoring configuration from mounted volume that is tied to + // secret manager. Translate it from RunMonitoring to OTel. + c, err := confgenerator.ReadConfigFromFile(ctx, userConfigFile) + if err != nil { + log.Fatal(err) + } - // 2. Translate from RunMonitoring to OTel. - // TODO(b/293137197) + // Create the OTel config and write it to disk + otel, err := c.GenerateOtelConfig(ctx) + if err != nil { + log.Fatal(err) + } + if err := os.MkdirAll(filepath.Dir(otelConfigFile), 0755); err != nil { + log.Fatalf("failed to create directory for %q: %v", otelConfigFile, err) + } + if err := ioutil.WriteFile(otelConfigFile, []byte(otel), 0644); err != nil { + log.Fatalf("failed to write file to %q: %v", otelConfigFile, err) + } - // 3. Spin up new-subprocess that runs the OTel collector and store the PID. + // Spin up new-subprocess that runs the OTel collector and store the PID. + // This OTel collector should use the generated config. var procAttr os.ProcAttr procAttr.Files = []*os.File{nil, /* stdin is not needed for the collector */ os.Stdout, os.Stderr} - collectorProcess, err := os.StartProcess("./rungmpcol", []string{"./rungmpcol", "--config", "/etc/rungmp/config.yml"}, &procAttr) + collectorProcess, err := os.StartProcess("./rungmpcol", []string{"./rungmpcol", "--config", otelConfigFile}, &procAttr) if err != nil { log.Fatal(err) } log.Printf("entrypoint: started OTel successfully") - // 4. Wait for signals from Cloud Run. Signal the sub process appropriately + // Wait for signals from Cloud Run. Signal the sub process appropriately // after making relevant changes to the config and/or health signals. // TODO(b/307317433): Consider having a timeout to shutdown the subprocess // non-gracefully. diff --git a/go.mod b/go.mod index f703a63..ad2d62a 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.20 require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/collector v0.42.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/collector/googlemanagedprometheus v0.42.0 + github.com/goccy/go-yaml v1.11.2 github.com/open-telemetry/opentelemetry-collector-contrib/exporter/fileexporter v0.81.0 github.com/open-telemetry/opentelemetry-collector-contrib/exporter/googlecloudexporter v0.81.0 github.com/open-telemetry/opentelemetry-collector-contrib/exporter/prometheusremotewriteexporter v0.81.0 @@ -12,10 +13,11 @@ require ( github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus v0.81.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor v0.81.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor v0.81.0 + github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstransformprocessor v0.81.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor v0.81.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourceprocessor v0.81.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor v0.81.0 - github.com/shirou/gopsutil v3.21.10+incompatible + github.com/shirou/gopsutil v3.21.11+incompatible github.com/stretchr/testify v1.8.4 go.opentelemetry.io/collector v0.81.0 go.opentelemetry.io/collector/component v0.81.0 @@ -38,7 +40,8 @@ require ( go.opentelemetry.io/collector/semconv v0.81.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.24.0 - golang.org/x/text v0.11.0 + golang.org/x/text v0.13.0 + gotest.tools/v3 v3.4.0 ) require ( @@ -46,6 +49,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect + github.com/go-playground/validator/v10 v10.15.5 // indirect github.com/google/s2a-go v0.1.4 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -76,10 +80,10 @@ require ( go.opentelemetry.io/collector/connector v0.81.0 // indirect go.opentelemetry.io/collector/extension/auth v0.81.0 // indirect go.opentelemetry.io/otel/bridge/opencensus v0.39.0 // indirect - golang.org/x/sys v0.10.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230717213848-3f92550aa753 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230717213848-3f92550aa753 // indirect - gotest.tools/v3 v3.4.0 // indirect ) require ( @@ -89,6 +93,7 @@ require ( github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl v0.81.0 // indirect; indir6.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/klog/v2 v2.100.1 // indirect + ) require ( @@ -98,7 +103,7 @@ require ( cloud.google.com/go/monitoring v1.15.1 // indirect cloud.google.com/go/trace v1.10.1 // indirect contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect - github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect + github.com/Azure/azure-sdk-for-go v67.1.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect @@ -107,12 +112,11 @@ require ( github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.15.2 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.16.1 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.18.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.42.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Showmax/go-fqdn v1.0.0 // indirect - github.com/StackExchange/wmi v1.2.1 // indirect github.com/alecthomas/participle/v2 v2.0.0 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/antonmedv/expr v1.12.5 // indirect @@ -191,7 +195,7 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 - github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -213,7 +217,7 @@ require ( github.com/prometheus/common v0.44.0 github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/procfs v0.11.0 // indirect - github.com/prometheus/prometheus v0.43.1 + github.com/prometheus/prometheus v1.8.2-0.20211119115433-692a54649ed7 github.com/prometheus/statsd_exporter v0.22.7 // indirect github.com/rs/cors v1.9.0 // indirect github.com/scaleway/scaleway-sdk-go v1.0.0-beta.20 // indirect @@ -238,13 +242,13 @@ require ( go.opentelemetry.io/otel/trace v1.16.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/goleak v1.2.1 // indirect - golang.org/x/crypto v0.11.0 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.12.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/term v0.10.0 // indirect + golang.org/x/term v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.11.0 // indirect gonum.org/v1/gonum v0.13.0 // indirect @@ -258,7 +262,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.27.3 // indirect - k8s.io/apimachinery v0.27.3 // indirect + k8s.io/apimachinery v0.27.3 k8s.io/client-go v0.27.3 // indirect; indirect4 k8s.io/klog/v2 v2.70.1 // indirect k8s.io/kube-openapi v0.0.0-20230525220651-2546d827e515 // indirect k8s.io/utils v0.0.0-20230711102312-30195339c3c7 // indirect diff --git a/go.sum b/go.sum index 4330d99..c51f692 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,8 @@ cloud.google.com/go/trace v1.10.1/go.mod h1:gbtL94KE5AJLH3y+WVpfWILmqgc6dXcqgNXd contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw= -github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v67.1.0+incompatible h1:oziYcaopbnIKfM69DL05wXdypiqfrUKdxUKrKpynJTw= +github.com/Azure/azure-sdk-for-go v67.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 h1:8q4SaHjFsClSvuVne0ID/5Ka8u3fcIHyqkLjcFpNRHQ= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= @@ -82,8 +82,8 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDm github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.15.2 h1:9VwVugD2NuPr6/IjrNJLpzaX7j+P6EJIup7cpNwcYhw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.15.2/go.mod h1:Xx0VKh7GJ4si3rmElbh19Mejxz68ibWg/J30ZOMrqzU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.16.1 h1:/o9L4jKKshKO6U4q6e5oo0SkVtF5DDNLGK+liqsDt+w= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.16.1/go.mod h1:Xx0VKh7GJ4si3rmElbh19Mejxz68ibWg/J30ZOMrqzU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/collector v0.42.0 h1:1iUFJlwEsO3jbEitj0i9+XIjaY1QsjnUy7J3J81HuH4= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/collector v0.42.0/go.mod h1:otJdxGjog6MfV/93oprfeXgyFrGrqsVvDhdM6P779Rk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/collector/googlemanagedprometheus v0.42.0 h1:Fa7XySHYw2HdumRuo2TYiYK5LkGJuwzTiqAPFoZ7t1s= @@ -102,8 +102,6 @@ github.com/Mottl/ctimefmt v0.0.0-20190803144728-fd2ac23a585a/go.mod h1:eyj2WSIdo github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Showmax/go-fqdn v1.0.0 h1:0rG5IbmVliNT5O19Mfuvna9LL7zlHyRfsSvBPZmF9tM= github.com/Showmax/go-fqdn v1.0.0/go.mod h1:SfrFBzmDCtCGrnHhoDjuvFnKsWjEQX/Q9ARZvOrJAko= -github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= -github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= github.com/alecthomas/participle/v2 v2.0.0/go.mod h1:rAKZdJldHu8084ojcWevWAL8KmEU+AT+Olodb+WoN2Y= @@ -217,6 +215,7 @@ github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -240,7 +239,6 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= @@ -251,6 +249,10 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24= +github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -260,6 +262,8 @@ github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-yaml v1.11.2 h1:joq77SxuyIs9zzxEjgyLBugMQ9NEgTWxXfz2wVqwAaQ= +github.com/goccy/go-yaml v1.11.2/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -478,6 +482,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/linode/linodego v1.19.0 h1:n4WJrcr9+30e9JGZ6DI0nZbm5SdAj1kSwvvt/998YUw= github.com/linode/linodego v1.19.0/go.mod h1:XZFR+yJ9mm2kwf6itZ6SCpu+6w3KnIevV0Uu5HNWJgQ= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= @@ -585,6 +590,8 @@ github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterproces github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor v0.81.0/go.mod h1:SCXzjDN/HNBffKyoJmAUkoAcGScmRkSFkiVY2PVlOUY= github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor v0.81.0 h1:MBF10bAMzWddNiivgiWwNl0NqFtwIwWhQTpsNhhgIwo= github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor v0.81.0/go.mod h1:wCZn4P2NaOQn9xzVLRRZBCED2zcxilJvrXJFeFiAMLk= +github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstransformprocessor v0.81.0 h1:ogNySeBs0zqWuf4cDGvBzIZwZ86MznAcv/EBtrj9s9w= +github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstransformprocessor v0.81.0/go.mod h1:Z0lAQbH2a3kOewras2isLHT+hfGw+wPhXXZu1NUtXqY= github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor v0.81.0 h1:fuMc2Hq7o66IjP6gNjRB4kLLwPPSqFG8KhE38VaGFYA= github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor v0.81.0/go.mod h1:Pxs1UpCisdoCzlSQoS8tLDqxaU3DPsQu9bTXWdBmwQ0= github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourceprocessor v0.81.0 h1:1ct0JB0jCi4cyBvl4Ektq3G47dg7pAbiXsHlOqSK/aI= @@ -669,8 +676,8 @@ github.com/scaleway/scaleway-sdk-go v1.0.0-beta.20 h1:a9hSJdJcd16e0HoMsnFvaHvxB3 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.20/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shirou/gopsutil v3.21.10+incompatible h1:AL2kpVykjkqeN+MFe1WcwSBVUjGjvdU8/ubvCuXAjrU= -github.com/shirou/gopsutil v3.21.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil/v3 v3.23.6 h1:5y46WPI9QBKBbK7EEccUPNXpJpNrvPuTD0O2zHEHT08= github.com/shirou/gopsutil/v3 v3.23.6/go.mod h1:j7QX50DrXYggrpN30W0Mo+I4/8U2UUIQrnrhqUeWrAU= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -851,8 +858,8 @@ golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -933,8 +940,8 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1031,14 +1038,14 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1051,8 +1058,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1110,6 +1117,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= diff --git a/run-service.yaml b/run-service.yaml index bed6bbc..4a3b716 100644 --- a/run-service.yaml +++ b/run-service.yaml @@ -24,6 +24,7 @@ spec: annotations: run.googleapis.com/execution-environment: gen1 run.googleapis.com/container-dependencies: '{"collector":["app"]}' + run.googleapis.com/secrets: '%SECRET%:projects/%PROJECT%/secrets/%SECRET%' spec: containers: - image: "%SAMPLE_APP_IMAGE%" @@ -31,24 +32,22 @@ spec: startupProbe: httpGet: path: /startup - port: 8080 + port: 8000 livenessProbe: httpGet: path: /liveness - port: 8080 + port: 8000 ports: - - containerPort: 8080 - volumeMounts: - - mountPath: /logging - name: shared-logs + - containerPort: 8000 - image: "%OTELCOL_IMAGE%" name: collector volumeMounts: - - mountPath: /logging - name: shared-logs + - mountPath: /etc/rungmp/ + name: config volumes: - - name: shared-logs - emptyDir: - medium: Memory - sizeLimit: 512Mi - + - name: config + secret: + items: + - key: latest + path: config.yml + secretName: '%SECRET%' diff --git a/sample-apps/simple-app/main.go b/sample-apps/simple-app/main.go index b03a3ae..eaef2bd 100644 --- a/sample-apps/simple-app/main.go +++ b/sample-apps/simple-app/main.go @@ -77,8 +77,8 @@ func main() { promMux.Handle("/metrics", promhttp.Handler()) go func() { - http.ListenAndServe(":8080", entrypointMux) + http.ListenAndServe(":8000", entrypointMux) }() - http.ListenAndServe(":8000", promMux) + http.ListenAndServe(":8080", promMux) } diff --git a/sample-apps/single-req-app/main.go b/sample-apps/single-req-app/main.go index d522210..8adcf86 100644 --- a/sample-apps/single-req-app/main.go +++ b/sample-apps/single-req-app/main.go @@ -105,11 +105,11 @@ func main() { promMux.Handle("/metrics", promhttp.Handler()) mainSrv := http.Server{ - Addr: ":8080", + Addr: ":8000", Handler: entrypointMux, } promSrv := http.Server{ - Addr: ":8000", + Addr: ":8080", Handler: promMux, }