scieee AI-readable full text Open interactive document viewer

Sistema distribuido para la ejecución controlada de código arbitrario

Sánchez Paredes, Jonathan; Velázquez Alonso, Alberto

Abstract

La motivación original de este proyecto es la mitigación de riesgos en el uso de un juez virtual, que es una aplicación que facilita el envío, ejecución y evaluación de ejercicios prácticos vía Internet. Los jueces virtuales más populares en entornos educativos son FLOP [27], DOMJudge, Mooshak y AceptaElReto. Este tipo de aplicaciones compilan y ejecutan código arbitrario, y por tanto precisan un entorno de ejecución seguro, aislado y en el que pueda limitarse el uso de recursos como memoria o tiempo de CPU. Además, dado que los alumnos pueden enviar sus prácticas en cualquier momento del día, estos sistemas necesitan alta disponibilidad. Este proyecto pretende dar respuesta a estas necesidades creando un entorno de ejecución seguro, controlado y de alta disponibilidad para la ejecución remota de código arbitrario. Para ello recogeremos los requisitos de la aplicación y analizaremos las distintas alternativas a partir de sus características y adecuación a los requisitos. Finalmente desarrollaremos una aplicación que dé solución a las necesidades indicadas basándonos en los requisitos y en los resultados de nuestra investigación.

Full text

Sistema distribuido para la ejecución controlada de código arbitrario TRABAJO FIN DE GRADO GRADO EN INGENIERÍA INFORMÁTICA Realizado por: Jonathan Sánchez Paredes Alberto Velázquez Alonso Director: Luis Llana Codirector: Enrique Martín Martín Departamento de Sistemas Informáticos y Computación Facultad de Informática Universidad Complutense de Madrid Madrid, junio 2017 1 Autorización de difusión viernes, 16 de junio de 2017 Los abajo firmantes, alumnos y tutor del Trabajo Fin de Grado (TFG) en el Grado en Ingeniería Informática de la Facultad de Informática, autorizan a la Universidad Complutense de Madrid (UCM) a difundir y utilizar con fines académicos, no comerciales y mencionando expresamente a su autor el presente Trabajo Fin de Grado (TFG): “Sistema distribuido para la ejecución controlada de código arbitrario”, realizado durante el curso académico 2016-2017 bajo la dirección de Luis Llana y la codirección de Enrique Martín en el Departamento de sistemas informáticos y computación. Así mismo autorizan a la Universidad Complutense de Madrid a que sea depositado en acceso abierto en el repositorio institucional con el objeto de incrementar la difusión, uso e impacto del TFG en Internet y garantizar su preservación y acceso a largo plazo. Jonathan Sánchez Paredes Alberto Velázquez Alonso 2 Agradecimientos Queremos agradecer de una manera especial a nuestro director Luis Llana y a nuestro codirector Enrique Martín, por habernos elegido para su proyecto, confiando en nosotros y facilitándonos un Clúster [11] para el desarrollo del proyecto. Por otro lado, también queremos agradece a nuestros familiares su implicación y estar en todo momento a nuestro lado, escuchando y dando su opinión a los problemas que nos iban surgiendo. Por último, agradecer a nuestros compañeros y animar con sus respectivos trabajos de fin de grado, estamos muy orgullosos de haber compartido este camino con vosotros aun sabiendo que no será el último camino que recorramos juntos. 3 Prólogo Todo comenzó en junio del 2016 tras la finalización de los exámenes, cuando nos planteamos la opción de coger un trabajo de fin de grado para el 2016/2017. Tras surgirnos varias ideas y dar varias vueltas a la lista de trabajos propuestos, nos topamos con la distribución de sistemas, el cual nos llamó mucho la atención ya que son tecnologías con muchas salidas laborales y en el grado apenas tenemos contacto con ellas. Siendo tecnologías con muy poco recorrido, es decir, son relativamente nuevas, por ejemplo, Kubernetes fue lanzado en junio del 2014. Estos fueron los principales alicientes para que nos pusiéramos en contacto con Luis para iniciar este trabajo de fin de grado juntos. Tras una primera reunión para una toma de contacto, nos comunican que el sistema distribuido es para reemplazar en un futuro la arquitectura de la aplicación Juez de la facultad de informática. 4 Resumen La motivación original de este proyecto es la mitigación de riesgos en el uso de un juez virtual, que es una aplicación que facilita el envío, ejecución y evaluación de ejercicios prácticos vía Internet. Los jueces virtuales más populares en entornos educativos son FLOP [27], DOMJudge, Mooshak y AceptaElReto. Este tipo de aplicaciones compilan y ejecutan código arbitrario, y por tanto precisan un entorno de ejecución seguro, aislado y en el que pueda limitarse el uso de recursos como memoria o tiempo de CPU. Además, dado que los alumnos pueden enviar sus prácticas en cualquier momento del día, estos sistemas necesitan alta disponibilidad. Este proyecto pretende dar respuesta a estas necesidades creando un entorno de ejecución seguro, controlado y de alta disponibilidad para la ejecución remota de código arbitrario. Para ello recogeremos los requisitos de la aplicación y analizaremos las distintas alternativas a partir de sus características y adecuación a los requisitos. Finalmente desarrollaremos una aplicación que dé solución a las necesidades indicadas basándonos en los requisitos y en los resultados de nuestra investigación. Palabras clave Docker Kubernetes Celery Redis RabbitMQ TaskQueue Orquestación Clúster Sistema Distribuido 5 Abstract The original motivation for this project is risk mitigation for the use of a virtual judge, which is an application that facilitates sending, running and grading practical assignments online. Some popular virtual judges in educational environments are FLOP, DOMJudge, Mooshak and AceptaElReto. This kind of applications compile and execute arbitrary code, so they require a running environment that is secure, isolated and that can limit resource usage such as memory or CPU time. Also, given that students can send their assignments at any time, these systems need high availability. This project intends to give an answer to these needs by providing a secure, controlled and high availability running environment for the remote execution of arbitrary code. To this end we will gather this application’s requirements and we will analyze the different alternatives based on their features and adherence to requirements. Finally, we will develop an application that can solve the stated needs, based on the requirements and the results of our research. Keywords Docker Kubernetes Celery Redis RabbitMQ TaskQueue Orchestration Cluster Distributed System 6 Índice 1. Introducción .............................................................................................................. 8 1.1. Motivación ......................................................................................................... 8 1.2. Objetivos ............................................................................................................ 9 1.3. Estado del arte ................................................................................................... 9 1.4. Plan de trabajo ................................................................................................. 11 1. Introduction............................................................................................................. 12 1.1. Motivation........................................................................................................ 12 1.2. Goals ................................................................................................................ 12 1.3. State of the Art................................................................................................. 13 2. Requisitos y consideraciones previas ...................................................................... 15 3. Tecnologías .............................................................................................................. 16 3.1. Celery ............................................................................................................... 16 3.2. RabbitMQ ......................................................................................................... 18 3.3. Redis ................................................................................................................. 19 3.4. Docker .............................................................................................................. 20 3.5. Kubernetes ....................................................................................................... 26 4. Decisiones arquitectónicas...................................................................................... 32 4.1. Orquestación .................................................................................................... 32 4.2. Tipo de cliente .................................................................................................. 37 4.2.1. Servicio Web ............................................................................................. 37 4.2.2. Websockets .............................................................................................. 40 4.2.3. Celery ........................................................................................................ 42 4.2.4. RabbitMQ con Redis ................................................................................. 43 4.3. Localización del cliente .................................................................................... 44 4.3.1. Workers dentro de los contenedores ...................................................... 44 4.3.2. Workers fuera de los contenedores ......................................................... 45 5. Implementación ...................................................................................................... 47 6. Manual de instalación ............................................................................................. 50 7. Pruebas de concepto y medidas de tiempos .......................................................... 54 8. Conclusiones y trabajo futuro ................................................................................. 55 7. Conclusions and future work .................................................................................. 56 7 8. Contribución al proyecto ......................................................................................... 58 9. Bibliografía .............................................................................................................. 62 10. Glosario ................................................................................................................ 63 8 1. Introducción La computación desde sus principios ha pasado por muchos cambios, siendo pioneros los grandes computadores que eran capaces de realizar innumerables tareas, pero con gran coste tanto en recursos como en consumo. En cambio, con el paso del tiempo los ordenadores personales han ido cogiendo protagonismo y a día de hoy son capaces de mejorar a los grandes computadores. De esta evolución surgen los denominados sistemas distribuidos, donde varios computadores pequeños trabajan concurrentemente, comunicándose para lograr un objetivo. También, aplicando modularidad a la aplicación separando el interfaz de la ejecución. Este avance también lo quiere trasladar la Facultad de Informática de la Universidad Complutense de Madrid, trasladando la aplicación del Juez, en concreto FLOP, desde un único host [22], a un sistema distribuido. Los jueces son utilizados principalmente en la docencia, este software facilita a los profesores poner ejercicios a sus alumnos, los alumnos desarrollan el código del ejercicio y lo envían para su corrección. El juez compila el programa y devuelve al alumno el resultado de la compilación, a su vez, deja almacenado la nota del alumno. Algunos jueces automáticos son FLOP, DOMJudge, Mooshak, AceptaElReto. FLOP Es un software que sigue la metodología Test Driven Design, el uso de este programa está destinado a la programación y el aprendizaje. Este software alberga problemas de programación, permite al profesor añadir fácilmente otros nuevos y también evalúa automáticamente las soluciones enviadas por los alumnos. 1.1. Motivación En los últimos años se han producido avances en cuanto a la arquitectura de los sistemas, trasladándose hacia la distribución con el fin de repartir la carga en los servidores, o incluso para consumir solo los recursos necesarios de un servicio y pagar solo por ellos, no por toda la máquina. Como el juez automático FLOP, está ejecutándose en un único host, se quiere trasladar a un sistema distribuido. Un sistema distribuido es un conjunto de computadores separados físicamente, pero conectados por una red. El usuario del sistema percibe esta distribución desde el exterior como un único sistema. Para facilitar la distribución y la ejecución de aplicaciones dentro del sistema distribuido se utiliza Docker, y para orquestarlo [15] se utilizarán distintas alternativas software, como son Kubernetes, Docker Swarm y Mesos. 15 2. Requisitos y consideraciones previas Para poner en contexto qué se va a desarrollar vamos a realizar unas consideraciones previas y los requisitos para el buen funcionamiento de esta.  Arquitectura preexistente La arquitectura con la que actualmente se ejecuta el código es un servidor web, en este caso Tomcat, que recibe un envío consistente en código fuente para ser evaluado. Posteriormente el servidor envía de forma genérica el código a otra aplicación o servicio que ejecuta el código y devuelve el resultado de la ejecución, sea positivo o negativo el servidor almacena este resultado y se lo muestra al usuario que envió el código.  Requisitos Para el correcto funcionamiento de la aplicación tenemos que cumplir diversos requisitos tales como: o Se lanza una tarea consistente en código fuente (Python en este caso, extensible a otros lenguajes). o Se ejecuta en una máquina de un clúster. o Se recupera el resultado para ser mostrado o almacenado (el almacenamiento está fuera del alcance del proyecto). o La ejecución del código debe realizarse dentro de un contenedor Docker. El contenedor debe reiniciarse o cerrarse después de la ejecución del código, de tal manera que no haya rastro de la ejecución anterior. Debe controlarse el uso de recursos por parte del contenedor.  Consideraciones sobre la arquitectura Nomenclatura: “dispatcher” es el servidor Tomcat que acaba de recibir el código de un alumno para ser evaluado; “ejecutor” es cada instancia que espera a recibir código fuente para ejecutarlo y devolver el resultado. Esquema de cola de tareas [5]. Para el inicio del sistema se realizaron unas primeras pruebas que consistieron en la recepción de la tarea, creación de nuevo contenedor con volumen compartido, ejecución y cierre, todo ello secuencial. Tras la primera prueba surgió un problema principal: penalización por cada una de las acciones, mucho tiempo entre el envío de la tarea y la recepción del resultado. 16 3. Tecnologías En este capítulo se detallan las tecnologías empleadas para el desarrollo del sistema distribuido. 3.1. Celery Celery es una biblioteca de Python y un servicio asociado que implementan una cola de tareas. Una cola de tareas es un tipo especial de cola que se utiliza para distribuir tareas entre diferentes procesos o hilos que se denominan “workers”. Este tipo de cola presenta dos características relevantes que la diferencian de una cola genérica: la primera es que las tareas requieren confirmación por parte del sistema que gestiona la cola, de tal forma que si una tarea no se confirma se interpreta que el “worker” asociado ha sufrido un fallo y dicha tarea vuelve a la cola, quedando disponible para que otro “worker” la procese. La segunda característica es que se suelen utilizar como un tipo específico de sistema distribuido para enviar tareas a diferentes procesos o hilos que suelen ejecutarse en diferentes máquinas. Celery está ideado desde un principio para ser ejecutado en diferentes nodos. Su modo de funcionamiento es bastante sencillo, al menos para el programador: sólo debe escribir una serie de funciones, que son las tareas que se ejecutarán en las diferentes máquinas, y lanzar el worker de Celery en cada uno de los nodos que vayan a ejecutar dichas tareas mediante el comando “celery worker”. El programador, entonces, sólo necesitará llamar a dichas funciones con los parámetros relevantes para la tarea en cuestión; Celery distribuirá el trabajo entre todos los nodos disponibles y devolverá el resultado al programador. 17 Arquitectura general de Celery. Fuente: https://www.packtpub.com/mapt/book/application_development/9781783288397/7/ ch07lvl1sec45/Understanding+Celery%27s+architecture Caben distinguirse dos flujos dentro de cualquier cola de tareas y especialmente de Celery. El primero es el mecanismo de distribución de las tareas y de los parámetros con que se ejecutarán éstas, y el segundo es la recogida de los resultados. Celery no es una herramienta independiente, sino que para la implementación de estos flujos utiliza software ajeno, y de hecho permite utilizar diferentes tecnologías para cada uno de los dos mecanismos. La instalación recomendada utiliza RabbitMQ y Redis, si bien Celery soporta otras tecnologías como MongoDB, CouchDB en incluso bases de datos relacionales. Para la distribución de mensajes a los “workers” se utiliza RabbitMQ. Más adelante se describe con más detalle esta tecnología, pero en esencia es una cola de mensajes [4], que permite la distribución de dichos mensajes desde varios productores hacia varios consumidores, y que permite implementar complejos modelos de distribución. En cuanto a los resultados, éstos se almacenan en Redis, que es un almacén de claves y valores. La recomendación de estas tecnologías se basa en el hecho de que RabbitMQ es un software muy probado y estable, idóneo para la distribución de mensajes volátiles, mientras que Redis permite el almacenamiento y persistencia de las respuestas durante todo el tiempo que el cliente necesite hasta recuperarlas, y que además permite leer varias veces el mismo dato. 18 A nivel interno, el programador solicita la ejecución remota de una tarea, Celery le devuelve un objeto, que puede entenderse como un “futuro” o una “promesa”. Una representación de una computación que puede o no haber terminado, y permite al cliente realizar otras tareas hasta que necesite el resultado de la ejecución de la tarea, ya que este modelo no bloquea el cliente. Una vez que el código cliente necesite el resultado puede solicitarlo explícitamente mediante una llamada bloqueante, que en este caso sí congela el hilo del cliente hasta que el resultado está disponible. Este modelo de programación permite por ejemplo lanzar varias tareas simultáneas para que sean ejecutadas en nodos diferentes y, una vez estén lanzadas todas, realizar otras operaciones independientes y después recuperar los resultados por orden. 3.2. RabbitMQ RabbitMQ es una cola de mensajes. Una cola de mensajes es un mecanismo para el envío de datos entre hilos o procesos, a veces incluso entre procesos que se ejecutan en diferentes máquinas. Todas las colas de mensajes implementan alguna variante del patrón publisher/subscriber que podríamos traducir como publicador/suscriptor en el que uno o varios hilos productores generan datos que son enviados a uno o varios hilos consumidores. PUBLISHER / SUBSCRIBER El patrón publisher / subscriber, también denominado pub/sub, define un escenario con productores y consumidores en el que los consumidores pueden especificar que no desean recibir todos los datos disponibles sino sólo aquellos que concuerdan con un patrón determinado. Cola de mensajes pub/sub única. Fuente: https://www.rabbitmq.com/getstarted.html RabbitMQ permite además implementar algunos patrones más avanzados de distribución de mensajes, como enrutamiento de mensajes, suscripción selectiva sólo a algunos temas o tipos de mensaje o RPC (petición/respuesta). RabbitMQ permite también algunas opciones avanzadas como la persistencia de mensajes, políticas de seguridad, notificación de recepción y la configuración explícita de diferentes parámetros como por ejemplo la política de entrega. 19 Patrón pub/sub detallado. Fuente: http://zguide.zeromq.org/page:all Esta tecnología implementa un protocolo abierto denominado AMQP para la distribución de mensajes, lo que permite entre otras cosas que existan clientes para casi todos los lenguajes y plataformas en uso. 3.3. Redis Redis es un servicio que almacena en tablas de hashes, conjuntos de clave y valor (key-value store) [9] en memoria RAM, el rendimiento puede ser muy elevado debido al almacenamiento en memoria RAM, comparándola con los motores de búsqueda de otras bases de datos. IN-MEMORY DATABASE [8] Una in-memory database almacena todos sus datos en memoria. Esto implica que las consultas son mucho más rápidas al no tener que leer ni escribir en un disco, pero como contrapartida el riesgo de pérdida de datos es mucho más elevado al no existir persistencia de datos. Algunas bases de datos en memoria permiten el volcado periódico de los datos al disco, lo que reduce el impacto de un fallo inesperado a la pérdida de la nueva información generada desde el último volcado. 20 KEY-VALUE STORE Al contrario que una base de datos relacional, una key-value store almacena pares clavevalor y no permite realizar búsquedas complejas, o al menos no tan complejas como SQL. Este tipo de bases de datos son idóneas para guardar datos sobre los que se sabe de antemano que no va a ser necesario realizar búsquedas complejas, ya que la velocidad de acceso e inserción es mucho mayor en comparación con las bases de datos tradicionales. Específicamente, las consultas de tipo JOIN o similares son imposibles en este tipo de base de datos y suelen implicar consultar todos los datos y realizar la búsqueda programáticamente, lo que implica un mayor costo en memoria, eficiencia y esfuerzo del programador. Esta tecnología no es una base de datos relacional porque no permite operaciones entre datos como JOINs ni consultas elaboradas mediante SQL. Sin embargo, ofrece el almacenamiento no sólo de cadenas de texto sino de diferentes tipos de datos abstractos como listas, conjuntos, tablas y otros tipos más especializados. Redis se ciñe estrictamente en establecer y recuperar datos sobre sus estructuras dejando de lado la búsqueda de relaciones o restricciones. Redis permite una forma especializada de persistir los datos, así como replicación maestro-esclavo e incluso clustering con tolerancia a fallos de los nodos. 3.4. Docker Docker es un software de virtualización y aislamiento de procesos a nivel de sistema operativo. Al contrario que las máquinas virtuales tradicionales, Docker no emula completamente las llamadas al sistema y por tanto no necesita de instrucciones especiales para mantener un rendimiento aceptable. Los procesos que se ejecutan bajo Docker corren directamente en el sistema operativo host sin software intermedio, si bien el kernel se encarga de aislar dichos procesos para que no interfieran con el resto del sistema. 21 Diferencia entre máquinas virtuales tradicionales y Docker, como podemos ver docker funciona con el sistema operativo de la propia máquina y comparte los bins y librerías entre las aplicaciones del mismo tipo. Fuente: http://www.zdnet.com/article/what-is-docker-and-why-is-it-so-darn-popular/ Diferencia entre máquinas virtuales tradicionales y Docker. En esta imagen al ser aplicaciones diferentes, la única diferencia existente es que Docker funciona con el sistema operativo de la máquina, que lo lanza. Fuente: https://www.docker.com/what-docker 22 Uso conjunto de Docker y máquinas virtuales. En esta imagen se ve claramente la ventaja de que Docker se lance con el sistema operativo de la máquina. Fuente: https://blog.docker.com/2016/04/containers-and-vms-together/ De momento Docker sólo funciona con el kernel de Linux, ya que éste proporciona tres características idóneas para aislar los procesos que corren dentro de los contenedores de Docker. Estas características son: Los grupos de control (“cgroups”) que limitan el acceso a recursos tales como memoria y CPU. Los espacios de nombres (“namespaces”), que aíslan los procesos que corren dentro de los contenedores entre sí y de los procesos del host. Un sistema de archivos como OverlayFS que no solo aísla los archivos que se encuentran dentro de un contenedor, sino que permite superponer los contenidos de múltiples sistemas de archivos, presentando un sólo sistema que resulta de la combinación de los contenidos de todos ellos. Docker se gestiona a través de una API que puede ser llamada desde las herramientas de Docker en línea de comandos, desde bibliotecas creadas expresamente para diferentes lenguajes de programación, o bien realizando peticiones directamente al endpoint [2] del servicio web que proporciona dicha API. 23 Arquitectura de Docker. Fuente: https://docs.docker.com/engine/docker-overview/ Docker sólo puede lanzar imágenes creadas previamente. Existe un repositorio de imágenes creadas por la comunidad (Docker Hub) y otros repositorios mantenidos por diferentes entidades, por ejemplo, Google. Cualquier usuario puede generar sus propias imágenes y subirlas a Docker Hub simplemente creando un archivo llamado Dockerfile y ejecutando unos sencillos comandos que generan a partir de este archivo la imagen en la máquina local, que luego puede subirse a un repositorio. Para crear una imagen se necesita un archivo DockerFile. Este archivo contiene el sistema operativo y las herramientas que se indiquen. Para construir la imagen debemos hacer docker build. Una vez creado la imagen podemos subirla al repositorio haciendo docker push. Si la imagen está subida podemos descargarla con docker pull. Una vez tenemos la imagen se puede lanzar con docker run y realizar commit para guardar los cambios. La imagen siguiente explica estos pasos más gráficamente. Fuente: https://dzone.com/refcardz/getting-started-with-docker-1 Hay que indicar Docker muestra un comportamiento que interesa destacar a la hora de lanzar o parar varios contenedores simultáneamente, como veremos en este 24 test de tiempos de arranque y parada de un contenedor en dana, el clúster de pruebas. Comando de lanzamiento: for i in $(seq 20); do /usr/bin/time -f '%e' docker run -dti --rm --name c$i debian > /dev/null & Done Comando de parada: for i in $(seq 20); do /usr/bin/time -f '%e' docker stop c$i > /dev/null & done run 1 run 2 run 3 run 4 run 5 stop 1 stop 2 stop 3 stop 4 stop 5 3.84 4.02 6.68 6.83 9.37 9.51 9.88 9.91 10.44 12.19 12.25 12.35 12.44 12.49 12.65 12.69 13.00 13.15 5.31 6.74 8.10 8.25 8.72 8.83 11.72 11.85 11.97 12.08 12.26 12.36 12.44 13.06 13.35 13.44 13.57 13.60 4.34 4.92 5.39 7.88 8.73 8.88 9.21 9.54 11.61 11.77 12.01 12.12 12.22 12.30 12.43 12.48 12.52 12.59 4.38 4.88 6.91 8.20 8.37 8.48 8.96 11.87 11.91 12.15 12.25 12.36 12.49 12.55 12.81 12.96 13.14 13.13 5.38 7.25 8.20 8.48 9.10 9.59 9.76 11.64 11.82 11.86 12.73 12.80 12.86 12.94 13.04 13.11 13.23 13.30 1.92 3.21 5.20 5.60 6.08 6.60 6.79 6.99 7.27 7.43 7.60 7.80 7.95 8.12 8.30 8.52 8.63 8.74 3.58 4.26 4.50 4.96 5.08 6.13 6.98 7.28 7.44 7.60 7.83 7.92 8.21 8.38 8.45 8.57 8.63 8.66 3.83 4.81 4.98 5.43 6.15 6.33 6.44 7.11 7.68 7.84 8.02 8.15 8.29 8.44 8.54 8.62 8.66 8.73 3.43 3.60 3.75 3.92 6.15 6.32 6.86 7.02 7.19 7.27 7.43 7.57 7.69 7.84 7.96 8.06 8.13 8.23 3.70 4.02 4.23 4.47 6.04 6.74 6.91 7.30 7.49 7.60 7.78 7.99 8.10 8.19 8.38 8.54 8.69 8.82 31 Fuente: https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/ 32 4. Decisiones arquitectónicas En este apartado se realizará una investigación entre las tecnologías más punteras que existen en la actualidad, con el fin de contestar las siguientes preguntas:  Que orquestación tendrá el sistema distribuido.  Tipo de cliente.  Localización del cliente. 4.1. Orquestación ORQUESTACIÓN En el ámbito de los contenedores software, se entiende como orquestación a la gestión unificada y automatizada de dichos contenedores. El software de orquestación recibe una configuración deseada y se encarga de llevarla a cabo en los nodos que gestiona, levantando contenedores de imágenes específicas cuando es necesario y reiniciándolos si es preciso. Los sistemas de orquestación proporcionan funcionalidades adicionales a la mera gestión de contenedores; por ejemplo, permiten exponer grupos de contenedores como un único servicio, así como balancear la carga entre ellos. Para la elección de qué tecnología se utilizará para orquestar el sistema distribuido se realizará una investigación exhaustiva de las tecnologías del mercado actual. Para una primera toma de contacto se realizará una breve descripción, basada en las funcionalidades de las tecnologías más punteras. Las siguientes imágenes han sido tomadas de las diapositivas de la página: https://es.slideshare.net/KarlIsenberg/container-orchestration-wars El primer punto que se va a analizar es la programación, la capacidad de cada tecnología en la escalabilidad, resurrección, despliegue continuo y colocación de los nodos. 33 Como se puede observar en este aspecto la tecnología más completa es Kubernetes, ya que tiene todos los puntos incluyendo la resurrección de nodos que es uno de los requisitos del sistema, otras dos tecnologías con bastantes puntos a favor, son Swarm y Mesos/Marathon. El siguiente punto a analizar es la administración de servicios, la cual para nuestro sistema distribuido es muy importante, ya que en los requisitos se pidió que se controlarán recursos como el consumo de CPU, limitar el espacio de disco o el tiempo de ejecución de una máquina. 34 En esta imagen podemos ver cómo la tecnología que más se amolda a nuestras peticiones es Mesos con Marathon y justo detrás de ella Kubernetes y Swarm de nuevo, por lo tanto, estas tres tecnologías cogen muchos puntos para ser elegidas. Por último, vamos a analizar la administración de servicios, como, por ejemplo, la carga balanceada y las etiquetas. De nuevo las tecnologías que más puntos a su favor tienen son Kubernetes y Mesos con Marathon. Para completar la investigación veremos dos gráficas más sobre las tecnologías más usadas en el mercado actualmente y a continuación describiremos una a una cada tecnología. La primera de ellas son las tecnologías que orquestan, en primer lugar, si nos fijamos en la línea de azul claro, se observa que las más utilizadas en producción son Kubernetes y Mesos. En cambio, si nos fijamos en la línea de azul oscura, se observa que los porcentajes son mayores en no producción, dando a entender que es una tecnología en crecimiento, todavía está en pruebas. 35 Fuente: https://thenewstack.io/tns-research-present-state-container-orchestration/ En esta segunda imagen podemos observar las herramientas de programación, compañeras de las de orquestación, en primer lugar, se encuentra Kubernetes y el compañero de Mesos, Marathon. También, destacar Swarm que es distribuido por Docker. 36 Fuente: https://thenewstack.io/tns-research-present-state-container-orchestration/  Docker Swarm Es la solución más simple, viene instalada con Docker Engine, ya que es una distribución oficial de docker, a partir de la versión 1.12. Es compatible con la API de Docker, pero se trata de una tecnología muy poco madura para entornos de producción.  Mesos con Marathon Se trata de una mezcla de tecnologías donde Marathon es el encargado de la orquestación del sistema y Mesos, en cambio, lanza los contenedores de imágenes de Docker, también podemos intercambiar Mesos por Kubernetes, creando una tecnología potente pero pesada. Se trata de una tecnología muy madura, al respecto de las demás. Pero tiene un consumo muy elevado de recursos.  Yarn Yarn es otro gestor de sistemas distribuidos, pero fue creado con la idea de utilizarse para Hadoop. 37  Nomad Nomad es la tecnología más reciente de todas, con menos de un año. Es la más simple tiene un binario que puede actuar como cliente y servidor, pero al ser tan joven apenas hay documentación, muy poca difusión y asume que se va a utilizar con Vagrant. 4.2. Tipo de cliente En este apartado se toma la decisión del tipo de cliente que se va a emplear para el desarrollo del sistema distribuido y las diferentes posibilidades existentes. 4.2.1. Servicio Web Una alternativa para enviar programas a los nodos que los van a ejecutar y posteriormente recuperar los resultados es mediante micro-servicios [1], utilizando la arquitectura REST. Este sistema es relativamente sencillo de implementar ya que en casi cualquier lenguaje existen bibliotecas que permiten la creación de un servicio web y la conexión al mismo, y además el protocolo utilizado, HTTP o HTTPS, es público, abierto y extremadamente estable, lo que permitiría la interoperabilidad de forma muy sencilla en caso de que se quisiera extender o modificar el programa en un futuro. MICROSERVICIOS Y REST La arquitectura de microservicios es un patrón por el cual una aplicación se divide en módulos o fragmentos que se comunican entre sí mediante peticiones HTTP. La ventaja principal es que obliga a desarrollar sistemas con bajo acoplamiento, a costa de añadir un cierto retraso por las peticiones HTTP y la serialización de los mensajes. El protocolo HTTP se desarrolló para la entrega de hipertexto y no para la comunicación entre módulos o procesos, por lo que se desarrolló un marco conceptual para la adaptación de HTTP a la comunicación entre módulos. Este marco se llamó REST, representational state transfer, y establece una correlación entre comandos HTTP y operaciones entre módulos. En REST, las peticiones GET, POST, PUT y DELETE equivalen a los conceptos crear, leer, actualizar y borrar. En este punto se nos plantean dos alternativas: podemos tener un servidor en el nodo maestro y clientes en los nodos ejecutores, o al revés. Estas alternativas tienen cada una sus pros y contras, que pasaremos a explorar. 38 Tal vez la alternativa más inmediata sea tener un micro-servicio en el nodo que recibe las peticiones de los usuarios y que los nodos que ejecutan las tareas soliciten los datos a dicho servicio (GET) y posteriormente devuelvan los resultados al mismo (POST). El problema principal de esta arquitectura es que, aunque el servidor no tenga tareas que enviar los clientes deben consultar periódicamente si hay nuevas tareas que ejecutar, creando una mínima carga residual en el sistema. Otro problema derivado del anterior es el hecho de que lleguen múltiples tareas mientras todos los clientes están dormidos, en cuyo caso dichas tareas no comenzarán a ejecutarse hasta que llegue el momento de que los clientes vuelvan a solicitar tareas al servidor, por lo que dependiendo la configuración de los delays puede tardarse un tiempo considerable en servir una petición de un usuario. Creemos que este fenómeno se daría con cierta frecuencia según los patrones típicos de uso de esta aplicación: los alumnos tienden a enviar en masa las tareas para ser evaluadas el último día e incluso la última hora. Hay que decir que este problema no se presentaría con numerosas solicitudes de usuarios en espera, sino sólo con un flujo bajo pero constante de tareas pendientes. ESPERA ACTIVA Y PUSH / PULL [6] En un escenario en el que hay productores y consumidores, el evento en el que un consumidor solicita datos a un productor se denomina pull, mientras que, si es el productor el que inicia la comunicación de los datos disponibles a los consumidores o les señaliza dicho evento de alguna forma, se denomina push. Cada una de las dos variantes tiene diferentes características de rendimiento. Cuando ningún productor tiene datos disponibles y tampoco dispone de ningún método de señalizar a los consumidores la presencia de nuevos datos, los clientes suelen tener que realizar espera activa, también denominada polling [21]. Ésta consiste en que los consumidores solicitan datos a los productores en bucle, lanzando tantas peticiones como el sistema permite por lo que el rendimiento del sistema se degrada. Una alternativa es esperar un tiempo determinado hasta lanzar la siguiente petición, lo que hace que el rendimiento del sistema disminuya. En contraste, si existe algún método para señalizar la presencia de datos, los consumidores pueden bloquear y mientras se encuentran bloqueados no consumen recursos. Los métodos de espera más comunes son variables de condición si sólo se necesita espera entre hilos, o bien las llamadas del sistema select o epoll si se necesita sincronización entre procesos, e incluso entre diferentes hosts. 39 En la imagen podemos ver el modelo gráfico de un micro-servicio, donde el servidor está en el nodo host y los nodos ejecutores son los clientes. La otra alternativa utilizando una arquitectura REST [3] sería a la inversa: el nodo que recibe las solicitudes de los usuarios sería el cliente, y los nodos que ejecutan las tareas serían los servidores. Esta arquitectura aparentemente extraña tendría la enorme ventaja de no depender del delay del cliente, ya que las tareas serían enviadas a los clientes disponibles a medida que fueran llegando sin depender de retrasos artificiales, pero tendría varias desventajas. La primera, el nodo “dispatcher” debería mantener una lista de nodos ejecutores disponibles, por ejemplo, contando con un micro-servicio en el que se registrara cada nodo al arrancar y/o al quedar disponible después de haber ejecutado una tarea. Esto complicaría la implementación y obligaría al programador a implementar un mecanismo para llevar el registro de nodos disponibles y tareas pendientes, así como controlar aquellas tareas enviadas, pero no finalizadas. 40 En la imagen podemos ver el modelo gráfico de un micro-servicio, donde el servidor está en los nodos ejecutores y el nodo host es el cliente. 4.2.2. Websockets WEBSOCKET [23] Websocket es una tecnología de comunicación bidireccional desarrollada primeramente para navegadores web, pero que en realidad es independiente de HTTP. El protocolo de Websocket establece una conexión permanente entre cliente y servidor que permanece abierta durante toda la sesión, lo que implica un mejor rendimiento, pero también un mayor consumo de recursos en el lado del servidor. Los motivos para el desarrollo de este protocolo son principalmente dos: por un lado, permitir al servidor enviar datos al cliente sin petición previa, y por el otro lado evitar que el cliente tenga que establecer una nueva conexión cada vez que desee realizar una petición al servidor. Una modificación de la implementación mediante micro-servicios es la utilización de websockets. Aunque no es un protocolo tan probado como HTTP 47 5. Implementación Llegado al punto de la implementación hay que decidir qué tecnologías van a ser utilizadas. Parte común La comunicación entre el dispatcher y los ejecutores es la primera decisión que debe tomarse, y en este caso la alternativa entre servicios web y una cola de mensajes es sencilla de dirimir en nuestro caso. La cola de mensajes tiene una ventaja determinante: permite que los clientes se bloqueen en espera en lugar de tener que realizar espera activa de forma mucho más sencilla para el desarrollador, lo que conlleva menos espacio para cometer errores. Elegida la opción de la cola de mensajes, se plantea la utilización de una opción que facilita su utilización aún más si cabe: Celery. Esta biblioteca es relevante ya que nos ofrece reintentos automáticos si el mensaje no se confirma, tiene la opción de confirmar antes y después de la ejecución, además proporciona una cola de tareas, RabbitMQ, para que los worker recojan las peticiones que el master ha dejado y una base de datos NoSQL, Redis, para que los workers almacenen sus respuestas una vez acabada la compilación del código. Además, Celery nos ofrece un balanceo de carga basado en una arquitectura interna de “prefetch”, que consiste en cada worker se queda con más tareas de la que está ejecutando, siendo útil para evitar un tráfico constante, no tienen que pedir una nueva tarea cada vez que termina una. Celery se trata de una librería de Python por lo que estamos limitando el sistema a la utilización de un solo lenguaje. También en este caso las ventajas son claras, si bien hay que tener muy en cuenta el mayor inconveniente: el código deberá cambiarse para excluir Celery si se decide no utilizar Python o no llamarlo desde otro lenguaje. PREFETCH Cuando un cliente obtiene tareas de una cola puede hacerlo de una en una o tomando varias a la vez, lo que se denomina prefetching. La principal ventaja de esta técnica es que, sobre todo con mensajes de gran tamaño o que necesitan relativamente mucho tiempo para ser leídos y/o decodificados, se reduce la latencia de lectura al solapar la ejecución de una tarea con la lectura de otras de la cola. El prefetch sirve además como un método rudimentario de balanceo, evitando que un sólo nodo acapare todas las tareas. La siguiente decisión es la localización del cliente. Como ya hemos visto, podemos lanzar un número de contenedores dentro de los cuales se ejecuta un cliente en cada uno, o bien podemos lanzar un solo cliente que se encargue de lanzar y cerrar 48 contenedores, así como de insertar el código en cada uno de ellos, ejecutarlo y tomar el resultado cada vez que llegue una petición. La decisión en este caso es más complicada, y para tomarla nos hemos basado en benchmarks del tiempo necesario para lanzar un worker de Celery así como para crear un nuevo contenedor y cerrarlo. Un worker de Celery sin opciones especiales tarda un segundo en arrancar, número que permanece estable, aunque se lancen 20 workers a la vez, aunque se puede reducir hasta unos 0.2 segundos con las opciones “--without-gossip -- without-mingle”. Sin embargo, el lanzamiento de un contenedor varía dependiendo del número de lanzamientos concurrentes que se intenten hacer. Un único lanzamiento tarda de 0.5 a 0.8 segundos, pero si se utilizan tres hilos independientes que lanzan contendores a la vez, el tiempo máximo de lanzamiento sube hasta los 5 segundos. Se produce un fenómeno similar en el cierre de los contenedores: el cierre de un sólo contenedor es casi instantáneo pero el cierre en paralelo de varios implica un retraso de un segundo. Estas cifras nos llevan a diseñar un mecanismo que nos permita tener múltiples contenedores abiertos y utilizarlos cuando lleguen peticiones; lo que se suele denominar “prefork”. Este sistema lanza y cierra los contenedores en un hilo independiente y no obstaculiza ni bloquea el hilo principal, por lo que los tiempos de lanzamiento y cierre quedan enmascarados, siempre que en el caso del lanzamiento haya contenedores disponibles en el pool cuando llega una petición. La construcción de este pool de contenedores conlleva la existencia de un único cliente por host, no un cliente por contenedor. La última decisión es el uso de herramientas de orquestación para gestionar las imágenes que contienen los clientes. En este punto indicamos dos variantes, debido a que el código ha sido desarrollado para soportar ambas. La decisión sobre cuál variante escoger depende de las características de las máquinas en las que se va a ejecutar este sistema, así como de las necesidades de uso. Variante A: con orquestación En primer lugar, decidir cuál va a ser la tecnología que se encargue de orquestar el clúster, debido a la cantidad de documentación y estar en el ranking de tecnologías más usadas tanto en el sector empresarial, como en el sector particular. Kubernetes es la elegida para orquestar el sistema, dicho clúster tiene un nodo master y 5 nodos worker, en los que se debe instalar Docker y ejecutar el código que nos proporciona el git de Kubernetes para poder lanzarlo, dependiendo si el nodo es worker o master. En este paso se descubrió un problema, el nodo master tenía ocupado el puerto 8080, y kubernetes, por defecto, se levanta en el puerto 8080, generando conflictos y no dejando que se inicie correctamente. Como solución se descargó el código de Kubernetes, se modificó y se volvió a subir para que se lanzará en el puerto 28080.Una vez instalado Kubernetes utiliza Flannel [24] para la conexión entre nodos. A la hora de lanzar un contenedor Kubernetes y Docker tienen una política de cierre prematuro, 49 que está relacionada con la “Docker restart policy”, que contiene cuatro políticas de reinicio, “no” (por defecto), “on-failure”, “unless-stopped” y “always”. Si el cierre del contenedor se produce antes de 10 segundos desde el arranque, se considera como un fallo y se aplica un “exponential backoff” [12], que vuelve a intentar lanzar el contenedor. EXPONENTIAL BACKOFF En general, exponential backoff es un algoritmo que disminuye la frecuencia de cierta operación hasta encontrar un punto aceptable. Kubernetes implementa exponential backoff en el reinicio de un contenedor. Si un contenedor recién lanzado se cierra antes de 10 segundos, K8s interpreta que ha habido algún tipo de error y retrasa su reinicialización en lugar de efectuarla inmediatamente, esperando más tiempo cada vez para el reinicio de dicha máquina hasta llegar al número máximo de reintentos. Una vez montado el sistema distribuido hay que ajustar el sistema a los requisitos, con la configuración ulimit de docker a la hora de lanzar las imágenes, añadiendo a la hora de lanzar las imágenes con algunas features, “--cpus” para controlar el número de CPUs que queremos usar para lanzar la imagen, “--stoptimeout” tiempo en segundos que tendrá de vida el contenedor. Uno de los requisitos consiste en que cada contenedor sólo puede ejecutar un código para evitar programas malintencionados, la parada y rearranque implican mantener datos en el sistema de archivos, por lo que la única solución es el cierre de cada contenedor después de ejecutar un código y lanzar uno nuevo para cada petición. Variante B: sin orquestación Otra opción es tener el mismo sistema, pero sin un orquestador, ya que Celery funciona como Kubernetes: en el nodo master se ejecuta Celery y distribuye sus tareas entre los nodos workers en los que se ha ejecutado el código de Celery Worker. 50 6. Manual de instalación En este capítulo se va a detallar una guía para la instalación de un sistema distribuido para la ejecución segura de código. La instalación de Kubernetes es opcional, se puede desplegar Celery igualmente. LINUX CGROUPS Cgroups es una característica del kernel de Linux cuyo fin es controlar y limitar los recursos asignados a grupos de tareas. En casi todos los sistemas operativos tipo Unix existe un mecanismo, ulimit, usado para limitar recursos por cada proceso. En contraste, Cgroups permite aplicar estos límites a un grupo de procesos, que en el caso de Docker está compuesto por los procesos que se ejecutan centro de cada contenedor. Además, hay un mayor número de límites que se pueden imponer a estos procesos. Cgroups está dividido en módulos que pueden venir por defecto activos o no activos dependiendo de la distribución de Linux que estemos utilizando. En el caso concreto de estas instrucciones nos hemos basado en Debian, por lo que para poder utilizar algunas de las características de Cgroups y que Docker funcione correctamente, hay que pasar ciertos parámetros al kernel a través del cargador de arranque. Kubernetes  Previo a la instalación debemos tener un nodo master y tantos nodos workers como queramos.  Instalación de Kubernetes, para este paso necesitaremos ser root. 1. Primero en todas las máquinas modificamos el grub. Editar: nano /etc/default/grub Poner: GRUB_CMDLINE_LINUX="cgroup_enable=memory" Ejecutar: update-grub2 Reiniciar: reboot 2. En todas las máquinas (Sólo si se tuviera que repetir los pasos, la primera vez no es necesario). docker stop $(docker ps -a -q) # Parar todas las instancias docker rm $(docker ps -a -q) # Eliminar instancias docker rmi $(docker images -q) # Eliminar imagenes 51 3. En la máquina Master. git clone https://github.com/kubernetes/kube-deploy cd kube-deploy/docker-multinode export K8S_VERSION=v1.5.6 export IP_ADDRESS=192.168.134.1 # La IP de este equipo en la red ./master.sh 4. En todas las máquinas Worker. git clone https://github.com/kubernetes/kube-deploy cd kube-deploy/docker-multinode export K8S_VERSION=v1.5.6 export MASTER_IP=192.168.134.1 # La IP del master export IP_ADDRESS=192.168.134.1 # La IP de este equipo en la red ./master.sh 5. Kubectl, Sólo en el nodo Master. curl -LO https://storage.googleapis.com/kubernetesrelease/release/$(curl -s https://storage.googleapis.com/kubernetesrelease/release/stable.txt)/bin/linux/amd64/kubectl chmod +x ./kubectl mv ./kubectl /usr/local/bin/kubectl 6. Comprobación, en el nodo Master que es el único que tiene instalado Kubectl. kubectl cluster-info #En master nos debería dar toda la información del Clúster kubectl get nodes #En master para consultar los nodos 52 7. Lanzar un replication controller desde un yaml. Creamos un fichero, por ejemplo, en la ruta: /opt/kubernetes/examples/nginx-pod.yaml Contenido del archivo: apiVersion: v1 kind: ReplicationController metadata: # Nombre del Replication Controller name: my-nginx spec: replicas: 1 selector: app: nginx template: metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80 restartPolicy: Always Para lanzar el pod: kubectl create –f /opt/kubernetes/examples/nginx-pod.yaml 53 Cliente Es prerrequisito tener Docker instalado tanto en el servidor como en los clientes. En los clientes, además, deberá instalarse la biblioteca Celery con soporte para Redis: pip3 install celery[redis] El lanzamiento del cliente es muy sencillo. Sólo es necesario obtener el código en el servidor y en los clientes, por ejemplo, clonando el repositorio que lo contiene: git clone https://github.com/velazq/executor-service.git Deben lanzarse los servicios auxiliares (RabbitMQ y Redis) en el servidor: cd executor-service ./start-services.sh En los clientes, en cambio, ha de ejecutarse el worker: cd executor-service celery worker -A celery_worker 54 7. Pruebas de concepto y medidas de tiempos Hemos creado un programa de prueba en Python que genera el valor N de la sucesión de Fibonacci usando el algoritmo recursivo, sin ningún tipo de optimización. Para esta prueba calcularemos la posición 48, que en un portátil tarda esto: $ time python3 fib.py 63245986 real 0m23.552s user 0m23.442s sys 0m0.040s Hemos generado 200 carpetas simulando el envío de prácticas al servidor. Todas ellas se encuentran bajo la carpeta “codetest”. Lanzamos el comando: time python3 executor-service/celery_client_multi.py codetest fib.py y obtenemos como resultado: real 3m22.149s user 0m0.704s sys 0m0.084s 55 8. Conclusiones y trabajo futuro Este proyecto involucra muchas tecnologías diferentes tales como containerización, orquestación, comunicación entre procesos (IPC), protocolos de serialización, colas de mensajes o almacenes de valores clave. La tecnología central de este proyecto, la containerización con Docker, se está acercando rápidamente a la madurez, aunque algunas peculiaridades todavía se mantienen. Su supuesta creación instantánea no es así en nuestras pruebas, probablemente debido a algún cuello de botella en el daemon Docker. Hay preocupaciones sobre el nivel de aislamiento que proporciona y si es suficiente para detener a un atacante determinado. Pero, por otro lado, su funcionalidad y rendimiento es inigualable. La virtualización real impone una sobrecarga pesada que Docker elimina casi por completo. La orquestación, por el contrario, sigue siendo inmadura. Incluso la tecnología más prometedora, Kubernetes, que es desarrollada por Google y tiene el mayor número de usuarios, carece de documentación y facilidad de uso. El concepto subyacente es sólido y sin duda se convertirá en un lugar común en unos años, pero en este momento está implementación todavía está limitada a devotos equipos con suficientes recursos para mantener y depurar esta nueva tecnología. IPC es una tecnología muy madura. Hay varias implementaciones con diferentes conjuntos de características y características de rendimiento, y la documentación es, en la mayoría de las alternativas, abundante. Nuestra elección, Celery, está bien documentada y tiene muchas opciones de configuración para ajustar su rendimiento, e incluso puede trabajar con muchas colas de mensajes y bases de datos diferentes, para la distribución y almacenamiento de tareas. Este trabajo puede ser ampliado o utilizado como base para varias otras ideas. El más directo sería una plataforma de prueba para nuevos lenguajes de programación o DSL (lenguajes específicos de dominio), para que los usuarios pudieran ser presentados con una interfaz web donde pudieran escribir o cargar sus pruebas y hacerlas evaluar por el sistema. Este proyecto también podría ser utilizado para implementar una de las últimas tendencias en computación: un proveedor sin servidor. Al igual que PaaS (plataforma como servicio) e IaaS (infraestructura como servicio), algunos proveedores como Amazon y Google están comenzando a ofrecer un servicio basado en eventos donde los desarrolladores suben su código y se ejecuta en respuesta a una llamada externa tal como un usuario que hace una solicitud a una página web o una base de datos que está siendo modificada. Estos servicios sólo se facturan por el uso de la CPU y por lo tanto no requieren tener una máquina virtual funcionando en todo momento, por lo que es más rentable para ciertos tipos de cargas. Otra ventaja es que estos servicios se 56 escalan automáticamente para el desarrollador, porque el código se puede ejecutar en cualquier número de máquinas en paralelo. 7. Conclusions and future work This project involves many different technologies such containerization, orchestration, inter-process communication (IPC), serialization protocols, message queues or key-value stores. The central technology of this project, containerization with Docker, is quickly approaching maturity, albeit some quirks still remain. Its purported instant creation is, in our tests, not so, probably due to some bottleneck in the Docker daemon. There are concerns about the level of isolation it provides and whether it is enough to stop a determined attacker. But, on the other hand, its functionality and performance is unparallelled. Real virtualization imposes a heavy overhead that Docker eliminates almost completely. Orchestration, in contrast, is still immature. Even the most promising technology, Kubernetes, that is developed by Google and has the largest number of users, is severely lacking in documentation and ease of use. The underlying concept is sound and will no doubt become commonplace in a few years’ time, but right now this implementation is still constrained to devops teams with enough resources to maintain and debug such a new technology. IPC is a very mature technology. There are several implementations with different feature sets and performance characteristics, and documentation is, in most alternatives, plenty. Our choice, Celery, is well-documented and has many configuration options to adjust its performance and it can even work with many different message queues and databases, for task distribution and storage. This work can be extended or used as the basis for several other ideas. The most straightforward one would be a testing platform for new programming languages or DSLs (domain-specific languages), so that users could be presented with a web interface where they could write or upload their tests and have them evaluated by the system. This project could also be used to implement one of the newest trends in computing: a serverless provider. In the same fashion as PaaS (platform as a service) and IaaS (infrastructure as a service) some providers such as Amazon and Google are beginning to offer an event-driven service where developers upload their code and it gets executed in response to an external event such as a user making a request to a web page or a database being modified. These services are only billed for CPU usage and therefore do not require having a virtual machine running at all times, thus making them more cost-effective for certain types of loads. Another advantage is that these 63 https://www.youtube.com/watch?v=C_u4_l84ED8 Kubernetes vs Docker Swarn https://platform9.com/blog/compare-kubernetes-vs-docker-swarm/ Guide Kubernetes https://deis.com/blog/2016/kubernetes-illustrated-guide/ Kubernetes architecture and use https://medium.com/@itmarketplace.net/kubernetes-101-12ad2424d2f1#.t65x0chdm http://blog.kubernetes.io/2016/09/kubernetes-1.4-making-it-easy-to-run-onkuberentes-anywhere.html https://deis.com/blog/2016/kubernetes-the-hard-way/ Kubernetes pods http://kubernetes.io/docs/user-guide/production-pods/ Kubernetes container http://kubernetes.io/docs/user-guide/pods/single-container/ FLOP http://dl.acm.org/citation.cfm?id=2401807 10. Glosario  [1] Micro-servicio: La arquitectura de microservicios es un patrón por el cual una aplicación se divide en módulos o fragmentos que se comunican entre sí mediante peticiones HTTP.  [2] Endpoint: Es el destino final de una conexión o aplicación.  [3] REST: Es un estilo de arquitectura software para sistemas hipermedia distribuidos como la World Wide Web.  [4] Cola de mensajes: Una cola de mensajes tiene características adicionales a una cola normal. Su función es la distribución de mensajes entre procesos, y permite que los clientes bloqueen cuando la cola está vacía. Tiene otras características interesantes como permitir que los clientes se suscriban sólo a ciertos mensajes y no a todos (ver publisher / subscriber). 64  [5] Cola de tareas: Una cola de tareas es, al menos conceptualmente, una extensión de la cola de mensajes que además requiere que, al recibirse una tarea, los clientes lo notifiquen.  [6] Push / Pull: En un escenario en el que hay productores y consumidores, el evento en el que un consumidor solicita datos a un productor se denomina pull, mientras que, si es el productor el que inicia la comunicación de los datos disponibles a los consumidores o les señaliza dicho evento de alguna forma, se denomina push. Cada una de las dos variantes tiene diferentes características de rendimiento.  [7] First come, first serve: El primero en llegar es el primero en salir, como en una fila.  [8] In memory database: Almacena todos sus datos en memoria. Esto implica que las consultas son mucho más rápidas al no tener que leer ni escribir en un disco, pero como contrapartida el riesgo de pérdida de datos es mucho más elevado al no existir persistencia de datos.  [9] Key-value store: Al contrario que una base de datos relacional, una keyvalue store almacena pares clave-valor y no permite realizar búsquedas complejas, o al menos no tan complejas como SQL  [10] Política (de reinicio): “Docker restart policy”, que contiene cuatro políticas de reinicio, “no” (por defecto), “on-failure”, “unless-stopped” y “always”.  [11] Clúster: Es un grupo de máquina que están relacionadas y se comunican en busca de un objetivo.  [12] Exponential backoff: Si el cierre del contenedor se produce antes de 10 segundos desde el arranque, se considera como un fallo y se aplica un “exponential back-off”, que vuelve a intentar lanzar el contenedor.  [13] Prefork: Cuando los recursos instanciados son procesos, a la acción de crear varios procesos y dejarlos en espera se suele llamar pre-lanzamiento o prefork. 65  [14] Balanceo: Es un concepto usado en informática que se refiere a la técnica usada para compartir el trabajo a realizar entre varios procesos, ordenadores, discos u otros recursos.  [15] Orquestación: En el ámbito de los contenedores software, se entiende como orquestación a la gestión unificada y automatizada de dichos contenedores.  [16] Container: Los contenedores no son más que “cajas” aisladas con lo esencial para poder ejecutar un determinado programa o aplicación. Eso se puede entender como una máquina virtual ligera, en vez de las completas y pesadas con las que se trabaja en la virtualización completa.  [17] Demonio: servicio o programa residente es un tipo especial de proceso informático no interactivo, es decir, que se ejecuta en segundo plano en vez de ser controlado directamente por el usuario.  [18] Pool: Un conjunto de recursos disponibles para el sistema que están esperando a ser utilizados se denomina pool.  [19] TAR: Se refiere en Informática a un formato de archivos ampliamente usado en entornos UNIX, identificados por el sufijo de archivo .tar  [20] Dispatcher, executor, task: El nodo Dispatcher es el nodo que recibe la tarea “task”, y la manda a un nodo executor para que la ejecute.  [21] Espera activa, poll: Quedarse en espera constantemente hasta recibir la petición.  [22] Nodo, host: Un host o anfitrión es un ordenador que funciona como el punto de inicio y final de las transferencias de datos. Comúnmente descrito como el lugar donde reside un sitio web. Un anfitrión de Internet tiene una dirección de Internet única (dirección IP) y un nombre de dominio único o nombre de anfitrión (hostname). 66  [23] Websocket: Es una tecnología de comunicación bidireccional desarrollada primeramente para navegadores web, pero que en realidad es independiente de HTTP.  [24] Flannel: Es una red virtual que proporciona una subred a cada host. Kubernetes asume que cada pod tiene una IP única y enrutable dentro del clúster. Reduce la complejidad de realizar asignaciones de puerto.  [25] K8s: Es una abreviación de Kubernetes derivada de las 8 letras de “ubernetes” en 8.  [26] RKT: Fue creado tras encontrar diversos fallos de seguridad en Docker, este nuevo sistema ofrece seguridad en las imágenes del contenedor, previene ataques de escalado de privilegios, más flexibilidad en la publicación de imágenes y portabilidad a otros sistemas de “containerización”.  [27] FLOP: Es un software que sigue la metodología Test Driven Design, el uso de este programa está destinado a la programación y el aprendizaje. Este software alberga problemas de programación, permite al profesor añadir fácilmente otros nuevos y también evalúa automáticamente las soluciones enviadas por los alumnos.