nginx + grafana
Este sitio web, junto con algún otro, se muestran a la 🙌 World Wide Web 🙌 a traves de un servidor Nginx
Este nginx, funciona como proxy inverso, redirigiendo todo el trafico entrante a su destino correspondiente, además de gestionar los certificados para el https.
Los logs que se generan en nginx, son (como todos los logs) infumables a primera vista, así que se me ocurrió hacer un dashboard en grafana para hacerlos un poco mas interesantes.
Stack#
El conjunto de tecnologías que uso consiste en:
Nginx -> Alloy -> Loki -> Grafana
Primero hace falta saber que es lo que queremos exactamente, en este caso lo que quiero es que los logs de nginx, sean mas _leíbles? por otros elementos, supongo que por convenio hay que pasarlos a JSON. Para hacerlo, se puede definir el formato de estos logs dentro de la configuración de Nginx ⤵️
Dentro del archivo nginx.conf:
# Este fragmento permite cambiar los logs feos, a unos JSON bonitos
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Define el formato de log en JSON UNA SOLA VEZ
log_format json_combined escape=json
'{'
'"time_local":"$time_local",'
'"remote_addr":"$remote_addr",'
'"request_method":"$request_method",'
'"request_uri":"$request_uri",'
'"server_protocol":"$server_protocol",'
'"status": "$status",'
'"body_bytes_sent":"$body_bytes_sent",'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for",'
'"server_name":"$server_name",'
'"request_time":"$request_time",'
'"upstream_addr":"$upstream_addr",'
'"upstream_status":"$upstream_status",'
'"upstream_response_time":"$upstream_response_time"'
'}';
access_log /var/log/nginx/access.log json_combined;
}
Ahora la definición de este contenedor Nginx, dentro de un docker-compose.yaml, podría ser algo así:
nginx:
image: nginx:latest
container_name: nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- nginx-logs:/var/log/nginx
depends_on:
- certbot
# Este comando, es lo que me ha servido para que nginx genere logs en archivos existentes, no en stdout
command: /bin/sh -c "rm -f /var/log/nginx/access.log /var/log/nginx/error.log && exec nginx -g 'daemon off;'"
restart: unless-stopped
El siguiente eslabón en la cadena imaginaria de la monitorización, es Alloy, es el componente desarrollado por Grafana para consumir estos logs y mandarlos a otro sitio con el ajuste que se necesite:
# La parte comentada, sirve para levantar una webui, para hacer debug si es necesario
alloy:
image: grafana/alloy:v1.9.2
container_name: alloy
volumes:
- ./alloy/config.alloy:/etc/alloy/config.alloy:ro
- nginx-logs:/var/log/nginx:ro
#ports:
# - 12345
command: run /etc/alloy/config.alloy #--server.http.listen-addr=0.0.0.0:12345
depends_on:
- loki
restart: unless-stopped
Esto ha sido un poco doloroso la verdad, esta configuración hace que todo funcione como debería, pero no acabo de entender por que:
loki.source.file "nginx_logs" {
targets = [{"__path__" = "/var/log/nginx/access.log"}]
forward_to = [loki.process.nginx.receiver]
}
loki.process "nginx" {
stage.json {
expressions = {
"server_name" = "server_name",
"status_code" = "status",
}
}
stage.labels {
values = {
"server_name" = "",
"status_code" = "",
}
}
forward_to = [loki.write.default.receiver]
}
loki.write "default" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
external_labels = {
job = "nginx",
}
}
Next Stop: Loki
Esto actúa como pilar fundamental entre todos los otros componentes, permitiendo a Grafana leer lo que manda Alloy.
loki:
image: grafana/loki:3.4.4
container_name: loki
ports:
- "3100:3100"
command: -config.file=/etc/loki/config.yaml
volumes:
- ./loki/loki-config.yaml:/etc/loki/config.yaml:ro
- loki-data:/loki/data
Con su respectiva configuracion:
auth_enabled: false
server:
http_listen_port: 3100
common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
ruler:
alertmanager_url: http://localhost:9093
Por ultimo, Grafana, esta parte no tiene nada de complejidad comparada con las anteriores, su contenedor con su volumen y poco mas:
grafana:
image: grafana/grafana:12.0.2
container_name: grafana
ports:
- 3000
depends_on:
- loki
volumes:
- grafana-data:/var/lib/grafana
Una vez estén todos los ficheros en su sitio, construimos todo con un:
docker compose up -d
Y si todo a ido bien, los contenedores pasaran a estar up. 😎
Grafana#
Ahora ya esta todo listo, falta crear un datasource de Loki que apunte a la URL del contenedor (http://loki:3100) y con eso ya se puede empezar a construir un dashboard a medida.
Por ejemplo esta query para devolver el total de peticiones agrupando por código de estado, todo junto en un grafico tipo tarta:
sum by(status_code) (count_over_time({job="nginx"}[$__range]))
O también esta otra para ver el numero total de peticiones por minuto:
sum(rate({job="nginx"}[1m]))
Otro que me ha parecido interesante es un ‘heatmap’ que muestra el ‘calor’ que genera una IP en función de sus peticiones al servidor:
sum by (remote_addr) (count_over_time({job="nginx"} | json [$__interval]))