Mr.PlanB Logo
    Volver al blog
    Kubernetes
    DevOps
    Infraestructura

    Cómo ejecutar un monolito Java en Kubernetes sin apaños con NodePort

    18 de febrero de 2026
    5 min de lectura

    Un desarrollador que ejecuta un monolito Java en Kubernetes describió esta configuración:

    • Host Ubuntu
    • Apache2 gestionando SSL
    • Clúster kind
    • Moqui + OpenSearch en pods
    • MySQL en el host
    • Tipo de servicio: NodePort
    • Apache como proxy inverso hacia 172.x.x.x:30083

    Funciona, pero parece montado con parches, y ese instinto es acertado. Esta es una estructura más limpia, orientada a producción, que se mantiene autogestionada y no necesita balanceadores de carga en la nube.

    1. ¿Es malo usar NodePort tras un proxy inverso de Apache?

    Para desarrollo local está bien. Para producción resulta frágil. Apache redirige el tráfico hacia una IP interna de kind, lo que resulta precario porque:

    • kind no está pensado para entornos de producción
    • Las IP del plano de control no son límites de diseño estables
    • Te estás saltando el modelo de ingress previsto en Kubernetes

    Evitar balanceadores de carga gestionados en la nube y ejecutarlo todo en tu propio servidor es una elección perfectamente razonable. En bare metal, una configuración limpia para producción tiene este aspecto:

    Arquitectura recomendada (autogestionada)

    Internet
       ↓
    Apache (or Nginx) — TLS termination
       ↓
    Kubernetes Ingress Controller
       ↓
    Service (ClusterIP)
       ↓
    Pods
    

    En lugar de NodePort, instala un controlador de Ingress como:

    • NGINX (nginx-ingress)
    • Traefik

    Luego puedes apuntar Apache al controlador de ingress en vez de a un NodePort o, mejor aún, eliminar Apache por completo y dejar que el controlador de ingress gestione TLS mediante cert-manager.

    Si prefieres mantenerte completamente autogestionado en un único servidor, la opción más limpia es instalar nginx-ingress, terminar TLS en el ingress y usar hostNetwork, o MetalLB si más adelante escalas a varios nodos.

    NodePort funciona bien como mecanismo de bajo nivel, e Ingress existe para no tener que conectar puertos a mano constantemente.

    2. Autoescalado de un monolito Java

    Los pods consumían entre 400 y 500MB de RAM cada uno, de modo que escalar de 1 a 3 réplicas requirió unos 1.5GB de memoria. Así es como opera el escalado horizontal. Kubernetes no comparte el heap de la JVM entre réplicas, por lo que cada réplica es una JVM completa, y el consumo de memoria deriva de la propia arquitectura de Java. Aun así, hay margen de optimización.

    Ajuste de la JVM

    Si no defines explícitamente estos flags, la JVM puede sobreasignar recursos:

    -XX:MaxRAMPercentage
    -Xms
    -Xmx
    

    Establece límites de memoria en Kubernetes:

    resources:
      requests:
        memory: "512Mi"
      limits:
        memory: "512Mi"
    

    Luego ajusta la JVM para que respete el límite de memoria del contenedor. De lo contrario, el pod consumirá más memoria de la prevista.

    Estrategia de HPA

    El Horizontal Pod Autoscaler responde mejor con CPU que con memoria, ya que el uso de memoria tiende a retenerse y el escalado basado en CPU reacciona con mayor rapidez. Un objetivo de uso de CPU del 90% es razonable. El dato más útil que conviene determinar es cuántas peticiones simultáneas puede soportar un pod de Moqui. Mide esa cifra y utilízala como tu unidad de escalado.

    La pregunta de fondo

    Tener 3 réplicas por disponibilidad o por volumen de tráfico es correcto. Si estás escalando porque la memoria se dispara con el almacenamiento de sesiones, eso señala un fallo de arquitectura, y conduce al problema principal de este montaje.

    3. Sesiones, logs, bloqueos y cuellos de botella en PVC

    El monolito mantiene estos directorios en una PVC compartida de tipo RWX entre varias réplicas, y se encuentra con fallos de bloqueo (LOCK):

    • logs/
    • txlogs/
    • sessions/

    Este es el verdadero obstáculo, originado por la semántica del sistema de archivos compartido. Varios pods Java escribiendo en el mismo volumen RWX generarán contención por bloqueos, especialmente con las sesiones.

    Las sesiones no deben estar en disco

    Las sesiones en disco no escalan horizontalmente. El método estándar en producción para escalar un monolito Java es Redis, ya sea un Redis externo, Redis en Kubernetes o un clúster de Redis. El almacén de sesiones pasa a ser un almacén clave-valor centralizado en memoria, eliminando bloqueos de archivos, el problema de RWX y la pérdida de sesión cuando una petición llega a un pod distinto.

    Los logs no deben estar en una PVC compartida

    Los logs deben ir a stdout, donde Fluent Bit, Loki o un stack ELK puedan recopilarlos, ya que el modelo de registro de Kubernetes espera stdout en lugar de archivos compartidos. Si necesitas trazabilidad de transacciones, envía los logs a un sistema centralizado en vez de a un disco compartido.

    Archivos estáticos y subidas

    Esto deja una separación de responsabilidades clara:

    • Sesiones → Redis
    • Logs → registro centralizado
    • Subidas → almacenamiento de objetos

    El almacenamiento de objetos encaja con las subidas porque resulta seguro para acceder desde cualquier réplica, no requiere bloqueos, previene la corrupción de archivos y escala de forma independiente. Si trabajas totalmente autoalojado, utiliza MinIO (compatible con S3) o NFS, aunque NFS es un enfoque más antiguo frente al almacenamiento de objetos. Una PVC RWX es la opción menos escalable aquí.

    4. ¿Debería MySQL ejecutarse dentro de Kubernetes?

    MySQL corre actualmente en el host, y eso es adecuado. Ejecutar bases de datos dentro de Kubernetes como StatefulSet es factible, pero añade gestión de volúmenes, orquestación de backups y complejidad en alta disponibilidad. En un único servidor bare metal, mantener MySQL fuera de Kubernetes es totalmente razonable. Si pasas a varios nodos, vuelve a evaluarlo.

    5. Administrar Kubernetes mediante una interfaz gráfica

    Puedes gestionar el clúster a través de una interfaz gráfica con:

    • Lens
    • Kubernetes Dashboard
    • Portainer

    Lens ofrece con diferencia la experiencia más limpia para la gestión remota de clústeres. Descargas el kubeconfig y te conectas, sin depender de accesos frágiles mediante NodePort.

    La arquitectura de producción para este caso

    Para un despliegue autoalojado en hardware propio con control total, una arquitectura limpia se estructura así:

    • Servidor bare metal
    • Distribución adecuada de Kubernetes (no kind), como RKE2, Talos o k3s
    • nginx-ingress instalado
    • TLS gestionado en el ingress
    • Pods de Moqui (sin sistema de archivos compartido para sesiones)
    • Redis para sesiones
    • Almacenamiento de objetos (MinIO) para subidas
    • MySQL fuera de k8s o como StatefulSet
    • Registro de logs centralizado

    Eso elimina el NodePort, las dependencias de IP del plano de control y los bloqueos en RWX.

    La visión sincera

    La configuración está atascada porque los monolitos esperan un disco compartido mientras que Kubernetes espera pods sin estado. En cuanto trasladas:

    • Sesiones → Redis
    • Logs → stdout
    • Subidas → almacenamiento de objetos

    el escalado se vuelve predecible, y lo predecible es una ventaja. La arquitectura actual funciona, pero está acoplada al esquema mental de un solo equipo, y Kubernetes rinde al máximo cuando desaparece esa atadura al sistema de archivos.