terça-feira, 7 de janeiro de 2020

SpringBoot - Métricas customizadas com Micrometer






A história:


Com o uso mais frequente de microsserviços, e a arquitetura baseada em eventos (mensageria), métricas de aplicações são importantes para que saibamos status básico dos componentes. Tais componentes, por vezes, encontram-se distribuídos em vários clusters, orquestrados em contêineres, que dificultam e alteram o modelo tradicional de monitoramento de indicadores operacionais das aplicações. 


Assim, perguntamos a seguir : Métricas básicas de aplicações, tais como threads, requests, tempo de resposta, são suficiente para atender a necessidade de negócio? Esse é o tema que vamos aprofundar, mostrando como gerar métricas customizadas em uma aplicação SpringBoot 2.x. 


O monitoramento básico de componentes computacionais e de aplicação não são suficientes, e não será o nosso alvo, reconhecemos que alguns indicadores ainda são relevantes ao time de infraestrutura, mas apresentar ao business owner (BO) tais indicadores não agregam valor e são tratados como comodity.


Necessidade:


Fazemos parte de um time site reliability engineer (SRE), o qual orienta soluções aderentes aos times de desenvolvimento, e a demanda por nós recebida foi de apresentarmos dashboards de negócio com o ciclo do pedido, um monitoramento capaz de produzir alertas quando um determinado pedido não evoluísse de estado em um determinado intervalo de tempo. 


Não podemos considerar sempre a criação de novos modelos, ou como se diz reinventar a roda, até mesmo pela importância de estabelecermos e após mantermos um padrão independente da stack de telemetria/monitoramento em uso. Com isto, ao decorrer do tempo observamos que em um módulo de negócio dos quais atendemos, com diversos microsserviços, uma stack de telemetria se destacava, pelo uso de um padrão como procurávamos, sua estabilidade e simplicidade de implementação. 


Deixamos aqui os méritos ao Marco Pokorski Stefani (https://www.linkedin.com/in/marcops) que implantou o padrão, ao Douglas Rossi (https://www.linkedin.com/in/douglasrossi) o qual estendeu em métricas customizadas de negócio, e ao César Mesquita (https://www.linkedin.com/in/cmesquita00) responsável pela concepção de arquitetura de telemetria do módulo.

Stack:  





  Componentes: 
Infraestrutura
Aplicação
SpringBoot (https://spring.io/)


Fluxo de funcionamento:  


Utilizamos soluções de continuous integration (CI), continuous deployment (CD) do cloud provider[1], onde durante o processo de publicação descritores, testes e dependências são satisfeitas em uma esteira[2]. Veja que em um ambiente distribuído não podemos concentrar o monitoramento no modelo tradicional, pois hora estaremos com 1 réplica da aplicação ativa, e outrora com mais, valores que são controlados pelo horizontal pod autoscaler (HPA)[3]. 


Dessa forma, cada réplica enviará suas métricas ao InfluxDB[4], e por fim estes dados serão consolidados com o uso do Grafana[5], este responsável pela apresentação, agregação e produção de alertas para os times.



Atualmente para envio de algumas métricas ainda não customizadas diretamente na aplicação utilizamos coletores externos que coletam os dados de origens distintas, tais como message broker, data lakes. Tais coletores implementam a mesma stack citada: Segue exemplo de funcionamento do e gatilhos para os alertas:


O coletor analisa um grupo de dados, em que para cada evento há uma atualização de compra. Para cada compra será incrementado o contador do estado atual correspondente. Quando o coletor analisar todas as atualizações de compras, fará o download de mais X atualizações de compras.




















No Grafana alertas são gerados quando:


  • O número de pedidos que estão ou já estiveram  no estado “solicitado” for menor do que o número de pedidos que estão ou já estiveram no estado “computado”;
  • O número de pedidos que estão ou já estiveram  no estado “embrulhado” for menor do que o número de pedidos que estão ou já estiveram no estado “pago”;
  • A somatória de pedidos que estão ou já estiveram no estado “pago” e “embrulhado” for menor que o número de pedidos que estão ou já estiveram no estado “disponível”;
  • O número de pedidos que estão ou já estiveram no estado “disponível” for menor que o número de pedidos que estão ou já estiveram no estado “entregue”.






Implementação:  

Iniciamos pela descrição breve do Actuator, uma simples que satisfaz o que precisamos aqui é de uma publicação no Medium (https://medium.com/projuristech/monitorando-uma-aplica%C3%A7%C3%A3o-spring-boot-2-x-cef826ae793c) que segue: 


“É uma biblioteca do próprio Spring para coletar métricas, entender o tráfego e o estado da aplicação.”
Assim como o Actuator, o envio de métricas utilizando o Micrometer é gerenciado pelo SpringBoot, logo não precisamos nos preocupar com dependências para métricas padrões do Actuator. 


Para implantarmos o Actuator devemos adicionar a dependência ‘spring-boot-starter-actuator’ ao projeto. Seja ele Maven ou Grandle. Após isto inclua no bootstrap, config-server o seguinte em seu YAML:


Properties config-server ou bootstrap:

management:
  metrics:
    export:
      influx:
        db: db_dev (aqui é a database onde serão persistidas as métricas do Actuator)
        uri: http://127.0.0.1:8086 (coloque aqui a URL do InfluxDB do seu ambiente)
        step: 1m (intervalo de envio)
        enabled: true


Classe de exemplo de métrica customizada:
package org.athome.example.metrics;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import org.athome.example.model.Order;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;

@Component
public class OrdersCustomMetrics {

@Autowired
private MeterRegistry registry;
public void CounterOrder(Order order, String reason) {
Counter.builder("Orders")
  .description("counter of orders")
  .tag("orderType", order.getOrder.Type())
  .tag("reason", reason)
  .tag("Id", order.getOrderId())
  .register(registry)
  .increment(1);
}
}





O uso da classe deve ser realizado por interação na implementação dos estados do pedido. Com isto, poderemos gerar alertas de total de pedidos e pedidos inválidos por razões de negócio - quanto se alguma ordem não mudar de estado em X tempo.  



O framework também permite o uso de outros tipos de métricas, tais como gauge, histogram, entre outros. Permite também enviar métricas para outras soluções como Elastic, Datadog, Dynatrace. O importante para métricas customizadas é saber o tipo de dado e tipo de métrica que irá expor.

Para maiores detalhes acesse: https://micrometer.io/docs/concepts#_meters

Conclusão e atualidade:
  


Conforme citado anteriormente, nem todas as aplicações geram métricas customizadas em tempo de execução. Por isto, ainda não conseguimos receber as alterações de estados no tempo em que ocorrem, precisando assim fazer uso de outros coletores com certo delay (atraso), consultando também dados analíticos, e observando a interação de eventos em um message broker. 


Ainda assim o product owner (PO) responsável pelos módulos está vinculando às novas entregas do produto stories que incluem a implementação de métricas customizadas como critério de aceitação para que não sejam necessários futuramente os coletores. 






Fontes







Sobre os autores



* Gabriel Prestes, formado em Gestão da Tecnologia da Informação, DevOps Engineer da ilegra, é um entusiasta de carros antigos que cultiva em sua garagem um Gurgel Supermini funcionando.


* Thamires Tissot, estudante de Analise e Desenvolvimento de Sistemas, Desenvolvedora na ilegra e ceramista nas horas vagas.

terça-feira, 18 de junho de 2019

Elastic Stack + Beats e como tudo isso funciona no Kubernetes

Começo explicando o que é o Elastic Stack e o que são os Beats, parece falar sobre mais do mesmo, mas algumas pessoas não sabem seu funcionamento real e arquitetura, e ainda que deem esse questionamento como respondido, erram principalmente na aplicação dos Beats Packages, disponibilizando-os neste mundo com IaC (InfraasCode) na mesma role e aplicando ao mesmo server de destino, o que não é conveniente, uma vez que faz mais sentido aplicar os Beats nos servers que enviarão informações ao ElasticSearch/LogStash.   

Elastic Stack se entende aqui por ELK ou EFK + Beats packages. Tais nomenclaturas significam: 

ELK: ElasticSearch + LogStash + Kibana
EFK: ElasticSearch + Fluentd + Kibana

Beats Packages:




Até o momento e já exposto aqui, uma das motivações da publicação é a correta aplicação da stack + beats, ou seja, os componentes dos beats precisamos instalar no servidor ou serviço que queremos monitorar, cujo destino das métricas, logs, eventos e etc é o ELK ou EFK. 

A segunda motivação é a implantação do Filebeat no Kubernetes, o material disponível que atende em grande parte a configuração de um cluster baremetal e com a imagem da versão 6.x do Filebeat orienta a configuração de coleta por daemonset por type : log e para coletar os STDOUT e STDERR dos contêineres/pods monitoram logs dos nodos, exemplo ‘/var/lib/containers…’. O problema que me ocorreu aqui foi que o AKS (Managed Service Kubernetes da Azure) não se comportou muito bem com isso, não obtendo alguns logs de outros namespaces. 

Confesso que não fui afundo nesse evento para descobrir uma alternativa, então foquei na versão 8.x da imagem do Filebeat e uma feature de ‘hints’ baseados em autodiscovery para o Kubernetes. 

A partir dessa feature criei uma configuração válida que atende o AKS, assim como permite através dos annotations do Kubernetes fazer o multiline por aplicação, ou até mesmo deixar uma configuração comum entre os pods caso não exista uma definida no annotations. 
Como ficou o yaml a ser aplicado via helm ou kubectl: 
_______
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-config
  namespace: kube-system
  labels:
    k8s-app: filebeat
data:
  filebeat.yml: |-
    filebeat.config:
      inputs:
        # Mounted `filebeat-inputs` configmap:
        path: ${path.config}/inputs.d/*.yml
        # Reload inputs configs as they change:
        reload.enabled: false
      modules:
        path: ${path.config}/modules.d/*.yml
        # Reload module configs as they change:
        reload.enabled: false

    filebeat.autodiscover:

      providers:

        - type: kubernetes
          hints.enabled: true
          include_annotations: '*'
          templates:
              config:
                - type: docker
                  containers.ids:
                    - "${data.kubernetes.container.id}"

    processors:
      - add_cloud_metadata:

    cloud.id: ${ELASTIC_CLOUD_ID}
    cloud.auth: ${ELASTIC_CLOUD_AUTH}

    output.elasticsearch:
      hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-inputs
  namespace: kube-system
  labels:
    k8s-app: filebeat
data:
  kubernetes.yml: ""
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: filebeat
  namespace: kube-system
  labels:
    k8s-app: filebeat
spec:
  template:
    metadata:
      labels:
        k8s-app: filebeat
    spec:
      serviceAccountName: filebeat
      terminationGracePeriodSeconds: 30
      containers:
      - name: filebeat
        image: docker.elastic.co/beats/filebeat:8.0.0
        args: [
          "-c", "/etc/filebeat.yml",
          "-e",
        ]
        env:
        - name: ELASTICSEARCH_HOST
          value: meuelastic.athome.org
        - name: ELASTICSEARCH_PORT
          value: "9200"
        - name: ELASTIC_CLOUD_ID
          value:
        - name: ELASTIC_CLOUD_AUTH
          value:
        securityContext:
          runAsUser: 0
        resources:
          limits:
            memory: 400Mi
          requests:
            cpu: 200m
            memory: 200Mi
        volumeMounts:
        - name: config
          mountPath: /etc/filebeat.yml
          readOnly: true
          subPath: filebeat.yml
        - name: inputs
          mountPath: /usr/share/filebeat/inputs.d
          readOnly: true
        - name: data
          mountPath: /usr/share/filebeat/data
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      volumes:
      - name: config
        configMap:
          defaultMode: 0600
          name: filebeat-config
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: inputs
        configMap:
          defaultMode: 0600
          name: filebeat-inputs
      - name: data
        hostPath:
          path: /var/lib/filebeat-data
          type: DirectoryOrCreate
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: filebeat
subjects:
- kind: ServiceAccount
  name: filebeat
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: filebeat
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: filebeat
  labels:
    k8s-app: filebeat
rules:
- apiGroups: [""]
  resources:
  - namespaces
  - pods
  verbs:
  - get
  - watch
  - list
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: filebeat
  namespace: kube-system
  labels:
    k8s-app: filebeat
---
_______

Para a parte de deploy dos PODs basta incluir as annotations conforme o multiline desejado: 

_______


apiVersion: v1

kind: Pod
metadata:
name: order-commerce-service
annotations:
 co.elastic.logs/multiline.pattern: '^\['
 co.elastic.logs/multiline.negate: true
 co.elastic.logs/multiline.match: after
_______

É comum não saber qual o multiline se aplica por aplicação, para isto a Elastic disponibiliza um simulador e patterns e uma documentação bem completa, explicando comportamento de negate e match. Abaixo compartilho estes links que apoiarão o time de desenvolvimento nesse processo, ou até mesmo se quiser definir um template de config no hint do Kubernetes: 

Simulador: 


Documentação do multiline: 


Exemplos de multiline: 

https://www.elastic.co/guide/en/beats/filebeat/master/_examples_of_multiline_configuration.html

Caso queira utilizar PacketBeat, AuditBeat, MetricBeat, ou outros no Kubernetes o processo em vias práticas é bem semelhante, com ressalva ao conteúdo e ajustes dos configmaps, daemonset e imagem utilizados. Mas não muda o cenário e nem mesmo o fluxo. 

sexta-feira, 8 de dezembro de 2017

Orquestradores - Aplicações e preocupações

 Presumo nesta publicação que já entendas de contêineres, caso precise temos um bom material para apresentar este conceito e passos iniciais, é a publicação do colega Maicon Baum que constará nas referências deste artigo.

 Independente do orquestrador que estejas utilizando, sendo os mais comuns Kubernetes ou Docker Swarm, os maiores problemas das plataformas são como dimensionar recursos para que atendam aos contratos com módulos ou microsserviços das aplicações que hospedam. 

 Não tenhas a ambição que os orquestradores resolverão automaticamente, ou por padrão de instalação, dimensionamento de aplicações, e ainda que o façam, este ponto envolve uma análise de regras operacionais das aplicações, viabilidade de escalonamento e expansão da própria plataforma, ou até mesmo a relação de custo e retorno das aplicações envolvidas. 

 É por essa razão que devemos nos atentarmos para limites e políticas sejam elas por contêiner, ou de forma mais inteligente por service (Docker Swarm) ou NameSpace (K8s). 

 Com relação a política, seja namespace ou stack, deves considerar condições que satisfaçam o bom  uso da plataforma quando relacionada às limitações de seu código.    

 De nada adianta liberar diversos processadores para uso de uma aplicação NodeJS se a mesma não foi desenvolvida para trabalhar multithread. Logo, isso força a mesma medida que permite a integração cada dia maior do desenvolvedor com o analista de infraestrutura da plataforma. Cito permite, pois proporciona a ambos um ganho de conhecimento. 

 Mencionaremos aqui como aplicar estes limites na prática nos orquestradores citados: Docker Swarm e Kubernetes. 

 IMPORTANTE: Em ambos é possível realizar a limitação por contêiner no Dockerfile, entretanto este não é o modelo mais eficiente quando estamos trabalhando com orquestradores e suas réplicas. 

 Docker Swarm

 Método(s) disponível(is) : Por serviço

 Aplicação: Na publicação do stack ou na criação, ou atualização individual dos serviços poderás utilizar restrições ou limitações de recursos.

 No site de documentação do produto existem exemplos de aplicação, seja criando um serviço ou atualizando um. 

 https://docs.docker.com/engine/reference/commandline/service_create



Kubernetes 

 Método(s) disponível(is) : 

 Resource Quotas

Quality of Service


Aplicação: O Kubernetes dispõe de um modelo mais eficiente e granular de administração de recursos, então temos dois modelos, um de QoS(quality of service) e outro de Resource Quota. 

  Documentação para aplicação dos métodos: 

 https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod
https://kubernetes.io/docs/concepts/policy/resource-quotas

 Um item não menos relevante é o gerenciamento de logs de sua aplicação, ainda que o limite de armazenamento de log possa ser administrado pelo orquestrador, isso demanda IO (entrada e saída) e por consequência deixar aplicações gravando informações desnecessárias e/ou com baixa criticidade afeta a performance da plataforma como um todo, lembre-se, seja na cloud pública, privada ou híbrida, IO é performance, e se tens bastante performance é porque também custa dinheiro. 


 Referências: 

 http://ilegra.com/beyonddata/2017/07/docker-for-beginners/

 Palavras chave de busca: 

 docker swarm k8s kubernetes container contêiner limites restrições constraints limits devops

  Agradecimentos:

 Agradeço ao engenheiro de infraestrutura, arquiteto de software e amigo Lincolm Aguiar (https://www.linkedin.com/in/lincolmaguiar) que foi parceiro de apresentação do talkabout que foi o conteúdo norteador e originou essa publicação.