我发现一旦手头的项目变多,且随着项目复杂度的提高,原本编码就已是个够头痛的问题,再加上部署到生产环境就更心累了 😵。javascript
以前在公司实习时,有一个依据用户输入网址进行截屏的项目,同时包含了 React 应用和 Node 应用。css
部署 React 应用比较方便,只要经过 scp 将 build 后的 dist 目录放置在服务器上。前端
而 Node 应用则较为复杂:java
在项目初期,版本迭代很是快,我天天都要反复执行以上步骤数次,waste time!node
况且,在标准的开发流程中,咱们还需引入 单元测试、覆盖率报告、代码风格检测 ……,并将应用部署到 不一样环境的服务器(开发、测试、生产)中,这无疑是一项繁琐的工做,本着 不想当运维的前端不是一个好全栈 的核心思想,我迫切须要解放个人双手。git
TIP:结尾有源码连接github
所谓前人栽树,后人乘凉,个人诉求早就在开发领域中被定义为两个专有名词:npm
听起来很高大上,我尝试经过一张图来解释:json
一个完整项目的迭代须要经历:编码 ➡️ 打包构建 ➡️ 测试 ➡️ 新代码和原有代码正确地集成在一块儿。安全
这一过程称为集成,而 持续集成强调了开发人员提交了新代码(git push)以后,马上进行以上步骤,无需人为干预。
同理,持续部署在持续集成的基础上,加了一个步骤: 将应用自动部署到指定环境(服务器)。
试想,当你提交代码后,CI/CD 服务会按照你的预设命令自动化以上步骤,那是多美妙的一件事!
为了提升软件开发的效率,咱们有必要使用 CI/CD,而市面上熟知的 CI/CD 服务有:Jenkins、gitlab,不过它们的使用成本很高。
我要推荐的是 Travis CI,它绑定 Github 上面的项目,只要有新的代码,就会自动抓取。而后,提供一个运行环境,执行测试,完成构建,还能部署到服务器。
为了确保你能顺利进行实践部分,请作好如下准备工做:
我会从零开始搭建一个用于 API 服务的 NodeJS 应用,并引入单元测试和 ESLint,最终实现 CI/CD。
整个思路以下:
为了简便,我将本机称为 local,远程服务器称为 remote
万事开头难,首先意识到如下两点:
因为 Travis CI 和 PM2 Deployment 在运行时不提供交互式界面,它只会按照预设的脚本命令去依次执行,当须要你输入密码时就会卡住,因此咱们须要 SSH 无密登陆,达到如下所示关系:
local => GitHub;
remote => GitHub;
local => remote;
复制代码
首先生成 ssh key 私钥公钥对,一路回车,无需 passphrase.
$ ssh-keygen -t rsa -b 4096 -C "<your-github-email>"
复制代码
在 local 的 ~/.ssh 目录下,会生成如下文件:
├── id_rsa
├── id_rsa.pub
复制代码
id_rsa
是私钥文件,表明 🔑;id_rsa.pub
是公钥文件,表明 🔒.
只有私钥才能打开公钥。
打开 github.com/settings/ke…,点击 New SSH key,复制 id_rsa.pub
中的内容。
以后选择你的任一仓库,点击 clone or download && Clone with SSH,若是能成功 clone,说明实现了 local 和 GitHub 的 SSH 链接。
ssh-copy-id 命令会默认将以前生成的公钥:id_rsa.pub
复制到 remote 中。
⚠️ 换成你本身的 remote IP.
$ ssh-copy-id root@47.106.87.3
复制代码
❗️ 若是 win 系统没法识别该命令,请使用 git bash.
查看 remote 的 ~/.ssh 目录,id_rsa.pub
中内容与 authorized_keys 一致。
├── authorized_keys
复制代码
尝试链接 remote.
$ ssh root@47.106.87.3
复制代码
若是无需输入密码,则说明实现了 local 和 remote 的 SSH 链接。
思路和 local 链接 GitHub 一致,因为咱们已经在 GitHub 上存放了公钥,咱们只需将私钥:id_rsa
上传到 remote 便可。
上传完毕后,remote 的 ~/.ssh 目录存在如下文件:
├── authorized_keys
├── id_rsa
复制代码
同理,你能够尝试在 remote 上使用 Clone with SSH 下载 GitHub 仓库来验证是否链接成功。
至此,咱们实现了三者的 SSH 互通。
先在 GitHub 上新建一个仓库,随后 Clone 到本地。
因为该应用基于 koa 框架来实现 API 服务,因此进行一些初始化配置:
$ yarn init -y
$ yarn add koa
复制代码
为了后续编码,你应该拥有如下目录:
├── lib
│ ├── app.js
├── server.js
├── package.json
└── yarn.lock
复制代码
开始编写代码:
// lib/app.js
const Koa = require("koa");
const app = new Koa();
app.use(ctx => {
if (ctx.method == "GET" && ctx.path == "/user") {
ctx.body = "hello, friend";
}
});
module.exports = app;
复制代码
// server.js
const app = require("./lib/app");
app.listen("8888", () => {
console.log("server is running at http://localhost:8888");
});
复制代码
启动 Node 应用:
$ node server.js
复制代码
我建立了一个最最简单的 API 服务,当用户访问 http://localhost:8888/user 时,返回 "hello, friend".
在这以前,你须要建立 Travis CI 的配置文件,在根目录下新建 .travis.yml
# 构建环境
language: node_js
# node_js 版本
node_js:
- 12
after_success:
- echo 'I successfully done'
复制代码
⚠️ Travis CI 默认会执行 install、script 这两个生命周期,即便没有显式在配置文件中定义。
就当前的配置文件而言,启动构建后,Travis CI 将执行 install ➡️ script ➡️ after_success.
而按照官方文档 Building a JavaScript and Node.js project:
npm install
npm test
而且,若是 Travis CI 检测到 yarn.lock
的存在,则分别替换命令为 yarn
和 yarn test
.
因此,咱们还需提供测试(test)脚本,在 package.json 中添加:
"scripts": {
"test": "echo just test it",
},
复制代码
最后,确保你在 /account/repositories 中,开启了对该仓库的监听。
一切就绪,只需将修改后的代码推送到远程仓库,来触发 Travis CI。
$ git push
复制代码
来到 travis-ci/dashboard,在 Active repositories 面板中选择 travis-test,能够看到如下信息:
查看下方日志信息,关键的地方我用文字标注了:
持续集成已经跑通,但感受少了点什么?对,访问 remote 的命令还未添加。
因为 Travis CI 至关于开启了一个虚拟化容器来执行整个构建过程,因此有必要将私钥:id_rsa
传递给它,来支持 remote 的 SSH 链接。那也总不能直接将 id_rsa
放到咱们的仓库中吧,那岂不是泄露了私钥,后果很是严重!
Travis CI 早就想到了这一点,它提供了针对私钥的加密方案。
加密私钥文件须要使用 travis 这个命令行工具,它是一个 ruby 包,使用 gem 安装:
$ gem install travis
$ travis login
复制代码
若是你安装 travis 失败,能够查阅 github.com/travis-ci/t….
输入帐号密码登陆成功后,使用 travis encrypt-file 加密:
$ travis encrypt-file ~/.ssh/id_rsa --add
# Detected repository as B2D1/travis-test, is this correct? |yes| yes
# Overwrite the config file /root/travis-test/.travis.yml with the content below? (y/N) y
# Make sure to add id_rsa.enc to the git repository.
# Make sure not to add /root/.ssh/id_rsa to the git repository.
# Commit all changes to your .travis.yml.
复制代码
上面命令执行完后,会生成一段解密命令并添加到 .travis.yml 中:
before_install:
- openssl aes-256-cbc -K $encrypted_9b2d7e19d83c_key -iv $encrypted_9b2d7e19d83c_iv
-in id_rsa.enc -out ~/.ssh/id_rsa -d
复制代码
而且提示 ❗️,必定要把加密后的 id_rsa.enc
复制到仓库中,必定不要把未加密的 id_rsa
复制到仓库中。
有可能你生成的是 -out ~\/.ssh/id_rsa -d
,切记改为 -out ~/.ssh/id_rsa -d
before_install
阶段发生在 install
阶段以前,这段代码的意思是:用 encrypted_9b2d7e19d83c_iv
和 encrypted_9b2d7e19d83c_key
这两个环境变量,对仓库中的 id_rsa.enc
进行解密,并在虚拟容器中的 ~/.ssh 目录下生成私钥:id_rsa
你能够在 travis-ci.org ➡️ 你的仓库 ➡️ More options ➡️ settings 中找到这对环境变量:
基本完成对 remote 的链接工做,但还有一些坑要填:
更改后的 .travis.yml 配置以下:
# 构建环境
language: node_js
# node_js 版本
node_js:
- 12
# 将远程服务器加入信任列表
addons:
ssh_known_hosts: 47.106.87.3
# 解密 id_rsa.enc,并修改 id_rsa 权限
before_install:
- openssl aes-256-cbc -K $encrypted_9b2d7e19d83c_key -iv $encrypted_9b2d7e19d83c_iv
-in id_rsa.enc -out ~/.ssh/id_rsa -d
- chmod 600 ~/.ssh/id_rsa
# 链接远程服务器,并打印系统版本
after_success:
- ssh root@47.106.87.3 'cat /etc/issue'
复制代码
提交代码(git push),查看构建结果:
成功打印了我远程服务器的版本信息:
在上一节,为了快速经过测试命令(yarn test),只是简单使用了 echo 命令。
如今要正式为 NodeJS 应用添加单元测试,建议选择 Jest + SuperTest 来实现。
Jest 是 Facebook 的一套开源的 JavaScript 测试框架,它自动集成了断言、JSDom、覆盖率报告等开发者所须要的全部测试工具,是一款几乎零配置的测试框架。
SuperTest: HTTP assertions made easy via superagent.
安装 npm 包:
$ yarn add jest supertest --dev
复制代码
更改 package.json:
"scripts": {
"test": "jest"
},
复制代码
在根目录下新建 __test__/app.test.js
,并编写测试代码:
const app = require("../lib/app");
const supertest = require("supertest");
const server = app.listen();
const request = supertest(server);
test("GET /user", async done => {
const res = await request.get("/user");
expect(res.status).toBe(200);
expect(res.text).toBe("hello, friend");
done();
});
afterAll(done => {
server.close();
done();
});
复制代码
执行测试脚本:
$ yarn test
复制代码
测试经过:
还能够经过 --coverage
参数来提供覆盖率报告:
这一节,继续完善 NodeJS 应用,为它添加 ESlint.
ESLint 是一个插件化而且可配置的 JavaScript 语法规则和代码风格的检查工具。ESLint 可以帮你轻松写出高质量的 JavaScript 代码
安装 npm 包:
$ yarn add eslint eslint-config-google --dev
复制代码
更改 package.json:
"scripts": {
"lint": "eslint .",
"test": "jest",
"pretest": "yarn run lint"
},
复制代码
❗️pretest 脚本会在 yarn test 以前自动执行。
在根目录下建立配置文件 .eslintrc.json
{
"extends": ["eslint:recommended", "google"],
"env": {
"node": true
},
"parserOptions": {
"ecmaVersion": 6
},
"rules": {
"eqeqeq": 2
},
"ignorePatterns": ["ecosystem.config.js", "__tests__"]
}
复制代码
这里采用了预设的 lint 规则:recommended & google.
并新增一条规则:非严格相等符(==)的存在,会致使程序退出(0 表明关闭,1 表明警告,2 表明错误)。
其余的配置项为:设置代码环境、ECMA 版本、指定哪些文件不参与检查。
执行 lint 命令:
$ yarn run lint
复制代码
发生了如下错误:
能够尝试运行 yarn run lint --fix
命令, ESlint 会自动修复错误。对于不能自动修复的,需手动修改。
通过上述步骤,已经基于 Travis CI 实现了 CI(持续集成)。
只差最后一步:将 NodeJS 应用部署到远程服务器上。
参照官方文档 PM2 Deployment,咱们只需建立配置文件便可,剩下的交给 PM2 来作。
在根目录下建立 ecosystem.config.js
module.exports = {
apps: [
{
// PM2 应用名称
name: "travis-test-deploy",
// node 启动文件
script: "server.js",
},
],
deploy: {
// "prod" 是环境名称
prod: {
// 私钥目录
key: "~/.ssh/id_rsa",
// 登陆用户
user: "root",
// 远程服务器
host: ["47.106.87.3"],
// 自动将 github 加入远程服务器的信任列表
ssh_options: "StrictHostKeyChecking=no",
// git 分支
ref: "origin/master",
// git 仓库地址(ssh)
repo: "git@github.com:B2D1/travis-test.git",
// 项目在远程服务器的存放路径
path: "/root/travis-test-deploy",
// PM2拉取最新分支后,安装 npm 包,并启动(重启)NodeJS 应用
"post-deploy":
"source ~/.nvm/nvm.sh && yarn install && pm2 startOrRestart ecosystem.config.js",
},
},
};
复制代码
同时修改 .travis.yml
# 构建环境
language: node_js
# node_js 版本
node_js:
- 12
# 将远程服务器加入信任列表
addons:
ssh_known_hosts: 47.106.87.3
# 解密 id_rsa.enc,并修改 id_rsa 权限
before_install:
- openssl aes-256-cbc -K $encrypted_9b2d7e19d83c_key -iv $encrypted_9b2d7e19d83c_iv
-in id_rsa.enc -out ~/.ssh/id_rsa -d
- chmod 600 ~/.ssh/id_rsa
# PM2 deploy
after_success:
- npm i -g pm2 && pm2 deploy ecosystem.config.js prod update
复制代码
⚠️ 在首次部署时,咱们须要先在远程服务器初始化项目。
$ pm2 deploy ecosystem.config.js prod setup
复制代码
❗️ 若是 win 系统出错,请使用 git bash.
随后提交代码(git push),等待 Travis CI 构建 和 PM2 部署完毕。
访问 Travis CI 显示构建成功,登陆远程服务器,输入 pm2 list,如图所示:
访问 http://<your remote ip>:8888/user
,显示 "hello,friend".
这个 NodeJS 应用虽然简单,但涉及的知识点很是之多:建立 API 服务、单元测试、ESLint、CI/CD、SSH、Linux 运维,须要掌握必定的实践能力。
因为篇幅有限,还有不少坑、细节来不及去讲,若有错误请联系我 📧.