5000 字手把手实战|Kubernetes+极狐GitLab CI,获得极致 CI/CD 体验


微服务是一种以业务功能为主的服务设计概念,每一个服务都具有自主运行的业务功能,对外开放不受语言限制的 API,应用程序则是由一个或多个微服务组成。

 

在微服务架构中,不可变的基础设施和容器的自包含环境,使发布变得更加简单快捷,不用再考虑如何根据不同项目区分 Runner 环境;可以随时拉起一个 Pod 来运行我们的 Job,运行完成后立即销毁,不仅能实现动态运行节省资源,而且不用担心多项目多任务并发构建问题。

 

本文分享如何应用极狐GitLab CI 将应用发布进 K8s,适用于以下场景和人群:

 

  • 业务容器微服务化,或半微服务化;

  • 有明确流程规范,上线部署规范及明确可循的标准;

  • 追求极致持续集成/持续交付体验的 GitOps 追崇者;

  • 具备 K8s 运维能力的运维人员。

 

极狐GitLab CI + K8s 架构解析

 

本文以构建一个 Java 软件项目并将其部署到阿里云容器服务 K8s 集群中为例,说明如何使用极狐GitLab CI 在阿里云 K8s 服务上运行极狐GitLab Runner、配置 K8s 类型的 Executor,并执行 Pipeline。

 

极狐GitLab CI 流程图

 

Kubernetes+极狐GitLab CI

 

 

流程详解

 

如上图为一个简单的极狐GitLab CI 部署进 K8s 流程图,同之前我们讲到的 CI 集成一致,只需要项目中存在 .gitlab-ci.yml 文件即可。

 

与之前的差异是,在集成 Kubernetes 时,gitlab-runner 运行在 K8s 内,其为一个 Pod 形式运行,控制着后续的 Pipeline 中各 Stage 执行。

 

可以看到,当一个 Pipeline 有多个 Stage,每个 Stage 都有一个单独的 Pod 去执行,这个 Pod 使用的镜像在 CI 的.gitlab-ci.yml 的每个 Stage 的 Image 中定义,如下所示为部署 Java 项目的流程:

 

  • 开发者或项目维护者 Merge Request 到特定分支,该分支存在 CI 文件,开始进行 CI;

  • CI  任务由已经注册在 gitlab-server 运行在 K8s 集群内的 gitlab-runner 进行下发:

    • 第一个为 Package,对 Java 项目利用 Maven 镜像进行打包,生成 War 包制品到缓存目录中;

    • 利用 Docker 镜像,根据项目中的 Dockerfile 对缓存中的制品进行镜像构建,构建完成后登录镜像仓库进行镜像推送;

    • 在此,将部署文件托管在项目内,也体现了 GitOps 思想,将之前构建推送成功的镜像地址信息在 deployment.yaml 文件进行修改,之后 Apply 进 k8s 中,Java 项目构建的镜像就运行在 K8s 集群内了,完成发布。

 

流程中有一些注意事项:

 

  • 可在 Stage 中添加自己业务需求的内容,例如单元测试、代码扫描等;

  • 在部署文件中,可以将整个项目制作成 Helm 的 Chart,替换其中的镜像,利用 Helm 来进行应用部署;

  • 应用部署中,单个 Stage 利用的是不同的 Image,在各个 Stage 中传递已经生成的制品例如 war/jar 包,需要使用外部存储来缓存制品。

 

极狐GitLab CI + K8s 架构优点

 

通过上面的极狐GitLab CI 流程,我们能够看到将极狐GitLab Runner 运行在 K8s 集群中,每个 Job 启动单独的 Pod 运行操作,此种方式完全凸显了 K8s 的优点:

 

  • 高可用:当某个节点出现故障时,K8s 会自动创建一个新的极狐GitLab Runner 容器,并挂载同样的 Runner 配置,使服务达到高可用;

  • 弹性伸缩:触发式任务,合理使用资源,每次运行脚本任务时,极狐GitLab Runner 会自动创建一个或多个新的临时 Runner 来运行 Job;

  • 资源最大化利用:动态创建 Pod 运行 Job,资源自动释放,而且 K8s 会根据每个节点资源的使用情况,动态分配临时 Runner 到空闲节点上,降低出现因某节点资源利用率高,还排队等待在该节点的情况;

  • 扩展性好:当 K8s 集群的资源严重不足而导致临时 Runner 排队等待时,可以很容易地添加一个 K8s Node 到集群中,从而实现横向扩展。

 

如果您的业务目前运行环境为 K8s,那么极狐GitLab CI 完全契合您的业务场景,您只需要自定义 gitlab-ci.yml 中的各个需求的 Stage 即可,配合 GitOps 将配置托管在项目内,跟随项目一起维护管理,实现端到端的 CI 工作流,使得运维工作也可通过 Git 追溯,提高工作效能,敏捷开发上线部署。

 

开启极狐GitLab CI + K8s 实战

 

通过上文内容,我们了解来极狐GitLab CI 与 K8s 的集成及其优点,接着,就让我们通过实战来更具体的了解其流程。

1

环境准备

  1. 极狐GitLab 服务器:可以是部署在物理服务器上,也可以部署在 K8s 集群内部;

  2. K8s 集群:可以是公有云的容器编排引擎,例如阿里云的 ACK、腾讯云的 TKE、华为云的CCE等都适用;

  3. 本次示例中使用 Helm 来安装,Helm 版本为 v2.14.3,有能力的伙伴,可以自己编写资源清单部署。

 

记录注册信息

 

登录极狐GitLab 服务器,记录极狐GitLab 的 URL 和注册令牌。

 

在部署进 K8s 的极狐GitLab Runnner 的配置中需要填写该信息,运行在 K8s 中的 Pod 就利用此信息在极狐GitLab 服务器进行注册。

 

Kubernetes+极狐GitLab CI

 

 

获取极狐GitLab Runner

 

由于单独部署极狐GitLab Runner 进 K8s 中,自己去写资源清单文件难度较大且容易出错,我们在此利用官方 Chart 镜像通过 Helm 来进行部署,仅需修改其中我们关心的字段即可。

 

首先登录 K8s 集群,进行极狐GitLab Runner 的 Helm Repo 的添加;之后将 Chart 下载到本地,解压文件并修改其中的 values.yml 文件。

 

 添加 Repo 获取 Charts

 

[root@master common-service]# helm repo add gitlab https://charts.gitlab.io
"gitlab" has been added to your repositories
[root@master common-service]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Skip local chart repository
...Successfully got an update from the "aliyun" chart repository
...Successfully got an update from the "apphub" chart repository
...Successfully got an update from the "gitlab" chart repository
...Successfully got an update from the "stable" chart repository
Update Complete.
[root@master common-service]# helm search gitlab-runner
NAME                    CHART VERSION   APP VERSION     DESCRIPTION  
gitlab/gitlab-runner    0.14.0          12.8.0          GitLab Runner

 

可以看到极狐GitLab Runner 的 Chart,其是 Helm 中描述相关的一组 K8s 资源文件集合,包含了一个 value.yaml 配置文件和一系列模板(deployment.yaml、svc.yaml 等)。

 

 创建角色并绑定权限

 

极狐GitLab Runner 在运行时,需要访问 K8s 的 API。我们在此为其创建 ServiceAccount 并为其进行 RBAC 授权。

 

首先创建一个极狐GitLab Runner 的名称空间,并创建 Role,将 ServiceAccount 与 Role 进行绑定。

 

[root@master gitlab-runner]# cat > rbac-runner-config.yaml <<EOF
apiVersion: v1
kind: ServiceAccount                                                                                # 在gitlab-runners名称空间下创建名为gitlab的serviceaccount
metadata:
  name: gitlab
  namespace: gitlab-runners
---
kind: Role                                                                                                                                # 创建gitlab角色,
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: gitlab-runners
  name: gitlab
rules:
- apiGroups: [""#"" indicates the core API group
  resources: ["*"]
  verbs: ["*"]
- apiGroups: ["apps"]                                                                
  resources: ["deployments"]
  verbs: ["*"]
- apiGroups: ["extensions"]
  resources: ["deployments"]
  verbs: ["*"]

---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: gitlab                                                                                                                                                # 将sa与角色进行绑定
  namespace: gitlab-runners
subjects:
- kind: ServiceAccount
  name: gitlab # Name is case sensitive
  apiGroup: ""
roleRef:
  kind: Role #this must be Role or ClusterRole
  name: gitlab # this must match the name of the Role or ClusterRole you wish to bind to
  apiGroup: rbac.authorization.k8s.io
  
EOF
# 创建资源清单
[root@master gitlab-runner]# kubectl create -f rbac-runner-config.yaml
serviceaccount/gitlab created
role.rbac.authorization.k8s.io/gitlab created
rolebinding.rbac.authorization.k8s.io/gitlab created

 

 获取 Charts 文件,并进行配置

 

[root@master gitlab-runner]# helm fetch gitlab/gitlab-runner                                                
[root@master gitlab-runner]# tar xf gitlab-runner-0.14.0.tgz
[root@master gitlab-runner]# vim gitlab-runner/values.yaml 

 

Helm 为我们提供了一个配置文件,可以在安装 Runner 时,为其注册一个默认 Runner。我们可以去 gitlab-runner 的项目源码中获取到 values.yaml 这个配置文件。

 

gitlab-runner 的项目源码:

https://gitlab.com/gitlab-org/charts/gitlab-runner

 

绑定 docker.sock

 

由于在极狐GitLab Runner 中需要执行 docker build 命令,需要 Docker 服务端,我们可以绑定 K8s Node 节点的 docker.sock 来实现,编辑 Chart 下的 Template 目录中的 configmap.yaml,在大约 42 行修改。

# add volume and bind docker.sock
cat >>/home/gitlab-runner/.gitlab-runner/config.toml <<EOF
  [[runners.kubernetes.volumes.pvc]]
    name = "{{.Values.maven.cache.pvcName}}"
    mount_path = "{{.Values.maven.cache.mountPath}}"
  [[runners.kubernetes.volumes.host_path]]
    name = "docker"
    mount_path = "/var/run/docker.sock"
EOF

 

配置缓存

 

在流程详解中,能够看到生成的 war/jar 包制品需要存储在一个地方,这个地方就需要挂载一块外置的存储设备,在k8s 中,我们需要为其提供 PVC,如果你可以用 NFS 存储或者分布式 Ceph 制成的存储类。

 

在此,我们机器中使用的为 Storageclass,演示利用存储类来声明 PVC,在 Pod 中进行挂载。

 

 创建 PVC 声明文件 gitlab-runner-pvc.yaml

 

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: gitlab-runner-pvc              # 创建名称为gitlab-runner-pvc的pvc
  namespace: gitlab-runners
spec:
  accessModes:
  - ReadWriteMany                      # 访问模式
  storageClassName: rbd                # 存储类名称
  resources:
    requests:
      storage: 2Gi                     # 存储大小

 

 创建 PVC

 

[root@master gitlab-runner]# kubectl apply -f gitlab-runner-pvc.yaml                                         
persistentvolumeclaim/gitlab-runner-pvc created
[root@master gitlab-runner]# kubectl get pvc -n gitlab-runners       
NAME                STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
gitlab-runner-pvc   Bound    pvc-a620cd43-f396-4626-8599-c973c19cddd3   2Gi        RWO            rbd            4s

 

查看已经在极狐GitLab Runner 名称空间下已经创建好了 PVC。

 

 配置 Values

 

在上面,我们配置绑定宿主机 Docker 进程时,已经将 PVC 配置进去了,我们还需要在 Charts 的 values.yaml 下,新添加配置 Volume 信息,并在 values.yaml 配置相应变量。

 

# 添加在values.yaml 的最后即可
maven:
  cache:
    pvcName: gitlab-runner-pvc
    mountPath: /home/cache/maven

 

安装极狐GitLab Runner

 

配置文件比较长,可以根据需要自行配置,下面是本文示例需要配置的地方。

 

Kubernetes+极狐GitLab CI

 

 

[root@master gitlab-runner]# egrep "^$|^#|^[[:space:]]+#" -v values.yaml 
imagePullPolicy: IfNotPresent
gitlabUrl: https://jihulab.com/                                                                                                # gitlab url
runnerRegistrationToken: "nnxxxxxxxxxxxxxxxS"                                        # gitlab token
unregisterRunners: true
terminationGracePeriodSeconds: 3600
concurrent: 10
checkInterval: 30
rbac:
  create: true
  clusterWideAccess: false
  serviceAccountName: gitlab                                                                                                        # rbac的sa
metrics:
  enabled: true
runners:
  image: ubuntu:16.04                                                                                                                                        # 使用的镜像
  tags: "gitlab-runner"
  privileged: true
  imagePullPolicy: "if-not-present"                                                                                # 如果执行具体job的runner不存在则拉取
  pollTimeout: 180
  outputLimit: 4096
  cache: {}
  builds: {}
  services: {}
  helpers: {}
  serviceAccountName: gitlab                                                                                                        # serviceaccount
  nodeSelector: {}

securityContext:
  fsGroup: 65533
  runAsUser: 100
resources: {}
affinity: {}
nodeSelector: {}
tolerations: []
hostAliases: []
podAnnotations: {}
podLabels: {}
maven:                                                                                                                                                                                                # 配置maven缓存信息
  cache:
    pvcName: gitlab-runner-pvc                                                                                                # pvc信息
    mountPath: /home/cache/maven                                                                                        # 挂载点

 

 安装极狐GitLab Runner

 

在配置完成 values.yaml 相关字段后,我们利用 Helm 来进行安装。后期如果有变动,直接修改 value.yml ,进行更新即可 helm upgrade gitlab-rujner gitlab-runner/

 

Kubernetes+极狐GitLab CI

 

 

[root@master gitlab-runner]# helm install --name gitlab-runner -f gitlab-runner/values.yaml --namespace gitlab-runners gitlab-runner/
NAME:   gitlab-runner
LAST DEPLOYED: Sun Feb 23 22:45:37 2020
NAMESPACE: gitlab-runners
STATUS: DEPLOYED

RESOURCES:
==> v1/ConfigMap
NAME                         DATA  AGE
gitlab-runner-gitlab-runner  5     1s

==> v1/Deployment
NAME                         READY  UP-TO-DATE  AVAILABLE  AGE
gitlab-runner-gitlab-runner  0/1    1           0          1s

==> v1/Pod(related)
NAME                                          READY  STATUS   RESTARTS  AGE
gitlab-runner-gitlab-runner-6745dd4cd6-r9z28  0/1    Pending  0         0s

==> v1/Secret
NAME                         TYPE    DATA  AGE
gitlab-runner-gitlab-runner  Opaque  2     1s

 

查看已经成功运行的相关 Pod 资源,极狐GitLab Runner的配置是通过 Configmap 挂载进去,如有新的配置可以修改,并删除之前的极狐GitLab Runner 的 Pod,其 Deployment 控制器会为 Pod 挂载新的配置文件。

 

 查看 Pod 运行状态

 

[root@master ~]# kubectl get po -n gitlab-runners 
NAME                                           READY   STATUS    RESTARTS   AGE
gitlab-runner-gitlab-runner-6745dd4cd6-r9z28   1/1     Running   0          21h

 

 查看 Configmap 及 Pod 中挂载的配置

 

此时,可以进入 Pod 查看 Runner 的配置文件(/home/gitlab-runner/.gitlab-runner/config.toml)。这个文件就是根据之前配置的 values.yaml 自动生成的。

 

[root@master ~]# kubectl get cm -n gitlab-runners  gitlab-runner-gitlab-runner 
NAME                          DATA   AGE
gitlab-runner-gitlab-runner   5      21h

 

 登录极狐GitLab 控制台查看目前极狐GitLab Runner 已经注册上了

 

Kubernetes+极狐GitLab CI

 

 

 当 Pod 运行起来后,查看完整配置

 

[root@master gitlab-runner]# kubectl exec -it -n gitlab-runners gitlab-runner-gitlab-runner-6c7dfd859c-62dv5 -- cat /home/gitlab-runner/.gitlab-runner/config.toml
listen_address = "[::]:9252"
concurrent = 10
check_interval = 30
log_level = "info"

[session_server]
  session_timeout = 1800

[[runners]]
  name = "gitlab-runner-gitlab-runner-6c7dfd859c-62dv5"
  output_limit = 4096
  request_concurrency = 1
  url = "https://jihulab.com/"         # 注册的url
  token = "eRxxxxxb63q"         # gitlab注册的token
  executor = "kubernetes"             # executor为kubernetes
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
  [runners.kubernetes]
    host = ""
    bearer_token_overwrite_allowed = false
    image = "ubuntu:16.04"            # 使用的镜像为ubuntu
    namespace = "gitlab-runners"         # 使用的namespace为gitlab-runners
    namespace_overwrite_allowed = ""
    privileged = true
    poll_timeout = 180
    service_account = "gitlab"          # serviceaccont 为gitlab
    service_account_overwrite_allowed = ""
    pod_annotations_overwrite_allowed = ""
    [runners.kubernetes.pod_security_context]
    [runners.kubernetes.volumes]
  [[runners.kubernetes.volumes.pvc]]
    name = "gitlab-runner-pvc"          # 挂载的pvc名称
    mount_path = "/home/cache/maven"       # 挂载在pod下的缓存目录
  [[runners.kubernetes.volumes.host_path]]
    name = "docker"
    mount_path = "/var/run/docker.sock"      # 绑定宿主机的docker.sock

 

至此,我们已经将极狐GitLab Runner 在 K8s 集群中运行起来。

 

接下来,让我们来集成 CI。

2

集成 CI

定义文件

 

由于使用的 Executor 不同,所以. gitlab-ci.yml 和之前服务器也有些不同,不如 Image,默认如果每个Stage 中没有指定某个 Image,则使用该 Image。

 

image: docker:latest
variables:
  DOCKER_DRIVER: overlay2
  # k8s 挂载本地卷作为 maven 的缓存
  MAVEN_OPTS: "-Dmaven.repo.local=/home/cache/maven"

stages:
  - package       # 源码打包阶段
  - docker_build        # 镜像构建和打包推送阶段
  - deploy_k8s     # 应用部署阶段
  
before_script:
  - export APP_TAG="${CI_COMMIT_TAG:-${CI_COMMIT_SHA::8}}"    # 定义制作好的镜像tag

maven-package:
  #image: maven:3.5-jdk-8-alpine
  image: registry.cn-beijing.aliyuncs.com/codepipeline/public-blueocean-codepipeline-slave-java:0.1-63b99a20  # maven镜像进行java源码的build
  tags:
    - gitlab-runner    # 指定使用gitlab-runner来追寻
  stage: package
  script:
    - mvn clean package -Dmaven.test.skip=true
  artifacts:       # 将生成的war包上传到pvc挂载目录中
    paths:
      - target/*.war
docker-build:
  tags:
    - gitlab-runner
  stage: build
  script:
    - echo "Building Dockerfile-based application..."
    - docker build -t ${REGISTRY}/${QHUB_NAMESPACE}/${APP_NAME}:${APP_TAG} .     # 构建镜像
    - docker login --username=${DOCKER_USERNAME} ${REGISTRY} -p ${DOCKER_PASSWORD}  # 登录镜像仓
    - docker push ${REGISTRY}/${QHUB_NAMESPACE}/${APP_NAME}:${APP_TAG}        # push镜像
  only:
    - master
k8s-deploy:
  image: bitnami/kubectl:latest    # 使用kubectl镜像来进行最终的部署
  tags:
    - gitlab-runner
  stage: deploy
  script:
    - echo "deploy to k8s cluster..."
    - sed -i "s@$(grep -E "^[[:space:]]+image:" deployment.yaml | awk '{print $2}' |head -1)@${REGISTRY}/${QHUB_NAMESPACE}/${APP_NAME}:${APP_TAG}@g"  deployment.yaml   # 替换deployment中的镜像
    - kubectl apply -f deployment.yaml      # 应用资源清单文件
  only:
    - master

 

可以看到 CI 文件中,我们定义了三个 Stage:

 

  • 利用 Maven 镜像进行打包,生成 war 包制品到缓存目录中;

  • 利用 Docker 镜像,根据项目中的 Dockerfile 对缓存中的制品进行镜像构建,构建完成后,登录镜像仓库进行镜像推送;

  • 利用 Kubectl 镜像先对原有 Deployment 文件进行镜像替换,之后将资源清单更新至 K8s 集群中;

  • 在为镜像打 Tag 时,可以自行设置,也可以利用 Commit 来对照 Git 版本。

 

注意事项

 

  • 由于在镜像构建的 Stage 中,需要使用 Docker 命令进行相关操作,因此需要绑定本地 Docker 守护进程;

  • Maven 仓库缓存,在需要生产制品并保存的构建中,需要外部存储来存放制品;

  • 在 Stage 中引用的镜像最好能事先 Pull 到各个Node 节点上,不然第一次运行 CI,如果网络有波动或带宽小,可能会因为镜像下载超时导致 CI 失败。

 

配置变量

 

由于在 CI 文件中有一些敏感信息,例如镜像仓库的登录信息、后期可以更改的镜像名称等,可利用环境注入方式,使得 CI 文件脱敏而且更具灵活性和适用性

 

添加极狐GitLab Runner 可用的环境变量,本示例添加变量如下:

 

极狐GitLab Runner

 

 

  • APP_NAME:制作完成后镜像的名称;

  • DOCKER_USERNAME:镜像仓库用户名;

  • DOCKER_PASSWORD 镜像仓库密码;

  • QHUB_NAMESPACE:镜像仓库名称空间。在该项目中,我们使用腾讯云的镜像仓库,当然可以自建 Harbor 或使用其他公有云提供的镜像仓库或者 Dockerhub 等;

  • REGISTRY:镜像仓库的地址。

3

运行测试

在配置好了 CI 文件后,让我们来运行测试,提交代码,或者手动运行。

 

运行 Pipeline

 

在项目 CI/CD 右上角有运行流水线按钮。

 

运行 Pipeline

 

 

在图中,我们可以看到可以并行运行多个 Pipeline,互不影响。

 

查看运行 Pod

 

登录 K8s 集群,查看此时运行的 Pod:

 

运行 Pod

 

 

查看 Job

 

 打包镜像

 

打包镜像

 

 

 镜像构建

 

打包镜像

 

 

 部署进 K8s

 

部署进 K8s

 

 

查看构建情况

 

 查看 Pipeline

 

部署进 K8s

 

 

 查看构建应用

 

查看构建应用

 

至此我们就完成了极狐GitLab CI + K8s 示例。让我们尽情享受 K8s +极狐GitLab CI 为我们带来畅快淋漓的发布体验吧。

 

注意事项

 

  • 实战中的资源清单文件只写了 Deployment,读者可以根据自己项目调整,可以将 Service/Configmap/HPA 等资源清单文件也放在项目中托管,当然也可以制作成 Helm 应用,每次发布利用 Helm 来更新应用;

  • 在极狐GitLab CI 中,注意将敏感信息例如镜像仓库的登录方式等进行脱敏,将有变化的字段设置为变量,使得 CI 更扩展性及灵活性;

  • 在此节中,部分内容需要读者具备一定的 K8s 经验,例如其中的 Helm 及相关的资源清单文件,如事先了解业务微服务后,看本章节内容更为合适;

  • 注意在写 RBAC 与 PVC 资源清单的名称空间为你想部署的业务名称空间,即例如你想把资源清单部署在 Common 名称空间下,就需要把 PVC 和 RBAC 创建在 Common 名称空间下,将 极狐GitLab Runner 注册在Common名称空间下。

 

总结思考

 

通过本文极狐GitLab CI 实战总结,笔者再次安利大家去了解极狐GitLab 的此项特性。如果您的公司困于多套系统维护的复杂性,不妨尝试下极狐GitLab CI 简单集成

 

在云原生时代,将工具或中间件下沉到基础设施中,用户只需要专注于自身业务开发,在敏捷高效的文化中协作、快速试错、快速反馈、持续改进、不断迭代,以 Git 为契机打破研发与运维的壁垒隔阂,实现产品更快、更频繁、更稳定的交付。