CI/CD一次尝试(简易版)(webhook + docker)

666 阅读3分钟

前言

本文是个实践文,具体技术来源来自于大佬的文章 docker + webhook 从零实现前端自动化部署。这里讲一下自己在搭建过程中遇到的问题,以及查阅了哪些资料。

所涉及的技术栈

  • docker
  • node
  • pm2
  • shell
  • webhook

具体实现的步骤

  • 环境配置
  • github配置
  • 云服务器配置

环境配置

  • docker说明及配置 docker入门及实践
    这里咱们就不展开讲了,原作者已经讲的非常棒了,你可以理解为docker是在服务器中的一个独立的"服务器",这里用docker去做持续交付和部署。本次部署需要两个docker文件,一个是docker配置文件Dockerfile,另一个是.dockerignore文件。具体内容如下

    Dockerfile

    # dockerfile
    # build stage
    FROM node:lts-alpine as build-stage
    WORKDIR /app
    COPY package*.json ./
    RUN npm install
    COPY . .
    RUN npm run build
    
    # production stage
    FROM nginx:stable-alpine as production-stage
    COPY --from=build-stage /app/dist /usr/share/nginx/html
    EXPOSE 80
    CMD ["nginx", "-g", "daemon off;"]
    

    .dockerignore

    # .dockerignore
    node_modules
    
  • 云服务器环境的安装

    • docker
       # Step 1: 安装必要的一些系统工具
       sudo yum install -y yum-utils
       # Step 2: 添加软件源信息,使用阿里云镜像
       sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
       # Step 3: 安装 docker-ce
       sudo yum install docker-ce docker-ce-cli containerd.io
       # Step 4: 开启 docker服务
       sudo systemctl start docker
       # Step 5: 运行 hello-world 项目
       sudo docker run hello-world
      
      弹出 Hello from Docker! 证明 Docker 已经成功安装啦~ docker安装成功
  • git

    yum install git
    
  • node

    • nvm安装
      curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
      
      export NVM_DIR="$HOME/.nvm"
      [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
      [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # 注册nvm命令,然后退出连接,再次进入就可以使用nvm命令了
      
    • nvm 安装 node
      nvm install lts # 安装最新的长期稳定版
      
  • pm2 你可以理解这个是在后台帮你执行js脚本,功能还是很多的

    npm install pm2 -g
    

真正动手了

  • 配置云服务器
    • 腾讯云或者阿里云都行,登录云平台控制台,找到端口并开放 3000端口,80443默认开放的,这里咱们只增加一个3000端口,用于webhook能够访问。
  • github新建个仓库,在仓库的setting里面设置webhook。然后clone到本地 image.png
    • webhook 类似于生命周期,在你的仓库触发了某些事件的时候就会触发该周期,从而执行某些特定操作。这里我们选择默认的push操作。
    • payload就是webhook触发时会去请求的地址。输入你的服务器地址或者域名,协议看你的服务器支持什么。例如 http://123.123.12.3:3000,端口号是3000,因为我们只开放了3000
    • Content type 选择 application/json 点击提交,然后查看是否修改正确。
  • 在clone下来的仓库中创建一个vue应用
    • vue create hello-world
    • 然后在app.vue中修改下字眼,确保等下部署时你能够发现改变。我是增加了一句话<p>CI/CD project</p>
  • 服务器操作
    • 登录服务器。
    • 通过rz上传我们前面生成的docker文件,Dockerfile.dockerignore
    • 编写node应用(index.js 内容如下),使用pm2启动node应用(pm2 start index.js),用来接收github仓库触发的webhook操作,接收仓库信息,执行相应的逻辑。
        // index.js
        const http = require("http")
        const {
          execSync
        } = require("child_process")
        const fs = require("fs")
        const path = require("path")
    
        // 递归删除目录
        function deleteFolderRecursive(path) {
          if (fs.existsSync(path)) {
            fs.readdirSync(path).forEach(function (file) {
              const curPath = path + "/" + file;
              if (fs.statSync(curPath).isDirectory()) { // recurse
                deleteFolderRecursive(curPath);
              } else { // delete file
                fs.unlinkSync(curPath);
              }
            });
            fs.rmdirSync(path);
          }
        }
    
        const resolvePost = req =>
          new Promise(resolve => {
            let chunk = "";
            req.on("data", data => {
              chunk += data;
            });
            req.on("end", () => {
              resolve(JSON.parse(chunk));
            });
          });
    
        http.createServer(async (req, res) => {
          console.log('receive request')
          console.log(req.url)
          if (req.method === 'POST' && req.url === '/') {
            const data = await resolvePost(req);
            const projectDir = path.resolve(`./${data.repository.name}`)
            deleteFolderRecursive(projectDir)
    
            // 拉取仓库最新代码 如 https://github.com/vuejs/vue.git
            execSync(`git clone https://github.com/你的github/${data.repository.name}.git ${projectDir}`, {
              stdio: 'inherit',
            })
    
            // 复制 Dockerfile 到项目目录
            fs.copyFileSync(path.resolve(`./Dockerfile`), path.resolve(projectDir, './Dockerfile'))
    
            // 复制 .dockerignore 到项目目录
            fs.copyFileSync(path.resolve(__dirname, `./.dockerignore`), path.resolve(projectDir, './.dockerignore'))
    
            // 创建 docker 镜像
            execSync(`docker build . -t ${data.repository.name}-image:latest `, {
              stdio: 'inherit',
              cwd: projectDir
            })
    
            // 销毁 docker 容器
            execSync(`docker ps -a -f "name=^${data.repository.name}-container" --format="{{.Names}}" | xargs -r docker stop | xargs -r docker rm`, {
              stdio: 'inherit',
            })
    
            // 创建 docker 容器  这里我们用主机的80转到容器的80端口
            execSync(`docker run -d -p 80:80 --name ${data.repository.name}-container  ${data.repository.name}-image:latest`, {
              stdio: 'inherit',
            })
    
            console.log('deploy success')
            res.end('ok')
          }
        }).listen(3000, () => {
          console.log('server is ready')
        })
    
    启动和部署成功后,就可以在你的服务器上打开就可以看到新的内容了。 image.png

查看效果步骤

  1. 在服务器上启动接收程序即 pm2 start index.js,查看log pm2 logs
  2. 本地应用,更新代码,并推送
  3. 静候,然后就可以在你的网站看到部署成功后的样子

tips

  • 一些操作
    • 调试时每次都要提交push操作才触发?
      • 可以用 Redeliver主动触发,然后服务上看日志 image.png
    • 出现 We couldn’t deliver this payload: timed out
      • 我这边暂时不影响,在处理当中,可以正常触发。调试时可以用postman测试应用是否正常,请求是否收到等
    • git clone 异常慢
      • 没有办法,可以绑定hosts,加快一丢丢。github加快
    • ...

存在的问题

  • 监听了push操作,无法监听具体某个分支,需要在node应用中获取到 requset的data中去做处理和判断。
  • 部署和操作,无法直接获取到日志,需要自己去做处理。
  • docker的启删创会导致应用有一段时间不可用,真实环境需要更合理的配置
  • 云服务器和github之间的网络通讯太慢了,建议 搭建gitlab,避免这个问题

总结

总的来说,这是一次CI/CD的尝试,但是离真正在项目中应用还需要很长的一段路。这是一次学习,向大佬表示敬意,同时也在慢慢的用自己的知识,去解决上面存在的问题。

业界方案