Como configurar uma pilha de log-in com o Elasticsearch, Fluentd e o Kibana (EFK) no Kubernetes

Introdução

Ao executar vários serviços e aplicativos em um cluster do Kubernetes, uma pilha de registros centralizada a nível de cluster pode ajudar você a classificar rapidamente e analisar o volume pesado de dados de registro produzidos por seus Pods. Uma solução popular de registro centralizada é a pilha Elasticsearch, Fluentd e Kibana (EFK).

O Elasticsearch é um motor de busca em tempo real, distribuído e escalável que permite uma pesquisa de texto completo e estruturada, além de análise. Geralmente, ele é utilizado para indexar e fazer pesquisas em grandes volumes de dados de registro, mas também pode ser usado para pesquisar muitos outros tipos de documentos.

O Elasticsearch é geralmente implantado ao lado do Kibana, um poderoso front-end de visualização de dados e painel para o Elasticsearch. O Kibana permite que você explore seus dados de registro do Elasticsearch através de uma interface Web e construa painéis e consultas para responder rapidamente a perguntas e obter conhecimento sobre seus aplicativos do Kubernetes.

Neste tutorial, usaremos o Fluentd para coletar, transformar e enviar dados de registro ao back-end do Elasticsearch. O Fluentd é um coletor de dados de código aberto popular que será configurado em nossos nós do Kubernetes para rastrear arquivos de registro de contêineres, filtrar e transformar os dados de registro e entregá-los ao cluster do Elasticsearch, onde eles serão indexados e armazenados.

Vamos começar configurando e iniciando um cluster escalável do Elasticsearch e, em seguida, criaremos o Serviço e Implantação do Kibana Kubernetes. Para finalizar,será configurado o Fluentd como um DaemonSet para que ele execute em cada nó de trabalho do Kubernetes.

Pré-requisitos

Antes de começar com este guia, certifique-se de que você tenha disponível o seguinte:

  • Um cluster do Kubernetes 1.10+ com controle de acesso baseado em função (RBAC) habilitado

    • Certifique-se de que seu cluster tenha recursos suficientes disponíveis para implantar a pilha EFK e, caso não tenha, aumente seu cluster adicionando nós de trabalho. Vamos implantar um cluster de 3 Pods do Elasticsearch (você pode reduzir esse número para 1, se necessário), bem como usar um único Pod de Kibana. Todo nó de trabalho também executará um pod do Fluentd. O cluster neste guia consiste em 3 nós de trabalho e em um plano de controle gerenciado.
  • A ferramenta kubectl de linha de comando instalada em sua máquina local, configurada para se conectar ao seu cluster. Você pode ler mais sobre como instalar o kubectl na documentação oficial.

Assim que tiver esses componentes configurados, você estará pronto para começar com este guia.

Passo 1 — Criando um Namespace

Antes de implantarmos um cluster do Elasticsearch, vamos primeiro criar um Namespace no qual instalaremos toda a nossa instrumentação de registro. O Kubernetes permite que você separe objetos que estiverem executando em seu cluster usando uma abstração de “cluster virtual” chamada Namespaces. Neste guia, vamos criar um namespace kube-logging no qual instalaremos os componentes de pilha EFK. Esse Namespace também permitirá que limpemos rapidamente e removamos a pilha de registro sem perder nenhuma função do cluster do Kubernetes.

Para começar, investigue primeiro os Namespaces existentes em seu cluster usando o kubectl:

kubectl get namespaces 

Você deverá ver os três primeiros Namespaces a seguir, que vêm pré-instalados com seu cluster do Kubernetes:

Output
  • NAME STATUS AGE
  • default Active 5m
  • kube-system Active 5m
  • kube-public Active 5m

O Namespace default abriga objetos que são criados sem especificar um Namespace. O Namespace kube-system contém objetos criados e usados pelo sistema Kubernetes, como o kube-dns, kube-proxy e o kubernetes-dashboard. É bom manter esse Namespace limpo e não polui-lo com seu as cargas de trabalho do seu aplicativo e instrumentação.

O Namespace kube-public é outro Namespace criado automaticamente que pode ser usado para armazenar os objetos que você gostaria que fossem lidos e possam ser acessados ao longo de todo o cluster, mesmo para os usuários não autenticados.

Para criar o Namespace kube-logging, abra e edite primeiro um arquivo chamado kube-logging.yaml usando seu editor favorito, como o nano:

  • nano kube-logging.yaml

Dentro do seu editor, cole o objeto Namespace YAML a seguir:

kube-logging.yaml

kind: Namespace apiVersion: v1 metadata:   name: kube-logging 

Então, salve e feche o arquivo.

Aqui, especificamos o objeto do Kubernetes kind como um objeto Namespace. Para aprender mais sobre objetos Namespace, consulte a documentação oficial do Kubernetes Passo a passo de Namespaces. Também especificamos a versão da API do Kubernetes usado para criar o objeto (v1) e damos a ele um name, kube-logging.

Assim que tiver criado o arquivo do objeto Namespace kube-logging.yaml, crie o Namespace usando kubectl create com a sinalização de nome de arquivo -f:

  • kubectl create -f kube-logging.yaml

Você deve ver o seguinte resultado:

Outputnamespace/kube-logging created 

Em seguida, você poderá confirmar que o Namespace foi criado com sucesso:

  • kubectl get namespaces

Neste ponto, você deve ver o novo Namespace kube-logging:

OutputNAME           STATUS    AGE default        Active    23m kube-logging   Active    1m kube-public    Active    23m kube-system    Active    23m 

Agora, podemos implantar um cluster do Elasticsearch neste Namespace de registro isolado.

Passo 2 — Criando o Elasticsearch StatefulSet

Agora que criamos um Namespace para abrigar nossa pilha de registros, podemos começar a implantar seus vários componentes. Primeiramente, vamos começar implantando um cluster de 3 nós do Elasticsearch.

Neste guia, usamos 3 Pods do Elasticsearch para evitar a questão de “dupla personalidade” que ocorre em clusters altamente disponíveis e com múltiplos nós. Em alto nível, “dupla personalidade” é o que aparece quando um ou mais nós não conseguem se comunicar com os outros e vários mestres “divididos” são eleitos. Com 3 nós, se um deles se desconectar temporariamente do cluster, os outros dois nós podem eleger um novo mestre e o cluster pode continuar funcionando enquanto o último nó tenta reingressar. Para aprender mais, leia as páginas Uma nova era para a coordenação de cluster em Elasticsearch e Configurações de votação.

Criando o serviço que executa em segundo plano

Para começar, vamos criar um serviço Kubernetes que executa em segundo plano, chamado elasticsearch, o qual definirá um domínio DNS para os 3 Pods. Um serviço que executa em segundo plano não realiza o balanceamento de carga nem tem um IP estático; para aprender mais sobre serviços que executam em segundo plano, consulte a documentação do Kubernetes oficial.

Abra um arquivo chamado elasticsearch_svc.yaml usando seu editor favorito:

  • nano elasticsearch_svc.yaml

Cole o arquivo YAML no seguinte serviço do Kubernetes:

elasticsearch_svc.yaml

kind: Service apiVersion: v1 metadata:   name: elasticsearch   namespace: kube-logging   labels:     app: elasticsearch spec:   selector:     app: elasticsearch   clusterIP: None   ports:     - port: 9200       name: rest     - port: 9300       name: inter-node 

Então, salve e feche o arquivo.

Definimos um Service chamado elasticsearch no Namespace kube-logging e o identificamos como app: elasticsearch. Então, configuramos o .spec.selector para app: elasticsearch de modo que o Serviço selecione os Pods com o rótulo app: elasticsearch. Quando associamos nosso Elasticsearch StatefulSet com esse serviço, o serviço irá retornar registros DNS A que apontam para Pods do Elasticsearch com o rótulo app: elasticsearch.

Então, configuramos o clusterIP: None, que torna o serviço remotamente gerenciável. Por fim, definimos as portas 9200 e 9300 que são usadas para interagir com a API REST e para a comunicação entre nós, respectivamente.

Crie o serviço usando o kubectl:

  • kubectl create -f elasticsearch_svc.yaml

Você deve ver o seguinte resultado:

Outputservice/elasticsearch created 

Por fim, verifique novamente se o serviço foi criado com sucesso usando o kubectl get:

kubectl get services --namespace=kube-logging 

Você deve ver o seguinte:

OutputNAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)             AGE elasticsearch   ClusterIP   None         <none>        9200/TCP,9300/TCP   26s 

Agora que configuramos nosso serviço que executa em segundo plano e um domínio .elasticsearch.kube-logging.svc.cluster.local estável para nossos Pods, podemos seguir em frente e criar o StatefulSet.

Criando o StatefulSet

Um Kubernetes StatefulSet permite que você atribua uma identidade estável aos Pods, dando a eles um armazenamento estável e persistente. O Elasticsearch exige armazenamento estável para manter dados através de reprogramações e reinícios dos Pods. Para aprender mais sobre a carga de trabalho do StatefulSet, consulte a página Statefulsets de documentos do Kubernetes.

Abra um arquivo chamado elasticsearch_statefulset.yaml no seu editor favorito:

  • nano elasticsearch_statefulset.yaml

Vamos passar pelas definições do objeto StatefulSet seção por seção, colando blocos nesse arquivo.

Inicie colando no seguinte bloco:

elasticsearch_statefulset.yaml

apiVersion: apps/v1 kind: StatefulSet metadata:   name: es-cluster   namespace: kube-logging spec:   serviceName: elasticsearch   replicas: 3   selector:     matchLabels:       app: elasticsearch   template:     metadata:       labels:         app: elasticsearch 

Nesse bloco, definimos um StatefulSet chamado es-cluster no namespace kube-logging. Então, associamos ele ao nosso serviço elasticsearch criado anteriormente usando o campo serviceName. Isso garante que cada Pod no StatefulSet estará acessível usando o seguinte endereço DNS: es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local, no qual [0,1,2] corresponde ao número ordinal inteiro atribuído ao Pod.

Especificamos 3 replicas (Pods) e configuramos o seletor matchLabels para o app: elasticseach, que então espelhamos na seção .spec.template.metadata. Os campos .spec.selector.matchLabels e .spec.template.metadata.labels devem corresponder.

Agora, podemos seguir em frente para as especificações do objeto. Cole o seguinte bloco de YAML imediatamente abaixo do bloco anterior:

elasticsearch_statefulset.yaml

. . .     spec:       containers:       - name: elasticsearch         image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0         resources:             limits:               cpu: 1000m             requests:               cpu: 100m         ports:         - containerPort: 9200           name: rest           protocol: TCP         - containerPort: 9300           name: inter-node           protocol: TCP         volumeMounts:         - name: data           mountPath: /usr/share/elasticsearch/data         env:           - name: cluster.name             value: k8s-logs           - name: node.name             valueFrom:               fieldRef:                 fieldPath: metadata.name           - name: discovery.seed_hosts             value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch"           - name: cluster.initial_master_nodes             value: "es-cluster-0,es-cluster-1,es-cluster-2"           - name: ES_JAVA_OPTS             value: "-Xms512m -Xmx512m" 

Aqui definimos os Pods no StatefulSet. Nomeamos os contêineres de elasticsearch e escolhemos a imagem Docker docker.elastic.co/elasticsearch/elasticsearch:7.2.0. Neste ponto, você pode modificar essa tag de imagem para corresponder à sua própria imagem interna do Elasticsearch ou uma versão diferente. Note que, para os fins deste guia, apenas o 7.2.0 do Elasticsearch foi testado.

Em seguida, usamos o campo resources para especificar que o contêiner precisa de uma vCPU (CPU virtual) de pelo menos 0,1 garantida para ele que possa estourar até 1 da vCPU (o que limita o uso de recursos do Pod ao realizar uma ingestão inicial grande ou lidar com um pico de carga). Você deve modificar esses valores, dependendo da sua carga esperada e dos recursos disponíveis. Para aprender mais sobre pedidos e limites de recursos, consulte a Documentação do Kubernetes oficial.

Em seguida, abrimos e nomeamos as portas 9200 e 9300 para a API REST e a comunicação entre nós, respectivamente. Especificamos um volumeMount chamado data que irá montar o PersistentVolume chamado data para o contêiner no caminho /usr/share/elasticsearch/data. Vamos definir os VolumeClaims para este StatefulSet em um bloco YAML posterior.

Finalmente, definimos algumas variáveis de ambiente no contêiner:

  • cluster.name: o nome do cluster do Elasticsearch, que neste guia é k8s-logs.
  • node.name: o nome do nó, que definimos para o campo .metadata.name usando o valueFrom. Isso resolverá para es-cluster[0,1,2], dependendo do número ordinal atribuído ao nó.
  • discovery.seed_hosts: este campo define uma lista de nós elegíveis para o mestre no cluster que irá semear o processo de descoberta do nós. Neste guia, graças ao serviço que executa em segundo plano que configuramos anteriormente, nossos Pods têm domínios na forma es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local, para que possamos definir essa variável adequadamente. Ao usar a resolução de namespace local de DNS Kubernetes, podemos encurtar isso para es-cluster[0,1,2].elasticsearch. Para aprender mais sobre a descoberta do Elasticsearch, consulte a documentação do Elasticsearch oficial.
  • cluster.initial_master_nodes: este campo também especifica uma lista de nós elegíveis para o mestre que participarão no processo de eleição do mestre. Note que para esse campo você deve identificar os nós por seus node.name e não seus nomes de host.
  • ES_JAVA_OPTS: aqui, definimos isso em -Xms512m -Xmx512m, o que diz ao JVM para usar um tamanho mínimo e máximo de pilha de 512 MB. Você deve ajustar esses parâmetros dependendo da disponibilidade de recursos e necessidades do seu cluster. Para aprender mais, consulte o tópico Configurando o tamanho de pilha.

O próximo bloco que vamos colar é semelhante ao seguinte:

elasticsearch_statefulset.yaml

. . .       initContainers:       - name: fix-permissions         image: busybox         command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]         securityContext:           privileged: true         volumeMounts:         - name: data           mountPath: /usr/share/elasticsearch/data       - name: increase-vm-max-map         image: busybox         command: ["sysctl", "-w", "vm.max_map_count=262144"]         securityContext:           privileged: true       - name: increase-fd-ulimit         image: busybox         command: ["sh", "-c", "ulimit -n 65536"]         securityContext:           privileged: true 

Nesse bloco, definimos vários contêineres Init que são executados antes do contêiner do app principal elasticsearch. Cada um desses Contêineres Init são executados até o fim na ordem em que foram definidos. Para aprender mais sobre os Contêineres Init, consulte a Documentação do Kubernetes oficial.

O primeiro, chamado fix-permissions, executa um comando chown para alterar o proprietário e o grupo do diretório de dados do Elasticsearch para 1000:1000, o UID do usuário do Elasticsearch. Por padrão, o Kubernetes monta o diretório de dados como root, o que o torna inacessível ao Elasticsearch. Para aprender mais sobre esse passo, consulte as “Notas para o uso de produção e padrões” do Elasticsearch.

O segundo, chamado increase-vm-max-map, executa um comando para aumentar os limites do sistema operacional em contagens do mmp, os quais, por padrão, podem ser baixos demais, resultando em erros de falta de memória. Para aprender mais sobre esse passo, consulte a documentação do Elasticsearch oficial.

O próximo Contêiner Init a ser executado é o increase-fd-ulimit, o qual executa o comando ulimit para aumentar o número máximo de descritores de arquivos abertos. Para aprender mais sobre este passo, consulte as “Notas para o uso de produção e padrões” da documentação oficial do Elasticsearch.

Nota: as Notas de uso de produção do Elasticsearch também mencionam a desativação do swap por razões de desempenho. Dependendo da sua instalação ou provedor do Kubernetes, o swap pode já estar desabilitado. Para verificar isso,utilize o exec em um contêiner em funcionamento e execute o cat/proc/swaps para listar dispositivos de swap ativos. Caso não tenha nada lá, o swap está desabilitado.

Agora que definimos nosso contêiner de app principal e os Contêineres Init que são executados antes dele para ajustar o SO do container, podemos adicionar a peça final ao nosso arquivo de definição de objeto do StatefulSet: o volumeClaimTemplates.

Cole o seguinte bloco volumeClaimTemplate :

elasticsearch_statefulset.yaml

. . .   volumeClaimTemplates:   - metadata:       name: data       labels:         app: elasticsearch     spec:       accessModes: [ "ReadWriteOnce" ]       storageClassName: do-block-storage       resources:         requests:           storage: 100Gi 

Nesse bloco, definimos o volumeClaimTemplates do StatefulSet. O Kubernetes usará isso para criar PersistentVolumes para os Pods. No bloco acima, nós o chamamos de data (que é o name ao qual nos referimos nos volumeMounts definidos anteriormente) e damos a ele o mesmo rótulo app: elasticsearch do nosso StatefulSet.

Então, especificamos seu modo de acesso como ReadWriteOnce, o que significa que ele só pode ser montado como leitura-gravação por um único nó. Definimos a classe de armazenamento como do-block-storage neste guia, já que usamos um cluster do Kubernetes da DigitalOcean para fins demonstrativos. Você deve alterar esse valor dependendo de onde estiver executando seu cluster do Kubernetes. Para aprender mais, consulte a documentação Volume persistente.

Por fim, especificamos que queremos que cada PersistentVolume tenha 100 GiB de tamanho. Você deve ajustar esse valor, dependendo das suas necessidades de produção.

As especificações completas do StatefulSet devem ter aparência semelhante a esta:

elasticsearch_statefulset.yaml

apiVersion: apps/v1 kind: StatefulSet metadata:   name: es-cluster   namespace: kube-logging spec:   serviceName: elasticsearch   replicas: 3   selector:     matchLabels:       app: elasticsearch   template:     metadata:       labels:         app: elasticsearch     spec:       containers:       - name: elasticsearch         image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0         resources:             limits:               cpu: 1000m             requests:               cpu: 100m         ports:         - containerPort: 9200           name: rest           protocol: TCP         - containerPort: 9300           name: inter-node           protocol: TCP         volumeMounts:         - name: data           mountPath: /usr/share/elasticsearch/data         env:           - name: cluster.name             value: k8s-logs           - name: node.name             valueFrom:               fieldRef:                 fieldPath: metadata.name           - name: discovery.seed_hosts             value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch"           - name: cluster.initial_master_nodes             value: "es-cluster-0,es-cluster-1,es-cluster-2"           - name: ES_JAVA_OPTS             value: "-Xms512m -Xmx512m"       initContainers:       - name: fix-permissions         image: busybox         command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]         securityContext:           privileged: true         volumeMounts:         - name: data           mountPath: /usr/share/elasticsearch/data       - name: increase-vm-max-map         image: busybox         command: ["sysctl", "-w", "vm.max_map_count=262144"]         securityContext:           privileged: true       - name: increase-fd-ulimit         image: busybox         command: ["sh", "-c", "ulimit -n 65536"]         securityContext:           privileged: true   volumeClaimTemplates:   - metadata:       name: data       labels:         app: elasticsearch     spec:       accessModes: [ "ReadWriteOnce" ]       storageClassName: do-block-storage       resources:         requests:           storage: 100Gi 

Assim que estiver satisfeito com sua configuração do Elasticsearch, salve e feche o arquivo.

Agora, implante o StatefulSet usando o kubectl:

  • kubectl create -f elasticsearch_statefulset.yaml

Você deve ver o seguinte resultado:

Outputstatefulset.apps/es-cluster created 

Você pode monitorar o StatefulSet enquanto ele é implantado usando a opção kubectl rollout status:

  • kubectl rollout status sts/es-cluster --namespace=kube-logging

Você deve ver o seguinte resultado enquanto o cluster está sendo implantado:

OutputWaiting for 3 pods to be ready... Waiting for 2 pods to be ready... Waiting for 1 pods to be ready... partitioned roll out complete: 3 new pods have been updated... 

Assim que todos os Pods tiverem sido implantados, você poderá verificar se o seu cluster do Elasticsearch está funcionando corretamente, executando um pedido na API REST.

Para fazer isso, primeiro encaminhe a porta local 9200 para a porta 9200 em um dos nós do Elasticsearch (es-cluster-0) usando o kubectl port-forward:

  • kubectl port-forward es-cluster-0 9200:9200 --namespace=kube-logging

Então, em uma janela de terminal separada, faça um pedido curl na API REST:

  • curl http://localhost:9200/_cluster/state?pretty

Você deve ver o seguinte resultado:

Output{   "cluster_name" : "k8s-logs",   "compressed_size_in_bytes" : 348,   "cluster_uuid" : "QD06dK7CQgids-GQZooNVw",   "version" : 3,   "state_uuid" : "mjNIWXAzQVuxNNOQ7xR-qg",   "master_node" : "IdM5B7cUQWqFgIHXBp0JDg",   "blocks" : { },   "nodes" : {     "u7DoTpMmSCixOoictzHItA" : {       "name" : "es-cluster-1",       "ephemeral_id" : "ZlBflnXKRMC4RvEACHIVdg",       "transport_address" : "10.244.8.2:9300",       "attributes" : { }     },     "IdM5B7cUQWqFgIHXBp0JDg" : {       "name" : "es-cluster-0",       "ephemeral_id" : "JTk1FDdFQuWbSFAtBxdxAQ",       "transport_address" : "10.244.44.3:9300",       "attributes" : { }     },     "R8E7xcSUSbGbgrhAdyAKmQ" : {       "name" : "es-cluster-2",       "ephemeral_id" : "9wv6ke71Qqy9vk2LgJTqaA",       "transport_address" : "10.244.40.4:9300",       "attributes" : { }     }   }, ... 

Isso indica que nosso cluster do Elasticsearch k8s-logs foi criado com sucesso com 3 nós: es-cluster-0, es-cluster-1 e es-cluster-2. O nó mestre atual é o es-cluster-0.

Agora que o seu cluster do Elasticsearch está funcionando, você prosseguir com a configuração de uma front-end do Kibana para ele.

Passo 3 — Criando a implantação e serviço do Kibana

Para iniciar o Kibana no Kubernetes, vamos criar um serviço chamado kibana e uma implantação constituída por uma réplica de um Pod. Você pode dimensionar o número de réplicas dependendo das suas necessidades de produção. Além disso, pode especificar opcionalmente um tipo de LoadBalancer para que o serviço faça o balanceamento da carga nos Pods da Implantação.

Desta vez, vamos criar o serviço e a implantação no mesmo arquivo. Abra um arquivo chamado kibana.yaml no seu editor favorito:

  • nano kibana.yaml

Cole a seguinte especificação de serviço:

kibana.yaml

apiVersion: v1 kind: Service metadata:   name: kibana   namespace: kube-logging   labels:     app: kibana spec:   ports:   - port: 5601   selector:     app: kibana --- apiVersion: apps/v1 kind: Deployment metadata:   name: kibana   namespace: kube-logging   labels:     app: kibana spec:   replicas: 1   selector:     matchLabels:       app: kibana   template:     metadata:       labels:         app: kibana     spec:       containers:       - name: kibana         image: docker.elastic.co/kibana/kibana:7.2.0         resources:           limits:             cpu: 1000m           requests:             cpu: 100m         env:           - name: ELASTICSEARCH_URL             value: http://elasticsearch:9200         ports:         - containerPort: 5601 

Então, salve e feche o arquivo.

Nessa especificação, definimos um serviço chamado kibana no namespace kube-logging e demos a ele o rótulo app: kibana.

Também especificamos que ele deve estar acessível na porta 5601 e usar o rótulo app: kibana para selecionar os Pods de destino do serviço.

Na especificação Deployment, definimos uma implantação chamada kibana e especificamos que gostaríamos 1 réplica de Pod.

Usamos a imagem docker.elastic.co/kibana/kibana:7.2.0. Neste ponto, você pode substituir sua própria imagem Kibana privada ou pública para usar.

Especificamos que queremos 0,1 de uma vCPU, pelo menos, garantida para o Pod, usando até um limite de 1 vCPU. Você pode modificar esses parâmetros, dependendo da sua carga esperada e dos recursos disponíveis.

Em seguida, usamos a variável de ambiente ELASTICSEARCH_URL para definir o ponto de extremidade e porta para o cluster do Elasticsearch. Ao usar o Kubernetes DNS, esse ponto de extremidade corresponde ao seu nome de serviço, elasticsearch. Este domínio fará a resolução em relação a uma lista de endereços IP para os 3 Pods do Elasticsearch. Para aprender mais sobre o DNS do Kubernetes, consulte DNS para serviços e Pods.

Por fim, definimos para o contêiner do Kibana a porta 5601, para a qual o serviço kibana irá encaminhar pedidos.

Assim que estiver satisfeito com sua configuração do Kibana, é possível implantar o serviço e a implantação usando o kubectl:

  • kubectl create -f kibana.yaml

Você deve ver o seguinte resultado:

Outputservice/kibana created deployment.apps/kibana created 

Você pode verificar se a implantação foi bem-sucedida, executando o seguinte comando:

  • kubectl rollout status deployment/kibana --namespace=kube-logging

Você deve ver o seguinte resultado:

Outputdeployment "kibana" successfully rolled out 

Para acessar a interface do Kibana, vamos novamente encaminhar uma porta local ao nó do Kubernetes executando o Kibana. Obtenha os detalhes do Pod do Kibana usando o kubectl get:

  • kubectl get pods --namespace=kube-logging
OutputNAME                      READY     STATUS    RESTARTS   AGE es-cluster-0              1/1       Running   0          55m es-cluster-1              1/1       Running   0          54m es-cluster-2              1/1       Running   0          54m kibana-6c9fb4b5b7-plbg2   1/1       Running   0          4m27s 

Aqui, observamos que nosso Pod do Kibana se chama kibana-6c9fb4b5b7-plbg2.

Encaminhe a porta local 5601 para a porta 5601 neste Pod:

  • kubectl port-forward kibana-6c9fb4b5b7-plbg2 5601:5601 --namespace=kube-logging

Você deve ver o seguinte resultado:

OutputForwarding from 127.0.0.1:5601 -> 5601 Forwarding from [::1]:5601 -> 5601 

Agora, no seu navegador Web, visite o seguinte URL:

http://localhost:5601 

Se ver a seguinte página de boas-vindas do Kibana, você implantou o Kibana no seu cluster do Kubernetes com sucesso:

Tela de boas-vindas do Kibana

Agora, é possível seguir em frente para a implantação do componente final da pilha do EFK: o coletor de registros, Fluentd.

Passo 4 — Criando o DaemonSet do Fluentd

Neste guia, vamos configurar o Fluentd como um DaemonSet, que é um tipo de carga de trabalho do Kubernetes que executa uma cópia de um determinado Pod em cada nó no cluster do Kubernetes. Ao usar este controle do DaemonSet, vamos implantar um Pod do agente de registros do Fluentd em cada nó do nosso cluster. Para aprender mais sobre essa arquitetura de registros, consulte o tópico “Usando um agente de registros de nó” dos documentos oficiais do Kubernetes.

No Kubernetes, aplicativos no contêiner que registram para o stdout e stderr têm seus fluxos de registro capturados e redirecionados para os arquivos em JSON nos nós. O Pod do Fluentd irá rastrear esses arquivos de registro, filtrar eventos de registro, transformar os dados de registro e despachá-los para o back-end de registros do Elasticsearch que implantamos no Passo 2.

Além dos registros no contêiner, o agente do Fluentd irá rastrear os registros de componentes do sistema do Kubernetes, como os registros kubelet, kube-proxy e Docker. Para ver uma lista completa das origens rastreadas pelo agente de registros do Fluentd, consulte o arquivo kubernetes.conf usado para configurar o agente de registros. Para aprender mais sobre como fazer registros em clusters do Kubernetes, consulte o tópico “Registro no nível de nó” da documentação oficial do Kubernetes.

Inicie abrindo um arquivo chamado fluentd.yaml no seu editor de texto favorito:

  • nano fluentd.yaml

Novamente, vamos colar as definições de objeto do Kubernetes bloco a bloco, fornecendo contexto enquanto prosseguimos. Neste guia, usamos a especificação DaemonSet do Fluentd fornecida pelos mantenedores do Fluentd. Outro recurso útil fornecido pelos mantenedores do Fluentd é o Fluentd para o Kubernetes.

Primeiro, cole a seguinte definição do ServiceAccount:

fluentd.yaml

apiVersion: v1 kind: ServiceAccount metadata:   name: fluentd   namespace: kube-logging   labels:     app: fluentd 

Aqui, criamos uma conta de serviço chamada fluentd que os Pods do Fluentd irão usar para acessar a API do Kubernetes. Criamos a conta no Namespace kube-logging e, mais uma vez, damos a ela o rótulo app: fluentd. Para aprender mais sobre contas de serviço no Kubernetes, consulte o tópico Configurar contas de serviço para Pods nos documentos oficiais do Kubernetes.

Em seguida, cole o seguinte bloco ClusterRole:

fluentd.yaml

. . . --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata:   name: fluentd   labels:     app: fluentd rules: - apiGroups:   - ""   resources:   - pods   - namespaces   verbs:   - get   - list   - watch 

Aqui, definimos um ClusterRole chamado fluentd ao qual concedemos as permissões get, list e watch nos objetos pods e namespaces. Os ClusterRoles permitem que você conceda acesso aos recursos Kubernetes no escopo do cluster como nós. Para aprender mais sobre o Controle de acesso baseado em função (RBAC) e as funções no cluster, consulte o tópico Usando a autorização RBAC da documentação oficial do Kubernetes.

Agora, cole no seguinte bloco ClusterRoleBinding:

fluentd.yaml

. . . --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata:   name: fluentd roleRef:   kind: ClusterRole   name: fluentd   apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount   name: fluentd   namespace: kube-logging 

Neste bloco, definimos um ClusterRoleBinding chamado fluentd, o qual vincula o ClusterRole fluentd à conta de serviço fluentd. Isso concede à ServiceAccount fluentd as permissões listadas na função do cluster fluentd.

Neste ponto, podemos começar a adicionar a especificação real do DaemonSet:

fluentd.yaml

. . . --- apiVersion: apps/v1 kind: DaemonSet metadata:   name: fluentd   namespace: kube-logging   labels:     app: fluentd 

Aqui, definimos um DaemonSet chamado fluentd no Namespace kube-logging e damos a ele o rótulo app: fluentd.

Em seguida, cole na seguinte seção:

fluentd.yaml

. . . spec:   selector:     matchLabels:       app: fluentd   template:     metadata:       labels:         app: fluentd     spec:       serviceAccount: fluentd       serviceAccountName: fluentd       tolerations:       - key: node-role.kubernetes.io/master         effect: NoSchedule       containers:       - name: fluentd         image: fluent/fluentd-kubernetes-daemonset:v1.4.2-debian-elasticsearch-1.1         env:           - name:  FLUENT_ELASTICSEARCH_HOST             value: "elasticsearch.kube-logging.svc.cluster.local"           - name:  FLUENT_ELASTICSEARCH_PORT             value: "9200"           - name: FLUENT_ELASTICSEARCH_SCHEME             value: "http"           - name: FLUENTD_SYSTEMD_CONF             value: disable 

Aqui, combinamos o rótulo app: fluentd definido em .metadata.labels e então atribuimos a conta de serviço fluentd ao DaemonSet. Também selecionamos o app: fluentd como os Pods gerenciados por esse DaemonSet.

Em seguida, definimos uma tolerância NoSchedule para corresponder ao taint (conceito de repelência) equivalente nos nós mestres do Kubernetes. Isso garantirá que o DaemonSet também seja implantado para os mestres do Kubernetes. Se não quiser executar um Pod do Fluentd nos seus nós mestres, remova essa tolerância. Para aprender mais sobre taints e tolerâncias do Kubernetes, consulte o tópico “Taints e tolerâncias” dos documentos oficiais do Kubernetes.

Em seguida, começamos a definir o contêiner do Pod, ao qual chamamos de fluentd.

Usamos a imagem oficial do Debian v1.4.2 fornecida pelos mantenedores do Fluentd. Se quiser usar sua própria imagem do Fluentd privada ou pública, ou usar uma versão diferente de imagem, modifique a tag image na especificação do contêiner. O Dockerfile e conteúdo dessa imagem estão disponíveis no repositório Github fluentd-kubernetes-daemonset do Fluentd.

Em seguida, configuramos o Fluentd usando algumas variáveis de ambiente:

  • FLUENT_ELASTICSEARCH_HOST: configuramos essa variável para o endereço de serviço do Elasticsearch que executa em segundo plano definido anteriormente: elasticsearch.kube-logging.svc.cluster.local. Isso resolverá para uma lista de endereços IP dos 3 Pods do Elasticsearch. O host real do Elasticsearch será provavelmente o primeiro endereço IP retornado nesta lista. Para distribuir os registros ao longo do cluster, será necessário modificar a configuração para plug-in de saída do Elasticsearch do Fluentd. Para aprender mais sobre esse plug-in, consulte o tópico Plug-in de saída do Elasticsearch.
  • FLUENT_ELASTICSEARCH_PORT: configuramos essa variável na porta do Elasticsearch que configuramos anteriormente cedo, a 9200.
  • FLUENT_ELASTICSEARCH_SCHEME: configuramos esta variável como http.
  • FLUENTD_SYSTEMD_CONF: configuramos esta variável como disable para suprimir resultados relacionados ao systemd que não são configurados no contêiner.

Por fim, cole na seguinte seção:

fluentd.yaml

. . .         resources:           limits:             memory: 512Mi           requests:             cpu: 100m             memory: 200Mi         volumeMounts:         - name: varlog           mountPath: /var/log         - name: varlibdockercontainers           mountPath: /var/lib/docker/containers           readOnly: true       terminationGracePeriodSeconds: 30       volumes:       - name: varlog         hostPath:           path: /var/log       - name: varlibdockercontainers         hostPath:           path: /var/lib/docker/containers 

Aqui, especificamos um limite de memória de 512 MiB no Pod do FluentD e garantimos-lhe 0,1 de vCPU e 200 MiB de memória. Você pode ajustar esses limites de recurso e pedidos dependendo do seu volume de registro previsto e dos recursos disponíveis.

Em seguida, vamos montar os caminhos de host /var/log e /var/lib/docker/containers no contêiner usando o varlog e os varlibdockercontainers dos volumeMounts. Esses volumes são definidos no final do bloco.

O parâmetro final que definimos neste bloco é o terminationGracePeriodSeconds, o qual dá ao Fluentd 30 segundos para desligar-se graciosamente após receber um sinal SIGTERM. Após 30 segundos, um sinal SIGKILL é enviado aos contêineres. O valor padrão para o terminationGracePeriodSeconds é 30 s, de modo que, na maioria dos casos, esse parâmetro pode ser omitido. Para aprender mais sobre encerrar graciosamente as cargas de trabalho do Kubernetes, consulte o tópico “Boas práticas do Kubernetes: encerrando com graça” do Google.

As especificações completas do Fluentd devem se parecer com isso:

fluentd.yaml

apiVersion: v1 kind: ServiceAccount metadata:   name: fluentd   namespace: kube-logging   labels:     app: fluentd --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata:   name: fluentd   labels:     app: fluentd rules: - apiGroups:   - ""   resources:   - pods   - namespaces   verbs:   - get   - list   - watch --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata:   name: fluentd roleRef:   kind: ClusterRole   name: fluentd   apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount   name: fluentd   namespace: kube-logging --- apiVersion: apps/v1 kind: DaemonSet metadata:   name: fluentd   namespace: kube-logging   labels:     app: fluentd spec:   selector:     matchLabels:       app: fluentd   template:     metadata:       labels:         app: fluentd     spec:       serviceAccount: fluentd       serviceAccountName: fluentd       tolerations:       - key: node-role.kubernetes.io/master         effect: NoSchedule       containers:       - name: fluentd         image: fluent/fluentd-kubernetes-daemonset:v1.4.2-debian-elasticsearch-1.1         env:           - name:  FLUENT_ELASTICSEARCH_HOST             value: "elasticsearch.kube-logging.svc.cluster.local"           - name:  FLUENT_ELASTICSEARCH_PORT             value: "9200"           - name: FLUENT_ELASTICSEARCH_SCHEME             value: "http"           - name: FLUENTD_SYSTEMD_CONF             value: disable         resources:           limits:             memory: 512Mi           requests:             cpu: 100m             memory: 200Mi         volumeMounts:         - name: varlog           mountPath: /var/log         - name: varlibdockercontainers           mountPath: /var/lib/docker/containers           readOnly: true       terminationGracePeriodSeconds: 30       volumes:       - name: varlog         hostPath:           path: /var/log       - name: varlibdockercontainers         hostPath:           path: /var/lib/docker/containers 

Assim que terminar de configurar o DaemonSet do Fluentd, salve e feche o arquivo.

Agora, implante o DaemonSet usando o kubectl:

  • kubectl create -f fluentd.yaml

Você deve ver o seguinte resultado:

Outputserviceaccount/fluentd created clusterrole.rbac.authorization.k8s.io/fluentd created clusterrolebinding.rbac.authorization.k8s.io/fluentd created daemonset.extensions/fluentd created 

Verifique se o seu DaemonSet foi implantado com sucesso usando o kubectl:

  • kubectl get ds --namespace=kube-logging

Você deve ver o seguinte status como resultado:

OutputNAME      DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE fluentd   3         3         3         3            3           <none>          58s 

Isso indica que há 3 Pods fluentd em execução, o que corresponde ao número de nós no nosso cluster do Kubernetes.

Agora, podemos pedir ao Kibana para verificar se os dados de registro estão sendo corretamente coletados e despachados para o Elasticsearch.

Com o kubectl port-forward ainda aberto, navegue até http://localhost:5601.

Clique em Discover (Descobrir) no menu de navegação à esquerda:

Kibana Discover

Você deve ver a seguinte janela de configuração:

Configuração do padrão de índice do Kibana

Isso permite que você defina os índices do Elasticsearch que você deseja explorar no Kibana. Para aprender mais, consulte tópico Definindo seus padrões de índice nos documentos oficiais do Kibana. Por enquanto, vamos usar o padrão curinga logstash-* para capturar todos os dados de registro no nosso cluster do Elasticsearch. Digite logstash-* na caixa de texto e clique em Next step (Próximo passo).

Na sequência, você será levado para a seguinte página:

Configurações do padrão de índice do Kibana

Isso permite que você configure qual campo o Kibana usará para filtrar dados de registro por horário. Na lista suspensa, selecione o campo @timestamp e clique em Create index pattern (Criar padrão de índice).

Agora, clique em Discover (Descobrir) no menu de navegação à esquerda.

Você deve ver um gráfico de histograma e algumas entradas recentes de registro:

Registros de entrada do Kibana

Neste ponto, você configurou e implantou com sucesso a pilha de EFK no seu cluster do Kubernetes. Para aprender como usar o Kibana para analisar seus dados de registro, consulte o Guia de usuário do Kibana.

Na próxima seção opcional, implantaremos um Pod contador simples que imprime números no stdout e encontraremos seus registros no Kibana.

Passo 5 (Opcional) — Testando o registro do contêiner

Para demonstrar um uso básico do Kibana para explorar os últimos registros de um determinado Pod, vamos implantar um Pod contador mínimo que imprima números sequenciais no stdout.

Vamos começar criando o Pod. Abra um arquivo chamado counter.yaml no seu editor favorito:

  • nano counter.yaml

Então, cole as especificações do Pod a seguir:

counter.yaml

apiVersion: v1 kind: Pod metadata:   name: counter spec:   containers:   - name: count     image: busybox     args: [/bin/sh, -c,             'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done'] 

Salve e feche o arquivo.

Este é um Pod mínimo chamado counter que executa um loop while, imprimindo números sequencialmente.

Implante o Pod counter usando o kubectl:

  • kubectl create -f counter.yaml

Assim que o Pod tiver sido criado e estiver em execução, navegue de volta para o painel do Kibana.

A partir da página Discover, na barra de busca digite kubernetes.pod_name:counter. Isso filtra os dados de registro dos Pods chamados counter.

Assim, você deve ver uma lista de entradas do registro do Pod counter:

Registros do contador no Kibana

Você pode clicar em qualquer uma das entradas de registro para ver metadados adicionais como o nome do contêiner, nó do Kubernetes, Namespace, entre outros.

Conclusão

Neste guia, demonstramos como configurar o Elasticsearch, Fluentd e Kibana em um cluster do Kubernetes. Usamos uma arquitetura de registro mínima que consiste em um único Pod de agente de registro em execução em cada nó de trabalho do Kubernetes.

Antes de implantar essa pilha de registro no seu cluster de produção do Kubernetes, é melhor ajustar os requisitos e limites de recursos conforme indicado neste guia. Você também pode querer configurar o X-Pack para habilitar recursos de monitoramento e segurança integrados.

A arquitetura de registro que usamos aqui compreende 3 Pods do Elasticsearch, um único Pod do Kibana (sem balanceamento de carga) e um conjunto de Pods do Fluentd implantados como um DaemonSet. Você pode querer dimensionar essa configuração dependendo da sua produção ou uso. Para aprender mais sobre como dimensionar sua pilha do Elasticsearch e do Kibana, consulte o tópico Dimensionando o Elasticsearch.

O Kubernetes também permite arquiteturas de agente de registro mais complexas que podem atender melhor ao seu caso de uso. Para aprender mais, consulte o tópico Arquitetura de registros dos documentos do Kubernetes.