One place for hosting & domains

      Logging

      How To Configure HAProxy Logging with Rsyslog on CentOS 8 [Quickstart]


      Introduction

      HAProxy, which stands for High Availability Proxy, is a widely used TCP and HTTP-based proxy server that runs on Linux, Solaris, and FreeBSD. It is used to load balance applications by distributing requests between multiple servers, and to ensure that applications are highly available for users.

      By default on many CentOS 8 installations, HAProxy is not configured to write its log output to a file. This quickstart tutorial will explain how to configure HAProxy logging with Rsyslog by using a Unix domain socket for reliability, speed, and security.

      Prerequisites

      To complete this tutorial, you will need a server running CentOS 8. This server should have a non-root user with administrative privileges. To set this up, follow the Initial Server Setup guide for CentOS 8 tutorial.

      Step 1 — Installing and Enabling HAProxy

      To install HAProxy, run the following dnf command:

      When you are prompted Is this ok [y/N]: enter y and press RETURN to install the package.

      Once you have installed HAProxy, enable and start it using the systemctl command:

      • sudo systemctl enable haproxy.service

      You should receive the following output:

      Output

      Created symlink /etc/systemd/system/multi-user.target.wants/haproxy.service → /usr/lib/systemd/system/haproxy.service.

      With the HAProxy service now enabled, you can start it up to ensure that it runs with a default configuration on your server:

      • sudo systemctl start haproxy.service

      Next examine HAProxy’s status to make sure it is running:

      • sudo systemctl status haproxy.service

      You should receive output like the following. Note the highlighted active (running) portion of the output. If your server shows the same highlighted section then HAProxy is running correctly on your server and you can proceed with configuring logging.

      Output

      ● haproxy.service - HAProxy Load Balancer Loaded: loaded (/usr/lib/systemd/system/haproxy.service; enabled; vendor preset: disabled) Active: active (running) since Wed 2020-09-09 21:16:39 UTC; 4min 39s ago Process: 21340 ExecStartPre=/usr/sbin/haproxy -f $CONFIG -c -q (code=exited, status=0/SUCCESS) Main PID: 21341 (haproxy) Tasks: 2 (limit: 2881) Memory: 2.7M CGroup: /system.slice/haproxy.service ├─21341 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid . . .

      If your output is different, or the status shows something like Active: failed, then follow the troubleshooting steps in the HAproxy Common Errors Series Introduction tutorial to determine what is preventing HAProxy from starting correctly.

      Once you have confirmed that HAProxy is enabled and running, you can continue to the next step, which is configuring HAProxy’s logging directives.

      Step 2 — Configuring HAProxy Logging Directives

      To configure HAProxy’s logging directives, open /etc/haproxy/haproxy.cfg in vi or your preferred editor:

      • sudo vi /etc/haproxy/haproxy.cfg

      Press i to switch to INSERT mode, then find the line log 127.0.0.1 local2 and comment it out by adding a # character to the beginning of the line, as highlighted in the following example:

      /etc/haproxy/haproxy.cfg

      . . .
      # 2) configure local2 events to go to the /var/log/haproxy.log
      #   file. A line like the following can be added to
      #   /etc/sysconfig/syslog
      #
      #    local2.*                       /var/log/haproxy.log
      #
          #log         127.0.0.1 local2
      
          chroot      /var/lib/haproxy
          pidfile     /var/run/haproxy.pid
      . . .
      

      Now add a line directly after the commented out line with the following contents:

          log         /dev/log local0
      

      The entire section of /etc/haproxy/haproxy.cfg that you edited should contain the following lines:

      /etc/haproxy/haproxy.cfg

      . . .
      #    local2.*                       /var/log/haproxy.log
      #
           #log         127.0.0.1 local2
           log         /dev/log local0
      
           chroot      /var/lib/haproxy
           pidfile     /var/run/haproxy.pid
      . . .
      

      The chroot line is important, because it restricts the HAProxy process to accessing files in the /var/lib/haproxy directory only. The log /dev/log local0 line will create a file inside that directory that Rsyslog will use to collect log entries from.

      Once you are finished editing the log lines in /etc/haproxy/haproxy.cfg, save and close the file by pressing ESC, typing :wq, and pressing ENTER.

      The last step that you need to complete in this section is to create the /var/lib/haproxy/dev directory since it does not exist by default.

      Create the directory using the mkdir command and then restart HAProxy:

      • sudo mkdir /var/lib/haproxy/dev
      • sudo systemctl restart haproxy.service

      You have now configured HAProxy to send its logs to a Unix domain socket that resides in /var/lib/haproxy/dev/log. In the next step, you will configure Rsyslog to create and access the socket.

      Step 3 — Configuring Rsyslog to Collect HAProxy Logs

      Rsyslog’s default configuration on CentOS 8 does not handle HAProxy logs. To collect logs from the HAProxy service, open a new file /etc/rsyslog.d/99-haproxy.conf using vi or your preferred editor:

      • sudo vi /etc/rsyslog.d/99-haproxy.conf

      Press i to switch to INSERT mode, then paste the following lines into the file:

      /etc/rsyslog.d/99-haproxy.conf

      $AddUnixListenSocket /var/lib/haproxy/dev/log
      
      # Send HAProxy messages to a dedicated logfile
      :programname, startswith, "haproxy" {
        /var/log/haproxy.log
        stop
      }
      

      The $AddUnixListenSocket directive tells Rsyslog to create a Unix domain socket in the specified location, in this case /var/lib/haproxy/dev/log. The :programname, startswith, "haproxy" section specifies the file where Rsyslog will write the log entries to that it collects from the socket.

      Once you are finished editing /etc/rsyslog.d/99-haproxy.conf, save and close the file by pressing ESC, typing :wq, and pressing ENTER.

      You have now configured Rsyslog to read log entries from the Unix domain socket in /var/lib/haproxy/dev/log and write them to a log file in /var/log/haproxy.log.

      However, before restarting Rsyslog you will need to determine if SELinux is enforcing access control on your CentOS 8 system.

      To check SELinux’s current policy, run the following:

      You will receive one of the following outputs:

      • Enforcing – In this mode, SELinux is enforcing access controls on your system. You will need to complete the following optional Step 4 — Configuring SELinux section.
      • Permissive – In this case, SELinux logs all access attempts to its log file, but does not enforce access controls on your system.
      • Disabled – If SELinux is disabled, then it is not logging or enforcing any access control policies on your system.

      If the getenforce command returned either Permissive or Disabled, then you can restart Rsyslog with the following command:

      • sudo systemctl restart rsyslog

      Once you restart Rsyslog, you will be able to view logs in the /var/log/haproxy.log file that you configured in /etc/rsyslog.d/99-haproxy.conf. Proceed to Step 5 — Testing HAProxy Logging to make sure that everything is working as expected.

      Otherwise, if your system is running SELinux in Enforcing mode, then the next section of this tutorial explains how to add a module to allow Rsyslog and HAProxy to communicate with each other over their shared Unix domain socket.

      Step 4 — (Optional) Configuring SELinux

      If your CentOS 8 system is configured with SELinux in Enforcing mode, then you will need to allow Rsyslog access to HAProxy’s chroot directory. Allowing this access will let Rsyslog create the Unix domain socket that HAproxy will send its logs to.

      If you are not familiar with SELinux, this tutorial series An Introduction to SELinux on CentOS 7 will help you learn how to manage and interact with SELinux. Although it is written for CentOS 7, the principles and commands in the series are equally applicable to CentOS 8.

      To enable Rsyslog and HAProxy access to their shared socket, the first task is to create a Type Enforcement policy file. Open a new file called rsyslog-haproxy.te in vi or your preferred editor:

      Press i to switch to INSERT mode, then paste the following lines into the file:

      rsyslog-haproxy.te

      module rsyslog-haproxy 1.0;
      
      require {
          type syslogd_t;
          type haproxy_var_lib_t;
          class dir { add_name remove_name search write };
          class sock_file { create setattr unlink };
      }
      
      #============= syslogd_t ==============
      allow syslogd_t haproxy_var_lib_t:dir { add_name remove_name search write };
      allow syslogd_t haproxy_var_lib_t:sock_file { create setattr unlink };
      

      The first line defines the module name and version. The require portion tells the SELinux module loader about the types and classes that are required for the policy to be loaded as a module. The last two lines are the rules that allow Rsyslog access to HAProxy’s chroot and socket file respectively.

      When you are done editing the file, save and close it by pressing ESC, typing :wq, and pressing ENTER.

      Next, run the following command to install the checkpolicy package, which contains the checkmodule utility that you will use to turn the Type Enforcement file into an SELinux module.

      • sudo dnf install checkpolicy

      Now that you have the checkmodule tool installed, the next step is to compile the module and then load it into SELinux. Run the following to compile the Type Enforcement file into an SELinux module:

      • checkmodule -M -m rsyslog-haproxy.te -o rsyslog-haproxy.mod

      Next, run semodule_package to generate a complete policy package that SELinux can load into the Linux kernel:

      • semodule_package -o rsyslog-haproxy.pp -m rsyslog-haproxy.mod

      The final step is to load the package that you generated into the Linux kernel using the semodule command:

      • sudo semodule -i rsyslog-haproxy.pp

      Adding the module may take a few seconds. Once the command completes you can confirm that the module is loaded into the kernel by running the semodule command:

      • sudo semodule -l |grep rsyslog-haproxy

      You should receive output like the following:

      Output

      rsyslog-haproxy

      Once the module is loaded you can restart Rsyslog with the following command:

      • sudo systemctl restart rsyslog

      You have now defined, compiled, and loaded an SELinux policy that will allow HAProxy and Rsyslog to communicate over their shared socket.

      In the next step you will test that everything works by making an HTTP request to HAProxy and examining its new log file.

      Step 5 — Testing HAProxy Logging

      Now that you have configured HAProxy, Rsyslog, and optionally SELinux, you can test that logging to /var/log/haproxy.log is working.

      By default the haproxy package ships with a configuration file that creates an HTTP listener socket on port 5000. The configuration points to a non-existent backend server, so any request to the port will result in an HTTP 503 error.

      To check for a 503 error in your /var/log/haproxy.log file, first generate an HTTP request using curl like this:

      • curl -si http://127.0.0.1:5000

      You should receive output like the following:

      Output

      HTTP/1.0 503 Service Unavailable Cache-Control: no-cache Connection: close Content-Type: text/html <html><body><h1>503 Service Unavailable</h1> No server is available to handle this request. </body></html>

      Now examine /var/log/haproxy.log for any HTTP 503 responses using the grep command:

      • sudo grep -E ‘NOSRV.+503’ /var/log/haproxy.log

      Note: The NOSRV.+503 portion of the command is a regular expression. This tutorial on Using Grep & Regular Expressions to Search for Text Patterns in Linux
      goes into more depth on using grep and regular expressions.

      You should receive a line (or multiple lines) like the following:

      [secondary_label Output
      Sep 9 21:32:22 centos-s-1vcpu-1gb-nyc3-01 haproxy[4451]: 127.0.0.1:56024 [9/Sep/2020:21:32:22.098] main app/<NOSRV> 0/-1/-1/-1/0 503 212 - - SC-- 1/1/0/0/0 0/0 "GET / HTTP/1.1"
      

      This line corresponds to the curl request that you made, which means that Rsyslog and HAProxy are configured to use their shared socket correctly.

      Conclusion

      In this quickstart tutorial, you configured HAProxy to log to a Unix domain socket. You also set up Rsyslog to create and read from the socket so that the two programs can communicate with each other without opening up any TCP/IP ports on your system. Finally, you optionally compiled, packaged, and loaded an SELinux policy to allow Rsyslog and HAProxy shared access to their socket.



      Source link

      How To Set Up an Elasticsearch, Fluentd and Kibana (EFK) Logging Stack on 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:

      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:

      Output

      namespace/kube-logging created

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

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

      Output

      NAME 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:

      Output

      service/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:

      Output

      NAME 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:

      Output

      statefulset.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:

      Output

      Waiting 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:

      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:

      Output

      service/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:

      Output

      deployment "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

      Output

      NAME 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:

      Output

      Forwarding 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:

      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:

      Output

      serviceaccount/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:

      Output

      NAME 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:

      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.



      Source link

      How to Set Up an Elasticsearch, Fluentd and Kibana (EFK) Logging Stack on Kubernetes


      Introduction

      When running multiple services and applications on a Kubernetes cluster, a centralized, cluster-level logging stack can help you quickly sort through and analyze the heavy volume of log data produced by your Pods. One popular centralized logging solution is the Elasticsearch, Fluentd, and Kibana (EFK) stack.

      Elasticsearch is a real-time, distributed, and scalable search engine which allows for full-text and structured search, as well as analytics. It is commonly used to index and search through large volumes of log data, but can also be used to search many different kinds of documents.

      Elasticsearch is commonly deployed alongside Kibana, a powerful data visualization frontend and dashboard for Elasticsearch. Kibana allows you to explore your Elasticsearch log data through a web interface, and build dashboards and queries to quickly answer questions and gain insight into your Kubernetes applications.

      In this tutorial we’ll use Fluentd to collect, transform, and ship log data to the Elasticsearch backend. Fluentd is a popular open-source data collector that we’ll set up on our Kubernetes nodes to tail container log files, filter and transform the log data, and deliver it to the Elasticsearch cluster, where it will be indexed and stored.

      We’ll begin by configuring and launching a scalable Elasticsearch cluster, and then create the Kibana Kubernetes Service and Deployment. To conclude, we’ll set up Fluentd as a DaemonSet so it runs on every Kubernetes worker node.

      Prerequisites

      Before you begin with this guide, ensure you have the following available to you:

      • A Kubernetes 1.10+ cluster with role-based access control (RBAC) enabled

        • Ensure your cluster has enough resources available to roll out the EFK stack, and if not scale your cluster by adding worker nodes. We’ll be deploying a 3-Pod Elasticsearch cluster (you can scale this down to 1 if necessary), as well as a single Kibana Pod. Every worker node will also run a Fluentd Pod. The cluster in this guide consists of 3 worker nodes and a managed control plane.
      • The kubectl command-line tool installed on your local machine, configured to connect to your cluster. You can read more about installing kubectl in the official documentation.

      Once you have these components set up, you’re ready to begin with this guide.

      Step 1 — Creating a Namespace

      Before we roll out an Elasticsearch cluster, we’ll first create a Namespace into which we’ll install all of our logging instrumentation. Kubernetes lets you separate objects running in your cluster using a “virtual cluster” abstraction called Namespaces. In this guide, we’ll create a kube-logging namespace into which we’ll install the EFK stack components. This Namespace will also allow us to quickly clean up and remove the logging stack without any loss of function to the Kubernetes cluster.

      To begin, first investigate the existing Namespaces in your cluster using kubectl:

      kubectl get namespaces
      

      You should see the following three initial Namespaces, which come preinstalled with your Kubernetes cluster:

      Output

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

      The default Namespace houses objects that are created without specifying a Namespace. The kube-system Namespace contains objects created and used by the Kubernetes system, like kube-dns, kube-proxy, and kubernetes-dashboard. It’s good practice to keep this Namespace clean and not pollute it with your application and instrumentation workloads.

      The kube-public Namespace is another automatically created Namespace that can be used to store objects you’d like to be readable and accessible throughout the whole cluster, even to unauthenticated users.

      To create the kube-logging Namespace, first open and edit a file called kube-logging.yaml using your favorite editor, such as nano:

      Inside your editor, paste the following Namespace object YAML:

      kube-logging.yaml

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

      Then, save and close the file.

      Here, we specify the Kubernetes object's kind as a Namespace object. To learn more about Namespace objects, consult the Namespaces Walkthrough in the official Kubernetes documentation. We also specify the Kubernetes API version used to create the object (v1), and give it a name, kube-logging.

      Once you've created the kube-logging.yaml Namespace object file, create the Namespace using kubectl create with the -f filename flag:

      • kubectl create -f kube-logging.yaml

      You should see the following output:

      Output

      namespace/kube-logging created

      You can then confirm that the Namespace was successfully created:

      At this point, you should see the new kube-logging Namespace:

      Output

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

      We can now deploy an Elasticsearch cluster into this isolated logging Namespace.

      Step 2 — Creating the Elasticsearch StatefulSet

      Now that we've created a Namespace to house our logging stack, we can begin rolling out its various components. We'll first begin by deploying a 3-node Elasticsearch cluster.

      In this guide, we use 3 Elasticsearch Pods to avoid the "split-brain" issue that occurs in highly-available, multi-node clusters. At a high-level, "split-brain" is what arises when one or more nodes can't communicate with the others, and several "split" masters get elected. To learn more, consult “Avoiding split brain.”

      One key takeaway is that you should set the discover.zen.minimum_master_nodes Elasticsearch parameter to N/2 + 1 (rounding down in the case of fractional numbers), where N is the number of master-eligible nodes in your Elasticsearch cluster. For our 3-node cluster, this means that we'll set this value to 2. That way, if one node gets disconnected from the cluster temporarily, the other two nodes can elect a new master and the cluster can continue functioning while the last node attempts to rejoin. It's important to keep this parameter in mind when scaling your Elasticsearch cluster.

      Creating the Headless Service

      To start, we'll create a headless Kubernetes service called elasticsearch that will define a DNS domain for the 3 Pods. A headless service does not perform load balancing or have a static IP; to learn more about headless services, consult the official Kubernetes documentation.

      Open a file called elasticsearch_svc.yaml using your favorite editor:

      • nano elasticsearch_svc.yaml

      Paste in the following Kubernetes service YAML:

      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
      

      Then, save and close the file.

      We define a Service called elasticsearch in the kube-logging Namespace, and give it the app: elasticsearch label. We then set the .spec.selector to app: elasticsearch so that the Service selects Pods with the app: elasticsearch label. When we associate our Elasticsearch StatefulSet with this Service, the Service will return DNS A records that point to Elasticsearch Pods with the app: elasticsearch label.

      We then set clusterIP: None, which renders the service headless. Finally, we define ports 9200 and 9300 which are used to interact with the REST API, and for inter-node communication, respectively.

      Create the service using kubectl:

      • kubectl create -f elasticsearch_svc.yaml

      You should see the following output:

      Output

      service/elasticsearch created

      Finally, double-check that the service was successfully created using kubectl get:

      kubectl get services --namespace=kube-logging
      

      You should see the following:

      Output

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

      Now that we've set up our headless service and a stable .elasticsearch.kube-logging.svc.cluster.local domain for our Pods, we can go ahead and create the StatefulSet.

      Creating the StatefulSet

      A Kubernetes StatefulSet allows you to assign a stable identity to Pods and grant them stable, persistent storage. Elasticsearch requires stable storage to persist data across Pod rescheduling and restarts. To learn more about the StatefulSet workload, consult the Statefulsets page from the Kubernetes docs.

      Open a file called elasticsearch_statefulset.yaml in your favorite editor:

      • nano elasticsearch_statefulset.yaml

      We will move through the StatefulSet object definition section by section, pasting blocks into this file.

      Begin by pasting in the following block:

      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
      

      In this block, we define a StatefulSet called es-cluster in the kube-logging namespace. We then associate it with our previously created elasticsearch Service using the serviceName field. This ensures that each Pod in the StatefulSet will be accessible using the following DNS address: es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local, where [0,1,2] corresponds to the Pod's assigned integer ordinal.

      We specify 3 replicas (Pods) and set the matchLabels selector to app: elasticseach, which we then mirror in the .spec.template.metadata section. The .spec.selector.matchLabels and .spec.template.metadata.labels fields must match.

      We can now move on to the object spec. Paste in the following block of YAML immediately below the preceding block:

      elasticsearch_statefulset.yaml

      . . .
          spec:
            containers:
            - name: elasticsearch
              image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.3
              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.zen.ping.unicast.hosts
                  value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch"
                - name: discovery.zen.minimum_master_nodes
                  value: "2"
                - name: ES_JAVA_OPTS
                  value: "-Xms512m -Xmx512m"
      

      Here we define the Pods in the StatefulSet. We name the containers elasticsearch and choose the docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.3 Docker image. At this point, you may modify this image tag to correspond to your own internal Elasticsearch image, or a different version. Note that for the purposes of this guide, only Elasticsearch 6.4.3 has been tested.

      The -oss suffix ensures that we use the open-source version of Elasticsearch. If you'd like to use the default version containing X-Pack (which includes a free license), omit the -oss suffix. Note that you will have to modify the steps in this guide slightly to account for the added authentication provided by X-Pack.

      We then use the resources field to specify that the container needs at least 0.1 vCPU guaranteed to it, and can burst up to 1 vCPU (which limits the Pod's resource usage when performing an initial large ingest or dealing with a load spike). You should modify these values depending on your anticipated load and available resources. To learn more about resource requests and limits, consult the official Kubernetes Documentation.

      We then open and name ports 9200 and 9300 for REST API and inter-node communication, respectively. We specify a volumeMount called data that will mount the PersistentVolume named data to the container at the path /usr/share/elasticsearch/data. We will define the VolumeClaims for this StatefulSet in a later YAML block.

      Finally, we set some environment variables in the container:

      • cluster.name: The Elasticsearch cluster's name, which in this guide is k8s-logs.
      • node.name: The node's name, which we set to the .metadata.name field using valueFrom. This will resolve to es-cluster-[0,1,2], depending on the node's assigned ordinal.
      • discovery.zen.ping.unicast.hosts: This field sets the discovery method used to connect nodes to each other within an Elasticsearch cluster. We use unicast discovery, which specifies a static list of hosts for our cluster. In this guide, thanks to the headless service we configured earlier, our Pods have domains of the form es-cluster-[0,1,2].elasticsearch.kube-logging.svc.cluster.local, so we set this variable accordingly. Using local namespace Kubernetes DNS resolution, we can shorten this to es-cluster-[0,1,2].elasticsearch. To learn more about Elasticsearch discovery, consult the official Elasticsearch documentation.
      • discovery.zen.minimum_master_nodes: We set this to (N/2) + 1, where N is the number of master-eligible nodes in our cluster. In this guide we have 3 Elasticsearch nodes, so we set this value to 2 (rounding down to the nearest integer). To learn more about this parameter, consult the official Elasticsearch documenation.
      • ES_JAVA_OPTS: Here we set this to -Xms512m -Xmx512m which tells the JVM to use a minimum and maximum heap size of 512 MB. You should tune these parameters depending on your cluster's resource availability and needs. To learn more, consult Setting the heap size.

      The next block we'll paste in looks as follows:

      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
      

      In this block, we define several Init Containers that run before the main elasticsearch app container. These Init Containers each run to completion in the order they are defined. To learn more about Init Containers, consult the official Kubernetes Documentation.

      The first, named fix-permissions, runs a chown command to change the owner and group of the Elasticsearch data directory to 1000:1000, the Elasticsearch user's UID. By default Kubernetes mounts the data directory as root, which renders it inaccessible to Elasticsearch. To learn more about this step, consult Elasticsearch's “Notes for production use and defaults.”

      The second, named increase-vm-max-map, runs a command to increase the operating system's limits on mmap counts, which by default may be too low, resulting in out of memory errors. To learn more about this step, consult the official Elasticsearch documentation.

      The next Init Container to run is increase-fd-ulimit, which runs the ulimit command to increase the maximum number of open file descriptors. To learn more about this step, consult the “Notes for Production Use and Defaults” from the official Elasticsearch documentation.

      Note: The Elasticsearch Notes for Production Use also mentions disabling swapping for performance reasons. Depending on your Kubernetes installation or provider, swapping may already be disabled. To check this, exec into a running container and run cat /proc/swaps to list active swap devices. If you see nothing there, swap is disabled.

      Now that we've defined our main app container and the Init Containers that run before it to tune the container OS, we can add the final piece to our StatefulSet object definition file: the volumeClaimTemplates.

      Paste in the following volumeClaimTemplate block:

      elasticsearch_statefulset.yaml

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

      In this block, we define the StatefulSet's volumeClaimTemplates. Kubernetes will use this to create PersistentVolumes for the Pods. In the block above, we name it data (which is the name we refer to in the volumeMounts defined previously), and give it the same app: elasticsearch label as our StatefulSet.

      We then specify its access mode as ReadWriteOnce, which means that it can only be mounted as read-write by a single node. We define the storage class as do-block-storage in this guide since we use a DigitalOcean Kubernetes cluster for demonstration purposes. You should change this value depending on where you are running your Kubernetes cluster. To learn more, consult the Persistent Volume documentation.

      Finally, we specify that we'd like each PersistentVolume to be 100GiB in size. You should adjust this value depending on your production needs.

      The complete StatefulSet spec should look something like this:

      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-oss:6.4.3
              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.zen.ping.unicast.hosts
                  value: "es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch"
                - name: discovery.zen.minimum_master_nodes
                  value: "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
      

      Once you're satisfied with your Elasticsearch configuration, save and close the file.

      Now, deploy the StatefulSet using kubectl:

      • kubectl create -f elasticsearch_statefulset.yaml

      You should see the following output:

      Output

      statefulset.apps/es-cluster created

      You can monitor the StatefulSet as it is rolled out using kubectl rollout status:

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

      You should see the following output as the cluster is rolled out:

      Output

      Waiting 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...

      Once all the Pods have been deployed, you can check that your Elasticsearch cluster is functioning correctly by performing a request against the REST API.

      To do so, first forward the local port 9200 to the port 9200 on one of the Elasticsearch nodes (es-cluster-0) using kubectl port-forward:

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

      Then, in a separate terminal window, perform a curl request against the REST API:

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

      You shoulds see the following output:

      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" : { } } }, ...

      This indicates that our Elasticsearch cluster k8s-logs has successfully been created with 3 nodes: es-cluster-0, es-cluster-1, and es-cluster-2. The current master node is es-cluster-0.

      Now that your Elasticsearch cluster is up and running, you can move on to setting up a Kibana frontend for it.

      Step 3 — Creating the Kibana Deployment and Service

      To launch Kibana on Kubernetes, we'll create a Service called kibana, and a Deployment consisting of one Pod replica. You can scale the number of replicas depending on your production needs, and optionally specify a LoadBalancer type for the Service to load balance requests across the Deployment pods.

      This time, we'll create the Service and Deployment in the same file. Open up a file called kibana.yaml in your favorite editor:

      Paste in the following service spec:

      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-oss:6.4.3
              resources:
                limits:
                  cpu: 1000m
                requests:
                  cpu: 100m
              env:
                - name: ELASTICSEARCH_URL
                  value: http://elasticsearch:9200
              ports:
              - containerPort: 5601
      

      Then, save and close the file.

      In this spec we've defined a service called kibana in the kube-logging namespace, and gave it the app: kibana label.

      We've also specified that it should be accessible on port 5601 and use the app: kibana label to select the Service's target Pods.

      In the Deployment spec, we define a Deployment called kibana and specify that we'd like 1 Pod replica.

      We use the docker.elastic.co/kibana/kibana-oss:6.4.3 image. At this point you may substitute your own private or public Kibana image to use. We once again use the -oss suffix to specify that we'd like the open-source version.

      We specify that we'd like at the very least 0.1 vCPU guaranteed to the Pod, bursting up to a limit of 1 vCPU. You may change these parameters depending on your anticipated load and available resources.

      Next, we use the ELASTICSEARCH_URL environment variable to set the endpoint and port for the Elasticsearch cluster. Using Kubernetes DNS, this endpoint corresponds to its Service name elasticsearch. This domain will resolve to a list of IP addresses for the 3 Elasticsearch Pods. To learn more about Kubernetes DNS, consult DNS for Services and Pods.

      Finally, we set Kibana's container port to 5601, to which the kibana Service will forward requests.

      Once you're satisfied with your Kibana configuration, you can roll out the Service and Deployment using kubectl:

      • kubectl create -f kibana.yaml

      You should see the following output:

      Output

      service/kibana created deployment.apps/kibana created

      You can check that the rollout succeeded by running the following command:

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

      You should see the following output:

      Output

      deployment "kibana" successfully rolled out

      To access the Kibana interface, we'll once again forward a local port to the Kubernetes node running Kibana. Grab the Kibana Pod details using kubectl get:

      • kubectl get pods --namespace=kube-logging

      Output

      NAME 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

      Here we observe that our Kibana Pod is called kibana-6c9fb4b5b7-plbg2.

      Forward the local port 5601 to port 5601 on this Pod:

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

      You should see the following output:

      Output

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

      Now, in your web browser, visit the following URL:

      http://localhost:5601
      

      If you see the following Kibana welcome page, you've successfully deployed Kibana into your Kubernetes cluster:

      Kibana Welcome Screen

      You can now move on to rolling out the final component of the EFK stack: the log collector, Fluentd.

      Step 4 — Creating the Fluentd DaemonSet

      In this guide, we'll set up Fluentd as a DaemonSet, which is a Kubernetes workload type that runs a copy of a given Pod on each Node in the Kubernetes cluster. Using this DaemonSet controller, we'll roll out a Fluentd logging agent Pod on every node in our cluster. To learn more about this logging architecture, consult “Using a node logging agent” from the official Kubernetes docs.

      In Kubernetes, containerized applications that log to stdout and stderr have their log streams captured and redirected to JSON files on the nodes. The Fluentd Pod will tail these log files, filter log events, transform the log data, and ship it off to the Elasticsearch logging backend we deployed in Step 2.

      In addition to container logs, the Fluentd agent will tail Kubernetes system component logs like kubelet, kube-proxy, and Docker logs. To see a full list of sources tailed by the Fluentd logging agent, consult the kubernetes.conf file used to configure the logging agent. To learn more about logging in Kubernetes clusters, consult “Logging at the node level” from the official Kubernetes documentation.

      Begin by opening a file called fluentd.yaml in your favorite text editor:

      Once again, we'll paste in the Kubernetes object definitions block by block, providing context as we go along. In this guide, we use the Fluentd DaemonSet spec provided by the Fluentd maintainers. Another helpful resource provided by the Fluentd maintainers is Kubernetes Logging with Fluentd.

      First, paste in the following ServiceAccount definition:

      fluentd.yaml

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

      Here, we create a Service Account called fluentd that the Fluentd Pods will use to access the Kubernetes API. We create it in the kube-logging Namespace and once again give it the label app: fluentd. To learn more about Service Accounts in Kubernetes, consult Configure Service Accounts for Pods in the official Kubernetes docs.

      Next, paste in the following ClusterRole block:

      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
      

      Here we define a ClusterRole called fluentd to which we grant the get, list, and watch permissions on the pods and namespaces objects. ClusterRoles allow you to grant access to cluster-scoped Kubernetes resources like Nodes. To learn more about Role-Based Access Control and Cluster Roles, consult Using RBAC Authorization from the official Kubernetes documentation.

      Now, paste in the following ClusterRoleBinding block:

      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
      

      In this block, we define a ClusterRoleBinding called fluentd which binds the fluentd ClusterRole to the fluentd Service Account. This grants the fluentd ServiceAccount the permissions listed in the fluentd Cluster Role.

      At this point we can begin pasting in the actual DaemonSet spec:

      fluentd.yaml

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

      Here, we define a DaemonSet called fluentd in the kube-logging Namespace and give it the app: fluentd label.

      Next, paste in the following section:

      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:v0.12-debian-elasticsearch
              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: FLUENT_UID
                  value: "0"
      

      Here, we match the app: fluentd label defined in .metadata.labels and then assign the DaemonSet the fluentd Service Account. We also select the app: fluentd as the Pods managed by this DaemonSet.

      Next, we define a NoSchedule toleration to match the equivalent taint on Kubernetes master nodes. This will ensure that the DaemonSet also gets rolled out to the Kubernetes masters. If you don't want to run a Fluentd Pod on your master nodes, remove this toleration. To learn more about Kubernetes taints and tolerations, consult “Taints and Tolerations” from the official Kubernetes docs.

      Next, we begin defining the Pod container, which we call fluentd.

      We use the official v0.12 Debian image provided by the Fluentd maintainers. If you'd like to use your own private or public Fluentd image, or use a different image version, modify the image tag in the container spec. The Dockerfile and contents of this image are available in Fluentd's fluentd-kubernetes-daemonset Github repo.

      Next, we configure Fluentd using some environment variables:

      • FLUENT_ELASTICSEARCH_HOST: We set this to the Elasticsearch headless Service address defined earlier: elasticsearch.kube-logging.svc.cluster.local. This will resolve to a list of IP addresses for the 3 Elasticsearch Pods. The actual Elasticsearch host will most likely be the first IP address returned in this list. To distribute logs across the cluster, you will need to modify the configuration for Fluentd’s Elasticsearch Output plugin. To learn more about this plugin, consult Elasticsearch Output Plugin.
      • FLUENT_ELASTICSEARCH_PORT: We set this to the Elasticsearch port we configured earlier, 9200.
      • FLUENT_ELASTICSEARCH_SCHEME: We set this to http.
      • FLUENT_UID: We set this to 0 (superuser) so that Fluentd can access the files in /var/log.

      Finally, paste in the following section:

      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
      

      Here we specify a 512 MiB memory limit on the FluentD Pod, and guarantee it 0.1vCPU and 200MiB of memory. You can tune these resource limits and requests depending on your anticipated log volume and available resources.

      Next, we mount the /var/log and /var/lib/docker/containers host paths into the container using the varlog and varlibdockercontainers volumeMounts. These volumes are defined at the end of the block.

      The final parameter we define in this block is terminationGracePeriodSeconds, which gives Fluentd 30 seconds to shut down gracefully upon receiving a SIGTERM signal. After 30 seconds, the containers are sent a SIGKILL signal. The default value for terminationGracePeriodSeconds is 30s, so in most cases this parameter can be omitted. To learn more about gracefully terminating Kubernetes workloads, consult Google's “Kubernetes best practices: terminating with grace.”

      The entire Fluentd spec should look something like this:

      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:v0.12-debian-elasticsearch
              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: FLUENT_UID
                  value: "0"
              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
      

      Once you've finished configuring the Fluentd DaemonSet, save and close the file.

      Now, roll out the DaemonSet using kubectl:

      • kubectl create -f fluentd.yaml

      You should see the following output:

      Output

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

      Verify that your DaemonSet rolled out successfully using kubectl:

      • kubectl get ds --namespace=kube-logging

      You should see the following status output:

      Output

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

      This indicates that there are 3 fluentd Pods running, which corresponds to the number of nodes in our Kubernetes cluster.

      We can now check Kibana to verify that log data is being properly collected and shipped to Elasticsearch.

      With the kubectl port-forward still open, navigate to http://localhost:5601.

      Click on Discover in the left-hand navigation menu.

      You should see the following configuration window:

      Kibana Index Pattern Configuration

      This allows you to define the Elasticsearch indices you'd like to explore in Kibana. To learn more, consult Defining your index patterns in the official Kibana docs. For now, we'll just use the logstash-* wildcard pattern to capture all the log data in our Elasticsearch cluster. Enter logstash-* in the text box and click on Next step.

      You'll then be brought to the following page:

      Kibana Index Pattern Settings

      This allows you to configure which field Kibana will use to filter log data by time. In the dropdown, select the @timestamp field, and hit Create index pattern.

      Now, hit Discover in the left hand navigation menu.

      You should see a histogram graph and some recent log entries:

      Kibana Incoming Logs

      At this point you've successfully configured and rolled out the EFK stack on your Kubernetes cluster. To learn how to use Kibana to analyze your log data, consult the Kibana User Guide.

      In the next optional section, we'll deploy a simple counter Pod that prints numbers to stdout, and find its logs in Kibana.

      Step 5 (Optional) — Testing Container Logging

      To demonstrate a basic Kibana use case of exploring the latest logs for a given Pod, we'll deploy a minimal counter Pod that prints sequential numbers to stdout.

      Let's begin by creating the Pod. Open up a file called counter.yaml in your favorite editor:

      Then, paste in the following Pod spec:

      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']
      

      Save and close the file.

      This is a minimal Pod called counter that runs a while loop, printing numbers sequentially.

      Deploy the counter Pod using kubectl:

      • kubectl create -f counter.yaml

      Once the Pod has been created and is running, navigate back to your Kibana dashboard.

      From the Discover page, in the search bar enter kubernetes.pod_name:counter. This filters the log data for Pods named counter.

      You should then see a list of log entries for the counter Pod:

      Counter Logs in Kibana

      You can click into any of the log entries to see additional metadata like the container name, Kubernetes node, Namespace, and more.

      Conclusion

      In this guide we've demonstrated how to set up and configure Elasticsearch, Fluentd, and Kibana on a Kubernetes cluster. We've used a minimal logging architecture that consists of a single logging agent Pod running on each Kubernetes worker node.

      Before deploying this logging stack into your production Kubernetes cluster, it’s best to tune the resource requirements and limits as indicated throughout this guide. You may also want to use the X-Pack enabled image with built-in monitoring and security.

      The logging architecture we’ve used here consists of 3 Elasticsearch Pods, a single Kibana Pod (not load-balanced), and a set of Fluentd Pods rolled out as a DaemonSet. You may wish to scale this setup depending on your production use case. To learn more about scaling your Elasticsearch and Kibana stack, consult Scaling Elasticsearch.

      Kubernetes also allows for more complex logging agent architectures that may better suit your use case. To learn more, consult Logging Architecture from the Kubernetes docs.



      Source link