Розгортання 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 з доступом до Jaeger UI через HTTPS. Застосунки зможуть надсилати 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 можна швидко знайти саме в момент інциденту.