Usando variables de ambiente Docker en .NET Core
Tweet Mon 06 February 2017Algo realmente bueno acerca de empaquetar tus aplicaciones con Docker es que estamos compilando una vez y publicando muchas veces en distintos lugares (entornos), debido a esto, es una buena idea hacer la aplicación configurable a nivel de entorno (no solamente a nivel de archivos). Esto significa que deberiamos evitar tener archivos de configuración en el contenedor que puedan ser diferentes en cada entorno en el que se corra el contenedor. "La configuración de una aplicación es todo aquello que es probable que varíe en cada publicación" (The 12 Factor App).
¿Pero por qué variables de entorno?
La idea viene de la metodología "The 12 Factor App" (https://12factor.net/config), donde dice que debemos preferir usar este tipo de configuración porque tener archivos puede comprometer información confidencial (la seguridad en Docker no es tan madura todavía porque podemos inspeccionar fácilmente el contenedor y ver todas las variables de entorno, pero eso es una charla interesante para otro tiempo).
No voy a repetir lo que seguro ya leiste en el enlace anterior, pero quiero enfocarme en lo siguiente: "En una '12 Factor App', las variables de ambiente son controles granulares, cada uno totalmente ortogonal a otras variables de ambiente. Nunca son agrupadas juntas como un 'ambientes', sino que se manejan de forma independiente para cada publicación. Este es un modelo que escala sin problemas ya que la aplicación naturalmente se expande en más publicaciones a lo largo de su vida útil. (The 12 Factor App)
Por lo tanto, la idea es que siempre debemos tener nuestra aplicación preparada para comportarse de acuerdo a cada entorno, no deberíamos tener que volver a compilar nuestro código sólo porque tenemos que cambiar alguna configuración, imagínate hacer eso en el software que utilizamos en nuestras computadoras.
Eso es bueno, pero ¿Cómo se puede hacer esto en .NET Core?
Me alegra que lo preguntes. Para este momento espero que ya estes convencido que las variables de ambiente son una buena opción y para suerte nuestra, así es como Docker funciona, solo necesitamos saber cómo lograr este comportamiento en .NET Core. Para esto, también hay una buena documentación aquí, pero en este post me centraré sólo en cómo leer variables de ambiente que vienen de Docker.
.NET Core tiene una muy buena integración con variables de ambiente, de hecho la plantilla predeterminada para una Web API lo usa por defecto, así que solo necesitamos hacer algunos cambios en nuestro código en la clase Startup.cs, crear una nueva clase POCO y usarla en nuestros controladores inyectando la configuración que necesitamos.
Cambios en la aplicación
Mientras listo los cambios que tenemos que hacer en la aplicación, voy a dar una explicación en cada paso. He subido el código de muestra en Github, por lo que si no me logro explicar lo suficiente, puedes ver el código fuente aquí. Aquí está la lista de pasos:
- Asegúrate de que en la clase Startup.cs de tu proyecto Web API tienes la línea para agregar las variables de ambiente. Esto debe estar en el constructor de la clase donde se está definiendo el "builder" de la configuración. En el constructor la varialbe "builder" debe incluir la llamada "AddEnvironmentVariables ()". Algo como esto:
var builder = nuevo ConfigurationBuilder()
.SetBasePath (env.ContentRootPath)
... otras llamadas de métodos
.AddEnvironmentVariables();
- Necesitas crear una clase POCO donde defines todas las propiedades que te interesan leer del ambiente. En mi caso quiero tratar el nombre del perfil como una variable de ambiente, por lo que creé una clase llamada EnvironmentConfig.cs y he incluido una propiedad string pública llamada "ProfileName" (su nombre debe coincidir con el de la variable de ambiente, la framework hace reflection al popular las propiedades con las variables de ambiente y debido a que esto se está haciendo en el inicio, no afectará el rendimiento al servir requests, esto también es porque en el contenedor sólo tendrá las variables de entorno que se definen al ejecutar el contenedor).
public class EnvironmentConfig
{
public string ProfileName { get; set; }
}
- Agrega la nueva clase de configuración en la definición de servicios en Startup.cs. Esto significa que en el método denominado "ConfigureServices" se debe incluir esta línea:
services.Configure<EnvironmentConfig>(Configuration);
- Por último, ya pasamos a utilizarla en el controlador (o en donde quieras) mediante la inyección de la configuración en el constructor de la clase. Es recomendable tener una variable local en la clase, entonces necesitamos especificar en el constructor la inyección de la clase de configuración que acabamos de definir, pero debe hacerse a la manera de .NET Core utilizando la interfaz 'IOptions'. En mi caso quiero incluirlo en el ValuesController.cs entonces mi constructor tiene este aspecto:
private readonly EnvironmentConfig _configuration;
public ValuesController(IOptions<EnvironmentConfig> configuration)
{
_configuration = configuration.Value;
}
En caso de que no lo veas, una de las cosas geniales al hacerlo de esta manera es que si no especificas una variable de ambiente llamada "ProfileName" tu aplicación no se detendrá (a menos que quieras/necesites) porque cuando lea la propiedad de la clase de configuración esta estará vacía pues no hubo ningun match.
Y eso es todo, si desea profundizar en cómo la configuración puede ser modificada/utilizada en .NET Core por favor lee el siguiente artículo aquí
¿Cómo probar y depurar esto con VS?
Supongo que ya has agregado el soporte a Docker en tu solución, si no entonces dale un vistazo a este artículo. En la solución, abre el archivo docker-compose.override.yml dentro de la sección docker-compose y en 'environment', escribe la variable de ambiente para el nombre de perfil, algo asi:
Ahora ve y debuggea en modo Docker, la aplicación debe leer ese valor desde el ambiente del contenedor.
Pero ¿qué pasa con Docker?
Sí, sé que casi me olvidé de esto. ¿Pero sabes que? Esto es realmente simple, voy a poner aquí la forma en que ejecuto mi contenedor para que puedas ver cómo definir una variable de ambiente que la aplicación en .NET Core leerá.
docker run -e ProfileName='Christian' -d -p 9000:80 christianhxc/hellodocker:env
Ahora vamos a la URL: http://localhost:9000/api/values y boom! Yo veo mi nombre allí. Por supuesto, no es necesario especificar todas las varialbes de ambiente con el comando 'run', puodemos hacer uso de docker compose para agruparlos, pero recuerda: no incluyas ningún archivo de configuración con el código fuente en el control de versiones.
No es necesario seguir esta guía para probarlo, simplemente ejecuta el comando anterior con valores diferentes :)