Skip to content

Commit 45c88b5

Browse files
authored
Handle edge case for ecs observer job relabel (#352)
* Handle edge case for ecs observer job relabel * Update readme for ecsobserver job_label_name * Fix linting --------- Co-authored-by: Akansha Agarwal <[email protected]>
1 parent af455c2 commit 45c88b5

File tree

5 files changed

+464
-1
lines changed

5 files changed

+464
-1
lines changed

extension/observer/ecsobserver/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ extensions:
3333
cluster_name: 'Cluster-1' # cluster name need manual config
3434
cluster_region: 'us-west-2' # region can be configured directly or use AWS_REGION env var
3535
result_file: '/etc/ecs_sd_targets.yaml' # the directory for file must already exists
36+
job_label_name: 'prometheus_job' # optional: override for job label name
3637
services:
3738
- name_pattern: '^retail-.*$'
3839
docker_labels:
@@ -86,6 +87,7 @@ service:
8687
| cluster_region | Mandatory | target ECS cluster's AWS region name |
8788
| refresh_interval | Optional | how often to look for changes in endpoints (default: 10s) |
8889
| result_file | Mandatory | path of YAML file to write scrape target results. NOTE: the observer always returns empty in initial implementation |
90+
| job_label_name | Optional | override for prometheus job label name. If empty or set to "job", no behavior change. |
8991
| services | Optional | list of service name patterns [detail](#ecs-service-name-based-filter-configuration) |
9092
| task_definitions | Optional | list of task definition arn patterns [detail](#ecs-task-definition-based-filter-configuration) |
9193
| docker_labels | Optional | list of docker labels [detail](#docker-label-based-filter-configuration) |
@@ -303,6 +305,15 @@ Required for prometheus to scrape the target.
303305
| ` __metrics_path__` | ECS TaskDefinition or Config | string | Default is `/metrics`, changes based on config/label |
304306
| `job` | ECS TaskDefinition or Config | string | Name for scrape job |
305307

308+
### Job Label Behavior
309+
310+
The `job_label_name` configuration controls how job labels are handled in the targets printed to the results file:
311+
312+
- **Empty or unset or set to "job"**: Preserves the original `job` label from Docker labels or task definitions
313+
- **Set to custom name**: Renames the `job` label to the specified name (e.g., `prometheus_job`)
314+
315+
This renaming works around Prometheus receiver limitations where the `job` label conflicts with internal processing.
316+
306317
### Additional Labels
307318

308319
Additional information from ECS and EC2.

extension/observer/ecsobserver/sd_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,41 @@ func TestNewDiscovery(t *testing.T) {
173173
assert.Equal(t, string(expectedContent), string(mustReadFile(t, outputFile)))
174174
})
175175

176+
t.Run("job_label_name_empty_string", func(t *testing.T) {
177+
cfg2 := cfg
178+
cfg2.JobLabelName = ""
179+
180+
sd, err := newDiscovery(cfg2, opts)
181+
require.NoError(t, err)
182+
183+
ctx, cancel := context.WithTimeout(context.Background(), cfg2.RefreshInterval*2)
184+
defer cancel()
185+
err = sd.runAndWriteFile(ctx)
186+
require.NoError(t, err)
187+
188+
assert.FileExists(t, outputFile)
189+
expectedFile := "testdata/ut_targets_expected_no_job_relabel.yaml"
190+
expectedContent := bytes.ReplaceAll(mustReadFile(t, expectedFile), []byte("\r\n"), []byte("\n"))
191+
assert.Equal(t, string(expectedContent), string(mustReadFile(t, outputFile)))
192+
})
193+
194+
t.Run("job_label_name_is_job", func(t *testing.T) {
195+
cfg2 := cfg
196+
cfg2.JobLabelName = "job"
197+
sd, err := newDiscovery(cfg2, opts)
198+
require.NoError(t, err)
199+
200+
ctx, cancel := context.WithTimeout(context.Background(), cfg2.RefreshInterval*2)
201+
defer cancel()
202+
err = sd.runAndWriteFile(ctx)
203+
require.NoError(t, err)
204+
205+
assert.FileExists(t, outputFile)
206+
expectedFile := "testdata/ut_targets_expected_no_job_relabel.yaml"
207+
expectedContent := bytes.ReplaceAll(mustReadFile(t, expectedFile), []byte("\r\n"), []byte("\n"))
208+
assert.Equal(t, string(expectedContent), string(mustReadFile(t, outputFile)))
209+
})
210+
176211
t.Run("fail to write file", func(t *testing.T) {
177212
cfg2 := cfg
178213
cfg2.ResultFile = "testdata/folder/does/not/exists/ut_targets.yaml"

extension/observer/ecsobserver/target.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func targetsToFileSDTargets(targets []prometheusECSTarget, jobLabelName string)
158158
// We can't relabel it using prometheus's relabel config as it would cause the same problem on receiver.
159159
// We 'relabel' it to job outside prometheus receiver using other processors in collector's pipeline.
160160
job := labels[labelJob]
161-
if job != "" && jobLabelName != labelJob {
161+
if job != "" && jobLabelName != "" && jobLabelName != labelJob {
162162
delete(labels, labelJob)
163163
labels[jobLabelName] = job
164164
}

extension/observer/ecsobserver/target_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,58 @@ func TestTargetToLabels(t *testing.T) {
2222
assert.Equal(t, "same", m["__meta_ecs_task_tags_ab"])
2323
})
2424
}
25+
26+
func TestTargetsToFileSDTargets(t *testing.T) {
27+
targets := []prometheusECSTarget{
28+
{
29+
Address: "192.168.1.1:9090",
30+
Job: "test-job",
31+
},
32+
}
33+
34+
t.Run("job label renamed when custom job_label_name provided", func(t *testing.T) {
35+
result, err := targetsToFileSDTargets(targets, "prometheus_job")
36+
assert.NoError(t, err)
37+
assert.Len(t, result, 1)
38+
39+
labels := result[0].Labels
40+
assert.Equal(t, "test-job", labels["prometheus_job"])
41+
assert.NotContains(t, labels, "job")
42+
})
43+
44+
t.Run("job label kept when job_label_name is 'job'", func(t *testing.T) {
45+
result, err := targetsToFileSDTargets(targets, "job")
46+
assert.NoError(t, err)
47+
assert.Len(t, result, 1)
48+
49+
labels := result[0].Labels
50+
assert.Equal(t, "test-job", labels["job"])
51+
assert.NotContains(t, labels, "prometheus_job")
52+
})
53+
54+
t.Run("job label kept when job_label_name is empty string", func(t *testing.T) {
55+
result, err := targetsToFileSDTargets(targets, "")
56+
assert.NoError(t, err)
57+
assert.Len(t, result, 1)
58+
59+
labels := result[0].Labels
60+
assert.Equal(t, "test-job", labels["job"])
61+
assert.NotContains(t, labels, "")
62+
})
63+
64+
t.Run("no job relabeling when job is empty", func(t *testing.T) {
65+
emptyJobTargets := []prometheusECSTarget{
66+
{
67+
Address: "192.168.1.1:9090",
68+
Job: "",
69+
},
70+
}
71+
result, err := targetsToFileSDTargets(emptyJobTargets, "prometheus_job")
72+
assert.NoError(t, err)
73+
assert.Len(t, result, 1)
74+
75+
labels := result[0].Labels
76+
assert.NotContains(t, labels, "job")
77+
assert.NotContains(t, labels, "prometheus_job")
78+
})
79+
}

0 commit comments

Comments
 (0)