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