Observability Backends
Ship host and container metrics from your VM deployment spaces to your own VictoriaMetrics, Grafana Mimir or Prometheus, and read them back on the service Observability tab.
An observability backend is a metrics store you run — VictoriaMetrics, Grafana Mimir or Prometheus — connected to ZopDay as an org-level integration. Attach it to a VM deployment space and that space’s VM ships host and container metrics to it every 30 seconds. The service Observability tab then reads those numbers back from your store, so the tab answers in well under a second and shows usage history instead of a single point-in-time reading.
Without a backend attached, or while the backend is unreachable, the Observability tab works exactly as before: it reads the VM directly over the cloud provider’s exec channel and shows the Checked on the VM pill.
Add a backend
Settings → Integrations → Observability → Add backend. Integrations are shared by every project in the organisation, so any VM space can attach the same backend.
| Field | What to enter |
|---|---|
| Name | A name your team recognises, e.g. prod-victoria. |
| Provider | VictoriaMetrics, Grafana Mimir or Prometheus. |
| Write URL | Where the VM pushes metrics (Prometheus remote write). https://<host>/api/v1/write for VictoriaMetrics and Prometheus, https://<host>/api/v1/push for Mimir. https only. |
| Read URL | The query_range endpoint ZopDay queries for the tab. https://<host>/api/v1/query_range, or https://<host>/prometheus/api/v1/query_range for Mimir. Must be reachable from ZopDay. https only. |
| Tenant | Optional. Sent as X-Scope-OrgID on every write and read. Required for Mimir. |
| Authentication | Username (basic auth), Bearer token, or a Custom header name and value. |
The paths are the stores’ own. See the Mimir HTTP API reference for /api/v1/push and /prometheus/api/v1/query_range, the Prometheus HTTP API for /api/v1/write (needs --web.enable-remote-write-receiver) and /api/v1/query_range, and the VictoriaMetrics URL examples for both.
Click Test connection before saving. The probe writes one sample to the write URL and then reads it back from the read URL:
- The write must pass; Save stays disabled until it does.
- A read that answers correctly but does not yet show the sample is reported as a note, not a failure — most stores hide the newest 30 seconds or so.
- A read that is rejected (wrong path, wrong credential) is reported as a warning so you can fix it before attaching.
Editing a saved backend re-runs the same probe, so a rotated credential is verified before it replaces the stored one.
/integrations/test Run the write/read connection probe against a backend without saving it. Same body as POST /integrations with category=observability. (config)
curl -X POST https://api.zop.dev/orgs/<orgID>/integrations/test \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "name": "prod-victoria", "provider": "victoriametrics", "category": "observability", "config": "{\"signal\":\"metrics\",\"writeUrl\":\"https://metrics.example.com/api/v1/write\",\"readUrl\":\"https://metrics.example.com/api/v1/query_range\"}", "credential": "{\"authType\":\"basic\",\"username\":\"vmadmin\",\"password\":\"…\"}" }'{ "data": { "writeOk": true, "readOk": true, "readNote": "read endpoint verified; the probe sample was not visible yet (ingestion delay)" }}POST /integrations, PUT /integrations/{id} and DELETE /integrations/{id} work as for every other integration. config carries signal (always metrics today), writeUrl, readUrl and optional tenant; credential carries authType (basic, bearer or header) with username/password, token, or headerName/headerValue. The credential is never returned by any read.
Attach it to a VM space
Attaching is what installs the agent. Two entry points, same result:
- Infrastructure → your VM space → Settings → Metrics backend. Pick the backend and Save. The confirmation says exactly what will happen on the VM.
- Infrastructure → your VM space → Components → Observability agent → Install. Pick the backend in the dialog and Install.
Either way ZopDay records the attach on the space and runs the observability-agent component on the VM. The Components card shows the install in progress and then Installed · enabled; the first samples reach your store within about a minute.
- Switch to another backend by picking it and saving: the agent is re-applied with the new settings and metrics resume within a minute.
- Detach by choosing No backend: the agent is removed from the VM and the tab goes back to reading the VM directly.
- A backend that any space still uses cannot be deleted; the Disconnect dialog names the spaces to detach first.
What runs on the VM
The agent is a single pinned OpenTelemetry Collector container (otel/opentelemetry-collector-contrib), capped at 0.25 CPU and 512 MiB. Measured footprint is 120 to 200 MiB for a VM with 20 to 30 live containers. It reads the Docker socket for per-container stats and the host for CPU, memory, disk, filesystem, load and network, every 30 seconds, and pushes them with Prometheus remote write. Its configuration and a root-only environment file with the credential are written to /etc/zop-observability/ on the VM. Docker must already be present, which is true for any VM space that has deployed a service.
/spaces/{spaceID} Attach, switch or detach: body { config: { observability: { metrics: '<integrationID>' | null } } }. The id must be an observability integration of this org for the metrics signal. Follow with the component install below; the UI does both on Save. (config)
/components Apply the agent: body { spaceId, targetType: 'vm', component: 'observability-agent' }. Installs or re-applies the collector for the space's current attach, or removes it when nothing is attached. Returns an actionId to poll. (deployer)
What the Observability tab shows
Open a VM service → Observability. With a backend attached and the agent shipping, the header shows a Metrics backend pill and a green Live · Ns ago pill that says how old the newest sample is.
- Status strip — status, replicas ready and restarts. A container that exited cleanly (exit code 0 or 143, not out-of-memory) rolls up to Scaled down; a crash stays Degraded; a VM that cannot be reached stays Unreachable.
- Usage — CPU, Memory, Network and Block I/O over 15m / 1h / 6h / 24h. The CPU chart draws the container’s
--cpuscap and the Memory chart its memory limit as dashed ceilings. Gaps in collection are drawn as gaps, not bridged. A scaled-down service shows dashes and the time of its last sample. - Replicas — one card per container: uptime, restarts, PIDs and exit code, plus CPU, memory, network and block I/O when there are no charts.
If the agent stops reporting for more than 90 seconds — the VM rebooted, the store is down, the credential was rotated without updating the integration — the tab falls back to reading the VM directly and the pill switches to Checked on the VM. It returns to the backend on its own within a minute of samples resuming.
/resources/{resourceID}/metrics/live Current state of the VM's containers. scope=vm; optional container=<name> narrows to one service. source is 'backend' or 'exec'. (aggregator)
/resources/{resourceID}/metrics/range Usage history for one container from the attached backend only. scope=vm, container=<name>, range=15m|1h|6h|24h. About 120 points per series; counters are returned as bytes per second. 404 when the space has no backend. (aggregator)
Metrics and labels
Everything the agent ships is standard OpenTelemetry hostmetrics and docker_stats data, so you can build your own Grafana dashboards on it. The names below are what lands in a Prometheus-compatible store after the exporter’s unit suffixes.
Every series carries these labels
| Label | Value |
|---|---|
org_id | Your ZopDay organisation id |
space_id | The deployment space id |
vm_uid | The cloud provider’s VM id, e.g. projects/<project>/zones/<zone>/instances/<name> on GCP |
cloud | aws, gcp or azure |
Container series also carry container_name (zopday-<service>-<id8>), service_id (the ZopDay service id) and container_runtime.
| Metric | Type | Extra labels | Used on the tab for |
|---|---|---|---|
container_cpu_utilization_ratio | gauge, fraction of one core | CPU chart and card | |
container_cpu_limit | gauge, cores | CPU chart ceiling | |
container_memory_usage_total_bytes | gauge | Memory chart and card | |
container_memory_usage_limit_bytes | gauge | Memory chart ceiling | |
container_network_io_usage_rx_bytes_total / _tx_bytes_total | counter | interface | Network chart, rate per interface then summed |
container_blockio_io_service_bytes_recursive_total | counter | operation (read/write), device_major, device_minor | Block I/O chart |
container_pids_count | gauge | PIDs on the replica card | |
container_restarts_total | counter | Restarts | |
container_uptime_seconds | gauge | Uptime |
Host series
| Metric | Type | Extra labels |
|---|---|---|
system_cpu_time_seconds_total | counter | cpu, state |
system_cpu_load_average_1m / _5m / _15m | gauge | |
system_memory_usage_bytes | gauge | state (used, free, cached, buffered, …) |
system_network_io_bytes_total and related | counter | device, direction |
system_disk_io_bytes_total and related | counter | device, direction |
system_filesystem_usage_bytes | gauge | device, mountpoint, mode, state |
system_memory_usage_bytes is also the liveness signal: the tab treats the agent as up while a host sample is newer than 90 seconds.
Application metrics per service
The agent can also collect a service’s own metrics. Set configs.metrics on the deployment and redeploy:
{ "metrics": { "mode": "scrape", "port": 8000, "path": "/metrics" } }scrape— the collector discovers the container and scrapeshttp://<container>:<port><path>every 30 seconds. Every scraped series carries the sameorg_id,space_id,vm_uid,container_nameandservice_idlabels, plus anupseries that reads 1 while the endpoint answers and 0 when it does not.otlp— the container is given the collector’s OTLP endpoint on the Docker bridge (http://172.17.0.1:4318, gRPC on4317); push from an OpenTelemetry SDK.off, or nometricskey — nothing is collected.modeis required whenever the key is present.
Application metrics are shipped as-is, with no name filter; the per-service switch is the gate.
From the MCP server
The same flow is available to an agent through the ZopNight MCP server. Four tools cover it, in the order you would use them:
| Tool | What it does |
|---|---|
test_integration | Runs the write/read probe on a backend without saving it. provider is victoriametrics, mimir or prometheus; integration_config and credential are the same objects as below. Returns writeOk, readOk and the per-direction messages. |
create_integration | Connects the backend: category: "observability", integration_config: { signal: "metrics", writeUrl, readUrl, tenant? }, credential: { authType: "basic" | "bearer" | "header", … }. The connect itself repeats the write probe and refuses a backend whose write fails. update_integration rotates the credential or changes the URLs; delete_integration disconnects it (refused while a space still uses it). |
set_infrastructure_metrics_backend | Attaches the integration to a VM pool (infrastructure_id from list_infrastructure), switches it, or detaches with detach: true. This records the attach only. |
install_infrastructure_component | With component: "observability-agent", installs, re-configures or removes the collector on the pool’s VMs according to the current attach. Poll list_infrastructure_jobs until the job settles. |
Reading back takes two tools:
get_live_metricswithscope: "vm"(and optionallycontainer) returns the live container rows the Observability tab’s strip shows.get_vm_usage_historyreturns one container’s CPU, memory, network and block I/O series over15m,1h,6hor24hfrom the backend.
Both address the VM by resource_id. Start from get_service_infrastructure: it names the pool a VM service runs on, its target, its infrastructureOrgId, and the pool’s machines under vms. Which id to pass depends on who owns the pool:
- Your own pool. Pass the VM’s id from the ZopDay inventory, the value
list_resourcesreports asidfor the machine of that name. - A shared pool (
target: "zopcloud"). Pass the machine’sinstanceIdfromvms, the provider’s id such assep-vm-0. A machine listed with anamebut noinstanceIdhas no key that fits the route and cannot be read this way yet. The VM belongs to the platform org, so also passinfrastructureOrgIdasresource_org_idon both reads while keepingorg_idas your own org. Without it the reads see no containers.
Container names follow zopday-<service>-<first 8 characters of the service id>. A 404 from get_vm_usage_history means one of three things, in the order to check them: the VM id is wrong or not visible to your org, the container is not one of your org’s deployments on that VM, or the pool has no metrics backend attached yet.
Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Test connection: write fails with 401 | Wrong credential or auth type for the store | Check the username/password or token; Mimir also needs the tenant. |
| Test connection: “resolves to a private address” | The store is only reachable inside your network | Expose it on a public https endpoint (a tunnel or ingress) that ZopDay and the VM can reach. |
| Tab shows Checked on the VM although a backend is attached | Agent not shipping: VM rebooted, store down, or credential changed | Check the Components card for the agent’s state; re-save the backend to re-apply; confirm the store accepts writes. The tab recovers on its own within a minute of samples resuming. |
| Tab is slow and says Collecting metrics… | The read fell back to the VM’s exec channel | Same as above; the exec read takes 20 to 60 seconds. |
| Charts have a gap | The agent or the store was down for that period, or the service was scaled to zero | Expected. A scaled-down service shows the time of its last sample. |
| Delete backend refused with 409 | A space still uses it | Detach in that space’s Settings first; the message names the spaces. |