第九章-系统部署与运维指南
第九章:系统部署与运维指南
目录
1. 部署环境准备
1.1 服务器要求
最低配置:
- CPU:2核
- 内存:4GB
- 硬盘:50GB SSD
- 带宽:5Mbps
推荐配置:
- CPU:4核或以上
- 内存:8GB或以上
- 硬盘:100GB SSD
- 带宽:10Mbps或以上
1.2 软件环境
必需软件:
| 软件 | 版本 | 用途 |
|---|---|---|
| .NET Runtime | 8.0 | 运行后端应用 |
| Nginx | 最新版 | 反向代理 |
| MySQL/SQL Server | 8.0/2019 | 数据库 |
| Redis | 7.x | 缓存服务 |
可选软件:
| 软件 | 用途 |
|---|---|
| Docker | 容器化部署 |
| Docker Compose | 容器编排 |
| Supervisor | 进程管理 |
| Elasticsearch | 日志存储 |
1.3 网络规划
Internet
│
▼
┌────────────────┐
│ 防火墙 │
│ (80, 443) │
└────────────────┘
│
▼
┌────────────────┐
│ Nginx │
│ (反向代理) │
└────────────────┘
│ │
┌─────────┘ └─────────┐
▼ ▼
┌────────────────┐ ┌────────────────┐
│ 前端静态文件 │ │ 后端API │
│ (Vue3 dist) │ │ (5005端口) │
└────────────────┘ └────────────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ MySQL │ │ Redis │ │ MinIO │
│ (3306) │ │ (6379) │ │ (9000) │
└────────────┘ └────────────┘ └────────────┘
1.4 安全配置
防火墙规则:
# 只开放必要端口
# HTTP
sudo ufw allow 80/tcp
# HTTPS
sudo ufw allow 443/tcp
# SSH
sudo ufw allow 22/tcp
# 启用防火墙
sudo ufw enable
SSL证书:
# 使用Let's Encrypt免费证书
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com
2. 后端项目发布
2.1 发布配置
修改appsettings.Production.json:
{
"Urls": "http://*:5005",
"DbSettings": {
"EnableInitDb": false,
"EnableInitSeed": false,
"EnableSqlLog": false,
"DbConfigs": [
{
"ConfigId": "1300000000001",
"DbType": "MySql",
"ConnectionString": "Data Source=localhost;Database=AdminNET;User ID=admin;Password=StrongPassword123!;pooling=true;port=3306;sslmode=none;CharSet=utf8mb4;AllowLoadLocalInfile=true",
"IsAutoCloseConnection": true
}
]
},
"Cache": {
"CacheType": "Redis",
"Redis": {
"ConnectionString": "localhost:6379,password=RedisPassword123!,defaultDatabase=0"
}
},
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft.AspNetCore": "Warning"
}
}
}
2.2 命令行发布
# 进入后端项目目录
cd Admin.NET/Admin.NET
# 发布Release版本
dotnet publish Admin.NET.Web.Entry -c Release -o ./publish
# 发布到指定运行时
# Windows
dotnet publish Admin.NET.Web.Entry -c Release -r win-x64 --self-contained false -o ./publish/win
# Linux
dotnet publish Admin.NET.Web.Entry -c Release -r linux-x64 --self-contained false -o ./publish/linux
# 独立部署(包含运行时)
dotnet publish Admin.NET.Web.Entry -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true -o ./publish/linux-self
2.3 Visual Studio发布
- 右键点击
Admin.NET.Web.Entry项目 - 选择"发布"
- 选择目标:
- 文件夹(用于手动部署)
- FTP/FTPS
- Web服务器(IIS)
- Azure
- 配置发布选项:
- 配置:Release
- 目标框架:net8.0
- 部署模式:框架依赖
- 目标运行时:可移植
- 点击"发布"
2.4 发布文件说明
publish/
├── Admin.NET.Web.Entry.dll # 主程序
├── Admin.NET.Web.Entry.pdb # 调试符号
├── Admin.NET.Core.dll # 核心层
├── Admin.NET.Application.dll # 应用层
├── appsettings.json # 配置文件
├── appsettings.Production.json # 生产环境配置
├── wwwroot/ # 静态文件
├── web.config # IIS配置
└── runtimes/ # 运行时依赖
3. 前端项目构建
3.1 环境配置
修改.env.production:
# 生产环境配置
VITE_PORT = 2800
VITE_OPEN = false
VITE_PROXY_URL =
# API地址(实际部署地址)
VITE_API_URL = https://api.yourdomain.com
# 是否启用压缩
VITE_BUILD_COMPRESS = gzip
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
3.2 构建命令
# 进入前端项目目录
cd Web
# 安装依赖(如果没有)
pnpm install
# 生产环境构建
pnpm build
# 构建并预览
pnpm build && pnpm preview
3.3 构建输出
dist/
├── index.html # 入口文件
├── assets/ # 静态资源
│ ├── css/ # 样式文件
│ │ └── index.[hash].css
│ ├── js/ # JS文件
│ │ ├── index.[hash].js
│ │ └── vendor.[hash].js
│ └── images/ # 图片
├── static/ # 静态资源
└── favicon.ico # 图标
3.4 构建优化
vite.config.ts配置:
export default defineConfig({
build: {
// 指定输出目录
outDir: 'dist',
// 资源内联阈值
assetsInlineLimit: 4096,
// CSS代码分割
cssCodeSplit: true,
// 生成sourcemap
sourcemap: false,
// 代码压缩
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
// 分块策略
rollupOptions: {
output: {
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'element-plus': ['element-plus'],
'utils': ['axios', 'dayjs', 'lodash-es']
}
}
}
}
})
4. Windows部署
4.1 IIS部署
安装IIS和ASP.NET Core托管模块:
- 打开"服务器管理器"
- 添加角色和功能
- 选择"Web服务器(IIS)"
- 下载并安装ASP.NET Core托管捆绑包:
https://dotnet.microsoft.com/download/dotnet/8.0
配置IIS站点:
- 打开IIS管理器
- 添加网站:
- 网站名称:AdminNET
- 物理路径:C:\inetpub\wwwroot\AdminNET
- 绑定:http://yourdomain.com:80
- 配置应用程序池:
- .NET CLR版本:无托管代码
- 托管管道模式:集成
- 标识:ApplicationPoolIdentity
web.config配置:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet" arguments=".\Admin.NET.Web.Entry.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" hostingModel="InProcess">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
</environmentVariables>
</aspNetCore>
</system.webServer>
</location>
</configuration>
4.2 Windows Service部署
安装为Windows服务:
# 创建服务
sc create AdminNET binPath= "C:\AdminNET\Admin.NET.Web.Entry.exe" start= auto
# 启动服务
sc start AdminNET
# 停止服务
sc stop AdminNET
# 删除服务
sc delete AdminNET
使用NSSM管理:
# 下载NSSM
# https://nssm.cc/download
# 安装服务
nssm install AdminNET
# 配置路径
# Application: C:\AdminNET\Admin.NET.Web.Entry.exe
# Startup directory: C:\AdminNET
# Arguments:
# 服务管理
nssm start AdminNET
nssm stop AdminNET
nssm restart AdminNET
5. Linux部署
5.1 安装.NET运行时
Ubuntu/Debian:
# 添加Microsoft包源
wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
# 安装运行时
sudo apt-get update
sudo apt-get install -y aspnetcore-runtime-8.0
# 验证安装
dotnet --list-runtimes
CentOS/RHEL:
# 添加Microsoft仓库
sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
# 安装运行时
sudo yum install aspnetcore-runtime-8.0
5.2 部署应用
# 创建应用目录
sudo mkdir -p /var/www/adminnet
sudo chown -R $USER:$USER /var/www/adminnet
# 上传发布文件
scp -r ./publish/* user@server:/var/www/adminnet/
# 设置执行权限
chmod +x /var/www/adminnet/Admin.NET.Web.Entry
# 测试运行
cd /var/www/adminnet
dotnet Admin.NET.Web.Entry.dll
5.3 使用Systemd管理
创建服务文件:
sudo nano /etc/systemd/system/adminnet.service
[Unit]
Description=Admin.NET Web Application
After=network.target
[Service]
WorkingDirectory=/var/www/adminnet
ExecStart=/usr/bin/dotnet /var/www/adminnet/Admin.NET.Web.Entry.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=adminnet
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
管理服务:
# 重新加载配置
sudo systemctl daemon-reload
# 启动服务
sudo systemctl start adminnet
# 停止服务
sudo systemctl stop adminnet
# 重启服务
sudo systemctl restart adminnet
# 开机启动
sudo systemctl enable adminnet
# 查看状态
sudo systemctl status adminnet
# 查看日志
sudo journalctl -u adminnet -f
5.4 使用Supervisor管理
安装Supervisor:
sudo apt-get install supervisor
创建配置文件:
sudo nano /etc/supervisor/conf.d/adminnet.conf
[program:adminnet]
command=/usr/bin/dotnet /var/www/adminnet/Admin.NET.Web.Entry.dll
directory=/var/www/adminnet
autostart=true
autorestart=true
stderr_logfile=/var/log/adminnet/err.log
stdout_logfile=/var/log/adminnet/out.log
environment=ASPNETCORE_ENVIRONMENT=Production
user=www-data
stopsignal=INT
管理应用:
# 创建日志目录
sudo mkdir -p /var/log/adminnet
# 重新加载配置
sudo supervisorctl reread
sudo supervisorctl update
# 启动应用
sudo supervisorctl start adminnet
# 停止应用
sudo supervisorctl stop adminnet
# 重启应用
sudo supervisorctl restart adminnet
# 查看状态
sudo supervisorctl status
6. Docker容器化部署
6.1 创建Dockerfile
后端Dockerfile:
# Admin.NET/Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 5005
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["Admin.NET/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj", "Admin.NET.Web.Entry/"]
COPY ["Admin.NET/Admin.NET.Web.Core/Admin.NET.Web.Core.csproj", "Admin.NET.Web.Core/"]
COPY ["Admin.NET/Admin.NET.Application/Admin.NET.Application.csproj", "Admin.NET.Application/"]
COPY ["Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj", "Admin.NET.Core/"]
RUN dotnet restore "Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj"
COPY Admin.NET/ .
WORKDIR "/src/Admin.NET.Web.Entry"
RUN dotnet build "Admin.NET.Web.Entry.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Admin.NET.Web.Entry.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Admin.NET.Web.Entry.dll"]
前端Dockerfile:
# Web/Dockerfile
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install
COPY . .
RUN pnpm build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
6.2 Docker Compose编排
# docker-compose.yml
version: '3.8'
services:
# MySQL数据库
mysql:
image: mysql:8.0
container_name: adminnet-mysql
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: AdminNET
MYSQL_USER: admin
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "3306:3306"
networks:
- adminnet-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
# Redis缓存
redis:
image: redis:7-alpine
container_name: adminnet-redis
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
ports:
- "6379:6379"
networks:
- adminnet-network
# 后端API
api:
build:
context: .
dockerfile: Admin.NET/Dockerfile
container_name: adminnet-api
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://+:5005
- ConnectionStrings__Default=Server=mysql;Database=AdminNET;Uid=admin;Pwd=${MYSQL_PASSWORD};
- Redis__ConnectionString=redis:6379,password=${REDIS_PASSWORD}
ports:
- "5005:5005"
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
networks:
- adminnet-network
restart: unless-stopped
# 前端Web
web:
build:
context: ./Web
dockerfile: Dockerfile
container_name: adminnet-web
ports:
- "80:80"
depends_on:
- api
networks:
- adminnet-network
restart: unless-stopped
volumes:
mysql_data:
redis_data:
networks:
adminnet-network:
driver: bridge
6.3 部署命令
# 创建环境变量文件
cat > .env << EOF
MYSQL_ROOT_PASSWORD=RootPassword123!
MYSQL_PASSWORD=AdminPassword123!
REDIS_PASSWORD=RedisPassword123!
EOF
# 构建镜像
docker-compose build
# 启动所有服务
docker-compose up -d
# 查看日志
docker-compose logs -f
# 停止服务
docker-compose down
# 停止并删除数据
docker-compose down -v
7. Nginx反向代理配置
7.1 安装Nginx
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install nginx
# CentOS
sudo yum install epel-release
sudo yum install nginx
# 启动Nginx
sudo systemctl start nginx
sudo systemctl enable nginx
7.2 配置文件
# /etc/nginx/conf.d/adminnet.conf
# 上游服务器
upstream adminnet_api {
server 127.0.0.1:5005;
keepalive 32;
}
# HTTP重定向到HTTPS
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$server_name$request_uri;
}
# HTTPS配置
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
# SSL证书
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
# SSL安全配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml application/xml+rss;
# 前端静态文件
location / {
root /var/www/adminnet/web;
index index.html;
try_files $uri $uri/ /index.html;
# 缓存静态资源
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# API代理
location /api {
proxy_pass http://adminnet_api;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
}
# SignalR WebSocket代理
location /hubs {
proxy_pass http://adminnet_api;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400;
}
# Swagger文档
location /swagger {
proxy_pass http://adminnet_api;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 健康检查
location /health {
proxy_pass http://adminnet_api/health;
access_log off;
}
# 错误页面
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
7.3 验证并重启
# 测试配置
sudo nginx -t
# 重新加载配置
sudo nginx -s reload
# 重启Nginx
sudo systemctl restart nginx
8. 运维监控与日志管理
8.1 健康检查
配置健康检查端点:
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks()
.AddDbContextCheck<DbContext>()
.AddRedis("redis连接字符串")
.AddCheck("disk", () =>
{
var driveInfo = new DriveInfo("C:");
var freeSpacePercent = (double)driveInfo.AvailableFreeSpace / driveInfo.TotalSize * 100;
return freeSpacePercent > 10
? HealthCheckResult.Healthy()
: HealthCheckResult.Unhealthy("磁盘空间不足");
});
}
public void Configure(IApplicationBuilder app)
{
app.UseHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json";
var result = new
{
status = report.Status.ToString(),
checks = report.Entries.Select(e => new
{
name = e.Key,
status = e.Value.Status.ToString(),
description = e.Value.Description
})
};
await context.Response.WriteAsJsonAsync(result);
}
});
}
8.2 日志配置
Serilog配置:
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "logs/log-.txt",
"rollingInterval": "Day",
"retainedFileCountLimit": 30,
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "Elasticsearch",
"Args": {
"nodeUris": "http://localhost:9200",
"indexFormat": "adminnet-{0:yyyy.MM.dd}",
"autoRegisterTemplate": true
}
}
],
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"]
}
}
8.3 监控脚本
服务监控脚本:
#!/bin/bash
# /usr/local/bin/monitor_adminnet.sh
# 配置
SERVICE_NAME="adminnet"
HEALTH_URL="http://localhost:5005/health"
ALERT_EMAIL="admin@example.com"
# 检查服务状态
check_service() {
if ! systemctl is-active --quiet $SERVICE_NAME; then
echo "服务未运行,正在重启..."
systemctl restart $SERVICE_NAME
send_alert "服务重启" "Admin.NET服务异常,已自动重启"
fi
}
# 检查健康状态
check_health() {
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" $HEALTH_URL)
if [ "$HTTP_CODE" != "200" ]; then
echo "健康检查失败,HTTP状态码:$HTTP_CODE"
send_alert "健康检查失败" "Admin.NET健康检查返回:$HTTP_CODE"
fi
}
# 检查磁盘空间
check_disk() {
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
send_alert "磁盘告警" "磁盘使用率已达${DISK_USAGE}%"
fi
}
# 检查内存
check_memory() {
MEM_USAGE=$(free | grep Mem | awk '{print int($3/$2 * 100)}')
if [ "$MEM_USAGE" -gt 90 ]; then
send_alert "内存告警" "内存使用率已达${MEM_USAGE}%"
fi
}
# 发送告警
send_alert() {
SUBJECT=$1
BODY=$2
echo "$BODY" | mail -s "[Admin.NET告警] $SUBJECT" $ALERT_EMAIL
}
# 执行检查
check_service
check_health
check_disk
check_memory
添加定时任务:
# 编辑crontab
crontab -e
# 每5分钟执行一次监控
*/5 * * * * /usr/local/bin/monitor_adminnet.sh >> /var/log/adminnet/monitor.log 2>&1
8.4 日志分析
使用Logrotate管理日志:
# /etc/logrotate.d/adminnet
/var/www/adminnet/logs/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 www-data www-data
sharedscripts
postrotate
systemctl reload adminnet > /dev/null 2>&1 || true
endscript
}
8.5 性能监控
Prometheus + Grafana:
// 添加Prometheus指标
services.AddHealthChecks()
.AddPrometheus();
app.UseHttpMetrics();
app.UseGrpcMetrics();
app.UseEndpoints(endpoints =>
{
endpoints.MapMetrics(); // /metrics端点
});
总结
本章详细介绍了Admin.NET的部署与运维:
- 环境准备:服务器要求、软件环境、网络规划
- 后端发布:配置修改、命令行发布、VS发布
- 前端构建:环境配置、构建优化、输出文件
- Windows部署:IIS部署、Windows服务
- Linux部署:Systemd、Supervisor管理
- Docker部署:Dockerfile、Docker Compose
- Nginx配置:反向代理、SSL、WebSocket
- 运维监控:健康检查、日志管理、监控告警
掌握这些部署运维知识,可以确保Admin.NET应用稳定运行。在下一章中,我们将学习最佳实践和常见问题解答。

浙公网安备 33010602011771号