28 KiB
28 KiB
外卖SaaS系统 - 部署运维
0. 仓库与服务入口(必读)
项目已拆分为多仓:AdminApi / TenantApi / Gateway / BuildingBlocks / Docs。本文档中若出现旧单仓路径(如
src/TakeoutSaaS.Api),请按以下映射替换为对应服务路径。
0.1 仓库划分
- TakeoutSaaS.AdminApi:管理后台 API(运营/客服/平台管理)。
- TakeoutSaaS.TenantApi:租户端 API(MiniApi/UserApi)。
- TakeoutSaaS.Gateway:YARP API 网关。
- TakeoutSaaS.BuildingBlocks:共享基础组件库(以子模块方式引入)。
- TakeoutSaaS.Docs:文档与运维资产。
0.2 旧路径映射(常见)
- 旧:
src/TakeoutSaaS.Api
新:src/Api/TakeoutSaaS.AdminApi(AdminApi)或src/Api/TakeoutSaaS.MiniApi/src/Api/TakeoutSaaS.UserApi(TenantApi)。 - Dockerfile:
- AdminApi:
src/Api/TakeoutSaaS.AdminApi/Dockerfile - MiniApi:
src/Api/TakeoutSaaS.MiniApi/Dockerfile - UserApi:
src/Api/TakeoutSaaS.UserApi/Dockerfile - Gateway:独立仓库根目录
Dockerfile(若以子模块挂回 AdminApi,则为src/Gateway/TakeoutSaaS.ApiGateway/Dockerfile)
- AdminApi:
1. 环境要求
1.1 开发环境
- .NET SDK:10.0 或更高版本
- IDE:Visual Studio 2022 / JetBrains Rider / VS Code
- 数据库:PostgreSQL 16+
- 缓存:Redis 7.0+
- 消息队列:RabbitMQ 3.12+
- Git:版本控制
- Docker Desktop:容器化开发(可选)
1.2 生产环境
- 操作系统:Linux (Ubuntu 22.04 LTS / CentOS 8+)
- 运行时:.NET Runtime 10.0
- Web服务器:Nginx 1.24+
- 数据库:PostgreSQL 16+ (主从复制)
- 缓存:Redis 7.0+ (哨兵模式)
- 消息队列:RabbitMQ 3.12+ (集群模式)
- 对象存储:MinIO / 阿里云OSS / 腾讯云COS
- 监控:Prometheus + Grafana
- 日志:ELK Stack (Elasticsearch + Logstash + Kibana)
1.3 硬件要求(生产环境)
- 应用服务器:4核8GB内存(最低),推荐8核16GB
- 数据库服务器:8核16GB内存,SSD存储
- Redis服务器:4核8GB内存
- 负载均衡器:2核4GB内存
2. 本地开发环境搭建
2.1 安装.NET SDK
# Windows
# 从官网下载安装:https://dotnet.microsoft.com/download
# Linux (Ubuntu)
wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install -y dotnet-sdk-10.0
# 验证安装
dotnet --version
2.2 安装PostgreSQL
# Ubuntu
sudo apt-get update
sudo apt-get install -y postgresql-16 postgresql-contrib-16
# 启动服务
sudo systemctl start postgresql
sudo systemctl enable postgresql
# 创建数据库
sudo -u postgres psql
CREATE DATABASE takeout_saas;
CREATE USER takeout_user WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE takeout_saas TO takeout_user;
\q
2.3 安装Redis
# Ubuntu
sudo apt-get install -y redis-server
# 启动服务
sudo systemctl start redis-server
sudo systemctl enable redis-server
# 测试连接
redis-cli ping
2.4 安装RabbitMQ
# Ubuntu
sudo apt-get install -y rabbitmq-server
# 启动服务
sudo systemctl start rabbitmq-server
sudo systemctl enable rabbitmq-server
# 启用管理插件
sudo rabbitmq-plugins enable rabbitmq_management
# 创建用户
sudo rabbitmqctl add_user admin password
sudo rabbitmqctl set_user_tags admin administrator
sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
# 访问管理界面:http://localhost:15672
2.5 使用Docker Compose(推荐)
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:16
container_name: takeout_postgres
environment:
POSTGRES_DB: takeout_saas
POSTGRES_USER: takeout_user
POSTGRES_PASSWORD: your_password
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
container_name: takeout_redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
rabbitmq:
image: rabbitmq:3.12-management
container_name: takeout_rabbitmq
environment:
RABBITMQ_DEFAULT_USER: admin
RABBITMQ_DEFAULT_PASS: password
ports:
- "5672:5672"
- "15672:15672"
volumes:
- rabbitmq_data:/var/lib/rabbitmq
minio:
image: minio/minio:latest
container_name: takeout_minio
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: admin
MINIO_ROOT_PASSWORD: password123
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio_data:/data
volumes:
postgres_data:
redis_data:
rabbitmq_data:
minio_data:
# 启动所有服务
docker-compose up -d
# 查看服务状态
docker-compose ps
# 停止服务
docker-compose down
2.6 配置项目
# 克隆项目
git clone https://github.com/your-org/takeout-saas.git
cd takeout-saas
# 还原依赖
dotnet restore
# 配置appsettings.Development.json
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=takeout_saas;Username=takeout_user;Password=your_password"
},
"Redis": {
"Configuration": "localhost:6379"
},
"RabbitMQ": {
"Host": "localhost",
"Port": 5672,
"Username": "admin",
"Password": "password"
}
}
# 执行数据库迁移
cd src/TakeoutSaaS.Api
dotnet ef database update
# 运行项目
dotnet run
2.7 EF Core 迁移基线
现已内置 dotnet-ef 本地工具与设计时 DbContext 工厂,可直接在命令行生成/更新数据库。运行前可通过环境变量 TAKEOUTSAAS_APP_CONNECTION、TAKEOUTSAAS_IDENTITY_CONNECTION 覆盖默认连接串(默认指向本地 PostgreSQL)。
# 业务主库(TakeoutAppDbContext,含租户/商户/门店/商品/订单等)
dotnet tool run dotnet-ef database update `
--project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
--startup-project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
--context TakeoutSaaS.Infrastructure.App.Persistence.TakeoutAppDbContext
# 身份库(IdentityDbContext)
dotnet tool run dotnet-ef database update `
--project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
--startup-project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
--context TakeoutSaaS.Infrastructure.Identity.Persistence.IdentityDbContext
# 业务/字典库(DictionaryDbContext,归属 AppDatabase)
dotnet tool run dotnet-ef database update `
--project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
--startup-project src/Infrastructure/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj `
--context TakeoutSaaS.Infrastructure.Dictionary.Persistence.DictionaryDbContext
Hangfire 使用 Scheduler.ConnectionString 指向的数据库,首次启动服务会自动建表;只需提前创建空数据库并授予账号权限。
3. Docker部署
3.1 创建Dockerfile
# Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["src/TakeoutSaaS.Api/TakeoutSaaS.Api.csproj", "src/TakeoutSaaS.Api/"]
COPY ["src/TakeoutSaaS.Application/TakeoutSaaS.Application.csproj", "src/TakeoutSaaS.Application/"]
COPY ["src/TakeoutSaaS.Domain/TakeoutSaaS.Domain.csproj", "src/TakeoutSaaS.Domain/"]
COPY ["src/TakeoutSaaS.Infrastructure/TakeoutSaaS.Infrastructure.csproj", "src/TakeoutSaaS.Infrastructure/"]
COPY ["src/TakeoutSaaS.Shared/TakeoutSaaS.Shared.csproj", "src/TakeoutSaaS.Shared/"]
RUN dotnet restore "src/TakeoutSaaS.Api/TakeoutSaaS.Api.csproj"
COPY . .
WORKDIR "/src/src/TakeoutSaaS.Api"
RUN dotnet build "TakeoutSaaS.Api.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "TakeoutSaaS.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "TakeoutSaaS.Api.dll"]
3.2 构建镜像
# 构建镜像
docker build -t takeout-saas-api:latest .
# 查看镜像
docker images | grep takeout-saas
# 运行容器
docker run -d \
--name takeout-api \
-p 8080:80 \
-e ASPNETCORE_ENVIRONMENT=Production \
-e ConnectionStrings__DefaultConnection="Host=postgres;Port=5432;Database=takeout_saas;Username=takeout_user;Password=your_password" \
takeout-saas-api:latest
3.3 生产环境Docker Compose
# docker-compose.prod.yml
version: '3.8'
services:
api:
image: takeout-saas-api:latest
container_name: takeout_api
restart: always
environment:
ASPNETCORE_ENVIRONMENT: Production
ConnectionStrings__DefaultConnection: "Host=postgres;Port=5432;Database=takeout_saas;Username=takeout_user;Password=${DB_PASSWORD}"
Redis__Configuration: "redis:6379"
RabbitMQ__Host: "rabbitmq"
ports:
- "8080:80"
depends_on:
- postgres
- redis
- rabbitmq
networks:
- takeout_network
nginx:
image: nginx:latest
container_name: takeout_nginx
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/ssl:/etc/nginx/ssl
depends_on:
- api
networks:
- takeout_network
networks:
takeout_network:
driver: bridge
4. Nginx配置
4.1 基础配置
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/rss+xml font/truetype font/opentype
application/vnd.ms-fontobject image/svg+xml;
# 限流配置
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
include /etc/nginx/conf.d/*.conf;
}
4.2 API服务配置
# /etc/nginx/conf.d/api.conf
upstream api_backend {
least_conn;
server api1:80 weight=1 max_fails=3 fail_timeout=30s;
server api2:80 weight=1 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 80;
server_name api.example.com;
# 重定向到HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
# SSL证书配置
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 客户端请求体大小限制
client_max_body_size 10M;
# API接口
location /api/ {
# 限流
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 10;
proxy_pass http://api_backend;
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_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 缓冲设置
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
}
# WebSocket
location /ws {
proxy_pass http://api_backend;
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;
# WebSocket超时
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
# 健康检查
location /health {
proxy_pass http://api_backend;
access_log off;
}
# 静态文件缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://api_backend;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
5. 数据库部署
5.1 PostgreSQL主从复制
# 主库配置 (postgresql.conf)
listen_addresses = '*'
wal_level = replica
max_wal_senders = 10
wal_keep_size = 64MB
hot_standby = on
# 主库配置 (pg_hba.conf)
host replication replicator 192.168.1.0/24 md5
# 创建复制用户
CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD 'repl_password';
# 从库配置
# 1. 停止从库
sudo systemctl stop postgresql
# 2. 清空从库数据目录
rm -rf /var/lib/postgresql/16/main/*
# 3. 从主库复制数据
pg_basebackup -h master_ip -D /var/lib/postgresql/16/main -U replicator -P -v -R -X stream -C -S replica1
# 4. 启动从库
sudo systemctl start postgresql
# 5. 验证复制状态
# 主库执行
SELECT * FROM pg_stat_replication;
5.2 数据库备份脚本
#!/bin/bash
# backup_db.sh
BACKUP_DIR="/backup/postgres"
DATE=$(date +%Y%m%d_%H%M%S)
DB_NAME="takeout_saas"
DB_USER="takeout_user"
RETENTION_DAYS=30
# 创建备份目录
mkdir -p $BACKUP_DIR
# 全量备份
pg_dump -h localhost -U $DB_USER -d $DB_NAME -F c -f $BACKUP_DIR/full_$DATE.dump
# 压缩备份
gzip $BACKUP_DIR/full_$DATE.dump
# 删除过期备份
find $BACKUP_DIR -name "full_*.dump.gz" -mtime +$RETENTION_DAYS -delete
# 上传到对象存储(可选)
# aws s3 cp $BACKUP_DIR/full_$DATE.dump.gz s3://your-bucket/backups/
echo "Backup completed: full_$DATE.dump.gz"
5.3 定时备份(Crontab)
# 编辑crontab
crontab -e
# 每天凌晨2点执行备份
0 2 * * * /path/to/backup_db.sh >> /var/log/backup.log 2>&1
TODO:基础设施部署脚本
- PostgreSQL 主从:整理主库/从库初始化脚本、basebackup 步骤与故障切换手册。
- Redis 哨兵/集群:补充 redis.conf/sentinel.conf 模板以及一主两从搭建命令。
- RabbitMQ:编写单节点到镜像队列的安装脚本,记录 VHost、用户、权限、监控等操作。
- 腾讯云 COS:整理桶创建、ACL、CDN 绑定与密钥轮换流程,并提供 coscmd/SDK 示例。
- Hangfire 存储:确认 PostgreSQL Schema 初始化脚本,补充定期备份、清理、监控的 SOP。
6. Redis部署
6.1 Redis哨兵模式
# redis.conf (主节点)
bind 0.0.0.0
port 6379
requirepass your_password
masterauth your_password
# sentinel.conf
port 26379
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel auth-pass mymaster your_password
sentinel down-after-milliseconds mymaster 5000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 10000
6.2 Redis持久化配置
# redis.conf
# RDB持久化
save 900 1
save 300 10
save 60 10000
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
# AOF持久化
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
7. CI/CD配置
7.1 GitHub Actions
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '10.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
- name: Publish
run: dotnet publish src/TakeoutSaaS.Api/TakeoutSaaS.Api.csproj -c Release -o ./publish
- name: Build Docker image
run: |
docker build -t takeout-saas-api:${{ github.sha }} .
docker tag takeout-saas-api:${{ github.sha }} takeout-saas-api:latest
- name: Push to Registry
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker push takeout-saas-api:${{ github.sha }}
docker push takeout-saas-api:latest
- name: Deploy to Server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/takeout-saas
docker-compose pull
docker-compose up -d
docker system prune -f
7.2 GitLab CI
# .gitlab-ci.yml
stages:
- build
- test
- deploy
variables:
DOCKER_IMAGE: registry.example.com/takeout-saas-api
build:
stage: build
image: mcr.microsoft.com/dotnet/sdk:10.0
script:
- dotnet restore
- dotnet build --configuration Release
artifacts:
paths:
- src/*/bin/Release/
test:
stage: test
image: mcr.microsoft.com/dotnet/sdk:10.0
script:
- dotnet test --configuration Release
deploy:
stage: deploy
image: docker:latest
services:
- docker:dind
script:
- docker build -t $DOCKER_IMAGE:$CI_COMMIT_SHA .
- docker tag $DOCKER_IMAGE:$CI_COMMIT_SHA $DOCKER_IMAGE:latest
- docker push $DOCKER_IMAGE:$CI_COMMIT_SHA
- docker push $DOCKER_IMAGE:latest
only:
- main
8. 监控告警
8.1 Prometheus配置
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'takeout-api'
static_configs:
- targets: ['api:80']
metrics_path: '/metrics'
- job_name: 'postgres'
static_configs:
- targets: ['postgres-exporter:9187']
- job_name: 'redis'
static_configs:
- targets: ['redis-exporter:9121']
- job_name: 'node'
static_configs:
- targets: ['node-exporter:9100']
8.2 应用监控指标(OpenTelemetry + Prometheus Exporter)
// Program.cs - 指标与探针
builder.Services.AddHealthChecks();
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddPrometheusExporter(); // /metrics
});
var app = builder.Build();
app.MapHealthChecks("/healthz"); // 存活/就绪探针
app.MapPrometheusScrapingEndpoint(); // 默认 /metrics
自定义业务指标(使用 System.Diagnostics.Metrics,由 Prometheus Exporter 暴露):
internal static class BusinessMetrics
{
private static readonly Meter Meter = new("TakeoutSaaS.App", "1.0.0");
public static readonly Counter<long> OrdersCreated = Meter.CreateCounter<long>("orders_created_total", "个", "订单创建计数");
public static readonly Histogram<double> OrderProcessingSeconds = Meter.CreateHistogram<double>("order_processing_duration_seconds", "s", "订单处理耗时");
}
Prometheus 抓取示例:见 deploy/prometheus/prometheus.yml,默认拉取 /metrics,告警规则见 deploy/prometheus/alert.rules.yml。
8.3 Grafana仪表板
{
"dashboard": {
"title": "外卖SaaS系统监控",
"panels": [
{
"title": "API请求速率",
"targets": [
{
"expr": "rate(http_requests_total[5m])"
}
]
},
{
"title": "订单创建数",
"targets": [
{
"expr": "increase(orders_created_total[1h])"
}
]
},
{
"title": "数据库连接数",
"targets": [
{
"expr": "pg_stat_activity_count"
}
]
}
]
}
}
8.4 告警规则
# alert.rules.yml
groups:
- name: takeout_alerts
interval: 30s
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "高错误率告警"
description: "API错误率超过5%"
- alert: DatabaseDown
expr: up{job="postgres"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "数据库宕机"
description: "PostgreSQL数据库不可用"
- alert: HighMemoryUsage
expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes > 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "内存使用率过高"
description: "内存使用率超过90%"
9. 日志管理
9.1 Serilog配置
{
"Serilog": {
"Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Elasticsearch"],
"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
}
},
{
"Name": "Elasticsearch",
"Args": {
"nodeUris": "http://elasticsearch:9200",
"indexFormat": "takeout-logs-{0:yyyy.MM.dd}",
"autoRegisterTemplate": true
}
}
],
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"]
}
}
9.2 ELK Stack部署
# docker-compose.elk.yml
version: '3.8'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
volumes:
- es_data:/usr/share/elasticsearch/data
logstash:
image: docker.elastic.co/logstash/logstash:8.11.0
volumes:
- ./logstash/pipeline:/usr/share/logstash/pipeline
ports:
- "5044:5044"
depends_on:
- elasticsearch
kibana:
image: docker.elastic.co/kibana/kibana:8.11.0
ports:
- "5601:5601"
environment:
ELASTICSEARCH_HOSTS: http://elasticsearch:9200
depends_on:
- elasticsearch
volumes:
es_data:
10. 安全加固
10.1 防火墙配置
# UFW防火墙
sudo ufw enable
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw deny 5432/tcp # 禁止外部访问数据库
sudo ufw deny 6379/tcp # 禁止外部访问Redis
10.2 SSL证书(Let's Encrypt)
# 安装Certbot
sudo apt-get install certbot python3-certbot-nginx
# 获取证书
sudo certbot --nginx -d api.example.com
# 自动续期
sudo certbot renew --dry-run
# 添加定时任务
0 3 * * * certbot renew --quiet
10.3 应用安全配置
// Program.cs
builder.Services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromDays(365);
options.IncludeSubDomains = true;
options.Preload = true;
});
builder.Services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status308PermanentRedirect;
options.HttpsPort = 443;
});
// 添加安全头
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
context.Response.Headers.Add("X-Frame-Options", "DENY");
context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
context.Response.Headers.Add("Referrer-Policy", "no-referrer");
await next();
});
11. 性能优化
11.1 数据库连接池
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=takeout_saas;Username=user;Password=pass;Pooling=true;MinPoolSize=5;MaxPoolSize=100;ConnectionLifetime=300"
}
}
11.2 Redis连接池
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = configuration["Redis:Configuration"];
options.InstanceName = "TakeoutSaaS:";
});
11.3 响应压缩
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<BrotliCompressionProvider>();
});
12. 故障恢复
12.1 数据库恢复
# 从备份恢复
pg_restore -h localhost -U takeout_user -d takeout_saas -v /backup/full_20240101.dump
# PITR恢复到指定时间点
# 1. 停止数据库
sudo systemctl stop postgresql
# 2. 恢复基础备份
rm -rf /var/lib/postgresql/16/main/*
tar -xzf /backup/base_backup.tar.gz -C /var/lib/postgresql/16/main/
# 3. 配置recovery.conf
restore_command = 'cp /backup/wal_archive/%f %p'
recovery_target_time = '2024-01-01 12:00:00'
# 4. 启动数据库
sudo systemctl start postgresql
12.2 应用回滚
# Docker回滚到上一个版本
docker-compose down
docker-compose up -d --force-recreate --no-deps api
# 或使用特定版本
docker pull takeout-saas-api:previous-version
docker-compose up -d
13. 网关 TakeoutSaaS.ApiGateway 部署
-
部署拓扑
- Nginx 负责域名
kjkj.qiyuesns.cn(含后续 HTTPS 证书),并将所有流量反代到本机http://127.0.0.1:5000。 - .NET 网关容器(TakeoutSaaS.ApiGateway)负责 YARP 路由、限流、日志与 OpenTelemetry 埋点,向下游 49.7.179.246 的 Admin/User/Mini API 转发。
- Nginx 负责域名
-
构建与运行
# 构建镜像 docker build -f src/Gateway/TakeoutSaaS.ApiGateway/Dockerfile -t takeoutsaas/apigateway:latest . # 启动容器(生产环境建议挂载独立配置) docker run -d --name takeout-gateway -p 5000:5000 ^ -e ASPNETCORE_ENVIRONMENT=Production ^ -v /opt/takeoutsaas/gateway/appsettings.Production.json:/app/appsettings.Production.json ^ takeoutsaas/apigateway:latestappsettings.json默认将/api/admin|mini|user/**指向主应用服务器(7801/7701/7901 端口)。appsettings.Development.json可在本地覆盖为localhost:5001/5002/5003,无需改代码。
-
Nginx 参考配置
server { listen 80; server_name kjkj.qiyuesns.cn; location / { proxy_pass http://127.0.0.1:5000; 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; } }- 后续启用 HTTPS 时,将
listen 443 ssl与证书配置加入同一server,其余保持不变。
- 后续启用 HTTPS 时,将
-
关键配置项说明
Gateway:RateLimiting:按客户端 IP 固定窗口限流(默认 300 次/60 秒),可通过配置文件调整或关闭。ReverseProxy:集中声明路由规则(/api/{service}/**),后端地址变更时只需改配置即可。OpenTelemetry:默认开启 OTLP 导出,Collector 地址通过OpenTelemetry:OtlpEndpoint指定。Serilog:统一输出到控制台,日志采集器可以直接收集 Docker stdout。
-
健康检查
GET /healthz:基础健康,用于探活或监控告警。GET /:返回服务元信息,可作为简易诊断接口。