# Docker # Introducción ## Objetivos * ¿Qué son los contenedores? * ¿Qué es Docker? * ¿Por qué lo necesitas? * ¿Qué puede hacer? * Ejecutar contenedores de Docker * Crear una imagen de Docker * Redes en Docker * Docker Compose * Conceptos de Docker en profundidad * Docker para Windows / Mac * Docker Swarm * Docker frente a Kubernetes ## Descripción general de Docker Descripción general de alto nivel sobre por qué necesitas Docker y qué puede hacer por ti. Docker es especialmente útil cuando tenemos muchos problemas al desarrollar una pila de aplicaciones con diferentes componentes. La compatibilidad con el sistema operativo puede ser un problema. Es posible que necesitemos volver atrás y ver diferentes sistemas operativos compatibles con todos los servicios que se van a utilizar. También, tenemos que comprobar la compatibilidad entre los servicios y, las bibliotecas y las dependencias del sistema operativo. Un servicio puede requerir otra versión. Este problema de métricas de compatibilidad generalmente se conoce como **The Matrix from Hell**. ![](https://i.imgur.com/n6QjNR0.png) ### ¿Por qué necesitas Docker? * **Compatibilidad / Dependencia** * **Tiempo de preparación prolongado** Los nuevos desarrolladores tienen que seguir un gran conjunto de instrucciones y ejecutar cientos de comandos para finalmente configurar su entorno. * **Diferentes entornos Dev / Test / Prod** Un desarrollador puede sentirse cómodo usando un sistema operativo y otros pueden sentirse cómodos usando otro. No se puede garantizar que las aplicaciones se ejecutarán de la misma manera en diferentes entornos. ### ¿Qué puedo hacer? Docker nos ayudará con el problema de compatibilidad y nos permitirá modificar o cambiar los componentes sin afectar a los demás componentes e incluso modificar los sistemas operativos subyacentes según sea necesario. Con Docker podremos ejecutar cada componente en un contenedor separado con sus propias dependencias y sus propias bibliotecas todo en la misma máquina virtual y el sistema operativo, pero dentro de entornos o contenedores separados. Solo tenemos que construir el "docker configuration" una vez y todos nuestros desarrolladores ahora podrán comenzar con un simple comando de run de Docker, independientemente del sistema operativo subyacente que ejecuten. Todo lo que tenemos que hacer es asegurarnos de que tengan Docker instalado en sus sistemas. * Aplicaciones en contenedores * Ejecutar cada servicio con sus propias dependencias en contenedores separados ![](https://i.imgur.com/d4gHhjX.png) ### ¿Qué son los contenedores? Los contenedores son entornos completamente aislados, ya que pueden tener sus propios procesos o servicios, sus propias interfaces de red, sus propios montajes, al igual que las máquinas, excepto que todas comparten el mismo kernel del sistema operativo. ![](https://i.imgur.com/2d1sj3o.png) Los contenedores no son nuevos con Docker. Los contenedores existen desde hace unos 10 años y algunos de los diferentes tipos de contenedores son LXC, LXD, LXCFS. Docker utiliza contenedores LXC. Configurar estos entornos de contenedores es difícil ya que son de muy bajo nivel y ahí es donde Docker ofrece una herramienta de alto nivel con varias funcionalidades poderosas que hacen que sea realmente fácil para los usuarios finales como nosotros entender cómo funciona Docker. ### Sistema operativo Si nos fijamos en sistemas operativos como Ubuntu, Fedora, Suse o CentOS, todos consisten en dos cosas: un kernel del sistema operativo y un conjunto de software. ![](https://i.imgur.com/cBLx0QT.png) El kernel del sistema operativo es responsable de interactuar con el hardware subyacente, mientras que el kernel del sistema operativo sigue siendo Linux, es el software que está encima lo que hace que estos sistemas operativos sean diferentes. Este software puede constar de una interfaz de usuario diferente, controladores, compiladores, administradores de archivos, herramientas de desarrollo, etc. Por lo tanto, tiene un Kernel Linux común compartido en todos los sistemas operativos y algún software personalizado que diferencia los sistemas operativos entre sí. ### Compartiendo el kernel Dijimos anteriormente que los contenedores Docker comparten el kernel subyacente, entonces, ¿qué significa eso realmente? Digamos que tenemos un sistema con un sistema operativo Ubuntu con Docker instalado en él. Docker puede ejecutar cualquier versión de sistema operativo, siempre y cuando todos estén basados en el mismo kernel, en este caso, Linux. ![](https://i.imgur.com/TYzdxei.png) Si el sistema operativo subyacente es Ubuntu, Docker puede ejecutar un contenedor basado en otra distribución como Debian, Fedora, Suse o CentOS. Cada contenedor de Docker solo tiene el software adicional del que acabamos de hablar en la diapositiva anterior que hace que estos sistemas operativos sean diferentes y Docker utiliza el kernel subyacente del host de Docker, por lo que funciona con todos los sistemas operativos anteriores. Entonces, ¿qué sistema operativo que no comparte el mismo kernel que estos?, Windows. Por lo tanto, no podrá ejecutar un contenedor basado en Windows en un host Docker con Linux. ![](https://i.imgur.com/wEB0RKP.png) Para eso, necesitará un Docker en un servidor de Windows. Cuando intentes instalar Docker en Windows y ejecutes un contenedor basado en Linux y veas que es posible, te preguntarás, ¿por qué funciona? Cuando instala Docker en Windows y ejecuta un contenedor de Linux en Windows, en realidad no está ejecutando un contenedor de Linux en Windows. Windows ejecuta un contenedor de Linux en una máquina virtual de Linux bajo el capó. Entonces, es realmente un contenedor de Linux en una máquina virtual de Linux en Windows. ¿No es una desventaja no poder ejecutar otro kernel en el sistema operativo? La respuesta es no porque, a diferencia de los hipervisores, Docker no está diseñado para virtualizar y ejecutar diferentes sistemas operativos y kernel en el mismo hardware. El objetivo principal de Docker es empaquetar y contener aplicaciones y enviarlas y ejecutarlas en cualquier lugar en cualquier momento, tantas veces como desee. ### Contenedores vs Máquinas virtuales A la derecha, en el caso de Docker, tenemos la infraestructura de hardware subyacente y luego el sistema operativo y luego Docker instalado en el sistema operativo. Luego, Docker administra los contenedores que se ejecutan con bibliotecas y dependencias. En el caso de las máquinas virtuales, tenemos el hipervisor como ESXi en el hardware y luego las máquinas virtuales en ellas. Como puede ver, cada máquina virtual tiene su propio sistema operativo dentro, luego las dependencias y luego la aplicación. La sobrecarga provoca una mayor utilización de los recursos subyacentes, ya que hay varios sistemas operativos virtuales y kernel en ejecución. Las máquinas virtuales también consumen más este espacio ya que cada máquina virtual es pesada y generalmente tiene un tamaño de gigabytes, mientras que los contenedores Docker son livianos y generalmente tienen un tamaño de megabytes. Esto permite que los contenedores de Docker se inicien más rápido, por lo general, en cuestión de segundos, mientras que las máquinas virtuales, como sabemos, tardan unos minutos en iniciarse, ya que necesitan iniciar todo el sistema operativo. También es importante tener en cuenta que la ventana acoplable tiene menos aislamiento ya que se comparten más recursos entre los contenedores como el kernel, mientras que las VM tienen un aislamiento completo entre sí. Ya que las VM no dependen del sistema operativo o kernel subyacente, puede ejecutar diferentes tipos de aplicaciones creadas en diferentes sistemas operativos, como aplicaciones basadas en Linux, en el mismo hipervisor. Entonces esas son algunas diferencias entre los dos. ![](https://i.imgur.com/xXXooMY.png) ### Contenedores y máquinas virtuales Ahora, habiendo dicho eso, no es una situación de contenedor o máquina virtual. Son contenedores y máquinas virtuales. ![](https://i.imgur.com/5NojkV6.png) Cuando tiene entornos grandes con miles de contenedores de aplicaciones que se ejecutan en miles de hosts Docker, a menudo verá contenedores aprovisionados en hosts Docker virtuales. De esa forma podemos aprovechar las ventajas de ambas tecnologías. Podemos utilizar los beneficios de la virtualización para aprovisionar o descomponer fácilmente los hosts de Docker según sea necesario, al mismo tiempo hacer uso de los beneficios de Docker para aprovisionar aplicaciones fácilmente y escalarlas rápidamente según sea necesario. ### ¿Cómo se hace? Hay muchas versiones de aplicaciones en contenedores disponibles. Por lo tanto, la mayoría de las organizaciones tienen sus productos en contenedores y disponibles en un repositorio público de Docker llamado Docker Hub o Docker Store. Por ejemplo, puede encontrar imágenes de los sistemas operativos, bases de datos y otros servicios y herramientas más comunes. Una vez que identifique las imágenes que necesita e instale Docker en su host abrir una aplicación es tan fácil como ejecutar un comando "docker run" con el nombre de la imagen. "docker run ansible" ejecutará una instancia de Ansible en el host de Docker. De manera similar con "docker run mongodb", "docker run redis", "docker run nodejs", etc. Si necesita ejecutar varias instancias del servicio web, simplemente agregue tantas instancias como necesite y configure un balanceador de carga de algún tipo en el frente. En caso de que una de las instancias falle, simplemente destruya esa instancia y lance una nueva. ![](https://i.imgur.com/KHgWj6P.png) ### Contenedor vs Imagen Una imagen es un paquete o una plantilla, al igual que una plantilla de máquina virtual con la que podría haber trabajado en el mundo de la virtualización, se utiliza para crear uno o más contenedores. Los contenedores ejecutan instancias de imágenes que están aisladas y tienen sus propios entornos y conjuntos de procesos, como hemos visto antes de que ya se hayan dockerizado muchos productos. En caso de que no pueda encontrar lo que está buscando, puede crear su propia imagen y enviarla al repositorio de Docker Hub para que esté disponible para el público. ![](https://i.imgur.com/jR5gVbV.png) Tradicionalmente, los desarrolladores desarrollaban aplicaciones, luego las entregaban al equipo de operaciones para implementarlas y administrarlas en entornos de producción. Lo hacen proporcionando un conjunto de instrucciones, como información sobre cómo se debe configurar el host, qué requisitos previos se instalarán en el host y cómo se configurarán las dependencias, etc. ![](https://i.imgur.com/qoR14tl.png) Dado que el equipo de operaciones no desarrolló realmente la aplicación por su cuenta, tienen dificultades para configurarla, cuando encuentran un problema, trabajan con los desarrolladores para resolverlo. ![](https://i.imgur.com/BIxVRNe.png) Con Docker, los desarrolladores y los equipos de operaciones trabajan mano a mano para transformar la guía en un archivo de Docker con ambos requisitos. Este archivo Docker luego se usa para crear una imagen para sus aplicaciones. Esta imagen ahora se puede ejecutar en cualquier host con Docker instalado y se garantiza que se ejecutará de la misma manera en todas partes. ![](https://i.imgur.com/JZf1n9A.png) Entonces, el equipo de operaciones ahora puede simplemente usar la imagen para implementar la aplicación, ya que la imagen ya estaba funcionando cuando el desarrollador la creó y las operaciones no la han modificado. Continúa funcionando de la misma manera cuando se implementa en producción y ese es un ejemplo de cómo una herramienta como Docker contribuye a la cultura de operaciones de desarrollo. ![](https://i.imgur.com/013IAjA.png) ## Empezando con Docker ### Ediciones de Docker Docker tiene dos ediciones, Community Edition y Enterprise Edition. Community Edition es el conjunto de productos Docker gratuitos. Enterprise Edition es una plataforma de contenedores certificada y compatible que viene con complementos empresariales como la seguridad de la imagen y el plan de control universal para administrar y orquestar los tiempos de ejecución de los contenedores, pero, por supuesto, estos tienen un precio. ![](https://i.imgur.com/buTB59Y.png) Por ahora seguiremos adelante con la edición comunitaria, la edición comunitaria está disponible en Linux, Mac, Windows o en plataformas en la nube como AWS o Azure. ![](https://i.imgur.com/wYgEP2L.png) Veremos cómo instalar y comenzar con Docker en un sistema Linux. Ahora, si está en Mac o Windows, tiene dos opciones: instalar una máquina virtual Linux usando VirtualBox o algún tipo de plataforma de virtualización y luego seguir con la próxima demostración, que es realmente la forma más fácil de comenzar con Docker. La segunda opción es instalar Docker Desktop para Mac o Windows, que son aplicaciones nativas. Entonces, si eso es realmente lo que desea, tome las secciones de Docker para Mac y Windows y luego regrese una vez que lo haya configurado. ## Demostración: configurar e instalar Docker Guía oficial: https://docs.docker.com/engine/install/ubuntu/ Confirmar nuestra versión de Linux: `cat /etc/*release` ![](https://i.imgur.com/8acRkYd.png) Desinstalar versiones antiguas: `sudo apt-get remove docker docker-engine docker.io containerd runc` ![](https://i.imgur.com/WiTAptn.png) El siguiente paso es configurar el repositorio e instalar el software, hay dos formas de hacerlo: 1. Usar el administrador de paquetes actualizando primero el repositorio con el comando apt-get update, luego instalando los paquetes de requisitos previos, agregando las claves GPG oficiales de Docker y luego instalando Docker 2. **[Manera más fácil]** Instalar Docker usando el script de convenience. Es un script que automatiza todo el proceso de instalación y funciona en la mayoría de los sistemas operativos. Seguiremos la forma 2 (https://docs.docker.com/engine/install/ubuntu/#install-using-the-convenience-script). Instalar curl: `sudo apt install curl -y` ![](https://i.imgur.com/ByZEqkx.png) Descarga una copia del script: `curl -fsSL https://get.docker.com -o get-docker.sh` ![](https://i.imgur.com/3dKvSf4.png) Ejecutar el script para instalar Docker automáticamente (dale unos minutos para completar la instalación): `sudo sh get-docker.sh` ![](https://i.imgur.com/xzxusCf.png) Comprobar la versión de Docker: `sudo docker version` ![](https://i.imgur.com/n3ZBpXx.png) En este caso, hemos instalado la versión 20.10.5. Ahora ejecutaremos un contenedor simple para asegurarnos de que todo funcione como se esperaba. Para ello, nos dirigimos a Docker Hub en https://hub.docker.com/. Aquí encontraremos una lista de las imágenes de Docker más populares como Nginx, MongoDB, Alpine, NodeJS, Redis, etc. Busquemos una imagen divertida llamada [Whalesay](https://hub.docker.com/r/docker/whalesay). Whalesay es la versión de Docker de cowsay, que es básicamente una aplicación simple que entrena a una vaca a decir algo. En este caso resulta ser una ballena. Copiamos el comando de run y recordamos agregar sudo y cambiaremos el mensaje a Hola-Mundo!. ![](https://i.imgur.com/nfy72vp.png) `docker run docker/whalesay cowsay Hola-Mundo!` ![](https://i.imgur.com/O9YcmSo.png) Al ejecutar este comando, Docker extrae la imagen de la aplicación whalesay de Docker Hub, la ejecuta y tenemos a nuestra ballena saludando. # Comandos de Docker ## Comandos básicos de Docker ### run - Iniciar un contenedor El comando `docker run` se usa para ejecutar un contenedor desde una imagen. La ejecución del comando `docker run nginx` ejecutará una instancia de la aplicación nginx en el host de Docker, si ya existe. Si la imagen no está presente en el host, saldrá a Docker Hub y bajará esa imagen. Pero esto solo se hace la primera vez para las ejecuciones posteriores. Se reutilizará la misma imagen. `sudo docker run nginx` ![](https://i.imgur.com/yiJxU3V.png) ### ps - Enumerar contenedores El comando `docker ps` enumera todos los contenedores en ejecución y cierta información básica sobre ellos, como el ID del contenedor, el nombre de la imagen que usamos para ejecutar los contenedores, el estado actual y el nombre del contenedor. Cada contenedor obtiene automáticamente una ID aleatoria y un nombre creado por Docker, que en este caso es awesome_chatterjee. `sudo docker ps` ![](https://i.imgur.com/sZAsBlj.png) Para ver todos los contenedores ejecutándose o no, usamos la opción -a. Esto genera todos los contenedores en ejecución, así como los contenedores previamente detenidos o salidos. Hablaremos sobre los campos de comando y puerto que se muestran en este resultado más adelante. `sudo docker ps -a` ![](https://i.imgur.com/qwgqGiB.png) ### stop - Detener un contenedor Para detener un contenedor en ejecución, use el comando `docker stop`, con el ID del contenedor o el nombre del contenedor. Si no está seguro del nombre, ejecute el comando `docker ps` para obtenerlo. `docker ps` ![](https://i.imgur.com/ifZG1xl.png) Si tiene éxito, verá el nombre impreso y al ejecutar docker ps nuevamente, no se mostrarán contenedores en ejecución. `sudo docker stop awesome_chatterjee` ![](https://i.imgur.com/di5bkxn.png) Sin embargo, al ejecutar "docker ps -a" se muestra el contenedor y su estado muestra que salió (Exited) hace unos segundos. `sudo docker ps -a` ![](https://i.imgur.com/zLWnTfp.png) ### rm - Eliminar un contenedor ¿Qué pasa si no queremos que un contenedor esté por ahí consumiendo espacio? ¿Y si queremos deshacernos de él? Utilice el comando `docker rm` para eliminar un contenedor detenido o salido (exited) de forma permanente. Si imprime el nombre, estamos bien. `sudo docker rm awesome_chatterjee` ![](https://i.imgur.com/DZKDZAP.png) Ejecutaremos el comando `docker ps -a` nuevamente para verificar que ya no esté presente. `sudo docker ps -a` ![](https://i.imgur.com/NCTa9qq.png) ### images - Listar imágenes Pero ¿qué pasa con la imagen nginx que descargamos al principio? Ya no estamos usando eso, entonces, ¿cómo nos deshacemos de esa imagen, pero primero cómo vemos una lista de imágenes presentes en nuestro host? Ejecutamos el comando `docker images` para ver una lista de las imágenes disponibles y sus tamaños en nuestro host. `sudo docker images` ![](https://i.imgur.com/wORIZaA.png) En este caso, tenemos 3 imágenes. Hablaremos de los tags (etiquetas) más adelante. ### rmi - Eliminar imágenes Para eliminar una imagen que ya no planea usar, ejecute el comando `docker rmi`. **Importante**: Debemos asegurarnos de que no se estén ejecutando contenedores de esa imagen antes de intentar eliminar la imagen. Debemos detener y eliminar todos los contenedores dependientes para poder eliminar una imagen. `sudo docker rmi nginx` ![](https://i.imgur.com/yJfDVc2.png) ### pull - Descargar una imagen Cuando ejecutamos el comando `docker run` anteriormente, se descargó la imagen de Ubuntu ya que no pudo encontrar una localmente. ¿Qué pasa si simplemente queremos descargar la imagen y mantenerla para que cuando ejecutamos el comando `docker run` no tengamos que esperar a que se descargue? Usamos el comando `docker pull` para extraer solo esa imagen y no ejecutar el contenedor. Entonces, en este caso, el comando `docker pull nginx` extrae la imagen Nginx y la almacena en nuestro host. ![](https://i.imgur.com/27vKEkP.png) ### Otro ejemplo Digamos que queremos ejecutar un contenedor de Docker desde una imagen de Ubuntu, cuando ejecutamos el comando `docker run ubuntu`, se ejecuta una instancia de la imagen de Ubuntu y sale inmediatamente. `sudo docker run ubuntu` ![](https://i.imgur.com/O5yxync.png) Si tuviera que enumerar los contenedores en ejecución, no vería el contenedor en ejecución. `sudo docker ps` ![](https://i.imgur.com/qSYnvx8.png) Si enumera todos los contenedores, incluidos los que están detenidos, verá que el nuevo contenedor que ejecutó está en una etapa de salida (exited). `sudo docker ps -a` ![](https://i.imgur.com/lEmSm3D.png) **Ahora, ¿por qué pasa esto?** A diferencia de las máquinas virtuales, los contenedores no están destinados a alojar un sistema operativo. Los contenedores están destinados a ejecutar una tarea o proceso específico, como alojar una instancia de un servidor web, un servidor de aplicaciones, una base de datos o simplemente para llevar a cabo algún tipo de tarea de cálculo o análisis. Una vez que se completa la tarea, los contenedores salen. El contenedor solo vive mientras el proceso dentro de él esté vivo. Si el servicio web dentro del contenedor se detiene o falla, el contenedor se cierra. Esta es la razón por la que cuando ejecuta un contenedor desde una imagen de Ubuntu, se detiene inmediatamente porque Ubuntu es solo una imagen de un sistema operativo que se usa como imagen base para las otras aplicaciones. No hay ningún proceso o aplicación ejecutándose en él de forma predeterminada. ### Agregar un comando Si la imagen no está ejecutando ningún servicio como es el caso de Ubuntu, puede indicarle a Docker que ejecute un proceso con el comando `docker run`, por ejemplo, un comando `sleep` con una duración de cinco segundos. Cuando el contenedor se inicia, ejecuta el comando `sleep` y entra en suspensión durante cinco segundos, luego el comando `sleep` sale y el contenedor se detiene. `sudo docker run ubuntu sleep 5` ![](https://i.imgur.com/wOeGUqk.png) `sudo docker ps` ![](https://i.imgur.com/Y1MUZ1K.png) Lo que acabamos de ver es ejecutar un comando cuando ejecutamos el contenedor. ### exec - Ejecutar un comando ¿Qué pasa si quisiéramos ejecutar un comando en un contenedor en ejecución? Por ejemplo, cuando ejecuto "docker ps -a", puedo ver que hay un contenedor en ejecución que usa la imagen de ubuntu y duerme durante 100 segundos. `sudo docker run ubuntu sleep 100` ![](https://i.imgur.com/XorIuLc.png) `sudo docker ps -a` ![](https://i.imgur.com/eDibBAI.png) Digamos que me gustaría ver el contenido de un archivo dentro de este contenedor en particular. Podría usar el comando `docker exec` para ejecutar un comando en mi contenedor Docker. En este caso, para imprimir el contenido del archivo /etc/hosts. `sudo docker exec thirsty_driscoll cat /etc/hosts` ![](https://i.imgur.com/aXyIGAR.png) ### run - adjuntar (attach) y separar (detach) Ahora vamos a ejecutar la imagen de Docker desarrollada para una aplicación web simple. El nombre del repositorio es kodekloud/simple-webapp. Ejecuta un servidor web simple que escucha en el puerto 8080. `sudo docker run kodekloud/simple-webapp` ![](https://i.imgur.com/oFW7AVm.png) Cuando ejecutamos un comando `docker run` como este, se ejecuta en primer plano o en un modo adjunto (attached), lo que significa que estará adjunto a la consola o la salida estándar del contenedor Docker y veremos la salida del servicio web en nuestra pantalla. No podremos hacer nada más en esta consola que no sea ver el resultado hasta que este contenedor de Docker se detenga. No responderá a nuestra entradas (inputs), presionaremos la combinación Ctrl+C para detener el contenedor y la aplicación alojada en el contenedor se sale y volverá al prompt. `Ctrl+C` ![](https://i.imgur.com/oUFotYi.png) Otra opción es ejecutar el contenedor Docker en el modo separado proporcionando la opción -d. Esto ejecutará el contenedor Docker en segundo plano y volverá a su prompt de inmediato. El contenedor seguirá ejecutándose en el background. `sudo docker run -d kodekloud/simple-webapp` ![](https://i.imgur.com/VVOagrM.png) Ejecute el comando docker ps para ver el contenedor en ejecución. Ahora, si deseamos volver a adjuntarnos (attachearnos) al contenedor en ejecución más tarde, ejecutaremos el comando `docker attach` y especificaremos el nombre o ID del contenedor Docker. Si estamos especificando el ID de un contenedor en cualquier comando de Docker, simplemente basta con proporcionar los primeros caracteres solo para que sea diferente de otros IDs de contenedores en el host. En este caso, especificamos 6c869. `sudo docker attach 6c869` ![](https://i.imgur.com/EFxkHXv.png) No tenemos que preocuparnos por acceder a la interfaz de usuario del servidor web por ahora. ### Demostración: Comandos de Docker Imágenes de Docker: https://hub.docker.com/search?q=&type=image Imagen de CentOS: https://hub.docker.com/_/centos Todas las imágenes oficiales se pueden descargar simplemente con solo el nombre de esa imagen en particular (ej.: centos). Se bajará la imagen del repositorio llamado library, que contiene las imágenes oficiales. Por otro lado, si desarrollamos nuestras propias imágenes, si necesitamos ejecutar alguna de nuestras imágenes de nuestros repositorios, entonces necesitamos usar el nombre en este formato en particular (<ID de usuario>/<nombre del repositorio>). Ejemplo: `mmumshad/anisable-playable` `sudo docker run centos` ![](https://i.imgur.com/IObVYoo.png) Se ejecutó pero salió inmediatamente porque no pedimos hacer nada. La imagen de CentOS es solo una imagen base para el sistema operativo CentOS. Como sabemos, Docker se usa para ejecutar servicios y aplicaciones. Entonces, si deseamos mantener vivo ese contenedor, debemos ejecutar algo. Así que vamos a ejecutarlo una vez más. Esta vez no tomará mucho tiempo porque la imagen ya está extraída y vamos a pedirle que ejecute el comando `bash`. Si queremos iniciar sesión automáticamente en el contenedor de Docker cuando se ejecuta, deberíamos usar las opciones -it. Veremos cuáles son estas opciones en un momento. `sudo docker run -it centos bash` ![](https://i.imgur.com/5OpJWjY.png) El prompt ha cambiado. Entonces, lo que ha sucedido es que Docker ejecutó el contenedor CentOS, que es el sistema operativo CentOS, luego ejecutó el comando `bash` y luego inició sesión directamente en el contenedor, ya que especificamos las opciones -it. Si miramos el prompt vemos que aparece root@ y un ID único, este es el ID único para el contenedor Docker. Vamos a comprobar el sistema operativo en el que estamos. `cat /etc/*release` ![](https://i.imgur.com/Bc1yvJ2.png) Como podemos ver, estamos en CentOS. Ahora, nos salimos del contenedor. `exit` ![](https://i.imgur.com/lvHOe0i.png) El comando `docker ps` enumera todos los contenedores en ejecución. `sudo docker ps` ![](https://i.imgur.com/qp5YIBj.png) En este caso, no tenemos un contenedor en ejecución porque solo ejecutamos CentOS, iniciamos sesión en ese CentOS, ejecutamos un par de comandos, luego salimos y eso en realidad nos sacó del contenedor y el contenedor no estaba ejecutando ningún servicio, por lo que Docker salió de ese contenedor. El comando `docker ps` enumera solo todos los contenedores en ejecución. Ahora no tenemos ningún contenedor en ejecución, así que lo que vamos a hacer es ejecutar el contenedor de CentOS una vez más y ejecutar el "comando sleep" y simplemente dormir durante 20 segundos. Entonces, el contenedor va a subir, simplemente se dormirá durante 20 segundos y luego simplemente se apagará. Luego, queremos ejecutar esto en segundo plano simplemente dándole la opción -d, para que regresemos a nuestra terminal. `sudo docker run -d centos sleep 20` ![](https://i.imgur.com/C4YyqGY.png) Ahora, si ejecutamos el comando `docker ps`, veremos el ID del contenedor, la imagen que se está ejecutando, el comando que se está ejecutando es "sleep 20", se creó hace 7 segundos, ha estado activo durante 6 segundos y tiene un nombre gracioso: frosty_yalow. `sudo docker ps` ![](https://i.imgur.com/8RXhwcC.png) Le pedimos al Docker que durmiera durante 20 segundos, solo para mantenerlo vivo durante 20 segundos. Entonces, si ejecutamos el comando "docker ps" ahora, probablemente debería haber desaparecido. `sudo docker ps` ![](https://i.imgur.com/XopK0dY.png) Se fue porque durmió durante 20 segundos y luego ese fue el final de ese comando y, por lo tanto, desde que el comando salió, el contenedor Docker también salió porque no tenía nada que ejecutar ni ningún servicio ejecutándose en él. Si quisiéramos ver todos los contenedores que ejecutamos en el pasado y todos los contenedores salidos, podríamos ejecutar un `docker ps -a` y ver todos los contenedores que salieron. `sudo docker ps -a` ![](https://i.imgur.com/MHjVmSv.png) El último que ejecutamos fue el centos con el comando `sleep`. Se creó hace unos 8 minutos y salió hace unos 7 minutos. De manera similar, antes de eso, ejecutamos CentOS con el bash la primera vez, y estuvimos en él algunos minutos verificando el sistema operativo, etc. y se creó hace unos 26 minutos. Del mismo modo, podemos ver todos los contenedores anteriores que ejecutamos. Entonces, cuando ejecutamos un contenedor y sale, el contenedor vive en la unidad de disco. **Otro ejemplo** `sudo docker run -d centos sleep 2000` ![](https://i.imgur.com/3cOWQ1V.png) `sudo docker ps` ![](https://i.imgur.com/qgsYlIk.png) `sudo docker stop objective_engelbart` ![](https://i.imgur.com/HHHar8Z.png) Se necesitan un par de segundos para detener o forzar la eliminación de ese contenedor Docker en particular. `sudo docker ps` ![](https://i.imgur.com/oJacpjX.png) `sudo docker ps -a` ![](https://i.imgur.com/9C4pnJn.png) El código de salida para los contenedores anteriores son todos 0 porque salen de él en condiciones normales, pero como el último contenedor lo matamos usando el comando de stop, podemos ver que el código de salida es 137. Eliminar contenedores: Por ID: `sudo docker rm c4f55835b244` ![](https://i.imgur.com/fSdxY5I.png) Por nombre: `sudo docker rm elegant_chaplygin` ![](https://i.imgur.com/7JconlP.png) Esto es lo que necesitamos para limpiar el espacio en disco. Enumerar todos los contenedores: `sudo docker ps -a` ![](https://i.imgur.com/ZWYXIbF.png) Del mismo modo, vamos a deshacernos de más contenedores. Eliminar varios contenedores a la vez en una línea especificando los primeros caracteres de los IDs: `sudo docker rm 6f4 0ad` ![](https://i.imgur.com/Fi8kJvm.png) `sudo docker ps -a` ![](https://i.imgur.com/U4lsNDI.png) Borramos el contenedor restante. `sudo docker rm cf9` ![](https://i.imgur.com/ot2z7Yn.png) `sudo docker ps -a` ![](https://i.imgur.com/q3X2PCT.png) Ver imágenes: `sudo docker images` ![](https://i.imgur.com/84saHWX.png) [busybox](https://hub.docker.com/_/busybox) es un juego de herramientas realmente liviano, tiene un tamaño aproximado de 1,23 MB. Si necesitamos probar algo realmente pequeño, podemos usar esta imagen en particular. Deshacerse de las imágenes: `sudo docker rmi busybox` ![](https://i.imgur.com/X7NZcYr.png) `sudo docker rmi ubuntu` ![](https://i.imgur.com/Xp9FZFt.png) Ver imágenes: `sudo docker images` ![](https://i.imgur.com/Sy1uahc.png) Ejecutar CentOS: `sudo docker run centos` ![](https://i.imgur.com/pRkNqLk.png) Enumerar todos los contenedores: `docker ps -a` ![](https://i.imgur.com/BJSBw1Y.png) Si intentamos eliminar la imagen de Docker para CentOS usando el comando rmi, en realidad fallará al decir que esta imagen en particular está siendo utilizada por un contenedor en particular. `sudo docker rmi centos` ![](https://i.imgur.com/AF6sw3U.png) Entonces, en realidad, tenemos un contenedor que depende de esta imagen en particular. Por lo tanto, primero debemos deshacernos de todos los contenedores antes de eliminar la imagen. Así que vamos a hacer un `docker rm`. `sudo docker rm 6b` ![](https://i.imgur.com/fiILkiQ.png) Nos aseguramos de que el contenedor ya no exista. `sudo docker ps -a` ![](https://i.imgur.com/A9Y0ZDT.png) Y luego quitamos la imagen. `sudo docker rmi centos` ![](https://i.imgur.com/KUuocZe.png) Ahora la imagen habrá desaparecido. `sudo docker images` ![](https://i.imgur.com/XOUsEQY.png) De manera similar, vamos a eliminar el contenedor "simple-webapp". ![](https://i.imgur.com/ZqVv7Dp.png) Imagen de hello-world: https://hub.docker.com/_/hello-world No tenemos contenedores y no tenemos imágenes ejecutándose en este momento. `sudo docker ps` ![](https://i.imgur.com/KrDUYAq.png) `sudo docker images` ![](https://i.imgur.com/wvgmo8k.png) Pull: Con `docker run` primero busca una imagen localmente, si no va a salir y sacar esa imagen e inmediatamente ejecutar esa imagen en un contenedor. Si no queremos que Docker se ejecute inmediatamente y si solo queremos extraer una imagen y mantenerla para que luego, si planeamos ejecutarla y no queremos perder tiempo extrayendo la imagen, podríamos simplemente tirar (pull) de esa imagen usando el comando `docker pull`. `sudo docker pull ubuntu` ![](https://i.imgur.com/dlaMoLR.png) Esto sale y extrae la imagen de Docker y la almacena localmente en el sistema local. Ver imágenes: `sudo docker images` ![](https://i.imgur.com/hkBgvMT.png) Ejecutar un comando en un contenedor en ejecución: `sudo docker run -d ubuntu sleep 100` ![](https://i.imgur.com/3o9sWYY.png) `sudo docker ps` ![](https://i.imgur.com/f9cOayg.png) `sudo docker exec e9f6130daa57 cat /etc/*release*` ![](https://i.imgur.com/7nA4aBm.png) ## Demostración: Docker Labs https://katacoda.com/kodekloud/scenarios/docker-for-beginners-fcc-basiccommands **¿Cuál es la versión de Docker Server Engine que se ejecuta en el host?** `docker version` ![](https://i.imgur.com/KteU5XK.png) **¿Cuántos contenedores se están ejecutando en este host?** `docker ps` ![](https://i.imgur.com/yLw7bvv.png) **¿Cuántas imágenes hay disponibles en este host?** `docker images` ![](https://i.imgur.com/YLJQopl.png) En realidad son 12 y no 10, hay una errata. **Ejecute un contenedor usando la imagen de redis** `docker run redis` ![](https://i.imgur.com/uRoTBDx.png) **Detenga el contenedor que acaba de crear** `Ctrl+C` o `docker stop <ID/nombre>` ![](https://i.imgur.com/IJWcTWJ.png) **¿Cuántos contenedores están FUNCIONANDO en este host ahora?** ``` docker ps docker ps -a ``` ![](https://i.imgur.com/gZqubg3.png) Hay 0 ya que de redis nos hemos salido anteriormente, no hay que confundir los contenedores en ejecución de los contenedores en los que nos hemos salido. **¿Cuántos contenedores están FUNCIONANDO en este host ahora?** **Acabamos de crear algunos.** `docker ps` ![](https://i.imgur.com/W5eYCGY.png) **¿Cuántos contenedores están PRESENTES en el host ahora?** **Incluidos los que se ejecutan y los que no se ejecutan** `docker ps -a` ![](https://i.imgur.com/eTY68Ua.png) **¿Cuál es la imagen utilizada para ejecutar el contenedor nginx-1?** `docker ps` ![](https://i.imgur.com/wpnSBkd.png) **¿Cuál es el nombre del contenedor creado usando la imagen de ubuntu?** `docker ps` ![](https://i.imgur.com/jHqhPUj.png) **¿Cuál es el ID del contenedor que usa la imagen alpine y no se está ejecutando?** `docker ps -a` ![](https://i.imgur.com/sYDXSkZ.png) **¿Cuál es el estado del contenedor alpine detenido?** `docker ps -a` ![](https://i.imgur.com/w080Cu5.png) **Eliminar todos los contenedores del host de Docker** **Tanto en ejecución como no en ejecución. Recuerde que es posible que deba detener los contenedores antes de eliminarlos.** ``` docker stop 87 08 95 ae 12 cb docker rm 87 08 95 ae 12 cb ``` ![](https://i.imgur.com/wjuAjpz.png) Es muy importante siempre parar antes de borrar los contenedores. **Eliminar la imagen de ubuntu** `docker rmi ubuntu` ![](https://i.imgur.com/HanUwuj.png) **Debe extraer una imagen de Docker que se utilizará para ejecutar un contenedor más adelante. Haga un pull de la imagen nginx:1.14-alpine** **Solo haga un pull de la imagen, no cree un contenedor.** `docker pull nginx:1.14-alpine` ![](https://i.imgur.com/Qe9fJP5.png) **Ejecute un contenedor con la imagen nginx:1.14-alpine y asígnele el nombre webapp** `docker run --name webapp nginx:1.14-alpine` ![](https://i.imgur.com/gsICxZZ.png) **Limpieza: elimine todas las imágenes en el host** **Retire los contenedores según sea necesario** ``` docker ps -a docker rm 4a docker images ``` ![](https://i.imgur.com/NZSpnDg.png) `docker rmi nginx:alpine mariadb:10 mariadb:latest redis:latest mysql:8 mysql:latest alpine:latest postgres:12 postgres:latest mongo:latest weaveworks/scope:1.11.4 nginx:1.14-alpine quay.io/ansible/molecule:2.20` ![](https://i.imgur.com/afREG3h.png) # Docker Run ## Docker Run ### run - tag Aprendimos que podíamos usar el comando `docker run redis` para ejecutar un contenedor con un servicio redis. En este caso, la última versión de redis, que resulta ser la 6.2.1. `sudo docker run redis` ![](https://i.imgur.com/NEfDek4.png) Pero, ¿y si queremos ejecutar otra versión de redis? Como, por ejemplo, una versión anterior, digamos 4.0. Para ello, especificamos la versión separada por dos puntos. A esto se le llama tag (etiqueta). `sudo docker run redis:4.0` ![](https://i.imgur.com/v7Txt95.png) En este caso, Docker extrae una imagen de la versión 4.0 de redis y la ejecuta. También tenga en cuenta que si no especificamos ningún tag como en el primer comando, Docker considerará que el tag predeterminado es latest. latest es una tag asociado a la última versión de ese software que se rige por los autores de ese software. Entonces, como usuario, ¿cómo encuentra información sobre estas versiones y cuál es la latest? En https://hub.docker.com/, buscamos una imagen y encontraremos todos los tags compatibles en su descripción. Cada versión del software puede tener múltiples tags largos y cortos asociados. En este caso, la versión 6.2.1 también tiene el tag latest. ![](https://i.imgur.com/RLQsdia.png) ### run - STDIN Veamos ahora los inputs (entradas). Contamos con una sencilla aplicación prompt que cuando se ejecuta pide un nombre y al ingresar el nombre imprime un mensaje de bienvenida. ![](https://i.imgur.com/1wPCALW.png) Si tuviéramos que dockerizar esta aplicación y ejecutarla como un contenedor de Docker como este, no esperaría el mensaje. Simplemente imprime lo que se supone que la aplicación debe imprimir en salida estándar (STDOUT). `sudo docker run kodekloud/simple-prompt-docker` ![](https://i.imgur.com/JmWLIiv.png) Esto se debe a que, de forma predeterminada, el contenedor Docker no escucha una entrada estándar (STDIN). Aunque estamos conectados a su consola, no puede leer ninguna entrada nuestra. No tiene una terminal para leer las entradas. Se ejecuta en un modo no interactivo, si quisiéramos proporcionar nuestra entrada, debemos asignar la entrada estándar de nuestro host al contenedor Docker usando el parámetro -i. El parámetro -i es para el modo interactivo y cuando ingresamos nuestro nombre imprime la salida esperada. `sudo docker run -i kodekloud/simple-prompt-docker` ![](https://i.imgur.com/ujFSx2h.png) ![](https://i.imgur.com/9ootFoS.png) Pero todavía falta algo en esto, el prompt, cuando ejecutamos la aplicación al principio, nos pidió nuestro nombre. Pero cuando está dockerizado, falta ese prompt, aunque parece haber aceptado nuestra entrada, eso es porque la aplicación, nos lo solicita en la terminal y no la hemos adjuntado a la terminal del contenedor. Para esto, usamos la opción -t. -t significa pseudoterminal. Entonces, con la combinación de -i y -t, ahora estamos conectados a la terminal, así como en un modo interactivo en el contenedor. `sudo docker run -it kodekloud/simple-prompt-docker` ![](https://i.imgur.com/hrzKuna.png) ### run - PORT mapping (asignación de puertos) Ahora veremos el mapeo de puertos o la publicación de puertos en contenedores. Volvamos al ejemplo en el que ejecutamos una aplicación web simple en un contenedor Docker en nuestro host Docker. Recuerde que el host subyacente donde está instalado Docker se llama Docker Host o Docker Engine. Cuando ejecutamos una aplicación web en contenedor, se ejecuta y podemos ver que el servidor se está ejecutando. Pero, ¿cómo accede un usuario a nuestra aplicación? `sudo docker run kodekloud/webapp` ![](https://i.imgur.com/pZdlct0.png) Como podemos ver, nuestra aplicación está escuchando en el puerto 5000, por lo que podríamos acceder a nuestra aplicación usando el puerto 5000. Pero, ¿qué IP utilizo para acceder a ella desde un navegador web? Hay dos opciones disponibles: 1. Usando la IP del contenedor de Docker, cada contenedor de Docker obtiene una IP asignada de forma predeterminada. En este caso es 172.17.0.2. Pero recuerde que esta es una IP interna y solo es accesible dentro del host de Docker. Entonces, si abre un navegador desde el host de Docker, puede ir a http://172.17.0.2:5000 para acceder a la dirección IP. Pero dado que se trata de una IP interna, los usuarios fuera del host de Docker no pueden acceder a ella utilizando esta IP. ![](https://i.imgur.com/nEwIVCK.png) 2. Para esto, podríamos usar la IP del host de Docker que es 192.168.1.5. Pero para que eso funcione, debemos haber asignado el puerto dentro del contenedor de Docker a un puerto libre en el host de Docker. Por ejemplo, si queremos que los usuarios accedan a nuestra aplicación a través del puerto 80 en nuestro host Docker, podríamos mapear el puerto 80 de localhost al puerto 5000 en el contenedor Docker usando el parámetro -p en nuestro comando de run, de este modo. `sudo docker run -p 80:5000 kodekloud/webapp` ![](https://i.imgur.com/nTLCc4E.png) Y así el usuario puede acceder a nuestra aplicación yendo a la URL http://192.168.1.5:80. ![](https://i.imgur.com/rAok7Cc.png) Todo el tráfico en el puerto 80 de nuestro host Docker se enrutará al puerto 5000 dentro del contenedor Docker. De esta manera, podemos ejecutar varias instancias de nuestra aplicación y asignarlas a diferentes puertos en el host de Docker o ejecutar instancias de diferentes aplicaciones en diferentes puertos. `sudo docker run -p 8000:5000 kodekloud/webapp` ![](https://i.imgur.com/wMIwC27.png) `sudo docker run -p 8001:5000 kodekloud/webapp` ![](https://i.imgur.com/rFmWUbC.png) ![](https://i.imgur.com/qT0aUWS.png) Por ejemplo, en este caso estamos ejecutando una instancia de MySQL que ejecuta una base de datos en nuestro host y escucha en el MySQL predeterminado, que resulta ser 3306. `sudo docker run -p 3306:3306 mysql` U otra instancia de MySQL en otro puerto: 8306. `sudo docker run -p 8306:3306 mysql` Así que podemos ejecutar tantas aplicaciones como estas y asignarlas a tantos puertos como queramos. Y, por supuesto, no se puede asignar el mismo puerto en el host de Docker más de una vez. Discutiremos más sobre mapeo de puertos y redes de contenedores en la sección de Red. ![](https://i.imgur.com/5OzHwKz.png) ### run - Volume mapping (Mapeo de volumen) Veamos ahora cómo se conservan los datos en un contenedor Docker. Por ejemplo, digamos que vamos a ejecutar un contenedor MySQL. `docker run mysql` Cuando se crean bases de datos y tablas, los archivos de datos se almacenan en la ubicación /var/lib/mysql dentro del contenedor de Docker. Recordemos que el contenedor Docker tiene su propio sistema de archivos aislado y cualquier cambio en cualquier archivo ocurre dentro del contenedor. Supongamos que volcamos una gran cantidad de datos en la base de datos, ¿qué sucede si elimináramos el contenedor MySQL? ``` docker run mysql docker rm mysql ``` Tan pronto como lo hace, el contenedor junto con todos los datos que contiene se destruyen, lo que significa que todos nuestros datos desaparecen. ![](https://i.imgur.com/ChEA0tc.png) Si quisiéramos conservar los datos, tendríamos que mapear un directorio fuera del contenedor en el host de Docker a un directorio dentro del contenedor. En este caso, creamos un directorio llamado /opt/datadir y lo asignamos a /var/lib/mysql dentro del contenedor de Docker usando la opción -v y especificando el directorio en el host de Docker seguido de dos puntos y el directorio dentro del contenedor de Docker. `docker run -v /opt/datadir:/var/lib/mysql mysql` De esta forma, cuando se ejecute el contenedor Docker, montará implícitamente el directorio externo en una carpeta dentro del contenedor Docker. De esta manera, todos nuestros datos ahora se almacenarán en el volumen externo en /opt/datadir y permanecerán incluso si se elimina el contenedor Docker. ![](https://i.imgur.com/Gbnw3tp.png) ### Inspeccionar un contenedor El comando `docker ps` es lo suficientemente bueno para obtener detalles básicos sobre contenedores como sus nombres e ID, pero si desea ver detalles adicionales sobre un contenedor específico, use el comando` docker inspect` y proporcione el nombre o ID del contenedor. `sudo docker inspect focused_panini` ![](https://i.imgur.com/Gx8qCzy.png) Devuelve todos los detalles de un contenedor en formato JSON, como el estado, los montajes, los datos de configuración, la configuración de red, etc. Recuerda usarlo cuando necesites encontrar detalles en un contenedor. ### Logs de contenedores ¿Cómo vemos los logs de un contenedor que ejecutamos en segundo plano? Por ejemplo, ejecutamos nuestra simple-webapp usando el parámetro -d y ejecutamos el contenedor en un modo detached (separado). `sudo docker run -d kodekloud/simple-webapp` ![](https://i.imgur.com/rGka1an.png) ¿Cómo veo los registros que resultan ser el contenido escrito en la salida estándar (STDOUT) de ese contenedor? Utilice el comando `docker logs` y especifique el ID o el nombre del contenedor. `sudo docker logs vibrant_yalow` ![](https://i.imgur.com/mxEk1xp.png) ## Demostración: funciones avanzadas de run en Docker En esta demostración, veremos algunas de las opciones avanzadas con el comando Docker, algunas de las opciones avanzadas disponibles con la ejecución de contenedores Docker usando el comando `docker run`. `sudo docker run ubuntu:17.10 cat /etc/*release*` ![](https://i.imgur.com/Jzal8i7.png) `sudo docker run -d ubuntu sleep 1500` ![](https://i.imgur.com/7KJ6cgx.png) La última línea representa el ID de contenedor real, el ID de contenedor único. Esto significa que en realidad funciona correctamente. Si ejecutamos el comando `docker ps`, el ID del contenedor solo toma los primeros caracteres. `sudo docker ps` ![](https://i.imgur.com/gyl23Iu.png) Hay una imagen de Docker para Jenkins. Jenkins es un sistema de construcción. Digamos que queremos probar una nueva aplicación como esta. De hecho, esta es mi forma favorita de probar cualquier aplicación. Entonces, Jenkins es un servidor de integración y entrega continua y si no hemos trabajado con él antes y si solo quiero jugar con él, adquirir algo de experiencia práctica en él, en lugar de seguir las instrucciones de instalación e instalar una gran cantidad de dependencias de nuestro host subyacente, lo que podemos hacer es simplemente ejecutar Jenkins como un contenedor y jugar con él de esa manera. Las instrucciones para ejecutar Jenkins se especifican en https://hub.docker.com/r/jenkins/jenkins. `sudo docker run jenkins/jenkins` ![](https://i.imgur.com/1e2hrna.png) Ahora, como sabemos, Jenkins es un servidor web, por lo que lo que esperamos es que una vez que implementemos este contenedor en particular, en realidad esperamos ir a un navegador y navegar hasta este servidor Jenkins y acceder a la interfaz de usuario web. Ahora bien, ¿cómo accedemos a él? Si ejecutamos el comando `docker ps`, podemos ver que Jenkins se está ejecutando y está en el puerto 8080 y 50000. `sudo docker ps` ![](https://i.imgur.com/Pag7HKp.png) Hay dos formas de acceder a la interfaz de usuario web: 1. Usando la IP interna (necesitamos estar dentro de nuestro Docker Host) Para averiguar la IP interna de nuestro contenedor Jenkins, podríamos usar el comando `docker inspect` seguido del ID del contenedor o el nombre del contenedor. `sudo docker inspect 2999a3ed2f17 | grep IPAddress` ![](https://i.imgur.com/L0cg0rQ.png) Estamos en la página web utilizando la IP interna. ![](https://i.imgur.com/D3ppKAY.png) 2. Asignar un puerto al host de Docker y acceder a él mediante la IP externa (para acceder externamente) `sudo docker run -p 8080:8080 jenkins/jenkins` ![](https://i.imgur.com/3gHWqKM.png) Ahora podemos ver la página de Jenkins usando la IP de nuestro host Docker accediendo a http://192.168.1.5:8080 ![](https://i.imgur.com/sumVvV6.png) ![](https://i.imgur.com/L7jXg2b.png) Ahora seguimos las instrucciones que se muestran en la página de Jenkins, si miramos el resultado después del comando `docker run`, podremos ver que se ha creado un usuario administrador y una contraseña generada automáticamente. Entonces usaremos esta contraseña para desbloquear e iniciar las configuraciones en Jenkins. ![](https://i.imgur.com/WwahimC.png) ![](https://i.imgur.com/5BmC2ca.png) Vamos a instalar Jenkins, nos llevará unos minutos instalar algunos de los complementos predeterminados. ![](https://i.imgur.com/7QIIpNY.png) Rellenamos el formulario. ![](https://i.imgur.com/UTJSzOo.png) Presionamos "Save and Finish". ![](https://i.imgur.com/U33C5w6.png) Creamos un nuevo trabajo. ![](https://i.imgur.com/XdUhYkd.png) Si volvemos a la página de inicio de Jenkins, veremos que hay un trabajo llamado test. Así que hemos realizado algunos cambios de configuración. ![](https://i.imgur.com/Dyvw4th.png) Si regresamos y detenemos el contenedor con Ctrl+C y ejecutamos otro contenedor con el mismo comando, tendríamos que hacer todo lo que acabamos de hacer de nuevo, ya que no se guarda nada. `Ctrl+C` ![](https://i.imgur.com/mmfxfDx.png) Para conservar esos datos en los contenedores de Docker o en los reinicios del contenedor de Docker o incluso si destruimos los contenedores de Docker y ejecutamos un nuevo contenedor y nos gustaría conservar nuestros datos de configuración, como podemos ver en las instrucciones tenemos que mapear un volumen. ![](https://i.imgur.com/ZFt8GP1.png) Esto es lo mismo para una base de datos MySQL o algo por el estilo. Vamos a crear un directorio local llamado datosjenkins en nuestro directorio personal. `mkdir ~/datosjenkins` ![](https://i.imgur.com/i5Bm5l1.png) Lo que vamos a hacer cuando ejecutemos Docker es mapear ese volumen que es /home/victor/datosjenkins a la ubicación donde Jenkins almacena sus datos por defecto, que está en /var/jenkins_home. `sudo docker run -p 8080:8080 -v /home/victor/datosjenkins:/var/jenkins_home jenkins/jenkins` ![](https://i.imgur.com/pzwwQKP.png) Ahora, al instalar, todos los datos se almacenarán en nuestro Docker Host y si tenemos algún problema con el contenedor podremos recuperar nuestra configuración e información porque persiste localmente. `ls -la ~/datosjenkins` ![](https://i.imgur.com/ek57kpE.png) ## Demostración: Docker Labs https://katacoda.com/kodekloud/scenarios/docker-for-beginners-fcc-run **Primero inspeccionaremos el medio ambiente. ¿Cuántos contenedores se están ejecutando en este host?** `docker ps` ![](https://i.imgur.com/aTkn4Iw.png) **¿Cuál es la imagen que utiliza el contenedor?** `docker ps` ![](https://i.imgur.com/jeuM15b.png) **¿Cuántos puertos se publican en este contenedor?** `docker ps` ![](https://i.imgur.com/fz0yPT1.png) En este apartado se refiere a cuántos pares de puertos hay. **¿Cuáles de los siguientes puertos están expuestos en el CONTENEDOR?** `docker ps` ![](https://i.imgur.com/RRbqjov.png) **¿Cuáles de los puertos siguientes se publican en el Host?** `docker ps` ![](https://i.imgur.com/mYUPJPt.png) **Ejecute una instancia de kodekloud/simple-webapp con una etiqueta blue y asigne el puerto 8080 en el contenedor y a 38282 en el host.** * Imagen: kodekloud/simple-webapp:blue * Puerto del Contenedor: 8080 * Puerto del Host: 38282 `docker run -p 38282:8080 kodekloud/simple-webapp:blue` ![](https://i.imgur.com/6pL6Zfj.png) # Imágenes de Docker ## Imágenes de Docker Vamos a ver cómo crear nuestra propia imagen. ### ¿Qué estoy contenedorizando? Ahora, antes de eso, ¿por qué necesitarías crear tu propia imagen? Podría deberse a que no podemos encontrar un componente o un servicio que queremos usar como parte de nuestra aplicación en Docker, o porque nosotros y nuestro equipo decidimos que la aplicación que estamos desarrollando se dockerizará para facilitar el envío y la implementación. En este caso, vamos a contenerizar una aplicación web simple que se construye utilizando el framework Flask de Python. ![](https://i.imgur.com/nvf8WYQ.png) ## ¿Cómo crear mi propia imagen? Primero, debemos entender qué estamos creando en contenedores, o para qué aplicación estamos creando una imagen y cómo se construye la aplicación. Así que, empezaremos a pensar en lo que podríamos hacer si queremos implementar la aplicación manualmente. Anotamos los pasos necesarios en el orden correcto. Para crear una imagen para una aplicación web simple, si tuviéramos que configurarla manualmente, comenzaríamos con un sistema operativo como Ubuntu, luego actualizaríamos los repositorios de source usando el comando apt, instalaríamos las dependencias usando el comando apt, luego instalaríamos las dependencias de Python usando el comando pip, luego copiaríamos el código fuente de nuestra aplicación en una ubicación como /opt y finalmente ejecutaríamos el comando flask. ![](https://i.imgur.com/HFQEZZG.png) Ahora que tenemos las instrucciones, creamos un Dockerfile usando esto. Aquí hay una descripción general rápida del proceso de creación de nuestra propia imagen. Primero, cree un archivo Docker llamado Dockerfile y escriba las instrucciones para configurar nuestra aplicación en él, como instalar dependencias, dónde copiar el código fuente y cuál es el punto de entrada de la aplicación, etc. ![](https://i.imgur.com/80clVNb.png) Una vez hecho esto, cree su imagen usando el comando `docker built` y especifique el Dockerfile como entrada, así como un nombre de etiqueta para la imagen. ![](https://i.imgur.com/4OvRhON.png) Esto creará una imagen localmente en nuestro sistema. Para que esté disponible en el registro público de Docker Hub, ejecutamos el comando `docker push` y especificamos el nombre de la imagen que acabamos de crear. ![](https://i.imgur.com/DOk7wgf.png) En este caso, el nombre de la imagen es el nombre de mi cuenta, que es mmumshad seguido del nombre de la imagen, que es my-custom-app. ### Dockerfile Ahora echemos un vistazo más de cerca al Dockerfile. Dockerfile es un archivo de texto escrito en un formato específico que Docker puede entender. Está en formato de instrucciones y argumentos. ![](https://i.imgur.com/ME531Fp.png) Por ejemplo, en este Dockerfile todo lo que está a la izquierda en MAYÚSCULAS es una instrucción, en este caso: FROM, RUN, COPY y ENTRYPOINT son todo instrucciones. Cada una de estas indica a Docker que realice una acción específica mientras crea la imagen. Todo lo que está a la derecha es un argumento para esas instrucciones. La primera línea de Ubuntu define cuál es el sistema operativo base que debe enfocarse en el contenedor. Cada imagen de Docker debe basarse en otra imagen, ya sea un sistema operativo u otra imagen que se creó antes basada en un sistema operativo. Podemos encontrar versiones oficiales de todos los sistemas operativos en Docker Hub. Es importante tener en cuenta que todos los Dockerfiles deben comenzar con una instrucción FROM. La instrucción RUN le indica a Docker que ejecute un comando particular en esas imágenes base. Entonces, en este punto, Docker ejecuta los comandos `apt-get update` para buscar los paquetes actualizados e instala las dependencias requeridas en la imagen. Luego, la instrucción COPY copia los archivos del sistema local en la imagen de Docker. En este caso, el código fuente de nuestra aplicación está en la carpeta actual y lo copiaremos en la ubicación /opt/source-code dentro de la imagen de Docker. Y finalmente, ENTRYPOINT nos permite especificar un comando que se ejecutará cuando la imagen se ejecute como contenedor. ![](https://i.imgur.com/YhkTjOl.png) ### Arquitectura en capas Cuando Docker crea las imágenes, las crea en una arquitectura en capas. Cada línea de instrucción crea una nueva capa en la imagen de Docker con solo los cambios de la capa anterior. Por ejemplo, la primera capa es un sistema operativo Ubuntu base seguido de una segunda instrucción que crea una segunda capa que instala todos los paquetes apt y luego la tercera instrucción crea una tercera capa con los paquetes de Python seguida de la cuarta capa que copia el código fuente. encima y la capa final que actualiza el ENTRPOINT de la imagen. Dado que cada capa solo almacena los cambios de la capa anterior, también se refleja en el tamaño. Si miramos la imagen base de Ubuntu, tiene un tamaño de alrededor de 120 MB, los paquetes de apt que instalamos son de alrededor de 300 MB, y luego las capas restantes son pequeñas. ![](https://i.imgur.com/OOXYEtK.png) Podríamos ver esta información si ejecutamos el comando `docker history` seguido del nombre de la imagen. ![](https://i.imgur.com/FHJByWD.png) ### Salida de docker build Cuando ejecutamos `docker build`, podemos ver los distintos pasos involucrados y el resultado de cada tarea. ![](https://i.imgur.com/IEBDNAh.png) Todas las capas de construcción se almacenan en caché, por lo que la arquitectura de capas nos ayuda a reiniciar el comando `docker build` desde ese paso en particular en caso de que falle o si tuviéramos que agregar nuevos pasos en el proceso de construcción, no tendríamos que comenzar de nuevo. ### failure (falla) Todas las capas son almacenadas en caché por Docker, por lo que en caso de que un paso en particular fallara, por ejemplo, en este caso, el paso 3 falló y tendríamos que solucionar el problema y volver a ejecutar `docker build`, se reutilizarán las capas de la caché y se continuarán creando las capas restantes. ![](https://i.imgur.com/tcz26Sq.png) ![](https://i.imgur.com/1inDYXA.png) Lo mismo es cierto si tuviéramos que agregar pasos adicionales en el Dockerfile. De esta manera, reconstruir nuestra imagen es más rápido, no tenemos que esperar a que Docker reconstruya la imagen completa cada vez. Esto es útil especialmente cuando actualizamos el código fuente de nuestra aplicación, ya que puede cambiar con más frecuencia, solo las capas por encima de las capas actualizadas deben reconstruirse. ![](https://i.imgur.com/K6BREgH.png) ### ¿Qué se puede contener en contenedores? ![](https://i.imgur.com/CYgqalP.png) Acabamos de ver una serie de productos en contenedores, como bases de datos, herramientas de desarrollo, sistemas operativos, etc. Pero eso no es todo. Podemos contener casi todas las aplicaciones, incluso las más simples como navegadores o utilidades como curl, aplicaciones como Spotify, Skype, etc. Básicamente, podemos contener todo y, en el futuro, vemos que así es como todos ejecutarán las aplicaciones. Ya nadie va a instalar nada en el futuro, en su lugar, solo lo ejecutarán usando Docker y cuando ya no lo necesiten, lo eliminarán fácilmente sin tener que limpiar demasiado. ## Demostración: creación de una nueva imagen de Docker En esta demostración, veremos cómo crear nuestra propia imagen de Docker para una aplicación que tenemos. Entonces, primero vamos a explicar un poco sobre nuestra aplicación. Tenemos una aplicación web simple, que se basa en el framework Flask de Python, está disponible en GitHub (https://github.com/mmumshad/simple-webapp-flask). Es una aplicación realmente simple que simplemente imprime un mensaje en la pantalla. Entonces, ¿cómo implementamos esta aplicación? Entonces, primero hay algunas dependencias. Por ejemplo, hay algunas dependencias de Python y luego instala las dependencias de la aplicación web como flask, flask-mysql, dependencias de Python y luego básicamente ejecuta la aplicación usando el comando flask. Y finalmente, una vez que está en funcionamiento, vamos al navegador y accedemos a la IP del host en el que se está ejecutando, vamos al puerto 5000 y veremos un mensaje de bienvenida. Entonces es una aplicación simple. Si preguntamos, ¿cómo estás? va a volver diciendo que estoy bien, ¿y tú ?. Vamos a ver el código, es solo un archivo app.py. ![](https://i.imgur.com/dSEIBrV.png) Si miramos este archivo, todo lo que tenemos es una ruta simple que es la ruta predeterminada que devuelve un mensaje de bienvenida y luego otra URL ¿Cómo estás?, que devuelve otro mensaje. Entonces es una aplicación web realmente simple. Entonces, primero veremos cómo implementarlo manualmente. Entonces, lo que nos gustaría hacer es no instalar nada en nuestro host Docker, queremos mantenerlo limpio. No queremos instalar algunas dependencias ahora y luego eliminarlas. Entonces, dado que ya tenemos Docker, la forma más fácil de hacerlo es comenzar con algún tipo de sistema operativo. Entonces, nos gustaría comenzar con un sistema operativo base como Ubuntu, ya que ya tenemos la imagen extraída. Entonces podríamos ejecutar un `docker run ubuntu`. Pero lo que queremos hacer es seguir vivos y queremos trabajar en ese contenedor en particular. Así que vamos a agregar un comando bash para que ejecute el comando bash cuando inicie el contenedor y vamos a mapear la entrada estándar (STDIN) usando el parámetro -i y la terminal usando el parámetro -t. De esta manera, tan pronto como se inicia la imagen de Ubuntu y ejecuta el comando bash, toma o adjunta nuestra entrada y terminal a ese contenedor en particular. Básicamente, eso ejecuta el contenedor y nos conecta con ese contenedor en particular, que es todo lo que queremos por ahora y, tan pronto como salgamos del contenedor, la imagen se detendrá. `sudo docker pull ubuntu:16.04` ![](https://i.imgur.com/js24TWv.png) `sudo docker run -it ubuntu:16.04 bash` ![](https://i.imgur.com/SwfHYFI.png) Así que esto es todo lo que queremos por ahora solo para jugar, realizar pruebas con nuestra aplicación. Así que tenemos nuestra imagen de Ubuntu ejecutándose y lo que vamos a hacer es seguir las instrucciones para configurar nuestra sencilla aplicación web. Entonces, lo primero que tenemos que hacer es actualizar el índice de paquetes. `apt-get update` ![](https://i.imgur.com/A66Ujb1.png) Ahora, vamos a instalar Python. `apt-get install -y python` ![](https://i.imgur.com/wf0FMEI.png) Entonces, instalamos Python y si ejecutamos el comando `python` podemos ver que Python 2.7.12 está instalado, lo cual es bueno. No salimos con `exit()`. ``` python exit() ``` ![](https://i.imgur.com/TYwU4oS.png) Necesitamos instalar pip antes de instalar dependencias de Python- `apt-get install python-pip` ![](https://i.imgur.com/9S4oStK.png) Así que pip ahora está instalado e instalaremos la dependencia de Flask para nuestra aplicación web usando pip. `pip install flask` ![](https://i.imgur.com/LUhfwVc.png) Ahora necesitamos el código fuente de la aplicación, vamos a copiar el archivo app.py (https://github.com/mmumshad/simple-webapp-flask/blob/master/app.py). Entonces vamos a pegar el código fuente en /opt/app.py. ``` cat > /opt/app.py import os from flask import Flask app = Flask(__name__) @app.route("/") def main(): return "Welcome!" @app.route('/how are you') def hello(): return 'I am good, how about you?' if __name__ == "__main__": app.run(host="0.0.0.0", port=8080) ^C ``` ![](https://i.imgur.com/t28VaxZ.png) Vamos a ejecutar la aplicación. ``` cd /opt FLASK_APP=app.py flask run --host=0.0.0.0 ``` ![](https://i.imgur.com/vRvecx2.png) Como podemos ver, nuestra aplicación Flask se está ejecutando actualmente. Ahora bien, ¿cómo accedemos a nuestro servidor web? Si recordamos, cuando ejecutamos el contenedor Docker usando el comando `docker run`, en realidad no hemos mapeado un puerto a nuestro host Docker, por lo que la única forma de acceder a nuestra aplicación es ir al host y acceder a él desde un navegador web. Vamos a entrar en http://172.17.0.2:5000/ porque es el puerto 5000 en el que está escuchando el servidor web y cuando lo ejecutamos podemos ver el mensaje de Welcome!. ![](https://i.imgur.com/1GNxEy8.png) De hecho, también podemos obtener algo de salida en el contenedor. ![](https://i.imgur.com/6PATXd8.png) Otra URL de la que hablamos fue `/how are you`. Y vuelve con el mensaje `I am good, how about you?`. ![](https://i.imgur.com/OGibZ28.png) Entonces la aplicación está funcionando. Así es como configuramos nuestra sencilla aplicación web. Ahora, ¿cómo lo dockerizamos? Vamos a salir de la aplicación, de hecho tenemos las instrucciones listas. Entonces, lo primero que debe hacer, sea lo que sea que necesite convertir en contenedor, primero revise los pasos básicos de instalación. Por lo tanto, intente hacerlo una vez manualmente y luego anote los pasos requeridos. Entonces, lo que vamos a hacer es abrir un bloc de notas y anotar los pasos que seguimos. Así que vamos a ejecutar el comando `history` y desplegar todas las instrucciones. ``` Ctrl+C history ``` ![](https://i.imgur.com/ucsnI3T.png) Por ejemplo, lo primero que hicimos fue `apt-get update`. Luego, ejecutamos el archivo `apt-get install -y python`. Luego ejecutamos `apt-get install python-pip`. Luego, `pip install flask`. Luego creamos nuestro archivo de aplicación en /opt/app.py, así que vamos a escribir `Crear o copiar el código de la aplicación en /opt/app.py`. Y finalmente, ejecutamos nuestra aplicación Flask usando el comando `FLASK_APP=/opt/app.py flask run --host=0.0.0.0`. ![](https://i.imgur.com/iWci542.png) Entonces, lo que vamos a hacer es salir de este contenedor y regresar a nuestro host de Docker, y ejecutar `docker` y vemos que no hay contenedores en ejecución. Entonces, como vimos, lo primero que debe hacer es crear un Dockerfile, así que antes de eso, crearemos un directorio llamado my-simple-webapp y crearemos todos los archivos de Docker dentro de este directorio en particular. ``` mkdir my-simple-webapp cd my-simple-webapp ``` ![](https://i.imgur.com/D4oDq45.png) Entonces, lo primero que necesitamos crear es el Dockerfile. Así que lo llamaré Dockerfile y lo que hicimos, si miramos nuestras instrucciones, es antes de ejecutar el comando `apt-get update` como vimos en las instrucciones, la primera instrucción siempre será FROM. Entonces, vamos a comenzar desde un sistema operativo base Ubuntu 16.04. Y luego ejecutaremos el comando `apt-get update` y luego` apt-get install -y` (se requiere -y, para que no espere espere a nuestro prompt), y luego podríamos hacer ambas cosas en un comando, podríamos instalar python y python-pip en el mismo comando. Y luego, necesitamos ejecutar Flask instalando las dependencias de Flask, así que hacemos `pip install flask`. Entonces, aquí es donde necesitamos copiar nuestro código fuente de la aplicación desde nuestro directorio local al contenedor Docker. Entonces usamos la instrucción COPY y luego decimos COPY app.py, esto significaría que asumimos que tenemos nuestro app.py en nuestro directorio local, que no tenemos por ahora, pero tan pronto como terminemos de crear el Dockerfile lo haremos a continuación. Entonces tenemos app.py y luego queremos que se copie en /opt/app.py dentro del contenedor Docker. Y finalmente, el código para ejecutar la aplicación que iría en ENTRYPOINT. Entonces diríamos que ENTRYPOINT es y simplemente copiamos el comando para ejecutar nuestra aplicación. ``` cat > Dockerfile FROM ubuntu:16.04 RUN apt-get update && apt-get install -y python python-pip RUN pip install flask COPY app.py /opt/ ENTRYPOINT FLASK_APP=/opt/app.py flask run --host=0.0.0.0 --port=8080 Ctrl+C ``` ![](https://i.imgur.com/2axvyxo.png) Ahora, necesito el código de mi aplicación en el mismo directorio. Así que crearemos app.py aquí y pegaremos el código fuente de la aplicación. ![](https://i.imgur.com/QgWMXNv.png) ``` cat > app.py import os from flask import Flask app = Flask(__name__) @app.route("/") def main(): return "Welcome!" @app.route('/how are you') def hello(): return 'I am good, how about you?' if __name__ == "__main__": app.run(host="0.0.0.0", port=8080) ^C ``` Así que estamos listos. Ahora podríamos ejecutar el comando `docker build .` y comenzará a construir la imagen. `sudo docker build .` ![](https://i.imgur.com/etz5Gg2.png) Ya tenemos Ubuntu 16.04, por lo que el paso 1 de 5 se completó de inmediato porque no tenía que salir y extraer la imagen, el paso dos es ejecutar `apt-get update`. Podemos ver la salida en pantalla de todos los pasos e instrucciones. Se terminó de construir nuestra imagen y se completaron todos los pasos. ![](https://i.imgur.com/UwVe1AM.png) Entonces, si miramos el comando `docker build`, no hemos especificado ningún nombre para esa imagen. Así que vamos a ejecutarlo de nuevo y le daremos un tag (etiqueta), vamos a poner `my-simple-webapp`. `sudo docker build . -t my-simple-webapp` ![](https://i.imgur.com/uBFHayw.png) Ahora, dado que ya está compilado, va a ir muy rápido porque no tiene que reconstruir toda la imagen nuevamente, como discutimos, cada vez que un Docker construye una imagen, crea y almacena en caché cada capa, por lo que todas las capas se almacenan en caché e incluso si reconstruimos la imagen sin ningún cambio, simplemente se compilará muy rápido y en realidad no pasará por todo el proceso de compilación. Así que ahora tenemos nuestra nueva imagen, así que si ejecutamos `docker images` podemos ver nuestra nueva imagen. `sudo docker images` ![](https://i.imgur.com/408OMVc.png) Así que vamos a ejecutarlo lanzando el comando `docker run my-simple-webapp`. `sudo docker run my-simple-webapp` ![](https://i.imgur.com/iiXeRRm.png) Como puede ver, se inició y está escuchando en el puerto 8080. Entonces, si vamos dentro de nuestro host y vamos a http://172.17.0.2:8080 y http://172.17.0.2:8080/how%20are%20you, deberíamos poder ver que está escuchando y está funcionando. ![](https://i.imgur.com/G7iQs1V.png)