<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Blog | AWS | Kubernetes | CNCF | Cloud Native | DevOps Tools | Python | Golang]]></title><description><![CDATA[Blog | AWS | Kubernetes | CNCF | Cloud Native | DevOps Tools | Python | Golang]]></description><link>https://staz.cloud</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1652684190598/YeHmFhvzd.png</url><title>Blog | AWS | Kubernetes | CNCF | Cloud Native | DevOps Tools | Python | Golang</title><link>https://staz.cloud</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 21 Oct 2026 15:31:43 GMT</lastBuildDate><atom:link href="https://staz.cloud/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[El Futuro de Infrastructure as Code: ¿Eliminará la IA las Herramientas Intermedias?]]></title><description><![CDATA[La inteligencia artificial puede transformar la gestión en la nube al eliminar la necesidad de herramientas intermediarias como Terraform y Pulumi]]></description><link>https://staz.cloud/el-futuro-de-infrastructure-as-code-eliminara-la-ia-las-herramientas-intermedias</link><guid isPermaLink="true">https://staz.cloud/el-futuro-de-infrastructure-as-code-eliminara-la-ia-las-herramientas-intermedias</guid><category><![CDATA[Devops]]></category><category><![CDATA[#IaC]]></category><category><![CDATA[Terraform]]></category><category><![CDATA[AI]]></category><category><![CDATA[futureoftech]]></category><category><![CDATA[Infrastructure as code]]></category><dc:creator><![CDATA[Staz]]></dc:creator><pubDate>Tue, 22 Jul 2025 22:41:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1753223854321/8702fb31-f72c-4b25-956c-4d1ece3148ea.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Como profesionales de DevOps, estamos acostumbrados a trabajar con múltiples capas de abstracción. Terraform, Pulumi, OpenTofu, Crossplane - cada herramienta con su propia sintaxis, manifiestos y peculiaridades, pero todas con el mismo objetivo final: traducir nuestras intenciones en <strong>llamadas API</strong> a proveedores cloud como AWS, Azure, GCP u otros providers, pero siempre usando <strong>APIs</strong>.</p>
<p>Pero, ¿qué pasaría si los agentes de IA pudieran eliminar completamente estas herramientas intermedias (Terraform, Pulumi, OpenTofu, Crossplane)?</p>
<h2 id="heading-el-estado-actual-capas-sobre-capas">El Estado Actual: Capas Sobre Capas</h2>
<p>Hoy en día, nuestro flujo típico se ve así:</p>
<pre><code class="lang-plaintext">Intención → Código IaC → Herramienta (Terraform) → API Provider → Infraestructura
</code></pre>
<p>Cada capa añade valor pero también complejidad:</p>
<ul>
<li><p><strong>Terraform/Pulumi</strong>: Gestión de estado, planificación, sintaxis específica</p>
</li>
<li><p><strong>Proveedores</strong>: Traducción entre la herramienta y las APIs nativas</p>
</li>
<li><p><strong>Estado</strong>: Archivos <code>.tfstate</code>, backends remotos, locks de concurrencia</p>
</li>
</ul>
<h2 id="heading-la-vision-disruptiva-agentes-ia-directos">La Visión Disruptiva: Agentes IA Directos</h2>
<p>Imagina un futuro donde un agente IA reciba instrucciones en lenguaje natural:</p>
<blockquote>
<p>"Despliega una aplicación web escalable con base de datos, load balancer y certificados SSL automáticos en AWS"</p>
</blockquote>
<p>Y ejecute directamente las llamadas API necesarias, sin código intermedio:</p>
<pre><code class="lang-plaintext">Intención → Agente IA → API Provider → Infraestructura
</code></pre>
<h3 id="heading-es-tecnicamente-viable">¿Es Técnicamente Viable?</h3>
<p><strong>La respuesta corta: Sí.</strong></p>
<p>Los agentes IA modernos ya demuestran capacidad para:</p>
<ul>
<li><p>Interpretar intenciones complejas en lenguaje natural</p>
</li>
<li><p>Entender documentación de APIs y sus interdependencias</p>
</li>
<li><p>Ejecutar secuencias complejas de operaciones</p>
</li>
<li><p>Manejar errores y reintentos de manera inteligente</p>
</li>
</ul>
<h2 id="heading-los-desafios-tecnicos-y-sus-soluciones">Los Desafíos Técnicos (Y Sus Soluciones)</h2>
<h3 id="heading-1-gestion-de-estado">1. Gestión de Estado</h3>
<p><strong>Desafío</strong>: ¿Cómo saber qué infraestructura existe sin un archivo <code>.tfstate</code>?</p>
<p><strong>Solución IA</strong>: El agente podría mantener su propio estado distribuido, consultando constantemente las APIs de los proveedores para sincronizar la realidad con su conocimiento interno.</p>
<h3 id="heading-2-planificacion-y-diff">2. Planificación y Diff</h3>
<p><strong>Desafío</strong>: ¿Cómo previsualizar cambios antes de ejecutarlos?</p>
<p><strong>Solución IA</strong>: Un agente avanzado podría simular cambios, analizar dependencias en tiempo real y presentar un plan detallado, incluso mejor que el <code>terraform plan</code> actual.</p>
<h3 id="heading-3-versionado-y-auditoria">3. Versionado y Auditoría</h3>
<p><strong>Desafío</strong>: ¿Cómo mantener trazabilidad de los cambios?</p>
<p><strong>Solución IA</strong>: El agente podría generar automáticamente:</p>
<ul>
<li><p>Logs estructurados de cada operación</p>
</li>
<li><p>Commits en repositorios Git con la configuración aplicada</p>
</li>
<li><p>Reportes de compliance y seguridad</p>
</li>
<li><p>Historiales de cambios navegables</p>
</li>
</ul>
<h3 id="heading-4-rollbacks-y-recovery">4. Rollbacks y Recovery</h3>
<p><strong>Desafío</strong>: ¿Cómo revertir cambios problemáticos?</p>
<p><strong>Solución IA</strong>: Con un conocimiento completo del estado actual y histórico, el agente podría implementar rollbacks más inteligentes, incluso prediciendo y previniendo problemas antes de que ocurran.</p>
<h2 id="heading-las-ventajas-competitivas-de-los-agentes-ia">Las Ventajas Competitivas de los Agentes IA</h2>
<p>Un agente IA no estaría limitado por las restricriciones de las herramientas actuales:</p>
<h3 id="heading-analisis-inteligente">Análisis Inteligente</h3>
<ul>
<li><p>Detectar configuraciones subóptimas</p>
</li>
<li><p>Sugerir mejoras de costos automáticamente</p>
</li>
<li><p>Identificar vulnerabilidades de seguridad en tiempo real</p>
</li>
</ul>
<h3 id="heading-adaptabilidad">Adaptabilidad</h3>
<ul>
<li><p>Aprender de patrones de la organización</p>
</li>
<li><p>Adaptarse a nuevos servicios cloud sin actualizaciones de proveedores</p>
</li>
<li><p>Optimizar recursos basado en métricas reales de uso</p>
</li>
</ul>
<h3 id="heading-interoperabilidad-natural">Interoperabilidad Natural</h3>
<ul>
<li><p>Trabajar con múltiples clouds simultáneamente</p>
</li>
<li><p>Migrar recursos entre proveedores de manera inteligente</p>
</li>
<li><p>Integrar servicios on-premise y cloud sin configuración adicional</p>
</li>
</ul>
<h2 id="heading-los-obstaculos-para-la-adopcion">Los Obstáculos Para la Adopción</h2>
<h3 id="heading-confianza-y-control">Confianza y Control</h3>
<p>La industria necesitará ver que estos agentes son <strong>más confiables</strong> que los humanos escribiendo Terraform. Considerando la cantidad de bugs, configuraciones incorrectas y estados corruptos que vemos actualmente, esta vara no está tan alta.</p>
<h3 id="heading-interoperabilidad-entre-agentes">Interoperabilidad Entre Agentes</h3>
<p>¿Cómo migrar infraestructura entre diferentes agentes IA? ¿Cómo auditar sistemas desplegados por agentes de distintos vendors?</p>
<h3 id="heading-regulacion-y-compliance">Regulación y Compliance</h3>
<p>Sectores altamente regulados necesitarán marcos de certificación para agentes IA manejando infraestructura crítica.</p>
<h2 id="heading-el-punto-de-inflexion">El Punto de Inflexión</h2>
<p>La transición no será inmediata, pero podríamos ver una evolución en fases:</p>
<p><strong>Fase 1</strong> (Actual): Agentes IA como <strong>generadores</strong> de código IaC <strong>Fase 2</strong> (2-3 años): Agentes híbridos que mantienen workflows tradicionales pero automatizan la generación <strong>Fase 3</strong> (5-7 años): Agentes completamente autónomos para casos de uso específicos <strong>Fase 4</strong> (7-10 años): Reemplazo completo para la mayoría de casos de uso.</p>
<h2 id="heading-reflexiones-finales">Reflexiones Finales</h2>
<p>Esta evolución podría ser más disruptiva de lo que inicialmente pensamos. Si los agentes IA logran implementar sus propios sistemas confiables de gestión de estado y cambios, las herramientas intermedias como Terraform podrían convertirse en legado.</p>
<p>Para los profesionales de DevOps, esto no significa obsolescencia, sino <strong>evolución</strong>. Nuestro valor se desplazará hacia:</p>
<ul>
<li><p>Diseño de arquitecturas y políticas de alto nivel</p>
</li>
<li><p>Supervisión y governance de agentes IA</p>
</li>
<li><p>Integración entre sistemas complejos</p>
</li>
<li><p>Resolución de problemas que requieren contexto humano</p>
</li>
</ul>
<p>¿Qué opinas? ¿Estamos preparados para un futuro donde la infraestructura se gestione a través de conversaciones con IA en lugar de código?</p>
<hr />
<p><em>¿Te ha gustado este análisis? Comparte tu perspectiva en los comentarios y conecta conmigo para más discusiones sobre el futuro de DevOps y la IA.</em></p>
]]></content:encoded></item><item><title><![CDATA[Adiós Contenedores, Hola Unikernels: Rompiendo Paradigmas Cloud Native]]></title><description><![CDATA[Descubre por qué los unikernels están revolucionando el mundo de los microservicios con mayor seguridad, velocidad y eficiencia que los contenedores]]></description><link>https://staz.cloud/adios-contenedores-hola-unikernels</link><guid isPermaLink="true">https://staz.cloud/adios-contenedores-hola-unikernels</guid><category><![CDATA[GraalVM]]></category><category><![CDATA[unikernel]]></category><category><![CDATA[DevSecOps]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[cloud native]]></category><category><![CDATA[Java]]></category><category><![CDATA[containers]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Security]]></category><category><![CDATA[performance]]></category><dc:creator><![CDATA[Staz]]></dc:creator><pubDate>Thu, 12 Jun 2025 22:14:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749767109089/9bd0bae6-9392-45d2-9fe9-0ac31bf1d110.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>Durante más de una década, los contenedores han dominado el paisaje de las aplicaciones cloud native. Docker, Kubernetes, y todo el ecosistema de contenedores se han convertido en el estándar de facto para el despliegue de microservicios. Pero, <strong>¿qué pasaría si te dijera que existe una tecnología que promete ser más segura, más rápida y más eficiente que los contenedores tradicionales?</strong></p>
<p>Bienvenidos al mundo de los <strong>Unikernels</strong>. 🚀</p>
<h2 id="heading-que-son-los-unikernels">¿Qué son los Unikernels? 🤔</h2>
<p>Los unikernels son imágenes de máquinas virtuales especializadas que incluyen únicamente el código de la aplicación y las partes del sistema operativo necesarias para ejecutarla. A diferencia de los contenedores, que comparten el kernel del host, los unikernels son sistemas operativos (no como Linux o Windows - Solo Kernel y App), de propósito único compilados junto con la aplicación.</p>
<h2 id="heading-contenedores-vs-unikernels-la-batalla-tecnologica">Contenedores vs Unikernels: La Batalla Tecnológica ⚔️</h2>
<blockquote>
<p><strong>TL;DR</strong>: Los unikernels ofrecen mejor aislamiento, arranque más rápido y mayor seguridad, pero los contenedores tienen un ecosistema más maduro.</p>
</blockquote>
<h3 id="heading-arquitectura-y-aislamiento">Arquitectura y Aislamiento 🏗️</h3>
<p><strong>Contenedores:</strong></p>
<ul>
<li><p>Comparten el kernel del sistema operativo host</p>
</li>
<li><p>Utilizan namespaces y cgroups para el aislamiento</p>
</li>
<li><p>Múltiples contenedores ejecutándose sobre un solo kernel</p>
</li>
<li><p>Superficie de ataque más amplia debido al kernel compartido</p>
</li>
</ul>
<p><strong>Unikernels:</strong></p>
<ul>
<li><p>Cada unikernel es un sistema operativo completo y aislado <em>(no es un SO tradicional como Linux o Windows, solo lleva el kernel y tu aplicativo - lo único necesario, sin mas procesos, librerías, módulos)</em></p>
</li>
<li><p>Aislamiento a nivel de hipervisor (más fuerte que los contenedores)</p>
</li>
<li><p>Superficie de ataque mínima - solo incluye lo necesario</p>
</li>
<li><p>Inmutables por diseño</p>
</li>
</ul>
<h3 id="heading-rendimiento-y-recursos">Rendimiento y Recursos 📊</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Métrica</td><td>Contenedores</td><td>Unikernels</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Tiempo de arranque</strong></td><td>1-5 segundos</td><td>10-100 milisegundos</td></tr>
<tr>
<td><strong>Uso de memoria</strong></td><td>Medio (SO + runtime + app)</td><td>Mínimo (solo lo necesario)</td></tr>
<tr>
<td><strong>Tamaño de imagen</strong></td><td>50MB - 1GB+</td><td>1-50MB</td></tr>
<tr>
<td><strong>Overhead de virtualización</strong></td><td>Bajo</td><td>Muy bajo</td></tr>
<tr>
<td><strong>Cold start</strong></td><td>Lento</td><td>Extremadamente rápido</td></tr>
</tbody>
</table>
</div><h3 id="heading-seguridad">Seguridad 🔒</h3>
<p><strong>Contenedores:</strong></p>
<ul>
<li><p>Vulnerabilidades del kernel afectan a todos los contenedores</p>
</li>
<li><p>Posibles escapes de contenedor</p>
</li>
<li><p>Requieren hardening adicional del host</p>
</li>
<li><p>Múltiples vectores de ataque</p>
</li>
</ul>
<p><strong>Unikernels:</strong></p>
<ul>
<li><p>Aislamiento completo a nivel de hipervisor</p>
</li>
<li><p>Superficie de ataque extremadamente reducida</p>
</li>
<li><p>Sin shell o utilidades del sistema que puedan ser comprometidas</p>
</li>
<li><p>Inmutabilidad inherente</p>
</li>
</ul>
<h3 id="heading-gestion-y-orquestacion">Gestión y Orquestación 🎛️</h3>
<p><strong>Contenedores:</strong></p>
<ul>
<li><p>Ecosistema maduro (Kubernetes, Docker Swarm)</p>
</li>
<li><p>Herramientas de monitoreo y debugging bien establecidas</p>
</li>
<li><p>Fácil depuración con acceso al contenedor</p>
</li>
<li><p>Amplia adopción y documentación</p>
</li>
</ul>
<p><strong>Unikernels:</strong></p>
<ul>
<li><p>Ecosistema emergente</p>
</li>
<li><p>Herramientas de debugging limitadas</p>
</li>
<li><p>Depuración más compleja (sin acceso directo)</p>
</li>
<li><p>Curva de aprendizaje pronunciada</p>
</li>
</ul>
<h2 id="heading-casos-de-uso-ideales-para-unikernels">Casos de Uso Ideales para Unikernels 🎯</h2>
<ol>
<li><p><strong>Funciones Serverless</strong>: Arranque instantáneo y uso mínimo de recursos</p>
</li>
<li><p><strong>Microservicios de alta seguridad</strong>: Donde la superficie de ataque debe ser mínima</p>
</li>
<li><p><strong>IoT y Edge Computing</strong>: Recursos limitados y requisitos de seguridad</p>
</li>
<li><p><strong>Aplicaciones de alto rendimiento</strong>: Donde cada milisegundo cuenta</p>
</li>
</ol>
<h2 id="heading-ejemplo-practico-desplegando-un-microservicio-java-con-unikernels">Ejemplo Práctico: Desplegando un Microservicio Java con Unikernels 💻</h2>
<blockquote>
<p><strong>⚠️ Pre requisito</strong>: Este tutorial asume conocimientos básicos de Java, Maven y virtualización.</p>
</blockquote>
<p>Vamos a crear un microservicio Java simple y desplegarlo usando <strong>GraalVM Native Image</strong> y <strong>Nanos</strong> como nuestro unikernel runtime.</p>
<h3 id="heading-pre-requisitos">Pre requisitos 📋</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Instalar GraalVM</span>
curl -s <span class="hljs-string">"https://get.sdkman.io"</span> | bash
<span class="hljs-built_in">source</span> <span class="hljs-string">"<span class="hljs-variable">$HOME</span>/.sdkman/bin/sdkman-init.sh"</span>
sdk install java 22.3-graal

<span class="hljs-comment"># Instalar Nanos</span>
curl https://nanos.org/install.sh -sSfL | sh

<span class="hljs-comment"># Verificar instalación</span>
ops version
</code></pre>
<blockquote>
<p><strong>💡 Tip</strong>: Para este tutorial se ha usado Nanos, puedes usar Unikraft, osv, QEMU u otro.</p>
</blockquote>
<h3 id="heading-paso-1-crear-el-microservicio-java">Paso 1: Crear el Microservicio Java ☕</h3>
<pre><code class="lang-java"><span class="hljs-comment">// src/main/java/com/example/HelloUnikernel.java</span>
<span class="hljs-keyword">package</span> com.example;

<span class="hljs-keyword">import</span> com.sun.net.httpserver.HttpServer;
<span class="hljs-keyword">import</span> com.sun.net.httpserver.HttpHandler;
<span class="hljs-keyword">import</span> com.sun.net.httpserver.HttpExchange;
<span class="hljs-keyword">import</span> java.io.IOException;
<span class="hljs-keyword">import</span> java.io.OutputStream;
<span class="hljs-keyword">import</span> java.net.InetSocketAddress;
<span class="hljs-keyword">import</span> java.time.LocalDateTime;
<span class="hljs-keyword">import</span> java.util.concurrent.Executors;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloUnikernel</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> IOException </span>{
        HttpServer server = HttpServer.create(<span class="hljs-keyword">new</span> InetSocketAddress(<span class="hljs-number">8080</span>), <span class="hljs-number">0</span>);

        server.createContext(<span class="hljs-string">"/health"</span>, <span class="hljs-keyword">new</span> HealthHandler());
        server.createContext(<span class="hljs-string">"/hello"</span>, <span class="hljs-keyword">new</span> HelloHandler());
        server.createContext(<span class="hljs-string">"/metrics"</span>, <span class="hljs-keyword">new</span> MetricsHandler());

        server.setExecutor(Executors.newCachedThreadPool());
        server.start();

        System.out.println(<span class="hljs-string">"🚀 Unikernel microservice started on port 8080"</span>);
        System.out.println(<span class="hljs-string">"📍 Endpoints:"</span>);
        System.out.println(<span class="hljs-string">"  GET /health - Health check"</span>);
        System.out.println(<span class="hljs-string">"  GET /hello - Hello world"</span>);
        System.out.println(<span class="hljs-string">"  GET /metrics - Basic metrics"</span>);
    }

    <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HealthHandler</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">HttpHandler</span> </span>{
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handle</span><span class="hljs-params">(HttpExchange exchange)</span> <span class="hljs-keyword">throws</span> IOException </span>{
            String response = <span class="hljs-string">"{\"status\":\"UP\",\"timestamp\":\""</span> + 
                            LocalDateTime.now() + <span class="hljs-string">"\"}"</span>;
            exchange.getResponseHeaders().set(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>);
            exchange.sendResponseHeaders(<span class="hljs-number">200</span>, response.getBytes().length);
            OutputStream os = exchange.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

    <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloHandler</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">HttpHandler</span> </span>{
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handle</span><span class="hljs-params">(HttpExchange exchange)</span> <span class="hljs-keyword">throws</span> IOException </span>{
            String response = <span class="hljs-string">"{\"message\":\"Hello from Unikernel! 🎉\",\"technology\":\"Java + GraalVM + Nanos\"}"</span>;
            exchange.getResponseHeaders().set(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>);
            exchange.sendResponseHeaders(<span class="hljs-number">200</span>, response.getBytes().length);
            OutputStream os = exchange.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

    <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MetricsHandler</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">HttpHandler</span> </span>{
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handle</span><span class="hljs-params">(HttpExchange exchange)</span> <span class="hljs-keyword">throws</span> IOException </span>{
            Runtime runtime = Runtime.getRuntime();
            <span class="hljs-keyword">long</span> memory = runtime.totalMemory() - runtime.freeMemory();
            String response = String.format(
                <span class="hljs-string">"{\"memory_used_bytes\":%d,\"memory_total_bytes\":%d,\"processors\":%d}"</span>,
                memory, runtime.totalMemory(), runtime.availableProcessors()
            );
            exchange.getResponseHeaders().set(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>);
            exchange.sendResponseHeaders(<span class="hljs-number">200</span>, response.getBytes().length);
            OutputStream os = exchange.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }
}
</code></pre>
<blockquote>
<p><strong>🔍 Análisis del código</strong>: Hemos creado un microservicio simple con 3 endpoints que nos permitirán evaluar el rendimiento y funcionalidad del unikernel.</p>
</blockquote>
<h3 id="heading-paso-2-configurar-el-build">Paso 2: Configurar el Build 🔧</h3>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- pom.xml --&gt;</span>
<span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0"</span>
         <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>hello-unikernel<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">packaging</span>&gt;</span>jar<span class="hljs-tag">&lt;/<span class="hljs-name">packaging</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">maven.compiler.source</span>&gt;</span>11<span class="hljs-tag">&lt;/<span class="hljs-name">maven.compiler.source</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">maven.compiler.target</span>&gt;</span>11<span class="hljs-tag">&lt;/<span class="hljs-name">maven.compiler.target</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">project.build.sourceEncoding</span>&gt;</span>UTF-8<span class="hljs-tag">&lt;/<span class="hljs-name">project.build.sourceEncoding</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.graalvm.buildtools<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>native-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.9.28<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">extensions</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">extensions</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">executions</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">execution</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">id</span>&gt;</span>build-native<span class="hljs-tag">&lt;/<span class="hljs-name">id</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">goals</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">goal</span>&gt;</span>compile-no-fork<span class="hljs-tag">&lt;/<span class="hljs-name">goal</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">goals</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">phase</span>&gt;</span>package<span class="hljs-tag">&lt;/<span class="hljs-name">phase</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">execution</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">executions</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">imageName</span>&gt;</span>hello-unikernel<span class="hljs-tag">&lt;/<span class="hljs-name">imageName</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">mainClass</span>&gt;</span>com.example.HelloUnikernel<span class="hljs-tag">&lt;/<span class="hljs-name">mainClass</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">buildArgs</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">buildArg</span>&gt;</span>--no-fallback<span class="hljs-tag">&lt;/<span class="hljs-name">buildArg</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">buildArg</span>&gt;</span>--enable-http<span class="hljs-tag">&lt;/<span class="hljs-name">buildArg</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">buildArg</span>&gt;</span>--enable-https<span class="hljs-tag">&lt;/<span class="hljs-name">buildArg</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">buildArgs</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span>
</code></pre>
<h3 id="heading-paso-3-construir-el-ejecutable-nativo">Paso 3: Construir el Ejecutable Nativo ⚡</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Compilar la aplicación</span>
mvn clean package -Pnative

<span class="hljs-comment"># Verificar el ejecutable</span>
ls -la target/hello-unikernel
file target/hello-unikernel
</code></pre>
<blockquote>
<p><strong>⏱️ Tiempo estimado</strong>: La compilación nativa puede tomar 2-5 minutos dependiendo de tu hardware.</p>
</blockquote>
<h3 id="heading-paso-4-crear-la-configuracion-del-unikernel">Paso 4: Crear la Configuración del Unikernel ⚙️</h3>
<pre><code class="lang-json">{
  <span class="hljs-attr">"Files"</span>: [<span class="hljs-string">"target/hello-unikernel"</span>],
  <span class="hljs-attr">"Dirs"</span>: [<span class="hljs-string">"target"</span>],
  <span class="hljs-attr">"Args"</span>: [<span class="hljs-string">"target/hello-unikernel"</span>],
  <span class="hljs-attr">"Boot"</span>: <span class="hljs-string">"target/hello-unikernel"</span>,
  <span class="hljs-attr">"Env"</span>: {
    <span class="hljs-attr">"USER"</span>: <span class="hljs-string">"root"</span>
  },
  <span class="hljs-attr">"MapDirs"</span>: {
    <span class="hljs-attr">"target"</span>: <span class="hljs-string">"./target"</span>
  },
  <span class="hljs-attr">"Ports"</span>: [<span class="hljs-string">"8080"</span>]
}
</code></pre>
<h3 id="heading-paso-5-construir-y-ejecutar-el-unikernel">Paso 5: Construir y Ejecutar el Unikernel 🚀</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Construir la imagen del unikernel</span>
ops build -c config.json

<span class="hljs-comment"># Ejecutar el unikernel</span>
ops run -c config.json -p 8080

<span class="hljs-comment"># En otra terminal, probar los endpoints</span>
curl http://localhost:8080/health
curl http://localhost:8080/hello
curl http://localhost:8080/metrics
</code></pre>
<blockquote>
<p><strong>🎉 ¡Felicidades!</strong> Si todo funciona correctamente, acabas de ejecutar tu primer microservicio en un unikernel.</p>
</blockquote>
<h3 id="heading-paso-6-analisis-de-rendimiento">Paso 6: Análisis de Rendimiento 📈</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Medir tiempo de arranque</span>
time ops run -c config.json -p 8080 &amp;

<span class="hljs-comment"># Medir uso de memoria</span>
ops run -c config.json -p 8080 &amp;
ps aux | grep hello-unikernel

<span class="hljs-comment"># Comparar con contenedor Docker equivalente</span>
docker build -t hello-container .
time docker run -p 8081:8080 hello-container &amp;
</code></pre>
<blockquote>
<p><strong>📊 Benchmark</strong>: Documenta estos resultados para comparar con contenedores tradicionales.</p>
</blockquote>
<h2 id="heading-resultados-esperados">Resultados Esperados 📊</h2>
<p>Al ejecutar este ejemplo, deberías observar:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Métrica</td><td>Unikernel</td><td>Contenedor</td><td>Mejora</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Tiempo de arranque</strong></td><td>~50-100ms</td><td>2-5s</td><td><strong>20-50x más rápido</strong></td></tr>
<tr>
<td><strong>Uso de memoria</strong></td><td>~20-50MB</td><td>100-200MB</td><td><strong>4-10x menos memoria</strong></td></tr>
<tr>
<td><strong>Tamaño de imagen</strong></td><td>~15-30MB</td><td>100-300MB</td><td><strong>3-20x más pequeño</strong></td></tr>
<tr>
<td><strong>Seguridad</strong></td><td>Superficie mínima</td><td>Kernel compartido</td><td><strong>Significativamente mejor</strong></td></tr>
</tbody>
</table>
</div><blockquote>
<p><strong>🚨 Importante</strong>: Estos resultados pueden variar según tu hardware y configuración específica.</p>
</blockquote>
<h2 id="heading-herramientas-del-ecosistema-unikernel">Herramientas del Ecosistema Unikernel 🛠️</h2>
<h3 id="heading-runtimes-y-frameworks">Runtimes y Frameworks</h3>
<ul>
<li><p><strong>Nanos</strong>: Runtime de unikernel para Linux</p>
</li>
<li><p><strong>OSv</strong>: Sistema operativo diseñado para cloud</p>
</li>
<li><p><strong>MirageOS</strong>: Unikernels funcionales en OCaml</p>
</li>
<li><p><strong>IncludeOS</strong>: Framework C++ para unikernels</p>
</li>
<li><p><strong>Rumprun</strong>: Ejecuta aplicaciones POSIX sin modificaciones</p>
</li>
<li><p><strong>Unikraft</strong>: Dentro de los proyectos de la CNCF</p>
</li>
</ul>
<h3 id="heading-orquestacion">Orquestación</h3>
<ul>
<li><p><strong>UniK</strong>: Compilador y orquestrador de unikernels</p>
</li>
<li><p><strong>Kubernetes con CRI-O</strong>: Soporte experimental para unikernels</p>
</li>
<li><p><strong>Firecracker</strong>: MicroVMs que pueden ejecutar unikernels</p>
</li>
</ul>
<h2 id="heading-limitaciones-y-consideraciones">Limitaciones y Consideraciones ⚠️</h2>
<blockquote>
<p><strong>💭 Reflexión</strong>: Como cualquier tecnología emergente, los unikernels no son una bala de plata.</p>
</blockquote>
<h3 id="heading-desafios-actuales">Desafíos Actuales 🚧</h3>
<ol>
<li><p><strong>Ecosistema inmaduro</strong>: Pocas herramientas comparado con contenedores</p>
</li>
<li><p><strong>Debugging complejo</strong>: Sin acceso directo al sistema en ejecución</p>
</li>
<li><p><strong>Curva de aprendizaje</strong>: Requiere cambio de mentalidad y nuevas habilidades</p>
</li>
<li><p><strong>Limitaciones de lenguaje</strong>: No todos los lenguajes tienen soporte maduro</p>
</li>
</ol>
<h3 id="heading-cuando-no-usar-unikernels">Cuándo NO usar Unikernels ❌</h3>
<ul>
<li><p>Aplicaciones que requieren debugging frecuente</p>
</li>
<li><p>Sistemas que necesitan acceso a muchas librerías del sistema</p>
</li>
<li><p>Entornos que requieren alta flexibilidad operacional</p>
</li>
<li><p>Equipos sin experiencia en virtualización de bajo nivel</p>
</li>
</ul>
<h2 id="heading-el-futuro-coexistencia-o-reemplazo">El Futuro: ¿Coexistencia o Reemplazo? 🔮</h2>
<p>Los unikernels no necesariamente reemplazarán completamente a los contenedores, pero están encontrando su nicho en:</p>
<ol>
<li><p><strong>Serverless Computing</strong>: Donde el arranque rápido es crucial</p>
</li>
<li><p><strong>Edge Computing</strong>: Recursos limitados y requisitos de seguridad</p>
</li>
<li><p><strong>Microservicios críticos</strong>: Donde la seguridad es prioritaria</p>
</li>
<li><p><strong>IoT</strong>: Dispositivos con recursos extremadamente limitados</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusión 🎯</h2>
<p>Los unikernels representan una evolución natural en la búsqueda de mayor eficiencia, seguridad y rendimiento en aplicaciones cloud native. Aunque aún están en una fase relativamente temprana de adopción, ofrecen ventajas significativas en casos de uso específicos.</p>
<p>Como profesionales Cloud Native, debemos estar preparados para esta nueva ola tecnológica. Los unikernels no son solo una curiosidad académica, sino una herramienta práctica que puede resolver problemas reales de seguridad y rendimiento que enfrentamos con los contenedores tradicionales.</p>
<blockquote>
<p><strong>🤔 La pregunta clave</strong>: No es si los unikernels tendrán un lugar en nuestro stack tecnológico, sino cuándo y cómo los adoptaremos de manera efectiva.</p>
</blockquote>
<p><strong>¿Estás listo para dar el salto a los unikernels?</strong> 🚀</p>
<hr />
<h2 id="heading-que-opinas">¿Qué opinas? 💬</h2>
<p>¿Has experimentado con unikernels en tu organización? ¿Crees que reemplazarán a los contenedores o coexistirán?</p>
<p><strong>Comparte tu experiencia en los comentarios</strong> y ayuda a construir esta conversación. Si este artículo te fue útil, no olvides darle <strong>❤️</strong> y <strong>compartirlo</strong> con tu equipo.</p>
<h2 id="heading-sigueme-para-mas-contenido-cloud-native">Sígueme para más contenido Cloud Native 📱</h2>
<ul>
<li><p>🐦 Twitter: <a target="_blank" href="https://twitter.com/stazdx">@stazdx</a></p>
</li>
<li><p>💼 LinkedIn: <a target="_blank" href="https://www.linkedin.com/in/stanislav-yanallalli/">Staz</a></p>
</li>
<li><p>📚 GitHub: <a target="_blank" href="https://github.com/stazdx">Staz</a></p>
</li>
</ul>
<h2 id="heading-referencias-y-recursos-adicionales">Referencias y Recursos Adicionales 📖</h2>
<ul>
<li><p><a target="_blank" href="https://nanos.org/docs">Nanos Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://www.graalvm.org/native-image/">GraalVM Native Image</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/solo-io/unik">UniK Project</a></p>
</li>
<li><p><a target="_blank" href="https://mirage.io/">MirageOS</a></p>
</li>
<li><p><a target="_blank" href="https://www.includeos.org/">IncludeOS</a></p>
</li>
<li><p><a target="_blank" href="https://www.unikraft.org/">Unikraft</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Maximizando la Eficiencia de Microservicios Java Contenerizados: Una Guía Práctica]]></title><description><![CDATA[Optimiza microservicios Java en Kubernetes con imágenes de BellSoft para mejorar eficiencia, seguridad y reducir costos]]></description><link>https://staz.cloud/maximizando-la-eficiencia-de-microservicios-java-contenerizados-una-guia-practica</link><guid isPermaLink="true">https://staz.cloud/maximizando-la-eficiencia-de-microservicios-java-contenerizados-una-guia-practica</guid><category><![CDATA[bellsoft]]></category><category><![CDATA[alpaquitaOS]]></category><category><![CDATA[LibericaJDK]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[Java]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[containers]]></category><dc:creator><![CDATA[Staz]]></dc:creator><pubDate>Mon, 06 Jan 2025 17:37:51 GMT</pubDate><content:encoded><![CDATA[<p>En mi experiencia en los últimos años gestionando microservicios Java en clusters de Kubernetes en diferentes proyectos y consultorías. He notado que los principales problemas que afrontamos son:</p>
<ul>
<li><p>Escalabilidad eficiente de microservicios.</p>
</li>
<li><p>Asignación correcta de recursos del Java Runtime dentro del pod.</p>
</li>
<li><p>Tiempo elevado de startup del pod.</p>
</li>
<li><p>Tamaño y consumo de recursos del contenedor (microservicio).</p>
</li>
<li><p>Seguridad del contenedor.</p>
</li>
</ul>
<p>Como se puede apreciar, estos problemas están enfocados directamente a los contenedores mas que al cluster en sí. Es decir lo primero a atacar, debería ser elegir una imagen base que sea eficiente, ligera, segura, escalable y con soporte.</p>
<p>Existen muchas alternativas de imagenes base para microservicios Java, hoy les comparto la que me ha dado mejores resultados estos últimos años. Se trata de <a target="_blank" href="https://hub.docker.com/r/bellsoft/liberica-runtime-container">https://hub.docker.com/r/bellsoft/liberica-runtime-container</a> , una imagen base creada por <a target="_blank" href="https://bell-sw.com/">BellSoft</a> que usa <code>Alpaquita</code> como OS y <code>Liberica</code> JDK/JRE.</p>
<p>Alpaquita es un sistema operativo basado en Alpine Linux, optimizado para contenedores base. Asi como Liberica JDK/JRE que es 100% codigo abierto y está optimizado para poder ejecutar microservicios Java de forma flexible, eficiente, ahorrando costos y de forma segura. Ambos Bellsoft brinda soporte y es la mejor opción para ambientes contenerizados.</p>
<p>A continuación muestro las opciones que nos brinda <a target="_blank" href="https://bell-sw.com/">Bellsoft</a> dependiendo la necesidad. En mi caso yo uso <code>liberica-runtime-container:jre-21-musl</code> para microservicios, el cual pesa <code>40 MB</code> y solo tiene el <code>JRE</code> que es lo único necesario para poder correr el JAR de mi microservicio.</p>
<p><img src="https://download.bell-sw.com/static/images/how-to-choose-optimal-java-image.jpg" alt="How to choose an optimal java image" /></p>
<p>Es decir un Dockerfile para mi microservicio, tendría el siguiente formato:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> bellsoft/liberica-runtime-container:jdk-<span class="hljs-number">21</span>-musl AS TEMP_BUILD_IMAGE
<span class="hljs-keyword">COPY</span><span class="bash"> src /usr/src/app/src</span>
<span class="hljs-keyword">COPY</span><span class="bash"> pom.xml settings.xml /usr/src/app/</span>
<span class="hljs-keyword">RUN</span><span class="bash"> mvn -s /usr/src/app/settings.xml -f /usr/src/app/pom.xml clean install -q -DcoverRatio=80 -Dmaven.test.skip=<span class="hljs-literal">true</span></span>

<span class="hljs-keyword">FROM</span> --platform=linux/amd64 bellsoft/liberica-runtime-container:jre-<span class="hljs-number">21</span>-musl
<span class="hljs-keyword">ENV</span> TIME_ZONE America/Lima
<span class="hljs-keyword">ENV</span> TZ=$TIME_ZONE
<span class="hljs-keyword">ENV</span> JAVA_OPTS <span class="hljs-string">"-Djava.awt.headless=true -Xmx1250m -Xms256m"</span>
<span class="hljs-keyword">COPY</span><span class="bash"> --from=TEMP_BUILD_IMAGE /usr/src/app/target/*.jar /app/spring-boot-application.jar</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apk --no-cache add openssl &amp;&amp; \
    apk --no-cache add curl &amp;&amp; \
    addgroup -S javauser &amp;&amp; adduser -S javauser -G javauser &amp;&amp; \
    chown -R javauser:javauser /app &amp;&amp; \
</span>
<span class="hljs-keyword">USER</span> javauser
<span class="hljs-keyword">ENTRYPOINT</span><span class="bash"> <span class="hljs-built_in">exec</span> java -jar /app/spring-boot-application.jar</span>
</code></pre>
<p>Como podemos apreciar, primero uso la imagen <code>bellsoft/liberica-runtime-container:jdk-21-musl</code> solo para generar mi JAR. Luego utilizo la imagen <code>bellsoft/liberica-runtime-container:jre-21-musl</code> para poder ejecutar el JAR.</p>
<h2 id="heading-que-pude-lograr-realizando-este-cambio">Qué pude lograr realizando este cambio?</h2>
<ul>
<li><p>El peso de mi imagen base, de <code>350MB</code> a <code>47MB</code> . (Cabe resaltar que algunas imágenes pesan mas de 1GB, lo he visto en clientes).</p>
</li>
<li><p>La cantidad de procesos que corren dentro del pod, solo existe <code>1 proceso</code>, la de mi aplicación JAVA, esto por la optimización de alpaquita como SO.</p>
</li>
<li><p>El startup de mi microservicio de <code>2-3 minutos</code> a <code>27 segundos</code>, por la optimización de <code>Liberica</code> JRE.</p>
</li>
<li><p>La seguridad de mi contenedor, antes usaba imágenes como la de amazon-coretto, openJDK, oracleJDK, entre otros. Pero aún así encontraba vulnerabilidades escaneando con <a target="_blank" href="https://trivy.dev/latest/"><code>trivy</code></a> . Usando Alpaquita y Liberica tenemos <code>0 vulnerabilidades</code>, esto debido a que <code>Bellsoft</code> se enfoca mucho en la seguridad.</p>
</li>
<li><p>La cantidad de nodos de mi cluster se <code>redujeron al 50%</code> porque administraba mejor los recursos, a menos asignación de recursos, mas capacidad de pods y menos nodos desplegados.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusión</h2>
<p>La optimización de microservicios Java contenerizados es crucial para mejorar la eficiencia, seguridad y escalabilidad de las aplicaciones. Al elegir una imagen base adecuada, como la proporcionada por <code>BellSoft</code> con <code>Alpaquita</code> y <code>Liberica</code>, se pueden lograr mejoras significativas en el rendimiento y la seguridad. La reducción del tamaño de la imagen, el tiempo de inicio más rápido y la eliminación de vulnerabilidades son beneficios clave que se traducen en un uso más eficiente de los recursos del cluster. Implementar estas prácticas no solo optimiza el rendimiento de los microservicios, sino que también contribuye a una gestión más efectiva de los recursos en entornos contenerizados.</p>
<p>Espero pueda servirles, cualquier duda o comentario no duden en contactarme.</p>
]]></content:encoded></item><item><title><![CDATA[Cost-Saving Strategies for Kubernetes: Managing Automated Shutdown and Startup in Non-Production Environments]]></title><description><![CDATA[Discover how automated shutdown and startup strategies in Kubernetes non-production environments can slash costs by up to 50%]]></description><link>https://staz.cloud/cost-saving-strategies-for-kubernetes-managing-automated-shutdown-and-startup-in-non-production-environments</link><guid isPermaLink="true">https://staz.cloud/cost-saving-strategies-for-kubernetes-managing-automated-shutdown-and-startup-in-non-production-environments</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[clusterautoscaler]]></category><category><![CDATA[CostSavings]]></category><category><![CDATA[Cronjobs]]></category><category><![CDATA[efficiency]]></category><dc:creator><![CDATA[Staz]]></dc:creator><pubDate>Wed, 24 Apr 2024 04:23:54 GMT</pubDate><content:encoded><![CDATA[<p>In a previous post, we discussed installing Karpenter to manage cluster auto-scaling efficiently. You can find it <a target="_blank" href="https://k8staz.com/how-to-optimize-ec2-costs-with-auto-scaling-in-kubernetes-using-karpenter-on-eks">here</a>. Today, we'll explore how to further enhance cost savings on nodes by implementing automated shutdown and startup behaviors for Kubernetes clusters, specifically in non-production environments. By adopting this strategy, you could potentially cut costs by up to 50% by operating nodes for only 12 hours daily.</p>
<ol>
<li><p><strong>Introduction</strong></p>
<p> It's very common to have non-production environments (DEV, QA, Staging, UAT, etc.), at least one or two to ensure the version that will be deployed to production and that productivity is not halted (Continuous Integration/Continuous Deployment). However, it's true that these non-production environments do not need to be available 24x7 in most cases. As our clusters grow in nodes, our costs increase, and we need to adopt a strategy to reduce costs without affecting the daily productivity of our teams.</p>
<p> One strategy is to use Shutdown/Startup behavior. We know that the cost of computing services is based on usage. We can reduce it by turning off the nodes when we don't need them, and this also contributes to the well-being of the team, since they will only have the environments available during working hours.</p>
</li>
<li><p><strong>Creating previous resources</strong></p>
<p> First, we will need a <code>ServiceAccount</code> that will have the necessary permissions defined in a <code>ClusterRole</code> and we will attach it with a <code>ClusterRoleBinding</code>. This <code>ServiceAccount</code> will be used for our <code>CronJobs</code>:</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">rbac.authorization.k8s.io/v1</span>
 <span class="hljs-attr">kind:</span> <span class="hljs-string">ClusterRole</span>
 <span class="hljs-attr">metadata:</span>
   <span class="hljs-attr">name:</span> <span class="hljs-string">cluster-manager-role</span>
 <span class="hljs-attr">rules:</span>
 <span class="hljs-bullet">-</span> <span class="hljs-attr">apiGroups:</span> [<span class="hljs-string">"apps"</span>]
   <span class="hljs-attr">resources:</span> [<span class="hljs-string">"deployments"</span>, <span class="hljs-string">"deployments/scale"</span>]
   <span class="hljs-attr">verbs:</span> [<span class="hljs-string">"get"</span>, <span class="hljs-string">"list"</span>, <span class="hljs-string">"update"</span>, <span class="hljs-string">"patch"</span>]
 <span class="hljs-string">---</span>
 <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">rbac.authorization.k8s.io/v1</span>
 <span class="hljs-attr">kind:</span> <span class="hljs-string">ClusterRoleBinding</span>
 <span class="hljs-attr">metadata:</span>
   <span class="hljs-attr">name:</span> <span class="hljs-string">cluster-manager-binding</span>
 <span class="hljs-attr">roleRef:</span>
   <span class="hljs-attr">apiGroup:</span> <span class="hljs-string">rbac.authorization.k8s.io</span>
   <span class="hljs-attr">kind:</span> <span class="hljs-string">ClusterRole</span>
   <span class="hljs-attr">name:</span> <span class="hljs-string">cluster-manager-role</span>
 <span class="hljs-attr">subjects:</span>
 <span class="hljs-bullet">-</span> <span class="hljs-attr">kind:</span> <span class="hljs-string">ServiceAccount</span>
   <span class="hljs-attr">name:</span> <span class="hljs-string">cluster-manager</span>
   <span class="hljs-attr">namespace:</span> <span class="hljs-string">kube-system</span>
 <span class="hljs-string">---</span>
 <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
 <span class="hljs-attr">kind:</span> <span class="hljs-string">ServiceAccount</span>
 <span class="hljs-attr">metadata:</span>
   <span class="hljs-attr">name:</span> <span class="hljs-string">cluster-manager</span>
   <span class="hljs-attr">namespace:</span> <span class="hljs-string">kube-system</span>
</code></pre>
</li>
<li><p><strong>Shutdown Behavior</strong></p>
<p> For this, we will use a <code>CronJob</code> that allows us to schedule the time to scale down the load of deployments and consequently the nodes. In the specification of the <code>Job</code> as an entrypoint, we define a command that allows us to scale all deployments from N namespaces we specify to 0 <code>--replicas=0</code>. This is to ensure our downgrade is cleaner, gradual, and allows our Cluster AutoScaler to detect that there is no longer a load on the cluster and remove unused nodes.</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">batch/v1</span>
 <span class="hljs-attr">kind:</span> <span class="hljs-string">CronJob</span>
 <span class="hljs-attr">metadata:</span>
   <span class="hljs-attr">name:</span> <span class="hljs-string">shutdown-manager</span>
   <span class="hljs-attr">namespace:</span> <span class="hljs-string">kube-system</span>
 <span class="hljs-attr">spec:</span>
   <span class="hljs-attr">schedule:</span> <span class="hljs-string">"0 01 * * *"</span> <span class="hljs-comment"># Everyday 8 PM</span>
   <span class="hljs-attr">successfulJobsHistoryLimit:</span> <span class="hljs-number">1</span>
   <span class="hljs-attr">failedJobsHistoryLimit:</span> <span class="hljs-number">1</span>
   <span class="hljs-attr">jobTemplate:</span>
     <span class="hljs-attr">spec:</span>
       <span class="hljs-attr">template:</span>
         <span class="hljs-attr">spec:</span>
           <span class="hljs-attr">serviceAccountName:</span> <span class="hljs-string">cluster-manager</span>
           <span class="hljs-attr">containers:</span>
           <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">shutdown-manager</span>
             <span class="hljs-attr">image:</span> <span class="hljs-string">fluxcd/flux-cli:v2.1.2</span>
             <span class="hljs-attr">command:</span> [<span class="hljs-string">"/bin/sh"</span>, <span class="hljs-string">"-c"</span>]
             <span class="hljs-attr">args:</span>
             <span class="hljs-bullet">-</span> <span class="hljs-string">|
               echo "Start: $(date) - Environment: $environment"
               for i in namespace1 namespace2 namespace3
               do
                 kubectl scale deployment -n $i --all --replicas=0
               done
</span>             <span class="hljs-attr">env:</span>
             <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">TZ</span>
               <span class="hljs-attr">value:</span> <span class="hljs-string">America/Lima</span>
             <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">environment</span>
               <span class="hljs-attr">value:</span> <span class="hljs-string">dev</span>
           <span class="hljs-attr">restartPolicy:</span> <span class="hljs-string">OnFailure</span>
</code></pre>
</li>
<li><p><strong>Startup Behavior</strong></p>
<p> For this, we will use a <code>CronJob</code> that allows us to schedule the time to increase the load of deployments and therefore the nodes. In the <code>Job</code> specification as an entrypoint, we define a command that allows us to scale all deployments from N namespaces we define to 1 <code>--replicas=1</code>. This is to ensure our startup is cleaner, more gradual, and allows our Cluster AutoScaler to detect that there is load on the cluster and create new nodes.</p>
<blockquote>
<p>If some deployments need more than one replica, it's best to have a HorizontalPodAutoScaling associated with them, so that they can increase the replicas when needed.</p>
</blockquote>
<pre><code class="lang-yaml"> <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">batch/v1</span>
 <span class="hljs-attr">kind:</span> <span class="hljs-string">CronJob</span>
 <span class="hljs-attr">metadata:</span>
   <span class="hljs-attr">name:</span> <span class="hljs-string">startup-manager</span>
   <span class="hljs-attr">namespace:</span> <span class="hljs-string">kube-system</span>
 <span class="hljs-attr">spec:</span>
   <span class="hljs-attr">schedule:</span> <span class="hljs-string">"0 13 * * 1-5"</span> <span class="hljs-comment"># Monday to Friday - 8 AM</span>
   <span class="hljs-attr">successfulJobsHistoryLimit:</span> <span class="hljs-number">1</span>
   <span class="hljs-attr">failedJobsHistoryLimit:</span> <span class="hljs-number">1</span>
   <span class="hljs-attr">jobTemplate:</span>
     <span class="hljs-attr">spec:</span>
       <span class="hljs-attr">template:</span>
         <span class="hljs-attr">spec:</span>
           <span class="hljs-attr">serviceAccountName:</span> <span class="hljs-string">cluster-manager</span>
           <span class="hljs-attr">containers:</span>
           <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">startup-manager</span>
             <span class="hljs-attr">image:</span> <span class="hljs-string">fluxcd/flux-cli:v2.1.2</span>
             <span class="hljs-attr">command:</span> [<span class="hljs-string">"/bin/sh"</span>, <span class="hljs-string">"-c"</span>]
             <span class="hljs-attr">args:</span>
             <span class="hljs-bullet">-</span> <span class="hljs-string">|
               echo "Start: $(date) - Environment: $environment"
               for i in namespace1 namespace2 namespace3
               do
                 kubectl scale deployment -n $i --all --replicas=1
               done
</span>             <span class="hljs-attr">env:</span>
             <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">TZ</span>
               <span class="hljs-attr">value:</span> <span class="hljs-string">America/Lima</span>
             <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">environment</span>
               <span class="hljs-attr">value:</span> <span class="hljs-string">dev</span>
           <span class="hljs-attr">restartPolicy:</span> <span class="hljs-string">OnFailure</span>
</code></pre>
</li>
<li><p><strong>Conclusion</strong></p>
<p> In conclusion, implementing automated shutdown and startup procedures in Kubernetes non-production environments is a highly effective cost-saving strategy. By utilizing <code>CronJobs</code> to manage the scaling of deployments outside of work hours, organizations can significantly reduce their operational costs without impacting daily productivity. This approach not only optimizes resource usage but also contributes to a more sustainable operation by reducing unnecessary computational waste. Implementing such measures can lead to substantial financial savings, making it an attractive option for businesses looking to maximize their cloud infrastructure efficiency.</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[How to Optimize EC2 Costs with Auto-Scaling in Kubernetes Using Karpenter on EKS]]></title><description><![CDATA[Learn how to reduce EC2 costs using Karpenter for auto-scaling in Kubernetes on AWS EKS, enhancing efficiency and responsiveness]]></description><link>https://staz.cloud/how-to-optimize-ec2-costs-with-auto-scaling-in-kubernetes-using-karpenter-on-eks</link><guid isPermaLink="true">https://staz.cloud/how-to-optimize-ec2-costs-with-auto-scaling-in-kubernetes-using-karpenter-on-eks</guid><category><![CDATA[EKS]]></category><category><![CDATA[karpenter]]></category><category><![CDATA[ #Efficiency-optimization]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[auto-scaling-aws]]></category><dc:creator><![CDATA[Staz]]></dc:creator><pubDate>Thu, 18 Apr 2024 04:09:26 GMT</pubDate><content:encoded><![CDATA[<p><strong>TL;DR</strong> — Optimizing EC2 costs in Kubernetes using Karpenter on AWS EKS involves leveraging auto-scaling features to adjust resource allocation dynamically, reducing operational costs. Key steps include setting up Karpenter, configuring NodeClass and NodePool, and testing the setup with deployments to ensure efficient scaling and cost management. This approach enhances application responsiveness while optimizing cloud infrastructure expenses.</p>
<h3 id="heading-introduction">Introduction</h3>
<p>In today's digital landscape, where efficiency is paramount, technology teams are tasked with optimizing cloud services without sacrificing the availability, resilience, and quality of applications. This post explores how to reduce costs by optimizing autoscaling in Kubernetes clusters, with a focus on using Karpenter on AWS EKS. While this demonstration is specific to AWS, it's worth noting that Karpenter also supports other cloud platforms like Azure and GCP. Let's delve into the essential resources employed in this guide.</p>
<p>Below are some definitions of the resources that will be used:</p>
<ol>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/eks/">EKS</a>:</p>
<p> Elastic Kubernetes Service is a managed Kubernetes service provided by AWS that makes it easier for you to run Kubernetes on AWS without needing to install, operate, and maintain your own Kubernetes control plane or nodes. It handles much of the complexity of managing a Kubernetes cluster by automating tasks such as patching, node provisioning, and updates.</p>
</li>
<li><p><a target="_blank" href="https://karpenter.sh/">Karpenter</a>:</p>
<p> Karpenter is an open-source, flexible, high-performance Kubernetes cluster autoscaler built by AWS. It aims to optimize the provisioning and scaling of compute resources by quickly launching right-sized instances in response to application needs and resource utilization. Karpenter is designed to improve upon the limitations of the Kubernetes Cluster Autoscaler, offering more responsive scaling decisions and better integration with cloud provider capabilities.</p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/ec2/">EC2</a>:</p>
<p> Elastic Compute Cloud is an AWS service that provides resizable compute capacity in the cloud, designed to make web-scale computing easier for developers. EC2 offers several types of instances optimized for different tasks, and it includes options for cost savings such as:</p>
<ul>
<li><p><strong>On-Demand Instances</strong>: Pay for compute capacity by the hour or second (minimum of 60 seconds) with no long-term commitments. This provides flexibility for applications with short-term, spiky, or unpredictable workloads that cannot be interrupted.</p>
</li>
<li><p><strong>Reserved Instances</strong>: Provide a significant discount (up to 75%) compared to On-Demand pricing and are best for applications with steady state or predictable usage.</p>
</li>
<li><p><strong>Spot Instances</strong>: Allow you to request spare Amazon EC2 computing capacity for up to 90% off the On-Demand price. Suitable for flexible start and end times, applications that are only feasible at very low compute prices, and users with urgent computing needs for large amounts of additional capacity.</p>
</li>
<li><p><strong>Savings Plans</strong>: Offer significant savings over On-Demand pricing, like Reserved Instances, but with more flexibility in how you use your compute capacity.</p>
</li>
<li><p><strong>Dedicated Hosts</strong>: Physical servers with EC2 instance capacity fully dedicated to your use. They can help you reduce costs by allowing you to use your existing server-bound software licenses.</p>
</li>
</ul>
</li>
<li><p><a target="_blank" href="https://helm.sh/">Helm</a>:</p>
<p> Helm is a package manager for Kubernetes that allows developers to package, configure, and deploy applications and services onto Kubernetes clusters. It uses packages called charts, which are collections of files that describe a related set of Kubernetes resources. Helm helps in managing Kubernetes applications through Helm Charts which simplify the deployment and management of applications on Kubernetes.</p>
</li>
</ol>
<h3 id="heading-karpenter-installation">Karpenter installation</h3>
<p>To install Karpenter on AWS, we need to have an EKS cluster, a role for Karpenter's serviceAccount, another role for Karpenter's custom NodePool, 1 SQS queue, and Helm installed.</p>
<ol>
<li><p>Creating an SQS queue for Karpenter</p>
<pre><code class="lang-bash"> aws sqs create-queue --queue-name karpenter-interruption-queue --tags Key=karpenter.sh/discovery,Value=<span class="hljs-variable">${EKS_CLUSTER_NAME}</span>
</code></pre>
</li>
<li><p>Creating a role in AWS for Karpenter's custom NodePool</p>
<ul>
<li><p>We create our TrustedPolicy. We will save it in a <code>karpenter-nodePool-trust-policy.json</code> file:</p>
</li>
<li><pre><code class="lang-json">        {
            <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
            <span class="hljs-attr">"Statement"</span>: [
                {
                    <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
                    <span class="hljs-attr">"Principal"</span>: {
                        <span class="hljs-attr">"Service"</span>: <span class="hljs-string">"ec2.amazonaws.com"</span>
                    },
                    <span class="hljs-attr">"Action"</span>: <span class="hljs-string">"sts:AssumeRole"</span>
                }
            ]
        }
</code></pre>
</li>
<li><p>We create our role with the previous trustedPolicy by replacing <code>EKS_CLUSTER_NAME</code>:</p>
<pre><code class="lang-json">  aws iam create-role --role-name KarpenterNodeRole-${EKS_CLUSTER_NAME} --assume-role-policy-document file:<span class="hljs-comment">//karpenter-nodePool-trust-policy.json --tags Key=karpenter.sh/discovery,Value=${EKS_CLUSTER_NAME}</span>

  aws iam attach-role-policy --role-name KarpenterNodeRole-${EKS_CLUSTER_NAME} --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
  aws iam attach-role-policy --role-name KarpenterNodeRole-${EKS_CLUSTER_NAME} --policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
  aws iam attach-role-policy --role-name KarpenterNodeRole-${EKS_CLUSTER_NAME} --policy-arn arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
  aws iam attach-role-policy --role-name KarpenterNodeRole-${EKS_CLUSTER_NAME} --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
</code></pre>
</li>
</ul>
</li>
<li><p>Creating a role in AWS for the serviceAccount that will use Karpenter:</p>
<ul>
<li><p>Getting EKS OIDC Link:</p>
</li>
<li><pre><code class="lang-bash">        aws eks describe-cluster --name <span class="hljs-variable">${EKS_CLUSTER_NAME}</span> --query <span class="hljs-string">"cluster.identity.oidc.issuer"</span> --output text
</code></pre>
</li>
<li><p>Then we replace the <code>OIDC EKS LINK</code>, <code>AWS_ACCOUNT_ID</code>, and <code>AWS_REGION</code> in our TrustedPolicy. We will save it in a file called <code>karpenter-trust-policy.json</code>:</p>
<pre><code class="lang-json">    {
        <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
        <span class="hljs-attr">"Statement"</span>: [
            {
                <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
                <span class="hljs-attr">"Principal"</span>: {
                    <span class="hljs-attr">"Federated"</span>: <span class="hljs-string">"arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/oidc.eks.XXXX.amazonaws.com/id/XXXXXX"</span> 
                },
                <span class="hljs-attr">"Action"</span>: <span class="hljs-string">"sts:AssumeRoleWithWebIdentity"</span>,
                <span class="hljs-attr">"Condition"</span>: {
                    <span class="hljs-attr">"StringEquals"</span>: {
                        <span class="hljs-attr">"oidc.eks.XXXXX.amazonaws.com/id/XXXXX:sub"</span>: <span class="hljs-string">"system:serviceaccount:karpenter:karpenter"</span>
                    }
                }
            }
        ]
    }
</code></pre>
</li>
<li><p>Now we create the Karpenter policyController, replacing <code>AWS_REGION</code>, <code>EKS_CLUSTER_NAME</code>, and <code>AWS_ACCOUNT_ID</code>. We will save it in a file called <code>karpenter-policy.json</code>:</p>
</li>
</ul>
</li>
</ol>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="e79e381fd9a9207f790b616cecfe5679"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/stazdx/e79e381fd9a9207f790b616cecfe5679" class="embed-card">https://gist.github.com/stazdx/e79e381fd9a9207f790b616cecfe5679</a></div><p> </p>
<ol>
<li><ul>
<li><p>We create the role by attaching the previously created policies and replacing <code>EKS_CLUSTER_NAME</code>:</p>
<pre><code class="lang-json">   aws iam create-role --role-name karpenterSARole --assume-role-policy-document file:<span class="hljs-comment">//karpenter-trust-policy.json --tags Key=karpenter.sh/discovery,Value=${EKS_CLUSTER_NAME}</span>

   aws iam put-role-policy --role-name karpenterSARole --policy-name KarpenterSAPolicy --policy-document file:<span class="hljs-comment">//karpenter-policy.json</span>
</code></pre>
<ul>
<li>Installing <code>Karpenter</code> using <code>Helm</code>:</li>
</ul>
</li>
</ul>
</li>
</ol>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> CLUSTER_ENDPOINT=<span class="hljs-string">"<span class="hljs-subst">$(aws eks describe-cluster --name ${EKS_CLUSTER_NAME} --query <span class="hljs-string">"cluster.endpoint"</span> --output text)</span>"</span>

helm upgrade --install --namespace karpenter --create-namespace \
  karpenter oci://public.ecr.aws/karpenter/karpenter \
  --version 0.36.0 \
  --<span class="hljs-built_in">set</span> serviceAccount.annotations.<span class="hljs-string">"eks\.amazonaws\.com/role-arn"</span>=karpenterSARole \
  --<span class="hljs-built_in">set</span> settings.aws.clusterName=<span class="hljs-variable">${EKS_CLUSTER_NAME}</span> \
  --<span class="hljs-built_in">set</span> settings.aws.clusterEndpoint=<span class="hljs-variable">${CLUSTER_ENDPOINT}</span> \
  --<span class="hljs-built_in">set</span> defaultProvisioner.create=<span class="hljs-literal">true</span> \
  --<span class="hljs-built_in">set</span> settings.aws.interruptionQueueName=karpenter-interruption-queue
</code></pre>
<h3 id="heading-creating-nodeclass-and-nodepool">Creating NodeClass and NodePool</h3>
<ol>
<li><p>Tagging EKS resources for our NodeClass policy: <code>EKS, VPC, Private Subnets, EKS Security Groups</code></p>
</li>
<li><pre><code class="lang-bash">    aws eks tag-resource --resource-arn <span class="hljs-variable">${EKS_ARN}</span> --tags karpenter.sh/discovery=<span class="hljs-variable">${EKS_CLUSTER_NAME}</span>

    aws ec2 create-tags \
        --resources <span class="hljs-variable">${VPC_ID}</span> \
        <span class="hljs-variable">${PRIVATE_SUBNET1_ID}</span> <span class="hljs-variable">${PRIVATE_SUBNET2_ID}</span> <span class="hljs-variable">${PRIVATE_SUBNET3_ID}</span> \
        <span class="hljs-variable">${EKS_SG1_ID}</span> <span class="hljs-variable">${EKS_SG2_ID}</span> \
        --tags Key=karpenter.sh/discovery,Value=<span class="hljs-variable">${EKS_CLUSTER_NAME}</span>
</code></pre>
</li>
<li><p>Creating NodeClass, this resource defines the EC2 instance family, in our case <code>AL2</code> which are Amazon Linux 2, as well as the role we previously created. This role will manage Karpenter's EC2 resources, and we can also filter by tags, for us it will be the tag <code>karpenter.sh/discovery: "${EKS_CLUSTER_NAME}"</code>. Additionally, we can see the private subnets where Karpenter will deploy the instances it needs, as well as the availability zone they belong to. We will save it as <code>nodeclass.yaml</code>.</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">karpenter.k8s.aws/v1beta1</span>
 <span class="hljs-attr">kind:</span> <span class="hljs-string">EC2NodeClass</span>
 <span class="hljs-attr">metadata:</span>
   <span class="hljs-attr">name:</span> <span class="hljs-string">MyNodeClass</span>
 <span class="hljs-attr">spec:</span>
   <span class="hljs-attr">amiFamily:</span> <span class="hljs-string">AL2</span> <span class="hljs-comment"># Amazon Linux 2</span>
   <span class="hljs-attr">role:</span> <span class="hljs-string">"KarpenterNodeRole-${EKS_CLUSTER_NAME}"</span> <span class="hljs-comment"># replace with your cluster name</span>
   <span class="hljs-attr">subnetSelectorTerms:</span>
     <span class="hljs-bullet">-</span> <span class="hljs-attr">tags:</span>
         <span class="hljs-attr">karpenter.sh/discovery:</span> <span class="hljs-string">"${EKS_CLUSTER_NAME}"</span> <span class="hljs-comment"># replace with your cluster name</span>
   <span class="hljs-attr">securityGroupSelectorTerms:</span>
     <span class="hljs-bullet">-</span> <span class="hljs-attr">tags:</span>
         <span class="hljs-attr">karpenter.sh/discovery:</span> <span class="hljs-string">"${EKS_CLUSTER_NAME}"</span> <span class="hljs-comment"># replace with your cluster name</span>
 <span class="hljs-attr">status:</span>
   <span class="hljs-attr">subnets:</span>
   <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">subnet-XXXXXX</span>
     <span class="hljs-attr">zone:</span> <span class="hljs-string">${AZ1}</span>
   <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">subnet-XXXXXX</span>
     <span class="hljs-attr">zone:</span> <span class="hljs-string">${AZ2}</span>
   <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">subnet-XXXXXX</span>
     <span class="hljs-attr">zone:</span> <span class="hljs-string">${AZ3}</span>
</code></pre>
<p> Run:</p>
<pre><code class="lang-bash"> kubectl apply -f nodeclass.yaml -n karpenter
</code></pre>
</li>
<li><p>Creating NodePool, this resource will allow you to configure the requirements for our EC2 instances. You can choose from various categories, types, families, etc. Karpenter will orchestrate the necessary one based on the cluster load. We will save it as <code>nodepool.yaml</code>.</p>
<p> In this case, we will use <code>EC2 Spot instances</code> since they save us up to 90% of the cost of an EC2 on-demand.</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">karpenter.sh/v1beta1</span>
 <span class="hljs-attr">kind:</span> <span class="hljs-string">NodePool</span>
 <span class="hljs-attr">metadata:</span>
   <span class="hljs-attr">name:</span> <span class="hljs-string">default</span>
 <span class="hljs-attr">spec:</span>
   <span class="hljs-attr">template:</span>
     <span class="hljs-attr">metadata:</span>
       <span class="hljs-attr">labels:</span>
         <span class="hljs-attr">app:</span> <span class="hljs-string">my-app</span>
     <span class="hljs-attr">spec:</span>
       <span class="hljs-attr">requirements:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">key:</span> <span class="hljs-string">kubernetes.io/arch</span>
           <span class="hljs-attr">operator:</span> <span class="hljs-string">In</span>
           <span class="hljs-attr">values:</span> [<span class="hljs-string">"amd64"</span>]
         <span class="hljs-bullet">-</span> <span class="hljs-attr">key:</span> <span class="hljs-string">kubernetes.io/os</span>
           <span class="hljs-attr">operator:</span> <span class="hljs-string">In</span>
           <span class="hljs-attr">values:</span> [<span class="hljs-string">"linux"</span>]
         <span class="hljs-bullet">-</span> <span class="hljs-attr">key:</span> <span class="hljs-string">karpenter.sh/capacity-type</span>
           <span class="hljs-attr">operator:</span> <span class="hljs-string">In</span>
           <span class="hljs-attr">values:</span> [<span class="hljs-string">"spot"</span>] <span class="hljs-comment"># ["spot", "on-demand"]</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">key:</span> <span class="hljs-string">karpenter.k8s.aws/instance-category</span>
           <span class="hljs-attr">operator:</span> <span class="hljs-string">In</span>
           <span class="hljs-attr">values:</span> [<span class="hljs-string">"r"</span>] <span class="hljs-comment"># ["c", "m", "r"]</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">key:</span> <span class="hljs-string">karpenter.k8s.aws/instance-family</span>
           <span class="hljs-attr">operator:</span> <span class="hljs-string">In</span>
           <span class="hljs-attr">values:</span> [<span class="hljs-string">"r6a"</span>] <span class="hljs-comment"># ["c7a", "m5", "r7a"]</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">key:</span> <span class="hljs-string">node.kubernetes.io/instance-type</span>
           <span class="hljs-attr">operator:</span> <span class="hljs-string">In</span>
           <span class="hljs-attr">values:</span> [<span class="hljs-string">"r6a.large"</span>, <span class="hljs-string">"r6a.xlarge"</span>] <span class="hljs-comment"># ["c7a.large", "m5.xlarge", "r7a.large"]</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">key:</span> <span class="hljs-string">"topology.kubernetes.io/zone"</span>
           <span class="hljs-attr">operator:</span> <span class="hljs-string">In</span>
           <span class="hljs-attr">values:</span> [<span class="hljs-string">"xx-xxxx-xx"</span>, <span class="hljs-string">"xx-xxxx-yy"</span>, <span class="hljs-string">"xx-xxxx-zz"</span>] <span class="hljs-comment"># ["us-east-1a", "us-east-1b", "us-east-1c"]</span>
       <span class="hljs-attr">nodeClassRef:</span>
         <span class="hljs-attr">name:</span> <span class="hljs-string">MyNodeClass</span>
   <span class="hljs-attr">disruption:</span>
     <span class="hljs-attr">consolidationPolicy:</span> <span class="hljs-string">WhenUnderutilized</span>
     <span class="hljs-comment"># consolidationPolicy: WhenEmpty</span>
     <span class="hljs-comment"># consolidateAfter: 30s</span>
     <span class="hljs-attr">expireAfter:</span> <span class="hljs-string">720h</span> <span class="hljs-comment"># 30 * 24h = 720h</span>
</code></pre>
<p> Run:</p>
<pre><code class="lang-bash"> kubectl apply -f nodepool.yaml -n karpenter
</code></pre>
</li>
</ol>
<h3 id="heading-testing-karpenter">Testing Karpenter</h3>
<ol>
<li><p>Running a <code>deployment</code></p>
<pre><code class="lang-bash"> cat &lt;&lt;EOF &gt; test.yaml
 apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: <span class="hljs-built_in">test</span>
 spec:
   replicas: 0
   selector:
     matchLabels:
       app: <span class="hljs-built_in">test</span>
   template:
     metadata:
       labels:
         app: <span class="hljs-built_in">test</span>
     spec:
       containers:
         - name: <span class="hljs-built_in">test</span>
           image: nginx
           resources:
             requests:
               cpu: 1
               memory: 1.5Gi
 EOF
 kubectl apply -f test.yaml
</code></pre>
</li>
<li><p>Now we will scale this deployment to see how Karpenter does its job. It will deploy new instances due to the demand for resources.</p>
<pre><code class="lang-bash"> kubectl scale deploy <span class="hljs-built_in">test</span> --replicas=8
</code></pre>
</li>
<li><p>We see the Karpenter logs in action.</p>
<pre><code class="lang-bash"> kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
</code></pre>
</li>
<li><p>After seeing how it has scaled, we can delete the deployment so Karpenter can do node downscale.</p>
<pre><code class="lang-bash"> kubectl delete deployment <span class="hljs-built_in">test</span>

 kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
</code></pre>
</li>
</ol>
<h3 id="heading-conclusion">Conclusion</h3>
<p>In conclusion, implementing Karpenter on AWS EKS is a robust solution for optimizing EC2 costs through efficient auto-scaling. By leveraging different EC2 options like Spot Instances and integrating with Kubernetes, Karpenter enhances the responsiveness and cost-effectiveness of resource allocation. This setup not only reduces operational costs but also ensures that applications run smoothly by dynamically adjusting to workload demands. As cloud technologies evolve, tools like Karpenter represent a significant advancement in managing cloud resources more effectively, making them indispensable for businesses looking to optimize their cloud infrastructure.</p>
]]></content:encoded></item><item><title><![CDATA[Building a Helm Chart for Microservices]]></title><description><![CDATA[Helm chart for microservices]]></description><link>https://staz.cloud/building-a-helm-chart-for-microservices</link><guid isPermaLink="true">https://staz.cloud/building-a-helm-chart-for-microservices</guid><category><![CDATA[Helm]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[cloud native]]></category><dc:creator><![CDATA[Staz]]></dc:creator><pubDate>Sun, 26 Jun 2022 08:04:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1655733627210/k5LVw-17c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction</h3>
<p><a target="_blank" href="https://helm.sh/">Helm</a> is a package manager for Kubernetes that helps us to deploy custom applications and generate parameterized templates. This is very useful when it comes to deploying microservices on Kubernetes.</p>
<p>Helm was accepted into CNCF on January 18, 2016 and is at the Graduate project maturity level.</p>
<p>The package format used by Helm is called a <strong>chart</strong>. Helm has a command-line tool that makes it easy to create charts for us to customize and then deploy. Also has a registry where the community and companies share their charts. If you are looking for one, it probably already exists, you can check it out <a target="_blank" href="https://artifacthub.io/">here</a>.</p>
<p>You can review the <a target="_blank" href="https://helm.sh/docs/topics/charts/">documentation</a> for more information on charts and best practices in its implementation.</p>
<h3 id="heading-demo">Demo</h3>
<p>This demo applies to any programming language and application/microservice you need to deploy on Kubernetes. Remember that it is a template and it is customizable.</p>
<h4 id="heading-install-and-run-an-ubuntu-instance"><strong>Install and run an Ubuntu instance</strong></h4>
<p>I will use <a target="_blank" href="https://multipass.run/">multipass</a> to generate an ubuntu instance and <a target="_blank" href="https://microk8s.io/">microk8s</a> to deploy kubernetes. Both are very good tools developed by <a target="_blank" href="https://canonical.com/">Canonical</a>.</p>
<p>Install <a target="_blank" href="https://multipass.run/install">multipass</a>.</p>
<p>For this demo, create a very light ubuntu instance with 2GB of RAM and 5 GB of Disk space:</p>
<p><code>$ multipass launch --name demo --mem 2G --disk 5G</code></p>
<h4 id="heading-install-and-setup-microk8s"><strong>Install and setup microk8s</strong></h4>
<p>After that, install microk8s inside the ubuntu instance:</p>
<pre><code class="lang-plaintext">$ multipass shell demo
$ sudo su
$ snap install microk8s --classic --channel=1.18/stable
$ iptables -P FORWARD ACCEPT
</code></pre>
<blockquote>
<p>The iptables command is necessary to permit traffic between the VM and host.</p>
</blockquote>
<p>Create an alias for kubectl command:</p>
<pre><code class="lang-plaintext">$ alias k="microk8s kubectl"
$ k cluster-info
</code></pre>
<p>We already have our lightweight Kubernetes cluster using microk8s.</p>
<h4 id="heading-install-and-setup-helm"><strong>Install and setup Helm</strong></h4>
<pre><code class="lang-plaintext">$ snap install helm --classic
$ helm
$ microk8s config &gt; ~/.kube/config
</code></pre>
<blockquote>
<p>It is important to copy the microk8s configuration to the <code>~/.kube/config</code> directory for Helm to work.</p>
</blockquote>
<h4 id="heading-create-a-helm-chart"><strong>Create a Helm Chart</strong></h4>
<p>Then, we can create a Helm chart for our microservice.</p>
<pre><code class="lang-plaintext">$ helm create microservice-template
$ snap install tree
$ tree microservice-template
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656222437471/PkQS2ZBEM.png" alt="image.png" /></p>
<p>This is the file structure of a chart. In the following, we will explain the most important ones:</p>
<h4 id="heading-the-most-important-files-of-a-chart"><strong>The most important files of a chart</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656222900127/nQavj-KeG.png" alt="image.png" /></p>
<p><code>Chart.yaml</code> is required, and in this gist repository you have all the allowed fields. You can call their variables like this <code>{{ .Chart.my_variable }}</code></p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="1cc56addc9eadccc72cbf5127f758a0c"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/stazdx/1cc56addc9eadccc72cbf5127f758a0c" class="embed-card">https://gist.github.com/stazdx/1cc56addc9eadccc72cbf5127f758a0c</a></div><p> </p>
<p><code>values.yaml</code> is the file containing all customizable input parameters for your chart. You can call their variables like this <code>{{ .Values.my_variable }}</code></p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="778d1f3b3479420279426fff8bd5b31a"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/stazdx/778d1f3b3479420279426fff8bd5b31a" class="embed-card">https://gist.github.com/stazdx/778d1f3b3479420279426fff8bd5b31a</a></div><p> </p>
<p><code>templates/_helpers.tpl</code> is the file where you can transform your input values and even define global variables for your chart. These cannot be entered as input parameters.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="171882dc0b14e26aa04abfc08d13e4c2"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/stazdx/171882dc0b14e26aa04abfc08d13e4c2" class="embed-card">https://gist.github.com/stazdx/171882dc0b14e26aa04abfc08d13e4c2</a></div><p> </p>
<p>Then there are the kubernetes manifests <code>*.yaml</code> which are the ones that will be created in your kubernetes cluster. These receive the input variables and the ones you have defined in <code>Chart.yaml</code>, <code>values.yaml</code> and <code>_helpers.tpl</code> files. Here is an example of what you can call them.</p>
<h4 id="heading-using-conditionals-and-calling-variables"><strong>Using conditionals and calling variables</strong></h4>
<p>For example in the <code>templates/hpa.yaml</code>, this is a Horizontal Pods Autoscaler manifest that uses variables from the <code>values.yaml file</code>, <code>_helpers.tpl</code> and even uses <code>if</code> conditional to create certain properties:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656224548066/EnRvqxUfV.png" alt="image.png" /></p>
<p>The conditional says that if the value of the <code>.Values.autoscaling.enabled</code> variable is <code>true</code> the whole manifest will be created, but if it is <code>false</code>, it will not create it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656224837371/a79VDK0Ic.png" alt="image.png" /></p>
<p>In this case, in <code>values.yaml</code>, the <code>.Values.autoscaling.enabled</code> parameter is <code>false</code>, then it will not create it.</p>
<p>Also, in the <code>templates/hpa.yaml</code> file uses <code>microservice-template.fullname</code> and <code>microservice-template.labels</code>, these refers to the variable that was defined in the <code>_helpers.tpl</code> file.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656224788566/j1KqE5Izf.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656225516547/PZi1OyggT.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656225567173/y1hz6l4XG.png" alt="image.png" /></p>
<p>Also uses <code>.Chart.name</code>, <code>.Chart.AppVersion</code> that are found within <code>Chart.yaml</code> file.</p>
<blockquote>
<p><code>.Release.Name</code> refers to the name you assign when you install the chart, for example: <code>helm install demo .</code> -&gt; . <code>demo</code> is the <code>.Release.Name</code> variable and <code>.Release.Service</code> is <code>Helm</code>.</p>
</blockquote>
<h4 id="heading-validate-a-helm-chart">** Validate a Helm chart**</h4>
<p><code>helm lint</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656227516699/nNipu7Kdf.png" alt="image.png" /></p>
<h4 id="heading-create-kubernetes-manifests-using-helm"><strong>Create Kubernetes manifests using Helm</strong></h4>
<p><code>helm template .</code></p>
<p>Output:</p>
<pre><code class="lang-plaintext">WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /root/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /root/.kube/config
---
# Source: microservice-template/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: RELEASE-NAME-microservice-template
  labels:
    helm.sh/chart: microservice-template-0.1.0
    app.kubernetes.io/name: microservice-template
    app.kubernetes.io/instance: RELEASE-NAME
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
---
# Source: microservice-template/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: RELEASE-NAME-microservice-template
  labels:
    helm.sh/chart: microservice-template-0.1.0
    app.kubernetes.io/name: microservice-template
    app.kubernetes.io/instance: RELEASE-NAME
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: microservice-template
    app.kubernetes.io/instance: RELEASE-NAME
---
# Source: microservice-template/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: RELEASE-NAME-microservice-template
  labels:
    helm.sh/chart: microservice-template-0.1.0
    app.kubernetes.io/name: microservice-template
    app.kubernetes.io/instance: RELEASE-NAME
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: microservice-template
      app.kubernetes.io/instance: RELEASE-NAME
  template:
    metadata:
      labels:
        app.kubernetes.io/name: microservice-template
        app.kubernetes.io/instance: RELEASE-NAME
    spec:
      serviceAccountName: RELEASE-NAME-microservice-template
      securityContext:
        {}
      containers:
        - name: microservice-template
          securityContext:
            {}
          image: "nginx:1.16.0"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            {}
---
# Source: microservice-template/templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
  name: "RELEASE-NAME-microservice-template-test-connection"
  labels:
    helm.sh/chart: microservice-template-0.1.0
    app.kubernetes.io/name: microservice-template
    app.kubernetes.io/instance: RELEASE-NAME
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']
      args: ['RELEASE-NAME-microservice-template:80']
  restartPolicy: Never
</code></pre>
<h4 id="heading-debug-and-dry-run-a-helm-chart"><strong>Debug and dry-run a Helm Chart</strong></h4>
<p>This command simulates a Helm Chart install and generates k8s manifests:</p>
<p><code>helm upgrade --install demo . --debug --dry-run</code></p>
<p>Output:</p>
<pre><code class="lang-plaintext">WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /root/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /root/.kube/config
history.go:56: [debug] getting history for release demo
upgrade.go:139: [debug] preparing upgrade for demo
upgrade.go:147: [debug] performing update for demo
upgrade.go:310: [debug] dry run for demo
Release "demo" has been upgraded. Happy Helming!
NAME: demo
LAST DEPLOYED: Sun Jun 26 02:15:07 2022
NAMESPACE: default
STATUS: pending-upgrade
REVISION: 2
USER-SUPPLIED VALUES:
{}

COMPUTED VALUES:
affinity: {}
autoscaling:
  enabled: false
  maxReplicas: 100
  minReplicas: 1
  targetCPUUtilizationPercentage: 80
fullnameOverride: ""
image:
  pullPolicy: IfNotPresent
  repository: nginx
  tag: ""
imagePullSecrets: []
ingress:
  annotations: {}
  className: ""
  enabled: false
  hosts:
  - host: chart-example.local
    paths:
    - path: /
      pathType: ImplementationSpecific
  tls: []
nameOverride: ""
nodeSelector: {}
podAnnotations: {}
podSecurityContext: {}
replicaCount: 1
resources: {}
securityContext: {}
service:
  port: 80
  type: ClusterIP
serviceAccount:
  annotations: {}
  create: true
  name: ""
tolerations: []

HOOKS:
---
# Source: microservice-template/templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
  name: "demo-microservice-template-test-connection"
  labels:
    helm.sh/chart: microservice-template-0.1.0
    app.kubernetes.io/name: microservice-template
    app.kubernetes.io/instance: demo
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']
      args: ['demo-microservice-template:80']
  restartPolicy: Never
MANIFEST:
---
# Source: microservice-template/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: demo-microservice-template
  labels:
    helm.sh/chart: microservice-template-0.1.0
    app.kubernetes.io/name: microservice-template
    app.kubernetes.io/instance: demo
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
---
# Source: microservice-template/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: demo-microservice-template
  labels:
    helm.sh/chart: microservice-template-0.1.0
    app.kubernetes.io/name: microservice-template
    app.kubernetes.io/instance: demo
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: microservice-template
    app.kubernetes.io/instance: demo
---
# Source: microservice-template/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-microservice-template
  labels:
    helm.sh/chart: microservice-template-0.1.0
    app.kubernetes.io/name: microservice-template
    app.kubernetes.io/instance: demo
    app.kubernetes.io/version: "1.16.0"
    app.kubernetes.io/managed-by: Helm
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: microservice-template
      app.kubernetes.io/instance: demo
  template:
    metadata:
      labels:
        app.kubernetes.io/name: microservice-template
        app.kubernetes.io/instance: demo
    spec:
      serviceAccountName: demo-microservice-template
      securityContext:
        {}
      containers:
        - name: microservice-template
          securityContext:
            {}
          image: "nginx:1.16.0"
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            {}

NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=microservice-template,app.kubernetes.io/instance=demo" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT
</code></pre>
<h4 id="heading-install-a-helm-chart"><strong>Install a Helm chart</strong></h4>
<p><code>$ helm install demo .</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656228418550/9TNz6vXwt.png" alt="image.png" /></p>
<p><code>$ helm ls</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656229104030/gb6HWC5Nj.png" alt="image.png" /></p>
<p><code>$ k get all</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656229141893/h5cjE5d29.png" alt="image.png" /></p>
<blockquote>
<p>I recommend using <code>helm upgrade --install</code>, because if the chart already exists, it will only apply the changes. If you use <code>helm install</code> and the chart already exists, returns an error.</p>
</blockquote>
<h4 id="heading-uninstall-a-helm-chart"><strong>Uninstall a Helm chart</strong></h4>
<p><code>$ helm del demo</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656229486220/38FAE8nat.png" alt="image.png" /></p>
<h4 id="heading-github-example"><strong>Github example</strong></h4>
<p>Here, a simple example of a springboot chart:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/stazdx/springboot-helm-chart">https://github.com/stazdx/springboot-helm-chart</a></div>
<p> </p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>You can use Helm if you have a lot of microservices and you want a template to easily deploy them in a parameterized way. Helm is not only for microservices, actually you can use it for everything.</p>
<p>I hope you find it useful and can apply it.</p>
<p>Later on we will see how powerful your deployments can be using GitOps, Kustomize and Helm.</p>
]]></content:encoded></item><item><title><![CDATA[Setting up Open Source Observability Stack on Kubernetes from scratch]]></title><description><![CDATA[Setting up Open Source Observability Stack on Kubernetes from scratch with Grafana Open Source Stack]]></description><link>https://staz.cloud/setting-up-open-source-observability-stack-on-kubernetes-from-scratch</link><guid isPermaLink="true">https://staz.cloud/setting-up-open-source-observability-stack-on-kubernetes-from-scratch</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[monitoring]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[#prometheus]]></category><category><![CDATA[loki]]></category><dc:creator><![CDATA[Staz]]></dc:creator><pubDate>Fri, 10 Jun 2022 05:19:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1655608234382/UNQF5e5lx.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>TL;DR</strong> — In this post we will use the Grafana Open source Stack, using Prometheus, Loki, Tempo as datasources to obtain metrics, logs and traces respectively. We will also correlate the trace_id with Java metrics using Exemplars and we will instrument a Spring Boot application with Opentelemetry. All this deployed on a Kubernetes cluster.</p>
<p>If you are an advanced user and want to test it directly, just check the <code>README.md</code> -&gt; <a target="_blank" href="https://github.com/stazdx/otel-springboot-grafana-tools">Github</a></p>
<p><strong>Resources:</strong></p>
<ul>
<li><p><a target="_blank" href="https://gist.github.com/stazdx/67fc87eb3eac6622f0d03fbfc6043b12">Spring boot project files</a></p>
</li>
<li><p><a target="_blank" href="https://gist.github.com/stazdx/0ee263ba9c8996ddac7ba62a377e32fc">Observability tools manifests</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/stazdx/otel-springboot-grafana-tools.git">Github repository</a></p>
</li>
</ul>
<p>I always follow closely new open source tools and especially if it is about observability tools, I consider that the world of observability is very broad, complex and every time new interesting tools come out. Now, I present you some tools that have caught my attention. Before starting a brief introduction about the tools.</p>
<ol>
<li><p><a target="_blank" href="https://opentelemetry.uptrace.dev/"><strong>OpenTelemetry</strong></a>: It helps us to collect metrics, logs and traces using an agent; then it can send them to other tools. It has a lot of instrumentation power and integration with several programming languages. It is in incubation within the CNCF.</p>
</li>
<li><p><a target="_blank" href="https://prometheus.io/"><strong>Prometheus</strong></a>: One of the largest and best known tools in the world of metric collection, it is graduated at the CNCF.</p>
</li>
<li><p><a target="_blank" href="https://grafana.com/docs/grafana/latest/basics/exemplars/"><strong>Exemplars</strong></a>: Tool that correlates the trace_id with metrics on the backend side. It is interesting because through resource metrics you can get traces and get to the log. It works hand in hand with Prometheus.</p>
</li>
<li><p><a target="_blank" href="https://grafana.com/docs/loki/latest/clients/promtail/"><strong>Promtail</strong></a>: Tool that sends the logs to Loki, just as Prometheus collects logs and works as an agent in each node of the cluster.</p>
</li>
<li><p><a target="_blank" href="https://grafana.com/oss/loki/"><strong>Loki</strong></a>: Tool that receives logs, centralizes them, distributes them, ingests, allows applying rules and has its own syntax to perform queries (LogQL). It has very good integration with Kubernetes.</p>
</li>
<li><p><a target="_blank" href="https://grafana.com/oss/tempo/"><strong>Tempo</strong></a>: Tool that receives OpenTelemetry traces to be able to visualize them with an embedded interface (Jaeger). It has an intuitive visualization and integrates with Loki and exemplars.</p>
</li>
<li><p><a target="_blank" href="https://grafana.com/"><strong>Grafana</strong></a>: Interface that allows to integrate multiple datasources, dashboards, alerts and many other visualization tools.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654493053812/JPHoVMMts.png" alt="blog_archobstools.png" class="image--center mx-auto" /></p>
<h3 id="heading-setting-up-a-backend-application"><strong>Setting up a Backend Application</strong></h3>
<p>For this demo we will use Java - Spring Boot, since it is very common to see microservices projects with this framework.</p>
<p>The first thing we need is to create a Java Spring boot project, we will use https://start.spring.io/ . It helps you to quickly generate a Java project using Spring Boot, it also allows you to add the dependencies you will use and other configurations to the project.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653783797799/kXUXU1_kg.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Make sure to configure it as shown in the screenshot. In this case I will use Gradle as build automation tool, Spring Boot in version 2.7, JAR as package build format and JDK11.</p>
<p>When you finish the configuration click on Generate; you can download, unzip and import it into your favorite IDE (I will use Visual Studio Code, you can download it <a target="_blank" href="https://code.visualstudio.com/download">here</a>).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653784693021/KfvCP8CA1.png" alt="image.png" class="image--center mx-auto" /></p>
<p>We will set up our <em>build.gradle</em> in the root of the project, make sure you have all the dependencies:</p>
<pre><code class="lang-plaintext">plugins {
    id 'org.springframework.boot' version '2.7.0'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

repositories {
  maven {
    url = uri('https://repo.spring.io/libs-snapshot')
  }
    mavenCentral()
}

dependencyManagement {
  imports {
    mavenBom 'io.micrometer:micrometer-bom:1.9.0-SNAPSHOT'
  }
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-actuator'
  implementation 'io.micrometer:micrometer-registry-prometheus:1.9.0'
  implementation 'org.springframework.boot:spring-boot-starter-web'
  implementation 'io.opentelemetry:opentelemetry-api:1.12.0'
}

tasks.named('test') {
    useJUnitPlatform()
}

group = 'com.staz'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
</code></pre>
<p>Then we will create a Controller Class <code>Controller.java</code> with 2 endpoints: <code>/fail</code> and <code>success</code>. The file must be located in the path <code>${project}/src/main/java/com/staz/observability/</code> in my case.</p>
<pre><code class="lang-plaintext">package com.staz.observability;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Controller {

    @PostMapping("/fail")
    public String fail() {
        return "Fail!";
    }

    @GetMapping("/success")
    public String success() {

        return "Success!";
    }

}
</code></pre>
<p>In order to correlate the Java metrics sent through prometheus with the trace_id generated by OpenTelemetry (using Exemplar), we will need to add a Configuration Class that will apply for the whole project <code>PrometheusExemplarConfiguration.java</code> in the same path as the previous <code>${project}/src/main/java/com/staz/observability/</code>:</p>
<pre><code class="lang-plaintext">package com.staz.observability;

import io.micrometer.core.instrument.Clock;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.opentelemetry.api.trace.Span;
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.exemplars.DefaultExemplarSampler;
import io.prometheus.client.exemplars.tracer.otel_agent.
       OpenTelemetryAgentSpanContextSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class PrometheusExemplarConfiguration {
    @Bean
    public PrometheusMeterRegistry prometheusMeterRegistryWithExemplar
    (PrometheusConfig prometheusConfig, CollectorRegistry collectorRegistry, 
    Clock clock) {
        return new PrometheusMeterRegistry(prometheusConfig, collectorRegistry, 
        clock, new DefaultExemplarSampler(new OpenTelemetryAgentSpanContextSupplier() {

                    @Override
                    public String getTraceId() {
                        if (!Span.current().getSpanContext().isSampled()) {
                            return null;
                        }
                        return super.getTraceId();
                    }
                })
        );
    }
}
</code></pre>
<p>Now, set the properties file <code>application.yml</code> -&gt; <code>${project}/src/main/resources/</code> :</p>
<pre><code class="lang-plaintext"># Enable Actuator endpoints including Prometheus
management:
  endpoints:
    web:
      exposure:
        include: health, info, prometheus
  metrics:
    # Exemplar metrics
    distribution:
      percentiles-histogram:
        http.server.requests: true
      minimum-expected-value:
        http.server.requests: 5ms
      maximum-expected-value:
        http.server.requests: 1000ms

# Add trace_id in log. OpenTelemetry set this value using logger-mdc.
# https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/logger-mdc-instrumentation.md
logging:
  pattern:
    level: '%prefix(%mdc{trace_id:-0}) %5p'
</code></pre>
<p>In order to run it in our local environment we will need the <code>OpenTelemetry Agent</code>, for this demo I am using version <code>1.12.1</code>. You can download it <a target="_blank" href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.12.1/opentelemetry-javaagent.jar">here</a>.</p>
<p>Done, now in the terminal, from the root path of our project run:</p>
<p><code>$ gradle build -x test</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653793074395/bV-naLzHk.png" alt="image.png" /></p>
<p>Then to start Spring Boot with the Opentelemetry Agent, our project pointing to application.yml, run:</p>
<p><code>$ java -javaagent:opentelemetry-javaagent.jar -Dspring.config.location=src/main/resources/application.yml -jar build/libs/observability-0.0.1-SNAPSHOT.jar</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653793294370/bPytBsEcx.png" alt="image.png" /></p>
<p>Our application has been deployed on the default port for Spring Boot which is <code>8080</code>. We will test our 2 endpoints <code>htttp://localhost:8080/fail</code> and <code>htttp://localhost:8080/success</code>.</p>
<p>For the first one the result should be a <code>405 Method Not Allowed error</code> and for the second one it should show the message <code>Success!</code> with status code <code>200</code>, this is how the result should look like:</p>
<p>Testing <code>/fail</code> endpoint:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653793699344/pR5JGiXyA.png" alt="image.png" /></p>
<p>Testing <code>/success</code> endpoint:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653793759656/ZHpaNWDZZ.png" alt="image.png" /></p>
<p>After having tested our endpoints we are going to see our Prometheus metrics, we will go to our actuator endpoint: http://localhost:8080/actuator/prometheus and look for the metrics we are interested in <code>http_server_requests_seconds_count</code>, <code>http_server_requests_seconds_sum</code> and <code>http_server_requests_seconds_bucket</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653794130224/lV4eYUetV.png" alt="image.png" /></p>
<p>To make sure Exemplar is correlating lod <code>trace_id</code> we will run a curl with the <code>OpenMetrics</code> header:</p>
<p><code>$ curl -H 'Accept: application/openmetrics-text; version=1.0.0; charset=utf-8' http://localhost:8080/actuator/prometheus | grep trace_id</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654496649080/sTm8iYBDK.png" alt="image.png" /></p>
<blockquote>
<p>We have our Spring Boot application ready, now we need to install the observability tools. Before that, we are going to install it on Kubernetes <code>k3s</code> locally, using <code>Multipass</code> we will create an ubuntu node and <code>Helm</code> to deploy our tools inside our single-node cluster.</p>
</blockquote>
<h3 id="heading-containerizing-our-spring-boot-project"><strong>Containerizing our Spring Boot project</strong></h3>
<p>Before creating our local cluster, let's create the <code>Dockerfile</code> for our Spring Boot Application, in the root of our project. If you don't have <code>Docker</code> installed, you can download it <a target="_blank" href="https://www.docker.com/get-started/">here</a>.</p>
<pre><code class="lang-plaintext"># Download OpenTelemetryAgent
FROM curlimages/curl:7.81.0 AS OTEL_AGENT
ARG OTEL_AGENT_VERSION="1.12.1"
RUN curl --silent --fail -L "https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v${OTEL_AGENT_VERSION}/opentelemetry-javaagent.jar" \
    -o "/tmp/opentelemetry-javaagent.jar"

# Build .JAR file
FROM gradle:7.1.1-jdk11-hotspot AS BUILD_IMAGE
COPY --chown=gradle:gradle . /home/gradle/src
WORKDIR /home/gradle/src
RUN gradle build -x test --no-daemon 

# Final image copying OTEL Agent and .JAR File
FROM gradle:7.1.1-jdk11-hotspot
ENV TIME_ZONE America/Lima
ENV TZ=$TIME_ZONE
ENV JAVA_OPTS "-Dspring.config.location=src/main/resources/application.yml"
COPY --from=OTEL_AGENT /tmp/opentelemetry-javaagent.jar /otel-javaagent.jar
COPY --from=BUILD_IMAGE home/gradle/src/build/libs/*.jar app.jar
ENTRYPOINT exec java -javaagent:/otel-javaagent.jar -jar app.jar
</code></pre>
<p>To test it we must build it and run it for it, run:</p>
<pre><code class="lang-plaintext">$ docker build --no-cache -t otel-springboot-prometheus .
$ docker run -it -p 8080:8080 otel-springboot-prometheus
</code></pre>
<p>After running them, you should be able to see the Spring Boot logs with the container started. Then in the browser you could go to <code>http://localhost:8080/success</code> and you should have the same result as before.</p>
<ul>
<li><strong>Creating our local single-node cluster</strong></li>
</ul>
<p>To install an Ubuntu instance we will use <a target="_blank" href="https://multipass.run/">Multipass</a>. After installing it, we will create an Ubuntu instance passing as parameters node name, ram and disk that we will assign (minimum 4G of Ram and 10Gb Disk).</p>
<p>Go to the terminal and run:</p>
<pre><code class="lang-plaintext">$ multipass launch --name demo --mem 4G --disk 20G
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653804228539/bEZZz38x0.png" alt="image.png" /></p>
<p>We will log in our already created instance:</p>
<pre><code class="lang-plaintext">$ multipass shell demo
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653804264683/ao6080upm.png" alt="image.png" /></p>
<p>Verify that we are inside the instance and we will scale to root:</p>
<pre><code class="lang-plaintext">$ sudo su
</code></pre>
<p>Install k3s:</p>
<pre><code class="lang-plaintext">$ curl -sfL https://get.k3s.io | sh -
</code></pre>
<p>Add KUBECONFIG environment variable:</p>
<pre><code class="lang-plaintext">$ export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
</code></pre>
<p>Verify that we have kubernetes installed:</p>
<pre><code class="lang-plaintext">$ kubectl cluster-info
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653804401197/K0BJywZ0H.png" alt="image.png" /></p>
<p>Install Helm:</p>
<pre><code class="lang-plaintext">$ snap install helm --classic
</code></pre>
<p>Copy <code>k3</code> KUBECONFIG in the <code>~/.kube/config</code> directory:</p>
<pre><code class="lang-plaintext">$ kubectl config view --raw &gt; ~/.kube/config
</code></pre>
<p>Verify that we have Helm installed:</p>
<pre><code class="lang-plaintext">$ helm
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653804554537/txJXyozsk.png" alt="image.png" /></p>
<h3 id="heading-deploying-our-observability-stack"><strong>Deploying our Observability Stack</strong></h3>
<p>Now we are ready to deploy our Helm charts that install <code>Prometheus</code>, <code>Promtail</code>, <code>Loki</code>, <code>Tempo</code> and <code>Grafana</code>. We will also finally deploy our Spring Boot application.</p>
<p>All the following steps are done inside the <code>demo</code> node that we created before:</p>
<pre><code class="lang-plaintext">$ multipass shell demo
$ sudo su
</code></pre>
<p>Clone the repository to have the manifests at hand:</p>
<pre><code class="lang-plaintext">$ git clone https://github.com/stazdx/otel-springboot-grafana-tools.git
$ cd otel-springboot-grafana-tools/kubernetes
</code></pre>
<p>We will add the charts we will need for the tools and update the Helm repository:</p>
<pre><code class="lang-plaintext">$ helm repo add grafana https://grafana.github.io/helm-charts
$ helm repo update
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653811192161/q_E_vBL46.png" alt="image.png" /></p>
<p>Then we will create our namespace for the observability tools:</p>
<p><code>$ kubectl create ns observability</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653811289547/W6oqE2P_m.png" alt="image.png" /></p>
<p><strong>Installing Promtail</strong></p>
<p>To install <code>Promtail</code>, run:</p>
<pre><code class="lang-plaintext">$ cd promtail
$ helm upgrade --install promtail grafana/promtail -n observability -f promtail.yaml
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653811382794/BlV3J9ulW.png" alt="image.png" /></p>
<blockquote>
<p>It is important that<code>Promtail</code> points to <code>Loki</code> service called <code>loki-loki-distributed-gateway</code>.</p>
</blockquote>
<p><strong>Installing Loki</strong></p>
<p>Deploy Loki inside the cluster by running:</p>
<p><code>$ helm upgrade --install loki grafana/loki-distributed -n observability</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653811868973/UXzHDtHiE.png" alt="image.png" /></p>
<blockquote>
<p>Here we are interested in the <code>loki-loki-distributed-gateway</code> service created by <code>Loki</code>, <code>Promtail</code> will need it to send the logs and <code>Grafana</code> will also need it to create it as datasource.</p>
</blockquote>
<p><strong>Installing Tempo</strong></p>
<p>We locate ourselves in the directory where the manifests are located:</p>
<p><code>$ cd ../tempo</code></p>
<p>To install Tempo as a prerequisite we need to install <code>minio</code> you can find the repository <a target="_blank" href="https://github.com/minio/minio">here</a>. It is a tool that helps us to simulate an <code>AWS</code> <code>Object Storage s3</code> service.</p>
<p>For this, run:</p>
<p><code>$ kubectl apply -f minio.yaml</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653812975857/QDN4h3f6M.png" alt="image.png" /></p>
<blockquote>
<p>Minio is deployed in the default namespace, since it is a more generic tool for Object Storage, not directly for observability.</p>
</blockquote>
<p>Now, to deploy <code>Tempo</code>, run:</p>
<p><code>$ helm upgrade --install tempo grafana/tempo-distributed -n observability -f tempo.yaml</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653812919409/nQe9rRrUu.png" alt="image.png" /></p>
<blockquote>
<p>Here we are interested in the <code>tempo-tempo-distributed-query-frontend</code> service that uses port <code>3100</code>, since <code>Grafana</code> will need it to create it as a datasource.</p>
</blockquote>
<p><strong>Installing Prometheus and Grafana</strong></p>
<p>To install <code>Prometheus</code> and <code>Grafana</code> we use this Git repository <a target="_blank" href="https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack">Github</a>.</p>
<pre><code class="lang-plaintext">$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$ helm repo update
</code></pre>
<p>After adding the helm repo to use, let's run it:</p>
<pre><code class="lang-plaintext">$ cd ../prometheus-grafana
$ helm dependency update
helm upgrade --install kube-prometheus-stack -n observability .
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654819624648/rq2gYsQ7p.png" alt="image.png" /></p>
<blockquote>
<p>With this we automate the generation of datasources (Prometheus, Loki, Tempo) and dashboards of Grafana, also we will allow anonymous access to Grafana, since it is a demo. For production environment it is necessary to add authentication.</p>
</blockquote>
<p><strong>Checking our deployments</strong></p>
<p><code>$ helm ls -n observability</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653814968436/4R6xLYgHg.png" alt="image.png" /></p>
<p><code>$ kubectl get po -n observability</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653815029942/6uOXaXar6.png" alt="image.png" /></p>
<p><code>$ kubectl get svc -n observability</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653815074301/yYhZZSSHN.png" alt="image.png" /></p>
<p>We can see that our observability tools were successfully deployed on our single-node Kubernetes cluster.</p>
<h3 id="heading-deploying-our-spring-boot-application-on-kubernetes">Deploying our Spring Boot Application on Kubernetes</h3>
<p>To deploy it we are going to enter the directory where the manifest is located:</p>
<p><code>$ cd ../springboot-app</code></p>
<p>It is important to identify these configurations for it to work:</p>
<p>Our Service configuration must have these annotations so that it can send metrics to <code>prometheus</code>.</p>
<pre><code class="lang-plaintext">  annotations:
    # Annotations for Prometheus - scrape config 
    prometheus.io/path: '/actuator/prometheus'
    prometheus.io/port: 'actuator'
    prometheus.io/scrape: 'true'
</code></pre>
<p>Another very important one is to add the environment variables for Deployment, for communication with OpenTelemetry:</p>
<pre><code class="lang-plaintext">          env:
            - name: SERVER_PORT
              value: '8080'
            - name: MANAGEMENT_SERVER_PORT
              value: '8081'
            # Setting OTEL_EXPORTER_METRICS: none - Default: OTLP
            - name: OTEL_METRICS_EXPORTER
              value: none
            - name: OTEL_TRACES_EXPORTER
              value: otlp,logging
            # Setting Tempo Distributor Service using GRPC Port -&gt; 4317
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: http://tempo-tempo-distributed-distributor.observability.svc.cluster.local:4317
            - name: OTEL_SERVICE_NAME
              value: springboot-app
            - name: KUBE_POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: OTEL_RESOURCE_ATTRIBUTES
              value: app=springboot-app
</code></pre>
<p>And finally we have the ConfigMap that contains our Grafana Dashboard for Spring Boot, this will allow us to see how Exemplar correlates with Tempo through metrics such as Request Latency.</p>
<p>Having explained this, we can now run our manifest:</p>
<p><code>kubectl apply -f springboot-app.yaml</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653817381107/hReH0IDVH.png" alt="image.png" /></p>
<p>Verify the created resources of our application by running:</p>
<p><code>$ kubectl get deploy,svc,cm -l app=springboot-app</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653818509915/g73cz8RZA.png" alt="image.png" /></p>
<p>We can see that everything deployed correctly, we can also see the <code>external-ip</code> of our Spring Boot Application <code>192.168.64.5</code> using port <code>8080</code> for our <code>/fail</code> and <code>/success</code> endpoints. The management port uses <code>8081</code> for the <code>/actuator</code> endpoints, the one we are interested in is <code>/actuator/prometheus</code>.</p>
<h3 id="heading-testing-endpoints">Testing endpoints</h3>
<p>With the above information we just need to go to the browser and enter our endpoints:</p>
<p>Testing <code>/fail</code> -&gt; http://{external-ip}:8080/fail, in my case:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653818949478/GrtGK2wIr.png" alt="image.png" /></p>
<p>Effectively it returns a Method Not Allowed 405 Error.</p>
<p>Testing <code>/success</code> -&gt; http://{external-ip}:8080/success, in my case:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653819037751/NQ2Oc0Z1P.png" alt="image.png" /></p>
<p>Effectively it returns <code>success</code>.</p>
<p>Testing <code>/actuator/prometheus</code> -&gt; http://{external-ip}:8081/actuator/prometheus, in my case:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653819189113/F5TLW7B0v.png" alt="image.png" /></p>
<p>We can see that it returns the metrics correctly.</p>
<h3 id="heading-testing-grafana">Testing Grafana</h3>
<p>Now we have everything configured, ready to perform our troubleshooting process. In order to open grafana, we need to obtain the port, run:</p>
<p><code>kubectl get svc -n observability</code></p>
<p>Identify the port, in my case: <code>32656</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654823584596/SQXeJ6cyx.png" alt="image.png" /></p>
<p>Go to the browser -&gt; <code>http://{external-ip}:32656</code> :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654823783104/W3mZf2h1P.png" alt="image.png" /></p>
<p>We will validate that our automated datasources are imported:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654823912874/0VhoGLw1Q.png" alt="image.png" /></p>
<p>We can see <code>Prometheus</code>, <code>Loki</code>, <code>Tempo</code> configured and we can test the connections:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654823992986/2DTR7N9dL.png" alt="image.png" /></p>
<p><code>Prometheus</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654824095555/uhYlYljg0.png" alt="image.png" /></p>
<blockquote>
<p>We can see <code>Prometheus</code> and<code>Tempo</code> correlated by<code>trace_id</code>, using Exemplars.</p>
</blockquote>
<p><code>Loki</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654824037060/pEfEC5Yp2.png" alt="image.png" /></p>
<blockquote>
<p>We can see <code>Loki</code> and <code>Tempo</code> correlated by <code>trace_id</code> in the log.</p>
</blockquote>
<p><code>Tempo</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654824260140/swd9FeAYq.png" alt="image.png" /></p>
<blockquote>
<p>We can see that here we correlate <code>Tempo</code> with <code>Loki</code> and map the <code>app</code> tag that we configured in the microservice.</p>
</blockquote>
<ul>
<li><strong>Let's put it to the test</strong></li>
</ul>
<p>We will see the log of our Spring Boot application, go to <code>Explore</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654824547174/3x44b40VL.png" alt="image.png" /></p>
<p>Select <code>Loki</code> as datasource:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654824590355/0zdXyU6y3.png" alt="image.png" /></p>
<p>With <code>Loki</code>, we can make filters to the Kubernetes resources. We will use the <code>app</code> label of our Spring Boot application. If we have many pods with this label, the logs are centralized.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654826066681/toBwVXDpL.png" alt="image.png" /></p>
<p>And we see how from the logs we can get to the trace:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654826183459/qVwaLOc8z.png" alt="image.png" /></p>
<p>Now let's see our Grafana dashboard:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654824705810/3n-GdIj3y.png" alt="image.png" /></p>
<p>We will see that the last one is <code>Spring Boot Demo</code>, this is the one that we create as ConfigMap at the moment of deploying our Spring Boot application.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654824744972/rA_PhUDUH.png" alt="image.png" /></p>
<p>This histogram shows <code>Requests Latency</code>, we can see the behavior of our Spring Boot application, we will see some stars, these are generated by Exemplar:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654824823896/mouXrJNQM.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654825426243/kF-71AuKR.png" alt="image.png" /></p>
<p>By hovering over it with the mouse, we can see how it is correlated to a <code>trace_id</code> which will then redirect us to <code>Tempo</code> when clicked:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654825503895/hC29cQrKX.png" alt="image.png" /></p>
<p>We can see the trace that it generates and we can also see the log, since it is also correlated with <code>Loki</code>, when clicking we will have:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654825608801/CI7OmvvEN.png" alt="image.png" /></p>
<p>The screen is split and we can see the log it generates.</p>
<p>Finally we have implemented observability correlated between metrics, logs and traces. This can help us in the process of microservices troubleshooting, identify bottlenecks, see the behavior of our application metrics and be able to get to specific traces and logs.</p>
<blockquote>
<p>If you made it this far, thank you very much for joining me in this demo. I'm happy to share with you all what I can learn!</p>
</blockquote>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[How to Run Ubuntu instances on M1-Based Mac]]></title><description><![CDATA[How to Run Ubuntu instance on an M1-Based Mac Ubuntu LTS Multipass Open source Kubernetes]]></description><link>https://staz.cloud/how-to-run-ubuntu-instances-on-m1-based-mac</link><guid isPermaLink="true">https://staz.cloud/how-to-run-ubuntu-instances-on-m1-based-mac</guid><category><![CDATA[macOS]]></category><category><![CDATA[Ubuntu]]></category><dc:creator><![CDATA[Staz]]></dc:creator><pubDate>Tue, 17 May 2022 05:09:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1652803027098/W4t3ctbsI.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I got a new laptop a few days ago and one of my concerns is the compatibility with my day to day work tools. One of them is to virtualize linux instances to emulate a local kubernetes cluster.</p>
<p>Surfing the internet, 99.9% of the results say that you can only do it with <a target="_blank" href="https://www.parallels.com/">Parallels</a> or <a target="_blank" href="https://www.vmware.com/products/fusion/fusion-evaluation.html">VMware Fusion</a>, the truth is that I am not a fan of these tools.</p>
<p>However I wanted to try with one of my favorite tools <a target="_blank" href="https://multipass.run/">Multipass</a>, which until then I used it with VirtualBox. Unfortunately VirtualBox does not work on M1-Based Mac.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652760694442/SCXILXxBZ.png" alt="https://ubuntu.com/blog/canonical-transforms-linux-on-mac#:~:text=Multipass%2C%20the%20quickest%20way%20to,as%20little%20as%2020%20seconds." class="image--center mx-auto" /></p>
<p>But I was surprised to discover that <a target="_blank" href="https://canonical.com/">Canonical</a> had already been working on a version for M1-Based Mac without Virtualbox as of Multipass version 1.18.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652760827618/9RtAGfkYo.png" alt="image.png" class="image--center mx-auto" /></p>
<p>I tried it and in two steps I had an instance of ubuntu running on my m1 based mac.</p>
<p>Below I quote the two steps needed:</p>
<ol>
<li><p>Install Multipass for macOS at this <a target="_blank" href="https://multipass.run/download/macos">link</a>.</p>
</li>
<li><p>Open the terminal and run:</p>
</li>
</ol>
<p><code>$ multipass launch --name node1</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652762418090/0AGzXi8rG.png" alt="image.png" /></p>
<p>Fifty seconds later ...</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652762591596/ViZqw180X.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652762849982/ocPg83gHl.png" alt="image.png" /></p>
<p>We can check the instance type with the following command:</p>
<p><code>$ multipass exec node1 -- lsb_release -a</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652762680724/ox7cOU7Gw.png" alt="image.png" /></p>
<p>We can also enter to our Ubuntu instance:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652762981851/oO0LUcA8_.png" alt="image.png" /></p>
<p>Do not forget to stop your instance when you are not using it, you can even delete it completely if necessary. With the following commands:</p>
<pre><code class="lang-plaintext">$ multipass stop node1
$ multipass delete node1
$ multipass purge
</code></pre>
<p>If you wish, you can adjust the resources assigned to your instance, to generate several of them. You can view the available properties when running a new instance with the following command:</p>
<p><code>$ multipass launch -h</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652763571542/ZiInFa_E7.png" alt="image.png" /></p>
<p>I hope you find it helpful, thank you very much for reaching the end of the post :).</p>
<p>Happy hacking!</p>
]]></content:encoded></item><item><title><![CDATA[How to set up a multi-node Kubernetes local cluster for CKA, CKAD, CKS exams]]></title><description><![CDATA[kubernetes cka cks ckad ubuntu cloud native cluster multi node certification exam console]]></description><link>https://staz.cloud/how-to-set-up-a-multi-node-kubernetes-local-cluster-for-cka-ckad-cks-exams</link><guid isPermaLink="true">https://staz.cloud/how-to-set-up-a-multi-node-kubernetes-local-cluster-for-cka-ckad-cks-exams</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Ubuntu]]></category><category><![CDATA[Certification]]></category><category><![CDATA[Cloud]]></category><dc:creator><![CDATA[Staz]]></dc:creator><pubDate>Mon, 16 May 2022 01:18:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1652663052738/uvO6kJu6u.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There are many ways to set up a Kubernetes cluster in a local environment, I have tested some tools and here I offer the best in my opinion, especially if your purpose is to practice for the CKA/CKAD/CKS certification exams.</p>
<p>We will need to create virtual machines on our PC/Laptop, for this I will use a <a target="_blank" href="https://canonical.com">Canonical</a> product: <a target="_blank" href="https://multipass.run"><strong>Multipass</strong></a>. I have been using it for a long time and it is the best choice to create Ubuntu VMs in my opinion.</p>
<p>For cluster initialization, we will use <a target="_blank" href="https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/"><strong>kubeadm</strong></a>, as recommended in the official Kubernetes documentation.</p>
<p>Here I quote some reasons why it is better to use this stack against other options such as minikube, microk8s, k3s, RKE, EKS, AKS, GKE, and others:</p>
<ol>
<li><p><strong>The main reason</strong>: No need to install <strong>Docker</strong>, you will use <strong>containerd</strong> as the container runtime, <strong>crictl</strong> as cli for CRI compliant container runtime and if you need to build a Dockerfile, you will use <strong>podman</strong>.</p>
</li>
<li><p>There is no need to install extra dependencies on the Ubuntu VMs, what you need are clean nodes that only have what you need to work with.</p>
</li>
<li><p>You need that your cluster to be clean as well, you do not need to install extra Kubernetes resources such as CRDs, statefulsets, daemons, services, deployments, among others.</p>
</li>
<li><p><strong>Multipass</strong> and Ubuntu were developed by Canonical, these have their support.</p>
</li>
<li><p>You need to have full control of your cluster, especially your control plane node. That’s why Kubernetes managed services are useless (EKS, AKS, GKE, and others).</p>
</li>
<li><p>You need Ubuntu nodes for simulating CKA/CKAD/CKS exams.</p>
</li>
</ol>
<h3 id="heading-requirements">Requirements:</h3>
<p>Make sure you have at least 4 GB of RAM,4 free CPU cores, and 20 Gb for VMs.</p>
<p>I recommend using these resources to avoid performance and disk space problems:</p>
<p><strong>Control Plane</strong> (master node):</p>
<ul>
<li>2 Gb RAM</li>
<li>2 CPU Core</li>
<li>10 Gb Disk</li>
</ul>
<p><strong>Worker</strong> (worker node):</p>
<ul>
<li>2 Gb RAM</li>
<li>2 CPU Core</li>
<li>10 Gb Disk</li>
</ul>
<h3 id="heading-kubernetes-cluster-configuration">Kubernetes Cluster Configuration</h3>
<p>Without more to say here I attach a Git repository with the readme file where you can see the step by step for the Kubernetes cluster configuration:</p>
<p>https://github.com/stazdx/k8s-local-environment</p>
<p>Note: What is applied in this post is based on my experience, it isn’t the only way to be able to do it.</p>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Tips and tricks for CKA, CKAD and CKS certifications]]></title><description><![CDATA[Kubernetes certification tips tricks cka ckad cks cncf cloud native]]></description><link>https://staz.cloud/tips-and-tricks-for-cka-ckad-and-cks-certifications</link><guid isPermaLink="true">https://staz.cloud/tips-and-tricks-for-cka-ckad-and-cks-certifications</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[tips]]></category><category><![CDATA[tricks]]></category><category><![CDATA[Certification]]></category><category><![CDATA[k8s]]></category><dc:creator><![CDATA[Staz]]></dc:creator><pubDate>Mon, 16 May 2022 00:33:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1655608663788/FydcmS3OP.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It's been more than 4 years since I started with Kubernetes. I set myself a challenge to get the certifications recently, it was out of curiosity; I took it as a personal challenge, to know how much I know about Kubernetes and everything that revolves around it. Plus the kind of test I had to take, where it's just you and the console.</p>
<p>Without further ado, I will give some considerations and tips so you can get the certifications in your first attempt. I have separated them into 3: Time, Speed and Space.</p>
<h3 id="heading-time">Time</h3>
<p>Each exam lasts 2 hours, approximately 15 to 22 questions (depending on the exam). The exam is made so that you will not be able to finish it, so you must manage your time well.</p>
<p><strong>Tips:</strong></p>
<ul>
<li>Give priority to the questions that have higher scores.</li>
</ul>
<p>You will see that there are questions that have a weight of 12%, 10%, 8%. These you have to do them yes or yes (it is the ideal). A common mistake is to do them in order, you may not have enough time and leave questions with high scores.</p>
<ul>
<li>Use the notepad provided by the exam.</li>
</ul>
<p>Based on the first tip, something that has worked for me is to use the notepad to write down the pending questions (not solved yet, because they have lower scores) and the percentage that each one represents. As you solve them, you update the notepad. This will help you to have an approximation of the score you are accumulating.</p>
<ul>
<li>Avoid reviewing your solution if the process is long.</li>
</ul>
<p>One thing I don't like about the exam is that the updates or changes you make to the Control Plane of your clusters take a long time and/or often get "stuck".</p>
<p>You will always want to verify your solution, but many times this just takes more time. I recommend that you trust yourself, practice will give you that confidence.</p>
<h3 id="heading-velocity">Velocity</h3>
<p>You will have to be quick to try to finish all the questions, this can only be achieved with constant practice.</p>
<p><strong>Tips:</strong></p>
<ul>
<li>Learn the shortcuts of the vim editor and put it into practice.</li>
</ul>
<p>I think this is the most important thing, since most of the time you will be editing files or creating new ones. Take advantage of the shortcuts and practice a lot.</p>
<ul>
<li>kubectl master</li>
</ul>
<p>Learn the kubectl imperative commands and generate your manifests quickly by adding dry-run=client and -oyaml. This will help you generate a manifest quickly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652660373797/YOuyhEo8f.png" alt="stazcli.png" /></p>
<ul>
<li>Learn the structure of manifests</li>
</ul>
<p>If you want to be faster, you will have to know very well the structure of your manifests. For example: where to place an annotation, security context, mount a volume, add labels, etc. This can only be done with practice (it takes a lot of time to search in the documentation to find out where in the manifest to add).</p>
<ul>
<li>Try to use the documentation as little as possible.</li>
</ul>
<p>In the exam you only have access to a browser tab, for CKA and CKAD only to the kubernetes documentation. Practice how to quickly search for what you need and use the right panel to go to the specifics, for example:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652660468635/eDRxWTOvk.png" alt="stazdocs.png" /></p>
<p>Still, my recommendation would be to take the exam if you are sufficiently prepared, without using the documentation. In the case of CKS it is more critical, you can use only one tab too, but they are several pages of documentation (kubernetes, apparmor, falco, trivy, sysdig). It is uncomfortable to have to use only one browser tab to switch from one documentation to another.</p>
<h3 id="heading-space">Space</h3>
<p>It is important that you practice as if you were actually taking the exam.</p>
<p><strong>Tips:</strong></p>
<ul>
<li><p>Simulate a cluster with Ubuntu servers (I will create another post for this soon, so you can do it on Windows, Linux and Mac).</p>
</li>
<li><p>Be very careful at exam time, to be in the right context, as you will be working with several clusters and each question may use a different cluster.</p>
</li>
<li><p>See well what each question asks you, many times we don't finish the whole question. Sometimes you are asked to save outputs or logs in a certain path and you can skip them.</p>
</li>
<li><p>Make a backup of the files you will edit, because if you mess up one of them, you can lose a lot of time trying to fix it. If you mess up a cluster, you will not be able to solve several questions, so be careful!</p>
</li>
<li><p>Use a prefix in your manifests, this will help you identify which question it belonged to, you can review them if you wish or finish something you left unfinished. For example: 1_pod.yaml, 2_networkpolicy.yaml.</p>
</li>
<li><p>Forget Docker! In the exam you will use crictl to see the containers inside the servers, especially in the CKA, CKS.</p>
</li>
</ul>
<blockquote>
<p>For the CKS, your Linux level must be very good. Since in many questions you will be troubleshooting at kernel, syscalls, processes, networking, etc. level.</p>
</blockquote>
<p>Finally, some extra recommendations:</p>
<ul>
<li><p>Go to the bathroom before giving the exam, ideally not to lose your concentration for a second.</p>
</li>
<li><p>You can have a glass/bottle of water at your desk, but I do not recommend it.
Give the exam with a fresh mind.</p>
</li>
<li><p>I will soon add more posts so you can generate a local practice environment and also the fixes on each topic of the certifications.</p>
</li>
</ul>
<p>Many successes :) !</p>
]]></content:encoded></item></channel></rss>