cpflow 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.agents/agent-workflow.yml +15 -0
- data/.agents/bin/README.md +19 -0
- data/.agents/bin/docs +5 -0
- data/.agents/bin/lint +5 -0
- data/.agents/bin/setup +5 -0
- data/.agents/bin/test +5 -0
- data/.agents/bin/validate +5 -0
- data/.agents/trusted-github-actors.yml +32 -0
- data/.agents/workflows/ai-rollout-e2e-test.md +166 -0
- data/.github/actions/cpflow-wait-for-health/action.yml +11 -4
- data/.github/workflows/claude-code-review.yml +2 -0
- data/.github/workflows/claude.yml +2 -0
- data/.github/workflows/cpflow-deploy-review-app.yml +16 -1
- data/.github/workflows/cpflow-promote-staging-to-production.yml +224 -37
- data/.github/workflows/rspec-shared.yml +15 -1
- data/.github/workflows/rspec-specific.yml +1 -0
- data/.github/workflows/rspec.yml +58 -1
- data/AGENTS.md +57 -0
- data/CHANGELOG.md +36 -1
- data/CLAUDE.md +3 -0
- data/CONTRIBUTING.md +6 -2
- data/Gemfile.lock +1 -1
- data/README.md +25 -7
- data/docs/ai-github-flow-prompt.md +18 -16
- data/docs/assets/logo/favicon.ico +0 -0
- data/docs/assets/logo/icon-1024.png +0 -0
- data/docs/assets/logo/icon-128.png +0 -0
- data/docs/assets/logo/icon-16.png +0 -0
- data/docs/assets/logo/icon-192.png +0 -0
- data/docs/assets/logo/icon-24.png +0 -0
- data/docs/assets/logo/icon-32.png +0 -0
- data/docs/assets/logo/icon-48.png +0 -0
- data/docs/assets/logo/icon-512.png +0 -0
- data/docs/assets/logo/icon-64.png +0 -0
- data/docs/assets/logo/icon-tile.svg +17 -0
- data/docs/assets/logo/mark-transparent.svg +16 -0
- data/docs/ci-automation.md +203 -15
- data/docs/commands.md +16 -1
- data/docs/grafana-opentelemetry.md +699 -0
- data/docs/secrets-and-env-values.md +29 -2
- data/docs/sidebars.ts +70 -0
- data/docs/telemetry/application-instrumentation.md +161 -0
- data/docs/telemetry/collector.md +297 -0
- data/docs/telemetry/index.md +152 -0
- data/docs/telemetry/pipelines.md +98 -0
- data/docs/telemetry/review-apps.md +55 -0
- data/docs/telemetry/troubleshooting.md +92 -0
- data/docs/terraform/example/.controlplane/controlplane.yml +0 -1
- data/docs/terraform/overview.md +11 -0
- data/docs/tips.md +458 -29
- data/examples/controlplane.yml +2 -0
- data/lib/command/ai_github_flow_prompt.rb +2 -2
- data/lib/command/base.rb +17 -2
- data/lib/command/deploy_image.rb +77 -5
- data/lib/command/maintenance_off.rb +1 -0
- data/lib/command/maintenance_on.rb +1 -0
- data/lib/command/promote_app_from_upstream.rb +1 -0
- data/lib/command/ps_wait.rb +2 -10
- data/lib/command/run.rb +25 -5
- data/lib/core/config.rb +94 -0
- data/lib/core/doctor_service.rb +44 -3
- data/lib/core/maintenance_mode.rb +93 -6
- data/lib/core/template_parser.rb +43 -9
- data/lib/cpflow/version.rb +1 -1
- data/lib/generator_templates/controlplane.yml +1 -2
- data/lib/github_flow_templates/.github/cpflow-help.md +23 -1
- data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +224 -39
- metadata +33 -2
|
@@ -9,10 +9,33 @@ For your "review apps," it is convenient to have simple ENVs stored in plain tex
|
|
|
9
9
|
keep some ENVs, like the Rails' `SECRET_KEY_BASE`, out of your source code. For staging and production apps, you will
|
|
10
10
|
set these values directly at the GVC or workload levels, so none of these ENV values are committed to the source code.
|
|
11
11
|
|
|
12
|
+
## Review app secrets
|
|
13
|
+
|
|
14
|
+
Review apps run application code from pull requests. For repositories with external contributors, do not put sensitive
|
|
15
|
+
production or long-lived staging secrets in review apps. A secret reference such as `cpln://secret/...` protects the
|
|
16
|
+
value while it is stored in Control Plane configuration, but the value becomes readable by application code after it is
|
|
17
|
+
mounted into the review-app workload. Use generated dummy values, disposable databases, review-only renderer credentials,
|
|
18
|
+
and revocable service tokens for review apps.
|
|
19
|
+
|
|
12
20
|
For storing ENVs in the source code, we can use a level of indirection so that you can store an ENV value in your source
|
|
13
21
|
code like `cpln://secret/my-app-review-env-secrets.SECRET_KEY_BASE` and then have the secret value stored at the org
|
|
14
22
|
level, which applies to your GVCs mapped to that org.
|
|
15
23
|
|
|
24
|
+
Avoid pointing review-app templates at shared production secret dictionaries. If staging and review apps live in the same
|
|
25
|
+
Control Plane org, keep review-app secret dictionaries separate from persistent staging secrets, and restrict the Control
|
|
26
|
+
Plane identity bound to review workloads so it can reveal only the values those workloads need.
|
|
27
|
+
|
|
28
|
+
Review-app identity and policy templates in `.controlplane/templates/`, plus any `shared_secret_grants` in
|
|
29
|
+
`controlplane.yml`, are read from the PR branch at deploy time for same-repository PRs. `cpflow setup-app` applies
|
|
30
|
+
templates at first review-app creation, and `cpflow deploy-image` can bind configured shared-secret policies on later
|
|
31
|
+
redeploys. A PR can modify either path to bind the review workload's identity to any secret dictionary, including staging
|
|
32
|
+
ones. Each generated trigger path has its own fork guard; see
|
|
33
|
+
[Review app security for repositories with external contributors](./ci-automation.md#review-app-security-for-repositories-with-external-contributors)
|
|
34
|
+
for details. Removing any guard leaves one or more trigger paths unprotected.
|
|
35
|
+
|
|
36
|
+
For repositories with external contributors, treat the entire review-app identity and policy scope as untrusted and ensure
|
|
37
|
+
the staging token cannot reach sensitive resources even if the PR changes the template.
|
|
38
|
+
|
|
16
39
|
For setting up secrets, you'll need:
|
|
17
40
|
|
|
18
41
|
- **Org-level Secret:** This is where the values will be stored.
|
|
@@ -75,8 +98,12 @@ updated, which helps existing review apps recover after the config is added. `cp
|
|
|
75
98
|
remove those shared policy bindings when a review app is deleted.
|
|
76
99
|
|
|
77
100
|
For shared databases, keep runtime data isolated by using a per-review-app database name, schema, or tenant key. A common
|
|
78
|
-
pattern is to keep the host, user, and password in the shared secret, then have `hooks.post_creation` create the
|
|
79
|
-
database/schema
|
|
101
|
+
pattern is to keep the host, user, and password in the shared secret, then have `hooks.post_creation` create the
|
|
102
|
+
PR-specific database/schema. Avoid a generic `hooks.pre_deletion` that drops the database: `cpflow delete` runs the
|
|
103
|
+
pre-deletion hook before it removes the app workloads, so live connections can make PostgreSQL reject the drop. Stop the
|
|
104
|
+
review app workloads first, or run cleanup from trusted admin automation against the shared Postgres workload. See
|
|
105
|
+
[Share One Control Plane Postgres for Staging and Review Apps](tips.md#share-one-control-plane-postgres-for-staging-and-review-apps)
|
|
106
|
+
for the full pattern.
|
|
80
107
|
|
|
81
108
|
Here are the manual steps for reference. We recommend that you follow the steps above:
|
|
82
109
|
|
data/docs/sidebars.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
|
2
|
+
|
|
3
|
+
const sidebars: SidebarsConfig = {
|
|
4
|
+
docsSidebar: [
|
|
5
|
+
{type: 'doc', id: 'index', label: 'Documentation Guide'},
|
|
6
|
+
{type: 'doc', id: 'README', label: 'Overview'},
|
|
7
|
+
{
|
|
8
|
+
type: 'category',
|
|
9
|
+
label: 'Start Here',
|
|
10
|
+
collapsed: false,
|
|
11
|
+
items: [
|
|
12
|
+
{type: 'doc', id: 'migrating-heroku-to-control-plane', label: 'Migrate from Heroku'},
|
|
13
|
+
{type: 'doc', id: 'ci-automation', label: 'Automate GitHub Flow'},
|
|
14
|
+
{type: 'doc', id: 'ai-github-flow-prompt', label: 'AI Rollout Prompt'},
|
|
15
|
+
{type: 'doc', id: 'commands', label: 'Command Reference'},
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
type: 'category',
|
|
20
|
+
label: 'Core Operations',
|
|
21
|
+
items: [
|
|
22
|
+
'secrets-and-env-values',
|
|
23
|
+
'dns',
|
|
24
|
+
'thruster',
|
|
25
|
+
'troubleshooting',
|
|
26
|
+
'tips',
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: 'category',
|
|
31
|
+
label: 'Telemetry',
|
|
32
|
+
link: {type: 'doc', id: 'telemetry/index'},
|
|
33
|
+
items: [
|
|
34
|
+
'telemetry/collector',
|
|
35
|
+
'telemetry/application-instrumentation',
|
|
36
|
+
'telemetry/pipelines',
|
|
37
|
+
'telemetry/review-apps',
|
|
38
|
+
'grafana-opentelemetry',
|
|
39
|
+
'telemetry/troubleshooting',
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'category',
|
|
44
|
+
label: 'Data Services',
|
|
45
|
+
items: [
|
|
46
|
+
'postgres',
|
|
47
|
+
'rds-private-networking',
|
|
48
|
+
'redis',
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'category',
|
|
53
|
+
label: 'Terraform',
|
|
54
|
+
items: [
|
|
55
|
+
'terraform/overview',
|
|
56
|
+
'terraform/details',
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: 'category',
|
|
61
|
+
label: 'Project',
|
|
62
|
+
items: [
|
|
63
|
+
{type: 'doc', id: 'releasing', label: 'Releasing the Gem'},
|
|
64
|
+
{type: 'doc', id: 'changelog', label: 'Changelog'},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export default sidebars;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Application Instrumentation
|
|
2
|
+
|
|
3
|
+
Application instrumentation is the code and configuration that creates telemetry
|
|
4
|
+
signals. `cpflow` deploys the infrastructure around that instrumentation, but it
|
|
5
|
+
does not add tracing or metrics libraries to your application.
|
|
6
|
+
|
|
7
|
+
## Standard Environment Variables
|
|
8
|
+
|
|
9
|
+
Set these at the GVC level when every app workload should inherit them. Set them
|
|
10
|
+
on one workload container when only that workload should emit telemetry.
|
|
11
|
+
|
|
12
|
+
The snippets below are fragments, not complete templates. In a `cpflow`
|
|
13
|
+
template, put the `env` list under `spec.env` for GVC-level values or under the
|
|
14
|
+
target workload container's `spec.containers[].env` list for workload-only
|
|
15
|
+
values. When setting values directly in the Control Plane console or with
|
|
16
|
+
`cpln`, replace `{{APP_NAME}}` with the actual app name before applying it.
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
env:
|
|
20
|
+
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
|
21
|
+
value: "http://open-telemetry-collector.{{APP_NAME}}.cpln.local:4318"
|
|
22
|
+
- name: OTEL_EXPORTER_OTLP_PROTOCOL
|
|
23
|
+
value: "http/protobuf"
|
|
24
|
+
- name: OTEL_RESOURCE_ATTRIBUTES
|
|
25
|
+
value: "deployment.environment=staging,service.namespace=example"
|
|
26
|
+
# This GVC-level snippet intentionally omits OTEL_SERVICE_NAME.
|
|
27
|
+
# Set OTEL_SERVICE_NAME per workload container, such as example-web or
|
|
28
|
+
# example-worker, and set inheritEnv: true on each container that should
|
|
29
|
+
# receive these shared values.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Change `deployment.environment=staging` to match the real environment. When this
|
|
33
|
+
value is set at the GVC level, each target workload container must set
|
|
34
|
+
`inheritEnv: true` to receive it. Use workload container env instead when only
|
|
35
|
+
one workload should receive the telemetry settings. Keep `OTEL_SERVICE_NAME`
|
|
36
|
+
workload-specific at the container level.
|
|
37
|
+
|
|
38
|
+
The `http://` collector endpoint is intended for a collector in the same GVC with
|
|
39
|
+
`same-gvc` inbound firewall isolation. Use `https://` only when a shared
|
|
40
|
+
telemetry endpoint terminates TLS; otherwise use the internal collector hostname
|
|
41
|
+
and protocol configured for that collector.
|
|
42
|
+
|
|
43
|
+
When using HTTP transport (`http/protobuf` or `http/json`), modern stable
|
|
44
|
+
OpenTelemetry SDKs treat `OTEL_EXPORTER_OTLP_ENDPOINT` as a base URL and append
|
|
45
|
+
`/v1/traces`, `/v1/metrics`, or `/v1/logs` automatically. For OTLP over gRPC,
|
|
46
|
+
most stable SDKs expect `http://host:port` for insecure connections and
|
|
47
|
+
`https://host:port` for TLS. Some older or pre-stable SDKs accepted a bare
|
|
48
|
+
`host:port`; check your SDK's documentation. For older or pre-stable SDKs, use
|
|
49
|
+
signal-specific variables such as `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` with the
|
|
50
|
+
full signal path when needed.
|
|
51
|
+
|
|
52
|
+
`ENABLE_OPEN_TELEMETRY` is not a standard OpenTelemetry environment variable.
|
|
53
|
+
Use it only if your application code explicitly reads that flag. Otherwise, use
|
|
54
|
+
standard SDK configuration such as `OTEL_SERVICE_NAME` and
|
|
55
|
+
`OTEL_EXPORTER_OTLP_ENDPOINT`, plus your framework's instrumentation setup.
|
|
56
|
+
|
|
57
|
+
## Optional StatsD TCP Variables
|
|
58
|
+
|
|
59
|
+
Use these only when your application client can send StatsD over TCP and your
|
|
60
|
+
collector has a matching `statsd/tcp` receiver. These environment variable names
|
|
61
|
+
are examples for application code that reads them; many StatsD clients require
|
|
62
|
+
TCP transport to be configured explicitly in code.
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
env:
|
|
66
|
+
- name: STATSD_HOST
|
|
67
|
+
value: "open-telemetry-collector.{{APP_NAME}}.cpln.local"
|
|
68
|
+
- name: STATSD_PORT
|
|
69
|
+
value: "9127"
|
|
70
|
+
- name: STATSD_PROTOCOL
|
|
71
|
+
value: "tcp"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`9127` is project-specific. The StatsD protocol default is `8125/UDP`; use the
|
|
75
|
+
port your collector's `statsd/tcp` receiver is configured to bind.
|
|
76
|
+
|
|
77
|
+
When applying with `cpln` directly, replace `{{APP_NAME}}` with the actual app
|
|
78
|
+
name.
|
|
79
|
+
|
|
80
|
+
## Generic Ruby Example
|
|
81
|
+
|
|
82
|
+
This example uses deliberately generic metric names. Keep labels low-cardinality.
|
|
83
|
+
Initialize your client with TCP transport before emitting metrics. The exact
|
|
84
|
+
constructor name varies by library, but do not rely on the client's default
|
|
85
|
+
transport when the collector listens on `statsd/tcp`.
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
statsd = MyStatsDClient.new(
|
|
89
|
+
host: ENV.fetch("STATSD_HOST"),
|
|
90
|
+
port: Integer(ENV.fetch("STATSD_PORT", "9127")),
|
|
91
|
+
protocol: ENV.fetch("STATSD_PROTOCOL", "tcp")
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
statsd.increment(
|
|
95
|
+
"example.tasks.completed",
|
|
96
|
+
tags: ["task_type:background", "status:success"]
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
statsd.timing(
|
|
100
|
+
"example.jobs.duration_ms",
|
|
101
|
+
elapsed_ms,
|
|
102
|
+
tags: ["job_type:scheduled"]
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Avoid tags such as `user_id`, `request_id`, raw URLs, or free-form error
|
|
107
|
+
messages. They create high-cardinality metrics and can leak sensitive data.
|
|
108
|
+
|
|
109
|
+
## Generic Node.js Example
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
const statsdHost = process.env.STATSD_HOST;
|
|
113
|
+
if (!statsdHost) {
|
|
114
|
+
throw new Error("STATSD_HOST is not set");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const statsd = createStatsDClient({
|
|
118
|
+
host: statsdHost,
|
|
119
|
+
port: (() => {
|
|
120
|
+
const port = parseInt(process.env.STATSD_PORT || "9127", 10);
|
|
121
|
+
if (!Number.isFinite(port)) {
|
|
122
|
+
throw new Error(`Invalid STATSD_PORT: ${process.env.STATSD_PORT}`);
|
|
123
|
+
}
|
|
124
|
+
return port;
|
|
125
|
+
})(),
|
|
126
|
+
protocol: process.env.STATSD_PROTOCOL || "tcp",
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
statsd.increment("example.requests.completed", 1, {
|
|
130
|
+
route: "health_check",
|
|
131
|
+
status: "success",
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
statsd.histogram("example.worker.duration_ms", elapsedMs, {
|
|
135
|
+
worker: "default",
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Service Names
|
|
140
|
+
|
|
141
|
+
Use names that identify the workload, not a real organization, account, or user.
|
|
142
|
+
|
|
143
|
+
Good:
|
|
144
|
+
|
|
145
|
+
- `example-web`
|
|
146
|
+
- `example-worker`
|
|
147
|
+
- `example-scheduler`
|
|
148
|
+
|
|
149
|
+
Avoid:
|
|
150
|
+
|
|
151
|
+
- real organization names
|
|
152
|
+
- account names
|
|
153
|
+
- user identifiers
|
|
154
|
+
- business-specific nouns copied from one product into reusable docs
|
|
155
|
+
|
|
156
|
+
## Logs
|
|
157
|
+
|
|
158
|
+
Keep logs structured, redacted, and useful without a collector. If your
|
|
159
|
+
application sends OTLP logs, use the same collector endpoint as traces and
|
|
160
|
+
metrics. If it logs to stdout/stderr, use `cpflow logs` for live tailing and the
|
|
161
|
+
platform log backend for historical queries.
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# Collector Workload
|
|
2
|
+
|
|
3
|
+
The OpenTelemetry Collector is a telemetry router. It receives data from
|
|
4
|
+
application workloads, optionally processes or samples it, and exports it to one
|
|
5
|
+
or more backends.
|
|
6
|
+
|
|
7
|
+
The two files that must agree are:
|
|
8
|
+
|
|
9
|
+
1. The Control Plane workload template, which exposes ports.
|
|
10
|
+
2. The collector `config.yaml`, which binds receivers and exporters to those
|
|
11
|
+
same ports.
|
|
12
|
+
|
|
13
|
+
If these files disagree, telemetry can fail silently.
|
|
14
|
+
|
|
15
|
+
## Control Plane Workload Template
|
|
16
|
+
|
|
17
|
+
Add a template such as
|
|
18
|
+
`.controlplane/templates/open-telemetry-collector.yml`.
|
|
19
|
+
|
|
20
|
+
```yaml
|
|
21
|
+
kind: workload
|
|
22
|
+
name: open-telemetry-collector
|
|
23
|
+
spec:
|
|
24
|
+
type: standard
|
|
25
|
+
containers:
|
|
26
|
+
- name: open-telemetry-collector
|
|
27
|
+
image: "registry.example.com/example/open-telemetry-collector:0.155.0"
|
|
28
|
+
args:
|
|
29
|
+
- "--config=/etc/otelcol-contrib/config.yaml"
|
|
30
|
+
# Uncomment when config.yaml references ${env:TELEMETRY_BACKEND_TOKEN}.
|
|
31
|
+
# env:
|
|
32
|
+
# - name: TELEMETRY_BACKEND_TOKEN
|
|
33
|
+
# value: cpln://secret/{{APP_NAME}}-telemetry-backend.TELEMETRY_BACKEND_TOKEN
|
|
34
|
+
cpu: 100m
|
|
35
|
+
memory: 256Mi
|
|
36
|
+
ports:
|
|
37
|
+
# OTLP over HTTP from application SDKs.
|
|
38
|
+
- number: 4318
|
|
39
|
+
protocol: http
|
|
40
|
+
# StatsD over TCP, not UDP. Delete this port unless your collector
|
|
41
|
+
# config enables statsd/tcp and app clients set TCP transport.
|
|
42
|
+
# 9127 is project-specific; StatsD defaults to 8125/UDP.
|
|
43
|
+
# When deleting it, also remove receivers.statsd/tcp and statsd/tcp
|
|
44
|
+
# from service.pipelines.metrics.receivers in config.yaml.
|
|
45
|
+
- number: 9127
|
|
46
|
+
protocol: tcp
|
|
47
|
+
# Prometheus-formatted metrics exposed by the collector.
|
|
48
|
+
- number: 9292
|
|
49
|
+
protocol: http
|
|
50
|
+
defaultOptions:
|
|
51
|
+
autoscaling:
|
|
52
|
+
metric: disabled
|
|
53
|
+
minScale: 1
|
|
54
|
+
maxScale: 1
|
|
55
|
+
capacityAI: false
|
|
56
|
+
firewallConfig:
|
|
57
|
+
internal:
|
|
58
|
+
inboundAllowType: same-gvc
|
|
59
|
+
external:
|
|
60
|
+
# Prefer a narrow allowlist for production. Use the hostnames or CIDRs
|
|
61
|
+
# required by your telemetry backend.
|
|
62
|
+
outboundAllowHostname:
|
|
63
|
+
- telemetry-backend.example.com
|
|
64
|
+
# Use a collector-specific identity when the collector reads telemetry
|
|
65
|
+
# backend secrets that app workloads should not reveal.
|
|
66
|
+
identityLink: "//identity/{{APP_NAME}}-otel-collector-identity"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Use `outboundAllowCIDR` instead of `outboundAllowHostname` when your backend
|
|
70
|
+
requires IP ranges. Avoid leaving collector egress open to `0.0.0.0/0` in
|
|
71
|
+
production unless your security model explicitly accepts that risk.
|
|
72
|
+
|
|
73
|
+
Create the collector identity and a secret policy in a separate
|
|
74
|
+
`open-telemetry-collector-secrets` template before applying the workload
|
|
75
|
+
template if the collector reads backend tokens from Control Plane secrets:
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
kind: identity
|
|
79
|
+
name: "{{APP_NAME}}-otel-collector-identity"
|
|
80
|
+
description: "{{APP_NAME}}-otel-collector-identity"
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
kind: policy
|
|
84
|
+
name: "{{APP_NAME}}-otel-collector-secrets"
|
|
85
|
+
description: "{{APP_NAME}}-otel-collector-secrets"
|
|
86
|
+
bindings:
|
|
87
|
+
- permissions:
|
|
88
|
+
- reveal
|
|
89
|
+
principalLinks:
|
|
90
|
+
- "//gvc/{{APP_NAME}}/identity/{{APP_NAME}}-otel-collector-identity"
|
|
91
|
+
targetKind: secret
|
|
92
|
+
targetLinks:
|
|
93
|
+
- "//secret/{{APP_NAME}}-telemetry-backend"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Create the `{{APP_NAME}}-telemetry-backend` dictionary secret with a
|
|
97
|
+
`TELEMETRY_BACKEND_TOKEN` key, or replace that name consistently before
|
|
98
|
+
applying the templates. Keep this policy scoped to only the backend secret the
|
|
99
|
+
collector needs.
|
|
100
|
+
|
|
101
|
+
`cpflow apply-template` replaces `{{APP_NAME}}` with the actual app name. When
|
|
102
|
+
applying the YAML directly with `cpln`, replace `{{APP_NAME}}` manually before
|
|
103
|
+
running `cpln apply`.
|
|
104
|
+
|
|
105
|
+
Keep the collector image pinned to a tested release and update it as part of
|
|
106
|
+
normal dependency maintenance. Do not rely on a floating `latest` tag for
|
|
107
|
+
production workloads.
|
|
108
|
+
|
|
109
|
+
## Delivering Collector Config
|
|
110
|
+
|
|
111
|
+
The official collector image does not include your application-specific
|
|
112
|
+
`config.yaml`. A simple Control Plane pattern is to build a small collector image
|
|
113
|
+
that starts from the pinned upstream image and copies the config into the path
|
|
114
|
+
used by the workload command.
|
|
115
|
+
|
|
116
|
+
```dockerfile
|
|
117
|
+
FROM otel/opentelemetry-collector-contrib:0.155.0
|
|
118
|
+
|
|
119
|
+
COPY config.yaml /etc/otelcol-contrib/config.yaml
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Build this image, publish it to your registry, and set the workload template
|
|
123
|
+
`image:` field to that published image. The upstream image is only the base image
|
|
124
|
+
for this Dockerfile because it does not contain your `config.yaml`.
|
|
125
|
+
|
|
126
|
+
Baking the config into the image keeps collector startup deterministic. If your
|
|
127
|
+
team already manages runtime files through Control Plane, you can instead mount a
|
|
128
|
+
secret-backed file with a `cpln://secret/...` URI and `path:` entry, following
|
|
129
|
+
the same pattern used by other file-delivery templates.
|
|
130
|
+
|
|
131
|
+
## Matching Collector Config
|
|
132
|
+
|
|
133
|
+
Mount or bake the collector config at the path passed to `--config`. The contrib
|
|
134
|
+
image convention is `/etc/otelcol-contrib/config.yaml`; use a different path only
|
|
135
|
+
when your image or command is built for it.
|
|
136
|
+
|
|
137
|
+
The collector config must bind every exposed port. This minimal config receives
|
|
138
|
+
OTLP over HTTP, optionally receives StatsD over TCP, and exposes metrics for
|
|
139
|
+
Prometheus scraping.
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
receivers:
|
|
143
|
+
otlp:
|
|
144
|
+
protocols:
|
|
145
|
+
http:
|
|
146
|
+
endpoint: 0.0.0.0:4318
|
|
147
|
+
|
|
148
|
+
statsd/tcp:
|
|
149
|
+
endpoint: 0.0.0.0:9127
|
|
150
|
+
transport: tcp
|
|
151
|
+
aggregation_interval: 60s # Use 5s during initial validation.
|
|
152
|
+
|
|
153
|
+
processors:
|
|
154
|
+
memory_limiter:
|
|
155
|
+
check_interval: 1s
|
|
156
|
+
limit_percentage: 80
|
|
157
|
+
spike_limit_percentage: 20
|
|
158
|
+
|
|
159
|
+
batch: {}
|
|
160
|
+
|
|
161
|
+
exporters:
|
|
162
|
+
prometheus:
|
|
163
|
+
endpoint: 0.0.0.0:9292
|
|
164
|
+
|
|
165
|
+
# Replace this placeholder with your real trace/log backend.
|
|
166
|
+
otlphttp/backend:
|
|
167
|
+
endpoint: "https://telemetry-backend.example.com"
|
|
168
|
+
# This is a base URL. The exporter appends /v1/traces, /v1/metrics,
|
|
169
|
+
# and /v1/logs automatically. Do not include the signal path here.
|
|
170
|
+
# headers:
|
|
171
|
+
# Authorization: "Bearer ${env:TELEMETRY_BACKEND_TOKEN}"
|
|
172
|
+
|
|
173
|
+
# Optional validation-only exporter. Do not leave debug in production
|
|
174
|
+
# pipelines because it writes telemetry payloads to collector logs.
|
|
175
|
+
# debug:
|
|
176
|
+
# verbosity: basic
|
|
177
|
+
|
|
178
|
+
service:
|
|
179
|
+
pipelines:
|
|
180
|
+
traces:
|
|
181
|
+
receivers: [otlp]
|
|
182
|
+
processors: [memory_limiter, batch]
|
|
183
|
+
exporters: [otlphttp/backend]
|
|
184
|
+
|
|
185
|
+
metrics:
|
|
186
|
+
receivers: [otlp, statsd/tcp]
|
|
187
|
+
processors: [memory_limiter, batch]
|
|
188
|
+
exporters: [prometheus, otlphttp/backend]
|
|
189
|
+
|
|
190
|
+
logs:
|
|
191
|
+
receivers: [otlp]
|
|
192
|
+
processors: [memory_limiter, batch]
|
|
193
|
+
exporters: [otlphttp/backend]
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Before applying the config, replace `telemetry-backend.example.com` with your
|
|
197
|
+
real backend endpoint and headers. Keep `memory_limiter` before `batch`, and
|
|
198
|
+
keep the Prometheus exporter only when your platform or backend scrapes collector
|
|
199
|
+
metrics from port `9292`. For push-only metrics backends, remove `prometheus`
|
|
200
|
+
from the metrics pipeline and keep `otlphttp/backend`. If your application only
|
|
201
|
+
sends OTLP metrics and does not use StatsD, also remove the `statsd/tcp`
|
|
202
|
+
receiver block and `statsd/tcp` from the `metrics.receivers` list. See
|
|
203
|
+
[StatsD and UDP](#statsd-and-udp) for the full three-piece removal.
|
|
204
|
+
|
|
205
|
+
If you have no OTLP push backend at all, remove `otlphttp/backend` from the
|
|
206
|
+
traces, metrics, and logs pipelines and delete the `otlphttp/backend` exporter
|
|
207
|
+
block entirely.
|
|
208
|
+
|
|
209
|
+
Store backend tokens such as `TELEMETRY_BACKEND_TOKEN` in Control Plane secrets
|
|
210
|
+
and bind them only to the collector workload identity shown in the template
|
|
211
|
+
above. Create the collector identity and a policy that grants `reveal` on only
|
|
212
|
+
the telemetry backend secret before applying the collector workload. When the
|
|
213
|
+
collector config references `${env:TELEMETRY_BACKEND_TOKEN}`, also add the
|
|
214
|
+
matching `env` entry to the collector workload so Control Plane injects the
|
|
215
|
+
secret value at startup. Use the app identity placeholder only when the collector
|
|
216
|
+
does not need secrets that are isolated from app workloads. See
|
|
217
|
+
[Secrets and ENV Values](../secrets-and-env-values.md) for the recommended
|
|
218
|
+
pattern.
|
|
219
|
+
|
|
220
|
+
The Prometheus exporter exposes an unauthenticated scrape endpoint. With
|
|
221
|
+
`same-gvc` firewall isolation, any workload in the same GVC that can reach port
|
|
222
|
+
`9292` can read exported metrics. Widen collector inbound access only when that
|
|
223
|
+
is acceptable.
|
|
224
|
+
|
|
225
|
+
Port `9292` is an example scrape port. Configure your scraper to match the port
|
|
226
|
+
you expose in the workload template and bind in the collector config.
|
|
227
|
+
|
|
228
|
+
## StatsD and UDP
|
|
229
|
+
|
|
230
|
+
The upstream OpenTelemetry StatsD receiver defaults to UDP on port `8125`.
|
|
231
|
+
Control Plane workload templates in this repository currently use `http` and
|
|
232
|
+
`tcp` ports, not `udp`. Do not document or deploy UDP StatsD on Control Plane
|
|
233
|
+
unless you have verified that the platform supports the required UDP forwarding
|
|
234
|
+
path for your workload.
|
|
235
|
+
|
|
236
|
+
Safer defaults:
|
|
237
|
+
|
|
238
|
+
1. Prefer OTLP metrics over HTTP on `4318`.
|
|
239
|
+
2. Use StatsD over TCP only when your app client and collector build support it.
|
|
240
|
+
3. Treat UDP StatsD as an environment-specific advanced option.
|
|
241
|
+
|
|
242
|
+
If you remove StatsD, remove all three pieces together: the workload port, the
|
|
243
|
+
`statsd/tcp` receiver block, and `statsd/tcp` from the metrics pipeline
|
|
244
|
+
receivers.
|
|
245
|
+
|
|
246
|
+
## `controlplane.yml`
|
|
247
|
+
|
|
248
|
+
Include the collector in setup and informational commands:
|
|
249
|
+
|
|
250
|
+
```yaml
|
|
251
|
+
aliases:
|
|
252
|
+
common: &common
|
|
253
|
+
setup_app_templates:
|
|
254
|
+
- app
|
|
255
|
+
- worker
|
|
256
|
+
- open-telemetry-collector-secrets
|
|
257
|
+
- open-telemetry-collector
|
|
258
|
+
|
|
259
|
+
app_workloads:
|
|
260
|
+
- app
|
|
261
|
+
- worker
|
|
262
|
+
|
|
263
|
+
additional_workloads:
|
|
264
|
+
- open-telemetry-collector
|
|
265
|
+
|
|
266
|
+
apps:
|
|
267
|
+
example:
|
|
268
|
+
<<: *common
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Use `open-telemetry-collector-secrets` for the identity and policy YAML shown
|
|
272
|
+
above. List it before `open-telemetry-collector` so `cpflow setup-app` creates
|
|
273
|
+
the collector identity and secret reveal policy before applying the workload
|
|
274
|
+
that references `identityLink`.
|
|
275
|
+
|
|
276
|
+
Keep `app_workloads` limited to real application workloads such as `app` and
|
|
277
|
+
`worker`. Put the collector under `additional_workloads` so informational
|
|
278
|
+
commands show it without treating it as an app process.
|
|
279
|
+
|
|
280
|
+
For an existing app, apply the identity and policy template before applying the
|
|
281
|
+
collector workload:
|
|
282
|
+
|
|
283
|
+
```sh
|
|
284
|
+
cpflow apply-template open-telemetry-collector-secrets -a $APP_NAME
|
|
285
|
+
cpflow apply-template open-telemetry-collector -a $APP_NAME
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
For a brand-new app, `cpflow setup-app` applies both collector templates with
|
|
289
|
+
the other entries listed in `setup_app_templates`, in the order shown above.
|
|
290
|
+
|
|
291
|
+
## Port Agreement Checklist
|
|
292
|
+
|
|
293
|
+
The Control Plane workload template and collector config must agree on every
|
|
294
|
+
exposed port. See
|
|
295
|
+
[Verify Port Agreement](troubleshooting.md#4-verify-port-agreement) for the
|
|
296
|
+
canonical port checklist. Remove any workload port that is not enabled in the
|
|
297
|
+
collector config.
|