In questi giorni ho studiato una volta per tutte la configurazione di Fluentd. Avrei voluto dire con fierezza che ora conosco il kung-fu ma non è così, è ancora da approfondire.

Tuttavia, vorrei riportare un po’ di info su come strutturare un debugging della configurazione in caso di stack EFK su K8s. Lo scopo principale è non ritrovarsi a fare replace della ConfigMap e andare per tentativi.

Scenario

La situazione è molto standard. Cluster Kubernetes con stack EFK. Un DaemonSet di Fluentd e una ConfigMap in cui è presente la configurazione.

Problema

I log non arrivano a meta, ovvero si fermano da qualche parte nel flusso di controlllo della configurazione.

Laboratorio per il debugging definitivo

Fluentd è una Gem Ruby per cui prima di tutto consiglio di installarla in locale sullla propria workstation.

gem install fluentd --no-doc

Sicuramente ci serviranno alcuni plugin (tra cui quello per Kubernetes). Riporto quelli che ho usato io nella mia ultima prova.

gem install fluent-plugin-kubernetes_metadata_filter --no-doc
gem install fluent-plugin-rewrite-tag-filter --no-doc

File configurazione minimale per iniziare

Suggerisco questi due blocchi per iniziare.

<source>
  @type tail
  path ./foobar.log
  pos_file ./foobar.log.pos
  @log_level debug
  tag foobar
  <parse>
    @type json
  </parse>
</source>

<match foobar>
  @type file
  path ./outputfoobar_log
</match>

Eseguire Fluentd con “fluentd -c ./fluentd.conf

In un’altra shell lanciare a ripetizione echo tipo questa.

echo '{"foo": "bar", "level": "ERROR"}' >> foobar.log

In questo modo potremmo debuggare sia lo stdout di Fluentd alla ricerca di errori e controllare che all’interno di outputfoobar_log, come specificato nel parametro path del match foobar, ci sia il file di log dove Fluentd scrive.

Fino a qui tutto bene, fino a qui tutto bene

Niente ho dovuto per forza citare uno dei miei film preferiti…

Su una ConfigMap di Fluentd complessa, con output che possono essere endpoint di rete MongoDB, Splunk o Elasticsearch, è probabile che troverete molti blocchi match, label e filter per effettuare tutte le trasformazioni che vi occorrono.

Mi sono trovato molto bene ad aggiungere un blocco alla volta all’interno del file di configurazione locale testando ad ogni “hop” l’output su file.

Naturalmente dovrete sicuramente modificare label o tags per modificare il flusso di controllo ma il risultato deve essere che ad ogni blocco che aggiungiamo, Fluentd deve sempre scrivere su file e arrivare in questo ultimo blocco.

<match foobar>
  @type file
  path ./outputfoobar_log
</match>

oppure

<label @OUTPUT_FOO>
  <match es.**>
    @type file
    path ./outputfoobar_log
  </match>
</label>

Per testare il tutto su Kubernetetes?

Su Kube non cambia tanto se non il fatto di dover editare la ConfigMap e di riavviare tutti i pod di Fluentd.

# Questo è bello perchè divertente ma non è una best practice
for i in $(kubectl get pods -n logstack -o wide | awk '{ print $1 }' | grep fluent); do kubectl delete pod $i -n logstack ; done

# Meglio
kubectl delete pod -l app=fluentd-logging -n logstack

Possiamo senza problemi entrare in shell in uno dei pod di Fluentd e rilanciare l’echo di prima.

kubectl exec -it fluentd-qjltj -n logging bash
cd /var/log/containers
echo '{"foo": "bar", "level": "ERROR"}' >> mymicroservice.log

Tutto questo magari lanciando un kubectl logs sul pod di Fluentd in un’altra shell.

Naturalmente nulla ci vieta di fare delle prove mantenendo il match con scrittura su file anche nel pod, magari in /tmp. Una volta che vediamo i log scritti correttamente è possibile procedere con l’inserimento di un output verso i vari Splunk, Elastic o altre destinazioni.