在常规的前端项目中,部署项目需要经过本地build,压缩文件,将压缩包上传至服务器并解压文件等步骤,过程较为繁琐。所以在日常工作中就编写了一套自动化部署流程,用来告别手动上传的过程,配置使用简单,实现前端一键自动化部署。
首先咱们来理清整个过程:
1. 代码上传到gitlab,Jenkins检测到gitlab上代码有变化,将代码下载到自己的工作空间。
2. 代码下载之后,Jenkins自动下载项目所需要的依赖文件。
3. 执行package.json文件里的自定义打包命令,将打包好的文件压缩成zip文件放入linux服务器中指定的路径下。
4. 部署成功,访问根据nginx发布的项目地址测试是否成功。
接下来就是进行环境搭建。
安装jenkins
下载Jenkins epel源
wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat-stable/jenkins.repo
然后执行 rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.key
Jenkins运行依赖Java环境,需要提前配置好
查看java版本信息 java -version
初始化jenkins yum install jenkins -y
查找java程序的执行路径 which java
在Jenkins启动脚本添加我们的Java命令路径,如果在脚本的默认路径中请忽略此步骤
然后执行chown -R jenkins:jenkins /var/lib/jenkins/
加载jenkins服务systemctl daemon-reload
执行vim /var/lib/jenkins/hudson.model.UpdateCenter.xml
,由于较早版本中在Jenkins运行前按照plugin时可能会出现问题,将https修改为http,如果没有这个文件则先启动Jenkins,启动后如果出现问题可以打开http://IP:8080/pluginManager/advanced连接修改
启动服务 service jenkins start
检查端口是否启动,默认8080 netstat -tnlp
判断jenkins是否下载成功并且运行正常 service jenkins status
这时候我们可以通过访问服务器的8080端口来进入jenkins的页面,如果打不开可以查看8080端口是否开放。
然后根据jenkins的引导进行初始化的工作:
- 根据提示获取管理员的密码然后输入jenkins生成的密码。
- 配置插件。
- 创建用户。
编写nodessh脚本
在项目根目录创建名为buildssh的目录,并在目录内创建nodessh.js文件,内容如下:
const commander = require("commander");
const fs = require("fs");
const node_ssh = require("node-ssh");
const archiver = require("archiver");
const ssh = new node_ssh();
commander
.version("0.1.0")
.option("--zip_dir [value]", "subproject dir")
.option("--zip", "need zip")
.option("--dist [value]", "upload file name , contain surfix")
.option("--service [value]", "gitbooks's name, folder in nginx")
.option("--env_mode [value]", "env mode")
.option("--remote_host [value]", "remote_host")
.option("--remote_user [value]", "remote_user")
.option("--remote_pwd [value]", "remote_pwd")
.option("--service_root [value]", "service_root")
.parse(process.argv);
if (!process.argv.slice(2).length) {
commander.outputHelp();
return;
}
if (!commander.zip_dir || !commander.dist || !commander.service) {
commander.outputHelp();
return;
}
let remote_host;
let remote_user;
let remote_pwd;
let service_root;
let cwd = process.cwd();
let dist_zip = `${commander.dist}`;
let local_zip = `${cwd}/${dist_zip}`;
let service_dir = `${commander.service}`;
if (commander.env_mode === "test") {
//测试环境
remote_host = "xxx.xxx.xxx.xxx"; //服务器ip
remote_user = "xxxx"; //用户名
remote_pwd = "xxx"; //密码
service_root = "/usr/share/nginx/html/xxx/"; //文件存放路径(也是nginx代理的前端路径)
} else if (commander.env_mode === "prod") {
//正式环境
remote_host = "";
remote_user = "";
remote_pwd = "";
service_root = "";
}
if (fs.existsSync(local_zip)) {
fs.unlinkSync(local_zip);
}
let output;
let archive;
// create a file to stream archive data to.
if (commander.zip) {
output = fs.createWriteStream(local_zip);
archive = archiver("zip", {
// Sets the compression level.
zlib: {
level: 5
}
});
// listen for all archive data to be written
// 'close' event is fired only when a file descriptor is involved
output.on("close", function() {
console.log(archive.pointer() + " total bytes");
console.log(
"archiver has been finalized and the output file descriptor has closed."
);
upload();
});
// This event is fired when the data source is drained no matter what was the data source.
// It is not part of this library but rather from the NodeJS Stream API.
// @see: https://nodejs.org/api/stream.html#stream_event_end
output.on("end", function() {
console.log("Data has been drained");
});
// good practice to catch warnings (ie stat failures and other non-blocking errors)
archive.on("warning", function(err) {
if (err.code === "ENOENT") {
// log warning
console.log(`${JSON.stringify(err)}`);
} else {
// throw error
throw err;
}
});
// good practice to catch this error explicitly
archive.on("error", function(err) {
throw err;
});
// pipe archive data to the file
archive.pipe(output);
// 添加src目录,第二个参数表示在压缩文件中不建立文件夹,散落在root目录
archive.directory(`${cwd}/${commander.zip_dir}`, false);
// 添加src目录,第二个参数表示在压缩文件中建立文件夹的名字
// archive.directory(`${cwd}/${commander.zip_dir}`, `${service_dir}`);
archive.finalize();
} else {
// 不用压缩文件,就上传指定文件
upload();
}
function upload() {
ssh
.connect({
host: remote_host,
username: remote_user,
password: remote_pwd
})
.then(
() => {
return ssh
.exec(`rm -rf ${service_root}/${service_dir}/*`, [], {
cwd: "",
onStdout(chunk) {
console.log("stdoutChunk", chunk.toString("utf8"));
},
onStderr(chunk) {
console.log("stderrChunk", chunk.toString("utf8"));
}
})
.then(() => {
console.log("开始上传文件");
return ssh.putFile(local_zip, `${dist_zip}`);
})
.then(() => {
console.log("上传完成,开始解压");
return ssh.exec(
`unzip -d ${service_root}/${service_dir} ${dist_zip}`,
[],
{
cwd: "",
onStdout(chunk) {
console.log("stdoutChunk", chunk.toString("utf8"));
},
onStderr(chunk) {
console.log("stderrChunk", chunk.toString("utf8"));
}
}
);
})
.then(() => {
console.log("解压完成,删除上传文件");
return ssh.exec(`rm -f ${dist_zip}`, [], {
cwd: "",
onStdout(chunk) {
console.log("stdoutChunk", chunk.toString("utf8"));
},
onStderr(chunk) {
console.log("stderrChunk", chunk.toString("utf8"));
}
});
})
.then(() => {
console.log("全完成");
return ssh.dispose();
});
},
error => {
console.log("Something's wrong");
console.log(error);
}
);
}
然后在package.json的scripts对象里面添加一条执行命令"pubtest": "node buildssh/nodessh.js --zip --zip_dir dist --dist dist.zip --service dist --env_mode test"
配置项目
- 登录jenkins创建项目
2.配置项目信息
3.选择构建环境
4.编写构建命令
5.点击保存和运用,进入项目首页点击立即构建,构建完成之后即可测试。