Skip to content

CubeObservability ​

CubeSandbox sandbox observability deployment and operations.

English | 简体中文

Observability is opt-in for one-click and Kubernetes installs. Enable it in the one-click environment file or in Helm values; there are no extra installer flags.

Collected by default:

  • Metrics: the cubesandbox_* resource metrics from Cubelet at 127.0.0.1:9998/v1/metrics/resource.
  • Logs: sandbox stdout and stderr at /data/cubelet/log/<sandbox-id>/{stdout,stderr}.

Helm can additionally collect CRI logs for an allowlist of CubeSandbox containers under /var/log/pods. This optional Collector DaemonSet is disabled by default and is available only through the Helm chart, not the one-click/systemd installer.

Prometheus, Grafana, and one log backend (Loki by default, or Elasticsearch) ship with both installers. Both can also export to external backends.

Ownership:

  • Grafana image and dashboards: CubeObservability/;
  • one-click CVM/systemd: deploy/one-click/observability/;
  • Helm Collector and backends: deploy/kubernetes/chart/.

1. Capabilities, versions, and preflight ​

1.1 Cubelet capability baseline ​

This solution uses the following data from the current Cubelet:

  • Sandbox CPU and memory metrics from /v1/metrics/resource. Each per-sandbox series carries sandbox_id; guest_workload series also carry container_id.
  • The node-level cubesandbox_node_sandbox_count and cubesandbox_node_sandbox_available_count gauges. Both return 0 when no sandbox is active.
  • Sandbox stdout and stderr logs from /data/cubelet/log/<sandbox-id>/{stdout,stderr}.

For the complete metric names, types, units, and labels, see the CubeSandbox resource metrics guide on GitHub. The host_sandbox series carry sandbox_id; guest_workload series carry both sandbox_id and container_id. The Cubelet Prometheus source is the implementation reference. Node-count gauges have no dynamic labels and do not contain a list of Sandbox IDs.

Preflight ​

Run these checks after Cubelet is installed. For a new one-click environment, complete the first-node steps in 2.1 first:

bash
# Cubelet version and service
/usr/local/services/cubetoolbox/Cubelet/bin/cubecli version
sudo systemctl is-active cube-sandbox-cubelet.service

# Sandbox resource endpoint and per-sandbox ID label
curl -fsS http://127.0.0.1:9998/v1/metrics/resource | \
  grep -E '^cubesandbox_(host_sandbox|guest_workload|node_sandbox_)'
curl -fsS http://127.0.0.1:9998/v1/metrics/resource | \
  grep 'sandbox_id='

# Standard sandbox stdout/stderr log path while a sandbox is active
SANDBOX_ID=019abc-example
ls -l "/data/cubelet/log/${SANDBOX_ID}"/{stdout,stderr}

The sandbox_id check can be empty when no sandbox is active; both cubesandbox_node_sandbox_* gauges should still return 0. A disabled plugin or pending first sample can also produce an empty result, so interpret it together with the plugin configuration and service state.

1.2 Installer versions and external compatibility baselines ​

Loki and Elasticsearch are mutually exclusive sandbox log backends. Loki is installed by default; Elasticsearch is installed only when explicitly selected with CUBE_LOGS_BACKEND=elasticsearch (or observability.logs.backend=elasticsearch for Helm). Only one log backend is active in a deployment, and logs are not duplicated.

ComponentInstaller-pinned and validated versionExternal/self-managed baselineDefault portRole and compatibility notes
OTel Collector Contrib0.162.0Prefer 0.162.x127.0.0.1:13133 (health)Per-node collector. The generated YAML uses the OTLP/HTTP and Elasticsearch exporter configuration from this release. Arbitrary later releases are not implicitly supported; rerun otelcol validate after any upgrade.
Prometheusone-click 3.13.4; Helm v3.2.1Prometheus 3.x with OTLP and Remote Write receivers9092Sandbox metrics backend. --web.enable-otlp-receiver, --web.enable-remote-write-receiver, and the documented otlp configuration are required.
Loki (default log backend; choose one)3.7.9Loki 3.x with native OTLP and structured metadata enabled3100Default sandbox logs backend. The Collector writes to /otlp; Grafana queries it with LogQL.
Elasticsearch (optional log backend; choose one)8.17.3Elasticsearch 8.x9200Replaces Loki when explicitly selected; uses raw mapping and the Bulk API. Validate other major releases separately.
Grafana OSS10.4.18>= 10.4 with dashboard schema 39 support3001Metrics and logs UI. Validate the selected log data source, three metrics dashboards, and logs dashboard before upgrading.

Pinned versions keep bundled installs reproducible. An external baseline is not a guarantee for every future release.

Upstream configuration references: Prometheus receiver, filelog receiver, Elasticsearch exporter, Prometheus OTLP ingestion, and Loki OTLP ingestion.

The bundled CVM/systemd installers require 64-bit Linux, systemd, curl, tar, and python3; Python is used to extract and validate the Loki release archive safely. Kubernetes and Helm do not require these host binaries.

1.3 Loki or Elasticsearch ​

  • Loki (default): lower resource overhead, suited to quick deployment and primarily label-based log queries.
  • Elasticsearch (optional): suited to teams with an existing ES operations model or a need for full-text search.
  • Choose one. In production, prefer an existing team-managed or hosted log platform.

Select the backend in .env for a new installation:

dotenv
CUBE_LOGS_BACKEND=loki
# Or:
# CUBE_LOGS_BACKEND=elasticsearch

Learn more in the Loki documentation, Loki OTLP ingestion guide, Elasticsearch logs documentation, and Elasticsearch Query DSL.

2. Enable with a standard deployment ​

DeploymentEntrypointEnable
first one-click node (all-in-one by default)deploy/one-click/install.shSet ONE_CLICK_ENABLE_OBSERVABILITY=1, then run the normal installer
one-click compute nodedeploy/one-click/install-compute.shSet ONE_CLICK_ENABLE_OBSERVABILITY=1 and the first-node address; only the node Collector is installed
existing one-click deploymentoriginal install.sh / install-compute.shUpdate the original .env, then run with --mode=upgrade --yes; see 2.1 C
Kubernetes Helmdeploy/kubernetes/chartPass --set observability.enabled=true

2.1 one-click ​

one-click keeps using its existing .env and installer entrypoints, and observability remains disabled by default. The first node is all-in-one by default. Additional compute nodes send metrics and logs to that first node; there is no separate monitoring control plane.

A. Install the first all-in-one node ​

  1. Enter deploy/one-click in a source checkout, or the one-click root of an extracted release. Copy the template only for a fresh installation:
bash
# Source checkout:
cd deploy/one-click
# Extracted release:
# cd /path/to/cube-sandbox-one-click
[[ -f .env ]] || cp env.example .env
chmod 600 .env
  1. Choose a private IP reachable from later compute nodes, such as 10.0.0.10, and set:
bash
# .env
CUBE_SANDBOX_NODE_IP=10.0.0.10
ONE_CLICK_ENABLE_OBSERVABILITY=1
CUBE_OBSERVABILITY_CLUSTER_ID=cube-prod
CUBE_LOGS_BACKEND=loki
CUBE_GRAFANA_ADMIN_PASSWORD='<random-password-at-least-16-chars>'

The installer rejects an unset or shorter Grafana password. Set CUBE_LOGS_BACKEND=elasticsearch to use Elasticsearch. The installer can detect CUBE_SANDBOX_NODE_IP when it is omitted, but confirm the resulting address after installation.

  1. Run the normal installer:
bash
sudo ./install.sh
  1. Record the final first-node address. Additional compute nodes must use this reachable private IP:
bash
grep '^CUBE_SANDBOX_NODE_IP=' \
  /usr/local/services/cubetoolbox/.one-click.env
  1. Allow only the required network paths:

    • compute nodes to the first node: TCP 9092, plus 3100 for Loki or 9200 for Elasticsearch;
    • administrators to the first node: Grafana 3001;
    • never expose 9092/3100/9200 directly to the public Internet.
  2. Verify the first node:

bash
sudo /usr/local/services/cubetoolbox/observability/install.sh status
sudo systemctl --no-pager --full status cube-sandbox-otelcol.service
curl -fsS http://127.0.0.1:9092/-/ready
curl -fsS http://127.0.0.1:3100/ready
# Elasticsearch users replace the previous line with:
# curl -fsS http://127.0.0.1:9200/

Open Grafana at http://10.0.0.10:3001 and check the CubeSandbox sandbox resources and CubeSandbox sandbox logs dashboards. Per-sandbox series and logs can be empty when no sandbox is active.

B. Install additional compute nodes ​

  1. Prepare the same one-click release and an .env on each compute node:
bash
[[ -f .env ]] || cp env.example .env
chmod 600 .env
  1. Set the first-node address and keep the cluster ID and log backend identical to the first node:
bash
# .env
ONE_CLICK_CONTROL_PLANE_IP=10.0.0.10
ONE_CLICK_ENABLE_OBSERVABILITY=1
CUBE_OBSERVABILITY_CLUSTER_ID=cube-prod
CUBE_LOGS_BACKEND=loki

ONE_CLICK_CONTROL_PLANE_IP is the first CubeSandbox node's private IP, not a newly created monitoring node. install-compute.sh exports ONE_CLICK_DEPLOY_ROLE=compute automatically, so do not repeat it in .env. With no explicit external endpoints, the installer derives the Prometheus and selected log-backend addresses from this IP.

  1. Install and inspect the local Collector:
bash
sudo ./install-compute.sh
sudo systemctl --no-pager --full status cube-sandbox-otelcol.service
sudo journalctl -u cube-sandbox-otelcol.service -n 100 --no-pager
  1. Create a test sandbox, then query by node_id and sandbox_id in Grafana on the first node. If no data arrives, first check local 127.0.0.1:9998, the log path, and connectivity to the required first-node ports.

C. Enable observability in an existing one-click deployment ​

Use a one-click release that contains the observability deployment files and keep maintaining the .env in the original installation directory. Do not overwrite it with env.example, and do not edit the generated .one-click.env directly.

  1. Add or update these values in .env on the first node:
dotenv
ONE_CLICK_ENABLE_OBSERVABILITY=1
CUBE_OBSERVABILITY_CLUSTER_ID=cube-prod
CUBE_LOGS_BACKEND=loki
CUBE_GRAFANA_ADMIN_PASSWORD='<random-password-at-least-16-chars>'
  1. Run the original one-click entrypoint in upgrade mode:
bash
sudo ./install.sh --mode=upgrade --yes
  1. After verifying the first node, add or update these values in .env on every compute node:
dotenv
ONE_CLICK_CONTROL_PLANE_IP=10.0.0.10
ONE_CLICK_ENABLE_OBSERVABILITY=1
CUBE_OBSERVABILITY_CLUSTER_ID=cube-prod
CUBE_LOGS_BACKEND=loki
bash
sudo ./install-compute.sh --mode=upgrade --yes

Replace 10.0.0.10 with the first node's reachable private IP, and use the same cluster ID and log backend on every node. These commands reconcile the one-click stack and observability components from the original .env; back up the configuration through the normal operations process before running them. On upgrade, a wholesale copy of env.example with its default ONE_CLICK_ENABLE_OBSERVABILITY=0 does not disable an already enabled stack. To explicitly disable it, run sudo env ONE_CLICK_ENABLE_OBSERVABILITY=0 ./install.sh --mode=upgrade --yes (or use ./install-compute.sh on a compute node).

D. Online installation ​

Online installation follows the same environment-variable forwarding style as bundled MinIO:

bash
# First all-in-one node. Bundled Grafana requires an admin password of at least
# 16 characters; store the generated value in your password manager.
GRAFANA_ADMIN_PASSWORD="$(openssl rand -hex 16)"
echo "Grafana admin password: ${GRAFANA_ADMIN_PASSWORD}"
curl -fsSL https://raw.githubusercontent.com/tencentcloud/CubeSandbox/master/deploy/one-click/online-install.sh \
  | sudo env ONE_CLICK_ENABLE_OBSERVABILITY=1 \
      CUBE_OBSERVABILITY_CLUSTER_ID=cube-prod \
      CUBE_LOGS_BACKEND=loki \
      CUBE_GRAFANA_ADMIN_PASSWORD="${GRAFANA_ADMIN_PASSWORD}" \
      bash

# Compute node
curl -fsSL https://raw.githubusercontent.com/tencentcloud/CubeSandbox/master/deploy/one-click/online-install.sh \
  | sudo env ONE_CLICK_DEPLOY_ROLE=compute \
      ONE_CLICK_CONTROL_PLANE_IP=10.0.0.10 \
      ONE_CLICK_ENABLE_OBSERVABILITY=1 \
      CUBE_OBSERVABILITY_CLUSTER_ID=cube-prod \
      CUBE_LOGS_BACKEND=loki \
      bash

online-install.sh downloads the release and invokes the same install.sh; it is not a separate deployment topology. For authentication tokens and other secrets, prefer downloading the release and storing values in a mode-0600.env rather than leaving secrets in shell history.

E. External backends ​

dotenv
ONE_CLICK_OBSERVABILITY_ROLE=auto
CUBE_LOGS_BACKEND=loki
CUBE_METRICS_OTLP_HTTP_ENDPOINT=http://10.0.0.10:9092/api/v1/otlp
CUBE_LOGS_OTLP_HTTP_ENDPOINT=http://10.0.0.10:3100/otlp

With explicit external endpoints, auto installs only the node Collector, not local backends. See 4.1 for external-backend authentication.

2.2 Kubernetes Helm ​

Deploy the in-cluster backend and the compute-node Collector:

bash
helm upgrade --install cube ./deploy/kubernetes/chart \
  -n cube-system --create-namespace \
  -f /path/to/runtime-values.yaml \
  --set observability.enabled=true \
  --set observability.clusterId=k8s-prod \
  --set observability.logs.backend=loki

The chart generates a release-scoped Grafana admin Secret with a random password and reuses it on upgrades. Read it from the cluster instead of passing credentials on the command line:

bash
kubectl get secret -n cube-system \
  -l app.kubernetes.io/component=grafana-admin \
  -o jsonpath='{.items[0].data.admin-password}' | base64 --decode; echo

Set observability.backend.grafana.existingSecret to a Secret with admin-user and admin-password to manage credentials outside Helm. An inline adminPassword must be at least 16 characters.

To also collect component container logs, add --set observability.logs.components.enabled=true, or set it in runtime-values.yaml:

yaml
observability:
  logs:
    components:
      enabled: true

This runs a second Collector that tails only the allowlisted containers (observability.logs.components.containers, default: master, API, Ops, TemplateCenter, Cubelet, proxy, lifecycle manager) in the release namespace, from a read-only /var/log/pods mount, on nodes matching observability.logs.components.nodeSelector, affinity, and tolerations. Collection starts at the file end, so existing history is not replayed. Query it in Grafana with service_name="cubesandbox-cube-master", or use the Component logs row of the logs dashboard. Check component logs for secrets before enabling, and add redaction when needed.

Collector, Prometheus, log backend, and Grafana Pods carry readiness/liveness probes, so Kubernetes gates rollout and restarts unhealthy monitoring Pods. This is not alerting; no Alertmanager rules are installed.

Deploy only the Collector and send data to existing backends:

bash
helm upgrade --install cube ./deploy/kubernetes/chart \
  -n cube-system --create-namespace \
  -f /path/to/runtime-values.yaml \
  --set observability.enabled=true \
  --set observability.backend.enabled=false \
  --set observability.clusterId=k8s-prod \
  --set observability.metrics.otlpEndpoint="http://10.0.0.10:9092/api/v1/otlp" \
  --set observability.logs.backend=loki \
  --set observability.logs.otlpEndpoint="http://10.0.0.10:3100/otlp"

Use the same runtime-values.yaml as a normal chart installation; it must contain non-default database/Redis credentials and placement settings. Observability values are additions to that existing install command. For Elasticsearch, set observability.logs.backend=elasticsearch and, for an external backend, observability.logs.esEndpoint. Do not set both log endpoints.

For authenticated external backends, create a Secret in the release namespace with your existing secret-management workflow and reference it from runtime-values.yaml. Store the complete Authorization header value (for example Bearer ... or Elasticsearch ApiKey ...) and do not pass credentials through --set:

yaml
# Example only. Create these Secrets through the cluster's secret-management workflow.
apiVersion: v1
kind: Secret
metadata:
  name: cube-observability-auth
  namespace: cube-system
type: Opaque
stringData:
  metrics-authorization: "Bearer <metrics-token>"
  metrics-tenant-id: "cube-prod"
  logs-authorization: "Bearer <logs-token>"
  logs-tenant-id: "cube-prod"
---
apiVersion: v1
kind: Secret
metadata:
  name: cube-grafana-admin
  namespace: cube-system
type: Opaque
stringData:
  admin-user: "admin"
  admin-password: "<strong-password>"

For an Elasticsearch API key, set logs-authorization to ApiKey <key>. Remove a tenant key when the backend does not require that header.

Reference those Secret keys from runtime-values.yaml:

yaml
observability:
  metrics:
    auth:
      existingSecret: cube-observability-auth
      authorizationKey: metrics-authorization
      tenantIdKey: metrics-tenant-id # optional; empty disables X-Scope-OrgID
  logs:
    auth:
      existingSecret: cube-observability-auth
      authorizationKey: logs-authorization
      tenantIdKey: logs-tenant-id # set "" when the backend needs no tenant header
  backend:
    grafana:
      existingSecret: cube-grafana-admin
      adminUserKey: admin-user
      adminPasswordKey: admin-password

HTTPS endpoints use the container system CA. The chart does not yet mount a custom CA Secret; extend the CA mount for a private-CA deployment instead of disabling certificate verification.

The Helm Collector always uses the host network and scrapes the same-node Cubelet at 127.0.0.1:9998. The built-in topology therefore requires the default cubeNode.hostNetwork=true. The chart does not currently discover a reliable per-node metrics target when Cubelet is moved to the Pod network.

Common values:

ValueDefaultMeaning
observability.enabledfalseObservability master switch
observability.clusterIdcubesandboxCluster label for metrics and logs
observability.otelCollector.scrapeInterval15sCubelet scrape interval; it must not be shorter than the fixed 10s scrape timeout
observability.otelCollector.cubeletMetricsTarget127.0.0.1:9998Node-local Cubelet endpoint; the default requires cubeNode.hostNetwork=true
observability.otelCollector.stateHostPath/var/lib/cube-sandbox/otelcol-k8sPer-node persistent filelog offsets, queues, and WAL
observability.backend.enabledtrueDeploy Prometheus, the selected log backend, and Grafana with the Collector
observability.metrics.otlpEndpoint""External Prometheus OTLP/HTTP base endpoint
observability.metrics.remoteWriteUrl""External Prometheus Remote Write URL; alternative to OTLP
observability.metrics.auth.existingSecret""Secret containing metrics Authorization / tenant headers
observability.logs.enabledtrueCollect sandbox stdout/stderr logs
observability.logs.components.enabledfalseCollect allowlisted component CRI logs from /var/log/pods with a second DaemonSet
observability.logs.components.hostPath/var/log/podsAbsolute, non-root host directory holding the pod log tree
observability.logs.components.containersCube component listContainer-name allowlist; only these directories are tailed
observability.logs.components.nodeSelector / affinity / tolerationsLinux nodes; control/compute labels and taintsPlacement of the component-log Collector
observability.logs.backendlokiLog backend: loki or elasticsearch
observability.logs.otlpEndpoint""External OTLP/HTTP base endpoint for backend=loki
observability.logs.esEndpoint""External Elasticsearch endpoint for backend=elasticsearch
observability.logs.auth.existingSecret""Secret containing logs Authorization / tenant headers
observability.backend.<component>.persistence.enabledtrueUse a PVC for built-in Prometheus, log backend, and Grafana
observability.backend.grafana.adminPassword""Empty generates a random password in a release-scoped Secret; inline values need 16+ characters
observability.backend.grafana.existingSecret""Grafana admin Secret (admin-user / admin-password); preferred in production

3. Standalone installation (optional) ​

The observability components can also be installed without a one-click or Helm release. The standalone installer manages cube-sandbox-otelcol, Prometheus, Loki/Elasticsearch, and Grafana, and does not touch CubeMaster, Cubelet, or other services. Run the preflight first, then pick a backend node reachable from the compute nodes.

3.1 Single node or first backend node ​

If the backend node also runs sandboxes, install both the backend and its local Collector:

bash
sudo env \
  CUBE_OBSERVABILITY_CLUSTER_ID=cube-prod \
  CUBE_LOGS_BACKEND=loki \
  CUBE_GRAFANA_ADMIN_PASSWORD='REPLACE_WITH_STRONG_PASSWORD' \
  ./deploy/one-click/observability/install.sh all

If that node hosts only the backend and runs no sandboxes, omit the local Collector:

bash
sudo env \
  CUBE_OBSERVABILITY_CLUSTER_ID=cube-prod \
  CUBE_LOGS_BACKEND=loki \
  CUBE_GRAFANA_ADMIN_PASSWORD='REPLACE_WITH_STRONG_PASSWORD' \
  ./deploy/one-click/observability/install.sh backend

Replace the Grafana password before running either command. To use Elasticsearch, set CUBE_LOGS_BACKEND=elasticsearch. Restrict the backend ports as described in 2.1 A.

3.2 Other CVM or bare-metal compute nodes ​

Install the Collector on each compute node, replacing 10.0.0.10 with the backend node's private IP:

bash
sudo env \
  CUBE_OBSERVABILITY_CLUSTER_ID=cube-prod \
  CUBE_METRICS_OTLP_HTTP_ENDPOINT=http://10.0.0.10:9092/api/v1/otlp \
  CUBE_LOGS_OTLP_HTTP_ENDPOINT=http://10.0.0.10:3100/otlp \
  ./deploy/one-click/observability/install.sh cvm

For Elasticsearch, remove CUBE_LOGS_OTLP_HTTP_ENDPOINT and use:

bash
sudo env \
  CUBE_OBSERVABILITY_CLUSTER_ID=cube-prod \
  CUBE_LOGS_BACKEND=elasticsearch \
  CUBE_METRICS_OTLP_HTTP_ENDPOINT=http://10.0.0.10:9092/api/v1/otlp \
  CUBE_LOGS_ES_ENDPOINT=http://10.0.0.10:9200 \
  ./deploy/one-click/observability/install.sh cvm

Add the variables from 4.1 when the external backend requires a token, tenant header, ES API key, or Basic Auth.

3.3 Verification and uninstall ​

bash
# Full systemd all-in-one installation
sudo ./deploy/one-click/observability/install.sh status

# Collector-only systemd node
sudo systemctl --no-pager --full status cube-sandbox-otelcol.service

# Remove independently installed systemd components; data directories are retained.
sudo ./deploy/one-click/observability/install.sh uninstall all

For a Kubernetes release, check the Collector DaemonSets instead (names are prefixed with the release fullname, cube below):

bash
kubectl -n cube-system rollout status daemonset/cube-node-otelcol
# Only with observability.logs.components.enabled=true
kubectl -n cube-system rollout status daemonset/cube-component-otelcol

4. OTel Collector configuration ​

Each node runs otelcol-contrib 0.162.0 and generates up to two independent pipelines according to the configured destinations:

PipelineReceiverInputExporter
metricsprometheusCubelet /v1/metrics/resourceotlp_http/metrics or prometheus_remote_write
logs/sandboxfilelog/sandbox/data/cubelet/log/*/{stdout,stderr}elasticsearch/logs or otlp_http/logs

These names come from the systemd generator. Helm uses the equivalent prometheus/cubelet_resource_metrics, filelog/sandbox_stdio, and logs names, plus one logs/component_<container> pipeline per allowlisted container in the separate component-log Collector. Implementation details are documented in the OTel design.

4.1 Core environment variables and export authentication ​

VariableDefaultExampleMeaning
CUBE_OBSERVABILITY_CLUSTER_IDcubesandboxcube-prodCluster label written to metrics and logs
CUBE_LOGS_BACKENDlokielasticsearchLog-backend selector for one-click/systemd
CUBE_SANDBOX_NODE_IPauto-detected10.0.0.20systemd only: node label; set it when auto-detection is unsuitable
CUBE_METRICS_OTLP_HTTP_ENDPOINTemptyhttp://10.0.0.10:9092/api/v1/otlpOTLP/HTTP base endpoint; omit /v1/metrics
CUBE_METRICS_REMOTE_WRITE_URLemptyhttp://10.0.0.10:9092/api/v1/writePrometheus Remote Write URL; alternative to metrics OTLP
CUBE_METRICS_AUTHORIZATION / CUBE_METRICS_REMOTE_WRITE_TOKENemptyBearer ...Metrics authentication for one-click/systemd; choose a complete header or a bare token
CUBE_METRICS_TENANT_IDemptycube-prodMetrics X-Scope-OrgID
CUBE_LOGS_ES_ENDPOINTemptyhttp://10.0.0.10:9200Elasticsearch endpoint
CUBE_LOGS_ES_INDEXcubesandbox-logscube-prod-logsElasticsearch index
CUBE_LOGS_ES_USER / CUBE_LOGS_ES_PASSWORDemptycube / ...one-click/systemd: ES Basic Auth; both values are required
CUBE_LOGS_ES_API_KEYempty...one-click/systemd: ES API key; alternative to Basic Auth
CUBE_LOGS_ES_TLS_INSECURE_SKIP_VERIFYfalsetrueSkip ES TLS verification for temporary diagnostics only
CUBE_LOGS_OTLP_HTTP_ENDPOINTemptyhttp://10.0.0.10:3100/otlpOTLP/HTTP logs base endpoint; alternative to Elasticsearch
CUBE_LOGS_AUTHORIZATION / CUBE_LOGS_TOKENemptyBearer ...Log authentication for one-click/systemd; choose a complete header or a bare token
CUBE_LOGS_TENANT_IDemptycube-prodLogs X-Scope-OrgID
CUBE_LOGS_SANDBOX_ID*019abc-exampleOptional sandbox ID filter for the systemd collector
CUBE_LOGS_FILE_START_ATbeginningendsystemd only: initial file position, either beginning or end

The bundled Grafana provisioning reuses these metrics/log credentials for its datasources. Running grafana/install.sh dashboards creates or updates datasources by UID.

Helm uses the Kubernetes Node name as the node label and does not read these systemd-only variables. Its fixed start_at: beginning applies only when no saved offset exists. Collector state is persisted per node at /var/lib/cube-sandbox/otelcol-k8s, so Pod replacement on the same node resumes from file_storage instead of rereading existing files.

One-click users can use the Observability section already present in deploy/one-click/env.example. After copying it to .env as shown in 2.1, uncomment optional settings only when needed:

dotenv
ONE_CLICK_ENABLE_OBSERVABILITY=1
CUBE_OBSERVABILITY_CLUSTER_ID=cube-prod
CUBE_LOGS_BACKEND=loki

# The standard all-in-one/compute topology derives these automatically.
# Set them only for a self-managed or external backend.
CUBE_METRICS_OTLP_HTTP_ENDPOINT=http://10.0.0.10:9092/api/v1/otlp
CUBE_LOGS_OTLP_HTTP_ENDPOINT=http://10.0.0.10:3100/otlp

Configure at least one metrics or logs destination. Do not configure both metrics destinations or both logs destinations at the same time. The standard one-click topology fills in its bundled backend endpoints automatically. For Elasticsearch, use CUBE_LOGS_BACKEND=elasticsearch, CUBE_LOGS_ES_ENDPOINT=http://10.0.0.10:9200, and optionally CUBE_LOGS_ES_INDEX.

Authentication in every topology ​

The supported one-click and Kubernetes workflows can export to authenticated external metrics and log backends. A minimal Bearer example is:

dotenv
CUBE_METRICS_OTLP_HTTP_ENDPOINT=https://prometheus.example.com/api/v1/otlp
CUBE_METRICS_AUTHORIZATION='Bearer <metrics-token>'
CUBE_LOGS_OTLP_HTTP_ENDPOINT=https://loki.example.com/otlp
CUBE_LOGS_AUTHORIZATION='Bearer <logs-token>'
  • the one-click/systemd installer reads these variables from the node's .env file;
  • Helm references a Secret through observability.metrics.auth.existingSecret and observability.logs.auth.existingSecret; see 2.2;
  • Elasticsearch uses CUBE_LOGS_ES_API_KEY, or the paired CUBE_LOGS_ES_USER and CUBE_LOGS_ES_PASSWORD.

An *_AUTHORIZATION value is the complete single-line header. An *_TOKEN value is a bare token that the installer converts to a Bearer header; do not set both forms. These settings configure Collector/Grafana client headers. They do not enable server-side authentication on bundled Prometheus, Loki, or Elasticsearch. Put an authentication gateway or reverse proxy in front of a bundled backend, or use a managed backend.

Official references: OTLP HTTP exporter, Prometheus Remote Write exporter, Loki authentication, Elasticsearch API keys, and Kubernetes Secrets.

Never commit real credentials. Set .env to mode 0600. HTTPS uses the system CA by default; install or mount a private CA instead of disabling verification.

4.2 Config locations and rendering ​

render and installation use the same generator. Values already configured in the environment are written into the generated YAML, so the YAML does not need a second manual edit. The one-click wrapper loads .env automatically; for a standalone run, export the variables first or pass them with env.

TopologyInstalled configurationRender with configured values
systemd/etc/cube-sandbox/otelcol/otelcol.yaml./deploy/one-click/observability/install.sh render cvm
HelmConfigMaps <release>-node-otelcol-config and <release>-component-otelcol-configUse the helm template command below

Helm uses observability.* values from values.yaml or --set; it does not read CUBE_* environment variables automatically.

Even a render-only Helm command must satisfy the chart-wide credential validation with non-default placeholders:

bash
helm template cube ./deploy/kubernetes/chart \
  --set observability.enabled=true \
  --set-string mysql.password=render-only \
  --set-string mysql.rootPassword=render-only \
  --set-string redis.password=render-only \
  -s templates/observability.yaml

Use render-only only for local rendering. Supply real credentials through secret values for an actual installation.

4.3 Generated configuration example ​

The excerpt below assumes the example values from 4.1 and node address 10.0.0.20, sending metrics to Prometheus and logs to Loki. It omits some exporter discover/queue/retry and service.telemetry fields for readability; use render for the complete output. Comments explain environment-variable mappings and are not guaranteed to appear in the generated file.

This is generated output, not a second configuration source. render and install substitute the configured environment values automatically. Rerun the installation after changing those values.

yaml
receivers:
  # Scrape sandbox resource metrics from the local Cubelet only.
  prometheus:
    trim_metric_suffixes: false
    config:
      scrape_configs:
        - job_name: cubesandbox-resource
          scrape_interval: 5s
          scrape_timeout: 3s
          metrics_path: /v1/metrics/resource
          static_configs:
            - targets: ["127.0.0.1:9998"]
  # CUBE_LOGS_SANDBOX_ID replaces "*"; the default collects every sandbox.
  filelog/sandbox:
    include:
      - /data/cubelet/log/*/stdout
      - /data/cubelet/log/*/stderr
    start_at: beginning # CUBE_LOGS_FILE_START_AT
    storage: file_storage
    include_file_path: true
    include_file_name: false
    max_log_size: 1MiB
    operators:
      - type: regex_parser
        parse_from: attributes["log.file.path"]
        regex: '^/data/cubelet/log/(?P<sandbox_id>[^/]+)/(?P<stream>stdout|stderr)$'
      - type: copy
        from: attributes["log.file.path"]
        to: attributes.log_file_path
      - type: copy
        from: attributes.sandbox_id
        to: attributes.cubesandbox_sandbox_id
      - type: move
        from: attributes.sandbox_id
        to: resource["cubesandbox.sandbox.id"]
      - type: copy
        from: attributes.stream
        to: attributes.log_iostream
      - type: move
        from: attributes.stream
        to: attributes["log.iostream"]

processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 256
  # Values come from CUBE_OBSERVABILITY_CLUSTER_ID and
  # CUBE_SANDBOX_NODE_IP (auto-detected when unset).
  resource/metrics:
    attributes:
      - {key: cluster, value: "cube-prod", action: upsert}
      - {key: node, value: "10.0.0.20", action: upsert}
      - {key: cubesandbox.cluster.id, value: "cube-prod", action: upsert}
      - {key: host.name, value: "10.0.0.20", action: upsert}
  resource/logs_common:
    attributes:
      - {key: host.name, value: "10.0.0.20", action: upsert}
      - {key: cubesandbox.cluster.id, value: "cube-prod", action: upsert}
  attributes/logs_common:
    actions:
      - {key: host_name, value: "10.0.0.20", action: upsert}
      - {key: cubesandbox_cluster_id, value: "cube-prod", action: upsert}
      - {key: cubesandbox_sandbox_id, value: "none", action: insert}
      - {key: log_iostream, value: "none", action: insert}
  resource/sandbox:
    attributes:
      - {key: service.name, value: "cubesandbox-sandbox", action: upsert}
      - {key: cubesandbox.log.source, value: "sandbox-init", action: upsert}
  attributes/sandbox:
    actions:
      - {key: service_name, value: "cubesandbox-sandbox", action: upsert}
      - {key: cubesandbox_log_source, value: "sandbox-init", action: upsert}
  batch:
    timeout: 2s
    send_batch_size: 1024

exporters:
  otlp_http/metrics:
    # CUBE_METRICS_OTLP_HTTP_ENDPOINT; the Collector appends /v1/metrics.
    endpoint: "http://10.0.0.10:9092/api/v1/otlp"
    compression: gzip
    sending_queue:
      enabled: true
      storage: file_storage
    retry_on_failure:
      enabled: true
      max_elapsed_time: 0
  otlp_http/logs:
    # CUBE_LOGS_OTLP_HTTP_ENDPOINT; the Collector appends /v1/logs.
    endpoint: "http://10.0.0.10:3100/otlp"
    compression: gzip
    sending_queue:
      enabled: true
      storage: file_storage
    retry_on_failure:
      enabled: true
      max_elapsed_time: 0

extensions:
  health_check:
    endpoint: 127.0.0.1:13133
  # Persist file offsets and the OTLP sending queue across process restarts.
  file_storage:
    directory: /var/lib/cube-sandbox/otelcol/storage
    create_directory: true

service:
  extensions: [health_check, file_storage]
  pipelines:
    # Metrics and logs are independent pipelines.
    metrics:
      receivers: [prometheus]
      processors: [memory_limiter, resource/metrics, batch]
      exporters: [otlp_http/metrics]
    logs/sandbox:
      receivers: [filelog/sandbox]
      processors: [memory_limiter, resource/logs_common, resource/sandbox, attributes/logs_common, attributes/sandbox, batch]
      exporters: [otlp_http/logs]

With Elasticsearch selected, the log exporter becomes elasticsearch/logs; its endpoint and index come from CUBE_LOGS_ES_ENDPOINT and CUBE_LOGS_ES_INDEX. The receiver, field extraction, and pipeline stay the same.

Metric and log collection rules ​

TypeSupported ruleConfiguration
Sandbox metricsScrape only the local Cubelet /v1/metrics/resource; includes sandbox CPU, memory, and node sandbox countsCluster/node labels and either an OTLP or Remote Write destination; Helm also exposes observability.otelCollector.scrapeInterval
Sandbox logsRead /data/cubelet/log/<sandbox-id>/{stdout,stderr} line by line and extract the sandbox ID and stream from the pathSend to Loki over OTLP by default, or to Elasticsearch; systemd also exposes CUBE_LOGS_SANDBOX_ID and CUBE_LOGS_FILE_START_AT
Component logs (Helm opt-in)Read CRI/containerd logs only for observability.logs.components.containers from /var/log/pods; start at end and use one pipeline per componentSeparate read-only component-log Collector DaemonSet on matching control/compute nodes; one-click does not support this option
State and retryUse file_storage for file offsets and OTLP queues, a WAL for Remote Write, and an in-memory queue with at most five retries for ElasticsearchFile state lives on the host for systemd and at /var/lib/cube-sandbox/otelcol-k8s per Kubernetes node; component logs use a separate -components host path

For the systemd collector, each original log line is kept in Body, with a maximum log-record size of 1MiB.

Both backends retain these semantic fields (Loki normalizes . in OTel attribute names to _):

  • service_name=cubesandbox-sandbox
  • cubesandbox_log_source=sandbox-init
  • cubesandbox_cluster_id
  • host_name
  • cubesandbox_sandbox_id
  • log_iostream=stdout|stderr
  • log_file_path
  • the log body and timestamp

Component CRI records use service_name=cubesandbox-<container>, cubesandbox_log_source=component, and cubesandbox_component_name=<container>; they do not contain the sandbox-only ID and stream fields above.

Loki indexes only the low-cardinality service_name, cubesandbox_cluster_id, and host_name labels. cubesandbox_sandbox_id, log_iostream, and the remaining fields stay as structured metadata. This avoids creating one high-cardinality stream per sandbox. Common LogQL filters:

logql
# All sandbox stderr
{service_name="cubesandbox-sandbox"} | log_iostream="stderr"

# Error-like messages from one sandbox
{service_name="cubesandbox-sandbox"}
  | cubesandbox_sandbox_id="<sandbox-id>"
  |~ "(?i)error|fatal|panic"

Elasticsearch documents additionally contain Body and @timestamp. Common Lucene filters:

text
# All sandbox stderr
service_name:"cubesandbox-sandbox" AND log_iostream:"stderr"

  # Error-like messages from sandbox stdout/stderr
cubesandbox_log_source:"sandbox-init" AND Body:(ERROR OR FATAL OR panic)

# One sandbox
cubesandbox_sandbox_id:"<sandbox-id>"
SettingBuilt-in valueRecommendation
Scrape interval / timeoutsystemd: 5s / 3s; Helm: 15s / 10sKeep script defaults; Helm intervals must not be shorter than the fixed 10s timeout, and larger deployments should keep the default 15s
CUBE_LOGS_FILE_START_AT (systemd only)beginningKeep beginning to read the current files in full; use end to collect only records written after the Collector starts
CUBE_LOGS_SANDBOX_ID (systemd only)*Keep * normally; specify one ID only for focused troubleshooting or temporary volume reduction
Memory limiterFixed at 256MiB; Kubernetes memory request/limit 128Mi / 512MiStart with the defaults; raising only the Pod limit does not change the limiter, which currently has no installer setting
Batch1024 records; timeout 2s for scripts and 5s for HelmKeep the defaults for normal low-latency delivery
OTLP queue1000, disk-backed with file_storage, retry without an elapsed-time limitEnsure the host state directory has enough capacity
Elasticsearch queueIn-memory 1000, 10 consumers, at most 5 retriesSustained backend outages can eventually drop logs; monitor Collector logs and backend availability
Remote WriteWAL-backedEnsure the WAL directory has enough capacity and monitor remote-write failures
Prometheus retentionone-click: 15d / 40GB; Helm: 15dFor one-click use CUBE_PROMETHEUS_RETENTION_TIME / CUBE_PROMETHEUS_RETENTION_SIZE; for Helm use observability.backend.prometheus.retention, which currently has no size value
Loki retentionone-click and Helm: 15dUse CUBE_LOKI_RETENTION_PERIOD for one-click or observability.backend.loki.retention for Helm
Elasticsearch heapone-click and Helm: 1gTune CUBE_ES_HEAP_SIZE or observability.backend.elasticsearch.javaOpts, normally keeping heap below 50% of available memory

Except for settings with an environment variable or Helm value listed above, scrape timeout, memory limiter, batch, and queue values are installer-owned defaults and should not be maintained by editing generated files.

Helm's bundled Prometheus, selected log backend, and Grafana use PVCs by default. Configure each component's persistence.storageClassName, existingClaim, or size under observability.backend; set persistence.enabled=false only for disposable validation. Use an external managed backend when high availability is required. Bundled Loki defaults to 15d; configure Elasticsearch ILM separately when Elasticsearch is selected.

Suggested monitoring expressions ​

The installer provisions dashboards but does not install Alertmanager, Prometheus alert rules, Grafana alerts, or Elasticsearch ILM. Suggested PromQL starting points are shown below. Use for: 5m for the CPU, throttling, memory ratio, and unavailable-sample conditions. Fire the memory-failure counter immediately or use only a short delay.

promql
# CPU usage above 80% of a finite sandbox CPU limit
100 * rate(cubesandbox_host_sandbox_cpu_usage_seconds_total[5m])
  / clamp_min(cubesandbox_host_sandbox_cpu_limit_cores, 0.001) > 80

# CPU throttled periods above 20%
100 * rate(cubesandbox_host_sandbox_cpu_throttled_periods_total[5m])
  / clamp_min(rate(cubesandbox_host_sandbox_cpu_periods_total[5m]), 0.001) > 20

# Memory usage above 85% of a finite sandbox memory limit
100 * cubesandbox_host_sandbox_memory_current_bytes
  / clamp_min(cubesandbox_host_sandbox_memory_limit_bytes, 1) > 85

# Any memory-limit failure during the last five minutes
increase(cubesandbox_host_sandbox_memory_failures_total[5m]) > 0

# A sandbox exists but has no available resource sample
cubesandbox_node_sandbox_count
  - cubesandbox_node_sandbox_available_count > 0

CPU and memory ratios only apply when a finite resource-limit series exists.

4.5 Extending collection to component logs ​

Helm already collects allowlisted component container logs (see 2.2); deploy/kubernetes/chart/templates/observability.yaml is the reference implementation. The one-click/systemd installer collects sandbox logs only.

Never edit the installed /etc/cube-sandbox/otelcol/otelcol.yaml or the ConfigMap: installation regenerates them. Change the generator instead:

  • systemd: write_otel_config() in deploy/one-click/observability/otelcol/install.sh;
  • Helm: the Collector ConfigMap and volumes in deploy/kubernetes/chart/templates/observability.yaml, behind a values.yaml switch that stays off by default.

Use one receiver and pipeline per component (filelog/component_cubelet, logs/component_cubelet) so unrelated formats never share a parser, and set both the OTel resource attribute and the flat Elasticsearch field:

  • service.name / service_name: stable low-cardinality name, e.g. cubesandbox-cubelet;
  • cubesandbox.log.source / cubesandbox_log_source: component;
  • cubesandbox.component.name / cubesandbox_component_name: e.g. cubelet;
  • keep the existing cubesandbox.cluster.id, host.name, log.file.path, and timestamp fields.

Constraints for any extension:

  • read files with start_at: end and a dedicated parser for JSON or multiline formats; a parser failure must keep the original Body and must not block other pipelines;
  • index only stable labels; keep PID, request ID, and sandbox ID as structured metadata;
  • mount only the required host directories, read-only; never the host root;
  • exclude credentials, tokens, and headers, adding a redaction or transform processor when needed;
  • verify file_storage offsets across log rotation, restarts, and backend outages;
  • update the generator, both documentation languages, the dashboards, and the Collector validate tests, and state which topologies stay unsupported.

5. Backends and dashboards ​

The bundled backend is Prometheus, Grafana, and one log backend. Grafana provisions three separate metrics dashboards plus one logs dashboard:

  • cubesandbox-cluster.json: sandbox resource totals for one cluster.
  • cubesandbox-node.json: sandbox resource totals for one node in that cluster.
  • cubesandbox-sandbox.json: one sandbox's Host Sandbox and Guest Workload metrics, with an optional guest-container filter.
  • cubesandbox-logs.json (Loki, default) or cubesandbox-logs-elasticsearch.json (Elasticsearch), both under the UID cubesandbox-logs: stdout/stderr volume, log query, and a collapsed Component logs row (Helm opt-in).

Metrics selectors cascade from cluster to node, sandbox_id, and container_id. All 24 currently exported metric families are covered; aggregate CPU rates are cores, and memory limits are sums of exported finite limits, not physical host capacity. Host and Guest measurements must not be added together. Missing data is not zero, and guest rates omit windows with observed epoch changes. The Loki sandbox filter remains a regex text input for historical IDs; Elasticsearch selectors cascade from cluster to node to sandbox.

See 1.2 for backend versions, default ports, and external-component baselines.

After a one-click/systemd installation, run sudo ./deploy/one-click/observability/install.sh status to check the Collector and backends. Check the default Loki with curl -fsS http://127.0.0.1:3100/ready; for Elasticsearch, use curl -fsS 'http://127.0.0.1:9200/cubesandbox-logs*/_count'. A sandbox removal deletes its local log directory, so the Collector must continuously tail and export logs while the sandbox exists.