Prometheus¶
Prometheus is an open-source monitoring and alerting toolkit designed for reliability and scalability. SerOps uses it as a metric provider, querying time-series data via PromQL to fetch performance metrics for correlation with CI/CD events.
| Type | Metrics |
| Auth | Basic / Bearer |
| Query Language | PromQL |
Prerequisites¶
- Prometheus 2.x or later
- Network access from the SerOps backend to the Prometheus server
- Valid credentials if authentication is enabled
Configuration¶
{
"provider": "prometheus",
"display_name": "Production Prometheus",
"capabilities": ["metrics"],
"host": "http://prometheus:9090",
"extra_config": {
"bearer_token": "optional-auth-token"
}
}
Authentication Options¶
Prometheus supports multiple authentication methods depending on your deployment:
- No Auth: Many Prometheus deployments run without authentication behind a VPN or network policy. Simply omit credentials.
- Basic Auth: Provide
usernameandpasswordat the top level for deployments using a reverse proxy with basic auth. - Bearer Token: Provide
bearer_tokeninextra_configfor token-based authentication.
Extra Configuration¶
| Field | Type | Description |
|---|---|---|
bearer_token |
string | Bearer token for authentication |
Kubernetes setup¶
Set the integration's topology_mode to kubernetes (Integrations → your Prometheus integration → Topology Mode, or PATCH the API) to switch SerOps from node-level PromQL to container-level metrics aggregated by service:
| Query | VM mode | Kubernetes mode |
|---|---|---|
| CPU | node_cpu_seconds_total{...} |
sum by (service_name) (rate(container_cpu_usage_seconds_total{...}[5m])) |
| Memory total | node_memory_MemTotal_bytes{...} |
sum by (service_name) (container_spec_memory_limit_bytes{...} > 0) |
| Memory free | node_memory_MemFree_bytes{...} |
sum by (service_name) (clamp_min(container_spec_memory_limit_bytes{...} - container_memory_working_set_bytes{...}, 0)) |
Services with no memory limit report no total
container_spec_memory_limit_bytes is 0 for a container with no
memory limit, which is the majority of real workloads — requests are
common, limits are not. The > 0 filter means such services are
absent from the memory-total series rather than reported as having
zero memory, and clamp_min keeps free memory from going negative.
If a service you expect is missing from memory charts, it has no
memory limit set; add one, or read its usage from
container_memory_working_set_bytes directly.
Prerequisites: your Prometheus must scrape cAdvisor / kubelet metrics
(standard in kube-prometheus-stack) AND have a service_name label on the
container metrics themselves. That second part is the one people get
wrong, so read the next warning before copying anything.
service_name must be attached to the cAdvisor job, not the pods job
container_cpu_usage_seconds_total and container_memory_* are
produced by cAdvisor, scraped through the kubelet. They are not
produced by the job that scrapes your pods' own /metrics endpoints.
So relabeling on a role: pod job does nothing for them: cAdvisor
samples arrive carrying only namespace, pod and container, and
sum by (service_name) then collapses everything into a single
series with an empty service_name — which looks exactly like
"node-level metrics" and is the most common cause of
"my Kubernetes metrics aren't per-service".
The fix is metric_relabel_configs on the cAdvisor job.
relabel_configs act on the target (one per node, which knows
nothing about pods); metric_relabel_configs act on each sample,
after cAdvisor has already labelled it.
Verify in one query — it must return one series per service, not one series total:
Raw Prometheus config¶
Validated end-to-end on Kubernetes 1.31 / containerd:
scrape_configs:
# Your application pods' own /metrics endpoints.
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
# Fallback first, canonical `app` second — the last rule to write
# wins, and `regex: (.+)` stops an absent label from blanking the
# value the previous rule set.
- source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name]
regex: (.+)
target_label: service_name
- source_labels: [__meta_kubernetes_pod_label_app]
regex: (.+)
target_label: service_name
- source_labels: [__meta_kubernetes_namespace]
target_label: environment
- target_label: organisation_name
replacement: <YOUR-ORG>
- target_label: project_name
replacement: <YOUR-PROJECT>
# Keep only pods you want scraped (add pod annotations to opt in).
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
# cAdvisor — this is where container_cpu_* / container_memory_* come from.
- job_name: 'kubernetes-cadvisor'
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
# THIS is what makes `sum by (service_name)` work.
metric_relabel_configs:
- source_labels: [container]
regex: (.+)
target_label: service_name
- source_labels: [namespace]
regex: (.+)
target_label: environment
- target_label: organisation_name
replacement: <YOUR-ORG>
- target_label: project_name
replacement: <YOUR-PROJECT>
This maps the container name to service_name, which is correct when
your container name matches your service name — the usual case. If they
differ, or you run sidecars you want folded into the parent service, join
against kube_pod_labels from
kube-state-metrics
with a recording rule instead, and have it emit service_name.
With kube-prometheus-stack¶
The chart manages the kubelet/cAdvisor scrape job for you, so set the same sample-level relabeling through its values rather than writing the job yourself. Note the field names are camelCase here, not snake_case — see the warning below:
kubelet:
serviceMonitor:
cAdvisorMetricRelabelings:
- sourceLabels: [container]
regex: (.+)
targetLabel: service_name
- sourceLabels: [namespace]
regex: (.+)
targetLabel: environment
- targetLabel: organisation_name
replacement: <YOUR-ORG>
- targetLabel: project_name
replacement: <YOUR-PROJECT>
camelCase here, snake_case in raw Prometheus config
These values become a ServiceMonitor custom resource, and the
Prometheus Operator CRD spells the fields sourceLabels and
targetLabel. Raw Prometheus config uses source_labels and
target_label. The two are not interchangeable, and getting it wrong
fails in the worst possible way:
Kubernetes silently discards the unknown snake_case keys, leaving
action: replace with no target. The operator then rejects the entire
kubelet ServiceMonitor, so you lose every container metric — not
just service_name. Prometheus simply has no kubelet job, with no
error in Prometheus itself.
The rejection is only visible in the operator's log:
kubectl logs -n monitoring -l app=kube-prometheus-stack-operator \
| grep -i 'rejected due to invalid configuration'
Expected output when correct: nothing. When wrong you get
metricRelabelConfigs: [0]: relabel configuration for replace action
needs targetLabel value.
Confirm the job exists and container metrics carry the label:
Verify with the ingest validator:
The report shows what tags SerOps sees in your host discovery — flags any missing service_name / organisation_name / project_name.
Health Check¶
Response:
{
"integrations": [
{
"provider": "PrometheusMetricProvider",
"provider_type": "metric",
"healthy": true,
"message": "OK"
}
]
}
Troubleshooting¶
| Issue | Solution |
|---|---|
| "Connection refused" | Verify host URL is reachable from SerOps backend |
| "401 Unauthorized" | Check username/password or bearer_token are correct |
| "No data returned" | Verify the time range has data; check that capabilities includes "metrics" |