From 8f126bd65e4b7aadd30b1faccfdbee37d5b5c12d Mon Sep 17 00:00:00 2001 From: Christian Simon Date: Wed, 29 Oct 2025 10:58:17 +0000 Subject: [PATCH 1/3] static-exporter: Implement exporter using busybox Add shell-based implementation for static-exporter using busybox as a lightweight alternative to the Apache web server implementation. The new implementation addresses dependency management concerns by providing a distroless container option that runs as non-root and includes built-in health checks. Changes - Added shell-based exporter option: New shell_exporter=true parameter enables busybox implementation - Automatic image selection: Uses gcr.io/distroless/static-debian12:debug for shell mode vs httpd:2.4-alpine for Apache - Embedded HTTP server: Pure bash implementation that serves /metrics and /health endpoints using netcat - Security improvements: Non-root execution (uid/gid 65532) with proper security context - Health checks: Added readiness probe for the shell implementation - Updated documentation: README now explains both implementations and their use cases --- static-exporter/README.md | 15 ++++- static-exporter/main.libsonnet | 105 +++++++++++++++++++++++++++++++-- 2 files changed, 113 insertions(+), 7 deletions(-) diff --git a/static-exporter/README.md b/static-exporter/README.md index fbbee94a0..67d0480f6 100644 --- a/static-exporter/README.md +++ b/static-exporter/README.md @@ -44,6 +44,19 @@ local static_exporter = import 'github.com/grafana/jsonnet-libs/static-expoter/m } ``` +## Using shell based implementation + +The original implementation using the Apache web server contains quite a few dependencies that might be tricky to keep updated. An optional [busybox] based implementation can be used by supplying the `shell_exporter` flag:following + +```jsonnet + static_exporter.new('team-holiday-exporter', shell_exporter=true) +``` + +This variant uses a [distroless] image, runs as non-root and comes with a health check. + +[distroless]:https://github.com/GoogleContainerTools/distroless +[busybox]:https://busybox.net/ + ## Updating httpd.conf There is a default httpd.conf that was added to this library. @@ -61,4 +74,4 @@ If there is a downstream change that requires updating this config file, run the ``` -This change adds a Header to the requests that enables Prometheus 3.x to scrape the static exporter. \ No newline at end of file +This change adds a Header to the requests that enables Prometheus 3.x to scrape the static exporter. diff --git a/static-exporter/main.libsonnet b/static-exporter/main.libsonnet index 781c71031..e7b114c18 100644 --- a/static-exporter/main.libsonnet +++ b/static-exporter/main.libsonnet @@ -1,10 +1,76 @@ local k = import 'ksonnet-util/kausal.libsonnet'; { - new(name, image='httpd:2.4-alpine'):: + new(name, image=null, shell_exporter=false, port=null):: + local _image = + if image == null + then ( + if shell_exporter + then 'gcr.io/distroless/static-debian12:debug' + else 'httpd:2.4-alpine' + ) + else image + ; + local _port = if port == null + then ( + if shell_exporter + then 8080 + else 80 + ) + else port; + { name:: name, - data:: { metrics: '' }, + data:: { + metrics: '', + [if shell_exporter then 'handler']: ||| + METRICS_FILE="/data/metrics" + + handle_request() { + local request_line + read -r request_line + + # Parse HTTP method and path + local method=$(echo "$request_line" | cut -d' ' -f1) + local path=$(echo "$request_line" | cut -d' ' -f2) + + # Read and discard headers + while IFS= read -r line && [ "$line" != $'\r' ]; do + : + done + + if [[ "$path" == "/metrics" ]]; then + # Serve Prometheus metrics + echo "HTTP/1.1 200 OK" + echo "Connection: close" + echo "Content-Type: text/plain; version=0.0.4; charset=utf-8" + echo "Content-Length: $(wc -c < "$METRICS_FILE")" + echo "" + cat "$METRICS_FILE" + elif [[ "$path" == "/health" ]]; then + # Health check endpoint + echo "HTTP/1.1 200 OK" + echo "Connection: close" + echo "Content-Type: text/plain" + echo "Content-Length: 3" + echo "" + echo "OK" + else + # 404 for other paths + echo "HTTP/1.1 404 Not Found" + echo "Connection: close" + echo "Content-Type: text/plain" + echo "Content-Length: 10" + echo "" + echo "Not Found" + fi + } + + handle_request + |||, + + + }, local configMap = k.core.v1.configMap, configmap: @@ -12,20 +78,47 @@ local k = import 'ksonnet-util/kausal.libsonnet'; local container = k.core.v1.container, container:: - container.new('static-exporter', image) + container.new('static-exporter', _image) + container.withPorts([ - k.core.v1.containerPort.newNamed(name='http-metrics', containerPort=80), + k.core.v1.containerPort.newNamed(name='http-metrics', containerPort=_port), ]) + k.util.resourcesRequests('10m', '10Mi') + + ( + if shell_exporter + then + container.withCommand([ + 'sh', + '-eu', + '-c', + ||| + # handler is created in a new file + mkdir -p "%(bin_dir)s" + echo '#!'$(which sh) > "%(bin_dir)s/handler" + cat /data/handler >> "%(bin_dir)s/handler" + chmod +x %(bin_dir)s/handler + + # run nc, which forks each handler in a process + exec nc -p %(port)d -l -k -e "%(bin_dir)s/handler" 0.0.0.0 + ||| % { + port: _port, + bin_dir: '/home/nonroot/bin', + }, + ]) + + container.securityContext.withRunAsUser(65532) + + container.securityContext.withRunAsGroup(65532) + + container.readinessProbe.httpGet.withPath('/health') + + container.readinessProbe.httpGet.withPort('http-metrics') + else {} + ) , local deployment = k.apps.v1.deployment, local volumeMount = k.core.v1.volumeMount, deployment: deployment.new(name, replicas=1, containers=[self.container]) - + k.util.configMapVolumeMount(self.configmap, '/usr/local/apache2/htdocs'), + + k.util.configMapVolumeMount(self.configmap, if shell_exporter then '/data' else '/usr/local/apache2/htdocs'), } - + self.withHttpConfig() + + (if shell_exporter then {} else self.withHttpConfig()) , withData(data):: { data: data }, From 9b57b55f0a26352ce2a4976efcd66e7ab651e66b Mon Sep 17 00:00:00 2001 From: Christian Simon Date: Thu, 30 Oct 2025 08:19:13 +0000 Subject: [PATCH 2/3] Update static-exporter/main.libsonnet Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- static-exporter/main.libsonnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static-exporter/main.libsonnet b/static-exporter/main.libsonnet index e7b114c18..3ca6593ab 100644 --- a/static-exporter/main.libsonnet +++ b/static-exporter/main.libsonnet @@ -97,7 +97,7 @@ local k = import 'ksonnet-util/kausal.libsonnet'; cat /data/handler >> "%(bin_dir)s/handler" chmod +x %(bin_dir)s/handler - # run nc, which forks each handler in a process + # run nc, which forks each handler in its own process exec nc -p %(port)d -l -k -e "%(bin_dir)s/handler" 0.0.0.0 ||| % { port: _port, From 6d60fce3a243bc9176511da98edb370b689bffbf Mon Sep 17 00:00:00 2001 From: Christian Simon Date: Thu, 30 Oct 2025 08:20:03 +0000 Subject: [PATCH 3/3] Update static-exporter/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- static-exporter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static-exporter/README.md b/static-exporter/README.md index 67d0480f6..0a588348b 100644 --- a/static-exporter/README.md +++ b/static-exporter/README.md @@ -46,7 +46,7 @@ local static_exporter = import 'github.com/grafana/jsonnet-libs/static-expoter/m ## Using shell based implementation -The original implementation using the Apache web server contains quite a few dependencies that might be tricky to keep updated. An optional [busybox] based implementation can be used by supplying the `shell_exporter` flag:following +The original implementation using the Apache web server contains quite a few dependencies that might be tricky to keep updated. An optional [busybox] based implementation can be used by supplying the `shell_exporter` flag: ```jsonnet static_exporter.new('team-holiday-exporter', shell_exporter=true)