jenkins+nodessh实现前端自动化部署

1,179 阅读3分钟

在常规的前端项目中,部署项目需要经过本地build,压缩文件,将压缩包上传至服务器并解压文件等步骤,过程较为繁琐。所以在日常工作中就编写了一套自动化部署流程,用来告别手动上传的过程,配置使用简单,实现前端一键自动化部署。

首先咱们来理清整个过程:

1. 代码上传到gitlab,Jenkins检测到gitlab上代码有变化,将代码下载到自己的工作空间。

2. 代码下载之后,Jenkins自动下载项目所需要的依赖文件。

3. 执行package.json文件里的自定义打包命令,将打包好的文件压缩成zip文件放入linux服务器中指定的路径下。

4. 部署成功,访问根据nginx发布的项目地址测试是否成功。

68C41962-F527-421b-881B-4C581315D708.png

接下来就是进行环境搭建。

安装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命令路径,如果在脚本的默认路径中请忽略此步骤

911490-20190215134549962-119504242.png

然后执行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连接修改

911490-20190215134717350-1584103592.png

启动服务 service jenkins start

检查端口是否启动,默认8080 netstat -tnlp

判断jenkins是否下载成功并且运行正常 service jenkins status

这时候我们可以通过访问服务器的8080端口来进入jenkins的页面,如果打不开可以查看8080端口是否开放。

A82379E5-764C-4f22-9AE1-351C0CDF50AC.png

然后根据jenkins的引导进行初始化的工作:

  1. 根据提示获取管理员的密码然后输入jenkins生成的密码。
  2. 配置插件。
  3. 创建用户。

编写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"

配置项目

  1. 登录jenkins创建项目

A82379E5-764C-4f22-9AE1-351C0CDF50AC.png

2.配置项目信息

BEC85684-5EB6-4341-985F-952BFEDAABB4.png

3B82454C-B5DD-45d5-8D7F-7FBA9C4CEF5B11.png

3.选择构建环境

8DCE84FE-1CC8-46ce-8BF1-91E73F260B64222.png

4.编写构建命令

418EB611-29DC-40f9-9978-FF2E51BC6A5B333.png

5.点击保存和运用,进入项目首页点击立即构建,构建完成之后即可测试。