Diseño de funciones de Azure para PowerShell
Esta publicación es parte de una serie sobre Azure Functions y PowerShell. ¡Consulta la lista de otras publicaciones de la serie!
Introducción
La compatibilidad de Azure Functions con PowerShell ha estado disponible de forma general desde el 4 de noviembre de 2019 y está funcionando muy bien.
En mi última publicación sobre el diseño de una aplicación de función de Azure para producción, mostré cómo instalar y configurar una aplicación de función para ejecutar PowerShell en Azure. En esta publicación, cubriré cómo implementar nuestra primera función y los aspectos a considerar al desarrollar el código de PowerShell que se ejecutará en la aplicación de funciones.
¡Empieza con un ejemplo!
Hay un excelente tutorial sobre cómo crear una función en Azure usando Visual Studio Code. Esto te ayudará a comenzar. Una vez que tengamos una función implementada en Azure, hay algunas cosas en las que quiero profundizar un poco más.
Configure scriptFile para deshacerse de todos esos run.ps1
A veces puedo estar un poco por todas partes cuando estoy trabajando. También soy una de esas personas que de repente terminan con 800 pestañas del navegador o más (OK, también soy muy malo reiniciando mi computadora…).
Ahora bien, ¿qué tiene esto que ver con Azure Functions? Bueno, eso también significa que cuando desarrollo funciones, suelo tener al menos cinco archivos llamados run.ps1 abiertos. ¡Esto termina siendo realmente confuso y extraño!
Por supuesto que hay una solución para esto. Cada función tiene su propia función.json. En ese archivo podemos agregar una propiedad llamada scriptFile y darle la ruta a un script que debe ejecutarse. La ruta debe ser relativa a function.json, pero no es necesario que el archivo real esté en la misma carpeta.
Por lo general, simplemente le doy a mi archivo el mismo nombre que mi función y lo guardo en la misma carpeta, de esa manera es más fácil realizar un seguimiento de qué archivos pertenecen a dónde. A continuación se muestra un ejemplo de cómo cambiar el nombre de run.ps1 a HttpTrigger1.ps1:
{
"bindings": [
],
"scriptFile": "HttpTrigger1.ps1"
}
Nivel de registro
Iniciar sesión desde PowerShell es realmente simple, simplemente use los cmdlets que conocemos y amamos para la salida, como write-error/warning/information/host/output/debug/verbose. Luego, el tiempo de ejecución de la función recogerá los diferentes flujos de salida y los enviará a Application Insights. Podemos elegir qué transmisiones seleccionar configurando el nivel de registro en cualquiera de Error (solo secuencia de error), Warning (error y secuencia de advertencia), Información< (error, advertencia, así como flujos de salida e información), Debug (todos los flujos de depuración anteriores) o Trace que también agrega el flujo detallado y los mensajes de progreso escritos. usando Write-Progress.
El nivel de registro se establece en el archivo llamado host.json en la raíz del código de nuestra aplicación de función. Al configurar la propiedad logging.logLevel, podemos especificar un valor predeterminado que se aplicará a todas las funciones de la aplicación y especificar "Function.MyFunction" para anular la configuración de una función específica llamada MyFunction.
"logging": {
"logLevel": {
"Function.HttpTrigger1": "Trace",
"default": "Warning"
}
}
Lea más sobre cómo iniciar sesión en la documentación.
Perfil de PowerShell
También en la raíz de nuestro proyecto hay un archivo llamado perfil.ps1. Esto funciona igual que perfil.ps1 en nuestras computadoras, se obtiene mediante puntos cada vez que se crea un nuevo espacio de ejecución. De forma predeterminada, hay una pequeña declaración if que busca la existencia de una variable de entorno MSI_SECRET y el módulo Az.Accounts. MSI_SECRET es una variable de entorno automática que Azure inyectará en su aplicación de función si la habilitó para usar una identidad administrada. La variable MSI_SECRET contiene un encabezado secreto que se puede usar para llamar a un servicio de token interno que sus funciones pueden usar para obtener tokens de acceso mediante la identidad administrada.
Azure PowerShell tiene soporte integrado para esto. Si tenemos instalado el módulo Az.Accounts, podemos simplemente llamar a "Connect-AzAccount -Identity" para autenticarnos en Azure utilizando la identidad administrada de nuestra aplicación de función. ¿Qué tan genial no es eso?
Sin embargo, si no desea que sus funciones llamen a la API de Azure, le recomiendo eliminar esta declaración if, incluso si solo toma unos pocos milisegundos evaluar la condición, es decir, milisegundos que se agregan al inicio en frío de nuestra llamada a función. Y lo que es peor, si se configura una identidad administrada (lo que suele ser el caso) y tenemos el módulo Az.Accounts disponible, nuestra función iniciará sesión en Azure, lo que podría agregar varios segundos al tiempo de inicio en frío.
Algunas pruebas simples que realicé mostraron que solo probar si existía Az.Accounts tomó poco menos de 2 segundos y, en realidad, llamar a Connect-AzAccount tomó alrededor de 12 segundos. ¡Esos son 12 segundos que no querrás gastar a menos que realmente los necesites!
Si necesita autenticarse en Azure en su aplicación de función que se ejecuta en el nivel de consumo y tiene muchos inicios en frío, aquí hay una forma muy ligera de obtener un token de acceso para llamar a la API de ARM en lugar de usar Azure PowerShell:
$BaseURI = 'https://management.azure.com/'
$URI = "${Env:MSI_ENDPOINT}?resource=${BaseURI}&api-version=2017-09-01"
$Response = Invoke-RestMethod -Method Get -Headers @{Secret = $Env:MSI_SECRET } -Uri $URI
$Env:Token = $Response.access_token
¡Esto generalmente se ejecuta en alrededor de 0,2 segundos!
Este método también funciona para conectarse a otros recursos siempre que su identidad administrada tenga acceso a ellos. Simplemente reemplace BaseURI con el identificador del recurso al que desea conectarse.
A continuación se muestra un ejemplo de cómo puede conectar el módulo Microsoft.Graph PowerShell desde una función de Azure:
$BaseURI = 'https://graph.microsoft.com/'
$URI = "${Env:MSI_ENDPOINT}?resource=${BaseURI}&api-version=2017-09-01"
$Response = Invoke-RestMethod -Method Get -Headers @{Secret = $Env:MSI_SECRET } -Uri $URI
$Env:Token = $Response.access_token
Connect-MgGraph -AccessToken $Env:Token -TenantId $TenantId
Y mientras estamos en eso, si lo que busca es velocidad, considere llamar a la API ARM usando Invoke-RestMethod en lugar de usar Azure PowerShell, hay muchos segundos preciosos que puede ahorrar a costa de algo más de codificación.
Usando módulos
Como siempre cuando trabajo con PowerShell, intento poner la mayor cantidad de código posible en módulos reutilizables. Estos módulos que escribo yo mismo junto con cualquier otro módulo que quiera usar en mis funciones deben instalarse en mi Function App.
Básicamente, existen dos formas de lograr esto: implementarlas yo mismo junto con mi código o dejar que la aplicación de funciones las descargue desde la galería de PowerShell. Permitir que la aplicación de funciones descargue los módulos y los instale por mí se maneja mediante una característica llamada dependencia administrada. Veamos cómo funciona.
La dependencia mangaged se puede habilitar configurando administradoDependency.enabled en verdadero en host.json. Al hacer esto, creemos un archivo de requisitos.pd1 en la raíz del proyecto. Este archivo es solo una tabla hash con los nombres de los módulos y las versiones requeridas. De forma predeterminada, obtenemos una dependencia del módulo 'Az' de la versión '4.*' al momento de escribir esto en septiembre de 2020. Cualquier dependencia de módulo que establezcamos en dependencies.psd1 se descargará de la galería de PowerShell cuando se abra la aplicación Funciton por primera vez. comienza, provocando un tiempo de espera considerable.
Las dependencias administradas suenan como una característica tentadora a primera vista, pero después de una mirada más profunda, siento que me da menos control. Prefiero descargar los módulos que necesito manualmente e implementarlos junto con el resto de mi código. De esta manera puedo estar seguro de lo que estoy ejecutando, no necesito depender de la galería de PowerShell y no necesito preocuparme por inicios en frío prolongados cuando la aplicación de funciones descarga los módulos requeridos. Como beneficio adicional, empaquetar todos los módulos con el resto de mi código de función hace que sea más fácil de ejecutar y depurar localmente, ya que también tengo todas mis dependencias allí.
Entonces, ¿cómo instalamos los módulos manualmente? ¡Fácil! Simplemente cree una carpeta titulada Módulos en la raíz de nuestro proyecto de funciones. El tiempo de ejecución de funciones buscará automáticamente módulos aquí. Si quiero instalar módulos públicos, simplemente ejecuto Save-Module y lo apunto a mi carpeta de módulos, así:
Save-Module Az.Accounts -Path .\Modules
¡Simple como eso!
A continuación se muestra un ejemplo de cómo podría verse host.json:
{
"version": "2.0",
"managedDependency": {
"enabled": false
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[1.*, 2.0.0)"
},
"logging": {
"logLevel": {
"Function.HttpTrigger1": "Trace",
"default": "Warning"
}
}
}
Establecer valores configurables mediante la configuración de la aplicación
Cuando creo una aplicación de función, no es inusual que necesite establecer alguna configuración que sea exclusiva del entorno donde se está ejecutando en este momento. Puede ser un nombre de etiqueta, una identificación de inquilino, un nombre de organización o incluso un secreto como una clave API, un certificado o, a veces, incluso un nombre de usuario y contraseña. ¡Definitivamente no quiero este tipo de cosas codificadas en mi código!
Aquí es donde se pueden utilizar las configuraciones de la aplicación. Si leyó mi publicación anterior sobre Diseño de funciones de Azure para producción, conocerá muy bien la configuración de la aplicación para configurar la aplicación de funciones. Estas mismas configuraciones se pueden usar para inyectar configuraciones y secretos de la plataforma en mi Function App. El valor de cualquier configuración de la aplicación estará disponible dentro de mi aplicación de funciones como una variable de entorno.
Entonces, si necesito inyectar un token secreto, puedo colocar ese token en mi Key Vault, agregar una configuración de aplicación denominada MyToken y hacer referencia a mi secreto de Key Vault. Luego puedo acceder a mi token secreto desde PowerShell simplemente leyendo la variable de entorno $Env:MyToken.
Por supuesto, todos los secretos y certificados deben almacenarse en Key Vault. También suelo almacenar los ajustes de configuración en Key Vault solo para mantener todo en el mismo lugar.
Más ejemplos
Microsoft tiene un gran repositorio de ejemplos sobre cómo crear funciones en GitHub
Resumen
Para resumir:
- Asegúrese de utilizar la última versión de PowerShell
- Considere cambiar el nombre de run.ps1
- Gestiona tus propias dependencias
- ¡Utilice la bóveda de claves!
¿Tiene otros consejos y trucos o información importante que siempre utiliza al escribir funciones con PowerShell?
¡Hágamelo saber en los comentarios!
