Skip to content

Commit 7ae8097

Browse files
simonswineCopilot
andauthored
static-exporter: Implement exporter using busybox (#1521)
* 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 * Update static-exporter/main.libsonnet Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update static-exporter/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Refactor into own function --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent d9709a2 commit 7ae8097

File tree

2 files changed

+108
-1
lines changed

2 files changed

+108
-1
lines changed

static-exporter/README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,19 @@ local static_exporter = import 'github.com/grafana/jsonnet-libs/static-expoter/m
4444
}
4545
```
4646

47+
## Using shell based implementation
48+
49+
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 using the `newShellExporter` function:
50+
51+
```jsonnet
52+
static_exporter.newShellExporter('team-holiday-exporter')
53+
```
54+
55+
This variant uses a [distroless] image, runs as non-root and comes with a health check.
56+
57+
[distroless]:https://github.com/GoogleContainerTools/distroless
58+
[busybox]:https://busybox.net/
59+
4760
## Updating httpd.conf
4861

4962
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
6174
</IfModule>
6275
```
6376

64-
This change adds a Header to the requests that enables Prometheus 3.x to scrape the static exporter.
77+
This change adds a Header to the requests that enables Prometheus 3.x to scrape the static exporter.

static-exporter/main.libsonnet

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,100 @@ local k = import 'ksonnet-util/kausal.libsonnet';
2828
+ self.withHttpConfig()
2929
,
3030

31+
newShellExporter(name, image='gcr.io/distroless/static-debian12:debug', port=8080)::
32+
{
33+
name:: name,
34+
data:: {
35+
metrics: '',
36+
handler: |||
37+
METRICS_FILE="/data/metrics"
38+
39+
handle_request() {
40+
local request_line
41+
read -r request_line
42+
43+
# Parse HTTP method and path
44+
local method=$(echo "$request_line" | cut -d' ' -f1)
45+
local path=$(echo "$request_line" | cut -d' ' -f2)
46+
47+
# Read and discard headers
48+
while IFS= read -r line && [ "$line" != $'\r' ]; do
49+
:
50+
done
51+
52+
if [[ "$path" == "/metrics" ]]; then
53+
# Serve Prometheus metrics
54+
echo "HTTP/1.1 200 OK"
55+
echo "Connection: close"
56+
echo "Content-Type: text/plain; version=0.0.4; charset=utf-8"
57+
echo "Content-Length: $(wc -c < "$METRICS_FILE")"
58+
echo ""
59+
cat "$METRICS_FILE"
60+
elif [[ "$path" == "/health" ]]; then
61+
# Health check endpoint
62+
echo "HTTP/1.1 200 OK"
63+
echo "Connection: close"
64+
echo "Content-Type: text/plain"
65+
echo "Content-Length: 3"
66+
echo ""
67+
echo "OK"
68+
else
69+
# 404 for other paths
70+
echo "HTTP/1.1 404 Not Found"
71+
echo "Connection: close"
72+
echo "Content-Type: text/plain"
73+
echo "Content-Length: 10"
74+
echo ""
75+
echo "Not Found"
76+
fi
77+
}
78+
79+
handle_request
80+
|||,
81+
},
82+
83+
local configMap = k.core.v1.configMap,
84+
configmap:
85+
configMap.new(name, self.data),
86+
87+
local container = k.core.v1.container,
88+
container::
89+
container.new('static-exporter', image)
90+
+ container.withPorts([
91+
k.core.v1.containerPort.newNamed(name='http-metrics', containerPort=port),
92+
])
93+
+ k.util.resourcesRequests('10m', '10Mi')
94+
+ container.withCommand([
95+
'sh',
96+
'-eu',
97+
'-c',
98+
|||
99+
# handler is created in a new file
100+
mkdir -p "%(bin_dir)s"
101+
echo '#!'$(which sh) > "%(bin_dir)s/handler"
102+
cat /data/handler >> "%(bin_dir)s/handler"
103+
chmod +x %(bin_dir)s/handler
104+
105+
# run nc, which forks each handler in its own process
106+
exec nc -p %(port)d -l -k -e "%(bin_dir)s/handler" 0.0.0.0
107+
||| % {
108+
port: port,
109+
bin_dir: '/home/nonroot/bin',
110+
},
111+
]) +
112+
container.securityContext.withRunAsUser(65532) +
113+
container.securityContext.withRunAsGroup(65532) +
114+
container.readinessProbe.httpGet.withPath('/health') +
115+
container.readinessProbe.httpGet.withPort('http-metrics')
116+
,
117+
118+
local deployment = k.apps.v1.deployment,
119+
local volumeMount = k.core.v1.volumeMount,
120+
deployment:
121+
deployment.new(name, replicas=1, containers=[self.container]),
122+
},
123+
124+
31125
withData(data):: { data: data },
32126

33127
withDataMixin(data):: { data+: data },

0 commit comments

Comments
 (0)