CI/CD探索与实践 (一 、Gitlab+Kubernetes)

Photo by Aleksejs Bergmanis from Pexels

前言:在2020年的时候观看了中国DevOps社区的流水线大赛直播,受益良多。从今年年初开始有时间来细细咀嚼、消化整理学习到的CI/CD相关知识。之所以只整理CI/CD部分的内容,是因为笔者当前所在公司在日志聚合(ELK)、观测(Prometheus+Grafana)、任务管理(Jira)等方面已经稳定使用了,而CI/CD部分还不成体系,所以当然得重点照顾啦 ~

本文的关注点是CI/CD的实践过程,例子为最简单的构建流程。如果有任何表述不当的地方欢迎评论交流。

1. 概念介绍

虽然是实践文章,但是基本的流程还是得过一下,简单的介绍下概念

缩略词 CI / CD 具有几个不同的含义。CI/CD 中的“CI”始终指持续集成,它属于开发人员的自动化流程。成功的 CI 意味着应用代码的新更改会定期构建、测试并合并到共享存储库中。该解决方案可以解决在一次开发中有太多应用分支,从而导致相互冲突的问题。

CI/CD 中的“CD”指的是持续交付和/或持续部署,这些相关概念有时会交叉使用。两者都事关管道后续阶段的自动化,但它们有时也会单独使用,用于说明自动化程度。

  • 持续交付通常是指开发人员对应用的更改会自动进行错误测试并上传到存储库,然后由运维团队将其部署到实时生产环境中。这旨在解决开发和运维团队之间可见性及沟通较差的问题。因此,持续交付的目的就是确保尽可能减少部署新代码时所需的工作量。

  • 持续部署(另一种“CD”)指的是自动将开发人员的更改从存储库发布到生产环境,以供客户使用。它主要为了解决因手动流程降低应用交付速度,从而使运维团队超负荷的问题。持续部署以持续交付的优势为根基,实现了管道后续阶段的自动化。

CI/CD 既可能仅指持续集成和持续交付构成的关联环节,也可以指持续集成、持续交付和持续部署这三项构成的关联环节。更为复杂的是,有时“持续交付”也包含了持续部署流程。

归根结底,我们没必要纠结于这些语义,您只需记得 CI/CD 其实就是一个流程(通常形象地表述为管道),用于实现应用开发中的高度持续自动化和持续监控。因案例而异,该术语的具体含义取决于 CI/CD 管道的自动化程度。许多企业最开始先添加 CI,然后逐步实现交付和部署的自动化(例如作为云原生应用的一部分)。

以上文本摘抄自:Red Hat: CI/CD是什么?如何理解持续集成、持续交付和持续部署

关于CI/CD概念介绍的文章网上实在太多了,在刚刚学习这块的时候完全不愁找不到介绍,但是却发现记录实现做法的文章少的可怜。所以一遍是对自己实践的总结,也希望能帮助到像我之前一样找不到方向的同学。

2. 流水线介绍

本篇实践的内容是中国DevOps社区2020届流水线大赛百姓网团队贡献的流水线实例,原项目地址见:https://gitlab.com/baixingwang/devops-user-service

项目整体内容在README文件里写的非常详细,本文则会描述实践过程以及一些心得。

以下为实践中用到的内容:

  • RKE:Rancher安装的Kubernetes引擎,个人学习向也可以换成Kind、Minikube单机集群,Kube adm安装的原生集群。个人觉得命令学习使用Minikube最方便,需要练习多节点以及模拟真实环境使用Rancher安装最方便。Kind和Kube adm我都尝试过,过程太曲折,不知道多少人是被K8S的环境搭建劝退的 ::>_<::。总之个人学习推荐minikube、kube adm、rancher,即使失败很多次也要坚持,都是宝贵的学习经验,因为生产上一般采用云服务商提供的引擎。

  • Spring Boot 2.3.0(因为有优雅下线功能,而且最近遇到一个较低版本Spring Cloud 版本配合Sentinel特别坑的问题,openfeign10.4.0中Contract的一个方法名写错了,直到10.6.0才修复)

  • Gitlab :代码版本库及持续集成工具

  • Maven:编译工具

  • Sonar:代码质控

  • Jacoco:单测覆盖度

  • Docker:容器技术

  • Harbor:镜像仓库

  • Skywalking:链路追踪,免费,开源,强大,但是本篇文章不会出现,这个整理在下一篇文章。当前只是说明用来替换原示例中的Elastic APM。

工作流就采用基本的GitFlow WorkFlow,网上介绍文章很多,比较好的文章推荐:https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow

3. 环境准备

3.1 硬件条件

32G+内存 (用于支撑至少 4核8G虚拟机 × 3 ,实际上用的会更多)

注:如果这一条不满足的话可能无法流畅的走完一条简化的流水线,或者需要借助外部资源,或者可以将Kubernetes换成Docker Swarm等资源要求很小的编排工具,理解思想就好

3.2 流水线环境

3.2.1 代码

任意可运行的服务Demo,用自己喜欢的语言编写,能查看效果就好 ~ (以下为一个SpringBoot示例)

其实这里的代码是把原百姓网的示例项目简化到只剩一个启动类和接口类了,因为我觉得我关注的重点只是gitlab-ci.yml文件而已 o

3.2.2 CentOS 7

个人虚拟机可以关闭firewalld,将虚拟机网络设置为桥接模式 + 静态IP

3.2.3 Docker

[landscape@centos ~]$ docker version
Client: Docker Engine - Community
 Version:           20.10.2
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        2291f61
 Built:             Mon Dec 28 16:17:48 2020
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.2
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       8891c58
  Built:            Mon Dec 28 16:16:13 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.3
  GitCommit:        269548fa27e0089a8b8278fc4fc781d7f65a939b
 runc:
  Version:          1.0.0-rc92
  GitCommit:        ff819c7e9184c13b7c2607fe6c30ae19403a7aff
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

3.2.4 Gitlab

(建议不要更改默认的80端口,不然后面的域名设置和CI拉取代码可能会踩坑,当然如果只用Gitlab触发持续集成是无所谓的,后面还有一篇Jenkins的持续集成实践文章)

[landscape@centos ~]$ docker container ls |grep gitlab
afd400642037   gitlab/gitlab-ce:latest        "/assets/wrapper"        3 weeks ago   Up 5 seconds (health: starting)   0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:8004->22/tcp   gitlab

3.2.4 Gitlab-Runner

实际执行CI流程中的操作,随意选择执行方式,我这里选择了SSH和Docker两种方式做演示

3.2.5 RKE

可以是任意形式的Kubernetes,不是Kubernetes也行,Minikube、Docker Swarm、Mesos、K3S、K0S等等,选自己喜欢的。

3.2.6 Harbor仓库

(个人学习可开放Http连接)

3.2.7 SonarQube

代码静态扫描。可选,不影响主流程

3.2.8 Gitlab-ci.yml

后面会有详细解释的,这里的配置仅供参考,我自己修改了很多东西

variables:
  REGISTRY_BASE: 192.168.31.197:8000/baixing/user
  IMAGE_REF: $REGISTRY_BASE:$CI_PIPELINE_ID
  MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"

stages:
  - compile
  - check
  - package
  - build
  - deploy

cache:
  paths:
    - .m2/repository/
    - target/

compile:
  stage: compile
  image: registry.cn-hangzhou.aliyuncs.com/acs/maven
  script:
    - mvn clean compile
  only:
    - /^feature-.*/
    - master
    - release

uni-test:
  stage: check
  image: registry.cn-hangzhou.aliyuncs.com/acs/maven
  script:
    - mvn clean test && cat target/jacoco/index.html
  only:
    - /^feature-.*/
    - master
    - release

sonar:
 image: registry.cn-hangzhou.aliyuncs.com/acs/maven
 stage: check
 variables:
   SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
   GIT_DEPTH: "0"
 script:
   - mvn sonar:sonar \
    -Dsonar.projectKey=devops-user-service \
    -Dsonar.host.url=http://192.168.31.200:9000 \
    -Dsonar.login=a56d61b1075a6de8d459f5f5d74846863a281a15
 only:
   - /^feature-.*/
   - master
   - release


maven package:
  stage: package
 dependencies:
   - compile
  image: registry.cn-hangzhou.aliyuncs.com/acs/maven
  script:
    - echo $REGISTRY_BASE
    - echo $CI_PIPELINE_ID
    - echo $IMAGE_REF
    - env
    - mvn package -Dmaven.test.skip=true spring-boot:repackage
  only:
    - /^feature-.*/
    - master
    - release
  artifacts:
    paths:
      - target/app.jar
    expire_in: 12h


docker build:
  stage: build
  image: docker:20.10.2
  script:
    - ls -R
    - docker login -u admin -p buzhidao http://192.168.31.197:8000/
    - docker build -t $IMAGE_REF .
    - docker push $IMAGE_REF
    - docker rmi $IMAGE_REF
  only:
    - /^feature-.*/
    - master
    - release
  tags:
    - docker


deploy to dev:
  stage: deploy
  script:
    - docker run --rm -v /home/landscape/kubetnetes/landscape/kubeconfig:/.kube/config bitnami/kubectl:latest set image deployment/user-service-deployment user-service-container=$IMAGE_REF
  only:
    - /^feature-.*/
  environment:
    name: dev
  tags:
    - ssh

3.3 Dockerfile及镜像推送

由于暂时不嵌入Agent,Demo也不需要考虑参数,所以Dockerfile特别简单

FROM openjdk:8u275-jre

COPY target/app.jar /app.jar

EXPOSE 8080

ENTRYPOINT ["java","-jar","/app.jar"]

手动构建一次推送latest镜像到Harbor仓库即可

3.4 Kubernetes 应用部署

以最低要求部署一个应用的Deployment,例如:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service-deployment
  labels:
    app: user-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service-container
        image: 192.168.31.197:8000/baixing/user:latest
        ports:
        - containerPort: 8080

结果验证:

> kubectl get deploy
NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
user-service-deployment   1/1     1            1           19d
> kubectl get pod -o wide
NAME                                       READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
user-service-deployment-8694b49757-nlcvc   1/1     Running   0          24m   10.42.0.192   centos   <none>           <none>

4. 流水线运行

随意修改一些代码并提交,确认流水线触发,注意由于在gitlab-ci.yml文件里写了feature-的分支将触发dev分支的构建流程,所以这里推送的分支需要被正则表达式 “feature-”匹配到,如feature-simple

4.1 Compile 阶段

Compile阶段会下载依赖的Jar包并编译代码,如果编译通过,则说明此次构建没有代码冲突。

因为我之前已经编译很多次了,所以这里不会重复下载,第一次构建可能有点慢哦 ~

4.2 Check 阶段

这里因为check阶段存在两个任务“sonar”和“uni-test”,所以并行运行了。结束后,我们可以查看单元测试的结果和Sonar静态检查的结果

一般如果将SonarQube分析的结果当作Review优化的标准的话,可以不对Sonar检查的结果强制要求,而单元测试的正确性则做强制性要求。

4.3 Package 阶段

Package阶段很简单,只是将编译好的代码打包成可执行文件。但是由于在gitlab-ci配置文件中指定了artifact,所以这里会有一个产物的展示

4.4 Image Build 阶段

此阶段将应用程序打包成镜像并推送到Harbor仓库。顺便,注意这里镜像标签是 56

4.5 Deploy 阶段

此阶段使用Kubectl命令更换运行中的容器镜像版本,滚动更新,查看部署结果:

5. 流水线细节

5.1 Gitlab-runner

我之前在公司里使用Jenkins的时候就出现过,因为流水线编写者偷懒,流水线中的功能直接依赖了宿主机上的工具(例如maven),导致Jenkisn迁移不便和Agent之间职能不方便调换。在这个例子中Gitlab-Runner的所有动作都是在Docker中完成的(除了部署阶段,那只是我想看看不同的Runner有什么区别而已 ¬_¬ ),不依赖宿主机的功能。

我贴出的示例配置中修改了很多镜像,其中maven换成了阿里云的maven镜像,如果有特殊的需求,也可以自己制作镜像并选择私服镜像进行构建。

实际上Docker Runner会默认挂载Docker.sock文件来获得直接操作Docker 的能力,我们可以来看以下Docker Runner的配置:

[landscape@centos ~]$ docker exec runner-docker cat /etc/gitlab-runner/config.toml
concurrent = 1
check_interval = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "My Docker Runner"
  url = "http://192.168.31.196/"
  token = "B49vV3ussbs7CVyEvsSZ"
  executor = "docker"
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]
  [runners.docker]
    tls_verify = false
    image = "docker:20.10.2"
    privileged = false
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache", "/opt/docker/daemon.json:/etc/docker/daemon.json"]
    shm_size = 0

......

5.2 多阶段缓存

Gitlab允许在构建时多阶段之间存在缓存,否则例如maven这样的工具可能在编译、测试、打包等阶段全部重新下载依赖,导致流水线运行实践很长。这点其实很容易理解,在gitlab-ci.yml中有如下配置:

cache:
  paths:
    - .m2/repository/
    - target/

其中,.m2/repository 缓存的就是maven配置的本地仓库,而target目录则是为编译、构建、镜像打包阶段准备,不需要重复的打包应用程序。

5.3 测试很重要

其实,release分支和master分支和普通feature分支的配置流程并没有多少区别,只是多了 集成测试、压力测试、回归测试以及一些清理工作而已,所以我没有演示,这部分应该由测试部门的小伙伴来配合完成。但持续部署意味着开发人员对于应用的修改在几分钟内就能影响到线上服务,所以测试的过程是非常、非常、非常重要的。

6. 结语

社区还是很Nice的 _ ,跟着大佬们学习实践,然后改善自己工作中的CI/CD流水线,后面一篇文章则是自己使用Jenkins的流水线实例。

这是一条签名的小尾巴: 任何变化都不是突然发生的,都是自己无意间一点一点选择的。
原文地址:https://www.cnblogs.com/novwind/p/14586469.html