mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-22 06:19:47 +08:00
* feat(server): add gateway capacity metrics and optional HPA Expose per-replica supervisor sessions, pending relay capacity, relay rejections and claim latency, and outbound peer request outcomes and latency. Use bounded labels and Prometheus histograms for the new latency metrics while preserving existing summary metrics. Add an optional Helm HPA with external-database and resource validation, conservative scale-down defaults, and support for custom metrics. Keep certificate hook pods outside gateway workload selectors. Document per-pod scraping, scaling limits, upgrade behavior, and PostgreSQL connection sizing. Part of #3528 Signed-off-by: Emilien Macchi <emacchi@redhat.com> * feat(server): unify routing metrics and clarify replica capacity Combine local relay setup and outbound peer requests in one counter, labeled by operation, route, target, outcome, and gRPC status. Preserve peer latency metrics. Rename the rejection reason from global_capacity to replica_capacity to reflect the per-replica relay budget. Keep capacity limits unchanged. Update tests and documentation. Signed-off-by: divesh <dgude@nvidia.com> * fix(server): align routed request metric labels and outcomes Count a local relay as successful only when its supervisor claims it, the same event that answers a peer relay on the owner, and rename the outcomes to local_error and remote_error so they say where an attempt failed. Label the peer latency histogram by operation, like the routed request counter, and rename the target label to relay_kind so it does not read as the Prometheus scrape target. Rename RelayCapacity.global to per_replica, drop the per-sandbox relay capacity gauge, which no per-sandbox series can pair with, describe the latency histogram as peer-only, and restore the note that unavailable spikes are expected during rollouts. Part of #3528 Signed-off-by: Emilien Macchi <emacchi@redhat.com> --------- Signed-off-by: Emilien Macchi <emacchi@redhat.com> Signed-off-by: divesh <dgude@nvidia.com> Co-authored-by: divesh <dgude@nvidia.com>
271 lines
13 KiB
Plaintext
271 lines
13 KiB
Plaintext
---
|
|
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
title: "Gateway Metrics"
|
|
sidebar-title: "Gateway Metrics"
|
|
description: "Scrape Prometheus metrics from the OpenShell gateway, including supervisor session, relay capacity, and peer routing signals for multi-replica deployments."
|
|
keywords: "Generative AI, Cybersecurity, Observability, Metrics, Prometheus, Gateway, Kubernetes, High Availability"
|
|
---
|
|
|
|
The gateway exports Prometheus metrics for API requests, database readiness,
|
|
gateway interceptors, the OCSF log file, and multi-replica capacity. Use them
|
|
to alert on saturation, to follow rollouts, and to choose autoscaling signals.
|
|
|
|
## Enable the Metrics Endpoint
|
|
|
|
The gateway serves `GET /metrics` in the Prometheus text format on its own
|
|
listener, separate from the API and health ports. When the listener is
|
|
disabled, the gateway records no metrics. Enable it in one of these ways:
|
|
|
|
- In `gateway.toml`, set `metrics_bind_address` under `[openshell.gateway]`,
|
|
for example `metrics_bind_address = "0.0.0.0:9090"`. Refer to
|
|
[Gateway Configuration](/how-it-works/gateways/configuration).
|
|
- On the command line, pass `--metrics-port` or set `OPENSHELL_METRICS_PORT`.
|
|
The standalone gateway binary defaults to `0`, which disables the endpoint.
|
|
- In the Helm chart, set `service.metricsPort`, which defaults to `9090`. The
|
|
chart renders the TOML key, a container port named `metrics`, and a
|
|
`metrics` port on the gateway Service. Set it to `0` to disable the
|
|
endpoint.
|
|
|
|
A stopping gateway keeps serving metrics until the process exits.
|
|
|
|
<Warning>
|
|
The metrics endpoint uses plaintext HTTP with no authentication. Anyone who
|
|
can reach the port can read the metrics. The Helm chart publishes the port on
|
|
the gateway Service, so a `LoadBalancer` or `NodePort` Service exposes it
|
|
outside the cluster. Keep the Service internal, and restrict the port to your
|
|
monitoring system.
|
|
</Warning>
|
|
|
|
The following NetworkPolicy keeps the API and health ports open and accepts
|
|
metrics scrapes only from the `monitoring` namespace. Adjust the ports if you
|
|
changed `service.port`, `service.healthPort`, or `service.metricsPort`.
|
|
|
|
```yaml
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: openshell-gateway-metrics
|
|
namespace: openshell
|
|
spec:
|
|
podSelector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: openshell
|
|
app.kubernetes.io/instance: openshell
|
|
policyTypes:
|
|
- Ingress
|
|
ingress:
|
|
- ports:
|
|
- port: 8080
|
|
- port: 8081
|
|
- from:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: monitoring
|
|
ports:
|
|
- port: 9090
|
|
```
|
|
|
|
## Scrape Each Replica
|
|
|
|
Capacity metrics describe one gateway replica, so scrape every gateway pod
|
|
rather than the Service, and keep the `namespace` and `pod` labels on each
|
|
series. With the Prometheus Operator, a PodMonitor adds those labels
|
|
automatically:
|
|
|
|
```yaml
|
|
apiVersion: monitoring.coreos.com/v1
|
|
kind: PodMonitor
|
|
metadata:
|
|
name: openshell-gateway
|
|
namespace: openshell
|
|
spec:
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: openshell
|
|
app.kubernetes.io/instance: openshell
|
|
podMetricsEndpoints:
|
|
- port: metrics
|
|
path: /metrics
|
|
interval: 30s
|
|
```
|
|
|
|
With a plain Prometheus configuration, use pod discovery and copy the labels
|
|
yourself:
|
|
|
|
```yaml
|
|
scrape_configs:
|
|
- job_name: openshell-gateway
|
|
kubernetes_sd_configs:
|
|
- role: pod
|
|
namespaces:
|
|
names: [openshell]
|
|
relabel_configs:
|
|
- source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_instance]
|
|
regex: openshell
|
|
action: keep
|
|
- source_labels: [__meta_kubernetes_pod_container_port_name]
|
|
regex: metrics
|
|
action: keep
|
|
- source_labels: [__meta_kubernetes_namespace]
|
|
target_label: namespace
|
|
- source_labels: [__meta_kubernetes_pod_name]
|
|
target_label: pod
|
|
```
|
|
|
|
To check one pod without Prometheus, read its metrics through the Kubernetes
|
|
API server proxy:
|
|
|
|
```shell
|
|
kubectl get --raw "/api/v1/namespaces/openshell/pods/<pod>:9090/proxy/metrics" \
|
|
| grep '^openshell_server_supervisor_sessions'
|
|
```
|
|
|
|
The API server connects to the pod from the control plane. A NetworkPolicy
|
|
like the one above blocks that connection unless it also allows the control
|
|
plane on the metrics port. `kubectl port-forward` reaches the pod through the
|
|
kubelet instead, and NetworkPolicy does not block it. Start the port-forward:
|
|
|
|
```shell
|
|
kubectl -n openshell port-forward pod/<pod> 9090:9090
|
|
```
|
|
|
|
Then read the metrics from another terminal:
|
|
|
|
```shell
|
|
curl -s http://localhost:9090/metrics | grep '^openshell_server_supervisor_sessions'
|
|
```
|
|
|
|
## Histograms and Summaries
|
|
|
|
The multi-replica latency metrics are Prometheus histograms with `_bucket`,
|
|
`_sum`, and `_count` series and buckets from 1 millisecond to 15 seconds, so
|
|
`histogram_quantile` can aggregate them across replicas. The older
|
|
`*_duration_seconds` metrics and the interceptor latency metric are summaries
|
|
with `quantile` labels that each replica computes on its own. Do not average
|
|
summary quantiles across pods.
|
|
|
|
## Multi-Replica Capacity Metrics
|
|
|
|
These metrics describe how much work one replica holds and how the replicas
|
|
interact. No metric carries a sandbox, channel, endpoint, or replica
|
|
identifier, because the scrape target already identifies the replica.
|
|
|
|
Supervisor sessions:
|
|
|
|
| Metric | Type | Labels | Description |
|
|
|---|---|---|---|
|
|
| `openshell_server_supervisor_sessions` | Gauge | None | Supervisor control sessions held by this replica. After a rollout settles, the sum across pods equals the number of connected sandboxes. |
|
|
|
|
Relays:
|
|
|
|
| Metric | Type | Labels | Description |
|
|
|---|---|---|---|
|
|
| `openshell_server_relay_pending` | Gauge | None | Relay channels on this replica waiting for the supervisor to connect back, including channels opened for peer replicas. A channel whose caller gave up stays counted until it is claimed or cleaned up, up to about 40 seconds. |
|
|
| `openshell_server_relay_pending_capacity` | Gauge | None | Pending relay limit per replica, `256`. |
|
|
| `openshell_server_relay_rejected_total` | Counter | `reason` | Relay opens rejected at a limit. Rejected relays fail the client request, so use this counter, not client error codes, to detect saturation. |
|
|
| `openshell_server_relay_expired_total` | Counter | None | Pending relays dropped because the supervisor did not connect back within 10 seconds. |
|
|
| `openshell_server_relay_claim_duration_seconds` | Histogram | None | Time from opening a relay to the supervisor claiming it. |
|
|
|
|
Routed requests include local relay setup and outbound requests to the replica
|
|
that owns a sandbox's supervisor session:
|
|
|
|
| Metric | Type | Labels | Description |
|
|
|---|---|---|---|
|
|
| `openshell_server_routed_request_attempts_total` | Counter | `operation`, `route`, `relay_kind`, `outcome`, `grpc_code` | Local relay setup and outbound peer attempts, counted once when they finish or are cancelled. Each retry counts separately. |
|
|
| `openshell_server_peer_request_duration_seconds` | Histogram | `operation`, `outcome` | Latency of outbound peer requests only (`route="peer"`). For relays, until the owner's supervisor claimed the relay. |
|
|
|
|
The labels take these values:
|
|
|
|
- `operation` is `relay`, `report_provider_readiness`, `report_endpoint_status`,
|
|
or `get_sandbox_provider_status`. Only relay setup includes the local route;
|
|
the other operations count outbound peer calls, not local API handling. On the
|
|
owning replica, `openshell_server_grpc_requests_total` records the matching
|
|
`method`, such as `PeerRelay` for `relay`.
|
|
- `relay_kind` is `ssh` or `tcp` for relays, and `none` for other operations. An
|
|
omitted relay target counts as `ssh`, matching the protocol's compatibility
|
|
behavior. TCP hostnames and ports are not labels.
|
|
- `route` is `local` when this replica tries its own supervisor session, or
|
|
`peer` when it tries another replica. A routed retry counts again, including a
|
|
change from local to peer routing. Waiting for an owner does not count. Incoming
|
|
`PeerRelay` requests do not increment this counter on the receiving replica;
|
|
they appear in that replica's gRPC request counter instead. This counter does
|
|
not measure active connections, unique client requests, or unfinished attempts.
|
|
- `reason` is `replica_capacity` when the gateway replica's pending-relay budget
|
|
is full, or `sandbox_capacity` when one sandbox's budget on that replica, 32
|
|
pending relays, is full.
|
|
- `outcome` is `success`, `local_error`, or `remote_error`. A relay succeeds
|
|
when the supervisor claims it; this does not measure the data stream.
|
|
`local_error` means the attempt failed on this replica, and `remote_error`
|
|
means the owning replica returned an error or the open connection to it
|
|
failed. On `route="local"` this replica is the owner, so failures that appear
|
|
as `remote_error` on the peer route, such as an unclaimed relay
|
|
(`deadline_exceeded`) or a full relay budget (`resource_exhausted`), appear as
|
|
`local_error`. A cancelled attempt records `local_error` with
|
|
`grpc_code="cancelled"`, even when the owner was already handling it.
|
|
- `grpc_code` is the lowercase gRPC status name, such as `ok`, `unavailable`,
|
|
or `resource_exhausted`, from local setup, the local supervisor claim, or the
|
|
peer RPC. For a peer relay, the gateway then reports a failure to the client
|
|
as `UNAVAILABLE`. The other RPCs return the owner's status to the client
|
|
unchanged. The label is not named `code` because
|
|
`openshell_server_grpc_requests_total` uses that name for the numeric status
|
|
code.
|
|
|
|
Gauges and counters exist from startup. They start at `0`, except
|
|
`openshell_server_relay_pending_capacity`, which starts at its limit. `openshell_server_routed_request_attempts_total` starts with seven
|
|
`outcome="success", grpc_code="ok"` series: four relay kind and route
|
|
combinations, and three other peer operations with `relay_kind="none"`. Other outcomes and codes,
|
|
and every histogram, appear after their first sample, so write alert expressions
|
|
that tolerate absent series.
|
|
|
|
## Request and Readiness Metrics
|
|
|
|
The gateway also records every API request and its background database check:
|
|
|
|
| Metric | Type | Labels | Description |
|
|
|---|---|---|---|
|
|
| `openshell_server_grpc_requests_total` | Counter | `method`, `code` | gRPC requests by method name and numeric status code, including internal peer requests. Streaming RPCs record the status known when the response headers are sent. |
|
|
| `openshell_server_grpc_request_duration_seconds` | Summary | `method`, `code` | Time until the response headers are sent. For streaming RPCs this covers setup only. |
|
|
| `openshell_server_http_requests_total` | Counter | `path`, `status` | HTTP requests on the API port. `path` is `/_ws_tunnel`, `/auth`, or `unknown`. |
|
|
| `openshell_server_http_request_duration_seconds` | Summary | `path`, `status` | Duration of those HTTP requests. |
|
|
| `openshell_server_readiness_database_healthy` | Gauge | None | `1` when the last background database check succeeded, otherwise `0`. |
|
|
| `openshell_server_readiness_database_probe_duration_seconds` | Summary | `outcome` | Duration of the background database check. `outcome` is `success`, `db_error`, or `timeout`. |
|
|
|
|
## Gateway Interceptor Metrics
|
|
|
|
Gateways with [gateway interceptors](/extensibility/gateway-interceptors)
|
|
record their evaluations:
|
|
|
|
| Metric | Type | Labels | Description |
|
|
|---|---|---|---|
|
|
| `openshell_gateway_interceptor_latency_seconds` | Summary | None | Duration of interceptor calls. |
|
|
| `openshell_gateway_interceptor_evaluations_total` | Counter | `decision`, `interceptor`, `binding_id` | Interceptor evaluations by decision. |
|
|
| `openshell_gateway_interceptor_patches_total` | Counter | `interceptor`, `binding_id` | Patches that interceptors applied. |
|
|
| `openshell_gateway_interceptor_fail_open_total` | Counter | None | Interceptor failures that let the request continue. |
|
|
| `openshell_gateway_interceptor_fail_closed_total` | Counter | None | Interceptor failures that rejected the request. |
|
|
| `openshell_gateway_interceptor_post_commit_observation_failures_total` | Counter | `stage` | Failed post-commit observations. `stage` is `response_body` or `evaluation`. |
|
|
|
|
## OCSF Log Metrics
|
|
|
|
Gateways that write an
|
|
[OCSF JSONL file](/how-it-works/gateways/configuration#ocsf-jsonl-output)
|
|
record its queue and writer:
|
|
|
|
| Metric | Type | Labels | Description |
|
|
|---|---|---|---|
|
|
| `openshell_ocsf_log_queued_total` | Counter | None | Records queued for the file. |
|
|
| `openshell_ocsf_log_written_total` | Counter | None | Records written to the file. |
|
|
| `openshell_ocsf_log_dropped_total` | Counter | `reason` | Records discarded, by bounded reason. |
|
|
| `openshell_ocsf_log_writer_errors_total` | Counter | None | Failed writes, reopens, and retention cleanups. |
|
|
| `openshell_ocsf_log_recovery_discarded_bytes_total` | Counter | None | Bytes of an incomplete trailing line removed when the writer opens the file. |
|
|
| `openshell_ocsf_log_queue_records` | Gauge | None | Records waiting to be written. |
|
|
| `openshell_ocsf_log_queue_bytes` | Gauge | None | Encoded bytes waiting to be written. |
|
|
|
|
## Next Steps
|
|
|
|
- To monitor capacity and choose autoscaling signals for multiple gateway
|
|
replicas, refer to
|
|
[High Availability](/kubernetes/high-availability#monitor-capacity).
|
|
- To configure the metrics listener in `gateway.toml`, refer to
|
|
[Gateway Configuration](/how-it-works/gateways/configuration).
|