Pular para o conteúdo principal

Aula 14: Docker, Observabilidade e Deploy

Objetivos​

  • Containerizar o CritiqueHub com Docker usando multi-stage builds
  • Orquestrar serviços (aplicação + banco de dados) com Docker Compose
  • Configurar health checks (liveness e readiness) para monitoramento de disponibilidade
  • Implementar métricas de aplicação com Micrometer para monitoramento de performance
  • Rastrear requisições de ponta a ponta com tracing distribuído via OpenTelemetry
  • Estruturar logging e perfis de configuração para múltiplos ambientes (dev, test, prod)

Contexto e Motivação​

Até agora desenvolvemos o CritiqueHub localmente, num ambiente controlado com quarkus dev. Porém, uma aplicação profissional precisa ser empacotada, monitorada e distribuída de forma reproduzível. Se cada desenvolvedor configura seu ambiente de forma diferente, surgem os clássicos problemas de "funciona na minha máquina".

Docker resolve este problema criando ambientes idênticos e reproduzíveis. Combinado com health checks e métricas, temos uma aplicação production-ready — pronta para executar em qualquer infraestrutura, de um servidor local a um cluster Kubernetes.

O Caminho para Produção​

Conteúdo​

Docker — Fundamentos para Desenvolvedores Java​

O Que é um Container?​

Um container é um processo isolado que executa com seu próprio sistema de arquivos, rede e variáveis de ambiente. Diferente de uma máquina virtual, um container compartilha o kernel do sistema operacional host, sendo muito mais leve e rápido para iniciar.

AspectoVMContainer
IsolamentoCompleto (SO próprio)Processo isolado (compartilha kernel)
TamanhoGBsMBs
StartupMinutosSegundos (ou menos com Quarkus)
OverheadAltoMínimo
PortabilidadeLimitadaTotal

Imagem, Container e Registry​

Antes de escrever o Dockerfile, vale separar três conceitos que costumam se misturar:

  • Imagem: um artefato imutável com sistema de arquivos, runtime, bibliotecas e aplicação
  • Container: uma instância em execução de uma imagem, com processo, rede e estado temporário
  • Registry: um repositório de imagens, como Docker Hub, GitHub Container Registry ou Quay

Pense assim:

  • a imagem é a "classe"
  • o container é o "objeto" em execução
  • o registry é o "repositório de distribuição"

No fluxo desta aula, o time produz a imagem do CritiqueHub, executa um ou mais containers localmente e, em um cenário real, publicaria essa imagem em um registry para depois fazer deploy em staging ou produção.

Como o Docker Build Funciona​

Quando executamos docker build, o Docker envia um contexto de build para o daemon: normalmente, todo o conteúdo do diretório atual. Em seguida, ele processa o Dockerfile instrução por instrução e gera camadas reutilizáveis.

Isso tem duas consequências práticas:

  • a ordem das instruções importa para o cache
  • copiar arquivos demais para o contexto deixa o build mais lento e menos previsível

Por isso o padrão abaixo é tão importante:

  1. copiar primeiro arquivos de dependência, como pom.xml
  2. baixar dependências
  3. copiar o código-fonte
  4. empacotar a aplicação

Assim, se você alterar apenas uma classe Java, o Docker reaproveita as camadas anteriores e refaz apenas o necessário.

Por Que Quarkus Combina Bem com Containers?​

O Quarkus foi pensado para tempos curtos de startup e baixo consumo de memória, duas características valiosas em ambientes containerizados. Isso melhora:

  • escalabilidade horizontal, porque novos containers sobem mais rápido
  • custo operacional, porque a aplicação consome menos recursos
  • recuperação, porque reinícios ficam menos caros

No modo JVM, temos um caminho mais simples para desenvolvimento e produção. No modo nativo, ganhamos inicialização ainda mais rápida e menor footprint, em troca de builds mais lentos e pipeline mais exigente.

Dockerfile Multi-Stage para Quarkus​

O multi-stage build separa a compilação da execução, resultando em imagens menores e mais seguras:

Em termos práticos, ele resolve um problema comum em aplicações Java:

  • a fase de build precisa de Maven, JDK completo e dependências de compilação
  • a fase de runtime precisa só do resultado final já empacotado

Sem essa separação, a imagem final carrega ferramentas que não são usadas em produção. Com a separação, o container final fica mais enxuto, inicia mais rápido e expõe uma superfície de ataque menor.

O fluxo costuma seguir esta lógica:

  1. montar uma imagem de build com tudo o que é necessário para compilar
  2. gerar o artefato final da aplicação
  3. copiar apenas esse artefato para uma imagem runtime mínima
  4. executar a aplicação como processo principal do container

Isso também melhora o cache do Docker. Se você altera só uma classe ou só um arquivo de código, o estágio de runtime continua reaproveitando tudo o que não mudou no build anterior.

Em resumo:

  • o primeiro estágio compila
  • o segundo estágio executa
  • a imagem final fica menor, mais previsível e mais adequada para deploy
# ====== Estágio 1: Build ======
FROM maven:3.9-eclipse-temurin-21-alpine AS build
WORKDIR /app

# Cache de dependências — só rebuilda se pom.xml mudar
COPY pom.xml .
RUN mvn dependency:go-offline -B

# Build da aplicação
COPY src ./src
RUN mvn package -DskipTests -B

# ====== Estágio 2: Runtime ======
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app

# Copia apenas o artefato final — imagem runtime não tem Maven nem código-fonte
COPY --from=build /app/target/quarkus-app/ ./quarkus-app/

# Usuário não-root por segurança
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

EXPOSE 8080

# Health check integrado ao Docker
HEALTHCHECK --interval=30s --timeout=3s --start-period=15s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:8080/q/health/ready || exit 1

ENTRYPOINT ["java", \
"-Djava.util.logging.manager=org.jboss.logmanager.LogManager", \
"-jar", "quarkus-app/quarkus-run.jar"]

Anatomia do Dockerfile​

Observe o papel de cada instrução no exemplo:

  • FROM: define a imagem base de cada estágio
  • WORKDIR: define o diretório de trabalho dentro do container
  • COPY: copia arquivos do projeto para o sistema de arquivos da imagem
  • RUN: executa comandos no momento do build e gera uma nova camada
  • EXPOSE: documenta a porta usada pela aplicação
  • HEALTHCHECK: define como o Docker testa se o processo está pronto
  • ENTRYPOINT: define o processo principal do container

Em especial, ENTRYPOINT é o que transforma a imagem num container útil: quando o container sobe, é esse processo que precisa permanecer vivo. Se o processo terminar, o container também termina.

Construção e execução:

# Build da imagem JVM
docker build -t critiquehub:latest .

# Executar o container
docker run -d --name critiquehub \
-p 8080:8080 \
-e QUARKUS_DATASOURCE_JDBC_URL=jdbc:postgresql://host.docker.internal:5432/critiquehub \
critiquehub:latest

# Verificar logs
docker logs -f critiquehub

O Que Ganha Com o Multi-Stage Build?​

Se fizéssemos tudo em um único estágio, a imagem final carregaria Maven, cache de dependências e arquivos de build que não são necessários em runtime. O multi-stage build evita isso.

Benefícios diretos:

  • imagem menor: menos tempo de pull e menos armazenamento
  • menos superfície de ataque: menos ferramentas instaladas em produção
  • separação de responsabilidades: um estágio compila, o outro apenas executa
  • melhor legibilidade: fica claro onde termina o build e começa o runtime

Em Java, isso é ainda mais importante porque o ambiente de compilação costuma ser muito maior que o ambiente necessário para apenas rodar o .jar ou o binário nativo.

Alternativa: deixar o Quarkus construir a imagem

Escrever o Dockerfile manualmente é importante para entender o processo, mas o Quarkus também constrói imagens automaticamente com a extensão quarkus-container-image-jib (não requer nem Docker instalado para o build):

./mvnw package -Dquarkus.container-image.build=true \
-Dquarkus.container-image.group=critiquehub \
-Dquarkus.container-image.tag=1.0.0

O Jib aplica boas práticas por padrão: camadas otimizadas (dependências separadas do código), usuário não-root e imagem base enxuta. Em projetos reais, essa costuma ser a abordagem preferida.

Docker Compose — Orquestração Local​

O Docker Compose define e executa múltiplos containers como um serviço unificado:

# docker-compose.yml
# (o atributo "version:" é obsoleto desde o Compose V2 e deve ser omitido)

services:
# Banco de dados PostgreSQL
postgres:
image: postgres:16-alpine
container_name: critiquehub-db
environment:
POSTGRES_DB: critiquehub
POSTGRES_USER: critiquehub
POSTGRES_PASSWORD: critiquehub123
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U critiquehub"]
interval: 10s
timeout: 5s
retries: 5

# Aplicação CritiqueHub
app:
build:
context: .
dockerfile: Dockerfile
container_name: critiquehub-app
ports:
- "8080:8080"
environment:
QUARKUS_DATASOURCE_JDBC_URL: jdbc:postgresql://postgres:5432/critiquehub
QUARKUS_DATASOURCE_USERNAME: critiquehub
QUARKUS_DATASOURCE_PASSWORD: critiquehub123
QUARKUS_HIBERNATE_ORM_DATABASE_GENERATION: update
QUARKUS_PROFILE: prod
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/q/health/ready"]
interval: 30s
timeout: 5s
start_period: 30s
retries: 3

volumes:
postgres_data:

Como o Compose "cola" os Serviços​

O papel do Compose não é apenas subir vários containers, mas também descrever como eles se relacionam:

  • quem depende de quem
  • quais portas ficam expostas para a máquina host
  • quais variáveis de ambiente entram em cada serviço
  • quais volumes persistem dados entre reinícios
  • quais redes permitem comunicação entre containers

No exemplo da aula:

  • postgres fornece o banco de dados
  • app depende do postgres saudável antes de iniciar plenamente
  • o hostname postgres funciona porque ambos estão na mesma rede do Compose
  • o volume postgres_data evita perder os dados a cada reinício

Esse detalhe do hostname é central: dentro do Compose, a aplicação não se conecta a localhost para falar com o banco, e sim ao nome do serviço, postgres.

Portas, Rede e Volume na Prática​

Há três mecanismos que aparecem quase sempre em aplicações reais:

  • ports: publica uma porta do container para a máquina host, como 8080:8080
  • networking: permite que os serviços se descubram pelo nome dentro da rede interna do Compose
  • volumes: preserva dados fora do ciclo de vida do container

Sem volume, o banco perderia tudo quando o container fosse removido. Sem rede interna, a aplicação não conseguiria resolver postgres como host. Sem mapeamento de portas, você não conseguiria acessar a API ou a UI do Jaeger pelo navegador.

Build vs Image no Compose​

No serviço app, usamos:

build:
context: .
dockerfile: Dockerfile

Isso diz ao Compose para construir a imagem localmente a partir do projeto atual. Já no serviço postgres, usamos:

image: postgres:16-alpine

Aqui não há build local: estamos reutilizando uma imagem pronta publicada no registry. Esse contraste é comum:

  • serviços da aplicação: normalmente construídos pelo próprio projeto
  • infraestrutura padrão, como banco, cache e broker: geralmente consumidos como imagem pronta
# Iniciar tudo
docker compose up -d

# Ver status dos serviços
docker compose ps

# Seguir logs da aplicação
docker compose logs -f app

# Parar tudo
docker compose down

# Parar e remover volumes (dados do banco)
docker compose down -v

Leitura Operacional dos Comandos do Compose​

Os comandos do Compose têm significados diferentes, e vale explicitar isso para não tratar tudo como "subir container":

  • docker compose up -d: cria ou recria os serviços e os deixa em background
  • docker compose ps: mostra estado, portas e saúde dos containers
  • docker compose logs -f app: segue os logs de um serviço específico
  • docker compose down: remove os containers e a rede criada pelo Compose
  • docker compose down -v: faz o mesmo e ainda remove os volumes nomeados

Em aula, isso ajuda a diferenciar o que é estado descartável do container e o que é estado persistente do banco.

Health Checks — Liveness e Readiness​

Health checks permitem que orquestradores (Docker, Kubernetes) monitorem a saúde da aplicação e tomem ações automáticas (reiniciar, remover do load balancer).

Conceitos​

TipoPerguntaAção se falhar
Liveness"A aplicação está viva?"Reiniciar o container
Readiness"A aplicação pode receber requisições?"Remover do load balancer
Startup"A aplicação já iniciou?"Aguardar antes de verificar liveness

Dependência​

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-health</artifactId>
</dependency>

Com essa dependência, o Quarkus automaticamente expõe:

  • /q/health — status geral
  • /q/health/live — liveness checks
  • /q/health/ready — readiness checks
  • /q/health/started — startup checks

Health Checks Customizados​

@Liveness
@ApplicationScoped
public class CritiqueHubLivenessCheck implements HealthCheck {

@Override
public HealthCheckResponse call() {
return HealthCheckResponse.named("CritiqueHub Liveness")
.up()
.withData("versao", "1.0.0")
.withData("timestamp", Instant.now().toString())
.build();
}
}

@Readiness
@ApplicationScoped
public class BancoDeDadosReadinessCheck implements HealthCheck {

@Inject
EntityManager em;

@Override
public HealthCheckResponse call() {
try {
// Testa conexão com o banco
em.createNativeQuery("SELECT 1").getSingleResult();
return HealthCheckResponse.named("Banco de Dados")
.up()
.withData("tipo", "PostgreSQL")
.build();
} catch (Exception e) {
return HealthCheckResponse.named("Banco de Dados")
.down()
.withData("erro", e.getMessage())
.build();
}
}
}

Resposta do endpoint /q/health:

{
"status": "UP",
"checks": [
{
"name": "CritiqueHub Liveness",
"status": "UP",
"data": {
"versao": "1.0.0",
"timestamp": "2026-06-10T10:30:00Z"
}
},
{
"name": "Banco de Dados",
"status": "UP",
"data": {
"tipo": "PostgreSQL"
}
}
]
}

Métricas com Micrometer​

Métricas permitem monitorar o comportamento da aplicação em produção: quantas requisições por segundo, tempo médio de resposta, uso de memória, estado do pool de conexões.

Dependência​

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-micrometer-registry-prometheus</artifactId>
</dependency>

O endpoint /q/metrics é exposto automaticamente no formato Prometheus.

Métricas Automáticas​

O Quarkus + Micrometer registra automaticamente:

  • HTTP: requests_total, request_duration_seconds, requests_active
  • JVM: jvm_memory_used_bytes, jvm_gc_pause_seconds, jvm_threads_live
  • Pool de Conexões: agroal_active_count, agroal_available_count
  • Hibernate: hibernate_query_executions_total, hibernate_entities_loads_total

Métricas Customizadas​

@ApplicationScoped
public class AvaliacaoService {

@Inject
AvaliacaoRepository avaliacaoRepository;

// Contador — quantas avaliações foram criadas
private final Counter avaliacoesCriadas;

// Timer — quanto tempo leva a busca
private final Timer buscaTimer;

// Métricas são registradas uma única vez, via injeção por construtor
@Inject
public AvaliacaoService(MeterRegistry registry) {
this.avaliacoesCriadas = Counter.builder("critiquehub.avaliacoes.criadas")
.description("Total de avaliações criadas")
.tag("aplicacao", "critiquehub")
.register(registry);
this.buscaTimer = Timer.builder("critiquehub.avaliacoes.busca")
.description("Tempo de busca de avaliações")
.register(registry);
}

@Transactional
public Avaliacao criarAvaliacao(AvaliacaoDTO dto) {
Avaliacao avaliacao = new Avaliacao();
// ... mapeamento
avaliacaoRepository.persist(avaliacao);

avaliacoesCriadas.increment(); // registra métrica
return avaliacao;
}

public List<Avaliacao> buscarPorFilme(Long filmeId) {
return buscaTimer.record(() -> // mede tempo de execução
avaliacaoRepository.find("filme.id", filmeId).list()
);
}
}

Anotação @Timed e @Counted​

Para simplificar, use anotações:

@ApplicationScoped
public class FilmeService {

@Inject
FilmeRepository filmeRepository;

@Timed(value = "critiquehub.filmes.listagem", description = "Tempo de listagem de filmes")
public List<Filme> listarTodos() {
return filmeRepository.listAll();
}

@Counted(value = "critiquehub.filmes.buscas", description = "Total de buscas realizadas")
public List<Filme> buscar(String termo) {
return filmeRepository.buscarPorTermo(termo);
}
}

Tracing Distribuído com OpenTelemetry​

A observabilidade moderna se apoia em três pilares: logs (o que aconteceu), métricas (quanto/quão rápido) e traces (o caminho completo de uma requisição pelo sistema). O tracing responde perguntas que logs e métricas não conseguem: "por que esta requisição específica demorou 3 segundos?" — mostrando quanto tempo foi gasto em cada etapa (endpoint REST → serviço → consulta SQL).

PilarPergunta que respondeFerramenta nesta aula
LogsO que aconteceu?JBoss Logging + JSON
MétricasQuanto? Quão rápido? Com que frequência?Micrometer + Prometheus
TracesPor onde a requisição passou e onde demorou?OpenTelemetry

O OpenTelemetry (OTel) é o padrão aberto da CNCF para telemetria — o mesmo instrumentado aqui funciona com Jaeger, Grafana Tempo, Datadog ou qualquer backend compatível.

Dependência e Configuração​

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>
# Endpoint do coletor OTLP (Jaeger, Grafana Alloy, OTel Collector...)
quarkus.otel.exporter.otlp.traces.endpoint=http://localhost:4317

# Inclui traceId e spanId em cada linha de log — correlaciona logs com traces
quarkus.log.console.format=%d{HH:mm:ss} %-5p traceId=%X{traceId}, spanId=%X{spanId} [%c{2.}] %s%e%n

# Rastreia também as consultas SQL
quarkus.datasource.jdbc.telemetry=true

Com isso, toda requisição HTTP já é rastreada automaticamente — sem nenhuma alteração no código. Cada requisição gera um trace composto de spans (unidades de trabalho), propagados inclusive entre microsserviços via cabeçalhos HTTP (traceparent).

Spans Customizados​

Para rastrear métodos de negócio específicos, use @WithSpan:

@ApplicationScoped
public class RecomendacaoService {

// Cria um span filho dentro do trace da requisição atual
@WithSpan("calcular-recomendacoes")
public List<Filme> recomendarPara(@SpanAttribute("usuario.id") Long usuarioId) {
// ... lógica de recomendação
}
}

Visualizando Traces com Jaeger​

Adicione o Jaeger ao docker-compose.yml para visualizar os traces localmente:

jaeger:
image: jaegertracing/jaeger:2.4.0
container_name: critiquehub-jaeger
ports:
- "16686:16686" # UI web
- "4317:4317" # Receptor OTLP gRPC

Acesse http://localhost:16686, selecione o serviço critiquehub e explore: cada trace mostra a linha do tempo da requisição, com spans aninhados do endpoint REST até as queries SQL.

Dev Services

Em modo quarkus dev, basta adicionar a extensão quarkus-observability-devservices-lgtm para o Quarkus subir automaticamente um container com a stack Grafana LGTM (Loki + Grafana + Tempo + Prometheus) — logs, métricas e traces visualizáveis sem nenhuma configuração manual.

Perfis de Configuração​

O Quarkus suporta perfis de configuração que ativam diferentes valores com base no ambiente:

# application.properties

# ====== Configuração padrão (todos os perfis) ======
quarkus.application.name=critiquehub
quarkus.application.version=1.0.0

# ====== Perfil DEV (quarkus dev) ======
%dev.quarkus.datasource.db-kind=h2
%dev.quarkus.datasource.jdbc.url=jdbc:h2:mem:critiquehub
%dev.quarkus.hibernate-orm.database.generation=drop-and-create
%dev.quarkus.hibernate-orm.log.sql=true
%dev.quarkus.log.level=DEBUG
%dev.quarkus.log.category."br.edu.ifsp".level=DEBUG

# ====== Perfil TEST ======
%test.quarkus.datasource.db-kind=h2
%test.quarkus.datasource.jdbc.url=jdbc:h2:mem:critiquehub-test
%test.quarkus.hibernate-orm.database.generation=drop-and-create
%test.quarkus.log.level=WARN

# ====== Perfil PROD ======
%prod.quarkus.datasource.db-kind=postgresql
%prod.quarkus.datasource.jdbc.url=${DATABASE_URL}
%prod.quarkus.datasource.username=${DATABASE_USER}
%prod.quarkus.datasource.password=${DATABASE_PASSWORD}
%prod.quarkus.hibernate-orm.database.generation=none
%prod.quarkus.log.level=INFO
%prod.quarkus.log.console.json=true

# ====== Health & Metrics apenas em prod ======
%prod.quarkus.smallrye-health.ui.always-include=false
%prod.quarkus.micrometer.export.prometheus.enabled=true

O perfil é selecionado automaticamente:

  • quarkus dev → perfil dev
  • @QuarkusTest → perfil test
  • java -jar → perfil prod
  • Manual: java -Dquarkus.profile=staging -jar app.jar

Logging Estruturado​

Em produção, logs devem ser estruturados (JSON) para integração com ferramentas de análise (ELK, Grafana Loki):

# Logging JSON em produção
%prod.quarkus.log.console.json=true
%prod.quarkus.log.console.json.date-format=yyyy-MM-dd'T'HH:mm:ss.SSSZ
%prod.quarkus.log.console.json.additional-field.app.value=critiquehub
%prod.quarkus.log.console.json.additional-field.environment.value=${ENVIRONMENT:production}

Uso no código:

@ApplicationScoped
public class FilmeService {

// Log com io.quarkus.logging.Log (estático, zero-cost se desabilitado)
public Filme criar(FilmeDTO dto) {
Log.infof("Criando filme: titulo=%s, ano=%d", dto.titulo(), dto.anoLancamento());

Filme filme = new Filme();
// ... mapeamento
filme.persist();

Log.infof("Filme criado com sucesso: id=%d", filme.id);
return filme;
}
}

Saída JSON em produção:

{
"timestamp": "2026-06-10T10:30:00.123-0300",
"level": "INFO",
"loggerName": "b.e.i.c.service.FilmeService",
"message": "Filme criado com sucesso: id=42",
"app": "critiquehub",
"environment": "production"
}

Laboratório 11 — Containerizando o CritiqueHub​

Objetivo​

Containerizar o CritiqueHub completo com Docker, configurar Docker Compose com PostgreSQL, implementar health checks customizados, adicionar métricas de negócio e visualizar traces das requisições.

Passo 1 — Dockerfile​

Crie o Dockerfile multi-stage conforme os exemplos (modo JVM).

Passo 2 — Docker Compose​

Crie o docker-compose.yml com serviços postgres e app conforme os exemplos.

Passo 3 — Health Checks​

Adicione a dependência quarkus-smallrye-health e implemente:

  • BancoDeDadosReadinessCheck que valida a conexão
  • Um readiness check que verifica se existe pelo menos um gênero cadastrado (dados iniciais carregados)

Passo 4 — Métricas de Negócio​

Adicione a dependência quarkus-micrometer-registry-prometheus e implemente:

  • Contador de avaliações criadas por nota (1-5 estrelas)
  • Timer nas operações de busca do FilmeRepository
  • Gauge do total de filmes cadastrados

Passo 5 — Tracing​

Adicione a dependência quarkus-opentelemetry, inclua o serviço jaeger no docker-compose.yml e configure o endpoint OTLP. Atenção à rede: rodando via quarkus dev o endpoint é http://localhost:4317, mas com a aplicação dentro do Compose use o nome do serviço — QUARKUS_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: http://jaeger:4317 nas variáveis de ambiente do serviço app. Anote o método de busca de filmes com @WithSpan e verifique na UI do Jaeger (http://localhost:16686) o trace completo: requisição HTTP → span customizado → queries SQL.

Passo 6 — Perfis​

Configure os três perfis (dev, test, prod) no application.properties conforme exemplos.

Passo 7 — Validação​

# Subir os serviços
docker compose up -d

# Verificar saúde
curl http://localhost:8080/q/health | jq .

# Verificar métricas
curl http://localhost:8080/q/metrics | grep critiquehub

# Fazer algumas operações e verificar métricas novamente
curl -X POST http://localhost:8080/filmes -H "Content-Type: application/json" \
-d '{"titulo": "Matrix", "anoLancamento": 1999}'

curl http://localhost:8080/q/metrics | grep critiquehub

Exercícios​

  1. Crie um docker-compose.prod.yml que adicione um container Nginx como reverse proxy na frente da aplicação
  2. Implemente um health check que verifica se o serviço de e-mail (SMTP) está alcançável
  3. Adicione um Gauge que monitore o número de sessões ativas (usuários logados)
  4. Configure logging para arquivo rotacionado em produção (quarkus.log.file)
  5. Crie um perfil staging com configurações intermediárias entre dev e prod
  6. Implemente um endpoint /q/info customizado que retorne versão da aplicação, uptime e contagem de registros das entidades principais
  7. Compare o build manual com Dockerfile e o build via quarkus-container-image-jib: tamanho da imagem, número de camadas (docker history) e tempo de rebuild após alterar apenas uma classe
  8. Usando o Jaeger, identifique a requisição mais lenta do CritiqueHub e descubra, pelos spans, se o gargalo está no código Java ou nas queries SQL

Referências​