Elastic APM Server в Kubernetes
Published: 2026-02-14
APM Server — точка приёма трассировок, метрик и ошибок Elastic APM. Он получает данные от агентов приложений (Go, Java, Node.js, Python) и пересылает их в Elasticsearch. Кроме того, принимает spans по OTLP/gRPC — именно так подключается мост OpenTelemetry Collector.
Архитектура
Поток трассировок в нашем стеке:
Приложение (OTEL SDK) → otel-collector → APM Server → Elasticsearch → Kibana APM
APM Server принимает данные по двум протоколам: от нативных агентов Elastic APM (порт 8200) и по OTLP/gRPC (тот же порт 8200). otel-collector в кластере агрегирует трассировки из нескольких приложений и пересылает их в APM Server, который складывает данные в индексы apm-* в Elasticsearch.
HelmRelease
Чарт apm-server входит в Helm-репозиторий elastic. Оператор не нужен — только Deployment и Service:
yamlapiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: apm-server
namespace: observability
spec:
chart:
spec:
chart: apm-server
version: "7.17.*"
sourceRef:
kind: HelmRepository
name: elastic
namespace: flux-system
retries: -1
values:
replicaCount: 1
config:
apm-server:
host: "0.0.0.0:8200"
rum:
enabled: true
allow_origins: ["*"]
kibana:
enabled: true
host: "http://kibana.observability.svc.cluster.local:5601"
output.elasticsearch:
hosts:
- "http://elasticsearch-master.observability.svc.cluster.local:9200"
username: "${APM_ES_USER}"
password: "${APM_ES_PASS}"
secretMounts:
- name: apm-credentials
secretName: apm-es-credentials
path: /usr/share/apm-server/config/credentials
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
retries: -1 — как и всем сервисам вокруг Elasticsearch, APM Server нужен готовый ES-кластер, поэтому для HelmRelease включены неограниченные повторы. Без этого, если APM Server развернётся раньше, чем Elasticsearch будет готов, HelmRelease упадёт и повторных попыток не будет.
rum.enabled: true включает Real User Monitoring: браузерный JavaScript отправляет трассировки напрямую в APM Server. allow_origins: ["*"] удобно для разработки, но в продакшене список стоит ограничить реальным доменом.
Учётные данные Elasticsearch через ESO
yamlapiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: apm-es-credentials
namespace: observability
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: apm-es-credentials
data:
- secretKey: APM_ES_USER
remoteRef:
key: secret/dev/elasticsearch
property: apm_user
- secretKey: APM_ES_PASS
remoteRef:
key: secret/dev/elasticsearch
property: apm_password
Сохраните учётные данные в Vault:
bashvault kv put secret/dev/elasticsearch \
apm_user=apm_writer \
apm_password=your-secure-password
Создайте выделенного пользователя Elasticsearch только с нужными правами:
bashPUT /_security/role/apm_writer
{
"indices": [
{
"names": ["apm-*"],
"privileges": ["write", "create_index", "manage"]
}
]
}
PUT /_security/user/apm_writer
{
"password": "your-secure-password",
"roles": ["apm_writer"]
}
Приём OTLP
APM Server 7.17 поддерживает OTLP по gRPC на порту 8200:
yamlapm-server:
otlp:
grpc:
enabled: true
host: "0.0.0.0:8200"
otel-collector в кластере отправляет spans сюда:
yamlexporters:
otlp/apm:
endpoint: apm-server.observability.svc.cluster.local:8200
tls:
insecure: true
Конфигурация пайплайна otel-collector
yamlreceivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 5s
send_batch_size: 1000
memory_limiter:
limit_mib: 256
exporters:
otlp/apm:
endpoint: apm-server.observability.svc.cluster.local:8200
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp/apm]
Процессор batch важен: без него каждый span уходит отдельным gRPC-вызовом, что при большом объёме трассировок крайне неэффективно.
Ingest pipeline для APM-индексов
APM создаёт собственные шаблоны индексов (apm-*). Для однонодовых кластеров без реплик:
bashcurl -X PUT "http://elasticsearch:9200/_template/apm-server" \
-H "Content-Type: application/json" \
-d '{
"index_patterns": ["apm-*"],
"settings": {
"number_of_replicas": 0
}
}'
Без этого Elasticsearch создаст индексы apm-* с одной репликой, и на однонодовом кластере они зависнут в статусе YELLOW.
ILM-политика для APM-индексов
bashPUT /_ilm/policy/apm-rollover
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "5gb",
"max_age": "7d"
}
}
},
"delete": {
"min_age": "30d",
"actions": {
"delete": {}
}
}
}
}
}
Проверка поступления spans
bash# port-forward к APM server
kubectl port-forward -n observability svc/apm-server 8200:8200
# health check — должен вернуть {"ok":true}
curl http://localhost:8200/
# поиск индексов spans в ES
curl http://localhost:9200/_cat/indices/apm-*?v
Трассировки отображаются в Kibana, раздел APM. Для каждого сервиса доступны карта сервисов, гистограмма задержек и частота ошибок.
APM Server не получает spans
Частые причины:
- Connection refused — под APM Server не запущен, проверьте
kubectl get pods -n observability - Auth error — неверные учётные данные ES, проверьте
kubectl logs -n observability deploy/apm-server | grep "authentication" - Несовпадение порта OTLP — otel-collector отправляет данные не на тот эндпоинт
- Нет шаблона индекса — ES отклоняет записи, потому что реплик больше, чем доступных узлов
bashkubectl logs -n observability deploy/apm-server --tail=50
kubectl exec -n observability deploy/apm-server -- \
curl -s http://elasticsearch-master.observability.svc.cluster.local:9200/_cluster/health