Global Azure Bootcamp 2017
Azure Functions
Agenda
- What is Azure Functions
- What can we build with Azure Functions
- How do we build Azure Functions
- How do we deploy Azure Functions
- Tips and tricks
Just ask questions along the way
About me
- + 15 years web developer (started with classic ASP)
- + 8 years professional Dev/Architect (.NET, SharePoint and Azure, first Azure cert in 2013)
- Working exclusively with Azure last 2.5 years
- Managing Consultant @ Delegate A/S (MS Gold Partner)
- Lives in Malmö, Sweden
- Author of Lets Encrypt Site Extension for Azure Web Apps
- CTO at tiimo ApS
mail@sjkp.dk
@simped
http://wp.sjkp.dk
What is Azure Functions
- Serverless in Azure
- Microsofts answer to AWS Lambda
- The natural evolution of Azure Web Jobs
- The platform to write code for Logic Apps / Microsoft Flow
- A site extension on Azure Web Apps + More
- The easiest way to get started with code in Azure
History of Azure Web Apps
Serverless compared
Virtual Machines | Container | Serverless | |
---|---|---|---|
Unit of scale | Machine | Application | Function |
Abstraction | Hardware | OS | Language Runtime |
Configure | Machine, Storage, Networking, OS | Run server, configure applications, scaling | Run code on demand |
Execution | Multi-threaded, multi-task | Multi-threaded, single task | Single threaded, single taks |
Execution time | Hours to months | Minutes to days | Microseconds to seconds |
Unit of Cost | Per VM per hour | Per VM per hour | Per memory/second per request |
Amazon | EC2 | ECS | AWS Lambda |
Azure | Azure Virtual Machines | ACS | Azure Functions |
What is Serverless
- Nothing is serverless, obviously
- Gained significant momentum lately
- Aka. Backend as a Service, Funciton as a Service
- AWS Lambda
- Typical use-case
- Event processing
- Dynamic Scale
- Dynamic Pricing
DEMO
Getting started with Azure Functions
Typical Azure Function Scenarios
- Simple Backends
- Web Backends, Mobile Backends, Bot Backends
- Data Processing
- IoT, Image Processing, Data pipelines
- Maintenance tasks
- Index Rebuild, Scheduled jobs
What can we build with Azure Functions
- Code in
- C#, F#, JavaScript (Bash, PHP, Python, PowerShell)
- Types of functions apps
- Scheduled, Triggered, RunOnce, Proxies
Function Bindings
Azure Functions vs Web Jobs vs Microsoft Flow vs Logic Apps
Functions | Web Jobs | Flow | Logic Apps | |
---|---|---|---|---|
Audience | Developers | Developers | End users | IT Pros |
Design Tools | VS, Browser | VS | Browser | VS, Browser |
Scale | Dynamic/App plan | App Plan | Hosted Service | App Plan/Dynamic |
Azure Functions Architecture
Service Plan
Web App
Func App 1
Func App 2
Azure Blob Storage
Central Listener
Function Runtime
Azure Function Pricing
- Consumption Plan
- Computed as a combination of memory size and execution time for all functions running in a function app
- Number of executions
- App Service Plan
- Tiered billing (Free, Shared, Basic, Standard, Premium)
- Flat rate
DEMO
Create a Function using the portal
Anatomy of a Function
- A “Run” file that containing the function code
- A “Function” file containing all service and trigger bindings and parameters
- A “Project” file containing project assembly and NuGet package references
- App Service settings, such as connection strings and API keys
Function structure
|---data
| |---Functions
| |---sampledata
| | QueueTriggerCSharp1.dat
| |
| |---secrets
| host.json
| queuetriggercsharp1.json
|
|---LogFiles
| |---Application
| | |---Functions
| | |---function
| | | ----QueueTriggerCSharp1
| | |---Host
| | debug_sentinel
| |
| |---kudu
| |---deployment
| |---trace
| 2017-03-05T09-50-51_e92a00_001_Startup_GET__0s.xml
| RD000D3AB10CF5-fa094f48-f993-47b4-8b65-8a435c854163.txt
|
|---site
|---deployments
| ----tools
|---diagnostics
| settings.json
|
|---locks
|---tools
|---wwwroot
| host.json
| hostingstart.html
|
|---QueueTriggerCSharp1
function.json
readme.md
run.csx
Important files
- function.json
- project.json
- run.csx
- host.json
function.json
{
"bindings": [
{
"type": "queueTrigger",
"direction": "in",
"queueName": "image-resize"
},
{
"type": "blob",
"name": "original",
"direction": "in",
"path": "images-original/{name}"
},
{
"type": "blob",
"name": "resized",
"direction": "out",
"path": "images-resized/{name}"
}
]
}
{
"disabled": true,
"scriptFile": "test.js",
"bindings": [
{
"type": "timerTrigger",
"direction": "in",
"schedule": "0 0 */5 * * *",
"runOnStartup": true
}
]
}
project.json
{
"frameworks": {
"net46":{
"dependencies": {
"Microsoft.ProjectOxford.Face": "1.1.0"
}
}
}
}
- reference 3rd party nugets
using System;
using Microsoft.ProjectOxford.Face; //Just reference the nuget with using
public static void Run(string myQueueItem, TraceWriter log)
{
log.Info($"C# Queue trigger function processed : {myQueueItem}");
}
run.csx
#r "Microsoft.WindowsAzure.Storage"
//#r "MyCustomDll.dll"
#load "../shared/models.csx"
using System;
using System.Net.Http;
using Microsoft.ProjectOxford.Face;
using Newtonsoft.Json;
using Microsoft.WindowsAzure.Storage;
public static void Run(string myQueueItem, TraceWriter log)
{
log.Info($"C# Queue trigger function processed : {myQueueItem}");
var client = new HttpClient();
var obj = JsonConvert.SerializeObject(new MyTestClass {Name = "hello world"});
}
public class MyTestClass {
public int Id {get;set;}
public string Name {get;set;}
}
models.csx
host.json
{
// The unique ID for this job host. Can be a lower case GUID
// with dashes removed (Required)
"id": "9f4ea53c5136457d883d685e57164f08",
// Value indicating the timeout duration for all functions.
// In Dynamic SKUs, the valid range is from 1 second to 5 minutes and the default value is 5 minutes.
// In Paid SKUs there is no limit and the default value is null (indicating no timeout).
"functionTimeout": "00:05:00",
// Configuration settings for 'http' triggers. (Optional)
"http": {
// Defines the default route prefix that applies to all routes. Default is 'api'.
// Use an empty string to remove the prefix.
"routePrefix": "api"
},
// Set of shared code directories that should be monitored for changes to ensure that
// when code in these directories is changed, it is picked up by your functions
"watchDirectories": [ "Shared" ],
// Array of functions to load. Only functions in this list will be enabled.
"functions": [ "QueueProcessor", "GitHubWebHook" ]
// Configuration settings for 'queue' triggers. (Optional)
"queues": {
// The maximum interval in milliseconds between
// queue polls. The default is 1 minute.
"maxPollingInterval": 2000,
// The number of queue messages to retrieve and process in
// parallel (per job function). The default is 16 and the maximum is 32.
"batchSize": 16,
// The number of times to try processing a message before
// moving it to the poison queue. The default is 5.
"maxDequeueCount": 5,
// The threshold at which a new batch of messages will be fetched.
// The default is batchSize/2.
"newBatchThreshold": 8
},
// Configuration settings for 'serviceBus' triggers. (Optional)
"serviceBus": {
// The maximum number of concurrent calls to the callback the message
// pump should initiate. The default is 16.
"maxConcurrentCalls": 16,
// The default PrefetchCount that will be used by the underlying MessageReceiver.
"prefetchCount": 100,
// the maximum duration within which the message lock will be renewed automatically.
"autoRenewTimeout": "00:05:00"
},
// Configuration settings for 'eventHub' triggers. (Optional)
"eventHub": {
// The maximum event count received per receive loop. The default is 64.
"maxBatchSize": 64,
// The default PrefetchCount that will be used by the underlying EventProcessorHost.
"prefetchCount": 256
},
// Configuration settings for logging/tracing behavior. (Optional)
"tracing": {
// The tracing level used for console logging.
// The default is 'info'. Options are: { off, error, warning, info, verbose }
"consoleLevel": "verbose",
// Value determining what level of file logging is enabled.
// The default is 'debugOnly'. Options are: { never, always, debugOnly }
"fileLoggingMode": "debugOnly"
},
// Configuration settings for Singleton lock behavior. (Optional)
"singleton": {
// The period that function level locks are taken for (they will auto renew)
"lockPeriod": "00:00:15",
// The period that listener locks are taken for
"listenerLockPeriod": "00:01:00",
// The time interval used for listener lock recovery if a listener lock
// couldn't be acquired on startup
"listenerLockRecoveryPollingInterval": "00:01:00",
// The maximum amount of time the runtime will try to acquire a lock
"lockAcquisitionTimeout": "00:01:00",
// The interval between lock acquisition attempts
"lockAcquisitionPollingInterval": "00:00:03"
}
}
Consumption Plan scaling
- Startup delay, up to 10 minute
- Max execution time per function job 5 min
- Scales up to 60 concurrent function job hosts
- Each function job host can run multiple function jobs
How to Develop with Visual Studio
- Azure SDK 2.9.3
- Visual Studio Function Tools
- Uses Azure Function CLI (node tools) for local debugging
DEMO
Develop a Azure Function with Visual Studio
Continues Integration
- Azure function runs on Azure Web Apps
- So git is available (makes the function readonly)
- Webdeploy (like VS did is available)
- FTP
- Kudu API
DEMO
Publish using Github
Is that good ALM?
- Pushing source code to production - I dont like it
- Might be ok for simple solutions
- We could push to different environments using branches (which I also don't like)
- Improve the ALM story
- We need compiled code
- We need a build step
- We need a test step
- Preferably we need multiple environments
Precompiled Functions
- Problems using the Web IDE (.csx)
- No intelisense
- Hard to maintain large code bases
- Debugging -> Console.WriteLine (not cool)
- Precompiled functions
- Fixes all of the above
- We lose some simplicity/agility
- Conclusion
- Portal is okay for prototypes
- Portal is okay for simple solutions
- If you want a traditional .NET experience -> precompiled functions is your friend
Precompiled functions, how to
- Move the code to a .NET class library
- Add nuget (Microsoft.Azure.WebJobs.Extensions / Microsoft.Azure.WebJobs)
- Update function.json
{
"disabled": false,
"scriptFile": "bin/SJKP.PrecompiledFunctions.dll",
"entryPoint": "SJKP.PrecompiledFunctions.Functions.Run",
"bindings": [
{
"authLevel": "function",
"name": "req",
"type": "httpTrigger",
"direction": "in"
},
{
"name": "res",
"type": "http",
"direction": "out"
}
]
}
Build and Deploy
- Building the project is now like building any .NET solution
- Deploy we need to copy the function app folders to the web app
- Normally we would use a webdeploy package (but that is broken in the function app project template)
- So we create a web project in the same folder (Dirty hack)
- Or we wait for VS2017 support
DEMO
Setting up webdeploy Continuous Integration for Azure Function
Building with VSTS
/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.stagingDirectory)"
Save WebDeploy Package
Setup Release Definition
Creating an Azure Function Environment Using ARM Templates
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountType": {
"type": "string",
"defaultValue": "Standard_LRS",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_ZRS",
"Premium_LRS"
],
"metadata": {
"description": "Storage Account type"
}
},
"functionAppName": {
"type": "string",
"defaultValue": "sjkptestfunction"
},
"functionAppDomainName": {
"type": "string",
"defaultValue": "myfunc.sjkp.dk"
}
},
"variables": {
"storageAccountName": "[concat('storage', uniqueString(resourceGroup().id))]",
"dynamicAppServicePlanName": "[concat('dynamic', uniqueString(resourceGroup().id))]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"name": "[variables('storageAccountName')]",
"apiVersion": "2015-06-15",
"location": "[resourceGroup().location]",
"properties": {
"accountType": "[parameters('storageAccountType')]"
}
},
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2015-04-01",
"name": "[variables('dynamicAppServicePlanName')]",
"location": "[resourceGroup().location]",
"properties": {
"name": "[variables('dynamicAppServicePlanName')]",
"computeMode": "Dynamic",
"sku": "Dynamic"
}
},
{
"apiVersion": "2015-08-01",
"type": "Microsoft.Web/sites",
"name": "[parameters('functionAppName')]",
"location": "[resourceGroup().location]",
"kind": "functionapp",
"properties": {
"name": "[parameters('functionAppName')]",
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('dynamicAppServicePlanName'))]"
},
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('dynamicAppServicePlanName'))]",
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
],
"resources": [
{
"apiVersion": "2015-08-01",
"type": "hostNameBindings",
"name": "[parameters('functionAppDomainName')]",
"dependsOn": [
"[concat('Microsoft.Web/sites/', parameters('functionAppName'))]"
],
"tags": {
"displayName": "hostNameBinding"
},
"properties": {
"domainId": null,
"hostNameType": "Verified",
"siteName": "variables('webAppName')"
}
},
{
"apiVersion": "2016-03-01",
"name": "appsettings",
"type": "config",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]",
"[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
],
"properties": {
"AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]",
"AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]",
"WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]",
"WEBSITE_CONTENTSHARE": "[parameters('functionAppName')]",
"FUNCTIONS_EXTENSION_VERSION": "latest",
"ROUTING_EXTENSION_VERSION": "~0.2"
}
},
{
"apiVersion": "2015-08-01",
"name": "TestFunction",
"type": "functions",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('functionAppName'))]"
],
"properties": {
"config": {
"bindings": [
{
"authLevel": "anonymous",
"name": "req",
"type": "httpTrigger",
"direction": "in"
},
{
"name": "res",
"type": "http",
"direction": "out"
}
]
},
"files": {
"run.csx": "using System.Net;\r\npublic static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)\r\n{\r\n log.Info($\"C# HTTP trigger function processed a request. RequestUri={req.RequestUri}\");\r\n\r\n // parse query parameter\r\n string name = req.GetQueryNameValuePairs()\r\n .FirstOrDefault(q => string.Compare(q.Key, \"name\", true) == 0)\r\n .Value;\r\n\r\n // Get request body\r\n dynamic data = await req.Content.ReadAsAsync<object>();\r\n\r\n // Set name to query string or body data\r\n name = name ?? data?.name;\r\n\r\n return name == null\r\n ? req.CreateResponse(HttpStatusCode.BadRequest, \"Please pass a name on the query string or in the request body\")\r\n : req.CreateResponse(HttpStatusCode.OK, \"Hello \" + name);\r\n}"
}
}
}
]
}
],
"outputs": { }
}
ARM template more info
- Same as the when deploying web apps
- Remember to set App Settings for storage account
- Both dynamic and standard app service plan can be used
- It is possible to deploy files into the function by embedding them in the template
- It is also possible to assign custom host names (Requires DNS is setup)
Tips and Tricks
Nothing Runs Forever
- Max run time for a dynamic function 5 mins
- 5 seconds to clean up -> termination
- Use cancellationtoken if you need to do things on shutdown
using System;
using System.Threading;
public static async Task Run(string input, TraceWriter log, CancellationToken cancel)
{
log.Info($"C# manually triggered function called with input: {input}");
do {
log.Info($"Output {DateTime.Now}");
await Task.Delay(2500);
} while(!cancel.IsCancellationRequested);
log.Info($"stopping: {cancel.IsCancellationRequested}");
}
Queues and Host.json
- Use queues in your function design
- When you use queues, ensure that you have configured host.json
- Check the poison queue
"queues": {
// The maximum interval in milliseconds between
// queue polls. The default is 1 minute.
"maxPollingInterval": 2000,
// The number of queue messages to retrieve and process in
// parallel (per job function). The default is 16 and the maximum is 32.
"batchSize": 16,
// The number of times to try processing a message before
// moving it to the poison queue. The default is 5.
"maxDequeueCount": 5,
// The threshold at which a new batch of messages will be fetched.
// The default is batchSize/2.
"newBatchThreshold": 8
},
Reprocess Poison Queue
#r "Microsoft.WindowsAzure.Storage"
using System;
using System.Configuration;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.Queue;
public static void Run(string input, TraceWriter log)
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(ConfigurationManager.AppSettings["AzureWebJobsStorage"]);
var queueClient = storageAccount.CreateCloudQueueClient();
var queue = queueClient.GetQueueReference("binfiles-poison");
var outqueue = queueClient.GetQueueReference("binfiles");
CloudQueueMessage msg = null;
int i = 0;
do
{
msg = queue.GetMessage();
if (msg != null)
{
log.Info($"Processing queue item {i++}");
outqueue.AddMessage(new CloudQueueMessage(msg.AsString));
queue.DeleteMessage(msg);
}
} while (msg != null);
}
The KUDU portal
https://[function-app-name].scm.azurewebsites.net
App Service Editor aka Monaco
Execution Context
- Get the name of the specific instance
using System.Net;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, ExecutionContext ctx, TraceWriter log)
{
log.Info($"C# HTTP trigger invocationId: {ctx.InvocationId}");
return req.CreateResponse(HttpStatusCode.OK, "ctx.InvocationId");
}
Application Insights
- First class support for application insights is comming (it is in beta)
- Add App Setting – APPINSIGHTS_INSTRUMENTATIONKEY = {Instrumentation Key}
- Change the App Setting FUNCTIONS_EXTENSION_VERSION to beta
- Standard .net diagnostics logging can be used (same as web apps)
- TraceWriter for debugging
- Monitoring tab, only last 20 executions
- To manually setup application insights, treat the function as a console app
- Configure application insights in code
- Ensure to flush to application insight on termination
Proxies
- Light weight alternative to API Management
- Bind many functions apps together under one common API
- Use it for trickery, rewrite GET to POST etc, reverse proxy, add headers
- It is just a proxies.json file in the wwwroot
- The schema http://json.schemastore.org/proxies.json
- Some examples: http://wp.sjkp.dk/use-azure-functions-as-a-proxy/
Where to get help
Thank you for your time!
Hands On Laps - Beginners
Azure Functions Global Azure Bootcamp 2017
By Simon J.K. Pedersen
Azure Functions Global Azure Bootcamp 2017
- 1,679