Развёртывание OpenTelemetry Collector и Jaeger для distributed tracing на VPS
TL;DR
OpenTelemetry Collector принимает трейсы от приложений по OTLP, обрабатывает их и передаёт в Jaeger, где можно искать запросы, смотреть цепочки вызовов и находить медленные сервисы. В этом руководстве вы развернёте self-hosted distributed tracing на Ubuntu VPS через Docker Compose, закроете технические порты firewall, опубликуете Jaeger UI по HTTPS и настроите резервное копирование данных.
- OpenTelemetry Collector будет принимать OTLP/gRPC на порту
4317и OTLP/HTTP на4318. - Jaeger будет хранить трейсы в локальном persistent-хранилище Badger и показывать их через веб-интерфейс.
- Jaeger UI будет доступен только через HTTPS reverse proxy Caddy, а не напрямую через порт
16686. - Минимально для тестов достаточно 2 vCPU, 4 GB RAM и 40 GB NVMe; для production-нагрузки разумнее начинать с 4 vCPU и 8 GB RAM.
- Трейсинговые endpoint’ы нельзя оставлять открытыми всему интернету: ограничьте источники IP-адресами вашей инфраструктуры или VPN-сетью.
- Конфиги, данные Jaeger и Docker volumes нужно регулярно копировать во внешнее хранилище.
Что мы настраиваем и зачем
Distributed tracing нужен, когда один пользовательский запрос проходит через несколько компонентов: frontend, API, очередь, worker, базу данных, сторонний платёжный сервис или LLM API. Обычные логи покажут отдельные события, метрики покажут общую картину нагрузки, но не ответят быстро на вопрос: «Почему конкретный запрос пользователя выполнялся 8 секунд и на каком сервисе возникла задержка?»
Трейс состоит из связанных span’ов. Каждый span описывает операцию: HTTP-запрос, SQL-запрос, публикацию сообщения в Kafka, выполнение job или обращение к Redis. У span есть время начала, длительность, статус, атрибуты и идентификаторы trace_id и span_id. Если сервисы передают trace context через HTTP-заголовки или очередь, Jaeger собирает путь запроса в единое дерево.
В этой схеме OpenTelemetry Collector выступает промежуточным слоем между приложениями и хранилищем трасс. Приложения отправляют данные в Collector по стандартному протоколу OTLP. Collector принимает данные, добавляет полезные атрибуты, ограничивает память, выполняет batching и экспортирует трейсы в Jaeger. Благодаря этому приложения не зависят от конкретного backend’а: позднее Jaeger можно заменить на Tempo, OpenSearch, ClickHouse-совместимое хранилище или облачный сервис без переписывания инструментирования.
| Компонент | Роль | Доступ извне |
|---|---|---|
| Инструментированное приложение | Создаёт spans и передаёт контекст между сервисами | Нет |
| OpenTelemetry Collector Contrib | Принимает, фильтрует, группирует и экспортирует OTLP-трейсы | Только от доверенных серверов или VPN |
| Jaeger all-in-one | Принимает трейсы, хранит их и выдаёт API для поиска | Только внутри Docker-сети |
| Caddy | Выдаёт TLS-сертификат и публикует защищённый Jaeger UI | TCP 80 и 443 |
| Badger | Локальное embedded-хранилище данных Jaeger | Нет |
Что получится в результате
После выполнения инструкции у вас будет домен вида traces.example.com с HTTPS-доступом к Jaeger UI. Приложения смогут отправлять OTLP/gRPC-трейсы на адрес https://otel.example.com:4317 либо на приватный IP Collector без TLS, если все сервисы находятся во внутренней сети или соединены WireGuard.
В Jaeger можно будет выбрать сервис, операцию и диапазон времени, открыть отдельный trace, увидеть waterfall-диаграмму span’ов, определить узкое место и перейти к связанным логам по trace_id. В конфигурации мы добавим атрибуты deployment.environment и host.name, чтобы production, staging и разные серверы не смешивались в одном поиске.
Self-hosted или cloud-managed tracing
У облачных платформ наблюдаемости обычно есть быстрый старт, готовые алерты, длительное хранение и масштабируемый backend. Недостатки — оплата за объём ingestion, зависимость от поставщика, передача технических метаданных третьей стороне и риск неожиданного роста счёта при всплеске трафика.
Self-hosted OpenTelemetry Collector и Jaeger на VPS подходят небольшой команде, внутренним сервисам, SaaS на ранней стадии, тестовым средам и проектам с требованиями к контролю данных. Вы самостоятельно выбираете retention, доступ, бэкапы и место размещения. Цена этой гибкости — необходимость следить за диском, обновлениями, безопасностью и ростом объёма трасс.
Jaeger с Badger в этой статье — практичный вариант для малого и среднего объёма: десятков сервисов и умеренного числа запросов. Для долгого хранения, высокой кардинальности, многотерабайтного потока и нескольких Collector-инстансов потребуется отдельный backend: OpenSearch, Elasticsearch, ClickHouse или другой поддерживаемый production storage. Архитектура с Collector сохраняется; меняется только exporter и backend Jaeger.
Какой VPS-конфиг нужен под эту задачу
Потребление ресурсов зависит не столько от количества сервисов, сколько от числа span’ов в секунду, среднего размера атрибутов и времени хранения. Один HTTP-запрос может создать от 3–5 span’ов в простом API до 30–100 span’ов в микросервисной системе с SQL, Redis, очередями и внешними API. Не включайте запись тела HTTP-запросов, токенов, паролей и больших payload’ов: это опасно и быстро заполняет хранилище.
| Сценарий | CPU | RAM | NVMe-диск | Сеть | Ожидаемый масштаб |
|---|---|---|---|---|---|
| Лаборатория, staging, 1–3 приложения | 2 vCPU | 4 GB | 40 GB | 100 Mbps | До нескольких тысяч span/min |
| Небольшой production | 4 vCPU | 8 GB | 100–160 GB | 1 Gbps | 5–30 сервисов, умеренный sampling |
| Активный SaaS или несколько окружений | 8 vCPU | 16–32 GB | 300 GB+ | 1 Gbps | Высокий ingestion, retention 7–30 дней |
Для старта production-развёртывания возьмите 4 vCPU, 8 GB RAM, 120 GB NVMe и порт 1 Gbps. Такой запас позволит Collector работать с memory limiter и очередями, Jaeger — кэшировать данные, а системе — не уходить в swap при кратковременной нагрузке. Как нейтральный вариант можно выбрать VPS с указанными характеристиками, но перед заказом проверьте тип накопителя, лимит трафика, IPv4-адрес и возможность расширить диск без миграции.
Как оценить объём диска
Начните с измерения, а не с предположений. Пусть приложение создаёт 20 span’ов на запрос, обрабатывает 10 запросов в секунду, а после сериализации и индексов один span в среднем занимает 1–3 KB. Это уже примерно 17–52 GB в сутки до учёта компрессии, служебных данных и sampling. При sampling 10% объём уменьшится примерно в десять раз, поэтому sampling — главный регулятор стоимости хранения.
Для первого месяца выделите минимум в два раза больше места, чем расчётный объём за retention-период. Это оставит резерв для обновлений Docker-образов, архивов бэкапа и внезапного роста трафика. Настройте алерт при заполнении диска на 70%, а не на 95%: Badger и Docker могут резко деградировать, когда свободное место заканчивается.
Когда нужен dedicated, а не VPS
VPS подходит, пока нагрузка предсказуема и latency диска не критична. Dedicated-сервер имеет смысл при постоянно высоком ingestion, больших retention-периодах, необходимости нескольких NVMe в RAID, требованиях к изоляции CPU и IOPS или использовании тяжёлого backend’а вроде OpenSearch. Также dedicated разумен, если tracing-система обслуживает десятки команд и становится внутренней платформой наблюдаемости.
Не переносите Jaeger на dedicated только потому, что в интерфейсе появились десятки тысяч traces. Сначала включите head sampling в SDK, удалите лишние атрибуты, уменьшите retention, проверьте скорость диска и вынесите production и staging в разные сервисы. Обычно это даёт более заметный эффект, чем немедленное увеличение сервера.
Выбор локации
Локация влияет на задержку отправки телеметрии, юридические требования и стоимость трафика. Collector лучше размещать близко к сервисам, которые отправляют трейсы: в том же дата-центре, регионе или хотя бы на том же континенте. Для синхронного OTLP/gRPC высокая RTT увеличивает накладные расходы приложения, особенно при проблемной сети.
Если production-сервисы находятся в разных регионах, не заставляйте каждый сервис писать через интернет в один Collector. Разверните локальные Collector-инстансы рядом с приложениями и отправляйте данные дальше асинхронно или в центральный backend. Для начала достаточно одного VPS, но архитектурно сохраните возможность такого разделения.
Подготовка сервера
Ниже используется Ubuntu Server 24.04 LTS. Команды выполняйте от root только на этапе первоначальной подготовки, затем работайте под отдельным пользователем otel. Предполагается, что у вас есть домены traces.example.com для Jaeger UI и, при необходимости, otel.example.com для публичного OTLP endpoint.
До настройки TLS создайте A-запись traces.example.com, указывающую на публичный IPv4 сервера. Если планируете принимать OTLP через отдельный домен, добавьте и запись otel.example.com. Проверить DNS можно с локального компьютера командой dig +short traces.example.com.
Обновление операционной системы
Сначала установите накопившиеся обновления и базовые утилиты. После обновления ядра перезагрузите сервер, прежде чем продолжать настройку Docker.
# Обновляет индексы пакетов и устанавливает обновления безопасности.
sudo apt update && sudo apt upgrade -y
# Устанавливает инструменты диагностики, firewall, редактор и поддержку HTTPS-репозиториев.
sudo apt install -y ca-certificates curl gnupg lsb-release \
ufw fail2ban unattended-upgrades jq vim htop dnsutils
# Перезагружает сервер, если обновлялось ядро или системные библиотеки.
sudo reboot
Пользователь, SSH-ключи и sudo
Создайте отдельную учётную запись. Не используйте пароль для SSH: ключ Ed25519 проще защитить от brute-force и он удобнее для автоматизации. Перед отключением входа root обязательно откройте вторую SSH-сессию и убедитесь, что новый пользователь может войти и выполнить sudo.
# Создаёт пользователя otel с домашним каталогом и добавляет его в группу sudo.
sudo adduser otel
sudo usermod -aG sudo otel
# Создаёт каталог ключей и копирует текущий authorised_keys для нового пользователя.
sudo install -d -m 700 -o otel -g otel /home/otel/.ssh
sudo cp /root/.ssh/authorized_keys /home/otel/.ssh/authorized_keys
sudo chown otel:otel /home/otel/.ssh/authorized_keys
sudo chmod 600 /home/otel/.ssh/authorized_keys
Отредактируйте SSH-конфигурацию. Если вы подключаетесь не как root или у провайдера ключ лежит в другом месте, не копируйте файл вслепую: добавьте свой публичный ключ через ssh-copy-id otel@SERVER_IP с локальной машины.
# Открывает отдельный SSH-конфиг для усиления настроек доступа.
sudo vim /etc/ssh/sshd_config.d/99-hardening.conf
# Проверяет синтаксис конфигурации и перезапускает SSH только при успехе.
sudo sshd -t && sudo systemctl restart ssh
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
X11Forwarding no
MaxAuthTries 3
AllowUsers otel
Firewall и Fail2ban
Открывайте наружу только SSH, HTTP и HTTPS. Порты 4317, 4318, 16686 и 14268 не нужно публиковать всему интернету. Если приложения находятся на других серверах, позже разрешите OTLP только из их приватной подсети или через WireGuard.
# Разрешает SSH до включения firewall, иначе можно потерять доступ к серверу.
sudo ufw allow OpenSSH
# Разрешает Caddy принимать HTTP для ACME-проверки и HTTPS для Jaeger UI.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Включает firewall и показывает активные правила.
sudo ufw enable
sudo ufw status verbose
# Включает Fail2ban для защиты SSH от повторяющихся неудачных попыток входа.
sudo systemctl enable --now fail2ban
# Проверяет, что jail SSH активен.
sudo fail2ban-client status sshd
Важная особенность Docker: опубликованные контейнерные порты могут обходить обычные UFW-правила из-за правил iptables, созданных Docker. В этой инструкции OTLP-порты вообще не публикуются по умолчанию. Если вы позже добавите их в секцию ports, ограничьте доступ отдельными правилами в цепочке DOCKER-USER или используйте private networking/VPN.
Установка ПО — пошагово
Мы используем Docker Engine 27.x или новее, Docker Compose Plugin 2.x, OpenTelemetry Collector Contrib 0.120.x и Jaeger all-in-one 1.67.x как совместимый стабильный набор. Перед production-обновлением сверяйте доступные теги образов с официальными release notes и тестируйте обновление на staging: версия Collector и образ Jaeger могут меняться независимо.
Установка Docker Engine из официального репозитория
Не используйте старый пакет docker.io из стандартного репозитория Ubuntu: он может заметно отставать по версии. Следующие команды добавляют официальный репозиторий Docker и устанавливают engine вместе с Compose plugin.
# Удаляет конфликтующие старые пакеты Docker, если они были установлены.
sudo apt remove -y docker.io docker-doc docker-compose docker-compose-v2 \
podman-docker containerd runc || true
# Создаёт каталог для ключей сторонних APT-репозиториев.
sudo install -m 0755 -d /etc/apt/keyrings
# Загружает и сохраняет ключ репозитория Docker в формате keyring.
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Добавляет официальный Docker APT-репозиторий для текущего релиза Ubuntu.
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Устанавливает Docker Engine, CLI, containerd и Compose plugin.
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# Запускает Docker при загрузке и проверяет установленную версию.
sudo systemctl enable --now docker
sudo docker version
sudo docker compose version
# Разрешает пользователю otel управлять Docker без sudo.
sudo usermod -aG docker otel
После добавления в группу docker выйдите из SSH-сессии и подключитесь заново как otel. Членство в группе Docker фактически даёт root-эквивалент на сервере, поэтому добавляйте в неё только доверенных администраторов.
Каталоги проекта и секреты
Храните Compose-файл, конфигурации и данные в предсказуемом каталоге. Каталог /opt/observability обычно удобнее, чем домашняя директория: его легче включить в бэкапы и не удалить случайно при смене пользователя.
# Создаёт структуру каталогов для конфигураций, Caddy и persistent-данных Jaeger.
sudo mkdir -p /opt/observability/{otel-collector,caddy,data/jaeger}
sudo chown -R otel:otel /opt/observability
# Переходит в рабочий каталог проекта.
cd /opt/observability
# Создаёт .env с правами только для владельца.
umask 077
touch .env
chmod 600 .env
Секретом в этой конфигурации будет хэш пароля для Jaeger UI. Caddy не хранит пароль в открытом виде. Сгенерируйте хэш интерактивно: команда попросит пароль и выведет строку, начинающуюся с $2a$ или похожий bcrypt-хэш.
# Генерирует bcrypt-хэш пароля для HTTP Basic Authentication в Caddy.
docker run --rm caddy:2 caddy hash-password --plaintext 'ЗАМЕНИТЕ_НА_ДЛИННЫЙ_ПАРОЛЬ'
# Открывает файл переменных окружения для записи доменов и сгенерированного хэша.
vim /opt/observability/.env
Не вставляйте реальный пароль в docker-compose.yml, не отправляйте .env в публичный Git-репозиторий и не используйте один пароль для SSH и веб-интерфейса. Для команды лучше использовать VPN или внешний identity proxy, а Basic Auth оставить дополнительным уровнем защиты.
Проверка системных лимитов
Jaeger с Badger использует файловую систему интенсивнее, чем простой stateless контейнер. Перед запуском проверьте свободный диск и отключите swap только если у вас есть достаточная RAM и понятная политика OOM. Для небольшого VPS swap полезен как аварийный буфер, но постоянная активность swap означает, что серверу не хватает памяти.
# Показывает объём свободного места, оперативной памяти и наличие swap.
df -h /
free -h
swapon --show
# Создаёт журнал Docker с ограничением размера, чтобы контейнерные логи не заполнили диск.
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF'
{
"log-driver": "local",
"log-opts": {
"max-size": "20m",
"max-file": "5"
}
}
EOF
# Перезапускает Docker для применения политики логирования.
sudo systemctl restart docker
Конфигурация OpenTelemetry Collector, Jaeger и HTTPS
Ниже приведена конфигурация, в которой наружу публикуются только Caddy-порты 80 и 443. Collector доступен внутри Docker-сети. Это безопасный вариант, если приложения запущены на том же сервере или в той же Docker-сети. Для удалённых приложений далее будет показано, как осторожно открыть OTLP-порты для доверенной подсети.
Файл переменных окружения
Замените домен, почту и bcrypt-хэш. В значении CADDY_BASIC_AUTH_HASH символ $ Docker Compose может интерпретировать как подстановку переменной, поэтому каждый $ заменяется на двойной $$. Это нормальная особенность Compose.
# /opt/observability/.env
TRACES_DOMAIN=traces.example.com
[email protected]
# Вставьте результат caddy hash-password, заменив каждый $ на $$.
CADDY_BASIC_AUTH_HASH=$$2a$$14$$REPLACE_WITH_YOUR_BCRYPT_HASH
# Среда добавляется Collector ко всем входящим трейсам.
DEPLOYMENT_ENVIRONMENT=production
Конфигурация OpenTelemetry Collector
Collector принимает OTLP по gRPC и HTTP, защищает сервер от переполнения памяти через memory_limiter, группирует spans в пакеты и отправляет их в Jaeger через OTLP/gRPC. Важно: receiver слушает 0.0.0.0 внутри контейнера, но это ещё не делает порт доступным из интернета, пока он не опубликован в Compose.
# /opt/observability/otel-collector/config.yaml
extensions:
health_check:
endpoint: 0.0.0.0:13133
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
memory_limiter:
check_interval: 1s
limit_mib: 512
spike_limit_mib: 128
resource:
attributes:
- key: deployment.environment
value: ${env:DEPLOYMENT_ENVIRONMENT}
action: upsert
- key: host.name
value: tracing-vps
action: upsert
batch:
send_batch_size: 8192
timeout: 5s
exporters:
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true
sending_queue:
enabled: true
num_consumers: 4
queue_size: 10000
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 30s
max_elapsed_time: 300s
service:
extensions: [health_check]
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, resource, batch]
exporters: [otlp/jaeger]
telemetry:
logs:
level: info
Лимит 512 MiB рассчитан на сервер с 8 GB RAM. На VPS с 4 GB уменьшите его до 256 MiB; на сервере с 16 GB можно поднять до 1024 MiB, если это оправдано нагрузкой. Collector при достижении лимита начнёт отклонять часть данных вместо того, чтобы вызвать OOM и уронить весь стек.
Docker Compose: Collector, Jaeger и Caddy
Jaeger all-in-one получает трейсы от Collector на внутреннем порту 4317. Badger-каталоги вынесены в bind mount, поэтому удаление контейнера не удалит данные. Переменная BADGER_SPAN_STORE_TTL задаёт retention 168 часов, то есть 7 дней. Настройте срок хранения по доступному диску и необходимости расследований.
# /opt/observability/docker-compose.yml
services:
jaeger:
image: jaegertracing/all-in-one:1.67.0
container_name: jaeger
restart: unless-stopped
environment:
COLLECTOR_OTLP_ENABLED: "true"
SPAN_STORAGE_TYPE: badger
BADGER_EPHEMERAL: "false"
BADGER_DIRECTORY_VALUE: /badger/data
BADGER_DIRECTORY_KEY: /badger/key
BADGER_SPAN_STORE_TTL: 168h
MEMORY_MAX_TRACES: "0"
volumes:
- ./data/jaeger:/badger
networks:
- tracing
otel-collector:
image: otel/opentelemetry-collector-contrib:0.120.0
container_name: otel-collector
restart: unless-stopped
command: ["--config=/etc/otelcol-contrib/config.yaml"]
environment:
DEPLOYMENT_ENVIRONMENT: ${DEPLOYMENT_ENVIRONMENT}
volumes:
- ./otel-collector/config.yaml:/etc/otelcol-contrib/config.yaml:ro
depends_on:
- jaeger
networks:
- tracing
caddy:
image: caddy:2.8.4
container_name: caddy
restart: unless-stopped
environment:
TRACES_DOMAIN: ${TRACES_DOMAIN}
ACME_EMAIL: ${ACME_EMAIL}
CADDY_BASIC_AUTH_HASH: ${CADDY_BASIC_AUTH_HASH}
ports:
- "80:80"
- "443:443"
volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- jaeger
networks:
- tracing
networks:
tracing:
name: tracing
driver: bridge
volumes:
caddy_data:
caddy_config:
Caddy и HTTPS-доступ к Jaeger UI
Caddy автоматически получает и продлевает TLS-сертификат через ACME, если DNS домена уже указывает на сервер, а TCP 80 и 443 доступны извне. Basic Auth защищает интерфейс от случайного просмотра traces. Не считайте Basic Auth полноценной системой управления доступом: для корпоративного использования добавьте VPN, SSO-proxy или сетевой ACL.
# /opt/observability/caddy/Caddyfile
{
email {$ACME_EMAIL}
auto_https prefer_wildcard
}
{$TRACES_DOMAIN} {
encode zstd gzip
basic_auth {
observability {$CADDY_BASIC_AUTH_HASH}
}
reverse_proxy jaeger:16686
header {
-Server
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "no-referrer"
Strict-Transport-Security "max-age=31536000; includeSubDomains"
}
log {
output stdout
format json
}
}
Запуск и проверка контейнеров
Перед запуском проверьте синтаксис Compose. Затем скачайте образы и запустите сервисы в фоне. Первый выпуск TLS-сертификата может занять до минуты; не меняйте DNS и не перезапускайте Caddy без необходимости.
# Проверяет подстановку переменных и итоговую конфигурацию Docker Compose.
cd /opt/observability
docker compose config > /tmp/observability-compose-rendered.yml
# Скачивает зафиксированные версии образов и запускает стек в фоне.
docker compose pull
docker compose up -d
# Показывает состояние контейнеров и последние строки журналов.
docker compose ps
docker compose logs --tail=100 otel-collector jaeger caddy
# Проверяет health endpoint Collector из его Docker-сети.
docker exec otel-collector wget -qO- http://127.0.0.1:13133/ || true
# Проверяет, что Jaeger API отвечает внутри контейнера.
docker exec jaeger wget -qO- http://127.0.0.1:16686/api/services
# Проверяет публичный HTTPS endpoint; потребуется пароль Basic Auth.
curl -I -u observability:'ВАШ_ПАРОЛЬ' https://traces.example.com
Откройте https://traces.example.com в браузере, введите пользователя observability и пароль, для которого генерировали хэш. Если Jaeger UI загрузился, но список сервисов пуст, это нормально: сервисы появятся только после отправки первого trace.
Как отправить тестовый trace
Самый простой тест — временно запустить контейнер telemetrygen в той же Docker-сети. Он создаст тестовые spans и отправит их в Collector. После этого в Jaeger выберите сервис telemetrygen, операцию traces и нажмите Find Traces.
# Отправляет 20 тестовых trace в секунду в Collector внутри Docker-сети в течение 10 секунд.
docker run --rm --network tracing \
ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:0.120.0 \
traces --otlp-endpoint otel-collector:4317 \
--otlp-insecure --rate 20 --duration 10s
# Проверяет, что сервис telemetrygen появился в Jaeger API.
docker exec jaeger wget -qO- http://127.0.0.1:16686/api/services | jq .
Подключение удалённых приложений
Самый безопасный подход — соединить серверы через WireGuard, Tailscale или приватную сеть провайдера и опубликовать порты Collector только на приватном интерфейсе. Если это невозможно, добавьте порты 4317 и 4318 в Compose, но разрешите доступ только с известного IP-адреса приложения. Не открывайте их для 0.0.0.0/0: любой сможет присылать мусорные spans и заполнить ваш диск.
# Добавьте в сервис otel-collector только при необходимости внешнего OTLP.
# Лучше заменить 10.20.0.10 на приватный IP интерфейса сервера.
ports:
- "10.20.0.10:4317:4317"
- "10.20.0.10:4318:4318"
Для приложения на Python, Node.js, Go, Java или .NET укажите endpoint через переменные окружения. Инструментирование библиотеками OpenTelemetry SDK выполняется отдельно, но базовые переменные почти одинаковы для всех языков.
# Пример общих переменных окружения приложения для OTLP/gRPC.
export OTEL_SERVICE_NAME=orders-api
export OTEL_RESOURCE_ATTRIBUTES="service.version=2026.03,deployment.environment=production"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://10.20.0.10:4317"
export OTEL_EXPORTER_OTLP_PROTOCOL="grpc"
export OTEL_TRACES_SAMPLER="parentbased_traceidratio"
export OTEL_TRACES_SAMPLER_ARG="0.1"
Sampling 0.1 означает запись примерно 10% новых trace. Родительский sampler сохраняет решение родительского запроса во всех дочерних сервисах, благодаря чему вы не получите обрывки одного trace. Для важных ошибок можно позже внедрить tail sampling в Collector: он позволяет сохранять 100% traces с ошибками и медленные запросы, но требует больше памяти и более тщательной настройки.
Бэкапы и обслуживание
В этом развёртывании необходимо копировать каталог /opt/observability/data/jaeger, Compose-файл, конфигурацию Collector, Caddyfile, файл .env и данные Caddy volume. Последние содержат TLS-сертификаты и состояние ACME; без них Caddy сможет выпустить сертификат повторно, но восстановление будет медленнее и может упереться в rate limits.
Не считайте Docker image бэкапом. Образ можно скачать заново, но traces в Badger и конфигурация не восстановятся сами. Также не храните единственную копию бэкапа на том же VPS: потеря диска, ошибочное удаление или компрометация сервера уничтожат и данные, и резерв.
Бэкап через restic в S3-совместимое хранилище
Restic шифрует содержимое до отправки и поддерживает дедупликацию, поэтому хорошо подходит для ежедневных копий. Создайте bucket у внешнего S3-провайдера или на отдельном MinIO-сервере. Данные доступа храните в root-доступном файле, а не в командной строке и не в shell history.
# Устанавливает restic из репозитория Ubuntu.
sudo apt install -y restic
# Создаёт каталог для секретов бэкапа с доступом только root.
sudo install -d -m 700 /root/.config/restic
# Создаёт файл переменных S3 и пароль репозитория.
sudo vim /root/.config/restic/observability.env
sudo chmod 600 /root/.config/restic/observability.env
# /root/.config/restic/observability.env
export RESTIC_REPOSITORY="s3:https://s3.example.net/observability-restic"
export RESTIC_PASSWORD="СГЕНЕРИРУЙТЕ_ОТДЕЛЬНЫЙ_ДЛИННЫЙ_ПАРОЛЬ"
export AWS_ACCESS_KEY_ID="REPLACE_ME"
export AWS_SECRET_ACCESS_KEY="REPLACE_ME"
export AWS_DEFAULT_REGION="us-east-1"
# Инициализирует зашифрованный restic-репозиторий один раз.
sudo bash -c 'source /root/.config/restic/observability.env && restic init'
# Создаёт каталог для скриптов администратора.
sudo install -d -m 700 /usr/local/sbin
# Создаёт ежедневный скрипт резервного копирования.
sudo vim /usr/local/sbin/backup-observability.sh
sudo chmod 700 /usr/local/sbin/backup-observability.sh
#!/usr/bin/env bash
set -euo pipefail
source /root/.config/restic/observability.env
cd /opt/observability
# Останавливает запись в Badger на время консистентной копии.
docker compose stop jaeger
# Копирует конфигурации, данные Jaeger и Docker volume Caddy.
restic backup \
/opt/observability/otel-collector \
/opt/observability/caddy \
/opt/observability/data/jaeger \
/opt/observability/docker-compose.yml \
/opt/observability/.env \
/var/lib/docker/volumes/observability_caddy_data \
--tag observability
# Запускает Jaeger даже при последующей ошибке очистки.
docker compose start jaeger
# Хранит 7 дневных, 4 недельных и 6 месячных наборов.
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
# Проверяет целостность части данных в репозитории.
restic check --read-data-subset=5%
Для гарантированного запуска Jaeger даже при ошибке можно дополнить скрипт trap 'docker compose start jaeger' EXIT. При небольшом объёме данных краткая остановка Jaeger ночью приемлема. Для больших production-хранилищ используйте нативные snapshot-механизмы backend’а вместо файлового копирования работающей базы.
# Добавляет ежедневный запуск бэкапа в 03:20 и записывает лог в защищённый файл.
sudo crontab -e
# Строка для root crontab:
20 3 * /usr/local/sbin/backup-observability.sh >> /var/log/backup-observability.log 2>&1
Проверка восстановления
Бэкап без теста восстановления — лишь предположение. Раз в месяц восстанавливайте данные на отдельный тестовый VPS или хотя бы во временный каталог. Проверьте, что после запуска Compose открывается Jaeger UI, а старые сервисы и traces доступны в поиске.
# Показывает доступные snapshots в restic-репозитории.
sudo bash -c 'source /root/.config/restic/observability.env && restic snapshots'
# Восстанавливает последний snapshot во временный каталог для проверки.
sudo mkdir -p /tmp/observability-restore
sudo bash -c 'source /root/.config/restic/observability.env && \
restic restore latest --target /tmp/observability-restore'
Обновления и retention
Для одного VPS обновление выполняйте в maintenance window: сделайте бэкап, прочитайте changelog образов, обновите один компонент, проверьте health и только затем переходите к следующему. Команда docker compose pull && docker compose up -d создаёт короткий перерыв в приёме traces. SDK обычно буферизуют часть данных, но рассчитывать на это без теста нельзя.
Rolling update нужен, когда потеря даже нескольких секунд телеметрии неприемлема. Тогда запускают минимум два Collector-инстанса за балансировщиком, используют очереди и HA backend для Jaeger. Один Jaeger all-in-one с локальным Badger не поддерживает настоящий rolling upgrade без перерыва и не является высокодоступной конфигурацией.
# Проверяет использование диска, контейнеров и размер данных Jaeger.
df -h /
docker stats --no-stream
sudo du -sh /opt/observability/data/jaeger
# Обновляет образы только после создания проверенного бэкапа.
cd /opt/observability
docker compose pull
docker compose up -d
docker compose logs --tail=100
Troubleshooting и FAQ
Почему Jaeger UI открывается, но список сервисов пуст?
Пустой список означает, что Jaeger пока не получил ни одного корректного trace либо Collector не может экспортировать данные. Сначала выполните тест с telemetrygen из раздела конфигурации. Затем проверьте docker compose logs otel-collector: ищите ошибки exporter, connection refused или deadline exceeded. Убедитесь, что endpoint exporter указан как jaeger:4317, а у Jaeger включено COLLECTOR_OTLP_ENABLED=true. Также проверьте, что приложение использует именно traces exporter, а не только metrics или logs.
Почему приложение получает connection refused на порту 4317?
По умолчанию в этой статье порт 4317 не опубликован наружу, поэтому удалённый сервер не может подключиться к Collector. Это сделано намеренно. Для приложений на том же хосте используйте Docker network и адрес otel-collector:4317. Для другого сервера организуйте WireGuard или private network, затем добавьте безопасную публикацию порта на приватный IP. Проверьте также, что приложение не пытается использовать HTTP endpoint 4318 с gRPC-протоколом или наоборот.
Caddy не получает TLS-сертификат: что проверить?
Проверьте, что A-запись домена указывает на публичный IP VPS: dig +short traces.example.com. Затем убедитесь, что порты 80 и 443 разрешены в UFW и в сетевом firewall панели провайдера. Посмотрите docker compose logs caddy; ошибки ACME обычно прямо указывают на DNS, закрытый порт или превышение лимита запросов сертификата. Не используйте CDN proxy в режиме, который мешает HTTP challenge, пока не убедитесь, что Caddy получил первый сертификат.
Collector потребляет много RAM или перезапускается с OOMKilled. Как исправить?
Сначала подтвердите причину командой docker inspect otel-collector и посмотрите поле OOMKilled. Уменьшите limit_mib и spike_limit_mib в processor memory_limiter, сократите queue_size, снизьте sampling в SDK и отключите избыточные атрибуты. Не увеличивайте лимиты памяти бесконечно: если поток trace выше возможностей VPS, Collector лишь накопит больше данных перед падением. Для высокой нагрузки добавьте второй Collector и балансировщик.
Диск быстро заполняется, хотя retention установлен на 7 дней.
Проверьте реальный размер Badger через du -sh /opt/observability/data/jaeger, Docker-логи через docker system df и системные логи через journalctl --disk-usage. Retention не всегда освобождает место мгновенно: compaction хранилища занимает время. Снизьте sampling, сократите BADGER_SPAN_STORE_TTL, очистите неиспользуемые Docker-образы командой docker image prune и не записывайте request/response body в атрибуты spans. Добавлять диск безопаснее до достижения 90% заполнения.
Какой VPS-конфиг минимально подойдёт?
Для тестов, домашней лаборатории или staging достаточно 2 vCPU, 4 GB RAM, 40 GB NVMe и 100 Mbps. Такой сервер подходит для нескольких приложений при sampling 5–20% и коротком retention. Для production лучше взять 4 vCPU, 8 GB RAM и 100–160 GB NVMe: будет запас под очереди Collector, кэш, обновления и рост объёма данных. Если диск медленный HDD, tracing backend может тормозить даже при достаточном количестве CPU.
Что выбрать — VPS или dedicated для этой задачи?
Начинайте с VPS, если это один проект, несколько сервисов и retention до нескольких недель. VPS проще масштабировать по CPU, RAM и диску, а OpenTelemetry Collector хорошо работает в такой среде. Dedicated нужен при устойчиво высокой записи spans, жёстких требованиях к IOPS, нескольких NVMe в RAID, длинном хранении и тяжёлом backend’е вместо локального Badger. Переход оправдан измерениями: загрузкой CPU, memory pressure, latency диска и скоростью заполнения хранилища.
Можно ли отправлять в Collector персональные данные, JWT или SQL-параметры?
Нет, по умолчанию не стоит. Атрибуты trace часто доступны разработчикам, попадают в бэкапы и могут храниться неделями. Исключите заголовки Authorization, cookies, пароли, токены, полные тела запросов, адреса электронной почты и платёжные данные. В SDK используйте allowlist атрибутов, а в Collector добавьте processors для удаления чувствительных ключей. Для SQL лучше записывать тип операции и имя таблицы, а не полный запрос с параметрами пользователя.
Почему trace состоит из одного span, хотя запрос проходит через несколько сервисов?
Обычно причина в потере контекста между сервисами. Проверьте, что HTTP-клиент автоматически передаёт заголовки W3C Trace Context: traceparent и tracestate. Для очередей убедитесь, что producer записывает context в message headers, а consumer извлекает его до создания дочернего span. Все сервисы должны использовать совместимый propagator, обычно tracecontext. Также проверьте, что разные сервисы не генерируют новый root trace из-за отключенного auto-instrumentation.
Выводы и следующие шаги
Теперь у вас есть self-hosted стек distributed tracing: приложения отправляют OTLP-трейсы в OpenTelemetry Collector, Collector передаёт их в Jaeger, а Jaeger UI доступен по HTTPS с базовой защитой. Конфигурация подходит для первого production-использования при умеренной нагрузке и даёт контроль над данными, retention и инфраструктурой.
- Инструментируйте основные HTTP-входы, исходящие HTTP-вызовы, SQL, Redis и очереди; начните с одного критического пользовательского сценария.
- Добавьте sampling по правилам: сохраняйте все ошибки и медленные запросы, а успешные типовые запросы записывайте с пониженной вероятностью.
- При росте нагрузки разделите Collector и Jaeger по разным серверам, вынесите storage в production backend и настройте мониторинг CPU, RAM, диска, queue size и скорости ingestion.
Регулярно проверяйте восстановление бэкапов, свободное место и актуальность образов. Трейсинг приносит пользу только тогда, когда нужный trace можно быстро найти именно в момент инцидента.