☰
.NET Core容器化部署实战:Docker集成MySQL与Nginx全攻略
2026/10/6 19:24:53 网站建设 项目流程

去年年中帮一个朋友把他手上的 .NET Core Web API 项目做容器化改造时,他问了我一个特别典型的问题:为什么非得用 Docker 把 MySQL 和 Nginx 也一起装进去?单独装个 MySQL、单独装个 Nginx,再用 .NET 自带的 Kestrel 跑起来不也挺好吗?这个问题其实代表了很多 .NET 开发者的真实困惑。今天这篇内容,我就围绕“从0到1打造高性能 .NET Core 应用容器化部署,MySQL 和 Nginx 无缝集成”这个实战项目,把架构思路、镜像选型、三服务编排、常见排错、性能优化一整套流程完整拆开来讲。无论你是刚接触容器的 .NET 新手,还是已经在生产环境摸爬滚打的老手,这篇东西应该都能给你一些可落地的参考。

这套方案的适用人群很明确:手上有一个 .NET Core 应用(Web API、MVC 都行),希望用 Docker 部署到 Linux 服务器,并且让 MySQL 数据库和 Nginx 反向代理跟应用本身形成一套可复制的标准环境。核心价值在于:环境一致性、一键启动、水平扩展方便,以及后续做 CI/CD 时的部署标准化。我下面说的每一步都是我实际验证过的方案,不是纸上谈兵。

1. 容器化部署的整体设计思路与架构拆解

1.1 为什么选择容器化:从一台裸机到三个容器的转变

很多 .NET 开发者习惯了在 Windows Server 上装 IIS、装 SQL Server 的玩法,切到 Linux + Docker 之后,最大的不习惯在于“应用和环境的关系”变了。以前部署是“准备一台机器,装好运行时,把文件发布上去”,换机器就要重来一遍;容器化之后,应用连同它依赖的运行时、配置、环境变量、端口定义全部打包进镜像,换任何一台装了 Docker 的机器都能跑出一模一样的结果。

我们这个项目里,最典型的依赖关系是这样的:

  • 应用程序容器:跑 .NET Core 的 Web API,内部用 Kestrel 监听 8080(不直接对宿主机暴露,仅内部网络可见)。
  • MySQL 容器:提供数据库服务,数据目录必须挂载到宿主机卷,否则容器删除数据就没了。
  • Nginx 容器:作为唯一对外的入口,监听宿主机的 80/443 端口,把请求反向代理到应用容器的 8080。

这个架构的优势在于:对外只有 Nginx 一个口子,应用容器不直接暴露端口,攻击面小了很多;MySQL 的端口是否对外暴露由你决定,生产环境通常不暴露,只让应用容器通过 Docker 内部网络访问。这比我以前用裸机部署时“防火墙开一堆端口”的做法要干净得多。

1.2 架构选型:Docker Compose 还是 Kubernetes?

很多人一上来就问“要不要直接上 Kubernetes”,我的建议是:单机场景,先老老实实用 Docker Compose。Kubernetes 解决的是多节点、自动伸缩、故障自愈的问题,但它的学习成本、运维成本是实打实的。对一个单体 .NET Core 应用加 MySQL 加 Nginx 的组合,Docker Compose 用一份 YAML 就能把三个服务定义清楚、一键拉起,足够应付 90% 的初期需求。

Docker Compose 的核心价值在于“编排”——它帮你管理三个容器之间的启动顺序、网络互通、卷挂载、环境变量传递。举个例子,如果没有 Compose,你要先启动 MySQL、等它初始化完成、再启动应用、再手动配 Nginx,每一步都要人工确认;有了 Compose,用depends_on加健康检查就能把这一串动作自动化。

等技术栈往后长到微服务规模,多个应用互相调用、需要自动扩容的时候,再考虑迁移到 Kubernetes 也不迟。实际上,你为 Compose 写的 Dockerfile 在 Kubernetes 里是直接可用的,只是从 Compose 编排变成了 Deployment/Service 编排,迁移成本并没有想象中那么大。

1.3 网络模型:服务名通信与端口映射的取舍

这里我要重点讲一下 Docker 网络,因为“无缝集成”的底层逻辑就在这。Docker Compose 默认会创建一个 bridge 网络,所有通过 Compose 启动的服务都加入这个网络,彼此之间可以用服务名作为主机名互相访问。

这个设计非常关键。意味着你连接 MySQL 时,连接字符串不用写localhost或服务器的 IP,而直接写Server=mysql;Port=3306;...,其中mysql就是 Compose 里定义的服务名。同理,Nginx 反代 .NET 应用时,proxy_pass http://app:8080;里的app也是服务名。这套机制让三个容器之间的地址关系变成了“名称解析”,配置写死也不怕内网 IP 变化。

但要注意,容器内部网络和宿主机网络是隔离的。只有显式声明了ports,宿主机端口才会映射到容器端口。这个机制对多环境部署特别友好:同一个 Compose 文件,开发环境映射一串端口、生产环境只映射 80/443,不用改代码。

2. 环境准备与镜像选型实战

2.1 .NET 镜像选择:SDK 镜像与 Runtime 镜像的区别

容器化 .NET Core 应用,第一步就是选镜像。很多新手直接拉mcr.microsoft.com/dotnet/sdk来跑生产环境,这是不对的。SDK 镜像是给构建用的,包含编译器、开发工具,体积大;生产环境只需要aspnet运行时镜像,里面已经带了 ASP.NET Core 运行时和 Kestrel 依赖。

所以 .NET 的 Dockerfile 通常采用多阶段构建:第一阶段用 SDK 镜像dotnet publish,第二阶段把发布产物复制到aspnet运行时镜像。这样最终镜像只包含运行所需的文件,通常比 SDK 镜像小一两百兆。

选具体版本时,我个人的原则是:

  • 生产环境尽量用8.0或9.0的 LTS/STS 版本,别追预览版。
  • 如果项目还在 .NET 6,也不用急着升,但要注意 .NET 6 已停止支持,安全补丁不再更新,建议尽早规划迁移。
  • 镜像 tag 别用latest,要锁定具体版本号,比如mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim,避免某天拉下来一个莫名其妙的版本。

2.2 MySQL 镜像版本选择:5.7.44、8.0 还是 8.4 LTS?

MySQL 的版本选择是这次项目里争议最多的点。从热词里也能看出来,很多人还在搜mysql 5.7.44 安装过程、mysql 5.7.26下载,也有人问“5.7.44 之后为什么官方直接跳到 5.7.43 没有新版本了”。这个问题其实很简单:MySQL 5.7 系列在 2023 年 10 月 EOL(停止维护)了,5.7.44 就是 5.7 系列的最终版本。官方不会再给 5.7 出修 bug 的版本,所以你在官方下载页看到 5.7 停在了 5.7.44。

生产环境我通常建议直接上 MySQL 8.0,因为 5.7 已经 EOL,出安全漏洞没人管。如果你特别在意 LTS 版本,可以选择 MySQL 8.4 LTS,这是官方定义的长期支持版本,支持周期到 2032 年。Docker 拉取就用mysql:8.0或者mysql:8.4,直接指定大版本即可。

但这里有个很现实的坑:从 MySQL 5.7 升级到 8.0,默认认证插件变了。8.0 默认用caching_sha2_password,而很多老客户端和旧连接字符串用的是mysql_native_password,会导致连接报错。我们的 .NET 应用一般用最新的 MySqlConnector,支持新插件没问题,但如果你的项目里还引用了旧版驱动,可能需要在 MySQL 启动命令里加--default-authentication-plugin=mysql_native_password来兼容。

2.3 Nginx 镜像的选择与 alpine 版本的坑

Nginx 镜像的官方版本主要有nginx(基于 Debian)和nginx:alpine(基于 Alpine Linux)。Alpine 体积小,但是坑也多。很多人在热词里搜“nginx alpine 挂载 conf.d 报错”,其实就是没搞懂 Alpine 的特殊性。

Alpine 镜像里没有 bash,用的是sh;默认不具备完整的中文字体、glibc 环境;如果你在配置里用了daemon off;以外的特殊启动方式,或者需要用到某些需要 glibc 的编译模块,就会出问题。我的建议是:求稳就用 Debian 版nginx:stable,想省体积再考虑 alpine。生产环境求稳,偏差一点点体积的代价换稳定性,值。

另外,Nginx 容器有个重要的行为:默认以daemon off;前台方式运行,这是 Docker 容器能够保持存活的前提。你在自己机器上习惯的nginx命令直接启动,在容器里是不行的,必须前台运行,否则容器启动之后立刻退出。

3. 核心配置:Dockerfile、docker-compose.yml 与三服务联调

3.1 .NET 应用 Dockerfile 多阶段构建

直接给一个我实测可用的 Dockerfile 模板,基于 .NET 8:

# 第一阶段:构建 FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim AS build WORKDIR /src # 先单独复制 csproj,利用 Docker 层缓存,避免每次改动代码都重新 restore COPY ["MyApp.Api/MyApp.Api.csproj", "MyApp.Api/"] RUN dotnet restore "MyApp.Api/MyApp.Api.csproj" # 复制全部源码并发布 COPY . . WORKDIR "/src/MyApp.Api" RUN dotnet publish "MyApp.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false # 第二阶段:运行 FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim AS final WORKDIR /app EXPOSE 8080 # 非 root 用户运行,安全加固 USER $APP_UID COPY --from=build /app/publish . ENTRYPOINT ["dotnet", "MyApp.Api.dll"]

这里有两个特别容易被忽略的细节:

  • /p:UseAppHost=false是用于生成纯 DLL 而不是原生可执行文件,在容器里跑更省资源。
  • USER $APP_UID是 .NET 8 镜像内置的非 root 用户,直接用它跑应用,比默认 root 安全得多。老版本的镜像没有这个环境变量,可以自行创建用户。

构建命令很简单:在项目根目录(包含 .sln 的地方)执行docker build -t myapp-api .。

3.2 MySQL 容器初始化与数据持久化

MySQL 容器最核心的是两件事:初始化脚本和数据卷。

第一次启动时,MySQL 会自动执行/docker-entrypoint-initdb.d/目录下的.sql、.sh、.sql.gz文件。这个机制特别适合做“首次建库建表”。你把init.sql放到一个宿主机目录,然后通过卷挂载进去:

volumes: - ./mysql/init:/docker-entrypoint-initdb.d - mysql_data:/var/lib/mysql

第一行是初始化脚本,第二行是数据持久化。数据卷mysql_data是 Docker 管理的卷,容器删了数据还在。这里有个重要提醒:初始化脚本只在数据卷为空时执行。如果你改动了init.sql,但数据卷里已经有了数据,这些改动不会生效。想重跑初始化,必须把数据卷删掉再重新创建容器。

MySQL 版本选择的时候,还要注意时区问题。不少应用往数据库里存时间会出现 8 小时偏差,就是因为 MySQL 容器默认使用了 UTC 时区。解决方案很简单,在 Compose 环境变量里加TZ: Asia/Shanghai,或启动参数加--default-time-zone=+08:00。

3.3 Nginx 反向代理与配置文件挂载

Nginx 在这套架构里的职责很纯粹:接收外部 HTTP/HTTPS 请求,转发给 .NET 应用容器。实际配置模板如下:

server { listen 80; server_name api.example.com; location / { proxy_pass http://app:8080; 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; proxy_connect_timeout 60s; proxy_read_timeout 60s; proxy_send_timeout 60s; } # 大文件上传场景,需要调大 body 大小限制 client_max_body_size 20m; }

proxy_pass http://app:8080;里的app是应用服务在 Compose 网络中的名称,Nginx 会自动解析这个主机名。注意Host、X-Real-IP、X-Forwarded-For这几个头部一定要转发过去,否则 .NET 应用拿不到用户的真实 IP,日志排查会很痛苦。

有 HTTPS 需求时,证书文件直接挂载进容器。我看到有人搜“nginx 替换 ssl 证书不生效”,大概率是没重启容器,或者只删了配置没删容器内的旧证书。Nginx 配置文件更新后,必须执行docker compose exec nginx nginx -s reload或者docker compose restart nginx,只改宿主机文件不重载配置是没用的。

3.4 docker-compose.yml 完整编排与启动顺序控制

下面是一份完整的docker-compose.yml,这是整个项目的核心配置文件:

services: app: build: context: . dockerfile: MyApp.Api/Dockerfile container_name: myapp-api restart: unless-stopped environment: - ASPNETCORE_ENVIRONMENT=Production - ConnectionStrings__Default=Server=mysql;Port=3306;Database=myapp;User=root;Password=YourPassword123;SslMode=Required; depends_on: mysql: condition: service_healthy networks: - app-net mysql: image: mysql:8.0 container_name: myapp-mysql restart: unless-stopped environment: - MYSQL_ROOT_PASSWORD=YourPassword123 - MYSQL_DATABASE=myapp - TZ=Asia/Shanghai command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci volumes: - ./mysql/init:/docker-entrypoint-initdb.d - mysql_data:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-proot"] interval: 10s timeout: 5s retries: 5 networks: - app-net nginx: image: nginx:stable container_name: myapp-nginx restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx/conf.d:/etc/nginx/conf.d - ./nginx/ssl:/etc/nginx/ssl depends_on: - app networks: - app-net volumes: mysql_data: networks: app-net: driver: bridge

启动顺序的坑,重点看depends_on在这里的用法。仅仅用裸的depends_on只能保证容器启动顺序,不保证 mysql 已经能接受连接。MySQL 容器起来后还要初始化数据库,这个初始化可能要几十秒。所以我给 mysql 加了一个healthcheck,然后应用层的depends_on写的是condition: service_healthy,意思就是“等 MySQL 健康了再启动应用”。这个细节对“无缝集成”非常重要,不加的话,应用启动时极大概率连不上数据库,报Unable to connect to any of the specified MySQL hosts。

4. 无缝集成的关键细节:连接字符串、环境变量与健康检查

4.1 环境变量传递与连接字符串的坑

.NET Core 的配置系统支持环境变量覆盖,而且有一套非常特殊的命名规则:环境变量中的双下划线__对应配置层级中的冒号:。所以ConnectionStrings__Default这个环境变量,在 .NET 里读到的就是ConnectionStrings:Default,也就是连接字符串。

这个机制很多人第一次接触时都会懵,因为 Linux 环境变量名里不允许有冒号,而 .NET 配置键里普遍用冒号分层,所以微软设计了双下划线的替代写法。理解了这一点,你就知道为什么 Compose 里写ConnectionStrings__Default=Server=mysql;Port=3306;...而不用ConnectionStrings:Default。

连接字符串本身还有个坑:需要在 Server 处写明容器服务名,不能写 localhost。因为应用跑在容器里,local 指向的是它自己的容器,而不是 MySQL 容器。这是我在实际项目里见过频率最高的集成错误。

另外,MySQL 8.0 默认启用了 SSL,所以在连接字符串里通常要带SslMode=Required或Preferred。如果你看到报错SSL connection error,优先检查这里。热词里也有“mysql ssl 连接错误”,等下我在排错章节专门展开。

4.2 健康检查配置与容器启动顺序

容器编排里,健康检查是个容易被忽视但极其重要的功能。Compose 的healthcheck定义了一个命令,Docker 会定期去执行它,根据返回码判断容器是否健康。状态从starting变到healthy之后,其他服务才能安全地依赖它。

MySQL 的健康检查最常用mysqladmin ping:这个命令虽然没有真正做登录验证(-p后的密码不对有时候也能返回 ping 成功),但至少能证明 mysqld 进程活着并接受连接。对生产环境更严格的做法是用真实 SQL 探测:

healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u$$MYSQL_USER", "-p$$MYSQL_PASSWORD"]

注意这里$$是 Compose 的转义写法,意思是把变量传给容器内执行,而不是由 Compose 在宿主机先解析。

.NET 应用的健康检查我建议也加上:如果你在 Program.cs 里引入了 ASP.NET Core 自带的健康检查中间件,可以用curl探测/health接口。但要注意容器里不一定有 curl,Debian 的 .NET 镜像通常也没有预装,所以要么在 Dockerfile 里装,要么用 .NET 自带的/health加一个 TCP 探针。

4.3 Nginx 与 .NET 应用配合的请求转发细节

Nginx 反代 Kestrel 时,最容易出的问题是HTTP/1.1 协议适配。Kestrel 默认启用了 HTTP/1.1,如果 Nginx 转发时没设置proxy_http_version 1.1,就可能出现upstream prematurely closed connection或 502 报错。这行配置我在模板里专门写了,非常重要。

另一个细节是 WebSocket 支持。如果 .NET 应用里有 SignalR 或原生 WebSocket 功能,Nginx 必须额外配置升级请求头:

location /hubs/ { proxy_pass http://app:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }

不配这个,SignalR 会连不上或频繁断开。这个场景我以前踩过,印象很深:前端页面能加载,但所有推送消息都收不到,一查日志全是 WebSocket 握手失败。

还有响应大小限制。默认 Nginx 缓冲机制有时候会让 .NET 的 SSE(Server-Sent Events)流式响应变卡,如果应用里有流式输出场景,建议在 location 里加上关闭缓冲的配置,避免 Nginx 因为缓冲导致 SSE 事件延迟推送。

5. 常见问题与排错实录

5.1 docker 安装 MySQL 失败的几个高频原因

热词里有一项很扎眼:“docker 安装 mysql 失败”。我在实际帮人排查时,见过最多的三类原因:

  • 端口冲突:宿主机 3306 已经被本机装过的 MySQL 占了。报错信息通常是Error starting userland proxy: listen tcp4 0.0.0.0:3306: bind: address already in use。解决办法是把容器端口映射改成3307:3306,或者把宿主机原 MySQL 先停掉。
  • 数据卷残留:之前初始化失败,留下了脏数据卷,新容器启动时把 MySQL 放到旧数据目录上进行不了初始化。报错里会出现[ERROR] --initialize specified but the data directory has files in it。解决方法是删掉旧卷docker volume rm 项目名_mysql_data,再重新启动。
  • 权限问题:如果直接把数据目录挂载到宿主机某个普通目录,而不是用 Docker 命名卷,很容易遇到 MySQL 无法写入数据文件的权限问题。因为容器内 MySQL 默认以mysql用户运行,但宿主机目录的 owner 是 root。解决办法是给目录授权chown -R 999:999 ./mysql/data,或者干脆用 Docker 命名卷省事。

5.2 Nginx 证书与 HTTPS 排错

Nginx 这块的高频问题,热词里也很明显:“nginx 替换 ssl 证书不生效”、“nginx 转发https 反向代理 net::err_cert_common_name_invalid”。

先说证书替换不生效。我排查过好几个案例,结论几乎一致:宿主机文件更新了,但容器里没更新。因为 Nginx 容器是通过卷挂载读证书的,所以宿主机文件更新后,容器里其实已经是新证书了,但 Nginx 进程还保持着旧证书的缓存。你必须在宿主机修改证书后执行重载:

docker compose exec nginx nginx -t docker compose exec nginx nginx -s reload

第二个问题net::err_cert_common_name_invalid,这个错误是证书域名不匹配。常见原因有两个:一是证书是给abc.example.com签的,但你用 IP 地址或localhost访问;二是证书只包含主域名,不包含www子域。解决办法是确保访问域名和证书的 Common Name / SAN 完全一致。如果只是本地测试,可以配置 Nginx 时把server_name写成访问时用的 IP,但浏览器依然会报警,因为 IP 地址证书本身几乎不会用于生产。

热词里还有“nginx mirror 超时时间”,这个是指mirror模块或者镜像流量复制时的上游超时。如果你开了mirror,上游镜像接口响应慢会影响主请求吗?实际上 Nginx 的mirror是异步的,但mirror_request_body的发送和响应读取有自己的超时配置。一般排查思路是:在 mirror 的 location 里单独设置proxy_read_timeout、proxy_connect_timeout,避免镜像请求拖垮主链路。

5.3 MySQL 连接报错与性能调优

连接报错这块,大家在热词里搜得最多的就是“mysql ssl 连接错误”。这个错误在 .NET 场景下有几种来源:

  • 连接字符串没带SslMode,而 MySQL 8.0 默认要求 SSL。报错像The host localhost does not support SSL connections。解决方法是连接字符串里显式写SslMode=Required或Preferred。
  • 证书校验失败。某些内网环境 MySQL 的 SSL 证书是自签名的,.NET 的 SSL 校验会失败。这种情况可以把SslMode设为Required(不校验证书但加密传输),不要设成VerifyFull。
  • 老驱动不支持新认证插件。如果用的还是旧版MySql.Data(4.x 或更早),升级到 8.0 服务端时会报Authentication method 'caching_sha2_password' not supported。解决办法是升级驱动到 MySqlConnector 或新版MySql.Data,不建议改服务端认证插件,因为那会降低安全性。

MySQL 性能调优,很多人问锁分类。锁这个东西说实话是个大话题,但容器化部署中你先要关心的不是行锁、间隙锁的细节,而是InnoDB 缓冲池大小。默认 MySQL 容器的innodb_buffer_pool_size是 128M,对生产应用来说太小了。调整方式是在 Compose 的command里加参数:

command: - --innodb_buffer_pool_size=1G - --max_connections=500

这个参数决定了 InnoDB 在内存里缓存多少数据页和索引页。命中率高了,磁盘 IO 就少了。我习惯用SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';和..._reads对比计算命中率,低于 99% 就该加 buffer pool 了。

5.4 其他现场问题速查表

下面整理一下这个项目里大家最容易遇到的一批高频问题和直接处理办法,都是我在实际操作中总结出来的:

现象可能原因解决方案
应用启动报Unable to connect to any of the specified MySQL hostsMySQL 还没初始化完成,应用就开始连接用depends_on: condition: service_healthy
访问站点返回 502 Bad GatewayNginx 解析不到app:8080或应用崩溃在 Compose 网络内docker compose exec nginx ping app测试连通性,再看应用日志
MySQL 容器启动后立刻退出数据目录权限问题或旧数据残留删卷重来或chown -R 999:999
上传大文件提示 413Nginx 默认client_max_body_size 1m在http/server/location配大一点
修改代码后镜像还是旧版本没重新构建,直接用了旧的 tagdocker compose build --no-cache或确认 tag 变化
容器内时间差了 8 小时镜像默认 UTC 时区Compose 里加TZ: Asia/Shanghai
Nginx 配置文件改完没生效只改了宿主机文件没重载docker compose exec nginx nginx -s reload
应用日志看不到用户真实 IPNginx 没转发X-Forwarded-For按前面模板配好 proxy_set_header

最后补一个很多人在搜的开发环境问题:“本地+虚拟机多端口 nginx 开发环境多站点自定义域名配置”。这个场景的实质是:你在一台虚拟机里跑了多个站点,想用site1.dev.com、site2.dev.com这样的域名访问不同端口。做法是给每个站点写一个 server 块,各自server_name不同,listen 80一样,Nginx 根据域名路由到不同的proxy_pass。宿主机上再把域名映射到虚拟机 IP,修改C:\Windows\System32\drivers\etc\hosts或 Linux 下的/etc/hosts。这套玩法对本地联调非常实用,配合容器化也是完全可行的,只要把每个站点映射到不同宿主机端口即可。

6. 性能优化与安全加固

6.1 镜像瘦身与启动加速

.NET 应用容器化之后,很多人发现镜像比较大(可能 200MB+),启动也慢。这里有两个方向可以优化。

第一是Dockerfile 层缓存。我平时写 Dockerfile 时,会把COPY csproj和RUN dotnet restore放在复制全部源码之前,这样只要依赖没变,dotnet restore层就会被缓存,改代码后重新构建基本秒级完成。这个习惯在本地开发调试时效率提升非常明显。

第二是裁剪发布。.NET 8 支持PublishTrimmed,可以把未使用的代码从托管程序集中移除,减小体积:

dotnet publish -r linux-x64 -c Release -p:PublishTrimmed=true -p:SelfContained=false

但要注意PublishTrimmed可能引发反射相关的运行时错误,因为裁剪器无法静态分析所有反射调用。有使用反射、动态加载程序集的场景,需要先用测试集充分验证。生产环境求稳,我建议保守一点,用 ReadyToRun 镜像编译优化启动速度,而不是贸然做很激进的裁剪。

6.2 MySQL 参数调整与 Nginx 性能配置

MySQL 容器化后,性能瓶颈常常在默认配置和宿主机资源之间不匹配。除了前面说的innodb_buffer_pool_size,还有两个经常要调的:

  • max_connections:默认 151,稍微有点并发就报Too many connections。但注意这个参数不是越大越好,每个连接都有内存开销,设太大反而会导致内存暴涨。
  • innodb_log_file_size:默认 48MB,写入日志频繁时性能会受限,可以试试 256MB 或 512MB。

Nginx 那边,性能优化的核心是worker_processes和worker_connections。默认配置只启动了 1 个 worker 进程。生产环境通常设置为worker_processes auto;,让它根据 CPU 核心数自动生成 worker,然后worker_connections调大,以支持更高并发。同时开启gzip压缩,对 .NET 应用返回的 JSON 数据有立竿见影的减负效果:

gzip on; gzip_types application/json application/javascript text/css text/xml; gzip_min_length 1k;

6.3 最小权限与安全配置

安全这个环节很多人不重视,但容器化架构里一旦出错,影响范围是全局的。三个容器分别说:

  • 应用容器:用非 root 用户运行(前面 Dockerfile 里的USER $APP_UID),不要跑 root。环境变量里别写死生产密码,用 Docker secret 或外部配置源管理。
  • MySQL 容器:不要用MYSQL_ROOT_PASSWORD作为日常连接账号。正确做法是启动时只用 root 建库建用户,然后在初始化脚本里创建专用账号并授予最小权限。类似:
CREATE USER 'app_user'@'%' IDENTIFIED BY 'SafePassword123'; GRANT SELECT, INSERT, UPDATE, DELETE ON myapp.* TO 'app_user'@'%'; FLUSH PRIVILEGES;

另外,生产环境不要把 MySQL 3306 端口映射到宿主机 0.0.0.0,只在 Compose 内部网络里给应用访问即可。如果确实需要外部连,用 SSH 隧道而不是直接暴露端口。

  • Nginx 容器:注意 TLS 版本配置,最低 TLSv1.2,禁用 TLSv1.0/1.1。安全相关的响应头也要加,比如X-Content-Type-Options: nosniff、X-Frame-Options: SAMEORIGIN、Strict-Transport-Security(如果全 HTTPS)。

关于热词里提到的“nginx 代理 ollama 设置 apikey”,这个玩法本质上是把 Nginx 当作 API 网关,在反代层统一注入鉴权头。原理跟反代 .NET 应用完全一样,只是proxy_set_header Authorization "Bearer xxx"或proxy_set_header X-API-Key "xxx",然后转发给上游服务。这套模式适合给内部 AI 服务做统一出入口,但要注意 key 放在 Nginx 配置里属于明文存储,生产环境还是建议用更正规的密钥管理方式。

最后再分享一个真实感受:这套“三容器”架构跑稳定之后,最大的红利是部署动作变成了一条命令。本地开发时一条docker compose up -d,生产发布时把镜像推到仓库、服务器上拉镜像再重启,整个生命周期干净利落。我在实际运维中遇到的大部分事故,仔细回想都是从“手工改服务器环境”开始的——而容器化会把这类不确定性降到很低。如果团队正在做 CI/CD 改造,我强烈建议把应用发布的 Dockerfile 作为标准化第一步,后面的流水线都建立在这层基础上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询