生产环境部署与故障排除指南
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
在实际开发中,要充分理解和运用这些技术,确保项目的稳定运行和高效开发。
生产环境部署与故障排除全解析
超级会员免费看
2024

被折叠的 条评论
为什么被折叠?



