GitLab CI/CD 自动化构建与发布实践

流程介绍

CI/CD 是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法。CI/CD 的核心概念是持续集成、持续交付和持续部署。这篇文章中,我将会介绍基于 GitLab CI/CD 的自动化构建与发布实践。如下图所示,整个流程将分为几个部分:

  • 1.首先开发人员在本地完成项目的开发之后,将代码推送到 Gitlab 仓库中。
  • 2.当代码提交到 Gitlab 仓库时,会触发 Pipeline,Gitlab Runner 会根据 .gitlab-ci.yml 配置文件运行 Pipeline 中各阶段的任务。我总共定义了 3 个阶段:compile,build,deploy。
  • 3.在 compile 阶段,Gitlab Runner 将项目编译成 jar 包,使用 MinIO 作为缓存,首次编译项目时会从 Maven 官网拉取依赖,之后会将依赖压缩后上传至 MinIo,在下一次编译时就可以直接从 MinIO 下载依赖文件。
  • 4.在 build 阶段,Gitlab Runner 使用在 compile 阶段编译生成的 jar 包构建 Docker 镜像,并将镜像推送至镜像仓库。
  • 5.在 deploy 阶段,Gitlab Runner 使用构建好 Docker 镜像在 Kubernetes 集群中部署应用。

Gitlab CI/CD

GitLab 介绍

GitLab 是一个利用 Ruby on Rails 开发的开源应用程序,实现一个自托管的 Git 项目仓库,可通过 Web 界面进行访问公开的或者私有的项目。它拥有与 GitHub 类似的功能,能够浏览源代码,管理缺陷和注释。可以管理团队对仓库的访问,它非常易于浏览提交过的版本并提供一个文件历史库。

GitLab CI/CD 介绍

Gitlab CI/CD 是一个内置在 GitLab 中的工具,用于通过持续方法进行软件开发。

  • 持续集成(Continuous Integration):频繁地(一天多次)将代码集成到主干。让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过自动化测试。
  • 持续交付(Continuous Delivery):频繁地将软件的新版本,交付给质量团队或者用户,以供评审。如果评审通过,代码就进入生产阶段。持续交付可以看作持续集成的下一步。它强调的是,不管怎么更新,软件是随时随地可以交付的。
  • 持续部署(continuous Deployment):代码通过评审以后,自动部署到生产环境。是持续部署是持续交付的下一步,持续部署的目标是,代码在任何时刻都是可部署的,可以进入生产阶段。

GitLab Runner 介绍

GitLab Runner 用于执行 Gitlab CI/CD 触发的一系列作业,并将结果发送回 Gitlab。GitLab Runner 可以在 Docker 容器内运行或部署到 Kubernetes 集群中。

Pipeline

Pipeline 中文称为流水线,是分阶段执行的构建任务。如:安装依赖、运行测试、打包、部署开发服务器、部署生产服务器等流程,合起来称为 Pipeline。

Stage

Stage 表示构建阶段,可以理解为上面所说安装依赖、运行测试等环节的流程。我们可以在一次 Pipeline 中定义多个 Stage。

Job

Job 表示构建的作业(或称之为任务),表示某个 Stage 里面执行的具体任务。我们可以在 Stages 里面定义多个 Jobs。

Pipeline,Stage 和 Job 的关系可以用下图表示。

以 Gitlab 中的某个实际的 Pipeline 为例解释 Pipeline,Stage,Job 的含义,具体请看下图。

MinIO 介绍

MinIO 是一款分布式,高性能的对象存储服务,专为大型私有云环境而设计。MinIO 兼容 Amazon S3 对象存储接口,非常适合存储大容量的非结构化数据,例如图片、视频、日志文件、镜像等等。本文将使用 MinIO 作为编译 Springboot 项目时使用的缓存,首次编译项目时会从 Maven 官网拉取依赖,之后会将依赖压缩后上传至 MinIo,在下一次编译时就可以直接从 MinIO 下载依赖文件。

环境搭建

前提条件

  • 部署好一套 Kubernetes 集群。
  • 安装好 Helm 工具,关于 Helm 安装可以参考 安装 Helm

MinIO 部署

Helm 是 Kubernetes 生态系统中的一个软件包管理工具,方便我们快速部署应用。这里选择使用 Helm 在 Kubernetes 集群中部署 MinIO。

添加 Helm 仓库。

helm repo add minio https://helm.min.io/

使用以下命令安装 Helm。设置用户名为 admin,密码为 admin123456,在 minio 命名空间中部署。

helm install minio \
--set accessKey=admin,secretKey=admin123456 \
--namespace minio --create-namespace \
minio/minio

#返回结果
minio/minio
NAME: minio
LAST DEPLOYED: Wed Aug 18 13:23:45 2021
NAMESPACE: minio
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Minio can be accessed via port 9000 on the following DNS name from within your cluster:
minio.minio.svc.cluster.local

To access Minio from localhost, run the below commands:

  1. export POD_NAME=$(kubectl get pods --namespace minio -l "release=minio" -o jsonpath="{.items[0].metadata.name}")

  2. kubectl port-forward $POD_NAME 9000 --namespace minio

Read more about port forwarding here: http://kubernetes.io/docs/user-guide/kubectl/kubectl_port-forward/

You can now access Minio server on http://localhost:9000. Follow the below steps to connect to Minio server with mc client:

  1. Download the Minio mc client - https://docs.minio.io/docs/minio-client-quickstart-guide

  2. Get the ACCESS_KEY=$(kubectl get secret minio -o jsonpath="{.data.accesskey}" | base64 --decode) and the SECRET_KEY=$(kubectl get secret minio -o jsonpath="{.data.secretkey}" | base64 --decode)

  3. mc alias set minio-local http://localhost:9000 "$ACCESS_KEY" "$SECRET_KEY" --api s3v4

  4. mc ls minio-local

Alternately, you can use your browser or the Minio SDK to access the server - https://docs.minio.io/categories/17

为了在本地电脑可以访问到 MinIO 的 Web 界面,使用以下命令开启端口转发。你也可以选择通过 NodePort 或者其他方式将 MinIO 服务暴露到集群外部。

export POD_NAME=$(kubectl get pods --namespace minio -l "release=minio" -o jsonpath="{.items[0].metadata.name}")
kubectl port-forward $POD_NAME 9000 --namespace minio

浏览器输入 http://localhost:9000 访问 MinIO 界面。用户名:admin,密码 admin123456,是我们前面用 helm install 安装 minio 时设置的。

创建一个 Bucket,命名为 gitlab-runner-cache-maven 用于存放编译项目的依赖文件。

Gitlab Runner 部署

创建 Secret 保存 MinIO 的用户名和密码,之后 Gitlab Runner 容器会使用这个这个用户名和密码来登录 MinIO。

kubectl create secret -n acp generic s3access \
--from-literal=accesskey=admin \
--from-literal=secretkey=admin123456

添加 Gilab Helm 仓库,并下载 Gitlab Runner Helm 资源文件。

helm repo add gitlab https://charts.gitlab.io
helm pull gitlab/gitlab-runner --untar

编辑 gitlab-runner 目录下的 values.yaml 文件,有以下几个配置需要修改。

  • gitlabUrl:设置 Gitlab 的 IP 地址。
  • runnerRegistrationToken:设置 Gitlab Runner 注册的 token。进入项目 -> Settings -> CI/CD -> Runners settings 查看注册 Gitlab Runner 所需的 registration token。
  • cache: 设置缓存。
# gitlab IP 地址
gitlabUrl: http://gitlab ip地址/

# 注册 gitlab runner 的 token
runnerRegistrationToken: "o_4r2uvptQYmmr79e2uF"

runners:
# 设置缓存
  cache:
    ## General settings
    cacheType: s3
    cachePath: "gitlab-runner-elasticsearch-api" # 缓存路径,gitlab runner 会自动在 bucket 下创建该目录
    cacheShared: true

    ## S3 settings
    s3ServerAddress: minio.minio.svc.cluster.local:9000 # kubernetes 集群 clusterip service 访问的地址
    s3BucketName: gitlab-runner-cache-maven # bucket 名字
    s3BucketLocation:
    s3CacheInsecure: true # http 登录
    secretName: s3access # 使用 Minio 用户名密码创建的 secert

配置完成后,使用以下命令安装 Gitlab Runner。

helm install -n acp gitlab-runner-elasticsearch-api gitlab-runner

一切顺利的话,可以在 Gitlab 上看到 Gitlab Runner 成功注册上来。

查看在 Kubernetes 集群中创建的 Gitlab Runner 的 Pod

❯ kubectl get pod -n acp | grep runner
gitlab-runner-elasticsearch-api-gitlab-runner-88f7b64fc-rdfch   1/1     Running     0          113s

配置 .gitlab-ci.yml 文件

Gitlab CI/CD 通过 .gitlab-ci.yml 配置文件中定义流水线(Pipeline)的各个阶段(Stage),以及各个阶段中的若干作业(Job)。例如以下配置文件,我们定义了 3 个 Stage:

  • 1.compile:使用 openjdk 镜像编译项目。
  • 2.build:使用 compile 阶段编译好的 jar 包构建 Docker 镜像,并推送至镜像仓库。
  • 3.deploy:在 Kubernetes 集群中使用构建好的 Docker 镜像部署应用。
stages:
  - compile
  - build
  - deploy

variables:
  KUBECONFIG: /tmp/config
  CI_REGISTRY: 你的镜像仓库 IP
  CI_REGISTRY_IMAGE: 你的镜像仓库项目路径

# maven 依赖缓存
cache:
  paths:
    - cache 

# 编译项目
compile:
  stage: compile
  image: openjdk:8-jdk-alpine
  # 只有打 tag 时才会触发任务
  only:
    - tags
  # 编译项目,跳过单元测试,指定本地依赖目录
  script:
    - ./mvnw package -Dmaven.test.skip=true -Dmaven.repo.local=cache
  # 将编译好的 jar 包传递给下一个阶段,用于 kaniko 构建 docker 镜像
  artifacts:
    paths:
      - target

# 构建镜像
build:
  image: gcr.io/kaniko-project/executor:debug # 可能需要手动提前下载
  stage: build
  only:
    - tags
  script:
   # 使用 kaniko 构建 docker 镜像
    - mkdir -p /kaniko/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG

#部署到 kubernetes 环境中
deploy:
  image: bitnami/kubectl:1.19
  stage: deploy
  only:
    - tags
  script:
    # 设置 kubectl 容器的 kubeconfig 文件,使 kubectl 容器有在 Kubernetes 中部署应用的权限
    - echo $kube_config | base64 -id > $KUBECONFIG
    - sed -i "s/CI_COMMIT_TAG/$CI_COMMIT_TAG/g" manifests/deployment.yaml
    - cat manifests/*.yaml
    - kubectl apply -f manifests/

注意事项:

  • 为了安全性,我们将镜像仓库的用户名、密码以及 Kubernetes 集群的 kubeconfig 文件设置在 Gitlab 的 Secret variables 中。进入项目 -> Settings -> CI/CD -> Secret variables。注意 kubeconfig 文件的值是 base64 加密后的,镜像仓库的用户名和密码正常设置即可。
  • 需要在项目根路径下创建一个 cache 目录,用于临时存放从 MinIo 下载的依赖,这个目录名可以自定义,要和 .gitlab-ci.yml 文件中设置的 cache path 一致。在 compile 阶段指定 -Dmaven.repo.local=cache 参数使用 cache 作为依赖仓库,这样就可以用上从 MinIO 中提取的依赖了。
  • 在 build 阶段使用 compile 阶段编译好的 jar 包构建 Docker 镜像,Dockerfile 内容如下。
FROM openjdk:8-jdk-alpine  
# 设置工作目录  
WORKDIR /app  
  
# 语言,时区设置  
ENV TZ=Asia/Shanghai  
ENV LANG=en_US.UTF-8  
ENV LANGUAGE=en_US:en  
ENV iC_ALL=en_US.UTF-8  
  
EXPOSE 8080  
# 拷贝 compile 阶段编译好的 jar 包  
COPY target/*.jar elasticsearch-api.jar  
CMD ["java","-jar","elasticsearch-api.jar"]

  • deploy 阶段用于在 Kubernetes 环境中部署的资源文件在 manifests 目录中,根据你自身项目的情况来编写。

流程验证

完成项目的开发之后,将代码推送到 Gitlab 仓库中。

git add .
git commit -m "首次触发任务"git push

此时并不会触发 Pipline,因为我们在 .gitlab-ci.yml 配置文件中设置了只有打了 tag 才会触发 Pipline。推送 tag 触发 Pipeline。

git tag 3.0.4
git push origin 3.0.4

等待 Pipeline 执行完成。

点击 Pipeline 编号查看详情,可以看到里面有 3 个 Stage。

第一次 Pipeline 在 compile 阶段会比较慢,因为编译项目时需要从公网下载依赖,在编译完成以后会将 cache 目录中的依赖压缩打包后上传到 Minio,等到下一次编译就可以直接拿来使用,避免重复去公网下载,速度会加快不少。

在 MinIO 上可以看到 Gitlab Runner 上传上来的依赖的压缩文件。

build 阶段会从 compile 阶段中 artifact 设置的 target 目录中获取编译好了 jar 包构建 Docker 镜像。

deploy 阶段将镜像部署到 Kubernetes 环境中。

至此就完成了这一整套 Gitlab CI/CD 流程:

  • 在镜像仓库中可以看到构建好的镜像。
  • 在 Kubernetes 中可以看到部署的应用。
❯ kubectl get pod -n acp | grep elasticsearch
elasticsearch-api-7d9656cf5d-5sngv                               2/2     Running     0          30s
elasticsearch-api-7d9656cf5d-9zgzt                               2/2     Running     0          30s
elasticsearch-api-7d9656cf5d-tthx5                               2/2     Running     0          30s

现在我们对代码做了修改,现在再次推送新的 tag 触发 Pipeline。

git add .
git commit -m "第二次触发任务"
git push

git tag 3.0.5
git push origin 3.0.5

可以看到这次在 compile 阶段并没有去公网下载依赖,而是直接使用从 Minio 下载了依赖。整个 Pipeline 流程也从 5 分钟缩短至 1 分钟。

参考资料

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/bc20bead3996eafa934eda51b
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券