Skip to content

Commit c8cb916

Browse files
authored
feat: OpenTelemetry module integration (#9062)
* OpenTelemetry module integration * e2e test * e2e test fix * default OpentelemetryConfig * e2e values * mount otel module for otel test only * propagate IS_CHROOT * propagate IS_CHROOT e2e test * code doc * comments * golint * opentelemetry doc * zipkin * zipkin * typo * update e2e test OpenTelemetry value * use opentelemetry value * revert merge conflict * fix * format * review comments * clean
1 parent c075793 commit c8cb916

File tree

23 files changed

+1131
-2
lines changed

23 files changed

+1131
-2
lines changed

docs/images/otel-grafana-demo.png

121 KB
Loading

docs/images/otel-jaeger-demo.png

117 KB
Loading

docs/images/otel-zipkin-demo.png

131 KB
Loading

docs/kubectl-plugin.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ modsecurity
208208
modules
209209
nginx.conf
210210
opentracing.json
211+
opentelemetry.toml
211212
owasp-modsecurity-crs
212213
template
213214
```

docs/user-guide/nginx-configuration/annotations.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
121121
|[nginx.ingress.kubernetes.io/enable-access-log](#enable-access-log)|"true" or "false"|
122122
|[nginx.ingress.kubernetes.io/enable-opentracing](#enable-opentracing)|"true" or "false"|
123123
|[nginx.ingress.kubernetes.io/opentracing-trust-incoming-span](#opentracing-trust-incoming-span)|"true" or "false"|
124+
|[nginx.ingress.kubernetes.io/enable-opentelemetry](#enable-opentelemetry)|"true" or "false"|
125+
|[nginx.ingress.kubernetes.io/opentelemetry-trust-incoming-span](#opentelemetry-trust-incoming-spans)|"true" or "false"|
124126
|[nginx.ingress.kubernetes.io/enable-influxdb](#influxdb)|"true" or "false"|
125127
|[nginx.ingress.kubernetes.io/influxdb-measurement](#influxdb)|string|
126128
|[nginx.ingress.kubernetes.io/influxdb-port](#influxdb)|string|
@@ -821,6 +823,24 @@ sometimes need to be overridden to enable it or disable it for a specific ingres
821823
nginx.ingress.kubernetes.io/opentracing-trust-incoming-span: "true"
822824
```
823825

826+
### Enable Opentelemetry
827+
828+
Opentelemetry can be enabled or disabled globally through the ConfigMap but this will sometimes need to be overridden
829+
to enable it or disable it for a specific ingress (e.g. to turn off telemetry of external health check endpoints)
830+
831+
```yaml
832+
nginx.ingress.kubernetes.io/enable-opentelemetry: "true"
833+
```
834+
835+
### Opentelemetry Trust Incoming Span
836+
837+
The option to trust incoming trace spans can be enabled or disabled globally through the ConfigMap but this will
838+
sometimes need to be overridden to enable it or disable it for a specific ingress (e.g. only enable on a private endpoint)
839+
840+
```yaml
841+
nginx.ingress.kubernetes.io/opentelemetry-trust-incoming-spans: "true"
842+
```
843+
824844
### X-Forwarded-Prefix Header
825845
To add the non-standard `X-Forwarded-Prefix` header to the upstream request with a string value, the following annotation can be used:
826846

docs/user-guide/nginx-configuration/configmap.md

100755100644
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,19 @@ The following table shows a configuration option's name, type, and the default v
157157
|[datadog-operation-name-override](#datadog-operation-name-override)|string|"nginx.handle"|
158158
|[datadog-priority-sampling](#datadog-priority-sampling)|bool|"true"|
159159
|[datadog-sample-rate](#datadog-sample-rate)|float|1.0|
160+
|[enable-opentelemetry](#enable-opentelemetry)|bool|"false"|
161+
|[opentelemetry-trust-incoming-span](#opentelemetry-trust-incoming-span)|bool|"true"|
162+
|[opentelemetry-operation-name](#opentelemetry-operation-name)|string|""|
163+
|[opentelemetry-config](#/etc/nginx/opentelemetry.toml)|string|"/etc/nginx/opentelemetry.toml"|
164+
|[otlp-collector-host](#otlp-collector-host)|string|""|
165+
|[otlp-collector-port](#otlp-collector-port)|int|4317|
166+
|[otel-max-queuesize](#otel-max-queuesize)|int||
167+
|[otel-schedule-delay-millis](#otel-schedule-delay-millis)|int||
168+
|[otel-max-export-batch-size](#otel-max-export-batch-size)|int||
169+
|[otel-service-name](#otel-service-name)|string|"nginx"|
170+
|[otel-sampler](#otel-sampler)|string|"AlwaysOff"|
171+
|[otel-sampler-parent-based](#otel-sampler-parent-based)|bool|"false"|
172+
|[otel-sampler-ratio](#otel-sampler-ratio)|float|0.01|
160173
|[main-snippet](#main-snippet)|string|""|
161174
|[http-snippet](#http-snippet)|string|""|
162175
|[server-snippet](#server-snippet)|string|""|
@@ -1009,6 +1022,46 @@ If true disables client-side sampling (thus ignoring `sample_rate`) and enables
10091022
Specifies sample rate for any traces created.
10101023
This is effective only when `datadog-priority-sampling` is `false` _**default:**_ 1.0
10111024

1025+
## enable-opentelemetry
1026+
1027+
Enables the nginx OpenTelemetry extension. _**default:**_ is disabled
1028+
1029+
_References:_
1030+
[https://github.com/open-telemetry/opentelemetry-cpp-contrib](https://github.com/open-telemetry/opentelemetry-cpp-contrib/tree/main/instrumentation/nginx)
1031+
1032+
## opentelemetry-operation-name
1033+
1034+
Specifies a custom name for the server span. _**default:**_ is empty
1035+
1036+
For example, set to "HTTP $request_method $uri".
1037+
1038+
## otlp-collector-host
1039+
1040+
Specifies the host to use when uploading traces. It must be a valid URL.
1041+
1042+
## otlp-collector-port
1043+
1044+
Specifies the port to use when uploading traces. _**default:**_ 4317
1045+
1046+
## otel-service-name
1047+
1048+
Specifies the service name to use for any traces created. _**default:**_ nginx
1049+
1050+
## opentelemetry-trust-incoming-span: "true"
1051+
Enables or disables using spans from incoming requests as parent for created ones. _**default:**_ true
1052+
1053+
## otel-sampler-parent-based
1054+
1055+
Uses sampler implementation which by default will take a sample if parent Activity is sampled. _**default:**_ false
1056+
1057+
## otel-sampler-ratio
1058+
1059+
Specifies sample rate for any traces created. _**default:**_ 0.01
1060+
1061+
## otel-sampler
1062+
1063+
Specifies the sampler to be used when sampling traces. The available samplers are: AlwaysOff, AlwaysOn, TraceIdRatioBased, remote. _**default:**_ AlwaysOff
1064+
10121065
## main-snippet
10131066

10141067
Adds custom configuration to the main section of the nginx configuration.
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
# OpenTelemetry
2+
3+
Enables requests served by NGINX for distributed telemetry via The OpenTelemetry Project.
4+
5+
Using the third party module [opentelemetry-cpp-contrib/nginx](https://github.com/open-telemetry/opentelemetry-cpp-contrib/tree/main/instrumentation/nginx) the NGINX ingress controller can configure NGINX to enable [OpenTelemetry](http://opentelemetry.io) instrumentation.
6+
By default this feature is disabled.
7+
8+
## Usage
9+
10+
To enable the instrumentation we must enable OpenTelemetry in the configuration ConfigMap:
11+
```yaml
12+
data:
13+
enable-opentelemetry: "true"
14+
```
15+
16+
To enable or disable instrumentation for a single Ingress, use
17+
the `enable-opentelemetry` annotation:
18+
```yaml
19+
kind: Ingress
20+
metadata:
21+
annotations:
22+
nginx.ingress.kubernetes.io/enable-opentelemetry: "true"
23+
```
24+
25+
We must also set the host to use when uploading traces:
26+
27+
```yaml
28+
otlp-collector-host: "otel-coll-collector.otel.svc"
29+
```
30+
NOTE: While the option is called `otlp-collector-host`, you will need to point this to any backend that recieves otlp-grpc.
31+
32+
Next you will need to deploy a distributed telemetry system which uses OpenTelemetry.
33+
[opentelemetry-collector](https://github.com/open-telemetry/opentelemetry-collector), [Jaeger](https://www.jaegertracing.io/)
34+
[Tempo](https://github.com/grafana/tempo), and [zipkin](https://zipkin.io/)
35+
have been tested.
36+
37+
Other optional configuration options:
38+
```yaml
39+
# specifies the name to use for the server span
40+
opentelemetry-operation-name
41+
42+
# sets whether or not to trust incoming telemetry spans
43+
opentelemetry-trust-incoming-span
44+
45+
# specifies the port to use when uploading traces, Default: 4317
46+
otlp-collector-port
47+
48+
# specifies the service name to use for any traces created, Default: nginx
49+
otel-service-name
50+
51+
# The maximum queue size. After the size is reached data are dropped.
52+
otel-max-queuesize
53+
54+
# The delay interval in milliseconds between two consecutive exports.
55+
otel-schedule-delay-millis
56+
57+
# How long the export can run before it is cancelled.
58+
otel-schedule-delay-millis
59+
60+
# The maximum batch size of every export. It must be smaller or equal to maxQueueSize.
61+
otel-max-export-batch-size
62+
63+
# specifies sample rate for any traces created, Default: 0.01
64+
otel-sampler-ratio
65+
66+
# specifies the sampler to be used when sampling traces.
67+
# The available samplers are: AlwaysOn, AlwaysOff, TraceIdRatioBased, Default: AlwaysOff
68+
otel-sampler
69+
70+
# Uses sampler implementation which by default will take a sample if parent Activity is sampled, Default: false
71+
otel-sampler-parent-based
72+
```
73+
74+
Note that you can also set whether to trust incoming spans (global default is true) per-location using annotations like the following:
75+
```yaml
76+
kind: Ingress
77+
metadata:
78+
annotations:
79+
nginx.ingress.kubernetes.io/opentelemetry-trust-incoming-span: "true"
80+
```
81+
82+
## Examples
83+
84+
The following examples show how to deploy and test different distributed telemetry systems. These example can be performed using Docker Desktop.
85+
86+
In the [esigo/nginx-example](https://github.com/esigo/nginx-example)
87+
GitHub repository is an example of a simple hello service:
88+
89+
```mermaid
90+
graph TB
91+
subgraph Browser
92+
start["http://esigo.dev/hello/nginx"]
93+
end
94+
95+
subgraph app
96+
sa[service-a]
97+
sb[service-b]
98+
sa --> |name: nginx| sb
99+
sb --> |hello nginx!| sa
100+
end
101+
102+
subgraph otel
103+
otc["Otel Collector"]
104+
end
105+
106+
subgraph observability
107+
tempo["Tempo"]
108+
grafana["Grafana"]
109+
backend["Jaeger"]
110+
zipkin["Zipkin"]
111+
end
112+
113+
subgraph ingress-nginx
114+
ngx[nginx]
115+
end
116+
117+
subgraph ngx[nginx]
118+
ng[nginx]
119+
om[OpenTelemetry module]
120+
end
121+
122+
subgraph Node
123+
app
124+
otel
125+
observability
126+
ingress-nginx
127+
om --> |otlp-gRPC| otc --> |jaeger| backend
128+
otc --> |zipkin| zipkin
129+
otc --> |otlp-gRPC| tempo --> grafana
130+
sa --> |otlp-gRPC| otc
131+
sb --> |otlp-gRPC| otc
132+
start --> ng --> sa
133+
end
134+
```
135+
136+
To install the example and collectors run:
137+
138+
1. Enable Ingress addon with:
139+
140+
```yaml
141+
opentelemetry:
142+
enabled: true
143+
image: registry.k8s.io/ingress-nginx/opentelemetry:v20230107-helm-chart-4.4.2-2-g96b3d2165@sha256:331b9bebd6acfcd2d3048abbdd86555f5be76b7e3d0b5af4300b04235c6056c9
144+
containerSecurityContext:
145+
allowPrivilegeEscalation: false
146+
```
147+
148+
2. Enable OpenTelemetry and set the otlp-collector-host:
149+
150+
```yaml
151+
$ echo '
152+
apiVersion: v1
153+
kind: ConfigMap
154+
data:
155+
enable-opentelemetry: "true"
156+
opentelemetry-config: "/etc/nginx/opentelemetry.toml"
157+
opentelemetry-operation-name: "HTTP $request_method $service_name $uri"
158+
opentelemetry-trust-incoming-span: "true"
159+
otlp-collector-host: "otel-coll-collector.otel.svc"
160+
otlp-collector-port: "4317"
161+
otel-max-queuesize: "2048"
162+
otel-schedule-delay-millis: "5000"
163+
otel-max-export-batch-size: "512"
164+
otel-service-name: "nginx-proxy" # Opentelemetry resource name
165+
otel-sampler: "AlwaysOn" # Also: AlwaysOff, TraceIdRatioBased
166+
otel-sampler-ratio: "1.0"
167+
otel-sampler-parent-based: "false"
168+
metadata:
169+
name: ingress-nginx-controller
170+
namespace: ingress-nginx
171+
' | kubectl replace -f -
172+
```
173+
174+
4. Deploy otel-collector, grafana and Jaeger backend:
175+
176+
```bash
177+
# add helm charts needed for grafana and OpenTelemetry collector
178+
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
179+
helm repo add grafana https://grafana.github.io/helm-charts
180+
helm repo update
181+
# deply cert-manager needed for OpenTelemetry collector operator
182+
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.yaml
183+
# create observability namespace
184+
kubectl apply -f https://raw.githubusercontent.com/esigo/nginx-example/main/observability/namespace.yaml
185+
# install OpenTelemetry collector operator
186+
helm upgrade --install otel-collector-operator -n otel --create-namespace open-telemetry/opentelemetry-operator
187+
# deploy OpenTelemetry collector
188+
kubectl apply -f https://raw.githubusercontent.com/esigo/nginx-example/main/observability/collector.yaml
189+
# deploy Jaeger all-in-one
190+
kubectl apply -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.37.0/jaeger-operator.yaml -n observability
191+
kubectl apply -f https://raw.githubusercontent.com/esigo/nginx-example/main/observability/jaeger.yaml -n observability
192+
# deploy zipkin
193+
kubectl apply -f https://raw.githubusercontent.com/esigo/nginx-example/main/observability/zipkin.yaml -n observability
194+
# deploy tempo and grafana
195+
helm upgrade --install tempo grafana/tempo --create-namespace -n observability
196+
helm upgrade -f https://raw.githubusercontent.com/esigo/nginx-example/main/observability/grafana/grafana-values.yaml --install grafana grafana/grafana --create-namespace -n observability
197+
```
198+
199+
3. Build and deploy demo app:
200+
201+
```bash
202+
# build images
203+
make images
204+
205+
# deploy demo app:
206+
make deploy-app
207+
```
208+
209+
5. Make a few requests to the Service:
210+
211+
```bash
212+
kubectl port-forward --namespace=ingress-nginx service/ingress-nginx-controller 8090:80
213+
curl http://esigo.dev:8090/hello/nginx
214+
215+
216+
StatusCode : 200
217+
StatusDescription : OK
218+
Content : {"v":"hello nginx!"}
219+
220+
RawContent : HTTP/1.1 200 OK
221+
Connection: keep-alive
222+
Content-Length: 21
223+
Content-Type: text/plain; charset=utf-8
224+
Date: Mon, 10 Oct 2022 17:43:33 GMT
225+
226+
{"v":"hello nginx!"}
227+
228+
Forms : {}
229+
Headers : {[Connection, keep-alive], [Content-Length, 21], [Content-Type, text/plain; charset=utf-8], [Date,
230+
Mon, 10 Oct 2022 17:43:33 GMT]}
231+
Images : {}
232+
InputFields : {}
233+
Links : {}
234+
ParsedHtml : System.__ComObject
235+
RawContentLength : 21
236+
```
237+
238+
6. View the Grafana UI:
239+
240+
```bash
241+
kubectl port-forward --namespace=observability service/grafana 3000:80
242+
```
243+
In the Grafana interface we can see the details:
244+
![grafana screenshot](../../images/otel-grafana-demo.png "grafana screenshot")
245+
246+
7. View the Jaeger UI:
247+
248+
```bash
249+
kubectl port-forward --namespace=observability service/jaeger-all-in-one-query 16686:16686
250+
```
251+
In the Jaeger interface we can see the details:
252+
![Jaeger screenshot](../../images/otel-jaeger-demo.png "Jaeger screenshot")
253+
254+
8. View the Zipkin UI:
255+
256+
```bash
257+
kubectl port-forward --namespace=observability service/zipkin 9411:9411
258+
```
259+
In the Zipkin interface we can see the details:
260+
![zipkin screenshot](../../images/otel-zipkin-demo.png "zipkin screenshot")

internal/ingress/annotations/annotations.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/imdario/mergo"
2121
"k8s.io/ingress-nginx/internal/ingress/annotations/canary"
2222
"k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity"
23+
"k8s.io/ingress-nginx/internal/ingress/annotations/opentelemetry"
2324
"k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl"
2425
"k8s.io/ingress-nginx/internal/ingress/annotations/sslcipher"
2526
"k8s.io/ingress-nginx/internal/ingress/annotations/streamsnippet"
@@ -94,6 +95,7 @@ type Ingress struct {
9495
EnableGlobalAuth bool
9596
HTTP2PushPreload bool
9697
Opentracing opentracing.Config
98+
Opentelemetry opentelemetry.Config
9799
Proxy proxy.Config
98100
ProxySSL proxyssl.Config
99101
RateLimit ratelimit.Config
@@ -145,6 +147,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
145147
"EnableGlobalAuth": authreqglobal.NewParser(cfg),
146148
"HTTP2PushPreload": http2pushpreload.NewParser(cfg),
147149
"Opentracing": opentracing.NewParser(cfg),
150+
"Opentelemetry": opentelemetry.NewParser(cfg),
148151
"Proxy": proxy.NewParser(cfg),
149152
"ProxySSL": proxyssl.NewParser(cfg),
150153
"RateLimit": ratelimit.NewParser(cfg),

0 commit comments

Comments
 (0)