30. 09. 2026 Paolo Seghetti APM, NetEye, Real User Experience

O11y of container : a real use case

The use of containers has now become part of everyday activities: in many cases we use them as software appliances or black-boxes. Sometimes, however, it happens that these black-boxes do not work as we expect or we would like to be able to evaluate their performance as the load increases.


Today we are going to talk about a real use case, the data collection of one or more Gotenberg containers. Gotenberg is an API that allows you to convert and manipulate documents in PDF format in a way that is easy for developers. It receives files via http (multipart/form-data) requests and returns the processed document without requiring you to manually configure complex dependencies on your server.


One of our customers has been using it for about a year having integrated it into their PHP-based application and as often happens, after an initial testing phase we encountered some performance problems.


Fortunately, in one of the latest releases, the function of exporting performance data via telemetry was implemented and we were able to integrate this data into the APM component of NetEye together with the PHP application data.


Anticipating an increase in load, the customer asked us to implement a new solution consisting of a reverse proxy that would balance the load between three Gotenberg containers. To these we added a container with the function of local otel collector that will take care of forwarding the collected data to the Neteye platform.

The gotenberg container configuration:


x-gotenberg-base: &gotenberg-base
build:
context: ./gotenberg
dockerfile: Dockerfile
image: ${GOTENBERG_IMAGE:-gotenberg:custom}
environment:
- GOTENBERG_CHROMIUM_AUTO_START=true
- GOTENBERG_CHROMIUM_RESTART_AFTER=0

# OTEL CONFIGURATION
    - OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
    - OTEL_TRACES_EXPORTER=otlp
    - OTEL_METRICS_EXPORTER=otlp
    - OTEL_LOGS_EXPORTER=otlp
    - OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_COLLECTOR_OTLP_HTTP_ENDPOINT:-http://otel-collector:4318}
    - OTEL_EXPORTER_OTLP_INSECURE=true
    - OTEL_RESOURCE_ATTRIBUTES=service.name=gotenberg,deployment.environment=staging
    - OTEL_TRACES_SAMPLER=always_on
 
networks:
    - gotenberg_net
  restart: unless-stopped

services:
  gotenberg:
    <<: *gotenberg-base
    # no ports: instances are reachable only via the internal network
    health-check:
      test: ["CMD-SHELL", "curl --fail http://127.0.0.1:3000/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

networks:
  gotenberg_net:
    external: true

The configuration shown allows us to maintain a single configuration regardless of the number of gotenberg containers we will run. The health-check function will be used by Neteye to verify the operation of the different containers.

The OTEL-COLLECTOR

The OTEL-COLLECTOR is also containerized. The configuration extracts the crucial information from the .env file present in the root of the project. Here is an example:

.env file

# Common values shared by compose files
PROJECT_NAME=gotenberg
OTEL_COLLECTOR_HOST=otel-collector
OTEL_COLLECTOR_OTLP_HTTP_ENDPOINT=http://otel-collector:4318
GOTENBERG_IMAGE=gotenberg:custom
APM_ENDPOINT=https://myapmendpoint:8200
APM_TOKEN=mysupersecrettoken

The compose.yml

services:

  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest 
    container_name: otel-collector

    ports:
      - "4317:4317"
      - "4318:4318"

    volumes:
      - ./otel/collector-config.yaml:/etc/otel/config.yaml:ro

    environment:
      APM_ENDPOINT: "${APM_ENDPOINT:?APM_ENDPOINT is required}"
      APM_TOKEN: "${APM_TOKEN:?APM_TOKEN is required}"



    command: ["--config", "/etc/otel/config.yaml"]

    networks:
      - gotenberg_net

    restart: unless-stopped

networks:
  gotenberg_net:
    external: true

The collector-config.yml

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: "0.0.0.0:4317"
      http:
        endpoint: "0.0.0.0:4318"

processors:
  batch:
  memory_limiter:
    check_interval: 5s
    limit_mib: 400
    spike_limit_mib: 64

exporters:
  otlp_http/elastic:
    endpoint: "${APM_ENDPOINT}"
    headers:
      Authorization: "Bearer ${APM_TOKEN}"
    tls:
      insecure_skip_verify: true
  debug:

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp_http/elastic, debug]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp_http/elastic, debug]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp_http/elastic, debug]


Go!

Run the container , try a test print and verify the data sent by the collector on the Elastic APM component.

Final Notes

That’s it.

As you can see, this makes it very easy to monitor the inner workings of a third-party container.

Write to us whether you have any doubts or are just curious.

These Solutions are Engineered by Humans

Did you find this article interesting? Does it match your skill set? Our customers often present us with problems that need customized solutions. In fact, we’re currently hiring for roles like this here at Würth IT Italy.

Paolo Seghetti

Paolo Seghetti

Author

Paolo Seghetti

Leave a Reply

Your email address will not be published. Required fields are marked *

Archive