07 · Azure Functions¶
Azure Functions is Azure's serverless compute service: you write a small function, it runs in response to a trigger (an HTTP request, a timer, a new blob, a queue message, ...), and on the Consumption plan you pay only for the time it actually executes — plus a generous free monthly grant. No server to provision or patch.
Triggers and bindings¶
- A trigger is what starts a function run (exactly one per function): HTTP request, Timer, Blob Storage, Queue Storage, Cosmos DB change feed, Event Grid, Service Bus, and more.
- A binding is a declarative way to read input from, or write output to, another service without writing SDK boilerplate — e.g. an "output binding" to Blob Storage lets you just return a value and have it written as a blob.
| Trigger type | Fires when |
|---|---|
| HTTP | A request hits the function's URL. |
| Timer | On a CRON-like schedule. |
| Blob | A blob is created/updated in a watched container. |
| Queue | A message arrives in a Storage Queue. |
| Cosmos DB | A document is inserted/updated (via the change feed). |
| Event Grid | An event is published (resource changes, custom events). |
The Consumption plan¶
The Consumption plan is the default serverless hosting plan: Azure allocates compute only while your function runs, scales to zero when idle, and scales out automatically under load. The free grant is 1 million requests and 400,000 GB-seconds of execution per month, forever — most learning and small production workloads never leave the free tier.
Other plans (Premium, App Service Plan, covered in Level 2, Module 09) trade the scale-to-zero cold start for always-warm instances or VNet integration.
Create a Function App¶
A Function App is the deployable/billable unit that hosts one or more individual functions.
az group create --name rg-func-demo --location eastus
# Functions need a storage account for internal state (triggers, logs)
az storage account create \
--name stfuncdemo$RANDOM \
--resource-group rg-func-demo \
--sku Standard_LRS
STORAGE_ACCOUNT=$(az storage account list --resource-group rg-func-demo --query "[0].name" --output tsv)
az functionapp create \
--resource-group rg-func-demo \
--name func-learn-demo$RANDOM \
--storage-account $STORAGE_ACCOUNT \
--consumption-plan-location eastus \
--runtime python \
--runtime-version 3.11 \
--functions-version 4 \
--os-type Linux
FUNCTION_APP=$(az functionapp list --resource-group rg-func-demo --query "[0].name" --output tsv)
Write and run a function locally¶
Install Azure Functions Core Tools, then scaffold a project:
# macOS
brew tap azure/functions
brew install azure-functions-core-tools@4
func init hello-func --python
cd hello-func
func new --name HttpHello --template "HTTP trigger" --authlevel "function"
This generates HttpHello/__init__.py:
import azure.functions as func
def main(req: func.HttpRequest) -> func.HttpResponse:
name = req.params.get("name", "world")
return func.HttpResponse(f"Hello, {name}! This ran on Azure Functions.")
Run it locally to test before deploying:
func start
# Functions:
# HttpHello: [GET,POST] http://localhost:7071/api/HttpHello
curl "http://localhost:7071/api/HttpHello?name=Azure"
# Hello, Azure! This ran on Azure Functions.
Deploy to Azure¶
# Get the deployed function's URL (includes its auth key for "function"-level auth)
az functionapp function show \
--resource-group rg-func-demo \
--name $FUNCTION_APP \
--function-name HttpHello \
--query invokeUrlTemplate --output tsv
curl "https://<func-app>.azurewebsites.net/api/HttpHello?code=<key>&name=Azure"
A timer-triggered function¶
DailyCleanup/function.json sets the CRON-style schedule
({second} {minute} {hour} {day} {month} {day-of-week}):
{
"bindings": [
{
"name": "mytimer",
"type": "timerTrigger",
"direction": "in",
"schedule": "0 0 2 * * *"
}
]
}
import datetime
import azure.functions as func
def main(mytimer: func.TimerRequest) -> None:
utc_now = datetime.datetime.utcnow().isoformat()
print(f"DailyCleanup ran at {utc_now}")
"0 0 2 * * *" fires once a day at 02:00 UTC — useful for nightly cleanup,
report generation, or polling an external API on a schedule.
Authorization levels¶
HTTP-triggered functions support three auth levels, set per function:
| Level | Behavior |
|---|---|
anonymous |
No key required — anyone with the URL can call it. |
function |
Requires a function-specific or host-wide key as a query param or header. |
admin |
Requires the master key — full admin access, rarely used for regular triggers. |
# List the keys for a function
az functionapp function keys list \
--resource-group rg-func-demo \
--name $FUNCTION_APP \
--function-name HttpHello
How It Actually Works¶
An Azure Function isn't a standalone process waiting for traffic — it's
code hosted by the Azure Functions runtime host, which itself runs
inside a language worker process (dotnet, node, python, ...) supervised
by the Azure Functions Host. On the Consumption plan, that host process
doesn't run continuously: Azure's scale controller monitors the trigger
source (queue depth, HTTP request rate, timer due time) out-of-band, and
when work appears it allocates a fresh sandboxed container on shared
infrastructure, cold-starts the language worker inside it, and only then
invokes your function — that cold-start (JIT/interpreter startup + your
package's dependency loading) is the actual mechanical cause of first-request
latency, not network delay. Because the whole app can scale to zero
containers when idle, you're billed per-execution (GB-seconds + invocation
count) rather than for reserved compute.
Triggers and bindings are implemented via the extension/binding framework: a Blob trigger, for instance, doesn't have Azure Functions poll storage directly for most trigger types — it registers an Event Grid subscription (or, on the older polling model, has the host scan blob container logs) so the host is woken by an event rather than by continuously listing blobs. An HTTP trigger runs the Function host as an ASP.NET Core- based web server behind Azure's Front Door / load balancer edge, which routes the request into whichever scaled-out instance is available. The Functions Host itself is the same open-source WebJobs SDK-based engine whether you're on Consumption, Premium, or a dedicated App Service plan — what changes across plans is only how instances are provisioned and scaled, not the execution model your code runs under.
Cheat sheet¶
| Command | Purpose |
|---|---|
func init <name> --python |
Scaffold a new local Functions project. |
func new --name --template |
Add a function with a given trigger type. |
func start |
Run functions locally at localhost:7071. |
az functionapp create --consumption-plan-location --runtime |
Create a Function App on the Consumption plan. |
func azure functionapp publish <app> |
Deploy local code to Azure. |
az functionapp function keys list |
Get the key(s) needed to call a function-level HTTP trigger. |
| Trigger vs. binding | Trigger starts the run (one per function); bindings read/write data declaratively. |
Exercise¶
- Scaffold a Python Functions project locally with an HTTP trigger that
accepts a
namequery parameter and returns a JSON greeting ({"message": "Hello, <name>"}) usingfunc.HttpResponsewithmimetype="application/json". - Run it locally with
func startand test withcurl. - Create a Function App on Azure and deploy it with
func azure functionapp publish. - Call the deployed URL (with its
function-level key) and confirm you get the same JSON back. - Delete the resource group when finished.