Skip to content

Commit 7c9214a

Browse files
authored
feat(universal-chart): add optional metrics service for ServiceMonitor (#88)
1 parent 3684c85 commit 7c9214a

File tree

7 files changed

+233
-1
lines changed

7 files changed

+233
-1
lines changed

charts/universal-chart/templates/service.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,21 @@ spec:
1313
name: http
1414
selector:
1515
{{- include "universal-chart.selectorLabels" . | nindent 4 }}
16+
{{- if .Values.serviceMonitor.alternatePort }}
17+
---
18+
apiVersion: v1
19+
kind: Service
20+
metadata:
21+
name: {{ include "universal-chart.fullname" . }}-metrics
22+
labels:
23+
{{- include "universal-chart.labels" . | nindent 4 }}
24+
spec:
25+
type: ClusterIP
26+
ports:
27+
- port: {{ .Values.serviceMonitor.alternatePort }}
28+
targetPort: {{ .Values.serviceMonitor.alternatePort }}
29+
protocol: TCP
30+
name: metrics
31+
selector:
32+
{{- include "universal-chart.selectorLabels" . | nindent 4 }}
33+
{{- end }}

charts/universal-chart/templates/servicemonitor.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ spec:
1010
matchLabels:
1111
{{- include "universal-chart.selectorLabels" . | nindent 6 }}
1212
endpoints:
13-
- port: http
13+
- port: {{ if .Values.serviceMonitor.alternatePort }}metrics{{ else }}http{{ end }}
1414
{{- with .Values.serviceMonitor.path }}
1515
path: {{ . }}
1616
{{- end }}

charts/universal-chart/values.schema.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,18 @@
6767
"type": "null"
6868
}
6969
]
70+
},
71+
"alternatePort": {
72+
"anyOf": [
73+
{
74+
"type": "integer",
75+
"minimum": 1,
76+
"maximum": 65535
77+
},
78+
{
79+
"type": "null"
80+
}
81+
]
7082
}
7183
}
7284
},

charts/universal-chart/values.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,12 @@ service:
242242
# - type: integer
243243
# minimum: 1
244244
# - type: "null"
245+
# alternatePort:
246+
# anyof:
247+
# - type: integer
248+
# minimum: 1
249+
# maximum: 65535
250+
# - type: "null"
245251
# @schema
246252
# -- Configure a ServiceMonitor for scraping metrics from the service.
247253
serviceMonitor:
@@ -251,6 +257,9 @@ serviceMonitor:
251257
path: /metrics
252258
# -- Optional scrape interval (in seconds). When null, the operator default is used.
253259
interval: null
260+
# -- Optional alternate port to scrape via a dedicated "<release>-metrics" Service.
261+
# When null, the ServiceMonitor targets the main service as before.
262+
alternatePort: null
254263

255264
# -- This block is for setting up the ingress.
256265
# More information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
---
2+
# Source: universal-chart/templates/serviceaccount.yaml
3+
apiVersion: v1
4+
kind: ServiceAccount
5+
metadata:
6+
name: universal-chart
7+
labels:
8+
helm.sh/chart: universal-chart-0.0.0-a.placeholder
9+
app.kubernetes.io/name: universal-chart
10+
app.kubernetes.io/instance: universal-chart
11+
app.kubernetes.io/managed-by: Helm
12+
automountServiceAccountToken: true
13+
---
14+
# Source: universal-chart/templates/service.yaml
15+
apiVersion: v1
16+
kind: Service
17+
metadata:
18+
name: universal-chart
19+
labels:
20+
helm.sh/chart: universal-chart-0.0.0-a.placeholder
21+
app.kubernetes.io/name: universal-chart
22+
app.kubernetes.io/instance: universal-chart
23+
app.kubernetes.io/managed-by: Helm
24+
spec:
25+
type: ClusterIP
26+
ports:
27+
- port: 3000
28+
targetPort: http
29+
protocol: TCP
30+
name: http
31+
selector:
32+
app.kubernetes.io/name: universal-chart
33+
app.kubernetes.io/instance: universal-chart
34+
---
35+
# Source: universal-chart/templates/service.yaml
36+
apiVersion: v1
37+
kind: Service
38+
metadata:
39+
name: universal-chart-metrics
40+
labels:
41+
helm.sh/chart: universal-chart-0.0.0-a.placeholder
42+
app.kubernetes.io/name: universal-chart
43+
app.kubernetes.io/instance: universal-chart
44+
app.kubernetes.io/managed-by: Helm
45+
spec:
46+
type: ClusterIP
47+
ports:
48+
- port: 9090
49+
targetPort: 9090
50+
protocol: TCP
51+
name: metrics
52+
selector:
53+
app.kubernetes.io/name: universal-chart
54+
app.kubernetes.io/instance: universal-chart
55+
---
56+
# Source: universal-chart/templates/deployment.yaml
57+
apiVersion: apps/v1
58+
kind: Deployment
59+
metadata:
60+
name: universal-chart
61+
labels:
62+
helm.sh/chart: universal-chart-0.0.0-a.placeholder
63+
app.kubernetes.io/name: universal-chart
64+
app.kubernetes.io/instance: universal-chart
65+
app.kubernetes.io/managed-by: Helm
66+
spec:
67+
replicas: 1
68+
revisionHistoryLimit: 3
69+
selector:
70+
matchLabels:
71+
app.kubernetes.io/name: universal-chart
72+
app.kubernetes.io/instance: universal-chart
73+
template:
74+
metadata:
75+
labels:
76+
helm.sh/chart: universal-chart-0.0.0-a.placeholder
77+
app.kubernetes.io/name: universal-chart
78+
app.kubernetes.io/instance: universal-chart
79+
app.kubernetes.io/managed-by: Helm
80+
spec:
81+
serviceAccountName: universal-chart
82+
containers:
83+
- name: universal-chart
84+
env: &containerenv
85+
# placeholder var so we can always make an env list
86+
- name: REDIS_ENABLED
87+
value: "false"
88+
image: "ghcr.io/example/app:1.2.3"
89+
imagePullPolicy: Always
90+
ports:
91+
- name: http
92+
containerPort: 3000
93+
protocol: TCP
94+
topologySpreadConstraints:
95+
- labelSelector:
96+
matchLabels:
97+
app.kubernetes.io/instance: universal-chart
98+
app.kubernetes.io/name: universal-chart
99+
maxSkew: 1
100+
topologyKey: topology.kubernetes.io/zone
101+
whenUnsatisfiable: ScheduleAnyway
102+
---
103+
# Source: universal-chart/templates/servicemonitor.yaml
104+
apiVersion: monitoring.coreos.com/v1
105+
kind: ServiceMonitor
106+
metadata:
107+
name: universal-chart
108+
labels:
109+
helm.sh/chart: universal-chart-0.0.0-a.placeholder
110+
app.kubernetes.io/name: universal-chart
111+
app.kubernetes.io/instance: universal-chart
112+
app.kubernetes.io/managed-by: Helm
113+
spec:
114+
selector:
115+
matchLabels:
116+
app.kubernetes.io/name: universal-chart
117+
app.kubernetes.io/instance: universal-chart
118+
endpoints:
119+
- port: metrics
120+
path: /metrics
121+
interval: 20s
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
image:
2+
repository: ghcr.io/example/app
3+
tag: "1.2.3"
4+
serviceMonitor:
5+
enabled: true
6+
path: /metrics
7+
interval: 20
8+
alternatePort: 9090

tests/test_universal_chart.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,70 @@ def test_service_monitor_supports_custom_path_and_interval(helm_runner) -> None:
254254
assert endpoint["interval"] == "30s"
255255

256256

257+
def test_metrics_service_renders_when_alternate_port_is_set(
258+
helm_runner,
259+
) -> None:
260+
"""Ensure a dedicated metrics service is created for alternate ports."""
261+
262+
rendered = render_chart(
263+
helm_runner,
264+
CHART,
265+
values={"serviceMonitor": {"alternatePort": 9090}},
266+
)
267+
manifests = load_manifests(rendered)
268+
269+
services = [
270+
manifest for manifest in manifests if manifest.get("kind") == "Service"
271+
]
272+
assert len(services) == 2
273+
274+
main_service = next(
275+
service
276+
for service in services
277+
if service["metadata"]["name"] == CHART.release
278+
)
279+
metrics_service = next(
280+
service
281+
for service in services
282+
if service["metadata"]["name"] == f"{CHART.release}-metrics"
283+
)
284+
285+
assert metrics_service["spec"]["type"] == "ClusterIP"
286+
assert (
287+
metrics_service["spec"]["selector"] == main_service["spec"]["selector"]
288+
)
289+
assert metrics_service["spec"]["ports"] == [
290+
{
291+
"name": "metrics",
292+
"port": 9090,
293+
"protocol": "TCP",
294+
"targetPort": 9090,
295+
}
296+
]
297+
298+
299+
def test_service_monitor_uses_metrics_service_for_alternate_port(
300+
helm_runner,
301+
) -> None:
302+
"""Ensure ServiceMonitor targets the metrics service when configured."""
303+
304+
rendered = render_chart(
305+
helm_runner,
306+
CHART,
307+
values={
308+
"serviceMonitor": {
309+
"enabled": True,
310+
"alternatePort": 9090,
311+
}
312+
},
313+
)
314+
manifests = load_manifests(rendered)
315+
endpoint = get_manifest(manifests, "ServiceMonitor")["spec"]["endpoints"][0]
316+
317+
assert endpoint["port"] == "metrics"
318+
assert endpoint["path"] == "/metrics"
319+
320+
257321
def test_service_monitor_interval_supports_minimum_value(helm_runner) -> None:
258322
"""Ensure the minimum interval value renders correctly."""
259323

0 commit comments

Comments
 (0)