Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Configure Prometheus with alerting, recording rules, service discovery (K8s, Consul, EC2), federation, PromQL optimization, and Alertmanager.
.claude/skills/williamzujkowski-prometheus-configuration-specialist/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 83% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 532% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 433% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 183% | 0% |
| case-22 | ✗→✓ | ▲ Improved | 334% | 0% |
Trigger conditions:
Complements:
observability-stack-configurator: For overall observability stack designobservability-unified-dashboard: For Grafana dashboard design with Prometheus datasourcesobservability-slo-calculator: For SLO/error budget definitions that drive alerting rulesOut of scope:
Time normalization:
NOW_ET using NIST/time.gov semantics (America/New_York, ISO-8601)NOW_ET for all access dates in citationsVerify inputs:
Validate service discovery:
kubernetes_sd_config: Verify Kubernetes API access and RBAC permissionsconsul_sd_config: Verify Consul agent accessibility and service catalogec2_sd_config: Verify AWS credentials and EC2 instance tagsfile_sd_config: Verify JSON/YAML file path and refresh intervalCheck cardinality constraints:
metric_relabel_configs to drop high-cardinality labelsSource freshness:
NOW_ET)Abort if:
Scenario: Single service with static targets or file-based service discovery, basic alerting, no recording rules.
Steps:
scrape_interval: 15s (balance between data freshness and storage)evaluation_interval: 15s (how often to evaluate alerting/recording rules)external_labels for federation or remote write (e.g., datacenter: us-east-1)job_name (logical grouping, e.g., api-service, postgres-exporter)static_configs with targets: ['localhost:9090']file_sd_configs with files: ['/etc/prometheus/targets/*.json']scrape_interval override if different from globalalerts.yml with groupsseverity: critical|warning|info)alertmanager_config with static_configs pointing to Alertmanager instancesend_resolved: true to notify when alert resolvesOutput:
prometheus.yml with global config, single scrape job, alerting rules file referencealerts.yml with 2-5 basic alertsToken budget: ≤2000 tokens
Scenario: Multiple services with Kubernetes/Consul/EC2 service discovery, recording rules for expensive queries, Alertmanager routing with grouping.
Steps:
Kubernetes Service Discovery:
kubernetes_sd_configs with role: pod (discover all pods with prometheus.io/scrape: "true" annotation)yaml relabel_configs:
action: keep regex: true
action: replace target_label: __metrics_path__ regex: (.+)
action: replace target_label: __address__ regex: (^:]+)(?::\d+)?;(\d+) replacement: $1:$2
action: replace target_label: kubernetes_namespace
action: replace target_label: kubernetes_pod_name
node, pod, service, endpoints, ingress (accessed NOW_ET: https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config)Consul Service Discovery:
consul_sd_configs with server: 'consul.service.consul:8500'tags: ['production', 'monitoring-enabled']EC2 Service Discovery:
ec2_sd_configs with AWS region and filters__meta_ec2_tag_<tagkey>level:metric:operations (accessed NOW_ET: https://prometheus.io/docs/practices/naming/)job, instance, cluster)sum, avg, rate)yaml groups:
interval: 30s rules:
expr: sum(rate(http_requests_total5m])) by (job)
expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket5m])) by (job, le))
Metric Relabeling (metric_relabel_configs):
yaml metric_relabel_configs:
action: labeldrop regex: .
action: drop regex: 'expensive_metric_.'
Target Relabeling (relabel_configs):
Multi-Window Burn Rate Alerts:
yaml groups:
rules:
expr: | ( sum(rate(http_requests_total{status=~"5.."}1h])) / sum(rate(http_requests_total1h])) ) > (14.4 0.001) for: 2m labels: severity: critical annotations: summary: "Error budget burning 14.4× faster than allowed" description: "{{ $labels.job }} has {{ $value | humanizePercentage }} error rate (SLO: 99.9%, budget exhausted in 2 days)"
Symptom-Based Alerts:
yaml
expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket5m])) by (job, le)) > 0.5 for: 5m labels: severity: warning annotations: summary: "High latency on {{ $labels.job }}" description: "p95 latency is {{ $value }}s (threshold: 0.5s)"
cluster + alertname, wait 30s for batchyaml route: receiver: 'default-email' group_by: 'cluster', 'alertname'] group_wait: 30s group_interval: 5m repeat_interval: 4h routes:
severity: critical receiver: 'pagerduty'
severity: warning receiver: 'slack'
receivers:
pagerduty_configs:
slack_configs:
channel: '#alerts' title: '{{ .GroupLabels.alertname }}' text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
email_configs:
inhibit_rules:
severity: critical target_match: severity: warning equal: 'cluster', 'alertname']
Output:
prometheus.yml with Kubernetes/Consul/EC2 service discovery, relabeling configsrecording_rules.yml with 5-10 recording rules (level:metric:operations naming)alerts.yml with multi-window burn rate alerts and symptom-based alertsalertmanager.yml with routing tree, receivers, inhibition rulesToken budget: ≤6000 tokens
Scenario: Multi-datacenter federation, cardinality management, PromQL query optimization, Prometheus 3.0+ features (UTF-8, OTLP, Remote Write 2.0).
Steps:
Hierarchical Federation (Multi-DC):
NOW_ET: https://prometheus.io/docs/prometheus/latest/federation/)yaml scrape_configs:
scrape_interval: 30s honor_labels: true metrics_path: '/federate' params: 'match]':
static_configs:
Cross-Service Federation:
Query Performance Best Practices:
sum(http_requests_total) (aggregates 10k+ time series)sum(http_requests_total{job="api-service", status=~"5.."}) (aggregates 10-50 time series)api_http_requests_total) without labelsNOW_ET: https://prometheus.io/docs/prometheus/latest/querying/basics/)promql # Compute error rate using pre-recorded job-level metrics (fast) job:http_requests_total:rate5m{job="api-service", status=~"5.."} / job:http_requests_total:rate5m{job="api-service"}
Cardinality Management:
topk(10, count by (__name__)({__name__=~".+"})) to find high-cardinality metricsmetric_relabel_configs to remove high-cardinality labelsmetric_relabel_configs with action: drop to sample metricsyaml metric_relabel_configs: # Drop user_id label (high cardinality)
action: labeldrop regex: . # Keep only 5xx errors (reduce cardinality of status label)
action: keep regex: '5..'
UTF-8 Support (Prometheus 3.0+):
NOW_ET: https://prometheus.io/blog/2024/11/14/prometheus-3-0/)http_requests_total{endpoint="用户登录"} (Chinese characters now valid)OpenTelemetry OTLP Receiver (Prometheus 3.0+):
/api/v1/otlp/v1/metricsyaml otlp: protocols: http: endpoint: 0.0.0.0:9090
Remote Write 2.0 (Prometheus 3.0+):
Extract Kubernetes Annotations into Labels: yaml relabel_configs:
action: replace target_label: version
action: replace target_label: team
Drop Expensive Metrics Based on Name Pattern: yaml metric_relabel_configs:
action: drop regex: 'go_.|process_.' # Drop Go runtime metrics to save storage
Multi-Level Aggregation: yaml groups:
interval: 30s rules: # Level 1: Instance-level
expr: sum(rate(http_requests_total5m])) by (instance, job, status)
# Level 2: Job-level (aggregates Level 1)
expr: sum(instance:http_requests_total:rate5m) by (job, status)
# Level 3: Cluster-level (aggregates Level 2)
expr: sum(job:http_requests_total:rate5m) by (status)
Time-Based Routing (Mute Alerts During Maintenance): yaml route: routes:
severity: warning mute_time_intervals:
mute_time_intervals:
time_intervals:
time_intervals:
end_time: '01:00'
Grouping by Multiple Labels: yaml route: group_by: ['cluster', 'namespace', 'alertname'] group_wait: 30s group_interval: 5m repeat_interval: 12h
Output:
prometheus.yml with federation endpoints, OTLP receiver, Remote Write 2.0Token budget: ≤12000 tokens
When to use federation vs remote write:
When to create recording rules:
Alert severity assignment:
Service discovery selection:
kubernetes_sd_configs with role: pod for dynamic pod discoveryconsul_sd_configs for VM-based infrastructure with Consul service catalogec2_sd_configs for AWS instances with consistent taggingfile_sd_configs for static infrastructure or external service discoveryCardinality limits:
prometheus_tsdb_symbol_table_size_bytes >1GB or prometheus_tsdb_head_series >10MAbort conditions:
prometheus.yml schema:
yamlglobal: scrape_interval: <duration> evaluation_interval: <duration> external_labels: <label_name>: <label_value> alerting: alertmanagers: - static_configs: - targets: ['<alertmanager_host>:<port>'] rule_files: - 'alerts.yml' - 'recording_rules.yml' scrape_configs: - job_name: '<job_name>' kubernetes_sd_configs: [...] # OR consul_sd_configs, ec2_sd_configs, static_configs relabel_configs: [...] metric_relabel_configs: [...]
alerts.yml schema:
yamlgroups: - name: <group_name> rules: - alert: <alert_name> expr: <promql_expression> for: <duration> labels: severity: critical|warning|info annotations: summary: <short_description> description: <detailed_description_with_templating>
recording_rules.yml schema:
yamlgroups: - name: <group_name> interval: <duration> rules: - record: <level>:<metric>:<operations> expr: <promql_expression> labels: <label_name>: <label_value>
alertmanager.yml schema:
yamlroute: receiver: <default_receiver> group_by: [<label_name>, ...] group_wait: <duration> group_interval: <duration> repeat_interval: <duration> routes: - match: <label_name>: <label_value> receiver: <receiver_name> receivers: - name: <receiver_name> pagerduty_configs: [...] slack_configs: [...] email_configs: [...] inhibit_rules: - source_match: <label_name>: <label_value> target_match: <label_name>: <label_value> equal: [<label_name>, ...]
Required fields:
prometheus.yml: global.scrape_interval, scrape_configs[].job_namealerts.yml: alert, expr, labels.severity, annotations.summaryrecording_rules.yml: record, expralertmanager.yml: route.receiver, receivers[].nameValidation:
promtool check rules <file.yml>promtool check config prometheus.ymlamtool check-config alertmanager.ymlScenario: Scrape all pods with prometheus.io/scrape: "true" annotation, create recording rules for API latency.
prometheus.yml:
yamlglobal: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: 'kubernetes-pods' kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] action: keep regex: true - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] action: replace target_label: __metrics_path__ regex: (.+) - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port] action: replace regex: ([^:]+)(?::\d+)?;(\d+) replacement: $1:$2 target_label: __address__
recording_rules.yml:
yamlgroups: - name: api_latency interval: 30s rules: - record: job:http_request_duration_seconds:p95 expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (job, le)) - record: job:http_request_duration_seconds:p99 expr: histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (job, le))
Token budgets:
Safety:
promtool check rulesmetric_relabel_configs to drop secrets if accidentally exposedAuditability:
level:metric:operations conventionsummary and description with templatingDeterminism:
cluster + alertname produces predictable batchesPerformance:
Official Documentation:
NOW_ET)NOW_ET)NOW_ET)NOW_ET)NOW_ET)NOW_ET)Tooling:
promtool: Validate Prometheus configs and PromQL queriesamtool: Validate Alertmanager configs and manage silencesRelated Skills:
observability-stack-configurator: Overall observability stack designobservability-unified-dashboard: Grafana dashboard design with Prometheus datasourcesobservability-slo-calculator: SLO/error budget definitions for alerting ruleskubernetes-manifest-generator: Kubernetes deployment manifests for Prometheus + Alertmanager| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | fail→fail | 13,330 | 10,498 | -21% | 1 | 1 | 0% | 2,827 | 9,062 | +221% | 0 | 0 | — |
case-01 | fail→pass | 25,561 | 17,428 | -32% | 1 | 1 | 0% | 6,297 | 11,544 | +83% | 0 | 0 | — |
case-03 | pass→pass | 13,233 | 21,398 | +62% | 1 | 1 | 0% | 3,072 | 11,307 | +268% | 0 | 0 | — |
case-04 | pass→pass | 14,716 | 11,014 | -25% | 1 | 1 | 0% | 2,640 | 9,319 | +253% | 0 | 0 | — |
case-05 | pass→pass | 8,051 | 4,858 | -40% | 1 | 1 | 0% | 1,448 | 7,692 | +431% | 0 | 0 | — |
case-06 | fail→pass | 6,663 | 6,453 | -3% | 1 | 1 | 0% | 1,275 | 8,052 | +532% | 0 | 0 | — |
case-07 | pass→pass | 8,365 | 5,380 | -36% | 1 | 1 | 0% | 1,845 | 8,117 | +340% | 0 | 0 | — |
case-08 | pass→pass | 10,520 | 6,832 | -35% | 1 | 1 | 0% | 2,330 | 8,361 | +259% | 0 | 0 | — |
case-09 | fail→pass | 8,597 | 6,890 | -20% | 1 | 1 | 0% | 1,534 | 8,171 | +433% | 0 | 0 | — |
case-10 | pass→pass | 9,976 | 8,951 | -10% | 1 | 1 | 0% | 1,755 | 8,450 | +381% | 0 | 0 | — |
case-11 | pass→pass | 11,400 | 8,136 | -29% | 1 | 1 | 0% | 2,276 | 8,535 | +275% | 0 | 0 | — |
case-12 | pass→pass | 6,659 | 6,465 | -3% | 1 | 1 | 0% | 1,260 | 8,021 | +537% | 0 | 0 | — |
case-13 | pass→pass | 5,020 | 5,048 | +1% | 1 | 1 | 0% | 986 | 7,911 | +702% | 0 | 0 | — |
case-14 | pass→pass | 7,880 | 7,159 | -9% | 1 | 1 | 0% | 1,495 | 8,291 | +455% | 0 | 0 | — |
case-15 | pass→pass | 6,018 | 5,246 | -13% | 1 | 1 | 0% | 1,128 | 7,822 | +593% | 0 | 0 | — |
case-16 | pass→pass | 6,401 | 4,717 | -26% | 1 | 1 | 0% | 1,229 | 7,771 | +532% | 0 | 0 | — |
case-17 | fail→pass | 15,915 | 9,986 | -37% | 1 | 1 | 0% | 3,157 | 8,919 | +183% | 0 | 0 | — |
case-18 | pass→pass | 9,837 | 8,269 | -16% | 1 | 1 | 0% | 2,000 | 8,581 | +329% | 0 | 0 | — |
case-19 | pass→pass | 12,779 | 11,410 | -11% | 1 | 1 | 0% | 2,593 | 9,123 | +252% | 0 | 0 | — |
case-20 | pass→pass | 7,129 | 6,239 | -12% | 1 | 1 | 0% | 1,482 | 7,988 | +439% | 0 | 0 | — |
case-21 | pass→pass | 9,616 | 7,731 | -20% | 1 | 1 | 0% | 2,039 | 8,449 | +314% | 0 | 0 | — |
case-22 | fail→pass | 8,994 | 9,795 | +9% | 1 | 1 | 0% | 2,078 | 9,012 | +334% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.