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
,那自然是最好的
全文完