{T}

Egg.js 应用部署与上线指南

1. 概述

本指南旨在为开发者提供一份详尽的 Egg.js 应用部署与上线手册。无论您是初次接触服务器部署,还是希望优化现有的部署流程,本文都将为您提供从环境准备到自动化部署的全方位指导。

1.1. 部署架构概览

下图展示了 Egg.js 应用的典型生产环境部署架构:

code
┌─────────────┐         ┌──────────────────────────────────────────┐
│   用户      │         │          服务器 (Linux)                  │
│  (浏览器)   │         │                                          │
└──────┬──────┘         │  ┌─────────────────────────────────────┐ │
       │                │  │         Nginx (反向代理)            │ │
       │ HTTPS (443)    │  │  - SSL 终止                         │ │
       │ HTTP  (80)     │  │  - 负载均衡                         │ │
       └────────────────┼─→│  - 静态资源服务                     │ │
                        │  └──────────────┬──────────────────────┘ │
                        │                 │ proxy_pass              │
                        │                 │ 127.0.0.1:7001          │
                        │  ┌──────────────▼──────────────────────┐ │
                        │  │      PM2 (进程管理器)               │ │
                        │  │  ┌───────────┐  ┌───────────┐       │ │
                        │  │  │ Worker 1  │  │ Worker 2  │ ...   │ │
                        │  │  │ (Egg.js)  │  │ (Egg.js)  │       │ │
                        │  │  └─────┬─────┘  └─────┬─────┘       │ │
                        │  └────────┼──────────────┼─────────────┘ │
                        │           │              │               │
                        │  ┌────────▼──────────────▼─────────────┐ │
                        │  │      MongoDB (数据库)               │ │
                        │  └─────────────────────────────────────┘ │
                        └──────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────┐
│                    CI/CD 流程 (GitHub Actions)                   │
│  ┌────────┐    ┌────────┐    ┌────────┐    ┌────────────────┐  │
│  │ 代码   │───→│ 测试   │───→│ 构建   │───→│ 自动部署服务器 │  │
│  │ 推送   │    │        │    │        │    │                │  │
│  └────────┘    └────────┘    └────────┘    └────────────────┘  │
└──────────────────────────────────────────────────────────────────┘

1.2. 核心组件说明

组件作用默认端口
Nginx反向代理、SSL 终止、负载均衡80, 443
PM2Node.js 进程管理、集群模式-
Egg.jsWeb 应用框架7001
MongoDB数据存储27017
CertbotSSL 证书自动管理-

2. 前提条件

在开始之前,请确保您已具备以下条件:

  • 一台 Linux 服务器:建议使用 Ubuntu 20.04 或其他主流的 Linux 发行版。
  • 一个域名 (可选):如果希望通过域名访问您的应用,您需要注册一个域名。
  • 一个 Egg.js 应用:一个已经开发完成、可以进行部署的 Egg.js 项目。
  • SSH 客户端:用于连接您的远程服务器。macOS 和 Linux 通常自带,Windows 用户可以使用 PuTTY 或 Windows Terminal。

3. 服务器环境准备

一个稳定、可靠的服务器环境是应用成功上线的基石。本章节将指导您完成 Node.js、MongoDB、Nginx 和 Git 的安装与配置。

在开始之前,建议先更新您的服务器软件包列表:

bash
sudo apt update && sudo apt upgrade -y

提示:为了提升软件包下载速度,建议将 Ubuntu 的软件源更换为国内镜像,例如清华大学开源软件镜像站

3.1. 安装 Node.js

推荐使用 nvm (Node Version Manager) 来安装和管理 Node.js,它可以让您轻松地在不同版本之间切换。

1. 安装 nvm:

bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash

安装完成后,您需要重新加载 shell 配置或重新登录才能使用 nvm 命令。

bash
source ~/.bashrc

2. 配置 nvm 镜像源 (可选但推荐):

为了加速 Node.js 的下载,您可以将 nvm 的镜像源指向国内镜像。

bash
export NVM_NODEJS_ORG_MIRROR=https://npm.taobao.org/mirrors/node
echo 'export NVM_NODEJS_ORG_MIRROR=https://npm.taobao.org/mirrors/node' >> ~/.bashrc

3. 安装并切换 Node.js 版本:

推荐安装最新的 LTS (长期支持) 版本。

bash
nvm install --lts
nvm use --lts
nvm alias default lts

验证安装是否成功:

bash
node -v
npm -v

4. 配置 npm 镜像源:

为了加速项目依赖的安装,建议将 npm 的镜像源也更换为国内镜像。

bash
npm config set registry https://registry.npm.taobao.org/

3.2. 安装 MongoDB

MongoDB 是一个流行的 NoSQL 数据库,常与 Node.js 应用配合使用。

1. 导入 MongoDB GPG 密钥:

bash
wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -

2. 创建 MongoDB 的源列表文件:

bash
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list

3. 更新软件包列表并安装 MongoDB:

bash
sudo apt-get update
sudo apt-get install -y mongodb-org

4. 管理 MongoDB 服务:

bash
# 启动 MongoDB 服务
sudo systemctl start mongod

# 查看服务状态
sudo systemctl status mongod

# 设置为开机自启
sudo systemctl enable mongod

# 停止服务
sudo systemctl stop mongod

# 重启服务
sudo systemctl restart mongod

您可以使用 mongo 命令进入 MongoDB 的 shell,进行数据库操作。

3.3. 安装 Nginx

Nginx 是一个高性能的 Web 服务器和反向代理服务器,将使用它来将外部请求转发到我们的 Egg.js 应用。

1. 安装 Nginx:

bash
sudo apt install nginx -y

2. 管理 Nginx 服务:

bash
# 启动 Nginx
sudo systemctl start nginx

# 查看 Nginx 状态
sudo systemctl status nginx

# 设置为开机自启
sudo systemctl enable nginx

# 重启 Nginx
sudo systemctl restart nginx

# 重新加载配置 (在不中断服务的情况下)
sudo systemctl reload nginx

安装完成后,在浏览器中访问您的服务器 IP 地址,如果看到 Nginx 的欢迎页面,则表示安装成功。

3.4. 安装 Git

将使用 Git 从代码仓库拉取我们的应用代码。

bash
sudo apt install git -y
git --version # 验证安装

4. 手动部署

手动部署是了解部署流程基础的好方法。它包括将代码部署到服务器、安装依赖并启动应用。

4.1. 获取应用代码

首先,通过 Git 将您的应用代码从代码仓库克隆到服务器。

bash
# 推荐将项目放在 /var/www 目录下
cd /var/www
git clone <你的 Git 仓库地址>
cd <你的项目目录>

4.2. 配置生产环境

在安装依赖之前,建议先配置 Egg.js 应用的生产环境参数。

1. 配置生产环境参数

创建或修改 config/config.prod.js

javascript
// config/config.prod.js
'use strict';

exports.keys = 'your-production-secret-keys-change-it';

// 安全配置
exports.security = {
  csrf: {
    enable: true, // 生产环境必须开启
  },
  xframe: {
    enable: true,
  },
};

// 日志配置
exports.logger = {
  level: 'INFO', // 生产环境使用 INFO 级别
  consoleLevel: 'ERROR', // 控制台只输出 ERROR
  dir: '/var/log/egg-app', // 日志存放目录
};

// 集群配置
exports.cluster = {
  listen: {
    port: 7001,
    hostname: '127.0.0.1', // 仅监听本地地址
  },
};

// 性能优化
exports.middleware = [
  'gzip', // 启用 gzip 压缩
];

exports.gzip = {
  threshold: 1024, // 大于 1KB 才压缩
};

// 静态资源缓存
exports.static = {
  maxAge: 31536000, // 1年
  gzip: true,
};

2. 环境变量配置

创建 .env 文件(确保已添加到 .gitignore):

bash
# .env
NODE_ENV=production
EGG_SERVER_ENV=prod

# 数据库配置
MONGODB_URL=mongodb://localhost:27017/your-database
MONGODB_USER=your-username
MONGODB_PASSWORD=your-password

# 应用密钥
APP_KEYS=your-secret-keys

# 其他配置
PORT=7001

3. 启动脚本配置

检查 package.json 中的启动脚本:

json
{
  "scripts": {
    "start": "egg-scripts start --daemon --title=my-egg-app",
    "stop": "egg-scripts stop --title=my-egg-app",
    "start:prod": "EGG_SERVER_ENV=prod npm start"
  }
}

重要提示:确保生产环境的敏感配置(如数据库密码、密钥等)不要硬编码在代码中,应通过环境变量注入。

4.3. 安装依赖

进入项目目录,安装生产环境所需的依赖。

bash
npm install --production

--production 标志会确保只安装 dependencies 中的依赖,而忽略 devDependencies,这在生产环境中是最佳实践。

4.4. 部署前检查清单

在启动应用前,请逐一检查以下项目:

检查项说明状态
✓ 环境变量所有必需的环境变量已配置
✓ 数据库连接MongoDB 连接正常
✓ 端口可用7001 端口未被占用
✓ 日志目录/var/log/egg-app 目录存在且可写
✓ 静态资源public 目录权限正确
✓ 密钥配置config.keys 已修改为生产密钥
✓ CSRF 保护已启用 CSRF 防护
✓ 日志级别生产环境日志级别设置为 INFO

验证端口是否被占用:

bash
netstat -tlnp | grep 7001
# 或
lsof -i :7001

4.5. 使用 PM2 管理应用 (推荐)

直接使用 npm start 启动应用在生产环境中是不可靠的。如果应用崩溃,它不会自动重启,并且您无法充分利用服务器的多核 CPU。因此,强烈推荐使用 PM2,一个功能强大的 Node.js 进程管理器。

1. 安装 PM2:

bash
npm install pm2 -g

2. 创建 PM2 配置文件

创建 ecosystem.config.js 文件,以便更好地管理应用配置:

javascript
// ecosystem.config.js
module.exports = {
  apps: [
    {
      name: 'my-egg-app',
      script: 'npm',
      args: 'start',
      cwd: '/var/www/my-egg-app',
      instances: 'max', // 启动最大 CPU 核心数的实例
      exec_mode: 'cluster', // 集群模式
      watch: false, // 生产环境不建议开启 watch
      max_memory_restart: '500M', // 内存达到 500M 自动重启
      env_production: {
        NODE_ENV: 'production',
        EGG_SERVER_ENV: 'prod',
      },
      // 日志配置
      error_file: '/var/log/egg-app/pm2-error.log',
      out_file: '/var/log/egg-app/pm2-out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      merge_logs: true,
      // 重启策略
      exp_backoff_restart_delay: 100, // 指数退避重启延迟
      max_restarts: 10, // 最大重启次数
      restart_delay: 1000, // 重启延迟 1 秒
      autorestart: true, // 自动重启
    },
  ],
};

配置参数详解:

参数说明推荐值
instances实例数量max (CPU 核心数)
exec_mode执行模式cluster (集群)
max_memory_restart内存超限重启500M - 1G
watch文件监听生产环境 false
autorestart自动重启true
max_restarts最大重启次数10-30 次

3. 使用配置文件启动应用:

bash
# 进入项目目录
cd /var/www/<你的项目目录>

# 使用配置文件启动
pm2 start ecosystem.config.js --env production

# 或者使用命令行方式启动
pm2 start npm --name "my-egg-app" -- run start -- -i max

4. PM2 常用命令:

bash
# 列出所有由 PM2 管理的应用
pm2 list

# 查看特定应用的详细信息
pm2 show my-egg-app

# 查看特定应用的实时日志
pm2 logs my-egg-app

# 查看最近 100 行日志
pm2 logs my-egg-app --lines 100

# 监控应用的 CPU 和内存使用情况
pm2 monit

# 停止应用
pm2 stop my-egg-app

# 重启应用 (硬重启)
pm2 restart my-egg-app

# 重载应用 (零停机重启)
pm2 reload my-egg-app

# 删除应用
pm2 delete my-egg-app

# 查看进程状态
pm2 status

5. 设置开机自启:

为了确保服务器重启后您的应用也能自动运行,您需要配置 PM2 的开机自启脚本。

bash
pm2 startup

该命令会生成一行配置命令,您需要复制并执行它。然后,保存当前的 PM2 进程列表:

bash
pm2 save

现在,您的 Egg.js 应用已经以稳定、可靠的方式在后台运行了。

在启动成功后,您可以先通过 http://<服务器IP地址>:7001 来测试应用是否正常运行。

注意:请确保您的服务器防火墙(如 ufw)或云服务商的安全组策略已开放了 7001 端口的访问权限。

bash
# 如果使用 ufw
sudo ufw allow 7001

6. PM2 监控仪表板

PM2 提供了一个在线监控平台 PM2 Plus,可以实时监控应用状态:

bash
# 连接到 PM2 Plus (需要注册账号)
pm2 link <secret_key> <public_key>

5. 自动化部署 (使用 GitHub Actions)

手动部署虽然直观,但在频繁更新的开发流程中效率低下且容易出错。自动化部署(CI/CD)可以将代码提交、测试、构建和部署等一系列流程自动化,极大地提升了开发效率和部署的可靠性。本节将介绍如何使用 GitHub Actions 实现自动化部署。

5.1. 工作原理

我们的目标是实现当代码被推送到 GitHub 仓库的 main 分支时,GitHub Actions 会自动触发一个工作流 (workflow),该工作流会:

  1. 连接到您的远程服务器。
  2. 进入项目目录,拉取最新的代码。
  3. 安装最新的依赖。
  4. 重新启动应用,使变更生效。

5.2. 配置 SSH 免密登录

为了让 GitHub Actions 能够安全地访问您的服务器,推荐使用 SSH 密钥进行认证,而不是密码。

1. 在服务器上生成 SSH 密钥对:

登录到您的服务器,执行以下命令。它会生成一个私钥 (id_rsa) 和一个公钥 (id_rsa.pub)。

bash
# -t 指定密钥类型,-b 指定密钥长度,-C 添加注释
ssh-keygen -t rsa -b 4096 -C "github_actions_deploy_key"

当提示输入文件路径和密码时,直接按回车键使用默认设置且不设置密码。

2. 将公钥添加到 GitHub 仓库的 Deploy Keys:

首先,查看并复制公钥的内容:

bash
cat ~/.ssh/id_rsa.pub

然后,在您的 GitHub 项目页面,进入 Settings > Deploy Keys,点击 Add deploy key

  • Title: 给这个密钥起一个可识别的名称,例如 My App Server
  • Key: 粘贴您刚刚复制的公钥内容。
  • Allow write access: 勾选此项,因为我们的部署脚本需要拉取代码。

3. 验证连接:

在服务器上执行以下命令,测试是否可以成功连接到 GitHub。

bash
ssh -T git@github.com

如果看到类似 Hi <your-username>/<your-repo>! You've successfully authenticated... 的消息,说明配置成功。

5.3. 配置 GitHub Secrets

为了安全地在 GitHub Actions 中使用敏感信息(如服务器 IP 地址、用户名和 SSH 私钥),需要将它们存储在 GitHub 的 Secrets 中。

在您的 GitHub 项目页面,进入 Settings > Secrets > Actions,点击 New repository secret 添加以下 secrets:

  • HOST: 您的服务器 IP 地址或域名。
  • PORT: SSH 连接端口,默认为 22
  • USERNAME: 您用于登录服务器的用户名(例如 rootubuntu)。
  • SSH_PRIVATE_KEY: 私钥的内容。复制您服务器上 ~/.ssh/id_rsa 文件的内容并粘贴到这里。
    bash
    cat ~/.ssh/id_rsa
  • APP_NAME: 您在 PM2 中为应用设置的名称(例如 my-egg-app)。
  • PROJECT_PATH: 您在服务器上的项目路径(例如 /var/www/my-egg-app)。

如果您的应用需要其他环境变量(例如数据库密码、API 密钥),也应该将它们添加为 Secrets,并在 workflow 文件中传递。

5.4. 编写 GitHub Actions Workflow

在您的项目根目录下,创建 .github/workflows 文件夹,并在其中创建一个 YAML 文件,例如 deploy.yml

yaml
# .github/workflows/deploy.yml

name: Deploy to Server

# 触发条件:当有代码 push 到 main 分支时触发
on:
  push:
    branches: [main] # 或者 master

jobs:
  deploy:
    # 运行环境
    runs-on: ubuntu-latest

    steps:
      # 步骤1: 使用 appleboy/ssh-action 连接到服务器并执行部署脚本
      - name: Deploy to Server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: ${{ secrets.PORT }}
          script: |
            # 进入项目目录
            cd ${{ secrets.PROJECT_PATH }}

            # 从 main 分支拉取最新代码
            git pull origin main

            # 使用 nvm 加载正确的 Node.js 版本
            # 注意:这里的路径可能需要根据您的 nvm 安装情况进行调整
            export NVM_DIR="$HOME/.nvm"
            [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

            # 安装生产依赖
            npm install --production

            # 使用 PM2 重新加载应用,实现零停机更新
            pm2 reload ${{ secrets.APP_NAME }}

Workflow 文件解析:

  • name: Workflow 的名称。
  • on.push.branches: 指定了触发此 workflow 的条件,即当代码被推送到 main 分支时。
  • jobs.deploy.runs-on: 指定了运行此 job 的虚拟机环境。
  • steps: 定义了 job 中要执行的步骤。
  • appleboy/ssh-action@master: 这是一个非常流行的 GitHub Action,用于通过 SSH 在远程服务器上执行命令。
  • with: 用于配置 ssh-action。在这里使用了之前设置的 Secrets。
  • script: 这是将在您的服务器上执行的 shell 脚本。
    • cd ${{ secrets.PROJECT_PATH }}: 进入项目目录。
    • git pull origin main: 拉取最新代码。
    • export NVM_DIR...: 加载 nvm 环境,以确保后续命令使用的是正确的 Node.js 版本。
    • npm install --production: 更新依赖。
    • pm2 reload ${{ secrets.APP_NAME }}: 这是关键的一步pm2 reload 会平滑地重启应用,新的请求会由新的进程处理,而旧的进程会在处理完当前请求后退出,从而实现零停机更新 (Zero-Downtime Reload)。这比 pm2 restart 更加优雅。

5.5. 触发自动部署

现在,将您的代码(包括新创建的 .github/workflows/deploy.yml 文件)提交并推送到 GitHub 仓库的 main 分支。

bash
git add .
git commit -m "feat: Add GitHub Actions for auto-deployment"
git push origin main

推送后,您可以进入 GitHub 项目的 Actions 标签页,查看您的 workflow 是否正在运行。如果一切顺利,几分钟后,您的服务器上的应用就会被更新。

6. 配置 Nginx 反向代理与 HTTPS

现在我们的应用已经通过 PM2 在后台运行,但直接通过 IP 地址和端口号访问是不专业且不安全的。需要配置 Nginx 作为反向代理,将来自公网的 HTTP/HTTPS 请求转发到本地运行的 Egg.js 应用。同时,还将配置 HTTPS 来加密数据传输。

6.1. 配置 Nginx 反向代理

1. 创建 Nginx 配置文件:

为您的应用创建一个新的 Nginx 配置文件。

bash
sudo nano /etc/nginx/sites-available/my-egg-app.conf

将以下内容粘贴到文件中,并根据您的实际情况进行修改:

nginx
server {
    listen 80;
    server_name your_domain.com; # 替换为您的域名

    location / {
        proxy_pass http://127.0.0.1:7001; # 转发到本地的 Egg.js 应用
        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_cache_bypass $http_upgrade;
    }
}

2. 启用该配置:

通过创建一个从 sites-availablesites-enabled 的符号链接来启用这个配置。

bash
sudo ln -s /etc/nginx/sites-available/my-egg-app.conf /etc/nginx/sites-enabled/

3. 测试并重启 Nginx:

在应用配置之前,务必测试 Nginx 配置的语法是否正确。

bash
sudo nginx -t

如果看到 syntax is oktest is successful 的消息,就可以安全地重启 Nginx 了。

bash
sudo systemctl restart nginx

现在,您应该可以通过您的域名(例如 http://your_domain.com)访问您的 Egg.js 应用了。

6.2. 配置 HTTPS (使用 Certbot 和 Let's Encrypt)

HTTPS 可以加密客户端和服务器之间的通信,保护数据不被窃取或篡改。强烈推荐使用 Certbot,它可以自动从 Let's Encrypt 获取免费的 SSL 证书,并自动配置 Nginx。

1. 安装 Certbot:

Certbot 团队提供了一个 PPA (Personal Package Archive) 来分发最新的 Certbot。

bash
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

2. 获取并安装 SSL 证书:

运行以下命令,Certbot 会自动检测您在 Nginx 中配置的 server_name,并为您申请证书。

bash
# --nginx: 指定使用 Nginx 插件
# -d: 指定要为其申请证书的域名
sudo certbot --nginx -d your_domain.com

在安装过程中,Certbot 会询问您几个问题:

  • 输入您的电子邮件地址:用于接收续订提醒和安全通知。
  • 同意服务条款
  • 是否愿意分享您的电子邮件地址
  • 选择是否将所有 HTTP 请求重定向到 HTTPS:强烈推荐选择 Redirect,这样可以确保所有访问者都使用安全连接。

完成后,Certbot 会自动修改您的 Nginx 配置文件 (/etc/nginx/sites-available/my-egg-app.conf),添加 SSL 证书相关的配置,并自动重启 Nginx。

3. 验证自动续订:

Let's Encrypt 颁发的证书有效期为 90 天,但 Certbot 会自动为您处理续订。您可以运行以下命令来模拟续订过程,以确保其正常工作。

bash
sudo certbot renew --dry-run

如果命令成功执行,说明自动续订已正确配置。

现在,再次访问您的域名,您会发现浏览器地址栏已经显示了安全锁标志,并且所有流量都通过 HTTPS 进行了加密。

7. Docker 容器化部署

Docker 提供了一种轻量级、可移植的应用部署方式,可以确保应用在任何环境中都能一致运行。

7.1. 创建 Dockerfile

在项目根目录创建 Dockerfile

dockerfile
# 使用 Node.js 官方镜像
FROM node:16-alpine

# 设置工作目录
WORKDIR /app

# 复制 package.json 和 package-lock.json
COPY package*.json ./

# 安装依赖
RUN npm install --production

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 7001

# 设置环境变量
ENV NODE_ENV=production \
    EGG_SERVER_ENV=prod

# 启动应用
CMD ["npm", "start"]

7.2. 创建 docker-compose.yml

使用 Docker Compose 可以更方便地管理多容器应用:

yaml
version: '3.8'

services:
  app:
    build: .
    container_name: egg-app
    restart: always
    ports:
      - "7001:7001"
    environment:
      - NODE_ENV=production
      - EGG_SERVER_ENV=prod
      - MONGODB_URL=mongodb://mongo:27017/egg-db
    depends_on:
      - mongo
    networks:
      - egg-network

  mongo:
    image: mongo:4.4
    container_name: egg-mongo
    restart: always
    ports:
      - "27017:27017"
    volumes:
      - mongo-data:/data/db
    environment:
      - MONGO_INITDB_ROOT_USERNAME=admin
      - MONGO_INITDB_ROOT_PASSWORD=password
    networks:
      - egg-network

  nginx:
    image: nginx:alpine
    container_name: egg-nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - app
    networks:
      - egg-network

volumes:
  mongo-data:

networks:
  egg-network:
    driver: bridge

7.3. 构建和运行

bash
# 构建镜像
docker build -t my-egg-app:latest .

# 使用 Docker Compose 启动
docker-compose up -d

# 查看日志
docker-compose logs -f app

# 停止服务
docker-compose down

7.4. Docker 部署优势

特性说明
环境一致性开发、测试、生产环境完全一致
快速部署几秒钟内启动应用
易于扩展轻松启动多个实例
资源隔离容器间相互隔离,互不影响
版本控制镜像可以打标签,方便回滚

8. 监控与告警

生产环境中的应用需要实时监控和告警机制,以确保服务的稳定性和可用性。

8.1. 应用性能监控 (APM)

使用 PM2 监控:

bash
# 启动监控
pm2 monit

# 查看进程详情
pm2 describe my-egg-app

集成 Egg-APM:

安装并配置 Egg-APM 插件:

bash
npm install egg-apm --save
javascript
// config/plugin.js
exports.apm = {
  enable: true,
  package: 'egg-apm',
};

// config/config.prod.js
exports.apm = {
  serverUrl: 'http://your-apm-server:8200',
  serviceName: 'my-egg-app',
};

8.2. 日志监控

ELK Stack 集成:

将 Egg.js 应用日志发送到 ELK Stack 进行集中管理:

javascript
// config/config.prod.js
exports.logger = {
  level: 'INFO',
  dir: '/var/log/egg-app',
  // 自定义日志格式
  formatter(meta) {
    return `${meta.date} ${meta.level} ${meta.pid} ${meta.message}`;
  },
};

日志分析工具对比:

工具特点适用场景
ELK Stack功能强大、可扩展性强大型应用、需要复杂查询
Graylog易于安装、界面友好中小型应用
Sentry错误追踪、性能监控错误监控、性能分析
LogDNA云端日志服务SaaS 方案

8.3. 告警配置

使用 PM2 Plus 告警:

bash
# 安装 pm2-logrotate 插件
pm2 install pm2-logrotate

# 配置日志切割
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 30

自定义告警脚本:

创建健康检查脚本 health-check.sh

bash
#!/bin/bash

# 健康检查脚本
APP_URL="http://localhost:7001/health"
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"

# 发送 HTTP 请求
response=$(curl -s -o /dev/null -w "%{http_code}" $APP_URL)

if [ $response != "200" ]; then
  # 发送告警到 Slack
  curl -X POST -H 'Content-type: application/json' \
    --data "{\"text\":\"⚠️ Egg.js 应用健康检查失败! HTTP 状态码: $response\"}" \
    $WEBHOOK_URL
  
  # 尝试重启应用
  pm2 restart my-egg-app
fi

添加到 crontab 定时任务:

bash
# 每 5 分钟检查一次
*/5 * * * * /var/www/my-egg-app/scripts/health-check.sh

8.4. 性能指标监控

关键性能指标 (KPI):

指标说明阈值建议
CPU 使用率应用进程 CPU 占用< 80%
内存使用应用内存占用< 预设限制的 80%
响应时间HTTP 请求平均响应时间< 200ms
错误率HTTP 5xx 错误比例< 1%
QPS每秒查询数根据业务峰值设定
连接数数据库连接数< 连接池上限

使用 Node.js 性能钩子:

javascript
// app.js
const perfHooks = require('perf_hooks');

// 监控事件循环延迟
const monitor = new perfHooks.Monitor({
  sampleInterval: 2000,
});

monitor.enable();

setInterval(() => {
  const lag = monitor.eventLoopLag;
  if (lag > 100) {
    console.warn(`事件循环延迟过高: ${lag}ms`);
  }
}, 10000);

9. 常见问题与最佳实践

9.1. 常见问题 (FAQ)

Q1: 为什么我的应用在服务器上启动后,通过 IP 地址无法访问?

A: 这通常是由于防火墙或安全组策略导致的。

  1. 检查服务器防火墙:如果您使用的是 ufw,请确保 Egg.js 应用的端口(默认为 7001)是开放的。
    bash
    sudo ufw status
    sudo ufw allow 7001
  2. 检查云服务商安全组:如果您使用的是云服务器(如阿里云、腾讯云、AWS),请登录到您的控制台,检查实例的安全组规则,确保入站规则允许了 7001 端口的 TCP 访问。

Q2: pm2 start npm --name "my-app" -- run startpm2 start app.js 有什么区别?

A: Egg.js 官方推荐使用 egg-scripts 来启动应用,npm start 通常就是执行 egg-scripts startegg-scripts 会处理好多核启动、日志切割等问题。直接使用 pm2 start app.js 会绕过 egg-scripts,可能会导致一些非预期的行为。因此,推荐使用 pm2 start npm -- run start 的方式。

Q3: GitHub Actions 部署失败,如何排查问题?

A:

  1. 查看 Actions 日志:在 GitHub 项目的 Actions 标签页,点击失败的 workflow,查看详细的日志输出,通常能定位到具体的错误命令和原因。
  2. 检查 Secrets:确保您在 GitHub Secrets 中配置的 HOST, USERNAME, SSH_PRIVATE_KEY 等变量是正确的。特别是 SSH_PRIVATE_KEY,要确保复制的是完整的私钥内容。
  3. 手动模拟执行:在您的本地机器上,尝试使用 ssh 命令和私钥连接到服务器,并手动执行 workflow 中的 script 部分,看是否能复现问题。

Q4: Certbot 申请证书失败,提示域名解析不正确。

A: Certbot 需要通过访问您的域名来验证您对该域名的所有权。

  1. 检查 DNS 解析:确保您的域名已经正确地解析到了您的服务器 IP 地址。您可以使用 ping your_domain.comnslookup your_domain.com 来验证。
  2. 等待 DNS 生效:如果您是刚刚修改的 DNS 解析,可能需要一些时间(几分钟到几小时不等)才能全球生效。

Q5: 应用内存占用过高,如何优化?

A: 内存问题通常由以下原因引起:

  1. 内存泄漏:使用 node --inspect 或 Chrome DevTools 进行内存分析
  2. 缓存过多:检查应用缓存策略,避免无限增长
  3. 大文件处理:使用流式处理大文件,避免一次性加载到内存
  4. 数据库连接池:检查连接池配置是否合理
bash
# 生成堆快照分析内存
kill -USR2 <pid>  # 生成 heapdump

Q6: 如何实现零停机部署?

A: 使用 PM2 的 reload 命令:

bash
# 零停机重启
pm2 reload my-egg-app

# 配合 graceful shutdown
// app.js
module.exports = app => {
  app.beforeClose(() => {
    // 清理工作,如关闭数据库连接
    return new Promise(resolve => setTimeout(resolve, 1000));
  });
};

9.2. 故障排查流程

遇到问题时,按以下流程进行排查:

code
开始
  │
  ├── 检查进程状态
  │   └─ pm2 list / pm2 logs
  │
  ├── 检查端口监听
  │   └─ netstat -tlnp | grep 7001
  │
  ├── 检查应用日志
  │   └─ /var/log/egg-app/
  │
  ├── 检查系统资源
  │   ├─ top / htop (CPU/内存)
  │   └─ df -h (磁盘空间)
  │
  ├── 检查网络连接
  │   ├─ curl http://localhost:7001
  │   └─ ping your_domain.com
  │
  └── 检查数据库连接
      └─ mongo --eval "db.stats()"

常见错误代码及解决方案:

错误代码可能原因解决方案
ECONNREFUSED服务未启动或端口被占用检查服务状态、端口占用
ETIMEDOUT网络超时检查网络、防火墙设置
ENOMEM内存不足增加服务器内存或优化应用
EMFILE打开文件过多增加 ulimit -n 限制
EADDRINUSE端口已被占用停止占用端口的进程
502 Bad Gateway后端服务未响应检查 Egg.js 应用状态
504 Gateway Timeout后端响应超时优化接口性能、增加超时时间

9.3. 最佳实践

1. 环境变量管理

  • 不要硬编码配置:切勿将数据库密码、API 密钥等敏感信息硬编码在代码中。
  • 使用 .env 文件:在开发环境中使用 .env 文件来管理环境变量,并将其添加到 .gitignore 中。
  • 生产环境使用 Secrets:在生产环境中,应该通过系统的环境变量或 CI/CD 工具的 Secrets 功能来注入这些变量。对于 PM2,您可以在启动时通过 --env 参数或生态系统文件来加载环境变量。

环境变量管理方案对比:

方案优点缺点适用场景
.env 文件简单易用需手动管理开发环境
PM2 ecosystem集中管理仅限 PM2PM2 部署
Kubernetes Secrets安全、原生配置复杂K8s 环境
Vault安全性高架构复杂大型企业

2. 日志管理

  • 使用 PM2 的日志功能:PM2 提供了强大的日志管理功能,包括日志切割、按日期归档等。
  • 结构化日志:在您的 Egg.js 应用中,考虑输出结构化(例如 JSON 格式)的日志,这会使日志的查询和分析变得更加容易。
  • 集中式日志:对于大型应用,可以考虑将日志发送到集中的日志管理平台(如 ELK Stack, Graylog, Sentry 等)。

日志配置示例:

javascript
// config/config.prod.js
exports.logger = {
  level: 'INFO',
  dir: '/var/log/egg-app',
  bufferLogs: true, // 启用日志缓冲
  outputJSON: true, // JSON 格式输出
  consoleLevel: 'NONE', // 生产环境不输出到控制台
};

// 自定义日志分类
exports.customLogger = {
  accessLogger: {
    file: '/var/log/egg-app/access.log',
    formatter: meta => `${meta.timestamp} ${meta.method} ${meta.url} ${meta.status}`,
  },
};

3. 安全加固

  • 定期更新:定期更新您的服务器操作系统、Node.js、Nginx 以及所有 npm 依赖,以修复已知的安全漏洞。
  • 最小权限原则:为您的应用和数据库使用专用的、权限受限的用户,而不是 root 用户。
  • 使用 Helmet:Egg.js 默认集成并开启了 egg-security 插件,它底层使用了 Helmet 来帮助设置一些重要的 HTTP 头,以防止常见的 Web 攻击(如 XSS, CSRF 等)。确保您没有禁用它。

安全配置清单:

javascript
// config/config.prod.js
exports.security = {
  csrf: {
    enable: true,
    ignoreJSON: false,
  },
  xframe: {
    enable: true,
    value: 'SAMEORIGIN',
  },
  hsts: {
    enable: true,
    maxAge: 31536000,
  },
  xssProtection: {
    enable: true,
  },
  csp: {
    enable: true,
    policy: {
      'default-src': ["'self'"],
      'script-src': ["'self'"],
      'style-src': ["'self'"],
    },
  },
};

// 限流配置
exports.ratelimit = {
  enable: true,
  max: 100, // 每分钟最大请求数
  duration: 60000, // 时间窗口(毫秒)
};

安全检查命令:

bash
# 检查 npm 依赖漏洞
npm audit

# 自动修复
npm audit fix

# 检查服务器开放端口
netstat -tlnp

# 检查防火墙状态
sudo ufw status verbose

4. 性能优化

  • 开启集群模式:通过 pm2 start ... -i max 来开启集群模式,充分利用服务器的多核 CPU 资源。
  • 使用 PM2 监控:使用 pm2 monit 来实时监控应用的 CPU 和内存使用情况。
  • 集成应用性能监控 (APM):考虑集成如 Egg-APM 或商业化的 APM 工具(如 New Relic, Datadog)来深入了解应用的性能瓶颈。

性能优化建议:

优化项方法预期提升
数据库查询添加索引、优化查询语句50-80%
缓存策略Redis 缓存热点数据30-50%
压缩传输启用 gzip/brotli60-70%
静态资源 CDN使用 CDN 分发40-60%
连接池合理配置数据库连接池20-40%
异步处理非核心操作异步化30-50%

连接池配置:

javascript
// config/config.prod.js
exports.mongoose = {
  url: process.env.MONGODB_URL,
  options: {
    poolSize: 10, // 连接池大小
    bufferMaxEntries: 0,
    useNewUrlParser: true,
    useUnifiedTopology: true,
    serverSelectionTimeoutMS: 5000,
    socketTimeoutMS: 45000,
  },
};

// Redis 连接池
exports.redis = {
  client: {
    port: 6379,
    host: '127.0.0.1',
    password: process.env.REDIS_PASSWORD,
    db: 0,
    pool: {
      max: 10, // 最大连接数
      min: 2, // 最小连接数
    },
  },
};

5. 数据库备份与恢复

  • 定期备份:为您的 MongoDB 数据库设置定期的自动备份策略。您可以使用 mongodump 结合 cron 来实现。
  • 异地备份:将备份文件存储在与应用服务器不同的物理位置,以防服务器发生灾难性故障。

备份脚本示例:

bash
#!/bin/bash
# backup-mongodb.sh

DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/var/backups/mongodb"
DB_NAME="egg-db"

# 创建备份目录
mkdir -p $BACKUP_DIR

# 执行备份
mongodump --db $DB_NAME --out $BACKUP_DIR/$DATE

# 压缩备份文件
tar -czf $BACKUP_DIR/$DATE.tar.gz -C $BACKUP_DIR/$DATE .

# 删除临时目录
rm -rf $BACKUP_DIR/$DATE

# 删除 7 天前的备份
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete

# 上传到云存储 (示例使用 AWS S3)
aws s3 cp $BACKUP_DIR/$DATE.tar.gz s3://your-bucket/mongodb-backups/

配置定时任务:

bash
# 编辑 crontab
crontab -e

# 每天凌晨 2 点执行备份
0 2 * * * /var/www/scripts/backup-mongodb.sh >> /var/log/mongodb-backup.log 2>&1

6. 高可用架构

对于生产环境,建议采用高可用架构:

code
                    ┌─────────────┐
                    │   负载均衡  │
                    │ (Nginx/ALB) │
                    └──────┬──────┘
                           │
              ┌────────────┼────────────┐
              │            │            │
        ┌─────▼─────┐ ┌───▼────┐ ┌────▼─────┐
        │  Server 1 │ │Server 2│ │ Server 3 │
        │  (Egg.js) │ │(Egg.js)│ │ (Egg.js) │
        └─────┬─────┘ └───┬────┘ └────┬─────┘
              │            │            │
              └────────────┼────────────┘
                           │
                    ┌──────▼──────┐
                    │   MongoDB   │
                    │  (主从复制)  │
                    └─────────────┘

高可用配置要点:

  • 负载均衡:使用 Nginx 或云服务商的负载均衡器
  • 多实例部署:至少部署 2 个以上应用实例
  • 数据库主从:MongoDB 配置副本集
  • 健康检查:定期检查服务可用性
  • 自动故障转移:配置故障自动切换机制

10. 部署流程总结

10.1. 完整部署流程

code
准备阶段
  │
  ├── 1. 准备服务器和域名
  ├── 2. 安装基础环境 (Node.js, MongoDB, Nginx, Git)
  └── 3. 配置防火墙和安全组
  │
部署阶段
  │
  ├── 4. 克隆代码到服务器
  ├── 5. 配置生产环境参数
  ├── 6. 安装依赖
  ├── 7. 使用 PM2 启动应用
  └── 8. 配置 Nginx 反向代理
  │
优化阶段
  │
  ├── 9. 配置 HTTPS 证书
  ├── 10. 配置自动化部署
  ├── 11. 配置监控和告警
  └── 12. 配置定期备份
  │
维护阶段
  │
  ├── 定期更新依赖
  ├── 监控应用性能
  ├── 检查安全漏洞
  └── 优化和扩容

10.2. 快速检查清单

部署前务必完成以下检查:

代码层面:

  • 生产环境配置已完成 (config/config.prod.js)
  • 敏感信息已提取到环境变量
  • 数据库连接配置正确
  • CSRF 和安全配置已启用
  • 日志级别设置为 INFO

服务器层面:

  • Node.js 版本符合要求 (推荐 LTS)
  • MongoDB 服务正常运行
  • Nginx 已安装并运行
  • 防火墙端口已开放 (80, 443, 7001)
  • PM2 已全局安装

安全层面:

  • SSH 密钥认证已配置
  • 不使用 root 用户运行应用
  • 数据库密码已修改
  • SSL 证书已配置
  • 依赖安全漏洞已修复

监控层面:

  • PM2 开机自启已配置
  • 日志切割已配置
  • 监控告警已配置
  • 数据库备份已配置

11. 术语表

术语英文说明
反向代理Reverse Proxy代理服务器接收客户端请求,转发给内部服务器
SSL 终止SSL Termination在负载均衡器或反向代理处解密 HTTPS 请求
零停机部署Zero-Downtime Deployment部署过程中服务不中断
CI/CDContinuous Integration/Deployment持续集成和持续部署
PM2Process Manager 2Node.js 进程管理工具
APMApplication Performance Monitoring应用性能监控
副本集Replica SetMongoDB 的主从复制架构
连接池Connection Pool预先建立的数据库连接集合
负载均衡Load Balancing将请求分发到多个服务器
健康检查Health Check定期检查服务可用性
灰度发布Canary Deployment逐步向部分用户发布新版本
回滚Rollback将应用恢复到之前的版本

12. 参考资源

12.1. 官方文档

12.2. 推荐工具

工具类型工具名称用途
进程管理PM2Node.js 进程管理
容器化Docker应用容器化部署
监控PM2 Plus应用性能监控
日志管理ELK Stack集中式日志管理
错误追踪Sentry应用错误追踪
负载测试Apache BenchHTTP 负载测试
压力测试Artillery现代化压力测试工具

12.3. 相关插件

bash
# Egg.js 生产环境推荐插件
npm install egg-scripts --save          # 启动脚本
npm install egg-logger --save           # 日志管理
npm install egg-mongoose --save         # MongoDB 连接
npm install egg-redis --save            # Redis 连接
npm install egg-cors --save             # CORS 支持
npm install egg-validate --save         # 参数验证
npm install egg-jwt --save              # JWT 认证
npm install egg-ratelimit --save        # 限流
npm install egg-helmet --save           # 安全头设置
npm install egg-compress --save         # 响应压缩

文档版本: v1.0
最后更新: 2026-02-14
维护者: Egg.js 技术团队