Выберите продукт

OpenTelemetry на VDS: Collector, трассировки и метрики для PHP/Node/Go

Разворачиваем OpenTelemetry на собственном VDS: ставим Collector, включаем OTLP gRPC/HTTP, собираем трассировки и метрики из PHP, Node.js и Go. Даю готовые конфиги и примеры, разбираю семплирование, кардинальность лейблов, TLS и отладку.
OpenTelemetry на VDS: Collector, трассировки и метрики для PHP/Node/Go

OpenTelemetry давно вышел за рамки эксперимента и стал стандартом де-факто для телеметрии: трассировки, метрики и логи в единой модели и протоколах. На собственном VDS это особенно удобно: вы контролируете конфигурацию, безопасность и источники затрат. Ниже — как развернуть OpenTelemetry Collector, настроить сбор трассировок и метрик и подключить PHP, Node.js и Go.

Зачем OpenTelemetry на VDS и как всё устроено

Классическая архитектура состоит из трёх слоёв:

  • SDK/автоинструментирование в приложении (PHP/Node/Go), которое формирует спаны и метрики.
  • OpenTelemetry Collector — универсальный агент/шлюз, принимающий данные по OTLP и отправляющий их в хранилища. Он же нормализует, семплирует, обогащает, буферизует.
  • Бэкенды наблюдаемости: хранилища трассировок и метрик. В статье сфокусируемся на Collector и приложениях; конкретный бэкенд подставите под свои требования.

Плюсы Collector на VDS:

  • Единая точка приёма: приложения шлют в локальный Collector, меньше завязок на внешние адреса и проще ACL.
  • Гибкость: смена экспортёра без перезапуска приложений.
  • Производительность: батчирование, ретраи и контроль «напора» телеметрии.
  • Безопасность: TLS, клиентские сертификаты, приватные сети внутри VDS.

Разворачиваем OpenTelemetry Collector на Ubuntu/Debian

Понадобится бинарник otelcol-contrib (вариант contrib с расширенным набором ресиверов/экспортёров), отдельный системный пользователь, директории для конфигурации и данных и unit для systemd.

Создаём пользователя и директории

sudo useradd --system --no-create-home --shell /usr/sbin/nologin otel
sudo mkdir -p /etc/otelcol-contrib
sudo mkdir -p /var/lib/otelcol-contrib
sudo chown -R otel:otel /etc/otelcol-contrib /var/lib/otelcol-contrib

Скачайте релизный архив otelcol-contrib под вашу архитектуру, распакуйте бинарник в /usr/local/bin/otelcol-contrib и назначьте права:

sudo mv otelcol-contrib /usr/local/bin/otelcol-contrib
sudo chown root:root /usr/local/bin/otelcol-contrib
sudo chmod 755 /usr/local/bin/otelcol-contrib

Systemd unit

[Unit]
Description=OpenTelemetry Collector Contrib
After=network-online.target
Wants=network-online.target

[Service]
User=otel
Group=otel
ExecStart=/usr/local/bin/otelcol-contrib --config /etc/otelcol-contrib/config.yaml
Restart=on-failure
RestartSec=3s
MemoryLimit=512M
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target

Сохраните как /etc/systemd/system/otelcol-contrib.service, затем:

sudo systemctl daemon-reload
sudo systemctl enable --now otelcol-contrib

Базовая конфигурация Collector

Минимальный config.yaml с приёмом OTLP по gRPC/HTTP, сбором метрик хоста и экспозом метрик Collector на порту 8889. Трассировки для отладки выводим в лог (logging), метрики — через prometheus-экспортёр.

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
  hostmetrics:
    collection_interval: 60s
    scrapers:
      cpu: {}
      memory: {}
      disk: {}
      filesystem: {}
      load: {}
      network: {}

exporters:
  logging:
    loglevel: info
  prometheus:
    endpoint: 0.0.0.0:8889
    namespace: otelcol

processors:
  batch:
    send_batch_size: 8192
    timeout: 5s
  memory_limiter:
    check_interval: 5s
    limit_percentage: 75
    spike_limit_percentage: 15

extensions:
  health_check:
    endpoint: 0.0.0.0:13133
  pprof:
    endpoint: 127.0.0.1:1777

service:
  telemetry:
    metrics:
      address: 0.0.0.0:8888
  extensions: [health_check, pprof]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [logging]
    metrics:
      receivers: [otlp, hostmetrics]
      processors: [memory_limiter, batch]
      exporters: [prometheus]

Перезапустите Collector, чтобы применить конфиг:

sudo systemctl restart otelcol-contrib
sudo systemctl status otelcol-contrib

Открываем порты в фаерволе

Если используете UFW, откройте 4317 (gRPC), 4318 (HTTP), 8889 (экспорт метрик Collector). Health-check 13133 можно оставить доступным только локально.

sudo ufw allow 4317/tcp comment "OTLP gRPC"
sudo ufw allow 4318/tcp comment "OTLP HTTP"
sudo ufw allow 8889/tcp comment "Prometheus exporter"
FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Подключаем PHP: трассировки и метрики

В PHP доступны автоинструментирование расширениями и ручное инструментирование через SDK. Для явного контроля ниже — минимальный пример ручного способа. В любом случае убедитесь, что у PHP-процессов выставлены OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_SERVICE_NAME и при необходимости OTEL_RESOURCE_ATTRIBUTES.

Переменные окружения в PHP-FPM

Добавьте в пул www.conf (или ваш пул):

env[OTEL_EXPORTER_OTLP_ENDPOINT]=http://127.0.0.1:4318
env[OTEL_EXPORTER_OTLP_PROTOCOL]=http/protobuf
env[OTEL_SERVICE_NAME]=php-frontend
env[OTEL_RESOURCE_ATTRIBUTES]=deployment.environment=prod,service.version=1.2.3

Перезапустите FPM:

sudo systemctl reload php8.2-fpm

Минимальный пример трассировки на PHP (SDK)

<?php
require __DIR__ . '/vendor/autoload.php';

use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\Contrib\Otlp\Exporter as OtlpExporter;

$exporter = new OtlpExporter('http://127.0.0.1:4318/v1/traces');
$tracerProvider = new TracerProvider(new SimpleSpanProcessor($exporter));
Globals::registerTracerProvider($tracerProvider);
$tracer = Globals::tracerProvider()->getTracer('php-demo');

$root = $tracer->spanBuilder('http.request')
    ->setSpanKind(SpanKind::KIND_SERVER)
    ->startSpan();

try {
    usleep(100000);
} finally {
    $root->end();
    $tracerProvider->shutdown();
}

Если используете middleware фреймворка, подключайте инициализацию SDK в ранней точке загрузки. Для метрик создайте счётчики и гистограммы в нужных местах кода (время рендеринга, обработанные задачи, размер ответа).

Пример unit-файла systemd и базового config.yaml для otelcol-contrib

Node.js: автоинструментирование и метрики

В Node.js проще всего стартовать с @opentelemetry/sdk-node и набором автоинструментов. Те же базовые переменные: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_SERVICE_NAME, а также OTEL_TRACES_SAMPLER при необходимости. Если разворачиваете сервисы на ARM, обратите внимание на заметки по производительности в материале про оптимизацию ARM VDS: ARM VDS: PHP/Node производительность.

Инициализация трассировок

// tracer.js
'use strict';
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');

const traceExporter = new OTLPTraceExporter({ url: 'http://127.0.0.1:4318/v1/traces' });

const sdk = new NodeSDK({
  traceExporter,
  instrumentations: [getNodeAutoInstrumentations()],
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'node-api',
    [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: process.env.DEPLOYMENT_ENVIRONMENT || 'prod'
  })
});

sdk.start().then(() => {
  console.log('OTel SDK started');
}).catch((err) => console.error(err));

process.on('SIGTERM', async () => {
  await sdk.shutdown();
  process.exit(0);
});

Старт приложения с предварительной загрузкой tracer.js:

OTEL_SERVICE_NAME=node-api OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318 node -r ./tracer.js app.js

Пример метрик в Node.js

// metrics.js
'use strict';
const { MeterProvider } = require('@opentelemetry/sdk-metrics');
const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-http');
const { AggregationTemporality } = require('@opentelemetry/sdk-metrics');

const metricExporter = new OTLPMetricExporter({ url: 'http://127.0.0.1:4318/v1/metrics', temporalityPreference: AggregationTemporality.CUMULATIVE });
const meterProvider = new MeterProvider();
meterProvider.addMetricReader(metricExporter);

const meter = meterProvider.getMeter('node-metrics');
const requests = meter.createCounter('http_requests_total', { description: 'HTTP requests' });

function onRequest(path) {
  requests.add(1, { path });
}

module.exports = { onRequest };

Следите за кардинальностью лейблов: не добавляйте «сырые» пути с уникальными идентификаторами. Нормализуйте путь, например, /orders/:id вместо /orders/123.

Go: чистое SDK, OTLP и контекст

В Go SDK стабилен и лаконичен: создаём экспортер OTLP (HTTP или gRPC), настраиваем TracerProvider и MeterProvider. Обязательно прокидывайте context.Context по стеку вызовов — иначе потеряете связность спанов.

Пример трассировки в Go

package main

import (
  "context"
  "log"
  "net/http"
  "time"

  "go.opentelemetry.io/otel"
  "go.opentelemetry.io/otel/attribute"
  "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
  "go.opentelemetry.io/otel/sdk/resource"
  sdktrace "go.opentelemetry.io/otel/sdk/trace"
  semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)

func initTracer(ctx context.Context) func(context.Context) error {
  exp, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpointURL("http://127.0.0.1:4318/v1/traces"))
  if err != nil { log.Fatal(err) }
  res, _ := resource.Merge(resource.Default(), resource.NewWithAttributes(
    semconv.SchemaURL,
    semconv.ServiceNameKey.String("go-api"),
    attribute.String("deployment.environment", "prod"),
  ))
  tp := sdktrace.NewTracerProvider(
    sdktrace.WithBatcher(exp),
    sdktrace.WithResource(res),
  )
  otel.SetTracerProvider(tp)
  return tp.Shutdown
}

func main() {
  ctx := context.Background()
  shutdown := initTracer(ctx)
  defer func() { _ = shutdown(ctx) }()

  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    ctx, span := otel.Tracer("go-api").Start(r.Context(), "http.request")
    defer span.End()
    time.Sleep(120 * time.Millisecond)
    span.SetAttributes(attribute.String("path", r.URL.Path))
    _, _ = w.Write([]byte("ok"))
    _ = ctx
  })

  log.Fatal(http.ListenAndServe(":8080", nil))
}

Метрики в Go настраиваются аналогично: поднимите MeterProvider и используйте OTLP HTTP на http://127.0.0.1:4318/v1/metrics; Collector примет и отдаст наружу через prometheus-экспортёр.

Пропагация контекста через прокси и балансировщики

Главное в трассировках — сквозная корреляция. Сохраняйте и передавайте заголовки W3C Trace Context: traceparent и baggage. Большинство HTTP-прокси (включая Nginx) проксируют неизвестные заголовки, но при строгих политиках добавьте их явно.

location /api/ {
  proxy_pass http://backend;
  proxy_set_header traceparent $http_traceparent;
  proxy_set_header baggage $http_baggage;
  proxy_set_header x-request-id $request_id;
}

Логи веб-сервера с x-request-id упростят сопоставление с трассировками. Для фоновых воркеров и демонов пригодится систематизация запуска под systemd: Supervisor/systemd: воркеры и перезапуски.

Сэмплирование и производительность

Собирать 100% трассировок в проде часто избыточно. Начните со 100% в стейджинге и сниженного процента в проде. Есть два уровня сэмплинга:

  • На стороне SDK: OTEL_TRACES_SAMPLER=parentbased_traceidratio с OTEL_TRACES_SAMPLER_ARG (например, 0.1 для 10%).
  • На стороне Collector: процессоры tail_sampling (по атрибутам/длительности/статусам) и probabilistic_sampler.

Пример tail_sampling в Collector: оставляем ошибки и долгие запросы, остальное — до 5%.

processors:
  tail_sampling:
    decision_wait: 5s
    num_traces: 50000
    policies:
      - name: errors
        type: status_code
        status_code:
          status_codes: [ERROR]
      - name: long_traces
        type: latency
        latency:
          threshold_ms: 500
      - name: default_prob
        type: probabilistic
        probabilistic:
          sampling_percentage: 5

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, tail_sampling, batch]
      exporters: [logging]

Для метрик следите за кардинальностью лейблов: высококардинальные значения (UUID, e-mail, полный путь) «размножают» ряды и перегружают систему. Нормализуйте лейблы и используйте гистограммы для латенси и размеров.

TLS и безопасность транспорта

OTLP поддерживает шифрование. Рекомендации:

  • Слушайте только 127.0.0.1 и/или внутренний адрес VDS.
  • Ограничьте доступ по фаерволу: разрешайте только хостам приложений.
  • Включите TLS на otlp-ресивере и храните ключи с ограниченными правами.
  • При междатацентровом обмене используйте mTLS.

Включение TLS в otlp с сертификатами:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
        tls:
          cert_file: /etc/otelcol-contrib/tls/cert.pem
          key_file: /etc/otelcol-contrib/tls/key.pem
      http:
        endpoint: 0.0.0.0:4318
        tls:
          cert_file: /etc/otelcol-contrib/tls/cert.pem
          key_file: /etc/otelcol-contrib/tls/key.pem

Если нужен публичный TLS, заранее оформите и обновляйте SSL-сертификаты.

Надёжность: буферы, ретраи, лимиты

Collector переживает кратковременные сбои экспортёров, аккумулируя данные в памяти. Для прод-среды задайте лимиты памяти (memory_limiter) и разумный размер батча в batch-процессоре. В приложениях включайте батч-экспортёры, снижайте частоту экспорта метрик (30–60 секунд), не публикуйте метрики чаще, чем нужно.

Плохой запах: синхронный экспорт OTLP из HTTP-обработчика. Используйте асинхронные батч-экспортёры.

Проверка и отладка

  • Проверьте health_check Collector: статус сервиса и метрики на 0.0.0.0:8888.
  • Смотрите логи logging-экспортёра: входящие спаны и метрики.
  • Временно поднимите сэмплер до 100% в стейджинге.
  • Проверьте синхронизацию времени: сдвиги ломают окна метрик.
  • Убедитесь, что приложение и Collector согласованы по OTLP: gRPC vs HTTP.

Пример логов OpenTelemetry Collector и проверка health-check

Типичные проблемы

  • Пустые трассы: забыты заголовки traceparent при проксировании или всегда создаются новые корневые спаны.
  • Большие задержки экспорта: слишком маленький send_batch_size или слишком частый экспорт метрик.
  • Взрыв кардинальности: лейблы с уникальными значениями.
  • Сбои в PHP при поздней инициализации: SDK подключайте до логики приложения и корректно завершайте.

Пример «боевого» конфига Collector

Добавим retry для экспортёров, transform для нормализации атрибутов, разнесём пайплайны. Это удобная основа для прод-среды.

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
  hostmetrics:
    collection_interval: 30s
    scrapers:
      cpu: {}
      memory: {}
      disk: {}
      filesystem: {}
      network: {}

processors:
  memory_limiter:
    check_interval: 2s
    limit_percentage: 70
    spike_limit_percentage: 10
  batch:
    send_batch_size: 4096
    send_batch_max_size: 8192
    timeout: 5s
  transform/traces:
    trace_statements:
      - context: span
        statements:
          - set(attributes["service.env"], attributes["deployment.environment"]) where attributes["deployment.environment"] != nil

exporters:
  logging:
    loglevel: warn
  prometheus:
    endpoint: 0.0.0.0:8889
    namespace: otel
    send_timestamps: true

service:
  telemetry:
    logs:
      level: info
  pipelines:
    metrics:
      receivers: [otlp, hostmetrics]
      processors: [memory_limiter, batch]
      exporters: [prometheus]
    traces:
      receivers: [otlp]
      processors: [memory_limiter, transform/traces, batch]
      exporters: [logging]

Далее замените logging-экспортёр трассировок на нужный вам (OTLP, Zipkin-совместимый и т. п.) и при необходимости добавьте аутентификацию.

Практические советы по внедрению

  • Начните с одного сервиса и «сквозной» ручки (endpoint), убедитесь, что пропагация работает, затем масштабируйте.
  • Стандартизируйте ресурс: service.name, service.version, deployment.environment, host.name.
  • Сразу договоритесь об именовании метрик и лейблов; избегайте скрытых динамических значений.
  • Разделяйте уровни сэмплинга: SDK — грубый рычаг, Collector — тонкая настройка по атрибутам/длительности.
  • Следите за GC и выделением памяти: огромные атрибуты в спанах ведут к паузам.
  • Лимитируйте размеры батчей и включите ретраи в экспортёрах — дешевле, чем терять критичные спаны.

Чеклист

  • Collector установлен как сервис, слушает 4317/4318, метрики на 8889.
  • Фаервол пропускает нужные порты только от доверенных хостов.
  • Приложения PHP/Node/Go настроены с OTEL_EXPORTER_OTLP_ENDPOINT и OTEL_SERVICE_NAME.
  • Пропагация traceparent и baggage через прокси подтверждена.
  • Сэмплирование согласовано: SDK и/или Collector; есть правила на ошибки и долгие запросы.
  • Метрики без высококардинальных лейблов; латенси — гистограммы.
  • TLS и/или приватная сеть для OTLP включены, ключи защищены.
  • Логи Collector чистые, health-check зелёный, тестовые спаны видны в экспортёре.

С такой базой вы быстро получите сквозную наблюдаемость: где тормозит запрос, какой микросервис виноват, как ведёт себя система на уровне CPU/памяти/сети и как меняются SLO. OpenTelemetry масштабируется и не привязывает к вендору: меняйте бэкенды, не трогая код — именно ради этого на VDS и разворачивается Collector.

Поделиться статьей

Вам будет интересно

Shared IPv4 vs Dedicated IPv4: rDNS (PTR), rate limiting, SSL SNI и репутация OpenAI Статья написана AI (GPT 5)

Shared IPv4 vs Dedicated IPv4: rDNS (PTR), rate limiting, SSL SNI и репутация

Shared и dedicated IPv4 отличаются не «магией», а контролем: PTR/rDNS, репутацией, rate limiting и влиянием соседей по IP. Разберё ...
OpenTelemetry: Jaeger vs Grafana Tempo vs Elastic APM — практичное сравнение для distributed tracing OpenAI Статья написана AI (GPT 5)

OpenTelemetry: Jaeger vs Grafana Tempo vs Elastic APM — практичное сравнение для distributed tracing

OpenTelemetry стандартизирует distributed tracing, но выбор бэкенда определяет цену хранения и удобство расследований. Сравниваем ...
TLS для SMTP/IMAP: Let's Encrypt, DV и wildcard, SNI и практические настройки Postfix/Dovecot OpenAI Статья написана AI (GPT 5)

TLS для SMTP/IMAP: Let's Encrypt, DV и wildcard, SNI и практические настройки Postfix/Dovecot

Шифрование почты — не только «включить STARTTLS». Разберём, какие имена нужны в сертификате для SMTP/IMAP, чем отличаются Let’s En ...