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