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

Частые причины:

  1. Connection refused — под APM Server не запущен, проверьте kubectl get pods -n observability
  2. Auth error — неверные учётные данные ES, проверьте kubectl logs -n observability deploy/apm-server | grep "authentication"
  3. Несовпадение порта OTLP — otel-collector отправляет данные не на тот эндпоинт
  4. Нет шаблона индекса — 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