Saltar al contenido

Blog técnico | Entornos de desarrollo temporal

Recientemente me uní al equipo central de un proyecto de código abierto llamado «Programas de muestra en cada lenguaje de programación». El objetivo del proyecto es proporcionar una creciente colección de programas sencillos en cada lenguaje de programación junto con artículos que expliquen cada implementación.Rápidamente me di cuenta de que los lenguajes que uso comúnmente eran en su mayoría completos, pero estaba feliz de ver oportunidades para los lenguajes que estoy aprendiendo, pero para los que no he tenido la oportunidad de desarrollar un proyecto completo.

Aunque anhelo una razón para configurar un entorno de desarrollo completo para algunos de estos lenguajes, los proyectos de un solo archivo no parecían ser esa razón, y mi configuración minimalista de Arch Linux estaba de acuerdo conmigo. Aún así, necesitaba al menos un compilador para cada uno de estos lenguajes, pero mi miedo a desordenar mi máquina me impedía simplemente instalar un montón de compiladores/sdks.

Blog técnico | Entornos de desarrollo temporal
Blog técnico | Entornos de desarrollo temporal

Requisitos

Quería ser capaz de realizar mi ciclo de desarrollo con los siguientes requisitos:- Construir código sin añadir paquetes a mi máquina – No dejar artefactos de construcción, dependencias, otros elementos almacenados en caché – No añadir ningún archivo o carpeta además del código fuente del proyecto – Persistir los cambios de código después de limpiar el espacio aislado de construcción/depuración/ejecución – Ejecutar y depurar continuamente el código durante el desarrollo

La solución: Docker

Lo primero que se me ocurrió que me proporcionó este tipo de entorno aislado fue Docker. Usando Docker pude crear rápidamente un entorno limpio para construir y ejecutar mi código, y pude derribarlo con la misma facilidad.

Flujo de trabajo de Docker

Dado que uno de mis requisitos para este ejercicio era evitar añadir archivos adicionales o dejar artefactos, opté por evitar los Dockerfiles y cualquier tipo de construcción de dockers locales.Decidí basar mi flujo de trabajo en Docker Run.Para Haskell se me ocurrió lo siguiente:

docker run -it --rm -v $(pwd):/haskell -w /haskell haskell /bin/bash

Vamos a dividir esto en partes:

docker run -it --rm ... haskell ...

Ejecuta interactivamente la última imagen oficial de Haskell desde el Docker Hub. Cuando el contenedor se detenga, sáquelo.

... -v $(pwd):/haskell ...

Enlazar el directorio actual con el directorio /haskell dentro del contenedor.Cualquier cosa en este directorio ahora existe dentro del contenedor.Eso significa que podemos desde el contenedor construir, empaquetar, modificar, etc…cualquier fuente que se encuentre en el directorio actual.Tenga en cuenta que esto es un montaje bind no una copia, por lo que cualquier modificación hecha a los archivos y carpetas dentro del contenedor también se produce fuera del contenedor y viceversa.

... -w /haskell ...

Poner el directorio de trabajo del contenedor en /haskell

... bash -c "cd /haskell && exec /bin/bash"

Esta es la orden que se da al contenedor tan pronto como se pone en marcha. Básicamente, esto te dará un bash prompt en el directorio /haskell.

Así que todo junto, lo que obtienes es un prompt de bash shell dentro de un entorno aislado que no contiene nada más que tu código fuente y las herramientas para construirlo y ejecutarlo.Puedes usar este entorno para interactuar con tu código fuente tal como lo harías en un entorno de desarrollo recién configurado.Después de salir del entorno todo lo que no sea /haskell se limpiará automáticamente.

Función genérica de flujo de trabajo Docker

Después de crear varios alias casi idénticos para el comando de ejecución del muelle de arriba, se me ocurrió una función más genérica:

función containerhere {[[ -z $1]]&&{echo "uso: containerhere IMAGEN [COMANDO]";return1;}command=$2if[[[ -z $command]];thencommand=$0027/bin/bash$0027fi bash -c "docker run --rm -it -v $(pwd):/data -w /data $1$command"}

Esta función hace básicamente lo mismo que el ejemplo anterior, pero requiere que se especifique una imagen Docker para ejecutarse, y también permite anular opcionalmente el comando que se pasa al contenedor en el inicio.

Se podría lograr el mismo resultado que en el ejemplo anterior llamando a containerhere haskellUna diferencia a tener en cuenta es que el directorio actual se monta en /data en lugar de en /haskell.

Docker: Las ventajas

Algunas de las ventajas de este enfoque son las siguientes:- El entorno está completamente aislado. Cualquier caché, instalación de paquetes o artefactos de construcción que no estén escritos en el directorio /haskell enlazado se limpian automáticamente.- El entorno comienza en un estado limpio con cada ejecución de docker- El montaje local de bind entre el directorio actual y el directorio /haskell permite hacer cambios desde dentro o fuera del contenedor. Esto significa que puedes usar tu editor de texto favorito para escribir código en tu máquina anfitriona y construir/ejecutar/prueba dentro del contenedor.

Docker: Las limitaciones

Siempre hay compensaciones para cada enfoque. Aquí hay algunas limitaciones que me vienen a la mente:- La persistencia: Sólo se mantienen los cambios dentro del directorio actual bind mount. Eso significa que puede que tengas que volver a descargar y/o reinstalar dependencias con cada nueva ejecución del docker. Hay soluciones a este problema, pero están fuera del alcance de este post.- Depuración: Este enfoque no proporciona una experiencia completa de depuración de IDE. Debido a que el SDK/compilador existe dentro del contenedor, tu IDE no tiene acceso automáticamente a él. Puede que quieras mirar en las opciones de depuración de la línea de comandos de tu lenguaje como la que se proporciona con python.

Ejemplo del mundo real

Digamos que quiero escribir el Quicksort canónico en Haskell

Empezaría creando un nuevo directorio para mi proyecto y creando un archivo main.hs en ese directorio

$ mkdir ~/código/quicksort$ cd ~/código/quicksort$ touch main.hs

Añadiría mi código (como sigue) a main.hs:

módulo principal del sistema de importación. Random(randomRIO)quicksort::Orda={[a]-[a]-[a]quicksort[]=[]quicksort(p:xs)=(quicksortlesser)++[p]++(quicksortgreater)wherelesser=filter(<p)xsgreater=filter(>=p)xsrandomList: :Int-;IO([Int])randomList0=retorno[]randomListn=dor<-randomRIO(1,1000)rs<-randomList(n-1)return(r:rs)main::IO()main=dorandom25<-randomList25print$quicksortrandom25

No me tomaré el tiempo de explicar este código Haskell excepto para decir que cuando se ejecute generará una lista de 25 números aleatorios entre 1 y 1000 y luego lo clasificará usando la clasificación rápida

En este momento voy a dar vuelta mi entorno Haskell:

$ containerhere haskell

Y recibiré un aviso similar a: [correo electrónico protegido]:/datos#

Si enumero el contenido del directorio actual, veré mi main.hs.

[correo electrónico protegido]:/datos# lsmain.hs

Si quiero probar mi código de forma interactiva, puedo traer el ghci, el REPL interactivo de Haskell, y cargar el main.hs

[correo electrónico protegido]:/datos# ghci
GHCi,versión8.6.3:http://www.haskell.org/ghc/:?forhelpPrelude>:lmain.hs[1de1]CompilaciónMain(main.hs,interpretado)main.hs:3:1:error:No se pudo encontrar el sistema del módulo.

Recibo un error que me dice que System.Random no está instalado en mi entorno. Salgo del ghci con :q e instalo usando cabal, un gestor de paquetes de Haskell:

Pre>Prelude;:qLeavingGHCi.
[correo electrónico protegido]:/datos# actualización de la cábala[correo electrónico protegido]:/datos# instalación de la cábala al azar...

Nota: He omitido algunas salidas de la cábala descargando e instalando el módulo aleatorio"

Ahora puedo intentar cargar el archivo principal en Ghci de nuevo:

[correo electrónico protegido]:/datos# ghci
GHCi,version8.6.3:http://www.haskell.org/ghc/:?forhelpPrelude>:lmain.hs[1of1]CompilaciónMain(main.hs,interpretado)Ok,onemodulado.

Y puedo ejecutarlo llamando a :main

*Main;:main[65,110,129,191,258,272,287,352,394,452,458,473,473,491,501,564,570,590,726,760,795,830,869,882,908]

En este punto puedo hacer cualquier cambio que quiera en mi código fuente, ya sea instalando un editor de texto CLI dentro del contenedor o haciendo modificaciones desde fuera del contenedor.generalmente mantengo abiertas dos ventanas de terminal.una está fuera del contenedor y es una ventana vim donde modifico mi código fuente.la otra es donde corrí el contenedor aquí, y la uso para cargar y ejecutar continuamente mi código.

Si necesito construir mi código también puedo hacerlo llamando al compilador de Haskell, ghc:

[correo electrónico protegido]:/datos# ghc main.hs
[1de1]Compilación Principal(main.hs,main.o)Enlace principal...

Esto me dará una compilación de archivos binarios.

[correo electrónico protegido]:/datos# lsmain main.hi main.hs main.o

Estos se pueden ejecutar desde el interior del contenedor

[correo electrónico protegido]:/data#./main[81,125,136,144,165,216,221,224,234,235,254,271,273,476,572,610,616,623,656,659,808,848,931,933,1000]

o desde fuera del contenedor

$./main[122,178,180,204,230,265,266,331,334,413,428,452,453,564,564,622,631,633,658,659,674,685,912,965,976]

Conclusión

En este post he descrito mi solución a un problema específico que tuve.Sin embargo, creo que este patrón puede ser usado en una plétora de situaciones.Es una forma genial y de bajo compromiso para empezar a aprender un nuevo idioma.Funciona cuando estoy usando un ordenador que no es el mío, por ejemplo al emparejar o hacer mobbing con mi equipo.He encontrado que es útil en cualquier momento que quiero rápidamente un entorno limpio y aislado que puede desaparecer sin un segundo pensamiento.Espero que, independientemente de su flujo de trabajo, este enfoque puede ayudarle siempre que necesite un entorno de desarrollo temporal.

Categorías: technicalTags: docker, getting started