<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Juan Manuel Hoyos</title>
    <description>The latest articles on DEV Community by Juan Manuel Hoyos (@juanhcode).</description>
    <link>https://dev.to/juanhcode</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2723973%2F9b114e4b-f776-4dc9-881a-a6c12611eec7.jpeg</url>
      <title>DEV Community: Juan Manuel Hoyos</title>
      <link>https://dev.to/juanhcode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/juanhcode"/>
    <language>en</language>
    <item>
      <title>CI/CD en AWS: Lab práctico para automatizar el despliegue de sitios web estáticos S3, CloudFront, CodePipeline y CodeBuild</title>
      <dc:creator>Juan Manuel Hoyos</dc:creator>
      <pubDate>Fri, 27 Mar 2026 20:41:54 +0000</pubDate>
      <link>https://dev.to/juanhcode/cicd-en-aws-lab-practico-para-automatizar-el-despliegue-de-sitios-web-estaticos-s3-cloudfront-11dc</link>
      <guid>https://dev.to/juanhcode/cicd-en-aws-lab-practico-para-automatizar-el-despliegue-de-sitios-web-estaticos-s3-cloudfront-11dc</guid>
      <description>&lt;h2&gt;
  
  
  1. &lt;strong&gt;Introducción&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Hoy en día, los equipos de desarrollo buscan automatizar el despliegue de aplicaciones para reducir errores manuales y acelerar las entregas. En este laboratorio construiremos un pipeline de CI/CD que desplegará automáticamente un sitio web estático cada vez que se realicen cambios en el repositorio.&lt;/p&gt;

&lt;p&gt;Utilizaremos los siguientes servicios de AWS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amazon S3&lt;/strong&gt;: Para alojar nuestro sitio web estático.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CodeBuild&lt;/strong&gt;: Para construir y empaquetar nuestro sitio web.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CodePipeline&lt;/strong&gt;: Para orquestar el proceso de CI/CD.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon CloudFront&lt;/strong&gt;: Para distribuir nuestro sitio web a nivel global.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Al final del laboratorio, cualquier cambio en el repositorio se desplegará automáticamente en el sitio web.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;strong&gt;Arquitectura de la Solución&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;La arquitectura del sistema es sencilla pero poderosa. Cada vez que se actualiza el repositorio, se ejecuta automáticamente un pipeline que construye y despliega la aplicación.&lt;/p&gt;

&lt;p&gt;Flujo del despliegue:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobe6th2c4tp7rr6ocf5z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobe6th2c4tp7rr6ocf5z.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. &lt;strong&gt;Crear el bucket para el sitio web&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Primero debemos crear el bucket que almacenará los archivos del sitio.&lt;/p&gt;

&lt;p&gt;Pasos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ir a la consola de Amazon S3.&lt;/li&gt;
&lt;li&gt;Hacer clic en "Create bucket".&lt;/li&gt;
&lt;li&gt;Ingresar un nombre único para el bucket (ejemplo: &lt;code&gt;mi-sitio-web-estatico&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Seleccionar la región deseada.&lt;/li&gt;
&lt;li&gt;Configurar las opciones de bucket según tus necesidades.&lt;/li&gt;
&lt;li&gt;Hacer clic en "Create bucket".&lt;/li&gt;
&lt;li&gt;Desactivar el bloqueo de acceso público para permitir que el sitio sea accesible públicamente.&lt;/li&gt;
&lt;li&gt;Configurar el bucket para alojar un sitio web estático:

&lt;ul&gt;
&lt;li&gt;Ir a la pestaña "Properties" del bucket.&lt;/li&gt;
&lt;li&gt;Hacer clic en "Static website hosting".&lt;/li&gt;
&lt;li&gt;Seleccionar "Use this bucket to host a website".&lt;/li&gt;
&lt;li&gt;Ingresar &lt;code&gt;index.html&lt;/code&gt; como documento de índice.&lt;/li&gt;
&lt;li&gt;Guardar los cambios.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Configurar las políticas de bucket para permitir el acceso público a los archivos del sitio web:

&lt;ul&gt;
&lt;li&gt;Ir a la pestaña "Permissions" del bucket.&lt;/li&gt;
&lt;li&gt;Hacer clic en "Bucket Policy".&lt;/li&gt;
&lt;li&gt;Agregar la siguiente política, reemplazando &lt;code&gt;mi-sitio-web-estatico&lt;/code&gt; con el nombre de tu bucket:
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PublicReadGetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::your-bucket-name/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. &lt;strong&gt;Crear la distribución CDN&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Para mejorar el rendimiento y la distribución global utilizaremos Amazon CloudFront.&lt;/p&gt;

&lt;p&gt;Configuración básica:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Origen: El bucket de S3 que acabamos de crear.&lt;/li&gt;
&lt;li&gt;Viewer protocol policy: Redirect HTTP to HTTPS&lt;/li&gt;
&lt;li&gt;Default root object: index.html (obligatorio para evitar errores 403/404)
Una vez creada la distribución, obtendremos un dominio como:
&lt;code&gt;https://xxxxx.cloudfront.net&lt;/code&gt; que será la URL de nuestro sitio web.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. &lt;strong&gt;Fork del repositorio de ejemplo&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Para este laboratorio, utilizaremos un repositorio de ejemplo que contiene el código de nuestro sitio web estático. &lt;/p&gt;

&lt;p&gt;Pasos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ve a &lt;a href="https://github.com/juanhcode/astro-static-site-aws-cicd" rel="noopener noreferrer"&gt;https://github.com/juanhcode/astro-static-site-aws-cicd&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Haz clic en el botón "Fork" en la esquina superior derecha.&lt;/li&gt;
&lt;li&gt;Selecciona tu cuenta de GitHub como destino del fork.&lt;/li&gt;
&lt;li&gt;Clona tu fork en tu máquina local:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/tu-usuario/astro-static-site-aws-cicd.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. &lt;strong&gt;Crear el pipeline&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Ahora crearemos el pipeline usando AWS CodePipeline para automatizar el flujo completo de despliegue.&lt;/p&gt;

&lt;p&gt;Paso a paso:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ir a la consola de CodePipeline.&lt;/li&gt;
&lt;li&gt;Seleccionar Create pipeline.&lt;/li&gt;
&lt;li&gt;Elegir Custom pipeline.&lt;/li&gt;
&lt;li&gt;Ingresar el nombre del pipeline (ejemplo: web-static-astro-pipeline).&lt;/li&gt;
&lt;li&gt;Dejar la configuración por defecto (rol y artifact store).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Etapa 1: Source&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proveedor: GitHub (a través de GitHub App)&lt;/li&gt;
&lt;li&gt;Repositorio: Repositorio: tu fork del repositorio astro-static-site-aws-cicd&lt;/li&gt;
&lt;li&gt;Rama: main&lt;/li&gt;
&lt;li&gt;Activar detección de cambios (CI automático)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Etapa 2: Build&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proveedor: AWS CodeBuild&lt;/li&gt;
&lt;li&gt;Crear un proyecto llamado: website-static-astro&lt;/li&gt;
&lt;li&gt;Usar un archivo buildspec.yml en la raíz del repositorio&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Etapa 3: Deploy&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proveedor: Amazon S3&lt;/li&gt;
&lt;li&gt;Bucket: site-astro-static&lt;/li&gt;
&lt;li&gt;Input artifact: BuildArtifact&lt;/li&gt;
&lt;li&gt;Activar opción: Extract file before deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esto permite que los archivos del sitio web se publiquen directamente en el bucket.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. &lt;strong&gt;Invalidar cache de CloudFront&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Después del despliegue, es necesario limpiar el cache de Amazon CloudFront para que los cambios sean visibles inmediatamente.&lt;br&gt;
Esto puede hacerse agregando un comando en el build:&lt;/p&gt;

&lt;p&gt;Mejor práctica: usar variables de entorno&lt;br&gt;
En lugar de hardcodear el ID de la distribución, se recomienda usar variables de entorno en CodeBuild.&lt;/p&gt;

&lt;p&gt;Paso 1: Definir variable en CodeBuild&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ir a la consola de CodeBuild.&lt;/li&gt;
&lt;li&gt;Editar el proyecto con el nombre que le diste.&lt;/li&gt;
&lt;li&gt;En la sección "Environment variables", agregar una variable llamada &lt;code&gt;CLOUDFRONT_ID&lt;/code&gt; con el valor del ID de tu distribución de CloudFront.&lt;/li&gt;
&lt;li&gt;Asegúrate de seleccionar el runtime adecuado para tu proyecto. Revisa la &lt;a href="https://docs.aws.amazon.com/codebuild/latest/userguide/available-runtimes.html" rel="noopener noreferrer"&gt;documentación oficial de runtimes disponibles&lt;/a&gt; para elegir la versión que mejor se adapte a tu entorno.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paso 2: Agregar comando de invalidación en buildspec.yml&lt;br&gt;
Si estás utilizando el repositorio de ejemplo, el buildspec.yml ya tiene la variable definida, pero si no, asegúrate de agregarla en tu buildspec.yml despues de la sección de build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;post_build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Invalidando cache de CloudFront..."&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_ID --paths "/*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paso 3: Configurar permisos&lt;br&gt;
Asegúrate de que el rol de servicio de CodeBuild tenga permisos para ejecutar la acción &lt;code&gt;cloudfront:CreateInvalidation&lt;/code&gt;. Esto se puede hacer agregando la siguiente política al rol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cloudfront:CreateInvalidation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:cloudfront::account-id:distribution/distribution-id"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  8. &lt;strong&gt;Probar el pipeline&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Para probar el pipeline, realiza un cambio en el repositorio (por ejemplo, modifica cualquier archivo dentro de &lt;code&gt;src&lt;/code&gt;) y haz un commit. Esto debería activar automáticamente el pipeline, que construirá y desplegará el sitio web actualizado. Puedes monitorear el progreso del pipeline en la consola de CodePipeline para asegurarte de que todo se ejecute correctamente. Si el pipeline falla, revisa los logs de CodeBuild para identificar y solucionar cualquier error.&lt;/p&gt;

&lt;p&gt;Ejemplo pipeline exitoso:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhgff31ydi3rdxpo263pz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhgff31ydi3rdxpo263pz.png" alt=" " width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  9. &lt;strong&gt;Verificar el despliegue&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Una vez que el pipeline se ejecute correctamente, puedes verificar que tu sitio web esté desplegado accediendo a la URL proporcionada por CloudFront:&lt;br&gt;
&lt;code&gt;https://xxxxx.cloudfront.net&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  10. &lt;strong&gt;Resultados finales&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Después de configurar el pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Se realiza un commit en el repositorio&lt;/li&gt;
&lt;li&gt;CodePipeline inicia automáticamente&lt;/li&gt;
&lt;li&gt;CodeBuild construye el proyecto&lt;/li&gt;
&lt;li&gt;Los archivos se despliegan en S3&lt;/li&gt;
&lt;li&gt;CloudFront entrega la nueva versión del sitio
El sitio queda disponible en:
&lt;code&gt;https://xxxxx.cloudfront.net&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  11. &lt;strong&gt;Conclusión&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;En este laboratorio construimos un pipeline de CI/CD completamente funcional utilizando servicios administrados de AWS. Este enfoque permite automatizar los despliegues, mejorar la consistencia entre entornos y reducir errores humanos.&lt;/p&gt;

&lt;p&gt;La combinación de Amazon S3, Amazon CloudFront, AWS CodePipeline y AWS CodeBuild proporciona una solución simple pero poderosa para desplegar sitios web estáticos de forma automática.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>cicd</category>
      <category>webdev</category>
    </item>
    <item>
      <title>🔍 ¿Tu aplicación funciona… pero no sabes qué pasa dentro?</title>
      <dc:creator>Juan Manuel Hoyos</dc:creator>
      <pubDate>Mon, 23 Feb 2026 03:06:48 +0000</pubDate>
      <link>https://dev.to/juanhcode/tu-aplicacion-funciona-pero-no-sabes-que-pasa-dentro-4m62</link>
      <guid>https://dev.to/juanhcode/tu-aplicacion-funciona-pero-no-sabes-que-pasa-dentro-4m62</guid>
      <description>&lt;p&gt;¿Te ha pasado que tu aplicación responde lento…&lt;br&gt;
pero no sabes exactamente dónde está el problema?&lt;/p&gt;

&lt;p&gt;¿El controller responde, pero no sabes cuánto tardó el service?&lt;br&gt;
¿La base de datos está lenta o es tu lógica?&lt;br&gt;
¿Todo funciona en local… pero no entiendes qué ocurre internamente?&lt;/p&gt;

&lt;p&gt;Ahí es donde entra la observabilidad moderna.&lt;/p&gt;

&lt;p&gt;En este artículo vas a entender, de forma práctica y profunda:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Qué es realmente la observabilidad&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cómo funcionan las trazas distribuidas&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Qué hace OpenTelemetry internamente&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Para qué sirve el Collector&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cómo Jaeger visualiza todo&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cómo instrumentar automáticamente una aplicación Spring Boot&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Todo aplicado a una aplicación tradicional, sencilla, real.&lt;/p&gt;

&lt;p&gt;Y lo mejor: puedes ejecutarlo tú mismo en este repositorio:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/juanhcode/otel-jaeger-observability-demo" rel="noopener noreferrer"&gt;https://github.com/juanhcode/otel-jaeger-observability-demo&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  🧠 1. Observabilidad
&lt;/h1&gt;

&lt;p&gt;La observabilidad se basa en tres pilares fundamentales:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Logs → Qué ocurrió&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Métricas → Cuánto ocurrió&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Trazas → Cómo ocurrió&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Las trazas permiten reconstruir el recorrido completo de una solicitud dentro de tu aplicación.&lt;/p&gt;

&lt;p&gt;No importa si es una app pequeña.&lt;br&gt;
Si quieres entender su comportamiento interno, necesitas trazas.&lt;/p&gt;




&lt;h1&gt;
  
  
  🔎 2. ¿Qué es una traza?
&lt;/h1&gt;

&lt;p&gt;Una traza representa el recorrido completo de una petición.&lt;/p&gt;

&lt;p&gt;Cliente → Controller → Service → Repository → Base de datos&lt;/p&gt;

&lt;p&gt;Cada parte es un Span.&lt;/p&gt;

&lt;p&gt;Una traza incluye:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Trace ID&lt;/li&gt;
&lt;li&gt;  Span ID&lt;/li&gt;
&lt;li&gt;  Relaciones padre-hijo&lt;/li&gt;
&lt;li&gt;  Atributos&lt;/li&gt;
&lt;li&gt;  Eventos&lt;/li&gt;
&lt;li&gt;  Duración&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  🏗 3. Arquitectura implementada
&lt;/h1&gt;

&lt;p&gt;Spring Boot&lt;br&gt;
↓&lt;br&gt;
OpenTelemetry Java Agent&lt;br&gt;
↓&lt;br&gt;
OTLP&lt;br&gt;
↓&lt;br&gt;
OpenTelemetry Collector&lt;br&gt;
↓&lt;br&gt;
Jaeger&lt;br&gt;
↓&lt;br&gt;
Jaeger UI&lt;/p&gt;




&lt;h1&gt;
  
  
  🐳 4. Infraestructura
&lt;/h1&gt;

&lt;p&gt;Se utiliza Docker Compose para levantar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Jaeger All-in-One&lt;/li&gt;
&lt;li&gt;  OpenTelemetry Collector&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Jaeger UI disponible en:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://localhost:16686" rel="noopener noreferrer"&gt;http://localhost:16686&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  ⚙️ 5. Configuración del Collector
&lt;/h1&gt;

&lt;p&gt;El Collector:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Recibe OTLP&lt;/li&gt;
&lt;li&gt;  Procesa datos (resource + batch)&lt;/li&gt;
&lt;li&gt;  Exporta a Jaeger&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El processor resource agrega:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  service.name&lt;/li&gt;
&lt;li&gt;  deployment.environment&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  ☕ 6. Aplicación
&lt;/h1&gt;

&lt;p&gt;La aplicación contiene:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Controller REST&lt;/li&gt;
&lt;li&gt;  Service&lt;/li&gt;
&lt;li&gt;  Repository JPA&lt;/li&gt;
&lt;li&gt;  Base H2 en memoria&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Endpoints:&lt;/p&gt;

&lt;p&gt;POST /user\&lt;br&gt;
GET /user&lt;/p&gt;




&lt;h1&gt;
  
  
  🎯 7. Instrumentación automática
&lt;/h1&gt;

&lt;p&gt;Variables:&lt;/p&gt;

&lt;p&gt;export OTEL_PROTOCOL="grpc"\&lt;br&gt;
export OTEL_EXPORTER_OTLP_ENDPOINT="&lt;a href="http://127.0.0.1:4318%22%5C" rel="noopener noreferrer"&gt;http://127.0.0.1:4318"\&lt;/a&gt;&lt;br&gt;
export OTEL_METRICS_EXPORTER=none&lt;/p&gt;

&lt;p&gt;Ejecución:&lt;/p&gt;

&lt;p&gt;java -javaagent:opentelemetry-javaagent.jar -jar demo-0.0.1-SNAPSHOT.jar&lt;/p&gt;




&lt;h1&gt;
  
  
  👀 8. Flujo interno
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt; Se intercepta la petición HTTP&lt;/li&gt;
&lt;li&gt; Se crea un span raíz&lt;/li&gt;
&lt;li&gt; Se instrumentan Controller, Service y JDBC&lt;/li&gt;
&lt;li&gt; Se exporta vía OTLP&lt;/li&gt;
&lt;li&gt; El Collector procesa&lt;/li&gt;
&lt;li&gt; Jaeger visualiza&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  🚀 9. Beneficios
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Detectar cuellos de botella&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Identificar latencias exactas&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Entender el flujo real de ejecución&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tener observabilidad estándar&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Evitar vendor lock-in&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Y todo esto en una aplicación sencilla.&lt;/p&gt;

&lt;p&gt;No necesitas tener 20 microservicios para necesitar observabilidad.&lt;/p&gt;




&lt;h1&gt;
  
  
  🏁 Conclusión
&lt;/h1&gt;

&lt;p&gt;La observabilidad no es solo para arquitecturas complejas.&lt;/p&gt;

&lt;p&gt;Incluso una aplicación tradicional puede beneficiarse enormemente de entender qué ocurre internamente.&lt;/p&gt;

&lt;p&gt;Con OpenTelemetry y Jaeger puedes pasar de “no sé qué está pasando”&lt;br&gt;
a tener visibilidad completa de tu aplicación en minutos.&lt;/p&gt;

&lt;p&gt;Y una vez entiendes lo que ocurre dentro… ya no quieres volver atrás.&lt;/p&gt;

&lt;p&gt;Y si quieres probarlo ahora mismo:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/juanhcode/otel-jaeger-observability-demo" rel="noopener noreferrer"&gt;https://github.com/juanhcode/otel-jaeger-observability-demo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sre</category>
      <category>devops</category>
      <category>java</category>
      <category>webdev</category>
    </item>
    <item>
      <title>🚀 Escala tus Pods en Kubernetes usando KEDA</title>
      <dc:creator>Juan Manuel Hoyos</dc:creator>
      <pubDate>Mon, 28 Jul 2025 15:25:16 +0000</pubDate>
      <link>https://dev.to/juanhcode/escala-tus-pods-en-kubernetes-usando-keda-3okk</link>
      <guid>https://dev.to/juanhcode/escala-tus-pods-en-kubernetes-usando-keda-3okk</guid>
      <description>&lt;p&gt;¿Te has preguntado cómo escalar automáticamente tus aplicaciones en Kubernetes según eventos reales, como una cola con tareas pendientes?&lt;/p&gt;

&lt;p&gt;En este artículo te mostraré cómo usar KEDA (Kubernetes-based Event Driven Autoscaler) para escalar dinámicamente un worker en Python en función de los mensajes en una cola Redis.&lt;/p&gt;

&lt;p&gt;✅ Ideal si usas colas de trabajo, microservicios, workers o tareas en background.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 ¿Qué es KEDA?
&lt;/h2&gt;

&lt;p&gt;KEDA es un componente liviano que se conecta a tu clúster de Kubernetes y escala tus pods basado en eventos, no solo en métricas de CPU o memoria. Esto permite que escales cuando hay:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mensajes en una cola&lt;/li&gt;
&lt;li&gt;Eventos en Kafka&lt;/li&gt;
&lt;li&gt;Entradas en una base de datos&lt;/li&gt;
&lt;li&gt;Y muchos otros eventos&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📦 Componentes del proyecto
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt;: Almacena tareas en una lista&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Worker Python&lt;/strong&gt;: Procesa tareas de la cola&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;KEDA&lt;/strong&gt;: Motor de escalado automático&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kustomize&lt;/strong&gt;: Gestión de manifiestos Kubernetes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔧 Requisitos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cluster Kubernetes (Minikube o en la nube)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl&lt;/code&gt; y &lt;code&gt;helm&lt;/code&gt; instalados&lt;/li&gt;
&lt;li&gt;Docker (opcional, para construir imágenes personalizadas)&lt;/li&gt;
&lt;li&gt;KEDA instalado&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📂 Archivos del proyecto
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dockerfile (opcional)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;redis
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; worker.py .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "worker.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  worker.py
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;redis_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REDIS_HOST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;redis_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REDIS_QUEUE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;myqueue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;redis_host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Worker started. Listening on queue: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;redis_queue&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blpop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redis_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Processing task: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  keda-scaledobject.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keda.sh/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ScaledObject&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-scaledobject&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scaleTargetRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-worker&lt;/span&gt;
  &lt;span class="na"&gt;pollingInterval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
  &lt;span class="na"&gt;cooldownPeriod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
  &lt;span class="na"&gt;minReplicaCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;maxReplicaCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
  &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis.default:6379&lt;/span&gt;
      &lt;span class="na"&gt;listName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myqueue&lt;/span&gt;
      &lt;span class="na"&gt;listLength&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  redis-worker.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-worker&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-worker&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis-worker&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;worker&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;juanhoyos/redis-worker:latest&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;128Mi"&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
          &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;256Mi"&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REDIS_HOST&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REDIS_QUEUE&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;myqueue"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  redis.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# redis-deployment.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;128Mi"&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
          &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;256Mi"&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6379&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  namespaces.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Namespace&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keda&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keda&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  kustomization.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kustomize.config.k8s.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Kustomization&lt;/span&gt;

&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;namespaces.yaml&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis.yaml&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis-worker.yaml&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;keda-scaledobject.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🛠️ Instalación paso a paso
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Instalar KEDA con Helm
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm &lt;span class="nb"&gt;install &lt;/span&gt;keda kedacore/keda &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version&lt;/span&gt; 2.17.2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; keda &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--create-namespace&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Construir imagen personalizada (opcional)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; &amp;lt;tu-usuario&amp;gt;/redis-worker:latest &lt;span class="nb"&gt;.&lt;/span&gt;
docker push &amp;lt;tu-usuario&amp;gt;/redis-worker:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;💡 También puedes usar la imagen preconstruida: juanhoyos/redis-worker:latest&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Desplegar la solución
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧠 ¿Cómo funciona?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;El worker Python se conecta a Redis y monitorea la lista myqueue&lt;/li&gt;
&lt;li&gt;KEDA mide la longitud de la cola&lt;/li&gt;
&lt;li&gt;Si hay más de 5 mensajes, escala hasta 5 pods&lt;/li&gt;
&lt;li&gt;Cuando la cola está vacía, reduce a 0 pods&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  🧪 Probar el autoscaling
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Acceder al pod de Redis
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; deploy/redis &lt;span class="nt"&gt;--&lt;/span&gt; sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Generar tareas de prueba
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Enviando tareas del &lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt; al &lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;i+39&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;j &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="k"&gt;$((&lt;/span&gt;i+39&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;redis-cli &lt;span class="nt"&gt;-h&lt;/span&gt; redis rpush myqueue &lt;span class="s2"&gt;"task&lt;/span&gt;&lt;span class="nv"&gt;$j&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;done
  &lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;i+40&lt;span class="k"&gt;))&lt;/span&gt;
  &lt;span class="nb"&gt;sleep &lt;/span&gt;10
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Monitorear los pods
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-w&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🏁 Conclusión
&lt;/h2&gt;

&lt;p&gt;KEDA permite construir sistemas eficientes que escalan según la demanda real. Es perfecto para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Workers en background&lt;/li&gt;
&lt;li&gt;Sistemas basados en colas&lt;/li&gt;
&lt;li&gt;Procesamiento de datos&lt;/li&gt;
&lt;li&gt;Automatización de tareas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;¿Interesado en ver ejemplos con Kafka o RabbitMQ? ¡Déjalo en los comentarios! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Recursos adicionales
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://keda.sh/docs" rel="noopener noreferrer"&gt;Documentación oficial de KEDA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redis.io/docs/ui/cli/" rel="noopener noreferrer"&gt;Redis CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kustomize.io/" rel="noopener noreferrer"&gt;Kustomize&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>How I Got SonarCloud to Detect Test Coverage in a Spring Boot Project with JaCoCo</title>
      <dc:creator>Juan Manuel Hoyos</dc:creator>
      <pubDate>Mon, 05 May 2025 16:53:02 +0000</pubDate>
      <link>https://dev.to/juanhcode/how-i-got-sonarcloud-to-detect-test-coverage-in-a-spring-boot-project-with-jacoco-1h62</link>
      <guid>https://dev.to/juanhcode/how-i-got-sonarcloud-to-detect-test-coverage-in-a-spring-boot-project-with-jacoco-1h62</guid>
      <description>&lt;p&gt;Recently, I ran into a pretty common problem while working on a &lt;strong&gt;Spring Boot&lt;/strong&gt; project with &lt;strong&gt;SonarCloud&lt;/strong&gt;: &lt;strong&gt;test coverage wasn't being reported correctly&lt;/strong&gt;. Even though my tests were running fine, SonarCloud was showing 0% coverage. 🤯&lt;/p&gt;

&lt;p&gt;After digging through the docs and trying different configurations, I finally got everything working. Here's exactly what I did, in case it helps you too.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Add &lt;code&gt;mockito-inline&lt;/code&gt; dependency
&lt;/h2&gt;

&lt;p&gt;One issue I had with &lt;strong&gt;Mockito&lt;/strong&gt; was mocking final classes or static methods. To solve this, I added the following dependency to my &lt;code&gt;pom.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.mockito&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;mockito-inline&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;5.2.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;test&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Configure JaCoCo properly
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;JaCoCo&lt;/strong&gt; plugin needs to be configured correctly to generate the &lt;code&gt;jacoco.xml&lt;/code&gt; file, which is what &lt;strong&gt;SonarCloud&lt;/strong&gt; uses to calculate code coverage. Here’s the configuration I used in &lt;code&gt;pom.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.jacoco&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jacoco-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.8.8&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;prepare-agent&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;prepare-agent&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;destFile&amp;gt;&lt;/span&gt;${project.build.directory}/jacoco.exec&lt;span class="nt"&gt;&amp;lt;/destFile&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;propertyName&amp;gt;&lt;/span&gt;surefireArgLine&lt;span class="nt"&gt;&amp;lt;/propertyName&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;report&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;test&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;report&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;formats&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;format&amp;gt;&lt;/span&gt;XML&lt;span class="nt"&gt;&amp;lt;/format&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/formats&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll also need to configure the &lt;strong&gt;Surefire plugin&lt;/strong&gt; to pass the &lt;code&gt;argLine&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.maven.plugins&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-surefire-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;argLine&amp;gt;&lt;/span&gt;@{surefireArgLine} -Xshare:off&lt;span class="nt"&gt;&amp;lt;/argLine&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Configure SonarCloud
&lt;/h2&gt;

&lt;p&gt;To let SonarCloud find the generated report, you need to specify the path to &lt;code&gt;jacoco.xml&lt;/code&gt;. Add this to your configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;sonar.coverage.jacoco.xmlReportPaths&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;target/site/jacoco/jacoco.xml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can do this either in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;sonar-project.properties&lt;/code&gt; file, or&lt;/li&gt;
&lt;li&gt;Directly in the SonarCloud web UI:&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Go to your project in SonarCloud.&lt;/li&gt;
&lt;li&gt;Navigate to &lt;code&gt;Administration &amp;gt; General Settings &amp;gt; JaCoCo&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;"Path to XML report"&lt;/strong&gt; field, set:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   target/site/jacoco/jacoco.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ✅ The Result
&lt;/h2&gt;

&lt;p&gt;After applying all these settings, I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn clean verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The coverage report was generated correctly, and &lt;strong&gt;SonarCloud finally recognized the actual test coverage&lt;/strong&gt;. 🎉&lt;/p&gt;




&lt;h2&gt;
  
  
  📝 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Connecting JaCoCo and SonarCloud in a Spring Boot project isn’t always straightforward, especially if you're just getting started. Hopefully, this guide saves you some debugging time.&lt;/p&gt;

&lt;p&gt;Did you run into a similar issue or find another solution? Let me know in the comments! 👇&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>java</category>
      <category>testing</category>
      <category>news</category>
    </item>
    <item>
      <title>Cobertura de pruebas en Spring Boot con Jacoco y SonarCloud: configuración paso a paso</title>
      <dc:creator>Juan Manuel Hoyos</dc:creator>
      <pubDate>Mon, 05 May 2025 16:04:19 +0000</pubDate>
      <link>https://dev.to/juanhcode/cobertura-de-pruebas-en-spring-boot-con-jacoco-y-sonarcloud-configuracion-paso-a-paso-1gbb</link>
      <guid>https://dev.to/juanhcode/cobertura-de-pruebas-en-spring-boot-con-jacoco-y-sonarcloud-configuracion-paso-a-paso-1gbb</guid>
      <description>&lt;p&gt;Hace poco me encontré con un problema bastante común al trabajar con proyectos en &lt;strong&gt;Spring Boot&lt;/strong&gt; y &lt;strong&gt;SonarCloud&lt;/strong&gt;: la cobertura de pruebas no se estaba reportando correctamente. Aunque las pruebas se ejecutaban sin errores, en SonarCloud la cobertura aparecía como si fuera &lt;strong&gt;0%&lt;/strong&gt;. 🤯&lt;/p&gt;

&lt;p&gt;Después de investigar y probar diferentes configuraciones, logré que todo funcionara. Aquí te comparto lo que hice, por si te sirve.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Agregar la dependencia de &lt;code&gt;mockito-inline&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Uno de los problemas que tuve al usar &lt;strong&gt;Mockito&lt;/strong&gt; fue con clases finales o métodos estáticos. Para solucionarlo, agregué esta dependencia en el archivo &lt;code&gt;pom.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.mockito&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;mockito-inline&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;5.2.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;test&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Configurar Jacoco correctamente
&lt;/h2&gt;

&lt;p&gt;El plugin de &lt;strong&gt;Jacoco&lt;/strong&gt; debe estar bien configurado para generar el archivo &lt;code&gt;jacoco.xml&lt;/code&gt;, que es el que &lt;strong&gt;SonarCloud&lt;/strong&gt; usa para calcular la cobertura. Esta fue la configuración que usé en el &lt;code&gt;pom.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.jacoco&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jacoco-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.8.8&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;prepare-agent&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;prepare-agent&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;destFile&amp;gt;&lt;/span&gt;${project.build.directory}/jacoco.exec&lt;span class="nt"&gt;&amp;lt;/destFile&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;propertyName&amp;gt;&lt;/span&gt;surefireArgLine&lt;span class="nt"&gt;&amp;lt;/propertyName&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;report&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;test&lt;span class="nt"&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;report&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;formats&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;format&amp;gt;&lt;/span&gt;XML&lt;span class="nt"&gt;&amp;lt;/format&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/formats&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;También fue necesario configurar el plugin de &lt;strong&gt;Surefire&lt;/strong&gt; para que reciba los argumentos necesarios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.maven.plugins&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-surefire-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;argLine&amp;gt;&lt;/span&gt;@{surefireArgLine} -Xshare:off&lt;span class="nt"&gt;&amp;lt;/argLine&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Configurar SonarCloud
&lt;/h2&gt;

&lt;p&gt;Para que SonarCloud encuentre el archivo generado por Jacoco, es necesario agregar la siguiente ruta en su configuración:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;sonar.coverage.jacoco.xmlReportPaths&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;target/site/jacoco/jacoco.xml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Puedes hacer esto de dos formas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;En el archivo &lt;code&gt;sonar-project.properties&lt;/code&gt; (si lo estás usando).&lt;/li&gt;
&lt;li&gt;O directamente en la configuración del proyecto en la web de SonarCloud:&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Ve a tu proyecto en SonarCloud.&lt;/li&gt;
&lt;li&gt;Abre &lt;code&gt;Administration &amp;gt; General Settings &amp;gt; JaCoCo&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;En el campo &lt;strong&gt;"Path to XML report"&lt;/strong&gt;, coloca:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   target/site/jacoco/jacoco.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ✅ Resultado
&lt;/h2&gt;

&lt;p&gt;Después de aplicar estas configuraciones y ejecutar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn clean verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El reporte de cobertura fue generado correctamente y &lt;strong&gt;SonarCloud empezó a reconocer la cobertura real de las pruebas&lt;/strong&gt;. 🎉&lt;/p&gt;




&lt;h2&gt;
  
  
  📝 Conclusión
&lt;/h2&gt;

&lt;p&gt;No siempre es obvio cómo conectar bien Jacoco con SonarCloud en un proyecto Spring Boot, especialmente si estás empezando. Espero que este post te haya ayudado a evitar horas de prueba y error.&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>webdev</category>
      <category>devops</category>
      <category>cicd</category>
    </item>
  </channel>
</rss>
