Cómo utilizar Docker para crear scripts de larga duración
Empecemos con el problema que estoy intentando resolver. He desarrollado un script de compilación de larga duración que contiene muchos pasos.
El script se ejecutará durante 1-2 horas.
Descarga archivos relativamente grandes (más de 300 M) de Internet.
Los siguientes pasos de compilación se basan en bibliotecas prediseñadas.
Pero lo más molesto es que lleva mucho tiempo ejecutar este script.
Los sistemas de archivos tienen estado por naturaleza
Normalmente interactuamos con los sistemas de archivos de manera con estado. Podemos agregar, eliminar o mover archivos. Podemos modificar los permisos o el tiempo de acceso del archivo. La mayoría de las operaciones independientes se pueden deshacer; por ejemplo, puede restaurar un archivo a su ubicación original después de moverlo a otro lugar. Pero no utilizamos instantáneas para restaurar archivos a su estado original. En este artículo, le mostraré cómo aprovechar las instantáneas en guiones más largos.
Uso de un sistema de archivos Union para instantáneas
Docker utiliza un sistema de archivos Union llamado AUFS. AUFS implementa el montaje de unión, lo que, como su nombre indica, significa que los archivos y directorios de diferentes sistemas de archivos se pueden montar jerárquicamente en un único sistema de archivos coherente. Esto se logra mediante capas. Si un archivo aparece en ambos sistemas de archivos, aparecerá el archivo en el nivel más alto (también existen otras versiones del archivo en esa capa, sin cambios, simplemente invisibles).
En Docker, cada sistema de archivos que se le transfiere en un montaje federado se denomina capa. Las instantáneas se pueden implementar fácilmente utilizando esta técnica, donde cada instantánea es un montaje federado de todos los niveles.
Generar instantáneas de scripts
El uso de instantáneas puede ayudar a crear un script de larga duración. La idea general es dividir un script grande en muchos scripts más pequeños (me gusta llamarlos scriptlets), luego ejecutar los scriptlets individualmente y tomar una instantánea de su sistema de archivos después de que se ejecute el script (Docker lo hace automáticamente). Si descubre que un script no se ejecuta, puede rebobinar rápidamente a la instantánea anterior e intentarlo nuevamente. Una vez que haya terminado de crear su script y esté seguro de que funciona, puede publicarlo en otros hosts.
¿Qué pasa si ya no utilizas instantáneas? Cuando has estado esperando una ardua hora y media para que tu guión no se construya, me imagino que muchas personas, excepto unos pocos pacientes, no quieren volver a hacerlo. Por supuesto, haría todo lo posible para restaurar el sistema a su estado anterior al fallo; por ejemplo, podría eliminar un directorio o ejecutar una limpieza.
Sin embargo, es posible que no comprendamos realmente los componentes que estamos construyendo. Podría tener un Makefile complejo que coloque archivos en un sistema de archivos que no conocemos, y la única forma real de saberlo con certeza es volver a una instantánea.
Scripts de compilación de instantáneas y Docker
En esta sección, presentaré cómo usar Docker para implementar el script de compilación del compilador cruzado ARM GHC 7.8.3. Hice muchas cosas que parecían inútiles o antiestéticas pero que eran necesarias, todo para asegurar que el tiempo total dedicado a desarrollar el guión se redujera al mínimo. El script de compilación se puede encontrar aquí.
Compilación con Dockerfile
Docker crea una imagen leyendo un Dockerfile, que indica explícitamente lo que se debe hacer mediante una serie de comandos. Consulte este artículo para obtener instrucciones específicas sobre cómo utilizar este archivo. En mis scripts utilizo principalmente WORKDIR, ADD y RUN. El comando ADD es útil porque le permite agregar archivos externos a la imagen actual de Docker y luego convertirlos al sistema de archivos de la imagen antes de ejecutarla. Puede ver el script de compilación, que consta de muchos scripts, aquí.
Diseño
1. Agregue scripts antes de ejecutar
Si agrega todos los scripts en el Dockerfile muy temprano, puede encontrar los siguientes problemas: Si el script La compilación falla, regresa y modifica el script y luego ejecuta Docker Build, ¡pero encontrará que Docker comienza a compilar desde donde agregó originalmente el script! Esto hace perder mucho tiempo y anula el propósito de utilizar instantáneas en primer lugar.
La razón por la que esto sucede es por la forma en que Docker maneja las imágenes intermedias (instantáneas). Cuando Docker crea una imagen a partir de un Dockerfile, compara el comando actual con la imagen intermedia para garantizar la coherencia. Sin embargo, en el caso del comando AGREGAR, también se verifica el contenido de los archivos cargados en la imagen. Si los archivos han cambiado en relación con la imagen intermedia existente, Docker no tiene más remedio que crear una nueva imagen a partir de este momento. Porque Docker no sabe si estos cambios afectarán la compilación.
Además, cuando utilice el comando RUN, debe tener en cuenta que cada vez que ejecute el comando, provocará diferentes cambios en el sistema de archivos. En este caso, Docker buscará y utilizará la imagen intermedia, pero esto es incorrecto. El comando RUN provoca los mismos cambios en el sistema de archivos cada vez que se ejecuta. Por ejemplo, me aseguro de que en mis scripts siempre descargue una versión conocida de un archivo con una suma de comprobación MD5 específica.
Para obtener una explicación más detallada del caché de compilación de Docker, consulte aquí.
2. No utilice el comando ENV para configurar variables de entorno, utilice un script.
Puede parecer tentador utilizar el comando ENV para configurar todas las variables de entorno necesarias para el script de compilación. Sin embargo, no admite la sustitución de variables, por ejemplo, ENV BASE=$HOME/base establecerá el valor de BASE en $HOME/base, que puede no ser lo que desea.
En su lugar, agregué un archivo llamado set-env.sh usando el comando ADD. Este archivo se incluye en el scriptlet siguiente:
THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" amp; amp; pwd )"
fuente $THIS_DIR/set-env-1.sh
¿Qué sucede si no obtienes set-env.sh en primer lugar? El hecho de que se haya agregado al Dockerfile con anticipación no significa que modificarlo invalidará las instantáneas posteriores.
Sí, esto causará problemas. Mientras desarrollaba el script, me di cuenta de que había olvidado agregar una variable de entorno útil en set-env.sh. La solución es crear un nuevo archivo set-env-1.sh que contenga:
THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" amp;amp; pwd )"
fuente $THIS_DIR/set-env.sh
if! [ -e "$CONFIG_SUB_SRC/config.sub" ] ; luego
CONFIG_SUB_SRC=${CONFIG_SUB_SRC:-$NCURSES_SRC}
fi
Seguir todos los archivos de script están incluidos en este archivo. Ahora que he terminado de crear el guión, podría volver atrás y solucionar el problema, pero en cierto sentido eso anularía el objetivo original. Tendré que ejecutar el script de compilación desde cero para ver si este cambio funciona.
Desventajas
Una desventaja importante de este enfoque es que el tamaño de la imagen construida es mayor de lo que realmente se necesita. Esto es especialmente cierto en mi caso porque eliminé muchos archivos al final. Sin embargo, estos archivos todavía existen en el sistema de archivos subyacente del sistema de archivos co-montado, por lo que la imagen completa es más grande de lo necesario, al menos por el tamaño de los archivos eliminados.
Sin embargo, hay una solución. En lugar de publicar la imagen en el registro de Docker Hub, yo:
Exporto el contenido como un archivo tar usando Docker Export.
Crea un nuevo Dockerfile y simplemente agrega el contenido del archivo tar.
Genera la imagen más pequeña posible.
Conclusión
Este enfoque tiene dos ventajas:
El tiempo de desarrollo se minimiza ya que los subcomponentes creados correctamente ya no se ejecutan. Puede concentrarse en aquellos componentes que fallaron.
Mantener scripts de compilación es muy fácil. La compilación puede fallar, pero una vez que domines Dockerfiel, al menos no tendrás que empezar desde cero.
Además, como mencioné anteriormente, Docker no solo facilita la escritura de estos scripts de compilación, sino que, con las herramientas adecuadas, estos scripts de compilación se pueden implementar en cualquier sistema de archivos que proporcione instantáneas.