gitlab可以做什么
大部分人可能认为gitlab只能托管代码,其实这只是冰山一角,gitlab算是个DevOps的一个实现,功能有这些
有兴趣的同学可以研究一下
gitlab CI/CD
本次分享打算用萌新 A 和老司机 B 对话的方式来写,讲述的是A如何从0开始,一步一步爬坑,写出一个完整的CI/CD文件,
看下这种方式会不会更好理解。
本次分享用的runner是 docker executor
开始了
A: B哥,我想用gitlab来做CI/CD,这玩意以前没有用过,听说你会,可以可以带带我吗?
B: 可以啊,首先国际惯例,先来个 Hello World,在项目根目录下先新建一个.gitlab-ci.yml,给任务取一个霸气一点的名字,就叫霸气的名字吧,用script来指定你要执行的命令,写好后,把代码push到gitlab
.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哥,这些命令本地可以执行,但push到gitlab上就不行了,说没有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: 我改成这样了,但是怎么执行的时候,是3个job同时执行了呢?
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
B: 这个问题你应该用stages和stage来解决
stages是字义执行顺序,默认值是:
stages:
- build
- test
- deploy
stage用来指定job属于阶段,必须是stages里面定义了的,如果用了stages中没有定义的,会报错,也许你会问,前面的job都没有写stage,怎么没有报错呢?那是因为stage的默认值是test
执行顺序是按stages定义的顺序来执行的,不同阶段串行执行,相同阶段并行执行
默认的执行顺序是:
build -> test -> deploy
假如build阶段有多个job,因为他们的stage相同,所以这些job同时执行,等所有build阶段的job都执行完了,才会执行下一个阶段test的job
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
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脚本执行前,会删掉,你看,这里不是告诉你了吗
A: 那怎么整?
B: 不要急,来,告诉你一个新指令,叫artifacts,这个是用来保存制品给后面的job使用。
pipeline执行完成后,如果有artifacts指令,一般右边3点那里会有制品的下载链接,可以下载下来,看构建的对不对
你的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 docker的IMAGE_NAME,到deploy阶段,变成空的了,导致我部署失败了,怎么整,我明明设置了啊
B: 小A啊,我不是说了吗,gitlab会在每个job执行前,重置环境的吗?当然包括这些环境变量啊
A: 那怎么整?
B: 还是用artifacts指令,这个指令下载有个reports:dotenv配置,指定一个文件,这个文件的内容会变成后续阶段的环境变量,可直接引用,最适合用来传递一些数据了,但是不能太多,最多20个,不过也足够了,dotenv的格式跟ini和property一样,是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镜像的版本没有变,导致的,但是这玩意我也不能每次更新的时候,都改下吧,万一哪次发版本我忘记改,那不死定了。
而且我想镜像的版本包含构建时间、pipeline和job信息,如果线上有问题,我可以快速定位到这个镜像的出处和当时构建的一些信息,怎么样?能整吗?
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 这个指令可以很好的解决构建模板共享的问题,其它项目引用模板并配置少量的参数就可以快速构建、发布项目,用模板主要解决以下问题
- 把整个发布标准化,并集中管理,减少维护成本,如果要调整发布过程,可以快速修复,不需要一个项目一个项目改
- 防止
ctrl+c ctrl+v文件后,因为对.gitlab-ci.yml格式不熟悉,出现漏改或错改配置的情况,导致项目构建、发布失败,浪费太多调试时间
-
常用指令基本就这些,
gitlab的指令还有很多,文档在这里 -
.gitlab-ci.yml文件写好后,想看下有没有问题,可以在每个项目的CI/CD -> CI lint中检测一下 -
runner的作用域有shared,group,specific,注册的时候大家根据实际情况来注册注册完成后,修改下
/etc/gitlab-runner/config.toml的concurrent的值,默认是1,表示runner只能同时执行一个job的构建,可根据服务器配置做适当调整如果你的构建比较特殊,不想跟其它项目共用,你用该项目
Settings > CI/CD > Runners下的token来注册,注册的runner就是specific如果你只想你们项目组的项目可以用,你用该项目组
Settings > CI/CD > Runners下的token来注册,注册的runner就是group,该组下面的所有项目都可以用shared表示所有项目都可以用,目前注册了一个,用的是docker executor,用的时候不用tags指令 -
建议大家注册
runner的时候,尽量用docker executor,为什么?-
为了保证构建环境的稳定,防止项目构建过程中破坏其它项目的构建环境,比如:A
项目需要python 2,B项目需要python 3`,嗯,互相伤害吧 -
构建环境对宿主机依赖比较小,只需要安装
docker就好了
-
-
variables可定义的地方太多,这里就不细讲了,有兴趣的同学可以私聊我,优先级如下:
好了,这次就先写这些,希望能让大家对
gitlab有个新的认识,如果大家都能学会写.gitlab-ci.yml,那自然是最好的
全文完