Introdução
Muitas organizações possuem um ambiente distribuído e adotam tecnologias distribuídas em nuvem para entregar uma melhor experiência para o usuário, com escalabilidade e eficiência. Porém, embora adotar tecnologias em nuvem e de microsserviços trará agilidade na entrega de funcionalidades, também irá aumentar consideravelmente a complexidade operacional de sustentar a operação.
Por isso foram criados processos e ferramentas como infrastructure as code, que permitem às equipes de SRE (Software Reliabiliy Engineering) automatizar a criação e o gerenciamento de ambientes. Contudo, entregar somente a infraestrutura não é suficiente. Isso porque com a entrega de cada microsserviço também é necessário acompanhar a saúde e o desempenho daquela aplicação no ambiente produtivo. Equipes de SRE e de desenvolvimento precisam criar dashboards e alarmes para acompanhar as aplicações e usá-los de forma proativa para mitigar possíveis problemas que possam impactar a experiência do usuário. E, quando ocorrem incidentes em produção, essas ferramentas devem auxiliar na correção rápida dos problemas.
Criar esses dashboards de acompanhamento e alarmes para cada microsserviço não é uma tarefa simples e requer muito cuidado, porque “buracos” na monitoração podem causar um desastre. Ambientes distribuídos podem falhar de maneiras imprevisíveis; por isso é necessária a instrumentação de todo o ambiente, facilitando a identificação de problemas de performance e falhas de forma proativa.
Por isso a observabilidade do ambiente produtivo é muito importante e não pode ser deixada de lado. Mas como fazer isso de maneira rápida, padronizada e evolutiva?
Spoiler alert: observability as code (observabilidade como código)!
Mas o que é observabilidade como código?
Assim como infraestrutura como código aplica práticas de engenharia de software para configurar e gerenciar infraestruturas, a observabilidade como código permite a times de SRE e de desenvolvimento criarem dashboards de acompanhamento e alarmes de forma consistente e acompanharem o estado e o comportamento das aplicações em todos os ambientes.
Ou seja, a criação de dashboards e alarmes não será feita manualmente após a entrega em produção do microsserviço. A monitoração codificada segue os ciclos de desenvolvimento. Ao entregar uma aplicação, o código da observabilidade é ativado, criando dashboards e alarmes. Usando boas práticas de programação, é possível e recomendável reutilizar o código fonte para várias aplicações. Assim, toda vez que for necessário evoluir o dashboard ou alarme para adicionar novas informações, basta evoluir o código e aplicá-lo. Pronto, todas as aplicações serão contempladas com as novas ferramentas de observabilidade! Simples, não é mesmo?
Como fazer?
Um ponto importante antes de começar a explicar como utilizar é escolher uma ferramenta de monitoração que tenha uma API que permita você criar e editar itens de monitoração de forma automática.
Nesse artigo irei demonstrar o básico da observabilidade com código. Para isso irei usar uma ferramenta muito popular: Terraform. A vantagem de usar essa ferramenta é que ela é amplamente utilizada como infraestrutura como código. Ou seja, se você já criou alguma infraestrutura com o Terraform não terá nenhum problema em criar observabilidade com código.
Se você nunca usou Terraform recomendo ler esses artigos:
Quando trabalhamos com sistemas distribuídos, nem todos os microsserviços possuem o mesmo grau de maturidade de monitoração. Isso deixa lacunas de monitoração, nas quais os desenvolvedores, SREs e plantonistas às cegas. Em um ambiente distribuído é impossível prever onde podem acontecer falhas e degradação. Caso ocorra um problema em um serviço que possui lacunas de monitoração (por exemplo, não tem monitorada sua taxa de erros, ou seu throughput), isso pode ter consequências desastrosas para o ambiente de produção, onde a queda de um serviço pode levar a degradação ou até mesmo queda de outros serviços.
Para me ajudar nisso, eu apliquei a observabilidade como código, criei com Terraform dashboards e alarmes padrões que eu quero que todas as aplicações possuam. Seguindo o framework SRE, criei códigos Terraform baseados nos Golden Signals da observabilidade. Criei um job parametrizável no Jenkins e o adicionei no job padrão de deploy. Com isso, todo serviço, desde o primeiro delivery, já possui um dashboard padrão e os alarmes principais. Mas isso ainda não é o suficiente. A observabilidade é uma escada sem fim. Mas com a observabilidade como código dei um primeiro e importante passo para a padronização e redução de lacunas de monitoração.
Hands-on
Para começar a usar observabilidade como código o primeiro passo é criar um arquivo provider.tf
Como o exemplo abaixo:
provider "newrelic" {
account_id = var.newrelic_account_id
api_key = var.newrelic_api_key
}
terraform {
required_providers {
newrelic = {
version = "~> 2.30.0"
}
}
}
No código acima estamos declarando o provider do New Relic e o configurando com a conta correspondente à chave de acesso. O provider é responsável por transformar o código Terraform nas chamadas de API do New Relic para criar os recursos declarados.
Depois disso, por boa prática de desenvolvimento, é importante salvarmos o tfsate
do Terraform em um bucket do S3.
terraform {
backend "s3" {
bucket = "nome_do_bucket"
key = "caminho_tfstate/terraform.state"
region = "sa-east-1"
profile = "profile"
}
}
Com isso pronto, agora iremos criar um arquivo chamado dashboard.tf
que será responsável em criar os dashboards de monitoração da aplicação.
resource "newrelic_one_dashboard" "app_dashboard" {
name = "App GoldenSignals" //aqui declaramos o nome do dashboard no new relic
account_id = var.newrelic_account_id //Id da conta onde será criado o dashboard
permissions = "public_read_write" #permissões de leitura escrita no dash board.
page { //aqui criamos uma aba dentro do dashboard
name = "Application Monitoring" # nome da aba
widget_line {# criamos um gráfico de linha
title = "APDEX Score"#nome do gráfico
row = 1 #quantidade de linhas
column = 1 #quantidade de colunas
width = 3 # comprimento
nrql_query { # Consulta NRQL que irá popular o gráfico, aqui um exemplo para mostrar o apdex da aplicação
query = format("SELECT apdex(apm.service.apdex) as 'App server', apdex(apm.service.apdex.user) as 'End user' FROM Metric WHERE (entity.guid = '%s') SINCE 1 hour AGO TIMESERIES", data.newrelic_entity.app_APM.guid)
}
}
widget_billboard { #Criando um gráfico que mostra a disponibilidade
title = "Availability"
row = 1
column = 4
width = 3
nrql_query {
query = format("SELECT percentage(count(*), WHERE httpResponseCode <= '500') as '%s Availability' FROM Transaction WHERE appName = '%s' AND httpResponseCode IS NOT NULL AND request.headers.userAgent NOT IN ('ELB-HealthChecker/2.0')", "%", var.newrelic_application_name)
}
}
}
}
Pronto, criamos nosso primeiro dashboard. Agora é importante criarmos um alarme para nos avisar quando a aplicação não está saudável. Para isso vamos criar um arquivo alert.tf
.
resource "newrelic_alert_policy" "policy_error_rate" {
name = "policy error"
incident_preference = "PER_CONDITION_AND_TARGET" #política irá alarmar para cada condition
}
resource "newrelic_alert_channel" "slack_channel" { #criando canal do slack que será enviando notificação em caso o gatilho desse alarme seja acionado
name = "slack-channel-example"
type = "slack"
config {
channel = "#example-channel"
url = "http://example-org.slack.com"
}
}
resource "newrelic_alert_policy_channel" "error_rate_alarm_channel" { #nesse block fazemos associação do canal do slack com a policy criada
policy_id = newrelic_alert_policy.policy_error_rate.id
channel_ids = [
newrelic_alert_channel.slack_channel.id
]
}
resource "newrelic_nrql_alert_condition" "alarm_error_rate" { # nesse block criamos o alarme
policy_id = newrelic_alert_policy.policy_error_rate.id
name = "nome do alarme" # nome do alarme
enabled = true # alarme habilitado
type = "static" #tipo do alarme, pode ser static ou baseline
description = "descrição do alarme"
runbook_url = "https://www.example.com" #url de runbook
violation_time_limit_seconds = 3600
fill_option = "static"
fill_value = 1.0
aggregation_window = 60
aggregation_method = "event_flow"
aggregation_delay = 120
expiration_duration = 120
open_violation_on_expiration = true
close_violations_on_expiration = true
slide_by = 30
nrql {# consulta nrql que irá alarmar
query = "format("SELECT percentage(count(*), WHERE httpResponseCode >= '500') FROM Transaction WHERE appName = '%s' AND httpResponseCode IS NOT NULL", var.newrelic_application_name)"
}
critical { #nesse block é declarado os thresholds para alarmar em estado critico
operator = "above"
threshold = 5.5
threshold_duration = 300
threshold_occurrences = "ALL"
}
warning { #nesse block é declarado os thresholds para alarmar em estado de atenção
operator = "above"
threshold = 3.5
threshold_duration = 600
threshold_occurrences = "ALL"
}
}
Pronto, agora temos o canal do alarme criado e o dashboard. Esse foi um “Hello World” de observabilidade como código. Muita coisa pode ser feita usando recursos avançados do Terraform, como módulos, blocos dinamicos, etc.