无服务器应用实战 #

项目概述 #

本案例演示如何使用 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