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[email protected]
    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:
    - [email protected]/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}