Skip to content

Commit 2a1b3e0

Browse files
authored
Merge pull request #19776 from dvdksn/initial-otel-support
cli: document initial otel support and usage
2 parents 27350ce + 003c6fe commit 2a1b3e0

File tree

4 files changed

+204
-31
lines changed

4 files changed

+204
-31
lines changed

.github/vale/config/vocabularies/Docker/accept.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ GeoNetwork
5454
Git
5555
GitHub( Actions)?
5656
Google
57+
Grafana
5758
HTTP
5859
HyperKit
5960
IPs?
@@ -77,6 +78,7 @@ Nginx
7778
Nuxeo
7879
OAuth
7980
OCI
81+
OTel
8082
Okta
8183
Postgres
8284
PowerShell

content/config/otel.md

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
---
2+
title: OpenTelemetry for the Docker CLI
3+
description: Learn about how to capture OpenTelemetry metrics for the Docker command line
4+
keywords: otel, opentelemetry, telemetry, traces, tracing, metrics, logs
5+
---
6+
7+
{{< introduced engine 26.1.0 >}}
8+
9+
The Docker CLI supports [OpenTelemetry](https://opentelemetry.io/docs/) instrumentation
10+
for emitting metrics about command invocations. This is disabled by default.
11+
You can configure the CLI to start emitting metrics to the endpoint that you
12+
specify. This allows you to capture information about your `docker` command
13+
invocations for more insight into your Docker usage.
14+
15+
Exporting metrics is opt-in, and you control where data is being sent by
16+
specifying the destination address of the metrics collector.
17+
18+
## What is OpenTelemetry?
19+
20+
OpenTelemetry, or OTel for short, is an open observability framework for
21+
creating and managing telemetry data, such as traces, metrics, and logs.
22+
OpenTelemetry is vendor- and tool-agnostic, meaning that it can be used with a
23+
broad variety of Observability backends.
24+
25+
Support for OpenTelemetry instrumentation in the Docker CLI means that the CLI can emit
26+
information about events that take place, using the protocols and conventions
27+
defined in the Open Telemetry specification.
28+
29+
## How it works
30+
31+
The Docker CLI doesn't emit telemetry data by default. Only if you've set an
32+
environment variable on your system will Docker CLI attempt to emit OpenTelemetry
33+
metrics, to the endpoint that you specify.
34+
35+
```bash
36+
DOCKER_CLI_OTEL_EXPORTER_OTLP_ENDPOINT=<endpoint>
37+
```
38+
39+
The variable specifies the endpoint of an OpenTelemetry collector, where telemetry data
40+
about `docker` CLI invocation should be sent. To capture the data, you'll need
41+
an OpenTelemetry collector listening on that endpoint.
42+
43+
The purpose of a collector is to receive the telemetry data, process it, and
44+
exports it to a backend. The backend is where the telemetry data gets stored.
45+
You can choose from a number of different backends, such as Prometheus or
46+
InfluxDB.
47+
48+
Some backends provide tools for visualizing the metrics directly.
49+
Alternatively, you can also run a dedicated frontend with support for
50+
generating more useful graphs, such as Grafana.
51+
52+
## Setup
53+
54+
To get started capturing telemetry data for the Docker CLI, you'll need to:
55+
56+
- Set the `DOCKER_CLI_OTEL_EXPORTER_OTLP_ENDPOINT` environment variable to point to an OpenTelemetry collector endpoint
57+
- Run an OpenTelemetry collector that receives the signals from CLI command invocations
58+
- Run a backend for storing the data received from the collector
59+
60+
The following Docker Compose file bootstraps a set of services to get started with OpenTelemetry.
61+
It includes an OpenTelemetry collector that the CLI can send metrics to,
62+
and a Prometheus backend that scrapes the metrics off the collector.
63+
64+
```yaml {collapse=true,title=compose.yml}
65+
name: cli-otel
66+
services:
67+
prometheus:
68+
image: prom/prometheus
69+
command:
70+
- "--config.file=/etc/prometheus/prom.yml"
71+
ports:
72+
# Publish the Prometheus frontend on localhost:9091
73+
- 9091:9090
74+
restart: always
75+
volumes:
76+
# Store Prometheus data in a volume:
77+
- prom_data:/prometheus
78+
# Mount the prom.yml config file
79+
- ./prom.yml:/etc/prometheus/prom.yml
80+
otelcol:
81+
image: otel/opentelemetry-collector
82+
restart: always
83+
depends_on:
84+
- prometheus
85+
ports:
86+
- 4317:4317
87+
volumes:
88+
# Mount the otelcol.yml config file
89+
- ./otelcol.yml:/etc/otelcol/config.yaml
90+
91+
volumes:
92+
prom_data:
93+
```
94+
95+
This service assumes that the following two configuration files exist alongside
96+
`compose.yml`:
97+
98+
- ```yaml {collapse=true,title=otelcol.yml}
99+
# Receive signals over gRPC and HTTP
100+
receivers:
101+
otlp:
102+
protocols:
103+
grpc:
104+
http:
105+
106+
# Establish an endpoint for Prometheus to scrape from
107+
exporters:
108+
prometheus:
109+
endpoint: "0.0.0.0:8889"
110+
111+
service:
112+
pipelines:
113+
metrics:
114+
receivers: [otlp]
115+
exporters: [prometheus]
116+
```
117+
118+
- ```yaml {collapse=true,title=prom.yml}
119+
# Configure Prometheus to scrape the OpenTelemetry collector endpoint
120+
scrape_configs:
121+
- job_name: "otel-collector"
122+
scrape_interval: 1s
123+
static_configs:
124+
- targets: ["otelcol:8889"]
125+
```
126+
127+
With these files in place:
128+
129+
1. Start the Docker Compose services:
130+
131+
```console
132+
$ docker compose up
133+
```
134+
135+
2. Configure Docker CLI to export telemetry to the OpenTelemetry collector.
136+
137+
```console
138+
$ export DOCKER_CLI_OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
139+
```
140+
141+
3. Run a `docker` command to trigger the CLI into sending a metric signal to
142+
the OpenTelemetry collector.
143+
144+
```console
145+
$ docker version
146+
```
147+
148+
4. To view telemetry metrics created by the CLI, open the Prometheus expression
149+
browser by going to <http://localhost:9091/graph>.
150+
151+
5. In the **Query** field, enter `command_time_milliseconds_total`, and execute
152+
the query to see the telemetry data.
153+
154+
## Available metrics
155+
156+
Docker CLI currently exports a single metric, `command.time`, which measures
157+
the execution duration of a command in milliseconds. This metric has the
158+
following attributes:
159+
160+
- `command.name`: the name of the command
161+
- `command.status.code`: the exit code of the command
162+
- `command.stderr.isatty`: true if stderr is attached to a TTY
163+
- `command.stdin.isatty`: true if stdin is attached to a TTY
164+
- `command.stdout.isatty`: true if stdout is attached to a TTY

data/toc.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,8 @@ Manuals:
15701570
title: Filter commands
15711571
- path: /config/formatting/
15721572
title: Format command and log output
1573+
- path: /config/otel/
1574+
title: OpenTelemetry
15731575
- sectiontitle: Manage resources
15741576
section:
15751577
- path: /config/pruning/
Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,41 @@
1-
<div class="group relative">
2-
<button x-data="{ code: '{{encoding.Base64Encode .Inner}}', copying: false }"
3-
class="absolute right-3 top-3 z-20 text-gray-light-300 dark:text-gray-dark-600" title="Copy" @click="window.navigator.clipboard.writeText(atob(code).replaceAll(/^\$\s*/gm, ''));
4-
copying = true;
5-
setTimeout(() => copying = false, 2000);">
6-
<span :class="{ 'group-hover:block' : !copying }" class="icon-svg hidden">{{ partialCached "icon" "content_copy" "content_copy" }}</span>
7-
<span :class="{ 'group-hover:block' : copying }" class="icon-svg hidden">{{ partialCached "icon" "check_circle" "check_circle" }}</span>
8-
</button>
9-
{{ $lang := .Type | default "text" }} {{ $result := transform.Highlight .Inner
10-
$lang .Options }}
11-
<div class="syntax-light dark:syntax-dark">
12-
{{ with .Attributes.collapse }}
13-
<div x-data="{ collapse: true }" class="relative scroll-mt-20 overflow-clip"
14-
x-init="$watch('collapse', value => $root.scrollIntoView({ behavior: 'smooth'}))">
15-
<div x-show="collapse"
16-
class="to-transparent absolute z-10 flex h-32 w-full flex-col-reverse items-center overflow-clip bg-gradient-to-t from-background-light to-75% bg-cover pb-4 dark:from-background-dark">
17-
<button @click="collapse = false"
18-
class="flex items-center rounded-full bg-blue-light px-2 text-sm text-white dark:bg-blue-dark-400">
19-
<span>Show more</span>
20-
<span class="icon-svg">{{ partialCached "icon" "expand_more" "expand_more" }}</span>
21-
</button>
1+
<div class="scroll-mt-20" x-data x-ref="root">
2+
{{ with .Attributes.title }}
3+
<div class="text-sm -mb-3 text-gray-light dark:text-gray-dark">{{ . }}</div>
4+
{{ end }}
5+
<div class="group relative">
6+
<button x-data="{ code: '{{encoding.Base64Encode .Inner}}', copying: false }"
7+
class="absolute right-3 top-3 z-20 text-gray-light-300 dark:text-gray-dark-600" title="Copy" @click="window.navigator.clipboard.writeText(atob(code).replaceAll(/^\$\s*/gm, ''));
8+
copying = true;
9+
setTimeout(() => copying = false, 2000);">
10+
<span :class="{ 'group-hover:block' : !copying }" class="icon-svg hidden">{{ partialCached "icon" "content_copy" "content_copy" }}</span>
11+
<span :class="{ 'group-hover:block' : copying }" class="icon-svg hidden">{{ partialCached "icon" "check_circle" "check_circle" }}</span>
12+
</button>
13+
{{ $lang := .Type | default "text" }} {{ $result := transform.Highlight .Inner
14+
$lang .Options }}
15+
<div class="syntax-light dark:syntax-dark">
16+
{{ with .Attributes.collapse }}
17+
<div x-data="{ collapse: true }" class="relative overflow-clip"
18+
x-init="$watch('collapse', value => $refs.root.scrollIntoView({ behavior: 'smooth'}))">
19+
<div x-show="collapse"
20+
class="to-transparent absolute z-10 flex h-32 w-full flex-col-reverse items-center overflow-clip bg-gradient-to-t from-background-light to-75% bg-cover pb-4 dark:from-background-dark">
21+
<button @click="collapse = false"
22+
class="flex items-center rounded-full bg-blue-light px-2 text-sm text-white dark:bg-blue-dark-400">
23+
<span>Show more</span>
24+
<span class="icon-svg">{{ partialCached "icon" "expand_more" "expand_more" }}</span>
25+
</button>
26+
</div>
27+
<div :class="{ 'h-32': collapse }">
28+
{{ $result }}
29+
<button @click="collapse = true" x-show="!collapse"
30+
class="mx-auto -mt-4 flex items-center rounded-b-lg bg-blue-light px-2 text-sm text-white dark:bg-blue-dark-400">
31+
<span>Hide</span>
32+
<span class="icon-svg">{{ partialCached "icon" "expand_less" "expand_less" }}</span>
33+
</button>
34+
</div>
2235
</div>
23-
<div :class="{ 'h-32': collapse }">
36+
{{ else }}
2437
{{ $result }}
25-
<button @click="collapse = true" x-show="!collapse"
26-
class="mx-auto -mt-4 flex items-center rounded-b-lg bg-blue-light px-2 text-sm text-white dark:bg-blue-dark-400">
27-
<span>Hide</span>
28-
<span class="icon-svg">{{ partialCached "icon" "expand_less" "expand_less" }}</span>
29-
</button>
30-
</div>
38+
{{ end }}
3139
</div>
32-
{{ else }}
33-
{{ $result }}
34-
{{ end }}
3540
</div>
3641
</div>

0 commit comments

Comments
 (0)