1. 从手动部署到容器化:一个.NET Core开发者的真实转型之路
三年前我刚接触.NET Core开发时,最痛苦的不是写代码,而是部署环节。记得第一次给客户部署电商系统时,我在服务器上折腾了整整两天:先要手动安装.NET Core运行时,然后配置MySQL权限,最后调试Nginx反向代理。最崩溃的是,当我在测试环境好不容易调通后,生产环境又出现各种不一致的问题。这种经历让我深刻认识到:传统部署方式已经成为现代开发流程中最大的效率瓶颈。
直到接触Docker容器化技术,我的部署工作才发生了质的变化。现在,我可以将一个包含.NET Core应用、MySQL数据库和Nginx反向代理的完整系统,通过一个docker-compose.yml文件一键部署到任何支持Docker的服务器上。部署时间从原来的几小时缩短到几分钟,而且完全消除了"在我机器上能跑"的环境差异问题。
2. 环境准备与工具选型
2.1 开发环境配置
在开始之前,我们需要准备以下环境:
- 开发机器:推荐使用Windows 10/11或Linux系统(如Ubuntu 20.04+)
- .NET Core SDK:安装最新LTS版本(当前是.NET 6.0)
bash复制# 查看已安装的.NET版本 dotnet --list-sdks - Docker Desktop:用于本地构建和运行容器
- Windows/Mac用户:从官网下载安装包
- Linux用户:使用官方仓库安装
bash复制sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io
- 代码编辑器:VS Code或Visual Studio 2022
注意:Docker需要开启虚拟化支持。在BIOS中确保Virtualization Technology(VT-x/AMD-V)已启用。
2.2 为什么选择Docker Compose?
在评估了多种部署方案后,我选择Docker Compose作为核心工具,主要基于以下考虑:
- 服务编排能力:可以定义和运行多容器应用
- 依赖管理:自动处理容器间的网络连接和启动顺序
- 配置即代码:所有环境配置都保存在YAML文件中
- 开发-生产一致性:使用相同的配置在开发和生产环境运行
3. 创建.NET Core Web API项目
3.1 初始化项目
我们从一个基础的Web API项目开始:
bash复制dotnet new webapi -n ProductService
cd ProductService
3.2 添加MySQL支持
安装Entity Framework Core和MySQL驱动:
bash复制dotnet add package Pomelo.EntityFrameworkCore.MySql
dotnet add package Microsoft.EntityFrameworkCore.Design
创建数据库上下文和模型:
csharp复制// Models/Product.cs
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
// Data/ProductContext.cs
public class ProductContext : DbContext
{
public ProductContext(DbContextOptions<ProductContext> options)
: base(options) { }
public DbSet<Product> Products { get; set; }
}
3.3 配置数据库连接
在appsettings.json中添加配置:
json复制{
"ConnectionStrings": {
"DefaultConnection": "server=mysql;port=3306;database=productdb;user=root;password=my-secret-pw"
}
}
在Program.cs中注册DbContext:
csharp复制builder.Services.AddDbContext<ProductContext>(options =>
options.UseMySql(builder.Configuration.GetConnectionString("DefaultConnection"),
ServerVersion.AutoDetect(builder.Configuration.GetConnectionString("DefaultConnection"))));
4. Docker化.NET Core应用
4.1 编写Dockerfile
在项目根目录创建Dockerfile:
dockerfile复制# 使用官方.NET镜像作为构建环境
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY . .
# 还原NuGet包并构建项目
RUN dotnet restore
RUN dotnet publish -c Release -o /app
# 使用运行时镜像
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build /app .
# 暴露端口并设置入口点
EXPOSE 80
ENTRYPOINT ["dotnet", "ProductService.dll"]
4.2 构建和测试镜像
构建Docker镜像:
bash复制docker build -t product-service .
运行容器进行测试:
bash复制docker run -p 8080:80 --name product-container product-service
注意:此时会失败,因为MySQL服务还未配置。这是预期行为,我们只是验证.NET应用能否独立运行。
5. 配置MySQL容器
5.1 选择MySQL镜像
我们使用官方MySQL镜像,并指定版本为8.0:
yaml复制services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: my-secret-pw
MYSQL_DATABASE: productdb
volumes:
- mysql-data:/var/lib/mysql
ports:
- "3306:3306"
5.2 数据持久化配置
为了防止容器重启后数据丢失,我们使用Docker卷:
yaml复制volumes:
mysql-data:
5.3 健康检查
添加健康检查确保应用只在MySQL就绪后启动:
yaml复制healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 10s
retries: 10
6. 集成Nginx反向代理
6.1 Nginx配置
创建nginx/nginx.conf:
nginx复制events { worker_connections 1024; }
http {
server {
listen 80;
location / {
proxy_pass http://product-service:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/ {
proxy_pass http://product-service:80/;
proxy_set_header Host $host;
}
}
}
6.2 Docker Compose配置
在docker-compose.yml中添加Nginx服务:
yaml复制services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
depends_on:
- product-service
7. 完整的docker-compose.yml
将所有服务整合到一个文件中:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: my-secret-pw
MYSQL_DATABASE: productdb
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 10s
retries: 10
product-service:
build: .
environment:
- ASPNETCORE_ENVIRONMENT=Production
depends_on:
mysql:
condition: service_healthy
ports:
- "5000:80"
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
depends_on:
- product-service
volumes:
mysql-data:
8. 部署与验证
8.1 启动所有服务
bash复制docker-compose up -d
8.2 验证服务
-
检查容器状态:
bash复制
docker-compose ps -
测试API端点:
bash复制
curl http://localhost/api/products -
检查Nginx日志:
bash复制
docker-compose logs nginx
9. 生产环境优化建议
9.1 安全加固
-
MySQL安全:
- 创建专用用户而非使用root
- 限制网络访问
yaml复制environment: MYSQL_USER: appuser MYSQL_PASSWORD: apppassword -
Nginx安全:
- 添加SSL/TLS支持
- 配置适当的CORS策略
9.2 性能调优
-
.NET Core配置:
csharp复制builder.WebHost.ConfigureKestrel(serverOptions => { serverOptions.Limits.MaxConcurrentConnections = 100; serverOptions.Limits.MaxRequestBodySize = 10_000_000; }); -
MySQL优化:
yaml复制environment: MYSQL_INNODB_BUFFER_POOL_SIZE: 512M
10. 常见问题与解决方案
10.1 容器启动顺序问题
症状:.NET应用启动时MySQL还未就绪
解决方案:
- 使用depends_on + healthcheck组合
- 在应用代码中添加重试逻辑:
csharp复制services.AddDbContext<ProductContext>(options => options.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0, 0)), options => options.EnableRetryOnFailure( maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null)));
10.2 性能瓶颈排查
症状:API响应缓慢
诊断步骤:
- 检查Nginx访问日志
- 使用docker stats查看资源使用情况
- 对MySQL容器执行EXPLAIN分析查询
10.3 数据备份策略
方案:
bash复制# 备份MySQL数据
docker exec -it container_name mysqldump -u root -p database_name > backup.sql
# 恢复数据
cat backup.sql | docker exec -i container_name mysql -u root -p database_name
11. 进阶技巧:CI/CD集成
11.1 GitHub Actions配置示例
yaml复制name: Build and Deploy
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Build and push
run: |
docker-compose build
docker-compose push
11.2 多环境配置管理
创建docker-compose.override.yml用于开发环境:
yaml复制version: '3.8'
services:
product-service:
environment:
- ASPNETCORE_ENVIRONMENT=Development
volumes:
- .:/app
ports:
- "5000:80"
12. 监控与日志收集
12.1 容器日志配置
yaml复制services:
product-service:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
12.2 健康检查端点
在.NET应用中添加健康检查:
csharp复制builder.Services.AddHealthChecks()
.AddMySql(builder.Configuration.GetConnectionString("DefaultConnection"));
app.MapHealthChecks("/health");
13. 实际项目中的经验教训
在电商项目中使用这套方案时,我们遇到了几个关键问题:
-
MySQL字符集问题:商品描述包含特殊字符时出现乱码
- 解决方案:在MySQL容器中明确设置字符集
yaml复制environment: MYSQL_CHARSET: utf8mb4 MYSQL_COLLATION: utf8mb4_unicode_ci
- 解决方案:在MySQL容器中明确设置字符集
-
Nginx缓存问题:商品价格更新后前端仍显示旧数据
- 解决方案:在Nginx配置中添加适当的缓存控制头
nginx复制location /api/ { proxy_pass http://product-service:80/; proxy_no_cache 1; proxy_cache_bypass 1; }
- 解决方案:在Nginx配置中添加适当的缓存控制头
-
.NET Core内存泄漏:长时间运行后容器内存占用持续增长
- 解决方案:配置内存限制并监控
yaml复制deploy: resources: limits: memory: 512M
- 解决方案:配置内存限制并监控
14. 扩展思考:Kubernetes迁移路径
当应用规模扩大后,可以考虑迁移到Kubernetes:
-
转换Docker Compose为K8s资源:
bash复制
kompose convert -
关键差异点:
- 使用Deployment而非直接运行容器
- 通过Service暴露应用
- 使用ConfigMap和Secret管理配置
-
示例部署文件:
yaml复制apiVersion: apps/v1 kind: Deployment metadata: name: product-service spec: replicas: 3 selector: matchLabels: app: product-service template: metadata: labels: app: product-service spec: containers: - name: product-service image: your-registry/product-service:latest ports: - containerPort: 80
这套容器化部署方案已经在我们团队的多个项目中得到验证,从中小型电商平台到企业内部管理系统都有成功案例。最大的收获不仅是部署时间的缩短,更重要的是建立了一套可靠、可重复的部署流程,让团队能够更专注于业务逻辑开发而非环境配置。