Appearance
Docker
La revolución de los contenedores y las aplicaciones en la nube
© 2020 by César Arcila
Cuando se habla de contenedores se puede ver como sinónimo de empaquetamiento (sin comprimir, pero con un entorno de ejecución interno y aislado, generalmente basado en Linux). Se puede asociar perfectamente por analogía con los contenedores de las embarcaciones, que actualmente llegan a usarse como bodegas, oficinas pequeñas y hasta algunas viviendas.
Si se ha escuchado sobre máquinas virtuales, Docker puede verse como una virtualización ligera dado que posibilita independencia del entorno y asociación de los recursos. Sin embargo, esto lo hace de modo que no ocupa los mismos recursos que una máquina virtual, mas bien dispone de los recursos de una manera más eficiente, acercándose a la velocidad del sistema anfitrión (Windows, macOS, Linux). Además permite mayor número de instancias que cuando se usan máquinas virtuales en un servidor, incluso se usan contendores Docker dentro de máquinas virtuales.
Podemos asociar mejor a Docker con el concepto de contenedores y su gestión, siendo posible proporcionar un entorno de desarrollo independiente colocándolo dentro de un contenedor con la posibilidad de portarlo a otro equipo de computo o guardar todo tu ambiente en un disco externo, además de pasar de un modo más fluido a producción, es decir, una gestión de configuración más confiable al lograr un entorno semejante, mitigando inconsistencias.
Sin embargo, esto requiere algunos conceptos fundamentales para utilizar y operar sobre contenedores de este tipo, es decir, se requieren hacer "scripts" y mantenerlos, siendo razonablemente comprensibles. Dependiendo del perfil del recurso humano o de la aplicación, la operación con contenedores puede ir de menos a más, y en definitiva es la práctica la que aporta el aprendizaje cuando te enfrentas a este tipo de herramientas en mayor medida. Hoy día, quién se dedica profesionalmente al desarrollo de software está llamado, como mínimo, a tener un concepto sobre estas tecnologías, en caso de involucrarse más se requieren conocimientos en Linux y conviene haber usado alguna máquina virtual previamente (ej. VirtualBox).
Este documento tiene un alcance sencillo y ágil a modo de abre bocas. Superado este alcance, para conocer más de este tema deberá investigarse.
Referencia sobre Linux
Conceptos esenciales de Docker
Docker. Herramienta que gestiona imágenes de repositorio de un sistema (o aplicación) y contenedores que operan como un entorno virtual aislado y ligero. Se instala frecuentemente en sistemas operativos Linux, con infraestructura orientada a la nube (incluso máquinas virtuales).
Docker Desktop. Versión de Docker en sistemas operativos de escritorio como Windows 10 y macOS. Principalmente es usado para entornos de desarrollo o pruebas, punto de partida para lograr un entorno de producción semejante, o integración continua, en dónde se mitigan inconsistencias con esta tecnología.
Docker Hub. Servicio en la nube que dispone de repositorios de imágenes (de sistemas operativos o aplicaciones) expuestas por compañías de tecnologías o desarrolladores.
Dockerfile. Archivo de "script" para producir nuevas imágenes personalizadas basadas en un repositorio (Docker Hub u otro). Se usa un archivo con este nombre por cada proyecto (de código) definido.
Imágen. Corresponde a un repositorio de la imágen de un sistema operativo o aplicación preparada para su reproducción, o bien, construida de modo personalizado usando un archivo Dockerfile. Una analogía que puede servir, si alcanzó a conocer el DVD (o los arhivos ISO), es que la imágen es un medio de instalación original que se usa para hacer copias, solo que en este caso consiste en un archivo descriptivo que define su contenido (Dockerfile).
Contenedor. Empaquetamiento de un entorno virtual aislado y ligero que se reproduce a partir de una imágen. Primero se debe obtener o construir una imágen, y a partir de ésta se ejecuta el contenedor. Puede verse como la instancia en ejecución del entorno que se obtiene basado en una imagen, aunque el contenedor puede haberse establecido sin estar ejecutándose. Si la analogía del DVD es útil, sería entonces la copia obtenida (un DVD) que se puede reproducir para ver su contenido (video, music, aplicación informática), por tanto Docker sería en ese caso el reproductor.
Docker Compose. Propuesta avanzada para simplificar el uso de multiples contenedores que se enlazan o comunican, con el fin de gestionar contenedores por cada servicio o cada aplicación y con una configuración declarativa más sencilla (usando archivo de tipo
YAML), en lugar de elaborar "scripts" complejos conDockerfile. Con esta herramienta es posible obtener una composición de contendores para servicios como Servidor Web, microservicios de aplicaciones (API), Email (ej. poste.io), Proxy (ej. Traefik), Chat (ej. Rocket Chat), Video chat (ej. jitsi.org). Esto nos llevaría a distinguir otros conceptos como servicios, volúmenes compartidos, mapeo de red, que pueden ser investigados en la documentación oficial de Docker Compose en tanto se requiera.
Docker Desktop para Windows 10 Pro (y mayor)
Para usar Docker en Windows debemos contar al menos con la versión de Windows 10 Pro a 64bits. En versiones anteriores se puede investigar sobre el uso de la herramienta Docker Toolbox, pero en este documento se usará Docker Desktop que es la herramienta actual, así que se debe descargar desde su sitio, dónde también debes crear una cuenta en el servicio en la nube para contenedores denominado Docker Hub.
Dado que Docker Desktop utiliza Hyper-V de Windows para las características de virtualización, se requiere habilitar privilegio con el siguiente comando desde PowerShell como administrador:
shell
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -AllPuede comprobarse que la instalación ha dejado a Docker configurado usando el comando:
shell
docker --versionSi no se ha iniciado aún el servicio puede esperarse o ejecutar el icono respectivo
Usaremos el ejemplo disponible en la documentación oficial de Docker para ilustrar la ejecución de un contenedor. Para ello se ejecuta:
shell
docker run hello-worldSuponiendo que deseamos instalar Linux Ubuntu, usamos el comando docker pull para descargar una imágen de repositorio y luego el comando docker run para lanzarla como contenedor. Por ejemplo:
shell
docker pull ubuntu
docker run -it ubuntuEl parámetro
-itse usa para entrar a modo interactivo de comandos. En otras palabras, la última línea es la manera como inicias un servidor en Linux Ubuntu. Para salir de esa sentencia se usan las teclasCTRL + D(o el comandoexit).
Si se ha entendido bien, con esta última y simple línea tienes operando otro sistema operativo sin instalación ni configuración de una máquina virtual convencional y en pocos minutos, así que es razonablemente sencillo hasta que se requieren cosas bien específicas.
En el caso de requerir Node.js se podría ejecutar:
shell
docker run -it node:lts-buster-slimDados los dos ejemplos anteriores, nótese que
docker pullpuede ser omitido al usar el comandodocker run, debido a que automaticamente hace la descarga cuando no se encuentra la imágen en la máquina local.
En este último ejemplo, la imágen de repositorio con nombrenode:lts-buster-slimutiliza el sistema operativo Debian (distribución sobre la que se basa Ubuntu). También podría usarsedocker pull debian:buster-slimy agregar las herramientas de desarrollo deseadas con un archivoDockerfile(como se verá avanzando el documento).
Docker en macOS
De manera semejante se puede instalar Docker Desktop para macOS. Sin embargo, conviene conocer como instalar el cliente de Docker en el sistema macOS. Actualmente, puede ustilizarse la herramienta brew con Colima. Para ello ejecutamos los siguientes comandos:
bash
brew install docker
brew install docker-compose
brew install colima
colima start --cpu 2 --memory 2 --disk 20
limactl list
colima startes el comando que inicia el servicio reservando una máquina virtual, en cuyo caso se puede especificar procesadores memoria y disco (que por defecto es de 60GB), incluso dns (--dns 1.1.1.1). Para volverlo a iniciar podemos usar el comando corto (sin especificaciones).
Podemos verficar si existe un proceso de contenedores docker ejecutándose mediante el comando: docker ps -a
Docker en Linux
Semejante a macOS, podríamos usar Colima. Además, en algunas distribuciones de servidor puede venir como servicio elegible desde la instalación, tal es el caso de Linux Ubuntu Server. Sin embargo, si quieres una instalación directa, podrías usar cada una de las siguientes sentencias:
bash
sudo apt install -y apt-transport-https ca-certificates software-properties-common
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER
newgrp dockerCon estas sentencias se dispone de paquetes necesarios, se obtiene llave GPG del repositorio de Docker, se configura el repositorio, y se instalan los paquetes respectivos. Al final, se habilita el usuario para usar privilegios de Docker.
Comandos esenciales de Docker
Podemos citar los siguientes comandos esenciales:
| Comando | Descripción |
|---|---|
docker --version | Muestra la versión de Docker (también puede usarse -v) |
docker run | Arranca un contenedor basado en una imágen. -it indica modo interactivo, sino se usa -t para salida tty (sin interactuar), o -d para lanzarlo en "background" (segundo plano). |
docker ps | Lista procesos asociados a contenedores en ejecución. Suele usarse con el parámetro -a |
docker image ls | Para listar las imagenes disponibles localmente |
docker pull | Descarga una imagen de contenedor disponible en docker-hub |
docker push | Publica una imagen de contenedor en docker-hub |
docker tag | Etiqueta una imagen de contenedor para publicar en docker-hub |
docker container ls | Lista los contenedores en ejecución. --all lista todos |
docker container stop | Detiene la ejecución del contenedor indicado. Puede abreviarse como docker stop |
docker container start | Inicia la ejecución del contenedor indicado. Puede abreviarse como docker start |
docker volume ls | Para listar volumenes (discos de docker) |
docker network ls | Para listar recursos de red (permitiendo comunicar otros contenedores) |
docker build | Crea una imágen a partir de un archivo de script con nombre Dockerfile. Ej. docker build -t image . |
docker logs | Muestra "logs" de un contenedor |
docker exec | Puede usarse para acceder al contenedor cuando se está ejecutando en "background" (-d). De esta manera se puede interacturar con comandos del sistema interno. Por ejemplo, podemos verificar la version de linux así: docker exec mycontainer uname -a |
Ejemplo con repositorio MariaDB
Los siguientees comandos representan los pasos para establecer un contenedor con MariaDB
bash
docker volume create mariadb
docker pull mariadb:10.9
docker run --name mariadb -e MYSQL_ROOT_PASSWORD=password -p 3306:3306 -v mariadb:/var/lib/mysql -d mariadb:10.9
docker exec -it mariadb bash
mysql -ppassword
docker volume create...establece un volumen externo para Docker. Se puede limpiar o remover los volúmenes sin uso ejecuandodocker volume prune.
En una siguiente ocasión, podemos iniciar el contenedor e ingresar de nuevo directamente así:
bash
docker start mariadb
docker exec -it mariadb mysql -pUna vez se ingresa al contenedor se puede crear la base de datos con: create database ...;
Ejemplo con repositorio PostgreSQL
Los siguientees comandos representan los pasos para establecer un contenedor con PostgreSQL
bash
docker volume create postgres
docker pull postgres:14.5
docker run --name postgres -e POSTGRES_PASSWORD=password -e PGDATA=/var/lib/postgresql/data/pgdata -p 5432:5432 -v postgres:/var/lib/postgresql/data -d postgres:14.5
docker exec -it postgres bash
psql -U postgresEn el comando
docker run.., podría usarse-e POSTGRES_USER=myuserpara indicar un usuario. En este caso asumiría el usuariopostgres.
En una siguiente ocasión, podemos iniciar el contenedor e ingresar de nuevo directamente así:
bash
docker start postgres
docker exec -it postgres psql -U postgresUna vez se ingresa al contenedor se puede crear la base de datos con: create database ...; (iniciando psql -U postgres)
Ejemplo con repositorio Redis
Los siguientees comandos representan los pasos para establecer un contenedor con Redis
bash
docker volume create redis
docker pull redis:latest
docker run --name redis -p 6379:6379 -v redis:/data -d redis:latest
docker exec -it redis redis-cliEjemplo con repositorio MongoDB
Los siguientees comandos representan los pasos para establecer un contenedor con MongoDB
bash
docker volume create mongodb
docker pull mongo:latest
docker run --name mongodb -p 27017:27017 -v mongodb:/data/db -d mongo:latest
docker exec -it mongodb bash
mongodUna vez se ingresa al contenedor se puede crear la base de datos con: use ....
Un mejor ejemplo puede encontrarse avanzando el documento con Docker Compose
Ejemplo con BitWarden (para guardar contraseñas)
Si quieres guardar contraseñas podemos citar BitWarden. Dado que ya conocemos un poco sobre Docker, esta herramienta se puede usar con contenedores y simplifica su configuración con un "script" de instalación ejecutando lo siguiente:
bash
mkdir btwrdn
cd btwrdn
curl -Lso bitwarden.sh https://go.btwrdn.co/bw/sh \
&& chmod +x bitwarden.sh
./bitwarden.sh installEl instalador crea un boveda y solicita nuestro email. Se requiere un cliente para gestionar la boveda.
Dockerfile - El script de Docker para crear Imágenes
📂 Dockerfile
~>build (image)~>run (container)
La dinámica sugiere que a partir del archivo Dockerfile se construye (build) una imágen que a su vez posibilita la ejecución (run) de un contendor, es decir, que los contenedores son generados a partir de una imágen, la cual puede definirse con un archivo Dockerfile. Por tanto, se puede empezar con una imágen ya creada o con tu propio archivo Dockerfile.
Con el servicio de Docker Hub se pueden descargar imagenes de repositorio usando el comando docker pull. Sin embargo, cuando se desea modificar o implementar tu propia imágen basada en un repositorio se hace uso de un archivo de script con nombre Dockerfile (tal como se ha escrito). Con este archivo se busca automatizar el proceso de creación de imágenes para contenedores y normalmente las sentencias son cercanas o equivalentes a comandos Linux. Veamos un ejemplo:
Dockerfile
FROM nginx:alpine
COPY index.html /usr/share/nginx/html
COPY assets/ /usr/share/nginx/htmlLa sentencia
FROMindica la imágen de repositorio base (nginx:alpine), mientras que la sentenciaCOPYenvía los archivos a un destino en la ruta esperada del contendor. En este caso, se están copiando los archivosindex.htmlyassets(que es un directorio).
Ubicándose en la carpeta que contiene el archivo Dockerfile, se construye (build) la imágen y se ejecuta (run) el contendor respectivo con los siguientes comandos:
shell
docker build -t user/web .
docker run -p 9000:80 --name web user/webPara cancelar su ejecución se usan las teclas
CTRL + C. Se ha logrado obtener una nueva imágen de repositorio denominadauser/web(dóndeuseres el usuario registrado en Docker Hub), basada en la imágen denginx:alpine, y se traduce (con-p) el puerto 80 como 9000 en nuestro anfitrión (localhost). De este modo, se puede abrir un navegador y consultar la direcciónlocalhost:9000.
Aunque es recomendable nombrar la nueva imagen producida (con --name), es posible simplificar los dos comandos anteriores de la siguiente manera:
shell
docker build -t web .
docker run -p 9000:80 webDe este modo se ve más sencillo.
Veamos el siguiente ejemplo donde usaremos un archivo index.js y Dockerfile para Node.js:
javascript
var http = require('http');
http.createServer((req, res) => {
res.write('Hi there!');
res.end();
}).listen(8080);Dockerfile
FROM node:lts-buster-slim
WORKDIR /usr/src/app
COPY index.js /usr/src/app
EXPOSE 8080
CMD [ "node", "index.js" ]Y ejecutamos las siguientes sentencias:
shell
docker build -t nodeapp .
docker run -d -p 80:8080 nodeappRecordar que cuando se usa
docker run -dse lanza el proceso en "background". Se puede abrir un navegador y consultar la direcciónlocalhost. Los comandos deDockerfilese explicarán a continuación.
Sentencias esenciales en Dockerfile
Podemos citar las siguientes sentencias esenciales para el archivo Dockerfile:
| Comando | Descripción |
|---|---|
FROM | Define la imágen sobre la que se basará nuestro contendor |
ENV | Permite definir variables de entorno |
COPY | Es el comando apropiado para copiar archivos locales al contendor (como alternativa remota existe ADD) |
ADD | Permite copiar archivos locales o tomarlos remotamente desde un enlace, incluso descomprime empaquetados |
RUN | Ejecuta sentencias del sistema interno cuando se crea la imágen para aprovisionar el contenedor (fecuentemente con Linux) |
CMD | Da paso a comandos que se ejecutan cuando el contenedor se ha inicializado (distinto de RUN que se ejecuta cuando se construye el contenedor). |
WORKDIR | Define directorio de trabajo en el contenedor |
EXPOSE | Expone un puerto para ser mapeado por el anfitrión o máquina |
ENTRYPOINT | Para lanzar un ejecutable cuando arranca el contenedor (generalmente servicios) |
ARG | Define argumentos o variables que se reciben al construir la imagen. Se utilizan con el formato ${ARG} |
MAINTAINER | Autor de la imágen |
Inspeccionando un Contenedor Docker
Suponiendo que deseas probar linux en Docker o una aplicción para Docker que ha fallado. Tomemos el último ejemplo de Dockerfile y reemplazamos (o comentamos con #) la última línea que corresponde a CMD para aplicar la siguiente:
Dockerfile
CMD ["sleep", "8080"]De este modo, al correr el contenedor puedes acceder o interactuar con el comando docker exec -it. Por ejemplo:
shell
docker exec -it jdkapp /bin/bash
/bin/bashespecifica el interprete de comandos, De modo genérico suele usarse/bin/shque es más simple y aplica para la mayoría de imágenes (con Linux), pero si se trata de una imágen conDebianpuede ir el del ejemplo.jdkappcorresponde al nombre del contenedor. Si no se ha nombrado debe reportarse el identificdor del proceso (docker ps -a)
Entorno de Desarollo con Dockerfile
Se ilustra a modo de ejemplo un entorno de desarollo más elaborado con Node.js, Java y MariaDB. En muchos casos es preferible Docker Compose que se verá posteriormente.
Dockerfile
FROM node:lts-buster-slim
WORKDIR /usr/src/app
COPY index.js /usr/src/app
RUN apt update && apt install -y git-core curl zip unzip
RUN apt install -y mariadb-server
RUN systemctl enable mariadb
EXPOSE 8080
CMD [ "node", "index.js" ]Se guarda y se ejecutan las siguientes sentencias:
shell
docker build -t nodeapp .
docker run -d -p 3000:8080 --name webapp nodeappPodría partirse también desde una version con JDK 11. Para comenzar a modo de ejemplo puedes probar lo siguiente:
Dockerfile
FROM adoptopenjdk/openjdk11:debianslim-slim
WORKDIR /usr/src/app
COPY build/libs/app.jar /usr/src/app
EXPOSE 8080
CMD [ "java", "-jar", "app.jar", "mymain" ]De modo semejante, ejecutamos:
shell
docker build -t javaapp .
docker run -d --name jdkapp javaapp
docker exec jdkapp java -versionUna imágen para un programa sencillo bajo Java también podría configurarse del modo siguiente:
Dockerfile
FROM alpine
WORKDIR /root/app
COPY app.java /root/app
RUN apk add openjdk11
ENV JAVA_HOME /usr/bin/jvm/java-11-openjdk
ENV PATH $PATH:JAVA_HOME/bin
RUN javac app.java
ENTRYPOINT java appAhora veamos un ejemplo de contendor con PHP
Dockerfile
FROM php:7.4-cli
COPY . /usr/src/app
WORKDIR /usr/src/app
CMD [ "PHP", "./index.php" ]Entorno de Desarrollo Docker + VSCode
Se ilustra a modo de ejemplo un entorno de desarollo esencial. En muchos casos es preferible algo más avanzado (Docker Compose).
Ahora usaremos para nuestro entorno de desarrollo el editor Visual Studio Code (VSCode) y Docker. Para ello ingresamos al editor (VSCode) e instalamos la extensión Remote - Containers.
Una vez instalada la extensión, usando una carpeta del disco duro, se plantea la siguiente estructura para el proyecto:
- 📂 docker
└── .devcontainer
⋅⋅⋅⋅⋅⋅⋅⋅├── devcontainer.json
⋅⋅⋅⋅⋅⋅⋅⋅└── Dockerfile
Como se puede observar, se requiere un archivo con nombre devcontainer.json dentro de la carpeta .devcontainer. Dentro de este archivo incluimos, por ejemplo, el siguiente contenido:
json
{
"name": "Docker Server",
"dockerFile": "Dockerfile",
"appPort": 8080,
}Puedes revisar la documentación sobre como funciona este archivo en el sitio de VSCode
Para nuestro archivo Dockerfile se tendría, por ejemplo, el siguiente contenido:
Dockerfile
FROM node:lts-buster-slim
EXPOSE 8080Con esto tendremos una imágen basada en node:lts-buster-slim para contar con un contenedor de sistema Linux (Debian).
Se debe reabrir VSCode para que identifique el archivo devcontainer.json dentro de la carpeta .devcontainer usado por la extensión instalada, la cual ejecutará nuestro contendor si confirmamos esta acción al reabrir el editor. De este modo, encontrarás que se abre una terminal para el espacio de trabajo y se puede usar el sistema de archivos de Linux y sus comandos (por ejemplo: mkdir src, cd src, touch app.js), es decir, puedes operar directamente en el sistema y el editor refleja la estructura de archivos del espacio de trabajo, gestionando el desarrollo con este editor.
© 2019 by César Arcila