gitlab 针对 go 项目做持续集成

gitlab 内置支持持续集成(CI),但是 go 有一点比较特殊,依赖 $GOPATH ,特别是使用了 glide 来管理包依赖后, vendor 目录必须在 $GOPATH 下,这就要求 gitlab 拉取项目源代码的位置符合 $GOPATH 的目录结构。

但是 gitlab 拉取代码后的目录结构类似 /home/gitlab-runner/builds/6913a759/0/myproject ,必须将 myproject 置于 src 目录下才符合 $GOPATH 约定。

GitLab CI with Go》给出的方案是将拉取的代码移到 $GOPATH 下的正确位置上,再进行 glide 操作以及跑编译和测试,这篇文章提供了示例配置文件 .gitlab-ci.yml ,但有以下几个问题需要解决:

  • mv 操作默认是不会移动隐藏目录(如: .git )到目标位置的,这会导致后面的任务拉取代码失败

    可以开启 bash 的选项 dotglob* 匹配隐藏文件

  • 文件移动到目标位置后,没有清理机制,会影响下一任务

    gitlabGIT_STRATEGY 变量配置为 fetch ,它会在拉取代码后执行 git clean 将未知的文件删除,如果我们将移动后的代码放在原来的位置下就可以做到自动清除没有负作用了。

  • 缓存 vendor 目录

    glide update 会更新 Go 项目依赖,比较耗时,构建有 build test deploy 等多个阶段,缓存 vendor 目录能够会快很多。这些阶段会依次执行,同阶段的多个任务是并行的,可以将 build 阶段的工作目录状态保留到其它阶段,可以用 cache 来实现,也可以将除了 build 阶段以外的其它阶段的 GIT_STRATEGY 置为 none 来实现。

  • 创建 docker 镜像

    安装完 gitlab-runner 后要将 gitlab-runner 用户加入到 docker 用户组,这样才可调用 docker 工具。

    usermod gitlab-runner -a -G docker
    

    不要在 .gitlab-ci.yml 中直接写死 docker 帐号和密码,而是引用环境变量,在 ~gitlab-runner/.bashrc 中设置环境变量

    export DOCKER_REGISTRY=gitlab.xxxxxx.com
    export DOCKER_USER=xxx@xxxxxx.com
    export DOCKER_PASSWORD=xxxxxxxx
    

    docker 镜像按照简单的约定:

    • git 打 tag 时打一个镜像,做为发布镜像

      之前一直想实现仅当 master 分支打 tag 时才创建镜像,但是实现起来会很麻烦,因为 git 的 tag 只是 commit 的引用,与具体的 branch 无关,tags 和 branchs 是平级的概念。

      相关讨论 Update `.gitlab-ci.yml` to support conjunction logic for build conditions (#27818)

    • dev 开头的分支进行代码提交时打一个镜像,做为测试镜像

示例配置:

.gitlab-ci.yml

variables:
  REPO_NAME: gitlab.example.com/mygroup/myproject
  BIN_NAME: mygroup.myproject

before_script:
  - go version
  - protoc --version
  - echo $CI_BUILD_REF
  - echo $CI_PROJECT_DIR
  - if [ ! -d "${CI_PROJECT_DIR}/src/$REPO_NAME" ];
    then
      mkdir -p ${CI_PROJECT_DIR}.src.tmp/$REPO_NAME;
      shopt -s dotglob;
      mv $CI_PROJECT_DIR/* ${CI_PROJECT_DIR}.src.tmp/$REPO_NAME/;
      mv ${CI_PROJECT_DIR}.src.tmp ${CI_PROJECT_DIR}/src;
      echo "${CI_PROJECT_DIR}/src/$REPO_NAME created";
    fi
  - export GOPATH=$CI_PROJECT_DIR
  - cd $GOPATH/src/$REPO_NAME

build:
  stage: build
  variables:
    GIT_STRATEGY: fetch
  script:
    - make

test:
  stage: test
  variables:
    GIT_STRATEGY: none
  script:
    - go test -v

# Build docker image for development when any branch named begin with "dev"
deploy-dev:
  stage: deploy
  variables:
    GIT_STRATEGY: none
  only:
    - /^dev.*/@mygroup/myproject
  except:
    - tags
  script:
    - VERSION=${CI_COMMIT_REF_NAME} make
    - docker build ./ -t ${REPO_NAME}/${BIN_NAME}:${CI_COMMIT_REF_NAME}
    - docker login ${DOCKER_REGISTRY} -u ${DOCKER_USER} -p ${DOCKER_PASSWORD}
    - docker push ${REPO_NAME}/${BIN_NAME}:${CI_COMMIT_REF_NAME}

# Build docker image for production when pushed a tag
deploy:
  stage: deploy
  variables:
    GIT_STRATEGY: none
  only:
    - tags@mygroup/myproject
  script:
    - VERSION=${CI_COMMIT_REF_NAME} make
    - docker build ./ -t ${REPO_NAME}/${BIN_NAME}:${CI_COMMIT_REF_NAME}
    - docker login ${DOCKER_REGISTRY} -u ${DOCKER_USER} -p ${DOCKER_PASSWORD}
    - docker push ${REPO_NAME}/${BIN_NAME}:${CI_COMMIT_REF_NAME}