18、生产环境部署与故障排除指南

生产环境部署与故障排除全解析

生产环境部署与故障排除指南

1. Shipit 工具介绍

在将代码上传到服务器时,无论是云端服务器还是本地服务器,操作可能简单如使用 scp 命令(一种使用 SSH 认证的安全复制命令),也可能复杂到需要遵循 20 个步骤的操作手册。对于基于微服务的平台,期望简单上传是不现实的。不过,有很多工具可以帮助我们自动化复杂的部署过程,Shipit 就是其中之一。

Shipit 完全基于 Node,使用它无需在系统中为其他语言安装额外支持,而使用其他常见工具(如 Capistrano,它需要 Ruby 才能运行)则可能需要。Shipit 实际上是两个工具的组合: shipit-cli shipit-deploy 。前者是核心功能集,可用于创建自定义自动化任务;后者基于前者构建,提供特定的部署任务,只需配置一些属性即可完成部署。

1.1 示例部署任务

以下是一个使用 shipit-deploy 的部署任务示例:

module.exports = function (shipit) {
  require('shipit-deploy')(shipit);
  shipit.initConfig({
    default: {
      workspace: '/tmp/your-project-name',
      deployTo: '/path/to/deployment',
      repositoryUrl: 'https://github.com/user/repo.git',
      ignores: ['.git', 'node_modules'],
      keepReleases: 3,
      deleteOnRollback: false,
      key: '/path/to/key',
      shallowClone: true
    },
    staging: {
      servers: 'user@staging-server.com'
    },
    production: {
      servers: 'user@production-server.com'
    }
  });
};

将上述代码保存为项目根目录下的 shipitfile.js 文件,然后使用以下命令将代码部署到暂存服务器:

$ npx shipit staging deploy

这里的 npx 是一个 CLI 工具,用于运行不同模块的二进制文件,它会检查这些文件是本地安装还是全局安装。

1.2 部署任务属性配置

以下是部署任务中配置属性的详细说明:
| 属性 | 描述 |
| — | — |
| default.workspace | Shipit 下载代码并进行处理的临时目录,处理完成后再部署到最终目录。 |
| default.deployTo | 目标服务器上的部署目录。 |
| default.repositoryUrl | 代码的来源仓库地址。 |
| default.ignores | 部署过程中需要忽略的文件或目录。 |
| default.keepReleases | 需要保留的历史版本数量,以便在需要回滚时使用。 |
| default.deleteOnRollback | 部署出错需要回滚时,是否删除不再需要的文件。 |
| default.key | SSH 公钥的路径,如果系统已正确配置默认密钥,则此属性可选。 |
| default.shallowClone | 与 git clone 的深度有关,通常建议设置为 true 。 |
| staging.servers | 要部署到的暂存服务器列表。 |
| production.servers | 要部署到的生产服务器列表。 |

1.3 Shipit 创建的文件夹结构

Shipit 会在目标服务器上创建特定的文件夹结构,以管理最新部署的版本和历史版本。以下是文件夹结构示例:

/your/app/folder
|- current -> releases/20180429140211
|- releases
    |-- 20180429140211
    |-- 20180427130000
    |-- 20171231200203

根目录是 deployTo 属性指定的目录,Shipit 不会直接将代码复制到该目录,而是在 releases 目录下创建一个以当前时间戳命名的新文件夹。 current 目录是一个符号链接,指向 releases 目录下的最新版本。因此,回滚版本只需重新指向这个符号链接即可。

1.4 安装 Shipit

使用以下命令安装 shipit-cli shipit-deploy

$ npm install -g shipit-cli
$ npm install -g shipit-deploy

1.5 与持续集成的结合

可以设置持续集成(CI)服务器来自动处理部署任务,在这些自动化任务中,可能会配置执行 Shipit 任务。常见的 CI 服务器有 TravisCI、Jenkins 和 Bamboo。

以下是 CI 与代码仓库、生产服务器交互的流程图:

graph LR
    A[代码仓库] -->|代码提交| B(CI 服务器)
    B -->|构建、测试| C{测试是否通过}
    C -->|是| D[Shipit 部署]
    C -->|否| E[等待修复]
    D --> F[生产服务器]

配置代码仓库的提交后钩子,当有新代码提交时通知 CI 环境。CI 任务触发后,项目会进行构建,通常会执行一些测试任务,如单元测试,以确保要部署的代码能够正常工作。CI 平台通常有插件来检测测试是否失败,以决定是否继续部署。最后,将代码复制到正确的位置,这里可以在 CI 服务器上预安装 Shipit 并使用任务运行 CLI 命令,也可以在目标服务器上安装 Shipit,从 CI 脚本远程运行命令。

2. PM2 进程管理器

当代码成功部署并开始运行后,需要对进程进行监控和维护,PM2 就是这样一个进程管理器,它可以保持进程运行并同时进行监控。

2.1 为什么需要进程管理器

理论上,Node.js 可以在没有进程管理器的情况下运行,但在生产环境中,失败是不可接受的。如果进程因错误、资源不足或其他原因崩溃,需要工具来理解问题发生的原因并进行恢复。

例如,使用以下命令启动生产进程:

$ sudo nohup node index.js &

这个命令可以让进程在后台运行,并将标准输出记录到 nohup.out 文件中,即使关闭 SSH 连接,进程也不会停止。但这种方式无法监控进程的内存使用情况,也无法在进程崩溃时自动重启。

2.2 安装和使用 PM2

使用以下命令全局安装 PM2:

$ npm install pm2 -g

安装完成后,可以使用 PM2 启动应用程序并进行监控。以下是一个示例程序:

const http = require('http');
function serve(ip, port)
{
    http.createServer( (req, res)  => {
        console.log("[LOG] Request received");
        res.writeHead(200, {'Content-Type': 'text/plain'}); 
        res.write(JSON.stringify(req.headers));             
        res.end("\nServer Address: "+ip+":"+port+"\n");     
    }).listen(port, ip);
    console.log('Server running at http://'+ip+':'+port+'/');
}
serve('0.0.0.0', 9000);

使用以下命令启动应用程序:

$ pm2 start index.js --name "my app" -i max

--name 参数为进程组提供一个可读的标识符, -i max 表示使用系统的最大核心数来集群应用程序。

2.3 PM2 常用命令

  • 查看日志 :使用 pm2 logs 命令查看每个进程的最后 15 行日志,也可以指定进程 ID 查看特定进程的日志。
  • 重启进程 :使用 pm2 restart "my app" 命令重启所有 API 进程。
  • 停止进程 :使用 pm2 stop "my app" 命令停止所有进程。
  • 监控进程 :使用 pm2 monit 命令打开一个控制台 UI,提供系统的全局概述,并可以使用箭头键浏览数据。

2.4 自定义进程指标

可以在代码中注册一个函数,为 PM2 监控 UI 提供自定义指标。以下是一个添加请求计数器的示例:

const http = require('http');
const Probe = require("pmx").probe();
let REQUEST_COUNTER = 0;
Probe.metric({
    name: 'request counter',
    value: () => REQUEST_COUNTER
});
function serve(ip, port)
{
    http.createServer( (req, res) => {
        console.log("[LOG] Request received");
        REQUEST_COUNTER++;
        res.writeHead(200, {'Content-Type': 'text/plain'}); 
        res.write(JSON.stringify(req.headers));             
        res.end("\nServer Address: "+ip+":"+port+"\n");     
    }).listen(port, ip);
    console.log('Server running at http://'+ip+':'+port+'/');
}
serve('0.0.0.0', 9000);

可以使用 pm2 show [ID] 命令获取进程的快照,或打开监控 UI 实时查看指标值。

3. 故障排除

在开发 RESTful API 过程中,可能会遇到一些问题,以下是一些常见问题及解决方法。

3.1 异步编程

对于非 JavaScript 或非 Node.js 开发者来说,异步编程的概念可能比较难理解。但在 Node.js 中,处理外部资源时,异步编程是必须掌握的。

在 API 代码中,很多地方都使用了异步编程,例如控制器中的数据库查询。以下是一个错误的数据库查询示例:

var authors = lib.db.model('Author')
   .find(criteria).exec()
if(!authors) return next(controller.RESTError('InternalServerError', authors))
controller.writeHAL(res, authors)

正确的做法是使用回调函数:

lib.db.model('Author')
    .find(criteria)
    .exec((err, authors) => {
        if(err) return next(controller.RESTError('InternalServerError', err))
        controller.writeHAL(res, authors)
    })

这是因为 Node.js 的 I/O 操作是异步的,查询数据库需要使用回调函数来处理响应。手动测试应用程序时,捕获这类错误可能比较困难,因为结果行为可能不一致。当代码足够复杂时,会出现异步函数获取响应的时间与代码使用该值的时间之间的竞争。

另外,由于 Node.js 解释器在方法或函数调用缺少参数时不会抛出错误,可能会导致一些难以理解的问题。例如:

const fs = require("fs");
function libraryMethod(attr1, callback) {
    fs.readFile(attr1, function(err, response){
        if(callback) callback(response)
    })
}
var returnValue = libraryMethod('index.js')

上述代码不会抛出错误,但 returnValue 始终为 undefined 。如果无法访问 libraryMethod 函数的代码,可能很难理解问题所在。

3.2 其他潜在问题

  • Swagger UI 配置细节 :有时文档可能不够详细,需要进一步探索配置细节。
  • CORS 问题 :了解 CORS 的基本原理,以便在开发中正确使用它。
  • 数据类型转换 :了解如何将 JSON Schemas 数据类型转换为 Mongoose 类型。

3.3 异步编程错误示例分析

为了更清晰地理解异步编程可能带来的问题,我们再看一个示例。以下代码尝试读取文件并处理内容:

const fs = require("fs");
let fileContent;
fs.readFile('./yourfile.txt', (err, response) => {
    fileContent = response;
});
console.log(fileContent);

在这个例子中, fs.readFile 是一个异步操作,当 console.log(fileContent) 执行时, fs.readFile 可能还没有完成,因此 fileContent 会是 undefined 。正确的做法是在回调函数中处理文件内容:

const fs = require("fs");
fs.readFile('./yourfile.txt', (err, response) => {
    if (err) {
        console.error('读取文件出错:', err);
        return;
    }
    const fileContent = response;
    console.log(fileContent);
});

3.4 Swagger UI 配置问题解决思路

Swagger UI 是一个强大的工具,用于展示 API 文档,但有时配置可能会遇到问题。以下是一些常见问题及解决思路:
| 问题 | 解决思路 |
| — | — |
| 文档显示不完整 | 检查 Swagger 规范文件是否正确,确保所有 API 定义都符合规范。 |
| 样式显示异常 | 检查 CSS 文件是否正确加载,可能需要调整样式配置。 |
| 无法访问 API | 检查 API 服务器的地址和端口是否正确配置,确保服务器正常运行。 |

3.5 CORS 问题处理

跨域资源共享(CORS)是一个常见的问题,特别是在前后端分离的项目中。CORS 允许浏览器在跨域请求时,服务器可以控制哪些请求是允许的。

3.5.1 CORS 基本原理

浏览器在进行跨域请求时,会先发送一个预检请求(OPTIONS 请求),询问服务器是否允许该请求。服务器需要返回相应的响应头,告诉浏览器是否允许跨域请求。

3.5.2 解决 CORS 问题的方法

在 Node.js 中,可以使用 cors 中间件来解决 CORS 问题。以下是一个示例:

const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors());

// 定义 API 路由
app.get('/api/data', (req, res) => {
    res.json({ message: '这是跨域请求的数据' });
});

const port = 3000;
app.listen(port, () => {
    console.log(`服务器运行在端口 ${port}`);
});

在上述代码中, app.use(cors()) 启用了 CORS 支持,允许所有跨域请求。如果需要更精细的控制,可以配置 cors 中间件的选项。

3.6 数据类型转换

在开发中,经常需要将 JSON Schemas 数据类型转换为 Mongoose 类型。以下是一些常见的数据类型转换示例:
| JSON Schemas 类型 | Mongoose 类型 |
| — | — |
| string | String |
| number | Number |
| boolean | Boolean |
| array | Array |
| object | Object |

例如,以下是一个 JSON Schema 定义:

{
    "type": "object",
    "properties": {
        "name": {
            "type": "string"
        },
        "age": {
            "type": "number"
        },
        "isAdmin": {
            "type": "boolean"
        }
    }
}

对应的 Mongoose 模式定义如下:

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    name: String,
    age: Number,
    isAdmin: Boolean
});

const User = mongoose.model('User', userSchema);

module.exports = User;

总结

本文介绍了 Shipit 工具用于代码部署,包括部署任务的配置、文件夹结构和与持续集成的结合;PM2 进程管理器用于进程的监控和维护,包括安装、常用命令和自定义指标;以及在开发 RESTful API 过程中可能遇到的异步编程、Swagger UI 配置、CORS 问题和数据类型转换等故障排除方法。通过掌握这些知识和工具,可以更好地进行生产环境部署和问题解决。

以下是整个开发和部署流程的流程图:

graph LR
    A[代码开发] --> B[代码提交到仓库]
    B --> C(CI 服务器)
    C -->|构建、测试| D{测试是否通过}
    D -->|是| E[Shipit 部署]
    D -->|否| F[代码修复]
    F --> B
    E --> G[生产服务器]
    G --> H[PM2 进程管理]
    I[故障排查] --> J{问题类型}
    J -->|异步编程| K[检查回调函数]
    J -->|Swagger UI| L[检查配置文件]
    J -->|CORS 问题| M[配置 CORS 中间件]
    J -->|数据类型转换| N[检查类型映射]
    K --> I
    L --> I
    M --> I
    N --> I

在实际开发中,要充分理解和运用这些技术,确保项目的稳定运行和高效开发。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值