Kubernetes
Run Prometheus Proxy on Kubernetes to scrape metrics from services that live inside firewalled or private clusters.
Topology
The two components play different roles, and that determines where each one runs:
- The agent runs inside each target cluster, alongside the services it scrapes. It opens an outbound gRPC connection to the proxy, so the target cluster never needs an inbound firewall rule.
- The proxy runs centrally, next to Prometheus. Agents dial in to it on the gRPC
port (
50051), and Prometheus scrapes it on the HTTP port (8080).
flowchart LR
subgraph monitoring["Monitoring cluster"]
prom["Prometheus"]
proxy["Prometheus Proxy<br/>:8080 HTTP / :50051 gRPC"]
prom -- "scrape :8080" --> proxy
end
subgraph target["Target cluster (behind firewall)"]
agent["Prometheus Agent"]
app["my-app :9100/metrics"]
agent -- "scrape in-cluster" --> app
end
agent -- "outbound gRPC :50051" --> proxy
The agent always initiates the connection, so a target cluster only needs egress to the proxy's gRPC endpoint — no inbound ports are exposed.
Namespaces in these examples
The manifests below put the proxy in a monitoring namespace and the agent in default.
Adjust the namespace fields and the in-cluster DNS names (e.g.
prometheus-proxy.monitoring.svc.cluster.local) to match your own layout.
Deploying the Proxy
Deploy the proxy as a Deployment plus a Service. Admin and metrics endpoints are enabled
through environment variables so Kubernetes can probe them and Prometheus can scrape them.
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus-proxy
namespace: monitoring
labels:
app: prometheus-proxy
spec:
replicas: 1
selector:
matchLabels:
app: prometheus-proxy
template:
metadata:
labels:
app: prometheus-proxy
spec:
containers:
- name: proxy
image: pambrose/prometheus-proxy:3.2.0
ports:
- { name: http, containerPort: 8080 } # Prometheus scrapes here
- { name: grpc, containerPort: 50051 } # agents connect here
- { name: metrics, containerPort: 8082 }
- { name: admin, containerPort: 8092 }
env:
- { name: ADMIN_ENABLED, value: "true" }
- { name: METRICS_ENABLED, value: "true" }
livenessProbe:
httpGet: { path: /ping, port: admin }
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet: { path: /healthcheck, port: admin }
initialDelaySeconds: 10
periodSeconds: 15
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { memory: 512Mi }
---
apiVersion: v1
kind: Service
metadata:
name: prometheus-proxy
namespace: monitoring
labels:
app: prometheus-proxy
spec:
selector:
app: prometheus-proxy
ports:
- { name: http, port: 8080, targetPort: http }
- { name: grpc, port: 50051, targetPort: grpc }
- { name: metrics, port: 8082, targetPort: metrics }
The Service above is a ClusterIP, which is enough when Prometheus and the agents live in
the same cluster. Prometheus reaches the proxy at
prometheus-proxy.monitoring.svc.cluster.local:8080, and same-cluster agents can use the same
DNS name for the gRPC port.
Exposing gRPC to remote agents
When agents run in other clusters, they need to reach the proxy's gRPC port from outside.
Expose it with a LoadBalancer Service (or an Ingress controller):
apiVersion: v1
kind: Service
metadata:
name: prometheus-proxy-grpc
namespace: monitoring
labels:
app: prometheus-proxy
spec:
type: LoadBalancer
selector:
app: prometheus-proxy
ports:
- { name: grpc, port: 50051, targetPort: grpc }
gRPC needs HTTP/2 end to end
gRPC runs over HTTP/2. A plain LoadBalancer (L4) forwards it transparently. If you put
an Ingress in front instead, it must support HTTP/2 / gRPC backends (for example, the
NGINX Ingress nginx.ingress.kubernetes.io/backend-protocol: GRPC annotation). Terminate
or pass through TLS as described under TLS.
Deploying the Agent
The agent's path mappings live in a ConfigMap. The proxy hostname is supplied separately
through the PROXY_HOSTNAME environment variable, so the same config works regardless of
where the proxy is exposed.
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-agent-config
namespace: default
data:
agent.conf: |
agent {
pathConfigs: [
{
name: "My App metrics"
path: my_app_metrics
url: "http://my-app.default.svc.cluster.local:8080/metrics"
}
]
}
Each entry in pathConfigs maps a proxy path (what Prometheus scrapes) to the in-cluster
url the agent fetches from. Point url at a Kubernetes Service DNS name such as
http://my-app.default.svc.cluster.local:8080/metrics.
Standalone Deployment
The common pattern is a single agent Deployment per cluster that scrapes several in-cluster endpoints:
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus-agent
namespace: default
labels:
app: prometheus-agent
spec:
replicas: 1
selector:
matchLabels:
app: prometheus-agent
template:
metadata:
labels:
app: prometheus-agent
spec:
containers:
- name: agent
image: pambrose/prometheus-agent:3.2.0
ports:
- { name: metrics, containerPort: 8083 }
- { name: admin, containerPort: 8093 }
env:
- name: PROXY_HOSTNAME
value: "proxy.example.com:50051" # the proxy's externally reachable address
- { name: AGENT_CONFIG, value: /app/agent.conf }
- { name: ADMIN_ENABLED, value: "true" }
- { name: METRICS_ENABLED, value: "true" }
volumeMounts:
- { name: config, mountPath: /app/agent.conf, subPath: agent.conf }
livenessProbe:
httpGet: { path: /ping, port: admin }
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet: { path: /healthcheck, port: admin }
initialDelaySeconds: 10
periodSeconds: 15
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { memory: 512Mi }
volumes:
- name: config
configMap:
name: prometheus-agent-config
Set PROXY_HOSTNAME to the proxy's externally reachable address (the LoadBalancer host from
above), including the gRPC port if it is not the default
50051.
Sidecar
Alternatively, run the agent as a sidecar next to a single application pod and scrape it over
localhost:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: default
labels:
app: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:latest
ports:
- { name: metrics, containerPort: 9100 }
- name: prometheus-agent
image: pambrose/prometheus-agent:3.2.0
env:
- { name: PROXY_HOSTNAME, value: "proxy.example.com:50051" }
- { name: AGENT_CONFIG, value: /app/agent.conf }
volumeMounts:
- { name: agent-config, mountPath: /app/agent.conf, subPath: agent.conf }
volumes:
- name: agent-config
configMap:
name: prometheus-agent-config # agent.conf url points at http://localhost:9100/metrics
With the sidecar pattern the agent's url should point at http://localhost:9100/metrics
rather than a Service DNS name.
Native sidecar containers
On Kubernetes 1.29+ you can run the agent as a native sidecar — an entry in
initContainers with restartPolicy: Always — so it starts before the app container and
is tracked by the pod's lifecycle. The plain multi-container form above works on all
versions.
Prometheus Integration
Prometheus scrapes the proxy, using the agent path as the metrics_path. The path must
match the path value from the agent ConfigMap.
Add a job to prometheus.yml pointing at the proxy Service:
If you run the Prometheus Operator (e.g. kube-prometheus-stack), use a ServiceMonitor
instead:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: prometheus-proxy
namespace: monitoring
labels:
release: kube-prometheus-stack # must match your Prometheus serviceMonitorSelector
spec:
selector:
matchLabels:
app: prometheus-proxy
endpoints:
# Add one endpoint per registered proxy path.
- port: http
path: /my_app_metrics
interval: 30s
The release label must match your Prometheus instance's serviceMonitorSelector. Add
one entry under endpoints for each registered proxy path.
Dynamic path discovery
Rather than listing every path by hand, enable the proxy's service-discovery endpoint and
let Prometheus pull the target list with http_sd_config. See
Service Discovery.
TLS
To secure the proxy ↔ agent gRPC channel, bundle your certificates into a Secret and mount
it into the proxy and agent containers:
# Bundle your certificate files into a Secret:
kubectl create secret generic prometheus-proxy-tls \
--namespace monitoring \
--from-file=cert.pem \
--from-file=key.pem \
--from-file=ca.pem
# Container spec excerpt — point the config at the mounted certs:
env:
- { name: PROXY_CONFIG, value: /app/tls.conf }
volumeMounts:
- { name: tls, mountPath: /app/certs, readOnly: true }
volumes:
- name: tls
secret:
secretName: prometheus-proxy-tls
The mounted config (tls.conf) references the certificate paths under /app/certs. See
TLS Setup for the full configuration, including mutual authentication.
Health Probes & Resources
The proxy and agent Deployments above wire Kubernetes probes to the admin endpoints:
| Probe | Endpoint | Meaning |
|---|---|---|
livenessProbe |
GET /ping |
Returns pong while the process is up |
readinessProbe |
GET /healthcheck |
Returns health status; non-200 if unhealthy |
Both endpoints are served on the admin port (8092 for the proxy, 8093 for the agent),
which requires ADMIN_ENABLED=true. The manifests also set conservative CPU/memory
requests and limits — tune them to your scrape volume and payload sizes.
No official Helm chart
Prometheus Proxy does not currently publish a Helm chart. The raw manifests on this page are the supported way to deploy on Kubernetes; adapt them to Kustomize or your own chart as needed.
Next Steps
-
Agent Configuration
Path configs, HTTP client tuning, and scrape options
-
Security & TLS
Secure the proxy-agent channel with TLS and mutual authentication
-
Service Discovery
Let Prometheus discover proxied targets dynamically
-
:material-activity:{ .lg .middle } Monitoring
Scrape the proxy's and agent's own operational metrics