Dash0 acquires Polar Signals

Last updated:

Set Up SignalControl Edge Without Kubernetes

Run the SignalControl Edge collector image with Docker, including region endpoints, the reference collector configuration, and a smoke test.

This page covers running SignalControl Edge without Kubernetes: the Dash0 edge collector image with Docker, the optional edge proxy, and the reference collector configuration. For the Kubernetes deployment, see Dash0 SignalControl Edge instead.

Dash0 ships SignalControl Edge as container images only. The examples use Docker, but any container runtime works.

Prerequisites

  • Docker: Or another container runtime. The smoke test at the end also uses curl.
  • A Dash0 auth token: An organization token (auth_…) with ingest and read permission. Creating sampling rules through the API additionally requires the admin role. See Auth Tokens.
  • A Dash0 dataset: The examples use default.
  • The Dash0 container images: Both are published to GitHub Container Registry and released together under the same version tag, for example 1.1.0. The collector is ghcr.io/dash0hq/signal-control-edge-collector and the edge proxy is ghcr.io/dash0hq/edge-proxy. The examples below use <tag> as a placeholder for the release you pick.

Endpoints

This is the canonical endpoint table for SignalControl Edge. Pick the row for your region.

RegionOTLP ingress (gRPC)Sampling upstreamAPI (settings)
EU Central (Frankfurt)ingress.eu-central-1.aws.dash0.com:4317decision-maker.eu-central-1.aws.dash0.com:443https://api.eu-central-1.aws.dash0.com
EU West (Ireland)ingress.eu-west-1.aws.dash0.com:4317decision-maker.eu-west-1.aws.dash0.com:443https://api.eu-west-1.aws.dash0.com
US East (N. Virginia)ingress.us-east-1.aws.dash0.com:4317decision-maker.us-east-1.aws.dash0.com:443https://api.us-east-1.aws.dash0.com
US West (Oregon)ingress.us-west-2.aws.dash0.com:4317decision-maker.us-west-2.aws.dash0.com:443https://api.us-west-2.aws.dash0.com
EU West (GCP)ingress.europe-west4.gcp.dash0.com:4317decision-maker.europe-west4.gcp.dash0.com:443https://api.europe-west4.gcp.dash0.com

Steps

The steps below use EU Central endpoints. Substitute your region's row from the table above.

  1. Export the environment variables the collector configuration and the commands below read. Use export so that Docker can pass them into the containers. An unset variable expands to the empty string, which fails the collector's configuration validation:

    sh
    export DASH0_AUTH_TOKEN="auth_..."
    export DASH0_DATASET="default"
    export DASH0_API="https://api.eu-central-1.aws.dash0.com"
  2. Start the edge proxy. This is optional but recommended at scale. Skip it to run in direct mode:

    sh
    docker run --rm --detach --name edge-proxy --publish 8011:8011 --publish 8012:8012 \
      -e UPSTREAM_ADDRESS=decision-maker.eu-central-1.aws.dash0.com:443 \
      -e UPSTREAM_HEADERS="authorization=Bearer ${DASH0_AUTH_TOKEN},Dash0-Dataset=${DASH0_DATASET}" \
      -e LISTENADDRESS=:8011 -e LISTENADDRESSINTERNAL=:8012 \
      ghcr.io/dash0hq/edge-proxy:<tag>
  3. Save the reference collector configuration below as config.yaml and replace its three placeholders:

    • <dash0 api endpoint for your region>: The API column of the endpoint table, for example https://api.eu-central-1.aws.dash0.com.
    • <dash0 ingress>: The host part of the OTLP ingress column, for example ingress.eu-central-1.aws.dash0.com.
    • <edge-proxy host:port>: host.docker.internal:8011 when the edge proxy from step 2 runs on the same machine. Also set decision_maker_insecure: true, because the local proxy speaks plaintext gRPC. In direct mode, use the sampling upstream column instead, for example decision-maker.eu-central-1.aws.dash0.com:443, and keep decision_maker_insecure: false.
  4. Start the collector with the configuration mounted at /etc/otelcol/config.yaml:

    sh
    docker run --rm --detach --name edge-collector --publish 4317:4317 --publish 4318:4318 \
      --add-host=host.docker.internal:host-gateway \
      -v "$(pwd)/config.yaml:/etc/otelcol/config.yaml" \
      -e DASH0_AUTH_TOKEN -e DASH0_DATASET \
      ghcr.io/dash0hq/signal-control-edge-collector:<tag>

    Read the first few seconds of the log with docker logs edge-collector. A correct configuration starts without WARN lines from the metering validator.

  5. Create a sampling rule. Without any rule, sampling is off for the dataset and every trace passes through, so the test would not exercise tail sampling. Once a rule exists, only traces matching a rule are kept. The rule below keeps every trace that contains an error span:

    sh
    curl -X POST "${DASH0_API}/api/sampling-rules?dataset=${DASH0_DATASET}" \
      -H "Authorization: Bearer ${DASH0_AUTH_TOKEN}" \
      -H "Content-Type: application/json" \
      -d '{
        "kind": "Dash0Sampling",
        "metadata": { "name": "keep-errors" },
        "spec": {
          "enabled": true,
          "display": { "name": "Keep error traces" },
          "conditions": { "kind": "error", "spec": {} }
        }
      }'

    The rule reaches the collector through its sampling connection, directly from the decision maker or via the edge proxy. Allow about a minute before sending test data.

  6. Send a test trace with one error span to the collector's OTLP/HTTP port:

    sh
    NOW="$(date +%s)000000000"
    curl -X POST http://localhost:4318/v1/traces \
      -H "Content-Type: application/json" \
      -d "{
        \"resourceSpans\": [{
          \"resource\": { \"attributes\": [
            { \"key\": \"service.name\", \"value\": { \"stringValue\": \"signalcontrol-smoke-test\" } }
          ] },
          \"scopeSpans\": [{ \"spans\": [{
            \"traceId\": \"5b8aa5a2d2c872e8321cf37308d69df2\",
            \"spanId\": \"051581bf3cb55c13\",
            \"name\": \"GET /smoke-test\",
            \"kind\": 2,
            \"startTimeUnixNano\": \"${NOW}\",
            \"endTimeUnixNano\": \"${NOW}\",
            \"status\": { \"code\": 2, \"message\": \"smoke test\" }
          }] }]
        }]
      }"

    An empty JSON object ({}) in the response means the collector accepted the span.

  7. Confirm the trace arrived. In Dash0, open Tracing and filter for the service signalcontrol-smoke-test. The trace can take up to the reservoir's buffer duration, one minute in the reference configuration, to appear. Then delete the keep-errors rule if you do not want to keep it. Note that removing the last rule turns sampling off again for the dataset.

Reference Collector Configuration

A complete, runnable edge collector: OTLP in for traces, logs, and metrics; tail sampling, RED metrics, and signal-to-metrics for traces; signal-to-metrics for logs; spam filtering for all three signals; and metering wired for all of it. Placeholders in angle brackets are environment-specific, and two environment variables must be set for it to load: DASH0_AUTH_TOKEN and DASH0_DATASET. Optional settings are shown commented out with their defaults, so you can see what is available to tune. The capability pages explain each one. You only need the uncommented lines to run.

yaml
extensions:
  # Tells every Dash0 component this collector runs at the edge, and supplies the settings
  # (signal-to-metrics rules, filter rules, dataset settings) the capabilities need.
  dash0settingsonedgeextension:
    # Direct HTTP: poll Dash0 directly. To use the edge proxy instead, remove these fields and set `proxy:` below.
    endpoint: "<dash0 api endpoint for your region>"
    auth_token: ${env:DASH0_AUTH_TOKEN}
    refresh_interval: 60s          # settings poll period; 10s–1h
    # proxy:                       # Edge proxy gRPC: subscribe to a local edge proxy instead of polling Dash0 directly
    #   address: "edge-proxy-host:8011"
    #   insecure: false            # true only for a plaintext local edge proxy

  # Holds the counters the Dash0 components record, until the paired receiver below drains
  # them into a metrics pipeline: the usage counters from the metering processors, and the
  # in/out volume counters from the capabilities that the SignalControl page in Dash0 shows.
  dash0metricrecorder/metering:
    flush_interval: 1m             # how often counters flush to the drain pipeline
    # queue_size: 1000             # in-memory queue of pending metric batches
    # max_stale: 5m                # drop a per-tenant recorder after this idle; must be > flush_interval

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

  # The drain side of the recorder. Its ID must name the extension instance above.
  dash0metricrecorder/metering:
    metric_recorder: dash0metricrecorder/metering

processors:
  memory_limiter:
    check_interval: 1s
    limit_percentage: 80
    spike_limit_percentage: 20

  batch: { }                       # optional: send_batch_size, timeout, send_batch_max_size

  # Enrichment. Not SignalControl capabilities, and never a reason to configure metering.
  # dash0resource handles traces, logs, and metrics. dash0operation handles traces and metrics.
  dash0resource: { }
  dash0operation: { }

  # Telemetry filtering. Drops telemetry matching your per-dataset filter rules. The same
  # processor instance serves the traces, logs, and metrics pipelines below.
  dash0filter:
    metric_recorder: dash0metricrecorder/metering   # records the filter's in/out volume; without it the SignalControl page shows no filter volume
    apply_positive_filters_only: true   # recommended on edge: skip negative-operator rules so an over-broad rule cannot drop everything
    # cache_expiration: 60s             # how long compiled filter rules are cached

  dash0sampling:
    metric_recorder: dash0metricrecorder/metering   # records sampling in/out volume; without it the SignalControl page shows no sampling volume
    decision_maker_endpoint: "<edge-proxy host:port>"   # edge proxy, or the Dash0 sampling upstream in direct mode
    # Required whenever organization_mode is single (the default): the headers authenticate the
    # organization for rule lookup, so the processor refuses to start without them.
    decision_maker_headers:
      Authorization: "Bearer ${env:DASH0_AUTH_TOKEN}"
      Dash0-Dataset: "${env:DASH0_DATASET}"
    decision_maker_insecure: false      # true only for a plaintext local edge proxy
    organization_mode: single           # single (one org, from the token) or multi
    default_dataset: "${env:DASH0_DATASET}"   # dataset used when a span carries none
    # enable_batching: false            # batch satisfaction reports to the upstream
    # max_batch_size: 500               # only when enable_batching is true
    # max_batch_wait: 200ms             # only when enable_batching is true
    # decision_channel_max_size: 10000  # buffer for incoming decisions
    # eviction_channel_size: 1000       # buffer for spans leaving the reservoir
    # fallback_sample_ratio: 0.01       # keep ratio when the upstream is unreachable / rules not yet delivered
    # fallback_min_connected_ratio: 1.0 # fall back when connected upstreams drop below this fraction
    # fallback_until_rules_received: true  # use the fallback ratio at startup until rules arrive
    # debug: false                      # verbose per-decision logging (high volume)
    reservoir:
      type: serialized_memory      # serialized_memory (recommended) | memory | disk (the default when unset)
      buffer_duration: 60s         # how long spans wait for a decision; must cover decision latency
      max_memory_bytes: 104857600  # 100 MB; used by memory and serialized_memory
      # shard_count: 16            # write parallelism; disk, serialized_memory, memory (memory defaults to CPU count)
      # ingest_channel_size: 100   # per-shard ingest buffer
      # eviction_scan_send_timeout: 1s
      # --- memory type only ---
      # estimated_bytes_per_span: 800   # size estimate used for the memory calculation
      # --- disk type only ---
      # data_dir: /var/lib/dash0/reservoir   # required for type: disk; use fast SSD/NVMe
      # max_disk_bytes: 0          # unlimited (age-based eviction only); set >0 to cap disk, e.g. 1073741824 (1 GiB)
      # rotation_interval: 5s      # how often active files are sealed
      # writer_buffer_size: 262144 # 256 KB per-shard write buffer

  # Metering. Exactly one count_and_mark instance per source and signal type. The otlp receiver
  # fans out per signal type, so traces, logs, and metrics each get their own counting instance
  # and every signal is counted exactly once.
  dash0metering/pre-sampling:
    metric_recorder: dash0metricrecorder/metering
    mode: count_and_mark           # count_and_mark | mark_only | count (count is invalid on edge)
    # old_data_threshold: 24h      # do not count signals older than this; 0 disables the check
    # web_spans_processing_enabled: false  # set true on a pipeline that processes web-event spans

  # Downstream of the traces counter in the same chain, so the mark keeps these spans from being
  # counted twice. It still marks, so dash0sampling below keeps acting once your organization is enforcing.
  dash0metering/sampling:
    metric_recorder: dash0metricrecorder/metering
    mode: mark_only

  dash0metering/logs:
    metric_recorder: dash0metricrecorder/metering
    mode: count_and_mark

  dash0metering/metrics:
    metric_recorder: dash0metricrecorder/metering
    mode: count_and_mark

connectors:
  forward/sampling: { }
  dash0signaltometrics:
    metric_recorder: dash0metricrecorder/metering   # records signal-to-metrics in/out volume for the SignalControl page
    # default_dataset: default
    # metrics_flush_interval: 60s  # how often generated metrics are exported
    # cache_expiration: 60s        # how long the compiled ruleset is cached
    # max_time_series: 30000       # in-memory series cap
    # histogram:
    #   exponential:
    #     max_size: 160            # exponential-histogram bucket count
  dash0redmetrics:
    # metrics_flush_interval: 60s
    # max_time_series: 5000        # soft in-memory series cap
    # max_time_series_age: 10m     # age at which idle series are cleaned up
    # min_time_series_age: 30s     # minimum age before cleanup
    # additional_span_attributes: []   # extra span attributes to add as metric dimensions
    # cardinality_threshold: 3     # max distinct values per additional attribute (1–3)
    # histogram:
    #   exponential:
    #     max_size: 160

exporters:
  otlp/dash0:
    endpoint: "<dash0 ingress>:4317"
    headers:
      Authorization: "Bearer ${env:DASH0_AUTH_TOKEN}"
      Dash0-Dataset: "${env:DASH0_DATASET}"

service:
  extensions:
    - dash0settingsonedgeextension
    - dash0metricrecorder/metering

  pipelines:
    # dash0metering/pre-sampling sits just before dash0filter, the first Dash0 capability here,
    # so it counts every span entering filtering, once. dash0signaltometrics and dash0redmetrics
    # then see the surviving spans on the exporter side, before sampling.
    traces/pre-sampling:
      receivers:
        - otlp
      processors:
        - memory_limiter
        - dash0resource
        - dash0operation
        - dash0metering/pre-sampling
        - dash0filter
      exporters:
        - dash0signaltometrics
        - dash0redmetrics
        - forward/sampling

    # Metering sits immediately before dash0sampling, the first Dash0 capability here.
    traces/sampling:
      receivers:
        - forward/sampling
      processors:
        - dash0metering/sampling
        - dash0sampling
        - batch
      exporters:
        - otlp/dash0

    # Logs: spam filtering, and signal-to-metrics rules that match logs. Metering sits just
    # before dash0filter, the first Dash0 capability in this pipeline.
    logs:
      receivers:
        - otlp
      processors:
        - memory_limiter
        - dash0resource
        - dash0metering/logs
        - dash0filter
        - batch
      exporters:
        - dash0signaltometrics
        - otlp/dash0

    # Metrics: spam filtering only. Time series aggregation runs in Dash0, not at the edge.
    metrics:
      receivers:
        - otlp
      processors:
        - memory_limiter
        - dash0resource
        - dash0operation
        - dash0metering/metrics
        - dash0filter
        - batch
      exporters:
        - otlp/dash0

    # Needs no metering: dash0signaltometrics and dash0redmetrics appear in receivers, and
    # their metrics were already counted upstream as the signals they were derived from. The
    # usage counters ride out here too, because a pipeline fed by a Dash0 connector reaches
    # Dash0 by construction.
    metrics/signal-control:
      receivers:
        - dash0signaltometrics
        - dash0redmetrics
        - dash0metricrecorder/metering
      exporters:
        - otlp/dash0

This does not meter any signal twice. For traces, only dash0metering/pre-sampling counts (count_and_mark); dash0metering/sampling is mark_only and never adds to your usage. Logs and metrics reach the collector as separate signal types, so their counting instances see disjoint data.

Authentication and Connecting Back to Dash0

The settings extension picks its connect-back path by whether proxy.address is set.

Direct HTTP. The extension polls the edge settings endpoint directly:

yaml
extensions:
  dash0settingsonedgeextension:
    endpoint: "https://api.<region>.aws.dash0.com"   # required
    auth_token: ${env:DASH0_AUTH_TOKEN}              # required; org is implicit in the token
    refresh_interval: 60s                            # 10s–1h

Edge proxy gRPC. The extension subscribes to a local edge proxy. The endpoint, auth_token, and refresh_interval fields are accepted but ignored:

yaml
extensions:
  dash0settingsonedgeextension:
    proxy:
      address: "edge-proxy-host:8011"
      insecure: false            # true only for local and plaintext

Other connect-back paths:

  • Telemetry export: A standard OTLP exporter to <dash0 ingress>:4317 with Authorization: Bearer <token> and a Dash0-Dataset header.
  • Sampling: dash0sampling.decision_maker_endpoint points at the edge proxy, or the Dash0 sampling upstream in direct mode. decision_maker_headers carry the bearer token and Dash0-Dataset, required whenever organization_mode is single (the default). Use decision_maker_insecure: true only for a plaintext local edge proxy.

Further Reading