EFK Stack: Logs Além da Depuração

Bianca Cristina
8 min readJun 30, 2021

Quando nossa aplicação apresenta algum problema ou comportamento inesperado, todos nós sabemos a quem recorrer: logs! Entretanto, e se os logs fossem úteis para além da depuração? Neste artigo, apresento uma maneira diferente de utilizar logs por meio da stack EFK e uma aplicação simples desenvolvida com Kotlin e Micronaut. Mas, afinal de contas, o que é essa stack EFK?

EFK: Visão Geral

A stack EFK é um acrônimo para três projetos open source famosos: Elasticsearch, Fluentd e Kibana. Por meio da utilização dessas ferramentas em conjunto, é possível coletar, armazenar, centralizar e visualizar logs de diversos tipos e provenientes de diferentes fontes.

EFK: Elasticsearch, Fluentd e Kibana

Nesse contexto, podemos utilizar a stack para coletar os logs da aplicação, centralizá-los em uma única fonte e possibilitar a visualização desses dados. Para tal, nas próximas seções, cada um dos open sources serão abordados e, além disso, realizaremos o deploy da stack no Kubernetes (localmente via minikube, mas o processo é semelhante caso seja em um ambiente real).

Elasticsearch

O Elascticsearch é uma engine de busca open source baseada no Apache Lucene, na qual é possível realizar buscas e análises quase em real-time para qualquer tipo de dado. Alguns dos casos de uso mais comuns do Elasticsearch são:

  • Armazenamento e análise de logs;
  • Automatização de fluxos de negócio usando o armazenamento do Elasticsearch;
  • Processamento de dados genéticos usando a engine de busca do Elasticsearch.

Nesse artigo, o Elasticsearch será usado como descrito no primeiro caso de uso, ou seja, como uma engine para armazenar e analisar os logs gerados pela nossa aplicação.

Fluentd

O Fluentd é um coletor de logs que funciona como um intermediário entre a fonte geradora do log e a fonte que armazena o log. Além disso, a ferramenta possui funcionalidades relacionadas à filtro, análise e formatação dos logs coletados.

Nesse artigo, o Fluentd será utilizado como um facilitador para a centralização dos logs: ao invés de realizar uma conexão direta com o Elasticsearch na aplicação, o Fluentd será instalado em cada nó da estrutura montada no Kubernetes, realizará a coleta de logs da aplicação e, posteriormente, enviará o log tratado para o Elasticsearch. As vantagens dessa abordagem em detrimento da conexão direta são as seguintes:

  • Baixo acoplamento entre a aplicação e o Elasticsearch, uma vez que a conexão direta não é necessária;
  • Análise e formatação dos logs antes do armazenamento;
  • Possibilidade de escalar os recursos necessários para a coleta de logs de forma independente, dado que em um cenário real o deploy da estrutura ocorreria em um ambiente propício (na cloud, por exemplo).

Finalmente, é importante ressaltar que, ainda que o Elasticsearch seja usado como fonte de armazenamento dos logs nesse artigo, o Fluentd também suporta outras ferramentas tais como o Apache Kafka, Amazon S3 e MongoDB.

Kibana

O Kibana é uma ferramenta open source para visualização, gerenciamento e monitoramento dos dados indexados no Elasticsearch. De modo geral, o Kibana é comumente associado às seguintes funcionalidades:

  • Busca, observação e proteção: a ferramenta atua como um portal capaz de permitir que os logs sejam monitorados e vulnerabilidades descobertas;
  • Visualização e análise: o Kibana oferece diversas funcionalidades gráficas que facilitam a visualização dos dados, potencializando a análise das informações;
  • Gerenciamento: com o Kibana é possível administrar os índices, verificar a saúde do cluster e controlar o acesso dos usuários via interface gráfica.

Nesse artigo, o Kibana será utilizado para visualizar os logs gerados pela aplicação.

Aplicação

Para a aplicação, escolhi dois fluxos simples: criação e remoção de usuários. Cada fluxo é iniciado no Controller (UserController), o qual possui os endpoints responsáveis pela criação e remoção de usuários.

Note que o endpoint responsável por processar a request de criação de usuário recebe um objeto do tipo UserRequest, o qual consiste de um nome e username para o usuário a ser criado. Já o endpoint para remoção de usuários contém apenas o uuid do usuário a ser removido como query string.

Ainda sobre os fluxos, após receber a request, o Controller utiliza o serviço (UserService) encarregado de processar o fluxo. Nesse ponto, dado que o objetivo da aplicação é apenas exemplificar o uso alternativo dos logs, cada serviço apenas loga a ação ocorrida, ou seja, loga informações acerca da criação ou remoção de usuários.

Apresentado os principais pontos da aplicação, cujo código completo está disponível aqui, é possível testar seu comportamento. Para isso, execute os seguintes comandos:

mvn clean package
mvn mn:run

Por padrão, a aplicação será iniciada em http://localhost:8080, sendo então possível testar os endpoints. Para testar a criação de usuários, utilize o seguinte comando:

curl --location --request POST 'http://localhost:8080/users' \
--header 'Content-Type: application/json' \
--data-raw '{
"username": "biahyonce",
"name": "Bianca"
}'

Caso não ocorra erros, a saída da aplicação deve ser algo semelhante a essa:

[15:45:58.775 default-nioEventLoopGroup-1-3 INFO  c.g.b.service.UserService] - LogRecord(type=SAVE_USER, uuid=2bb05eaf-a4d6-4b7b-97b3-c03291b3d450, created=2021-06-28T15:45:58.775224)

Já para a remoção dos usuários, utilize o seguinte comando:

curl --location --request DELETE 'http://localhost:8080/users/2bb05eaf-a4d6-4b7b-97b3-c03291b3d450'

Caso não ocorra erros, a saída da aplicação assemelha-se a essa:

[15:47:04.071 default-nioEventLoopGroup-1-3 INFO  c.g.b.service.UserService] - LogRecord(type=DELETE_USER, uuid=2bb05eaf-a4d6-4b7b-97b3-c03291b3d450, created=2021-06-28T15:47:04.071010)

Finalmente, após verificar o comportamento da aplicação e constatar que está funcionando corretamente, podemos iniciar o deploy.

Deploy

Nessa etapa, realizaremos tanto o deploy da stack EFK quanto da aplicação usando o Kubernetes. Por praticidade, escolhi o minikube para o teste local do Kubernetes, mas fique à vontade para utilizar outras ferramentas, dado que o processo será semelhante. Caso também opte pelo minikube, inicie a ferramenta com os seguintes comandos:

minikube start
eval $(minikube docker-env)

Namespace

Após iniciar o minikube, é preciso criar o namespace que determina o escopo das ferramentas (exceto o Fluentd que deve ser instalado no namespace kube-system) e da aplicação. Essa criação pode ser realizada com a execução do seguinte comando:

kubectl create namespace efk

Elasticsearch

Considerando os fins do artigo, a configuração do Elasticsearch não possui nada especial: a porta utilizada será a padrão e o tipo de discovery é single-node. Entretanto, em produção, caso a escolha seja por um serviço não gerenciado do Elasticsearch, é aconselhável trocar o tipo de configuração para StatefulSet ao invés de Deployment, uma vez que a ferramenta realiza persistência de dados.

kubectl create -f elastic.yaml

Kibana

Em relação a configuração do Kibana, um detalhe que é importante ressaltar é a URL do Elasticsearch, a qual segue o mesmo nome definido nos arquivos de configuração do Elasticsearch. Realizadas as devidas configurações, basta efetuar o seguinte comando:

kubectl create -f kibana.yaml

Fluentd

Já em relação ao Fluentd, a configuração é um pouco mais complexa, sendo necessário configurar a ServiceAccount, a autorização RBAC (ClusterRole e ClusterRoleBinding), o ConfigMap e o DaemonSet. Opcionalmente, também é possível configurar os Secrets para informações relacionadas ao acesso do Elasticsearch.

Inicialmente, define-se uma ServiceAccount para o Fluentd e, posteriormente, uma ClusterRole é criada para conceder permissões de get, list e watch nas pods e namespaces do cluster e associada à ServiceAccount via ClusterRoleBinding. A criação de tais configurações é dada da seguinte forma:

kubectl create -f service-account.yaml
kubectl create -f cluster-role.yaml
kubectl create -f cluster-role-binding.yaml

Em seguida, é criado o ConfigMap, no qual residem as configurações do Fluentd. Dentre as diretrizes disponibilizadas pela ferramenta, duas são utilizadas: source e match.

A diretriz source determina a origem do log e, dado que o Fluentd será instalado em nós do Kubernetes, a origem serão os logs do container. Além disso, dentro da tag source, um plugin de parser é utilizado para capturar apenas os logs que seguem uma determinada expressão regular, a qual corresponde ao “toString()” do objeto LogRecord definido na aplicação.

Já a diretriz match define a saída do log, ou seja, a fonte na qual ele será armazenado após coleta e formatação. No nosso caso, essa diretriz será responsável por enviar os logs para o Elasticsearch e, para tal, é necessário informar dados de conexão dentro da tag. Opcionalmente, foi definido um padrão de nome de índice diferente do padrão: ao invés de índices diários, índices mensais com prefixo “efk” (efk-%Y-%m) foram definidos. Além disso, dentro da match, um plugin de buffer foi utilizado para controlar a frequência de envio dos logs ao Elasticsearch.

É válido salientar que essa configuração adotada é específica para o contexto do artigo e o Fluentd oferece inúmeras outras formas para as diretrizes e plugins tais como utilização de outras fontes de origem/saída, formatação de JSON e CSV, entre outras funcionalidades que podem ser encontradas na documentação. Dito isso, para criar o ConfigMap, basta executar o seguinte comando:

kubectl create -f config-map.yaml

Finalmente, temos a configuração do DaemonSet, na qual informações como conexão do Elasticsearch, variáveis de ambiente e montagem do volume são definidas. Opcionalmente, um Kubernetes Secrets também foi criado para armazenar valores de referência do usuário e senha do Elasticsearch (note que em um cenário real esses Secrets não devem ser publicados, uma vez que informações de acesso seriam expostas).

Para criar o Secrets e o DaemonSet execute os seguintes comandos:

kubectl create -f secret.yaml
kubectl create -f daemon-set.yaml

Aplicação

Em relação à configuração da aplicação, os seguintes comandos são suficientes para o deploy:

mvn clean package
docker build -t efk:v1 .
kubectl create deployment efk --image=efk:v1 -n efk
kubectl expose deployment efk --type=NodePort --port=8080 -n efk

Funcionamento

Para checar se todas as etapas do deploy foram executadas com sucesso, verifique o status das PODs da seguinte forma:

kubectl get pods -n efkNAME                             READY   STATUS    RESTARTS   AGE
efk-5c94869b67-55blp 1/1 Running 0 10m
elasticsearch-57c9895555-gj6dq 1/1 Running 0 11m
kibana-ccc79f499-lnj7w 1/1 Running 0 11m
kubectl get pods -n kube-systemNAME READY STATUS RESTARTS AGE
coredns-74ff55c5b-r5gdd 1/1 Running 0 12m
etcd-minikube 1/1 Running 0 12m
fluentd-sbs5b 1/1 Running 0 6m
kube-apiserver-minikube 1/1 Running 0 12m
kube-controller-manager-minikube 1/1 Running 0 12m
kube-proxy-9lxm4 1/1 Running 0 12m
kube-scheduler-minikube 1/1 Running 0 12m
storage-provisioner 1/1 Running 0 12m

Para gerar os logs na aplicação, execute os comandos curl mencionados anteriormente. Para verificar qual endereço da aplicação, basta:

minikube service efk -n efk

Após alguns segundos, o log será coletado e enviado para o Elasticsearch via Fluentd. De modo semelhante à aplicação, os endereços do Elasticsearch e Kibana podem ser obtidos pela execução dos seguintes comandos:

minikube service elasticsearch -n efk
minikube service kibana -n efk

Verificando os logs armazenados via Kibana, o resultado deve ser semelhante a imagem a seguir:

À partir daqui o universo dos logs expande-se, dado que, além da depuração, podemos agora utilizar os logs para registrar eventos da nossa aplicação e, uma vez armazenados, é possível utilizar o Kibana para criar métricas, gráficos e monitoramento sobre esses dados. A imagem a seguir, por exemplo, ilustra uma visualização criada comparando a quantidade de usuários removidos (verde) e criados (azul).

Conclusão

Nesse artigo, falamos sobre uma maneira de registrar eventos da aplicação via logs e centralizá-los utilizando a stack EFK. Caso deseje aprofundar-se ainda mais no assunto, sugiro as seguintes leituras:

Além disso, você também pode consultar o repositório deste artigo, o qual possui a implementação da aplicação e os arquivos necessários para o deploy.

--

--