Metrics¶
The operator exposes Prometheus metrics over HTTP on the /metrics endpoint of its metrics server, which listens on the address given by the manager's --metrics-bind-address flag. The binary's default of 0 disables the server.
Enabling the Metrics Endpoint¶
Helm: set metrics.enabled: true. The chart then binds the server to metrics.port (default 8080) and creates the pocket-id-operator-metrics-service Service with an http port, labelled control-plane: controller-manager.
Kustomize / dist/install.yaml: the metrics server is enabled on port 8080, with the same Service in the pocket-id-operator-system namespace.
ServiceMonitor¶
If you are using the Prometheus Operator, the chart creates a ServiceMonitor with metrics.serviceMonitor.enabled: true. To apply it manually instead:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: pocket-id-operator-metrics
namespace: pocket-id-operator-system
spec:
endpoints:
- path: /metrics
port: http
scheme: http
honorLabels: true
selector:
matchLabels:
control-plane: controller-manager
app.kubernetes.io/name: pocket-id-operator
Static Scrape Config¶
Without the Prometheus Operator, point a scrape config at the metrics Service:
- job_name: pocket-id-operator
scheme: http
static_configs:
- targets: ['pocket-id-operator-metrics-service.pocket-id-operator-system.svc.cluster.local:8080']
Grafana Dashboard¶
A pre-built dashboard is included in the chart at charts/pocket-id-operator/files/grafana-dashboard.json.
- Helm (ConfigMap):
metrics.dashboard.enabled: truecreates a ConfigMap labelledgrafana_dashboard: "1"(set bymetrics.dashboard.labels), which Grafana's sidecar picks up automatically. - Helm (GrafanaDashboard): when the grafana-operator CRDs are installed, the chart also creates a
GrafanaDashboardreferencing that ConfigMap. Its instance selector defaults tografana.internal/instance: grafanaand can be overridden withmetrics.dashboard.grafanaDashboard.instanceSelector.matchLabels. Setmetrics.dashboard.grafanaDashboard.enabled: falseto skip it. - Manual import: in Grafana go to Dashboards → Import → Upload JSON file, and select
charts/pocket-id-operator/files/grafana-dashboard.jsonfrom the repository.
Alerting Rules¶
The chart creates a PrometheusRule automatically when the monitoring.coreos.com/v1 CRDs are installed. Disable it with metrics.prometheusRule.enabled: false, turn off individual rules with metrics.prometheusRule.rules.<name>.enabled: false, and append your own with metrics.prometheusRule.additionalRules. See the template for the exact expressions.
| Rule | Severity | Fires when |
|---|---|---|
PocketIDInstanceNotReady |
critical | A PocketIDInstance has not been Ready for 5 minutes. |
PocketIDResourceNotReady |
warning | Any other custom resource has not been Ready for 5 minutes. |
PocketIDAPIHighErrorRate |
critical | More than 50% of Pocket-ID API requests have failed for 5 minutes. |
PocketIDAPISlowRequests |
warning | Pocket-ID API p99 latency has been above 5 seconds for 5 minutes. |
PocketIDExternalDeletions |
warning | A resource was deleted outside the operator in the last 10 minutes. |
PocketIDReconcileErrors |
warning | A controller has produced reconcile errors continuously for 10 minutes. |
PocketIDWorkQueueBackup |
warning | A work queue has held more than 10 items for 10 minutes. |
PocketIDOIDCClientPKCESupported |
warning | A client uses PKCE but has spec.pkceEnabled: false. |
PocketIDOIDCClientRotationFailing |
warning | A client-secret rotation failed in the last hour. |
PocketIDOIDCClientRotationOverdue |
warning | A client with no maintenance window has been eligible for rotation for longer than its interval (capped at 48h) without rotating, usually from instance min-spacing starvation or a stalled operator. |
PocketIDOIDCClientRotationWindowMissed |
warning | A window-driven client has had its window open and close without rotating for over 24h. |
PocketIDOIDCClientRotationDeferrals |
info | A due rotation has been deferred by min_spacing or window_error for over 6 hours. The benign window_closed reason is excluded, and window_missed has its own rule. |
Default Metrics¶
The following metrics are emitted by controller-runtime and are available automatically. They are not duplicated by the custom metrics below.
| Metric | Description |
|---|---|
controller_runtime_reconcile_total |
Total reconcile calls per controller and result |
controller_runtime_reconcile_errors_total |
Total reconcile errors per controller |
controller_runtime_reconcile_time_seconds |
Reconcile duration per controller |
workqueue_* |
Work-queue depth, latency, and processing time |
Custom Metrics¶
Pocket-ID API¶
pocketid_operator_pocketid_api_requests_total¶
Type: Counter · Labels: operation, result
Counts every call made to the Pocket-ID REST API, partitioned by operation and outcome. Use this to track error rates and call volumes for individual API operations.
| Label | Values |
|---|---|
operation |
Snake-case operation name, see below |
result |
success, not_found, or error |
All operation names
`get_current_version`, `list_users`, `get_user`, `create_user`, `update_user`, `update_user_custom_claims`, `delete_user`, `create_one_time_access_token`, `create_api_key_for_user`, `delete_api_key`, `list_user_groups`, `get_user_group`, `create_user_group`, `update_user_group`, `update_user_group_users`, `update_user_group_custom_claims`, `delete_user_group`, `list_oidc_clients`, `get_oidc_client`, `create_oidc_client`, `update_oidc_client`, `update_oidc_client_allowed_groups`, `delete_oidc_client`, `delete_oidc_client_logo`, `refresh_oidc_client_metadata`, `create_oidc_client_secret`, `delete_oidc_client_secret`, `get_oidc_client_scim_service_provider`, `create_scim_service_provider`, `update_scim_service_provider`, `delete_scim_service_provider`, `list_apis`, `get_api`, `create_api`, `update_api`, `update_api_permissions`, `set_api_cimd_access`, `delete_api`, `list_api_client_grants`, `set_api_client_grant`, `remove_api_client_grant`pocketid_operator_pocketid_api_request_duration_seconds¶
Type: Histogram · Labels: operation · Buckets: 1ms, then the Prometheus defaults (5ms to 10s)
Latency of each Pocket-ID API call in seconds, partitioned by operation. Use this to identify slow API calls or to alert on p99 latency regressions.
Resource Lifecycle¶
pocketid_operator_resource_operations_total¶
Type: Counter · Labels: kind, operation
Counts changes the operator made in Pocket-ID. An increment means the operator actually created, adopted, updated, or deleted something, not just that a reconcile ran.
| Label | Values |
|---|---|
kind |
PocketIDUser, PocketIDUserGroup, PocketIDOIDCClient, PocketIDAPI |
operation |
created, adopted, updated, deleted |
created: the resource did not exist in Pocket-ID and was created.adopted: the resource already existed in Pocket-ID and was taken over without being recreated.updated: the operator detected drift and pushed a change to Pocket-ID.deleted: the operator deleted the resource from Pocket-ID while finalizing the custom resource.
pocketid_operator_resource_ready¶
Type: Gauge · Labels: kind, namespace, name
Current readiness of each custom resource: 1 when the Ready condition is True, 0 otherwise. The series is removed when the resource is deleted, so stale series do not accumulate.
| Label | Values |
|---|---|
kind |
PocketIDInstance, PocketIDUser, PocketIDUserGroup, PocketIDOIDCClient, PocketIDAPI |
namespace |
Namespace of the resource |
name |
Name of the resource |
pocketid_operator_external_deletions_total¶
Type: Counter · Labels: kind
Counts resources deleted from Pocket-ID outside the operator, e.g. in the Pocket-ID UI or through its API. The operator detects this when a lookup of a resource it manages returns 404, and recreates it. A non-zero value typically indicates out-of-band changes in Pocket-ID.
| Label | Values |
|---|---|
kind |
PocketIDUser, PocketIDUserGroup, PocketIDOIDCClient, PocketIDAPI |
API Keys¶
pocketid_operator_api_key_operations_total¶
Type: Counter · Labels: instance_namespace, instance_name, operation
Counts PocketIDUser API keys the operator created or deleted, per PocketIDInstance. Keys provided through spec.apiKeys[].secretRef are not counted.
| Label | Values |
|---|---|
instance_namespace |
Namespace of the user's PocketIDInstance |
instance_name |
Name of the user's PocketIDInstance |
operation |
created or deleted |
Instance¶
pocketid_operator_instance_info¶
Type: Gauge (info-style, always 1) · Labels: namespace, name, version, deployment_type, app_url
Metadata about each PocketIDInstance. The value is always 1; use the labels to identify the instance. When the Pocket-ID version changes, the old series is deleted and replaced so stale series do not accumulate.
| Label | Values |
|---|---|
namespace |
Namespace of the PocketIDInstance |
name |
Name of the PocketIDInstance |
version |
Pocket-ID version (e.g. v2.14.0), empty until fetched |
deployment_type |
Deployment or StatefulSet |
app_url |
Value of spec.appUrl, empty if not set |
User Groups¶
pocketid_operator_usergroup_member_count¶
Type: Gauge · Labels: namespace, name
Number of members in each PocketIDUserGroup, including those not managed by the operator, as reported by Pocket-ID after each successful reconcile. The series is removed when the group is deleted.
OIDC Clients¶
pocketid_operator_oidcclient_allowed_group_count¶
Type: Gauge · Labels: namespace, name
Number of user groups allowed to access each PocketIDOIDCClient, from both spec.allowedUserGroups and groups listing the client in spec.allowedOIDCClients. 0 means the client is not group-restricted. The series is removed when the client is deleted.
pocketid_operator_oidcclient_pkce_supported¶
Type: Gauge (0/1) · Labels: namespace, name
Mirrors status.pkceSupported: 1 when Pocket-ID observed the client using PKCE during an authorization flow while spec.pkceEnabled is false, signalling PKCE can be enabled. Use sum(pocketid_operator_oidcclient_pkce_supported) for the number of clients that should enable it. The series is removed when the client is deleted.
Secret Rotation¶
These metrics track scheduled client-secret rotation, configured with spec.clientSecretRotation on a PocketIDOIDCClient and throttled by spec.OIDCClientRotation.minSpacing on a PocketIDInstance. A rotation fires only when its trigger is due (an elapsed interval, a window opening, or both), the maintenance window is open when one is configured, and the instance's minimum spacing is satisfied.
All of them carry the labels namespace and name of the PocketIDOIDCClient, and are removed when the client is deleted.
pocketid_operator_oidcclient_rotation_enabled¶
Type: Gauge (0/1)
Whether scheduled rotation is enabled for the client (spec.clientSecretRotation.enabled). Use sum(pocketid_operator_oidcclient_rotation_enabled) for the number of clients with rotation on. When rotation is disabled this is 0 and the schedule gauges below are removed.
pocketid_operator_oidcclient_rotation_interval_seconds¶
Type: Gauge
The configured rotation interval in seconds. Present for interval-driven clients; absent for window-driven clients (no interval), whose schedule is described by the window gauges below.
pocketid_operator_oidcclient_last_rotation_timestamp_seconds¶
Type: Gauge (Unix seconds)
Time of the most recent client-secret rotation, read from the credentials Secret's pocketid.internal/last-rotated-at annotation. Compute the time since the last rotation with time() - pocketid_operator_oidcclient_last_rotation_timestamp_seconds. Absent until the client has rotated at least once.
pocketid_operator_oidcclient_next_rotation_timestamp_seconds¶
Type: Gauge (Unix seconds)
Time at which the secret next becomes eligible for rotation: the rotation anchor plus the interval. The rotation may still be delayed by the maintenance window or the instance's minimum spacing, so a value in the past means "eligible now" rather than "rotating now". Present for interval-driven clients; for window-driven clients, see pocketid_operator_oidcclient_rotation_window_next_open_timestamp_seconds instead.
pocketid_operator_oidcclient_rotation_window_open¶
Type: Gauge (0/1)
Whether the client's maintenance window is currently open, refreshed on every reconcile (by default every ~2 minutes). The bundled dashboard shows it as the Window column of the Rotation Schedule table, and it also works well in a state-timeline panel. Only present for clients that configure spec.clientSecretRotation.window.
pocketid_operator_oidcclient_rotation_window_next_open_timestamp_seconds¶
Type: Gauge (Unix seconds)
Time at which the maintenance window next opens. While the window is open, this is the following opening. Render the time until the next window with pocketid_operator_oidcclient_rotation_window_next_open_timestamp_seconds - time(). Only present for clients that configure a window.
pocketid_operator_oidcclient_rotation_window_next_close_timestamp_seconds¶
Type: Gauge (Unix seconds)
Time at which the maintenance window next closes: the close of the current window while it is open, otherwise the close of the next one. Render the time until it closes with pocketid_operator_oidcclient_rotation_window_next_close_timestamp_seconds - time(). The bundled dashboard shows it as the Window Closes (in) column of the Rotation Schedule table. Only present for clients that configure a window.
pocketid_operator_oidcclient_secret_rotations_total¶
Type: Counter · Additional labels: result, trigger
Counts client-secret rotations that reached Pocket-ID, partitioned by outcome and what triggered them. Use it to track rotation success and failure rates, and to alert on failed scheduled rotations.
All eight result × trigger series are seeded at 0 the first time the operator reconciles a client with a secret, so a client's first rotation of any kind shows up as a 0 → 1 step. Without the seed, increase() and rate() could not measure it, and a one-off rotation would be dropped from the dashboard's event timeline and the alerts.
| Label | Values |
|---|---|
result |
success, error |
trigger |
scheduled (interval- or window-driven rotation), manual (the regenerate-client-secret annotation), initial (no credentials Secret existed yet, e.g. a newly created or adopted client), drift (the stored secret was deleted in Pocket-ID and replaced) |
pocketid_operator_oidcclient_secrets¶
Type: Gauge
How many secrets Pocket-ID holds for the client, expired ones included, as read at the start of the last reconcile, so a secret created or retired during that reconcile shows up on the next one. Only confidential clients report it. Pocket-ID allows at most 20 secrets per client, so a value climbing towards that means superseded secrets are not being retired, usually from repeated retirement failures.
A 0 means every secret was deleted outside the operator. Unless the secret is managed externally (spec.secret.storeClientSecret: false), the operator creates a replacement on the next reconcile, so this should not persist.
pocketid_operator_oidcclient_secrets_retired_total¶
Type: Counter · Additional labels: reason
Counts superseded secrets the operator deleted from Pocket-ID. In steady state this tracks rotations one-for-one.
| Label | Values |
|---|---|
reason |
superseded (the overlap elapsed), cap (the client hit Pocket-ID's 20-secret limit, so a secret was retired early to make room for a replacement) |
pocketid_operator_oidcclient_rotation_deferred_total¶
Type: Counter · Additional labels: reason
Counts times a scheduled rotation was due (an elapsed interval, or an unserved window opening in window-driven mode) but another gate kept it from firing.
| Label | Values |
|---|---|
reason |
window_closed (interval-driven: an elapsed interval is waiting for its window to open, which is healthy), window_missed (window-driven: an opening came and went unserved, so the rotation has fallen a full cycle behind), min_spacing (the instance's minimum spacing is not yet satisfied), window_error (invalid window configuration) |