Docker para el desarrollador de asp.net (iii)

En el post anterior vimos como empaquetar y desplegar en Docker una sencilla aplicación (un hello world) en asp.net core. En este post vamos a ver como desplegar en Docker una aplicación asp.net core (con sus controladores y vistas) y también ver como lo podemos usar usando una imagen base que no tenga el SDK, solo el runtime.

¡Vamos allá!

Creando el proyecto

Vamos a usar ya Visual Studio 2015 para la creación del proyecto. De momento seguimos con la preview2 de las herramientas (es decir con project.json). Para crear el proyecto simplemente abrimos VS2015 y creamos un nuevo proyecto de tipo “ASP.NET Core Web Application (.NET Core)” y luego seleccionamos la plantilla de “Web Application”. Con eso VS2015 nos creará una aplicación con un HomeController que muestra la clásica vista inicial de ASP.NET. No haremos más cambios al proyecto, con esto nos basta 🙂

Publicando el proyecto

Vamos a crear una imagen de Docker para servir esta aplicación web desde un contenedor. A diferencia del post anterior donde partíamos de una imagen base que tenía el SDK de NetCore, en este caso vamos a partir de una imagen que tenga solo el runtime. Si lo piensas tiene sentido: qué necesidad tenemos de compilar (con dotnet run) la aplicación cada vez que levantamos un contenedor? Podemos compilarla una sola vez y desplegar una imagen con el resultado de la compilación. De hecho, es lo que haríamos si lo desplegaramos en una WebApp de Azure o en un IIS ¿no? Pues no hay motivo alguno para tratar Docker de forma distinta.

Eso nos implica que debemos publicar nuestra aplicación y copiar el resultado de la publicación a la imagen. Vamos a ver como.

Para ello empecemos por añadir un Dockerfile a la solución y establezcamos la imagen base:

FROM microsoft/aspnetcore:1.0.1

Esa es la que vamos a usar que contiene el runtime de asp.net core (¡pero no el SDK!)

El siguiente paso es publicar la aplicación en local, esto se puede hacer desde la lína de comandos de forma trivial. Abre una línea de comandos y sitúate en el directorio donde hay el project.json. Luego teclea “dotnet publish -o app” para publicar la aplicación.

Es posible que recibas un error: No executable found matching command “bower”. Eso es debido a que la plantilla por defecto de VS2015 utiliza bower (un gestor de paquetes JavaScript) y no lo tienes instalado en tu máquina. Personalmente no soy muy amigo de usar bower, pero bueno… Dado que la plantilla por defecto, lo requiere, vamos a ver como podemos hacerlo. Una solución seria installar bower a nivel global (npm install –g bower) pero es recomendable instalarlo de forma local (es decir, solo para tu proyecto). Con eso te aseguras de que la versión de bower que requiere tu proyecto es la que tiene instalada. Visual Studio 2015 debería gestionar eso, pero no lo hace (asume que bower está instalado de forma global). Veamos como podemos arreglarlo de forma rápida. Para ello desde el propio Visual Studio, añadimos un elemento al proyecto (Add –> New Item) y seleccionamos “npm Configuration file” (si no te aparece entra el texto “package.json” en el cuadro de búsqueda):

SNAGHTML313e81

(Por supuesto, puedes usar npm init desde la línea de comandos, si así lo prefieres).

Ahora que ya tenemos un package.json ya podemos añadir dependencias npm. Eso lo necesitamos porque bower es un paquete npm. Ahora abre el package.json con VS2015 y añade bower como una dependencia de desarrollo:

"devDependencies": {
  "bower": "^1.8.0"
}

(Si usas la línea de comandos puedes conseguir el mismo efecto con npm install bower –save-dev)

Eso nos instala bower a nivel local, pero la publicación directamente seguirá dando un error, porque bower no está en el PATH (está instalado en .\node_modules\.bin, siendo “.” el directorio inicial del proyecto). No es necesario que lo añadas, al path, vamos a modificar el project.json y colocar la ruta. Para ello abre el project.json y edita la sección “scripts” para añadir el path entero de bower:

"scripts": {
  "prepublish": [ "./node_modules/.bin/bower.cmd install", "dotnet bundle" ],
  "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}

¡Ahora sí, que sí! Abre una consola de línea de comandos y publica la aplicación “dotnet publish –o app”. Ahora no debería darte errores:

image

Eso ha creado un directorio app con nuestra aplicación publicada. Esta publicación contiene todos los paquetes de NuGet necesarios para ejecutar la app, junto con la aplicación compilada y la carpeta wwwroot. Solo falta… meterlo en el contenedor.

Creando la imagen

Volvamos a nuestro Dockerfile. Tenemos que copiar el directorio app de nuestra app al contenedor y coloca el siguiente código:

FROM microsoft/aspnetcore:1.0.1
ENTRYPOINT ["dotnet", "DemoWebApp.dll"]
WORKDIR /app
EXPOSE 80
COPY ./DemoWebApp/app .

Este Dockerfile contiene algunas sentencias nuevas, en concreto ENTRYPOINT y EXPOSE, vamos a analizar lo que hace línea por línea:

  1. Usa la imágen base microsoft/aspnetcore:1.0.1 que contiene el runtime de asp.net
  2. Con ENTRYPOINT le indicamos al contenedor cual es el proceso que debe lanzar al inicio. Quizá te preguntes cual es la diferencia con CMD que vimos en el post anterior. Es muy sencillo: recuerda que un contenedor lanza un solo proceso. Por defecto este proceso es /bin/sh –c. El valor de CMD es lo que pasa como parámetro al –c. Ahora bien, puede ser que no queramos ejecutar /bin/sh, sino otro ejecutable al iniciar el contenedor. En este caso es cuando usamos ENTRYPOINT. ENTRYPOINT indica el ejecutable a ejecutar al iniciar un contenedor, mientras que CMD indica el parámetro a pasar al ejecutable (que por defecto, es decir sin ENTRYPOINT es /bin/sh). El formato de ENTRYPOINT es ENTRYPOINT [“ejecutable”, “param1”,…,”paramN”] para ejecutar el ejecutable indicado con todos los parámetros pasados. Pero si quieres puedes usar también CMD para pasar parámetros adicionales al ENTRYPOINT.
  3. Establecemos /app como el directorio inicial del contenedor
  4. Le indicamos a Docker que este contenedor debe exponer el puerto 80
  5. Copiamos el contenido de DemoWebApp/app (es decir el directorio local donde hemos publicado la web, cambialo para que sea el tuyo. Observa que es relativo al Dockerfile) al directorio actual del contenedor (que es app debido al WORKDIR anterior).

Vale, ahora ya podemos construir la imagen. Para ello desde una línea de comandos situados en el directorio donde hay el Dockerfile teclea “docker build –t eiximenis/demoweb .”:

image

El parámetro –t es para poner un nombre a la imagen. Genial ya tenemos la imagen construida. Para ejecutarla necesitamos saber su ID (no nos sirve el nombre). Recuerda que docker te dice el ID como resultado del docker build pero siempre puedes obtenerlo mediante el comando docker images. Este comando lista todas las imagenes que tengas instaladas con su nombre  (si lo tiene) y su ID:

image

Lanzando la imagen

Para lanzar la imagen nos basta como siempre con docker run <id> pero antes de que te lances a ejecutarlo, tenemos que hablar del mapeo de puertos. La imagen indica que el contenedor expone el puerto 80. Este puerto es el puerto local del contenedor y nada tiene que ver con el puerto del host. Recuerda que podrías tener varios contenedores corriendo al mismo tiempo, todos ellos generados por la misma imagen y todos ellos exponiendo su puerto 80. Es obvio pues que el puerto 80 del contenedor no puede ser el 80 del host. Así, al ejecutar el contenedor debemos decirle a docker a que puerto del host debe mapear el puerto 80 del contenedor. Para ello usaremos el parámetro –p, que toma la forma de –p:<puerto_local>:<puerto_contenedor>, es decir si p. ej. queremos que nuestro contenedor use el puerto del host 8001 usaríamos el comando “docker run –p 8001:80 <id>. Esto crea un contenedor basado en la imagen <id> y mapea el puerto 80 del contenedor (que este expone via el EXPOSE) al 8001 del host:

image

Se puede ver como el contenedor se queda escuchando. Por lo tanto si ahora abrimos un navegador y navegamos a http://localhost:8001

image

¡Ya tenemos nuestra web funcionando en Docker!

Podemos ver que nuestro contenedor está corriendo abriendo una consola de línea de comandos y tecleando docker ps. Eso nos muestra un listado de todos los contenedores actualmente en marcha. Podemos parar el contenedor con docker stop <id_contenedor> y reiniciarlo cuando queramos con docker start <id_contenedor>. Finalmente podemos borrarlo con docker rm <id_contenedor>

Bueno… lo dejamos aquí por hoy. En estos tres posts hemos avanzado bastante… Hemos cubierto lo básico de Docker y hemos visto paso a paso como publicar un proyecto en Docker de forma “manual”. Estamos listos ya para el siguiente paso, que se llama docker-compose… 😉

4 comentarios en “Docker para el desarrollador de asp.net (iii)”

  1. Gracia por la serie de artículos. Pero tengo algunas dudas sobre lo que cuentas.

    Dices que no puedes mapear el puerto 80 del host a varios docker. Pero ¿Qué pasa si quieres montar varios dominios en producción? Tendría que haber una manera de mapear el puerto 80 a varios docker con dominios separados.

    Una de las características del IIS es el reciclaje de procesos y el levantarlos en caso de caídas. ¿Los docker tienen algo parecido? En .Net Core ya no existen los dominios de aplicación. ¿Como se van a gestionar este tipo de cosas?

    Otra cosa que no existe en .Net Core es la parte servidor de WCF. ¿Hay intención de desarrollarla o deberemos usar REST?

    1. Hola Andrés!

      1. Si quieres mapear varios subdominios en producción, eso queda fuera del alcance de Docker. Habitualmente cuando despliegas Docker en producción lo haces detrás de algún proxy, tipo nginx o HAproxy. Entonces debes configurarlos para que mapeen las peticiones al subdominio x:80 al puerto interno que corresponda al contenedor Docker que debe servir ese subdominio.

      2. Docker “per se” no tiene nada parecido a un reciclaje de procesos. Para eso debe usarse algún orquestador de contenedores, como pueden ser Kubernetes (de Google), DC/OS (basado en Apache Mesos) o Swarm (de la propia Docker). Esos productos deben configurarse aparte y son capaces de autoescalar los contenedores necesarios para tu aplicación.

      3. Pues sí, no hay planes por ahora (que yo sepa) de añadir soporte para SOAP en servicios core. Eso significa usar REST. Otra opción podrían ser los contenedores basados en Windows (que vienen con Windows Server 2016). En este caso supongo que se podrían usar contenedores con el net framework full, pero no estoy seguro.

      Un saludo!

  2. Gracias por la información. Pero por lo queme cuentas, con los asp.net core y los docker se pierden muchas cosas.
    El mundo del software va por modas. Y parece que la tendencia va por aquí. Habrá que estar atento, a ver como evoluciona.

    1. Buenas!
      Pierdes por un lado, ganas por otro. Usar contenedores te permite, p. ej. escalar independientemente los servicios de tu aplicación, algo dificil de hacer si los tienes desplegados en IIS (a no ser, claro, que tengas cada servicio en su propio IIS).
      Al final, se trata de elegir la mejor herramienta para cada caso: ni netcore tiene por qué ser tu elección, ni usar contenedores o arquitecturar en base a microservicios tiene que ser lo que mejor se adapta a tus necesidades.

      Como siempre… no hay balas de plata 😉

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *