Crea un servidor Cloud con Docker y despliega una aplicación en un contenedor

Docker es una plataforma para desarrollo y despliegue de aplicaciones, en entornos aislados que se conocen como contenedores. Existen muchas ventajas en el uso de contenedores, de las que ya hemos hablado en varias ocasiones. Para obtener más información en este sentido podemos ver la página sobre la aplicación Docker en Arsys. En este artículo pretendemos ofrecer una explicación sencilla sobre cómo desplegar una aplicación con Docker en un servidor cloud de Cloudbuilder.

Crear un servidor cloud con Docker

Crear un servidor cloud e instalar Docker es algo que podemos hacer de manera casi inmediata en Cloudbuilder. El proceso se puede resumir prácticamente a unos golpes de clic, ya que tenemos disponible la posibilidad de crear el servidor con Docker instalado de antemano.

Conseguirlo es tan sencillo como crear un servidor cloud. En el formulario de alta del servidor, en la zona de «Imágenes» encontraremos la pestaña «Aplicaciones». Allí podremos encontrar la aplicación de Docker, que tenemos que seleccionar. Si no aparece la aplicación directamente, podemos buscarla usando el buscador por palabras de la derecha. Una vez seleccionada la imagen, el servidor se creará pasados unos minutos.

Crear una aplicación con NodeJS

Por supuesto, si queremos desplegar una aplicación con Docker, tendremos que haber construido la aplicación previamente Ya de por sí, este es un tema suficientemente complejo como para abordarlo de manera independiente, pero vamos a dar unos pasos rápidos para cubrir unas necesidades mínimas.

Nota: En nuestra práctica vamos a usar NodeJS, aunque por supuesto podemos usar Docker con cualquier stack de tecnologías.

Comenzaremos creando una carpeta para el proyecto completo, con el nombre que se desee. Luego, dentro de la carpeta del proyecto, vamos a crear un subdirectorio llamado «app». En ese subdirectorio es donde construiremos una sencilla aplicación en NodeJS. Dentro de la carpeta «app» ejecutamos el comando para iniciar el proyecto con npm:

npm init

Después de contestar las preguntas del asistente se habrá construido el archivo package.json y podemos instalar las dependencias de la aplicación.

npm install express

Con ese comando habremos instalado Express para crear fácilmente una aplicación basada en NodeJS. Todo el código de la aplicación lo vamos a incluir en un archivo llamado index.js, que colocaremos en la carpeta raíz del proyecto. Nuestra app, en su mínima expresión, podría tener un código como este:

// Requerimos Express
const express = require('express');
// Creamos la aplicación
const app = express();
// Construimos una ruta
app.get('/', function (req, res) {
  res.send('Bienvenidos a mi aplicación NodeJS');
});
// Arrancamos el servidor
app.listen(3030, function () {
console.log('Arrancada la aplicación en http://localhost:3030');
});

Una vez creado el index.js y escrito el anterior código fuente, podremos arrancar el servidor desde el terminal con el comando siguiente, lanzado desde la carpeta «app»:

node index

Una vez en marcha el servidor, podemos ver la aplicación funcionando en la ruta: http://localhost:3030

Veremos un sencillo mensaje en el navegador, ya que nuestra aplicación es meramente un «Hola mundo» en Node y Express.

Dockerizar la aplicación

Ahora, si deseamos arrancar la aplicación con Docker, tenemos que hacer algunos pasos extra y por supuesto, apoyarnos en las herramientas de Docker que tenemos instaladas en local.

Archivo dockerfile

El primer paso será crear un archivo dockerfile para la aplicación, que nos permita definir cómo ha de configurarse el contenedor. Veremos un sencillo archivo que usaremos como punto de partida. Este archivo se guardará en la ruta «app/dockerfile».

FROM node:12
WORKDIR /usr/src/app
ADD . /usr/src/app
RUN npm install
EXPOSE 3030

Gracias al código anterior estamos indicando que vamos a usar una imagen de contenedor con NodeJS 12, estamos definiendo el directorio de trabajo y el comando para arrancar la aplicación, así como el puerto va a exponer hacia afuera. Además en nuestra carpeta «app» convendría realizar el correspondiente .dockerignore, indicando como contenido, al menos la carpeta node_modules.

Archivo docker-compose.yml

En las aplicaciones generalmente tenemos que trabajar con varios contenedores, donde se colocará cada servicio por separado. Todos los contenedores, por supuesto, pueden trabajar de manera combinada y comunicarse unos con los otros para cumplir los objetivos de las aplicaciones. Por ejemplo, una aplicación típica podría tener contenedores para los siguientes servicios:

  • Frontend, con el código de la aplicación frontend, que podría estar construido en una tecnología como Angular, o cualquier otra.
  • Backend, con el código de un servicio web al que se conectará la aplicación frontend para obtener los datos, que podríamos construir usando Express, por ejemplo.
  • MongoDB, con la base de datos MongoDB para que nuestro backend la pueda usar.

Para lograr que nuestra aplicación pueda desplegar todos los contenedores necesarios y que éstos puedan trabajar correctamente los unos con los otros, vamos a usar Docker Compose. Se trata de un complemento muy usado con el propio Docker.

Nota: nuestro proyecto en realidad es muy sencillo y usará un único contenedor, pero veremos igualmente cómo configurar Docker Compose para hacernos una idea más exacta de los pasos típicos en el despliegue de aplicaciones con Docker.

El archivo «docker-compose.yml» lo colocaremos en la raíz de la aplicación y tendrá un código como el que sigue:

version: "3.8"
services:
  express:
    build: ./app
    ports:
      - "3030:3030"
    command: node index

Con el código anterior estamos indicando la versión de Docker Compose y el servicio que vamos a levantar en el contenedor. Sobre el servicio indicamos la ruta y la redirección de puertos, así como el comando para arrancarlo. Estos serían los pasos básicos para crear la estructura de nuestra aplicación. Una vez terminado de componer este código tendríamos más o menos esta estructura de carpetas y archivos.

Despliegue con Docker

Ahora vamos a realizar el despliegue de la aplicación. Tendremos que realizar una serie de pasos. El primero será conectarnos por SSH con el servidor que hemos creado en el primer paso de este tutorial. Una vez dentro del servidor tendremos que traernos el código del proyecto. Aquí lo normal sería clonar el repositorio donde hemos podido almacenar el código, pero también podríamos subir los archivos simplemente por SCP.

Instalar docker-compose

Una vez copiados los archivos de código de la aplicación, procedemos a lanzar el comando docker-compose para ponerla en marcha. Este comando hay que instalarlo de manera independiente al propio Docker. Es decir, aunque la máquina ya viene con Docker instalado, el comando docker-compose no se encuentra disponible de manera predeterminada en las instalaciones de Linux. Los comandos necesarios para hacer esta instalación son los siguientes:

sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

Nota: para resolver posibles dudas o obtener datos adicionales sobre esta instalación, por favor, revisa la página Install Docker Compose

Arrancar los contenedores

Ahora ya podemos arrancar los contenedores de nuestro proyecto. Sólo teníamos uno en realidad, pero recuerda que gracias a Docker Compose podríamos haber configurado varios. El arranque lo realizamos mediante este comando:

docker-compose up

La primera vez que ejecutamos este archivo veremos que tarda algo más que las siguientes veces. El motivo es que se tiene que descargar las imágenes del contenedor y las dependencias del proyecto con npm.

Acceder al servidor de la aplicación

Ahora que tenemos los contenedores funcionando ya podemos acceder a la aplicación y comprobar si funciona todo como esperábamos. Sin embargo, hay que tener en cuenta un detalle extra.

Se trata de activar el puerto correspondiente del firewall. Recuerda que nuestra aplicación estaba escuchando solicitudes en el puerto 3030 y que no llegamos a realizar ninguna redirección de puertos específica. Por tanto, ese puerto tendrá que estar abierto para poder ver alguna cosa desde fuera.

Los puertos los puedes configurar en Cloudbuilder creando una política de firewall personalizada y luego asignando esa política al servidor. Para ver los pasos detallados puedes consultar el post dedicado a las políticas de firewall. Una vez habilitado el puerto correspondiente, podremos acceder a nuestra aplicación a partir de la IP del servidor: http://IP_DEL_SERVIDOR:3030

Docker Portainer

Para administrar los contenedores Docker de un servidor y facilitar el despliegue de las aplicaciones existe una aplicación de escritorio llamada Docker Portainer, que puede resultar muy útil en diversos escenarios.

Básicamente se trata de una interfaz gráfica para la administración de los contenedores instalados en un servidor, que funciona vía web. Gracias a ella podemos ahorrarnos la complejidad de administrar nuestras instalaciones por medio de la consola de comandos, lo que ahorra tiempo en el despliegue y el trabajo en el día a día en la administración de las aplicaciones.

Portainer funciona sobre un contenedor, por lo que es igualmente necesario tener Docker instalado en el servidor. En el caso que deseemos administrar contenedores Docker que tenemos en local obviamente tendremos Docker instalado, pero también podría darse el caso de administrar los contenedores de servidores remotos, para lo cual Portainer comunica vía Docker API o mediante la instalación del Portainer Agent que ofrece prestaciones adicionales. La instalación de Portainer en el servidor se realiza mediante este comando:

docker volume create portainer_data

Posteriormente podemos arrancar el contenedor con este comando:

docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

Por supuesto, también podríamos crear una configuración de arranque usando Docker Compose. Encontrarás más información sobre este asunto en la documentación.

Una vez lo tenemos instalado se encontrará arrancado en el puerto 9000 del servidor. Podrás acceder a través de su IP y el puerto, pero recuerda que para que la conexión se pueda realizar es necesario abrir el puerto en la configuración del firewall. Una vez instalado y creado nuestro usuario administrador (pedirá que creemos una clave en el primer acceso), encontramos la pantalla de creación de conexiones.

Seleccionando conexión local, ya que vamos a administrar los contenedores instalados en la misma máquina, accedemos a la pantalla que resume todas las conexiones que tenemos configuradas. De momento solamente hay una, pero podríamos desde esta misma instalación de Portainer conectarnos con varios servidores remotos y administrarlos todos desde un único lugar.

De una manera muy intuitiva podemos navegar por los menús y encontrar pantallas que resumen la configuración y administración de los contenedores. Por ejemplo, esta es la pantalla de gestión de los contenedores de un servidor.

Encontraremos mayores informaciones y la documentación completa en la página de Portainer.

Conclusión

Con estos pasos hemos conseguido construir una aplicación sencilla, dockerizarla para poder ejecutarla en el contexto de contenedores de Docker y posteriormente desplegarla con la ayuda de Docker Compose.

La aplicación que hemos realizado es muy sencilla, pero la comunidad ofrece numerosos repositorios donde encontraremos configuraciones mucho más avanzadas, que hacen uso de varios servidores, usando Apache o Nginx como proxy inverso sobre el puerto 80 y mucho más. En este sentido queremos recomendaros un repositorio que ofrece toda una compleja configuración de Docker para usar el stack MEAN. Por supuesto, en GitHub encontrarás proyectos que usan cualquier stack de tecnologías imaginable.