Docker

Docker es una plataforma que permite construir, ejecutar y compartir aplicaciones mediante contenedores.

Figura 1. Arquitectura de Docker.

Los componentes de Docker son; Docker daemon el cual es el corazón ya que gracias a él podemos comunicarnos con los servicios de docker, REST API expone el docker daemon y el cliente de docker permite hacer uso de los servicios (por defecto es la línea de comandos). También encontramos:

  • Un contenedor es una agrupación de procesos que corren nativamente en la máquina anfitriona, estos no pueden ver más allá del contenedor. No puede consumir más recursos de los que se les permite.
  • Las imágenes son artefactos que usa docker para empaquetar contenedores, similar a un snapshot.
  • Los volúmenes de datos permiten ingresar a los datos de la máquina anfitriona.
  • La red (network) permite comunicarse entre contenedores o con el mundo exterior.

Comandos importantes de Docker

  • docker run name: ejecuta un contenedor con el nombre del contenedor.
  • docker ps: muestra los contenedores activos.
  • docker ps -a: muestra todos los contenedores:
  • docker inspect <container ID>: muestra los detalles completos de un contenedor.
  • docker inspect <name>: similar al anterior pero con el nombre.
  • docker run –name nombre: se le asigna un nombre personalizado.
  • docker rename old new: cambia el nombre.
  • docker rm <ID o nombre>: elimina el contenedor.
  • docker container prune: elimina todos los contenedores que estén parados.
  • docker exec -it name-container

Datos en Docker

  • Bind mount: permite enlazar datos dentro del contenedor hacía el sistema de archivos local.
  • Volúmenes: es una manera más estándar de manejar los datos. Permite solo a docker – solo con permisos – hacer uso de estos volúmenes, por lo cual es más seguro que bind mount. En otras palabras, no se puede ver algún directorio con archivos enlazados… mas bien, se debe acceder desde docker.
    • Insertar archivos de un contenedor: docker cp archivo.extension nombreContenedor:/directorio/archivo.extension.
    • Extraer archivos de un contenedor: docker cp nombreContenedor:/nombreArchivo ruta

Imágenes

Una imagen contiene distintas capas de datos: distribución, diferente software, bibliotecas, etcetera. En otras palabras, una imagen se compone de diferentes capas de personalización partiendo una capa inicial (base image).

Comandos importantes

  • spa<em>docker image ls</em>: ver las imágenes que existen localmente.
  • spa<em>docker pull imageName:Version:</em> descargar una imagen de docker.
  • spadocker history nombreImagen:nombreTag: ver el historial de capas.
  • spa<em>dive nombreImagen:tag</em>: analizar las capas usando Dive.

Construir una imagen propia

Partiendo de un archivo podemos generar alguna imagen propia, por ejemplo:

Se crea un archivo llamado Dockerfile con el comando touch Dockerfile, se agrega el contenido de este archivo:

FROM ubuntu:latest RUN touch /ust/src/hola-mundo.txt # este comando se ejecutará en tiempo de build

Se crea la imagen pasando el contexto del build: docker build -t ubuntu:holaMundo

Se inicia una conexión por terminal al contenedor de ubuntu docker run -it ubuntu:holaMundo

Para publicar la imagen se cambia el tag ubuntu (ya que este existe como uno oficial) con docker tag ubuntu:holaMundo miNombreUsuario/ubuntu:holaMundo

Para publicar esta imagen – es parecido a git – se usa docker push miusuario/ubuntu:holaMundo

Caché de capas para estructurar correctamente las imágenes

Es recomendable seguir una estructura para el archivo Dockerfile; por ejemplo:

  1. Se usa una imagen base.
  2. Dependencias del código.
  3. Código de la aplicación.

De esta manera cuando se construya la imagen personalizada se rehusaran las capas de la imagen base y las dependencias; y solo se modificara el código de la aplicación. Además es recomendable que el código de la aplicación tenga un bind mount a nuestro sistema de archivos local para que no se tenga que construir la imagen continuamente, en otras palabras, solo se edita el código local y se actualiza en el contenedor.

Docker networking

Sirve para colaborar entre contenedores, por ejemplo: que un contenedor de una aplicación se conecte a un contenedor de base de datos.

Comandos

  • spadocker network ls: listar las redes.
  • spadocker network create –attachable : crear una red y se pueda usar con un contenedor.
  • spadocker network inspect : inspeccionar una red.
  • spadocker network connect : conectar un conector a la red.
  • spadocker run –env MY_VAR=” : correr un contenedor usando variables de entorno.

Docker compose

Esta funcionalidad de Docker permite gestionar la estructura de un conjunto de contenedores sin la necesidad de hacerlo todo por terminal de comandos mediante un archivo compose, por ejemplo:

# Versión del compose fileversion: "3.8"# Servicios que componen nuestra aplicación.## Un servicio puede estar compuesto por uno o más contenedores.services:# nombre del servicio.  app:  # Imagen a utilizar.    image: app	# Declaración de variables de entorno.    environment:      MONGO_URL: "mongodb://db:27017/test"	# Indica que este servicio depende de otro, en este caso DB.	# El servicio app solo iniciara si el servicio debe inicia correctamente.    depends_on:      - db	# Puerto del contenedor expuesto.    ports:      - "3000:3000"  db:    image: mongo

Docker compose vs Dockerfile

Dockerfile es lo que se usa para crear una imagen de contenedor, y Docker Compose es lo que se usa para implementar una instancia de esa imagen como contenedor.

Un ejemplo rápido de esta diferencia: https://docs.docker.com/compose/gettingstarted/

Docker compose en equipo

Si se trabaja en equipo con docker es conveniente usar un archivo compose override el cual sobre escribirá la información del compose original; y mantendrá los datos de configuración del compose original en caso de que en el compose override no encuentre los datos de configuración. En otras palabras, le da prioridad al archivo de compose override.

Comandos importantes

  • spadocker-compose build: construir los servicios.
  • spadocker-compose up: levantar los servicios.
  • spadocker-compose up -d: levantar los servicios en dettach.
  • spadocker-compose down: bajar los servicios.
  • spadocker-compose logs: permite ver los logs.
  • spadocker-compose logs app: permite ver los logs solo de un contenedor del compose.
  • spadocker-compose logs -f app: se hace seguimiento de los logs.
  • spadocker-compose exec app bash: ingresar al shell del contenedor app.
  • spadocker-compose ps: ver los contenedores generados por docker compose.
  • spatouch docker-compose.override.yml: crear archivo para sobre escribir el original.
  • spadocker-compose up -d –scale app=2: escalar dos contenedores de app.

Docker avanzado

Administrando ambiente de Docker

Una vez que se ha usado docker constantemente es recomendable eliminar los archivos, imágenes, cache, contenedores, etc. que no se usaran. Para esto se tienen los siguientes comandos.

  • spadocker container prune: elimina todos los contenedores que no se están ejecutando o están apagados.
  • spadocker rm -f $(docker ps -aq): elimina todos los contenedores incluyendo los que se están ejecutando (-q: muestra por ID, -a: muestra los ocultos, -f: fuerza bruta).
  • spadocker network prune: elimina las redes que no se están usando.
  • spadocker volume ls: lista los volumenes.
  • spadocker volume prune: elimina los volúmenes que no se están usando.
  • spadocker system prune: elimina todos los contenedores, imágenes, redes y cache.
  • spadocker run -d –name app –memory 1g nombreApp: se limita el uso de memoria.
  • spadocker stats: se muestran los recursos que consume docker en el sistema.

SHELL vs EXEC: detener contenedores correctamente

Cuando se tiene un contenedor recién creado o activo este debería estar ejecutando algún proceso para mantenerse en funcionamiento. Si el proceso principal se detiene entonces el contenedor debería dejar de funcionar.

Cuando se usa el comando docker stop <name-container> docker manda una señal estándar de linux llamada SIGTERM al proceso donde después de un tiempo el proceso se debería detener; pero si el proceso no se detiene enviara una señal llamada SIGKILL que garantiza que el proceso se terminara forzadamente.

Existen dos formas de definir CMD:

  1. CMD /process.sh: se genera el shell como un shell hijo del principal.
  2. CMD [“/process.sh”]: manera recomendada.

La linea CMD ejecuta un archivo bash a través de un shell , esta forma de expresar esta línea (primer forma) se conoce como shell form y esto genera que al momento de detener un contenedor que esté ejecutando un proceso por shell form necesariamente tendrá que usar SIGKILL; para evitar esto, el CMD debe ejecutar el proceso main de form exec form. Es decir, debería estar configurado cómo la segunda manera para que el contenedor tenga tiempo a que el contenedor procese las solicitudes restantes y pueda hacer un great full shutdown.

Comandos importantes

  • spadocker stop <name><name>: envía la señal SIGTERM al contenedor.</name>
  • spadocker kill <name>: envía la señal SIGKILL al contenedor. </name>

ENTRYPOINT vs CMD: contenedores ejecutables.

ENTRYPOINT permite ejecutar la configuración de un contenedor como ejecutable; que tiene dos formas:

  1. exec form: ENTRYPOINT [“executable”, “param1”, “param2”]
  2. shell form: ENTRYPOINT command param1 param2

CMD son instrucciones dentro del Dockerfile que si se llega a colocar más de uno tomará el último y ese tomará efecto en la ejecución. Hay tres formas de usarlo:

  1. exec form: CMD [“executable”,”param1″,”param2″]
  2. cómo parametro del ENTRYPOINT: CMD [“param1″,”param2”]
  3. shell form: CMD command param1 param2

Contexto de build

Existe un archivo .dockerignore que funciona de manera similar que .gitignore. En otras palabras, omite archivos locales hacia un contenedor.

Multi-stage build

Para que no este todo el código en la imagen entonces Docker permite crear diferentes imágenes. Ya que se pueden crear diferentes archivos dockerfile o crear diferentes builds dependientes en un dockerfile. Por ejemplo, supongamos que :

  1. El archivo Dockerfile de producción contiene 2 “fases de build” que se pueden pensar cómo hacer 2 build seguidos, en donde al final la imagen construida contendrá lo especificado en el ultimo de los build.
  2. El primer build corre un test que verifica que todo funcione bien. El segundo build construye la imagen final aprovechando el caché de las capas del primer build.
  3. Al final el segundo build es solo una extracción de lo que nos intereza del primer build.
  4. Lo importante en este caso especifico es que si el test falla, entonces el build 2 no se corre, lo que significa que la imagen no se construye.

Docker-in-Docker

Usar Docker dentro de un contenedor es posible con la ayuda de sockets; con bind mount se accede al archivo docker socket a la máquina anfitriona; y accediendo a el desde el otro Docker el cliente puede accederlo.

Comentarios