Hace poco que Azure Kubernetes Service está en disponibilidad general, con nuevas características que lo hacen más que interesante. Sin embargo, los contenedores no son algo ni mucho menos exclusivos de la nube y es perfectamente posible poner a punto una plataforma on-premises; es más, podemos establecer un entorno híbrido donde ambos trabajen de forma conjunta.

Aunque la llegada de Kubernetes a Docker ha propiciado que Docker Community Edition sea una plataforma de desarrollo on-premises fácil de instalar, muy completa y sobre todo compatible con Kubernetes; lo cierto es que echaba de menos establecer en mi infraestructura on-premises un entorno más parecido a uno productivo.

El stack de contenerización

Antes de continuar con el artículo, considero importante hacer un inciso breve sobre el stack de contenerización:

Si vamos de abajo hacia arriba:

  • Hardware. No íbamos a ejecutar todo sobre un ábaco ¿no?
  • Sistema operativo. Fundamentalmente Linux, ojo con Windows y su Windows Subsystem for Linux al que Docker le está dando soporte experimental y no va nada mal.
  • Motor de contenerización. Si bien el más conocido, extendido y maduro es Docker, hay que tener en cuenta que no es el único y que hay más iniciativas. El motor no es ni más ni menos que nuestro puerto de carga al cual van a llegar nuestros barcos -o imágenes Docker- cargados de contenedores. Es de crucial importancia pues es la base de operación de todo el sistema que se contruye por encima.
  • Orquestador. El motor nos es más que suficiente para ejecutar... digamos unos 10-20 contenedores. ¿Pero y si resulta que tenemos 5000? ¿Y 20000? Probablemente necesitemos algo de ayuda y es justo aquí donde un orquestador entra en juego. De entre todos los que tenemos para elegir, Kubernetes se ha impuesto de manera clara en el mercado.
  • Plataforma. Probablemente un orquestador y un motor es todo lo que necesitemos en un entorno de desarrollo, incluso en uno de pruebas; pero esto no es suficiente en un entorno productivo. Probablemente debamos complementar el orquestador con herramientas de gestión avanzadas, un sistema de gestión de gobernanza y permisos, escalabilidad automática en entornos de cloud o sistemas de virtualización on-premises... Aquí es donde una plataforma de contenerización entra en juego, como puede ser Docker Enterprise Edition, RedHat's OpenShift, DC/OS o Rancher.

La elección de un orquestador o una plataforma

Para un entorno de laboratorio doméstico como servidor quería, pensaba que inicialmente no tenía mucha alternativa a implementar un cluster de Kubernetes sin más, cosa que iba a hacer mediante Kubespray. ¿Razones? Docker EE y OpenShift saltan bastante de lo que el bolsillo de servidor iba a permitirse para este menester, mientras que DC/OS -que recientemente ha incorporado soporte para Kubernetes- tiene un concepto mucho más amplio.

Sin embargo, hablando con un antiguo profesor mío de la siempre excelente Universidad de Almería, José Antonio Martínez, me comentaba que hay una plataforma que definitivamente debía probar: Rancher.

Pasé a echarle un vistazo y:

  • Recién salida la versión 2.0, anuncian Kubernetes como orquestador, descatalogando el que usaban anteriormente, llamado cattle (ganado, muy apropiado). ¡Genial!
  • Código abierto bajo licencia Apache 2.0. Así, puedes ver todo el código fuente de Rancher y contrubuir al mismo; lo que se deriva que no hay coste de licencia por implementar la plataforma en sí. Intuyo que su modelo de negocio es mediante soporte y consultoría del producto.

Quiero dejar claro que bajo mi punto de vista, que un software sea código abierto no lo hace per-sé mejor que otro que no lo sea; sin embargo para construirme un entorno doméstico estable no era cuestión invertir las cifras que cuestan las licencias de plataformas consagradas como Docker EE u OpenShift, por lo que en este caso concreto sí que inclina claramente la balanza. Dicho esto, el código abierto es una de las filosofías que más ha hecho por el mundo de la computación y la informática, y como tal siempre tendrá apoyo por parte de servidor.

Abandoné la idea de Kubespray y me adentré en Rancher.

Implementando Kubernetes on-premises mediante Rancher 2.0: ¿qué necesitamos?

Si en algo brilla Rancher, es en que puedes tener tu cluster de Kubernetes en alta disponibilidad funcionando en apenas una tarde sin tener siquiera conocimientos de Kubernetes. No voy a explicar en este artículo cómo se hace, pues para eso tienen esta guía de implementación.

Entre todas las posibilidades, servidor quería una implementación en alta disponibilidad con dos nodos y que permitera además cifrar las comunicaciones con Let's Encrypt.

Para los que como servidor, pasamos el día en Azure, no viene mal dar una vuelta de vez en cuando al on-premises para hacernos conscientes de la cantidad de facilidades que la nube nos da y que en on-premises debemos manejar manualmente. En este caso para implementar Rancher en alta disponibilidad necesitamos:

ESCENARIO 1. Un balanceador de carga L4 e Ingress Controllers de k8s con NGINX, los cuales manejan tráfico HTTPS y necesitan sus correspondientes certificados TLS.

ESCENARIO 2. Un balanceador de carga L7 y los mismos Ingress Controllers de k8s con NGINX, pero que en este caso ya esperan tráfico HTTP y por tanto no manejan certificados TLS.

Como os podéis imaginar, para usarlo con Let's Encrypt es mucho más sencillo el escenario 2, dado que sólo necesitamos mantener los certificados TLS en el balanceador L7 externo y a partir de aquí se hace offloading a HTTP. La contrapartida es que a partir de este punto nuestro tráfico no está cifrado y por tanto la red local debe ser de confianza. Para un laboratorio es suficiente, pero la era de las redes privadas de confianza y la seguridad basada puramente en perímetro vió su fin hace ya bastantes años, por lo que en un entorno productivo probablemente no queramos hacer terminación TLS.

IIS ARR como balanceador L7 (sólo HTTP y HTTPS)

Si optamos por un balanceador externo, la documentación de Rancher nos da las guías de lo que debe soportar para ser compatible con la solución, que viene a ser:

  • Soportar conexiones websocket.
  • Soportar los protocolos SPDY o bién HTTP/2.
  • Permitir hacerle llegar a Rancher los siguientes encabezados HTTP:
    • Host. Con el FQDN que el cliente ha utilizado para llegar a nuestra infraestructura, como puede ser tetrinet.calnus.com.
    • X-Forwarded-Proto. Identificar el protocolo con el que el cliente se conecta al balanceador de carga. Si es valor es https, Rancher no hara redirección HTTP a HTTPS.
    • X-Forwarded-Port. Se utiliza para identificar el protocolo con el que se extá conectando a Rancher.
    • X-Forwarded-For. La IP original del cliente que acceder al balanceador de carga.

El ya venerable y tremendamente versátil módulo oficial de Microsoft para Internet Information Server, ARR 3.0 (Application Request Routing), soporta todos estos puntos y por tanto nos puede hacer las veces de balanceador L7.

IIS ARR sólo soporta balanceo de carga de protocolo HTTP o HTTPS; por lo que no es una solución universal para nuestro cluster on-premises de Kubernetes. En la implementación doméstica de servidor, utilizo IIS ARR para todas las aplicaciones HTTP. ¿Y aquellas que hablan protocolos distintos? Utilizo NodePort en la definición de Kubernetes y mapeo mediante un balanceador de L4 que tengo en mi red doméstica.

Implementando Rancher

Como he comentado al principio, no voy a explicar su implementación, pues la documentación oficial de Rancher es bastante clara y sencilla, con las acciones que debemos dar paso a paso.

Teniendo nuestros sistemas operativos preparados y con una versión soportada de Docker, podemos utilizar RKE y el siguiente archivo JSON para instanciar nuestra implementación.

nodes:
  - address: MAD-RANCHER0
    user: root
    role: [controlplane,etcd,worker]
    ssh_key_path: ~/.ssh/id_rsa
  - address: MAD-RANCHER1
    user: root
    role: [controlplane,etcd,worker]
    ssh_key_path: ~/.ssh/id_rsa

addons: |-
  ---
  kind: Namespace
  apiVersion: v1
  metadata:
    name: cattle-system
  ---
  kind: ServiceAccount
  apiVersion: v1
  metadata:
    name: cattle-admin
    namespace: cattle-system
  ---
  kind: ClusterRoleBinding
  apiVersion: rbac.authorization.k8s.io/v1
  metadata:
    name: cattle-crb
    namespace: cattle-system
  subjects:
  - kind: ServiceAccount
    name: cattle-admin
    namespace: cattle-system
  roleRef:
    kind: ClusterRole
    name: cluster-admin
    apiGroup: rbac.authorization.k8s.io
  ---
  apiVersion: v1
  kind: Service
  metadata:
    namespace: cattle-system
    name: cattle-service
    labels:
      app: cattle
  spec:
    ports:
    - port: 80
      targetPort: 80
      protocol: TCP
      name: http
    selector:
      app: cattle
  ---
  apiVersion: extensions/v1beta1
  kind: Ingress
  metadata:
    namespace: cattle-system
    name: cattle-ingress-http
    annotations:
      nginx.ingress.kubernetes.io/proxy-connect-timeout: "30"
      nginx.ingress.kubernetes.io/proxy-read-timeout: "1800"   # Max time in seconds for ws to remain shell $
      nginx.ingress.kubernetes.io/proxy-send-timeout: "1800"   # Max time in seconds for ws to remain shell $
      nginx.ingress.kubernetes.io/ssl-redirect: "false"        # Disable redirect to ssl
  spec:
    rules:
    - host: rancher.hispamsx.org
      http:
        paths:
      http:
        paths:
        - backend:
            serviceName: cattle-service
            servicePort: 80
  ---
  kind: Deployment
  apiVersion: extensions/v1beta1
  metadata:
    namespace: cattle-system
    name: cattle
  spec:
    replicas: 1
    template:
      metadata:
        labels:
          app: cattle
      spec:
        serviceAccountName: cattle-admin
        containers:
        - image: rancher/rancher:latest
          args:
          - --no-cacerts
          imagePullPolicy: Always
          name: cattle-server
          ports:
          - containerPort: 80
            protocol: TCP

Sólo hay dos elementos que tenemos que modificar en todo este JSON:

  • Nodes no es mas que un array con todas las máquinas que vamos a implementar en alta disponibilidad.
  • Línea 67. Especifico el FQDN del host por el cual se accede al entorno.

Y con ello listo para ejecutar:

# ./rke_linux-amd64 up --config ./micluster.yml

Insisto en que esto no es una guía de implementación completa, para ello referirse aquí.

Configurando IIS ARR para trabajar con Rancher

Imaginemos que tenemos dos nodos en nuestra implementación de HA de Rancher, mad-rancher0.hispamsx.org y mad-rancher1.hispamsx.org. Así que creamos un nuevo serverFarm y los especificamos.

Hecho esto, vamos a empezar a cumplir con los requerimientos de protocolo y encabezados.

Habilitando el soporte websocket

Esto lo hacemos desde Server Manager agregando WebSocket Protocol al rol de Web Server

HTTP/2

Para soportar esta importante mejora del protocolo HTTP debemos cumplir dos requisitos:

  • Nuestra versión de IIS debe ser 10.0 o superior, es decir, debemos estar bajo Windows Server 2016 como mínimo.
  • En IIS ARR, tenemos que ir a la configuración de Proxy de nuestra granja y especificar en HTTP version: Pass through

X-FORWARDED-FOR

En la misma página de configuración de Proxy nos aseguramos de que en Custom Headers tenemos correctamente especificado el nombre bajo el cual se preserva la IP del cliente: X-Forwarded-For. Por defecto viene correctamente.

X-FORWARDED-PROTO, HOST y X-FORWARD-PORT

Estas tres variables las debemos configurar en la regla de enrutado de nuestra granja, por lo que debemos ir a Routing Rules, nos aseguramos de que Enable SSL Offloading está activado y después vamos a URL Rewrite para editar la regla.

Una vez en la regla, con independencia de las condiciones que hayamos puesto, y que son específicas para nuestro entorno, debemos agregar las siguientes *Server Variables:

  • Name: HTTP_X_Fordwarded_Proto; Value: https
  • Name: HTTP_Host; Value: {HTTP_HOST}
  • Name: HTTP_X_Forward_Port; Value: 80

En Action comprobamos que tenemos Route to Server Farm y que el Scheme es http en lugar de https (insisto, en un entorno productivo debería ser https y no hacer offloading).

¡Con esto ya deberíamos tener la granja funcionando a falta de configurar el SSL en ARR!

Healthcheck

Rancher nos proporciona un punto HTTP para saber si nuestro cluster se encuentra en un estado saludable: /healthz.

Muy útil para que ARR sepa a qué nodo redirigirnos en caso de que alguno no se encuentre en un estado saludable. Configurarlo es tan sencillo como ir a Health Test en nuestra granja de servidores e introducir la URL completa; en caso de servidor http://rancher.hispamsx.org/healthz

Let's Encrypt en IIS ARR

Lo bueno de esta arquitectura de offloading es que no tenemos que preocuparnos de implementar una automatización de renovación de certificados en todos los nodos de nuestro cluster de Kubernetes, nos vale con hacerlo en el proxy inverso. Sin embargo, si estamos ante un escenario productivo, y no un laboratorio doméstico como la presente publicación, mejor que implementemos TLS de principio a fin.

Normalmente servidor combina Let's Encrypt con IIS mediante el uso de Centralized Certificate Store; pero con ARR se vuelve muy truculento por el requerimiento de ServerNames; así que en este caso sencillamente tengo un script que importa y enlaza los certificados en el Default Web Site.

La parte principal del script es la siguiente:

Import-Module WebAdministration
Write-Output "Removing existing binding in HTTP.SYS..."
Remove-Item -Path "IIS:\SslBindings\0.0.0.0!443" -ErrorAction SilentlyContinue
Remove-Item -Path "IIS:\SslBindings\::!443" -ErrorAction SilentlyContinue
Write-Output "Adding new binding in HTTP.SYS..."
New-Item -Path "IIS:\SslBindings\0.0.0.0!443" -Value $importedCert -SSLFlags 0 -Force
New-Item -Path "IIS:\SslBindings\::!443" -Value $importedCert -SSLFlags 0 -Force
cmd.exe /c "iisreset"

Antes de eso hay toda una lógica para comprobar si el certificado va a expirar e importarlo en la variable $importedCert, pero eso será cuestión de otro post.

¿Has llegado hasta aquí? ¡Estamos listos para despegar!

Utilizando kubectl

Como he comentado, Rancher pone a nuestra disposición una interfaz gráfica para gestionar nuestro cluster, pero seguimos teniendo Kubernetes por debajo y si preferimos ceñirnos al kubectl y nuestros YAMLs nada más sencillo que agregar un nuevo contexto a su configuración que podemos descargar desde el panel de control tal como muestra la siguiente captura.

Happy on-premises Kubernetes!