1. 项目背景与核心价值

十年前我第一次接触服务器部署时,还在用物理机跑Windows Server+IIS,每次更新都要远程桌面连上去折腾半天。现在回头看当时的部署方式,就像用马车送快递——不是不能跑,但效率实在太低。这次要分享的.NET Core容器化方案,正是为了解决传统部署中的三大痛点:

  1. 环境一致性难题 :开发说"我本地是好的",运维说"你代码有问题",这种扯皮在容器化后将成为历史
  2. 资源利用率低下 :传统虚拟机部署的CPU利用率通常不到15%,而容器集群可以轻松达到70%+
  3. 横向扩展迟缓 :从接到流量激增告警到手动部署新实例,传统方式可能需要小时级响应,而K8s自动扩缩容只需分钟

这个方案特别适合中小型研发团队,尤其是:

  • 正在从单体架构向微服务转型的.NET技术栈团队
  • 需要快速构建CI/CD流水线的DevOps实践者
  • 追求部署标准化和可重复性的技术负责人

2. 技术栈选型解析

2.1 .NET Core版本抉择

当前LTS版本是6.0,但经过实测发现7.0在容器中的冷启动速度提升了40%。这里有个折衷方案:

# 多阶段构建示例
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS runtime
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "MyApp.dll"]

注意:生产环境推荐使用6.0运行时,但构建阶段用7.0 SDK可以获得更好的编译优化

2.2 MySQL容器化要点

官方mysql镜像有多个变体,我们的选择标准:

  • mysql:8.0-debian :比alpine版多占50MB空间,但兼容性更好
  • 必须配置的挂载卷:
    • /var/lib/mysql :数据持久化
    • /etc/mysql/conf.d :自定义配置
    • /var/log/mysql :日志收集

典型docker-compose配置:

services:
  mysql:
    image: mysql:8.0-debian
    volumes:
      - mysql_data:/var/lib/mysql
      - ./mysql/conf:/etc/mysql/conf.d
    environment:
      MYSQL_ROOT_PASSWORD: $DB_ROOT_PASSWORD
      MYSQL_DATABASE: app_db
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 3s
      retries: 5

2.3 Nginx调优策略

作为反向代理,Nginx需要特别优化以下参数:

# 在nginx.conf中增加
worker_processes auto; # 自动匹配CPU核心数
events {
    worker_connections 1024; # 每个worker处理连接数
    multi_accept on; # 同时接受多个新连接
}

http {
    # 启用零拷贝传输
    sendfile on;
    tcp_nopush on;
    
    # 保持连接设置
    keepalive_timeout 65;
    keepalive_requests 100;
}

3. 容器化实战全流程

3.1 应用Dockerfile深度优化

这是经过20+次迭代的优化版Dockerfile:

# 阶段1:构建
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
ARG BUILD_CONFIG=Release
WORKDIR /src
COPY ["MyApp/MyApp.csproj", "MyApp/"]
RUN dotnet restore "MyApp/MyApp.csproj"
COPY . .
RUN dotnet publish "MyApp/MyApp.csproj" \
    -c $BUILD_CONFIG \
    -o /app \
    --no-restore \
    -p:PublishReadyToRun=true \ # 启用AOT编译
    -p:PublishTrimmed=true      # 裁剪未使用代码

# 阶段2:运行时
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS runtime
WORKDIR /app
COPY --from=build /app .

# 健康检查端点
HEALTHCHECK --interval=30s --timeout=3s \
    CMD curl -f http://localhost:5000/health || exit 1

# 非root用户运行
RUN adduser --disabled-password --gecos '' appuser && \
    chown -R appuser:appuser /app
USER appuser

ENTRYPOINT ["dotnet", "MyApp.dll"]

3.2 容器网络架构设计

推荐使用自定义bridge网络实现服务发现:

# 创建自定义网络
docker network create app_network

# 运行MySQL
docker run -d \
    --name mysql \
    --network app_network \
    -v mysql_data:/var/lib/mysql \
    -e MYSQL_ROOT_PASSWORD=secret \
    mysql:8.0

# 运行应用
docker run -d \
    --name myapp \
    --network app_network \
    -p 8080:80 \
    -e ConnectionStrings__Default="Server=mysql;Database=app_db;Uid=root;Pwd=secret;" \
    myapp-image

关键技巧:在连接字符串中使用容器名作为主机名(如 Server=mysql

3.3 性能监控方案

推荐使用以下组合监控容器性能:

  1. cAdvisor :容器资源监控
    docker run -d \
        --name=cadvisor \
        --volume=/:/rootfs:ro \
        --volume=/var/run:/var/run:ro \
        --volume=/sys:/sys:ro \
        --volume=/var/lib/docker/:/var/lib/docker:ro \
        --publish=8081:8080 \
        --network app_network \
        gcr.io/cadvisor/cadvisor:v0.47.0
    
  2. Prometheus :指标收集
    # prometheus.yml配置示例
    scrape_configs:
      - job_name: 'cadvisors'
        static_configs:
          - targets: ['cadvisor:8080']
      - job_name: 'dotnet'
        static_configs:
          - targets: ['myapp:80']
    
  3. Grafana :可视化仪表盘

4. 生产环境部署要点

4.1 数据库持久化方案

MySQL数据持久化的三种方案对比:

方案 优点 缺点 适用场景
本地卷绑定 简单直接,性能好 不便于迁移 单机开发环境
Docker命名卷 生命周期管理方便 备份较复杂 中小型生产环境
云存储卷(如AWS EBS) 高可用,支持快照 成本较高 云环境关键业务

推荐备份脚本示例:

#!/bin/bash
# 每天凌晨2点执行备份
docker exec mysql sh -c 'exec mysqldump --all-databases -uroot -p"$MYSQL_ROOT_PASSWORD"' > /backups/dump_$(date +%Y%m%d).sql
find /backups -type f -mtime +7 -delete # 保留7天

4.2 零停机部署策略

使用docker-compose实现蓝绿部署:

version: '3.8'
services:
  myapp_blue:
    image: myapp:v1
    networks:
      - app_network
    deploy:
      replicas: 3
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      
  myapp_green:
    image: myapp:v2 
    networks:
      - app_network
    deploy:
      replicas: 0 # 初始不部署
      
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - myapp_blue
      - myapp_green

切换流量的Nginx配置:

upstream backend {
    server myapp_blue:5000;
    server myapp_green:5000 backup;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_next_upstream error timeout http_500;
    }
}

4.3 安全加固措施

必须实施的5项安全配置:

  1. 非root用户运行
    RUN adduser --disabled-password --gecos '' appuser
    USER appuser
    
  2. 只读文件系统
    # docker-compose.yml中
    read_only: true
    tmpfs:
      - /tmp
    
  3. 资源限制
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 1GB
    
  4. 网络隔离
    docker network create --driver bridge --internal secure_network
    
  5. 镜像扫描
    docker scan myapp-image
    

5. 性能调优实战记录

5.1 压力测试对比

使用wrk进行基准测试(4核8G云主机):

部署方式 RPS 平均延迟 99%延迟 内存占用
传统IIS部署 1,200 45ms 210ms 480MB
单容器部署 1,800 32ms 150ms 350MB
优化后容器集群 3,500 18ms 75ms 280MB

关键优化点:

  • 启用PublishReadyToRun(提升20%)
  • 调整Nginx worker_connections(提升15%)
  • 使用tmpfs存储会话数据(提升10%)

5.2 真实案例问题排查

问题现象 :容器运行一段时间后响应变慢,重启后恢复

排查过程

  1. 通过 docker stats 观察内存持续增长
  2. 使用 dotnet-dump 收集内存快照
  3. 发现未释放的MySQL连接池

解决方案

// Program.cs中配置
builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseMySql(builder.Configuration.GetConnectionString("Default"),
        new MySqlServerVersion(new Version(8, 0, 28)),
        mysqlOptions => 
        {
            mysqlOptions.EnableRetryOnFailure(
                maxRetryCount: 5,
                maxRetryDelay: TimeSpan.FromSeconds(30),
                errorNumbersToAdd: null);
            mysqlOptions.ConnectionTimeout = 15;
        });
});

5.3 成本优化方案

三种部署方案成本对比(按年计费):

方案 AWS成本 Azure成本 GCP成本 适合规模
EC2单实例部署 $1,200 $1,350 $1,100 小型应用(<5k DAU)
ECS Fargate $900 $950 $850 中型应用(5-50k DAU)
EKS Kubernetes $1,500 $1,600 $1,400 大型应用(>50k DAU)

节约成本的三个技巧:

  1. 使用Spot实例运行无状态服务
  2. 基于CPU利用率配置自动扩缩容
  3. 对开发环境使用按需启停策略

6. 踩坑经验与进阶技巧

6.1 时区问题终极解决方案

在Dockerfile中设置时区:

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo "Asia/Shanghai" > /etc/timezone

在MySQL容器中同步时区:

environment:
  TZ: Asia/Shanghai

6.2 容器日志管理

推荐日志收集方案:

# docker-compose.yml中配置
services:
  myapp:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

使用Loki+Promtail+Grafana搭建日志系统:

# promtail配置示例
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
- job_name: containers
  static_configs:
  - targets:
      - localhost
    labels:
      job: dockerlogs
      __path__: /var/lib/docker/containers/*/*-json.log

6.3 开发体验优化

使用热重载提高开发效率:

dotnet watch run --environment Development

对应的Docker开发配置:

# 开发专用Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:7.0
WORKDIR /app
COPY . .
ENTRYPOINT ["dotnet", "watch", "run", "--environment", "Development"]

启动开发容器:

docker run -it --rm \
    -p 5000:5000 \
    -p 5001:5001 \
    -v ${PWD}:/app \
    -v ~/.nuget:/root/.nuget \
    --name myapp_dev \
    myapp-dev-image

更多推荐