• KubeSphere DevOps 流水线入门指南


    作者:赵海亮,浙江大学计算机专业四年级在读博士生,研究方向为云计算、边缘计算、分布式系统等。

    虽然 KubeSphere 能够将我们从 yaml 文件的编写中解放出来,但是项目上云仍然十分繁琐。 此外,一旦项目源代码发生更替(如发布新功能或去除 bug 等),所有组件都需要重新经历 “源码打包 --> 制作镜像 --> 启动容器” 这个流程。 这意味着,项目运维人员不得不从事大量重复性劳动。为了提高项目发布的效率,工业界引入了 DevOps 的概念。

    本文首先将介绍 DevOps 是什么,随后尝试利用 KubeSphere 集成的功能来实现 DevOps。

    什么是 DevOps

    目前绝大多数互联网公司将开发和系统管理划分成不同的部门。 开发部门的驱动力通常是 “频繁交付新特性”,而运维部门则更关注 IT 服务的可靠性和 IT 成本投入的效率。 两者目标的不匹配,因而存在鸿沟,从而减慢了 IT 交付业务价值的速度。 为了解决这个问题,DevOps(Development 和 Operations 的组合词)被提出。 DevOps 的目的是在企业内部搭建一个自动化 “软件交付” 和“架构变更”的流程,来使得构建、测试、发布软件能够更加地快捷、频繁和可靠。

    实现 DevOps 通常需要多个软件和工具的密切配合。 如图 1 所示,DevOps 将软件的交付流程依次划分为 Plan、Code、Build、Test、Release、Deploy、Operate 以及 Monitor 这些阶段。 当需求变更时,将会从 Monitor 重新平滑过渡至 Plan 阶段。每个阶段都有一系列的软件和工具可供选择。 对于任意项目,我们只需要基于这些软件和工具 搭建一条自动化流水线 ,再设置类似于 “一旦代码变更就自动执行” 这样的钩子函数,整个项目即可自动实现“持续集成 / 持续交付(CI/CD)”,这将大大减少重复劳动。

    图 1 DevOps 技术栈

    KubeSphere DevOps 基于 Kubernetes Jenkins Agent 实现。 和传统的 Jenkins Controller-Agent 架构不同的是,在 KubeSphere 中,Jenkins Agent 可以动态扩缩容,从而降低 CI/CD 对集群资源的盲目占用。 KubeSphere 的 DevOps 用户指南参见 https://kubesphere.io/zh/docs/devops-user-guide/。 本文将依照该指南将一个开源项目上云。

    基于 DevOps 的项目部署

    项目介绍

    本次实验要部署的项目叫做尚医通,这是一个基于 Spring-Boot 实现的预约挂号统一平台。 该项目一共包含三个子部分,分别为 yygh-parentyygh-siteyygh-admin。 在架构上,该项目依赖的数据层中间件有 mysql、redis、mongodb 以及 rabbitmq,依赖的流量治理中间件有 sentinel 和 nacos。

    接下来,我们约定项目根目录为 his,然后分别从开源地址 https://gitee.com/leifengyang/yygh-parent、https://gitee.com/leifengyang/yygh-sitehttps://gitee.com/leifengyang/yygh-admin 拉取源代码:

    (base) ➜  his lsa
    total 0
    drwxr-xr-x   5 hliangzhao  staff   160B Nov 15 10:33 .
    drwxr-xr-x@ 42 hliangzhao  staff   1.3K Nov 15 10:33 ..
    drwxr-xr-x  24 hliangzhao  staff   768B Nov 15 10:33 yygh-admin
    drwxr-xr-x  15 hliangzhao  staff   480B Nov 15 10:33 yygh-parent
    drwxr-xr-x  24 hliangzhao  staff   768B Nov 15 10:34 yygh-site
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    依次查看三个项目的文件布局:

    (base) ➜  his cd yygh-parent
    (base) ➜  yygh-parent git:(master) tree -L 2
    .
    ├── common                      # 通用模块
    │   ├── common-util
    │   ├── pom.xml
    │   ├── rabbit-util
    │   └── service-util
    ├── data                        # 项目演示数据
    │   ├── json
    │   └── sql
    ├── hospital-manage             # 医院后台
    │   ├── Dockerfile
    │   ├── deploy
    │   ├── pom.xml
    │   ├── src
    ├── model                       # 数据模型
    │   ├── pom.xml
    │   └── src
    ├── pom.xml
    ├── server-gateway              # 网关
    │   ├── Dockerfile
    │   ├── deploy
    │   ├── pom.xml
    │   └── src
    ├── service                     # 微服务层
    │   ├── pom.xml
    │   ├── service-cmn             # 公共服务
    │   ├── service-hosp            # 医院数据服务
    │   ├── service-order           # 预约下单服务
    │   ├── service-oss             # 对象存储服务
    │   ├── service-sms             # 短信服务
    │   ├── service-statistics      # 统计服务
    │   ├── service-task            # 定时服务
    │   └── service-user            # 会员服务
    └── service-client
        ├── pom.xml
        ├── service-cmn-client
        ├── service-hosp-client
        ├── service-order-client
        └── service-user-client
    
    30 directories, 12 files
    (base) ➜  yygh-parent git:(master) cd ../yygh-admin
    (base) ➜  yygh-admin git:(master) tree -L 1        # 医院挂号后台(前端 UI)
    .
    ├── Dockerfile
    ├── LICENSE
    ├── build
    ├── config
    ├── deploy
    ├── favicon.ico
    ├── index.html
    ├── package.json
    ├── src
    └── static
    
    5 directories, 9 files
    (base) ➜  yygh-site git:(master) tree -L 1        # 用户挂号前台(前端 UI)
    .
    ├── Dockerfile
    ├── api
    ├── assets
    ├── components
    ├── deploy
    ├── layouts
    ├── middleware
    ├── nuxt.config.js
    ├── package-lock.json
    ├── package.json
    ├── pages
    ├── plugins
    ├── static
    ├── store
    └── utils
    
    11 directories, 7 files
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    对于本项目,我们需要部署如下内容:

    yygh-parent/hospital-manage         # 医院管理
    yygh-parent/server-gateway          # 网关
    # 8 个微服务
    yygh-parent/service/service-cmn
    yygh-parent/service/service-hosp
    yygh-parent/service/service-order
    yygh-parent/service/service-oss
    yygh-parent/service/service-sms
    yygh-parent/service/service-statistics
    yygh-parent/service/service-task
    yygh-parent/service/service-user
    # 2 个前端
    yygh-admin
    yygh-site
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    以上 12 个待部署的子项目将以独立 Pod 的形式在集群中部署。 每一个子项目根目录需要具有一个 Dockerfile 文件以及一个名为 deploy 的文件夹。 前者是本子项目的镜像制作文件,后者是本子项目的资源清单文件 *.yaml(用于在集群中部署)。 以 service-cmn 为例,其文件布局如下:

    (base) ➜  service-cmn git:(master) tree -L 2
    .
    ├── Dockerfile        # 将本子项目构建为镜像的 Dockerfile
    ├── deploy            # 存放用于部署本子项目的资源清单文件
    │   └── deploy.yml
    ├── pom.xml           # 项目依赖
    ├── src               # 源代码
    │   └── main
    └── target            # maven 打包后自动创建
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    遵循上的一篇文章 使用 KubeSphere 部署 Ruoyi-Cloud · KS 实践 02 中所述的部署流程,我们首先需要将中间件上云。然后,我们将三个项目以流水线的方式上云。

    部署中间件

    本项目所使用的中间件除了 Sentinel 和 MongoDB,其他均已在前文中部署。 接下里依次部署这两个中间件。

    对于 Sentinel,我们直接使用雷丰阳已经制作好的镜像 leifengyang/sentinel:1.8.2,然后暴露一个 NodePort 类型的 Service,端口号为 32636。 访问 http://192.168.23.160:32636,以默认用户 sentinel 和默认密码 sentinel 登录,可以进入 Sentinel 控制台。 如果一切顺利,应该可以看到类似的页面:

    图 2 访问 sentinel 暴露的 NodePort 类型的 Service

    对于 MongoDB,我们直接通过应用模版部署它(不勾选登录认证):

    图 3 Bitnami 官方镜像仓库提供的 MongoDB

    为 MongoDB 应用暴露一个 NodePort 类型的 Service,端口号为 31801,然后在本机通过 MongoDB Compass 连接它(192.168.23.160:31801):

    图 4 使用 MongoDB Compass 连接 MongoDB 实例

    如果可以连上,则一切正常。

    导入初始数据

    使用 DataGrip 将位于 his/yygh-parent/data/sql 目录下的全部演示数据(一共有 5 个 sql 文件需要执行,会创建 5 个 yygh 打头的数据库)导入集群中的 MySQL 实例:

    图 5 导入数据至集群 MySQL 实例中

    MongoDB 的演示数据将在项目启动后导入。

    在 Nacos 中创建微服务的启动配置

    观察每一个子项目的 Dockerfile,以 service-cmn 为例:

    # service-cmn 的 Dockerfile
    FROM openjdk:8-jdk
    LABEL maintainer=leifengyang
    
    # 启动 prod 环境,以 service-cmn-prod.yml 作为启动配置
    ENV PARAMS="--server.port=8080 --spring.profiles.active=prod --spring.cloud.nacos.server-addr=his-nacos.his:8848 --spring.cloud.nacos.config.file-extension=yml"
    RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
    
    COPY target/*.jar /app.jar
    EXPOSE 8080
    ENTRYPOINT ["/bin/sh","-c","java -Dfile.encoding=utf8  -Djava.security.egd=file:/dev/./urandom -jar /app.jar ${PARAMS}"]
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这意味着该子项目在启动时,会激活 prod 环境,并从 Nacos 中读取 service-cmn-prod.yml 文件作为启动配置。 因此,我们首先需要在 Nacos 中创建其生产环境配置文件 service-cmn-prod.yml,然后将 子项目路径 / src/main/resources/application-dev.yml 的内容复制进去,在其基础上修改。 需要修改的内容主要是中间件的访问地址。 以 service-cmn 为例,它的配置文件被命名为 service-cmn-prod.yml,其最终内容如下:

    # service-cmn-prod.yml
    server:
      port: 8080
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      mapper-locations: classpath:mapper/*.xml
      global-config:
        db-config:
          logic-delete-value: 1
          logic-not-delete-value: 0
    spring:
      cloud:
        sentinel:
          transport:
            # 修改 sentinel 访问地址
            dashboard: http://his-sentinel-nodeport.his:8080
      redis:
        # 修改 redis 访问地址
        host: his-redis-nodeport.his
        port: 6379
        database: 0
        timeout: 1800000
        password:
        lettuce:
          pool:
            max-active: 20      # 最大连接数
            max-wait: -1        # 最大阻塞等待时间 (负数表示没限制)
            max-idle: 5         # 最大空闲
            min-idle: 0         # 最小空闲
      datasource:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.jdbc.Driver
        # 修改 mysql 访问地址和连接凭证
        url: jdbc:mysql://his-mysql-nodeport.his:3306/yygh_cmn?characterEncoding=utf-8&useSSL=false
        username: root
        password: 123456
        hikari:
          connection-test-query: SELECT 1
          connection-timeout: 60000
          idle-timeout: 500000
          max-lifetime: 540000
          maximum-pool-size: 12
          minimum-idle: 10
          pool-name: GuliHikariPool
      jackson:
        date-format: yyyy-MM-dd HH:mm:ss
        time-zone: GMT+8
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    如图 6 所示,除了 hospitla-manage,其余所有 9 个 Spring-Boot 子项目均需要按照上述规则编写对应的配置文件。 hospitla-manage 的启动不依赖 Nacos,因此不需要。

    图 6 所有微服务的配置均已在 Nacos 中添加

    创建微服务部署流水线

    流水线表示应用从代码编译、测试、打包和部署的过程,KubeSphere 的流水线管理使用了业界常用的 Jenkinsfile 来表述一组 CI/CD 流程。 Jenkinsfile 是一个文本文件,使用了 Jenkins 提供的 DSL(Domain-Specific Language)语法。 KubeSphere 提供了可视化编辑器,用户只需在页面上输入少量配置信息,接口自动组装完成 Jenkinsfile。 当然,也可直接编辑 Jenkinsfile。

    流水线涉及如下几个概念:

    • Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作。Stage 是一个逻辑分组的概念,可以跨多个 Node。
    • Node:节点,一个 Node 就是一个 Jenkins 节点,或者是 Master,或者是 Agent,是执行 Step 的具体运行时环境。
    • Step:步骤,Step 是最基本的操作单元,小到创建一个目录,大到构建一个 Docker 镜像,由各类 Jenkins Plugin 提供。

    KubeSphere 默认提供的 Agent 有 base、go、maven 和 nodejs。它们分别适用于不同编程语言开发的项目的打包构建。 因为我们即将部署的 10 个子项目均是 Spring-Boot 应用,因此我们选择 maven 作为启动流水线的 agent。

    我们可以直接编写流水线的 Jenkinsfile,也可以通过 KubeSphere 提供的可视化页面编辑流水线。 通常,流水线的第一步是下载项目源代码 4,我们在 UI 上直接添加相关命令:

    图 7 基于 UI 编辑流水线第一步:源码拉取

    KubeSphere 会自动生成这次编辑的 Jenkinsfile 代码片段:

    stage('clone code') {
      agent none
      steps {
        // 拉取代码并展示代码文件布局
        container('maven') {
          git(url: 'https://gitee.com/leifengyang/yygh-parent', branch: 'master', changelog: true, poll: false)
          sh 'ls -al'
        }
      }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    流水线的第二个阶段通常是项目的打包与编译。 默认情况下,Maven 从官方仓库下载项目依赖,如果想要修改默认镜像仓库,需要修改集群中名为 ks-devops-agent 的 ConfigMap,它拥有一个叫做 MavenSetting 的键:

    k8s@ubuntu:~$ k get cm -A | grep devops
    his-devopsqxxv7                   istio-ca-root-cert                                           1      24h
    his-devopsqxxv7                   kube-root-ca.crt                                             1      24h
    kubesphere-devops-system          devops-config                                                1      5d7h
    kubesphere-devops-system          devops-jenkins                                               9      5d7h
    kubesphere-devops-system          istio-ca-root-cert                                           1      5d7h
    kubesphere-devops-system          jenkins-agent-config                                         1      5d7h
    kubesphere-devops-system          jenkins-casc-config                                          2      5d7h
    kubesphere-devops-system          kube-root-ca.crt                                             1      5d7h
    kubesphere-devops-worker          istio-ca-root-cert                                           1      5d7h
    kubesphere-devops-worker          ks-devops-agent                                              1      5d7h
    kubesphere-devops-worker          kube-root-ca.crt                                             1      5d7h
    k8s@ubuntu:~$ k describe cm ks-devops-agent -n kubesphere-devops-worker
    Name:         ks-devops-agent
    Namespace:    kubesphere-devops-worker
    Labels:       app.kubernetes.io/managed-by=Helm
    Annotations:  meta.helm.sh/release-name: devops
                  meta.helm.sh/release-namespace: kubesphere-devops-system
    
    Data
    ====
    MavenSetting:
    ----
    
    ...