无服务器应用实战 #
项目概述 #
本案例演示如何使用 Azure Functions 构建一个完整的无服务器 API 应用。
text
┌─────────────────────────────────────────────────────────────┐
│ 项目架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 技术栈 │
│ ├── API: Azure Functions │
│ ├── API 网关: Azure API Management │
│ ├── 数据库: Cosmos DB │
│ ├── 存储: Blob Storage │
│ ├── 队列: Queue Storage │
│ └── 监控: Application Insights │
│ │
│ 架构图 │
│ │
│ 客户端 ──► API Management ──► Azure Functions │
│ │ │
│ ┌──────────────────────┼───────────────────┐ │
│ ▼ ▼ ▼ │
│ Cosmos DB Blob Storage Queue │
│ │
└─────────────────────────────────────────────────────────────┘
步骤 1: 创建函数应用 #
项目结构 #
text
my-serverless-app/
├── src/
│ ├── functions/
│ │ ├── GetProducts/
│ │ │ ├── function.json
│ │ │ └── index.js
│ │ ├── CreateProduct/
│ │ │ ├── function.json
│ │ │ └── index.js
│ │ └── ProcessQueue/
│ │ ├── function.json
│ │ └── index.js
│ └── shared/
│ └── cosmos.js
├── host.json
├── local.settings.json
├── package.json
└── azure-pipelines.yml
函数代码 #
javascript
// src/functions/GetProducts/index.js
const { CosmosClient } = require('@azure/cosmos');
const client = new CosmosClient(process.env.COSMOS_CONNECTION_STRING);
const container = client.database('products').container('items');
module.exports = async function (context, req) {
try {
const { resources: products } = await container.items
.query('SELECT * FROM c')
.fetchAll();
context.res = {
status: 200,
body: products
};
} catch (error) {
context.res = {
status: 500,
body: { error: error.message }
};
}
};
json
// src/functions/GetProducts/function.json
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get"],
"route": "products"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
创建产品函数 #
javascript
// src/functions/CreateProduct/index.js
const { CosmosClient } = require('@azure/cosmos');
const client = new CosmosClient(process.env.COSMOS_CONNECTION_STRING);
const container = client.database('products').container('items');
module.exports = async function (context, req) {
const product = req.body;
product.id = generateId();
product.createdAt = new Date().toISOString();
try {
const { resource } = await container.items.create(product);
// 发送消息到队列进行后续处理
context.bindings.outputQueueItem = {
productId: resource.id,
action: 'created'
};
context.res = {
status: 201,
body: resource
};
} catch (error) {
context.res = {
status: 500,
body: { error: error.message }
};
}
};
function generateId() {
return Math.random().toString(36).substring(2, 15);
}
json
// src/functions/CreateProduct/function.json
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["post"],
"route": "products"
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "queue",
"direction": "out",
"name": "outputQueueItem",
"queueName": "product-events",
"connection": "AzureWebJobsStorage"
}
]
}
步骤 2: 部署基础设施 #
Bicep 模板 #
bicep
// main.bicep
param location string = resourceGroup().location
param appName string
param environment string = 'dev'
var functionAppName = 'func-${appName}-${environment}'
var storageAccountName = 'st${replace(appName, '-', '')}${uniqueString(resourceGroup().id)}'
var cosmosAccountName = 'cosmos-${appName}-${uniqueString(resourceGroup().id)}'
var apimName = 'apim-${appName}'
// 存储账户
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: storageAccountName
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
accessTier: 'Hot'
}
}
// 函数应用计划
resource functionPlan 'Microsoft.Web/serverfarms@2023-01-01' = {
name: 'plan-${appName}-func'
location: location
sku: {
name: 'Y1'
tier: 'Dynamic'
}
properties: {
maximumElasticWorkerCount: 1
}
}
// 函数应用
resource functionApp 'Microsoft.Web/sites@2023-01-01' = {
name: functionAppName
location: location
kind: 'functionapp'
properties: {
serverFarmId: functionPlan.id
siteConfig: {
appSettings: [
{
name: 'AzureWebJobsStorage'
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=core.windows.net;AccountKey=${storageAccount.listKeys().keys[0].value}'
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'node'
}
{
name: 'COSMOS_CONNECTION_STRING'
value: '@Microsoft.KeyVault(VaultName=${kvName};SecretName=CosmosConnectionString)'
}
]
}
}
identity: {
type: 'SystemAssigned'
}
}
// Cosmos DB
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = {
name: cosmosAccountName
location: location
properties: {
databaseAccountOfferType: 'Standard'
consistencyPolicy: {
defaultConsistencyLevel: 'Session'
}
locations: [
{
locationName: location
failoverPriority: 0
}
]
}
}
resource cosmosDatabase 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2023-04-15' = {
parent: cosmosAccount
name: 'products'
properties: {
resource: {
id: 'products'
}
}
}
// API Management
resource apim 'Microsoft.ApiManagement/service@2023-03-01-preview' = {
name: apimName
location: location
sku: {
name: 'Consumption'
capacity: 0
}
properties: {
publisherEmail: 'admin@example.com'
publisherName: 'My Company'
}
}
output functionAppUrl string = 'https://${functionApp.defaultHostName}'
步骤 3: 配置 API Management #
API 定义 #
xml
<!-- api-definition.xml -->
<api name="Products API">
<operations>
<operation name="GetProducts" method="GET" url-template="/products" />
<operation name="CreateProduct" method="POST" url-template="/products" />
</operations>
<policies>
<inbound>
<base />
<set-backend-service base-url="https://func-myapp-prod.azurewebsites.net/api" />
<authentication-managed-identity resource="https://management.azure.com" />
</inbound>
</policies>
</api>
配置策略 #
bash
# 创建 API
az apim api create \
--resource-group rg-serverless-prod \
--service-name apim-myapp \
--api-id products-api \
--path products \
--protocols https
# 添加操作
az apim api operation create \
--resource-group rg-serverless-prod \
--service-name apim-myapp \
--api-id products-api \
--operation-id get-products \
--method GET \
--url-template /products
步骤 4: 配置 CI/CD #
Azure Pipelines #
yaml
# azure-pipelines.yml
trigger:
- main
variables:
functionAppName: 'func-myapp-prod'
stages:
- stage: Build
jobs:
- job: Build
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '18.x'
- script: |
npm install
npm run build --if-present
displayName: 'Install dependencies'
- task: ArchiveFiles@2
inputs:
rootFolderOrFile: '$(Build.SourcesDirectory)'
includeRootFolder: false
archiveType: 'zip'
archiveFile: '$(Build.ArtifactStagingDirectory)/functionapp.zip'
- publish: '$(Build.ArtifactStagingDirectory)'
artifact: drop
- stage: Deploy
dependsOn: Build
jobs:
- deployment: Deploy
environment: 'production'
pool:
vmImage: 'ubuntu-latest'
strategy:
runOnce:
deploy:
steps:
- task: AzureFunctionApp@1
inputs:
azureSubscription: 'myServiceConnection'
appType: functionApp
appName: $(functionAppName)
package: '$(Pipeline.Workspace)/drop/functionapp.zip'
步骤 5: 监控和日志 #
配置 Application Insights #
javascript
// host.json
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[3.*, 4.0.0)"
}
}
监控指标 #
bash
# 查看函数执行统计
az monitor metrics list \
--resource /subscriptions/<sub>/resourceGroups/rg-serverless-prod/providers/Microsoft.Web/sites/func-myapp-prod \
--metric "FunctionExecutionCount,FunctionExecutionUnits" \
--output table
最佳实践 #
无服务器设计建议 #
text
┌─────────────────────────────────────────────────────────────┐
│ 无服务器最佳实践 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 函数设计 │
│ ├── 单一职责 │
│ ├── 无状态 │
│ └── 快速执行 │
│ │
│ 2. 连接管理 │
│ ├── 重用连接 │
│ └── 使用连接池 │
│ │
│ 3. 错误处理 │
│ ├── 重试策略 │
│ └── 死信队列 │
│ │
│ 4. 安全 │
│ ├── 使用托管身份 │
│ ├── API 密钥管理 │
│ └── 输入验证 │
│ │
│ 5. 成本优化 │
│ ├── 合理设置超时 │
│ └── 监控执行时间 │
│ │
└─────────────────────────────────────────────────────────────┘
下一步 #
恭喜你完成了 Azure 云服务文档的学习!你现在可以从 Azure 简介 重新开始复习,或者探索更多 Azure 服务!
最后更新:2026-03-29