Docker 101: Fundamentos y práctica

Si está cansado de escuchar a sus compañeros de trabajo elogiar a Docker y sus beneficios en cada oportunidad que tienen, o está cansado de asentir con la cabeza y alejarse cada vez que se encuentra en una de estas conversaciones, ha venido sitio.

Además, si estás buscando una nueva excusa para alejarte sin que te despidan, sigue leyendo y me lo agradecerás más tarde.

Estibador

Aquí está la definición de Docker, según Wikipedia:

Docker es un programa informático que realiza virtualización a nivel de sistema operativo.

Bastante simple, ¿verdad? Bueno no exactamente. Muy bien, aquí está mi definición de lo que es Docker:

Docker es una plataforma para crear y ejecutar contenedores a partir de imágenes .

¿Aún perdido? No se preocupe, es porque probablemente no sepa qué son los contenedores o las imágenes .

Las imágenes son archivos únicos que contienen todas las dependencias y configuraciones necesarias para ejecutar un programa, mientras que los contenedores son las instancias de esas imágenes. Sigamos adelante y veamos un ejemplo de eso en la práctica para aclarar las cosas.

Nota importante: antes de continuar, asegúrese de instalar la ventana acoplable siguiendo los pasos recomendados para su sistema operativo.

Parte 1. "¡Hola, mundo!" de una imagen de Python

Digamos que no tiene Python instalado en su máquina, o al menos no tiene la última versión, y necesita Python para imprimir "¡Hola, mundo!" en tu terminal. ¿Qué haces? ¡Usas Docker!

Continúe y ejecute el siguiente comando:

docker run --rm -it python:3 python

No se preocupe, le explicaré ese comando en un segundo, pero en este momento probablemente esté viendo algo como esto:

Eso significa que actualmente estamos dentro de un contenedor de ventana acoplable creado a partir de una imagen de ventana acoplable de Python 3 , ejecutando el pythoncomando. Para terminar el ejemplo, escriba print("Hello, World!")y observe cómo ocurre la magia.

Muy bien, lo hiciste, pero antes de empezar a darte unas palmaditas en la espalda, demos un paso atrás y comprendamos cómo funcionó.

Rompiéndolo

Vamos a empezar desde el principio. El docker runcomando es la herramienta estándar de Docker para ayudarlo a iniciar y ejecutar sus contenedores.

La --rmbandera está ahí para decirle al Docker Daemon que limpie el contenedor y elimine el sistema de archivos después de que el contenedor salga. Esto le ayuda a ahorrar espacio en disco después de ejecutar contenedores de corta duración como este, que recién comenzamos a imprimir "¡Hola, mundo!".

La -t (or --tty)bandera le dice a Docker que asigne una sesión de terminal virtual dentro del contenedor. Esto se usa comúnmente con la -i (or --interactive)opción, que mantiene STDIN abierto incluso si se ejecuta en modo separado (más sobre esto más adelante).

Nota: No se preocupe demasiado por estas definiciones en este momento. Solo sepa que usará la -itbandera cada vez que desee escribir algunos comandos en su contenedor.

Por último, python:3es la imagen base que usamos para este contenedor. En este momento, esta imagen viene con la versión 3.7.3 de Python instalada, entre otras cosas. Ahora, es posible que se pregunte de dónde vino esta imagen y qué hay dentro de ella. Puede encontrar las respuestas a ambas preguntas aquí mismo, junto con todas las demás imágenes de Python que podríamos haber utilizado para este ejemplo.

Por último, pero no menos importante, pythonestaba el comando que le dijimos a Docker que ejecutara dentro de nuestra python:3imagen, que inició un shell de Python y permitió que nuestra print("Hello, World!")llamada funcionara.

Una cosa más

Para salir de Python y terminar nuestro contenedor, puede usar CTRL / CMD + D o exit(). Adelante, haz eso ahora mismo. Después de eso, intente ejecutar nuestro docker runcomando nuevamente y verá algo un poco diferente y mucho más rápido.

Eso es porque ya descargamos la python:3imagen, por lo que nuestro contenedor comienza ahora mucho más rápido.

Parte 2. "¡Hola mundo!" Automatizado de una imagen de Python

¿Qué es mejor que escribir "¡Hola, mundo!" en tu terminal una vez? ¡Lo tienes, escribiéndolo dos veces!

Ya que estamos ansiosos por ver "¡Hola, mundo!" impreso en nuestra terminal nuevamente, y no queremos pasar por el ajetreo de abrir Python y escribir printnuevamente, sigamos adelante y automaticemos ese proceso un poco. Comience creando un hello.pyarchivo en cualquier lugar que desee.

# hello.py
print("Hello, World!")

A continuación, siga adelante y ejecute el siguiente comando desde esa misma carpeta.

docker run --rm -it -v $(pwd):/src python:3 python /src/hello.py

Este es el resultado que buscamos:

Nota: utilicé lsantes del comando para mostrarle que estaba en la misma carpeta en la que creé el hello.pyarchivo.

Como hicimos antes, demos un paso atrás y entendamos cómo funcionó.

Rompiéndolo

Prácticamente estamos ejecutando el mismo comando que ejecutamos en la última sección, aparte de dos cosas.

La -v $(pwd):/srcopción le dice al Docker Daemon que inicie un volumen en nuestro contenedor . Los volúmenes son la mejor manera de conservar los datos en Docker. En este ejemplo, le estamos diciendo a Docker que queremos que el directorio actual, recuperado $(pwd), se agregue a nuestro contenedor en la carpeta /src.

Nota: Puede utilizar cualquier otro nombre o carpeta que desee, no solo/src

Si desea verificar que /src/hello.pyrealmente existe dentro de nuestro contenedor, puede cambiar el final de nuestro comando de python hello.pya bash. Esto abrirá un caparazón interactivo dentro de nuestro contenedor y podrá usarlo tal como lo espera.

Nota: Solo podemos usar bashaquí porque viene preinstalado en la python:3imagen. Algunas imágenes son tan simples que ni siquiera lo tienen bash. Eso no significa que no pueda usarlo, pero tendrá que instalarlo usted mismo si lo desea.

El último bit de nuestro comando es la python /src/hello.pyinstrucción. Al ejecutarlo, le estamos diciendo a nuestro contenedor que mire dentro de su /srccarpeta y ejecute el hello.pyarchivo usando python.

Tal vez ya puedas ver las maravillas que puedes hacer con este poder, pero lo resaltaré para ti de todos modos. Con lo que acabamos de aprender, podemos ejecutar prácticamente cualquier código desde cualquier idioma dentro de cualquier computadora sin tener que instalar ninguna dependencia en la máquina host, excepto Docker, por supuesto.Eso es mucho texto en negrita para una oración, ¡así que asegúrese de leerlo dos veces!

Parte 3. "¡Hola, mundo!" posible desde una imagen de Python usando Dockerfile

¿Estás cansado de saludar a nuestro hermoso planeta, todavía? ¡Es una pena, porque lo haremos de nuevo!

El último comando que aprendimos fue un poco detallado, y ya puedo verme cansándome de escribir todo ese código cada vez que quiero decir "¡Hola, mundo!" Automaticemos las cosas un poco más ahora. Cree un archivo con nombre Dockerfiley agregue el siguiente contenido:

# Dockerfile
FROM python:3
WORKDIR /src/app
COPY . .
CMD [ "python", "./hello.py" ]

Ahora ejecute este comando en la misma carpeta en la que creó Dockerfile:

docker build -t hello .

Todo lo que queda por hacer ahora es volverse loco usando este código:

docker run hello

Ya sabes como es. Tomemos un momento para comprender cómo funciona un Dockerfile ahora.

Rompiéndolo

A partir de nuestra Dockerfile, la primera línea FROM python:3está diciendo acoplable a empezar todo de la imagen base que ya estamos familiarizados con con, python:3.

La segunda línea, WORKDIR /src/appestablece el directorio de trabajo dentro de nuestro contenedor. Esto es para algunas instrucciones que ejecutaremos más adelante, como CMDo COPY. Puede ver el resto de las instrucciones admitidas WORKDIRaquí mismo.

La tercera línea, COPY . .básicamente, le dice a Docker que copie todo de nuestra carpeta actual (primera .) y lo pegue /src/app(segunda .). La ubicación de pegado se estableció con el WORKDIRcomando justo encima.

Nota: Podríamos lograr los mismos resultados eliminando la WORKDIRinstrucción y reemplazando la COPY . .instrucción con COPY . /src/app. En ese caso, también necesitaríamos cambiar la última instrucción, CMD ["python", "./hello.py"]a CMD ["python", "/src/app/hello.py"].

Finalmente, la última línea CMD ["python", "./hello.py"]proporciona el comando predeterminado para nuestro contenedor. Básicamente, está diciendo que cada vez que tengamos runun contenedor de esta configuración, debería ejecutarse python ./hello.py. Tenga en cuenta que estamos ejecutando implícitamente en /src/app/hello.pylugar de solo hello.py, ya que eso es a lo que apuntamos WORKDIR.

Nota: El CMDcomando se puede sobrescribir en tiempo de ejecución. Por ejemplo, si desea ejecutar en su bashlugar, lo haría docker run hello bashdespués de construir el contenedor.

Con nuestro Dockerfile terminado, seguimos adelante y comenzamos nuestro buildproceso. El docker build -t hello .comando lee toda la configuración que agregamos a nuestro Dockerfile y crea una imagen de la ventana acoplable a partir de él. Así es, como la python:3imagen que hemos estado usando para todo este artículo. El .al final le dice a Docker que queremos ejecutar un Dockerfile en nuestra ubicación actual, y la -t helloopción le da a esta imagen el nombre hello, para que podamos hacer referencia a ella fácilmente en tiempo de ejecución.

Después de todo eso, todo lo que tenemos que hacer es ejecutar la docker runinstrucción habitual , pero esta vez con el hellonombre de la imagen al final de la línea. Eso iniciará un contenedor a partir de la imagen que creamos recientemente y finalmente imprimirá el viejo "¡Hola, mundo!" en nuestra terminal.

Ampliando nuestra imagen base

¿Qué hacemos si necesitamos alguna dependencia para ejecutar nuestro código que no viene preinstalado con nuestra imagen base? Para resolver ese problema, Docker tiene la RUNinstrucción.

Siguiendo nuestro ejemplo de Python, si necesitáramos la numpybiblioteca para ejecutar nuestro código, podríamos agregar la RUNinstrucción justo después de nuestro FROMcomando.

# Dockerfile
FROM python:3
# NEW LINERUN pip3 install numpy
WORKDIR /src/app
COPY . .
CMD [ "python", "./hello.py" ]

La RUNinstrucción básicamente da un comando para ser ejecutado por la terminal del contenedor. De esa manera, dado que nuestra imagen base ya viene pip3instalada, podemos usar pip3 install numpy.

Nota: Para una aplicación de Python real, probablemente agregaría todas las dependencias que necesita a un requirements.txtarchivo, lo copiaría en el contenedor y luego actualizaría la RUNinstrucción a RUN pip3 install -r requirements.txt.

Parte 4. "¡Hola, mundo!" de una imagen de Nginx usando un contenedor separado de larga duración

Sé que probablemente estés cansado de oírme decirlo, pero tengo un "hola" más que decir antes de irme. Sigamos adelante y usemos nuestro poder de docker recién adquirido para crear un contenedor simple de larga duración, en lugar de estos de corta duración que hemos estado usando hasta ahora.

Cree un index.htmlarchivo en una carpeta nueva con el siguiente contenido.

# index.html

Hello, World!

Ahora, creemos un nuevo Dockerfile en la misma carpeta.

# Dockerfile
FROM nginx:alpine
WORKDIR /usr/share/nginx/html
COPY . .

Construye la imagen y dale el nombre simple_nginx, como hicimos anteriormente.

docker build -t simple_nginx .

Por último, ejecutemos nuestra imagen recién creada con el siguiente comando:

docker run --rm -d -p 8080:80 simple_nginx

Puede que estés pensando que no pasó nada porque has vuelto a tu terminal, pero echemos un vistazo más de cerca con el docker pscomando.

El docker pscomando muestra todos los contenedores en ejecución en su máquina. Como puede ver en la imagen de arriba, tengo un contenedor llamado simple_nginxejecutándose en mi máquina en este momento. Abramos un navegador web y veamos si nginxestá haciendo su trabajo accediendo localhost:8080.

Todo parece funcionar como se esperaba, y estamos sirviendo una página estática a través de la nginxejecución dentro de nuestro contenedor. Tomemos un momento para entender cómo lo logramos.

Rompiéndolo

Voy a omitir la explicación de Dockerfile porque ya aprendimos esos comandos en la última sección. Lo único "nuevo" en esa configuración es la nginx:alpineimagen, que puede leer más sobre ella aquí.

Aparte de lo nuevo, esta configuración funciona porque nginxusa la usr/share/nginx/htmlcarpeta para buscar un index.htmlarchivo y comenzar a servirlo, así que dado que nombramos nuestro archivo index.htmly lo configuramos WORKDIRpara que fuera usr/share/nginx/html, esta configuración funcionará de inmediato.

El buildcomando también es exactamente como el que usamos en la última sección, solo estamos usando la configuración de Dockerfile para construir una imagen con un nombre determinado.

Ahora, la parte divertida, la docker run --rm -d -p 8080:80 simple_nginxinstrucción. Aquí tenemos dos nuevas banderas. La primera es la -dbandera detached ( ), lo que significa que queremos ejecutar este contenedor en segundo plano, y es por eso que estamos de vuelta en nuestra terminal justo después de usar el docker runcomando, aunque nuestro contenedor todavía se está ejecutando.

La segunda bandera nueva es la -p 8080:80opción. Como habrás adivinado, esta es la portbandera, y básicamente está mapeando el puerto 8080de nuestra máquina local al puerto 80dentro de nuestro contenedor. Podría haber usado cualquier otro puerto en lugar de 8080, pero no puede cambiar el puerto 80sin agregar una configuración adicional a la nginximagen, ya que 80es el puerto estándar que nginxexpone la imagen.

Nota: Si desea detener un contenedor separado como este, puede usar el docker pscomando para obtener el nombre del contenedor (no la imagen) y luego usar la docker stopinstrucción con el nombre del contenedor deseado al final de la línea.

Parte 5. El final

¡Eso es! Si todavía está leyendo esto, tiene todos los conceptos básicos para comenzar a usar Docker hoy en sus proyectos personales o trabajo diario.

Hágame saber lo que pensó sobre este artículo en los comentarios y me aseguraré de escribir un artículo de seguimiento que cubra temas más avanzados, como docker-composeen algún lugar en el futuro cercano.

Si tiene alguna pregunta, por favor hágamelo saber.

¡Salud!