Web 应用部署实战 #

项目概述 #

本案例演示如何将一个 Node.js Web 应用部署到 Azure App Service,并配置完整的 CI/CD 流水线。

text
┌─────────────────────────────────────────────────────────────┐
│                    项目架构                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  技术栈                                                      │
│  ├── 前端: Node.js + Express                                │
│  ├── 数据库: Azure SQL Database                             │
│  ├── 缓存: Azure Cache for Redis                            │
│  ├── 托管: Azure App Service                                │
│  └── CI/CD: Azure DevOps Pipelines                          │
│                                                             │
│  架构图                                                      │
│                                                             │
│  用户 ──► Azure DNS ──► App Service ──► Redis Cache         │
│                              │                               │
│                              └──► Azure SQL Database         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

步骤 1: 创建基础设施 #

使用 Bicep 创建资源 #

bicep
// main.bicep
param location string = resourceGroup().location
param appName string
param environment string = 'dev'

var uniqueString = uniqueString(resourceGroup().id)
var sqlServerName = 'sql-${appName}-${uniqueString}'
var appServicePlanName = 'plan-${appName}'
var webAppName = 'app-${appName}-${environment}'
var redisCacheName = 'redis-${appName}-${uniqueString}'

// App Service 计划
resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = {
  name: appServicePlanName
  location: location
  sku: {
    name: 'P1v3'
    tier: 'PremiumV3'
    capacity: 1
  }
  properties: {
    reserved: false
  }
}

// Web 应用
resource webApp 'Microsoft.Web/sites@2023-01-01' = {
  name: webAppName
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      nodeVersion: '18-lts'
      appSettings: [
        {
          name: 'NODE_ENV'
          value: environment
        }
        {
          name: 'REDIS_URL'
          value: '@Microsoft.KeyVault(VaultName=${kvName};SecretName=RedisConnectionString)'
        }
        {
          name: 'DATABASE_URL'
          value: '@Microsoft.KeyVault(VaultName=${kvName};SecretName=DatabaseConnectionString)'
        }
      ]
    }
  }
  identity: {
    type: 'SystemAssigned'
  }
}

// SQL Server
resource sqlServer 'Microsoft.Sql/servers@2023-05-01-preview' = {
  name: sqlServerName
  location: location
  properties: {
    administratorLogin: 'sqladmin'
    administratorLoginPassword: sqlAdminPassword
  }
}

// SQL Database
resource sqlDatabase 'Microsoft.Sql/servers/databases@2023-05-01-preview' = {
  parent: sqlServer
  name: 'appdb'
  location: location
  sku: {
    name: 'S1'
    tier: 'Standard'
  }
}

// Redis Cache
resource redisCache 'Microsoft.Cache/redis@2023-05-01-preview' = {
  name: redisCacheName
  location: location
  properties: {
    sku: {
      name: 'Standard'
      family: 'C'
      capacity: 1
    }
  }
}

output webAppUrl string = 'https://${webApp.defaultHostName}'

部署基础设施 #

bash
# 创建资源组
az group create --name rg-webapp-prod --location eastus

# 部署 Bicep 文件
az deployment group create \
  --resource-group rg-webapp-prod \
  --template-file main.bicep \
  --parameters appName=mywebapp environment=prod

步骤 2: 准备应用代码 #

应用结构 #

text
my-webapp/
├── src/
│   ├── index.js
│   ├── routes/
│   │   └── api.js
│   └── config/
│       └── database.js
├── package.json
├── azure-pipelines.yml
└── .env.example

应用入口 #

javascript
// src/index.js
const express = require('express');
const redis = require('redis');
const app = express();

const redisClient = redis.createClient({
  url: process.env.REDIS_URL
});

redisClient.connect().catch(console.error);

app.get('/', async (req, res) => {
  const cacheKey = 'homepage';
  const cached = await redisClient.get(cacheKey);
  
  if (cached) {
    return res.json({ source: 'cache', data: JSON.parse(cached) });
  }
  
  const data = { message: 'Hello from Azure!', timestamp: new Date() };
  await redisClient.setEx(cacheKey, 3600, JSON.stringify(data));
  
  res.json({ source: 'database', data });
});

app.get('/health', (req, res) => {
  res.json({ status: 'healthy' });
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

package.json #

json
{
  "name": "my-webapp",
  "version": "1.0.0",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "test": "jest"
  },
  "dependencies": {
    "express": "^4.18.2",
    "redis": "^4.6.0"
  },
  "devDependencies": {
    "jest": "^29.5.0"
  }
}

步骤 3: 配置 CI/CD #

Azure Pipelines 配置 #

yaml
# azure-pipelines.yml
trigger:
- main

variables:
  azureSubscription: 'myServiceConnection'
  appName: 'app-mywebapp-prod'

stages:
- stage: Build
  jobs:
  - job: BuildJob
    pool:
      vmImage: 'ubuntu-latest'
    steps:
    - task: NodeTool@0
      inputs:
        versionSpec: '18.x'
    
    - script: |
        npm install
        npm run build --if-present
      displayName: 'Install and build'
    
    - script: |
        npm test
      displayName: 'Run tests'
    
    - task: ArchiveFiles@2
      inputs:
        rootFolderOrFile: '$(Build.SourcesDirectory)'
        includeRootFolder: false
        archiveType: 'zip'
        archiveFile: '$(Build.ArtifactStagingDirectory)/app.zip'
    
    - task: PublishBuildArtifacts@1
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)'
        ArtifactName: 'drop'

- stage: Deploy
  dependsOn: Build
  condition: succeeded()
  jobs:
  - deployment: DeployJob
    environment: 'production'
    pool:
      vmImage: 'ubuntu-latest'
    strategy:
      runOnce:
        deploy:
          steps:
          - task: AzureWebApp@1
            inputs:
              azureSubscription: $(azureSubscription)
              appName: $(appName)
              package: '$(Pipeline.Workspace)/drop/app.zip'

步骤 4: 配置自定义域名 #

添加自定义域名 #

bash
# 添加自定义域名
az webapp config hostname add \
  --resource-group rg-webapp-prod \
  --webapp-name app-mywebapp-prod \
  --hostname www.example.com

# 获取域名验证 ID
az webapp config hostname get-external-ip \
  --resource-group rg-webapp-prod \
  --webapp-name app-mywebapp-prod

# 配置 DNS CNAME 记录
# www.example.com -> app-mywebapp-prod.azurewebsites.net

绑定 SSL 证书 #

bash
# 上传 SSL 证书
az webapp config ssl upload \
  --resource-group rg-webapp-prod \
  --name app-mywebapp-prod \
  --certificate-file ./certificate.pfx \
  --certificate-password <password>

# 绑定证书到域名
az webapp config ssl bind \
  --resource-group rg-webapp-prod \
  --name app-mywebapp-prod \
  --certificate-thumbprint <thumbprint> \
  --ssl-type SNI

步骤 5: 配置监控 #

启用 Application Insights #

bash
# 创建 Application Insights
az monitor app-insights component create \
  --app mywebapp-insights \
  --location eastus \
  --resource-group rg-webapp-prod

# 关联到 Web 应用
az webapp config appsettings set \
  --resource-group rg-webapp-prod \
  --name app-mywebapp-prod \
  --settings APPINSIGHTS_INSTRUMENTATIONKEY=<instrumentation-key>

配置警报 #

bash
# 创建警报规则
az monitor metrics alert create \
  --name "High Response Time" \
  --resource-group rg-webapp-prod \
  --scopes /subscriptions/<sub>/resourceGroups/rg-webapp-prod/providers/Microsoft.Web/sites/app-mywebapp-prod \
  --condition "avg response_time > 5000" \
  --window-size 5m \
  --evaluation-frequency 1m \
  --action <action-group-id>

最佳实践 #

部署建议 #

text
┌─────────────────────────────────────────────────────────────┐
│                    部署最佳实践                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 使用部署槽                                               │
│     └── 零停机部署                                          │
│                                                             │
│  2. 启用自动扩展                                             │
│     └── 根据负载自动调整                                    │
│                                                             │
│  3. 配置健康检查                                             │
│     └── 自动检测和恢复                                      │
│                                                             │
│  4. 使用 Key Vault                                           │
│     └── 安全存储机密                                        │
│                                                             │
│  5. 启用诊断日志                                             │
│     └── 问题排查                                            │
│                                                             │
│  6. 配置备份                                                 │
│     └── 数据保护                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

下一步 #

现在你已经完成了 Web 应用部署实战,接下来学习 无服务器应用 了解无服务器架构!

最后更新:2026-03-29