<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>SleakOps</title>
	<atom:link href="https://sleakops.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://sleakops.com/</link>
	<description>Deploy and Automate your Infra</description>
	<lastBuildDate>Sun, 12 Apr 2026 20:41:03 +0000</lastBuildDate>
	<language>es-AR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://sleakops.com/wp-content/uploads/2025/03/faviconSleakops.svg</url>
	<title>SleakOps</title>
	<link>https://sleakops.com/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Alternativas Open Source a Heroku</title>
		<link>https://sleakops.com/2026/03/20/alternativas-open-source-a-heroku/</link>
		
		<dc:creator><![CDATA[matias]]></dc:creator>
		<pubDate>Fri, 20 Mar 2026 11:52:02 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://sleakops.com/?p=2073</guid>

					<description><![CDATA[<p>Alternativas Open Source a Heroku: Cuándo usarlas y cuándo necesitás algo más Guía 2026 Cuándo usarlas y cuándo necesitás algo más Estas herramientas son ideales para arrancar. Pero si tu negocio crece, vas a necesitar una infraestructura que crezca con vos. Desde que Heroku eliminó su tier gratuito en 2022, miles de desarrolladores y startups [&#8230;]</p>
<p>The post <a href="https://sleakops.com/2026/03/20/alternativas-open-source-a-heroku/">Alternativas Open Source a Heroku</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p>
<p>La entrada <a href="https://sleakops.com/2026/03/20/alternativas-open-source-a-heroku/">Alternativas Open Source a Heroku</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <title>Alternativas Open Source a Heroku: Cuándo usarlas y cuándo necesitás algo más</title>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&#038;display=swap" rel="stylesheet">
  <style>
    :root {
      --navy: #1a202c;
      --navy-up: #232b3b;
      --navy-card: #283141;
      --navy-hover: #2f3a4d;
      --blue: #90cdf4;
      --blue-dim: rgba(144,205,244,0.12);
      --blue-dimmer: rgba(144,205,244,0.07);
      --coral: #e37562;
      --coral-dim: rgba(227,117,98,0.12);
      --coral-hover: #d4624f;
      --text-primary: #f2f2f2;
      --text-secondary: #a0aec0;
      --text-muted: #718096;
      --border: rgba(255,255,255,0.08);
      --border-hover: rgba(144,205,244,0.25);
      --green: #68d391;
      --green-dim: rgba(104,211,145,0.12);
    }

    * { margin: 0; padding: 0; box-sizing: border-box; }

    body {
      font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
      background: var(--navy);
      color: var(--text-secondary);
      line-height: 1.75;
      font-size: 17px;
    }

    .post-container {
      max-width: 820px;
      margin: 0 auto;
      padding: 40px 24px 80px;
    }

    /* ── HERO ── */
    .hero {
      background: var(--navy-up);
      border: 1px solid var(--border);
      border-radius: 16px;
      padding: 56px 48px;
      margin-bottom: 48px;
      color: var(--text-primary);
      position: relative;
      overflow: hidden;
    }
    .hero::before {
      content: '';
      position: absolute;
      inset: 0;
      background:
        radial-gradient(circle at 80% 20%, rgba(144,205,244,0.1) 0%, transparent 50%),
        radial-gradient(circle at 15% 85%, rgba(227,117,98,0.07) 0%, transparent 40%);
    }
    .hero::after {
      content: '';
      position: absolute;
      top: -60px;
      right: -40px;
      width: 260px;
      height: 260px;
      border: 1px solid rgba(144,205,244,0.08);
      clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
    }
    .hero-badge {
      position: relative;
      display: inline-block;
      background: var(--blue-dim);
      color: var(--blue);
      padding: 6px 16px;
      border-radius: 20px;
      font-size: 12px;
      font-weight: 700;
      letter-spacing: 1.5px;
      text-transform: uppercase;
      margin-bottom: 20px;
      border: 1px solid rgba(144,205,244,0.15);
    }
    .hero h1 {
      position: relative;
      font-size: 36px;
      font-weight: 900;
      line-height: 1.2;
      margin-bottom: 16px;
      color: var(--text-primary);
    }
    .hero h1 span { color: var(--blue); }
    .hero p {
      position: relative;
      font-size: 18px;
      color: var(--text-secondary);
      max-width: 600px;
      line-height: 1.6;
    }

    /* ── CONTENT ── */
    h2 {
      font-size: 28px;
      font-weight: 900;
      color: var(--text-primary);
      margin: 48px 0 20px;
      line-height: 1.3;
      letter-spacing: -0.02em;
    }
    h3 {
      font-size: 22px;
      font-weight: 700;
      color: var(--text-primary);
      margin: 36px 0 16px;
    }
    p { margin-bottom: 18px; }
    a { color: var(--coral); text-decoration: none; font-weight: 600; }
    a:hover { text-decoration: underline; color: #f09080; }
    strong { color: var(--text-primary); font-weight: 600; }
    code {
      background: var(--blue-dim);
      color: var(--blue);
      padding: 2px 8px;
      border-radius: 5px;
      font-size: 14px;
      border: 1px solid rgba(144,205,244,0.1);
    }

    /* ── INTRO BOX ── */
    .intro-box {
      background: var(--blue-dimmer);
      border-left: 3px solid var(--blue);
      border-radius: 0 12px 12px 0;
      padding: 24px 28px;
      margin: 32px 0;
    }
    .intro-box p { color: var(--text-secondary); margin-bottom: 0; }

    /* ── TOOL CARDS ── */
    .tools-grid {
      display: grid;
      grid-template-columns: 1fr;
      gap: 16px;
      margin: 32px 0;
    }
    .tool-card {
      background: var(--navy-card);
      border: 1px solid var(--border);
      border-radius: 14px;
      padding: 28px;
      transition: all 0.25s ease;
    }
    .tool-card:hover {
      border-color: var(--border-hover);
      box-shadow: 0 8px 32px rgba(0,0,0,0.2);
      transform: translateY(-2px);
    }
    .tool-header {
      display: flex;
      align-items: center;
      gap: 14px;
      margin-bottom: 14px;
    }
    .tool-icon {
      width: 48px;
      height: 48px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 22px;
      font-weight: 800;
      color: var(--navy);
      flex-shrink: 0;
      clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
    }
    .tool-name {
      font-size: 20px;
      font-weight: 700;
      color: var(--text-primary);
    }
    .tool-tagline {
      font-size: 13px;
      color: var(--text-muted);
      font-weight: 500;
    }
    .tool-desc { font-size: 15px; margin-bottom: 14px; color: var(--text-secondary); }
    .tool-tags { display: flex; flex-wrap: wrap; gap: 8px; }
    .tag {
      display: inline-block;
      padding: 4px 12px;
      border-radius: 20px;
      font-size: 12px;
      font-weight: 600;
    }
    .tag.blue { background: var(--blue-dim); color: var(--blue); }
    .tag.green { background: var(--green-dim); color: var(--green); }
    .tag.default { background: rgba(255,255,255,0.05); color: var(--text-muted); }

    /* ── COMPARISON TABLE ── */
    .comparison-table {
      width: 100%;
      border-collapse: collapse;
      margin: 32px 0;
      font-size: 15px;
      background: var(--navy-card);
      border-radius: 14px;
      overflow: hidden;
      border: 1px solid var(--border);
    }
    .comparison-table thead { background: rgba(144,205,244,0.1); }
    .comparison-table th {
      padding: 14px 18px;
      text-align: left;
      font-weight: 700;
      font-size: 13px;
      letter-spacing: 0.5px;
      text-transform: uppercase;
      color: var(--blue);
    }
    .comparison-table td {
      padding: 14px 18px;
      border-bottom: 1px solid var(--border);
      color: var(--text-secondary);
    }
    .comparison-table tr:last-child td { border-bottom: none; }
    .comparison-table tr:nth-child(even) { background: rgba(255,255,255,0.02); }
    .comparison-table td strong { color: var(--text-primary); }

    /* ── VERDICT BOX ── */
    .verdict-box {
      background: var(--navy-up);
      border: 1px solid var(--border);
      border-radius: 14px;
      padding: 32px;
      margin: 40px 0;
    }
    .verdict-box h3 { margin-top: 0; }
    .pros-cons {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 20px;
      margin-top: 20px;
    }
    @media (max-width: 600px) {
      .pros-cons { grid-template-columns: 1fr; }
    }
    .pros-col, .cons-col {
      background: var(--navy-card);
      border: 1px solid var(--border);
      border-radius: 10px;
      padding: 20px;
    }
    .pros-col h4, .cons-col h4 {
      font-size: 13px;
      font-weight: 700;
      margin-bottom: 14px;
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }
    .pros-col h4 { color: var(--green); }
    .cons-col h4 { color: var(--coral); }
    .pros-col ul, .cons-col ul { list-style: none; padding: 0; }
    .pros-col li, .cons-col li {
      padding: 5px 0;
      font-size: 14px;
      color: var(--text-secondary);
      padding-left: 22px;
      position: relative;
    }
    .pros-col li::before {
      content: '✓';
      position: absolute; left: 0;
      color: var(--green);
      font-weight: 800;
    }
    .cons-col li::before {
      content: '✗';
      position: absolute; left: 0;
      color: var(--coral);
      font-weight: 800;
    }

    /* ── SCENARIO CARDS ── */
    .scenario-section { margin: 32px 0; }
    .scenario-card {
      display: flex;
      gap: 16px;
      padding: 22px 24px;
      background: var(--navy-card);
      border: 1px solid var(--border);
      border-radius: 12px;
      margin-bottom: 12px;
      align-items: flex-start;
      transition: all 0.2s ease;
    }
    .scenario-card:hover {
      border-color: rgba(227,117,98,0.3);
      background: var(--navy-hover);
    }
    .scenario-icon {
      width: 44px;
      height: 44px;
      background: var(--coral-dim);
      border-radius: 10px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 20px;
      flex-shrink: 0;
    }
    .scenario-text h4 {
      font-size: 16px;
      font-weight: 700;
      color: var(--text-primary);
      margin-bottom: 6px;
    }
    .scenario-text p {
      font-size: 15px;
      margin-bottom: 0;
      color: var(--text-muted);
      line-height: 1.6;
    }

    /* ── SLEAKOPS SECTION ── */
    .sleakops-section {
      background: var(--navy-up);
      border-radius: 16px;
      padding: 40px;
      margin: 48px 0;
      border: 1px solid var(--border-hover);
      position: relative;
      overflow: hidden;
    }
    .sleakops-section::before {
      content: '';
      position: absolute;
      inset: 0;
      background: radial-gradient(circle at 90% 10%, rgba(144,205,244,0.06) 0%, transparent 50%);
      pointer-events: none;
    }
    .sleakops-section h2 {
      position: relative;
      margin-top: 0;
    }
    .sleakops-section h2 span { color: var(--blue); }
    .sleakops-section p { position: relative; }
    .feature-grid {
      position: relative;
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 14px;
      margin: 28px 0 0;
    }
    @media (max-width: 600px) {
      .feature-grid { grid-template-columns: 1fr; }
    }
    .feature-item {
      background: var(--navy-card);
      border-radius: 10px;
      padding: 20px;
      border: 1px solid var(--border);
      transition: border-color 0.2s;
    }
    .feature-item:hover { border-color: var(--border-hover); }
    .feature-item h4 {
      font-size: 15px;
      font-weight: 700;
      color: var(--text-primary);
      margin-bottom: 6px;
      display: flex;
      align-items: center;
      gap: 10px;
    }
    .feature-item h4 .fi {
      width: 30px;
      height: 30px;
      background: var(--blue-dim);
      border-radius: 8px;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      font-size: 15px;
      flex-shrink: 0;
    }
    .feature-item p {
      font-size: 14px;
      color: var(--text-muted);
      margin-bottom: 0;
      line-height: 1.5;
    }

    /* ── CTA ── */
    .cta-box {
      border-radius: 16px;
      padding: 52px 44px;
      text-align: center;
      margin: 56px 0 0;
      position: relative;
      overflow: hidden;
      /* Gradient border effect */
      background:
        linear-gradient(var(--navy-up), var(--navy-up)) padding-box,
        linear-gradient(135deg, var(--blue), var(--coral)) border-box;
      border: 2px solid transparent;
    }
    .cta-box::before {
      content: '';
      position: absolute;
      inset: 0;
      background:
        radial-gradient(circle at 20% 80%, rgba(227,117,98,0.08) 0%, transparent 40%),
        radial-gradient(circle at 80% 20%, rgba(144,205,244,0.08) 0%, transparent 40%);
    }
    .cta-box h2 {
      position: relative;
      color: var(--text-primary);
      margin-top: 0;
      margin-bottom: 14px;
      font-size: 30px;
    }
    .cta-box p {
      position: relative;
      color: var(--text-muted);
      font-size: 17px;
      max-width: 540px;
      margin: 0 auto 28px;
    }
    .cta-button {
      position: relative;
      display: inline-block;
      background: var(--coral);
      color: #fff;
      padding: 15px 40px;
      border-radius: 10px;
      font-size: 16px;
      font-weight: 700;
      text-decoration: none;
      transition: all 0.25s ease;
      letter-spacing: 0.02em;
    }
    .cta-button:hover {
      background: var(--coral-hover);
      transform: translateY(-2px);
      box-shadow: 0 8px 28px rgba(227, 117, 98, 0.3);
      text-decoration: none;
      color: #fff;
    }
    .cta-secondary {
      position: relative;
      display: block;
      margin-top: 14px;
      font-size: 14px;
      color: var(--blue);
      font-weight: 500;
    }

    /* ── DIVIDER ── */
    .section-divider {
      border: none;
      height: 1px;
      background: var(--border);
      margin: 48px 0;
    }

    /* ── RESPONSIVE ── */
    @media (max-width: 640px) {
      .hero { padding: 36px 24px; }
      .hero h1 { font-size: 26px; }
      .post-container { padding: 20px 16px 60px; }
      .cta-box { padding: 36px 24px; }
      .sleakops-section { padding: 28px 20px; }
    }
  </style>
</head>
<body>

<article class="post-container">

  <!-- ═══ HERO ═══ -->
  <div class="hero">
    <div class="hero-badge">Guía 2026</div>
    <h1><span>Cuándo usarlas</span> y cuándo necesitás algo más</h1>
    <p>Estas herramientas son ideales para arrancar. Pero si tu negocio crece, vas a necesitar una infraestructura que crezca con vos.</p>
  </div>

  <!-- ═══ INTRO ═══ -->
  <p>Desde que Heroku eliminó su tier gratuito en 2022, miles de desarrolladores y startups salieron a buscar alternativas más accesibles. La buena noticia es que el ecosistema open source respondió con herramientas que hoy te permiten deployar aplicaciones con una experiencia muy similar a Heroku, pero sin costo de licencia y con control total sobre tu infraestructura.</p>

  <div class="intro-box">
    <p><strong>¿Estás validando una idea, construyendo un MVP o recién arrancando?</strong> Estas alternativas open source son perfectas para vos. Te permiten salir rápido al mercado, iterar tu producto y gastar lo mínimo mientras comprobás si tu idea tiene tracción.</p>
  </div>

  <p>Pero ojo: que una herramienta sea gratuita no significa que sea gratis para siempre. Detrás de cada una hay un VPS que mantener, configuraciones que ajustar y decisiones de infraestructura que tomar. Cuando tu proyecto crece, esas herramientas empiezan a mostrar sus límites.</p>

  <p>Vamos a repasar las mejores alternativas open source, qué ofrecen, y en qué momento necesitás dar el salto a algo más robusto.</p>

  <!-- ═══ TOOLS ═══ -->
  <h2>Las mejores alternativas Open Source a Heroku</h2>

  <div class="tools-grid">

    <div class="tool-card">
      <div class="tool-header">
        <div class="tool-icon" style="background: var(--blue);">C</div>
        <div>
          <div class="tool-name">Coolify</div>
          <div class="tool-tagline">La alternativa más popular y completa</div>
        </div>
      </div>
      <p class="tool-desc">Coolify es una plataforma open source y self-hosted que te permite deployar aplicaciones, bases de datos y servicios en tu propio servidor con una interfaz visual moderna. Es probablemente la alternativa a Heroku más pulida que existe hoy. Soporta más de 280 servicios con un solo click (incluyendo WordPress, Supabase, y bases de datos), tiene integración con GitHub, GitLab y Bitbucket, backups automáticos a S3 y certificados SSL automáticos con Let&#8217;s Encrypt.</p>
      <div class="tool-tags">
        <span class="tag green">100% gratuito</span>
        <span class="tag blue">Self-hosted</span>
        <span class="tag default">Docker</span>
        <span class="tag blue">+280 servicios</span>
      </div>
    </div>

    <div class="tool-card">
      <div class="tool-header">
        <div class="tool-icon" style="background: #63b3ed;">D</div>
        <div>
          <div class="tool-name">Dokku</div>
          <div class="tool-tagline">El &#8220;mini Heroku&#8221; original</div>
        </div>
      </div>
      <p class="tool-desc">Dokku es la alternativa open source más veterana. Es un PaaS ligero que corre sobre Docker y te permite hacer deploy con <code>git push</code>, exactamente como en Heroku. Es ideal si querés algo simple y liviano en un solo servidor. Soporta Heroku buildpacks, lo que facilita la migración, y tiene un sistema de plugins para agregar bases de datos, TLS y más.</p>
      <div class="tool-tags">
        <span class="tag green">Gratuito</span>
        <span class="tag blue">Git push deploy</span>
        <span class="tag default">Heroku buildpacks</span>
        <span class="tag default">Single server</span>
      </div>
    </div>

    <div class="tool-card">
      <div class="tool-header">
        <div class="tool-icon" style="background: var(--coral);">⚓</div>
        <div>
          <div class="tool-name">CapRover</div>
          <div class="tool-tagline">Simple, visual y con soporte Docker nativo</div>
        </div>
      </div>
      <p class="tool-desc">CapRover apunta a desarrolladores que quieren una experiencia visual sin complicarse con la terminal. Tiene un dashboard web desde el que podés deployar apps en cualquier lenguaje, configurar HTTPS automático y escalar horizontalmente con Docker Swarm. Incluye un marketplace de &#8220;One-Click Apps&#8221; para levantar servicios como MongoDB, PostgreSQL, Redis y más en segundos.</p>
      <div class="tool-tags">
        <span class="tag green">Gratuito</span>
        <span class="tag blue">Dashboard web</span>
        <span class="tag default">Docker Swarm</span>
        <span class="tag default">One-click apps</span>
      </div>
    </div>

    <div class="tool-card">
      <div class="tool-header">
        <div class="tool-icon" style="background: #ecc94b;">Dp</div>
        <div>
          <div class="tool-name">Dokploy</div>
          <div class="tool-tagline">El nuevo jugador que gana terreno rápido</div>
        </div>
      </div>
      <p class="tool-desc">Dokploy es una alternativa más reciente que combina la simplicidad de Dokku con una interfaz moderna estilo Coolify. Usa Docker y Traefik bajo el capó, soporta múltiples proyectos y bases de datos, y es ideal tanto para proyectos personales como para equipos pequeños que buscan algo fácil de operar.</p>
      <div class="tool-tags">
        <span class="tag green">Gratuito</span>
        <span class="tag blue">Docker + Traefik</span>
        <span class="tag default">UI moderna</span>
        <span class="tag default">Multi-proyecto</span>
      </div>
    </div>

    <div class="tool-card">
      <div class="tool-header">
        <div class="tool-icon" style="background: #fc8181;">λ</div>
        <div>
          <div class="tool-name">OpenFaaS</div>
          <div class="tool-tagline">Functions as a Service sobre Kubernetes</div>
        </div>
      </div>
      <p class="tool-desc">Si tu enfoque es serverless y funciones, OpenFaaS te permite deployar funciones en cualquier lenguaje sobre Kubernetes o Docker Swarm. Es ideal para microservicios y arquitecturas event-driven. Soporta escalar a cero para ahorrar costos y ejecutar funciones bajo demanda.</p>
      <div class="tool-tags">
        <span class="tag green">Gratuito (Community)</span>
        <span class="tag blue">Serverless</span>
        <span class="tag default">Kubernetes</span>
        <span class="tag default">Scale to zero</span>
      </div>
    </div>

  </div>

  <!-- ═══ COMPARISON TABLE ═══ -->
  <h3>Comparativa rápida</h3>

  <table class="comparison-table">
    <thead>
      <tr>
        <th>Herramienta</th>
        <th>Ideal para</th>
        <th>Complejidad</th>
        <th>Escalabilidad</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><strong>Coolify</strong></td>
        <td>Startups y equipos pequeños</td>
        <td>Baja</td>
        <td>Media</td>
      </tr>
      <tr>
        <td><strong>Dokku</strong></td>
        <td>Desarrolladores individuales</td>
        <td>Baja-Media</td>
        <td>Baja</td>
      </tr>
      <tr>
        <td><strong>CapRover</strong></td>
        <td>Proyectos con UI visual</td>
        <td>Baja</td>
        <td>Media</td>
      </tr>
      <tr>
        <td><strong>Dokploy</strong></td>
        <td>Proyectos personales y equipos</td>
        <td>Baja</td>
        <td>Media</td>
      </tr>
      <tr>
        <td><strong>OpenFaaS</strong></td>
        <td>Microservicios y funciones</td>
        <td>Media-Alta</td>
        <td>Alta (con K8s)</td>
      </tr>
    </tbody>
  </table>

  <!-- ═══ VERDICT BOX ═══ -->
  <div class="verdict-box">
    <h3>¿Son buenas opciones? Absolutamente, pero con matices</h3>
    <p style="margin-bottom: 0;">Estas herramientas son excelentes para <strong>validar ideas, construir MVPs y arrancar con presupuesto limitado</strong>. Te dan la experiencia de un PaaS sin los costos de Heroku y con la libertad de correr todo en tu propio servidor.</p>
    <div class="pros-cons">
      <div class="pros-col">
        <h4>✓ Lo bueno</h4>
        <ul>
          <li>Sin costos de licencia</li>
          <li>Control total de tu infra</li>
          <li>Deploy rápido y familiar</li>
          <li>Sin vendor lock-in</li>
          <li>Ideal para MVP y validación</li>
        </ul>
      </div>
      <div class="cons-col">
        <h4>✗ Las limitaciones</h4>
        <ul>
          <li>Vos sos el DevOps</li>
          <li>Escalabilidad limitada</li>
          <li>Sin soporte empresarial</li>
          <li>Seguridad bajo tu responsabilidad</li>
          <li>No preparadas para alta demanda</li>
        </ul>
      </div>
    </div>
  </div>

  <hr class="section-divider">

  <!-- ═══ WHEN YOU NEED MORE ═══ -->
  <h2>¿Cuándo estas herramientas te quedan cortas?</h2>

  <p>Llega un punto en el que tu proyecto deja de ser un experimento y se convierte en un negocio. Y cuando eso pasa, las reglas cambian. La infraestructura ya no es un detalle técnico: es un pilar estratégico que puede impulsar o frenar tu crecimiento.</p>

  <p><strong>Si te identificás con alguno de estos escenarios, es señal de que necesitás una solución profesional:</strong></p>

  <div class="scenario-section">

    <div class="scenario-card">
      <div class="scenario-icon">⏱️</div>
      <div class="scenario-text">
        <h4>Estás gastando demasiado tiempo operativo manteniendo la infra</h4>
        <p>Si tu equipo de desarrollo pierde horas configurando servidores, debugueando deploys fallidos o parchando problemas de infraestructura en lugar de construir producto, algo está mal. Cada hora que un dev gasta en ops es una hora que no está generando valor para tu negocio.</p>
      </div>
    </div>

    <div class="scenario-card">
      <div class="scenario-icon">🔍</div>
      <div class="scenario-text">
        <h4>Estás buscando un DevOps para que mantenga y mejore tu infra</h4>
        <p>Contratar un DevOps senior cuesta entre USD 5.000 y 15.000 al mes. Y aun así, una sola persona no cubre todo: seguridad, monitoreo, escalabilidad, backups, CI/CD, networking. Si estás en este punto, quizás no necesitás una persona, sino una plataforma que automatice todo eso.</p>
      </div>
    </div>

    <div class="scenario-card">
      <div class="scenario-icon">💰</div>
      <div class="scenario-text">
        <h4>Estás buscando inversiones y necesitás mostrar una infra robusta</h4>
        <p>Los inversores quieren ver que tu stack tecnológico puede soportar el crecimiento que proyectás. Una infra montada sobre un VPS con Coolify o Dokku no transmite la confianza de una arquitectura basada en Kubernetes sobre AWS con buenas prácticas de seguridad y compliance.</p>
      </div>
    </div>

    <div class="scenario-card">
      <div class="scenario-icon">📈</div>
      <div class="scenario-text">
        <h4>Tu stack actual no escala y no se adapta a las necesidades de tu negocio</h4>
        <p>Tu app empieza a tener latencia, las bases de datos se saturan, los deploys rompen producción. Las herramientas open source están pensadas para escenarios simples. Cuando tu arquitectura necesita múltiples servicios, ambientes separados y alta disponibilidad, necesitás otra cosa.</p>
      </div>
    </div>

    <div class="scenario-card">
      <div class="scenario-icon">🔥</div>
      <div class="scenario-text">
        <h4>Estás gastando miles de dólares porque escalar en Heroku es caro</h4>
        <p>Heroku cobra premium por escalar. Agregar dynos, base de datos con mayor capacidad o Redis managed puede llevarte fácilmente a miles de dólares al mes por una app relativamente simple. Migrar a AWS con una herramienta de gestión automatizada puede reducir esos costos hasta un 60%.</p>
      </div>
    </div>

    <div class="scenario-card">
      <div class="scenario-icon">🚀</div>
      <div class="scenario-text">
        <h4>Necesitás una infra que se adapte a picos de demanda</h4>
        <p>Tenés un ecommerce y se viene un Hot Sale, un Black Friday, o un evento de marketing que va a multiplicar tu tráfico por 10x. ¿Tu infraestructura actual puede absorber eso sin caerse? Si la respuesta es &#8220;no estoy seguro&#8221;, es momento de cambiar. Necesitás auto-scaling real, no esperanzas.</p>
      </div>
    </div>

  </div>

  <p>Para cualquiera de estos casos, la herramienta open source te queda corta. Necesitás una solución que <strong>escale de verdad, sea segura, eficiente y te permita operarla con mínimo esfuerzo</strong>, sin tener que convertir a tu equipo de desarrollo en un equipo de infraestructura.</p>

  <hr class="section-divider">

  <!-- ═══ SLEAKOPS ═══ -->
  <div class="sleakops-section">
    <h2>Sleak<span>Ops</span>: la infraestructura profesional sin la complejidad</h2>

    <p><a href="https://sleakops.com" target="_blank" rel="noopener">SleakOps</a> es una plataforma de CloudOps que automatiza toda la gestión de tu infraestructura sobre AWS utilizando Kubernetes y las mejores prácticas del AWS Well-Architected Framework. Es como tener un equipo DevOps senior trabajando 24/7, pero sin los costos de contratación.</p>

    <p style="margin-bottom: 0;">Lo que diferencia a SleakOps de las alternativas open source es que no estás solo. No sos vos configurando un VPS a las 3 de la mañana porque se cayó producción. SleakOps te da una infraestructura enterprise-grade desde el día uno, con todo lo que tu negocio necesita para escalar con confianza.</p>

    <div class="feature-grid">
      <div class="feature-item">
        <h4><span class="fi">⚡</span> Auto-deployments</h4>
        <p>Pusheá código y mirá cómo se despliega automáticamente. Sin pasos manuales. De horas a minutos.</p>
      </div>
      <div class="feature-item">
        <h4><span class="fi">🔀</span> Gestión de ambientes</h4>
        <p>Creá ambientes aislados para dev, staging, QA y producción sin que uno impacte al otro.</p>
      </div>
      <div class="feature-item">
        <h4><span class="fi">📊</span> Monitoreo en tiempo real</h4>
        <p>Métricas, logs y alertas al instante. Visibilidad completa para detectar anomalías antes de que escalen.</p>
      </div>
      <div class="feature-item">
        <h4><span class="fi">🌙</span> Night Mode</h4>
        <p>Suspende clusters inactivos automáticamente. Rendimiento alto cuando lo necesitás, costos bajos cuando no.</p>
      </div>
      <div class="feature-item">
        <h4><span class="fi">🗄️</span> Dependencias integradas</h4>
        <p>RDS, Redis, ElastiCache, S3, OpenSearch. Conectá y gestioná todo desde la plataforma.</p>
      </div>
      <div class="feature-item">
        <h4><span class="fi">🔒</span> Seguridad desde el diseño</h4>
        <p>SSL automático, gestión de secretos, VPN y permisos granulares. Mejores prácticas de AWS.</p>
      </div>
      <div class="feature-item">
        <h4><span class="fi">📈</span> Escalabilidad real</h4>
        <p>Basada en EKS. Tu infra escala automáticamente según la demanda. Hot Sale sin drama.</p>
      </div>
      <div class="feature-item">
        <h4><span class="fi">💵</span> Ahorro de costos</h4>
        <p>Startups que migraron de Heroku reportan hasta un 60% de reducción en costos de infraestructura.</p>
      </div>
    </div>
  </div>

  <!-- ═══ CTA ═══ -->
  <div class="cta-box">
    <h2>¿Listo para dar el siguiente paso?</h2>
    <p>Si tu proyecto creció más allá de lo que una herramienta open source puede manejar, es momento de profesionalizar tu infraestructura. Startups reales ya están corriendo su infra 100% automatizada con SleakOps.</p>
    <a href="https://sleakops.com" target="_blank" rel="noopener" class="cta-button">Conocé SleakOps →</a>
    <a href="https://sleakops.com" target="_blank" rel="noopener" class="cta-secondary">sleakops.com</a>
  </div>

  <hr class="section-divider">

  <!-- ═══ CONCLUSION ═══ -->
  <h2>Conclusión</h2>

  <p>Las alternativas open source a Heroku como Coolify, Dokku, CapRover y Dokploy son herramientas fantásticas. Si estás arrancando, validando una idea o construyendo tu MVP con presupuesto ajustado, son exactamente lo que necesitás. Te permiten salir rápido al mercado sin gastar de más.</p>

  <p>Pero hay un momento en el que tu negocio necesita más: más seguridad, más escalabilidad, más confiabilidad, y menos tiempo dedicado a mantener servidores. En ese momento, necesitás una plataforma que se encargue de la infraestructura por vos para que tu equipo pueda enfocarse en lo que realmente importa: construir producto y crecer.</p>

  <p><strong>SleakOps te da infraestructura enterprise-grade sobre AWS, automatizada, escalable y segura, sin necesitar un equipo DevOps dedicado.</strong> Es el puente natural entre el mundo open source y una infraestructura de producción profesional.</p>

</article>

</body>
</html>



<p></p><p>The post <a href="https://sleakops.com/2026/03/20/alternativas-open-source-a-heroku/">Alternativas Open Source a Heroku</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p><p>La entrada <a href="https://sleakops.com/2026/03/20/alternativas-open-source-a-heroku/">Alternativas Open Source a Heroku</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Patrones de Sistemas Distribuidos y Kubernetes</title>
		<link>https://sleakops.com/2026/03/20/patrones-de-sistemas-distribuidos-kubernetes/</link>
		
		<dc:creator><![CDATA[matias]]></dc:creator>
		<pubDate>Fri, 20 Mar 2026 11:51:45 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://sleakops.com/?p=2083</guid>

					<description><![CDATA[<p>Arquitectura &#38; DevOps Kubernetes ⏱ 12 min de lectura Los patrones de diseño de sistemas distribuidos son soluciones probadas que encapsulan décadas de experiencia colectiva para resolver los problemas más comunes de la computación distribuida: redes poco fiables, nodos que fallan y consistencia de datos bajo presión. Entenderlos no es opcional si trabajás con Kubernetes [&#8230;]</p>
<p>The post <a href="https://sleakops.com/2026/03/20/patrones-de-sistemas-distribuidos-kubernetes/">Patrones de Sistemas Distribuidos y Kubernetes</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p>
<p>La entrada <a href="https://sleakops.com/2026/03/20/patrones-de-sistemas-distribuidos-kubernetes/">Patrones de Sistemas Distribuidos y Kubernetes</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></description>
										<content:encoded><![CDATA[<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;900&family=JetBrains+Mono:wght@400;500&display=swap');

.sleakops-post {
  --blue: #90cdf4;
  --navy: #1a202c;
  --coral: #e37562;
  --white: #ffffff;
  --surface: #161d27;
  --surface-raised: #1e2736;
  --border: rgba(144,205,244,0.08);
  --text-primary: #e2e8f0;
  --text-secondary: #a0aec0;
  --text-muted: #718096;
  --blue-dim: rgba(144,205,244,0.08);
  --coral-dim: rgba(227,117,98,0.10);
  font-family: 'Inter', Arial, sans-serif;
  color: var(--text-primary);
  line-height: 1.78;
  font-size: 17px;
  -webkit-font-smoothing: antialiased;
  max-width: 820px;
  margin: 0 auto;
  padding: 0 20px;
}

.sleakops-post *, .sleakops-post *::before, .sleakops-post *::after {
  box-sizing: border-box;
}

.sleakops-post ::selection {
  background: rgba(144,205,244,0.25);
  color: var(--white);
}

.sleakops-post strong { color: var(--blue); font-weight: 600; }
.sleakops-post em { color: var(--text-primary); }

.sleakops-post a.ilink {
  color: var(--blue);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: opacity 0.2s;
}
.sleakops-post a.ilink:hover { opacity: 0.8; }

/* ── HERO (badges only) ── */
.sleakops-post .hero-badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  padding: 32px 0 28px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 40px;
}

.sleakops-post .badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  padding: 5px 14px;
  border-radius: 100px;
  line-height: 1;
}

.sleakops-post .badge--cat {
  background: var(--blue-dim);
  border: 1px solid rgba(144,205,244,0.15);
  color: var(--blue);
}

.sleakops-post .badge--cat::before {
  content: '';
  width: 7px;
  height: 7px;
  background: var(--coral);
  border-radius: 50%;
  flex-shrink: 0;
}

.sleakops-post .badge--time {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-muted);
}

/* ── INTRO ── */
.sleakops-post .intro {
  border-left: 3px solid;
  border-image: linear-gradient(180deg, var(--blue), var(--coral)) 1;
  padding: 20px 0 20px 24px;
  margin-bottom: 48px;
  font-size: 18px;
  line-height: 1.8;
  color: var(--text-secondary);
}

.sleakops-post .intro p { margin: 0 0 14px; }
.sleakops-post .intro p:last-child { margin-bottom: 0; }

/* ── SECTION HEADERS ── */
.sleakops-post .section-label {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 56px 0 8px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-muted);
  letter-spacing: 0.3px;
}

.sleakops-post .section-label .num {
  color: var(--coral);
  font-weight: 700;
}

.sleakops-post .section-h2 {
  font-weight: 900;
  font-size: 1.55rem;
  color: var(--white);
  margin: 0 0 20px;
  line-height: 1.3;
}

/* ── PARAGRAPHS inside sections ── */
.sleakops-post .s-body p {
  margin: 0 0 20px;
  color: var(--text-secondary);
}
.sleakops-post .s-body p:last-child { margin-bottom: 0; }

/* ── CALLOUTS ── */
.sleakops-post .callout {
  border-radius: 12px;
  padding: 20px 24px;
  margin: 24px 0;
  font-size: 15px;
  line-height: 1.7;
}

.sleakops-post .callout--warn {
  background: var(--coral-dim);
  border-left: 4px solid var(--coral);
  color: #f0b4a8;
}

.sleakops-post .callout--info {
  background: var(--blue-dim);
  border-left: 4px solid var(--blue);
  color: #b8ddf8;
}

.sleakops-post .callout-label {
  display: block;
  font-weight: 700;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1px;
  margin-bottom: 6px;
}

.sleakops-post .callout--warn .callout-label { color: var(--coral); }
.sleakops-post .callout--info .callout-label { color: var(--blue); }

/* ── ARROW LISTS ── */
.sleakops-post .arrow-list {
  list-style: none;
  padding: 0;
  margin: 16px 0 24px;
}

.sleakops-post .arrow-list li {
  padding: 6px 0 6px 24px;
  position: relative;
  color: var(--text-secondary);
  font-size: 16px;
}

.sleakops-post .arrow-list li::before {
  content: '→';
  position: absolute;
  left: 0;
  color: var(--blue);
  font-weight: 700;
}

/* ── TABLE ── */
.sleakops-post .summary-table {
  width: 100%;
  border-collapse: collapse;
  margin: 32px 0;
  font-size: 15px;
}

.sleakops-post .summary-table thead th {
  background: var(--surface-raised);
  color: var(--blue);
  font-weight: 700;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  padding: 14px 16px;
  text-align: left;
  border-bottom: 2px solid var(--blue-dim);
}

.sleakops-post .summary-table tbody td {
  padding: 13px 16px;
  color: var(--text-secondary);
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

.sleakops-post .summary-table tbody tr:last-child td {
  border-bottom: none;
}

.sleakops-post .summary-table tbody td:first-child {
  color: var(--white);
  font-weight: 600;
}

.sleakops-post .summary-table code {
  font-family: 'JetBrains Mono', monospace;
  font-size: 13px;
  background: var(--blue-dim);
  color: var(--blue);
  padding: 2px 7px;
  border-radius: 4px;
}

/* ── SNIPPET BLOCK (featured snippets) ── */
.sleakops-post .snippet-block {
  background: var(--surface-raised);
  border: 1px solid rgba(144,205,244,0.12);
  border-radius: 16px;
  padding: 32px;
  margin: 48px 0;
}

.sleakops-post .snippet-block h3 {
  font-weight: 900;
  font-size: 1.15rem;
  color: var(--white);
  margin: 0 0 16px;
}

.sleakops-post .snippet-block p {
  color: var(--text-secondary);
  margin: 0 0 12px;
  font-size: 15px;
}
.sleakops-post .snippet-block p:last-child { margin-bottom: 0; }

/* ── CTA ── */
.sleakops-post .cta-box {
  background: var(--surface-raised);
  border: 1px solid rgba(144,205,244,0.12);
  border-radius: 20px;
  padding: 48px 36px;
  margin: 56px 0 40px;
  text-align: center;
  position: relative;
  overflow: hidden;
}

.sleakops-post .cta-box::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 25% 50%, rgba(144,205,244,0.06), transparent 60%),
    radial-gradient(ellipse at 75% 50%, rgba(227,117,98,0.04), transparent 60%);
  pointer-events: none;
}

.sleakops-post .cta-box > * { position: relative; z-index: 1; }

.sleakops-post .cta-box h3 {
  font-weight: 900;
  font-size: 1.6rem;
  color: var(--white);
  margin: 0 0 14px;
  line-height: 1.3;
}

.sleakops-post .cta-box p {
  color: var(--text-secondary);
  max-width: 540px;
  margin: 0 auto 28px;
  font-size: 16px;
}

.sleakops-post .cta-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: var(--blue);
  color: var(--navy) !important;
  font-weight: 700;
  font-size: 16px;
  padding: 15px 34px;
  border-radius: 10px;
  text-decoration: none;
  transition: transform 0.2s, box-shadow 0.2s;
  box-shadow: 0 4px 20px rgba(144,205,244,0.25);
}

.sleakops-post .cta-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 28px rgba(144,205,244,0.35);
}

.sleakops-post .cta-btn svg { width: 18px; height: 18px; flex-shrink: 0; }

.sleakops-post .cta-sub {
  margin-top: 16px;
  font-size: 13px;
  color: var(--text-muted);
}

/* ── TAGS ── */
.sleakops-post .post-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-top: 32px;
  border-top: 1px solid var(--border);
  margin-top: 48px;
}

.sleakops-post .post-tags span {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
  background: var(--blue-dim);
  padding: 4px 12px;
  border-radius: 6px;
  letter-spacing: 0.3px;
}

/* ── RESPONSIVE ── */
@media (max-width: 640px) {
  .sleakops-post { font-size: 16px; padding: 0 16px; }
  .sleakops-post .section-h2 { font-size: 1.3rem; }
  .sleakops-post .cta-box { padding: 36px 24px; }
  .sleakops-post .cta-box h3 { font-size: 1.3rem; }
  .sleakops-post .summary-table { font-size: 14px; }
  .sleakops-post .summary-table thead th,
  .sleakops-post .summary-table tbody td { padding: 10px 12px; }
}
</style>

<div class="sleakops-post">

  <!-- HERO: badges only -->
  <div class="hero-badges" role="complementary" aria-label="Metadata del artículo">
    <span class="badge badge--cat">Arquitectura &amp; DevOps</span>
    <span class="badge badge--cat">Kubernetes</span>
    <span class="badge badge--time">⏱ 12 min de lectura</span>
  </div>

  <!-- INTRO -->
  <div class="intro">
    <p>Los <strong>patrones de diseño de sistemas distribuidos</strong> son soluciones probadas que encapsulan décadas de experiencia colectiva para resolver los problemas más comunes de la computación distribuida: redes poco fiables, nodos que fallan y consistencia de datos bajo presión. Entenderlos no es opcional si trabajás con <a href="#kubernetes" class="ilink">Kubernetes</a> en producción.</p>
    <p>En este post repasamos los 8 patrones más utilizados, cómo <strong>Kubernetes</strong> los implementa de forma nativa y cómo <a href="https://sleakops.com" class="ilink" rel="noopener">SleakOps</a> te permite aprovecharlos sin ahogarte en complejidad operacional.</p>
  </div>

  <!-- ═══════════════════════════════════ SECTION 01 ═══════════════════════════════════ -->
  <div class="section-label"><span class="num">// 01</span> · Proxy inteligente para microservicios</div>
  <h2 class="section-h2">Ambassador Pattern: Descargá la complejidad de red de tu aplicación</h2>
  <div class="s-body">
    <p>El patrón Ambassador actúa como intermediario entre tu servicio y el mundo exterior. En lugar de que tu aplicación maneje logging, reintentos, rate limiting y monitoreo, un proxy dedicado se encarga de todo eso mientras tu código se enfoca exclusivamente en la lógica de negocio.</p>
    <p>Es como tener un asistente personal que filtra todas tus comunicaciones: vos solo recibís lo que importa, ya procesado y en el formato correcto.</p>

    <div class="callout callout--warn">
      <span class="callout-label">⚠ Problema</span>
      Si cada microservicio implementa su propia lógica de red (retries, timeouts, circuit breaking), terminás con código duplicado, inconsistencias y un acoplamiento invisible que hace imposible depurar fallos en cascada.
    </div>

    <div class="callout callout--info">
      <span class="callout-label">✓ Solución en Kubernetes</span>
      <a href="#envoy-proxy" class="ilink">Envoy Proxy</a> corre como contenedor Ambassador dentro del Pod, manejando toda la comunicación de red. Service meshes como <a href="#istio" class="ilink">Istio</a> automatizan la inyección de este proxy en cada Pod del clúster.
    </div>

    <ul class="arrow-list">
      <li>Centraliza logging y tracing de requests sin tocar código de negocio</li>
      <li>Maneja TLS termination, retries y circuit breaking a nivel de infraestructura</li>
      <li>Permite cambiar políticas de red en caliente sin redesplegar servicios</li>
    </ul>
  </div>

  <!-- ═══════════════════════════════════ SECTION 02 ═══════════════════════════════════ -->
  <div class="section-label"><span class="num">// 02</span> · Contenedores auxiliares co-localizados</div>
  <h2 class="section-h2">Sidecar Pattern: Extendé tu servicio sin modificar su código</h2>
  <div class="s-body">
    <p>El Sidecar viaja acoplado al contenedor principal dentro del mismo Pod, compartiendo red y filesystem. Se usa para tareas transversales: recolección de logs, exportación de métricas, sincronización de configuración, terminación TLS o service mesh.</p>
    <p>La clave es la <strong>separación de responsabilidades</strong>: tu servicio principal se mantiene simple y enfocado, mientras los sidecars son componentes reutilizables entre distintos servicios.</p>

    <div class="callout callout--warn">
      <span class="callout-label">⚠ Problema</span>
      Inyectar lógica de observabilidad, seguridad o comunicación directamente en el código de cada servicio produce aplicaciones infladas, difíciles de mantener y con ciclos de release acoplados.
    </div>

    <div class="callout callout--info">
      <span class="callout-label">✓ Solución en Kubernetes</span>
      Los Pods multi-contenedor son ciudadanos de primera clase en K8s. <a href="#fluent-bit" class="ilink">Fluent Bit</a> como sidecar para logs, <a href="#istio" class="ilink">Istio/Envoy</a> para service mesh y <a href="#dapr" class="ilink">Dapr</a> para state management son implementaciones estándar del patrón.
    </div>

    <ul class="arrow-list">
      <li>El contenedor sidecar comparte el ciclo de vida del Pod</li>
      <li>Equipos diferentes pueden desarrollar y versionar el sidecar de forma independiente</li>
      <li>Un mismo sidecar de logging sirve para servicios en Node, Python, Go o Java</li>
    </ul>
  </div>

  <!-- ═══════════════════════════════════ SECTION 03 ═══════════════════════════════════ -->
  <div class="section-label"><span class="num">// 03</span> · Resiliencia frente a fallos en cascada</div>
  <h2 class="section-h2">Circuit Breaker Pattern: Cortá la cadena de fallos antes de que se propague</h2>
  <div class="s-body">
    <p>Cuando una tubería de agua se rompe, lo primero que hacés es cerrar la llave principal. El Circuit Breaker funciona igual: cuando un servicio downstream deja de responder, el circuito se &#8220;abre&#8221; para detener las peticiones y evitar que el fallo se propague por todo el sistema.</p>
    <p>Opera en tres estados: <strong>Cerrado</strong> (flujo normal), <strong>Abierto</strong> (requests bloqueados, se devuelve un fallback) y <strong>Semi-abierto</strong> (se permiten requests limitados para testear la recuperación del servicio).</p>

    <div class="callout callout--warn">
      <span class="callout-label">⚠ Problema</span>
      Sin circuit breaking, un servicio lento o caído acumula requests pendientes que consumen threads, memoria y conexiones, provocando un efecto dominó que puede tumbar servicios completamente sanos.
    </div>

    <div class="callout callout--info">
      <span class="callout-label">✓ Solución en Kubernetes</span>
      <a href="#istio" class="ilink">Istio</a> permite configurar circuit breaking declarativamente por servicio. A nivel de aplicación, <a href="#resilience4j" class="ilink">Resilience4j</a> ofrece circuit breakers, retries, rate limiters y fallbacks como librería ligera.
    </div>

    <ul class="arrow-list">
      <li>Previene que un servicio degradado consuma los recursos de todo el clúster</li>
      <li>El estado Semi-abierto permite recuperación automática sin intervención manual</li>
      <li>Combinado con <strong>Bulkhead Pattern</strong>, aísla compartimentos para mayor resiliencia</li>
    </ul>
  </div>

  <!-- ═══════════════════════════════════ SECTION 04 ═══════════════════════════════════ -->
  <div class="section-label"><span class="num">// 04</span> · Separación de lecturas y escrituras</div>
  <h2 class="section-h2">CQRS: Escalá lecturas y escrituras de forma independiente</h2>
  <div class="s-body">
    <p>En la mayoría de aplicaciones, las lecturas superan ampliamente a las escrituras. Un e-commerce recibe miles de consultas de productos por cada compra. CQRS (Command Query Responsibility Segregation) separa ambas operaciones en servicios independientes, permitiendo que cada uno escale según su demanda real.</p>
    <p>El servicio de lectura puede usar réplicas optimizadas y cachés agresivos; el de escritura se enfoca en consistencia transaccional. Cada lado evoluciona y escala sin afectar al otro.</p>

    <div class="callout callout--warn">
      <span class="callout-label">⚠ Problema</span>
      Un modelo único para lectura y escritura genera cuellos de botella: las consultas pesadas compiten con las transacciones críticas por los mismos recursos de base de datos.
    </div>

    <div class="callout callout--info">
      <span class="callout-label">✓ Solución en Kubernetes</span>
      <a href="#deployments" class="ilink">Deployments</a> separados para servicios de lectura y escritura, cada uno con su propio <a href="#hpa" class="ilink">Horizontal Pod Autoscaler</a>. Las lecturas escalan a decenas de réplicas mientras las escrituras se mantienen controladas.
    </div>

    <ul class="arrow-list">
      <li>El servicio de queries puede usar bases de datos read-only optimizadas</li>
      <li>Permite adoptar tecnologías diferentes para cada lado (SQL para escrituras, Elasticsearch para lecturas)</li>
      <li>Se combina naturalmente con Event Sourcing para un flujo de datos reactivo completo</li>
    </ul>
  </div>

  <!-- ═══════════════════════════════════ SECTION 05 ═══════════════════════════════════ -->
  <div class="section-label"><span class="num">// 05</span> · Historial inmutable de cambios de estado</div>
  <h2 class="section-h2">Event Sourcing: Registrá cada cambio como un evento inmutable</h2>
  <div class="s-body">
    <p>En lugar de guardar solo el estado actual de tus datos, Event Sourcing almacena cada cambio como un evento inmutable en una secuencia ordenada. El estado se reconstruye &#8220;reproduciendo&#8221; los eventos. Esto ofrece auditoría completa, capacidad de depuración temporal y una integración natural con arquitecturas event-driven.</p>

    <div class="callout callout--warn">
      <span class="callout-label">⚠ Problema</span>
      Cuando solo almacenás el estado final, perdés el contexto de cómo se llegó ahí. Depurar bugs, auditar cambios o reconstruir estados pasados se vuelve imposible o requiere sistemas de logging separados e incompletos.
    </div>

    <div class="callout callout--info">
      <span class="callout-label">✓ Solución en Kubernetes</span>
      <a href="#kafka" class="ilink">Apache Kafka</a> desplegado con el <a href="#strimzi" class="ilink">Strimzi Operator</a> en K8s proporciona un event store distribuido y duradero. Los consumidores pueden re-leer eventos desde cualquier punto en el tiempo.
    </div>

    <ul class="arrow-list">
      <li>Auditoría completa: cada cambio queda registrado con timestamp y contexto</li>
      <li>Permite &#8220;time travel debugging&#8221; reproduciendo eventos hasta un punto específico</li>
      <li>Ideal para sistemas financieros, colaboración en tiempo real y compliance</li>
    </ul>
  </div>

  <!-- ═══════════════════════════════════ SECTION 06 ═══════════════════════════════════ -->
  <div class="section-label"><span class="num">// 06</span> · Coordinación de nodos distribuidos</div>
  <h2 class="section-h2">Leader Election: Un solo nodo coordina, los demás están listos para asumir</h2>
  <div class="s-body">
    <p>Cuando múltiples nodos pueden ejecutar la misma tarea, necesitás un mecanismo para designar a uno como &#8220;líder&#8221; que coordine las operaciones. Si el líder cae, otro nodo asume automáticamente el rol. El patrón maneja la temida situación de <strong>split-brain</strong> (dos líderes activos simultáneos) mediante generation clocks o epoch numbers.</p>

    <div class="callout callout--warn">
      <span class="callout-label">⚠ Problema</span>
      Sin Leader Election, múltiples nodos pueden intentar ejecutar la misma operación crítica simultáneamente, causando duplicación, corrupción de datos o deadlocks.
    </div>

    <div class="callout callout--info">
      <span class="callout-label">✓ Solución en Kubernetes</span>
      K8s provee la <a href="#lease-api" class="ilink">Lease API</a> nativa para Leader Election. Herramientas como los controladores internos de K8s, Kafka y bases de datos distribuidas usan este mecanismo directamente.
    </div>

    <ul class="arrow-list">
      <li>Garantiza que una sola instancia ejecute tareas críticas como migraciones o cron jobs</li>
      <li>Failover automático: si el líder no renueva el lease, otro nodo asume en segundos</li>
      <li>Bases de datos como Cassandra y brokers como Kafka dependen de este patrón</li>
    </ul>
  </div>

  <!-- ═══════════════════════════════════ SECTION 07 ═══════════════════════════════════ -->
  <div class="section-label"><span class="num">// 07</span> · Comunicación desacoplada entre servicios</div>
  <h2 class="section-h2">Pub/Sub: Desacoplá productores y consumidores con mensajería asíncrona</h2>
  <div class="s-body">
    <p>En lugar de que los servicios se comuniquen directamente (acoplamiento fuerte), el patrón Publisher/Subscriber introduce un intermediario: los publicadores envían mensajes a topics y los suscriptores reciben solo los que les interesan. Productores y consumidores no se conocen entre sí.</p>

    <div class="callout callout--warn">
      <span class="callout-label">⚠ Problema</span>
      La comunicación síncrona directa entre microservicios crea un &#8220;monolito distribuido&#8221;: si un servicio se cae o responde lento, todos los que dependen de él se degradan.
    </div>

    <div class="callout callout--info">
      <span class="callout-label">✓ Solución en Kubernetes</span>
      <a href="#kafka" class="ilink">Kafka</a>, <a href="#rabbitmq" class="ilink">RabbitMQ</a> o <a href="#nats" class="ilink">NATS</a> desplegados en K8s con sus respectivos Operators. Combinados con <a href="#keda" class="ilink">KEDA</a>, los consumidores escalan automáticamente según el backlog de mensajes.
    </div>

    <ul class="arrow-list">
      <li>Los productores publican sin saber quién consume; los consumidores suscriben sin saber quién produce</li>
      <li>Resiliencia bajo carga: los mensajes se encolan si un consumidor está temporalmente caído</li>
      <li>Base de arquitecturas event-driven para notificaciones, pagos y analytics</li>
    </ul>
  </div>

  <!-- ═══════════════════════════════════ SECTION 08 ═══════════════════════════════════ -->
  <div class="section-label"><span class="num">// 08</span> · Particionamiento horizontal de datos</div>
  <h2 class="section-h2">Sharding Pattern: Distribuí datos masivos entre múltiples nodos</h2>
  <div class="s-body">
    <p>Cuando los datos crecen más allá de lo que un solo nodo puede manejar, el Sharding particiona la información en subconjuntos distribuidos según criterios como rangos de claves o algoritmos de hashing. Cada shard opera de forma semi-independiente, escalando horizontalmente tanto capacidad como throughput.</p>

    <div class="callout callout--warn">
      <span class="callout-label">⚠ Problema</span>
      El escalado vertical tiene un techo físico y económico. Una base de datos monolítica se convierte en cuello de botella y punto único de fallo cuando los datos superan los terabytes.
    </div>

    <div class="callout callout--info">
      <span class="callout-label">✓ Solución en Kubernetes</span>
      <a href="#statefulsets" class="ilink">StatefulSets</a> proveen identidades estables y almacenamiento persistente para cada shard. <a href="#operators" class="ilink">Operators</a> de bases de datos como MongoDB, CockroachDB o Vitess automatizan el sharding y rebalanceo dentro de K8s.
    </div>

    <ul class="arrow-list">
      <li>Escala horizontalmente: más datos = más shards, no servidores más caros</li>
      <li>Cada shard puede tener sus propias réplicas para alta disponibilidad</li>
      <li>Bases de datos como MongoDB, Cassandra y CockroachDB lo implementan nativamente</li>
    </ul>
  </div>

  <!-- ═══════════════════════════════════ TABLE ═══════════════════════════════════ -->
  <div class="section-label"><span class="num">//</span> · Referencia rápida</div>
  <h2 class="section-h2">Tabla resumen: Patrones de sistemas distribuidos en Kubernetes</h2>

  <table class="summary-table" role="table" aria-label="Tabla resumen de patrones distribuidos y su implementación en Kubernetes">
    <thead>
      <tr>
        <th>Patrón</th>
        <th>Problema que resuelve</th>
        <th>Implementación en K8s</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Ambassador</td>
        <td>Complejidad de red en cada servicio</td>
        <td><code>Envoy</code> / Istio sidecar proxy</td>
      </tr>
      <tr>
        <td>Sidecar</td>
        <td>Lógica transversal acoplada al servicio</td>
        <td>Pods multi-contenedor</td>
      </tr>
      <tr>
        <td>Circuit Breaker</td>
        <td>Fallos en cascada entre servicios</td>
        <td>Istio DestinationRules / <code>Resilience4j</code></td>
      </tr>
      <tr>
        <td>CQRS</td>
        <td>Lecturas y escrituras compiten por recursos</td>
        <td>Deployments separados + <code>HPA</code></td>
      </tr>
      <tr>
        <td>Event Sourcing</td>
        <td>Pérdida de historial de cambios</td>
        <td><code>Kafka</code> + Strimzi Operator</td>
      </tr>
      <tr>
        <td>Leader Election</td>
        <td>Coordinación sin punto único de fallo</td>
        <td><code>Lease API</code> nativa de K8s</td>
      </tr>
      <tr>
        <td>Pub/Sub</td>
        <td>Acoplamiento síncrono entre servicios</td>
        <td>Kafka / RabbitMQ / <code>NATS</code> + KEDA</td>
      </tr>
      <tr>
        <td>Sharding</td>
        <td>Datos que superan un solo nodo</td>
        <td><code>StatefulSets</code> + Operators de DB</td>
      </tr>
    </tbody>
  </table>

  <!-- ═══════════════════════════════════ SNIPPET ═══════════════════════════════════ -->
  <div class="snippet-block">
    <h3>¿Cuáles son los patrones de sistemas distribuidos más usados en Kubernetes?</h3>
    <p>Los 8 patrones más utilizados son: <strong>Ambassador</strong> (proxy de red), <strong>Sidecar</strong> (contenedor auxiliar), <strong>Circuit Breaker</strong> (resiliencia), <strong>CQRS</strong> (separación lectura/escritura), <strong>Event Sourcing</strong> (historial inmutable), <strong>Leader Election</strong> (coordinación), <strong>Pub/Sub</strong> (mensajería asíncrona) y <strong>Sharding</strong> (particionamiento de datos).</p>
    <p>Kubernetes implementa estos patrones nativamente a través de Pods multi-contenedor, Lease API, StatefulSets, Horizontal Pod Autoscaler y un ecosistema de Operators que automatizan su operación en producción.</p>
  </div>

  <!-- ═══════════════════════════════════ CTA ═══════════════════════════════════ -->
  <div class="cta-box">
    <h3>Kubernetes en producción,<br>sin la complejidad operacional</h3>
    <p>SleakOps automatiza tu infraestructura en AWS con las mejores prácticas del Well-Architected Framework. Clúster Kubernetes seguro, CI/CD integrado, observabilidad completa y gestión de ambientes, todo en minutos. Los recursos quedan en tu cuenta de AWS, con control total.</p>
    <a href="https://sleakops.com" class="cta-btn" rel="noopener">
      Empezá gratis con SleakOps
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
    </a>
    <p class="cta-sub">Disponible en AWS Marketplace · Deploy en minutos · Soporte humano real</p>
  </div>

  <!-- TAGS -->
  <div class="post-tags" role="list" aria-label="Tags del artículo">
    <span role="listitem">Sistemas Distribuidos</span>
    <span role="listitem">Kubernetes</span>
    <span role="listitem">Ambassador</span>
    <span role="listitem">Sidecar</span>
    <span role="listitem">Circuit Breaker</span>
    <span role="listitem">CQRS</span>
    <span role="listitem">Event Sourcing</span>
    <span role="listitem">Pub/Sub</span>
    <span role="listitem">Sharding</span>
    <span role="listitem">DevOps</span>
    <span role="listitem">AWS</span>
    <span role="listitem">SleakOps</span>
  </div>

</div>



<p></p><p>The post <a href="https://sleakops.com/2026/03/20/patrones-de-sistemas-distribuidos-kubernetes/">Patrones de Sistemas Distribuidos y Kubernetes</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p><p>La entrada <a href="https://sleakops.com/2026/03/20/patrones-de-sistemas-distribuidos-kubernetes/">Patrones de Sistemas Distribuidos y Kubernetes</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>5 Mejores Herramientas de Test de Carga en Kubernetes</title>
		<link>https://sleakops.com/2026/03/20/5-herramientas-para-hacer-test-de-carga-en-kubernetes-y-como-elegir-la-correcta-para-tu-caso/</link>
		
		<dc:creator><![CDATA[matias]]></dc:creator>
		<pubDate>Fri, 20 Mar 2026 11:51:41 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://sleakops.com/?p=1923</guid>

					<description><![CDATA[<p>Kubernetes DevOps Load Testing Performance 7 minutos de lectura Tu aplicación funciona perfecto en desarrollo. Los tests pasan. El deploy a staging sale limpio. Todo se ve bien. Hasta que un martes a las 11 AM entran 5.000 usuarios simultáneos y tu servicio de checkout empieza a devolver 502s, la base de datos se satura [&#8230;]</p>
<p>The post <a href="https://sleakops.com/2026/03/20/5-herramientas-para-hacer-test-de-carga-en-kubernetes-y-como-elegir-la-correcta-para-tu-caso/">5 Mejores Herramientas de Test de Carga en Kubernetes</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p>
<p>La entrada <a href="https://sleakops.com/2026/03/20/5-herramientas-para-hacer-test-de-carga-en-kubernetes-y-como-elegir-la-correcta-para-tu-caso/">5 Mejores Herramientas de Test de Carga en Kubernetes</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="sleakops-post">
<style>
:root {
    --navy:      #1a202c;
    --navy-light:#232c3e;
    --navy-mid:  #2d3748;
    --blue:      #90cdf4;
    --blue-dim:  rgba(144,205,244,0.09);
    --blue-mid:  rgba(144,205,244,0.17);
    --coral:     #e37562;
    --coral-dim: rgba(227,117,98,0.10);
    --white:     #ffffff;
    --gray:      #a0aec0;
    --gray-dim:  rgba(160,174,192,0.09);
    --border:    rgba(144,205,244,0.13);
  }

  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

  .sleakops-post {
    font-family: 'Inter', sans-serif;
        color: var(--white);
    line-height: 1.75;
    font-size: 16px;
    overflow-x: hidden;
  }

  /* ── HERO ── */
  .hero {
    background: var(--navy);
    padding: 44px 0 40px;
    border-bottom: 1px solid var(--border);
    position: relative;
    overflow: hidden;
  }
  .hero::before {
    content: '';
    position: absolute; inset: 0;
    background-image:
      repeating-linear-gradient( 60deg, transparent, transparent 38px, rgba(144,205,244,.03) 38px, rgba(144,205,244,.03) 40px),
      repeating-linear-gradient(-60deg, transparent, transparent 38px, rgba(144,205,244,.03) 38px, rgba(144,205,244,.03) 40px);
    pointer-events: none;
  }
  .hero::after {
    content: '';
    position: absolute; top: -110px; right: -110px;
    width: 400px; height: 400px;
    background: radial-gradient(circle, rgba(144,205,244,.08) 0%, transparent 70%);
    pointer-events: none;
  }

  .container { max-width: 820px; margin: 0 auto; padding: 0 32px; position: relative; z-index: 1; }

  .meta-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
  .badge {
    font-size: 10px; font-weight: 900; letter-spacing: .14em; text-transform: uppercase;
    padding: 4px 11px; border-radius: 4px;
    background: var(--blue-dim); color: var(--blue); border: 1px solid rgba(144,205,244,.26);
  }
  .badge.coral { background: var(--coral-dim); color: var(--coral); border-color: rgba(227,117,98,.26); }
  .badge.gray  { background: var(--gray-dim);  color: var(--gray);  border-color: rgba(160,174,192,.2); }

  .reading-pill {
    margin-left: auto;
    display: inline-flex; align-items: center; gap: 7px;
    background: var(--navy-mid); border: 1px solid var(--border);
    border-radius: 32px; padding: 6px 15px; font-size: 12px; color: var(--gray);
  }
  .reading-pill svg { color: var(--blue); flex-shrink: 0; }

  /* ── CONTENT ── */
  .content { padding: 52px 0 80px; }

  /* intro */
  .intro-block {
    background: linear-gradient(135deg, var(--navy-light), var(--navy-mid));
    border: 1px solid var(--border); border-radius: 12px;
    padding: 30px 34px; margin-bottom: 56px;
    position: relative; overflow: hidden;
  }
  .intro-block::before {
    content: ''; position: absolute; top: 0; left: 0;
    width: 4px; height: 100%;
    background: linear-gradient(to bottom, var(--blue), var(--coral));
  }
  .intro-block p { color: var(--gray); font-size: 15.5px; line-height: 1.85; margin-bottom: 13px; }
  .intro-block p:last-child { margin-bottom: 0; }
  .intro-block strong { color: var(--white); font-weight: 600; }

  /* ── TOOL SECTIONS ── */
  .section { margin-bottom: 62px; }

  .section-number {
    display: inline-block; font-family: 'JetBrains Mono', monospace;
    font-size: 11px; font-weight: 600; color: var(--blue);
    letter-spacing: .1em; margin-bottom: 8px; opacity: .8;
  }

  .tool-header {
    display: flex; align-items: flex-start; gap: 14px; margin-bottom: 6px;
  }
  .tool-logo {
    flex-shrink: 0; width: 42px; height: 42px;
    background: var(--blue-dim); border: 1px solid rgba(144,205,244,.22);
    border-radius: 10px; display: flex; align-items: center; justify-content: center;
    font-size: 20px; margin-top: 2px;
  }
  .tool-title-block { flex: 1; }
  .tool-title {
    font-weight: 900; font-size: clamp(19px, 2.8vw, 25px);
    color: var(--white); line-height: 1.2; margin-bottom: 4px;
  }
  .tool-sub {
    font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 600;
    color: var(--gray); letter-spacing: .06em; opacity: .7;
  }
  .tool-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; }
  .tool-tag {
    font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
    padding: 2px 8px; border-radius: 3px; font-family: 'Inter', sans-serif;
    background: var(--navy-mid); color: var(--gray); border: 1px solid rgba(160,174,192,.18);
  }
  .tool-tag.blue { background: var(--blue-dim); color: var(--blue); border-color: rgba(144,205,244,.22); }
  .tool-tag.coral { background: var(--coral-dim); color: var(--coral); border-color: rgba(227,117,98,.22); }

  .section-divider { width: 44px; height: 3px; background: linear-gradient(to right, var(--blue), transparent); margin: 12px 0 20px; border-radius: 2px; }

  .section p { color: var(--gray); margin-bottom: 15px; line-height: 1.85; font-size: 15.5px; }
  .section p strong { color: var(--white); font-weight: 600; }
  .section p:last-child { margin-bottom: 0; }

  /* sub-headings dentro de sección */
  .sub-heading {
    font-size: 12px; font-weight: 900; letter-spacing: .1em; text-transform: uppercase;
    color: var(--blue); margin: 20px 0 10px; font-family: 'JetBrains Mono', monospace;
  }

  /* callouts */
  .callout {
    border-radius: 10px; padding: 20px 24px; margin: 20px 0;
    border: 1px solid; position: relative; overflow: hidden;
  }
  .callout-warning { background: rgba(227,117,98,.07); border-color: rgba(227,117,98,.27); }
  .callout-warning::after { content:'⚠'; position:absolute; top:12px; right:16px; font-size:18px; opacity:.22; }
  .callout-tip    { background: var(--blue-dim); border-color: rgba(144,205,244,.22); }
  .callout-tip::after    { content:'💡'; position:absolute; top:12px; right:16px; font-size:16px; opacity:.4; }
  .callout-label { font-size:10px; font-weight:900; letter-spacing:.14em; text-transform:uppercase; margin-bottom:8px; }
  .callout-warning .callout-label { color: var(--coral); }
  .callout-tip    .callout-label { color: var(--blue); }
  .callout p { font-size:14.5px !important; margin-bottom:8px !important; line-height:1.75 !important; }
  .callout p:last-child { margin-bottom:0 !important; }
  .callout strong { color: var(--white); font-weight:600; }

  /* when-grid */
  .when-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 18px 0; }
  @media(max-width:540px){ .when-grid { grid-template-columns: 1fr; } }
  .when-card {
    background: var(--navy-light); border: 1px solid var(--border);
    border-radius: 10px; padding: 16px 18px;
  }
  .when-card.yes { border-color: rgba(144,205,244,.28); }
  .when-card.no  { border-color: rgba(227,117,98,.22); }
  .when-label {
    font-size: 10px; font-weight: 900; letter-spacing: .12em; text-transform: uppercase;
    margin-bottom: 10px; display: flex; align-items: center; gap: 6px;
  }
  .when-card.yes .when-label { color: var(--blue); }
  .when-card.no  .when-label { color: var(--coral); }
  .when-list { list-style: none; display: flex; flex-direction: column; gap: 7px; }
  .when-list li { font-size: 13.5px; color: var(--gray); line-height: 1.6; display: flex; gap: 8px; align-items: flex-start; }
  .when-list li::before { flex-shrink: 0; margin-top: 1px; font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 12px; }
  .when-card.yes .when-list li::before { content: '✓'; color: var(--blue); }
  .when-card.no  .when-list li::before { content: '✗'; color: var(--coral); }

  /* styled list */
  .styled-list { list-style:none; margin:14px 0 18px; display:flex; flex-direction:column; gap:10px; }
  .styled-list li { display:flex; align-items:flex-start; gap:12px; color:var(--gray); font-size:15px; line-height:1.72; }
  .styled-list li::before { content:'→'; color:var(--blue); font-weight:700; flex-shrink:0; margin-top:1px; font-family:'JetBrains Mono',monospace; }
  .styled-list li strong { color:var(--white); }

  code {
    font-family:'JetBrains Mono',monospace; font-size:13px;
    background:var(--navy-mid); color:var(--blue);
    padding:2px 6px; border-radius:4px; border:1px solid var(--border);
  }

  /* ── COMPARISON TABLE ── */
  .table-wrapper {
    background: var(--navy); border: 1px solid var(--border);
    border-radius: 12px; overflow: hidden; margin: 44px 0;
    overflow-x: auto;
  }
  .table-header {
    background: linear-gradient(135deg, var(--navy-mid), var(--navy-light));
    padding: 16px 24px; border-bottom: 1px solid var(--border);
    display: flex; align-items: center; gap: 10px;
  }
  .table-header span { font-size:11px; font-weight:900; letter-spacing:.12em; text-transform:uppercase; color:var(--blue); }
  table { width:100%; border-collapse:collapse; min-width: 560px; }
  th {
    text-align:left; padding:11px 18px; font-size:10px; font-weight:700;
    letter-spacing:.1em; text-transform:uppercase; color:var(--gray);
    background:var(--navy-mid); border-bottom:1px solid var(--border);
  }
  td { padding:12px 18px; font-size:13.5px; border-bottom:1px solid rgba(255,255,255,.04); color:var(--gray); vertical-align:middle; }
  td:first-child { font-family:'JetBrains Mono',monospace; font-size:13px; font-weight:600; color:var(--white); white-space:nowrap; }
  tr:last-child td { border-bottom:none; }
  tr:hover td { background:rgba(255,255,255,.025); }

  .lang-pill {
    display:inline-block; font-size:10px; font-weight:700; padding:2px 8px; border-radius:3px;
    letter-spacing:.06em; font-family:'JetBrains Mono',monospace;
    background:var(--navy-mid); color:var(--blue); border:1px solid var(--border);
  }
  .curve-low  { color: #68d391; font-weight:600; font-size:12px; }
  .curve-mid  { color: var(--blue); font-weight:600; font-size:12px; }
  .curve-high { color: var(--coral); font-weight:600; font-size:12px; }

  /* ── CTA ── */
  .cta-block {
    background:linear-gradient(135deg, var(--navy-mid), rgba(26,32,44,.95));
    border:1px solid rgba(144,205,244,.26); border-radius:16px;
    padding:46px 38px; text-align:center; position:relative; overflow:hidden; margin-top:64px;
  }
  .cta-block::before {
    content:''; position:absolute; bottom:-80px; left:50%; transform:translateX(-50%);
    width:340px; height:340px;
    background:radial-gradient(circle, rgba(144,205,244,.08) 0%, transparent 70%);
    pointer-events:none;
  }
  .cta-block h2 { font-weight:900; font-size:clamp(19px,3.2vw,29px); color:var(--white); margin-bottom:11px; position:relative; }
  .cta-block p  { color:var(--gray); font-size:15px; max-width:500px; margin:0 auto 28px; line-height:1.8; position:relative; }
  .cta-btn {
    display:inline-block; background:var(--blue); color:var(--navy) !important;
    font-weight:900; font-size:13px; letter-spacing:.07em; text-transform:uppercase;
    text-decoration:none; padding:13px 34px; border-radius:8px;
    transition:transform .15s, box-shadow .15s; position:relative;
  }
  .cta-btn:hover { transform:translateY(-2px); box-shadow:0 8px 28px rgba(144,205,244,.26); }

  /* share row */
  .share-row { margin-top:42px; padding-top:24px; border-top:1px solid var(--border); display:flex; align-items:center; gap:9px; flex-wrap:wrap; }
  .share-label { font-size:11px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--gray); }
  .share-tag { background:var(--navy-mid); border:1px solid var(--border); color:var(--gray); font-size:12px; padding:4px 12px; border-radius:20px; text-decoration:none; transition:border-color .15s, color .15s; }
  .share-tag:hover { border-color:var(--blue); color:var(--blue); }

  /* tool separator */
  .tool-sep { border:none; border-top:1px solid var(--border); margin:0 0 60px; }

  @media(max-width:640px){
    .container { padding:0 18px; }
    .hero { padding:32px 0 28px; }
    .intro-block { padding:22px 20px; }
    td,th { padding:10px 12px; }
    .cta-block { padding:30px 20px; }
    .reading-pill { margin-left:0; }
  }
</style>
<!-- HERO -->
<header class="hero">
  <div class="container">
    <div class="meta-bar">
      <span class="badge">Kubernetes</span>
      <span class="badge">DevOps</span>
      <span class="badge coral">Load Testing</span>
      <span class="badge gray">Performance</span>
      <div class="reading-pill">
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
        7 minutos de lectura
      </div>
    </div>
  </div>
</header>

<!-- CONTENT -->
<main class="content">
  <div class="container">

    <!-- INTRO -->
    <div class="intro-block">
      <p>Tu aplicación funciona perfecto en desarrollo. Los tests pasan. El deploy a staging sale limpio. Todo se ve bien. Hasta que un martes a las 11 AM entran 5.000 usuarios simultáneos y tu servicio de checkout empieza a devolver 502s, la base de datos se satura y el equipo entra en <strong>modo bombero</strong>.</p>
      <p>La pregunta incómoda: <strong>¿probaste tu aplicación bajo carga real antes de mandarla a producción?</strong> La mayoría de los equipos no lo hace — no por falta de voluntad, sino porque el load testing siempre quedó en el backlog como algo que &#8220;vamos a hacer la semana que viene&#8221;.</p>
      <p>El ecosistema de herramientas maduró mucho, y varias están diseñadas específicamente para correr dentro de Kubernetes — lo cual te da ventajas enormes en términos de escala, acceso a servicios internos y realismo del test.</p>
    </div>

    <!-- 01 k6 -->
    <section class="section">
      <span class="section-number">// 01</span>
      <div class="tool-header">
        <div class="tool-logo">⚡</div>
        <div class="tool-title-block">
          <div class="tool-title">k6 <span style="color:var(--gray);font-weight:400;font-size:18px;">— Grafana k6</span></div>
          <div class="tool-sub">escrito en Go · tests en JavaScript</div>
          <div class="tool-tags">
            <span class="tool-tag blue">Developer-first</span>
            <span class="tool-tag blue">Stack Grafana</span>
            <span class="tool-tag">Alta eficiencia</span>
          </div>
        </div>
      </div>
      <div class="section-divider"></div>

      <p>Probablemente la herramienta de load testing más popular de los últimos años. La propuesta de valor de k6 es clara: <strong>developer experience ante todo</strong>. Escribir un test de carga se siente como escribir un test unitario — definís escenarios con stages que simulan rampas de usuarios, carga sostenida y bajada gradual, todo en un archivo JavaScript que vive en tu repositorio.</p>

      <div class="callout callout-tip">
        <div class="callout-label">Por qué destaca en Kubernetes</div>
        <p>k6 está escrito en Go, lo que significa que <strong>un solo pod puede generar miles de requests por segundo</strong> sin consumir grandes cantidades de memoria o CPU. La integración con Prometheus y Grafana es nativa — las métricas del test aparecen en los mismos dashboards donde monitoreás tu aplicación, permitiéndote correlacionar carga con latencia, CPU, memoria y error rates en una sola vista.</p>
      </div>

      <div class="when-grid">
        <div class="when-card yes">
          <div class="when-label">✓ Elegilo cuando</div>
          <ul class="when-list">
            <li>Querés adoptar load testing sin curva de aprendizaje pronunciada</li>
            <li>Ya usás el stack Grafana para observabilidad</li>
            <li>Tu equipo trabaja en JavaScript o TypeScript</li>
          </ul>
        </div>
        <div class="when-card no">
          <div class="when-label">✗ No lo uses cuando</div>
          <ul class="when-list">
            <li>Necesitás generar carga distribuida masiva desde un solo pod (existe k6-operator pero agrega complejidad)</li>
            <li>Tu equipo es mayoritariamente Python — la experiencia no va a ser tan natural</li>
          </ul>
        </div>
      </div>
    </section>

    <hr class="tool-sep">

    <!-- 02 LOCUST -->
    <section class="section">
      <span class="section-number">// 02</span>
      <div class="tool-header">
        <div class="tool-logo">🦗</div>
        <div class="tool-title-block">
          <div class="tool-title">Locust</div>
          <div class="tool-sub">escrito en Python · tests en Python</div>
          <div class="tool-tags">
            <span class="tool-tag blue">UI en tiempo real</span>
            <span class="tool-tag blue">Escenarios complejos</span>
            <span class="tool-tag">Distribuido</span>
          </div>
        </div>
      </div>
      <div class="section-divider"></div>

      <p>Si tu equipo vive en Python, Locust se siente como casa. Los tests son <strong>clases de Python</strong> donde definís el comportamiento de cada usuario simulado: qué endpoints llama, en qué orden, con qué datos, cuánto espera entre requests. Esta flexibilidad permite modelar comportamientos de usuario muy realistas y complejos.</p>

      <div class="callout callout-tip">
        <div class="callout-label">La UI en tiempo real</div>
        <p>Locust tiene una <strong>interfaz web integrada</strong> donde podés ver en tiempo real cuántos usuarios están activos, la tasa de requests, los tiempos de respuesta y los errores. Podés arrancar un test y subir la carga gradualmente desde la UI sin tocar la terminal. Para demos y sesiones de equipo, esto es invaluable. La arquitectura master/workers se mapea perfectamente a Kubernetes.</p>
      </div>

      <div class="when-grid">
        <div class="when-card yes">
          <div class="when-label">✓ Elegilo cuando</div>
          <ul class="when-list">
            <li>Tu equipo trabaja en Python y querés máxima flexibilidad</li>
            <li>Necesitás modelar flujos de usuario complejos con lógica custom</li>
            <li>La UI en tiempo real es importante para el equipo</li>
          </ul>
        </div>
        <div class="when-card no">
          <div class="when-label">✗ No lo uses cuando</div>
          <ul class="when-list">
            <li>Tu objetivo es puro volumen de requests por segundo — las coroutines de Python son menos eficientes que Go para eso</li>
            <li>Recursos son muy limitados — necesitás más pods de worker que k6 para el mismo volumen</li>
          </ul>
        </div>
      </div>
    </section>

    <hr class="tool-sep">

    <!-- 03 GATLING -->
    <section class="section">
      <span class="section-number">// 03</span>
      <div class="tool-header">
        <div class="tool-logo">🏹</div>
        <div class="tool-title-block">
          <div class="tool-title">Gatling</div>
          <div class="tool-sub">Scala / Java · ecosistema JVM</div>
          <div class="tool-tags">
            <span class="tool-tag blue">Reportes profesionales</span>
            <span class="tool-tag blue">Flujos complejos</span>
            <span class="tool-tag coral">Curva alta</span>
          </div>
        </div>
      </div>
      <div class="section-divider"></div>

      <p>Gatling lleva más de una década en el mercado y es especialmente popular en equipos enterprise y ecosistemas JVM. Si hay algo que lo define es <strong>la calidad de sus reportes</strong>: al terminar un test genera un HTML detallado con distribución de tiempos de respuesta, percentiles (p50, p95, p99), requests por segundo y errores por tipo — segmentado por escenario y endpoint.</p>

      <div class="callout callout-tip">
        <div class="callout-label">Flujos encadenados y correlación</div>
        <p>El DSL de Gatling permite modelar flujos de usuario complejos con pasos encadenados, <strong>extracción de datos de respuestas anteriores (correlation)</strong>, manejo de sesiones y lógica condicional. Para testear flujos como <em>login → buscar producto → agregar al carrito → checkout</em>, Gatling brilla. Usa Akka y Netty para I/O asincrónico, lo que lo hace muy eficiente para concurrencia.</p>
      </div>

      <div class="when-grid">
        <div class="when-card yes">
          <div class="when-label">✓ Elegilo cuando</div>
          <ul class="when-list">
            <li>Necesitás reportes de calidad profesional sin esfuerzo adicional</li>
            <li>Tus flujos de usuario son complejos y encadenados con correlation</li>
            <li>Tu equipo ya trabaja en el ecosistema JVM</li>
            <li>Necesitás presentar resultados en auditorías o compliance</li>
          </ul>
        </div>
        <div class="when-card no">
          <div class="when-label">✗ No lo uses cuando</div>
          <ul class="when-list">
            <li>Tu equipo no viene del mundo JVM — el DSL de Scala tiene curva pronunciada</li>
            <li>Recursos son escasos — la JVM tiene cold start lento y mayor consumo de memoria base</li>
          </ul>
        </div>
      </div>
    </section>

    <hr class="tool-sep">

    <!-- 04 VEGETA -->
    <section class="section">
      <span class="section-number">// 04</span>
      <div class="tool-header">
        <div class="tool-logo">🗡️</div>
        <div class="tool-title-block">
          <div class="tool-title">Vegeta</div>
          <div class="tool-sub">CLI puro en Go · sin configuración</div>
          <div class="tool-tags">
            <span class="tool-tag blue">CI/CD gate</span>
            <span class="tool-tag blue">Unix-style</span>
            <span class="tool-tag">Curva mínima</span>
          </div>
        </div>
      </div>
      <div class="section-divider"></div>

      <p>Vegeta es una herramienta de línea de comandos pura con una filosofía radicalmente diferente: <strong>una cosa, bien hecha</strong>. Mientras k6 o Locust modelan &#8220;usuarios virtuales&#8221;, Vegeta trabaja con &#8220;requests por segundo&#8221;. Le decís <em>&#8220;mandá 500 req/s a este endpoint durante 60 segundos&#8221;</em> y eso es exactamente lo que hace.</p>

      <div class="callout callout-tip">
        <div class="callout-label">Ideal para CI/CD gates</div>
        <p>Un solo comando genera carga, otro analiza los resultados. Podés integrarlo en un pipeline de CI/CD como un <strong>gate automático</strong>: si la latencia p99 supera X milisegundos, el deploy falla. Para tests rápidos y exploratorios — <em>&#8220;¿cuántos req/s aguanta este endpoint antes de romperse?&#8221;</em> — no hay nada más rápido de levantar.</p>
      </div>

      <div class="when-grid">
        <div class="when-card yes">
          <div class="when-label">✓ Elegilo cuando</div>
          <ul class="when-list">
            <li>Querés una respuesta en minutos, no en horas</li>
            <li>Necesitás un gate de performance en CI/CD</li>
            <li>Preferís herramientas Unix-style que se componen con pipes y scripts</li>
          </ul>
        </div>
        <div class="when-card no">
          <div class="when-label">✗ No lo uses cuando</div>
          <ul class="when-list">
            <li>Necesitás simular un flujo de usuario realista con múltiples pasos — Vegeta no modela sesiones ni encadena requests</li>
            <li>Necesitás manejo automático de cookies o tokens entre requests</li>
          </ul>
        </div>
      </div>
    </section>

    <hr class="tool-sep">

    <!-- 05 ARTILLERY -->
    <section class="section">
      <span class="section-number">// 05</span>
      <div class="tool-header">
        <div class="tool-logo">🎯</div>
        <div class="tool-title-block">
          <div class="tool-title">Artillery</div>
          <div class="tool-sub">Node.js · config en YAML + JS</div>
          <div class="tool-tags">
            <span class="tool-tag blue">Multiprotocolo</span>
            <span class="tool-tag blue">YAML-first</span>
            <span class="tool-tag">Sin código</span>
          </div>
        </div>
      </div>
      <div class="section-divider"></div>

      <p>Artillery se posiciona como el punto medio entre la simplicidad de Vegeta y la flexibilidad de Locust o k6. Los tests se definen en <strong>YAML</strong> con la posibilidad de extender con JavaScript cuando necesitás lógica custom. Para el 80% de los casos, el YAML es suficiente.</p>

      <div class="callout callout-tip">
        <div class="callout-label">Soporte nativo multiprotocolo</div>
        <p>Artillery puede testear <strong>HTTP, WebSockets, Socket.io, gRPC y GraphQL out of the box</strong>. Si tu aplicación usa WebSockets para comunicación en tiempo real o gRPC para servicios internos, Artillery te permite testear esos flujos sin plugins ni configuración adicional — una ventaja significativa sobre el resto de las herramientas.</p>
      </div>

      <div class="when-grid">
        <div class="when-card yes">
          <div class="when-label">✓ Elegilo cuando</div>
          <ul class="when-list">
            <li>Querés empezar rápido con YAML sin escribir código</li>
            <li>Necesitás testear WebSockets, gRPC o GraphQL</li>
            <li>Tu equipo trabaja en Node.js y quiere extender con JS cuando sea necesario</li>
          </ul>
        </div>
        <div class="when-card no">
          <div class="when-label">✗ No lo uses cuando</div>
          <ul class="when-list">
            <li>Necesitás generar cientos de miles de req/s desde pocos pods — Node.js es menos eficiente que Go para esto</li>
            <li>El volumen masivo es prioridad por sobre la facilidad de configuración</li>
          </ul>
        </div>
      </div>
    </section>

    <!-- TABLA COMPARATIVA -->
    <div class="table-wrapper">
      <div class="table-header">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#90cdf4" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="9" x2="9" y2="21"/></svg>
        <span>Comparativa rápida — Las 5 herramientas</span>
      </div>
      <table>
        <thead>
          <tr>
            <th>Herramienta</th>
            <th>Lenguaje</th>
            <th>Protocolos</th>
            <th>Curva</th>
            <th>Mejor para</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>k6</td>
            <td><span class="lang-pill">JavaScript</span></td>
            <td>HTTP, gRPC, WS</td>
            <td><span class="curve-low">Baja</span></td>
            <td>Equipos dev-first, stack Grafana</td>
          </tr>
          <tr>
            <td>Locust</td>
            <td><span class="lang-pill">Python</span></td>
            <td>HTTP (extensible)</td>
            <td><span class="curve-low">Baja–Media</span></td>
            <td>Escenarios complejos, UI en vivo</td>
          </tr>
          <tr>
            <td>Gatling</td>
            <td><span class="lang-pill">Scala/Java</span></td>
            <td>HTTP, WS, JMS</td>
            <td><span class="curve-high">Media–Alta</span></td>
            <td>Reportes profesionales, flujos JVM</td>
          </tr>
          <tr>
            <td>Vegeta</td>
            <td><span class="lang-pill">CLI (Go)</span></td>
            <td>HTTP</td>
            <td><span class="curve-low">Muy baja</span></td>
            <td>Tests rápidos, CI/CD gates</td>
          </tr>
          <tr>
            <td>Artillery</td>
            <td><span class="lang-pill">YAML + JS</span></td>
            <td>HTTP, WS, gRPC, GraphQL</td>
            <td><span class="curve-low">Baja</span></td>
            <td>Multiprotocolo, inicio sin código</td>
          </tr>
        </tbody>
      </table>
    </div>

    <!-- CLOSING -->
    <section class="section">
      <div class="callout callout-warning">
        <div class="callout-label">El test que no hacés es el más caro</div>
        <p>La decisión más importante no es <em>cuál</em> herramienta usar — es <strong>decidir hacer load testing en primer lugar</strong>. Un test de 30 minutos antes de un lanzamiento puede ahorrarte horas de downtime, miles de dólares en facturación perdida y la confianza de tus usuarios.</p>
        <p>No tiene que ser perfecto ni exhaustivo. Un test básico que responda <em>&#8220;¿cuántos usuarios concurrentes aguanta mi servicio antes de degradarse?&#8221;</em> ya es infinitamente mejor que no tener nada.</p>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">Ventaja de correr en Kubernetes</div>
        <p>El test de carga genera tráfico <strong>desde la misma red donde vive tu aplicación</strong>, lo cual te da resultados más realistas que cualquier test externo. Tenés acceso a servicios internos, el mismo DNS, los mismos timeouts de red — el ambiente es idéntico a producción.</p>
      </div>
    </section>

    <!-- CTA -->
    <div class="cta-block">
      <h2>Correr workloads en Kubernetes debería ser simple</h2>
      <p>Ya sea un test de carga, un CronJob o un worker — desplegar workloads en Kubernetes no debería requerir ser experto en manifiestos YAML. En SleakOps, la infraestructura de red, observabilidad y autoescalado ya están resueltos para que te enfoques en lo que importa.</p>
      <a href="#" class="cta-btn">Conocé SleakOps →</a>
    </div>

    <!-- SHARE -->
    <div class="share-row">
      <span class="share-label">Tags:</span>
      <a href="#" class="share-tag">#LoadTesting</a>
      <a href="#" class="share-tag">#Kubernetes</a>
      <a href="#" class="share-tag">#Performance</a>
      <a href="#" class="share-tag">#k6</a>
      <a href="#" class="share-tag">#Locust</a>
      <a href="#" class="share-tag">#DevOps</a>
      <a href="#" class="share-tag">#CloudNative</a>
    </div>

  </div>
</main>
</div><p>The post <a href="https://sleakops.com/2026/03/20/5-herramientas-para-hacer-test-de-carga-en-kubernetes-y-como-elegir-la-correcta-para-tu-caso/">5 Mejores Herramientas de Test de Carga en Kubernetes</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p><p>La entrada <a href="https://sleakops.com/2026/03/20/5-herramientas-para-hacer-test-de-carga-en-kubernetes-y-como-elegir-la-correcta-para-tu-caso/">5 Mejores Herramientas de Test de Carga en Kubernetes</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Cero Downtime en Kubernetes: Guía para Deploys Sin Errores</title>
		<link>https://sleakops.com/2026/03/20/como-conseguir-cero-downtime-en-kubernetes-en-cada-uno-de-tus-deploys/</link>
		
		<dc:creator><![CDATA[matias]]></dc:creator>
		<pubDate>Fri, 20 Mar 2026 11:51:38 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://sleakops.com/?p=1925</guid>

					<description><![CDATA[<p>Kubernetes DevOps Zero Downtime Deploy 8 minutos de lectura Hay pocos momentos más tensos en la vida de un equipo de ingeniería que el deploy a producción. Alguien ejecuta el comando, todos miran el canal de Slack, y durante unos segundos nadie respira. ¿Funcionó? ¿Se cayó algo? ¿Los usuarios lo notaron? Si tu equipo siente [&#8230;]</p>
<p>The post <a href="https://sleakops.com/2026/03/20/como-conseguir-cero-downtime-en-kubernetes-en-cada-uno-de-tus-deploys/">Cero Downtime en Kubernetes: Guía para Deploys Sin Errores</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p>
<p>La entrada <a href="https://sleakops.com/2026/03/20/como-conseguir-cero-downtime-en-kubernetes-en-cada-uno-de-tus-deploys/">Cero Downtime en Kubernetes: Guía para Deploys Sin Errores</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div data-elementor-type="wp-post" data-elementor-id="1925" class="elementor elementor-1925" data-elementor-post-type="post">
				<div class="elementor-element elementor-element-3add6653 e-flex e-con-boxed e-con e-parent" data-id="3add6653" data-element_type="container" data-e-type="container" id="test">
					<div class="e-con-inner">
				<div class="elementor-element elementor-element-5a82c7a7 elementor-widget elementor-widget-text-editor" data-id="5a82c7a7" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
									
<div class="sleakops-post">
<style>
:root {
    --navy:      #1a202c;
    --navy-light:#232c3e;
    --navy-mid:  #2d3748;
    --blue:      #90cdf4;
    --blue-dim:  rgba(144,205,244,0.10);
    --blue-mid:  rgba(144,205,244,0.18);
    --coral:     #e37562;
    --coral-dim: rgba(227,117,98,0.10);
    --white:     #ffffff;
    --gray:      #a0aec0;
    --gray-dim:  rgba(160,174,192,0.10);
    --border:    rgba(144,205,244,0.13);
  }

  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

  .sleakops-post {
    font-family: 'Inter', sans-serif;
        color: var(--white);
    line-height: 1.75;
    font-size: 16px;
    overflow-x: hidden;
  }

  /* ── HERO ── */
  .hero {
    background: var(--navy);
    padding: 44px 0 40px;
    border-bottom: 1px solid var(--border);
    position: relative;
    overflow: hidden;
  }
  .hero::before {
    content: '';
    position: absolute; inset: 0;
    background-image:
      repeating-linear-gradient( 60deg, transparent, transparent 38px, rgba(144,205,244,.032) 38px, rgba(144,205,244,.032) 40px),
      repeating-linear-gradient(-60deg, transparent, transparent 38px, rgba(144,205,244,.032) 38px, rgba(144,205,244,.032) 40px);
    pointer-events: none;
  }
  .hero::after {
    content: '';
    position: absolute; top: -110px; right: -110px;
    width: 400px; height: 400px;
    background: radial-gradient(circle, rgba(144,205,244,.09) 0%, transparent 70%);
    pointer-events: none;
  }

  .container {
    max-width: 820px;
    margin: 0 auto;
    padding: 0 32px;
    position: relative;
    z-index: 1;
  }

  /* meta bar */
  .meta-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
  }
  .badge {
    font-size: 10px; font-weight: 900;
    letter-spacing: .14em; text-transform: uppercase;
    padding: 4px 11px; border-radius: 4px;
    background: var(--blue-dim); color: var(--blue);
    border: 1px solid rgba(144,205,244,.26);
  }
  .badge.coral { background: var(--coral-dim); color: var(--coral); border-color: rgba(227,117,98,.26); }
  .badge.gray  { background: var(--gray-dim);  color: var(--gray);  border-color: rgba(160,174,192,.2); }

  .reading-pill {
    margin-left: auto;
    display: inline-flex; align-items: center; gap: 7px;
    background: var(--navy-mid); border: 1px solid var(--border);
    border-radius: 32px; padding: 6px 15px;
    font-size: 12px; color: var(--gray);
  }
  .reading-pill svg { color: var(--blue); flex-shrink: 0; }

  /* ── CONTENT ── */
  .content { padding: 52px 0 80px; }

  /* intro */
  .intro-block {
    background: linear-gradient(135deg, var(--navy-light), var(--navy-mid));
    border: 1px solid var(--border); border-radius: 12px;
    padding: 30px 34px; margin-bottom: 54px;
    position: relative; overflow: hidden;
  }
  .intro-block::before {
    content: ''; position: absolute; top: 0; left: 0;
    width: 4px; height: 100%;
    background: linear-gradient(to bottom, var(--blue), var(--coral));
  }
  .intro-block p { color: var(--gray); font-size: 15.5px; line-height: 1.85; margin-bottom: 13px; }
  .intro-block p:last-child { margin-bottom: 0; }
  .intro-block strong { color: var(--white); font-weight: 600; }

  /* ── HOW IT WORKS (non-numbered explainer) ── */
  .explainer { margin-bottom: 52px; }
  .explainer h2 {
    font-weight: 900; font-size: clamp(18px,2.8vw,24px);
    color: var(--white); margin-bottom: 6px;
    display: flex; align-items: flex-start; gap: 12px;
  }
  .hex-icon {
    flex-shrink: 0; width: 34px; height: 34px;
    background: var(--blue-dim); border: 1px solid rgba(144,205,244,.2);
    border-radius: 8px; display: flex; align-items: center;
    justify-content: center; font-size: 15px; margin-top: 2px;
  }
  .section-divider { width: 44px; height: 3px; background: linear-gradient(to right, var(--blue), transparent); margin: 10px 0 20px; border-radius: 2px; }

  /* rolling update flow */
  .flow {
    background: var(--navy-light); border: 1px solid var(--border);
    border-radius: 10px; padding: 6px 0; margin: 20px 0;
  }
  .flow-step {
    display: flex; align-items: flex-start; gap: 14px;
    padding: 13px 24px; border-bottom: 1px solid rgba(255,255,255,.04);
  }
  .flow-step:last-child { border-bottom: none; }
  .flow-num {
    font-family: 'JetBrains Mono', monospace; font-size: 11px; font-weight: 600;
    color: var(--blue); background: var(--blue-dim); border: 1px solid rgba(144,205,244,.2);
    border-radius: 4px; padding: 3px 8px; flex-shrink: 0; margin-top: 1px; letter-spacing: .04em;
  }
  .flow-text { color: var(--gray); font-size: 14.5px; line-height: 1.7; }
  .flow-text strong { color: var(--white); }

  /* params cards */
  .params-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 18px 0; }
  @media(max-width:540px){ .params-grid { grid-template-columns: 1fr; } }
  .param-card {
    background: var(--navy-light); border: 1px solid var(--border); border-radius: 10px; padding: 18px 20px;
    transition: border-color .2s;
  }
  .param-card:hover { border-color: rgba(144,205,244,.35); }
  .param-name { font-family: 'JetBrains Mono', monospace; font-size: 13px; font-weight: 600; color: var(--blue); margin-bottom: 6px; }
  .param-val  { font-size: 11px; font-weight: 700; color: var(--coral); font-family: 'JetBrains Mono', monospace; margin-bottom: 8px; letter-spacing: .04em; }
  .param-desc { font-size: 13.5px; color: var(--gray); line-height: 1.65; }
  .param-desc strong { color: var(--white); }

  /* ── SECTIONS ── */
  .section { margin-bottom: 58px; }
  .section-number {
    display: inline-block; font-family: 'JetBrains Mono', monospace;
    font-size: 11px; font-weight: 600; color: var(--blue);
    letter-spacing: .1em; margin-bottom: 7px; opacity: .8;
  }
  .section h2 {
    font-weight: 900; font-size: clamp(18px,2.8vw,24px);
    color: var(--white); line-height: 1.25; margin-bottom: 6px;
    display: flex; align-items: flex-start; gap: 12px;
  }
  .section p { color: var(--gray); margin-bottom: 15px; line-height: 1.85; font-size: 15.5px; }
  .section p strong { color: var(--white); font-weight: 600; }
  .section p:last-child { margin-bottom: 0; }

  /* callouts */
  .callout {
    border-radius: 10px; padding: 20px 24px; margin: 20px 0;
    border: 1px solid; position: relative; overflow: hidden;
  }
  .callout-warning { background: rgba(227,117,98,.07); border-color: rgba(227,117,98,.27); }
  .callout-warning::after { content:'⚠'; position:absolute; top:13px; right:17px; font-size:19px; opacity:.22; }
  .callout-tip    { background: var(--blue-dim); border-color: rgba(144,205,244,.22); }
  .callout-tip::after    { content:'💡'; position:absolute; top:13px; right:17px; font-size:17px; opacity:.4; }
  .callout-label { font-size:10px; font-weight:900; letter-spacing:.14em; text-transform:uppercase; margin-bottom:9px; }
  .callout-warning .callout-label { color: var(--coral); }
  .callout-tip    .callout-label { color: var(--blue);  }
  .callout p { font-size:14.5px !important; margin-bottom:8px !important; line-height:1.75 !important; }
  .callout p:last-child { margin-bottom:0 !important; }
  .callout strong { color: var(--white); font-weight:600; }

  /* lists */
  .styled-list { list-style:none; margin:14px 0 18px; display:flex; flex-direction:column; gap:10px; }
  .styled-list li { display:flex; align-items:flex-start; gap:12px; color:var(--gray); font-size:15px; line-height:1.72; }
  .styled-list li::before { content:'→'; color:var(--blue); font-weight:700; flex-shrink:0; margin-top:1px; font-family:'JetBrains Mono',monospace; }
  .styled-list li strong { color:var(--white); }

  code {
    font-family:'JetBrains Mono',monospace; font-size:13px;
    background:var(--navy-mid); color:var(--blue);
    padding:2px 6px; border-radius:4px; border:1px solid var(--border);
  }

  /* ── STRATEGIES ── */
  .strat-grid { display:grid; grid-template-columns:1fr 1fr 1fr; gap:12px; margin:22px 0; }
  @media(max-width:620px){ .strat-grid { grid-template-columns:1fr; } }
  .strat-card {
    background:var(--navy-light); border:1px solid var(--border); border-radius:10px; padding:18px 20px;
    transition:border-color .2s;
  }
  .strat-card:hover { border-color:rgba(144,205,244,.35); }
  .strat-card.featured { border-color:rgba(144,205,244,.32); background:linear-gradient(135deg,var(--navy-light),var(--navy-mid)); }
  .strat-name { font-family:'JetBrains Mono',monospace; font-size:12px; font-weight:600; color:var(--blue); margin-bottom:6px; letter-spacing:.04em; }
  .strat-badge { display:inline-block; font-size:9px; font-weight:900; letter-spacing:.1em; text-transform:uppercase; padding:2px 7px; border-radius:3px; margin-bottom:8px; }
  .strat-badge.ok   { background:var(--blue-dim); color:var(--blue); border:1px solid rgba(144,205,244,.2); }
  .strat-badge.warn { background:var(--coral-dim); color:var(--coral); border:1px solid rgba(227,117,98,.2); }
  .strat-desc { font-size:13px; color:var(--gray); line-height:1.65; }
  .strat-desc strong { color:var(--white); }

  /* ── CHECKLIST TABLE ── */
  .table-wrapper {
    background:var(--navy); border:1px solid var(--border); border-radius:12px; overflow:hidden; margin:44px 0;
  }
  .table-header {
    background:linear-gradient(135deg, var(--navy-mid), var(--navy-light));
    padding:16px 24px; border-bottom:1px solid var(--border);
    display:flex; align-items:center; gap:10px;
  }
  .table-header span { font-size:11px; font-weight:900; letter-spacing:.12em; text-transform:uppercase; color:var(--blue); }
  .table-section-row td {
    background:var(--navy-mid); color:var(--gray); font-size:10px; font-weight:700;
    letter-spacing:.1em; text-transform:uppercase; padding:9px 22px;
    border-bottom:1px solid var(--border);
  }
  table { width:100%; border-collapse:collapse; }
  th { text-align:left; padding:11px 22px; font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--gray); background:var(--navy-mid); border-bottom:1px solid var(--border); }
  td { padding:12px 22px; font-size:14px; border-bottom:1px solid rgba(255,255,255,.04); color:var(--gray); vertical-align:top; }
  td:first-child { color:var(--white); font-family:'JetBrains Mono',monospace; font-size:12.5px; white-space:nowrap; font-weight:600; }
  tr:last-child td { border-bottom:none; }
  tr:hover td { background:rgba(255,255,255,.02); }
  .pill { display:inline-block; font-size:9px; font-weight:900; padding:2px 8px; border-radius:3px; letter-spacing:.08em; text-transform:uppercase; font-family:'Inter',sans-serif; }
  .pill-k8s  { background:var(--blue-dim);  color:var(--blue);  border:1px solid rgba(144,205,244,.2); }
  .pill-app  { background:rgba(144,205,244,.06); color:#68b5e8; border:1px solid rgba(144,205,244,.14); }
  .pill-ci   { background:var(--gray-dim);  color:var(--gray);  border:1px solid rgba(160,174,192,.2); }
  .pill-crit { background:var(--coral-dim); color:var(--coral); border:1px solid rgba(227,117,98,.2); }

  /* ── CTA ── */
  .cta-block {
    background:linear-gradient(135deg, var(--navy-mid), rgba(26,32,44,.95));
    border:1px solid rgba(144,205,244,.26); border-radius:16px;
    padding:46px 38px; text-align:center; position:relative; overflow:hidden; margin-top:62px;
  }
  .cta-block::before {
    content:''; position:absolute; bottom:-80px; left:50%; transform:translateX(-50%);
    width:340px; height:340px;
    background:radial-gradient(circle, rgba(144,205,244,.08) 0%, transparent 70%);
    pointer-events:none;
  }
  .cta-block h2 { font-weight:900; font-size:clamp(19px,3.2vw,29px); color:var(--white); margin-bottom:11px; position:relative; }
  .cta-block p  { color:var(--gray); font-size:15px; max-width:500px; margin:0 auto 28px; line-height:1.8; position:relative; }
  .cta-btn {
    display:inline-block; background:var(--blue); color:var(--navy) !important;
    font-weight:900; font-size:13px; letter-spacing:.07em; text-transform:uppercase;
    text-decoration:none; padding:13px 34px; border-radius:8px;
    transition:transform .15s, box-shadow .15s; position:relative;
  }
  .cta-btn:hover { transform:translateY(-2px); box-shadow:0 8px 28px rgba(144,205,244,.26); }

  /* share row */
  .share-row { margin-top:42px; padding-top:24px; border-top:1px solid var(--border); display:flex; align-items:center; gap:9px; flex-wrap:wrap; }
  .share-label { font-size:11px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--gray); }
  .share-tag { background:var(--navy-mid); border:1px solid var(--border); color:var(--gray); font-size:12px; padding:4px 12px; border-radius:20px; text-decoration:none; transition:border-color .15s, color .15s; }
  .share-tag:hover { border-color:var(--blue); color:var(--blue); }

  @media(max-width:640px){
    .container { padding:0 18px; }
    .hero { padding:32px 0 28px; }
    .intro-block { padding:22px 20px; }
    td,th { padding:10px 14px; }
    .cta-block { padding:30px 20px; }
    .reading-pill { margin-left:0; }
  }
</style>
<!-- HERO -->
<header class="hero">
  <div class="container">
    <div class="meta-bar">
      <span class="badge">Kubernetes</span>
      <span class="badge">DevOps</span>
      <span class="badge coral">Zero Downtime</span>
      <span class="badge gray">Deploy</span>
      <div class="reading-pill">
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
        8 minutos de lectura
      </div>
    </div>
  </div>
</header>

<!-- CONTENT -->
<main class="content">
  <div class="container">

    <!-- INTRO -->
    <div class="intro-block">
      <p>Hay pocos momentos más tensos en la vida de un equipo de ingeniería que el deploy a producción. Alguien ejecuta el comando, todos miran el canal de Slack, y durante unos segundos nadie respira. <strong>¿Funcionó? ¿Se cayó algo? ¿Los usuarios lo notaron?</strong></p>
      <p>Si tu equipo siente esa tensión cada vez que despliega, hay un problema. Los deploys no deberían dar miedo. En un setup de Kubernetes bien configurado, podés desplegar una nueva versión de tu aplicación <strong>sin que ningún usuario note que algo cambió</strong>. Cero errores 502. Cero conexiones cortadas. Cero downtime.</p>
      <p>Kubernetes no te da zero downtime por defecto. Te da las herramientas para lograrlo. <strong>Configurarlas correctamente es tu responsabilidad.</strong></p>
    </div>

    <!-- HOW IT WORKS -->
    <div class="explainer">
      <h2><span class="hex-icon">🔍</span>Cómo funciona un deploy en Kubernetes por dentro</h2>
      <div class="section-divider"></div>

      <p style="color:var(--gray);font-size:15.5px;line-height:1.85;margin-bottom:18px;">Cuando ejecutás un <code>helm upgrade</code> o un <code>kubectl apply</code>, el Rolling Update sigue una coreografía precisa:</p>

      <div class="flow">
        <div class="flow-step"><span class="flow-num">01</span><div class="flow-text">Kubernetes <strong>crea un pod nuevo</strong> con la versión actualizada de tu aplicación.</div></div>
        <div class="flow-step"><span class="flow-num">02</span><div class="flow-text">Espera a que el pod pase los <strong>health checks</strong> y esté en estado &#8220;Ready&#8221;.</div></div>
        <div class="flow-step"><span class="flow-num">03</span><div class="flow-text">Una vez listo, <strong>empieza a enviarle tráfico</strong> al pod nuevo.</div></div>
        <div class="flow-step"><span class="flow-num">04</span><div class="flow-text">Simultáneamente, <strong>marca un pod viejo para terminar</strong> y deja de enviarle tráfico.</div></div>
        <div class="flow-step"><span class="flow-num">05</span><div class="flow-text">El pod viejo recibe <code>SIGTERM</code> y tiene un <strong>período de gracia</strong> para terminar lo que estaba haciendo.</div></div>
        <div class="flow-step"><span class="flow-num">06</span><div class="flow-text"><strong>Repite el ciclo</strong> pod por pod hasta que el 100% corre la versión nueva.</div></div>
      </div>

      <p style="color:var(--gray);font-size:15px;line-height:1.85;margin-top:18px;margin-bottom:18px;">Dos parámetros controlan cómo se hace esa transición:</p>

      <div class="params-grid">
        <div class="param-card">
          <div class="param-name">maxUnavailable</div>
          <div class="param-val">recomendado: 0</div>
          <div class="param-desc">Cuántos pods pueden estar fuera de servicio simultáneamente. Con <strong>0</strong>, nunca se baja un pod viejo hasta que uno nuevo esté listo — la opción más conservadora.</div>
        </div>
        <div class="param-card">
          <div class="param-name">maxSurge</div>
          <div class="param-val">recomendado: 1+</div>
          <div class="param-desc">Cuántos pods extra puede crear Kubernetes por encima del número deseado. Permite crear el pod nuevo antes de bajar el viejo, <strong>sin perder capacidad nunca</strong>.</div>
        </div>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">La combinación ideal</div>
        <p><code>maxUnavailable: 0</code> + <code>maxSurge: 1</code> — nunca bajés un pod viejo hasta que el nuevo esté listo, y podés crear pods extras temporalmente para lograrlo. Más lento y usa más recursos durante el deploy, pero <strong>garantiza que la capacidad nunca baje del 100%</strong>.</p>
      </div>
    </div>

    <!-- 01 READINESS -->
    <section class="section">
      <span class="section-number">// 01</span>
      <h2><span class="hex-icon">❤️</span>No tener readiness probes (o tenerlos mal)</h2>
      <div class="section-divider"></div>

      <p>Este es el error más común y el más impactante. El readiness probe le dice a Kubernetes: <strong>&#8220;este pod está listo para recibir tráfico&#8221;</strong>. Sin él, Kubernetes asume que el pod está listo en el momento que el container arranca — no cuando tu aplicación realmente está lista.</p>

      <div class="callout callout-warning">
        <div class="callout-label">El problema</div>
        <p>La mayoría de las aplicaciones necesitan unos segundos (a veces más) para levantar — cargar configuración, conectarse a la base de datos, calentar caches. Si Kubernetes empieza a enviar tráfico antes de que todo esto termine, los primeros usuarios que caigan en el pod nuevo van a recibir <strong>errores 503, conexiones rechazadas o timeouts</strong>.</p>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">La solución</div>
        <p>Configurá un readiness probe apuntando a un endpoint que valide que la aplicación <strong>genuinamente está lista</strong> — incluyendo conexiones a dependencias críticas como la base de datos. No solo que el proceso está corriendo.</p>
        <p>Si tu app tarda significativamente en arrancar (Spring Boot, modelos de ML, caches grandes), sumá un <strong>startupProbe</strong> para que Kubernetes no la marque como muerta mientras levanta.</p>
      </div>
    </section>

    <!-- 02 SIGTERM -->
    <section class="section">
      <span class="section-number">// 02</span>
      <h2><span class="hex-icon">🛑</span>No manejar el SIGTERM correctamente</h2>
      <div class="section-divider"></div>

      <p>Cuando Kubernetes decide terminar un pod viejo, le envía <code>SIGTERM</code>. Este es un aviso: <strong>&#8220;terminá lo que estás haciendo, tenés X segundos antes del SIGKILL&#8221;</strong>. El problema es que muchas aplicaciones no lo manejan y simplemente mueren, cortando cualquier request en vuelo.</p>

      <div class="callout callout-warning">
        <div class="callout-label">El impacto</div>
        <p>El usuario que tenía un request en proceso recibe un error. Si era un request de pago, una operación de escritura o una transacción, las consecuencias pueden ser graves. <strong>Cada deploy con SIGTERM sin manejar es una ventana de potencial corrupción de datos.</strong></p>
      </div>

      <p>Para zero downtime real, tu aplicación necesita implementar <strong>graceful shutdown</strong>:</p>

      <ul class="styled-list">
        <li>Escuchar la señal <code>SIGTERM</code> y dejar de aceptar conexiones nuevas.</li>
        <li>Terminar los requests que ya están en vuelo.</li>
        <li>Cerrar conexiones a bases de datos y servicios externos de forma limpia.</li>
        <li>Recién entonces, terminar el proceso.</li>
      </ul>

      <div class="callout callout-tip">
        <div class="callout-label">Configuración de Kubernetes</div>
        <p>Ajustá <code>terminationGracePeriodSeconds</code> para darle a tu app tiempo suficiente. El default de <strong>30 segundos puede no ser suficiente</strong> para algunas aplicaciones con transacciones largas o cierres de conexiones complejos.</p>
      </div>
    </section>

    <!-- 03 PRESTOP -->
    <section class="section">
      <span class="section-number">// 03</span>
      <h2><span class="hex-icon">⏱️</span>La ventana de muerte entre SIGTERM y desregistración</h2>
      <div class="section-divider"></div>

      <p>Este es el <em>gotcha</em> más sutil y el que menos equipos conocen. Cuando Kubernetes decide terminar un pod, <strong>dos cosas pasan al mismo tiempo pero de forma asincrónica</strong>:</p>

      <div class="params-grid">
        <div class="param-card">
          <div class="param-name">acción A</div>
          <div class="param-desc">El pod recibe <code>SIGTERM</code> para que empiece a apagarse.</div>
        </div>
        <div class="param-card">
          <div class="param-name">acción B</div>
          <div class="param-desc">Kubernetes le dice al Service que deje de enviarle tráfico a ese pod.</div>
        </div>
      </div>

      <div class="callout callout-warning">
        <div class="callout-label">El problema</div>
        <p>Estas dos acciones <strong>no están sincronizadas</strong>. Tu app puede recibir SIGTERM y empezar a cerrarse, pero el Service todavía no sabe que ese pod está saliendo. Durante esos milisegundos — a veces segundos — el Service sigue enviando requests nuevos a un pod que ya está muriendo.</p>
        <p>Los errores son <strong>intermitentes y casi imposibles de reproducir</strong> en desarrollo porque dependen del timing exacto de la propagación de red.</p>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">La solución: preStop hook con sleep</div>
        <p>Agregá un <code>preStop</code> hook con un <code>sleep</code> de <strong>3 a 10 segundos</strong>. Este delay le da tiempo al sistema de networking de Kubernetes para propagar el cambio antes de que tu aplicación empiece a cerrarse. Es contraintuitivo — estás retrasando el shutdown a propósito — pero es necesario para evitar requests perdidos.</p>
      </div>
    </section>

    <!-- 04 RÉPLICAS -->
    <section class="section">
      <span class="section-number">// 04</span>
      <h2><span class="hex-icon">🔢</span>No tener suficientes réplicas</h2>
      <div class="section-divider"></div>

      <p>Si tu servicio corre con 1 o 2 réplicas y hacés un Rolling Update, <strong>la matemática no da</strong>. Con una sola réplica, Kubernetes necesita matar el pod viejo para crear el nuevo. Durante ese intercambio, hay un momento donde potencialmente ningún pod está listo.</p>

      <div class="callout callout-warning">
        <div class="callout-label">El riesgo con pocas réplicas</div>
        <p>Con 2 réplicas y <code>maxUnavailable: 1</code>, durante el deploy tenés un solo pod sirviendo toda la carga mientras el otro se reemplaza. Si ese pod no puede manejar toda la carga solo, <strong>tus usuarios van a experimentar degradación o timeouts</strong>.</p>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">El mínimo para zero downtime</div>
        <p>Al menos <strong>3 réplicas</strong> en cualquier servicio que reciba tráfico de usuarios. Esto le da a Kubernetes suficiente margen para rotar pods sin perder capacidad total. Complementá con un <strong>Pod Disruption Budget (PDB)</strong> para evitar que Kubernetes mate demasiados pods al mismo tiempo durante operaciones de mantenimiento.</p>
      </div>
    </section>

    <!-- 05 CI/CD -->
    <section class="section">
      <span class="section-number">// 05</span>
      <h2><span class="hex-icon">🚀</span>El pipeline de CI/CD que no espera el rollout</h2>
      <div class="section-divider"></div>

      <p>Un error frecuente en pipelines: asumir que el deploy terminó cuando <code>helm upgrade</code> retorna éxito. <strong>Helm retorna éxito cuando el manifiesto se aplica, no cuando los pods están listos.</strong></p>

      <div class="callout callout-warning">
        <div class="callout-label">El problema</div>
        <p>Si tu pipeline reporta &#8220;deploy exitoso&#8221; en el momento que Helm termina, puede estar mintiendo. Los pods nuevos todavía están levantando, los readiness probes todavía están fallando, y la versión vieja todavía sirve tráfico. Si algo sale mal, no te vas a enterar hasta que los usuarios empiecen a reportar errores.</p>
      </div>

      <ul class="styled-list">
        <li><strong>Ejecutá <code>kubectl rollout status</code></strong> después del deploy — espera activamente a que el Rolling Update complete y todos los pods nuevos estén en estado Ready.</li>
        <li><strong>Configurá rollback automático</strong> si el rollout no completa en un tiempo razonable o si los readiness probes fallan repetidamente.</li>
        <li><strong>Definí un timeout explícito</strong> para el rollout — si no completa en X minutos, algo salió mal y hay que actuar.</li>
      </ul>
    </section>

    <!-- ESTRATEGIAS -->
    <section class="section">
      <span class="section-number">// 06</span>
      <h2><span class="hex-icon">🗺️</span>Rolling Update no es la única estrategia</h2>
      <div class="section-divider"></div>

      <p>El Rolling Update es la estrategia por defecto, pero no la única. Dependiendo del caso podés considerar:</p>

      <div class="strat-grid">
        <div class="strat-card featured">
          <div class="strat-name">Rolling Update</div>
          <div><span class="strat-badge ok">Recomendado</span></div>
          <div class="strat-desc">Reemplaza pods de a uno. Bien configurado, logra zero downtime con los recursos justos. <strong>Suficiente para la mayoría de los equipos.</strong></div>
        </div>
        <div class="strat-card">
          <div class="strat-name">Blue / Green</div>
          <div><span class="strat-badge warn">Más recursos</span></div>
          <div class="strat-desc">Levantás el set completo de pods nuevos antes de cortar el tráfico. Rollback instantáneo. <strong>Requiere el doble de recursos durante el deploy.</strong></div>
        </div>
        <div class="strat-card">
          <div class="strat-name">Canary</div>
          <div><span class="strat-badge warn">Más complejidad</span></div>
          <div class="strat-desc">Desplegás en un porcentaje pequeño de pods y monitoreás antes de escalar. <strong>Requiere Argo Rollouts o Flagger para gestión automática.</strong></div>
        </div>
      </div>
    </section>

    <!-- CHECKLIST TABLE -->
    <div class="table-wrapper">
      <div class="table-header">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#90cdf4" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
        <span>Checklist completo — Zero Downtime Deploy</span>
      </div>
      <table>
        <thead>
          <tr>
            <th>Configuración</th>
            <th>Qué hace</th>
            <th style="text-align:right">Capa</th>
          </tr>
        </thead>
        <tbody>
          <tr class="table-section-row"><td colspan="3">En tu Deployment de Kubernetes</td></tr>
          <tr>
            <td>maxUnavailable: 0</td>
            <td>Nunca baja pods viejos antes de tener uno nuevo listo</td>
            <td style="text-align:right"><span class="pill pill-k8s">K8s</span></td>
          </tr>
          <tr>
            <td>maxSurge: 1+</td>
            <td>Permite pods extras temporales durante el deploy</td>
            <td style="text-align:right"><span class="pill pill-k8s">K8s</span></td>
          </tr>
          <tr>
            <td>réplicas ≥ 3</td>
            <td>Margen suficiente para rotar pods sin perder capacidad</td>
            <td style="text-align:right"><span class="pill pill-crit">Crítico</span></td>
          </tr>
          <tr>
            <td>readinessProbe</td>
            <td>Tráfico solo cuando la app está genuinamente lista</td>
            <td style="text-align:right"><span class="pill pill-crit">Crítico</span></td>
          </tr>
          <tr>
            <td>livenessProbe</td>
            <td>Detecta pods vivos pero atascados (deadlock, leak)</td>
            <td style="text-align:right"><span class="pill pill-k8s">K8s</span></td>
          </tr>
          <tr>
            <td>startupProbe</td>
            <td>Tiempo extra para apps con startup lento</td>
            <td style="text-align:right"><span class="pill pill-k8s">K8s</span></td>
          </tr>
          <tr>
            <td>preStop sleep 3–10s</td>
            <td>Tiempo para propagar la desregistración del Service</td>
            <td style="text-align:right"><span class="pill pill-crit">Crítico</span></td>
          </tr>
          <tr>
            <td>terminationGracePeriod</td>
            <td>Tiempo suficiente para el graceful shutdown completo</td>
            <td style="text-align:right"><span class="pill pill-k8s">K8s</span></td>
          </tr>
          <tr>
            <td>PodDisruptionBudget</td>
            <td>Protege contra bajas masivas en mantenimiento de nodos</td>
            <td style="text-align:right"><span class="pill pill-k8s">K8s</span></td>
          </tr>
          <tr class="table-section-row"><td colspan="3">En tu aplicación</td></tr>
          <tr>
            <td>Manejo de SIGTERM</td>
            <td>Para de aceptar conexiones, termina requests en vuelo</td>
            <td style="text-align:right"><span class="pill pill-crit">Crítico</span></td>
          </tr>
          <tr>
            <td>Health endpoint</td>
            <td>Devuelve 200 solo cuando la app puede servir correctamente</td>
            <td style="text-align:right"><span class="pill pill-app">App</span></td>
          </tr>
          <tr class="table-section-row"><td colspan="3">En tu pipeline de CI/CD</td></tr>
          <tr>
            <td>rollout status wait</td>
            <td>Espera a que todos los pods nuevos estén en Ready</td>
            <td style="text-align:right"><span class="pill pill-ci">CI/CD</span></td>
          </tr>
          <tr>
            <td>Rollback automático</td>
            <td>Revierte si el rollout falla o los probes fallan repetidamente</td>
            <td style="text-align:right"><span class="pill pill-ci">CI/CD</span></td>
          </tr>
        </tbody>
      </table>
    </div>

    <!-- CTA -->
    <div class="cta-block">
      <h2>Zero downtime debería ser el default, no un logro</h2>
      <p>Desde SleakOps, los workloads se despliegan con todas estas configuraciones aplicadas por defecto — Rolling Update, probes, graceful shutdown, preStop hook. No necesitás descubrir cada gotcha por tu cuenta.</p>
      <a href="#" class="cta-btn">Conocé SleakOps →</a>
    </div>

    <!-- SHARE -->
    <div class="share-row">
      <span class="share-label">Tags:</span>
      <a href="#" class="share-tag">#Kubernetes</a>
      <a href="#" class="share-tag">#ZeroDowntime</a>
      <a href="#" class="share-tag">#DevOps</a>
      <a href="#" class="share-tag">#RollingUpdate</a>
      <a href="#" class="share-tag">#Deploy</a>
      <a href="#" class="share-tag">#CloudNative</a>
      <a href="#" class="share-tag">#EKS</a>
    </div>

  </div>
</main>
</div>
								</div>
				</div>
					</div>
				</div>
				</div><p>The post <a href="https://sleakops.com/2026/03/20/como-conseguir-cero-downtime-en-kubernetes-en-cada-uno-de-tus-deploys/">Cero Downtime en Kubernetes: Guía para Deploys Sin Errores</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p><p>La entrada <a href="https://sleakops.com/2026/03/20/como-conseguir-cero-downtime-en-kubernetes-en-cada-uno-de-tus-deploys/">Cero Downtime en Kubernetes: Guía para Deploys Sin Errores</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Desarrollo en Kubernetes: 5 Claves para Apps Eficientes</title>
		<link>https://sleakops.com/2026/03/20/que-tener-en-cuenta-a-la-hora-de-desarrollar-aplicaciones-que-viven-dentro-de-kubernetes/</link>
		
		<dc:creator><![CDATA[matias]]></dc:creator>
		<pubDate>Fri, 20 Mar 2026 11:51:35 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://sleakops.com/?p=1927</guid>

					<description><![CDATA[<p>Kubernetes DevOps Best Practices Desarrollo 9 minutos de lectura Hay un momento en la vida de todo equipo de desarrollo donde alguien dice: &#8220;ya tenemos el cluster de Kubernetes andando, ahora hay que meter la app&#8221;. Y ahí empieza un descubrimiento que nadie anticipa: desarrollar una aplicación que corre en Kubernetes no es lo mismo [&#8230;]</p>
<p>The post <a href="https://sleakops.com/2026/03/20/que-tener-en-cuenta-a-la-hora-de-desarrollar-aplicaciones-que-viven-dentro-de-kubernetes/">Desarrollo en Kubernetes: 5 Claves para Apps Eficientes</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p>
<p>La entrada <a href="https://sleakops.com/2026/03/20/que-tener-en-cuenta-a-la-hora-de-desarrollar-aplicaciones-que-viven-dentro-de-kubernetes/">Desarrollo en Kubernetes: 5 Claves para Apps Eficientes</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div data-elementor-type="wp-post" data-elementor-id="1927" class="elementor elementor-1927" data-elementor-post-type="post">
				<div class="elementor-element elementor-element-104cfce3 e-flex e-con-boxed e-con e-parent" data-id="104cfce3" data-element_type="container" data-e-type="container" id="test">
					<div class="e-con-inner">
				<div class="elementor-element elementor-element-44d0586d elementor-widget elementor-widget-text-editor" data-id="44d0586d" data-element_type="widget" data-e-type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
									
<div class="sleakops-post">
<style>
:root {
    --navy: #1a202c;
    --navy-light: #232c3e;
    --navy-mid: #2d3748;
    --blue: #90cdf4;
    --blue-dim: rgba(144, 205, 244, 0.1);
    --blue-glow: rgba(144, 205, 244, 0.22);
    --coral: #e37562;
    --coral-dim: rgba(227, 117, 98, 0.12);
    --white: #ffffff;
    --gray: #a0aec0;
    --border: rgba(144, 205, 244, 0.14);
    --body-bg: #111827;
  }

  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

  .sleakops-post {
    font-family: 'Inter', sans-serif;
    background: var(--body-bg);
    color: var(--white);
    line-height: 1.75;
    font-size: 16px;
    overflow-x: hidden;
  }

  /* ─── HERO ─── */
  .hero {
    position: relative;
    background: var(--navy);
    padding: 48px 0 44px;
    overflow: hidden;
    border-bottom: 1px solid var(--border);
  }
  .hero::before {
    content: '';
    position: absolute;
    inset: 0;
    background-image:
      repeating-linear-gradient(60deg, transparent, transparent 38px, rgba(144,205,244,0.035) 38px, rgba(144,205,244,0.035) 40px),
      repeating-linear-gradient(-60deg, transparent, transparent 38px, rgba(144,205,244,0.035) 38px, rgba(144,205,244,0.035) 40px);
    pointer-events: none;
  }
  .hero::after {
    content: '';
    position: absolute;
    top: -100px; right: -100px;
    width: 420px; height: 420px;
    background: radial-gradient(circle, rgba(144,205,244,0.1) 0%, transparent 70%);
    pointer-events: none;
  }

  .container {
    max-width: 820px;
    margin: 0 auto;
    padding: 0 32px;
    position: relative;
    z-index: 1;
  }

  .meta-bar {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
  }

  .badge {
    font-size: 10px;
    font-weight: 900;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    padding: 5px 12px;
    border-radius: 4px;
    background: var(--blue-dim);
    color: var(--blue);
    border: 1px solid rgba(144,205,244,0.28);
  }
  .badge.coral {
    background: var(--coral-dim);
    color: var(--coral);
    border-color: rgba(227,117,98,0.28);
  }
  .badge.gray {
    background: rgba(160,174,192,0.1);
    color: var(--gray);
    border-color: rgba(160,174,192,0.2);
  }

  .reading-pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: var(--navy-mid);
    border: 1px solid var(--border);
    border-radius: 32px;
    padding: 7px 16px;
    font-size: 12px;
    color: var(--gray);
    margin-left: auto;
  }
  .reading-pill svg { color: var(--blue); flex-shrink: 0; }

  /* ─── MAIN CONTENT ─── */
  .content { padding: 52px 0 80px; }

  /* ─── INTRO ─── */
  .intro-block {
    background: linear-gradient(135deg, var(--navy-light), var(--navy-mid));
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 32px 36px;
    margin-bottom: 56px;
    position: relative;
    overflow: hidden;
  }
  .intro-block::before {
    content: '';
    position: absolute;
    top: 0; left: 0;
    width: 4px; height: 100%;
    background: linear-gradient(to bottom, var(--blue), var(--coral));
  }
  .intro-block p {
    color: var(--gray);
    font-size: 15.5px;
    line-height: 1.85;
    margin-bottom: 14px;
  }
  .intro-block p:last-child { margin-bottom: 0; }
  .intro-block strong { color: var(--white); font-weight: 600; }

  /* ─── SECTIONS ─── */
  .section { margin-bottom: 60px; }

  .section-number {
    display: inline-block;
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    font-weight: 600;
    color: var(--blue);
    letter-spacing: 0.1em;
    margin-bottom: 8px;
    opacity: 0.8;
  }

  .section h2 {
    font-weight: 900;
    font-size: clamp(19px, 3vw, 25px);
    color: var(--white);
    line-height: 1.25;
    margin-bottom: 6px;
    display: flex;
    align-items: flex-start;
    gap: 12px;
  }

  .hex-icon {
    flex-shrink: 0;
    width: 36px; height: 36px;
    background: var(--blue-dim);
    border: 1px solid rgba(144,205,244,0.22);
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    margin-top: 2px;
  }

  .section-divider {
    width: 48px; height: 3px;
    background: linear-gradient(to right, var(--blue), transparent);
    margin: 12px 0 22px;
    border-radius: 2px;
  }

  .section p {
    color: var(--gray);
    margin-bottom: 16px;
    line-height: 1.85;
    font-size: 15.5px;
  }
  .section p strong { color: var(--white); font-weight: 600; }
  .section p:last-child { margin-bottom: 0; }

  /* ─── CALLOUTS ─── */
  .callout {
    border-radius: 10px;
    padding: 22px 26px;
    margin: 22px 0;
    border: 1px solid;
    position: relative;
    overflow: hidden;
  }
  .callout-warning {
    background: rgba(227,117,98,0.07);
    border-color: rgba(227,117,98,0.28);
  }
  .callout-warning::after {
    content: '⚠';
    position: absolute;
    top: 14px; right: 18px;
    font-size: 20px;
    opacity: 0.25;
  }
  .callout-tip {
    background: var(--blue-dim);
    border-color: rgba(144,205,244,0.22);
  }
  .callout-tip::after {
    content: '💡';
    position: absolute;
    top: 14px; right: 18px;
    font-size: 18px;
    opacity: 0.45;
  }
  .callout-label {
    font-size: 10px;
    font-weight: 900;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    margin-bottom: 10px;
  }
  .callout-warning .callout-label { color: var(--coral); }
  .callout-tip .callout-label { color: var(--blue); }
  .callout p {
    font-size: 14.5px !important;
    margin-bottom: 8px !important;
    line-height: 1.75 !important;
  }
  .callout p:last-child { margin-bottom: 0 !important; }
  .callout strong { color: var(--white); font-weight: 600; }

  /* ─── LISTS ─── */
  .styled-list {
    list-style: none;
    margin: 16px 0 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .styled-list li {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    color: var(--gray);
    font-size: 15px;
    line-height: 1.72;
  }
  .styled-list li::before {
    content: '→';
    color: var(--blue);
    font-weight: 700;
    flex-shrink: 0;
    margin-top: 1px;
    font-family: 'JetBrains Mono', monospace;
  }
  .styled-list li strong { color: var(--white); }

  /* ─── DEPLOY STEPS ─── */
  .deploy-steps {
    background: var(--navy-light);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 22px 26px;
    margin: 18px 0;
    display: flex;
    flex-direction: column;
    gap: 0;
  }
  .deploy-step {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    padding: 12px 0;
    border-bottom: 1px solid rgba(255,255,255,0.05);
  }
  .deploy-step:last-child { border-bottom: none; padding-bottom: 0; }
  .deploy-step:first-child { padding-top: 0; }
  .step-num {
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    font-weight: 600;
    color: var(--blue);
    background: var(--blue-dim);
    border: 1px solid rgba(144,205,244,0.2);
    border-radius: 4px;
    padding: 3px 8px;
    flex-shrink: 0;
    margin-top: 1px;
    letter-spacing: 0.05em;
  }
  .step-text {
    color: var(--gray);
    font-size: 14.5px;
    line-height: 1.7;
  }
  .step-text strong { color: var(--white); }

  /* ─── PROBE TABLE ─── */
  .probe-grid {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 12px;
    margin: 20px 0;
  }
  @media (max-width: 600px) { .probe-grid { grid-template-columns: 1fr; } }

  .probe-card {
    background: var(--navy-light);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 18px 20px;
    transition: border-color 0.2s;
  }
  .probe-card:hover { border-color: rgba(144,205,244,0.35); }
  .probe-name {
    font-family: 'JetBrains Mono', monospace;
    font-size: 12px;
    font-weight: 600;
    color: var(--blue);
    margin-bottom: 6px;
    letter-spacing: 0.05em;
  }
  .probe-question {
    font-size: 12px;
    font-weight: 700;
    color: var(--white);
    margin-bottom: 8px;
    line-height: 1.4;
  }
  .probe-desc {
    font-size: 13px;
    color: var(--gray);
    line-height: 1.65;
  }

  /* ─── SUMMARY TABLE ─── */
  .table-wrapper {
    background: var(--navy);
    border: 1px solid var(--border);
    border-radius: 12px;
    overflow: hidden;
    margin: 40px 0;
  }
  .table-header {
    background: linear-gradient(135deg, var(--navy-mid), var(--navy-light));
    padding: 18px 26px;
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .table-header span {
    font-size: 11px;
    font-weight: 900;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--blue);
  }
  table { width: 100%; border-collapse: collapse; }
  th {
    text-align: left;
    padding: 12px 26px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--gray);
    background: var(--navy-mid);
    border-bottom: 1px solid var(--border);
  }
  td {
    padding: 13px 26px;
    font-size: 14px;
    border-bottom: 1px solid rgba(255,255,255,0.04);
    color: var(--gray);
    vertical-align: top;
  }
  td:first-child {
    color: var(--white);
    font-weight: 600;
    font-family: 'JetBrains Mono', monospace;
    font-size: 13px;
    white-space: nowrap;
  }
  td:last-child { color: var(--gray); }
  tr:last-child td { border-bottom: none; }
  tr:hover td { background: rgba(255,255,255,0.02); }
  .tag-ok {
    display: inline-block;
    font-size: 10px;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 3px;
    background: rgba(144,205,244,0.12);
    color: var(--blue);
    border: 1px solid rgba(144,205,244,0.2);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    margin-right: 4px;
    font-family: 'Inter', sans-serif;
  }
  .tag-warn {
    display: inline-block;
    font-size: 10px;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 3px;
    background: var(--coral-dim);
    color: var(--coral);
    border: 1px solid rgba(227,117,98,0.2);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-family: 'Inter', sans-serif;
  }

  /* ─── INLINE CODE ─── */
  code {
    font-family: 'JetBrains Mono', monospace;
    font-size: 13px;
    background: var(--navy-mid);
    color: var(--blue);
    padding: 2px 7px;
    border-radius: 4px;
    border: 1px solid var(--border);
  }

  /* ─── CTA ─── */
  .cta-block {
    background: linear-gradient(135deg, var(--navy-mid), rgba(26,32,44,0.95));
    border: 1px solid rgba(144,205,244,0.28);
    border-radius: 16px;
    padding: 48px 40px;
    text-align: center;
    position: relative;
    overflow: hidden;
    margin-top: 64px;
  }
  .cta-block::before {
    content: '';
    position: absolute;
    bottom: -80px; left: 50%;
    transform: translateX(-50%);
    width: 360px; height: 360px;
    background: radial-gradient(circle, rgba(144,205,244,0.08) 0%, transparent 70%);
    pointer-events: none;
  }
  .cta-block h2 {
    font-weight: 900;
    font-size: clamp(20px, 3.5vw, 30px);
    color: var(--white);
    margin-bottom: 12px;
    position: relative;
  }
  .cta-block p {
    color: var(--gray);
    font-size: 15px;
    margin-bottom: 30px;
    max-width: 520px;
    margin-left: auto;
    margin-right: auto;
    position: relative;
    line-height: 1.8;
  }
  .cta-btn {
    display: inline-block;
    background: var(--blue);
    color: var(--navy) !important;
    font-weight: 900;
    font-size: 13px;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    text-decoration: none;
    padding: 14px 36px;
    border-radius: 8px;
    position: relative;
    transition: transform 0.15s, box-shadow 0.15s;
  }
  .cta-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 32px rgba(144,205,244,0.28);
  }

  /* ─── SHARE ROW ─── */
  .share-row {
    margin-top: 44px;
    padding-top: 26px;
    border-top: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
  }
  .share-label {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--gray);
  }
  .share-tag {
    background: var(--navy-mid);
    border: 1px solid var(--border);
    color: var(--gray);
    font-size: 12px;
    padding: 4px 13px;
    border-radius: 20px;
    text-decoration: none;
    transition: border-color 0.15s, color 0.15s;
  }
  .share-tag:hover { border-color: var(--blue); color: var(--blue); }

  @media (max-width: 640px) {
    .container { padding: 0 20px; }
    .hero { padding: 36px 0 32px; }
    .intro-block { padding: 24px 22px; }
    td, th { padding: 12px 16px; }
    .cta-block { padding: 32px 22px; }
    .reading-pill { margin-left: 0; }
  }
</style>
<!-- HERO -->
<header class="hero">
  <div class="container">
    <div class="meta-bar">
      <span class="badge">Kubernetes</span>
      <span class="badge">DevOps</span>
      <span class="badge coral">Best Practices</span>
      <span class="badge gray">Desarrollo</span>
      <div class="reading-pill">
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
        9 minutos de lectura
      </div>
    </div>
  </div>
</header>

<!-- CONTENT -->
<main class="content">
  <div class="container">

    <!-- INTRO -->
    <div class="intro-block">
      <p>Hay un momento en la vida de todo equipo de desarrollo donde alguien dice: <strong>&#8220;ya tenemos el cluster de Kubernetes andando, ahora hay que meter la app&#8221;</strong>. Y ahí empieza un descubrimiento que nadie anticipa: desarrollar una aplicación que corre en Kubernetes no es lo mismo que desarrollar una que corre en un servidor.</p>
      <p>No se trata de Docker — dockerizar tu app es el paso más fácil. Se trata de que Kubernetes introduce un conjunto de reglas y comportamientos que tu aplicación necesita respetar. <strong>Cosas que aparecen a las 3 AM cuando algo se rompe en producción y nadie entiende por qué.</strong></p>
      <p>Este post es la guía que nos hubiera gustado tener antes de poner nuestra primera aplicación en un cluster.</p>
    </div>

    <!-- 01 MÚLTIPLES RÉPLICAS -->
    <section class="section">
      <span class="section-number">// 01</span>
      <h2><span class="hex-icon">⚡</span>Tu aplicación va a tener múltiples réplicas. Preparate.</h2>
      <div class="section-divider"></div>

      <p>En un servidor tradicional, tu app corre como un único proceso. Un solo estado en memoria. Un solo sistema de archivos. En Kubernetes, tu aplicación va a tener 2, 3, 10 o más réplicas corriendo al mismo tiempo — cada pod independiente, sin compartir memoria ni disco con los demás.</p>

      <div class="callout callout-warning">
        <div class="callout-label">Problemas comunes</div>
        <p><strong>Estado en memoria:</strong> si un usuario hace login en el pod A y el siguiente request cae en el pod B, el pod B no tiene idea de quién es. Las sesiones en memoria, los caches locales y los contadores en variables globales no funcionan con múltiples réplicas.</p>
        <p><strong>Archivos locales:</strong> si tu app guarda algo en <code>/tmp/reports/</code>, ese archivo solo existe en ese pod. Las otras réplicas no lo ven. Y cuando el pod muere, desaparece.</p>
        <p><strong>Tareas de inicio:</strong> si tu app corre migraciones al arrancar y tenés 5 réplicas levantando al mismo tiempo, tenés 5 migraciones en paralelo — con resultados que van de inofensivos a catastróficos.</p>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">Cómo resolverlo</div>
        <p><strong>Todo estado compartido vive fuera de tu app:</strong> sesiones en Redis, cache en Memcached, archivos en S3 o un PersistentVolume compartido. Tu app debe poder responder cualquier request en cualquier réplica sin asumir que el anterior cayó en el mismo pod.</p>
        <p><strong>Las migraciones deben ser un Job separado</strong> que corre antes del deploy, no parte del startup de tu aplicación.</p>
      </div>
    </section>

    <!-- 02 RETROCOMPATIBILIDAD -->
    <section class="section">
      <span class="section-number">// 02</span>
      <h2><span class="hex-icon">🔄</span>Durante un deploy, dos versiones de tu app conviven.</h2>
      <div class="section-divider"></div>

      <p>Con Rolling Update, los pods se reemplazan de a uno. Esto significa que durante segundos — o minutos — van a estar corriendo pods con la versión vieja y la nueva <strong>al mismo tiempo</strong>. Este es probablemente el punto más importante y el menos intuitivo para equipos que vienen del mundo de &#8220;un servidor, un deploy&#8221;.</p>

      <div class="callout callout-warning">
        <div class="callout-label">El problema con los cambios destructivos</div>
        <p>Si la versión nueva renombra una columna de base de datos, los pods viejos (que todavía están sirviendo tráfico) van a explotar porque esperan el esquema anterior. Lo mismo aplica para cambios en contratos de API entre servicios.</p>
      </div>

      <p>Los cambios destructivos de esquema necesitan hacerse en múltiples deploys:</p>

      <div class="deploy-steps">
        <div class="deploy-step">
          <span class="step-num">Deploy 1</span>
          <div class="step-text"><strong>Agregar la columna nueva</strong> — sin eliminar la vieja. Ambas versiones coexisten sin errores.</div>
        </div>
        <div class="deploy-step">
          <span class="step-num">Deploy 2</span>
          <div class="step-text"><strong>Migrar el código</strong> para usar la columna nueva. La vieja sigue presente como fallback.</div>
        </div>
        <div class="deploy-step">
          <span class="step-num">Deploy 3</span>
          <div class="step-text"><strong>Eliminar la columna vieja</strong> — recién cuando ya no hay pods que la usen.</div>
        </div>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">Reglas de retrocompatibilidad</div>
        <p><strong>APIs y mensajes:</strong> los cambios en contratos deberían ser siempre aditivos (agregar campos, agregar endpoints) y nunca destructivos — al menos no sin un período de coexistencia.</p>
        <p><strong>Feature flags:</strong> permiten activar funcionalidad nueva solo cuando el 100% de los pods ya están en la versión nueva. Esto desacopla el deploy del release.</p>
      </div>
    </section>

    <!-- 03 GRACEFUL SHUTDOWN -->
    <section class="section">
      <span class="section-number">// 03</span>
      <h2><span class="hex-icon">💀</span>Tu aplicación va a morir. Muchas veces. Aceptalo.</h2>
      <div class="section-divider"></div>

      <p>En un servidor tradicional, tu proceso corre durante meses. En Kubernetes, tus pods pueden morir y renacer constantemente — y esto es <strong>comportamiento normal, no un error</strong>.</p>

      <div class="callout callout-warning">
        <div class="callout-label">Por qué puede morir tu pod</div>
        <p>Rolling Update, autoscaler bajando réplicas, Karpenter consolidando nodos, una Spot Instance interrumpiéndose, el liveness probe fallando, <code>OOMKilled</code> por exceder el memory limit, o un problema de hardware en el nodo.</p>
      </div>

      <ul class="styled-list">
        <li><strong>Implementá graceful shutdown:</strong> cuando Kubernetes envía <code>SIGTERM</code>, tu app debería dejar de aceptar conexiones nuevas, terminar los requests en vuelo, cerrar conexiones a bases de datos y colas, y recién entonces terminar. Sin esto, los requests en vuelo se pierden.</li>
        <li><strong>Startup rápido y repetible:</strong> levantá el proceso, conectate a las dependencias críticas, reportá que estás listo, empezá a servir. Todo lo que no sea necesario para el primer request puede hacerse de forma asincrónica.</li>
        <li><strong>No schedules tareas internas con <code>setInterval</code></strong> para dentro de 6 horas — ese pod probablemente no va a existir. Usá CronJobs de Kubernetes o una cola externa para tareas diferidas.</li>
      </ul>
    </section>

    <!-- 04 CONFIGURACIÓN -->
    <section class="section">
      <span class="section-number">// 04</span>
      <h2><span class="hex-icon">⚙️</span>La configuración no va en el código. Va en el entorno.</h2>
      <div class="section-divider"></div>

      <p>Tu aplicación no debería saber si está corriendo en desarrollo, staging o producción mirando un <code>if</code> en el código. Debería comportarse diferente porque <strong>recibe configuración diferente del entorno</strong>.</p>

      <div class="callout callout-tip">
        <div class="callout-label">Qué debe ser variable de entorno</div>
        <p>URL de la base de datos, credenciales de servicios externos, feature flags, nivel de logging, URLs de APIs internas, configuración de cache. Kubernetes tiene <strong>ConfigMaps</strong> para configuración no sensible y <strong>Secrets</strong> para credenciales.</p>
      </div>

      <p>El mismo build de Docker debería funcionar en cualquier entorno. Si tenés que buildear una imagen diferente para staging y producción, algo está mal. <strong>El artefacto es el mismo; lo que cambia es la configuración que recibe.</strong> Esto hace que tus tests en staging sean representativos de producción — porque es literalmente el mismo código.</p>
    </section>

    <!-- 05 HEALTH CHECKS -->
    <section class="section">
      <span class="section-number">// 05</span>
      <h2><span class="hex-icon">❤️</span>Los health checks no son opcionales. Son tu contrato con Kubernetes.</h2>
      <div class="section-divider"></div>

      <p>Kubernetes necesita saber tres cosas sobre tu aplicación. Cada una con una probe específica:</p>

      <div class="probe-grid">
        <div class="probe-card">
          <div class="probe-name">startup probe</div>
          <div class="probe-question">¿Arrancó correctamente?</div>
          <div class="probe-desc">Le dice a Kubernetes &#8220;todavía estoy levantando, no me mates&#8221;. Crítico para apps con startup lento — sin esto, Kubernetes puede reiniciarla en loop infinito.</div>
        </div>
        <div class="probe-card">
          <div class="probe-name">readiness probe</div>
          <div class="probe-question">¿Está lista para recibir tráfico?</div>
          <div class="probe-desc">Si devuelve error (porque perdió conexión a la BD), Kubernetes deja de enviarle tráfico temporalmente. La app no se reinicia — solo se saca de la rotación.</div>
        </div>
        <div class="probe-card">
          <div class="probe-name">liveness probe</div>
          <div class="probe-question">¿Está viva y funcionando?</div>
          <div class="probe-desc">Si deja de responder (deadlock, memory leak, thread pool agotado), Kubernetes la reinicia. Es la red de seguridad para problemas que no se arreglan solos.</div>
        </div>
      </div>

      <div class="callout callout-warning">
        <div class="callout-label">El error más común</div>
        <p>Usar el mismo endpoint para readiness y liveness. Si tu readiness check valida la conexión a la BD y la BD tiene un problema momentáneo, querés que Kubernetes <strong>saque el pod de la rotación</strong> (readiness), no que lo mate y reinicie (liveness) — reiniciarlo no va a arreglar un problema en la base de datos.</p>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">Recomendación</div>
        <p><strong>Liveness:</strong> endpoint básico que valida que el proceso está respondiendo (devuelve 200). <strong>Readiness:</strong> valida que la app puede servir requests correctamente — conexión a dependencias críticas, estado interno sano.</p>
      </div>
    </section>

    <!-- 06 LOGS -->
    <section class="section">
      <span class="section-number">// 06</span>
      <h2><span class="hex-icon">📋</span>Los logs van a stdout. Siempre.</h2>
      <div class="section-divider"></div>

      <p>En un servidor tradicional escribís logs en archivos. En Kubernetes, <strong>no hagas esto</strong>. Los archivos de log dentro de un pod desaparecen cuando el pod muere. Y el pod va a morir.</p>

      <div class="callout callout-tip">
        <div class="callout-label">Convención en Kubernetes</div>
        <p>Las aplicaciones escriben sus logs a <code>stdout</code> y <code>stderr</code>. Kubernetes los captura automáticamente y los hace disponibles via <code>kubectl logs</code>. Si tenés Loki, recolecta los logs de stdout de cada pod automáticamente — sin que tu app tenga que saber que Loki existe.</p>
      </div>

      <ul class="styled-list">
        <li><strong>Formato estructurado (JSON):</strong> cuando tenés 50 pods generando logs simultáneamente, poder filtrar por campos (<code>level</code>, <code>service</code>, <code>request_id</code>) es la diferencia entre encontrar el problema en segundos o en horas.</li>
        <li><strong>Correlation ID en cada log:</strong> cuando un request atraviesa múltiples servicios, el correlation ID te permite seguir el rastro completo a través de todos los pods involucrados.</li>
        <li><strong>No loguees información sensible</strong> — los logs centralizados son accesibles por más personas que el servidor original. El blast radius de un log con credenciales es mucho mayor.</li>
      </ul>
    </section>

    <!-- 07 RECURSOS -->
    <section class="section">
      <span class="section-number">// 07</span>
      <h2><span class="hex-icon">📊</span>Definí tus recursos. No es opcional.</h2>
      <div class="section-divider"></div>

      <p>Cada pod debería tener <code>requests</code> y <code>limits</code> de CPU y memoria configurados. Sin ellos, Kubernetes no puede tomar buenas decisiones sobre dónde colocar tus pods, el autoscaler no puede dimensionar los nodos, y un pod con memory leak puede tumbar a todos los pods que comparten su nodo.</p>

      <div class="callout callout-warning">
        <div class="callout-label">Los dos peores errores</div>
        <p><strong>No poner recursos:</strong> Kubernetes trata tu pod como &#8220;best effort&#8221; — el primero en morir cuando hay presión en el nodo. El autoscaler no tiene información para dimensionar correctamente.</p>
        <p><strong>Adivinar los valores:</strong> poner <code>1Gi</code> de memoria porque &#8220;suena bien&#8221; es tan malo como no poner nada. Los valores deberían basarse en datos reales de uso — de un dashboard de observabilidad que muestre consumo bajo carga normal y picos.</p>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">Diferencia entre requests y limits</div>
        <p><strong>Requests</strong> es lo mínimo que tu pod necesita. Kubernetes lo usa para decidir en qué nodo colocar el pod. <strong>Limits</strong> es el máximo que puede consumir — si excede la memoria, el pod es <code>OOMKilled</code>; si excede la CPU, es throttled pero no muere.</p>
      </div>
    </section>

    <!-- 08 COMUNICACIÓN -->
    <section class="section">
      <span class="section-number">// 08</span>
      <h2><span class="hex-icon">🔗</span>Pensá en la comunicación entre servicios</h2>
      <div class="section-divider"></div>

      <p>En un monolito, llamar a otra parte de la app es una función. En microservicios dentro de Kubernetes, <strong>llamar a otro servicio es un request de red. Y la red falla.</strong></p>

      <ul class="styled-list">
        <li><strong>Retries con backoff exponencial:</strong> si un request a otro servicio falla, no lo reintentes inmediatamente en loop. Usá backoff con jitter para no generar una avalancha de reintentos que empeore el problema.</li>
        <li><strong>Timeouts en todas las llamadas externas:</strong> cada request HTTP, query a BD, conexión a Redis. Sin timeouts, un servicio colgado puede bloquear a todos los que dependen de él — efecto dominó.</li>
        <li><strong>Circuit breakers:</strong> si un servicio downstream falla consistentemente, tu app debería dejar de llamarlo temporalmente. Esto protege a ambos servicios y le da tiempo para recuperarse.</li>
        <li><strong>DNS no siempre resuelve instantáneamente:</strong> bajo carga o durante cambios de topología puede haber latencia. Cachear resoluciones DNS y manejar errores de resolución es importante para servicios de alta frecuencia.</li>
      </ul>
    </section>

    <!-- TABLA RESUMEN -->
    <div class="table-wrapper">
      <div class="table-header">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#90cdf4" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
        <span>Checklist — Aplicación lista para Kubernetes</span>
      </div>
      <table>
        <thead>
          <tr>
            <th>Área</th>
            <th>Qué validar</th>
            <th style="text-align:right">Estado</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>Estado</td>
            <td>Sin estado en memoria ni en filesystem local entre requests</td>
            <td style="text-align:right"><span class="tag-ok">Crítico</span></td>
          </tr>
          <tr>
            <td>Migraciones</td>
            <td>Corren como Job separado, no al startup de cada réplica</td>
            <td style="text-align:right"><span class="tag-ok">Crítico</span></td>
          </tr>
          <tr>
            <td>Retrocompat.</td>
            <td>Cambios de esquema y API en múltiples deploys aditivos</td>
            <td style="text-align:right"><span class="tag-ok">Crítico</span></td>
          </tr>
          <tr>
            <td>Shutdown</td>
            <td>Escucha SIGTERM, termina requests en vuelo, cierra conexiones</td>
            <td style="text-align:right"><span class="tag-ok">Crítico</span></td>
          </tr>
          <tr>
            <td>Config</td>
            <td>ConfigMaps y Secrets, mismo build para todos los entornos</td>
            <td style="text-align:right"><span class="tag-ok">Crítico</span></td>
          </tr>
          <tr>
            <td>Health checks</td>
            <td>Startup, readiness y liveness probes distintos y correctos</td>
            <td style="text-align:right"><span class="tag-ok">Crítico</span></td>
          </tr>
          <tr>
            <td>Logging</td>
            <td>stdout, JSON estructurado, correlation ID, sin datos sensibles</td>
            <td style="text-align:right"><span class="tag-warn">Importante</span></td>
          </tr>
          <tr>
            <td>Recursos</td>
            <td>requests y limits basados en datos reales de observabilidad</td>
            <td style="text-align:right"><span class="tag-warn">Importante</span></td>
          </tr>
          <tr>
            <td>Comunicación</td>
            <td>Retries con backoff, timeouts explícitos, circuit breakers</td>
            <td style="text-align:right"><span class="tag-warn">Importante</span></td>
          </tr>
        </tbody>
      </table>
    </div>

    <!-- CTA -->
    <div class="cta-block">
      <h2>La infraestructura que acompaña a tu equipo</h2>
      <p>Desde SleakOps, la infraestructura de Kubernetes viene configurada con las mejores prácticas para que tu equipo pueda enfocarse en estos patrones de aplicación — sin preocuparse además por la complejidad del cluster, el networking, el autoescalado y la observabilidad.</p>
      <a href="#" class="cta-btn">Conocé SleakOps →</a>
    </div>

    <!-- SHARE -->
    <div class="share-row">
      <span class="share-label">Tags:</span>
      <a href="#" class="share-tag">#Kubernetes</a>
      <a href="#" class="share-tag">#DevOps</a>
      <a href="#" class="share-tag">#BestPractices</a>
      <a href="#" class="share-tag">#Docker</a>
      <a href="#" class="share-tag">#Microservices</a>
      <a href="#" class="share-tag">#CloudNative</a>
      <a href="#" class="share-tag">#EKS</a>
    </div>

  </div>
</main>
</div>



<p></p>
								</div>
				</div>
					</div>
				</div>
				</div><p>The post <a href="https://sleakops.com/2026/03/20/que-tener-en-cuenta-a-la-hora-de-desarrollar-aplicaciones-que-viven-dentro-de-kubernetes/">Desarrollo en Kubernetes: 5 Claves para Apps Eficientes</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p><p>La entrada <a href="https://sleakops.com/2026/03/20/que-tener-en-cuenta-a-la-hora-de-desarrollar-aplicaciones-que-viven-dentro-de-kubernetes/">Desarrollo en Kubernetes: 5 Claves para Apps Eficientes</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Costos de AWS: 7 Gastos Ocultos y Cómo Reducirlos</title>
		<link>https://sleakops.com/2026/03/20/costos-ocultos-de-aws/</link>
		
		<dc:creator><![CDATA[matias]]></dc:creator>
		<pubDate>Fri, 20 Mar 2026 11:51:31 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://sleakops.com/?p=1929</guid>

					<description><![CDATA[<p>AWS DevOps FinOps · Infraestructura &#038; Costos Costos Ocultos de AWS que Están Inflando tu Factura (Y Cómo Evitarlos) Entre un 15% y 30% de tu factura de AWS puede provenir de costos que nadie en tu equipo recuerda haber contratado. NAT Gateway, ECR, EBS, Data Transfer. Centavos por hora que se convierten en cientos [&#8230;]</p>
<p>The post <a href="https://sleakops.com/2026/03/20/costos-ocultos-de-aws/">Costos de AWS: 7 Gastos Ocultos y Cómo Reducirlos</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p>
<p>La entrada <a href="https://sleakops.com/2026/03/20/costos-ocultos-de-aws/">Costos de AWS: 7 Gastos Ocultos y Cómo Reducirlos</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="sleakops-post">
<style>
:root {
    --navy: #1a202c;
    --navy-light: #232c3e;
    --navy-mid: #2d3748;
    --blue: #90cdf4;
    --blue-dim: rgba(144, 205, 244, 0.12);
    --blue-glow: rgba(144, 205, 244, 0.25);
    --coral: #e37562;
    --coral-dim: rgba(227, 117, 98, 0.15);
    --white: #ffffff;
    --gray: #a0aec0;
    --gray-light: #e2e8f0;
    --body-bg: #111827;
    --card-bg: #1a202c;
    --border: rgba(144, 205, 244, 0.15);
  }

  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

  .sleakops-post {
    font-family: 'Inter', sans-serif;
    color: var(--white);
    line-height: 1.75;
    font-size: 16px;
    overflow-x: hidden;
  }

  /* ─── HERO ─── */
  .hero {
    position: relative;
    background: var(--navy);
    padding: 80px 0 60px;
    overflow: hidden;
    border-bottom: 1px solid var(--border);
  }

  .hero::before {
    content: '';
    position: absolute;
    inset: 0;
    background-image:
      repeating-linear-gradient(
        60deg,
        transparent,
        transparent 38px,
        rgba(144,205,244,0.04) 38px,
        rgba(144,205,244,0.04) 40px
      ),
      repeating-linear-gradient(
        -60deg,
        transparent,
        transparent 38px,
        rgba(144,205,244,0.04) 38px,
        rgba(144,205,244,0.04) 40px
      );
    pointer-events: none;
  }

  .hero::after {
    content: '';
    position: absolute;
    top: -120px; right: -120px;
    width: 480px; height: 480px;
    background: radial-gradient(circle, rgba(144,205,244,0.12) 0%, transparent 70%);
    pointer-events: none;
  }

  .container {
    max-width: 820px;
    margin: 0 auto;
    padding: 0 32px;
    position: relative;
    z-index: 1;
  }

  .meta-bar {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 28px;
    flex-wrap: wrap;
  }

  .badge {
    font-size: 10px;
    font-weight: 900;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    padding: 5px 12px;
    border-radius: 4px;
    background: var(--blue-dim);
    color: var(--blue);
    border: 1px solid rgba(144,205,244,0.3);
  }

  .badge.coral {
    background: var(--coral-dim);
    color: var(--coral);
    border-color: rgba(227,117,98,0.3);
  }

  .meta-text {
    font-size: 12px;
    color: var(--gray);
    letter-spacing: 0.04em;
  }

  .hero h1 {
    font-family: 'Inter', sans-serif;
    font-weight: 900;
    font-size: clamp(28px, 5vw, 46px);
    line-height: 1.15;
    color: var(--white);
    margin-bottom: 20px;
  }

  .hero h1 .accent { color: var(--blue); }
  .hero h1 .danger { color: var(--coral); }

  .hero-lead {
    font-size: 17px;
    color: var(--gray);
    line-height: 1.8;
    max-width: 680px;
    margin-bottom: 32px;
    border-left: 3px solid var(--blue);
    padding-left: 20px;
  }

  /* ─── READING TIME ─── */
  .reading-pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: var(--navy-mid);
    border: 1px solid var(--border);
    border-radius: 32px;
    padding: 8px 18px;
    font-size: 12px;
    color: var(--gray);
  }
  .reading-pill svg { color: var(--blue); flex-shrink: 0; }

  /* ─── MAIN CONTENT ─── */
  .content {
    padding: 56px 0 80px;
  }

  /* Intro paragraph */
  .intro-block {
    background: linear-gradient(135deg, var(--navy-light), var(--navy-mid));
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 32px 36px;
    margin-bottom: 56px;
    position: relative;
    overflow: hidden;
  }
  .intro-block::before {
    content: '';
    position: absolute;
    top: 0; left: 0;
    width: 4px; height: 100%;
    background: linear-gradient(to bottom, var(--blue), var(--coral));
  }
  .intro-block p {
    color: var(--gray);
    font-size: 16px;
    line-height: 1.85;
    margin-bottom: 14px;
  }
  .intro-block p:last-child { margin-bottom: 0; }
  .intro-block strong { color: var(--white); font-weight: 600; }

  /* ─── SECTION HEADERS ─── */
  .section {
    margin-bottom: 64px;
  }

  .section-number {
    display: inline-block;
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    font-weight: 600;
    color: var(--blue);
    letter-spacing: 0.1em;
    margin-bottom: 8px;
    opacity: 0.8;
  }

  .section h2 {
    font-weight: 900;
    font-size: clamp(20px, 3vw, 26px);
    color: var(--white);
    line-height: 1.25;
    margin-bottom: 6px;
    display: flex;
    align-items: flex-start;
    gap: 12px;
  }

  .section h2 .hex-icon {
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    background: var(--blue-dim);
    border: 1px solid rgba(144,205,244,0.25);
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    margin-top: 2px;
  }

  .section-divider {
    width: 48px;
    height: 3px;
    background: linear-gradient(to right, var(--blue), transparent);
    margin: 12px 0 22px;
    border-radius: 2px;
  }

  .section p {
    color: var(--gray);
    margin-bottom: 16px;
    line-height: 1.85;
    font-size: 15.5px;
  }
  .section p strong { color: var(--white); font-weight: 600; }

  /* ─── CALLOUT BOXES ─── */
  .callout {
    border-radius: 10px;
    padding: 24px 28px;
    margin: 24px 0;
    border: 1px solid;
    position: relative;
    overflow: hidden;
  }

  .callout-warning {
    background: rgba(227,117,98,0.08);
    border-color: rgba(227,117,98,0.3);
  }
  .callout-warning::before {
    content: '⚠';
    position: absolute;
    top: 16px; right: 20px;
    font-size: 22px;
    opacity: 0.3;
  }

  .callout-tip {
    background: var(--blue-dim);
    border-color: rgba(144,205,244,0.25);
  }
  .callout-tip::before {
    content: '💡';
    position: absolute;
    top: 16px; right: 20px;
    font-size: 20px;
    opacity: 0.5;
  }

  .callout-label {
    font-size: 10px;
    font-weight: 900;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    margin-bottom: 10px;
  }
  .callout-warning .callout-label { color: var(--coral); }
  .callout-tip .callout-label { color: var(--blue); }

  .callout p {
    font-size: 14.5px !important;
    margin-bottom: 8px !important;
  }
  .callout p:last-child { margin-bottom: 0 !important; }

  /* ─── LIST STYLES ─── */
  .styled-list {
    list-style: none;
    margin: 16px 0 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .styled-list li {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    color: var(--gray);
    font-size: 15px;
    line-height: 1.7;
  }
  .styled-list li::before {
    content: '→';
    color: var(--blue);
    font-weight: 700;
    flex-shrink: 0;
    margin-top: 1px;
    font-family: 'JetBrains Mono', monospace;
  }
  .styled-list li strong { color: var(--white); }

  /* ─── COST TABLE ─── */
  .cost-table-wrapper {
    background: var(--navy);
    border: 1px solid var(--border);
    border-radius: 12px;
    overflow: hidden;
    margin: 40px 0;
  }

  .cost-table-header {
    background: linear-gradient(135deg, var(--navy-mid), var(--navy-light));
    padding: 20px 28px;
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .cost-table-header span {
    font-size: 12px;
    font-weight: 900;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--blue);
  }

  table {
    width: 100%;
    border-collapse: collapse;
  }
  th {
    text-align: left;
    padding: 14px 28px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--gray);
    background: var(--navy-mid);
    border-bottom: 1px solid var(--border);
  }
  td {
    padding: 15px 28px;
    font-size: 14px;
    border-bottom: 1px solid rgba(255,255,255,0.04);
    color: var(--gray);
  }
  td:first-child { color: var(--white); font-weight: 500; }
  td:last-child {
    font-family: 'JetBrains Mono', monospace;
    font-weight: 600;
    color: var(--coral);
    text-align: right;
  }
  tr:last-child td { border-bottom: none; }
  tr.total td {
    background: var(--blue-dim);
    border-top: 1px solid rgba(144,205,244,0.25);
    color: var(--blue);
    font-weight: 700;
  }
  tr.total td:last-child { color: var(--blue); }
  tr:hover td { background: rgba(255,255,255,0.02); }

  /* ─── SOLUTION GRID ─── */
  .solution-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
    margin: 28px 0;
  }

  .solution-card {
    display: flex;
    gap: 16px;
    background: var(--navy-light);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 20px 22px;
    align-items: flex-start;
    transition: border-color 0.2s;
  }
  .solution-card:hover { border-color: rgba(144,205,244,0.4); }

  .solution-icon {
    width: 40px; height: 40px;
    background: var(--blue-dim);
    border: 1px solid rgba(144,205,244,0.2);
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    flex-shrink: 0;
  }
  .solution-title {
    font-size: 14px;
    font-weight: 700;
    color: var(--white);
    margin-bottom: 4px;
  }
  .solution-desc {
    font-size: 13.5px;
    color: var(--gray);
    line-height: 1.65;
  }

  /* ─── CTA BLOCK ─── */
  .cta-block {
    background: linear-gradient(135deg, var(--navy-mid) 0%, rgba(26,32,44,0.9) 100%);
    border: 1px solid rgba(144,205,244,0.3);
    border-radius: 16px;
    padding: 48px 40px;
    text-align: center;
    position: relative;
    overflow: hidden;
    margin-top: 64px;
  }
  .cta-block::before {
    content: '';
    position: absolute;
    bottom: -80px; left: 50%;
    transform: translateX(-50%);
    width: 360px; height: 360px;
    background: radial-gradient(circle, rgba(144,205,244,0.1) 0%, transparent 70%);
    pointer-events: none;
  }
  .cta-block h2 {
    font-weight: 900;
    font-size: clamp(22px, 3.5vw, 32px);
    color: var(--white);
    margin-bottom: 14px;
    position: relative;
  }
  .cta-block p {
    color: var(--gray);
    font-size: 15px;
    margin-bottom: 32px;
    max-width: 520px;
    margin-left: auto;
    margin-right: auto;
    position: relative;
  }
  .cta-btn {
    display: inline-block;
    background: var(--blue);
    color: var(--navy) !important;
    font-weight: 900;
    font-size: 14px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    text-decoration: none;
    padding: 14px 36px;
    border-radius: 8px;
    position: relative;
    transition: transform 0.15s, box-shadow 0.15s;
  }
  .cta-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 32px rgba(144,205,244,0.3);
  }

  /* ─── LOGO ─── */
  .logo {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 32px;
  }
  .logo-hex {
    width: 38px; height: 38px;
    background: var(--blue-dim);
    clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
  }
  .logo-hex::after {
    content: '▲';
    color: var(--blue);
    font-size: 14px;
    position: absolute;
  }
  .logo-wordmark {
    font-family: 'Quicksand', sans-serif;
    font-size: 22px;
    color: var(--white);
    letter-spacing: -0.02em;
  }
  .logo-wordmark b {
    font-weight: 700;
    color: var(--blue);
  }

  /* ─── INLINE CODE ─── */
  code {
    font-family: 'JetBrains Mono', monospace;
    font-size: 13px;
    background: var(--navy-mid);
    color: var(--blue);
    padding: 2px 7px;
    border-radius: 4px;
    border: 1px solid var(--border);
  }

  /* ─── VPN HIGHLIGHT ─── */
  .vpn-highlight {
    background: linear-gradient(135deg, rgba(227,117,98,0.08), rgba(144,205,244,0.05));
    border: 1px solid rgba(227,117,98,0.25);
    border-radius: 12px;
    padding: 28px 32px;
    margin: 32px 0;
  }
  .vpn-highlight h3 {
    font-size: 16px;
    font-weight: 700;
    color: var(--coral);
    margin-bottom: 10px;
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .vpn-highlight p { color: var(--gray); font-size: 15px; line-height: 1.8; }
  .vpn-highlight p + p { margin-top: 12px; }
  .vpn-highlight strong { color: var(--white); }

  /* ─── SHARE ROW ─── */
  .share-row {
    margin-top: 48px;
    padding-top: 28px;
    border-top: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
  }
  .share-label {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--gray);
  }
  .share-tag {
    background: var(--navy-mid);
    border: 1px solid var(--border);
    color: var(--gray);
    font-size: 12px;
    padding: 5px 14px;
    border-radius: 20px;
    text-decoration: none;
    transition: border-color 0.15s, color 0.15s;
  }
  .share-tag:hover { border-color: var(--blue); color: var(--blue); }

  @media (max-width: 640px) {
    .container { padding: 0 20px; }
    .hero { padding: 48px 0 40px; }
    th, td { padding: 12px 16px; }
    .cta-block { padding: 36px 24px; }
  }
</style>
<!-- HERO -->
<header class="hero">
  <div class="container">
    <div class="meta-bar">
      <span class="badge">AWS</span>
      <span class="badge">DevOps</span>
      <span class="badge coral">FinOps</span>
      <span class="meta-text">· Infraestructura &#038; Costos</span>
    </div>

    <h1>
      Costos <span class="danger">Ocultos</span> de AWS<br>
      que Están Inflando tu <span class="accent">Factura</span><br>
      (Y Cómo Evitarlos)
    </h1>

    <p class="hero-lead">
      Entre un 15% y 30% de tu factura de AWS puede provenir de costos que nadie en tu equipo recuerda haber contratado.
      NAT Gateway, ECR, EBS, Data Transfer. Centavos por hora que se convierten en cientos de dólares al mes.
    </p>

    <div class="reading-pill">
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
      9 minutos de lectura
    </div>
  </div>
</header>

<!-- CONTENT -->
<main class="content">
  <div class="container">

    <!-- INTRO -->
    <div class="intro-block">
      <p>Abrís la factura de AWS del mes y el número es más alto de lo que esperabas. <strong>Otra vez.</strong> Mirás el breakdown y lo obvio está ahí: EC2, RDS, tal vez EKS. Pero hay una porción significativa que se reparte entre conceptos que nadie recuerda haber contratado: Data Transfer, NAT Gateway Processing, ECR Storage, CloudWatch Logs Ingestion, EBS Snapshots.</p>
      <p>Son centavos por hora o por GB. Parecen insignificantes de forma aislada. Pero sumados a lo largo de un mes, en un cluster con decenas de servicios, estos costos &#8220;ocultos&#8221; <strong>pueden representar entre un 15% y 30% de tu factura total</strong> — a veces más.</p>
      <p>El problema no es que AWS te esté cobrando de más. El problema es que la mayoría de estos costos son <strong>completamente evitables</strong> con configuraciones que deberían existir desde el día uno.</p>
    </div>

    <!-- 1. NAT GATEWAY -->
    <section class="section">
      <span class="section-number">// 01</span>
      <h2>
        <span class="hex-icon">🌐</span>
        NAT Gateway: el peaje silencioso de tu red
      </h2>
      <div class="section-divider"></div>

      <div class="callout callout-warning">
        <div class="callout-label">El costo que no esperabas</div>
        <p>AWS cobra el NAT Gateway de dos formas: <strong>~USD 0.045/hora</strong> por funcionamiento (unos USD 32/mes por NAT Gateway) y <strong>~USD 0.045/GB procesado</strong>. La tarifa por hora es predecible. La por GB es donde la sorpresa aparece.</p>
        <p>En un cluster de Kubernetes, cada pull de imagen Docker desde ECR, cada upload de logs a CloudWatch, cada lectura/escritura a S3 pasa por el NAT Gateway y paga peaje.</p>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">Cómo evitarlo: VPC Endpoints</div>
        <p>Un VPC Endpoint crea una conexión directa entre tu VPC y el servicio de AWS sin pasar por internet — y por lo tanto, sin pagar el peaje del NAT Gateway.</p>
      </div>

      <ul class="styled-list">
        <li><strong>S3 Gateway Endpoint</strong> — Gratuito, sin costo por hora ni por GB. En un cluster con Loki, backups y assets, este solo endpoint puede ahorrar cientos de dólares.</li>
        <li><strong>ECR Endpoints</strong> — Cada pull de imagen Docker deja de pagar NAT. Con 50 pods que se recrean frecuentemente, el ahorro es considerable.</li>
        <li><strong>CloudWatch, SQS, SNS, DynamoDB, STS</strong> — Interface Endpoints (~USD 7/mes cada uno) cuyo ahorro en NAT suele ser muy superior a su costo.</li>
      </ul>

      <p style="font-style:italic; font-size:14px; color: #718096;">La ironía: configurar VPC Endpoints toma minutos y el ahorro es inmediato. La mayoría no los implementa porque no saben que ese tráfico está pagando peaje.</p>
    </section>

    <!-- 2. ECR -->
    <section class="section">
      <span class="section-number">// 02</span>
      <h2>
        <span class="hex-icon">📦</span>
        ECR sin lifecycle policy: pagando por imágenes que nadie usa
      </h2>
      <div class="section-divider"></div>

      <div class="callout callout-warning">
        <div class="callout-label">El costo que no esperabas</div>
        <p>Si tu equipo despliega 3 veces por día y tiene 10 servicios, son <strong>30 imágenes nuevas por día</strong>. En un año, más de 10.000 imágenes. ECR cobra <strong>USD 0.10/GB al mes</strong>. Con miles de imágenes acumuladas, el costo crece silenciosamente mes a mes.</p>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">Cómo evitarlo: Lifecycle Policies</div>
        <p>Una regla simple: <strong>mantené las últimas 10 imágenes tagueadas</strong> de cada repositorio y eliminá el resto automáticamente. También eliminá imágenes sin tag después de 1 día e imágenes con más de 30 días de antigüedad. Toma minutos configurar y evita crecimiento indefinido.</p>
      </div>
    </section>

    <!-- 3. EBS -->
    <section class="section">
      <span class="section-number">// 03</span>
      <h2>
        <span class="hex-icon">💾</span>
        EBS Snapshots y volúmenes huérfanos: el storage fantasma
      </h2>
      <div class="section-divider"></div>

      <div class="callout callout-warning">
        <div class="callout-label">El costo que no esperabas</div>
        <p>Cada vez que eliminás un nodo o borrás un PersistentVolume en Kubernetes, el volumen EBS puede quedar atrás según el <code>reclaimPolicy</code>. Estos volúmenes <strong>siguen existiendo y cobrando</strong> sin estar adjuntos a nada. Lo mismo pasa con snapshots diarios sin política de retención: después de un año, tenés 365 snapshots de cada volumen, todos pagando storage.</p>
      </div>

      <ul class="styled-list">
        <li>Asegurate de que el <code>reclaimPolicy</code> de tus PersistentVolumes sea <strong>Delete</strong> (no Retain) para volúmenes que no necesitás conservar.</li>
        <li>Revisá periódicamente volúmenes en estado <strong>&#8220;Available&#8221;</strong> — no adjuntos a ninguna instancia. Son candidatos a eliminar.</li>
        <li>Usá <strong>AWS Data Lifecycle Manager (DLM)</strong> para automatizar creación y eliminación de snapshots con retención definida.</li>
      </ul>
    </section>

    <!-- 4. CROSS-AZ -->
    <section class="section">
      <span class="section-number">// 04</span>
      <h2>
        <span class="hex-icon">🔀</span>
        Data Transfer entre Availability Zones: el costo de la resiliencia mal configurada
      </h2>
      <div class="section-divider"></div>

      <div class="callout callout-warning">
        <div class="callout-label">El costo que no esperabas</div>
        <p>AWS cobra <strong>USD 0.01/GB</strong> por tráfico entre AZs en la misma región. Si tu aplicación hace 10.000 requests/minuto con payloads de 10 KB promedio a un servicio en otra AZ, son <strong>~4 TB de tráfico cross-AZ al mes</strong> — unos USD 40 solo por esa comunicación. Multiplicalo por cada par de servicios.</p>
      </div>

      <ul class="styled-list">
        <li><strong>Topology-aware routing</strong> en Kubernetes — prefiere pods en la misma AZ cuando es posible, sin sacrificar disponibilidad.</li>
        <li><strong>Colocar bases de datos y caches</strong> en la misma AZ que la mayoría de los pods que los consumen.</li>
        <li><strong>VPC Flow Logs</strong> para monitorear el tráfico inter-AZ e identificar qué pares de servicios generan más tráfico cross-AZ.</li>
      </ul>
    </section>

    <!-- 5. CLOUDWATCH -->
    <section class="section">
      <span class="section-number">// 05</span>
      <h2>
        <span class="hex-icon">📊</span>
        CloudWatch Logs: pagando por logs que nadie lee
      </h2>
      <div class="section-divider"></div>

      <div class="callout callout-warning">
        <div class="callout-label">El costo que no esperabas</div>
        <p>CloudWatch cobra <strong>USD 0.50/GB de ingesta</strong> y USD 0.03/GB al mes de almacenamiento. Un servicio con 10 pods que genera 1 KB por request a 100 req/seg produce <strong>~260 GB de logs al mes</strong>. Solo la ingesta cuesta USD 130/mes — por un solo servicio.</p>
      </div>

      <ul class="styled-list">
        <li>Asegurate de que el nivel de logging en producción sea <strong>INFO o WARNING</strong>, nunca DEBUG.</li>
        <li>No usés CloudWatch como destino principal. <strong>Loki sobre S3</strong> reduce el costo de almacenamiento de USD 0.50/GB a centavos/GB con la misma capacidad de búsqueda.</li>
        <li>Configurá <strong>retención de logs</strong> en CloudWatch — por defecto retiene para siempre. 7, 14 o 30 días para logs no críticos libera costo significativo.</li>
      </ul>
    </section>

    <!-- 6. ZOMBIES -->
    <section class="section">
      <span class="section-number">// 06</span>
      <h2>
        <span class="hex-icon">🧟</span>
        Elastic IPs y Load Balancers sin uso: recursos zombies
      </h2>
      <div class="section-divider"></div>

      <div class="callout callout-warning">
        <div class="callout-label">El costo que no esperabas</div>
        <p>Cada Elastic IP no asociada cobra <strong>USD 0.005/hora (~USD 3.60/mes)</strong>. Los Load Balancers (ALB/NLB) cuestan <strong>USD 16-22/mes base</strong> más costo por capacidad. De entornos eliminados, branches de feature, experimentos sin limpiar — cada uno cobra silenciosamente.</p>
      </div>

      <ul class="styled-list">
        <li><strong>Auditorías periódicas</strong>: Elastic IPs no asociadas, Load Balancers sin targets, Security Groups vacíos, ENIs detachadas. AWS no te avisa.</li>
        <li><strong>Tags de ownership</strong>: si cada recurso tiene tag de quién lo creó y para qué, es fácil identificar qué eliminar sin miedo.</li>
        <li><strong>Infrastructure as Code</strong>: si toda la infra está en Terraform o CloudFormation, los recursos zombies son fáciles de detectar — si no está en el código, no debería existir.</li>
      </ul>
    </section>

    <!-- 7. EGRESS -->
    <section class="section">
      <span class="section-number">// 07</span>
      <h2>
        <span class="hex-icon">📡</span>
        Transferencia de datos hacia internet: el costo de cada byte que sale
      </h2>
      <div class="section-divider"></div>

      <div class="callout callout-warning">
        <div class="callout-label">El costo que no esperabas</div>
        <p>AWS cobra <strong>USD 0.09/GB</strong> de datos que salen a internet. Un servicio API que devuelve 50 KB por response y maneja 1 millón de requests diarios genera <strong>~1.5 TB de egress al mes</strong> — unos USD 135 solo en data transfer.</p>
      </div>

      <ul class="styled-list">
        <li><strong>CloudFront como CDN</strong> — cachea en edge locations y reduce drásticamente el egress. Para S3, elimina completamente el costo de egress.</li>
        <li><strong>Compresión gzip o brotli</strong> — puede reducir el tamaño de responses entre 60% y 80%. Menos bytes = menos costo. Mejor relación esfuerzo/impacto.</li>
        <li>Revisá qué servicios generan más egress: endpoints con payloads enormes que podrían paginar, assets estáticos fuera de CDN, logs sin comprimir hacia servicios externos.</li>
      </ul>
    </section>

    <!-- TABLA DE COSTOS -->
    <div class="cost-table-wrapper">
      <div class="cost-table-header">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#90cdf4" stroke-width="2"><rect x="2" y="2" width="20" height="20" rx="2"/><line x1="2" y1="9" x2="22" y2="9"/><line x1="2" y1="15" x2="22" y2="15"/><line x1="9" y1="9" x2="9" y2="22"/></svg>
        <span>Resumen de Costos Ocultos — Cluster EKS Mediano</span>
      </div>
      <table>
        <thead>
          <tr>
            <th>Costo oculto</th>
            <th style="text-align:right">Estimación mensual</th>
          </tr>
        </thead>
        <tbody>
          <tr><td>NAT Gateway processing (sin VPC Endpoints)</td><td>USD 50–200</td></tr>
          <tr><td>ECR storage sin lifecycle policy</td><td>USD 20–80</td></tr>
          <tr><td>EBS snapshots y volúmenes huérfanos</td><td>USD 30–100</td></tr>
          <tr><td>Data transfer inter-AZ</td><td>USD 20–60</td></tr>
          <tr><td>CloudWatch Logs ingesta y storage</td><td>USD 50–300</td></tr>
          <tr><td>Elastic IPs y LBs sin uso</td><td>USD 20–80</td></tr>
          <tr><td>Data transfer egress sin optimizar</td><td>USD 50–200</td></tr>
          <tr class="total"><td><strong>Total potencial ahorro</strong></td><td><strong>USD 240–1.020</strong></td></tr>
        </tbody>
      </table>
    </div>

    <!-- VPN BONUS -->
    <div class="vpn-highlight">
      <h3>🎁 Yapa: VPN open source a una fracción del costo</h3>
      <p>AWS Client VPN cobra <strong>USD 0.10/hora por conexión activa</strong>. Con 10 personas conectadas 8hs/día, el costo ronda <strong>USD 200–300 mensuales</strong> solo por la VPN.</p>
      <p>SleakOps implementa una solución basada en <strong>OpenVPN</strong> — completamente open source, corriendo dentro de tu propia infraestructura. Costo: el de la instancia donde corre, <strong>USD 10–15 al mes</strong>. Misma funcionalidad, misma seguridad, entre 10 y 20 veces más barato.</p>
    </div>

    <!-- CTA -->
    <div class="cta-block">
      <h2>Tu factura de AWS no debería tener sorpresas</h2>
      <p>Con SleakOps, estas optimizaciones no son un paso adicional — son parte de cómo se construye la infraestructura desde el primer momento. VPC Endpoints, lifecycle policies, networking cross-AZ, Loki sobre S3, IaC completa.</p>
      <a href="#" class="cta-btn">Conocé SleakOps →</a>
    </div>

    <!-- SHARE -->
    <div class="share-row">
      <span class="share-label">Tags:</span>
      <a href="#" class="share-tag">#AWS</a>
      <a href="#" class="share-tag">#FinOps</a>
      <a href="#" class="share-tag">#DevOps</a>
      <a href="#" class="share-tag">#Kubernetes</a>
      <a href="#" class="share-tag">#CloudCosts</a>
      <a href="#" class="share-tag">#EKS</a>
    </div>

  </div>
</main>
</div>



<p></p><p>The post <a href="https://sleakops.com/2026/03/20/costos-ocultos-de-aws/">Costos de AWS: 7 Gastos Ocultos y Cómo Reducirlos</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p><p>La entrada <a href="https://sleakops.com/2026/03/20/costos-ocultos-de-aws/">Costos de AWS: 7 Gastos Ocultos y Cómo Reducirlos</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>CronJobs en Kubernetes: Ventajas, Retos y Alternativas</title>
		<link>https://sleakops.com/2026/03/20/ventajas-y-desventajas-de-usar-cronjobs-en-kubernetes-es-la-herramienta-correcta-para-tu-caso/</link>
		
		<dc:creator><![CDATA[matias]]></dc:creator>
		<pubDate>Fri, 20 Mar 2026 11:51:24 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://sleakops.com/?p=1921</guid>

					<description><![CDATA[<p>Kubernetes DevOps CronJobs Automatización 7 minutos de lectura Toda aplicación tiene tareas que necesitan ejecutarse de forma periódica: limpiar registros viejos, enviar reportes diarios, sincronizar datos con un sistema externo, renovar certificados. En el mundo pre-cloud esto se resolvía con un crontab en un servidor Linux. Simple, predecible, conocido. Cuando tu infraestructura se muda a [&#8230;]</p>
<p>The post <a href="https://sleakops.com/2026/03/20/ventajas-y-desventajas-de-usar-cronjobs-en-kubernetes-es-la-herramienta-correcta-para-tu-caso/">CronJobs en Kubernetes: Ventajas, Retos y Alternativas</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p>
<p>La entrada <a href="https://sleakops.com/2026/03/20/ventajas-y-desventajas-de-usar-cronjobs-en-kubernetes-es-la-herramienta-correcta-para-tu-caso/">CronJobs en Kubernetes: Ventajas, Retos y Alternativas</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="sleakops-post">
<style>
:root {
    --navy:      #1a202c;
    --navy-light:#232c3e;
    --navy-mid:  #2d3748;
    --blue:      #90cdf4;
    --blue-dim:  rgba(144,205,244,0.09);
    --coral:     #e37562;
    --coral-dim: rgba(227,117,98,0.10);
    --white:     #ffffff;
    --gray:      #a0aec0;
    --gray-dim:  rgba(160,174,192,0.09);
    --border:    rgba(144,205,244,0.13);
  }

  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

  .sleakops-post {
    font-family: 'Inter', sans-serif;
        color: var(--white);
    line-height: 1.75;
    font-size: 16px;
    overflow-x: hidden;
  }

  /* ── HERO ── */
  .hero {
    background: var(--navy);
    padding: 44px 0 40px;
    border-bottom: 1px solid var(--border);
    position: relative;
    overflow: hidden;
  }
  .hero::before {
    content: '';
    position: absolute; inset: 0;
    background-image:
      repeating-linear-gradient( 60deg, transparent, transparent 38px, rgba(144,205,244,.03) 38px, rgba(144,205,244,.03) 40px),
      repeating-linear-gradient(-60deg, transparent, transparent 38px, rgba(144,205,244,.03) 38px, rgba(144,205,244,.03) 40px);
    pointer-events: none;
  }
  .hero::after {
    content: '';
    position: absolute; top: -110px; right: -110px;
    width: 400px; height: 400px;
    background: radial-gradient(circle, rgba(144,205,244,.08) 0%, transparent 70%);
    pointer-events: none;
  }

  .container { max-width: 820px; margin: 0 auto; padding: 0 32px; position: relative; z-index: 1; }

  .meta-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
  .badge {
    font-size: 10px; font-weight: 900; letter-spacing: .14em; text-transform: uppercase;
    padding: 4px 11px; border-radius: 4px;
    background: var(--blue-dim); color: var(--blue); border: 1px solid rgba(144,205,244,.26);
  }
  .badge.coral { background: var(--coral-dim); color: var(--coral); border-color: rgba(227,117,98,.26); }
  .badge.gray  { background: var(--gray-dim);  color: var(--gray);  border-color: rgba(160,174,192,.2); }

  .reading-pill {
    margin-left: auto;
    display: inline-flex; align-items: center; gap: 7px;
    background: var(--navy-mid); border: 1px solid var(--border);
    border-radius: 32px; padding: 6px 15px; font-size: 12px; color: var(--gray);
  }
  .reading-pill svg { color: var(--blue); flex-shrink: 0; }

  /* ── CONTENT ── */
  .content { padding: 52px 0 80px; }

  /* intro */
  .intro-block {
    background: linear-gradient(135deg, var(--navy-light), var(--navy-mid));
    border: 1px solid var(--border); border-radius: 12px;
    padding: 30px 34px; margin-bottom: 56px;
    position: relative; overflow: hidden;
  }
  .intro-block::before {
    content: ''; position: absolute; top: 0; left: 0;
    width: 4px; height: 100%;
    background: linear-gradient(to bottom, var(--blue), var(--coral));
  }
  .intro-block p { color: var(--gray); font-size: 15.5px; line-height: 1.85; margin-bottom: 13px; }
  .intro-block p:last-child { margin-bottom: 0; }
  .intro-block strong { color: var(--white); font-weight: 600; }

  /* ── BLOCK HEADERS (ventajas / desventajas) ── */
  .block-header {
    display: flex; align-items: center; gap: 14px;
    padding: 18px 24px; border-radius: 10px;
    margin-bottom: 32px;
  }
  .block-header.pros {
    background: linear-gradient(135deg, rgba(144,205,244,.08), rgba(144,205,244,.03));
    border: 1px solid rgba(144,205,244,.22);
  }
  .block-header.cons {
    background: linear-gradient(135deg, rgba(227,117,98,.08), rgba(227,117,98,.03));
    border: 1px solid rgba(227,117,98,.22);
  }
  .block-icon { font-size: 22px; flex-shrink: 0; }
  .block-title { font-weight: 900; font-size: clamp(16px,2.5vw,20px); }
  .block-header.pros .block-title { color: var(--blue); }
  .block-header.cons .block-title { color: var(--coral); }
  .block-sub { font-size: 13px; color: var(--gray); margin-top: 2px; }

  /* ── SECTIONS ── */
  .section { margin-bottom: 52px; }
  .section-number {
    display: inline-block; font-family: 'JetBrains Mono', monospace;
    font-size: 11px; font-weight: 600; color: var(--blue);
    letter-spacing: .1em; margin-bottom: 8px; opacity: .8;
  }
  .section-number.coral-num { color: var(--coral); }

  .section h2 {
    font-weight: 900; font-size: clamp(18px,2.8vw,23px);
    color: var(--white); line-height: 1.25; margin-bottom: 6px;
    display: flex; align-items: flex-start; gap: 12px;
  }
  .hex-icon {
    flex-shrink: 0; width: 34px; height: 34px;
    background: var(--blue-dim); border: 1px solid rgba(144,205,244,.2);
    border-radius: 8px; display: flex; align-items: center; justify-content: center;
    font-size: 15px; margin-top: 2px;
  }
  .hex-icon.coral-icon {
    background: var(--coral-dim); border-color: rgba(227,117,98,.2);
  }
  .section-divider {
    width: 44px; height: 3px; border-radius: 2px; margin: 10px 0 20px;
    background: linear-gradient(to right, var(--blue), transparent);
  }
  .section-divider.coral-div {
    background: linear-gradient(to right, var(--coral), transparent);
  }

  .section p { color: var(--gray); margin-bottom: 15px; line-height: 1.85; font-size: 15.5px; }
  .section p strong { color: var(--white); font-weight: 600; }
  .section p:last-child { margin-bottom: 0; }

  /* callouts */
  .callout {
    border-radius: 10px; padding: 20px 24px; margin: 20px 0;
    border: 1px solid; position: relative; overflow: hidden;
  }
  .callout-warning { background: rgba(227,117,98,.07); border-color: rgba(227,117,98,.27); }
  .callout-warning::after { content:'⚠'; position:absolute; top:12px; right:16px; font-size:18px; opacity:.22; }
  .callout-tip    { background: var(--blue-dim); border-color: rgba(144,205,244,.22); }
  .callout-tip::after    { content:'💡'; position:absolute; top:12px; right:16px; font-size:16px; opacity:.4; }
  .callout-label { font-size:10px; font-weight:900; letter-spacing:.14em; text-transform:uppercase; margin-bottom:8px; }
  .callout-warning .callout-label { color: var(--coral); }
  .callout-tip    .callout-label { color: var(--blue); }
  .callout p { font-size:14.5px !important; margin-bottom:8px !important; line-height:1.75 !important; }
  .callout p:last-child { margin-bottom:0 !important; }
  .callout strong { color: var(--white); font-weight:600; }

  /* styled list */
  .styled-list { list-style:none; margin:14px 0 18px; display:flex; flex-direction:column; gap:10px; }
  .styled-list li { display:flex; align-items:flex-start; gap:12px; color:var(--gray); font-size:15px; line-height:1.72; }
  .styled-list li::before { content:'→'; color:var(--blue); font-weight:700; flex-shrink:0; margin-top:1px; font-family:'JetBrains Mono',monospace; }
  .styled-list li strong { color:var(--white); }

  code {
    font-family:'JetBrains Mono',monospace; font-size:13px;
    background:var(--navy-mid); color:var(--blue);
    padding:2px 6px; border-radius:4px; border:1px solid var(--border);
  }

  /* section separator */
  .block-sep {
    border: none;
    border-top: 1px dashed rgba(144,205,244,.15);
    margin: 8px 0 56px;
  }

  /* ── DECISION TABLE ── */
  .table-wrapper {
    background: var(--navy); border: 1px solid var(--border);
    border-radius: 12px; overflow: hidden; margin: 44px 0;
    overflow-x: auto;
  }
  .table-header {
    background: linear-gradient(135deg, var(--navy-mid), var(--navy-light));
    padding: 16px 24px; border-bottom: 1px solid var(--border);
    display: flex; align-items: center; gap: 10px;
  }
  .table-header span { font-size:11px; font-weight:900; letter-spacing:.12em; text-transform:uppercase; color:var(--blue); }
  table { width:100%; border-collapse:collapse; min-width: 480px; }
  th {
    text-align:left; padding:11px 20px; font-size:10px; font-weight:700;
    letter-spacing:.1em; text-transform:uppercase; color:var(--gray);
    background:var(--navy-mid); border-bottom:1px solid var(--border);
  }
  td { padding:13px 20px; font-size:14px; border-bottom:1px solid rgba(255,255,255,.04); color:var(--gray); vertical-align:top; }
  td:first-child { color:var(--white); font-weight:600; font-size:13.5px; }
  tr:last-child td { border-bottom:none; }
  tr:hover td { background:rgba(255,255,255,.02); }
  .pill-yes {
    display:inline-flex; align-items:center; gap:5px;
    font-size:10px; font-weight:700; padding:2px 9px; border-radius:3px;
    letter-spacing:.08em; text-transform:uppercase; font-family:'Inter',sans-serif;
    background:var(--blue-dim); color:var(--blue); border:1px solid rgba(144,205,244,.2);
  }
  .pill-no {
    display:inline-flex; align-items:center; gap:5px;
    font-size:10px; font-weight:700; padding:2px 9px; border-radius:3px;
    letter-spacing:.08em; text-transform:uppercase; font-family:'Inter',sans-serif;
    background:var(--coral-dim); color:var(--coral); border:1px solid rgba(227,117,98,.2);
  }

  /* ── WHEN GRID ── */
  .when-grid { display:grid; grid-template-columns:1fr 1fr; gap:16px; margin:28px 0; }
  @media(max-width:540px){ .when-grid { grid-template-columns:1fr; } }
  .when-card {
    background:var(--navy-light); border:1px solid var(--border);
    border-radius:12px; padding:22px 22px;
  }
  .when-card.yes { border-color:rgba(144,205,244,.28); }
  .when-card.no  { border-color:rgba(227,117,98,.24); }
  .when-label {
    font-size:11px; font-weight:900; letter-spacing:.12em; text-transform:uppercase;
    margin-bottom:14px; display:flex; align-items:center; gap:8px;
  }
  .when-card.yes .when-label { color:var(--blue); }
  .when-card.no  .when-label { color:var(--coral); }
  .when-list { list-style:none; display:flex; flex-direction:column; gap:9px; }
  .when-list li { font-size:14px; color:var(--gray); line-height:1.65; display:flex; gap:10px; align-items:flex-start; }
  .when-list li::before { flex-shrink:0; margin-top:1px; font-family:'JetBrains Mono',monospace; font-weight:700; font-size:12px; }
  .when-card.yes .when-list li::before { content:'✓'; color:var(--blue); }
  .when-card.no  .when-list li::before { content:'✗'; color:var(--coral); }
  .when-list li strong { color:var(--white); }

  /* ── CTA ── */
  .cta-block {
    background:linear-gradient(135deg, var(--navy-mid), rgba(26,32,44,.95));
    border:1px solid rgba(144,205,244,.26); border-radius:16px;
    padding:46px 38px; text-align:center; position:relative; overflow:hidden; margin-top:64px;
  }
  .cta-block::before {
    content:''; position:absolute; bottom:-80px; left:50%; transform:translateX(-50%);
    width:340px; height:340px;
    background:radial-gradient(circle, rgba(144,205,244,.08) 0%, transparent 70%);
    pointer-events:none;
  }
  .cta-block h2 { font-weight:900; font-size:clamp(19px,3.2vw,29px); color:var(--white); margin-bottom:11px; position:relative; }
  .cta-block p  { color:var(--gray); font-size:15px; max-width:500px; margin:0 auto 28px; line-height:1.8; position:relative; }
  .cta-btn {
    display:inline-block; background:var(--blue); color:var(--navy) !important;
    font-weight:900; font-size:13px; letter-spacing:.07em; text-transform:uppercase;
    text-decoration:none; padding:13px 34px; border-radius:8px;
    transition:transform .15s, box-shadow .15s; position:relative;
  }
  .cta-btn:hover { transform:translateY(-2px); box-shadow:0 8px 28px rgba(144,205,244,.26); }

  /* share row */
  .share-row { margin-top:42px; padding-top:24px; border-top:1px solid var(--border); display:flex; align-items:center; gap:9px; flex-wrap:wrap; }
  .share-label { font-size:11px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--gray); }
  .share-tag { background:var(--navy-mid); border:1px solid var(--border); color:var(--gray); font-size:12px; padding:4px 12px; border-radius:20px; text-decoration:none; transition:border-color .15s, color .15s; }
  .share-tag:hover { border-color:var(--blue); color:var(--blue); }

  @media(max-width:640px){
    .container { padding:0 18px; }
    .hero { padding:32px 0 28px; }
    .intro-block { padding:22px 20px; }
    td, th { padding:10px 14px; }
    .cta-block { padding:30px 20px; }
    .reading-pill { margin-left:0; }
  }
</style>
<!-- HERO -->
<header class="hero">
  <div class="container">
    <div class="meta-bar">
      <span class="badge">Kubernetes</span>
      <span class="badge">DevOps</span>
      <span class="badge coral">CronJobs</span>
      <span class="badge gray">Automatización</span>
      <div class="reading-pill">
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
        7 minutos de lectura
      </div>
    </div>
  </div>
</header>

<!-- CONTENT -->
<main class="content">
  <div class="container">

    <!-- INTRO -->
    <div class="intro-block">
      <p>Toda aplicación tiene tareas que necesitan ejecutarse de forma periódica: limpiar registros viejos, enviar reportes diarios, sincronizar datos con un sistema externo, renovar certificados. En el mundo pre-cloud esto se resolvía con un <code>crontab</code> en un servidor Linux. Simple, predecible, conocido.</p>
      <p>Cuando tu infraestructura se muda a Kubernetes, la pregunta inevitable aparece: <strong>¿dónde pongo mis cron jobs?</strong> La respuesta obvia son los CronJobs nativos de Kubernetes. Pero como todo en este ecosistema, &#8220;nativo&#8221; no siempre significa &#8220;simple&#8221;.</p>
      <p>Los CronJobs de Kubernetes son una herramienta poderosa con ventajas reales, pero también con trampas que pueden convertir una tarea de 5 líneas en un dolor de cabeza operativo. Vamos a desgranar ambos lados para que puedas decidir con información.</p>
    </div>

    <!-- ── VENTAJAS ── -->
    <div class="block-header pros">
      <div class="block-icon">✅</div>
      <div>
        <div class="block-title">Las ventajas: por qué los CronJobs de Kubernetes valen la pena</div>
        <div class="block-sub">6 razones para adoptarlos como primera opción</div>
      </div>
    </div>

    <!-- 01 -->
    <section class="section">
      <span class="section-number">// 01</span>
      <h2><span class="hex-icon">🏠</span>Viven donde vive tu aplicación</h2>
      <div class="section-divider"></div>
      <p>La ventaja más importante y la que justifica todo lo demás: tus jobs corren <strong>dentro del mismo cluster donde corre tu aplicación</strong>. Tienen acceso nativo a tus servicios internos, bases de datos, colas de mensajes, Secrets y ConfigMaps — sin necesidad de configurar VPNs, whitelists de IPs o credenciales externas.</p>
      <div class="callout callout-tip">
        <div class="callout-label">Acceso sin fricción</div>
        <p>Si tu tarea necesita conectarse a la base de datos de producción para limpiar registros, no necesitás abrir un túnel ni gestionar credenciales adicionales. El CronJob tiene <strong>el mismo acceso de red y los mismos mecanismos de autenticación</strong> que cualquier otro pod del cluster.</p>
      </div>
    </section>

    <!-- 02 -->
    <section class="section">
      <span class="section-number">// 02</span>
      <h2><span class="hex-icon">📈</span>Escalan con tu infraestructura</h2>
      <div class="section-divider"></div>
      <p>A diferencia de un crontab en un servidor fijo, los CronJobs se benefician del autoescalado del cluster. Si un job necesita más recursos de lo habitual, Karpenter puede levantar un nodo adicional para acomodarlo. <strong>Cuando el job termina, el nodo se libera.</strong> No estás pagando por un servidor dedicado idle el 95% del tiempo esperando que llegue la hora de ejecutar una tarea.</p>
    </section>

    <!-- 03 -->
    <section class="section">
      <span class="section-number">// 03</span>
      <h2><span class="hex-icon">🔁</span>Reintentos y gestión de fallos integrados</h2>
      <div class="section-divider"></div>
      <p>Kubernetes maneja automáticamente qué pasa cuando un job falla. Podés configurar <strong>cuántas veces debe reintentar</strong> antes de darse por vencido, qué hacer si la ejecución anterior no terminó cuando arranca la siguiente, y cuántos registros de ejecuciones pasadas mantener. Todo esto viene de fábrica — no tenés que programar lógica de reintentos ni escribir scripts de supervisión.</p>
    </section>

    <!-- 04 -->
    <section class="section">
      <span class="section-number">// 04</span>
      <h2><span class="hex-icon">📊</span>Observabilidad unificada</h2>
      <div class="section-divider"></div>
      <p>Si ya tenés un stack de monitoreo (Prometheus, Grafana, Loki), los CronJobs <strong>aparecen automáticamente en tus dashboards y logs</strong>. Podés ver cuándo se ejecutó cada job, cuánto tardó, si falló, y qué pasó — en la misma interfaz que usás para monitorear el resto de tu aplicación. No necesitás una herramienta separada para saber si tu tarea de las 3 AM realmente corrió.</p>
    </section>

    <!-- 05 -->
    <section class="section">
      <span class="section-number">// 05</span>
      <h2><span class="hex-icon">📁</span>Declarativos y versionables</h2>
      <div class="section-divider"></div>
      <p>La definición de un CronJob es un manifiesto de Kubernetes como cualquier otro. <strong>Vive en tu repositorio de Git, pasa por code review, se despliega con tu pipeline de CI/CD</strong>, y tiene historial de cambios. Si alguien cambia el schedule de un job de diario a cada hora por error, el diff está en el pull request.</p>
      <div class="callout callout-tip">
        <div class="callout-label">Comparalo con la alternativa</div>
        <p>Un <code>crontab -e</code> donde los cambios no dejan rastro, no tienen revisión, y solo el autor sabe qué hace cada línea. <strong>El manifiesto de Kubernetes es la documentación viva del job.</strong></p>
      </div>
    </section>

    <!-- 06 -->
    <section class="section">
      <span class="section-number">// 06</span>
      <h2><span class="hex-icon">🛡️</span>Aislamiento de recursos</h2>
      <div class="section-divider"></div>
      <p>Cada ejecución crea un pod nuevo con sus propios <code>requests</code> y <code>limits</code> de CPU y memoria. Si un job consume más recursos de lo esperado, <strong>no afecta a los demás pods del cluster</strong>. En un servidor tradicional, un cron que se come toda la RAM puede tumbar otros procesos que corren en la misma máquina.</p>
    </section>

    <hr class="block-sep">

    <!-- ── DESVENTAJAS ── -->
    <div class="block-header cons">
      <div class="block-icon">⚠️</div>
      <div>
        <div class="block-title">Las desventajas: lo que nadie te cuenta</div>
        <div class="block-sub">7 trampas que podés evitar si sabés que existen</div>
      </div>
    </div>

    <!-- 01 -->
    <section class="section">
      <span class="section-number coral-num">// 01</span>
      <h2><span class="hex-icon coral-icon">⏱️</span>La precisión del schedule no es exacta</h2>
      <div class="section-divider coral-div"></div>
      <p>Los CronJobs de Kubernetes <strong>no garantizan ejecución al segundo</strong>. El controlador revisa periódicamente si hay jobs que deban ejecutarse, y si hay alta carga en el API server, el job puede arrancar tarde. En la mayoría de los casos no importa — si tu reporte corre a las 6:00:03 en vez de 6:00:00, nadie lo nota.</p>
      <div class="callout callout-warning">
        <div class="callout-label">El límite de 100 ejecuciones perdidas</div>
        <p>Si el controlador detecta que se perdieron <strong>más de 100 ejecuciones consecutivas</strong> (porque el cluster estuvo caído o el job suspendido mucho tiempo), deja de intentar y requiere intervención manual. Es un límite poco conocido que puede sorprender en clusters que se apagan por períodos.</p>
      </div>
    </section>

    <!-- 02 -->
    <section class="section">
      <span class="section-number coral-num">// 02</span>
      <h2><span class="hex-icon coral-icon">⚡</span>Ejecuciones concurrentes pueden ser peligrosas</h2>
      <div class="section-divider coral-div"></div>
      <p>Por defecto, Kubernetes puede lanzar una nueva ejecución de un CronJob <strong>incluso si la anterior todavía está corriendo</strong>. Si tu tarea tarda más de lo esperado y el schedule es ajustado, podés terminar con múltiples instancias del mismo job corriendo simultáneamente.</p>
      <div class="callout callout-warning">
        <div class="callout-label">El riesgo real</div>
        <p>Imaginá dos instancias simultáneas de un job que procesa pagos pendientes o que envía emails a clientes. Kubernetes te da la opción de configurar la <strong>política de concurrencia</strong> (prohibir, reemplazar o permitir), pero tenés que saber que existe y configurarla explícitamente — no se resuelve solo.</p>
      </div>
    </section>

    <!-- 03 -->
    <section class="section">
      <span class="section-number coral-num">// 03</span>
      <h2><span class="hex-icon coral-icon">🔗</span>No hay dependencias entre jobs</h2>
      <div class="section-divider coral-div"></div>
      <p>Si tenés un job que necesita ejecutarse después de que otro termine (exportar datos → procesarlos → enviar reporte), los CronJobs de Kubernetes <strong>no tienen forma nativa de expresar esa dependencia</strong>. Cada CronJob es independiente.</p>
      <div class="callout callout-tip">
        <div class="callout-label">Alternativas para orquestación</div>
        <p>Para orquestar cadenas de tareas con dependencias, necesitás herramientas adicionales como <strong>Argo Workflows</strong> o <strong>Apache Airflow</strong>. Los CronJobs son un cron, no un workflow engine.</p>
      </div>
    </section>

    <!-- 04 -->
    <section class="section">
      <span class="section-number coral-num">// 04</span>
      <h2><span class="hex-icon coral-icon">🐛</span>Debugging puede ser frustrante</h2>
      <div class="section-divider coral-div"></div>
      <p>Cuando un CronJob falla, el pod que lo ejecutó eventualmente se limpia. <strong>Sin logs centralizados, los logs desaparecen con él.</strong> Los errores más comunes — imagen de Docker que no se puede pullear, secrets que no existen, permisos insuficientes — producen mensajes no siempre intuitivos.</p>
      <div class="callout callout-warning">
        <div class="callout-label">El ciclo de feedback lento</div>
        <p>Como los CronJobs corren en horarios específicos — muchas veces de noche o madrugada — el ciclo de feedback para debuggear es lento: hacés un cambio, <strong>esperás a que se ejecute, ves si funcionó, repetís</strong>. Sin Loki u otro agregador de logs, el historial de errores simplemente no existe.</p>
      </div>
    </section>

    <!-- 05 -->
    <section class="section">
      <span class="section-number coral-num">// 05</span>
      <h2><span class="hex-icon coral-icon">🌍</span>La gestión de timezones es relativamente nueva</h2>
      <div class="section-divider coral-div"></div>
      <p>Durante años, los CronJobs usaban la timezone del nodo donde corría el controlador — y no había forma de especificar otra. Recién en versiones recientes de Kubernetes se agregó soporte para configurar la timezone del schedule, pero <strong>muchos clusters todavía corren versiones donde esto es beta o no está disponible</strong>.</p>
      <div class="callout callout-warning">
        <div class="callout-label">Un error clásico de todos los equipos</div>
        <p>Si tu equipo está en Argentina (UTC-3), tu cluster corre en UTC, y tu cron debe ejecutarse a las 8 AM hora local, necesitás hacer la conversión mental en cada cambio. Parece menor, pero <strong>es una fuente constante de errores de configuración y confusion</strong> que afecta a casi todos los equipos que no son UTC.</p>
      </div>
    </section>

    <!-- 06 -->
    <section class="section">
      <span class="section-number coral-num">// 06</span>
      <h2><span class="hex-icon coral-icon">💸</span>Los recursos se consumen aunque el job dure segundos</h2>
      <div class="section-divider coral-div"></div>
      <p>Cada ejecución crea un pod que requiere un nodo con capacidad disponible. Si tu cluster está ajustado y no hay espacio, <strong>el job queda pendiente hasta que el autoscaler levante un nodo nuevo</strong> — lo cual puede tardar minutos. Para jobs que tardan segundos en ejecutarse, esperar 2-3 minutos por un nodo es absurdo.</p>
      <div class="callout callout-warning">
        <div class="callout-label">El spike de medianoche</div>
        <p>Si tenés muchos CronJobs que se ejecutan al mismo horario (varios jobs a medianoche), podés generar un <strong>spike de demanda que levante nodos innecesariamente</strong>. Esos nodos tardan minutos en estar listos, ejecutan jobs de segundos, y quedan idle hasta que el autoscaler los elimine. Distribuir los horarios mitiga esto.</p>
      </div>
    </section>

    <!-- 07 -->
    <section class="section">
      <span class="section-number coral-num">// 07</span>
      <h2><span class="hex-icon coral-icon">🔧</span>No reemplazan un scheduler real</h2>
      <div class="section-divider coral-div"></div>
      <p>Para tareas simples y periódicas, los CronJobs son perfectos. Pero si tus necesidades crecen — <strong>reintentos condicionales, dependencias entre tareas, ejecuciones manuales con parámetros, historial detallado, notificaciones por canal</strong> — vas a necesitar herramientas adicionales. Los CronJobs son un cron, no un workflow engine.</p>
    </section>

    <hr class="block-sep">

    <!-- ── CUÁNDO SÍ / CUÁNDO NO ── -->
    <div class="when-grid">
      <div class="when-card yes">
        <div class="when-label">✓ Usá CronJobs cuando</div>
        <ul class="when-list">
          <li>La tarea es <strong>simple y autónoma</strong> — no depende de que otra termine primero</li>
          <li>El schedule es fijo y no necesitás precisión de subsegundos</li>
          <li>La tarea necesita <strong>acceso a recursos internos</strong> del cluster</li>
          <li>Querés que el job viva en el mismo pipeline de CI/CD que tu app</li>
          <li>La duración del job es predecible entre ejecuciones</li>
        </ul>
      </div>
      <div class="when-card no">
        <div class="when-label">✗ Considerá alternativas cuando</div>
        <ul class="when-list">
          <li>Necesitás <strong>orquestar cadenas de tareas</strong> con dependencias (Argo Workflows, Airflow)</li>
          <li>La tarea requiere precisión de timing al segundo</li>
          <li>Necesitás un historial detallado con UI de gestión y <strong>reintentos manuales con parámetros</strong></li>
          <li>La tarea no tiene relación con el cluster — podría correr en Lambda o Step Functions</li>
        </ul>
      </div>
    </div>

    <!-- TABLA RESUMEN -->
    <div class="table-wrapper">
      <div class="table-header">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#90cdf4" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
        <span>Resumen — Características clave de los CronJobs de Kubernetes</span>
      </div>
      <table>
        <thead>
          <tr>
            <th>Característica</th>
            <th>¿Viene de fábrica?</th>
            <th>Notas</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>Acceso a servicios internos</td>
            <td><span class="pill-yes">✓ Sí</span></td>
            <td>Misma red y autenticación que el resto del cluster</td>
          </tr>
          <tr>
            <td>Reintentos automáticos</td>
            <td><span class="pill-yes">✓ Sí</span></td>
            <td>Configurable con <code>backoffLimit</code></td>
          </tr>
          <tr>
            <td>Logs y observabilidad</td>
            <td><span class="pill-yes">✓ Sí</span></td>
            <td>Requiere agregador de logs para persistir tras el pod</td>
          </tr>
          <tr>
            <td>Versionable en Git</td>
            <td><span class="pill-yes">✓ Sí</span></td>
            <td>Como cualquier manifiesto de Kubernetes</td>
          </tr>
          <tr>
            <td>Control de concurrencia</td>
            <td><span class="pill-yes">✓ Sí</span></td>
            <td>Hay que configurarlo explícitamente — no es el default seguro</td>
          </tr>
          <tr>
            <td>Precisión al segundo</td>
            <td><span class="pill-no">✗ No</span></td>
            <td>El controlador puede tener delay según la carga del API server</td>
          </tr>
          <tr>
            <td>Dependencias entre jobs</td>
            <td><span class="pill-no">✗ No</span></td>
            <td>Requiere Argo Workflows u otra herramienta de orquestación</td>
          </tr>
          <tr>
            <td>Timezone configurable</td>
            <td><span class="pill-no">✗ Parcial</span></td>
            <td>Solo en versiones recientes de Kubernetes — verificar en tu cluster</td>
          </tr>
        </tbody>
      </table>
    </div>

    <!-- CTA -->
    <div class="cta-block">
      <h2>Implementar CronJobs no debería ser complicado</h2>
      <p>Para el 80% de los casos — limpieza de datos, reportes diarios, sincronizaciones periódicas — los CronJobs de Kubernetes son la herramienta correcta. En SleakOps podés definirlos y desplegarlos de forma directa, con la configuración de recursos, secrets, schedules y políticas de concurrencia integrada en un flujo claro.</p>
      <a href="#" class="cta-btn">Conocé SleakOps →</a>
    </div>

    <!-- TAGS -->
    <div class="share-row">
      <span class="share-label">Tags:</span>
      <a href="#" class="share-tag">#Kubernetes</a>
      <a href="#" class="share-tag">#CronJobs</a>
      <a href="#" class="share-tag">#DevOps</a>
      <a href="#" class="share-tag">#Automatización</a>
      <a href="#" class="share-tag">#CloudNative</a>
      <a href="#" class="share-tag">#EKS</a>
      <a href="#" class="share-tag">#Jobs</a>
    </div>

  </div>
</main>
</div><p>The post <a href="https://sleakops.com/2026/03/20/ventajas-y-desventajas-de-usar-cronjobs-en-kubernetes-es-la-herramienta-correcta-para-tu-caso/">CronJobs en Kubernetes: Ventajas, Retos y Alternativas</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p><p>La entrada <a href="https://sleakops.com/2026/03/20/ventajas-y-desventajas-de-usar-cronjobs-en-kubernetes-es-la-herramienta-correcta-para-tu-caso/">CronJobs en Kubernetes: Ventajas, Retos y Alternativas</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Grafana Loki en Kubernetes: Guía de Observabilidad</title>
		<link>https://sleakops.com/2026/03/20/observabilidad-con-stack-open-source-grafana-loki-en-kubernetes/</link>
		
		<dc:creator><![CDATA[matias]]></dc:creator>
		<pubDate>Fri, 20 Mar 2026 11:51:13 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://sleakops.com/?p=1919</guid>

					<description><![CDATA[<p>Kubernetes Observabilidad Grafana · Loki · Prometheus FinOps 7 minutos de lectura Hay una pregunta que separa a los equipos que operan infraestructura con confianza de los que viven apagando incendios: ¿cuánta memoria y CPU deberían tener tus pods? Si la respuesta de tu equipo es &#8220;los pusimos en 512Mi y 500m porque era el [&#8230;]</p>
<p>The post <a href="https://sleakops.com/2026/03/20/observabilidad-con-stack-open-source-grafana-loki-en-kubernetes/">Grafana Loki en Kubernetes: Guía de Observabilidad</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p>
<p>La entrada <a href="https://sleakops.com/2026/03/20/observabilidad-con-stack-open-source-grafana-loki-en-kubernetes/">Grafana Loki en Kubernetes: Guía de Observabilidad</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="sleakops-post">
<style>
:root {
    --navy:      #1a202c;
    --navy-light:#232c3e;
    --navy-mid:  #2d3748;
    --blue:      #90cdf4;
    --blue-dim:  rgba(144,205,244,0.09);
    --coral:     #e37562;
    --coral-dim: rgba(227,117,98,0.10);
    --white:     #ffffff;
    --gray:      #a0aec0;
    --gray-dim:  rgba(160,174,192,0.09);
    --border:    rgba(144,205,244,0.13);
  }

  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

  .sleakops-post {
    font-family: 'Inter', sans-serif;
        color: var(--white);
    line-height: 1.75;
    font-size: 16px;
    overflow-x: hidden;
  }

  /* ── HERO ── */
  .hero {
    background: var(--navy);
    padding: 44px 0 40px;
    border-bottom: 1px solid var(--border);
    position: relative; overflow: hidden;
  }
  .hero::before {
    content: ''; position: absolute; inset: 0;
    background-image:
      repeating-linear-gradient( 60deg, transparent, transparent 38px, rgba(144,205,244,.03) 38px, rgba(144,205,244,.03) 40px),
      repeating-linear-gradient(-60deg, transparent, transparent 38px, rgba(144,205,244,.03) 38px, rgba(144,205,244,.03) 40px);
    pointer-events: none;
  }
  .hero::after {
    content: ''; position: absolute; top: -110px; right: -110px;
    width: 400px; height: 400px;
    background: radial-gradient(circle, rgba(144,205,244,.08) 0%, transparent 70%);
    pointer-events: none;
  }

  .container { max-width: 820px; margin: 0 auto; padding: 0 32px; position: relative; z-index: 1; }

  .meta-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
  .badge {
    font-size: 10px; font-weight: 900; letter-spacing: .14em; text-transform: uppercase;
    padding: 4px 11px; border-radius: 4px;
    background: var(--blue-dim); color: var(--blue); border: 1px solid rgba(144,205,244,.26);
  }
  .badge.coral { background: var(--coral-dim); color: var(--coral); border-color: rgba(227,117,98,.26); }
  .badge.gray  { background: var(--gray-dim);  color: var(--gray);  border-color: rgba(160,174,192,.2); }
  .reading-pill {
    margin-left: auto;
    display: inline-flex; align-items: center; gap: 7px;
    background: var(--navy-mid); border: 1px solid var(--border);
    border-radius: 32px; padding: 6px 15px; font-size: 12px; color: var(--gray);
  }
  .reading-pill svg { color: var(--blue); flex-shrink: 0; }

  /* ── CONTENT ── */
  .content { padding: 52px 0 80px; }

  /* intro */
  .intro-block {
    background: linear-gradient(135deg, var(--navy-light), var(--navy-mid));
    border: 1px solid var(--border); border-radius: 12px;
    padding: 30px 34px; margin-bottom: 56px;
    position: relative; overflow: hidden;
  }
  .intro-block::before {
    content: ''; position: absolute; top: 0; left: 0;
    width: 4px; height: 100%;
    background: linear-gradient(to bottom, var(--blue), var(--coral));
  }
  .intro-block p { color: var(--gray); font-size: 15.5px; line-height: 1.85; margin-bottom: 13px; }
  .intro-block p:last-child { margin-bottom: 0; }
  .intro-block strong { color: var(--white); font-weight: 600; }

  /* ── SECTIONS ── */
  .section { margin-bottom: 56px; }
  .section-number {
    display: inline-block; font-family: 'JetBrains Mono', monospace;
    font-size: 11px; font-weight: 600; color: var(--blue);
    letter-spacing: .1em; margin-bottom: 8px; opacity: .8;
  }
  .section h2 {
    font-weight: 900; font-size: clamp(18px,2.8vw,24px);
    color: var(--white); line-height: 1.25; margin-bottom: 6px;
    display: flex; align-items: flex-start; gap: 12px;
  }
  .hex-icon {
    flex-shrink: 0; width: 36px; height: 36px;
    background: var(--blue-dim); border: 1px solid rgba(144,205,244,.2);
    border-radius: 8px; display: flex; align-items: center; justify-content: center;
    font-size: 16px; margin-top: 2px;
  }
  .section-divider { width: 44px; height: 3px; background: linear-gradient(to right, var(--blue), transparent); margin: 10px 0 20px; border-radius: 2px; }
  .section p { color: var(--gray); margin-bottom: 15px; line-height: 1.85; font-size: 15.5px; }
  .section p strong { color: var(--white); font-weight: 600; }
  .section p:last-child { margin-bottom: 0; }

  /* questions list */
  .q-list {
    list-style: none; margin: 16px 0 20px;
    display: flex; flex-direction: column; gap: 8px;
  }
  .q-list li {
    display: flex; align-items: flex-start; gap: 12px;
    color: var(--gray); font-size: 15px; line-height: 1.7;
    background: var(--navy-light); border: 1px solid var(--border);
    border-radius: 8px; padding: 12px 16px;
  }
  .q-list li::before {
    content: '?'; color: var(--coral); font-weight: 900; flex-shrink: 0;
    font-family: 'JetBrains Mono', monospace; font-size: 13px; margin-top: 1px;
  }

  /* callouts */
  .callout {
    border-radius: 10px; padding: 20px 24px; margin: 20px 0;
    border: 1px solid; position: relative; overflow: hidden;
  }
  .callout-warning { background: rgba(227,117,98,.07); border-color: rgba(227,117,98,.27); }
  .callout-warning::after { content:'⚠'; position:absolute; top:12px; right:16px; font-size:18px; opacity:.22; }
  .callout-tip    { background: var(--blue-dim); border-color: rgba(144,205,244,.22); }
  .callout-tip::after    { content:'💡'; position:absolute; top:12px; right:16px; font-size:16px; opacity:.4; }
  .callout-label { font-size:10px; font-weight:900; letter-spacing:.14em; text-transform:uppercase; margin-bottom:8px; }
  .callout-warning .callout-label { color: var(--coral); }
  .callout-tip    .callout-label { color: var(--blue); }
  .callout p { font-size:14.5px !important; margin-bottom:8px !important; line-height:1.75 !important; }
  .callout p:last-child { margin-bottom:0 !important; }
  .callout strong { color: var(--white); font-weight:600; }

  /* styled list */
  .styled-list { list-style:none; margin:14px 0 18px; display:flex; flex-direction:column; gap:10px; }
  .styled-list li { display:flex; align-items:flex-start; gap:12px; color:var(--gray); font-size:15px; line-height:1.72; }
  .styled-list li::before { content:'→'; color:var(--blue); font-weight:700; flex-shrink:0; margin-top:1px; font-family:'JetBrains Mono',monospace; }
  .styled-list li strong { color:var(--white); }

  code {
    font-family:'JetBrains Mono',monospace; font-size:13px;
    background:var(--navy-mid); color:var(--blue);
    padding:2px 6px; border-radius:4px; border:1px solid var(--border);
  }

  /* ── STACK GRID (Prometheus / Loki / Grafana) ── */
  .stack-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; margin: 24px 0; }
  @media(max-width:580px){ .stack-grid { grid-template-columns: 1fr; } }
  .stack-card {
    background: var(--navy-light); border: 1px solid var(--border);
    border-radius: 10px; padding: 20px 18px; transition: border-color .2s;
  }
  .stack-card:hover { border-color: rgba(144,205,244,.35); }
  .stack-name { font-family: 'JetBrains Mono', monospace; font-size: 13px; font-weight: 600; color: var(--blue); margin-bottom: 6px; }
  .stack-role { font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--gray); margin-bottom: 10px; opacity: .7; }
  .stack-desc { font-size: 13.5px; color: var(--gray); line-height: 1.65; }
  .stack-desc strong { color: var(--white); }

  /* ── COST COMPARISON ── */
  .cost-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 22px 0; }
  @media(max-width:540px){ .cost-grid { grid-template-columns: 1fr; } }
  .cost-card {
    background: var(--navy-light); border: 1px solid var(--border);
    border-radius: 10px; padding: 18px 20px;
  }
  .cost-card.expensive { border-color: rgba(227,117,98,.25); }
  .cost-card.cheap     { border-color: rgba(144,205,244,.28); }
  .cost-tool { font-size: 13px; font-weight: 700; color: var(--white); margin-bottom: 4px; }
  .cost-price {
    font-family: 'JetBrains Mono', monospace; font-size: 18px; font-weight: 600;
    margin-bottom: 6px;
  }
  .cost-card.expensive .cost-price { color: var(--coral); }
  .cost-card.cheap     .cost-price { color: var(--blue); }
  .cost-note { font-size: 12.5px; color: var(--gray); line-height: 1.6; }

  /* ── DASHBOARD PREVIEW ── */
  .dashboard-preview {
    background: var(--navy-light); border: 1px solid var(--border);
    border-radius: 12px; padding: 24px 26px; margin: 22px 0;
    position: relative; overflow: hidden;
  }
  .dashboard-preview::before {
    content: ''; position: absolute; top: 0; left: 0; right: 0;
    height: 3px;
    background: linear-gradient(to right, var(--blue), var(--coral));
  }
  .dp-label {
    font-size: 10px; font-weight: 900; letter-spacing: .14em; text-transform: uppercase;
    color: var(--blue); margin-bottom: 16px;
  }
  .dp-rows { display: flex; flex-direction: column; gap: 10px; }
  .dp-row {
    display: flex; align-items: center; gap: 14px;
    padding: 10px 14px; border-radius: 8px;
    background: var(--navy-mid); border: 1px solid rgba(255,255,255,.05);
  }
  .dp-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
  .dp-dot.green  { background: #68d391; }
  .dp-dot.yellow { background: #f6e05e; }
  .dp-dot.red    { background: var(--coral); }
  .dp-metric { font-size: 13px; color: var(--gray); flex: 1; line-height: 1.4; }
  .dp-metric strong { color: var(--white); }
  .dp-value { font-family: 'JetBrains Mono', monospace; font-size: 12px; font-weight: 600; color: var(--blue); white-space: nowrap; }

  /* ── COMPARISON TABLES ── */
  .table-wrapper {
    background: var(--navy); border: 1px solid var(--border);
    border-radius: 12px; overflow: hidden; margin: 32px 0;
    overflow-x: auto;
  }
  .table-header {
    background: linear-gradient(135deg, var(--navy-mid), var(--navy-light));
    padding: 16px 24px; border-bottom: 1px solid var(--border);
    display: flex; align-items: center; gap: 10px;
  }
  .table-header span { font-size:11px; font-weight:900; letter-spacing:.12em; text-transform:uppercase; color:var(--blue); }
  table { width:100%; border-collapse:collapse; min-width: 440px; }
  th { text-align:left; padding:11px 20px; font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--gray); background:var(--navy-mid); border-bottom:1px solid var(--border); }
  td { padding:12px 20px; font-size:14px; border-bottom:1px solid rgba(255,255,255,.04); color:var(--gray); vertical-align:top; }
  td:first-child { color:var(--white); font-weight:600; font-size:13.5px; }
  tr:last-child td { border-bottom:none; }
  tr:hover td { background:rgba(255,255,255,.02); }
  .td-bad  { color: var(--coral) !important; font-family: 'Inter', sans-serif; }
  .td-good { color: var(--blue) !important; font-family: 'Inter', sans-serif; }

  /* ── CTA ── */
  .cta-block {
    background: linear-gradient(135deg, var(--navy-mid), rgba(26,32,44,.95));
    border: 1px solid rgba(144,205,244,.26); border-radius: 16px;
    padding: 46px 38px; text-align: center; position: relative; overflow: hidden; margin-top: 64px;
  }
  .cta-block::before {
    content:''; position:absolute; bottom:-80px; left:50%; transform:translateX(-50%);
    width:340px; height:340px;
    background:radial-gradient(circle, rgba(144,205,244,.08) 0%, transparent 70%);
    pointer-events:none;
  }
  .cta-price {
    font-family: 'JetBrains Mono', monospace;
    font-size: 42px; font-weight: 600; color: var(--blue);
    margin-bottom: 4px; position: relative;
  }
  .cta-price-label { font-size: 13px; color: var(--gray); margin-bottom: 20px; position: relative; }
  .cta-block h2 { font-weight:900; font-size:clamp(19px,3.2vw,29px); color:var(--white); margin-bottom:11px; position:relative; }
  .cta-block p  { color:var(--gray); font-size:15px; max-width:500px; margin:0 auto 28px; line-height:1.8; position:relative; }
  .cta-btn {
    display:inline-block; background:var(--blue); color:var(--navy) !important;
    font-weight:900; font-size:13px; letter-spacing:.07em; text-transform:uppercase;
    text-decoration:none; padding:13px 34px; border-radius:8px;
    transition:transform .15s, box-shadow .15s; position:relative;
  }
  .cta-btn:hover { transform:translateY(-2px); box-shadow:0 8px 28px rgba(144,205,244,.26); }

  /* share row */
  .share-row { margin-top:42px; padding-top:24px; border-top:1px solid var(--border); display:flex; align-items:center; gap:9px; flex-wrap:wrap; }
  .share-label { font-size:11px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--gray); }
  .share-tag { background:var(--navy-mid); border:1px solid var(--border); color:var(--gray); font-size:12px; padding:4px 12px; border-radius:20px; text-decoration:none; transition:border-color .15s, color .15s; }
  .share-tag:hover { border-color:var(--blue); color:var(--blue); }

  @media(max-width:640px){
    .container { padding:0 18px; }
    .hero { padding:32px 0 28px; }
    .intro-block { padding:22px 20px; }
    td, th { padding:10px 14px; }
    .cta-block { padding:30px 20px; }
    .reading-pill { margin-left:0; }
    .cta-price { font-size:32px; }
  }
</style>
<!-- HERO -->
<header class="hero">
  <div class="container">
    <div class="meta-bar">
      <span class="badge">Kubernetes</span>
      <span class="badge">Observabilidad</span>
      <span class="badge coral">Grafana · Loki · Prometheus</span>
      <span class="badge gray">FinOps</span>
      <div class="reading-pill">
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
        7 minutos de lectura
      </div>
    </div>
  </div>
</header>

<!-- CONTENT -->
<main class="content">
  <div class="container">

    <!-- INTRO -->
    <div class="intro-block">
      <p>Hay una pregunta que separa a los equipos que operan infraestructura con confianza de los que viven apagando incendios: <strong>¿cuánta memoria y CPU deberían tener tus pods?</strong></p>
      <p>Si la respuesta de tu equipo es &#8220;los pusimos en 512Mi y 500m porque era el default&#8221; o &#8220;los subimos hasta que dejó de crashear&#8221;, no estás solo. El resultado es siempre el mismo: <strong>pods que consumen el doble de lo que necesitan</strong> e inflan tu factura, o pods que se quedan cortos y mueren con <code>OOMKilled</code> a las 3 AM.</p>
      <p>La solución existe y es open source: <strong>Grafana + Loki + Prometheus</strong>. El problema, como siempre, no es la tecnología — es todo lo que implica tenerla corriendo de forma útil y mantenible.</p>
    </div>

    <!-- 01 POR QUÉ NECESITÁS OBSERVABILIDAD -->
    <section class="section">
      <span class="section-number">// 01</span>
      <h2><span class="hex-icon">🔍</span>¿Por qué necesitás observabilidad en Kubernetes?</h2>
      <div class="section-divider"></div>

      <p>Kubernetes es una caja negra por diseño. Abstrae la infraestructura para que los desarrolladores no tengan que pensar en servidores. Pero esa abstracción tiene un costo: <strong>sin herramientas de observabilidad, no tenés idea de qué está pasando adentro.</strong></p>

      <p>Sin observabilidad, estas preguntas no tienen respuesta:</p>

      <ul class="q-list">
        <li>¿Cuánta memoria está usando realmente mi aplicación versus lo que le asigné?</li>
        <li>¿Mis pods están siendo CPU-throttled sin que nadie lo note?</li>
        <li>¿Por qué el servicio de pagos empezó a responder lento hace 20 minutos?</li>
        <li>¿Qué pod generó ese error que reportó el cliente?</li>
        <li>¿Estoy pagando por nodos que están al 20% de utilización?</li>
      </ul>

      <div class="callout callout-warning">
        <div class="callout-label">La más importante de todas</div>
        <p>¿Mis <code>requests</code> y <code>limits</code> de CPU/memoria están bien configurados, o estoy <strong>tirando plata o generando inestabilidad</strong>? Sin métricas históricas, esta pregunta no tiene respuesta posible.</p>
      </div>
    </section>

    <!-- 02 LOGS EFÍMEROS -->
    <section class="section">
      <span class="section-number">// 02</span>
      <h2><span class="hex-icon">💨</span>El problema invisible: tus logs desaparecen</h2>
      <div class="section-divider"></div>

      <p>Antes de hablar de dashboards, hay un problema más fundamental que muchos equipos descubren de la peor manera: <strong>en Kubernetes, los logs son efímeros.</strong></p>

      <p>Cuando un pod se reinicia, se escala, se mueve a otro nodo o simplemente muere — y en Kubernetes esto pasa todo el tiempo — sus logs desaparecen con él. No hay disco persistente que los guarde. Se fueron.</p>

      <div class="callout callout-warning">
        <div class="callout-label">La escena del crimen ya fue limpiada</div>
        <p>Si tu aplicación tuvo un error a las 2 AM y el pod se recicló automáticamente, cuando tu equipo llega a las 9 AM a investigar, <strong>no hay nada que mirar</strong>. El pod que falló ya no existe, fue reemplazado por uno nuevo y sano que no tiene idea de lo que pasó antes.</p>
      </div>

      <p>El problema se multiplica con Spot Instances (que se interrumpen sin aviso) y Karpenter con consolidación activa (que mueve pods entre nodos constantemente). <strong>Cuanto más dinámica es tu infraestructura, más frecuentemente desaparecen tus logs.</strong></p>
    </section>

    <!-- 03 OPCIONES Y COSTOS -->
    <section class="section">
      <span class="section-number">// 03</span>
      <h2><span class="hex-icon">💸</span>Las opciones para persistir logs (y sus costos)</h2>
      <div class="section-divider"></div>

      <p>La industria tiene soluciones, pero la mayoría viene con una factura considerable:</p>

      <div class="cost-grid">
        <div class="cost-card expensive">
          <div class="cost-tool">Datadog Logs</div>
          <div class="cost-price">USD 500–2.000</div>
          <div class="cost-note">mensuales solo por logs para un equipo mediano. Sin contar métricas ni APM.</div>
        </div>
        <div class="cost-card expensive">
          <div class="cost-tool">Elastic Cloud (ELK)</div>
          <div class="cost-price">USD 300–800</div>
          <div class="cost-note">mensuales de infraestructura para volumen moderado. Mantenerlo estable es casi un trabajo full-time.</div>
        </div>
        <div class="cost-card expensive">
          <div class="cost-tool">CloudWatch Logs</div>
          <div class="cost-price">Escala rápido</div>
          <div class="cost-note">Los costos de ingesta y almacenamiento crecen con el volumen. Experiencia de búsqueda y correlación limitada.</div>
        </div>
        <div class="cost-card cheap">
          <div class="cost-tool">Loki sobre S3</div>
          <div class="cost-price">~USD 30</div>
          <div class="cost-note">mensuales para el stack completo. Storage en S3 a centavos por GB, sin nodos dedicados.</div>
        </div>
      </div>

      <div class="callout callout-warning">
        <div class="callout-label">El resultado en la mayoría de los equipos</div>
        <p>O pagan de más por logs, o directamente <strong>no los persisten y esperan que los problemas no ocurran fuera del horario laboral</strong>. Ninguna de las dos opciones es buena.</p>
      </div>
    </section>

    <!-- 04 EL STACK -->
    <section class="section">
      <span class="section-number">// 04</span>
      <h2><span class="hex-icon">🧩</span>El stack open source: Grafana + Loki + Prometheus</h2>
      <div class="section-divider"></div>

      <div class="stack-grid">
        <div class="stack-card">
          <div class="stack-name">Prometheus</div>
          <div class="stack-role">métricas</div>
          <div class="stack-desc">Recolecta métricas de tu cluster y aplicaciones. Uso de <strong>CPU, memoria, red, disco, latencia, error rates</strong> — todo lo que se puede medir, lo almacena como series temporales.</div>
        </div>
        <div class="stack-card">
          <div class="stack-name">Loki</div>
          <div class="stack-role">logs</div>
          <div class="stack-desc">Como &#8220;Prometheus pero para logs&#8221;. Indexa solo labels (pod, namespace, container) y almacena logs comprimidos en <strong>S3 como backend</strong> — centavos por GB, sin nodos dedicados.</div>
        </div>
        <div class="stack-card">
          <div class="stack-name">Grafana</div>
          <div class="stack-role">visualización</div>
          <div class="stack-desc">Dashboards, alertas, exploración de logs, correlación de métricas con eventos — <strong>todo en una sola interfaz</strong> que une Prometheus y Loki.</div>
        </div>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">La clave de Loki</div>
        <p>A diferencia de Elasticsearch, Loki no indexa el contenido completo de cada línea de log. Indexa solo los labels y almacena los logs comprimidos. Esto lo hace <strong>dramáticamente más barato de operar</strong>. Y al usar S3 como backend, el storage es el más barato y duradero de AWS.</p>
      </div>

      <p>Es 100% open source — sin licencias por usuario, sin costos por volumen de ingesta, sin sorpresas en la factura. Es el estándar de facto para observabilidad en Kubernetes. <strong>¿El problema? Instalarlo es fácil. Tenerlo configurado para que sea realmente útil es otra historia.</strong></p>
    </section>

    <!-- 05 LO QUE IMPLICA -->
    <section class="section">
      <span class="section-number">// 05</span>
      <h2><span class="hex-icon">⚙️</span>Lo que implica tener este stack funcionando de verdad</h2>
      <div class="section-divider"></div>

      <p>La instalación no es un <code>helm install</code> y listo. El stack tiene múltiples componentes que necesitan coordinarse:</p>

      <ul class="styled-list">
        <li><strong>Prometheus:</strong> decidir cuántos días de retención, cuánto storage asignar, cada cuánto scrappear métricas (más frecuente = más storage = más costo), y cómo dimensionar la memoria para que no se caiga bajo carga.</li>
        <li><strong>Loki + agente recolector:</strong> configuración de almacenamiento en S3 (bucket, permisos IAM, esquema de indexación), retención de logs, límites de queries para que una búsqueda pesada no tumbe el servicio, y un agente corriendo en cada nodo.</li>
        <li><strong>Grafana:</strong> persistencia para no perder dashboards ante reinicios, configuración de data sources apuntando a Prometheus y Loki, y mecanismos para cargar dashboards automáticamente.</li>
      </ul>

      <div class="callout callout-warning">
        <div class="callout-label">El error de networking que nadie cuenta</div>
        <p>Un error en la configuración de networking puede significar que Loki esté enviando tráfico por internet público en lugar de por la red interna — convirtiendo un costo de centavos en una <strong>factura de cientos de dólares por data transfer</strong>.</p>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">El costo real en tiempo</div>
        <p>Un equipo que lo hace por primera vez invierte fácilmente <strong>2 a 4 semanas</strong> antes de tener dashboards realmente útiles. Y muchos nunca llegan al punto donde la observabilidad les dice cosas accionables — se quedan en &#8220;tenemos Grafana instalado&#8221; sin que nadie lo use.</p>
      </div>
    </section>

    <!-- 06 CASO PRÁCTICO -->
    <section class="section">
      <span class="section-number">// 06</span>
      <h2><span class="hex-icon">📐</span>Caso práctico: configurar requests y limits correctamente</h2>
      <div class="section-divider"></div>

      <p>Supongamos que tenés un servicio de API con <code>512Mi</code> de memory request y <code>500m</code> de CPU request. <strong>Sin Grafana, esta configuración es una apuesta.</strong> ¿Está bien? ¿Es mucho? ¿Es poco? No hay forma de saberlo sin datos.</p>

      <p>Con un dashboard bien configurado, descubrís esto:</p>

      <div class="dashboard-preview">
        <div class="dp-label">Dashboard — API Service · uso real vs. configurado</div>
        <div class="dp-rows">
          <div class="dp-row">
            <div class="dp-dot green"></div>
            <div class="dp-metric"><strong>Memoria — uso real:</strong> 180–220 Mi en operación normal, picos de 350 Mi en deploys</div>
            <div class="dp-value">request: 512Mi</div>
          </div>
          <div class="dp-row">
            <div class="dp-dot yellow"></div>
            <div class="dp-metric"><strong>Memoria — desperdicio:</strong> el request está un 60% por encima del uso real</div>
            <div class="dp-value">→ bajar a 256Mi</div>
          </div>
          <div class="dp-row">
            <div class="dp-dot green"></div>
            <div class="dp-metric"><strong>CPU — uso real:</strong> raramente supera los 80m (milicores) bajo carga normal</div>
            <div class="dp-value">request: 500m</div>
          </div>
          <div class="dp-row">
            <div class="dp-dot red"></div>
            <div class="dp-metric"><strong>CPU — desperdicio:</strong> reservás 6× más CPU de la que usás en cada réplica</div>
            <div class="dp-value">→ bajar a 100m</div>
          </div>
        </div>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">El impacto en tu factura</div>
        <p>Kubernetes cree que tus pods necesitan mucho más de lo que realmente usan. Karpenter provisiona nodos más grandes de lo necesario. <strong>Multiplicá esta corrección por cada servicio en tu cluster</strong> y la cantidad de nodos necesarios se reduce significativamente — lo cual se traduce directamente en menos plata en tu factura de AWS.</p>
        <p>Pero para llegar a este insight necesitás el stack corriendo, dashboards con las queries correctas, y días o semanas de datos históricos. <strong>Sin observabilidad, estás adivinando.</strong></p>
      </div>
    </section>

    <!-- 07 LOGS Y ALERTAS -->
    <section class="section">
      <span class="section-number">// 07</span>
      <h2><span class="hex-icon">🔔</span>Logs centralizados y alertas: de ver a actuar</h2>
      <div class="section-divider"></div>

      <p><strong>Sin Loki</strong>, debuggear un error en producción significa conectarse al cluster, listar los 47 pods de tu servicio, abrir los logs de uno, buscar el error, no encontrarlo, abrir los logs del siguiente, y repetir. Y si el pod ya se recicló, los logs simplemente no existen.</p>

      <div class="callout callout-tip">
        <div class="callout-label">Con Loki en Grafana</div>
        <p>Buscás por namespace, servicio y palabra clave, y en segundos tenés los logs de todos los pods filtrados. Podés <strong>correlacionar un spike de errores con un aumento de latencia</strong> en las métricas de Prometheus, identificar exactamente qué endpoint está fallando, y ver en qué momento empezó — todo en la misma interfaz.</p>
      </div>

      <p>El paso final es configurar alertas que disparen notificaciones cuando algo está mal. Pero cada alerta necesita tuning:</p>

      <ul class="styled-list">
        <li><strong>Umbrales incorrectos</strong> generan ruido (alert fatigue) y el equipo empieza a ignorar todas las notificaciones.</li>
        <li><strong>Umbrales muy altos</strong> y te enterás tarde — cuando el problema ya afectó a usuarios.</li>
        <li>Configurar rutas de notificación, definir quién responde a qué, y <strong>mantener las reglas actualizadas</strong> a medida que tus servicios cambian es trabajo continuo.</li>
      </ul>
    </section>

    <!-- TABLA RESUMEN -->
    <div class="table-wrapper">
      <div class="table-header">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#90cdf4" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="9" x2="9" y2="21"/></svg>
        <span>Lo que implica tener observabilidad real — componente por componente</span>
      </div>
      <table>
        <thead>
          <tr>
            <th>Componente</th>
            <th>Qué implica configurar</th>
          </tr>
        </thead>
        <tbody>
          <tr><td>Prometheus</td><td>Instalación, storage, retención, scrape config, tuning de recursos</td></tr>
          <tr><td>Loki + agente</td><td>Storage en S3, permisos IAM, esquema de labels, retención, networking</td></tr>
          <tr><td>Grafana</td><td>Data sources, persistencia, carga automática de dashboards</td></tr>
          <tr><td>Dashboards útiles</td><td>Decenas de paneles con queries de PromQL por servicio y namespace</td></tr>
          <tr><td>Alertas</td><td>Reglas con umbrales correctos, rutas de notificación, mantenimiento continuo</td></tr>
          <tr><td>Networking</td><td>Tráfico interno para evitar costos ocultos de data transfer</td></tr>
        </tbody>
      </table>
    </div>

    <!-- TABLA COMPARATIVA SIN/CON SLEAKOPS -->
    <div class="table-wrapper">
      <div class="table-header">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#90cdf4" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
        <span>Sin observabilidad vs. con SleakOps</span>
      </div>
      <table>
        <thead>
          <tr>
            <th>Sin observabilidad</th>
            <th>Con SleakOps</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td class="td-bad">Instalar y configurar Prometheus + Loki + Grafana manualmente</td>
            <td class="td-good">Stack completo pre-instalado — listo desde el día uno</td>
          </tr>
          <tr>
            <td class="td-bad">Configurar S3, IAM y networking para Loki</td>
            <td class="td-good">Storage y redes optimizadas por defecto</td>
          </tr>
          <tr>
            <td class="td-bad">Perder logs cuando pods mueren o Spots se interrumpen</td>
            <td class="td-good">Logs persistentes sobre S3 con retención configurada</td>
          </tr>
          <tr>
            <td class="td-bad">Aprender PromQL para crear queries útiles</td>
            <td class="td-good">Dashboards preseteados listos para usar</td>
          </tr>
          <tr>
            <td class="td-bad">Adivinar requests y limits sin datos</td>
            <td class="td-good">Panel de right-sizing con datos reales de uso</td>
          </tr>
          <tr>
            <td class="td-bad">Pagar USD 500–2.000/mes por herramientas SaaS</td>
            <td class="td-good">Stack open source optimizado por USD 30/mes</td>
          </tr>
        </tbody>
      </table>
    </div>

    <!-- CTA -->
    <div class="cta-block">
      <div class="cta-price">USD 30<span style="font-size:22px;color:var(--gray)">/mes</span></div>
      <div class="cta-price-label">Stack completo de observabilidad · Grafana + Loki + Prometheus sobre S3</div>
      <h2>Observabilidad real desde el primer día</h2>
      <p>Cuando levantás tu infraestructura con SleakOps, Grafana, Loki y Prometheus ya están corriendo con dashboards preseteados que te muestran exactamente lo que necesitás ver — sin escribir una sola query de PromQL.</p>
      <a href="#" class="cta-btn">Conocé SleakOps →</a>
    </div>

    <!-- TAGS -->
    <div class="share-row">
      <span class="share-label">Tags:</span>
      <a href="#" class="share-tag">#Grafana</a>
      <a href="#" class="share-tag">#Loki</a>
      <a href="#" class="share-tag">#Prometheus</a>
      <a href="#" class="share-tag">#Kubernetes</a>
      <a href="#" class="share-tag">#Observabilidad</a>
      <a href="#" class="share-tag">#FinOps</a>
      <a href="#" class="share-tag">#DevOps</a>
    </div>

  </div>
</main>
</div><p>The post <a href="https://sleakops.com/2026/03/20/observabilidad-con-stack-open-source-grafana-loki-en-kubernetes/">Grafana Loki en Kubernetes: Guía de Observabilidad</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p><p>La entrada <a href="https://sleakops.com/2026/03/20/observabilidad-con-stack-open-source-grafana-loki-en-kubernetes/">Grafana Loki en Kubernetes: Guía de Observabilidad</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Pipeline de CI/CD: De GitHub a Producción en 5 Minutos</title>
		<link>https://sleakops.com/2026/03/20/pipeline-de-ci-cd-de-github-a-produccion-en-5-minutos/</link>
		
		<dc:creator><![CDATA[matias]]></dc:creator>
		<pubDate>Fri, 20 Mar 2026 11:51:04 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://sleakops.com/?p=1913</guid>

					<description><![CDATA[<p>CI/CD Kubernetes GitHub Actions DevOps 6 minutos de lectura Todos hemos visto el diagrama perfecto: un desarrollador hace push a main, se ejecutan los tests, se construye la imagen Docker, se despliega en Kubernetes y en minutos el cambio está en producción. Limpio. Automático. Elegante. La realidad es otra. Antes de que ese pipeline funcione, [&#8230;]</p>
<p>The post <a href="https://sleakops.com/2026/03/20/pipeline-de-ci-cd-de-github-a-produccion-en-5-minutos/">Pipeline de CI/CD: De GitHub a Producción en 5 Minutos</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p>
<p>La entrada <a href="https://sleakops.com/2026/03/20/pipeline-de-ci-cd-de-github-a-produccion-en-5-minutos/">Pipeline de CI/CD: De GitHub a Producción en 5 Minutos</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="sleakops-post">
<style>
:root {
    --navy:       #1a202c;
    --navy-light: #232c3e;
    --navy-mid:   #2d3748;
    --blue:       #90cdf4;
    --blue-dim:   rgba(144,205,244,0.09);
    --coral:      #e37562;
    --coral-dim:  rgba(227,117,98,0.10);
    --white:      #ffffff;
    --gray:       #a0aec0;
    --gray-dim:   rgba(160,174,192,0.09);
    --border:     rgba(144,205,244,0.13);
    --green:      #68d391;
    --orange:     #f6ad55;
  }

  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

  .sleakops-post {
    font-family: 'Inter', sans-serif;
        color: var(--white);
    line-height: 1.75;
    font-size: 16px;
    overflow-x: hidden;
  }

  /* ── HERO ── */
  .hero {
    background: var(--navy);
    padding: 44px 0 40px;
    border-bottom: 1px solid var(--border);
    position: relative; overflow: hidden;
  }
  .hero::before {
    content: ''; position: absolute; inset: 0;
    background-image:
      repeating-linear-gradient( 60deg, transparent, transparent 38px, rgba(144,205,244,.03) 38px, rgba(144,205,244,.03) 40px),
      repeating-linear-gradient(-60deg, transparent, transparent 38px, rgba(144,205,244,.03) 38px, rgba(144,205,244,.03) 40px);
    pointer-events: none;
  }
  .hero::after {
    content: ''; position: absolute; top: -110px; right: -110px;
    width: 400px; height: 400px;
    background: radial-gradient(circle, rgba(144,205,244,.08) 0%, transparent 70%);
    pointer-events: none;
  }

  .container { max-width: 820px; margin: 0 auto; padding: 0 32px; position: relative; z-index: 1; }

  .meta-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
  .badge {
    font-size: 10px; font-weight: 900; letter-spacing: .14em; text-transform: uppercase;
    padding: 4px 11px; border-radius: 4px;
    background: var(--blue-dim); color: var(--blue); border: 1px solid rgba(144,205,244,.26);
  }
  .badge.coral { background: var(--coral-dim); color: var(--coral); border-color: rgba(227,117,98,.26); }
  .badge.gray  { background: var(--gray-dim);  color: var(--gray);  border-color: rgba(160,174,192,.2); }
  .reading-pill {
    margin-left: auto;
    display: inline-flex; align-items: center; gap: 7px;
    background: var(--navy-mid); border: 1px solid var(--border);
    border-radius: 32px; padding: 6px 15px; font-size: 12px; color: var(--gray);
  }
  .reading-pill svg { color: var(--blue); flex-shrink: 0; }

  /* ── CONTENT ── */
  .content { padding: 52px 0 80px; }

  /* intro */
  .intro-block {
    background: linear-gradient(135deg, var(--navy-light), var(--navy-mid));
    border: 1px solid var(--border); border-radius: 12px;
    padding: 30px 34px; margin-bottom: 56px;
    position: relative; overflow: hidden;
  }
  .intro-block::before {
    content: ''; position: absolute; top: 0; left: 0;
    width: 4px; height: 100%;
    background: linear-gradient(to bottom, var(--blue), var(--coral));
  }
  .intro-block p { color: var(--gray); font-size: 15.5px; line-height: 1.85; margin-bottom: 13px; }
  .intro-block p:last-child { margin-bottom: 0; }
  .intro-block strong { color: var(--white); font-weight: 600; }
  .intro-block .reality {
    font-weight: 900; font-size: 18px; color: var(--coral);
    margin: 6px 0 16px; display: block;
  }

  /* ── ACCESS TRIO ── */
  .access-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; margin: 24px 0; }
  @media(max-width:580px){ .access-grid { grid-template-columns: 1fr; } }
  .access-card {
    background: var(--navy-light); border: 1px solid var(--border);
    border-radius: 10px; padding: 18px 18px;
    transition: border-color .2s; position: relative; overflow: hidden;
  }
  .access-card::before {
    content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;
    background: var(--coral);
  }
  .access-num {
    font-family: 'JetBrains Mono', monospace; font-size: 10px; font-weight: 600;
    color: var(--coral); letter-spacing: .1em; margin-bottom: 6px; opacity: .8;
  }
  .access-title { font-size: 13px; font-weight: 700; color: var(--white); margin-bottom: 6px; }
  .access-desc { font-size: 12.5px; color: var(--gray); line-height: 1.6; }

  /* ── SECTIONS ── */
  .section { margin-bottom: 56px; }
  .section-number {
    display: inline-block; font-family: 'JetBrains Mono', monospace;
    font-size: 11px; font-weight: 600; color: var(--blue);
    letter-spacing: .1em; margin-bottom: 8px; opacity: .8;
  }
  .section h2 {
    font-weight: 900; font-size: clamp(18px,2.8vw,24px);
    color: var(--white); line-height: 1.25; margin-bottom: 6px;
    display: flex; align-items: flex-start; gap: 12px;
  }
  .hex-icon {
    flex-shrink: 0; width: 36px; height: 36px;
    background: var(--blue-dim); border: 1px solid rgba(144,205,244,.2);
    border-radius: 8px; display: flex; align-items: center; justify-content: center;
    font-size: 16px; margin-top: 2px;
  }
  .section-divider { width: 44px; height: 3px; background: linear-gradient(to right, var(--blue), transparent); margin: 10px 0 20px; border-radius: 2px; }
  .section p { color: var(--gray); margin-bottom: 15px; line-height: 1.85; font-size: 15.5px; }
  .section p strong { color: var(--white); font-weight: 600; }
  .section p:last-child { margin-bottom: 0; }

  /* callouts */
  .callout {
    border-radius: 10px; padding: 20px 24px; margin: 20px 0;
    border: 1px solid; position: relative; overflow: hidden;
  }
  .callout-warning { background: rgba(227,117,98,.07); border-color: rgba(227,117,98,.27); }
  .callout-warning::after { content:'⚠'; position:absolute; top:12px; right:16px; font-size:18px; opacity:.22; }
  .callout-tip    { background: var(--blue-dim); border-color: rgba(144,205,244,.22); }
  .callout-tip::after    { content:'💡'; position:absolute; top:12px; right:16px; font-size:16px; opacity:.4; }
  .callout-label { font-size:10px; font-weight:900; letter-spacing:.14em; text-transform:uppercase; margin-bottom:8px; }
  .callout-warning .callout-label { color: var(--coral); }
  .callout-tip    .callout-label { color: var(--blue); }
  .callout p { font-size:14.5px !important; margin-bottom:8px !important; line-height:1.75 !important; }
  .callout p:last-child { margin-bottom:0 !important; }
  .callout strong { color: var(--white); font-weight:600; }

  /* styled list */
  .styled-list { list-style:none; margin:14px 0 18px; display:flex; flex-direction:column; gap:10px; }
  .styled-list li { display:flex; align-items:flex-start; gap:12px; color:var(--gray); font-size:15px; line-height:1.72; }
  .styled-list li::before { content:'→'; color:var(--blue); font-weight:700; flex-shrink:0; margin-top:1px; font-family:'JetBrains Mono',monospace; }
  .styled-list li strong { color:var(--white); }

  code {
    font-family:'JetBrains Mono',monospace; font-size:13px;
    background:var(--navy-mid); color:var(--blue);
    padding:2px 6px; border-radius:4px; border:1px solid var(--border);
  }

  /* ── CODE BLOCKS ── */
  .code-block {
    background: var(--navy-mid); border: 1px solid var(--border);
    border-radius: 10px; overflow: hidden; margin: 18px 0;
  }
  .code-header {
    background: var(--navy-light); padding: 9px 18px;
    border-bottom: 1px solid var(--border);
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
  }
  .code-header-left { display: flex; align-items: center; gap: 10px; }
  .code-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--blue-dim); border: 1px solid rgba(144,205,244,.3); }
  .code-title { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--gray); letter-spacing: .06em; }
  .code-badge {
    font-size: 9px; font-weight: 900; letter-spacing: .1em; text-transform: uppercase;
    padding: 2px 8px; border-radius: 3px; font-family: 'Inter', sans-serif;
  }
  .code-badge.warn { background: var(--coral-dim); color: var(--coral); border: 1px solid rgba(227,117,98,.25); }
  .code-badge.ok   { background: rgba(104,211,145,.1); color: var(--green); border: 1px solid rgba(104,211,145,.25); }
  pre {
    padding: 18px 20px; overflow-x: auto;
    font-family: 'JetBrains Mono', monospace; font-size: 12.5px;
    line-height: 1.72; color: var(--gray);
    white-space: pre; margin: 0;
  }
  pre .kw  { color: var(--blue); }
  pre .val { color: var(--green); }
  pre .cmt { color: rgba(160,174,192,.45); font-style: italic; }
  pre .key { color: var(--orange); }
  pre .str { color: #fbd38d; }
  pre .cmd { color: var(--green); }
  pre .sleakops { color: var(--blue); font-weight: 600; }

  /* ── LINE COUNT BADGE ── */
  .line-count {
    display: inline-flex; align-items: center; gap: 6px;
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
    background: var(--coral-dim); color: var(--coral); border: 1px solid rgba(227,117,98,.25);
    padding: 3px 10px; border-radius: 4px; margin: 4px 0 16px;
  }
  .line-count.ok {
    background: rgba(104,211,145,.08); color: var(--green); border-color: rgba(104,211,145,.2);
  }

  /* ── PREREQ TABLE ── */
  .table-wrapper {
    background: var(--navy); border: 1px solid var(--border);
    border-radius: 12px; overflow: hidden; margin: 32px 0; overflow-x: auto;
  }
  .table-header {
    background: linear-gradient(135deg, var(--navy-mid), var(--navy-light));
    padding: 16px 24px; border-bottom: 1px solid var(--border);
    display: flex; align-items: center; gap: 10px;
  }
  .table-header span { font-size:11px; font-weight:900; letter-spacing:.12em; text-transform:uppercase; color:var(--blue); }
  table { width:100%; border-collapse:collapse; min-width: 400px; }
  th { text-align:left; padding:11px 20px; font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--gray); background:var(--navy-mid); border-bottom:1px solid var(--border); }
  td { padding:12px 20px; font-size:14px; border-bottom:1px solid rgba(255,255,255,.04); color:var(--gray); vertical-align:top; }
  td:first-child { color:var(--white); font-weight:600; font-family:'JetBrains Mono',monospace; font-size:13px; }
  tr:last-child td { border-bottom:none; }
  tr:hover td { background:rgba(255,255,255,.02); }

  /* where badge */
  .where-badge {
    display:inline-block; font-size:10px; font-weight:700; padding:2px 8px; border-radius:3px;
    letter-spacing:.06em; font-family:'Inter',sans-serif;
  }
  .where-aws  { background:rgba(246,173,85,.1);  color:var(--orange); border:1px solid rgba(246,173,85,.2); }
  .where-k8s  { background:var(--blue-dim);       color:var(--blue);   border:1px solid rgba(144,205,244,.2); }
  .where-gh   { background:var(--gray-dim);       color:var(--gray);   border:1px solid rgba(160,174,192,.2); }
  .where-infra{ background:var(--coral-dim);      color:var(--coral);  border:1px solid rgba(227,117,98,.2); }

  /* ── BEFORE/AFTER ── */
  .ba-table td:first-child { font-family:'Inter',sans-serif; font-size:14px; }
  .td-bad  { color:var(--coral)  !important; }
  .td-good { color:var(--green)  !important; }

  /* ── CTA ── */
  .cta-block {
    background: linear-gradient(135deg, var(--navy-mid), rgba(26,32,44,.95));
    border: 1px solid rgba(144,205,244,.26); border-radius: 16px;
    padding: 46px 38px; text-align: center; position: relative; overflow: hidden; margin-top: 64px;
  }
  .cta-block::before {
    content:''; position:absolute; bottom:-80px; left:50%; transform:translateX(-50%);
    width:340px; height:340px;
    background:radial-gradient(circle, rgba(144,205,244,.08) 0%, transparent 70%);
    pointer-events:none;
  }
  .cta-block h2 { font-weight:900; font-size:clamp(19px,3.2vw,29px); color:var(--white); margin-bottom:11px; position:relative; }
  .cta-block p  { color:var(--gray); font-size:15px; max-width:500px; margin:0 auto 28px; line-height:1.8; position:relative; }
  .cta-btn {
    display:inline-block; background:var(--blue); color:var(--navy) !important;
    font-weight:900; font-size:13px; letter-spacing:.07em; text-transform:uppercase;
    text-decoration:none; padding:13px 34px; border-radius:8px;
    transition:transform .15s, box-shadow .15s; position:relative;
  }
  .cta-btn:hover { transform:translateY(-2px); box-shadow:0 8px 28px rgba(144,205,244,.26); }

  /* share row */
  .share-row { margin-top:42px; padding-top:24px; border-top:1px solid var(--border); display:flex; align-items:center; gap:9px; flex-wrap:wrap; }
  .share-label { font-size:11px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--gray); }
  .share-tag { background:var(--navy-mid); border:1px solid var(--border); color:var(--gray); font-size:12px; padding:4px 12px; border-radius:20px; text-decoration:none; transition:border-color .15s, color .15s; }
  .share-tag:hover { border-color:var(--blue); color:var(--blue); }

  @media(max-width:640px){
    .container { padding:0 18px; }
    .hero { padding:32px 0 28px; }
    .intro-block { padding:22px 20px; }
    td, th { padding:10px 14px; }
    .cta-block { padding:30px 20px; }
    .reading-pill { margin-left:0; }
    pre { font-size:11px; }
  }
</style>
<!-- HERO -->
<header class="hero">
  <div class="container">
    <div class="meta-bar">
      <span class="badge">CI/CD</span>
      <span class="badge">Kubernetes</span>
      <span class="badge coral">GitHub Actions</span>
      <span class="badge gray">DevOps</span>
      <div class="reading-pill">
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
        6 minutos de lectura
      </div>
    </div>
  </div>
</header>

<!-- CONTENT -->
<main class="content">
  <div class="container">

    <!-- INTRO -->
    <div class="intro-block">
      <p>Todos hemos visto el diagrama perfecto: un desarrollador hace push a <code>main</code>, se ejecutan los tests, se construye la imagen Docker, se despliega en Kubernetes y en minutos el cambio está en producción. Limpio. Automático. Elegante.</p>
      <span class="reality">La realidad es otra.</span>
      <p>Antes de que ese pipeline funcione, alguien en tu equipo tuvo que resolver un rompecabezas de accesos, credenciales, configuraciones de red y permisos que fácilmente puede llevar <strong>semanas — no minutos</strong>. Y lo peor: cada acceso mal configurado es una puerta abierta a tu infraestructura de producción.</p>
    </div>

    <!-- EL PROBLEMA -->
    <section class="section">
      <span class="section-number">// El problema</span>
      <h2><span class="hex-icon">🧩</span>Lo que ningún tutorial te muestra</h2>
      <div class="section-divider"></div>

      <p>Cualquier tutorial te muestra cómo escribir un archivo <code>.github/workflows/deploy.yml</code> con 30 líneas de YAML. Lo que ninguno muestra es todo lo que tiene que existir <strong>antes de que ese YAML funcione</strong>. Para que tu pipeline pueda desplegar en un cluster de EKS en una VPC privada, necesitás resolver tres accesos críticos:</p>

      <div class="access-grid">
        <div class="access-card">
          <div class="access-num">ACCESO // 01</div>
          <div class="access-title">Acceso a la red privada</div>
          <div class="access-desc">Tu cluster vive en subnets privadas. El pipeline corre fuera de tu red. Alguien tiene que tender el puente.</div>
        </div>
        <div class="access-card">
          <div class="access-num">ACCESO // 02</div>
          <div class="access-title">Autenticación en Kubernetes</div>
          <div class="access-desc">Una vez dentro de la red, el pipeline necesita autenticarse contra el API server con permisos exactos.</div>
        </div>
        <div class="access-card">
          <div class="access-num">ACCESO // 03</div>
          <div class="access-title">Registry de imágenes</div>
          <div class="access-desc">El pipeline pushea la imagen Docker. El cluster tiene que poder pulllearla. Son dos flujos de auth diferentes.</div>
        </div>
      </div>

      <p style="margin-top:4px;">Tres accesos. Tres conjuntos de credenciales. <strong>Tres vectores de ataque si se configuran mal.</strong></p>
    </section>

    <!-- 01 VPN -->
    <section class="section">
      <span class="section-number">// 01</span>
      <h2><span class="hex-icon">🔒</span>Conectar el pipeline a tu red privada</h2>
      <div class="section-divider"></div>

      <p>Si seguiste las buenas prácticas, tu cluster vive en subnets privadas — no es accesible desde internet. Correcto desde la perspectiva de seguridad. <strong>Problemático para el CI/CD:</strong> GitHub Actions corre en runners efímeros fuera de tu red y no puede hablar con tu cluster.</p>

      <div class="callout callout-warning">
        <div class="callout-label">Las opciones y sus costos</div>
        <p><strong>Hacer público el API server:</strong> terrible para la seguridad. <strong>Configurar VPN en el pipeline:</strong> complejo y frágil. <strong>Self-hosted runners dentro de tu VPC:</strong> otro servidor que mantener, parchar y monitorear.</p>
      </div>

      <p>Si elegís la ruta de VPN — la más común — tu pipeline empieza así <em>solo para resolver la conectividad de red</em>:</p>

      <div class="line-count">+15 líneas · 4 secrets · solo para &#8220;entrar&#8221; a la red</div>

      <div class="code-block">
        <div class="code-header">
          <div class="code-header-left">
            <div class="code-dot"></div>
            <span class="code-title">deploy.yml — solo el paso de VPN</span>
          </div>
          <span class="code-badge warn">Acceso 01</span>
        </div>
        <pre>      - <span class="key">name:</span> <span class="str">Install OpenVPN</span>
        <span class="key">run:</span> <span class="val">sudo apt-get update && sudo apt-get install -y openvpn</span>

      - <span class="key">name:</span> <span class="str">Connect to VPN</span>
        <span class="key">run:</span> <span class="val">|</span>
          echo <span class="str">"${{ secrets.VPN_CONFIG }}"</span> > vpn.ovpn
          echo <span class="str">"${{ secrets.VPN_CA_CERT }}"</span> > ca.crt
          echo <span class="str">"${{ secrets.VPN_CLIENT_CERT }}"</span> > client.crt
          echo <span class="str">"${{ secrets.VPN_CLIENT_KEY }}"</span> > client.key
          sudo openvpn --config vpn.ovpn \
            --ca ca.crt --cert client.crt --key client.key \
            --daemon
          sleep 15</pre>
      </div>

      <div class="callout callout-warning">
        <div class="callout-label">El riesgo silencioso</div>
        <p>Si los certificados expiran un viernes a las 6 PM, <strong>nadie despliega hasta el lunes</strong>. Los certificados VPN son un punto de falla que nadie recuerda hasta que producción está frenada.</p>
      </div>
    </section>

    <!-- 02 KUBERNETES AUTH -->
    <section class="section">
      <span class="section-number">// 02</span>
      <h2><span class="hex-icon">🔑</span>Autenticarse contra Kubernetes</h2>
      <div class="section-divider"></div>

      <p>Estás dentro de la red. Ahora necesitás que el pipeline pueda ejecutar comandos contra el cluster. La opción más segura es OIDC Federation — el pipeline asume un IAM Role sin Access Keys estáticas. Suena limpio. <strong>Para que funcione necesitás coordinar al menos 4 piezas diferentes entre AWS IAM y Kubernetes.</strong></p>

      <div class="code-block">
        <div class="code-header">
          <div class="code-header-left">
            <div class="code-dot"></div>
            <span class="code-title">IAM Trust Policy + RBAC Kubernetes</span>
          </div>
          <span class="code-badge warn">Acceso 02</span>
        </div>
        <pre><span class="cmt">// Trust policy del IAM Role para GitHub Actions (OIDC)</span>
{
  <span class="key">"Statement":</span> [{
    <span class="key">"Effect":</span> <span class="str">"Allow"</span>,
    <span class="key">"Principal":</span> {
      <span class="key">"Federated":</span> <span class="str">"arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"</span>
    },
    <span class="key">"Action":</span> <span class="str">"sts:AssumeRoleWithWebIdentity"</span>,
    <span class="key">"Condition":</span> {
      <span class="key">"StringLike":</span> {
        <span class="str">"token.actions.githubusercontent.com:sub"</span>: <span class="str">"repo:mi-org/mi-repo:ref:refs/heads/main"</span>
      }
    }
  }]
}

<span class="cmt"># RBAC en Kubernetes - qué puede hacer el pipeline</span>
<span class="kw">apiVersion:</span> <span class="val">rbac.authorization.k8s.io/v1</span>
<span class="kw">kind:</span> <span class="val">ClusterRole</span>
<span class="kw">metadata:</span>
  <span class="key">name:</span> <span class="val">deployer-role</span>
<span class="kw">rules:</span>
  - <span class="key">apiGroups:</span> <span class="val">["apps"]</span>
    <span class="key">resources:</span> <span class="val">["deployments","replicasets"]</span>
    <span class="key">verbs:</span> <span class="val">["get","list","watch","update","patch"]</span></pre>
      </div>

      <div class="callout callout-warning">
        <div class="callout-label">El error más frustrante</div>
        <p>Si cualquier pieza no encaja — y hay muchas piezas — el pipeline falla con un error críptico de <code>Unauthorized</code> que <strong>puede tardar horas en diagnosticar</strong>. No siempre es claro cuál de las 4 capas (OIDC, IAM, aws-auth, RBAC) es la que falló.</p>
      </div>
    </section>

    <!-- 03 REGISTRY -->
    <section class="section">
      <span class="section-number">// 03</span>
      <h2><span class="hex-icon">📦</span>Push y Pull de imágenes Docker</h2>
      <div class="section-divider"></div>

      <p>Tu pipeline construye la imagen. Esa imagen necesita ir a un registry donde el cluster pueda descargarla. <strong>Son dos flujos de autenticación diferentes:</strong> el pipeline pushea, el cluster pullea. Si usás Amazon ECR, el token de autenticación dura 12 horas y necesitás renovarlo antes de cada push.</p>

      <div class="code-block">
        <div class="code-header">
          <div class="code-header-left">
            <div class="code-dot"></div>
            <span class="code-title">ECR login + build + push + deploy</span>
          </div>
          <span class="code-badge warn">Acceso 03</span>
        </div>
        <pre>      - <span class="key">name:</span> <span class="str">Login to Amazon ECR</span>
        <span class="key">uses:</span> <span class="val">aws-actions/amazon-ecr-login@v2</span>

      - <span class="key">name:</span> <span class="str">Build and Push</span>
        <span class="key">run:</span> <span class="val">|</span>
          docker build -t <span class="str">$REGISTRY</span>/my-app:<span class="str">${{ github.sha }}</span> .
          docker push <span class="str">$REGISTRY</span>/my-app:<span class="str">${{ github.sha }}</span>

      - <span class="key">name:</span> <span class="str">Deploy to Kubernetes</span>
        <span class="key">run:</span> <span class="val">|</span>
          kubectl set image deployment/my-app \
            my-app=<span class="str">$REGISTRY</span>/my-app:<span class="str">${{ github.sha }}</span> \
            -n production</pre>
      </div>

      <div class="callout callout-warning">
        <div class="callout-label">Lo que tiene que existir antes</div>
        <p>El ECR repository tiene que existir con <strong>lifecycle policies configuradas</strong> para no acumular imágenes infinitas, con permisos de push para el rol del pipeline y permisos de pull para los nodos del cluster. Si usás cuentas AWS separadas por entorno, sumale Cross-Account Policies.</p>
      </div>
    </section>

    <!-- PANORAMA COMPLETO -->
    <section class="section">
      <span class="section-number">// El resultado</span>
      <h2><span class="hex-icon">📜</span>El panorama completo: 50+ líneas y semanas de config previa</h2>
      <div class="section-divider"></div>

      <p>Cuando juntás los 3 accesos en un solo pipeline, el resultado es esto — <strong>en la versión simplificada</strong>, sin tests, linting, security scanning, multi-environment ni rollback automático:</p>

      <div class="line-count">50+ líneas · versión simplificada · sin los casos de uso reales</div>

      <div class="code-block">
        <div class="code-header">
          <div class="code-header-left">
            <div class="code-dot"></div>
            <span class="code-title">deploy.yml — pipeline completo &#8220;simple&#8221;</span>
          </div>
          <span class="code-badge warn">Complejo</span>
        </div>
        <pre><span class="kw">name:</span> <span class="str">Deploy to Production</span>
<span class="kw">on:</span>
  <span class="key">push:</span>
    <span class="key">branches:</span> <span class="val">[main]</span>

<span class="kw">jobs:</span>
  <span class="key">deploy:</span>
    <span class="key">runs-on:</span> <span class="val">ubuntu-latest</span>
    <span class="key">steps:</span>
      - <span class="key">uses:</span> <span class="val">actions/checkout@v4</span>

      <span class="cmt"># ACCESO 1: VPN (~15 líneas + 4 secrets)</span>
      - <span class="key">name:</span> <span class="str">Install and Connect VPN</span>
        <span class="key">run:</span> <span class="val">|</span>
          sudo apt-get install -y openvpn
          echo <span class="str">"${{ secrets.VPN_CONFIG }}"</span> > vpn.ovpn
          echo <span class="str">"${{ secrets.VPN_CA_CERT }}"</span> > ca.crt
          echo <span class="str">"${{ secrets.VPN_CLIENT_CERT }}"</span> > client.crt
          echo <span class="str">"${{ secrets.VPN_CLIENT_KEY }}"</span> > client.key
          sudo openvpn --config vpn.ovpn \
            --ca ca.crt --cert client.crt --key client.key --daemon
          sleep 15

      <span class="cmt"># ACCESO 2: AWS + Kubernetes (~10 líneas + IAM/OIDC/RBAC previo)</span>
      - <span class="key">name:</span> <span class="str">Configure AWS (OIDC)</span>
        <span class="key">uses:</span> <span class="val">aws-actions/configure-aws-credentials@v4</span>
        <span class="key">with:</span>
          <span class="key">role-to-assume:</span> <span class="str">${{ secrets.AWS_DEPLOY_ROLE_ARN }}</span>
          <span class="key">aws-region:</span> <span class="val">us-east-1</span>

      - <span class="key">name:</span> <span class="str">Connect to EKS</span>
        <span class="key">run:</span> <span class="val">aws eks update-kubeconfig --name my-cluster --region us-east-1</span>

      <span class="cmt"># ACCESO 3: Registry (~10 líneas + ECR policies previas)</span>
      - <span class="key">name:</span> <span class="str">Login to ECR</span>
        <span class="key">id:</span> <span class="val">ecr</span>
        <span class="key">uses:</span> <span class="val">aws-actions/amazon-ecr-login@v2</span>

      <span class="cmt"># BUILD + DEPLOY</span>
      - <span class="key">name:</span> <span class="str">Build, Push and Deploy</span>
        <span class="key">run:</span> <span class="val">|</span>
          docker build -t <span class="str">${{ steps.ecr.outputs.registry }}</span>/my-app:<span class="str">${{ github.sha }}</span> .
          docker push <span class="str">${{ steps.ecr.outputs.registry }}</span>/my-app:<span class="str">${{ github.sha }}</span>
          kubectl set image deployment/my-app \
            my-app=<span class="str">${{ steps.ecr.outputs.registry }}</span>/my-app:<span class="str">${{ github.sha }}</span> \
            -n production
          kubectl rollout status deployment/my-app -n production --timeout=300s

      - <span class="key">name:</span> <span class="str">Disconnect VPN</span>
        <span class="key">if:</span> <span class="val">always()</span>
        <span class="key">run:</span> <span class="val">sudo killall openvpn || true</span></pre>
      </div>

      <p>Pero el YAML es solo la punta del iceberg. Para que estas líneas funcionen, previamente necesitaste configurar todo esto:</p>
    </section>

    <!-- TABLA PREREQUISITOS -->
    <div class="table-wrapper">
      <div class="table-header">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#90cdf4" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="9" x2="9" y2="21"/></svg>
        <span>Configuración previa requerida antes del primer deploy</span>
      </div>
      <table>
        <thead>
          <tr>
            <th>Qué configurar</th>
            <th>Dónde</th>
          </tr>
        </thead>
        <tbody>
          <tr><td>Servidor VPN desplegado y configurado</td><td><span class="where-badge where-aws">AWS VPC</span></td></tr>
          <tr><td>Certificados VPN generados y distribuidos</td><td><span class="where-badge where-infra">Infra</span> <span class="where-badge where-gh">GitHub Secrets</span></td></tr>
          <tr><td>OIDC Identity Provider</td><td><span class="where-badge where-aws">AWS IAM</span></td></tr>
          <tr><td>IAM Role con trust policy específica</td><td><span class="where-badge where-aws">AWS IAM</span></td></tr>
          <tr><td>aws-auth ConfigMap mapeado</td><td><span class="where-badge where-k8s">Kubernetes</span></td></tr>
          <tr><td>RBAC — ClusterRole + ClusterRoleBinding</td><td><span class="where-badge where-k8s">Kubernetes</span></td></tr>
          <tr><td>ECR Repository con lifecycle policies</td><td><span class="where-badge where-aws">AWS ECR</span></td></tr>
          <tr><td>IAM Policy de pull en nodos del cluster</td><td><span class="where-badge where-aws">AWS IAM</span></td></tr>
          <tr><td>6+ secrets configurados en el repositorio</td><td><span class="where-badge where-gh">GitHub Settings</span></td></tr>
        </tbody>
      </table>
    </div>

    <div class="callout callout-warning" style="margin-bottom:48px;">
      <div class="callout-label">El costo real en tiempo</div>
      <p>Configurar todo esto correctamente le toma a un equipo experimentado <strong>entre 3 y 10 días hábiles</strong>. A un equipo que lo hace por primera vez, fácilmente <strong>2 a 4 semanas</strong>. Y después viene el mantenimiento: certificados que expiran, tokens que rotan, APIs que se deprecan, versiones de Kubernetes que cambian. Cada incidente son horas de debugging y deploys frenados.</p>
    </div>

    <!-- LA ALTERNATIVA -->
    <section class="section">
      <span class="section-number">// La alternativa</span>
      <h2><span class="hex-icon">✨</span>5 minutos reales con SleakOps</h2>
      <div class="section-divider"></div>

      <p>¿Y si toda esa infraestructura previa — la VPN, los roles IAM, el cluster, el registry, los permisos de RBAC — <strong>ya existiera configurada y segura</strong>? ¿Y si solo tuvieras que copiar un comando en tu pipeline?</p>

      <div class="line-count ok">1 comando · sin secrets que mantener · sin certificados que expiran</div>

      <div class="code-block">
        <div class="code-header">
          <div class="code-header-left">
            <div class="code-dot"></div>
            <span class="code-title">deploy.yml — con SleakOps CLI</span>
          </div>
          <span class="code-badge ok">Simple</span>
        </div>
        <pre><span class="kw">name:</span> <span class="str">Deploy to Production</span>
<span class="kw">on:</span>
  <span class="key">push:</span>
    <span class="key">branches:</span> <span class="val">[main]</span>

<span class="kw">jobs:</span>
  <span class="key">deploy:</span>
    <span class="key">runs-on:</span> <span class="val">ubuntu-latest</span>
    <span class="key">steps:</span>
      - <span class="key">uses:</span> <span class="val">actions/checkout@v4</span>

      - <span class="key">name:</span> <span class="str">Deploy with SleakOps</span>
        <span class="key">run:</span> <span class="val">|</span>
          <span class="sleakops">sleakops deploy \
            --app my-app \
            --env production \
            --image-tag ${{ github.sha }}</span></pre>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">Lo que hace ese único comando</div>
        <p>La CLI se encarga de conectarse a tu red privada, autenticarse contra el cluster, construir y pushear la imagen al registry, desplegar en Kubernetes y verificar que el rollout fue exitoso. Todo con <strong>credenciales temporales y menor privilegio por defecto</strong>. Y funciona igual en GitHub Actions, GitLab CI, Bitbucket Pipelines o cualquier sistema que pueda ejecutar bash.</p>
      </div>
    </section>

    <!-- TABLA ANTES/DESPUÉS -->
    <div class="table-wrapper">
      <div class="table-header">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#90cdf4" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
        <span>Pipeline manual vs. SleakOps</span>
      </div>
      <table class="ba-table">
        <thead>
          <tr>
            <th>Pipeline manual</th>
            <th>Con SleakOps</th>
          </tr>
        </thead>
        <tbody>
          <tr><td class="td-bad">50+ líneas de YAML solo para los 3 accesos</td><td class="td-good">1 comando: <code>sleakops deploy</code></td></tr>
          <tr><td class="td-bad">6+ secrets que mantener y rotar manualmente</td><td class="td-good">Sin secrets en el pipeline — credenciales temporales</td></tr>
          <tr><td class="td-bad">Certificados VPN que expiran en el peor momento</td><td class="td-good">Conectividad gestionada automáticamente</td></tr>
          <tr><td class="td-bad">3–10 días de config para un equipo experimentado</td><td class="td-good">Funciona desde el primer deploy</td></tr>
          <tr><td class="td-bad">Mantenimiento continuo de cada capa</td><td class="td-good">Actualizaciones gestionadas por SleakOps</td></tr>
          <tr><td class="td-bad">Solo GitHub Actions (config específica por plataforma)</td><td class="td-good">GitHub, GitLab, Bitbucket o cualquier CLI</td></tr>
        </tbody>
      </table>
    </div>

    <!-- CTA -->
    <div class="cta-block">
      <h2>Tu pipeline no debería ser un proyecto de infraestructura</h2>
      <p>La pregunta no es si tu equipo puede configurar todo esto manualmente. Seguramente puede. La pregunta es: ¿debería invertir semanas en plomería de infraestructura, o en construir producto?</p>
      <a href="#" class="cta-btn">Conocé SleakOps →</a>
    </div>

    <!-- TAGS -->
    <div class="share-row">
      <span class="share-label">Tags:</span>
      <a href="#" class="share-tag">#CICD</a>
      <a href="#" class="share-tag">#GitHubActions</a>
      <a href="#" class="share-tag">#Kubernetes</a>
      <a href="#" class="share-tag">#DevOps</a>
      <a href="#" class="share-tag">#EKS</a>
      <a href="#" class="share-tag">#AWS</a>
      <a href="#" class="share-tag">#Pipeline</a>
    </div>

  </div>
</main>
</div><p>The post <a href="https://sleakops.com/2026/03/20/pipeline-de-ci-cd-de-github-a-produccion-en-5-minutos/">Pipeline de CI/CD: De GitHub a Producción en 5 Minutos</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p><p>La entrada <a href="https://sleakops.com/2026/03/20/pipeline-de-ci-cd-de-github-a-produccion-en-5-minutos/">Pipeline de CI/CD: De GitHub a Producción en 5 Minutos</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Ahorra 60% en AWS con Karpenter y Spot Instances</title>
		<link>https://sleakops.com/2026/03/20/como-reducir-tu-factura-de-aws-en-un-60-usando-karpenter-y-spot-instances-sin-sacrificar-estabilidad/</link>
		
		<dc:creator><![CDATA[matias]]></dc:creator>
		<pubDate>Fri, 20 Mar 2026 11:50:00 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://sleakops.com/?p=1916</guid>

					<description><![CDATA[<p>Kubernetes Karpenter FinOps Spot Instances 8 minutos de lectura Hay un momento que todo CTO o Engineering Manager conoce: abrís la consola de AWS Billing, ves el número del mes, y sentís que algo no cuadra. Tus aplicaciones no crecieron tanto, tu equipo no cambió de tamaño, pero la factura sigue subiendo. Mes tras mes. [&#8230;]</p>
<p>The post <a href="https://sleakops.com/2026/03/20/como-reducir-tu-factura-de-aws-en-un-60-usando-karpenter-y-spot-instances-sin-sacrificar-estabilidad/">Ahorra 60% en AWS con Karpenter y Spot Instances</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p>
<p>La entrada <a href="https://sleakops.com/2026/03/20/como-reducir-tu-factura-de-aws-en-un-60-usando-karpenter-y-spot-instances-sin-sacrificar-estabilidad/">Ahorra 60% en AWS con Karpenter y Spot Instances</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="sleakops-post">
<style>
:root {
    --navy:       #1a202c;
    --navy-light: #232c3e;
    --navy-mid:   #2d3748;
    --blue:       #90cdf4;
    --blue-dim:   rgba(144,205,244,0.09);
    --coral:      #e37562;
    --coral-dim:  rgba(227,117,98,0.10);
    --white:      #ffffff;
    --gray:       #a0aec0;
    --gray-dim:   rgba(160,174,192,0.09);
    --border:     rgba(144,205,244,0.13);
    --green:      #68d391;
  }

  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

  .sleakops-post {
    font-family: 'Inter', sans-serif;
        color: var(--white);
    line-height: 1.75;
    font-size: 16px;
    overflow-x: hidden;
  }

  /* ── HERO ── */
  .hero {
    background: var(--navy);
    padding: 44px 0 40px;
    border-bottom: 1px solid var(--border);
    position: relative; overflow: hidden;
  }
  .hero::before {
    content: ''; position: absolute; inset: 0;
    background-image:
      repeating-linear-gradient( 60deg, transparent, transparent 38px, rgba(144,205,244,.03) 38px, rgba(144,205,244,.03) 40px),
      repeating-linear-gradient(-60deg, transparent, transparent 38px, rgba(144,205,244,.03) 38px, rgba(144,205,244,.03) 40px);
    pointer-events: none;
  }
  .hero::after {
    content: ''; position: absolute; top: -110px; right: -110px;
    width: 400px; height: 400px;
    background: radial-gradient(circle, rgba(144,205,244,.08) 0%, transparent 70%);
    pointer-events: none;
  }

  .container { max-width: 820px; margin: 0 auto; padding: 0 32px; position: relative; z-index: 1; }

  .meta-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
  .badge {
    font-size: 10px; font-weight: 900; letter-spacing: .14em; text-transform: uppercase;
    padding: 4px 11px; border-radius: 4px;
    background: var(--blue-dim); color: var(--blue); border: 1px solid rgba(144,205,244,.26);
  }
  .badge.coral { background: var(--coral-dim); color: var(--coral); border-color: rgba(227,117,98,.26); }
  .badge.gray  { background: var(--gray-dim);  color: var(--gray);  border-color: rgba(160,174,192,.2); }
  .reading-pill {
    margin-left: auto;
    display: inline-flex; align-items: center; gap: 7px;
    background: var(--navy-mid); border: 1px solid var(--border);
    border-radius: 32px; padding: 6px 15px; font-size: 12px; color: var(--gray);
  }
  .reading-pill svg { color: var(--blue); flex-shrink: 0; }

  /* ── CONTENT ── */
  .content { padding: 52px 0 80px; }

  /* intro */
  .intro-block {
    background: linear-gradient(135deg, var(--navy-light), var(--navy-mid));
    border: 1px solid var(--border); border-radius: 12px;
    padding: 30px 34px; margin-bottom: 56px;
    position: relative; overflow: hidden;
  }
  .intro-block::before {
    content: ''; position: absolute; top: 0; left: 0;
    width: 4px; height: 100%;
    background: linear-gradient(to bottom, var(--blue), var(--coral));
  }
  .intro-block p { color: var(--gray); font-size: 15.5px; line-height: 1.85; margin-bottom: 13px; }
  .intro-block p:last-child { margin-bottom: 0; }
  .intro-block strong { color: var(--white); font-weight: 600; }

  /* ── SECTIONS ── */
  .section { margin-bottom: 56px; }
  .section-number {
    display: inline-block; font-family: 'JetBrains Mono', monospace;
    font-size: 11px; font-weight: 600; color: var(--blue);
    letter-spacing: .1em; margin-bottom: 8px; opacity: .8;
  }
  .section h2 {
    font-weight: 900; font-size: clamp(18px,2.8vw,24px);
    color: var(--white); line-height: 1.25; margin-bottom: 6px;
    display: flex; align-items: flex-start; gap: 12px;
  }
  .hex-icon {
    flex-shrink: 0; width: 36px; height: 36px;
    background: var(--blue-dim); border: 1px solid rgba(144,205,244,.2);
    border-radius: 8px; display: flex; align-items: center; justify-content: center;
    font-size: 16px; margin-top: 2px;
  }
  .section-divider { width: 44px; height: 3px; background: linear-gradient(to right, var(--blue), transparent); margin: 10px 0 20px; border-radius: 2px; }
  .section p { color: var(--gray); margin-bottom: 15px; line-height: 1.85; font-size: 15.5px; }
  .section p strong { color: var(--white); font-weight: 600; }
  .section p:last-child { margin-bottom: 0; }

  /* callouts */
  .callout {
    border-radius: 10px; padding: 20px 24px; margin: 20px 0;
    border: 1px solid; position: relative; overflow: hidden;
  }
  .callout-warning { background: rgba(227,117,98,.07); border-color: rgba(227,117,98,.27); }
  .callout-warning::after { content:'⚠'; position:absolute; top:12px; right:16px; font-size:18px; opacity:.22; }
  .callout-tip    { background: var(--blue-dim); border-color: rgba(144,205,244,.22); }
  .callout-tip::after    { content:'💡'; position:absolute; top:12px; right:16px; font-size:16px; opacity:.4; }
  .callout-label { font-size:10px; font-weight:900; letter-spacing:.14em; text-transform:uppercase; margin-bottom:8px; }
  .callout-warning .callout-label { color: var(--coral); }
  .callout-tip    .callout-label { color: var(--blue); }
  .callout p { font-size:14.5px !important; margin-bottom:8px !important; line-height:1.75 !important; }
  .callout p:last-child { margin-bottom:0 !important; }
  .callout strong { color: var(--white); font-weight:600; }

  /* styled list */
  .styled-list { list-style:none; margin:14px 0 18px; display:flex; flex-direction:column; gap:10px; }
  .styled-list li { display:flex; align-items:flex-start; gap:12px; color:var(--gray); font-size:15px; line-height:1.72; }
  .styled-list li::before { content:'→'; color:var(--blue); font-weight:700; flex-shrink:0; margin-top:1px; font-family:'JetBrains Mono',monospace; }
  .styled-list li strong { color:var(--white); }

  /* ── PROBLEMA LIST (bullets con ✗) ── */
  .problem-list { list-style:none; margin:14px 0 18px; display:flex; flex-direction:column; gap:9px; }
  .problem-list li { display:flex; align-items:flex-start; gap:12px; color:var(--gray); font-size:15px; line-height:1.72; }
  .problem-list li::before { content:'✗'; color:var(--coral); font-weight:900; flex-shrink:0; margin-top:1px; font-family:'JetBrains Mono',monospace; font-size:13px; }
  .problem-list li strong { color:var(--white); }

  /* ── SAVING BANNER ── */
  .saving-banner {
    display: flex; align-items: center; justify-content: center; gap: 24px;
    background: linear-gradient(135deg, rgba(144,205,244,.08), rgba(104,211,145,.06));
    border: 1px solid rgba(144,205,244,.25); border-radius: 12px;
    padding: 24px 28px; margin: 24px 0; flex-wrap: wrap; text-align: center;
  }
  .saving-num {
    font-family: 'JetBrains Mono', monospace; font-size: 48px; font-weight: 600;
    color: var(--blue); line-height: 1;
  }
  .saving-label { font-size: 13px; color: var(--gray); line-height: 1.5; max-width: 280px; }
  .saving-label strong { color: var(--white); display: block; margin-bottom: 4px; font-size: 15px; }

  /* ── CODE BLOCK ── */
  .code-block {
    background: var(--navy-mid); border: 1px solid var(--border);
    border-radius: 10px; overflow: hidden; margin: 18px 0;
  }
  .code-header {
    background: var(--navy-light); padding: 10px 18px;
    border-bottom: 1px solid var(--border);
    display: flex; align-items: center; gap: 10px;
  }
  .code-header span {
    font-family: 'JetBrains Mono', monospace; font-size: 11px;
    color: var(--gray); letter-spacing: .06em;
  }
  .code-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--blue-dim); border: 1px solid rgba(144,205,244,.3); }
  pre {
    padding: 18px 20px; overflow-x: auto;
    font-family: 'JetBrains Mono', monospace; font-size: 12.5px;
    line-height: 1.7; color: var(--gray);
    white-space: pre; margin: 0;
  }
  pre .kw  { color: var(--blue); }
  pre .val { color: var(--green); }
  pre .cmt { color: rgba(160,174,192,.5); font-style: italic; }
  pre .key { color: #f6ad55; }

  code {
    font-family:'JetBrains Mono',monospace; font-size:13px;
    background:var(--navy-mid); color:var(--blue);
    padding:2px 6px; border-radius:4px; border:1px solid var(--border);
  }

  /* ── COMPLEXITY TABLE ── */
  .table-wrapper {
    background: var(--navy); border: 1px solid var(--border);
    border-radius: 12px; overflow: hidden; margin: 32px 0; overflow-x: auto;
  }
  .table-header {
    background: linear-gradient(135deg, var(--navy-mid), var(--navy-light));
    padding: 16px 24px; border-bottom: 1px solid var(--border);
    display: flex; align-items: center; gap: 10px;
  }
  .table-header span { font-size:11px; font-weight:900; letter-spacing:.12em; text-transform:uppercase; color:var(--blue); }
  table { width:100%; border-collapse:collapse; min-width: 440px; }
  th { text-align:left; padding:11px 20px; font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--gray); background:var(--navy-mid); border-bottom:1px solid var(--border); }
  td { padding:12px 20px; font-size:14px; border-bottom:1px solid rgba(255,255,255,.04); color:var(--gray); vertical-align:top; }
  td:first-child { color:var(--white); font-weight:600; font-size:13.5px; }
  tr:last-child td { border-bottom:none; }
  tr:hover td { background:rgba(255,255,255,.02); }
  .complexity-high { color: var(--coral) !important; font-weight: 700; font-size: 12px; font-family: 'Inter', sans-serif; }
  .complexity-mid  { color: #f6ad55 !important; font-weight: 700; font-size: 12px; font-family: 'Inter', sans-serif; }
  .complexity-cont { color: var(--gray) !important; font-style: italic; font-size: 12px; }

  /* ── BEFORE/AFTER TABLE ── */
  .ba-table td:first-child { font-family: 'Inter', sans-serif; font-size: 14px; }
  .td-bad  { color: var(--coral) !important; }
  .td-good { color: var(--green) !important; }

  /* ── SCHEDULE VISUAL ── */
  .schedule-visual {
    background: var(--navy-light); border: 1px solid var(--border);
    border-radius: 12px; padding: 22px 24px; margin: 20px 0;
  }
  .sv-label {
    font-size: 10px; font-weight: 900; letter-spacing: .14em; text-transform: uppercase;
    color: var(--blue); margin-bottom: 14px;
  }
  .sv-row { display: flex; gap: 3px; align-items: center; margin-bottom: 10px; }
  .sv-day-label {
    font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--gray);
    width: 28px; flex-shrink: 0; text-align: right; margin-right: 6px;
  }
  .sv-block {
    height: 22px; border-radius: 3px; flex: 1;
    display: flex; align-items: center; justify-content: center;
    font-size: 9px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  }
  .sv-block.on   { background: rgba(144,205,244,.2); border: 1px solid rgba(144,205,244,.3); color: var(--blue); }
  .sv-block.off  { background: rgba(160,174,192,.06); border: 1px solid rgba(160,174,192,.1); color: rgba(160,174,192,.4); }
  .sv-legend { display: flex; gap: 16px; margin-top: 10px; }
  .sv-legend-item { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--gray); }
  .sv-dot { width: 10px; height: 10px; border-radius: 2px; }
  .sv-dot.on  { background: rgba(144,205,244,.4); border: 1px solid rgba(144,205,244,.4); }
  .sv-dot.off { background: rgba(160,174,192,.15); border: 1px solid rgba(160,174,192,.2); }
  .sv-savings {
    margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border);
    display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px;
  }
  .sv-savings-text { font-size: 13px; color: var(--gray); }
  .sv-savings-num { font-family: 'JetBrains Mono', monospace; font-size: 18px; font-weight: 600; color: var(--green); }

  /* ── CTA ── */
  .cta-block {
    background: linear-gradient(135deg, var(--navy-mid), rgba(26,32,44,.95));
    border: 1px solid rgba(144,205,244,.26); border-radius: 16px;
    padding: 46px 38px; text-align: center; position: relative; overflow: hidden; margin-top: 64px;
  }
  .cta-block::before {
    content:''; position:absolute; bottom:-80px; left:50%; transform:translateX(-50%);
    width:340px; height:340px;
    background:radial-gradient(circle, rgba(144,205,244,.08) 0%, transparent 70%);
    pointer-events:none;
  }
  .cta-block h2 { font-weight:900; font-size:clamp(19px,3.2vw,29px); color:var(--white); margin-bottom:11px; position:relative; }
  .cta-block p  { color:var(--gray); font-size:15px; max-width:500px; margin:0 auto 28px; line-height:1.8; position:relative; }
  .cta-btn {
    display:inline-block; background:var(--blue); color:var(--navy) !important;
    font-weight:900; font-size:13px; letter-spacing:.07em; text-transform:uppercase;
    text-decoration:none; padding:13px 34px; border-radius:8px;
    transition:transform .15s, box-shadow .15s; position:relative;
  }
  .cta-btn:hover { transform:translateY(-2px); box-shadow:0 8px 28px rgba(144,205,244,.26); }

  /* share row */
  .share-row { margin-top:42px; padding-top:24px; border-top:1px solid var(--border); display:flex; align-items:center; gap:9px; flex-wrap:wrap; }
  .share-label { font-size:11px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--gray); }
  .share-tag { background:var(--navy-mid); border:1px solid var(--border); color:var(--gray); font-size:12px; padding:4px 12px; border-radius:20px; text-decoration:none; transition:border-color .15s, color .15s; }
  .share-tag:hover { border-color:var(--blue); color:var(--blue); }

  @media(max-width:640px){
    .container { padding:0 18px; }
    .hero { padding:32px 0 28px; }
    .intro-block { padding:22px 20px; }
    td, th { padding:10px 14px; }
    .cta-block { padding:30px 20px; }
    .reading-pill { margin-left:0; }
    .saving-num { font-size:36px; }
    pre { font-size:11px; }
  }
</style>
<!-- HERO -->
<header class="hero">
  <div class="container">
    <div class="meta-bar">
      <span class="badge">Kubernetes</span>
      <span class="badge">Karpenter</span>
      <span class="badge coral">FinOps</span>
      <span class="badge gray">Spot Instances</span>
      <div class="reading-pill">
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
        8 minutos de lectura
      </div>
    </div>
  </div>
</header>

<!-- CONTENT -->
<main class="content">
  <div class="container">

    <!-- INTRO -->
    <div class="intro-block">
      <p>Hay un momento que todo CTO o Engineering Manager conoce: abrís la consola de AWS Billing, ves el número del mes, y sentís que algo no cuadra. Tus aplicaciones no crecieron tanto, tu equipo no cambió de tamaño, <strong>pero la factura sigue subiendo</strong>. Mes tras mes.</p>
      <p>La razón suele ser la misma: <strong>estás pagando por capacidad que no usás</strong>. Nodos de Kubernetes corriendo 24/7 al 30% de utilización. Instancias sobredimensionadas &#8220;por si acaso&#8221;. Infraestructura que se escaló para un pico de tráfico hace tres meses y nunca se redujo.</p>
      <p>La combinación <strong>Karpenter + Spot Instances</strong> puede reducir tu factura de compute entre un 40% y 60%. El problema es que configurarlo correctamente — de forma estable y mantenible — es bastante más complejo de lo que parece.</p>
    </div>

    <!-- 01 EL PROBLEMA -->
    <section class="section">
      <span class="section-number">// 01</span>
      <h2><span class="hex-icon">👻</span>El problema: estás pagando por capacidad fantasma</h2>
      <div class="section-divider"></div>

      <p>En un setup típico de EKS con el Cluster Autoscaler tradicional, la historia suele ser así: ASGs con tipos de instancia fijos, nodos On-Demand las 24 horas, y un autoscaler que escala en bloques enteros.</p>

      <ul class="problem-list">
        <li>Los nodos se agregan en minutos, pero <strong>tardan mucho más en removerse</strong> — el autoscaler es conservador por diseño.</li>
        <li>Todos los nodos son del mismo tipo y tamaño, <strong>aunque tus workloads tengan necesidades muy diferentes.</strong></li>
        <li>Los nodos corren como On-Demand, <strong>pagando precio completo las 24 horas.</strong></li>
        <li>La utilización real rara vez supera el 40-50%, porque el autoscaler escala en bloques enteros.</li>
      </ul>

      <div class="callout callout-warning">
        <div class="callout-label">La metáfora correcta</div>
        <p>Estás pagando por un <strong>edificio de oficinas</strong> cuando solo necesitás coworking. Capacidad fija, precio fijo, sin importar cuánto la usás realmente.</p>
      </div>
    </section>

    <!-- 02 LA SOLUCIÓN -->
    <section class="section">
      <span class="section-number">// 02</span>
      <h2><span class="hex-icon">⚡</span>La solución: Karpenter + Spot Instances</h2>
      <div class="section-divider"></div>

      <p>Karpenter reemplaza al Cluster Autoscaler con un enfoque radicalmente distinto. En vez de trabajar con Auto Scaling Groups pre-definidos, <strong>mira directamente los pods que no pueden ser programados</strong> y provisiona exactamente la instancia que necesita en ese momento.</p>

      <ul class="styled-list">
        <li><strong>Selección inteligente:</strong> ¿Tenés un pod que necesita 2 vCPUs y 8 GB de RAM? Karpenter elige entre docenas de tipos de instancia y selecciona la opción más barata disponible en ese instante.</li>
        <li><strong>Consolidación activa:</strong> si detecta que puede mover pods de un nodo subutilizado a otro y liberar ese nodo, lo hace. Menos nodos ociosos, menos dinero desperdiciado.</li>
      </ul>

      <div class="callout callout-tip">
        <div class="callout-label">Las Spot Instances: el mismo hierro a otro precio</div>
        <p>Son capacidad sobrante de AWS que podés usar <strong>entre un 60% y 90% más barata</strong> que On-Demand. El catch: AWS puede reclamarlas con 2 minutos de aviso. Karpenter puede priorizar Spot automáticamente, seleccionar entre múltiples familias para maximizar disponibilidad, y <strong>caer a On-Demand solo cuando Spot no está disponible</strong>.</p>
      </div>

      <div class="saving-banner">
        <div class="saving-num">40–60%</div>
        <div class="saving-label">
          <strong>Reducción en factura de compute</strong>
          reportada por equipos que implementan correctamente Karpenter + Spot Instances en producción.
        </div>
      </div>
    </section>

    <!-- 03 INSTALAR KARPENTER -->
    <section class="section">
      <span class="section-number">// 03</span>
      <h2><span class="hex-icon">🔧</span>Instalar y configurar Karpenter</h2>
      <div class="section-divider"></div>

      <p>Karpenter necesita permisos IAM específicos para crear y destruir instancias EC2, gestionar ENIs, lanzar en subnets específicas y asumir roles. <strong>No es un simple <code>helm install</code>.</strong></p>

      <div class="callout callout-warning">
        <div class="callout-label">Antes del YAML: lo que necesitás tener listo</div>
        <p>Un IAM Role dedicado con políticas específicas, un Instance Profile para los nodos, e IRSA (IAM Roles for Service Accounts) correctamente mapeado. Y tus subnets y security groups taggeados — si no, <strong>Karpenter no encuentra dónde lanzar los nodos y falla silenciosamente.</strong></p>
      </div>

      <div class="code-block">
        <div class="code-header">
          <div class="code-dot"></div>
          <span>EC2NodeClass — configuración básica</span>
        </div>
        <pre><span class="kw">apiVersion:</span> <span class="val">karpenter.sh/v1</span>
<span class="kw">kind:</span> <span class="val">EC2NodeClass</span>
<span class="kw">metadata:</span>
  <span class="key">name:</span> <span class="val">default</span>
<span class="kw">spec:</span>
  <span class="key">role:</span> <span class="val">"KarpenterNodeRole"</span>
  <span class="key">amiSelectorTerms:</span>
    - <span class="key">alias:</span> <span class="val">al2023@latest</span>
  <span class="key">subnetSelectorTerms:</span>
    - <span class="key">tags:</span>
        <span class="key">karpenter.sh/discovery:</span> <span class="val">"my-cluster"</span>
  <span class="key">securityGroupSelectorTerms:</span>
    - <span class="key">tags:</span>
        <span class="key">karpenter.sh/discovery:</span> <span class="val">"my-cluster"</span>
  <span class="key">tags:</span>
    <span class="key">Environment:</span> <span class="val">production</span>
    <span class="key">ManagedBy:</span> <span class="val">karpenter</span></pre>
      </div>
    </section>

    <!-- 04 NODEPOOLS -->
    <section class="section">
      <span class="section-number">// 04</span>
      <h2><span class="hex-icon">🗂️</span>Definir NodePools con la estrategia correcta</h2>
      <div class="section-divider"></div>

      <p>Aquí empieza la complejidad real. Un NodePool define qué tipos de instancias puede usar Karpenter, en qué capacity types (Spot vs On-Demand), con qué límites. <strong>Un NodePool mal configurado puede generar más problemas que soluciones.</strong></p>

      <div class="code-block">
        <div class="code-header">
          <div class="code-dot"></div>
          <span>NodePool — workloads generales con Spot prioritario</span>
        </div>
        <pre><span class="kw">apiVersion:</span> <span class="val">karpenter.sh/v1</span>
<span class="kw">kind:</span> <span class="val">NodePool</span>
<span class="kw">metadata:</span>
  <span class="key">name:</span> <span class="val">general-workloads</span>
<span class="kw">spec:</span>
  <span class="key">template:</span>
    <span class="key">spec:</span>
      <span class="key">requirements:</span>
        <span class="cmt"># Más variedad = más disponibilidad Spot</span>
        - <span class="key">key:</span> <span class="val">karpenter.k8s.aws/instance-family</span>
          <span class="key">operator:</span> <span class="val">In</span>
          <span class="key">values:</span> <span class="val">["m5","m5a","m6i","m6a","m7i","c5","c6i","r5","r6i"]</span>
        <span class="cmt"># Muy chicos = overhead, muy grandes = desperdicio</span>
        - <span class="key">key:</span> <span class="val">karpenter.k8s.aws/instance-size</span>
          <span class="key">operator:</span> <span class="val">In</span>
          <span class="key">values:</span> <span class="val">["large","xlarge","2xlarge"]</span>
        <span class="cmt"># Spot primero, On-Demand como fallback</span>
        - <span class="key">key:</span> <span class="val">karpenter.sh/capacity-type</span>
          <span class="key">operator:</span> <span class="val">In</span>
          <span class="key">values:</span> <span class="val">["spot","on-demand"]</span>
      <span class="key">nodeClassRef:</span>
        <span class="key">group:</span> <span class="val">karpenter.k8s.aws</span>
        <span class="key">kind:</span> <span class="val">EC2NodeClass</span>
        <span class="key">name:</span> <span class="val">default</span>
  <span class="cmt"># Límites para no despertar con una factura sorpresa</span>
  <span class="key">limits:</span>
    <span class="key">cpu:</span> <span class="val">"200"</span>
    <span class="key">memory:</span> <span class="val">"400Gi"</span>
  <span class="key">disruption:</span>
    <span class="key">consolidationPolicy:</span> <span class="val">WhenEmptyOrUnderutilized</span>
    <span class="key">consolidateAfter:</span> <span class="val">30s</span></pre>
      </div>

      <div class="callout callout-warning">
        <div class="callout-label">Las decisiones no son triviales</div>
        <p><strong>¿Cuántas familias de instancias incluir?</strong> Pocas = más interrupciones Spot, demasiadas = instancias subóptimas. <strong>¿Qué límites poner?</strong> Muy bajos = tu app no escala, muy altos = riesgo de factura desbocada. <strong>¿Qué política de consolidación?</strong> Muy agresiva = inestabilidad, muy conservadora = desperdicio.</p>
      </div>
    </section>

    <!-- 05 SEPARAR WORKLOADS -->
    <section class="section">
      <span class="section-number">// 05</span>
      <h2><span class="hex-icon">⚖️</span>Separar workloads críticos de no-críticos</h2>
      <div class="section-divider"></div>

      <p><strong>No todo debería correr en Spot.</strong> Tu base de datos, tu sistema de colas, tu control plane — necesitan estabilidad. Esto implica múltiples NodePools con estrategias distintas.</p>

      <div class="code-block">
        <div class="code-header">
          <div class="code-dot"></div>
          <span>NodePool crítico — solo On-Demand</span>
        </div>
        <pre><span class="kw">apiVersion:</span> <span class="val">karpenter.sh/v1</span>
<span class="kw">kind:</span> <span class="val">NodePool</span>
<span class="kw">metadata:</span>
  <span class="key">name:</span> <span class="val">critical</span>
<span class="kw">spec:</span>
  <span class="key">template:</span>
    <span class="key">spec:</span>
      <span class="key">requirements:</span>
        - <span class="key">key:</span> <span class="val">karpenter.sh/capacity-type</span>
          <span class="key">operator:</span> <span class="val">In</span>
          <span class="key">values:</span> <span class="val">["on-demand"]</span>   <span class="cmt"># Sin Spot para lo crítico</span>
      <span class="key">taints:</span>
        - <span class="key">key:</span> <span class="val">workload-type</span>
          <span class="key">value:</span> <span class="val">critical</span>
          <span class="key">effect:</span> <span class="val">NoSchedule</span>     <span class="cmt"># Solo pods que toleren este taint</span>
      <span class="key">nodeClassRef:</span>
        <span class="key">group:</span> <span class="val">karpenter.k8s.aws</span>
        <span class="key">kind:</span> <span class="val">EC2NodeClass</span>
        <span class="key">name:</span> <span class="val">default</span>
  <span class="key">limits:</span>
    <span class="key">cpu:</span> <span class="val">"50"</span></pre>
      </div>

      <div class="callout callout-warning">
        <div class="callout-label">El riesgo de un error de configuración</div>
        <p>Cada uno de tus deployments necesita los <code>tolerations</code> y <code>node affinity</code> correspondientes. <strong>Si un pod crítico termina en un nodo Spot por un error de configuración</strong>, vas a tener interrupciones en producción que no deberían haber ocurrido.</p>
      </div>
    </section>

    <!-- 06 INTERRUPCIONES SPOT -->
    <section class="section">
      <span class="section-number">// 06</span>
      <h2><span class="hex-icon">🛡️</span>Manejar las interrupciones de Spot</h2>
      <div class="section-divider"></div>

      <p>Spot Instances se interrumpen. Es parte del trato. AWS te da 2 minutos de aviso, y si no estás preparado, tus pods mueren sin gracia. Para manejar esto correctamente necesitás:</p>

      <ul class="styled-list">
        <li><strong>Integración nativa de Karpenter</strong> con las señales de interrupción de Spot (o AWS Node Termination Handler).</li>
        <li><strong>Pod Disruption Budgets (PDB)</strong> en cada deployment crítico para garantizar que siempre haya réplicas disponibles.</li>
        <li><strong>Topology spread constraints</strong> para que las réplicas de un mismo servicio no caigan todas en el mismo nodo o zona.</li>
        <li><strong>Graceful shutdown</strong> configurado en tus aplicaciones para terminar requests en vuelo antes de morir.</li>
      </ul>

      <div class="code-block">
        <div class="code-header">
          <div class="code-dot"></div>
          <span>PDB + Topology Spread — resiliencia ante interrupciones Spot</span>
        </div>
        <pre><span class="kw">apiVersion:</span> <span class="val">policy/v1</span>
<span class="kw">kind:</span> <span class="val">PodDisruptionBudget</span>
<span class="kw">metadata:</span>
  <span class="key">name:</span> <span class="val">my-app-pdb</span>
<span class="kw">spec:</span>
  <span class="key">minAvailable:</span> <span class="val">2</span>    <span class="cmt"># Siempre al menos 2 réplicas disponibles</span>
  <span class="key">selector:</span>
    <span class="key">matchLabels:</span>
      <span class="key">app:</span> <span class="val">my-app</span>
<span class="cmt">---</span>
<span class="cmt"># Deployment con topology spread para distribuir réplicas</span>
<span class="kw">      topologySpreadConstraints:</span>
        - <span class="key">maxSkew:</span> <span class="val">1</span>
          <span class="key">topologyKey:</span> <span class="val">kubernetes.io/hostname</span>
          <span class="key">whenUnsatisfiable:</span> <span class="val">DoNotSchedule</span>
        - <span class="key">maxSkew:</span> <span class="val">1</span>
          <span class="key">topologyKey:</span> <span class="val">topology.kubernetes.io/zone</span>
          <span class="key">whenUnsatisfiable:</span> <span class="val">ScheduleAnyway</span></pre>
      </div>

      <div class="callout callout-warning">
        <div class="callout-label">El costo real de ignorar esto</div>
        <p>Sin PDBs ni topology spread, una interrupción de Spot puede significar un downtime que <strong>le cuesta más a tu negocio que lo que ahorraste</strong> en la factura de AWS. Los ahorros de Spot solo valen si la resiliencia está configurada correctamente.</p>
      </div>
    </section>

    <!-- 07 MONITOREO -->
    <section class="section">
      <span class="section-number">// 07</span>
      <h2><span class="hex-icon">📊</span>Monitorear y ajustar continuamente</h2>
      <div class="section-divider"></div>

      <p>Karpenter no es &#8220;configurar y olvidarse&#8221;. Para mantener los ahorros necesitás visibilidad continua sobre preguntas como:</p>

      <ul class="styled-list">
        <li>¿Cuál es la proporción real de <strong>Spot vs On-Demand</strong> en este momento?</li>
        <li>¿Con qué frecuencia se interrumpen tus Spot Instances?</li>
        <li>¿Cuál es la <strong>utilización real de los nodos</strong> después de la consolidación?</li>
        <li>¿Estás cerca de los <strong>límites de tu NodePool</strong>?</li>
        <li>¿Hay Spot pools que se están agotando y deberías agregar más familias de instancias?</li>
      </ul>

      <div class="callout callout-tip">
        <div class="callout-label">Lo que requiere</div>
        <p>Dashboards en Grafana o CloudWatch, alertas configuradas, y alguien en el equipo que entienda tanto Kubernetes como el mercado de Spot para <strong>tomar decisiones informadas</strong> cuando los patrones cambian.</p>
      </div>
    </section>

    <!-- TABLA COMPLEJIDAD -->
    <div class="table-wrapper">
      <div class="table-header">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#90cdf4" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="9" x2="9" y2="21"/></svg>
        <span>Resumen — Lo que implica hacerlo bien</span>
      </div>
      <table>
        <thead>
          <tr>
            <th>Qué configurar</th>
            <th>Complejidad</th>
            <th>Detalle</th>
          </tr>
        </thead>
        <tbody>
          <tr><td>IAM Roles y políticas para Karpenter</td><td class="complexity-high">Alta</td><td>IRSA, Instance Profile, 15+ acciones IAM específicas</td></tr>
          <tr><td>EC2NodeClass con subnets y SGs taggeados</td><td class="complexity-mid">Media</td><td>Depende de cómo esté tu networking existente</td></tr>
          <tr><td>NodePools con estrategia Spot/On-Demand</td><td class="complexity-high">Alta</td><td>Decisiones de familias, tamaños, límites y consolidación</td></tr>
          <tr><td>Separación de workloads críticos vs generales</td><td class="complexity-high">Alta</td><td>Taints, tolerations y affinity por deployment</td></tr>
          <tr><td>Manejo de interrupciones Spot</td><td class="complexity-high">Alta</td><td>PDBs, topology spread, graceful shutdown</td></tr>
          <tr><td>Monitoreo y ajuste continuo</td><td class="complexity-cont">Continua</td><td>Dashboards, alertas, revisión periódica del mercado Spot</td></tr>
        </tbody>
      </table>
    </div>

    <!-- YAPA -->
    <section class="section">
      <span class="section-number">// Yapa</span>
      <h2><span class="hex-icon">🌙</span>Apagado programado de clusters no productivos</h2>
      <div class="section-divider"></div>

      <p>Hay un ahorro más que la mayoría de los equipos ni siquiera considera: <strong>¿para qué tener prendidos tus clusters de dev y staging las 24 horas si tu equipo trabaja 8?</strong></p>

      <div class="schedule-visual">
        <div class="sv-label">Cluster dev/staging — ventana de actividad semanal</div>
        <div class="sv-row">
          <div class="sv-day-label">Lun</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block on">On</div>
          <div class="sv-block on">On</div>
          <div class="sv-block on">On</div>
          <div class="sv-block on">On</div>
          <div class="sv-block on">On</div>
          <div class="sv-block on">On</div>
          <div class="sv-block on">On</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
        </div>
        <div class="sv-row">
          <div class="sv-day-label">Vie</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block on">On</div>
          <div class="sv-block on">On</div>
          <div class="sv-block on">On</div>
          <div class="sv-block on">On</div>
          <div class="sv-block on">On</div>
          <div class="sv-block on">On</div>
          <div class="sv-block on">On</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
        </div>
        <div class="sv-row">
          <div class="sv-day-label">Sáb</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
          <div class="sv-block off">Off</div>
        </div>
        <div class="sv-legend">
          <div class="sv-legend-item"><div class="sv-dot on"></div>Cluster activo (9 AM–6 PM L–V)</div>
          <div class="sv-legend-item"><div class="sv-dot off"></div>Cluster apagado</div>
        </div>
        <div class="sv-savings">
          <div class="sv-savings-text">Cluster dev/staging de <strong style="color:var(--white)">USD 1.500/mes</strong> corriendo 24/7</div>
          <div class="sv-savings-num">→ USD 400/mes</div>
        </div>
      </div>

      <div class="callout callout-tip">
        <div class="callout-label">El impacto real</div>
        <p>Si tu cluster de desarrollo cuesta USD 1.500/mes corriendo 24/7, con apagado programado pasa a costar aproximadamente <strong>USD 400/mes</strong>. Multiplicá eso por cada entorno no productivo (dev, staging, QA, demo) y el ahorro puede <strong>superar al de Spot Instances</strong>.</p>
      </div>
    </section>

    <!-- TABLA ANTES/DESPUÉS -->
    <div class="table-wrapper">
      <div class="table-header">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#90cdf4" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
        <span>Sin SleakOps vs. Con SleakOps</span>
      </div>
      <table class="ba-table">
        <thead>
          <tr>
            <th>Sin SleakOps</th>
            <th>Con SleakOps</th>
          </tr>
        </thead>
        <tbody>
          <tr><td class="td-bad">Instalar y configurar Karpenter manualmente</td><td class="td-good">Pre-instalado y listo desde el día uno</td></tr>
          <tr><td class="td-bad">Diseñar estrategia de NodePools desde cero</td><td class="td-good">NodePools optimizados por defecto, personalizables</td></tr>
          <tr><td class="td-bad">Configurar manejo de interrupciones Spot</td><td class="td-good">Resiliencia integrada: PDBs, topology spread, graceful shutdown</td></tr>
          <tr><td class="td-bad">Crear dashboards de monitoreo de costos</td><td class="td-good">Visibilidad de costos incluida con Grafana</td></tr>
          <tr><td class="td-bad">Semanas de prueba y ajuste antes de ver ahorros</td><td class="td-good">Ahorros desde el primer deployment</td></tr>
          <tr><td class="td-bad">Clusters no productivos corriendo 24/7</td><td class="td-good">Apagado programado por entorno configurable</td></tr>
        </tbody>
      </table>
    </div>

    <!-- CTA -->
    <div class="cta-block">
      <h2>Tu factura de AWS no debería ser un misterio</h2>
      <p>Reducir costos en AWS no requiere magia — requiere las herramientas correctas configuradas correctamente. La pregunta es si tu equipo debería invertir semanas en convertirse en expertos en Karpenter, o si ese tiempo estaría mejor invertido construyendo el producto que genera ingresos.</p>
      <a href="#" class="cta-btn">Conocé SleakOps →</a>
    </div>

    <!-- TAGS -->
    <div class="share-row">
      <span class="share-label">Tags:</span>
      <a href="#" class="share-tag">#Karpenter</a>
      <a href="#" class="share-tag">#SpotInstances</a>
      <a href="#" class="share-tag">#AWS</a>
      <a href="#" class="share-tag">#FinOps</a>
      <a href="#" class="share-tag">#Kubernetes</a>
      <a href="#" class="share-tag">#EKS</a>
      <a href="#" class="share-tag">#CostOptimization</a>
    </div>

  </div>
</main>
</div><p>The post <a href="https://sleakops.com/2026/03/20/como-reducir-tu-factura-de-aws-en-un-60-usando-karpenter-y-spot-instances-sin-sacrificar-estabilidad/">Ahorra 60% en AWS con Karpenter y Spot Instances</a> first appeared on <a href="https://sleakops.com">SleakOps</a>.</p><p>La entrada <a href="https://sleakops.com/2026/03/20/como-reducir-tu-factura-de-aws-en-un-60-usando-karpenter-y-spot-instances-sin-sacrificar-estabilidad/">Ahorra 60% en AWS con Karpenter y Spot Instances</a> se publicó primero en <a href="https://sleakops.com">SleakOps</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
