Un microservicio Quarkus no es solo un endpoint REST en un contenedor. Para sobrevivir en Kubernetes necesita responder preguntas fundamentales: ¿estás vivo? ¿estás listo para recibir tráfico? ¿cómo te comportas bajo carga?
Es aquí donde MicroProfile entra en escena. Esta especificación define las respuestas estándar a estas preguntas, y Quarkus las implementa de forma optimizada. A lo largo de este artículo vamos a crear un microservicio con todo lo necesario para funcionar en un entorno cloud-native:
- Crear un microservicio con Health, Metrics, Config y OpenAPI
- Escribir casos de prueba para validar todos los endpoints
- Configurar logging estructurado para producción
- Containerizar con Podman/Docker usando el Dockerfile de Quarkus
- Compilar un ejecutable nativo con Mandrel (~25x más rápido de arrancar)
- Entender cómo Kubernetes usa cada capacidad
Al final tendrás un microservicio listo para producción y entenderás por qué cada pieza es importante.
¿Qué hace «cloud-native» a un microservicio?
Kubernetes (K8s) es un orquestador de contenedores: decide dónde ejecutar tu aplicación, cuántas instancias levantar, y qué hacer si algo falla. Pero para tomar esas decisiones, necesita que tu aplicación le informe su estado.
Un contenedor que solo expone un endpoint REST no es suficiente. Kubernetes necesita saber:
| Pregunta | Especificación | Qué pasa si falla |
|---|---|---|
| ¿Está vivo? | Health (Liveness) | K8s reinicia el pod |
| ¿Está listo? | Health (Readiness) | K8s no le envía tráfico |
| ¿Terminó de arrancar? | Health (Startup) | K8s espera antes de verificar |
Además, para operar en producción necesitas:
| Capacidad | Especificación | Para qué |
|---|---|---|
| Monitoreo | Metrics | Prometheus alerta, Grafana muestra |
| Configuración externa | Config | Variables de entorno, ConfigMaps |
| Logs estructurados | (stdout + JSON) | Loki/ELK indexan y buscan |
Vamos a implementar todo esto en un microservicio simple.
Requisitos
- Java 17 ó 21 (soportadas por Quarkus)
- Maven 3.9 o superior
- Podman o Docker funcionando
- Quarkus CLI
¿No cumples todos los requisitos? Revisa Entorno de desarrollo ideal para Quarkus donde explicamos cómo instalar todo fácilmente.
Si usas Podman, te sugerimos revisar Usando Podman con Quarkus: la configuración esencial
Crear el proyecto
quarkus create app io.quarkiverso:microservicio-cloud \
--extension=rest,smallrye-health,micrometer-registry-prometheus,smallrye-openapi,logging-json \
--no-codeLa opción
--no-codeevita que Quarkus genere código de ejemplo, así empezamos con un proyecto limpio.
Esto incluye:
- REST: para exponer endpoints
- SmallRye Health: implementación de MicroProfile Health
- Micrometer Registry Prometheus: métricas en formato Prometheus
- SmallRye OpenAPI: documentación automática de la API
- Logging JSON: logs estructurados para producción
💡 ¿Por qué Micrometer y no SmallRye Metrics? Desde la versión 1.9, Quarkus recomienda Micrometer para la recolección de métricas. Aunque SmallRye Metrics implementa la especificación MicroProfile Metrics, Micrometer se alinea mejor con las necesidades actuales de entornos cloud y microservicios, ofreciendo mayor flexibilidad y compatibilidad con diferentes backends de monitoreo.
Entra al proyecto:
cd microservicio-cloudEl servicio: un saludo configurable
Ahora empecemos con algo simple: un endpoint que saluda. Sin embargo, no será un saludo cualquiera. En lugar de valores fijos, tendrá configuración externalizada y métricas automáticas.
Crea el archivo src/main/java/io/quarkiverso/SaludoResource.java:
package io.quarkiverso;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import io.micrometer.core.annotation.Counted;
import io.micrometer.core.annotation.Timed;
@Path("/saludo")
public class SaludoResource {
@ConfigProperty(name = "saludo.mensaje", defaultValue = "Hola")
String mensaje;
@ConfigProperty(name = "saludo.version", defaultValue = "1.0")
String version;
@GET
@Counted(value = "saludo_total", description = "Cuántas veces se llamó al endpoint")
@Timed(value = "saludo_tiempo", description = "Tiempo de respuesta del endpoint")
public String saludar(@QueryParam("nombre") String nombre) {
String quien = (nombre != null) ? nombre : "viajero";
return mensaje + ", " + quien + "! (v" + version + ")";
}
}Analicemos cada anotación:
@ConfigProperty: inyecta valores desdeapplication.propertieso variables de entorno@Counted: cuenta cuántas veces se invoca el método@Timed: mide el tiempo de ejecución
Crear el test
A continuación, crea el archivo src/test/java/io/quarkiverso/SaludoResourceTest.java:
package io.quarkiverso;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;
@QuarkusTest
class SaludoResourceTest {
@Test
void testSaludoEndpoint() {
given()
.when().get("/saludo?nombre=Quarkiverso")
.then()
.statusCode(200)
.body(containsString("Quarkiverso"));
}
@Test
void testHealthLive() {
given()
.when().get("/q/health/live")
.then()
.statusCode(200);
}
@Test
void testHealthReady() {
given()
.when().get("/q/health/ready")
.then()
.statusCode(200);
}
@Test
void testHealthStarted() {
given()
.when().get("/q/health/started")
.then()
.statusCode(200);
}
}Configuración externalizada
Ahora, edita src/main/resources/application.properties:
# Configuración del saludo
saludo.mensaje=Bienvenido al Quarkiverso
saludo.version=1.0
# En Kubernetes, estas propiedades vendrían de:
# - ConfigMap: SALUDO_MENSAJE, SALUDO_VERSIONEs importante entender cómo MicroProfile Config busca valores en múltiples fuentes. Si una propiedad está definida en varias, gana la de mayor precedencia:
| Precedencia | Fuente | Ejemplo |
|---|---|---|
| 1 (mayor) | System properties | -Dsaludo.mensaje=... |
| 2 | Variables de entorno | SALUDO_MENSAJE=... |
| 3 | application.properties | saludo.mensaje=... |
Gracias a este orden, en Kubernetes los ConfigMaps y Secrets se inyectan como variables de entorno, por lo que sobrescriben los valores de application.properties. Esto te permite empaquetar valores por defecto en tu aplicación y personalizarlos en cada entorno sin recompilar.
Health Checks
Por defecto, Quarkus expone automáticamente tres endpoints de salud:
| Endpoint | Pregunta | Si falla… |
|---|---|---|
/q/health/live | ¿Está vivo? | K8s reinicia el pod |
/q/health/ready | ¿Está listo? | K8s no le envía tráfico |
/q/health/started | ¿Terminó de arrancar? | K8s espera antes de verificar liveness |
Por defecto, los tres responden «UP» si la aplicación arrancó correctamente.
¿Cuándo usa cada uno?
- Liveness: detecta si la app está colgada (deadlock, loop infinito). Si falla, la reinicia.
- Readiness: detecta si la app puede procesar requests (ej: conexión a DB lista). Si falla, no le envía tráfico pero no la reinicia.
- Startup: para apps que tardan en arrancar. Evita que liveness las reinicie antes de tiempo.
Métricas
Quarkus expone métricas en /q/metrics en formato Prometheus/OpenMetrics gracias a Micrometer.
Las métricas incluyen:
- JVM: memoria, threads, GC (solo en modo JVM)
- HTTP: requests, tiempos de respuesta por endpoint
- Personalizadas: tus métricas con
@Counted,@Timed
El formato Prometheus/OpenMetrics es compatible con:
- Prometheus + Grafana (stack clásico)
- OpenShift Monitoring (usa Prometheus internamente)
- Datadog, New Relic, Dynatrace (scrapean formato Prometheus)
💡 ¿Y en modo nativo? Las métricas JVM reportan valores diferentes o cero (no hay carga dinámica de clases, por ejemplo). Sin embargo, tus métricas personalizadas con
@Countedy@Timedfuncionan exactamente igual.
OpenAPI y Swagger UI
La extensión SmallRye OpenAPI genera documentación automática:
/q/openapi→ especificación OpenAPI en YAML/q/swagger-ui→ interfaz visual para probar la API
Lo mejor es que no necesitas escribir nada: la documentación se genera automáticamente desde tus endpoints REST.
ℹ️ Swagger UI no está habilitado en producción por seguridad. El endpoint
/q/openapisigue disponible (útil para API Gateways), pero también puedes desactivarlo conquarkus.smallrye-openapi.enable=false.
Logging
Otro aspecto importante es el logging. En Kubernetes, los logs deben ir a stdout (no a archivos). Afortunadamente, Quarkus ya lo hace por defecto.
Ya incluimos la extensión logging-json al crear el proyecto. Para activar logs JSON solo en producción (y mantener texto plano en desarrollo) lo configuramos de la siguiente manera:
# Desarrollo: logs en texto plano (más legibles)
%dev.quarkus.log.console.json.enabled=false
# Producción: la extensión logging-json habilita JSON por defectoAsí en quarkus dev verás logs legibles, pero en contenedores (que corren en modo prod por defecto) tendrás JSON estructurado. Cada línea de log será un objeto JSON que se puede indexar y buscar. Esto funciona con:
- Loki (stack por defecto en OpenShift)
- ELK/EFK (Elasticsearch + Fluentd/Filebeat + Kibana)
- Splunk, Datadog, y otros
Sin esta configuración, los logs son texto plano (útil para desarrollo, pero menos práctico para producción).
Ejecutar y probar
Vamos a poner en marcha el microservicio con:
quarkus devCon la aplicación corriendo, es momento de explorar lo que construimos. Para ello, abre otra terminal y prueba cada capacidad.
El endpoint principal
curl "http://localhost:8080/saludo?nombre=Quarkiverso"Respuesta:
Bienvenido al Quarkiverso, Quarkiverso! (v1.0)Health Checks
# Liveness: ¿está vivo?
curl http://localhost:8080/q/health/live
# Readiness: ¿está listo?
curl http://localhost:8080/q/health/ready
# Startup: ¿terminó de arrancar?
curl http://localhost:8080/q/health/started
# Todos juntos
curl http://localhost:8080/q/healthRespuesta de ejemplo:
{
"status": "UP",
"checks": []
}Métricas
# Todas las métricas (formato Prometheus)
curl http://localhost:8080/q/metricsUna vez que llames al endpoint varias veces, verás métricas como:
# HELP saludo Cuántas veces se llamó al endpoint
# TYPE saludo counter
saludo_total{class="io.quarkiverso.SaludoResource",method="saludar",result="success"} 5.0
# HELP saludo_tiempo_seconds Tiempo de respuesta del endpoint
# TYPE saludo_tiempo_seconds summary
saludo_tiempo_seconds_count{class="io.quarkiverso.SaludoResource",method="saludar"} 5.0
saludo_tiempo_seconds_sum{class="io.quarkiverso.SaludoResource",method="saludar"} 1.91333E-4OpenAPI
Abre en el navegador en http://localhost:8080/q/swagger-ui
Allí verás tu API documentada con la opción de probarla directamente. Ejecuta el siguiente comando para verla en formato YAML.
curl http://localhost:8080/q/openapiConfiguración con variables de entorno
Una de las ventajas de MicroProfile Config es que puedes cambiar el comportamiento de tu aplicación sin tocar una línea de código. Veamos cómo funciona esto en la práctica. Detén quarkus dev (presiona ‘q’ en la terminal) y ejecuta esta línea:
SALUDO_MENSAJE="Hola desde K8s" SALUDO_VERSION="2.0" quarkus devDe esta manera, el endpoint responde con los nuevos valores de configuración. Así es exactamente como Kubernetes la inyectaría.
Containerizar con Podman
Hasta ahora ejecutamos en modo desarrollo (Dev Mode). No obstante, es hora de empaquetar nuestra aplicación como imagen de contenedor. Quarkus genera automáticamente varios Dockerfiles en src/main/docker/:
| Archivo | Para qué | Tamaño aprox. |
|---|---|---|
Dockerfile.jvm | JAR en JVM (rápido de construir) | ~400MB |
Dockerfile.native | Ejecutable nativo sobre UBI minimal | ~150MB |
Dockerfile.native-micro | Nativo sobre imagen micro (sin shell ni utilidades) | ~50MB |
Construir la imagen
# Primero, construir el JAR
quarkus build
# Luego, construir la imagen con Podman
podman build -f src/main/docker/Dockerfile.jvm \
-t quarkiverso/microservicio-cloud .
Ejecutar el contenedor
podman run -d --name microservicio-jvm \
-p 8081:8080 \
-e SALUDO_MENSAJE="Hola desde JVM" \
quarkiverso/microservicio-cloud💡 Usamos el puerto 8081 para no interferir con
quarkus devque usa 8080.
Probar el contenedor
En este punto, puedes verificar que todo funciona como lo haría Kubernetes:
# El endpoint principal
curl "http://localhost:8081/saludo?nombre=Podman"
# Health checks (lo que K8s consultaría)
curl http://localhost:8081/q/health/live
curl http://localhost:8081/q/health/ready
curl http://localhost:8081/q/health/started
# Métricas (lo que Prometheus scrapearía)
curl http://localhost:8081/q/metrics
Ver los logs
podman logs microservicio-jvmVerás cada línea como JSON estructurado (el modo prod activa logging-json por defecto).
Ejecutable nativo con Mandrel
Hasta ahora usamos el modo JVM, donde tu aplicación corre sobre la máquina virtual de Java. Si bien funciona correctamente, hay otra forma de viajar por el Quarkiverso: como un ejecutable nativo.
Un binario nativo no necesita JVM. Como resultado, arranca en milisegundos, consume menos memoria, y resulta perfecto para entornos donde los recursos son preciados.
Mandrel es una distribución de GraalVM optimizada para Quarkus, mantenida por Red Hat. A diferencia de GraalVM, Mandrel incluye solo lo necesario para compilar ejecutables nativos de aplicaciones Quarkus, sin componentes extras.
Compilar nativo
quarkus build --native -Dquarkus.native.container-build=true💡 La opción
container-build=truecompila dentro de un contenedor Linux usando Mandrel. De esta manera, no necesitas instalar GraalVM o Mandrel localmente y el ejecutable será compatible con Docker/Podman.
Construir la imagen nativa
podman build -f src/main/docker/Dockerfile.native-micro \
-t quarkiverso/microservicio-cloud:native .Como resultado, la imagen native-micro usa una base minimalista (~50MB total).
Ejecutar el contenedor nativo
podman run -d --name microservicio-native \
-p 8082:8080 \
-e SALUDO_MENSAJE="Hola desde nativo" \
quarkiverso/microservicio-cloud:nativeComparar tiempos de arranque
Con ambos contenedores corriendo (JVM en 8081, nativo en 8082), ejecuta:
podman logs microservicio-jvm | grep started
# JVM: started in 0.256s
podman logs microservicio-native | grep started
# Nativo: started in 0.010s (¡25 veces más rápido!)Probar ambos microservicios
curl "http://localhost:8081/saludo" # JVM
curl "http://localhost:8082/saludo" # NativoCómo Kubernetes usa todo esto
Hasta este punto, hemos construido un microservicio con health checks, métricas, configuración externalizada y logging estructurado. Ahora bien, veamos cómo todas estas piezas encajan cuando tu aplicación viaja al Quarkiverso de producción.
A continuación, verás cómo en un deployment de Kubernetes cada capacidad que implementamos tiene un propósito concreto:
apiVersion: apps/v1
kind: Deployment
metadata:
name: microservicio-cloud
spec:
template:
spec:
containers:
- name: app
image: quarkiverso/microservicio-cloud
# Configuración desde ConfigMap
envFrom:
- configMapRef:
name: saludo-config
# Startup: espera a que arranque antes de verificar liveness
startupProbe:
httpGet:
path: /q/health/started
port: 8080
failureThreshold: 30
periodSeconds: 2
# Liveness: si falla, reinicia el pod
livenessProbe:
httpGet:
path: /q/health/live
port: 8080
periodSeconds: 10
# Readiness: si falla, no le envía tráfico
readinessProbe:
httpGet:
path: /q/health/ready
port: 8080
periodSeconds: 5
# Puerto para métricas (Prometheus las scrapea)
ports:
- containerPort: 8080
Además, necesitarás un ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: saludo-config
data:
SALUDO_MENSAJE: "Hola desde producción"
SALUDO_VERSION: "1.5"
Resumen
Has recorrido un largo camino. Tu microservicio ya no es un simple endpoint: ahora es un ciudadano del Quarkiverso, preparado para sobrevivir y prosperar en Kubernetes.
| Capacidad | Endpoint / Config | Uso |
|---|---|---|
| ¿Terminó de arrancar? | /q/health/started | K8s startupProbe |
| ¿Está vivo? | /q/health/live | K8s livenessProbe |
| ¿Está listo? | /q/health/ready | K8s readinessProbe |
| Monitoreo | /q/metrics | Prometheus scrape |
| Configuración | Variables de entorno | K8s ConfigMap/Secret |
| Logs | stdout + JSON | Loki/ELK/Splunk |
| Ejecutable nativo | Mandrel | Arranque en 0.010s |
| Documentación | /q/openapi | Especificación OpenApi |
Cada una de estas capacidades es una estrella en la constelación cloud-native. Juntas, permiten que tu microservicio navegue el Quarkiverso de producción con total confianza.
Próximos pasos
Como usamos puertos diferentes, puedes tener todo corriendo en paralelo:
- 8080:
quarkus dev(Swagger UI, Dev UI, hot reload) - 8081: contenedor JVM
- 8082: contenedor nativo
Para seguir explorando en modo desarrollo:
- Prueba la API desde Swagger UI: http://localhost:8080/q/swagger-ui
- Explora el Dev UI de Quarkus: http://localhost:8080/q/dev-ui
💡 Swagger UI y Dev UI solo están disponibles en modo desarrollo (puerto 8080).
Código fuente: github.com/quarkiverso/microservicio-cloud

