Monitorizar logs con Docker. Grafana + Loki + Promtail

Tiempo de lectura: 8 minutos

Hola, hoy os voy a enseñar cómo podéis monitorizar los logs del sistema o de otros contenedores o incluso de ficheros .txt/.log etc con Grafana + Loki + Promtail. Además, lo vamos a juntar con el contenedor de monitorización creado en el tutorial anterior (https://devcodelight.com/?p=3847).

Para empezar, vamos a crear un docker-compose.yml con la siguiente estructura:

version: "3.1"

services:
  grafana:
    image: grafana/grafana
    container_name: grafana
    restart: unless-stopped
    volumes:
      - ./config/grafana/data:/var/lib/grafana
    ports:
      - 3000:3000
    networks:
      - docker-network

  prometheus:
    image: prom/prometheus
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - ./config/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - ./config/prometheus/data:/prometheus
    ports:
      - 9090:9090
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=1y'
      - '--web.enable-lifecycle'
    networks:
      - docker-network
  node_exporter:
    image: quay.io/prometheus/node-exporter:latest
    container_name: node_exporter
    restart: unless-stopped
    ports:
      - 9100:9100
      ##### linux
    command:
      - '--path.rootfs=/host'
    pid: host
    volumes:
      - './config/node_exporter:/host:ro,rslave'
    networks:
      - docker-network
      ###### windows
      #volumes:
      #- /proc:/host/proc:ro
      #- /sys:/host/sys:ro
      #command:
      #- '--path.procfs=/host/proc'
      #- '--path.sysfs=/host/sys'
      #- --collector.filesystem.ignored-mount-points
      #- "^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)"

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.47.0
    container_name: cadvisor
    restart: unless-stopped
    expose:
      - 8080
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro

    networks:
      - docker-network

  loki:
    image: grafana/loki:2.0.0
    container_name: loki
    restart: unless-stopped
    volumes:
      - ./config/loki/loki-config.yml:/mnt/config/loki-config.yml
      - ./config/loki/data:/loki
    ports:
      - 3100:3100
    command:
      - '-config.file=/mnt/config/loki-config.yml'
    networks:
      - docker-network

  promtail:
    image: grafana/promtail:2.0.0
    container_name: promtail
    restart: unless-stopped
    volumes:
      - ./config/promtail/promtail-config.yml:/mnt/config/promtail-config.yml
      - ./config/promtail/data:/promtail
      - /var/log:/var/log:ro
      - ./config/promtail/tmp:/tmp
      - ./web/logs/:/web:ro
    #ports:
      #- 9080:9080
    command:
      - '-config.file=/mnt/config/promtail-config.yml'
    networks:
      - docker-network

networks:
  docker-network:
    driver: bridge
    external: true

Ahora voy a explicar los contenedores creados:

Primero hemos creado un contenedor de grafana:

grafana:
    image: grafana/grafana
    container_name: grafana
    restart: unless-stopped
    volumes:
      - ./config/grafana/data:/var/lib/grafana
    ports:
      - 3000:3000
    networks:
      - docker-network

Utilizamos la imagen grafana/grafana (https://hub.docker.com/r/grafana/grafana).

Nombre de contenedor grafana

Creamos un volumen dentro de la carpeta config/grafana/data para que almacene los datos de configuración de forma persistente.

El puerto que utiliza grafana es el 3000.

Y unimos a una red que vamos a crear en común a todos los contenedores docker-network.

Contenedor prometheus:

prometheus:
    image: prom/prometheus
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - ./config/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - ./config/prometheus/data:/prometheus
    ports:
      - 9090:9090
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=1y'
      - '--web.enable-lifecycle'
    networks:
      - docker-network

Creamos la imagen a partir de prometheus docker (https://hub.docker.com/r/prom/prometheus)

El nombre de contenedor es prometheus (se utilizará más adelante en el panel de control de grafana).

Creamos dos volúmenes:

  • config/prometheus/prometheus.yml (archivo de configuración de prometheus:
global:
  scrape_interval:     15s 
  evaluation_interval: 15s 

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
    - targets: ['prometheus:9090']
    
  - job_name: 'cadvisor'
    static_configs:
    - targets: ['cadvisor:8080']

  - job_name: 'node_exporter'
    static_configs:
    - targets: ['node_exporter:9100']  

En este archivo, incluimos cadvisor y node_exporter (contenedores que explicaré más adelante) en los targets incluimos el nombre del contenedor, ya que el DNS de docker es capaz de resolver estos dominios. Además, apuntamos al puerto de cada servicio.

  • /config/prometheus/data (directorio persistente de datos de configuración de prometheus).

Contenedor de Node exporter

node_exporter:
    image: quay.io/prometheus/node-exporter:latest
    container_name: node_exporter
    restart: unless-stopped
    ports:
      - 9100:9100
      ##### linux
    command:
      - '--path.rootfs=/host'
    pid: host
    volumes:
      - './config/node_exporter:/host:ro,rslave'

Utilizamos la imagen de quay.io/prometheus/node-exporter:latest (https://quay.io/repository/prometheus/node-exporter?tab=tags&tag=latest)

Creamos un volumen para almacenar la configuración del contenedor en la carpeta ./config/node_exporter

Contenedor Cadvisor:

cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.47.0
    container_name: cadvisor
    restart: unless-stopped
    expose:
      - 8080
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro

    networks:
      - docker-network

Utilizamos la imagen cadvisor de google: (https://github.com/google/cadvisor/releases)

Apunta a los volúmenes dónde va a monitorizar los contenedores docker.

Ahora ya comenzamos con la monitorización de logs.

Para ello añadimos el contenedor loki:

Loki es un sistema de registro de logs de código abierto diseñado para su uso con contenedores de Docker y orquestadores de contenedores como Kubernetes. Loki fue creado por Grafana Labs y se enfoca en proporcionar un almacenamiento eficiente y escalable de logs para el análisis y la visualización.

Lo que diferencia a Loki de otros sistemas de registro de logs es que utiliza una arquitectura de almacenamiento de logs inspirada en Prometheus, donde los registros se almacenan como series de tiempo comprimidas. Esto hace que Loki sea muy escalable y eficiente en términos de almacenamiento de logs, especialmente en entornos con un alto volumen de logs.

Además, Loki se integra con Grafana, lo que permite a los usuarios visualizar y analizar los logs en tiempo real. La integración con Grafana también permite a los usuarios realizar consultas y crear paneles de datos para visualizar y analizar los logs almacenados en Loki.

En resumen, Loki es una herramienta de registro de logs escalable, eficiente y de código abierto diseñada para su uso en entornos de contenedores y orquestadores de contenedores.

El contenedor Loki creado queda de la siguiente forma:

loki:
    image: grafana/loki:2.0.0
    container_name: loki
    restart: unless-stopped
    volumes:
      - ./config/loki/loki-config.yml:/mnt/config/loki-config.yml
      - ./config/loki/data:/loki
    ports:
      - 3100:3100
    command:
      - '-config.file=/mnt/config/loki-config.yml'
    networks:
      - docker-network

Utilizamos la imagen grafana/loki (https://hub.docker.com/r/grafana/loki)

El nombre del contenedor es loki (lo utilizaremos más adelante para indicar el endpoint de los logs)

Creamos dos volúmenes:

  • Uno para la configuración loki-config.yml
auth_enabled: false

server:
  http_listen_port: 3100

ingester:
  lifecycler:
    address: 127.0.0.1
    ring:
      kvstore:
        store: inmemory
      replication_factor: 1
    final_sleep: 0s
  chunk_idle_period: 1h       # Any chunk not receiving new logs in this time will be flushed
  max_chunk_age: 1h           # All chunks will be flushed when they hit this age, default is 1h
  chunk_target_size: 1048576  # Loki will attempt to build chunks up to 1.5MB, flushing first if chunk_idle_period or max_chunk_age is reached first
  chunk_retain_period: 30s    # Must be greater than index read cache TTL if using an index cache (Default index read cache TTL is 5m)
  max_transfer_retries: 0     # Chunk transfers disabled

schema_config:
  configs:
    - from: 2020-10-24
      store: boltdb-shipper
      object_store: filesystem
      schema: v11
      index:
        prefix: index_
        period: 24h

storage_config:
  boltdb_shipper:
    active_index_directory: /loki/boltdb-shipper-active
    cache_location: /loki/boltdb-shipper-cache
    cache_ttl: 24h         # Can be increased for faster performance over longer query periods, uses more disk space
    shared_store: filesystem
  filesystem:
    directory: /loki/chunks

compactor:
  working_directory: /loki/boltdb-shipper-compactor
  shared_store: filesystem

limits_config:
  reject_old_samples: true
  reject_old_samples_max_age: 168h

chunk_store_config:
  max_look_back_period: 0s

table_manager:
  retention_deletes_enabled: true
  retention_period: 1460h

ruler:
  storage:
    type: local
    local:
      directory: /loki/rules
  rule_path: /loki/rules-temp
  alertmanager_url: http://localhost:9093
  ring:
    kvstore:
      store: inmemory
  enable_api: true

Utilizamos la configuración por defecto, en este caso además hemos añadido que los datos de logs se borren cada 2 meses (1460h).

  • Directorio data para la configuración persistente de loki.

La url de loki es http://loki:3100 y se utilizará en adelante.

Ahora se configura promtail:

Promtail es un componente de registro de logs de código abierto que forma parte del ecosistema de herramientas de Prometheus, diseñado para recolectar logs de varios sistemas y enviarlos a un servidor Prometheus para su análisis y monitoreo.

Promtail es altamente escalable y está diseñado para manejar grandes volúmenes de logs de manera eficiente. Utiliza una arquitectura de cliente-servidor en la que se ejecuta un agente Promtail en cada host para recolectar los logs y enviarlos a un servidor centralizado.

Promtail es altamente configurable y puede utilizarse para recolectar logs de varios formatos, incluyendo JSON, syslog, y logs de contenedores de Docker. También puede utilizarse para extraer métricas y etiquetas de los logs, lo que facilita el análisis y la visualización en Prometheus y Grafana.

En resumen, Promtail es una herramienta de recolección de logs de alta escalabilidad, diseñada para trabajar en conjunto con el sistema de monitoreo y alerta de Prometheus, y está diseñada para recolectar logs de diferentes sistemas y formatos.

Utilizamos la siguiente configuración de contenedor:

promtail:
    image: grafana/promtail:2.0.0
    container_name: promtail
    restart: unless-stopped
    volumes:
      - ./config/promtail/promtail-config.yml:/mnt/config/promtail-config.yml
      - ./config/promtail/data:/promtail
      - /var/log:/var/log:ro
      - ./config/promtail/tmp:/tmp
      - ./web/logs/:/web:ro
    #ports:
      #- 9080:9080
    command:
      - '-config.file=/mnt/config/promtail-config.yml'
    networks:
      - docker-network

Utilizamos la imagen oficial de promtail: (https://hub.docker.com/r/grafana/promtail)

Asignamos de nombre de contenedor: promtail (servirá de dominio dentro de la red docker-network)

Creamos distintos volúmenes:

  • Uno para almacenar los datos de promtail de forma persistente.
  • Otro para obtener los logs de la carpeta /var/log
  • Otro para almacenar los datos temporales de promtail.
  • Y otro para obtener los logs .txt de un servidor php que he creado de ejemplo y que se almacenan en la carpeta /web/logs con formato fecha.txt
  • Uno para guardar el promtail-config.yml.
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
# Logs sistema
- job_name: system
  static_configs:
  - targets:
      - localhost
    labels:
      job: varlogs
      __path__: /var/log/*log

# Logs de la web:
- job_name: logs_web
  static_configs:
  - targets:
      - localhost
    labels:
      job: web
      __path__: /web/*.txt

Ahora voy a explicar este código:

server:
http_listen_port: 9080
grpc_listen_port: 0

El servidor escucha sobre el puerto 9080 (en docker lo he comentado para tenerlo de referencia, pero no es necesario ni recomendado exponerlo de forma pública)

Archivo temporal para almacenar las posiciones de los logs y de esta forma reconocer los datos nuevos:

positions:
filename: /tmp/positions.yml

URL del cliente loki:

clients:

  • url: http://loki:3100/loki/api/v1/push

Esta URL se compone del nombre del contenedor (en este caso loki) y el puerto en el que opera 3100, y es el endpoint dónde enviará las notificaciones de los Logs.

Creamos un job para obtener los logs generales del sistema:

scrape_configs:
# Logs sistema
- job_name: system
  static_configs:
  - targets:
      - localhost
    labels:
      job: varlogs
      __path__: /var/log/*log

Y creamos un job para obtener los logs de nuestra web:

# Logs de la web:
- job_name: logs_web
  static_configs:
  - targets:
      - localhost
    labels:
      job: web
      __path__: /web/*.txt

Este job se llama web (hay que tener en cuenta el nombre del label).

Además, indicamos el path del log externo que se comunica con el docker creado.

En este caso indicamos que tenga en cuenta todos los archivos .txt dentro del directorio web.

Una vez configurado todo, tendremos que crear la red docker-network:

docker network create docker-network

Si necesitas instalar Docker puedes seguir este tutorial: https://devcodelight.com/desplegar-servidor-web-apache-con-docker-compose/

También podemos activar el plugin de docker para enviar todos los logs de nuestros contenedores a loki.

docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions

Si tenemos ARM64 el plugin fallará, tendremos que seguir estos pasos: https://devcodelight.com/instalar-plugin-loki-para-docker-en-arm64/

Para utilizar este plugin, tendremos que añadir esta configuración a nuestro contenedor:

logging:
  driver: loki
  options:
    loki-url: "http://loki:3100/loki/api/v1/push"
    loki-retries: "1"
    loki-batch-size: "102400"
    loki-external-labels: "container_name={{.Name}}"

Una vez ejecutado todo el conjunto de docker-compose.yml podremos acceder a la página de grafana.

Tendremos que crear un nombre de usuario y contraseña.

Por defecto son:

Usuario: admin

Contraseña: admin

Una vez dentro, vamos a configurar los logs, para ello vamos a ajustes:

Y seleccionamos, configuración > orígenes de datos (o data source)

Buscamos loki y lo añadimos indicando como url la siguiente: http://loki:3100

Ahora ya podemos obtener los logs. Para ello vamos a explorar:

Seleccionamos Loki:

Y buscamos el label que queremos consultar.

Aparecen dos tipos, filename o job. Si seleccionamos Job, en select value aparecerán los nombres de los jobs que hemos creado anteriormente. Cuando tengamos elegido el que queremos, pulsamos en Run Query

Y ya podemos ver los logs creados:

Si elegimos filename, podremos seleccionar cualquier log.

Deja un comentario