angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

¿Debo migrar mi Docker Swarm a Kubernetes?

He migrado un proyecto Docker-Compose a Kubernetes y me he decidido por Kubernetes.

15 septiembre 2023
post main image
https://www.pexels.com/nl-nl/@fotios-photos

Cuando lees posts en internet diciendo que Docker Swarm está muerto, te asustas. Yo tengo un Docker Swarm funcionando y me gusta, es fácil cuando ya usas Docker.

¿Cuáles son las alternativas? Hemos leído todo el tiempo que sólo hay una cosa que hacer y es migrar a Kubernetes y olvidarse de todo lo demás.

Yo estaba a medio camino de migrar de Docker a Docker Swarm, y quería saber si debía continuar, o pasar a centrarme en Kubernetes.

Quizás el siguiente paso más lógico sería utilizar Docker Desktop porque incluye Kubernetes. Sin embargo, yo no uso Docker Desktop, estoy ejecutando Docker Swarm también en mi máquina de desarrollo y es perfecto.

Volviendo a Kubernetes. Todas mis aplicaciones se ejecutan en Ubuntu y Debian y después de leer un poco más Microk8s parecía buena opción, se puede utilizar para el desarrollo y la producción. Así que vamos a suponer que nos movemos a Microk8s, entonces, ¿qué hay que hacer para migrar? He utilizado uno de mis actuales proyectos Docker-Compose para averiguarlo.

Este post es principalmente acerca de todas las cosas que tuve que hacer para llegar al resultado,no muestro todos los details del proyecto Docker-Compose.

Como siempre estoy en Ubuntu 22.04.

Mi aplicación

Mi aplicación consta de varios bloques. Cada bloque consta de uno o más proyectos Docker-Compose. Por el momento, sólo los Task Runners se gestionan con Docker Swarm. Se conectan al Backend mediante una red superpuesta Ingress .

   +----------+   +---------+         +-------------+
 --| Frontend |---| Backend |----+----| Task Runner |   
   +----------+   +---------+    |    +-------------+
                                 |
                                 |    +-------------+
                   Ingress ->    +----| Task Runner |   
                   overlay       |    +-------------+
                   network       //
                                 |    +-------------+
                                 +----| Task Runner |   
                                      +-------------+

          manage with              manage with
   |<--- Docker-Compose --->|<---- Docker Swarm ---->|
                                        v
                                      migrate
                                        v
                                   manage with
                            |<----- Kubernetes ---->|

El Task Runner es un proyecto Docker-Compose con cinco servicios:

rabbitmq
task
local-web
unbound-cloudflare
unbound-quad9

'local-web' es una imagen Nginx ligeramente modificada, que devuelve una página web personalizada.

                          +-------------+
                          |  local-web  |   
                          +-------------+
                                 ^
                                 |
     +----------+       +-----------------+
     |          |       |                 |-+
     |          |------>|                 | |-+ 
 <-->| rabbitmq |       |      task       | | |
     |          |<------|                 | | |
     |          |       |                 | | |
     +----------+       +-----------------+ | |
                              |     | ------+ |
                              |     | --------+
     +--------------------+   |     |
     | unbound-cloudflare |<--+     |
     +--------------------+         |
                                    |
     +--------------------+         |
     | unbound-quad9      |<--------+
     +--------------------+

El rabbitmq-service se conecta al Backend. Con el Docker Swarm creo réplicas del task-service.

Aquí está una versión despojada del archivo de proyecto Docker-Compose:

# docker-compose.yml 

services:
  rabbitmq:
    ...
    networks:
      task-runner-network
  task:
    ...
  local-web:
    ...
  unboud-cloudflare:
    ...

networks:
  task-runner-network:
    external: true

Sólo rabbitmq está conectado a una red externa. En este post voy a ejecutar un Task Runner sobre Kubernetes, con el servicio 'task' replicado.

Preguntas y respuestas

Antes de empezar cualquier trabajo hice una lista de preguntas y traté de obtener algunas respuestas. Aquí están:

¿Es lo mismo un Pod que un Docker Container?

No, no es lo mismo. Según la documentación: Un Pod es la unidad de computación desplegable más pequeña que se puede crear y gestionar en Kubernetes. Puede haber varios contenedores en un Pod. Un Pod proporciona un entorno para que los contenedores se ejecuten y compartan recursos, almacenamiento, red y comunicación entre procesos. Comparten el mismo espacio de nombres de red y pueden comunicarse entre sí utilizando localhost.

En resumen, a menos que hagas algo especial, deberías usar un Pod por contenedor.

Pods se comunican mediante Servicios

Un contenedor en un Pod se comunica con otro Pod usando su IP address. Pero este no es el camino a seguir. En general creamos un Servicio para cada Pod, y accedemos al Pod a través de su Servicio. Nos referimos a un Servicio usando su nombre, también podemos mapear puertos aquí.

Si tenemos un Pod con contenedores replicados, el Servicio distribuirá peticiones a través de estos contenedores. En este caso, el Servicio también actúa como equilibrador de carga. Esto es muy parecido a Docker Swarm.

¿Cuándo utilizar Deployments?

Un Deployment se utiliza para gestionar un Pod. Con un Deployment puede especificar las réplicas de Pod , la ubicación de Pod en los nodos y cómo se publican las nuevas actualizaciones. Esto significa que (casi) siempre se necesita un Deployment para un Pod. Como podemos especificar un Pod dentro de un Deployment, ¡no necesitamos archivos YAML separados para el Pods!

¿Cómo hacer el registro Pods ?

El Pods no hace logging. Son los contenedores los que generan los logs. Esto no difiere del registro Docker . En mi sistema, los archivos de registro están en:

/var/log/pods

¿Podemos asignar un directorio en el host a un Pod como Docker Volumes?

Sí, para ello podemos utilizar la directiva 'hostPath'.

¿ Kubernetes es sólo para producción o también podemos utilizarla para desarrollo?

Microk8s puede utilizarse tanto en desarrollo como en producción. Pero para producción también podemos utilizar otra cosa, como una variante de Kubernetes de un proveedor en la nube. En mi máquina de desarrollo utilizo Microk8s junto con Docker sin problemas.

En Docker Swarm tenemos Task.Slot, ¿cuál es el equivalente en Kubernetes?

No es exactamente lo mismo pero comparable es el Kubernetes 'StatefulSet'. Todavía no lo he probado.

¿Podemos conectar una red Docker Swarm a un Kubernetes Pod (contenedor)?

La razón por la que querría esto es la migración. Por supuesto, siempre podemos construir nuestro propio equilibrador de carga proxy, pero ¿hay una manera simple / estándar de hacer esto? Por el momento creo un Servicio 'nodePort'. Esto mapea el puerto de un Servicio dentro de Kubernetes, a un puerto en el host.

Antes de la conversión, comprueba y corrige los nombres de los objetos

Tenía todos estos bonitos nombres en mis archivos Docker-Compose y estaban usando guiones bajos ('_'). En Kubernetes nombres de los objetos deben cumplir con RFC 1123, lo que significa:

  • Máximo 63 caracteres.
  • Deben empezar y terminar con una letra minúscula o un número.
  • Contener letras minúsculas, números y guiones.

Antes de hacer nada cambié esto en mi proyecto Docker-Compose.

Kompose: Convertir el archivo de proyecto Docker-Compose

Ahora vamos a tratar de convertir el Task Runner Docker-Compose archivos a algo Kubernetes, utilizando Kompose. Kompose es una herramienta de conversión de Docker Compose a Kubernetes. Y enseguida nos encontramos con problemas. Kompose parece no ser capaz de manejar el archivo '.env' que es bastante esencial. Además (¿como consecuencia?) no se generó ConfigMap . Hay un problema reportado por esto.

Buscaré una nueva versión una de las próximas semanas pero por ahora sustituimos las variables de entorno yo mismo usando Docker-Compose 'config':

> docker-compose -f docker_compose_shared_file.yml -f docker_compose_deployment_file.yml config > docker-compose-config.yml

Y luego ejecutando Kompose. Usamos la opción hostPath aquí porque uso volúmenes usando directorios locales del sistema:

> kompose -v convert -f docker-compose-config.yml --volumes hostPath

Kompose creó los siguientes archivos:

local-web-pod.yaml
rabbitmq-pod.yaml
rabbitmq-service.yaml
task-pod.yaml
unbound-cloudflare-pod.yaml
unbound-quad9-pod.yaml

Para cada servicio Docker-Compose se crea un fichero '-pod.yaml', y sólo para uno un fichero '-service.yaml'. También he visto ejemplos en los que Kompose generaba Deployments en lugar de Pods. ¿Por qué? Para mí, los resultados de Kompose son sólo un punto de partida, debo crear Deployment YAML a partir de los archivos Pod YAML y añadir también archivos YAML de servicio. Probablemente se pueden obtener mejores resultados con Kompose (añadiendo instrucciones al archivo 'docker-compose.yml') pero no tengo tiempo para experimentar en este momento.

Instalación de Microk8s

Ahora vamos a ensuciarnos las manos. Estoy en Ubuntu 22.04 así que esto es fácil, usamos Microk8s Para obtener la última versión estable:

> sudo snap install microk8s --classic

Vamos a ver lo que está habilitado:

> microk8s status

Resultado:

microk8s is running
high-availability: no
  datastore master nodes: 127.0.0.1:19001
  datastore standby nodes: none
addons:
  enabled:
    dns                  # (core) CoreDNS
    ha-cluster           # (core) Configure high availability on the current node
    helm                 # (core) Helm - the package manager for Kubernetes
    helm3                # (core) Helm 3 - the package manager for Kubernetes
  disabled:
    cert-manager         # (core) Cloud native certificate management
    community            # (core) The community addons repository
    dashboard            # (core) The Kubernetes dashboard
    gpu                  # (core) Automatic enablement of Nvidia CUDA
    host-access          # (core) Allow Pods connecting to Host services smoothly
    hostpath-storage     # (core) Storage class; allocates storage from host directory
    ingress              # (core) Ingress controller for external access
    kube-ovn             # (core) An advanced network fabric for Kubernetes
    mayastor             # (core) OpenEBS MayaStor
    metallb              # (core) Loadbalancer for your Kubernetes cluster
    metrics-server       # (core) K8s Metrics Server for API access to service metrics
    minio                # (core) MinIO object storage
    observability        # (core) A lightweight observability stack for logs, traces and metrics
    prometheus           # (core) Prometheus operator for monitoring and logging
    rbac                 # (core) Role-Based Access Control for authorisation
    registry             # (core) Private image registry exposed on localhost:32000
    storage              # (core) Alias to hostpath-storage add-on, deprecated

Habilitar hostpath-storage

Algunos servicios en mi archivo docker-compose.yml usan datos persistentes en directorios en el localhost. Activar/habilitar los servicios de almacenamiento en Microk8s:

> microk8s.enable hostpath-storage

Kubernetes cuadro de mandos

Microk8s incluye el panel de control Kubernetes . En primer lugar, actívalo:

> microk8s enable dashboard

Hay varias opciones para iniciarlo, esta es fácil:

> microk8s dashboard-proxy

Luego, en tu navegador, navega a:

https://127.0.0.1:10443

Acepta el certificado autofirmado, copia-pega el token del terminal y ya estás dentro.

Parar e iniciar

Para parar e iniciar Microk8s:

> microk8s stop
> microk8s start

Uso de kubectl

Kubectl es una herramienta de línea de comandos utilizada para ejecutar comandos para gestionar clusters Kubernetes .

Para utilizar 'kubectl' en lugar de 'microk8s kubectl':

> alias kubectl='microk8s kubectl'

Para hacerlo permanente, añádelo a tu archivo 'bashrc':

> echo "alias kubectl='microk8s kubectl'" >> ~/.bashrc

Haz muchos alias, ¡o se te cansarán los dedos!

Obtener ayuda por ejemplo sobre los puertos de un contenedor en un Pod:

> kubectl explain pod.spec.containers.ports

Para crear y actualizar objetos Kubernetes podemos usar una sintaxis declarativa ('apply'), o imperativa ('create').

He aquí algunos comandos:

> kubectl get nodes
> kubectl apply -f <manifest file>
> kubectl get pods -o wide
> kubectl get deployments
> kubectl get services
> kubectl delete pods local-web
> kubectl describe pods local-web
> kubectl logs local-web

Para obtener los últimos eventos:

> kubectl get events

Y, para obtener todo:

> kubectl get all -A

Recuerda, un Deployment contiene una especificación ReplicaSet y una especificación Pod , lo que significa que si creas un Deployment, obtendrás uno o más Pods dependiendo de las réplicas que hayas especificado.

Despliegue de un Pod

¿Podemos desplegar uno de los Pods creados por Kompose? Kompose nos ha creado el siguiente fichero:

local-web-pod.yaml

Sin editar este fichero intentamos desplegar este Pod:

> kubectl apply -f local-web-pod.yaml

Resultado:

pod/local-web created

¿Está el Pod ?

> kubectl get pods

Resultado:

NAME        READY   STATUS    RESTARTS   AGE
local-web   0/1     Pending   0          119s

Está ahí pero el estado es 'Pendiente'. Comprobemos los registros:

> kubectl logs --timestamps local-web

No hay registros, no se devuelve nada. Comprobemos los eventos:

> kubectl get events

Resultado:

LAST SEEN   TYPE      REASON                OBJECT          MESSAGE
9m31s       Warning   FailedScheduling      pod/local-web   0/1 nodes are available: persistentvolumeclaim "local-web-claim0" not found. preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod..
101s        Warning   FailedScheduling      pod/local-web   0/1 nodes are available: 1 node(s) didn't match Pod's node affinity/selector. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling..

Parecía que 'nodeAffinity' tenía el valor del fichero Docker-Compose 'deploy - node.hostname'. Tuve que cambiar esto por el nombre del host que estoy utilizando en este momento, por supuesto ... :-(

Añadir un registro privado (imagen)

Eliminar el Pod, aplicar de nuevo y obtener el estado:

> kubectl delete pod local-web
> kubectl apply -f local-web-pod.yaml
> kubectl get pods

Resultado:

NAME        READY   STATUS             RESTARTS   AGE
local-web   0/1     ErrImagePull       0          76s

Estoy usando un registro privado para Docker ya. Para utilizarlo en Kubernetes, debemos añadir autenticación para este registro.

Compruebe si hay un elemento registry-credential presente:

> kubectl get secret registry-credential --output=yaml

Resultado:

Error from server (NotFound): secrets "registry-credential" not found

Aquí estoy utilizando la información ya presente en Docker. Crear el elemento registry-credential:

> kubectl create secret generic registry-credential \
    --from-file=.dockerconfigjson=/home/peter/.docker/config.json \
    --type=kubernetes.io/dockerconfigjson

Resultado:

secret/registry-credential created

Para comprobarlo:

> kubectl get secret registry-credential --output=yaml

Primero empujamos la imagen al registro. A continuación, editamos los archivos Pod y añadimos la referencia a las credenciales del registro:

  imagePullSecrets:
    - name: registry-credential

Ahora borramos el Pod y aplicamos de nuevo:

> kubectl get pods

Resultado:

NAME        READY   STATUS    RESTARTS   AGE
local-web   1/1     Running   0          9s

¡Por fin está funcionando!

Entra en el contenedor Pod

Entra en el contenedor 'local-web':

> kubectl exec -it local-web -- /bin/bash

Ahora podemos comprobar las variables de entorno, montajes, etc.

También puedes entrar en el contenedor como root. Mi Microk8s está usando 'runc' (Open Container Initiative runtime) para acceder a los contenedores. Obtén el ID del contenedor:

> kubectl describe pod <your pod> | grep containerd

Esto mostrará algo como:

Container ID: containerd://6a060ba8436f575b86b4f3fe10a373125aaf7c125af835180d792f5382836355

Luego ejecuta como root en el contenedor:

> sudo runc --root /run/containerd/runc/k8s.io/ exec -t -u 0 6a060ba8436f575b86b4f3fe10a373125aaf7c125af835180d792f5382836355 sh

Accediendo al Pod a través de un Servicio

Después de parar e iniciar un Pod, un nuevo IP address puede ser asignado a un Pod. Por eso utilizamos un Servicio para acceder a un Pod, y utilizamos el nombre del Servicio.

Creando un servicio para un Pod podemos acceder al Pod usando su nombre en lugar de IP address. Para ello necesitamos el DNS del cluster Kubernetes , debe estar habilitado:

> kubectl get services kube-dns --namespace=kube-system

Resultado:

NAME       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.152.183.10   <none>        53/UDP,53/TCP,9153/TCP   3d5h

Crear un servicio para el local-web Pod

El archivo local-web-pod.yaml no tenía una entrada de puertos, así que añadí un containerPort:

      ports:
      - containerPort: 80

Debido a que el archivo local-web-service.yaml no fue creado por Kompose, lo creé yo mismo. Importante: Aquí añado un puerto que da acceso al Pod. En Docker-Compose no necesitamos este puerto porque el servicio está en la red interna.

apiVersion: v1
kind: Service
metadata:
  name: local-web
  namespace: default
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  selector:
    io.kompose.service: local-web

El 'selector' debe coincidir con el 'label' del Pod.

Después iniciamos el servicio:

> kubectl apply -f local-web-service.yaml

Ahora deberíamos poder acceder al servicio local-web desde otro contenedor Pod .

Vamos a iniciar un contenedor Busybox . Hay problemas con los recientes Busybox images y Kubernetes, así que usamos la versión 1.28.

> kubectl run -i --tty --image busybox:128 test --restart=Never --rm /bin/sh

A continuación, dentro del contenedor:

# wget local-web-service:80

Resultado:

Connecting to local-web-service:80 (10.152.183.30:80)
saving to 'index.html'
index.html           100% |**********************************************************************************************************************************|   400  0:00:00 ETA
'index.html' saved

Genial.

Creando Deployments y Servicios

Como ya hemos dicho, no creamos ficheros Pod YAML sino ficheros Deployment YAML . Y también añadimos un Servicio para cada Deployment que tenga puertos.

En Kubernetes los nombres de Servicio son globales, en Docker-Compose los servicios en una red interna son locales al proyecto Docker-Compose. Esto significa que en Kubernetes debemos namespace nuestros servicios.

Podemos hacer esto de dos maneras:

  • Añadir un namespace para este proyecto Docker-Compose
  • Añadir un prefix al nombre del servicio

En este momento creo que añadir un prefix es la mejor opción:

task-runner-

Esto nos da los nombres Deployment y nombres de servicio:

task-runner-rabbitmq-deployment
task-runner-local-web-deployment
task-runner-task-deployment
task-runner-unbound-cloudflare-deployment
task-runner-unbound-quad9-deployment

task-runner-rabbitmq-service
task-runner-local-web-service
task-runner-task-service
task-runner-unbound-cloudflare-service
task-runner-unbound-quad9-service

Y nombres de archivo:

task-runner-rabbitmq-deployment.yaml
task-runner-local-web-deployment.yaml
task-runner-task-deployment.yaml
task-runner-unbound-cloudflare-deployment.yaml
task-runner-unbound-quad9-deployment.yaml

task-runner-rabbitmq-service.yaml
task-runner-local-web-service.yaml
task-runner-task-service.yaml
task-runner-unbound-cloudflare-service.yaml
task-runner-unbound-quad9-service.yaml

En el Deployment empiezo con 1 replica. Aquí está la primera parte de un archivo Deployment :

# task-runner-local-web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  # metadata name must not contain dots
  name: task-runner-local-web-deployment
  namespace: default
spec:
  # number of copies of each pod we want
  replicas: 1

  strategy:
    type: Recreate

  # pods managed by this deployment
  selector:
    # match the labels we set on the pod, see below
    matchLabels:
      task-runner.local-web.deployment: task-runner-local-web-deployment

  # template field is a regular pod configuration nested inside the deployment spec
  template:
    metadata:
      # set labels on the pod, used in the deployment selector, see above
      labels:
        task-runner.local-web.deployment: task-runner-local-web-deployment
    spec:
      ...

Haz que el archivo '.env' esté disponible cuando se inicien los contenedores

Tengo muchos pares clave-valor en el archivo '.env' utilizado por Docker-Compose. En Kubernetes podemos utilizar un ConfigMap para importarlos en nuestros archivos Deployment YAML .

Primero creamos un ConfigMap. El ConfigMap se crea en el espacio de nombres 'por defecto', puede cambiar esto añadiendo la bandera y el valor del espacio de nombres:

> kubectl create configmap task-runner-env-configmap --from-env-file=.env

Listar el ConfigMaps:

> kubectl get configmaps

Resultado:

NAME                      DATA   AGE
...
task-runner-env-configmap   51     19m

Podemos ver el resultado como yaml:

> kubectl get configmaps task-runner-env-configmap -o yaml

Entonces en los ficheros Deployment YAML eliminamos la sección 'env' y añadimos la sección 'envFrom'.

De:

    spec:
      containers:
      - env:
        - name: ENV_VAR1
          value: "1"
        ...
        image: ...

Hasta:

    spec:
      containers:
      - envFrom:
        - configMapRef:
            name: task-runner-env-configmap
        env:
          - name: VAR_C = $(VAR_A)/$(VAR_B)
        image: ...

Aquí también creamos una nueva variable de entorno VAR_C a partir de dos variables de entorno presentes en el ConfigMap.

Podemos actualizar el ConfigMap de la siguiente manera:

> kubectl create configmap task-runner-env-configmap --from-env-file=.env -o yaml --dry-run | kubectl apply -f -

Creamos un ConfigMap para pasar los pares clave-valor del fichero '.env' a los contenedores. Pero hay un gran problema. No podemos utilizar variables de entorno en otras partes de nuestros manifiestos. Por ejemplo, supongamos que queremos ser capaces de cambiar el repositorio con una variable de entorno IMAGE_REGISTRY:

    spec:
      containers:
      - envFrom:
        - configMapRef:
            name: task-runner-env-configmap
        env:
          - name: VAR_C = $(VAR_A)/$(VAR_B)
        image: $(IMAGE_REGISTRY)...

¡Esto no funcionará! Estamos en 2023 y como esta (característica trivial) no funciona, en todo el mundo la gente está creando sus propios scripts para hacer que esto funcione. Maravilloso. De todos modos, lo que debe hacer el script:

  1. Iniciar un nuevo shell (Bash).
  2. Exportar los pares clave-valor en el archivo '.env'.
  3. Usar 'envsubst' para reemplazar las variables en nuestro manifiesto.

Primero, creamos un nuevo shell para evitar que dist perturbe el entorno actual. En el nuevo entorno, creamos las variables de entorno utilizando el archivo '.env'. Por último, sustituimos las variables

> envsubst < mydeploy.yaml | kubectl apply -f -

PersistentVolumes y VolumeClaims

Kompose me ha creado una sección VolumeMounts y una sección Volumes en los ficheros Pods YAML utilizando hostPath. Esto significa que los directorios reales localhost están codificados en los archivos Deployment . Obviamente no es lo que queremos.

Así que creé algunos manifiestos PersistentVolume y PersistentVolumeClaim y los utilicé en los archivos Deployment .

Crear servicios para el Deployments

Aquí muestro sólo el servicio local-web .

# task-runner-local-web-service.yaml
apiVersion: v1
kind: Service
metadata:
  # metadata name must not contain dots
  name: task-runner-local-web-service
  namespace: default
spec:
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
  selector:
    task-runner.local-web.deployment: task-runner-local-web-deployment

Inicie el servicio:

> kubectl apply -f task-runner-local-web-service.yaml

Cambios en el código de la aplicación

Sólo he necesitado pequeños cambios en el código de mi aplicación. Como mencioné anteriormente, prefixed los nombres iniciales del servicio con 'task-runner-'. Para mantener la compatibilidad con Docker-Compose ahora utilizo variables de entorno para los nombres de Servicio y puertos, especificados como pares clave-valor en el archivo '.env'.

Aunque los puertos ahora se especifican en todos los Servicios, no hay necesidad de cambiar nada porque los puertos están vinculados a los Servicios, y cada Servicio tiene su propio IP address.

Para comprobar dónde se está ejecutando una instancia de un Pod almaceno la variable de entorno HOSTNAME junto con el resultado de una tarea. Para Docker Swarm he utilizado .Node.Hostname. En los logs podemos comprobar este campo.

Usando nodePort para exponer el servicio rabbitmq al Back End

El Task Runner está en funcionamiento pero no hay conexión con el mundo exterior. Hay varias formas de exponer el Servicio rabbitmq y aquí utilizo nodePort. Simplemente creamos un Servicio extra con el tipo nodePort y especificamos el número de puerto:

# task-runner-rabbitmq-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  # metadata name must not contain dots
  name: task-runner-rabbitmq-nodeport
  namespace: default
spec:
  type: NodePort
  # The range of valid ports is 30000-32767
  ports:
    - name: amqp
      port: 10763
      targetPort: 5672
      nodePort: 31763
    - name: http
      port: 20763
      targetPort: 15672
      nodePort: 32763
  selector:
    task-runner.rabbitmq.deployment: task-runner-rabbitmq-deployment

Entonces podemos referirnos a este Servicio Kubernetes en el host como: localhost:<port>.

Para conectar el contenedor Backend Docker a este puerto añadimos algunas líneas al fichero 'docker-compose.yaml':

    extra_hosts:
      - "host.docker.internal:host-gateway"

Y luego nos referimos al servicio como:

host.docker.internal:<port>

Añadiendo un nodo Trabajador, DNS no funciona

Por supuesto que quería añadir un nuevo nodo y ejecutar algunos Pods allí. Con VitualBox en mi máquina de desarrollo he creado un VM con Ubuntu servidor 22.04. A continuación, añadir Microk8s y se unió al nuevo nodo.

Problemas ... Kubernetes DNS no funcionaba en el nuevo nodo. Hay muchos problemas de DNS mencionados en la página Microk8s Github . Esto probablemente tiene que ver con iptables ...

Pude tener un cluster Kubernetes funcionando correctamente de dos máquinas VirtualBox . En este momento estoy investigando esto.

Desarrollando y solucionando problemas en Kubernetes

Uno de los Pods no funcionaba. Pude ver en el registro de lo que estaba mal, pero no queremos entrar en un bucle de:

--+-> make changes ---> build image ---> test --+--->
  ^                                             |
  |                                             v
  +--------------------<------------------------+ 

Así que volví a montar el código del proyecto externo en el contenedor, lo que me devuelve al entorno de desarrollo y pude resolver el problema muy rápidamente.

Herramientas

Sólo utilicé una herramienta adicional durante la migración: yamllint para validar los manifiestos (archivos YAML ).

> sudo apt-get install yamllint

Y luego ejecutar por ejemplo:

> yamllint task-runner-local-web-deployment.yaml

Resumen

Para empezar, sólo he pasado dos semanas con Kubernetes (no disparéis al pianista). Puede que haya tomado algunas decisiones equivocadas, pero parte de mi aplicación se está ejecutando y gestionado por Kubernetes.

Docker Swarm es una elección fácil cuando se trata de orquestar proyectos Docker-Compose. Simplemente funciona, sin limitaciones serias. Para mi aplicación, que consta de varios proyectos Docker-Compose, no necesito miles de réplicas. Con Docker Swarm, arrancas un nuevo nodo, añades algunas directivas de despliegue a tus archivos docker-compose.yml, y listo. La red Docker es una de las mejores características de Docker. Con Docker Swarm, basta con convertir una red en una red superpuesta Ingress cifrada para comunicarse entre nodos.

Pasarse a Kubernetes da más flexibilidad de despliegue, pero la razón principal por la que Kubernetes se ha convertido en la primera opción es que está muy extendido y muchas herramientas se integran con él. Ahí es donde Docker Swarm se queda atrás. En el momento de escribir este post, el desarrollo de Docker Swarm parece más o menos estancado (¿se considera totalmente completo?). Es una pena, porque es una gran solución a un problema muy complejo.

En un mundo ideal, el entorno de desarrollo es idéntico al entorno de producción. Si funciona en desarrollo, también funciona en producción. Docker Swarm se acerca mucho a esto, sin mucha complejidad. Usar Kubernetes, por otro lado, crea una brecha. Es como, Ok desarrollador, has hecho tu trabajo, ahora deja que los DevOps lo pongan en producción. Si el desarrollador realiza cambios en los proyectos Docker-Compose, los DevOps también tienen que trabajar en ellos.

Si se utiliza Kubernetes para producción, creo que es inevitable que el entorno de desarrollo también ejecute Kubernetes. Y, en muchos casos, también Docker Swarm . ¿Esto facilita o dificulta el desarrollo y la producción en comparación con Docker Swarm? Microk8s es muy fácil de utilizar en un entorno de desarrollo, pero eso es sólo el principio. Crear archivos de proyecto Docker-Compose es muy fácil. Normalmente tienes dos archivos yaml y un archivo '.env'. Después de la conversión, mi proyecto Kubernetes ya tenía 14 archivos yaml Kubernetes y más.

Sería una muy buena idea si Kubernetes añadiera algunas extensiones para los archivos YAML que permitan importar/añadir otros archivos (como Nginx) y datos de entorno. Por ahora, tenemos que escribir nuestros propios scripts o utilizar algo como Helm ... más cosas que aprender.

De todos modos, en este post he hecho un intento de investigar lo que supondría mover parte de mi aplicación de Docker Swarm a Kubernetes. Basándome en los resultados, he decidido pasar a Kubernetes, no seguiré utilizando Docker Swarm para producción. Y también utilizaré Kubernetes para desarrollo.

Empezar con Kubernetes no es muy difícil, pero a veces es muy confuso. Después de unos días entenderás la mayoría de los conceptos y tendrás algo funcionando. A partir de ahí puedes mejorar y ampliar. Puede haber problemas (muy) serios y que lleven mucho tiempo, como que el DNS no funcione. No quiero convertirme en un especialista en iptables, pero no hay otra manera.

El número de cambios que he tenido que hacer en mi aplicación es muy limitado:

  • Cambiar nombres a RFC1123.
  • Prefijar nombres de servicios, usando variables de entorno.
  • Almacenamiento de otras variables de entorno en los resultados.

De vuelta a la codificación ...

Enlaces / créditos

Configure a Pod to Use a ConfigMap
https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap

Connecting Kubernetes and Docker
https://developers.redhat.com/blog/2017/09/22/connecting-kubernetes-docker

Helm - The package manager for Kubernetes
https://helm.sh

How to go from Docker to Kubernetes the right way
https://www.opensourcerers.org/2021/02/01/how-to-go-from-docker-to-kubernetes-the-right-way

How To Migrate a Docker Compose Workflow to Kubernetes
https://www.digitalocean.com/community/tutorials/how-to-migrate-a-docker-compose-workflow-to-kubernetes

Kompose
https://github.com/kubernetes/kompose

Kubernetes - Documentation - Concepts
https://kubernetes.io/docs/concepts

Kubernetes - Documentation - kubectl Cheat Sheet
https://kubernetes.io/docs/reference/kubectl/cheatsheet

Kustomize - Kubernetes native configuration management
https://kustomize.io

MicroK8s documentation - home
https://microk8s.io/docs

Running Kubernetes locally on Linux with Microk8s
https://kubernetes.io/blog/2019/11/26/running-kubernetes-locally-on-linux-with-microk8s

Using private docker registry inside kubernetes
https://sam-thomas.medium.com/using-private-docker-registry-inside-kubernetes-46a3cede7cb1

When should I use envFrom for configmaps?
https://stackoverflow.com/questions/66352023/when-should-i-use-envfrom-for-configmaps

Deje un comentario

Comente de forma anónima o inicie sesión para comentar.

Comentarios

Deje una respuesta.

Responda de forma anónima o inicie sesión para responder.