Objetivo 5: Creación de un contenedor para pruebas

Descripción

Usar imágenes de contenedores es una de las formas estándar actuales para crear despliegues repetibles de cualquier tipo de aplicación o en cualquier tipo de caso de uso, desde test a microservicios.

En esta práctica se trata de diseñar, usando una herramienta de compilación y gestión de imágenes de contenedores, una imagen con la que se puedan ejecutar fácilmente los tests unitarios sobre la aplicación que se está diseñando.

Prerrequisitos

Haber realizado el objetivo anterior, o al menos que pase los tests. Evidentemente, si no hay tests o no son correctos tampoco podrán ejecutarse en el contenedor, así que es bastante aconsejable que efectivamente se haya superado ese objetivo.

Explicación

El principal objetivo del uso de contenedores gestionados por Docker u otro gestor es aislar la ejecución de una aplicación de forma que sea mucho más fácil desplegarla, incluyendo los datos y el estado en el que se encuentre en un momento determinado; también permite crear fácilmente infraestructuras que se pueden reproducir en cualquier servicio o proveedor en la nube (combinando uno o más contenedores). Además de usarse para entorno de prueba, se puede usar también como entorno de producción, en caso necesario, por ejemplo, taperizando la aplicación de forma que se pueda desplegar con seguridad en cualquier entorno IaaS (infraestructura como servicio) donde esté instalado un gestor de contenedores o permita directamente el despliegue de contenedores (en lo que habitualmente se denominan PaaS, plataforma como servicio). De hecho, los PaaS usan Docker (o algún tipo de infraestructura similar) para crear contenedores con los que se ejecutan las aplicaciones. En este objetivo, sin embargo, nos vamos a centrar en la creación de entornos de prueba, ya que la mayoría de los servicios de integración continua permiten usarlos; el tener en este paso ya listo un entorno de prueba desplegable permite, en los siguientes pasos, usarlos directamente para configurarlos.

El objetivo secundario es el que el alumno tenga instaladas las herramientas necesarias para trabajar con contenedores; también en qué casos conviene usarlas por motivos de seguridad o de conveniencia. Estas herramientas se añadirán a la panoplia de un administrador que al terminar la asignatura tendría que tener el alumno.

Todavía cabe un objetivo terciario, y es que se entienda como se empaquetan las aplicaciones para su distribución usando esta plataforma.

Para los fines del proyecto que se está realizando y objetivos de la asignatura, lo importante es que la creación de ese entorno de pruebas sea reproducible, por eso se requiere del estudiante el diseño de un Dockerfile, que además tendrá que subir a un repositorio público. No bastará mostrar que el entorno funciona, sino que habrá que crear una serie de scripts tales que, en una instalación determinada sin el contenedor o jaula, se pueda crear fácilmente ese entorno y reproducir la aplicación que se va a probar. Generalmente, si se usa un solo contenedor es suficiente con un Dockerfile. Si se usan varios, habrá que orquestarlos usando la aplicación correspondiente.

En este sentido, si se usa cualquier otro servicio de contenedores como Azure, GitHub container registry, el de RedHat, Quay o AWS se tendrá que hacer de forma automática o automatizarse con algún script de shell que se incluirá también en el repositorio.

El énfasis de esta práctica es en la creación y uso de Docker en un entorno de pruebas, por lo que también se valorará cómo se han diseñado esas pruebas y lo realista que es ese entorno. Por supuesto, también se busca que el alumno empiece a usar sistemas de despliegue continuo reales en su aplicación, usando git, claves, integración continua y el resto de los sistemas que se usan en el ciclo de vida de un aplicación moderna.

No se exigirá que se desarrolle ningún código adicional para desplegar este fichero como contenedor, pero se pedirá que se usen las herramientas de construcción para llevar a cabo los tests y por supuesto cualquier avance (siguiendo, por ejemplo, los consejos en las anteriores evaluaciones) se valorará también. Y por supuesto la implementación de historias de usuario adicionales se valora en la rúbrica correspondiente.

La creación de un contenedor para pruebas (y, en general cualquiera), contiene varias fases

  1. Elección de un contenedor base. Siempre habrá diferentes opciones, tanto si optamos por el “oficial” de un lenguaje, como si optamos por un sistema operativo sobre el que vamos a instalar el lenguaje.
  2. Instalación de paquetes adicionales que podamos necesitar para pruebas. Por ejemplo ¿necesitaré git? ¿Un compilador de C? ¿Una herramienta para descargar de la web? ¿Otro lenguaje que no sea estrictamente el que voy a testear?
  3. Instalación del task runner y de las bibliotecas que necesite el mismo.
  4. Instalación de los módulos/bibliotecas de test y cualquier otro que se necesite para los tests.
  5. Cualquier otra cosa que se necesite para ejecutar los tests.

Como tal contenedor de pruebas, este contenedor será, efectivamente, un entorno de desarrollo. Muchos lenguajes tienen este concepto, en el que se trata esencialmente de establecer una serie de variables de entorno para que la aplicación y las herramientas del lenguaje sepan donde encontrar las bibliotecas instaladas. Este entorno de desarrollo, en la mayor parte de los casos, es opcional, pero lo que no es opcional es que las herramientas de test (la herramienta que ejecute todos los tests, el test runner de la línea de órdenes, invocado desde tu gestor de tareas) sean capaces de encontrarlos. Se puede optar o no, por tanto, por usarlo, pero lo que hay que tener en cuenta es que las bibliotecas estarán dentro del contenedor, y los fuentes fuera del mismo, en un directorio donde no hay derechos de escritura.

Aviso: este es un caso en el que las IAs generativas pueden generar automáticamente el Dockerfile completo, y sistemáticamente lo hacen mal, sin seguir las buenas prácticas habituales, con versiones aleatorias o deprecadas y en general en disonancia con lo que se pide en la ingeniería de software. Aunque la asignatura está diseñada para que si se envía algo que no cumpla todas las condiciones se explique al estudiante qué problemas hay a través del PR, se ahorrará bastante trabajo tanto al estudiante como a los otros estudiantes que revisen el PR como al profesor si se hace a mano siguiendo el manual o algún tutorial, entendiéndose así mejor las diferentes partes del Dockerfile y por supuesto la retroalimentación en el PR y cómo construir otros más complejos.

Lista de comprobación

Recuerda que hay que copiar y pegar en el PR de tu repositorio, y marcar lo que hayas hecho y se aplique:

La primera es común absolutamente a todos los objetivos desde el tercero.

* [ ] ¿Se han establecido criterios previos de elección de la imagen base, así
como los criterios de búsqueda que han conducido a las imágenes que se están
considerando?
* [ ] ¿Se ha tenido en cuenta que la imagen es para testear, y que por tanto
tiene una función específica, tal como testear cómo va nuestra aplicación en la
última versión publicada del lenguage?
* [ ] ¿Se ha tenido en cuenta que la versión de la imagen base sea la última?
* [ ] ¿Se han tenido en cuenta criterios reales para la elección, y no criterios
irrelevantes como cuanto tiempo se tarda en construirla? (Se va a construir una
vez, se va a usar cientos o miles de veces)
* [ ] ¿Se han seguido las buenas prácticas en las órdenes que construyen
   la imagen? ¿Especialmente las relativas a usuarios no privilegiados y demás?
* [ ] ¿La imagen incluye sólo y exclusivamente la infraestructura
   necesaria para pasar los tests? Por ejemplo, si se ha elegido la imagen
   "oficial" (lo que hay que hacer sólo en casos muy justificados) ¿se ha
   comprobado que lo que se instale sea sólo y exclusivamente lo necesario para
   nuestro proyecto?
* [ ] ¿Se han documentado y enlazado los commits a las imágenes que se
   han testeado?
* [ ] ¿Está establecido correctamente como `WORKDIR` el que se va a usar en los
   tests que se van a lanzar?
* [ ] ¿Se usa algún directorio adecuado para copiar contenidos del repositorio
en vez del directorio de trabajo?
* [ ] ¿Se han establecido claramente los criterios de búsqueda y de aceptación de
   la imagen base?
* [ ] ¿Tienes claro cuales son las condiciones en las que tiene que actualizarse
la imagen en DockerHub y has configurado el workflow de acuerdo con ello? En
particular, ¿sabes qué condiciones tienen que darse para que se actualice y qué
condiciones *no* deben darse?
* [ ] Si se usan GitHub actions para su publicación, ¿se han considerado
cuidadosamene los criterios (ramas, paths) para que se lancen?
* [ ] ¿Se ha comprobado que el ENTRYPOINT del contenedor no escriba nada en el
directorio que monta, ya que no tiene derecho de escritura?
* [ ] ¿Se ha abierto un shell en el contenedor una vez construida la imagen y
comprobado que los ficheros que se necesitan están todos incluidos, y que no hay
ficheros que no se necesiten?

Entrega del material evaluable en este objetivo

Se recuerda al estudiante que este objetivo no consiste en “poner las tres o cuatro imágenes oficiales del lenguaje que esté usando y quedarme con Alpine”. Para superarlo, tiene realmente que demostrar que ha seguido las mejores prácticas en la búsqueda y elección de un contenedor base que sea el más adecuado para testear el repositorio.

Subir los fuentes a GitHub mediante un pull request al documento que describa las prácticas y que se anunciará en el repositorio de la asignatura, como es habitual.

Como siempre, toda rúbrica tiene que esta correctamente identificada y enlazada desde el README, que, como en todos los objetivos, reflejará el estado del proyecto del estudiante en este punto. Llegados a este punto es efectivamente imprescindible que el README.md refleje el estado del proyecto, porque es lo que va a aparecer en Docker Hub.

Aparte, se tendrá que configurar un contenedor en Docker Hub con el mismo nombre que el proyecto. Este repositorio estará configurado para que se construya el contenedor automáticamente cada vez que se actualice el repo en GitHub.

El contenedor se va a testear de la manera siguiente

docker run -u 1001 -t -v `pwd`:/app/test [nick-estudiante]/[nombre-del-repo]

Que ejecutará los tests, y los tests deben pasar.

⚠ Este directorio que se monta está protegido contra escritura, por lo que no se podrá escribir en el mismo, como es natural. Muchos sistemas de gestión de dependencias y/o ejecutores de test lo hacen desde donde se lanzan, así que habrá que configurarlos de forma que escriban en otro lugar o elegirlos de forma que no lo hagan. En este caso, a mi me funciona se debe a que cuando se trabaja con Linux y el contenedor, el UID del usuario del contenedor y el del propio usuario son el mismo, así que no hay problemas de permisos. Sin embargo, el usuario del runner en la Github action no es el mismo, por lo que fallará. Por eso se ejecuta con el usuario 1001, para que no coincidan el usuario Linux (que suele tener UID 1000) y el no privilegiado del contenedor (que suele tener el mismo).

Si por cualquier razón no se ha podido usar en Docker Hub el mismo nick que en GitHub, se creará un fichero DOCKER_USER en el directorio principal con el mismo.

Objetivos a alcanzar

  1. Saber elegir un contenedor base y en general mostrar que se sabe llevar a cabo una elección con bases sólidas.
  2. Crear correctamente un contenedor para testear, que haga uso de los gestores de tareas y funcione correctamente.
  3. Automatizar la actualización del contenedor en Docker Hub y/o otro registro.
  4. Hacerlo todo usando buenas prácticas: hito/PMV correspondiente (o parte de él), issues y demás.

Valoración

El alcanzar este objetivo avanzará, en principio, 5% de la puntuación de este apartado.

Este es el objetivo mínimo a superar si se quiere aprobar la asignatura. Cuando se supere este objetivo se puede solicitar evaluación del 20% restante al profesor, o continuar con el resto de los objetivos hasta alcanzar la nota máxima en este apartado.