Hace unos días recibí la invitación para probar Docker para Mac y Windows, bueno hace unas semanas realmente, y desde entonces quería escribir un post sobre las pruebas que he hecho en Mac con Docker y ASP.NET Core.
La intención de este post no es explicar qué es Docker, ni qué es ASP.NET Core en detalles, ni cómo fue implementada la aplicación web de ejemplo, el cual es muy sencillo, sino más bien mostrar los pasos que seguí para ejecutar una aplicación web ASP.NET Core desde un contenedor Docker utilizando el beta para Mac.
Docker
Tomado de su sitio web:
Los contenedores de Docker envuelven una pieza de software en un sistema de archivos (filesystem) que contiene todo lo que dicho software necesita para ejecutarse: código, entorno de ejecución, herramientas y librerías del sistema – cualquier dependencia que normalmente instalarías en un servidor. Esto garantiza que siempre se ejecutará de la misma manera sin importar el entorno en que este ejecutándose.
Es importante entender qué es un contenedor y de igual manera qué lo diferencia de una máquina virtual, esa información la pueden entontrar en la página referenciada anteriormente y haciendo una búsqueda rápida en Google ya que Docker es muy popular por estos días.
Para conocer un poco mas sobre el programa de beta de Docker para Mac puedes leer el post acerca del tema en el blog oficial de Docker.
Aplicación Web de ejemplo
La aplicación que vamos a desplegar en un contenedor Docker es muy sencilla, pueden encontrar el código de ejemplo en: https://github.com/geykel/HolaAspNetCoreDocker
Para ejecutar la aplicación localmente necesitamos instalar .Net Core para OSX el cual podemos obtener desde https://www.microsoft.com/net/core#macosx, luego clonamos el repositorio, ejecutamos dotnet restore para restaurar los paquetes NuGet y luego dotnet run para ejecutar la aplicación, una vez que esté ejecutándose podemos apuntar el navegador de nuestra preferencia a la dirección http://localhost:5000 y veremos el resultado, la página que aparecerá debe mostrar los logos de Docker y ASP.NET junto a algunos enlances.
Docker beta para Mac
Luego de recibir la invitación, solamente debemos descargar el archivo .dmg que contiene la aplicacion de Docker, abrimos el .dmg y arrastramos la aplicación a tu carpeta Applications.

Cuando abrimos Docker por primera vez nos va a pedir permisos de root para instalar algunas cosas y luego veremos el icono de Docker en la barra de sistema de OSX.

Y eso es todo lo que necesitamos. Si habías utilizado Docker anteriormente en Mac sentirás el alivio de no tener que instalar todas las herramientas que eran necesarias para utilizar Docker anteriormente (bueno…, aún lo necesitas porque aún es un beta).
Manos a la obra
Lo primero que podemos hacer es ejecutar algunos comandos básicos para ver que Docker está funcionando, como:
docker info
docker images
.NET Core Image
Actualmente Microsoft está manteniendo una imagen oficial de .NET Core en Docker Hub. Vamos a probar descargandola ejecutando el siguiente comando:
docker pull microsoft/dotnet:latest
Vamos a probar ejecutar dicha imagen de forma interactiva, crear una aplicación de consola y ejecutarla dentro del contener:
Como podemos ver, ejecutamos el comando docker run -it microsoft/dotnet:latest el parámetro -it es el que especifica que va a ejecutarse de forma interactiva, es por ello que luego de dicho comando vemos un prompt bash. Luego con mkdir prueba creamos la carpeta “prueba” y nos cambiamos a “prueba” ejecutando cd prueba. Utilizando el comando dotnet new del CLI de .NET Core, creamos una aplicación de consola básica, ejecutando el comando ls vemos que se han creado los archivos Program.cs y project.json dentro de la carpeta “prueba”, ahora con dotnet restore restauramos todos los paquetes NuGet, veremos como comienzan a descargarse e instalarse los paquetes, luego de instalados todos los paquetes ejecutamos la aplicación con dotnet run, como vemos en la siguiente imagen:
Finalmente ejecutamos cat Program.cs para ver que efectivamente el programa de consola lo que hace es imprimir el texto “Hello World!”. Todo esto fue ejecutado dentro del contenedor de Docker, para salir ejecutamos exit.
El Dockerfile
Un Dockerfile es un archivo texto que contiene todas las instrucciones que un usuario escribiría en el terminal para ensamblar una imagen. Docker puede construir automaticamente imágenes leyendo las instrucciones que estan en un Dockerfile. Con el comando docker build y archivos Dockerfile podemos automatizar el proceso de construcción de imágenes.
Podemos echar un vistazo al archivo Dockerfile que se encuentra en la aplicación de ejemplo que vamos a utilizar:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FROM microsoft/dotnet | |
COPY . /app | |
WORKDIR /app | |
RUN ["dotnet", "restore"] | |
RUN ["dotnet", "build"] | |
EXPOSE 5000/tcp | |
ENTRYPOINT ["dotnet", "run"] |
A partir de este Dockerfile vamos a construir nuestra imagen.
En la primera línea podemos ver que utilizamos el comando FROM para establecer nuestra imagen base, FROM debe ser el primer comando en todo Dockerfile (sin contar los comentarios). En nuestro caso vamos a utilizar como base la imagen oficial de .NET Core (microsoft/dotnet).
En la línea 3 utilizamos el comando COPY para copiar el contenido de nuestra aplicación a la carpeta /app del contenedor. En este caso vamos a copiar todo el código de la aplicación desde nuestra Mac porque vamos a construirla y ejecutarla dentro del contenedor, puede darse el caso en que quieras solo copiar los binarios y recursos necesarios para ejecutar la aplicación y también puede darse el caso en que no quieras copiar directamente desde tu Mac o estación de trabajo sino descargar la aplicación desde un repositorio de código fuente, por ejemplo, entornos de integración continua y/o entrega continua.
Utilizamos WORKDIR en la línea 4 para establecer la /app como carpeta de trabajo para otros comandos como RUN, ENTRYPOINT, CMD, ADD y COPY.
En las líneas 6 y 7 utilizamos el comando RUN para ejecutar dotnet restore y dotnet build para restaurar los parquetes NuGet y construir la aplicación respectivamente.
En la línea 9 informamos a Docker que el contenedor va a escuchar en el puerto 5000 en tiempo de ejecución y para ello utilizamos EXPOSE. Aquí debemos hacer una pausa para destacar algo muy importante, si vemos el código de la clase Program en el archivo Program.cs de nuestra applicación:
using System.IO; using Microsoft.AspNetCore.Hosting; namespace HolaAspNetCoreDocker { public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseStartup() .UseUrls("http://*:5000") .Build(); host.Run(); } } }
Veremos como explícitamente en la línea 14 espeficicamos las urls en las que el “web host” de nuestra aplicación va a escuchar, esto es algo propio de ASP.NET Core, no de Docker, en Docker utilizamos el comando EXPOSE en el Dockerfile.
En la última línea utilizamos ENTRYPOINT para configurar qué comando se va a ejecutar en el contener cuando utilicemos el comando docker run, en este caso dotnet run. y como con WORKDIR especificamos que la carpeta de trabajo es /app, lo que estamos haciendo cuando ejecutamos esta imagen es ejecutar la applicación.
Construcción y ejecución de la imagen
Primeramente vamos a ejecutar docker images para ver que imágenes tenemos localmente:

Como vemos, en mi caso, sólo tengo imágenes oficiales de Docker Hub, vamos a construir una nueva imagen a partir de nuestro Dockerfile, llamaremos a la nueva imagen “holadocker”, nos movemos a la carpeta donde se encuentra dicho archivo y ejecutamos el comando docker build…, como vemos en la imagen:

Como vemos ejecutamos el comando docker build -t holadocker ., el parámetro -t nos permite especificar el nombre de la nueva imagen, en este caso “holadocker”. Debemos notar el signo “.” luego de la palabra “holadocker”, el mismo especifica el contexto del comando build que no es más que un camino, pero MUY importante, ver más detalles en la documentación de build.
Podemos ver como se van ejecutando las instrucciones del Dockerfile paso a paso. Luego del paso 4, que es el dotnet restore, sigen el resto:

Ahora podemos verificar que efectivamente se ha creado la imagen, ejecutando docker images:

Y vemos “holadocker” como en el listado de imágenes.
Vamos a ejecutar dicha imagen de forma interactiva, muy similar a como ya hicimos con la imagen microsoft/dotnet, ejecutamos docker run -it -p 80:5000 holadocker, aquí estamos utilizando un parámetro que no habíamos utilizado antes, -p, el cual nos permite mapear un puerto del sistema operativo host (OSX) a un puerto del contenedor, en este caso del puerto 80 al puerto 5000 del host que es donde va a estar escuchando nuestra aplicación.

Esta primera ejecución de forma interactiva la hicimos para poder ver los logs que va generando la aplicación, todos esos mensajes que podemos ver (las líneas verdes) se generan porque en mi caso ya accedí en el navegador a http://localhost por lo que se empiezan a generar los logs en el terminal, cuando accedemos a localhost por defecto se utiliza el puerto 80, si en tu caso estas utilizando el puerto 80 con otro servicio pueder ejecutar la imagen con otro mapeo de puerto por ejemplo -p 5000:5000, y en ese caso tendrás que acceder a http://localhost:5000, en el navegador podrás ver ya la aplicación funcionando desde el contenedor:

Otro modo de ejecutar una imagen es hacerlo en el fondo (background) como un servicio, esto es conocido como modo Detached, con el parámetro -d.

Podemos ver como el cursor va a regresar al promt del host (no se ve en la imagen anterior pero lo verán en sus computadoras) pero sin embargo el contenedor está ejecutándose en el fondo, podemos verlo ejecutando docker ps.

Ahí vemos como dice en la columna STATUS “up 55 seconds”, o sea el contenedor lleva 55 seguntos ejecutándose.
Ahora podemos volver a apuntar en el navegador a http://localhost (si es que utilizaron -p 80:5000) y veremos la aplicación web. Como este no es el modo interactivo no podremos ver los logs en el terminal.
Y eso es todo en este post. Pueden seguir probando más cosas por su cuenta 🙂
Es muy chévere empezar a ver como con .NET Core ya podemos traer al ecosistema de .NET al mundo de Docker y los contenedores en sentido general.