Nota: Este post ha sido importado de mi blog de geeks.ms. Es posible que algo no se vea del todo "correctamente". En cualquier caso puedes acceder a la versión original aquí
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):
(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:
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:
- Usa la imágen base microsoft/aspnetcore:1.0.1 que contiene el runtime de asp.net
- 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.
- Establecemos /app como el directorio inicial del contenedor
-
Le indicamos a Docker que este contenedor debe exponer el puerto 80
- 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).
-
- Establecemos /app como el directorio inicial del contenedor
- 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.
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 .”:
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:
Lanzando la imagen
Para lanzar la imagen nos basta como siempre con docker run
Se puede ver como el contenedor se queda escuchando. Por lo tanto si ahora abrimos un navegador y navegamos a http://localhost:8001…
¡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… 😉