Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

read resource attributes from annotations #3204

Merged
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7eb63af
read resource attributes from annotations
zeitlinger Aug 7, 2024
407ea97
use labels
zeitlinger Aug 7, 2024
882a9c7
changelog entry
zeitlinger Aug 7, 2024
65d9aa7
fix rebase
zeitlinger Aug 7, 2024
47a7fec
change prefix to "resource.opentelemetry.io/"
zeitlinger Aug 7, 2024
7059349
change prefix to "resource.opentelemetry.io/"
zeitlinger Aug 7, 2024
930dd3d
fix rebase
zeitlinger Aug 12, 2024
ef827b2
update changelog and docs
zeitlinger Aug 14, 2024
7733e72
changes from feedback
zeitlinger Aug 30, 2024
5b0fbde
changes from feedback
zeitlinger Aug 30, 2024
182ee4b
generate
zeitlinger Aug 30, 2024
34c51d6
Update pkg/instrumentation/apachehttpd.go
zeitlinger Aug 30, 2024
9bb63f0
pr review
zeitlinger Aug 30, 2024
091c26f
comments
zeitlinger Aug 30, 2024
063eebe
lint
zeitlinger Aug 30, 2024
4bc7582
lint
zeitlinger Aug 30, 2024
79b79ea
Update README.md
zeitlinger Sep 2, 2024
976f68e
change priority to avoid breaking change
zeitlinger Sep 2, 2024
4366c12
add e2e test
zeitlinger Sep 2, 2024
e549a5b
add e2e test
zeitlinger Sep 2, 2024
2e9f6ec
add e2e test
zeitlinger Sep 3, 2024
c196665
Merge branch 'main' into resource-attribute-from-annotations
zeitlinger Sep 6, 2024
f0b4c6a
pr review
zeitlinger Sep 9, 2024
247720e
Merge branch 'main' into resource-attribute-from-annotations
zeitlinger Sep 11, 2024
1bc62e5
use pod methods
zeitlinger Sep 11, 2024
6e3d2a0
Merge branch 'main' into resource-attribute-from-annotations
zeitlinger Sep 25, 2024
b385810
don't modify the existing chainsaw test
zeitlinger Sep 25, 2024
24919eb
don't modify the existing chainsaw test
zeitlinger Sep 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .chloggen/resource-attribute-from-annotations.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
change_type: enhancement

component: auto-instrumentation

note: Add support for k8s labels such as app.kubernetes.io/name for resource attributes

issues: [3112]

subtext: |
You can opt-in as follows:
```yaml
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: my-instrumentation
spec:
defaults:
useLabelsForResourceAttributes: true
```
The following labels are supported:
- `app.kubernetes.io/name` becomes `service.name`
- `app.kubernetes.io/version` becomes `service.version`
- `app.kubernetes.io/part-of` becomes `service.namespace`
- `app.kubernetes.io/instance` becomes `service.instance.id`
58 changes: 56 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,9 @@ spec:
EOF
```

### Setting instrumentation resource attributes via namespace annotations
## Configure resource attributes

### Configure resource attributes with annotations

This example shows a pod configuration with OpenTelemetry annotations using the `resource.opentelemetry.io/` prefix. These annotations can be used to add resource attributes to data produced by OpenTelemetry instrumentation.

Expand All @@ -734,7 +736,59 @@ spec:
containers:
- name: main-container
image: your-image:tag
```
```

### Configure resource attributes with labels

You can also use common labels to set resource attributes.
zeitlinger marked this conversation as resolved.
Show resolved Hide resolved

The following labels are supported:
- `app.kubernetes.io/name` becomes `service.name`
- `app.kubernetes.io/version` becomes `service.version`
- `app.kubernetes.io/part-of` becomes `service.namespace`
- `app.kubernetes.io/instance` becomes `service.instance.id`

```yaml
apiVersion: v1
kind: Pod
metadata:
name: example-pod
labels:
app.kubernetes.io/name: "my-service"
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/part-of: "shop"
app.kubernetes.io/instance: "my-service-123"
spec:
containers:
- name: main-container
image: your-image:tag
```

This requires an explicit opt-in as follows:

```yaml
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: my-instrumentation
spec:
defaults:
useLabelsForResourceAttributes: true
```

### Priority for setting resource attributes

The priority for setting resource attributes is as follows (first found wins):

1. Resource attributes set via `OTEL_RESOURCE_ATTRIBUTES` and `OTEL_SERVICE_NAME` environment variables
2. Resource attributes set via annotations (with the `resource.opentelemetry.io/` prefix)
3. Resource attributes set via labels (e.g. `app.kubernetes.io/name`)
if the `Instrumentation` CR has defaults.useLabelsForResourceAttributes=true (see above)
4. Resource attributes calculated from the pod's metadata (e.g. `k8s.pod.name`)
5. Resource attributes set via the `Instrumentation` CR (in the `spec.resource.resourceAttributes` section)

This priority is applied for each resource attribute separately, so it is possible to set some attributes via
annotations and others via labels.

## Compatibility matrix

Expand Down
13 changes: 13 additions & 0 deletions apis/v1alpha1/instrumentation_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ type InstrumentationSpec struct {
// +optional
Sampler `json:"sampler,omitempty"`

// Defaults defines default values for the instrumentation.
Defaults Defaults `json:"defaults,omitempty"`

// Env defines common env vars. There are four layers for env vars' definitions and
// the precedence order is: `original container env vars` > `language specific env vars` > `common env vars` > `instrument spec configs' vars`.
// If the former var had been defined, then the other vars would be ignored.
Expand Down Expand Up @@ -114,6 +117,16 @@ type Sampler struct {
Argument string `json:"argument,omitempty"`
}

// Defaults defines default values for the instrumentation.
type Defaults struct {
// UseLabelsForResourceAttributes defines whether to use common labels for resource attributes:
// - `app.kubernetes.io/name` becomes `service.name`
// - `app.kubernetes.io/version` becomes `service.version`
// - `app.kubernetes.io/part-of` becomes `service.namespace`
// - `app.kubernetes.io/instance` becomes `service.instance.id`
UseLabelsForResourceAttributes bool `json:"useLabelsForResourceAttributes,omitempty"`
}

// Java defines Java SDK and instrumentation configuration.
type Java struct {
// Image is a container image with javaagent auto-instrumentation JAR.
Expand Down
16 changes: 16 additions & 0 deletions apis/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ spec:
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
type: object
defaults:
properties:
useLabelsForResourceAttributes:
type: boolean
type: object
dotnet:
properties:
env:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ spec:
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
type: object
defaults:
properties:
useLabelsForResourceAttributes:
type: boolean
type: object
dotnet:
properties:
env:
Expand Down
5 changes: 5 additions & 0 deletions config/crd/bases/opentelemetry.io_instrumentations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,11 @@ spec:
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
type: object
defaults:
properties:
useLabelsForResourceAttributes:
type: boolean
type: object
dotnet:
properties:
env:
Expand Down
38 changes: 38 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ InstrumentationSpec defines the desired state of OpenTelemetry SDK and instrumen
ApacheHttpd defines configuration for Apache HTTPD auto-instrumentation.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#instrumentationspecdefaults">defaults</a></b></td>
<td>object</td>
<td>
Defaults defines default values for the instrumentation.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#instrumentationspecdotnet">dotnet</a></b></td>
<td>object</td>
Expand Down Expand Up @@ -887,6 +894,37 @@ only the result of this request.<br/>
</table>


### Instrumentation.spec.defaults
<sup><sup>[↩ Parent](#instrumentationspec)</sup></sup>



Defaults defines default values for the instrumentation.

<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>useLabelsForResourceAttributes</b></td>
<td>boolean</td>
<td>
UseLabelsForResourceAttributes defines whether to use common labels for resource attributes:
- `app.kubernetes.io/name` becomes `service.name`
- `app.kubernetes.io/version` becomes `service.version`
- `app.kubernetes.io/part-of` becomes `service.namespace`
- `app.kubernetes.io/instance` becomes `service.instance.id`<br/>
</td>
<td>false</td>
</tr></tbody>
</table>


### Instrumentation.spec.dotnet
<sup><sup>[↩ Parent](#instrumentationspec)</sup></sup>

Expand Down
18 changes: 12 additions & 6 deletions pkg/constants/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,18 @@ const (
AnnotationDefaultAutoInstrumentationApacheHttpd = InstrumentationPrefix + "default-auto-instrumentation-apache-httpd-image"
AnnotationDefaultAutoInstrumentationNginx = InstrumentationPrefix + "default-auto-instrumentation-nginx-image"

EnvPodName = "OTEL_RESOURCE_ATTRIBUTES_POD_NAME"
EnvPodUID = "OTEL_RESOURCE_ATTRIBUTES_POD_UID"
EnvPodIP = "OTEL_POD_IP"
EnvNodeName = "OTEL_RESOURCE_ATTRIBUTES_NODE_NAME"
EnvNodeIP = "OTEL_NODE_IP"
OtelAnnotationNamespace = "resource.opentelemetry.io/"
LabelAppName = "app.kubernetes.io/name"
LabelAppInstance = "app.kubernetes.io/instance"
LabelAppVersion = "app.kubernetes.io/version"
LabelAppPartOf = "app.kubernetes.io/part-of"

ResourceAttributeAnnotationPrefix = "resource.opentelemetry.io/"

EnvPodName = "OTEL_RESOURCE_ATTRIBUTES_POD_NAME"
EnvPodUID = "OTEL_RESOURCE_ATTRIBUTES_POD_UID"
EnvPodIP = "OTEL_POD_IP"
EnvNodeName = "OTEL_RESOURCE_ATTRIBUTES_NODE_NAME"
EnvNodeIP = "OTEL_NODE_IP"

FlagCRMetrics = "enable-cr-metrics"
FlagApacheHttpd = "enable-apache-httpd-instrumentation"
Expand Down
8 changes: 4 additions & 4 deletions pkg/instrumentation/apachehttpd.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const (
6) Inject mounting of volumes / files into appropriate directories in application container
*/

func injectApacheHttpdagent(_ logr.Logger, apacheSpec v1alpha1.ApacheHttpd, pod corev1.Pod, index int, otlpEndpoint string, resourceMap map[string]string) corev1.Pod {
func injectApacheHttpdagent(_ logr.Logger, apacheSpec v1alpha1.ApacheHttpd, pod corev1.Pod, useLabelsForResourceAttributes bool, index int, otlpEndpoint string, resourceMap map[string]string) corev1.Pod {

// caller checks if there is at least one container
container := &pod.Spec.Containers[index]
Expand Down Expand Up @@ -162,7 +162,7 @@ func injectApacheHttpdagent(_ logr.Logger, apacheSpec v1alpha1.ApacheHttpd, pod
Env: []corev1.EnvVar{
{
Name: apacheAttributesEnvVar,
Value: getApacheOtelConfig(pod, apacheSpec, index, otlpEndpoint, resourceMap),
Value: getApacheOtelConfig(pod, useLabelsForResourceAttributes, apacheSpec, index, otlpEndpoint, resourceMap),
},
{Name: apacheServiceInstanceIdEnvVar,
ValueFrom: &corev1.EnvVarSource{
Expand Down Expand Up @@ -201,7 +201,7 @@ func isApacheInitContainerMissing(pod corev1.Pod, containerName string) bool {

// Calculate Apache HTTPD agent configuration file based on attributes provided by the injection rules
// and by the pod values.
func getApacheOtelConfig(pod corev1.Pod, apacheSpec v1alpha1.ApacheHttpd, index int, otelEndpoint string, resourceMap map[string]string) string {
func getApacheOtelConfig(pod corev1.Pod, useLabelsForResourceAttributes bool, apacheSpec v1alpha1.ApacheHttpd, index int, otelEndpoint string, resourceMap map[string]string) string {
template := `
#Load the Otel Webserver SDK
LoadFile %[1]s/sdk_lib/lib/libopentelemetry_common.so
Expand All @@ -222,7 +222,7 @@ LoadModule otel_apache_module %[1]s/WebServerModule/Apache/libmod_apache_otel%[2
if otelEndpoint == "" {
otelEndpoint = "http://localhost:4317/"
}
serviceName := chooseServiceName(pod, resourceMap, index)
serviceName := chooseServiceName(pod, useLabelsForResourceAttributes, resourceMap, index)
serviceNamespace := pod.GetNamespace()
if len(serviceNamespace) == 0 {
serviceNamespace = resourceMap[string(semconv.K8SNamespaceNameKey)]
Expand Down
4 changes: 2 additions & 2 deletions pkg/instrumentation/apachehttpd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ func TestInjectApacheHttpdagent(t *testing.T) {

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pod := injectApacheHttpdagent(logr.Discard(), test.ApacheHttpd, test.pod, 0, "http://otlp-endpoint:4317", resourceMap)
pod := injectApacheHttpdagent(logr.Discard(), test.ApacheHttpd, test.pod, false, 0, "http://otlp-endpoint:4317", resourceMap)
assert.Equal(t, test.expected, pod)
})
}
Expand Down Expand Up @@ -527,7 +527,7 @@ func TestInjectApacheHttpdagentUnknownNamespace(t *testing.T) {

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pod := injectApacheHttpdagent(logr.Discard(), test.ApacheHttpd, test.pod, 0, "http://otlp-endpoint:4317", resourceMap)
pod := injectApacheHttpdagent(logr.Discard(), test.ApacheHttpd, test.pod, false, 0, "http://otlp-endpoint:4317", resourceMap)
assert.Equal(t, test.expected, pod)
})
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/instrumentation/nginx.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const (
6) Inject mounting of volumes / files into appropriate directories in the application container
*/

func injectNginxSDK(_ logr.Logger, nginxSpec v1alpha1.Nginx, pod corev1.Pod, index int, otlpEndpoint string, resourceMap map[string]string) corev1.Pod {
func injectNginxSDK(_ logr.Logger, nginxSpec v1alpha1.Nginx, pod corev1.Pod, useLabelsForResourceAttributes bool, index int, otlpEndpoint string, resourceMap map[string]string) corev1.Pod {

// caller checks if there is at least one container
container := &pod.Spec.Containers[index]
Expand Down Expand Up @@ -217,7 +217,7 @@ mv ${NGINX_AGENT_CONF_DIR_FULL}/opentelemetry_agent.conf ${NGINX_AGENT_CONF_DIR
Env: []corev1.EnvVar{
{
Name: nginxAttributesEnvVar,
Value: getNginxOtelConfig(pod, nginxSpec, index, otlpEndpoint, resourceMap),
Value: getNginxOtelConfig(pod, useLabelsForResourceAttributes, nginxSpec, index, otlpEndpoint, resourceMap),
},
{
Name: "OTEL_NGINX_I13N_SCRIPT",
Expand Down Expand Up @@ -277,12 +277,12 @@ func isNginxInitContainerMissing(pod corev1.Pod, containerName string) bool {

// Calculate Nginx agent configuration file based on attributes provided by the injection rules
// and by the pod values.
func getNginxOtelConfig(pod corev1.Pod, nginxSpec v1alpha1.Nginx, index int, otelEndpoint string, resourceMap map[string]string) string {
func getNginxOtelConfig(pod corev1.Pod, useLabelsForResourceAttributes bool, nginxSpec v1alpha1.Nginx, index int, otelEndpoint string, resourceMap map[string]string) string {

if otelEndpoint == "" {
otelEndpoint = "http://localhost:4317/"
}
serviceName := chooseServiceName(pod, resourceMap, index)
serviceName := chooseServiceName(pod, useLabelsForResourceAttributes, resourceMap, index)
serviceNamespace := pod.GetNamespace()
if len(serviceNamespace) == 0 {
serviceNamespace = resourceMap[string(semconv.K8SNamespaceNameKey)]
Expand Down
4 changes: 2 additions & 2 deletions pkg/instrumentation/nginx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ func TestInjectNginxSDK(t *testing.T) {

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pod := injectNginxSDK(logr.Discard(), test.Nginx, test.pod, 0, "http://otlp-endpoint:4317", resourceMap)
pod := injectNginxSDK(logr.Discard(), test.Nginx, test.pod, false, 0, "http://otlp-endpoint:4317", resourceMap)
assert.Equal(t, test.expected, pod)
})
}
Expand Down Expand Up @@ -600,7 +600,7 @@ func TestInjectNginxUnknownNamespace(t *testing.T) {

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pod := injectNginxSDK(logr.Discard(), test.Nginx, test.pod, 0, "http://otlp-endpoint:4317", resourceMap)
pod := injectNginxSDK(logr.Discard(), test.Nginx, test.pod, false, 0, "http://otlp-endpoint:4317", resourceMap)
assert.Equal(t, test.expected, pod)
})
}
Expand Down
Loading
Loading