ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

NestJS项目部署到阿里云ECS全攻略:从环境搭建到Nginx反向代理与PM2进程守护

NestJS项目部署到阿里云ECS全攻略:从环境搭建到Nginx反向代理与PM2进程守护 在开始之前我先说明一下这篇文章写成的时间点是我把一个基于NestJS的团队协作服务端项目从本地开发环境完整迁移到阿里云ECS上的全过程。整个项目采用NestJS TypeORM MySQL的经典组合同时带有WebSocket实时通信模块部署目标是阿里云的轻量应用服务器ECS同源配置系统镜像为CentOS 7.9。如果你正准备把NestJS项目部署上云这篇文章里记录的绝大多数坑你大概率都会遇到。先说结论NestJS项目部署到Linux服务器这件事本身不复杂真正折磨人的地方在于环境差异、进程守护、反向代理、数据库连通性和安全组配置这些“外围工程”。我前前后后折腾了两天把部署流程完整跑通、把日志量控制到合理水平、把服务重启策略调稳定之后回头整理这篇文章希望能帮你绕过我踩过的坑。1. 部署前的整体规划与服务器选型1.1 为什么选择ECS而不是Serverless或容器服务在动手部署之前我先梳理了一下最终选型逻辑。对于中小型NestJS项目来说摆在面前的无非是三条路直接买一台ECS自己搭环境、用Serverless应用引擎SAE直接托管、或者上容器服务K8s。我选择ECS而不是Serverless或K8s核心原因有三个。第一是成本可控一台2核4G的ECS按量付费或者包年包月费用远低于K8s集群的最低开销而且Serverless对于长连接场景尤其是WebSocket往往会有闲置超时限制这恰好是NestJS实时通信类需求的大忌。第二是部署路径透明可控ECS上所有环境都是自己装的出了问题可以从底层的Node.js版本、系统库、Nginx配置一层层往上排查而不是在厂商托管的黑盒里靠猜。第三是迁移灵活ECS上的部署方案几乎可以无缝平移到任何一家云厂商的同配置服务器甚至移到物理机不绑定厂商特性。如果你对Docker比较熟用Docker确实能减少很多环境差异带来的坑。我当时没选Docker是因为项目里有一些历史遗留的静态资源处理逻辑直接挂载宿主机路径更直观而且团队对Docker的维护经验不足出了问题反而绕远路。但这不代表Docker方案不好——如果你维护的是多个服务强烈建议从第一天就上Docker Compose。1.2 服务器配置与购买前要确认的三件事购买阿里云ECS之前有三个关键参数需要先确认清楚。第一个是地域一定要选择和你的目标用户群网络延迟最低的地域这一步的延迟差距在50ms到100ms之间直接影响接口响应体验。第二个是操作系统镜像我之前踩过坑建议直接选CentOS 7.9或Alibaba Cloud Linux 3.0系统镜像要在购买时就确认好后面换系统盘要么重装要么花时间迁移数据非常麻烦。第三个是带宽计费方式如果项目有文件上传下载需求固定带宽可能不够用按使用流量计费更适合绝大多数Web项目。购买成功后第一件事是重置root密码并绑定密钥对。密钥对登录比密码登录安全得多而且后续用SSH工具连接时也省去输密码的麻烦。密钥对在阿里云控制台的ECS实例详情页面可以一键绑定绑定后记得测试一下能否正常登录不要等部署到一半才发现密钥失效。1.3 整体架构预览Nginx PM2 NestJS MySQL部署方案的架构比较经典我直接列一下每个组件的职责方便你对号入座Nginx负责监听80/443端口将请求反向代理到NestJS应用实际监听的端口同时承担静态资源服务、gzip压缩、HTTPS证书终止。PM2Node.js进程守护工具负责管理NestJS应用的启动、崩溃自动重启、日志输出、内存监控。NestJS业务服务层监听127.0.0.1的3000端口不对外网直接暴露处理API请求和WebSocket连接。MySQL数据存储层监听3306端口通过TypeORM与NestJS进行ORM映射。完整链路是用户请求DNS解析到ECS公网IP - Nginx监听80/443端口接收请求 - Nginx通过反向代理将请求转发到127.0.0.1:3000 - NestJS处理业务逻辑并读写MySQL - 返回响应给Nginx - Nginx返回给用户。这一层间接转发是很标准的生产部署架构它带来的好处是NestJS服务本身不直接暴露在公网减少被恶意扫描和攻击的风险同时Nginx层面就能完成负载均衡、静态资源缓存、请求体大小限制等杂活。2. 基础环境搭建从零开始装好Node.js和MySQL2.1 Node.js版本管理nvm还是直接二进制包NestJS对Node.js版本有硬性要求尤其是NestJS 10及以上版本要求Node.js 16。这里有一个很典型的坑CentOS 7自带的yum源里Node.js版本往往停留在12.x或14.x直接用yum install nodejs安装编译NestJS项目时必然报错最典型的就是SyntaxError: Unexpected token .这类语法错误因为新版本的ES语法在老版本Node里无法解析。我最终选择的方案是用nvmNode Version Manager来管理Node.js版本。原因有三个一是nvm支持用户级安装不需要全局root权限安装路径在用户home目录下不会污染系统环境二是后续升级Node.js版本非常方便一行命令搞定三是如果项目里有多个Node.js版本依赖nvm可以随时切换这对以后维护多个项目很有用。安装nvm并配置Node.js 18的具体命令如下实测可用# 安装nvm curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash # 使nvm命令生效 source ~/.bashrc # 查看远程可用版本 nvm ls-remote # 安装Node.js 18 LTS版本 nvm install 18.18.0 # 设置默认版本 nvm alias default 18.18.0 # 验证版本 node -v npm -v这里有个细节希望你注意nvm安装后的环境变量只对当前用户生效如果你用root用户部署没问题但如果你用普通用户部署PM2以系统服务方式启动时可能会找不到node命令。解决方案是在PM2的启动配置里明确指定node解释器路径这个后面讲PM2配置时会详细说。2.2 MySQL安装与基础配置数据库我用的是MySQL 8.0安装方式选择了阿里云镜像源比官方源快很多。CentOS 7上安装MySQL 8.0需要先配置阿里云的yum源否则默认源里只有MariaDB。把阿里云yum源配置好的命令我当时是这样操作的# 备份原yum源 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak # 下载阿里云CentOS 7镜像源 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo # 清理缓存并重新生成 yum clean all yum makecache装MySQL的过程有几个位置比较容易卡住我直接列关键步骤和注意点# 安装MySQL 8.0仓库 rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm # 安装MySQL服务 yum install mysql-community-server -y # 启动MySQL systemctl start mysqld # 获取临时root密码这一步很关键首次安装后root密码会写入日志 grep temporary password /var/log/mysqld.log拿到临时密码后第一件事就是用临时密码登录然后立即修改root密码并创建业务账号-- 修改root密码 ALTER USER rootlocalhost IDENTIFIED BY 你的强密码; -- 创建业务账号 CREATE USER nest_applocalhost IDENTIFIED BY 业务密码; -- 创建数据库 CREATE DATABASE nest_deploy DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 授权 GRANT ALL PRIVILEGES ON nest_deploy.* TO nest_applocalhost; FLUSH PRIVILEGES;关于MySQL 8.0的密码策略默认的validate_password组件要求密码必须包含大小写字母、数字和特殊字符且长度至少8位。如果你嫌烦可以降低密码策略级别set global validate_password.policy LOW; set global validate_password.length 6;但这里建议你在生产环境还是保持默认的强密码策略数据库安全不值得省这三秒钟。2.3 阿里云安全组规则最容易忽略的第一道坎这里必须花单独一个小节来讲因为我在这上面浪费了整整两个小时。阿里云ECS和家用服务器最大的区别在于它有两层防火墙第一层是实例内部的防火墙iptables/firewalld第二层是阿里云控制台里的安全组规则。安全组规则相当于云平台在虚拟机外面的一道闸门即使你实例内部的防火墙完全关闭安全组不放开端口外部流量也进不来。我当时的情况是NestJS服务在服务器本机curl 127.0.0.1:3000完全正常但浏览器访问公网IP:3000就是超时排查了半天才发现是安全组没配置入方向规则。具体操作路径阿里云控制台 - ECS实例 - 安全组 - 配置规则 - 入方向 - 手动添加。至少需要放行以下端口端口协议用途备注22TCPSSH远程连接建议限制来源IP80TCPHTTP访问允许所有来源443TCPHTTPS访问允许所有来源3306TCPMySQL连接强烈建议仅允许指定内网IP不要暴露公网3306端口暴露公网是数据库安全的大忌如果确实需要在本地用Navicat等工具连接建议用SSH隧道代替直接暴露端口。配置好安全组后用telnet或nc命令验证端口连通性telnet 你的公网IP 80 telnet 你的公网IP 22如果telnet不通优先检查安全组再检查实例内防火墙状态。3. NestJS项目部署核心流程3.1 代码上传Git拉取还是scp传输代码传到服务器上的方式主要有三种git clone、scp/rsync、以及通过GitHub/Gitee的webhook自动拉取。我个人最推荐git clone 服务器端配置deploy key原因很简单后续每次更新代码只需要在服务器上git pull省去重复上传的时间而且代码变更历史可追溯。如果项目仓库是私有的需要在服务器上生成一对SSH密钥并把公钥添加到代码仓库的Deploy Keys里ssh-keygen -t ed25519 -C deployecs cat ~/.ssh/id_ed25519.pub然后把输出的公钥添加到Git服务器GitHub/Gitee的仓库部署密钥中。代码路径建议统一放到/var/www/project-name或者/home/deploy/project-name目录规划越标准化后面维护越省心。我当时用的路径是/var/www/nest-collab所有项目文件都在这一个目录下环境变量、日志、上传文件都按子目录区分。3.2 依赖安装与构建TypeORM实体同步要注意的事代码拉下来后进入项目根目录安装依赖和构建。NestJS项目的依赖安装有两个关键点一是必须用npm ci而不是npm install因为npm ci会严格按照package-lock.json的版本安装避免因依赖版本漂移导致本地能跑、服务器上跑不起来的问题二是如果构建过程涉及TypeORM的实体要注意synchronize这个配置项。我当时的项目配置了TypeORM开发环境为了方便打开了自动同步实体功能synchronize: true。但部署到生产环境时这个配置一定要关掉否则每次服务启动时TypeORM都会根据实体自动修改数据库表结构万一表结构变更带有破坏性操作数据丢失的风险很高。生产环境的正确姿势是用TypeORM的migration功能管理表结构变更或者在不影响业务的情况下手动执行SQL。# 安装生产依赖 npm ci --production # 全局安装nest cli如果还没装 npm install -g nestjs/cli # 构建项目 npm run build构建完成后项目结构大致是dist/目录下包含了所有编译后的JS文件。NestJS默认的构建输出路径是dist/main.js这个入口文件是PM2需要直接管理的。这里还有一个经常遇到的问题如果你的NestJS用了nestjs/config加载.env文件构建后的dist目录下不会自动包含.env文件。我建议把.env文件放在项目根目录并且确保PM2的工作目录cwd设置为项目根目录这样process.cwd()才能正确找到.env文件。更稳妥的做法是用绝对路径加载环境变量配置。3.3 PM2配置进程守护的关键细节PM2是NestJS部署的标配进程管理工具它解决的问题有三个进程崩溃后自动重启、开机自启动、统一日志管理。npm install -g pm2全局安装后我建议不要直接用pm2 start裸启动而是创建一个进程配置文件把所有配置项集中管理。我用的ecosystem.config.js配置如下实测稳定module.exports { apps: [ { name: nest-collab, script: dist/main.js, cwd: /var/www/nest-collab, instances: 1, exec_mode: fork, autorestart: true, max_memory_restart: 512M, env: { NODE_ENV: production, PORT: 3000 }, error_file: /var/www/nest-collab/logs/err.log, out_file: /var/www/nest-collab/logs/out.log, log_date_format: YYYY-MM-DD HH:mm:ss, time: true, kill_timeout: 5000, listen_timeout: 5000 } ] };逐个解释一下配置项instances和exec_modeNestJS单实例模式用fork多实例用cluster。我当时没有用cluster模式因为NestJS应用内部使用了WebSocketcluster模式下需要额外处理Redis适配器来跨进程同步消息复杂度上升不少。如果你的服务没有WebSocket这类有状态连接可以考虑cluster模式充分利用多核CPU。max_memory_restart当进程内存占用超过512M时自动重启这是防止内存泄漏拖垮服务器的保命配置。error_file和out_file日志输出路径必须提前建好logs目录否则PM2启动会报错。kill_timeout和listen_timeout分别控制优雅退出和启动超时时间。NestJS应用启动时需要初始化数据库连接、缓存等如果listen_timeout设置太短默认3000ms可能被PM2误杀。启动命令如下pm2 start ecosystem.config.js pm2 save pm2 startuppm2 save会把当前进程列表保存到dump文件pm2 startup生成开机自启动脚本。注意pm2 startup执行后会输出一段需要复制的命令一定复制到终端里执行一遍才算真正完成开机自启配置。3.4 Nginx反向代理配置Nginx是整个部署链路的前端入口核心配置是反向代理NestJS应用同时处理WebSocket升级和静态资源缓存。先通过yum install nginx安装然后修改/etc/nginx/conf.d/nest-collab.conf。一个可以直接参考的NestJS反代配置示例server { listen 80; server_name your-domain.com; # 没有域名就写服务器公网IP client_max_body_size 50m; # gzip压缩配置 gzip on; gzip_min_length 1k; gzip_comp_level 5; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xmlrss text/javascript; location / { proxy_pass http://127.0.0.1:3000; 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_read_timeout 60s; proxy_send_timeout 60s; } location /uploads/ { alias /var/www/nest-collab/uploads/; expires 30d; access_log off; } location /nginx_status { stub_status; access_log off; } }location /里的proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection upgrade;这两行是WebSocket能否正常工作的关键。NestJS的网关WebSocketGateway需要通过HTTP Upgrade协议切换为WebSocket长连接如果Nginx不转发Upgrade头客户端会一直报WebSocket connection failed: Error during WebSocket handshake。配置完Nginx后测试并重载nginx -t systemctl enable nginx systemctl restart nginx3.5 HTTPS证书配置免费证书就够用现在很多浏览器对HTTP站点会有警告提示为了安全性和用户信任度HTTPS是必须的。阿里云提供免费的SSL证书在控制台搜“SSL证书”选择免费证书填写域名即可申请。申请下来的证书是pem格式把证书文件和私钥文件放到/etc/nginx/cert/目录下然后在Nginx配置里添加443 server块。如果你的域名没有备案阿里云ECS上不能直接80/443端口访问域名这是一个设置了就没办法绕过的限制。建议提前确认域名备案状态备案流程一般需要1-2周这个时间成本一定要算进项目上线计划里。4. 部署全流程中遇到的坑与排查实录4.1 服务启动后端口占用与EADDRINUSE报错这个坑出现在我第一次修改了NestJS代码后执行pm2 restart时。PM2重启的过程中旧的进程可能还占着3000端口新的进程在listen时直接抛出Error: listen EADDRINUSE: address already in use 127.0.0.1:3000。PM2的restart逻辑是先停止旧进程再启动新进程。但在某些情况下尤其是kill_timeout设置过短旧进程没有来得及释放端口新进程就会遇到端口占用问题。解决方案有两个一是把kill_timeout调大一点给旧进程足够的优雅退出时间二是在代码层面处理好进程退出监听主动关闭HTTP Server。NestJS主入口文件main.ts里可以这么处理优雅退出import { NestFactory } from nestjs/core; import { AppModule } from ./app.module; async function bootstrap() { const app await NestFactory.create(AppModule); await app.listen(3000); const server app.getHttpServer(); process.on(SIGTERM, () { server.close(() process.exit(0)); }); process.on(SIGINT, () { server.close(() process.exit(0)); }); } bootstrap();这样PM2向进程发送SIGTERM信号时NestJS会主动关闭HTTP Server后再退出端口能及时释放。4.2 MySQL连接报错Access denied和connect ETIMEDOUT部署时最容易遇到的两个数据库连接错误一个是权限拒绝Access denied for user一个是连接超时connect ETIMEDOUT。Access denied的根因一般是MySQL账号的host不匹配。MySQL的账号由用户名host组成例如nest_applocalhost和nest_app%是两个完全不同的账号。如果NestJS通过127.0.0.1连接数据库MySQL会将其识别为localhost而如果你的账号只授权了%所有主机某些MySQL版本下反而会拒绝连接。我当时解决的方法是在创建账号时同时授权localhost和127.0.0.1CREATE USER nest_applocalhost IDENTIFIED BY 密码; CREATE USER nest_app127.0.0.1 IDENTIFIED BY 密码; GRANT ALL PRIVILEGES ON nest_deploy.* TO nest_applocalhost; GRANT ALL PRIVILEGES ON nest_deploy.* TO nest_app127.0.0.1; FLUSH PRIVILEGES;connect ETIMEDOUT的主要原因是NestJS应用所在服务器无法连通MySQL服务器的3306端口。如果你用的是阿里云RDS排查顺序是RDS白名单是否包含ECS实例的IP、安全组是否放行、数据库账号的host是否允许远程连接、以及网络类型是否一致VPC和经典网络互相不通。如果你是像我一样在ECS上自建MySQL一般不存在这些问题确认服务在跑就行systemctl status mysqld netstat -tlnp | grep 33064.3 WebSocket连接失败的排查过程这是我整个部署过程中最痛苦的一个坑。本地开发环境一切正常部署到ECS后客户端WebSocket连接始终失败浏览器控制台报错信息是Error during WebSocket handshake: Unexpected response code: 200。刚开始我以为是Nginx配置的问题反复检查Upgrade头没有发现问题。后来查阅了大量资料才发现真正的坑在NestJS的CORS配置上。我的NestJS主入口文件里配置了CORSconst app await NestFactory.create(AppModule); app.enableCors({ origin: true, credentials: true });这个配置对HTTP请求有效但对于WebSocket的握手请求某些情况下会拦截跨域连接返回200而不是101状态码。WebSocket握手要求服务器返回101 Switching Protocols如果CORS配置不正确Nginx返回给客户端的可能是200状态码导致握手失败。解决方法是去掉CORS配置中对WebSocket路由的影响或者在前端WebSocket连接时明确指定origin。最简单有效的方案是如果NestJS服务只通过Nginx反向代理对外提供前端与后端使用同一域名或端口CORS其实是不需要的——因为浏览器看到的请求是同源的。真正跨域的场景下CORS需要在Nginx层面也做允许头配置。不过在排查过程中我还发现另一个容易混淆的点如果你用了nestjs/platform-socket.io默认的WebSocket路径是/socket.io。Nginx配置里如果有location /socket.io/的特殊处理需要确保不会和location /产生冲突。4.4 静态文件路径404问题NestJS非常规的静态资源服务方式比如用nestjs/serve-static模块在本地开发时一切正常但部署到ECS后用户上传的头像等文件访问全部404。原因有两个一是项目中配置了ServeStaticModule的rootPath指向项目根目录下的uploads但用PM2启动时工作目录cwd设置不对导致相对路径解析错误二是上传文件的写入路径是相对于进程工作目录的PM2配置里的cwd指向了别的目录。解决方案是把所有涉及文件路径的配置都改为绝对路径不要依赖相对路径ServeStaticModule.forRoot({ rootPath: /var/www/nest-collab/uploads, serveRoot: /uploads })上传文件的保存逻辑里也用绝对路径import { join } from path; const uploadPath join(/var/www/nest-collab/uploads, filename);这个改动虽然看起来繁琐但能彻底避免PM2切换cwd、执行npm run build时的路径差异导致的问题。4.5 构建时内存溢出JS heap out of memory项目规模大了之后npm run build或pm2 start阶段可能会报--- Last few GCs --- [1206:0x4a57c50] 42822 ms: Mark-sweap 2032.8 - 2031.2 (2065.6) MB, 1540.5 / 0.0 ms (average mu 0.156, current mu 0.001) allocation failure scavenge might not succeed --- JS stacktrace --- FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory原因就是Node.js默认的内存上限大约是2GB64位系统当构建过程或应用启动过程中需要的内存超过这个限制时Node.js会直接崩溃。解决方案是在启动或构建时指定更大的内存上限# 构建时增加内存 NODE_OPTIONS--max-old-space-size4096 npm run build # PM2进程增加内存 # 在ecosystem.config.js中增加 node_args: --max-old-space-size1024对于普通NestJS项目进程本身内存一般不会超过512M但如果你的项目加载了大量数据到内存比如缓存全量配置到Redis前先放内存适当调大--max-old-space-size可以减少崩溃概率。4.6 时间同步与日志时区问题这个坑比较隐蔽——部署后NestJS日志里的时间比北京时间慢了8个小时。原因是CentOS 7默认的时区是UTC协调世界时而北京在东八区需要把系统时区切换为Asia/Shanghaitimedatectl set-timezone Asia/Shanghai同时注意MySQL也要设置时区如果MySQL里存的DATETIME字段类型会读取数据库会话的时区也会出现偏差-- 修改会话时区为东八区 SET time_zone 8:00; -- 持久化修改 -- 在my.cnf中的[mysqld]段下添加 default-time-zone 8:00PM2日志的时区则由log_date_format控制它读取的是服务器系统时区只要系统时区设置正确PM2日志时间就正确。但NestJS应用内部的new Date()生成的时间戳时区取决于Node.js运行的系统时区所以两者要保持一致。4.7 服务器重启后Nginx和PM2的自启动顺序服务器重启后我遇到的情况是PM2管理的NestJS进程自动启动了Nginx也自动启动了但前端访问依然报502 Bad Gateway。排查日志发现NestJS应用比Nginx启动得慢Nginx一旦发现后端不可用会把上游标记为不可用状态后续请求直接返回502即使NestJS起来了也不恢复。解决方法是配置Nginx的上游健康检查或者在Nginx配置里加上proxy_next_upstream更高效的方式是确保NestJS就绪后再启动Nginx。有一个比较实用的做法是在PM2的ecosystem.config.js里配置listen_timeout和wait_ready让PM2只有在NestJS应用发送了ready信号后才认为启动完成。但更简单粗暴的方案是直接用systemd管理Nginx依赖关系在/etc/systemd/system/multi-user.target.wants/nginx.service里添加Afternetwork.target pm2-nest-collab.service。我当时实际操作中采用的是最省事的方案写一个启动脚本按顺序先启动PM2等3秒再启动Nginx。虽然不够优雅但完全够用pm2 resurrect sleep 3 systemctl restart nginx4.8 阿里云安全组忘记放行WebSocket端口这是一个很容易被忽略的坑。如果NestJS的WebSocket监听端口和HTTP端口不是同一个比如HTTP跑3000WebSocket单独跑3001那么在阿里云安全组里必须分别放行这两个端口。很多人在安全组里只放行了80/443导致WebSocket连接失败然后开始怀疑Nginx配置浪费了很多时间。最好的方案是让WebSocket和HTTP共用同一个NestJS服务端口3000通过Nginx统一转发。这既简化了安全组配置又避免了跨域问题。4.9 数据库连接数耗尽与MySQL连接池配置部署到生产环境后随着并发量的上升NestJS日志里开始出现QueryFailedError: Too many connections这是MySQL的默认最大连接数只有151导致的。NestJS的TypeORM配置里没有显式设置连接池大小默认行为会根据驱动创建一个连接池。如果每个请求都从连接池拿连接而连接池上限不高同时并发一多就容易打满。解决方案是两面同时调一方面在MySQL端把max_connections调大另一方面在NestJS端明确配置TypeORM连接池参数TypeOrmModule.forRoot({ type: mysql, host: 127.0.0.1, port: 3306, username: nest_app, password: 密码, database: nest_deploy, extra: { connectionLimit: 10 } })connectionLimit不要设置过大因为每个连接都会占用MySQL资源合理范围是10~50之间根据你的服务器内存和业务并发量调整。5. 部署后的安全加固与日常维护5.1 防火墙设置与SSH安全加固服务器上线后第一件事就是检查防火墙状态。CentOS 7默认使用firewalld如果之前没有禁用它的规则要跟上文提到的安全组配合使用。# 查看防火墙状态 firewall-cmd --state # 放行80和443端口 firewall-cmd --permanent --add-port80/tcp firewall-cmd --permanent --add-port443/tcp firewall-cmd --reloadSSH安全方面建议修改默认端口22改为高位端口并限制root密码登录只允许密钥登录。修改SSH配置前确保你已经设置好密钥登录并能正常登录否则改完可能彻底连不上服务器只能走阿里云控制台里的VNC紧急登录。vim /etc/ssh/sshd_config # 修改以下内容 Port 22222 PermitRootLogin prohibit-password PasswordAuthentication no5.2 Nginx日志切割与磁盘空间清理Nginx默认会把访问日志写到/var/log/nginx/access.log时间长了会占满磁盘空间进而导致数据库写入失败、服务崩溃。一个简单的日志切割方案是用logrotateCentOS 7已经预装了logrotate我们只需要写一个配置vim /etc/logrotate.d/nginx /var/log/nginx/*.log { daily missingok rotate 14 compress delaycompress notifempty create 640 nginx adm sharedscripts postrotate [ -f /var/run/nginx.pid ] kill -USR1 cat /var/run/nginx.pid endscript }同时PM2的日志也需要定期清理可以在ecosystem.config.js里配置max_size字段让PM2自动切割日志文件。5.3 定期备份数据库策略数据库备份这件事部署初期最容易忽略等出问题才后悔。我的备份策略很简单每天凌晨3点用mysqldump全量备份数据库保留最近7天的备份文件同时定期把备份文件同步到OSS对象存储。这样即使服务器磁盘损坏也能从对象存储恢复数据。mkdir -p /var/backups/mysql crontab -e # 添加定时任务 0 3 * * * mysqldump -u backup_user -p密码 nest_deploy | gzip /var/backups/mysql/nest_deploy_$(date \%Y\%m\%d).sql.gz find /var/backups/mysql -name *.sql.gz -mtime 7 -delete注意mysqldump用户不一定要用root可以创建一个只有SELECT、LOCK TABLES、SHOW VIEW权限的备份账号降低风险。5.4 阿里云监控与报警配置阿里云ECS自带基础监控功能在控制台可以看到CPU使用率、内存使用率、磁盘IO、网络流量等指标。我建议至少设置两个报警规则一个是CPU使用率超过80%持续5分钟报警另一个是磁盘使用率超过85%报警。报警方式选择短信和邮件确保第一时间收到通知。对于NestJS应用层面的监控可以用PM2的pm2 monit命令实时查看各进程的CPU和内存占用情况。如果想要更完善的方案可以考虑接一套开源的监控平台比如Prometheus Grafana把NestJS的请求延迟、错误率、数据库连接池状态都纳入监控范围但这已经是另一个量级的工程了等业务规模真的起来之后再上不迟。6. 一些值得交代的实战细节6.1 环境变量管理不要写在代码里NestJS项目里最容易被忽视的安全问题是把数据库密码、JWT密钥、OSS密钥等敏感信息直接硬编码在代码里。部署后我花了点时间把所有敏感信息迁移到了.env文件并且确保.env文件被添加到了.gitignore中不会被提交到代码仓库。生产环境的.env文件内容类似NODE_ENVproduction PORT3000 DB_HOST127.0.0.1 DB_PORT3306 DB_USERNAMEnest_app DB_PASSWORD你的强密码 DB_DATABASEnest_deploy JWT_SECRET一段随机的长字符串在NestJS里通过nestjs/config模块加载即可ConfigModule.forRoot({ isGlobal: true, envFilePath: .env })6.2 日志管理从console.log到结构化日志默认情况下NestJS项目里到处是console.log部署后这些日志会全部输出到PM2的out.log文件混乱且难以搜索。建议在部署前对日志来一次统一治理最简单的方案是使用nestjs/common的Logger类替换console.log并配置日志级别。如果追求更好的可观测性可以引入pino或winston。我当时的项目直接用了NestJS内置的Logger配合PM2的日志切割基本满足中小项目的排障需求。6.3 版本回滚别等到出问题才想到部署后最怕的是上线了新版本才发现严重Bug必须回滚到上一个稳定版本。我采用的方案是在服务器上保留上一个发布版本的目录# 发布新版本时先把当前稳定版本复制一份 cp -r /var/www/nest-collab /var/www/nest-collab_bak # 如果新版本有问题直接回滚 pm2 stop nest-collab rm -rf /var/www/nest-collab mv /var/www/nest-collab_bak /var/www/nest-collab pm2 start nest-collab如果用了Git管理代码更规范的做法是打tag并利用git checkout回滚到指定tag。总之回滚方案一定要在出问题之前准备好别等到生产环境出故障了才临时想对策。6.4 使用阿里云OSS存放上传文件如果项目的上传文件量比较大放在ECS本机磁盘有两个问题一是磁盘空间有限升级成本高二是服务器重装或迁移时本地文件容易丢失。建议把上传的文件直接存到阿里云OSS通过NestJS的nestjs/oss或者官方SDK对接。存OSS的好处是存储成本低、支持CDN加速、天然高可用。虽然增加了一点代码量但省去了未来的文件迁移和备份麻烦值得做。7. 常见问题速查表整理一份我在部署过程中遇到的典型问题速查表方便你直接对照排查现象可能原因排查方向与解决浏览器无法访问公网IP:80安全组未放行80端口检查阿里云控制台安全组入方向规则502 Bad GatewayNginx转发无响应检查NestJS进程是否存活curl 127.0.0.1:3000403 Forbidden目录权限不足检查Nginx运行用户对项目目录的访问权限WebSocket连接失败Nginx未配置Upgrade头在proxy_set_header中配置Upgrade和Connection数据库连接被拒MySQL账号host不匹配创建对应host的账号或授权pm2进程自动退出内存超限或未捕获异常查看error.log增大max_memory_restart访问HTTPS提示不安全证书配置错误或未部署nginx -t检查证书路径确认443监听上传文件后404ServeStaticModule路径错误改用绝对路径检查cwd设置服务启动后自动退出端口被占用检查endaddrInuse报错配置SIGTERM优雅退出日志时间不对系统时区为UTCtimedatectl set-timezone Asia/Shanghai接口响应慢未开启gzip或数据库慢查询开启Nginx gzip检查MySQL慢查询日志以上每个问题前文都有对应的详细分析。如果遇到表里没有覆盖的情况优先查看NestJS应用日志pm2 logs nest-collab和Nginx错误日志tail -f /var/log/nginx/error.log绝大多数问题都能从日志里找到线索。最后再分享一个运维小技巧上线前先在本地用生产模式跑一遍完整流程npm run build 生产环境变量确认无误再部署到ECS。这一步能过滤掉80%的低级配置问题剩下来的才是真正和服务器环境相关的坑。我在实际部署过程中体会最深的一点是——本地能跑通和线上能跑通之间差的就是对操作系统、网络策略、进程管理、反向代理这些基础设施的理解。把NestJS项目成功部署到阿里云ECS不只是一个CtrlC和CtrlV的过程它是对整个服务端工程能力的一次综合检验。希望这篇文章里记录的这些坑能让你少走一些弯路。
返回列表