chore: 更改文件夹结构
This commit is contained in:
976
Document/05_部署运维.md
Normal file
976
Document/05_部署运维.md
Normal file
@@ -0,0 +1,976 @@
|
||||
# 外卖SaaS系统 - 部署运维
|
||||
|
||||
## 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
|
||||
```bash
|
||||
# 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
|
||||
```bash
|
||||
# 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
|
||||
```bash
|
||||
# Ubuntu
|
||||
sudo apt-get install -y redis-server
|
||||
|
||||
# 启动服务
|
||||
sudo systemctl start redis-server
|
||||
sudo systemctl enable redis-server
|
||||
|
||||
# 测试连接
|
||||
redis-cli ping
|
||||
```
|
||||
|
||||
### 2.4 安装RabbitMQ
|
||||
```bash
|
||||
# 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(推荐)
|
||||
```yaml
|
||||
# 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:
|
||||
```
|
||||
|
||||
```bash
|
||||
# 启动所有服务
|
||||
docker-compose up -d
|
||||
|
||||
# 查看服务状态
|
||||
docker-compose ps
|
||||
|
||||
# 停止服务
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
### 2.6 配置项目
|
||||
```bash
|
||||
# 克隆项目
|
||||
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
|
||||
```
|
||||
|
||||
## 3. Docker部署
|
||||
|
||||
### 3.1 创建Dockerfile
|
||||
```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 构建镜像
|
||||
```bash
|
||||
# 构建镜像
|
||||
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
|
||||
```yaml
|
||||
# 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 基础配置
|
||||
```nginx
|
||||
# /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服务配置
|
||||
```nginx
|
||||
# /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主从复制
|
||||
```bash
|
||||
# 主库配置 (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 数据库备份脚本
|
||||
```bash
|
||||
#!/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)
|
||||
```bash
|
||||
# 编辑crontab
|
||||
crontab -e
|
||||
|
||||
# 每天凌晨2点执行备份
|
||||
0 2 * * * /path/to/backup_db.sh >> /var/log/backup.log 2>&1
|
||||
```
|
||||
|
||||
## 6. Redis部署
|
||||
|
||||
### 6.1 Redis哨兵模式
|
||||
```bash
|
||||
# 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持久化配置
|
||||
```bash
|
||||
# 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
|
||||
```yaml
|
||||
# .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
|
||||
```yaml
|
||||
# .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配置
|
||||
```yaml
|
||||
# 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 应用监控指标
|
||||
```csharp
|
||||
// Program.cs - 添加Prometheus监控
|
||||
builder.Services.AddPrometheusMetrics();
|
||||
|
||||
app.UseMetricServer(); // /metrics端点
|
||||
app.UseHttpMetrics(); // HTTP请求指标
|
||||
|
||||
// 自定义指标
|
||||
public class MetricsService
|
||||
{
|
||||
private static readonly Counter OrderCreatedCounter = Metrics
|
||||
.CreateCounter("orders_created_total", "Total orders created");
|
||||
|
||||
private static readonly Histogram OrderProcessingDuration = Metrics
|
||||
.CreateHistogram("order_processing_duration_seconds", "Order processing duration");
|
||||
|
||||
public void RecordOrderCreated()
|
||||
{
|
||||
OrderCreatedCounter.Inc();
|
||||
}
|
||||
|
||||
public IDisposable MeasureOrderProcessing()
|
||||
{
|
||||
return OrderProcessingDuration.NewTimer();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 Grafana仪表板
|
||||
```json
|
||||
{
|
||||
"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 告警规则
|
||||
```yaml
|
||||
# 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配置
|
||||
```json
|
||||
{
|
||||
"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部署
|
||||
```yaml
|
||||
# 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 防火墙配置
|
||||
```bash
|
||||
# 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)
|
||||
```bash
|
||||
# 安装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 应用安全配置
|
||||
```csharp
|
||||
// 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 数据库连接池
|
||||
```json
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=localhost;Port=5432;Database=takeout_saas;Username=user;Password=pass;Pooling=true;MinPoolSize=5;MaxPoolSize=100;ConnectionLifetime=300"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 11.2 Redis连接池
|
||||
```csharp
|
||||
services.AddStackExchangeRedisCache(options =>
|
||||
{
|
||||
options.Configuration = configuration["Redis:Configuration"];
|
||||
options.InstanceName = "TakeoutSaaS:";
|
||||
});
|
||||
```
|
||||
|
||||
### 11.3 响应压缩
|
||||
```csharp
|
||||
builder.Services.AddResponseCompression(options =>
|
||||
{
|
||||
options.EnableForHttps = true;
|
||||
options.Providers.Add<GzipCompressionProvider>();
|
||||
options.Providers.Add<BrotliCompressionProvider>();
|
||||
});
|
||||
```
|
||||
|
||||
## 12. 故障恢复
|
||||
|
||||
### 12.1 数据库恢复
|
||||
```bash
|
||||
# 从备份恢复
|
||||
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 应用回滚
|
||||
```bash
|
||||
# Docker回滚到上一个版本
|
||||
docker-compose down
|
||||
docker-compose up -d --force-recreate --no-deps api
|
||||
|
||||
# 或使用特定版本
|
||||
docker pull takeout-saas-api:previous-version
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user