Missing Resource Detection
Detect companion resources that should exist based on workload configuration.
Table of contents
- Overview
- Built-in Rules
- Rule Details
- How Results Appear
- Debounce Behavior
- Configuration
- Custom Requirements via Annotations
Overview
Missing resource detection identifies companion Kubernetes resources that should exist based on a workload’s annotations, ports, or namespace labels but don’t. Unlike adapters (which parse existing policy objects), requirement rules reason about the absence of expected resources.
For example, a Deployment that exposes a port named metrics conventionally needs a ServiceMonitor so Prometheus can scrape it. If no matching ServiceMonitor exists, Potoo flags the gap.
Results surface in two places:
- MCP pre-check (
potoo_checktool) – returnsmissing_prerequisitesimmediately, with no debounce delay. - ConstraintReport (
status.machineReadable.missingResources) – uses the evaluator’s debounce window (default 120 seconds) to avoid false positives during rolling deployments.
Built-in Rules
| Rule | Trigger Condition | Expected Resource | Severity |
|---|---|---|---|
prometheus-monitor | Port named metrics or http-metrics | ServiceMonitor or PodMonitor | Warning |
istio-routing | Annotation sidecar.istio.io/status | VirtualService or DestinationRule | Warning |
istio-mtls | Namespace label istio-injection=enabled | PeerAuthentication | Warning |
cert-issuer | Annotation cert-manager.io/cluster-issuer or cert-manager.io/issuer-name | ClusterIssuer or Issuer | Critical |
crd-installed | Metrics port, sidecar annotation, or cert-manager annotation present | Corresponding CRD exists in cluster | Warning |
annotation-requirements | Annotation potoo.io/requires | Resources declared in the annotation | Warning |
Rule Details
prometheus-monitor
Trigger: Any container in the workload’s pod template exposes a port named metrics or http-metrics.
Detection logic:
- Scan
spec.template.spec.containers[].ports[].name(orspec.containers[].ports[].namefor bare Pods). - If a matching port is found, extract pod template labels (falling back to top-level labels).
- Look for a
ServiceMonitor(monitoring.coreos.com/v1) whosespec.selector.matchLabelsmatches the workload’s labels. - If no ServiceMonitor matches, look for a
PodMonitor(monitoring.coreos.com/v1) with matching labels. - If neither exists, emit a Warning constraint.
Why it matters: Without a ServiceMonitor or PodMonitor, Prometheus will not scrape the metrics endpoint even though the workload is configured to expose one.
istio-routing
Trigger: Workload has the annotation sidecar.istio.io/status (injected by the Istio sidecar injector).
Detection logic:
- List all
VirtualServiceresources (networking.istio.io/v1) in the workload’s namespace. - Check
spec.http[].route[].destination.host,spec.tcp[].route[].destination.host, andspec.tls[].route[].destination.hostfor a host that matches the workload name. - Host matching accepts short names (
my-app), namespaced form (my-app.default), and FQDN (my-app.default.svc.cluster.local). - If no VirtualService matches, check
DestinationRuleresources (networking.istio.io/v1) viaspec.host. - If neither exists, emit a Warning constraint.
Assumption: The workload name matches its Service name (conventional Kubernetes pattern).
istio-mtls
Trigger: The workload’s namespace has the label istio-injection=enabled.
Detection logic:
- List
PeerAuthenticationresources (security.istio.io/v1) in the workload’s namespace. - If none exist, check for a mesh-wide PeerAuthentication in
istio-system(one with no selector or emptymatchLabels). - If the
istio-systemnamespace is inaccessible (RBAC restrictions), the mesh-wide check is silently skipped – this is non-fatal. - If no PeerAuthentication is found at either scope, emit a Warning constraint.
Why it matters: Without an explicit PeerAuthentication, mTLS behavior depends on Istio’s mesh-wide defaults, which may not match the namespace’s security requirements.
cert-issuer
Trigger: Workload has either cert-manager.io/cluster-issuer or cert-manager.io/issuer-name annotation.
Detection logic:
- If
cert-manager.io/cluster-issueris set, look up the namedClusterIssuer(cert-manager.io/v1, cluster-scoped). - If the ClusterIssuer exists, stop (cert-manager uses one issuer annotation at a time).
- Otherwise, if
cert-manager.io/issuer-nameis set, look up the namedIssuer(cert-manager.io/v1, namespace-scoped). - If the referenced issuer does not exist, emit a Critical constraint.
Why this is Critical: A missing issuer means cert-manager cannot issue or renew TLS certificates for the workload, which can cause TLS failures and outages.
How Results Appear
MCP Pre-check
The potoo_check tool returns missing resources in the missing_prerequisites array. Results are computed on demand with no debounce delay:
{
"missing_prerequisites": [
{
"expected_kind": "ServiceMonitor",
"expected_api_version": "monitoring.coreos.com/v1",
"reason": "Workload has a port named metrics or http-metrics but no Prometheus monitor targets it",
"severity": "Warning",
"for_workload": "production/Deployment/my-app"
}
]
}
See MCP Tools Reference for the full response schema.
ConstraintReport
The report reconciler populates status.machineReadable.missingResources using the evaluator, which applies debounce:
machineReadable:
missingResources:
- expectedKind: "ServiceMonitor"
expectedAPIVersion: "monitoring.coreos.com/v1"
reason: "Workload has a port named metrics or http-metrics but no Prometheus monitor targets it"
severity: "Warning"
forWorkload:
kind: "Deployment"
name: "api-server"
remediation:
summary: "Create ServiceMonitor"
steps: [...]
See ConstraintReport CRD for the full schema.
Debounce Behavior
The evaluator debounces missing-resource alerts to prevent false positives during deployments. When resources are deployed via GitOps or CI/CD, the workload and its companion resources may arrive at slightly different times. Without debouncing, Potoo would briefly flag a missing resource that is actually in-flight.
| Setting | Default | Description |
|---|---|---|
requirements.debounceSeconds | 120 | Seconds to wait before emitting a missing-resource constraint |
How debouncing works:
- The evaluator tracks
workloadUID:ruleNamepairs with both afirstSeenandlastSeentimestamp. - A constraint is only emitted after the debounce window has elapsed since first detection, with the resource still absent.
- If the resource appears before the window elapses, the tracking entry is cleared.
- If the resource disappears again, a fresh debounce window starts.
- Each evaluation refreshes
lastSeen, so actively-detected missing resources are never evicted by cleanup. Stale entries (not seen for longer than 2x the debounce duration) are cleaned up periodically.
MCP pre-check is not debounced. The potoo_check tool evaluates rules directly against the current cluster state, making it suitable for pre-deploy validation where immediate feedback is desired.
Configuration
requirements:
enabled: true
debounceSeconds: 120
See Configuration Reference for the full Helm values.
crd-installed
Trigger: Workload has any of the trigger conditions from the other rules (metrics port, sidecar.istio.io/status annotation, cert-manager.io/cluster-issuer or cert-manager.io/issuer-name annotation).
Detection logic:
- For each triggered condition, probe the corresponding CRD by issuing a namespace-scoped list via the API server.
- If the API server returns a 404 (resource type not registered), the CRD is not installed.
- Other errors (RBAC, network) are silently skipped to avoid false positives.
- If the CRD is missing, emit a Warning constraint with the fully-qualified CRD name.
Checked CRDs:
| Trigger | CRD |
|---|---|
Metrics port (metrics or http-metrics) | servicemonitors.monitoring.coreos.com |
| Istio sidecar annotation | peerauthentications.security.istio.io |
| cert-manager annotation | clusterissuers.cert-manager.io |
Why it matters: If the CRD itself is not installed, companion resource checks (ServiceMonitor, PeerAuthentication, ClusterIssuer) cannot function. This rule catches the higher-level misconfiguration — the entire operator or controller that manages those resources is absent from the cluster.
annotation-requirements
Trigger: Workload has the annotation potoo.io/requires containing a YAML list of required companion resources.
Annotation format:
annotations:
potoo.io/requires: |
- gvr: monitoring.coreos.com/v1/servicemonitors
matching: app=my-service
reason: "Prometheus won't scrape without a ServiceMonitor"
- gvr: v1/services
reason: "Workload needs a headless Service"
Each entry has:
| Field | Required | Description |
|---|---|---|
gvr | Yes | GroupVersionResource in group/version/resource format. Core API resources use 2-part format: v1/services. |
matching | No | Label selector (key=value,key2=value2). If set, at least one resource with these labels must exist. If omitted, any resource of the GVR type satisfies the requirement. |
reason | No | Human-readable explanation included in the constraint summary. If omitted, a default message is generated. |
Detection logic:
- Read the
potoo.io/requiresannotation value and parse as YAML. - For each entry, parse the GVR string and optional label selector.
- List resources of the specified GVR in the workload’s namespace.
- If
matchingis set, filter by resource metadata labels. - If no matching resource exists, emit a Warning constraint.
Error handling:
- Invalid YAML annotation: logged as a warning by the evaluator, skipped.
- Invalid GVR format or label selector: logged as a warning, skipped.
- Cluster-scoped workloads (no namespace): silently skipped.
Why it matters: Built-in rules cover common scenarios (Prometheus, Istio, cert-manager), but teams often have project-specific companion resources. This annotation lets workload owners declare arbitrary dependencies without modifying the controller.
Custom Requirements via Annotations
The potoo.io/requires annotation is the mechanism for declaring custom missing-resource checks. Workload owners add the annotation to their Deployments, StatefulSets, or other workload objects, and the annotation-requirements rule evaluates them automatically.
This replaces the need for custom rule registration — any GVR can be checked without code changes to the controller.