学习实践下gitlab cicd,目前公司正在试用。

gitlab安装

1docker run --detach   --privileged=true --hostname 192.168.110.128  --publish 8881:443 --publish 80:80 --publish 8883:22  --name gitlab  --restart always  --volume `pwd`/config:/etc/gitlab  --volume `pwd`/logs:/var/log/gitlab --volume `pwd`/data:/var/opt/gitlab  yrzr/gitlab-ce-arm64v8

gitlab-runner 安装

二进制安装

 1# Download the binary for your system
 2sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm64
 3
 4# Give it permission to execute
 5sudo chmod +x /usr/local/bin/gitlab-runner
 6
 7# Create a GitLab Runner user
 8sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
 9
10# Install and run as a service
11sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
12# Runtime platform   arch=arm64 os=linux pid=3745 revision=81ab07f6 version=16.10.0
13sudo gitlab-runner start
14
15# sudo gitlab-runner stop 

docker方式安装

1docker run -d --name gitlab-runner --privileged=true  --restart always -v `pwd`/config:/home/gitlab-runner/.gitlab-runner -v /var/run/docker.sock:/var/run/docker.sock bitnami/gitlab-runner:latest
2
3# 注册runner 
4 docker run  --rm -v `pwd`/config:/home/gitlab-runner/.gitlab-runner bitnami/gitlab-runner:latest register --non-interactive --executor "docker" --docker-image alpine:latest --url "http://192.168.110.128/"  --registration-token "9Z-84Qi21FkxvKmpkKjv\n" --description "test"  --tag-list "docker,test" --run-untagged="true" --locked="false" --access-level="not_protected"

注册runner ,全局共享runner, project runner

1gitlab-runner register  --url http://192.168.110.128  --token glrt-iVgXJFrDCxgtVJLaywdG

生成的config.toml,注意这里试用了虚拟机的ip, 不然用域名,docker那边dns解析有问题。

volumes : 增加了 /var/run/docker.sock:/var/run/docker.sock 以防万一

 1concurrent = 1
 2check_interval = 0
 3connection_max_age = "15m0s"
 4shutdown_timeout = 0
 5
 6[session_server]
 7  session_timeout = 1800
 8
 9[[runners]]
10  name = "golang"
11  url = "http://192.168.110.128"
12  id = 1
13  token = "glrt-j7hZyxN8skmFn4ZLiKs8"
14  token_obtained_at = 2024-04-20T01:47:03Z
15  token_expires_at = 0001-01-01T00:00:00Z
16  executor = "docker"
17  [runners.docker]
18    tls_verify = false
19    image = "alpine:latest"
20    privileged = false
21    disable_entrypoint_overwrite = false
22    oom_kill_disable = false
23    disable_cache = false
24    volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
25    shm_size = 0
26    network_mtu = 0
27
28[[runners]]
29  name = "mom"
30  url = "http://192.168.110.128"
31  id = 2
32  token = "glrt-Zsow2xwszLieJ-i4uXWD"
33  token_obtained_at = 2024-04-20T03:19:23Z
34  token_expires_at = 0001-01-01T00:00:00Z
35  executor = "docker"
36  [runners.docker]
37    tls_verify = false
38    image = "alpine:latest"
39    privileged = false
40    disable_entrypoint_overwrite = false
41    oom_kill_disable = false
42    disable_cache = false
43    volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
44    allowed_pull_policies = ["always", "if-not-present"]
45    shm_size = 0
46    network_mtu = 0
47
48[[runners]]
49  name = "mom"
50  url = "http://192.168.110.128"
51  id = 3
52  token = "glrt-4wNTBXzS8QNiEAjSp-yK"
53  token_obtained_at = 2024-04-20T07:42:33Z
54  token_expires_at = 0001-01-01T00:00:00Z
55  executor = "docker"
56  [runners.cache]
57    MaxUploadedArchiveSize = 0
58  [runners.docker]
59    tls_verify = false
60    image = "alpine:latest"
61    pull_policy="if-not-present"
62    privileged = false
63    disable_entrypoint_overwrite = false
64    oom_kill_disable = false
65    disable_cache = false
66    volumes = ["/cache"]
67    shm_size = 0
68    network_mtu = 0
  • concurrent : 限制runner能够同时执行多少个作业

初阶关键词

stages

stages 默认提供,如果不指定stages,可以试用这些stage。

  • .pre
  • build
  • test
  • deploy
  • .post

script

1npm_inst:
2  script: npm install
1npm_inst:
2   image: node
3   script:
4     - npm isntall
5     - npm build 

cache

用来缓存依赖包,避免重复执行下载。

全局配置的key

1default:
2  cache:
3    key: "$CI_COMMIT_PEF_SLUG"
4    paths:
5      - binaries/

key不变就会试用本地的缓存

key 可以执向一个文件列表,文件列表没变,key就是没变

 1test-job:
 2   stage: build
 3   cache:
 4   - key: 
 5       files:
 6         - yarn.lock
 7     paths:
 8        - .yarn-cache/
 9   - key:
10       files:
11         - Gemfile.lock
12     paths:
13        - vendor/ruby
14   script:
15   - bundle install --path=vendor
16   - yarn install --cache-folder .yarn-cache
17   - echo 'install done'

cache:policy

默认在开始执行的时候下载缓存,结束的时候上传缓存

policy: pull 跳过上传步骤

policy: push 跳过下载步骤

但是如果我们已经知道,某个 job 只是使用的其他 job 改变的文件,自身并无改变对应路径的文件,那么就不需要进行文件上传操作,采用pull 策略即可。

反过来,某个 job 不依赖于其他 job 改变的文件,自身改变的文件被其他 job 所依赖,那么就不需要在 job 开始前进行文件下载操作,采用push 策略。这样减少了不必要的操作,在一定程度上节约了时间。

如 php 项目一开始要构建 vendor 包,那么cache 就是 push 策略, 在构建镜像阶段需要这个 cache,则进行 pull策略。

images

runner执行器是 docker的前提才有效

1use_image_job:
2  image: node:12.21.0
3  script: npm -v

tags

tags 用于指定用哪个runner来执行当前作业。

runner 配置了tag

1tags_example:
2  tags:
3    - laravel
4  script: echo "hello fizz"

variables

定义变量

1variables:
2  USER_NAME: 'FIZZ'
3
4test:
5 variables:
6    USER_NAME: 'ZK'
7 script: echo 'hello' $USER_NAME
8# 输出ZK ,局部变量

在ci/cd设置中定义变量

也可以针对 group 进行设置变量

预定义变量

  • CI_COMMIT_BRANCH : 提交分支的名称
  • CI_COMMIT_REF_NAME : 正在构建项目的分支或tag名
  • CI_JOB_NAME: 作业名称
  • CI_PROJECT_NAME: 项目名称

when

  • on_sucess : 默认值, 在此前之前的阶段的在其他作业都成功执行后,才会触发当前的作业
  • on_failure: 当之前的阶段有作业失败,才会触发当前的作业
  • always : 不管之前的作业如何, 都被执行
  • manual : 该作业只能被手动执行
  • nerver: 不被执行
  • deplayed: 延迟执行
    • start_in 来定义延迟多久时间

手动执行

1manual_job:
2  script: echo 'I think therefor i am'
3  when: manual
1fail_job:
2  script: echo 'everything is going to be alright'
3  when: on_failure

artifacts

将构建出的文件保存起来,

cache 一般用于项目的依赖保,artifacts常用于输出的一些文件、文件夹。 如dist目录,jar包、测试报告。cache可以被手动清空,而artifacts是会过期的。

 1upload:
 2  script: npm install build
 3  artifacts:
 4    paths:
 5      - /dist
 6      - *.jar
 7    exclude:
 8      - binaries/**/*.0
 9    expire_in: 1 week
10    name: "$CI_JOB_NAME"

before_script

before_script 必须是个数组,执行时机是 script之前,artifacts恢复之后。可以定义全局default 关键中定义全局before_script,定义后在每个作业中执行。

after_script

after_script是独立的执行环境,before_script、script中的变量是无权访问的。作业失败也会被执行。如果作业取消或超时,则不会被执行。

only与except

控制作业是否被执行,或当前作业的时机。 only满足条件的时候才会被执行作业。 except不常用,排除某个条件其他都会被执行。

如果没定义only,except或者rules修饰。那么作业将被默认被only修饰,值为tags或者branchs.

只有修改了test分支的代码 ,才会被执行。

1only_example:
2  script: deploy test 
3  only:
4    - test 

only与 except 下可以配置 4 种值

  • refs
  • variables
  • changes
  • kubernetes

only:refs ,表明作业只有在某个分支或某一个流水线类型下才会被添加到流水线。

 1test:
 2  script: test
 3  only:
 4    - test 
 5
 6build:
 7 script:  build test
 8 only:
 9  refs:
10    - test 
11
12deploy:
13  script: deploy test
14  only:
15    refs:
16      - tags
17      - schedules

test,build 其实都一样只要 test 分支发生改变后,则被执行

deploy阶段是需要创建了新的 tag,或者 使用定时部署,则会被执行。

only:variables

1test:
2  only:
3    variables:
4      - $USER_NAME === "CRMAO"

only:changes

1test:
2  only:
3    changes:
4      - Dockerfile
5      - fe/**/*

当修改 Dockerfile或者 fe 目录下 则被执行

only:kubernetes

1deploy:
2 script: deploy test
3 only:
4   kubernetes: active

只有项目中存在可用的kubernetes服务时,作业才会被执行。

中阶关键词

coverage

配置一个正则表达式提取作业日志中输出的代码覆盖率,提取后可以将之展示到代码分支上。

1test:
2 script: npm test
3 coverage: '/Code coverage: \d+\.\d+/'

如果该作业输出Code coverage: 67.89 这种格式的日志,会被 gitlab ci/cd 记录下来。 只取最后一条。

dependencies

dependencies关键词定义当前作业下载哪些前置作业的artifacts ,只能是上一阶段的值。 可以定义当前作业下载哪些前置的 artifacts.或者不下载的 artifacts.可以是一个数组,

如果是空数组表示不下载任何artifacts。 默认所有 artifacts 在下个接口都是会被默认下载(这样很慢)

 1stages:
 2  - build
 3  - deploy
 4
 5build_windows:
 6  stage: build
 7  script:
 8    - echo "start build on windows"
 9  artifacts:
10    paths:
11    - binaries  
12  
13build_mac:
14  stage: build
15  script:
16    - echo "start build on mac"
17  artifacts:
18    paths:
19    - binaries
20
21deploy_mac:
22  stage: deploy
23  dependencies:
24    - build_mac
25# 只下载 build_mac 作业的 artifacts
26release_job:
27  stage: deploy
28  script: echo 'release version'
29  dependencies: []
30
31# 不下载任何 artifacts

allow_failure

设置当前作业失败流水线是否继续运行

默认false, 不允许,失败则停止流水线。

1test2:
2  stage: test
3  allow_failure: true

extends

可以用于继承一些配置模板

.开头的作业,是隐藏作业,任何时候不会被执行。

 1.test:
 2 script: npm lint
 3 stage: test
 4 only:
 5  refs:
 6   - branches
 7test_job:
 8 extends: .test
 9 script: npm test
10 only:
11  variables:
12   - $USER_NAME  

extends后的作业

1test_job:
2  stage: test
3  script: npm test
4  only:
5   refs:
6    - branches
7   variables:
8    - $USER_NAME

default 、inherit

全局关键词,不能定义在具体的作业中。

default 中设置的所有值自动合并到流水线所有的作业中.

可以使用 default 设置的属性有 after_script,artifacts,before_script,cache,image,interruptible,retry,services,tags,timeout

inherit: 不拿全局的,或者指定拿具体的

 1vriables:
 2  NAME: "this 1"
 3  AGE: "2"
 4  SEX: "MAN"
 5
 6# 整个都不用 default
 7test: 
 8  script: echo 'hello'
 9  inherit:
10    # 不会继承 default 的
11    default: false 
12    # 全局变量不会被引入
13    variables: false 
14
15deploy:
16  script: echo 'hello'
17  inherit:
18    # 指定合并 retry,image属性
19    default:
20      - retry
21      - image
22    #  指定拿 下面 2 个变量
23    variables:
24      - NAME
25      - AGE

interruptible

新流水线把老的流水线给取消掉。 只要有一个 job 不能被取消的 job 运行了。 那么新流水线是无法取消旧流水线的。

如果需要新的把旧的给取消掉,那么可以在 default 关键词下设置 interruptible: true

needs

一个作业使用 needs 设置依赖作业后,只要依赖的作业完成,它就会运行,不需要等上阶段作业完成。 减少总运行时间。

retry

retry 关键词用于设置作业运行失败时的重试次数。

1build:
2 script: npm build
3 retry:
4  max: 2

timeout

设置一个作业的超时时间,超过该时间,流水线旧会被标记失败。

3600seconds,60minutes,one hour,3h,30m等

1build:
2 script: npm build
3 timeout: 1h 

高阶关键词

rules,workflow,trigger,include,resource_group,environment,services,secrets 和 dast_configurtion

rules

决定作业是否运行。

rules:if

条件判断,可以配置多个表达式,当表达式的结果为 true 时,该作业将被运行。

1test_job:
2  script: echo "hello ,zy"
3  rules:
4    - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/ && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME != $CI_DEFAULT_BRANCH'
5      when: never
6    - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature/'
7      when: manual
8      allow_failure: true 
9    - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME'

如果第一个 if 命中, 则不会运行,配置了 when:never

rules:changes

文件改变则运行

1docker_build:
2  script: docker build -t my-image:$CI_COMMIT_REF_SLUG
3  rules:
4    - changes:
5        - Dockerfile

rules:exists

文件存在则运行

1docker_build:
2  script: docker build -t my-image:$CI_COMMIT_REF_SLUG
3  rules:
4    - exists:
5        - Dockerfile

rules:allow_failure

同 rule 第一个案例。 allow_failure:true 作业失败,流水线不会停止运行

rules:variables

对变量进行操作

 1rules_var:
 2  variables:
 3    DEPLOY_VARIABLE: "default-deploy""
 4  rules:
 5    - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
 6      variables:
 7         DEPLOY_VARIABLE: "default-product" 
 8    - if: $CI_COMMIT_REF_NAME == 'feature'
 9      variables:
10         IS_A_FEATURE: "true"
11  script:
12    - echo "run script with $DEPLOY_VARIABLE as an argument"
13    - echo "run another if $IS_A_FEATURE exists"

workflow

workflow是个全局关键字,可以配置多个规规则来限定流水线是否运行。如果命中了一条规则,流水线则会运行

1workflow:
2  rules:
3    - if: $CI_COMMIT_MESSAGE =~ /-draft$/
4      when: never
5    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
6      variables:
7        IS_A_MR: "true"
8    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

trigger

trigger关键词用于创建下游流水线,即在流水线中再触发新的流水线,适合于构建部署复杂的项目。

如果一个作业配置了 trigger,那么该作业的一些属性不能被定义如 script、after_script、before_script

能够配置的属性只有以下几个:stage,allow_failure,needs,rules,except,only,when,extends.

 1trigger-other-project:
 2 stage: deploy
 3 trigger: 
 4   project: my/depoyment
 5   branch: stable-2022
 6trigger-child-pipeline:
 7  stage: deploy
 8  trigger:
 9    include: /path/to/microservie_a.yaml
10   # strategy: depend

第一个作业触发my/deployment项目的stable-2022分支,如果不定义 branch 则触发该项目的默认分支

第二个作业触发下游的流水线。 上游流水线不会等待下游完成。如果需要完成后再运行,可以在作业上配置strategy: depend.

include

include 关键词用于引用其他流水线,可以引用其他流水线中的作业。

include:local

用于引入本地文件,且只能引入当前项目的文件。无法使用 git submodule 的路径。

1include:
2- local: '/templates/.fe-ci-template.yml'

批量引入 include:‘fe/*.yml’

如果要引入一盒目录下所有的 yaml 文件,并引入该目录所有子级目录下的 yaml 文件 include:‘fe/**.yml’

include:file

用于引入其他项目文件。如果有群组,那么需要加上群组路径。

 1include:
 2  - project: 'fe-group/my-project'
 3    ref: main
 4    file: 'fe/my-job.yml'
 5  - project: 'test-group/my-project'
 6    ref: v1.0.0
 7    file: '/templates/.gitlab-ci-template.yml'
 8  - project: 'be-group/my-project'
 9    ref: 7871238238238hdsisidsds  # git sha
10    file:
11      - '/template/.gitlab-ci-template.yml'
12      - '/templates/.test.yml'

include:template

用于引入官方模板文件

golang ci实践

目前只有 build 阶段,只是为了演示大体怎么使用,构建出镜像后,还需要部署,因为这个每个公司都不一样,可能是上k8s,可能是走负载均衡器,有的需要改 nginx 端口等,这里不做演示。

harbor仓库安装

具体见 https://www.crblog.cc/cloud-native/devops-basic.html

地址 192.168.56.83:8080

创建一个项目

gocicd基础镜像准备

cicdDockerfile 内容如下

1FROM golang:1.21-alpine
2ENV CGO_ENABLED=0 GOOS=linux GO111MODULE=on GOPROXY=https://goproxy.cn/,direct  TZ=Asia/Shanghai 
3RUN set -eux && sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
4RUN apk update && apk add --no-cache tzdata git sshpass openssh make docker
5RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2
6RUN echo "    StrictHostKeyChecking no" >> /etc/ssh/ssh_config && \
7    echo "    UserKnownHostsFile /dev/null" >> /etc/ssh/ssh_config
1docker build -t go1.21cicd:latest -f cicdDockerfile .
2docker tag go1.21cicd:latest 192.168.56.83:8080/golang/gitlab-cicd-go1.21:latest
3docker login --username=admin 192.168.56.83:8080
4# 输入密码
5docker push 192.168.56.83:8080/golang/gitlab-cicd-go1.21:latest

流水线 yml 编写

就是先进行镜像构建,然后推送到harbor 仓库 示例

 1variables:
 2  DOCKER_IMAGE_NAME: godemo
 3  IMAGE_NAME: 192.168.56.83:8080/golang/${DOCKER_IMAGE_NAME}:${CI_COMMIT_REF_SLUG}-${CI_PIPELINE_ID}
 4
 5image: 192.168.56.83:8080/golang/gitlab-cicd-go1.21:latest
 6
 7stages:
 8  - build
 9
10# 根据down.sh构建镜像包
11image_build:
12  stage: build
13  before_script:
14    - docker ps
15    - docker login --username=admin -p harbor12345 192.168.56.83:8080
16    - ls -al
17  script:
18    - docker build -f docker/Dockerfile -t $DOCKER_IMAGE_NAME .
19    - docker tag $DOCKER_IMAGE_NAME $IMAGE_NAME
20    - docker push $IMAGE_NAME
21  after_script:
22    - docker rmi $DOCKER_IMAGE_NAME
23    - docker rmi $IMAGE_NAME
24  tags:
25    - docker
26  only:
27    - develop
28    - main

docker/Dockerfile 内容如下

 1FROM golang:1.21-alpine AS build
 2ENV CGO_ENABLED=0 GOOS=linux GO111MODULE=on GOPROXY=https://goproxy.cn/,direct  TZ=Asia/Shanghai
 3RUN set -eux && sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
 4RUN apk update && apk add --no-cache tzdata git
 5
 6WORKDIR /app
 7
 8COPY ./go.mod ./
 9COPY ./go.sum ./
10
11RUN go mod tidy
12
13COPY . ./
14
15RUN  go build -o godemo main.go && go clean -cache
16
17FROM alpine:3.15.0
18RUN set -eux && sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
19RUN apk update && apk add --no-cache tzdata
20ENV TZ "Asia/Shanghai"
21WORKDIR /app
22COPY --from=build /app/godemo /app/godemo
23RUN chmod +x /app/godemo
24
25EXPOSE 8000
26
27CMD ["/app/godemo"]

提交代码merge request后,就会执行流水线

可见镜像已经推送成功