目录

    从 GitLab 8.0 开始,GitLab 开始集成 CI(持续集成) 功能。只需要在服务器上添加一个 Runner,同时在项目中添加一个 .gitlab-ci.yml 文件,就可以进行 CI。在 GitLab 搭建与配置 中笔者记录了从零开始搭建 GitLab 服务的整个流程。在 GitLab CI 持续集成 中笔者交代了 GitLab CI 的一些基本概念,并给出了一个简单的 Demo。 本文主要讨论的是使用 Git 作为代码仓库时,多人开发的工作模式;在前端开发过程中,如何利用 GitLab-CI 工具,自动化编译 Webpack 项目。

    1. 问题与需求

    笔者从事 SaaS 开发,简单描述下目前团队项目开发的现状。

    SaaS 基于 PaaS 提供的框架和 CI/CD 功能进行开发、预发布、发布,使用 SVN 进行代码的管理。PaaS 提供了 SaaS 开发人员快速开发应用的能力。

    但是,在多人合作开发的项目中,SVN 提供的分支功能不便于管理分支、合并代码。

    相比较于 SVN,Git 拉取代码速度快、允许上千个并行开发的分支、完全分布式,受到大量开发人员的喜爱。多人合作开发项目时,倾向于使用 Git 来管理代码,而在准备部署时才提交到 SVN。为了缩短流程,一方面,可以推动 PaaS 提供 Git 代码托管服务;另一方面,可以通过一定的工具来自动化整个流程(Git -> SVN),节省开发部署的时间。

    另外一个问题是,前端团队使用的是 Webpack 模块打包管理工具,前端工程需要打包编译。针对一些复杂的项目,可能会有多人参与前端的开发工作。有时,前端人员在打包 Webpack 项目之前,没有拉取最新代码,导致其他前端人员开发的功能没有及时更新到最新的版本文件中。

    幸运的是,GitLab 提供 CI 的功能,能解决上面的问题。GitLab CI 提供了在服务器上执行脚本、自动化流程的能力。

    2. Git 工作流

    通常 Git 的工作流程,采用的是功能驱动式开发。先有需求,再有功能分支或补丁分支。完成开发后,该分支就合并到主分支,然后删除分支。

    广泛使用的工作流程有三种:

    • Git flow
    • Github flow
    • Gitlab flow

    Git flow 需要同时维护两个非常相似的分支 develop 和 master,比较适合具有较长版本发布周期的项目;Github flow 只需要维护一个分支,根据新需求从 master 拉取新分支,合并上线后,再删除新分支。Gitlab flow 在 master 分支以外,再建立不同的环境分支,通过不同环境的上下游关系合并新功能。

    目前团队的SaaS 更新周期短,迭代频繁,新的功能和修复的 Bug 很快就能合并到 master ,个人认为使用 Github flow 工作流程会比较简单方便。

    当有新需求或者 Bug 时,从最新的 master 拉出一个新的分支,在新的分支上进行开发。在新分支上开发完成之后,发起 Pull Request 合并到 master ,最后删掉新建的分支。

    3. GitLab-CI 工作流

    通过在项目中配置 CI 流程,GitLab 默认在代码提交之后触发 CI 过程。默认的 CI 配置文件路径是项目根目录下 .gitlab-ci.yml 文件,也可以在项目的【Settings】-【Pipelines】- 【Pipelines】中指定配置文件路径。

    简单说,就是在项目下新增一个 .gitlab-ci.yml 文件,在文件中定义 CI 流程,提交代码之后,GitLab CI 就自动开始构建了。下面的一些讨论,主要是围绕 .gitlab-ci.yml 文件。

    3.1 .gitlab-ci.yml 关键字及含义

    • before_script 定义任何 Jobs 运行前都会执行的命令。
    • after_script 定义任何 Jobs 运行完后都会执行的命令。(要求 GitLab 8.7+ 和 GitLab Runner 1.2+)
    • variables && Job.variables 定义环境变量。如果定义了 Job 级别的环境变量的话,该 Job 会优先使用 Job 级别的环境变量。(要求 GitLab Runner 0.5.0+)
    • cache && Job.cache 定义需要缓存的文件。每个 Job 开始的时候,Runner 都会删掉 .gitignore 里面的文件。如果有些文件 (如 node_modules/) 需要多个 Jobs 共用的话,我们只能让每个 Job 都先执行一遍 npm install。(要求 GitLab Runner 0.7.0+)
    • Job.script 定义 Job 要运行的命令,必填项
    • Job.stage 定义 Job 的 stage,默认为 test
    • Job.artifacts 定义 Job 中生成的附件。当该 Job 运行成功后,生成的文件可以作为附件 (如生成的二进制文件) 保留下来,打包发送到 GitLab,之后我们可以在 GitLab 的项目页面下下载该附件。

    3.2 工作流

    本地开发功能、定义流程,提交代码后,GitLab 通过 Pipeline 完成 CI/CD 过程。

    4. Webpack CI 实践

    GitLab 服务器最低配置要求是 1 core CPU、1GB RAM、3GB swap,推荐配置是 2 cores CPU、4GB RAM。团队使用的 GitLab 服务器配置为:

    • Intel(R) Xeon(R) CPU E5-2420 六核,1.90GHz,线程数 12
    • 内存 64G
    • 硬盘 300G,10000RPM,SAS

    项目的文件结构:

    - webpack/src
    - webpack/package.json
    - webpack/...
    - .gitlab-ci.yml
    

    4.1 .gitlab-ci.yaml 配置

    需要注意的是 GitLab CI 提供了一些内置的环境变量,比如,$CI_PROJECT_PATH 表示项目的路径。使用这些内置变量,能够显著提高 yaml 配置的可移植性,减少拼写错误。下面是配置的 yaml 文件,具体每个步骤的功能,写在注释中。

    before_script:
      # 激活 nodeenv 虚拟环境
      - source /data/gitlab-runner/paas-webfe/bin/activate
      # 查看 node 版本
      - which node && node --version
      # 为了能方便使用 npm ,给它取一个别名
      - alias npm="/data/gitlab-runner/node/bin/npm"
      - which npm && npm --version
    
    stages:
      - build
    
    build-test-webpack:
      variables:
        # 需要修改为项目的 GitLab 地址格式 
        CI_REPOSITORY_URL:
          http://$GIT_USERNAME:$GIT_PASSWORD@gitlab.yourdomain.com/$CI_PROJECT_PATH.git
        # 打包生成的文件存放目录
        OUT_PUT_DIR:
          test
      stage: build
      # 允许在 GitLab 页面上,直接下载 $OUT_PUT_DIR 内容
      artifacts:
        paths:
          - $OUT_PUT_DIR
      # 没有 Git 版本的文件,设置缓存,可以避免每次 npm install 重复安装
      cache:
        untracked: true
      script:
        # 开始执行打包编译命令,并提交到当前的 Git 仓库,具体的命令,需要根据项目编写,也可以放在一个 shell 文件,执行
        - echo "start build test"
        - rm -rf  $CI_PROJECT_NAME $OUT_PUT_DIR
        - cd ./webpack && tnpm install && tnpm run build-test && cd ..
        - git clone $CI_REPOSITORY_URL
        - rm -rf $CI_PROJECT_NAME/$OUT_PUT_DIR && cp -r $OUT_PUT_DIR $CI_PROJECT_NAME/
        - cd $CI_PROJECT_NAME
        - git add $OUT_PUT_DIR
        - git status >> build.log
        - date >> build.log
        - git add build.log
        - git commit -m "auto build-test[ci skip]"
        - git push $CI_REPOSITORY_URL master
        - echo "end build test"
    

    上面的 yaml 配置实现了,提交代码之后,自动打包编译 Webpack 项目,并将编译生成的文件更新到 GitLab 仓库。如果需要更新到 SVN ,只需要执行 SVN 相应的提交命令即可,这个就当做是作业布置给看本篇文档的你了。

    4.2 通过环境变量隐藏敏感信息

    在项目页面依次选择 - 【Settings】- 【Pipelines】- 【Secret variables】

    4.3 查看 CI 结果

    向 GitLab 提交代码之后,CI 过程将被自动触发。等待执行的完成。

    需要说明的另外一个关键点是:循环构建。在 script 里面,在每次构建时,会执行一次 git 的提交,这样会触发一次新的构建,如此反复,不停地执行构建。为了解决这个问题,在 script 里面提交代码时,需要在 commit 的 message 中加上 “[ci skip] 或者 [skip ci] 关键字”,跳过当次构建。在 Web 上查看 【Pipeline】 的执行状态时,可以看到一次提交会新增两条记录,其中一条为 【skipped】。 执行 job 时,除了 console 会输出构建信息。在 【Jobs】选项中还可以查看 job 的执行流和执行结果,为了更好的页面效果,下面是另外一个 Example 的执行结果(特意写了很多个 stage 和 job)。

    4.4 一些小技巧

    • job 中使用 when 关键字,可以控制 job 在满足一定条件时,才被执行
      when: [on_success | on_failure | always | manual]
      
    • 跳过 CI,在提交代码时,增加 ci skip或skip ci,可以跳过当次提交触发的 CI 过程

      git commit -m "[ci skip]"
      
    • 在 job 中可以设置 only ,限制仅指定分支才执行

      only:
        - master
      

    5. 参考

    6. 附录

    另外一份将 Git 仓库代码推送到 SVN 的配置

    before_script:
      - svn --version
      - git --version
      - 'echo "clear svn & git dir"'
      - rm -rf svn-dit git-dir
      - LANG="zh_CN.utf8"
      - export LC_ALL=zh_CN.UTF-8
    
    
    stages:
      - deploy
    
    push-to-svn:
      stage: deploy
      variables:
        CI_REPOSITORY_URL:
          http://$GIT_USERNAME:$GIT_PASSWORD@gitlab.domain.com/$CI_PROJECT_PATH.git
      artifacts:
        paths:
          - svn-dir
          - git-dir
      script:
        - 'echo "start push to svn"'
        - 'echo "step 1/3: git clone"'
        - git clone $CI_REPOSITORY_URL git-dir
        - 'echo "finished"'
        - 'echo "setp 2/3: svn checkout"'
        - svn checkout $SVN_PATH svn-dir --username $SVN_USERNAME --password $SVN_PASSWORD --no-auth-cache
        - 'echo "finshed"'
        - 'echo "step 3/3: push git master to svn trunk"'
        - cd svn-dir
        - svn delete *
        - cd ..
        - cp -rf git-dir/* svn-dir/
        - cd ./svn-dir
        - svn add * --force
        - svn commit -m "`git log -1 --pretty=%B`" --username $SVN_USERNAME --password $SVN_PASSWORD  --no-auth-cache
        - 'echo "end push to svn"'
    
      only:
        - master