Artículos

Cómo crear una configuración básica de Kubernetes

Kubernetes es una plataforma robusta de orquestación de contenedores. Puede gestionar tus contenedores, programarlos en distintos nodos según los recursos disponibles, escalar automáticamente tu infraestructura en respuesta a la demanda y hacer muchas otras cosas.

Cuando deben desplegar sus aplicaciones, la mayoría de los desarrolladores suele buscar la solución con menos complicaciones, ya sea con un proveedor totalmente administrado (por ejemplo, Heroku) o creando sus propios scripts sencillos de despliegue (es decir, conectarse al servidor por ssh, descargar imágenes de Docker e iniciar contenedores).

A medida que crece tu proyecto, dedicarás cada vez más tiempo a ajustar tus scripts de despliegue, agregarás más servidores, necesitarás ejecutar comandos antes o después de terminar el despliegue, querrás poder volver a una versión anterior del software si algo falla y luego necesitarás un sistema para comprobar el estado de todos esos servicios... En algún momento empezarás a notar que tu script se está convirtiendo en una plataforma de orquestación de contenedores por sí mismo y que probablemente deberías dejar de trabajar en él.

Quizá sientas que usar Kubernetes para un proyecto de uno o dos contenedores es excesivo o innecesario, pero no se puede subestimar la flexibilidad que ofrece. Tendrás una forma estándar de gestionar tu sistema, podrás ampliar fácilmente tu conjunto de tecnologías si hace falta y también podrás migrar de un proveedor de nube a otro con mayor facilidad.

Hoy, la mayoría de los proveedores de servidores en la nube tiene su propia oferta de Kubernetes. Esto significa que poner en marcha tus clústeres de Kubernetes es muy fácil. Usaremos DigitalOcean para nuestros ejemplos, pero deberías poder hacer lo mismo con Amazon Web Services y otros proveedores.

Configuración

Creemos un nuevo clúster de Kubernetes en DigitalOcean y configuremos la utilidad kubectl para comunicarse con él.

  1. Inicia sesión en tu cuenta de DigitalOcean y haz clic en Kubernetes en el panel izquierdo,
  2. Haz clic en "Create a Kubernetes Cluster"
  3. Selecciona una región y reduce la cantidad de nodos a 1, ya que por ahora no necesitaremos más.
  4. Haz clic en "Create Cluster". Verás una barra de progreso en la parte superior. Tu clúster estará listo cuando esa barra desaparezca.
  5. Instala Kubectl. Necesitarás esta herramienta para gestionar tu clúster de Kubernetes.
  6. Sigue esta guía para configurar la herramienta doctl y conectar kubectl a tu clúster. Otra opción es hacer clic en "Download Config File" y pasar ese archivo a kubectl para conectarte a tu clúster:
$ kubectl --kubeconfig=path/to/config-file.yaml <commands>

Ahora deberíamos poder usar el comando kubectl para comunicarnos con nuestro clúster.

$ kubectl get nodes
NAME                    STATUS   ROLES    AGE     VERSION
pool-dsayjvr1j-33ipn    Ready    <none>   8m44s   v1.16.6

Si usamos el enfoque del archivo de configuración, el comando anterior debería verse parecido a esto:

$ kubectl --kubeconfig=path/to/k8s...kubeconfig.yaml get nodes
NAME                    STATUS   ROLES   AGE   VERSION
pool-dsayjvr1j-33ipn    Ready    <none>  12m   v1.16.6

Podemos ver que nuestro clúster consta de un nodo (es decir, una máquina de trabajo de Kubernetes).

Si usamos el método del archivo kubeconfig, no podemos olvidar pasar la opción --kubeconfig a kubectl cada vez. De ahora en adelante, todas las invocaciones de kubectl aparecerán sin la opción --kubeconfig, así que debemos ajustarlas según corresponda.

Pods y Deployments

Kubernetes define los pods como «las unidades de computación desplegables más pequeñas que se pueden crear y gestionar en Kubernetes». Un Pod puede estar compuesto por uno o más contenedores. Nuestros Pods en este ejemplo, como suele ocurrir, estarán compuestos por un solo contenedor.

Los Deployments, por otra parte, describen el estado deseado del sistema con respecto a los Pods; por ejemplo, podemos crear un deployment que solicite dos instancias de un Pod determinado, y Kubernetes intentará cumplir ese requisito lo mejor posible según los recursos disponibles.

Ahora haremos que Kubernetes despliegue nuestra aplicación frontend. Creemos un nuevo archivo llamado "frontend.yaml" con el siguiente contenido:

kind: Deployment
apiVersion: apps/v1
metadata:
  name: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
    labels:
        app: frontend
    spec:
      containers:
    - name: frontend
        image: sophilabs/k8s-demo-frontend
        ports:
        - name: http
          containerPort: 80

Las primeras dos líneas le indican a Kubernetes que estamos definiendo un Deployment con la versión apps/v1 del esquema. Después viene la sección "metadata", donde damos un nombre al Deployment que estamos creando, en este caso “frontend”.

La sección "spec" es donde realmente definimos el estado deseado de nuestro Deployment. Primero declaramos que queremos al menos una réplica de los Pods que coincidan con el “selector” indicado. La sección “selector” especifica cómo debe seleccionar el Deployment los Pods sobre los que actúa; en este caso, coincidirá con cualquier Pod etiquetado como “app: frontend”.

La sección "template" es donde definimos nuestro Pod. Al igual que el Deployment, contiene una sección “metadata” donde agregamos la etiqueta “app: frontend” al Pod. Después viene la sección “spec”, donde definimos los contenedores que estarán dentro de este Pod.

En este caso tenemos un contenedor llamado "frontend" que usa la imagen de Docker “sophilabs/k8s-demo-frontend”. La sección ports define los puertos que expone el contenedor y les da un nombre; en este caso, “http” y el puerto 80. Usaremos el nombre del puerto más adelante.

Ahora es momento de indicarle a Kubernetes que aplique este deployment.

$ kubectl apply -f frontend.yaml
deployment.apps/frontend created

Ahora podemos inspeccionar nuestro clúster de Kubernetes.

$ kubectl get deployments
NAME      READY  UP-TO-DATE  AVAILABLE  AGE
frontend  1/1    1           1          5s

Aquí podemos ver que nuestro deployment "frontend" se creó y está listo. Veamos si se creó nuestro Pod frontend:

$ kubectl get pods
NAME                       READY    STATUS  RESTARTS   AGE
frontend-84865ff7bd-d5w4w  1/1      Running     0          8s

Muy bien, nuestro pod también está en funcionamiento. Ten en cuenta que este proceso tarda unos segundos. Sigue ejecutando los subcomandos "get deployments" y “get pods” hasta que la columna “ready” muestre “1/1.”

Ahora podemos ejecutar comandos en el pod frontend y anotar el nombre del Pod que obtuvimos con el comando "get pods".

$ kubectl exec frontend-84865ff7bd-d5w4w -- ls
bin
boot
dev
...
var

Así de fácil, ejecutamos el comando "ls" en el contenedor frontend sin tener que configurar claves SSH, nombres de host ni nada más. ¡Excelente!

Si queremos abrir una shell en un contenedor en ejecución, podemos hacerlo con:

$ kubectl exec -ti frontend-84865ff7bd-d5w4w -- sh
/ # ls
bin dev etc home  lib media mnt opt
proc  root  run sbin  srv sys tmp usr
var
/ #

¿Cómo haremos ahora una solicitud a ese Pod? Antes de ver cómo hacerlo correctamente, revisemos otra gran función de kubectl: "port-forward"

$ kubectl port-forward frontend-84865ff7bd-d5w4w 9080:80
Forwarding from 127.0.0.1:9080 -> 80
Forwarding from [::1]:9080 -> 80

¡Ahora todas las solicitudes que hagamos a localhost:9080 se enviarán al puerto 80 del Pod!

Abre un navegador y ve a http://localhost:9080

No podría ser más fácil. ¿Qué tal si revisamos los logs del contenedor?

$ kubectl logs frontend-84865ff7bd-d5w4w

127.0.0.1 - - [23/Apr/2020:18:50:08 +0000] "GET / HTTP/1.1" 200 77 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0" "-"

2020/04/23 18:50:08 [error] 6#6: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "localhost:9080"

127.0.0.1 - - [23/Apr/2020:18:50:08 +0000] "GET /favicon.ico HTTP/1.1" 404 154 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0" "-"

¡Ahora deberías ver los logs que generó nginx cuando visitaste la página!

Servicios

Ahora tenemos un servidor frontend en funcionamiento en nuestro clúster de Kubernetes, pero no podemos acceder a él sin ejecutar el comando port-forward. Para exponer nuestra aplicación frontend al exterior, necesitamos crear un "Service"

La documentación de Kubernetes define un "Service" como «Una forma abstracta de exponer una aplicación que se ejecuta en un conjunto de Pods como un servicio de red».

Hay varias formas de configurar Services en Kubernetes. Ahora nos centraremos en los servicios "NodePort". Un “NodePort Service” configura un puerto en los nodos que redirigirá el tráfico entrante hacia el Service.

Creamos un archivo frontend-service.yaml con el siguiente contenido:

kind: Service
apiVersion: v1
metadata:
  name: frontend
spec:
  type: NodePort
  selector:
    app: frontend
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: http

Aquí definimos un Service llamado "frontend" de tipo “NodePort” para todos los Pods etiquetados como “app: frontend”. Redirige el tráfico del puerto 80 al puerto del contenedor llamado “http” (recuerda que llamamos http al puerto 80 del contenedor frontend).

Necesitamos aplicar este archivo yaml:

$ kubectl apply -f frontend-service.yaml
service/frontend created

Inspeccionemos el Service que acabamos de crear.

$ kubectl describe service frontend
Name:                   frontend
Namespace:              default
Labels:                 <none>
Annotations:            Selector:  app=frontend
Type:                   NodePort
IP:                     10.245.4.91
Port:                   http  80/TCP
TargetPort:             http/TCP
NodePort:               http  30695/TCP
Endpoints:              10.244.0.31:80
Session Affinity:       None
External Traffic Policy:  Cluster
Events:                 <none>

La salida muestra que el "NodePort" asignado a este Service es 30695. Ahora necesitamos la dirección ip externa de nuestro nodo.

$ kubectl describe nodes | grep ExternalIP
ExternalIP:  159.89.140.126

Ahora deberías poder acceder al servidor frontend visitando: http://159.89.140.120:30695 (Por supuesto, reemplaza estos números por los que devuelva kubectl en tu caso).

Hay otro aspecto importante de haber creado este Service frontend. Ahora el servidor frontend es accesible con el nombre frontend en todo el clúster; es decir, podemos enviar una solicitud a [&lt;http://frontend&gt;](&lt;http://frontend&gt;) desde el interior del clúster y llegará a uno de los Pods frontend que tenemos en ejecución (por ahora, solo uno). Probémoslo:

Abrimos una shell en el contenedor frontend y ejecutamos curl http://frontend.

$ kubectl exec -ti frontend-84865ff7bd-d5w4w -- sh
# curl http://frontend
<!DOCTYPE html>
<html>
  <body>
      <h1>Hello</h1>
  </body>
</html>

Ahora eliminaremos el servicio que acabamos de crear.

$ kubectl delete -f frontend-service.yaml
service "frontend" deleted

Intentemos ejecutar otra vez el comando curl:

$ kubectl exec -ti frontend-84865ff7bd-d5w4w -- sh
# curl <http://frontend>
curl: (6) Could not resolve host: frontend

Podemos ver que, sin la definición del servicio "frontend", ya no podemos llegar al Pod frontend usando el nombre de dominio “frontend”.

Controlador Ingress

Nuestra aplicación frontend sirve de poco si no podemos acceder fácilmente a ella mediante un nombre de dominio legible. La forma habitual de hacerlo en DigitalOcean y en la mayoría de los otros proveedores de nube es crear un balanceador de carga y dejar que el proveedor administre nuestras reglas DNS. Kubernetes ofrece Services de tipo Load Balancer, que pueden crear un balanceador de carga usando la API de tu proveedor de nube y configurarlo para que apunte al Service dentro del clúster. Este tipo de servicio funcionaría en nuestro caso porque solo tenemos un Service frontend, pero ¿qué hacemos si necesitamos un control detallado de cómo se enruta el tráfico dentro del clúster? Para eso podemos usar un Ingress-Controller.

Los controladores Ingress son servidores HTTP (como Nginx) modificados para saber que se ejecutan dentro de un clúster de Kubernetes. Esto les permite responder a eventos del clúster, como la creación y eliminación de recursos Ingress; estos recursos se usan para configurar el controlador Ingress de modo que implemente nuestros requisitos de enrutamiento.

Usaremos la herramienta Helm para instalar nuestro controlador ingress.

Ve al sitio de Helm e instala el programa.

Luego ejecuta estos comandos:

$ helm repo add stable [<https://kubernetes-charts.storage.googleapis.com/>](<https://kubernetes-charts.storage.googleapis.com/>)

$ helm install nginx-ingress stable/nginx-ingress

Si usas el parámetro --kubeconfig para kubectl, agrega --kubeconfig=path/to/k8s-....-.yaml después del comando helm, por ejemplo, helm --kubeconfig=path/to/file.yaml install &lt;etc,...&gt;

Veamos qué cambió en nuestro clúster:

$ kubectl get pods
NAME                                           READY  STATUS  (...)
frontend-84865ff7bd-d5w4w                      1/1    Running (...)
nginx-ingress-controller-676d7fcf55-5cwtq      1/1    Running (...)
nginx-ingress-default-backend-5b967cf596-w6kd4 1/1    Running (...)

Helm agregó dos pods más a nuestro clúster.

Revisemos nuestros servicios:

$ kubectl get services
NAME                           TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                     AGE
kubernetes                     ClusterIP      10.245.0.1      <none>          443/TCP                     27h 
nginx-ingress-controller       LoadBalancer   10.245.108.229  167.172.10.106  80:31122/TCP,443:31442/TCP  15m
nginx-ingress-default-backend  ClusterIP      10.245.17.126   <none>          80/TCP                      15m

Ahora tenemos un servicio nginx-ingress-controller de tipo “LoadBalancer”, lo que significa que Kubernetes creó un LoadBalancer de DigitalOcean que apunta a nuestro nuevo controlador Ingress.

Inicia sesión en tu cuenta de DigitalOcean, haz clic en el enlace Networking (panel izquierdo), luego en la pestaña "Load balancers", y deberías ver allí tu nuevo balanceador de carga.

Observa que la dirección IP del balanceador de carga es la misma que obtuvimos con kubectl: “167.172.10.106” en nuestro caso.

Lo único que queda por hacer es reinstalar nuestro servicio frontend y agregar una ruta Ingress que apunte a él.

Abre el archivo frontend-service.yaml que creamos antes y elimina la línea type: NodePort. Esto hará que nuestro servicio frontend sea accesible solo dentro del clúster.

Aplica el archivo del servicio:

$ kubectl apply -f frontend-service.yaml

A continuación, crea un archivo frontend-ingress.yaml con el siguiente contenido:

kind: Ingress
apiVersion: extensions/v1beta1
metadata:
  name: frontend
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - http:
    paths:
    - path: /
        backend:
          serviceName: frontend
          servicePort: http

Aquí puedes ver que estamos definiendo una nueva regla de enrutamiento. Cada solicitud con la ruta / se dirigirá a nuestro servicio frontend en el puerto "http" (es decir, el puerto 80 definido en frontend-service.yaml).

Copiamos la dirección IP de nuestro balanceador de carga y visitamos el sitio: http://167.172.10.106/ ¡Deberíamos volver a ver la página "Hello"!

¡Y eso es todo! Solo queda apuntar un nombre de dominio a nuestro nuevo balanceador de carga; podemos leer cómo hacerlo en la documentación de nuestro proveedor de nube.

Lecturas adicionales

Esta fue una pequeña guía para ayudarte a obtener una configuración básica pero completa de Kubernetes. Seguramente aún tienes muchas preguntas sin responder; puedes encontrar mucha más información y guías en kubernetes.io. Te recomiendo empezar por los tutoriales interactivos y avanzar desde allí.

“Cómo crear una configuración básica de Kubernetes” de Juan Cabrera está bajo la licencia CC BY SA. Los ejemplos de código fuente están bajo la licencia MIT.

Foto de sophilabs.

Clasificado en Investigación y aprendizaje.

Lecturas relacionadas