Azure Functions is Azure's Functions-as-a-Service (FaaS) platform: you write a small unit of code bound to a trigger, and the platform handles process lifecycle, scaling, and (on serverless plans) billing per execution. The same Functions runtime and programming model can run in five very different places — Consumption, Flex Consumption, Premium, Dedicated (App Service Plan), or as a plain container on Kubernetes/AKS scaled by KEDA — and picking the wrong one is the single most consequential (and most common) mistake teams make.
Why this matters
The "serverless vs Kubernetes" debate is a false dichotomy for Functions: the runtime is portable. The real decision is about who operates the scaling substrate — Azure (Consumption/Flex/Premium), your App Service plan (Dedicated), or your own cluster (AKS + KEDA). Interviewers and architecture reviews probe exactly this distinction.
Functions core
Event-driven execution: a trigger (HTTP, queue, timer, blob, Event Hub...) invokes your function; bindings declaratively wire inputs/outputs without SDK boilerplate.
Scale controller
On Azure-hosted plans, a scale controller (not KEDA) monitors trigger sources and adds/removes host instances. On Kubernetes, that same signal is exposed through KEDA to the standard Horizontal Pod Autoscaler.
2026 default
Flex Consumption is almost always the right pick over classic Consumption for new projects — same idle cost, greater scale, VNet support, and always-ready instances for latency-sensitive paths.
Core Concepts
Trigger, binding, function app
Azure Functions uses triggers to start function execution and bindings to connect code to other services like storage, queues, and databases, without hand-written SDK plumbing for the common case.
Concept
Purpose
Key trait
Trigger
Defines what invokes the function (exactly one per function)
HTTP, Timer, Queue, Blob, Event Hub, Service Bus, Cosmos DB change feed, Durable orchestration/entity/activity
Input/Output binding
Declarative data in/out without manual SDK calls
Zero or more per function; e.g. write a Cosmos DB doc as an output binding
Function app
Deployment/scale unit — a collection of functions sharing a process and configuration
All functions in an app scale together (per Consumption); Flex scales more granularly by trigger type
Stateful orchestration on top of stateless functions
Orchestrator, Activity, and Entity triggers; orchestrator code must be deterministic
Programming models
The v2 programming model uses a decorator-based approach to define triggers and bindings directly in code, replacing the v1 model's separate function.json metadata file per function. This is now the recommended model across Python, Node.js (v4), and other supported languages.
Flash: Why can't Durable Functions orchestrator code call an HTTP API directly?
Orchestrator functions replay their history on every reactivation (the "event sourcing" execution model), so any non-deterministic operation (I/O, random numbers, current time) must be pushed into an Activity function — otherwise a replay produces different results.
Architecture Overview
Every hosting option is built from the same two logical layers: the Functions host/runtime (your code + the language worker + the extension/binding framework) and a scaling substrate that decides how many instances of that host should run.
Fig 1. One portable Functions host, four different scaling substrates.
Internal Working
How event-driven scaling actually decides "add an instance"
When a function is deployed, it runs inside the Functions runtime host process, which pulls events from the source defined by the trigger and passes them to your function code. One instance of the runtime is rarely adequate: it's wasteful if you get one message a day, and insufficient if you get thousands of events per second — so a separate autoscaling mechanism monitors the trigger source and adjusts instance count.
On Azure-hosted plans: the built-in scale controller polls the trigger source directly (e.g., Storage Queue length, Event Hub partition lag) roughly every few seconds and issues scale decisions independent of Kubernetes/HPA — there's no cluster involved at all.
On Kubernetes: KEDA exposes custom metrics for the Kubernetes Horizontal Pod Autoscaler; by default HPA can only use CPU/memory, which is limiting when you want to scale on e.g. requests/second or queue depth — KEDA's metrics adapter fills that gap.
Cold start mechanics
A cold start = provisioning a brand-new host instance: pull the runtime/worker image (or spin up a new App Service worker), load your app's dependencies, run any startup code, then invoke the trigger. Pre-warmed ("always ready") instances on Premium/Flex sidestep this by keeping idle-but-ready hosts around.
Common confusion
"Serverless" does not mean "no scaling infrastructure" — it means Azure operates it for you. On AKS + KEDA, you get the same event-driven, scale-to-zero behavior, but you are the one who owns node capacity, cluster upgrades, and the metrics adapter's health.
Key Components
Functions runtime host
Cross-platform process (also ships as a Docker image) that loads your function app, dispatches triggers, and manages bindings.
Language worker
Out-of-process worker per language (Node, Python, Java, PowerShell, custom handlers) that the host communicates with over gRPC.
Extension bundle
Versioned bundle of binding extensions (Storage, Service Bus, Cosmos DB, Durable Task, etc.) referenced in host.json.
Scale controller (Azure-hosted)
Azure-managed component monitoring trigger sources to add/remove instances on Consumption/Flex/Premium plans.
KEDA operator (Kubernetes)
Allows workloads to scale from 0 to N with support for Deployments, Jobs, StatefulSets, or any custom resource defining a /scale subresource.
KEDA metrics server
Exposes external metrics to the Kubernetes HPA for autoscaling purposes, such as messages in a queue or events in an Event Hub.
Durable Task extension
Adds orchestration/entity/activity triggers and the durable store (Storage/Netherite/SQL/Durable Task Scheduler) backing orchestration state.
Azure Functions Core Tools
Local CLI for scaffolding, running, and — for Kubernetes — generating deployment manifests (func kubernetes commands).
Request / Execution Flow
Trace of a queue-triggered function scaling out under load, contrasting the Azure-hosted path with the Kubernetes/KEDA path:
Fig 2. Same function code, two different scaling substrates for the same queue trigger.
Hosting Plans Compared
Plan
Scale to zero
Cold starts
VNet
Billing
Max instances
Consumptionlegacy
Yes
Yes, always
No
Per-execution + GB-s
Per-function-app cap
Flex Consumptionrecommended
Yes
Reduced (supports always-ready instances)
Yes — virtual network integration is a core feature
Per-execution, rounded per 100ms
Up to 1,000 (default limit 100, configurable)
Premium
No
None — no cold starts, essential for production APIs and latency-sensitive workloads
Yes, in/outbound
vCPU/memory per hour
Up to 100 in some regions (Linux)
Dedicated (App Service Plan)
No
No (always-on)
Yes
Fixed VM cost, shared with other App Service apps
Manual or App Service autoscale
Kubernetes / AKS + KEDA
Yes
Depends on image pull/pod start time
Native (your cluster's VNet)
Your cluster's compute cost
Container Apps default 10, configurable up to 1,000; AKS bounded by cluster capacity
Retirement notice
Function apps still on the end-of-life v3 runtime on Linux Consumption stop running after September 30, 2026, and the Linux Consumption plan itself is retiring on 30 September 2028 with no new features or language versions in the meantime — migrate to Flex Consumption.
Azure Functions + KEDA + AKS
Every Azure Functions trigger implementation has a matching KEDA scaler, which is what makes it possible to run the Functions runtime in a Docker container with event-driven scaling through KEDA on any Kubernetes cluster, including AKS — replicating serverless scale-to-zero/scale-to-N behavior outside Azure's managed platform entirely.
Why you'd choose this over a managed plan
Reasons to run Functions on AKS
You already operate an AKS cluster and want one platform (RBAC, observability, networking) for both microservices and event-driven functions.
You need scalers KEDA supports that aren't native Azure Functions triggers (Kafka, RabbitMQ, Prometheus, Postgres, etc.).
Data residency/network isolation requires functions to run entirely inside your own cluster's VNet without any Azure-managed compute layer.
You want a single autoscaling model (HPA) across both regular Deployments and Functions-based workloads.
Trade-offs you accept
You now own node capacity, cluster upgrades, and the KEDA install itself — none of that is "serverless" anymore.
HTTP-triggered autoscaling on Kubernetes is trickier than event-source triggers — default resource-based HPA doesn't give real scale-to-zero for HTTP, so an HTTP-aware KEDA scaler (e.g., Kedify's) or KEDA's own HTTP add-on is required.
No Azure billing simplicity — you pay for cluster compute whether or not functions are actively invoked (aside from scaling nodes down).
How the pieces fit together on AKS
Fig 3. KEDA sits between the event source and the standard Kubernetes HPA; the Functions container is just a Deployment.
AKS-native KEDA integration
AKS ships a managed KEDA add-on supporting ScaledObjects (Deployments, StatefulSets, or any custom resource with a /scale subresource) and ScaledJobs for job-like workloads, with production-grade security by decoupling autoscaling authentication from workloads, support for bring-your-own external scalers, and integration with Microsoft Entra Workload ID. On AKS Automatic, KEDA is preconfigured by default; on AKS Standard it's an opt-in add-on, and if you plan to use workload identity you must enable that before enabling KEDA.
Building and deploying a Functions container to AKS
# 1. Scaffold and containerize a function app locally
func init OrdersProcessor --docker
cd OrdersProcessor
func new --name ProcessOrder --template "Azure Queue Storage trigger"
# 2. Build and push the image
docker build -t myacr.azurecr.io/orders-processor:1.0 .
az acr login --name myacr
docker push myacr.azurecr.io/orders-processor:1.0
# 3. Generate Kubernetes manifests (Deployment + KEDA ScaledObject) from the func app
func kubernetes deploy \
--name orders-processor \
--image-name myacr.azurecr.io/orders-processor:1.0 \
--namespace orders
Explanation: func kubernetes deploy generates the Deployment, Secret (for trigger connection strings), and a KEDA ScaledObject wired to the function's trigger — the same artifact you could hand-author for full GitOps control.
Example: hand-authored ScaledObject for a queue-triggered function
Explanation: minReplicaCount: 0 is what gives true scale-to-zero; queueLength: "5" tells KEDA to add a replica for roughly every 5 queued messages, mirroring the Consumption plan's own queue-scaling heuristic. authenticationRef points at a TriggerAuthentication object — ideally backed by Entra Workload ID rather than a static connection string.
Decision guide
Default: Flex Consumption — for nearly all new event-driven serverless workloads.
Need zero cold starts + full VNet, still don't want to run a cluster: Premium plan.
Already run AKS, want one platform for services + functions, or need a non-Azure-native KEDA scaler: Functions-on-Kubernetes with the AKS KEDA add-on.
Want managed KEDA scaling without owning a Kubernetes cluster at all: consider Azure Container Apps, which uses a managed KEDA component under the hood to power container autoscaling including scale to zero.
Triggers, Bindings & APIs
Trigger type
Fires on
KEDA scaler available?
HTTP
Inbound HTTP request
Yes (HTTP add-on / third-party HTTP scalers)
Timer
CRON schedule
Yes (cron scaler)
Storage Queue
New message in an Azure Storage queue
Yes (azure-queue)
Service Bus Queue/Topic
New message / subscription message
Yes (azure-servicebus)
Event Hub
New events on a partition
Yes (azure-eventhub)
Blob Storage
New/updated blob (Event Grid-based)
Indirect, via Event Grid/queue scaler
Cosmos DB
Change feed activity
Yes (azure-cosmos-db)
Durable orchestration/entity/activity
Internal Durable Task Framework events
Not applicable — internal to the Durable Task extension
Trigger types requiring runtime-driven triggers are supported only in Kubernetes, Azure IoT Edge, and other self-hosted modes — not on the Consumption plan, which is one reason some workloads deliberately choose Kubernetes hosting.
Command Cheat Sheets
Azure Functions Core Tools (func)
Command
What it does
func init MyApp --docker
Scaffold a new function app with a Dockerfile
func new --template "Timer trigger"
Add a new function to the app
func start
Run the app locally with the Functions host
func azure functionapp publish NAME
Deploy to an Azure-hosted plan
func kubernetes deploy --name NAME --image-name IMG
Generate/apply Deployment + KEDA ScaledObject manifests
func kubernetes install --namespace NS
Install the KEDA component into a cluster
az CLI — function app lifecycle
Command
What it does
az functionapp create --flexconsumption-location eastus ...
Create a Flex Consumption function app
az functionapp create --consumption-plan-location eastus ...
Create a legacy Consumption function app
az functionapp plan create --sku EP1
Create a Premium plan
az functionapp deployment source config-zip --src app.zip
Zip-deploy code to an existing app
az functionapp config appsettings set --settings KEY=VALUE
Explanation: the runtime opens the connection, holds the message lock, calls the function, and completes/abandons the message automatically based on disposition — no manual receive/complete boilerplate is required.
Dockerfile for a Functions app targeting Kubernetes
FROM mcr.microsoft.com/azure-functions/python:4-python3.11
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
AzureFunctionsJobHost__Logging__Console__IsEnabled=true
COPY . /home/site/wwwroot
RUN pip install -r /home/site/wwwroot/requirements.txt
TriggerAuthentication backed by Entra Workload Identity
Explanation: this avoids putting a Storage/Service Bus connection string in a Kubernetes Secret — KEDA authenticates to Azure using the same Workload Identity federation pattern used for application pods.
Best Practices
Default new serverless workloads to Flex Consumption; reserve Premium for genuinely latency-sensitive, always-warm requirements.
Keep Durable Functions orchestrator code deterministic — push all I/O, randomness, and wall-clock reads into Activity functions.
Set a realistic functionTimeout in host.json rather than relying on plan defaults, especially on Consumption where the ceiling is short.
On Kubernetes, size minReplicaCount/maxReplicaCount per ScaledObject deliberately — an unbounded maxReplicaCount can starve cluster capacity from other workloads during a burst.
Use Entra Workload Identity (via KEDA's TriggerAuthentication with podIdentity.provider: azure-workload) instead of static connection strings for both the function's own bindings and KEDA's scaler authentication.
For HTTP-triggered functions on Kubernetes, don't rely on default resource-based HPA for scale-to-zero — use an HTTP-aware KEDA scaler.
Version and pin your extension bundle range in host.json; an unpinned range can silently pull breaking binding changes.
Load-test with realistic trigger burst patterns before committing to a plan — Consumption's per-function-app scale cap is a common surprise under multi-function fan-out.
Common Mistakes
Mistake Choosing Premium "just in case" for a low-traffic webhook, paying for always-on compute that Flex Consumption's always-ready instances would have handled far more cheaply.
Mistake Performing non-deterministic operations (HTTP calls, DateTime.Now, random numbers) directly inside a Durable Functions orchestrator instead of an Activity function.
Mistake Assuming Kubernetes-hosted Functions get "free" scale-to-zero for HTTP triggers the same way Consumption/Flex do — plain HPA cannot scale to zero and needs a KEDA HTTP scaler.
Mistake Leaving apps on the end-of-life v3 runtime on Linux Consumption past the retirement date instead of migrating proactively.
Mistake Embedding Storage/Service Bus connection strings as plaintext Kubernetes Secrets for KEDA's TriggerAuthentication instead of using Workload Identity federation.
Mistake Not setting a cooldownPeriod on a ScaledObject, causing thrashing (rapid scale-to-zero-and-back) under bursty, intermittent traffic.
Performance Considerations
Cold starts: most pronounced on classic Consumption; Flex Consumption and Premium mitigate via always-ready/pre-warmed instances; on Kubernetes, cold start = pod scheduling + image pull + container start, so image size and node capacity headroom matter directly.
Trigger scaling granularity: Flex Consumption scales HTTP/SignalR, Durable, and blob (Event Grid) triggers as separate groups, while everything else scales independently per function — this avoids one noisy timer trigger starving HTTP capacity, a real problem on classic Consumption.
KEDA polling interval: the default polling interval for most scalers is ~30 seconds — tune pollingInterval down for latency-sensitive scale-out, understanding the added load on the metrics source.
Node capacity on AKS: KEDA can decide to scale a Deployment out, but if no node has room, the pod sits Pending until the cluster's node autoprovisioner (Karpenter) reacts — plan for that extra latency versus Azure-hosted plans' effectively instant compute allocation.
Durable Functions orchestrator replay: long-running orchestrations with large history logs replay slower over time; use sub-orchestrations or continue-as-new to bound history size.
Security Considerations
Prefer Microsoft Entra ID-based authentication (managed identity / Workload Identity) over connection-string-based bindings wherever the extension supports it.
Set the appropriate authLevel (Function/Admin) on HTTP triggers, and rotate function keys regularly if not using Entra auth for HTTP endpoints.
Restrict inbound access via VNet integration and private endpoints on Flex Consumption/Premium/Dedicated; on AKS, use NetworkPolicy to scope which namespaces can reach the Functions pods.
On Kubernetes, never store event-source credentials as plain Kubernetes Secrets when a Workload Identity-based TriggerAuthentication is available.
Keep extension bundles and the Functions runtime major version current — the v1.x runtime is end-of-life and no longer receives security fixes.
Apply least-privilege RBAC/Azure role assignments to the managed identity backing both the function app and any KEDA scaler authentication.
Troubleshooting Guide
Function never fires despite messages in the queue
Diagnose: confirm the trigger's connection app setting name matches exactly what's referenced in code/bindings; check Application Insights (Azure-hosted) or kubectl logs (Kubernetes) for connection errors.
Fix: verify the storage/Service Bus connection string or managed identity role assignment; on Kubernetes also check the KEDA ScaledObject's authenticationRef resolved correctly.
Excessive cold starts despite Premium/Flex plan
Diagnose: check the configured "always ready"/pre-warmed instance count — it may be zero.
Fix: set a nonzero always-ready instance count for the specific trigger group experiencing cold starts.
KEDA ScaledObject shows but replicas never scale down to zero
Diagnose:kubectl describe scaledobject NAME — check cooldownPeriod and confirm the underlying metric is actually reporting zero.
Fix: verify minReplicaCount: 0 is set (default is often non-zero); confirm no other HPA or PodDisruptionBudget is preventing scale-in.
HTTP-triggered function on AKS doesn't scale down under no load
Diagnose: plain CPU/memory-based HPA keeps at least 1 replica warm because there's no "zero requests" signal it understands.
Fix: add an HTTP-aware KEDA scaler (KEDA HTTP Add-on or a third-party HTTP scaler) in front of the Deployment.
Durable Function orchestration appears "stuck"
Diagnose: check the orchestration history table/instance status via the Durable Functions HTTP management API or Durable extension's status query.
Common cause: a non-deterministic call inside the orchestrator broke replay consistency, or an Activity function is throwing and retries are exhausted.
Fix: move offending code into an Activity; check Activity function logs for the underlying exception.
Function app hitting Flex Consumption regional quota
Diagnose: executions/deployments delayed or throttled — check subscription+region core usage against the shared 250-core default quota.
Fix: request a quota increase, redistribute apps across regions, or reduce per-instance memory size if over-provisioned.
Interview Questions
Q: What's the fundamental difference between how Consumption and AKS+KEDA scale a Functions app?
Consumption's Azure-managed scale controller polls the trigger source directly and provisions Azure-managed host instances outside Kubernetes entirely. On AKS, KEDA polls the same kind of trigger source but publishes it as a custom metric that the standard Kubernetes HPA consumes to scale a Deployment — the scaling decision logic is conceptually the same, but the execution substrate and who operates it are completely different.
Q: Why is HTTP-triggered scale-to-zero harder on Kubernetes than on Consumption?
Native Kubernetes HPA only understands CPU/memory/custom metrics that already have a value — it can't detect "zero incoming requests" the way Azure's scale controller can for HTTP. KEDA needs a dedicated HTTP-aware scaler (proxying requests and reporting request-rate as a metric) to achieve true HTTP scale-to-zero.
Q: Why must Durable Functions orchestrator code be deterministic?
The Durable Task Framework uses event sourcing: it replays the orchestrator's history from the start every time it's reactivated. Non-deterministic operations (I/O, random values, current time) would produce different results on replay, corrupting orchestration state — so they must be isolated in Activity functions instead.
Q: When would you pick Flex Consumption over Premium?
When you want serverless per-execution billing and true scale-to-zero but still need VNet integration and reduced (not necessarily zero) cold starts via always-ready instances — Premium is for workloads that cannot tolerate any cold start and are willing to pay for continuously running compute.
Q: What does the KEDA add-on give you on AKS that vanilla open-source KEDA wouldn't automatically have?
Managed lifecycle (upgrades tied to AKS releases), decoupled autoscaling authentication (so scaler credentials aren't workload credentials), and native Microsoft Entra Workload ID integration — plus, on AKS Automatic, zero setup since it's preconfigured by default.
Q: Why might a team deliberately run Functions on AKS instead of any Azure-hosted plan?
To use a KEDA scaler with no native Azure Functions trigger equivalent (e.g., Kafka, RabbitMQ, Prometheus-based scaling), to unify the operational platform (RBAC, network policy, observability) with existing AKS microservices, or to satisfy network-isolation requirements that keep all compute inside a self-managed VNet/cluster.
Revision Cheat Sheet
2026 serverless default: Flex Consumption
Zero cold starts: Premium plan
Legacy, still exists: Consumption plan (Linux retiring Sep 2028)
v1.x runtime EOL: Sep 14, 2026
Scale on Azure-hosted plans: built-in scale controller
Scale on Kubernetes: KEDA → Kubernetes HPA
KEDA scale-to-zero unit: ScaledObject.minReplicaCount = 0
HTTP scale-to-zero on K8s: needs HTTP-aware KEDA scaler