Hoy vamos a ver qué es docker y por qué es tan importante y usado en el mundo SecDevOps. En muchas entradas del blog, habréis podido ver muchas entradas relacionadas con docker, desde cómo instalar docker en Ubuntu, dockerizar una aplicación con Spring Boot, capturar tráfico, hasta cómo depurar nuestras aplicaciones en docker.
Pero, ¿qué es docker?
Introducción a Docker
Docker es una plataforma open source, desarrollada por Solomon Hykes, que permite crear y administrar contenedores. Los contenedores podemos definirlos como una capa de virtualización a nivel de sistema operativo. Se podría entender como una evolución de una de las principales características de Linux, chroot, el cual permite aislar o «enjaular» procesos, de manera que este proceso «enjaulado» no pueda acceder ni ejecutar ficheros que estén fuera del directorio en el cual se aplica el comando.
Esta evolución dio lugar a los Linux Containers (LXC) que se basan en namespaces y cgroups ofreciendo un entorno de virtualización real utilizando contenedores. De esta manera, se pueden dividir y aislar procesos dándole diferentes funciones del sistema operativo. Y tras esto, apareció Docker, el cual se basa en estos LXC.
Diferencias entre Máquinas Virtuales y contenedores Docker
Cuando hablamos de virtualización, lo primero que nos viene a la cabeza son las máquinas virtuales. Sin embargo, existe ciertas diferencias entre máquinas virtuales y contenedores docker. La principal de ellas es que Docker no requiere un sistema operativo completo en cada contenedor.
Una máquina virtual requiere la instalación de un sistema operativo, mientras que Docker comparte las funcionalidades del sistema operativo anfitrión. Esto se traduceen un menor consumo de recursos de la máquina anfitriona, haciendo que los contenedores sean más ligeros y rápidos que las máquinas virtuales, ya que no necesita tanto espacio para contener el sistema operativo (ni en disco ni en memoria) y no necesita levantar un sistema operativo para ejecutarse.
Componentes de Docker
Es importante conocer los distintos componentes de los que consta Docker para comprender su funcionamiento y poder trabajar con él.
Los principales componentes de Docker son:
- Cliente Docker: Se usa como herramienta de comunicación entre el usuario y el demonio docker. Es el encargado de gestionar las operaciones que se realizan sobre los contenedores docker.
- Demonio Docker: El demonio docker es el encargado de gestionar los contenedores y los servicios necesarios. Este demonio se instala en el host donde queramos ejecutar docker
- Registro Docker: Es donde se almacenan las imágenes docker, el cual puede ser público (como el docker hub) o privado.
- Imagen Docker: Contienen las imágenes que se usan como base, además de las plantillas (con las librerías y binarios necesarios) que se usan para crear las imágenes docker.
- Contenedores Docker: El contenedor docker es el ejecutable que contiene el conjunto de librerías, carpetas, binarios, etc. necesarios para ejecutar la aplicación que queremos lanzar. Los contenedores docker se basan en alguna imagen a la cual podemos añadir nuestra aplicación para poder ejecutarla.
- Dockerfile: Este fichero sirve para construir imágenes a partir de otras, indicando la imagen base, los ficheros a copiar en la nueva imagen, los comandos a ejecutar, etc.
Resumiendo el proceso, el cliente docker ejecuta el comando docker build para indicar al docker daemon que quiere crear una nueva imagen a partir del dockerfile (por defecto, se utiliza el dockerfile existente en el directorio donde ejecutamos el comando, pero se puede indicar otro fichero). El docker daemon irá añadiendo capas a la nueva imagen, basándose en una imagen base indicada, según vaya procesando el dockerfile. Una vez generada la imagen, la cual puede también subirse al registro docker, se crea un contenedor a partir de esa imagen.
Además de crear imágenes, existe la opción de descargar una imagen ya existente en el registro docker. Para ello el cliente docker ejecuta el comando docker pull para indicar al docker daemon que queremos descargar una determinada imagen.
Una vez tenemos la imagen creada (o descargada) y el contenedor creado, el cliente docker puede indicar al docker daemon que quiere ejecutar un contenedor mediante el comando docker run.
Uso de docker en el mundo SecDevOps
Teniendo ya una idea general de qué es Docker, vamos a ver por qué su uso se ha extendido tanto y su importancia en el mundo SecDevOps.
Si recordamos el muro que existía entre desarrolladores y administradores de sistemas cuando hablábamos de qué es SecDevOps, nos podemos dar cuenta de que Docker es ideal para salvar ese muro. Uno de los problemas que nos podemos encontrar a la hora de desplegar una aplicación, es que el entorno de desarrollo sea distinto al entorno de producción. Pensemos por ejemplo que estamos desarrollando una aplicación Java. El desarrollador podría tener instalada una versión de Java distinta a la que hay en el entorno de producción, ya que podría estar trabajando en otro proyecto en que se requiriera una versión más reciente de Java y desarrollar la nueva aplicación en base a esa versión. Esto podría dar problemas de compatibilidad a la hora de desplegar la aplicación.
Si ya desde el momento en que empieza el desarrollo se define un entorno de ejecución, se puede generar una imagen docker con una determinada versión de Java, entre otras cosas. Esta imagen será la misma que utilice un desarrollador para generar un contenedor y ejecutar su aplicación mientras desarrolla, que la que se utilizará para desplegar en el entorno de producción.
Además de resultar muy útil para salvar ese muro que existía entre ambos mundos, para un desarrollador docker resulta de mucha utilidad. Ya no solo nos va a librar de tener que instalar una versión distinta de lo que necesitemos en cada desarrollo, sino que podemos desplegar otros servicios en nuestro entorno de desarrollo sin necesidad de instalar nada.
Un ejemplo sencillo es tener una base de datos. Antiguamente era bastante común tener un servidor donde se instalaba y configuraba una base de datos, donde todos los desarrolladores se conectaban para desarrollar sus aplicaciones que necesitaran acceso a una base de datos. Ante la escasez de recursos, ese mismo servidor podía albergar distintas bases de datos para los distintos proyectos. Si este recurso de repente fallaba, todos los desarrolladores se veían paralizados hasta que el problema estuviera resuelto. Además, si alguien necesitaba hacer una «limpieza», podía afectar al trabajo de otro compañero.
Docker apenas necesita recursos para ejecutarse (recordemos que a diferencia de una máquina virtual, no necesita instalar un sistema operativo completo), por lo que todos los desarrolladores pueden levantar fácilmente un servicio de base de datos en su máquina local sin necesidad de compartir dicho recurso.
Otro ejemplo puede ser un desarrollador frontend, el cual puede basar su desarrollo en llamadas a servicios rest desplegados en un servicio a parte. Nuestro desarrollador puede igualmente desplegar los servicios de base de datos y servicios rest en su máquina local sin depender de tener acceso a un servicio externo y compartido. Y así hasta poder incluso desplegar una arquitectura compleja de alta disponibilidad, con varios servicios rest, un cluster de bases de datos, etc.
También resulta útil en el ámbito de la seguridad. Un pentester por ejemplo, que vaya a auditar una aplicación web. No solo le va a resultar más sencillo poder trabajar con una imagen docker de la aplicación a la que atacar, sino que se evitan riesgos innecesarios. Si por error o bien por las características del ataque que esté ejecutando (pensemos en un ataque de fuerza bruta para conseguir contraseñas o un DDoS) tira el servicio y este es un servicio compartido (un entorno de producción, un entorno de pruebas o incluso de desarrollo), habrá dejado el servicio caido durante un tiempo, impidiendo que otros puedan trabajar sobre él.