cicd集成持续部署


一.持续集成和布署总览

  1. 技术栈:前台 Vue , 后台 node.js
  2. 服务器:前端 nginx , 后端 node.js

图片总览:

image-20250530081210486

二、项目前期准备工作

2.1、编写后端服务

2.1.1、package.json

/usr/projects/vue-back/package.json

{
  "name": "vue-back",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./server.js "
  }
}

2.1.2、server.js,一个非常简单的接口服务

/usr/projects/vue-back/server.js

let http = require("http");
let users = [
  { id: 1, name: "zhufeng1" },
  { id: 2, name: "zhufeng2" },
  { id: 3, name: "zhufeng3" },
];
let server = http.createServer(function (req, res) {
  console.log(req.method, req.url);
  if (req.url == "/api/users") {
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.end(JSON.stringify(users));
  } else {
    res.end("Now Found!");
  }
});
server.listen(3000, () => {
  console.log("服务正在3000端口上启动!");
});

2.1.3、.gitignore 文件

/usr/projects/vue-back/.gitignore

node_modules
lib
package-lock.json

2.2、前端项目

2.2.1、安装脚手架生成项目

cnpm i @vue/cli -g
vue create vue-front
cd vue-front
cnpm i axios -S

2.2.2、App.vue

/usr/projects/vue-front/src/App.vue

<template>
  <ul>
    <li v-for="user in users" :key="user.id">{{ user.id }}:{{ user.name }}</li>
  </ul>
</template>

<script>
import axios from "axios";
export default {
  name: "app",
  data() {
    return {
      users: [],
    };
  },
  mounted() {
    axios.get("http://localhost:3000/api/users").then((response) => {
      this.users = response.data;
    });
  },
};
</script>
<style></style>

2.3、CICD 服务器(webhooks)

什么是 CICD 服务器,这个以一个 git 的服务 webhooks 为例,其实就是一个钩子服务,当我们在 github 上提交代码的时候,会触发这个钩子服务,这样的话我们就可以借用这个钩子服务进行后续的相关操作,例如:当提交代码时,服务器可以同步最新的代码并进行持续部署操作

image-20250530083953131

image-20250530083718220

2.3.1、创建 webhook 服务

mkdir vue-webhook
cd vue-webhook
cnpm init -y
cnpm i nodemailer -S

2.3.2、webhook.js

/usr/projects/vue-webhook/webhook.js

以下代码表明当我们提交代码的时候会创建一个子线程执行自己事先创建好的脚本文件,并同时给邮箱进行提醒

核心代码体现:

//创建子线程
var spawn = require("child_process").spawn;
//子线程根据提交的项目名称执行对应的脚本文件
let child = spawn("sh", [`./${payload.repository.name}.sh`]);
//脚本执行结束之后进行邮箱的发送提醒
sendMail(`
            <h1>部署日期: ${new Date()}</h1>
            <h2>部署人: ${payload.pusher.name}</h2>
            <h2>部署邮箱: ${payload.pusher.email}</h2>
            <h2>提交信息: ${
              payload.head_commit && payload.head_commit["message"]
            }</h2>
            <h2>布署日志: ${logs.replace("\r\n", "<br/>")}</h2>
        `);

具体代码体现:

let http = require("http");
let crypto = require("crypto");
var spawn = require("child_process").spawn;
let sendMail = require("./sendMail");
const SECRET = "123456";
function sign(data) {
  return "sha1=" + crypto.createHmac("sha1", SECRET).update(data).digest("hex");
}
let server = http.createServer(function (req, res) {
  console.log(req.method, req.url);
  if (req.url == "/webhook" && req.method == "POST") {
    let buffers = [];
    req.on("data", function (data) {
      buffers.push(data);
    });
    req.on("end", function () {
      let body = Buffer.concat(buffers);
      let sig = req.headers["x-hub-signature"];
      let event = req.headers["x-github-event"];
      let id = req.headers["x-github-delivery"];
      if (sig !== sign(body)) {
        return res.end("Not Allowed");
      }
      res.setHeader("Content-Type", "application/json");
      res.end(JSON.stringify({ ok: true }));
      //===========分割线===================
      if (event === "push") {
        let payload = JSON.parse(body);
        let child = spawn("sh", [`./${payload.repository.name}.sh`]);
        let buffers = [];
        child.stdout.on("data", function (buffer) {
          buffers.push(buffer);
        });
        child.stdout.on("end", function () {
          let logs = Buffer.concat(buffers).toString();
          sendMail(`
            <h1>部署日期: ${new Date()}</h1>
            <h2>部署人: ${payload.pusher.name}</h2>
            <h2>部署邮箱: ${payload.pusher.email}</h2>
            <h2>提交信息: ${
              payload.head_commit && payload.head_commit["message"]
            }</h2>
            <h2>布署日志: ${logs.replace("\r\n", "<br/>")}</h2>
        `);
        });
        child.on("close", (code) => {
          console.log(`子进程退出,退出码 ${code}`);
        });
      }
    });
    req.on("error", function (err) {
      console.error("Request error:", err);
      res.end("Error");
    });
  } else {
    res.end("Now Found!");
  }
});
server.listen(4000, () => {
  console.log("服务正在4000端口上启动!");
});

发邮件服务 sendMail.js

/usr/projects/vue-webhook/sendMail.js

const nodemailer = require("nodemailer");
let transporter = nodemailer.createTransport({
  // host: 'smtp.ethereal.email',
  service: "qq", // 使用了内置传输发送邮件 查看支持列表:https://nodemailer.com/smtp/well-known/
  port: 465, // SMTP 端口
  secureConnection: true, // 使用了 SSL
  auth: {
    user: "[email protected]->12xxxxx@qq.com", //你的邮箱地址
    // 这里密码不是qq密码,是你设置的smtp授权码,登录网页qq邮箱,设置账号里有
    pass: "zpdf0teyhjfbpcaff",
  },
});

function sendMail(message) {
  let mailOptions = {
    from: '"83687401" <[email protected]> -> "125XXXXXX" <125XXXXXX@qq.com>', // 发送地址
    to: "[email protected]->12xxxxx@qq.com", // 接收者
    subject: "部署通知", // 主题
    html: message, // 内容主体
  };
  // send mail with defined transport object
  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      return console.log(error);
    }
    console.log("Message sent: %s", info.messageId);
  });
}
module.exports = sendMail;

2.3.3、package.json

/usr/projects/vue-webhook/package.json

{
  "name": "vue-webhooks",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "pm2 start ./webhook.js --watch --name='vue-webhook'",
    "stop": "pm2 stop vue-webhook"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "nodemailer": "^6.3.0"
  }
}

上述代码中的 pm2 是为了在 linux 服务器中启动和管理 Node.js 应用程序时所用到的,其中**pm2 start ./webhook.js –watch –name ‘vue-webhook’**这句话的含义是:在当前路径下启动 webhook.js 文件,并设置它为自动重启模式,并且指定一个名字为 vue-webhook,使用 --watch 参数时,PM2 会监视文件的变化并自动重启应用。文档下面会有所体现

image-20250530102508520

三、Linux 服务器的相关环境安装及配置

3.1、更新系统

#升级所有包同时也升级软件和系统内核
yum update
#只升级所有包,不升级软件和系统内核
yum upgrade

3.2、docker 的简单介绍及安装

3.2.1、什么是 docker?

  • Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。
  • Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样

image-20250530090437196

3.2.2、docker 的安装

//安装docker环境依赖
yum install -y yum-utils   device-mapper-persistent-data   lvm2
//配置国内docker-ce的yum源(阿里云)
yum-config-manager \
    --add-repo \
    https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

安装 docker-ce

// 查看仓库中的所有docker版本,可以自定义选择版本安装。
yum list docker-ce --showduplicates | sort -r

// 以下三种方式,三选一
方式1.默认稳定版
//直接使用以下命令安装,该操作会默认安装最新稳定版docker,由于repo中默认只开启stable仓库
yum install docker-ce

方式2.选择版本安装
// 选择版本安装
yum install -y docker-ce-19.03.13

// 方式三 旧版(未确认现在是否可继续使用)
yum install docker-ce docker-ce-cli containerd.io -y

说明:1、docker-ce-cli 作用是docker 命令行工具包 2、containerd.io 作用是容器接口相关包 3、yum info 软件包名字,可以查看一个包的具体作用

阿里云加速

mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
  //如果代理不好用了,这个后面还可以从具体的配置文件中改
  "registry-mirrors": [
  	"https://docker.1ms.run",
	"https://docker.1panel.live/"
	]
}
EOF
# 重载所有修改过的配置文件
systemctl daemon-reload
systemctl restart docker

3.3、安装 git

yum install git -y

3.3.1、生成公钥并添加到 github 以便于使用 ssh 方式拉取代码

ssh-keygen -t rsa -b 4096 -C "[email protected]邮箱地址"
ssh-keygen -t rsa -b 4096 -C "1251583434@qq.com"
cat /root/.ssh/id_rsa.pub

3.3.2、给 git 命令起别名

~.gitconfig

[alias]
    a = add -A
    c = commit -m"msg"
    p = push origin master

3.3.3、从 git 中拉取代码到服务器上

//先进入到指定目录,没有目录先进行创建
cd /usr/local/projects
git clone [email protected]:xxxx/vue-front.git
git clone [email protected]:xxxx/vue-back.git
git clone [email protected]:xxxx/vue-webhook.git

3.4、安装 node 和 npm

node 和 npm 这个根据自己情况可自行安装,因为我们已经安装了 docker,后续项目也可用通过 docker 镜像安装使用,这里只是说明 linux 如何安装这些环境

nvm首先可以先安装一下 nvm,以便于后续 node 版本的管理

wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
source /root/.bashrc
nvm install stable //安装稳定版本
npm i nrm -g  //为了配置npm的镜像源
nrm use taobao //切换使用taobao镜像源
npm i pm2 -g  //安装pm2,以便于运行和管理node.js的程序

四、安装启动项目服务并测试

4.1、vue-back

cd /usr/projects/vue-back
npm i
npm run start
curl http://localhost:3000 //测试是否成功运行

4.2、vue-front

cd /usr/projects/vue-front
npm i
npm run serve
curl http://localhost:8080

4.3、vue-webhook

cd /usr/projects/vue-webhook
npm i
npm run start
curl http://localhost:4000
curl http://47.104.15.123:4000/webhook

五、编写 shell 脚本以及项目目录下的 Dockerfile 文件部署

5.1、后台部署

vue-back.sh

/usr/projects/vue-webhook/vue-back.sh

#!/bin/bash
WORK_PATH='/usr/projects/vue-back'
cd $WORK_PATH
echo "清理代码"
git reset --hard origin/master
git clean -f
echo "拉取最新代码"
git pull origin master
echo "开始构建镜像"
docker build -t vue-back:1.0 .
echo "删除旧容器"
docker stop vue-back-container
docker rm vue-back-container
echo "启动新容器"
docker container run -p 3000:3000 -d --name vue-back-container vue-back:1.0
echo "删除名称为none的镜像"
docker images | grep none | awk '{print $3}' | xargs docker rmi

Dockerfile

/usr/projects/vue-back/Dockerfile

FROM node
LABEL name="vue-back"
LABEL version="1.0"
COPY . /app
WORKDIR /app
RUN npm install
EXPOSE 3000
CMD npm start

.dockerignore

/usr/projects/vue-back/.dockerignore

.git
node_modules
package-lock.json
Dockerfile
.dockerignore

5.2、前台部署

vue-front.sh

/usr/projects/vue-webhook/vue-front.sh

#!/bin/bash
WORK_PATH='/usr/projects/vue-front'
cd $WORK_PATH
echo "清理代码"
git reset --hard origin/master
git clean -f
echo "拉取最新代码"
git pull origin master
echo "打包最新代码"
npm run build
echo "开始构建镜像"
docker build -t vue-front:1.0 .
echo "删除旧容器"
docker stop vue-front-container
docker rm vue-front-container
echo "启动新容器"
docker container run -p 80:80 -d --name vue-front-container vue-front:1.0
echo "删除名称为none的镜像"
docker images | grep none | awk '{print $3}' | xargs docker rmi

Dockerfile

/usr/projects/vue-front/Dockerfile

FROM nginx
LABEL name="vue-front"
LABEL version="1.0"
COPY  ./dist/ /usr/share/nginx/html/
COPY ./vue-front.conf /etc/nginx/conf.d/
EXPOSE 80

vue-front.conf

/usr/projects/vue-front/vue-front.conf

server {
    listen       80;
    server_name  47.104.15.123;
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
    location /api {
      proxy_pass http://47.104.15.123:3000;
    }
}

.dockerignore

.git
node_modules
package-lock.json
Dockerfile
.dockerignore

六、集成部署

  • Compose 通过一个配置文件来管理多个 Docker 容器
  • 在配置文件中,所有的容器通过 services 来定义,然后使用 docker-compose 脚本来启动、停止和重启应用和应用中的服务以及所有依赖服务的容器
  • 最后,运行 docker-compose up,Compose 将启动并运行整个应用程序 配置文件组成

6.1、docker-compose.yml

/usr/projects/docker-compose.yml

version: "2"
services:
  api:
    build:
      context: ./vue-back
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
  web:
    build:
      context: ./vue-front
      dockerfile: Dockerfile
    ports:
      - "80:80"

6.2、安装 docker-compose

curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

6.3、启动服务

docker-compose up
docker-compuse up -d

6.4、cicd.sh

/usr/projects/vue-webhook/cicd.sh

#!/bin/bash
WORK_PATH='/usr/projects/vue-back'
cd $WORK_PATH
echo "清理后台代码"
git reset --hard origin/master
git clean -f
echo "拉取后台最新代码"
git pull origin master

WORK_PATH='/usr/projects/vue-front'
cd $WORK_PATH
echo "清理前台代码"
git reset --hard origin/master
git clean -f
echo "拉取前台最新代码"
git pull origin master
echo "打包前台最新代码"
npm run build

cd /usr/projects
echo "删除老资源"
docker-compose down
echo "重启所有服务"
docker-compose up -d

文章作者: Liu Yuan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Liu Yuan !
—— 评论区 ——
  目录