¿Quieres automatizar la infraestructura de tu aplicación? Aprende a cómo hacerlo con Docker

Automatiza la infraestructura de tus aplicaciones usando Docker, en este artículo veremos cómo, aprovechando los conceptos y técnicas de DevOps, tener un ambiente de desarrollo que se comporte igual en producción

jsteven

6 minute read

Visualiza el siguiente escenario: Un programador es contratado para trabajar en el proyecto X, inicialmente la inclusión de nuevos integrantes al equipo de desarrollo era muy sencillo, la persona solo descargaba los repositorios del proyecto, añadía algunas librerías standalone y en su IDE con solo dar Run todo se levantaba correctamente. A medida que el proyecto fue creciendo, se añaden más microservicios, integraciones con servicios externos, certificados de seguridad que se requieren añadir, modificación de archivos Host, entre otros.

Developer

Ahora el nuevo integrante tiene que lidiar con un manual de configuración de casi 100 páginas que al parecer ya nadie actualiza y cada página está llena de agujeros los cuales solo es posible llenar en base “hacks” que sólo los demás miembros del equipo conocen. Finalmente al nuevo integrante le toma cerca de 2 semanas en tener configurado el proyecto y comenzar a trabajar en el desarrollo.

Esto es algo que pasaba y sigue pasando en muchos proyectos, lidiar con la infraestructura nos aleja del objetivo principal del proyecto que es generar valor. Por eso el automatizar la capa de infraestructura es tan importante dentro de la cultura DevOps. Cuando tenemos un ciclo de Continuos Integration o Continuos Delivery no sólo es importante que tengamos buenos test, es también importante eliminar el “en mi local funcionaba” ó “paso desarrollo y laboratorio sin problemas, pero no sé que pasó en producción”. Esto se logra teniendo una capa de infraestructura que se comporta igual en cualquier ambiente sin importar el SO o el IDE que se utilice.

Existen infinidad de técnicas y herramientas que permiten lograr esto, en este artículo trataremos la más básica que es usando Docker, una herramienta que nos permite empaquetar nuestras aplicaciones en contenedores y poder levantarlos en cualquier máquina, ya sea en local, un servidor privado o en la nube. El concepto de contenedor nace a raíz del transporte de mercancías en barcos de carga, un contenedor permite optimizar el transporte de carga al mantenerla protegida de agentes externos como el clima. En el caso de las aplicaciones de software nos permite tener todo el contexto de nuestra aplicación de forma compacta, es decir, dentro de él tiene todo lo que necesita para funcionar y aquí entonces podemos recalcar algo muy importante, para sacarle el mayor provecho a los contenedores debemos desarrollar pequeños módulos, cada uno en su propio contenedor, que realicen una única tarea (microservicios). Desacoplar nuestra aplicación en pequeños módulos nos permite escalar bajo de manda cada uno de forma independiente e incluso en caliente (en tiempo real).

Veamos un ejemplo práctico con una aplicación bastante sencilla pero ilustrativa usando Node, no es el objetivo de este artículo el cómo instalar Docker ya que varía según la plataforma en que te encuentres, pero en (Docker , está toda la información sobre cómo hacerlo. No necesitamos tener Node instalado para correr la aplicación, esto lo haremos a través de Docker.

Comenzamos creando una nueva carpeta en nuestro lugar preferido para pruebas e inicializamos una aplicación Node con un package.json:

$ mkdir docker-node-example
$ cd docker-node-example
$ touch package.json // creamos el archivo
$ touch server.js

Con esto ya tenemos la estructura básica de una aplicación en Node, dentro del package.json vamos a copiar el siguiente código:

{
    "name": "docker-node-example",
    "version": "1.0.0",
    "description": "docker and node example for development",
    "main": "server.js",
    "author": "jsteven",
    "license": "MIT",
    "scripts": {
        "start": "node server.js"
    },
    "dependencies": {
        "express": "^4.16.2"
    }
}

Con esto ya tenemos las dependencias que necesitamos para un servidor usando express. Adicionalmente, en el server.js copiamos lo siguiente:

'use strict';

const express = require('express');

// Constantes
const PORT = 3200;
const HOST = '0.0.0.0'; // Hacemos bind a cualquier ip

// Inicializar App
const app = express();
// servir los archivos estáticos
app.use(express.static('public'));

app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);

Simplemente estamos sirviendo archivos estáticos que se encuentren en el directorio public, pero podría ser el punto de entrada de nuestra aplicación, nuevamente es solo para temas ilustrativos. Creamos una nueva carpeta llamada public y dentro de ella un archivo index.html con el siguiente código:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <!-- Latest compiled and minified CSS -->
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
        crossorigin="anonymous">
        <title>Docker - Example</title>
    </head>
    <body>
        <div class="container">
            <h1>Ejemplo Docker y Node</h1>
            <a href="https://www.jsteven.co" class="btn btn-success">Saber mas...</a>
        </div>
        <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
            crossorigin="anonymous"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
            crossorigin="anonymous"></script>
    </body>
</html>

Después de esto ya tenemos una aplicación lista para ser Dockerizada, primero creamos un archivo Dockerfile en la raíz de nuestro proyecto:

# Obtenemos una imagen con Node instalado
FROM node:boron
# Establecemos nuestro directorio de trabajo
WORKDIR /usr/src/app
# Copiamos nuestro package.json para instalar las dependencias
# No copiamos todo el proyecto de una vez para aprovechar la cache de imágenes
COPY package.json .
# Instalamos las dependencias del proyecto
RUN npm install
# Copiamos todo el proyecto
# Con dockerignore vamos a evitar copiar archivos innecesarios como node_modules 
# o el repositorio de git
COPY . .
# Exponemos el puerto por el cual nuestra aplicación se comunicará hacia afuera, este debe ser el mismo por
# el que tenemos escuchando nuestra app de express
EXPOSE 3200
# Finalmente especificamos el comando para iniciar nuestra app, este será el que se corra cuando iniciemos el contenedor
CMD [ "npm", "start" ]

Todo el código de este ejemplo estará disponible en mi repositorio GitHub. Hasta este momento ya tenemos lo necesario para ejecutar nuestra aplicación desde un contenedor, para esto vamos a la consola y ejecutamos los siguientes comandos:

$ docker build -t jsteven/docker-node-example .

Con esto creamos la imagen a partir de nuestro Dockerfile y le asignamos una etiqueta para identificarla fácilmente (no olvidar el . al final). una vez termine de construir nuestra imagen podemos verla con el comando:

$ docker images

Ahora que tenemos nuestra imagen podremos crear contenedores a partir de ella, esto es importante, cada vez que modifiquemos el código de nuestra aplicación debemos hacer un build de la imagen de nuevo, afortunadamente la caché de Docker nos ayuda a que este proceso no tarde tanto la siguiente vez que construyamos la imagen. En futuros artículos explicaré como automatizar la construcción de estás imágenes y los contenedores usando Docker Compose.

Para crear un contenedor de nuestra app ejecutamos:

$ docker run -p 49160:3200 -d jsteven/docker-node-example

Recuerden que en la aplicación y en el Dockerfile definimos el puerto 3200 como nuestro punto de entrada, ahora lo que hacemos es mapear ese puerto del contenedor a el puerto 49160 de la máquina en la que estamos ejecutando Docker (tambiés es posible que estemos haciendo un attach a un Docker que está en Amazon por ejemplo), esto nos permite dirigirnos a localhost:49160 y ver nuestra aplicación en vivo.

Docker App

Con esto ya tenemos nuestra aplicación en un contenedor y podremos compartirla con cualquier miembro del equipo sin ningún problema, nuestro compañero solo debe hacer el build de la imagen, arrancar el contenedor y Voila, puede comenzar a trabajar desde el minuto cero.

Como vimos este es solamente el inicio de lo que significa automatizar la infraestructura, próximamente entraré mas a fondo en temas de gestión y orquestación que nos ayudan a manejar los escenarios en que tenemos muchos microservicios y levantarlos uno a uno se vuelve una tarea tediosa. También ahondaré aun más en la optimización de imágenes y tips que ayudan a gestionar la memoria que utilizan los contenedores de Docker. Déjame tus opiniones acerca de cómo enfrentas en tus aplicaciones los temas de infraestructura. Hasta pronto.

comments powered by Disqus