Kubernetes: proxy-to-service para publicar tus puertos en entornos de desarrollo

¿Estás desarrollando con Kubernetes? ¿Es frustrante no publicar servicios por debajo del puerto 30000 sin reconfigurar el cluster? ¿Queremos mantener nuestro servicio dentro de las buenas prácticas? Es explico una alternativa más que interesante rapidísima de implementar.

4 min de lectura
Kubernetes: proxy-to-service para publicar tus puertos en entornos de desarrollo

Ya no se puede hablar de cloud o en su defecto de Azure sin hablar de contenedores y por consiguiente Docker. Este mundo está evolucionando con una cantidad ingente de actores tanto en las tecnologías de contenerización (Docker, rkt, CRI-O...) como de orquestación (Swarm, Kubernetes, DC/OS...).

Entre todas ellas la combinación que se está erigiendo como estandar de la industria es Docker y Kubernetes, algo que recientemente ha sido muy impulsado por el anuncio del soporte a Kubernetes en Docker Enterprise Edition y la aparición de AKS, el PaaS de Azure para implementar un cluster de Kubernetes que aún se encuentra en preview.

Hay estupendos lugares para aprender Kubernetes, entre los que destaco su web oficial de tutoriales, el genial curso de introducción de a Kubernetes de Nigel Poulton o su versión libro.

Con tanta información de calidad ahí fuera, no voy a escribir tutoriales de Docker o Kubernetes en este blog, sino que, como de costumbre, os contaré curiosidades que me encuentro y soluciones a problemas.

Un problema habitual en entornos de desarrollo

En Kubernetes los Services son un objeto fundamental que nos permite dar acceso estable a nuestros contenedores sin importar si estos se destruyen, se crean o cambian de nodo. Para exponerlos fuera de la red de Kubernetes tenemos dos métodos:

  • NodePort. Donde publicamos un puerto del nodo al que podemos conectarnos y este mapea al la Cluster IP interna.
  • LoadBalancer. Si estamos desplegado en un entorno cloud, podemos instruir a Kubernetes que cree un balanceador de carga y lo apunte a la Cluster IP. Esta opción no es válida si estamos en un entorno on-premises.

NodePort es una alternativa fácil y rápida para probar nuestro entorno, sin embargo tiene el problema de que en una configuración por defecto de Kubernetes sólo se nos permite asignar puertos que se encuentren en el rango 30000-32767. Aunque podemos cambiar esta configuración, el que esté por defecto nos impulsa a la buena práctica de que en frente de nuestro cluster debería haber un balanceador de carga. Imaginad lo rápido que nos quedaríamos sin puertos si cualquier servicio puede dejar ocupados el 80 o el 443 por ejemplo.

k8s-proxy-to-service1-1

Así pues:

  • Si estamos en on-premises publicamos con NodePort y ajustamos nuestro balanceador.
  • Si estamos en cloud utilizamos LoadBalancer y Kubernetes nos crea uno.

¿Y si estamos en un entorno de desarrollo local y tenemos que apañarnos con NodePort? ¡Hay alternativa!

Solución: Ingress ó proxy-to-service

Ingress es un objeto pensado para dotar a Kubernetes de servicios de acceso externos avanzados que pueden operar en L7, tales como balanceo de carga HTTP, terminación TLS y hosting virtual basado en nombres. Sin embargo, este objeto aun se encuentra en desarrollo e implementarlo requiere más complejidad de la que seguramente estamos dispuestos a asumir en nuestro entorno de programación. Lo dejaremos para otro día.

¿Qué es entonces el proxy-to-service? Se trata de una imagen Docker que contiene un sistema mínimo busybox y el pequeño pero poderoso socat (SOcket CAT), una herramienta UNIX de línea de comandos que permite establecer flujos de datos de red bidireccionales entre dos puntos. Gracias a esta imagen podemos establecer un Pod ligerísimo para redireccionar el puerto que queramos a nuestro Service de Kubernetes, ya sea el 23 o el 80.

Esta es una solución válida para entornos de desarrollo, laboratorios o demos. ¡No la implementes en producción! Utiliza un balanceador de carga en ese caso.

Quedaría con un aspecto tal que así:
k8s-proxy-to-service2

La implementación de este servicio es trivial:

apiVersion: v1
kind: Pod
metadata:
  name: port-proxy
spec:
  hostname: port-proxy
  containers:
  - name: http-proxy-tcp
    image: k8s.gcr.io/proxy-to-service:v2
    args: [ "tcp", "80", "web-server" ]
    ports:
    - name: http
      protocol: TCP
      containerPort: 80
      hostPort: 80
  - name: https-proxy-tcp
    image: k8s.gcr.io/proxy-to-service:v2
    args: [ "tcp", "443", "web-server" ]
    ports:
    - name: https
      protocol: TCP
      containerPort: 443
      hostPort: 443

La definición práctimanete habla por si sola, pero merece la pena comentar:

  • args. Son los parámetros que le haremos llegar al socat. El primer es el protocolo, el segundo es el puerto donde va a escuchar en origen y conectar en destino y el último es la IP o nombre DNS del Service destino al que nos conectamos.
  • hostPort. El puerto que se va a publicar en el nodo donde despleguemos este Pod. A diferencia de los Services, los Pods no tienen restricciones en qué rango de puerto utilizar en un nodo. No obstante, se recomienda no utilizarlo salvo que se entienda muy bien las implicaciones de escalabilidad por estar ocupando puertos clave. En nuestro entorno de desarrollo esto no nos importa.

Como curiosidad podréis ver que este Pod consta de dos contenedores, uno por cada puerto que redireccionamos.

¡Con esto solucionamos el problema y podemos simular conexiones productivas en nuestro entorno de desarrollo a falta de un balanceador de carga!

¡Happy connecting!