Logging
Pod logs (kubectl)
Every NDIP service on mickey and speedy logs to stdout/stderr. The quickest way to read logs is kubectl (or the
Rancher UI):
kubectl -n ndip logs deploy/galaxy -c galaxy --since=1h # Galaxy (gunicorn, Celery, job handlers)
kubectl -n ndip logs deploy/galaxy -c gx-it-proxy # interactive tools proxy
kubectl -n ndip logs deploy/galaxy -c galaxy --previous # the container before its last restart (after an OOM kill)
kubectl -n ndip logs deploy/galaxy -c tools-sync # tool clone at pod start
kubectl -n ndip logs deploy/rucio-server -c rucio-server
kubectl -n ndip logs galaxy-rabbitmq-0
kubectl -n ndip logs galaxy-pg-1 # Postgres (JSON lines, from CNPG)
kubectl -n ndip logs deploy/rdb-galaxy
kubectl logs only covers the current and the previous container. After a pod is deleted or rescheduled, its logs are
gone from the node.
Useful Galaxy greps:
Mapped job to destination id: where the job router sent a job;cannot get destination status/no resources available: jobs deferred by the router (Prometheus unreachable, or no healthy host with free resources).
Rancher Logging and Loki
Both clusters run Rancher Logging, which ships all pod logs to Loki in namespace cattle-monitoring-system. Current
limitations:
- Grafana has no Loki data source yet, so the logs can't be browsed there (Rancher's Grafana only has a dashboard
sidecar, so the data source has to be added in the
rancher-monitoringapp values,grafana.additionalDataSources). - mickey's Loki is down.
Loki holds history that has already rotated off the nodes (and is older than ELK on prod). Query it with a port-forward, for example to save Galaxy's log for a time window:
kubectl -n cattle-monitoring-system port-forward svc/loki 3100 &
curl -sG localhost:3100/loki/api/v1/query_range \
--data-urlencode 'query={namespace="ndip",container="galaxy"}' \
--data-urlencode 'start=2026-10-07T03:14:00Z' --data-urlencode 'end=2026-10-07T03:20:00Z' \
--data-urlencode 'limit=5000' --data-urlencode 'direction=forward' | jq -r '.data.result[].values[][1]'
Each line is a JSON record; the log text is in its message field. Results are capped at 5000 lines per request,
so page through longer windows by moving start past the last timestamp.
ELK
calvera's k8s/elk/ deploys Elasticsearch, Kibana and Filebeat into namespace ndip on both clusters. Deploy with
CI job elk-test (mickey) or elk-k8s-prod (speedy); k8s/deploy-all.sh also runs it in both environments.
| test (mickey) | prod (speedy) | |
|---|---|---|
| Elasticsearch storage | hostPath PV on mickey-wn01 (elasticsearch-pv.yaml, class manual) | 50Gi Longhorn volume, no node pin |
| Values | elasticsearch-values.yaml + elasticsearch-values-test.yaml | elasticsearch-values.yaml + elasticsearch-values-prod.yaml |
| Kibana | https://ndip-test.ornl.gov/logs, via the Galaxy chart's kibana Ingress (no auth) | https://ndip.ornl.gov/logs, kibana-ingress.yaml, basic auth (user ndip) |
- Filebeat (
filebeat-k8s.yaml, imageelastic/filebeat:8.12.1, same version as Elasticsearch and Kibana) is a DaemonSet on the worker nodes. It tails the containers of every pod in namespacendipand writes to daily data streamsndip-k8s-YYYY.MM.dd, taggedservice.name: k8sandenvironment: test|prod, with the Kubernetes fields (kubernetes.namespace,kubernetes.pod.name,kubernetes.container.name). Pulsar hosts ship their own logs with thefilebeat pulsarAnsible job (ndip-pulsar-*); they still point at mickey's Elasticsearch. - Retention:
es-retention-cronjob.yamldeletesndip-*data streams older than 30 days, daily at 03:00. - Kibana password (prod): set the masked CI variable
KIBANA_PASSWORDand runelk-k8s-prod; the job hashes it into Secretkibana-basic-auth. Without the variable the existing Secret is kept. - Elasticsearch is also exposed on NodePort
31750(no auth, internal network) for the Pulsar-host Filebeats. - The files in
k8s/elk/are rendered per environment byk8s/lib/env.sh(@NDIP_HOST@,@ENV_NAME@). - Quota: ELK adds about 3.8Gi of memory limits on speedy, where the
ndipquota is 32Gi. - Unauthenticated routes: Kibana's Service is a NodePort (
31550) and Elasticsearch's too (31750). Both are reachable without the password from the internal network. Test's/logshas no auth.
Kibana views
The ELK deploy imports a standard set of data views and saved searches from k8s/elk/kibana-objects.ndjson
(Kibana saved objects API, overwrite=true). They have fixed IDs, so every deploy restores them without touching
objects people made by hand. The deploy sets "NDIP pods (k8s)" as the default data view only if none is set, and
sets Kibana's defaultColumns to kubernetes.container.name and message, so Discover shows timestamp, source and
message instead of the whole document. The saved searches use the same columns (host.name and message for
Pulsar). Add any other field from the field list on the left when you need it.
| data views | saved searches (Discover → Open) |
|---|---|
NDIP pods (k8s) ndip-k8s-*, Pulsar hosts ndip-pulsar-*, Galaxy VM (legacy) ndip-galaxy-* | Galaxy, Galaxy errors, Galaxy job routing, Rucio server, RabbitMQ, Postgres (CNPG), RDB, All pods: errors, Pulsar, Pulsar errors |
To change them: edit them in Kibana, export them (Stack Management → Saved objects → Export), and replace the
matching lines in kibana-objects.ndjson, or edit the file directly. To load them without a full deploy:
kubectl -n ndip exec -i deploy/ndip-kibana -- curl -s -X POST 'localhost:5601/logs/api/saved_objects/_import?overwrite=true' \
-H 'kbn-xsrf: true' -F 'file=@-;filename=kibana-objects.ndjson' < k8s/elk/kibana-objects.ndjson
Troubleshooting
- Nothing arrives, Filebeat logs
Cannot index event (status=400): dropping event!: Elasticsearch is rejecting every event, almost always a field whose type clashes with the index template. Filebeat loads its ECS template forndip-k8s-*, so custom fields must fit it:serviceis an object (useservice.name, notservice). Check the mapping withGET ndip-k8s-*/_mapping/field/<field>. Rejected events are dropped, not retried. - Count documents and see what's shipping:
kubectl -n ndip exec ndip-elasticsearch-master-0 -c elasticsearch -- curl -s localhost:9200/ndip-k8s-*/_count - Filebeat pods in
ImagePullBackOff: the image must beelastic/filebeat:<full version>; there is nobeats/filebeatrepository and no short8.12tag.
Backfilling
Filebeat records how far it has read in each container log file (its registry, in /var/lib/filebeat-data on each
node) and moves past events Elasticsearch rejected. To ship logs again:
- Delete the data streams that would otherwise be duplicated:
curl -X DELETE localhost:9200/_data_stream/ndip-k8s-*(from the Elasticsearch pod). - Give Filebeat a fresh registry: set
filebeat.registry.pathin the ConfigMap to a new directory (e.g./usr/share/filebeat/data/registry-v2) andkubectl -n ndip rollout restart ds/filebeat. It then re-reads every container log file still on the nodes from the start.
This only reaches as far back as the kubelet keeps container logs on the nodes (a few rotated files per container: hours for Galaxy, longer for quiet pods; nothing for deleted pods). Older history is in Loki (above).
Pulsar hosts and VMs
- Pulsar:
/var/log/pulsar.log(prod) and/var/log/pulsar_test.log(test); RDB in/var/log/rdb.log. Fluentd is off on the Pulsar hosts (fluentd_install: false). Filebeat is deployed to some hosts by thefilebeat pulsarjob. - Old prod VM (proxy): nginx logs in
/var/log/nginx/. The stream proxy logs only errors by default.