gitlab cicd入坑指南

1,663 阅读8分钟

gitlab可以做什么

大部分人可能认为gitlab只能托管代码,其实这只是冰山一角,gitlab算是个DevOps的一个实现,功能有这些

1.png

有兴趣的同学可以研究一下


gitlab CI/CD

本次分享打算用萌新 A 和老司机 B 对话的方式来写,讲述的是A如何从0开始,一步一步爬坑,写出一个完整的CI/CD文件, 看下这种方式会不会更好理解。

本次分享用的runnerdocker executor


开始了


A: B哥,我想用gitlab来做CI/CD,这玩意以前没有用过,听说你会,可以可以带带我吗?

B: 可以啊,首先国际惯例,先来个 Hello World,在项目根目录下先新建一个.gitlab-ci.yml,给任务取一个霸气一点的名字,就叫霸气的名字吧,用script来指定你要执行的命令,写好后,把代码pushgitlab

.gitlab-ci.yml内容如下

霸气的名字:
  script:  echo 'Hello World'

A: 这么简单的?但是如果我的script比较多,这样写成一行是不是不太好啊

B: 哦,script可以是个数组,随便你写多少个,比如:

霸气的名字:
  script:  
    - echo 'Hello World'
    - date

A: 原来如此,我找个go项目来写下

go job:
  script:  
    - ./deploy.sh

deploy.sh内容如下

#!/bin/bash
go version
docker version
kubectl version

A: B哥,这些命令本地可以执行,但pushgitlab上就不行了,说没有go命令

B: 你特喵这写的啥啊?你为什么要写成一个shell文件呢,我可以找出100个理由来反驳你

1. 你把所有命令都写到`shell`里面,执行脚本的时候,我都不知道现在执行到哪了,也不知道,每一个命令执行了多久
2. 每次执行命令的时候,都只能从头开始,不能从指定命令开始,你想下,有的时候,我已经有`docker`镜像了,现在只想执行最后一个任务`kubectl`命令,可以吗?不可以吧
3. 所有命令都只能串行执行,像`go build``go test`其实是可以并行执行的
4. 剩下97个理由还没想好

提示没有go命令,是因为这个runner用的是docker executor,我设置的默认镜像是alpine,你如果想用有go命令的,可以用image指令指定镜像名称,go可以用golang镜像,最好显示指定版本,比如:image: golang:1.14

如果不显示指定,默认用的latest版,万一哪天这个latest镜像更新了,这里就会用最新的镜像来构建,这样就无法保证每次构建环境都一样,不出问题还好,出了问题就脑壳疼,所以最好显示指定镜像版本,保证每次构建都是一样的环境

A: 我改好了,go命令是有了,但是这里又说没有docker命令

go job:
  image: golang:1.14
  script:  
    - go version
    - docker version
    - kubectl version

B: 你把它拆分成多个job,每个job用一个有这些命令的docker镜像来构建

A: 我改成这样了,但是怎么执行的时候,是3job同时执行了呢?

build go:
  image: golang:1.14
  script:    
    - go version
   
build docker:
  image: docker:19.03.12
  script:    
    - docker version

deploy:
  image: roffe/kubectl:v1.13.2
  script: 
    - kubectl version

7.png

B: 这个问题你应该用stagesstage来解决

stages是字义执行顺序,默认值是:

stages:
    - build
    - test
    - deploy

stage用来指定job属于阶段,必须是stages里面定义了的,如果用了stages中没有定义的,会报错,也许你会问,前面的job都没有写stage,怎么没有报错呢?那是因为stage的默认值是test

执行顺序是按stages定义的顺序来执行的,不同阶段串行执行,相同阶段并行执行

默认的执行顺序是: build -> test -> deploy

假如build阶段有多个job,因为他们的stage相同,所以这些job同时执行,等所有build阶段的job都执行完了,才会执行下一个阶段testjob

A: 改了下,这样就会顺序执行了

build go:
  image: golang:1.14
  stage: build
  script:    
    - go  version

build docker:
  image: docker:19.03.12
  stage: test
  script:    
    - docker version

deploy:
  image: roffe/kubectl:v1.13.2
  stage: deploy
  script: 
    - kubectl version

8.png

B: 你写的啥啊,你就不能自己定义一个stages吗,非要用默认的?语义也很重要啊,你看docker build明明是打包docker镜像,你stage用了个test,再改下

A: 改好了,看着好像可以用样子,我用一个项目来实际用下

stages:
  - build
  - docker
  - deploy
  
build go:
  image: golang:1.14
  stage: build
  script:    
    - go version

build docker:
  image: docker:19.03.12
  stage: docker
  script:      
    - docker version

deploy:
  image: roffe/kubectl:v1.13.2
  stage: deploy
  script: 
    - kubectl version
     

几分钟后

A: B哥,我现在这样写的,build go阶段我编译输出了main,用ls 看了下,也确实有,但是到build docker的时候,又没有了

stages:
  - build
  - docker
  - deploy
  
build go:
  image: golang:1.14
  stage: build
  script:    
    - go mod vendor
    - go build -o main main.go
    - go test
    - ls

build docker:
  image: docker:19.03.12
  stage: docker
  script:    
    - ls
    - IMAGE_TAG='example:v1.2.3'
    - docker build -t $IMAGE_TAG .    
    - docker push $IMAGE_TAG

deploy:
  image: roffe/kubectl:v1.13.2
  stage: deploy
  script: 
    - kubectl --kubeconfig k8s_config -n omms-qa set image deployment/example-deploy example=$IMAGE_TAG

B: 哦,这个问题啊,gitlab执行每个阶段前,会清除上一阶段生成的文件和目录,保证每个阶段的构建环境都是干净的,所以你输出的main文件,在build docker脚本执行前,会删掉,你看,这里不是告诉你了吗 8.png

A: 那怎么整?

B: 不要急,来,告诉你一个新指令,叫artifacts,这个是用来保存制品给后面的job使用。

pipeline执行完成后,如果有artifacts指令,一般右边3点那里会有制品的下载链接,可以下载下来,看构建的对不对

10.png

你的build go改成这样

build go:
  image: golang:1.14
  stage: build
  script:    
    - go mod vendor
    - go build -o main main.go
    - go test
  artifacts:
    paths:
      - main

A: B哥,B哥,现在又有一个问题,build dockerIMAGE_NAME,到deploy阶段,变成空的了,导致我部署失败了,怎么整,我明明设置了啊

B: 小A啊,我不是说了吗,gitlab会在每个job执行前,重置环境的吗?当然包括这些环境变量啊

A: 那怎么整?

B: 还是用artifacts指令,这个指令下载有个reports:dotenv配置,指定一个文件,这个文件的内容会变成后续阶段的环境变量,可直接引用,最适合用来传递一些数据了,但是不能太多,最多20个,不过也足够了,dotenv的格式跟iniproperty一样,是key value形式的,像这样

IMAGE_TAG=example:202108281957

你的build docker这样改下

build docker:
  image: docker:19.03.12
  stage: docker
  script:    
    - IMAGE_TAG='example:v1.2.3'
    - docker build -t $IMAGE_TAG .    
    - docker push $IMAGE_TAG
    - echo "IMAGE_TAG=$IMAGE_TAG" >> variables.env
  artifacts:
    reports:
      dotenv: variables.env

A终于成功了,流下了激动的泪水

刚过2分钟,A又来了

A: B哥,我刚触发了一下pipeline,发现线上的k8s容器没有更新,查了下原因 ,是docker镜像的版本没有变,导致的,但是这玩意我也不能每次更新的时候,都改下吧,万一哪次发版本我忘记改,那不死定了。

而且我想镜像的版本包含构建时间pipelinejob信息,如果线上有问题,我可以快速定位到这个镜像的出处和当时构建的一些信息,怎么样?能整吗?

B: 可以,gitlab有很多系统变量,它通过环境变量的方式提供,你可以看这里,所有CI_开头的环境变量,都是gitlab提供的。

pipeline id对应的环境变量是CI_PIPELINE_ID job id对应的环境变量是CI_JOB_ID

所以你的build docker这样改下

build docker:
  image: docker:19.03.12
  stage: docker
  script:    
    - BUILD_TIME=`date '+%Y%m%d%H%M'`
    - IMAGE_TAG="example:$BUILD_TIME-$CI_PIPELINE_ID-$CI_JOB_ID"
    - docker build -t $IMAGE_TAG .    
    - docker push $IMAGE_TAG
    - echo "IMAGE_TAG=$IMAGE_TAG" >> variables.env
  artifacts:
    reports:
      dotenv: variables.env

A: 终于成功了,多谢B

B: 谢就不用了,说吧,晚上哪里吃[微笑.gif]

故事到此结束


再介绍一些常用指令

only 用来判断的,条件为真会执行流水线,为假则跳过,有些job你只想在master 分支上执行,可以这样写

only:
  - master

except 用法跟only一样,只是条件为真的时候,会跳过流水线

tags 用来指定runner服务器

tags:
  - docker

services 可以很好的解决单元测试的问题,现在大家测试DB的时候,都是用各种mock功能,我是不太喜欢用mock,现在好了,你可以用services创建一个DB,直接连接到DB测试,再也不用mock了,它的实现其实就是docker启两个容器,然后把DB容器link到构建容器中

include 这个指令可以很好的解决构建模板共享的问题,其它项目引用模板并配置少量的参数就可以快速构建、发布项目,用模板主要解决以下问题

  1. 把整个发布标准化,并集中管理,减少维护成本,如果要调整发布过程,可以快速修复,不需要一个项目一个项目改
  2. 防止ctrl+c ctrl+v文件后,因为对.gitlab-ci.yml格式不熟悉,出现漏改或错改配置的情况,导致项目构建、发布失败,浪费太多调试时间

  1. 常用指令基本就这些,gitlab的指令还有很多,文档在这里

  2. .gitlab-ci.yml文件写好后,想看下有没有问题,可以在每个项目的CI/CD -> CI lint中检测一下 10.png

  3. runner的作用域有sharedgroupspecific,注册的时候大家根据实际情况来注册

    注册完成后,修改下/etc/gitlab-runner/config.tomlconcurrent的值,默认是1,表示runner只能同时执行一个job的构建,可根据服务器配置做适当调整

    如果你的构建比较特殊,不想跟其它项目共用,你用该项目 Settings > CI/CD > Runners下的token来注册,注册的runner就是specific

    如果你只想你们项目组的项目可以用,你用该项目组 Settings > CI/CD > Runners下的token来注册,注册的runner就是group,该组下面的所有项目都可以用

    shared表示所有项目都可以用,目前注册了一个,用的是docker executor,用的时候不用tags指令

  4. 建议大家注册runner的时候,尽量用docker executor,为什么?

    1. 为了保证构建环境的稳定,防止项目构建过程中破坏其它项目的构建环境,比如:A项目需要python 2B项目需要python 3`,嗯,互相伤害吧

    2. 构建环境对宿主机依赖比较小,只需要安装docker就好了

  5. variables可定义的地方太多,这里就不细讲了,有兴趣的同学可以私聊我,优先级如下:

10.png

好了,这次就先写这些,希望能让大家对gitlab有个新的认识,如果大家都能学会写.gitlab-ci.yml,那自然是最好的


全文完