Una guía no tan rápida de Docker y Kubernetes

Mauricio Collazos
Ingeniería en Tranqui Finanzas
9 min readJun 19, 2018

Lo digo todo el tiempo: Docker fue ‘The Next Big Thing’, y es un tanto adictivo porque una vez lo pruebas en serio, luego se te hace dificil vivir sin el.

Y bueno, el pragmatismo me obliga a dar una breve definición de ¿qué es docker? y antes de eso ¿qué es un contenedor? lo que luego nos llevará a preguntarnos ¿qué es un orquestador? y luego ¿por qué es difícil vivir sin ellos? y son preguntas que intentaremos resolver con esta breve guía.

Hace ya un par de meses di una charla llamada ‘Con la ballena a la nube’ en un evento local y creo que tomaré un par de definiciones de esa charla:

¿Qué es un contenedor?

Tomado de techglimpse

‘Es una pieza de software liviana, independiente, empaquetable y ejecutable que incluye todo lo que necesita para correr: código, runtime, herramientas de sistema, librerías y configuraciones’ traducido de la página oficial de docker, eso traducido a lenguaje práctico significa que es un pedacito de software que puede ejecutarse por si mismo sin necesidad de nada más. Un proceso que se aloja en la memoria de un anfitrión y que idealmente copia sus características y las extiende para obtener la funcionalidad necesaria. Muy distinto a las máquinas virtuales, pues ellas tienen una copia completa del sistema operativo, mientras que los contenedores solo incluyen las diferencias con respecto al anfitrión.

Cabe aclarar que un contenedor se ejecuta como un proceso independiente dentro de un sistema operativo por consiguiente puede ser afectado por el controlador de procesos del sistema operativo.

Mas allá de docker, existen otros gestores de contenedores, entre los cuales destacan:

  • rkt: Un gestor de contenedores creado por coreos
  • runC: Una implementación de contenedores basado en la propuesta de OpenContainers
  • lxc: La implementación original de contenedores ahora manejada por Linux Containers
  • singularity: Una propuesta de contenedores para la computación científica.

¿Qué es docker?

Si vamos a ser pragmáticos, debemos decir que Docker es una compañía. Pero esto es algo mas bien reciente, si nos vamos al 2013 Docker fue un proyecto de código abierto liderado por Solomon Hykes, que comenzó a ganar fuerza rápidamente, por sus integraciones con OpenShift, AWS, IBM y más. La idea de una implementación de contenedores ampliamente aceptada por proveedores de nube a bajo costo de configuración comenzó a inundar a los emprendedores tecnológicos haciendo que el proyecto creciera en popularidad rápidamente.

Hoy en día Docker, la compañía ofrece servicios empresariales y mantiene la versión de la comunidad, sin embargo desde el 2017 el proyecto cambió de nombre y ahora es the Moby Project.

Arquitecturalmente hablando Docker se compone de un servidor llamado Docker Daemon, un API y un cliente.

Tomado de la documentación oficial de docker

El cliente utiliza el API para comunicarse con el servidor que realiza las operaciones necesarias. El servidor está alojado en un Host y los contenedores se ejecutan a partir de las imágenes construidas. Cada imágen está compuesta por un conjunto de capas almacenadas en volúmenes que son equivalentes a un sistema de archivos. Docker optimiza el almacenamiento de estos volúmenes con un concepto de diferencias.

Tomado de la documentación oficial de docker

El concepto de diferencias es similar al de git, donde solamente se almacena en cada commit los cambios con respecto al estado anterior. En el caso de los contenedores, solamente se almacena en ellos las diferencias con respectos a una base común.

Bajo este concepto el espacio requerido por cada contenedor se reduce considerablemente y lo ideal sería tener una sola capa común que pueda ser compartida por muchos contenedores.

Para la instalación de Docker, recomendamos visitar la página oficial para los detalles específicos de cada sistema operativo.

Cada imágen está descrita en un archivo llamado Dockerfile dentro de este archivo se encuentran todas las etapas necesarias para construir la imágen. Mostramos un ejemplo de una aplicación sencilla en django

# Especificamos la imágen de la que se heredaráFROM python:3.6-alpine# Definimos algunas variables de ambienteENV LIBRARY_PATH=/lib:/usr/libENV PYTHONUNBUFFERED 1# Ejecutamos algunos comandos para aprovisionar la imágenRUN mkdir /codeWORKDIR /codeADD requirements.txt /code/RUN pip install -r requirements.txt# Copiamos el códigoADD . /code/# Exponemos el puerto por donde se interactuará con la aplicaciónEXPOSE 8000# Definimos el comando de entrada a la aplicaciónCMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

La especificación del archivo Dockerfile la encontramos en la documentación oficial.

Para tener un contenedor ejecutando esta especificación, contruimos la imágen con el comando docker build

docker build -t contraslash/ejemplo-django .

Verificamos la creación de esta imágen con el comando

docker images

La ejecución de la imágen construida se realiza por medio de

docker run -p 8000:8000 contraslash/ejemplo-django

Hasta este punto tenemos una imágen construida y un contenedor con esa imágen ejecutándose en nuestra máquina local, sin embargo la potencia de los contenedores se presenta a la hora de realizar despliegues con ellos. Para esto añadimos un nuevo concepto llamado registro.

Docker registry es un repositorio de artefactos donde podemos almacenar imágenes de docker. La versión comercial se encuentra en hub.docker.com, pero es posible tener nuestro propio registro a partir de la imágen oficial del registro de docker.

Antes de desplegar una imágen al registro, se hace útil diferenciar una construcción de otra. Siguiendo el concepto de versiones, docker añade un nuevo concepto llamado etiquetas.

Las etiquetas se usan para diferenciar una construcción de otra. Se denominan tags que representan una versión de construcción de una imágen. Por defecto la etiqueta de cada imágen al realizar el proceso de construcción es latest

Para interactuar con las etiquetas existe el comando docker tag

docker tag contraslash/ejemplo-django:latest contraslash/ejemplo-django:v1

Cuando hemos marcado nuestra versión actual por medio de una etiqueta podemos desplegar esta imágen a nuestro registro.

# Nos autenticamos en el registro, si no se especifica un servidor se utilizará hub.docker.com
docker login https://registry.contraslash.com
# Luego desplegamos la versión v1 a nuestro registro
docker push contraslash/ejemplo-django:v1

El proceso de obtener una imágen de un registro se realiza por medio del comando docker pull

docker pull contraslash/ejemplo-django:v1

Hasta ahora hemos creado imágenes y contenedores con docker, pero ¿cómo se manejan estas imágenes? son procesos, así que ¿quién los inicia? si el sistema operativo los mata, ¿quién los reinicia? La respuesta a todas estas preguntas es: Un orquestador

¿Qué es un orquestador?

Los orquestadores llevan mucho tiempo entre nosotros y hacen referencia a esa figura de director de orquesta que se encarga que todo suene bien. Creo que la definición mas precisa la encontré en Search IT Operations: ‘Un orquestador es un programa de software que maneja las interconexiones e interacciones entre cargas de trabajo en nubes públicas y privadas. Conecta las tareas automatizadas en un flujo de trabajo cohesivo para cumplir las metas, con vigilancia de permisos y aplicación de políticas’.

Entre las labores de un orquestador está la labor de levantar y aprovisionar servidores, monitorear y configurar la red, escalar servicios, volúmenes y todas las demás configuraciones necesarias para que las aplicaciones estén siempre disponibles.

La idea detrás de todo esto es automatizar al máximo las operaciones, para minimizar el factor humano y tener un control mas holístico de toda la infraestructura de red.

Un concepto clave que me encanta mencionar es que los servidores deben ser tratados como rebaños y no como mascotas lo que nos atrae a los servidores inmutables, trayendo la referencia siempre práctica de que los servidores deben ser mas que copos de nieve unos fénix que puedan morir y renacer en la medida de lo necesario sin que tengamos que realizar una configuración única a cada uno de ellos.

Tomando de la página oficial de kubernetes

Entendiendo ahora que nuestra aplicación no pertenece a un mundo donde ella es el centro del mundo, requiriendo cuidados especiales, y que mas allá de eso podemos delegarle el cuidado y mantenimiento a una herramienta automática podemos hablar de Kubernetes.

Kubernetes nace como una iniciativa de código abierto a Borg, el maestro de marionetas detrás de la infraestructura de Google. Su idea principal es independizar los servicios de tal manera que no existan conflictos entre ellos, creando capas de abstracción para que el mantenimiento sea simple y no requiera interacción directa de un humano.

El concepto báse de Kubernetes es el Pod, un conjunto de contenedores que estarán ejecutándose en el mismo anfitrión con una ip única y que comparten recursos. Todos los contenedores dentro del pod se pueden comunicar entre ellos a través de su interfaz de red común localhost

apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox
command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']

Los pods viven y mueren, la manera de controlar este comportamiento es a través de un Replica Set, un mecanismo de autoescalamiento y control de pods para mantener servicios disponibles.

También es importante garantizar que se mantenga información persistente generada o procesada por un pod, y dado que viven y mueren y cada contenedor al interior de cada pod tiene un sistema de archivos propio que se pierde con cada muerte, el concepto de Volumen viene al rescate, permitiendo que la información se mantenga en el tiempo

Como se mencionó anteriormente, cada pod tiene su propia dirección IP, y asegurar que un pod siempre tendrá una dirección única no es posible, por tal motivo existen los Servicios, que dan un nivel de abstracción de comunicación entre pods, definiendo puertos nombrados a través de etiquetas.

kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376

Los despliegues son una manera declarativa de especificar pods con replica sets.

Kubernetes es un servicio muy popular y es ofrecido por distintos proveedores de nube como:

Y muchos otros, no es mi deber compararlos a ningún nivel, por el contrario, ilustraré brevemente el proceso de creación de un cluster de K8s con Kops, para ello resumimos el tutorial de la página oficial de k8s.

Como pre requisito se solicita la instalación de kubectl, la herramienta de línea de comandos de K8s y por supuesto kops.

En Route53 creamos una nueva zona alojada y luego creamos un registro registro NS apuntando a la zona recién creada.

Luego creamos un nuevo bucket en S3 y en nuestra línea de comandos almacenamos una variable de entorno que apunte a este bucket.

export KOPS_STATE_STORE=s3://clusters.dev.example.com

Recordamos que es recomendable nombrar al bucket de una manera clara porque ahí se almacenará toda la información relacionada con el cluster.

Kops permite configurar primero el cluster sin crearlo, lo cual permite una revisión previa a la creación de recursos

kops create cluster --zones=us-east-1c useast1.dev.example.com

Para crear efectivamente el cluster

kops update cluster --zones=us-east-1c useast1.dev.example.com --yes

Con esto podremos ver en nuestra consola de EC2 que se han creado nuevos recursos, grupos de seguridad, y subredes relacionadas con este cluster.

Podemos verificar el proceso con

kubectl get nodes

El punto de toda esta configuración es tener nuestra aplicaciones manejadas por K8s, y como primera instancia debemos configurar nuestro registro de docker con las credenciales apropiadas. Para esto seguimos la guía oficial

kubectl create secret docker-registry regcred --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-pword> --docker-email=<your-email>

A continuación crearemos un despliegue con dos replicas de nuestra aplicación, un contenedor instancia del ejemplo anterior, un puerto de comunicación con otros contenedores.

Llamamos a este archivo deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
name:
ejemplo-django
spec:
selector:
matchLabels:
app:
ejemplo-django
replicas: 2
template:
metadata:
labels:
app:
ejemplo-django
spec:
containers:
- name: ejemplo-django-container
image: contraslash/ejemplo-django:v1
ports:
- name: ed-port
containerPort: 8000
env:
- name: DEBUG
value: "False"

imagePullSecrets:
- name: regcred

Reflejaremos los cambios en nuestro servidor utilizando

kubectl apply -f deployment.yml

Y verificamos con

kubectl get nodes

Nuestra aplicación está corriendo en el cluster, pero no tenemos un mecanismo para acceder a ella, para esto usamos un servicio de tipo LoadBalancer que definimos en service.yml en donde definiremos un puerto de que enlace los puertos de nuestro despliegue a una URL que se asignará por medio de ELB, también le añadiremos un certificado SSL por medio de ACM. Una descripción un poco mas precisa de como utilizar certificados SSL con Servicios está en este post.

apiVersion: v1
kind: Service
metadata:
name:
ejemplo-django-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:{region}:{user id}:certificate/{id}
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https"
spec:
ports:
- port: 80
name: http
targetPort: ed-port
protocol: TCP
- port: 443
name: https
targetPort: ed-port
protocol: TCP
selector:
app:
ejemplo-django
type: LoadBalancer

Ejecutamos

kubectl apply -f service.yml

Verificamos con

kubectl get services

Y para obtener la URL del balanceador de carga

kubectl describe service ejemplo-django-service

La url estará en el parámetro LoadBalancer Ingress

Por último si se desea, se puede crear un Alias en Route53 que apunte al balanceador de carga para tener una URL un poco mas presonalizada.

--

--

Mauricio Collazos
Ingeniería en Tranqui Finanzas

Master Student in Computer Science at Universidad del Valle. Software engineer, system administrator and speech technologies researcher