一、前期准备
自从上手了docker之后,发现docker会使得程序的启动、运行、维护、迁移都变得十分容易,因此希望把很多常用的东西都容器化。Yapi是目前一直在使用的API管理工具,虽然发现了不少不方便使用的地方,但能满足基本需求,尤其胜在免费开源……有关Yapi的介绍参考之前的文章《API集成管理平台YAPI的搭建和使用》。
首先去Yapi github看官方推荐的第三方docker镜像(目前没有出官方的docker镜像)是怎么做的,总结如下:
(1)使用了node:11-alpine作为基础镜像
(2)Dockerfile使用了多阶段构建的方式
(3)使用了两个基础镜像的源:
https://mirrors.aliyun.com/alpine/v3.6/main/
https://mirrors.aliyun.com/alpine/v3.6/community/
整体涵盖了网络创建、mongodb初始化等操作,用一个start.sh作为入口,通过和用户命令行交互来执行对应的脚本。
【优点】
脚本比较完善,用户操作少
【缺点】
- 如果自动重启的话,这些交互都必须要等待用户输入;
- 没有使用docker-compose这类编排工具
(1)使用了基础镜像node:10.18.1-jessie
(2)支持任意版本
【优点】
支持任意版本安装
【缺点】
docker-compose版本为2,且将mongo强行绑定在同一个docker-compose文件进行编排
(1)Dockerfile使用了多阶段构建的方式
(2)编译阶段基础镜像是自己做的,运行阶段还是用node:alpine
(3)几乎完整支持了所有yapi配置,且增加了对密码修改的支持,这个解决了我当时对Yapi很大的抱怨:初始化密码强行设置为ymfe.org,不支持自定义
(4)yapi版本较新,支持了很多新配置,比如mongodb集群
【优点】
功能全面,版本最新。
【缺点】
使用node.js去写的脚本,对于我这种前端小白,看懂容易,修改起来各种坑。
二、研究需求
第1、2个,基本就是用docker做了个套子。第3个非常棒,对比我的需求:
(1)支持插件配置,第3个也声明支持,但是未测试过
(2)支持密码初始化,第3个支持。
(3)mongo独立开,第3个也将mongo写在了同一个docker-compose里面
(4)支持数据备份和还原,第3个不支持。
(5)支持LDAP,第3个支持。
(6)体积小,第3个支持。
(7)支持升级,第3个支持。
基本上第3个支持了所有的需求,最主要想自己做的原因是,我想能改代码,nodejs对我来说太痛苦了。于是基本以第3个为模板,写纯shell脚本。
三、docker-yapi路上的坑
基本上用到了这些知识:
- hub.docker.com找基础镜像
alpine镜像采用了busybox为核心组件,没有bash,用apk进行更新,类似apt
npm源、install相关命令
mongodb基本语法和常识
node.js基本语法、mongoose操作mongodb
Dockerfile多阶段构建、换行、多行文本
docker-compose v3基本语法
shell脚本语法
hub.docker.com自动关联github自动编译、制作和发布镜像
1、mongodb更换版本后起不来
我在创建mongodb更换版本之后起不来,后来想起我把volume映射到宿主机了,把宿主机文件删掉即可,要仔细点啊。
volumes:
- /opt/mongo:/data/db
2、dockerfile多阶段构建,如何把编译阶段文件拷贝过来
COPY --from=build-stage /opt/yapi /opt/yapi
3、docker-compose采用volume映射之后,无法找到启动文件
最开始我映射的是:
volumes:
- "/opt/yapi:/opt/yapi"
检查了语法、容器内是否有这个文件等等,都是正常的,但映射成volume就是空文件夹。这里不该映射含有初始化需要的文件所在文件夹,而是那些运行时会产生的新数据所在文件夹,例如data、log等。
volumes:
- "/opt/yapi/vendors/log:/opt/yapi/vendors/log"
其实就是我理解成,容器创建之后,会自动把镜像中的文件映射到宿主机上;事实上应该是宿主机上存在文件夹,被挂载到容器里面去,然后容器产生的数据会在该文件夹生成,看起来就像是文件互通一样。
4、shell函数传递识别空格引号等内容异常
要保证:调用者和接收者的参数都用引号引起来
// 调用者引号引起来
replaceFileContent "../config.json" "$1" "$2" "$3"
// 接收者引号接收
file="$1"
str="$2"
default_val="$3"
5、sed识别一些特殊字符异常
特殊字符需要转义。
// 引号的转义
TRANS_YAPI_PLUGINS=${YAPI_PLUGINS//\"/\\\"}
// 小数点的转义
TRANS_YAPI_PLUGINS=${TRANS_YAPI_PLUGINS//\./\\\.}
// 斜杠的转义
TRANS_YAPI_PLUGINS=${TRANS_YAPI_PLUGINS//\//\\\/}
// 左右中括号的转义,示例右中括号
TRANS_YAPI_PLUGINS=${TRANS_YAPI_PLUGINS//\]/\\\]}
// 替换的时候用引号引起来
sed -i -e 's/'"${str}"'/'"${val}"'/' ${file}
6、shell设置默认值
用冒号和横杠,且在${}符号内。
val=${new_val:-${default_val}}
7、在指定行插入多行文本
用斜杠划分多行文本,利于阅读。
sed -i "26i\\\
userInst.findByEmail(yapi.WEBCONFIG.adminAccount).exec((err,dupAdmin)=>{\n\
if (dupAdmin) {\n\
console.log(\`find duplicated adminAccount, username=\${dupAdmin.username}, will be delete\`);\n\
userInst.del(dupAdmin._id).exec();\n\
}\n\
});\n" \
"./server/install.js"
8、nodejs的mongoose查询返回一个Query属性
本来是看了一下yapi其他源码的写法,包括server/controllers/user.js、server/models/user.js、utils/db.js、install.js,看到其他的这么用的:
user = await userInst.save(data);
我也模仿着这么用,提示await不能用于同步方法,去掉了await,结果返回了一个Query对象。
查了好久,才查到:
findByEmail(email) {
return this.model.findOne({ email: email });
}
这种会返回一个Query对象,而要获取结果,还需要这样执行:
query.exec((err,res)=>{});
于是尝试用echo打印了res,才终于发现成功了。
这个如果没有nodejs的常识,就要吃这种亏……
四、一些经验
简单介绍一下yapi的结构,主要逻辑都在server文件夹里:
- controller就是控制层,包含了业务逻辑的方法,比如login
- middleware是一个mockServer.js组件
- models是数据库层,mongodb的数据结构,包含了一些基本增删改查的方法
- utils是基本组件层,比如数据库连接、ldap等等基本功能
- app.js就是运行入口
- install.js就是安装入口
连接数据库是这样的:
const dbModule = require('./utils/db.js');
yapi.connect = dbModule.connect();
yapi.connect
.then(function() {……});
操作model是这样的,以新建user为例:
const userModel = require('./models/user.js');
let result = userInst.save({
username: yapi.WEBCONFIG.adminAccount.substr(0, yapi.WEBCONFIG.adminAccount.indexOf('@')),
email: yapi.WEBCONFIG.adminAccount,
password: yapi.commons.generatePassword('ymfe.org', passsalt),
passsalt: passsalt,
role: 'admin',
add_time: yapi.commons.time(),
up_time: yapi.commons.time()
});
result.then(
function() {
fs.ensureFileSync(yapi.path.join(yapi.WEBROOT_RUNTIME, 'init.lock'));
console.log(
`初始化管理员账号成功,账号名:"${yapi.WEBCONFIG.adminAccount}",密码:"ymfe.org"`
); // eslint-disable-line
process.exit(0);
},
function(err) {
throw new Error(`初始化管理员账号 "${yapi.WEBCONFIG.adminAccount}" 失败, ${err.message}`); // eslint-disable-line
}
);
config参数可以这样引入,其实就是yapi.js读取了config文件。
const yapi = require('./yapi.js');
yapi.WEBCONFIG.xxxxx
在运行之后就会产生init.lock文件,避免再次运行时重复安装。
五、最终成果
github:https://github.com/BEWINDOWEB/docker-yapi
dockerhub:bewindoweb/yapi:1.0.0
- ✔ yapi 1.8.5
- ✔ 支持mongodb集群
- ✔ 支持初始化密码
- ✔ 支持使用docker的方式配置yapi大部分参数
- ✖ 不支持yapi更新
- ✖ 不支持yapi插件
- ✖ 不支持yapi数据备份
yapi插件尝试了一下会报错,在下个版本解决吧。
以前的yapi数据备份要求用mongodump,而这里只有mongoose,学习估计要花费一些时间,下个版本加入。