目录

    KubeSphere DevOps 包含 S2I 和 Pipeline 两部分。在社区中,openshift 提供了一个打包应用的工具 S2I,具体请参考 使用 S2I 构建云原生应用 。KubeSphere 将其做成了服务,采用 CRD 使用一个单独的 Operator 对其进行管理,功能比较独立。而在 3.0 中 Pipeline 与 KubeShere Core 耦合依然十分紧密,在搭建环境和调试上略显复杂。本篇主要提供开发者维护和二次开发 KubeSphere DevOps 3.0 指引。

    1. DevOps 流水线架构

    下图是流水线的整体架构:

    1.1 存储模型

    产品概念Kubernetes 对象Jenkins 对象
    DevOps 工程DevopsProject文件夹
    流水线Pipeline流水线/多分支流水线
    凭证Credential文件夹下的凭据

    1.2 数据流

    主要涉及两类,一类是创建类型的操作,另一类是触发类型的操作。

    创建类型的操作,主要包括三种 CRD 类型的创建,DevopsProject、Pipeline、Credential 。用户通过前端,调用 ks-apiserver 接口,创建对应资源存储在 Etcd 中,然后 ks-controller-manager 不断地将这些对象同步到 Jenkins 。

    触发类型的操作,主要是执行、审核流水线等瞬时动作。用户通过前端,调用 ks-apiserver 经过数据转换,直接调用 Jenkins API 。

    2. DevOps 流水线相关的组件

    2.1 系统核心组件

    • ks-apiserver

    ks-apiserver 是访问服务的 API 入口。在 3.0 中,ks-apigateway、ks-account 被合并到了 ks-apiserver。因此,ks-apiserver 承载了这两个组件的功能。

    • ks-controller-manager

    在 3.0 中,DevOps 依然与 KubeSphere Core 代码紧密耦合,没有运行单独的 Operator 处理相关的 CRD 资源。DevOps 所有 CRD 资源的处理都在 ks-controller-manager 中进行。

    2.2 Jenkins 流水线

    • ks-jenkins

    Jenkins 采用的是 Helm 进行安装和维护,相关的配置可以查看 GitHub 上 ks-installer 仓库

    其中 Jenkins 镜像使用的是官方的,没有进行任何定制。

    • uc-jenkins-update-center

    uc 是提供给 Jenkins 下载插件的服务。有两方面的原因需要 uc: 一方面是适配离线环境,同时线上官方地址下载慢;另一方面是有自行开发的插件需要集成。

    uc 提供的只是一个 Nginx 下载服务,相关的镜像内容也只是为了存储 Jenkins 插件而已。

    3. 如何搭建流水线的开发环境

    3.1 本地安装 Go、Kubebuilder 基础环境

    在此不会详细描述,仅以 OS X 为例。

    • 安装 Golang
    brew install golang
    

    查看版本

    go version
    
    go version go1.14.4 darwin/amd64
    
    • 安装 Kubebuilder
    brew install kubebuilder
    

    查看版本

    kubebuilder version
    
    Version: version.Version{KubeBuilderVersion:"2.3.0", KubernetesVendor:"1.16.4", GitCommit:"800f63a7e41a6a8016d4cb9d583e1705b0812c9d", BuildDate:"2020-02-28T19:15:41Z", GoOs:"unknown", GoArch:"unknown"}
    

    3.2 安装并配置本地访问

    • 安装 Kubernetes 集群

    推荐安装工具 Kubekey。现在主流的集群安装工具是基于 Kubeadm 的二次封装。Kubekey 的优势是国内安装快,配置简单。使用 Kubeadm 半小时的工作量,Kubekey 两分钟解决,还集成了不少插件。这也符合我提倡的,技能文档化,文档工具化,工具产品化,产品服务化 的想法。

    在安装时,需要注意一个参数。

    controlPlaneEndpoint:
        domain: k2
    

    由于 kube-apiserver 采用的是 https 通信,这里的 domain 会被设置到 certSANs 中生成证书。通过 certSANs 中地址进行地访问才是合法的。

    • 开发环境配置 hosts

    完成安装 Kubernetes 集群之后,在开发环境配置 hosts ,即可直接通过 domain 进行远程访问 kube-apiserver 。下面是我本地的 /etc/hosts 配置,一共有三个集群:

    cat /etc/hosts
    
    139.198.x.x  k1
    139.198.x.x  k2
    139.198.x.x  k3
    
    • 开发环境配置 kubeconfig

    简单一点,可以直接拷贝服务器的文件到本地保存。这里提供一份脚本,可以快速切换多个环境。注意,替换 your_password 为你的远程登陆密码。

    # Switch K8s
    function on_k8s() {
       if test -f ~/.kube/config.bk; then
         rm -rf ~/.kube/config.bk
       fi
       if test -f ~/.kube/config; then
         mv ~/.kube/config ~/.kube/config.bk
       fi
       sed -i'.s' -e '/$1/d'  ~/.ssh/known_hosts
       sshpass  -p "your_password" ssh -o StrictHostKeyChecking=no [email protected]$1 "cat /etc/kubernetes/admin.conf" > ~/.kube/config
       sed -i'.s' -E 's/([0-9]{1,3}\.){3}[0-9]{1,3}'/$1/ ~/.kube/config
       sed -i'.s' -E 's/[email protected]'/$1/ ~/.kube/config
    }
    

    使用时,执行 on_k8s k1 切换到 k1 环境,执行 on_k8s k2 切换到 k2 环境。

    • 验证配置是否成功
    # 切换环境
    on_k8s k2
    
    # 执行 kubectl 命令
    kubectl get node
    
    NAME     STATUS   ROLES    AGE   VERSION
    master   Ready    master   54d   v1.17.9
    node1    Ready    worker   54d   v1.17.9
    node2    Ready    worker   54d   v1.17.9
    

    3.3 克隆 KubeSphere 仓库代码

    star & fork GitHub 项目 kubesphere/kubesphere 。Git 的提交流程可以参考文档: 一个完整的 Git 提交流程

    • 克隆代码
    git clone https://github.com/shaowenchen/kubesphere
    
    • 进入项目目录
    cd kubesphere
    

    3.4 配置 Webhook 证书

    tree $TMPDIR/k8s-webhook-server
    
    /Users/shaowenchen/Temp/k8s-webhook-server
    └── serving-certs
        ├── ca.crt
        ├── ca.key
        ├── ca.srl
        ├── tls.crt
        └── tls.key
    
    1 directory, 5 files
    

    可以拷贝其他地方已经生产的证书,也可以参考文档 生成自签证书 。放置在 TMPDIR 目录的指定文件夹中,如上命令所示。

    3.5 配置 kubesphere.yaml

    • 在项目根目录下,新增 kubesphere.yaml 文件。
    kubernetes:
      kubeconfig: "/Users/shaowenchen/.kube/config"
      master: k2:6443
      qps: 1e+06
      burst: 1000000
    
    devops:
      host: http://k2:30180/
      username: admin
      password: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFkbWluQGt1YmVzcGhlcmUuaW8iLCJ1c2VybmFtZSI6ImFkbWluIiwidG9rZW5fdHlwZSI6InN0YXRpY190b2tlbiJ9.eoVAs9uWPi54YTknQ4NaaomVdq3q-THsZMOb4TwChU4
      maxConnections: 100
    
    sonarQube:
      host: http://k2:30594
      token: cfa96640569d9ce3b6f84ae287bcc1a970973958
    s3:
      endpoint: http://k2:9001
      region: us-east-1
      disableSSL: true
      forcePathStyle: true
      accessKeyID: openpitrixminioaccesskey
      secretAccessKey: openpitrixminiosecretkey
      bucket: s2i-binaries
    authentication:
      authenticateRateLimiterMaxTries: 10
      authenticateRateLimiterDuration: 10m0s
      loginHistoryRetentionPeriod: 168h
      maximumClockSkew: 10s
      multipleLogin: true
      kubectlImage: kubesphere/kubectl:v1.0.0
      jwtSecret: "2b0WJPoacvbvMB82bTEWO4s4vFlmzoPd"
      oauthOptions:
        accessTokenMaxAge: 0
        AccessTokenInactivityTimeout: 0
      authorization:
        mode: "RBAC"
        # mode: "AlwaysAllow"
    monitoring:
      endpoint: FAKE
    ldap:
      host: FAKE
    redis:
      host: FAKE
    

    这里我直接使用远程环境的访问地址,比使用 telepresence 通过集群服务地址访问效率更高。kubesphere.yaml 中相关的配置值,可以从 kubectl get cm kubesphere-config -n kubesphere-system -o yaml 的输出中获取,粘贴到开发环境中即可。

    值得注意的是 3.0 中,采用外置的 Sonarqube ,因此,需要根据官方文档进行单独配置。kubeconfig 字段指向的是开发环境 kubeconfig 文件地址,而线上使用的是 serviceaccount 。S2I 依赖于 S3 服务存储二进制文件,流水线不需要 S3。

    3.6 本地运行测试服务

    • 运行 ks-apiserver
    go run cmd/ks-apiserver/apiserver.go --logtostderr=true --v=8 --debug=true
    

    服务启动之后,会打印 Start listening on :9090 表示,ks-apiserver 监听在 9090 端口,可以访问。

    通过 Postman 进行访问时,需要带上集群前端的 Token,也可以使用 kubesphere-config 中的永久 Token,还可以将 mode 改为 AlwaysAllow 关掉鉴权。

    在 Postman 中,可以设置一下变量,通过 的形式可以引用,十分方便。下面有两类 API 的调用示例:

    一种是 CRD 资源的增删改查,带上 Token 即可。

    另一种是透传 Jenkins API ,不仅需要带上 Token,还需要带上 Jenkins Crumb 。这些参数通过页面访问时,在 Cookies 中都可以拿到。

    • 运行 ks-controller-manager

    为了避免干扰,需要先暂停集群中的 ks-controller-manager 。

    kubectl scale deploy ks-controller-manager --replicas=0 -n kubesphere-system
    

    运行本地的 ks-controller-manager

    go run cmd/controller-manager/controller-manager.go --logtostderr=true --v=8 --multiple-clusters=false
    

    4. 如何发布到集群环境

    建议将相关服务的 imagePullPolicy 改为 Always ,确保每次使用的都是最新的镜像。

    4.1 更新插件

    建立如下目录结构:

    tree -L 1
    .
    |-- Dockerfile
    `-- webroot
    
    1 directory, 2 files
    

    Dockerfile 内容

    FROM busybox:1.29.3
    
    COPY webroot/ /webroot/
    

    webroot 中的内容,可以从 kubesphere/jenkins-uc:v3.0.0 中使用 docker cp 命令拷贝,然后替换、增删其中的离线插件。

    使用 docker build . -t shaowenchen/jenkins-uc:latest 命令打包,推送镜像。然后执行命令 kubectl -n kubesphere-devops-system edit deploy uc-jenkins-update-center ,修改 uc 的服务镜像为 shaowenchen/jenkins-uc:latest ,重启 deploy 即可。

    这里需要注意的是 Jenkins 只有在首次初始化时,访问 uc 获取插件。初始化插件列表在 kubectl -n kubesphere-devops-system get cm ks-jenkins -o yaml 中可以查看。

    4.2 更新 ks-apiserver 或 ks-controller-manger

    • 编译 ks-apiserver
    make ks-apiserver
    
    • 编译并推送 ks-apiserver 镜像
    docker build -f build/ks-apiserver/Dockerfile -t shaowenchen/ks-apiserver:latest .
    docker push shaowenchen/ks-apiserver:latest
    
    • 更新 ks-apiserver 服务

    执行命令,更新镜像为 shaowenchen/ks-apiserver:latest 即可。

    kubectl -n kubesphere-system edit deploy ks-apiserver
    
    • 编译 ks-controller-manager
    make controller-manager
    
    • 编译并推送 ks-controller-manager 镜像
    docker build -f build/ks-controller-manager/Dockerfile -t shaowenchen/ks-controller-manager:latest .
    docker push shaowenchen/ks-controller-manager:latest
    
    • 更新 ks-controller-manger 服务

    执行命令,更新镜像为 shaowenchen/ks-controller-manager:latest 即可。

    kubectl -n kubesphere-system edit deploy ks-controller-manager
    

    5. 关于鉴权插件

    kubesphere-token-auth-plugin 插件主要是为了集成 KubeSphere 的权限体系,使 Jenkins 与之保持一致。如下图,无论是 CRD 资源类型,还是触发动作类型,在调用 Jenkins 时,都需要经过 ks-apiserver 进行 token 的 review。

    在 KubeSphere 中 Token 是 bearer 类型,但是插件是基于 Basic 鉴权进行的扩展,所以需要转换。代码如下:

    https://github.com/kubesphere/kubesphere/blob/release-3.0/pkg/simple/client/devops/jenkins/request.go#L47:6

    func SetBasicBearTokenHeader(header *http.Header) error {
        bearTokenArray := strings.Split(header.Get("Authorization"), " ")
        bearFlag := bearTokenArray[0]
        var err error
        if strings.ToLower(bearFlag) == "bearer" {
            bearToken := bearTokenArray[1]
            if err != nil {
                return err
            }
            claim := authtoken.Claims{}
            parser := jwt.Parser{}
            _, _, err = parser.ParseUnverified(bearToken, &claim)
            if err != nil {
                return err
            }
            creds := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", claim.Username, bearToken)))
            header.Set("Authorization", fmt.Sprintf("Basic %s", creds))
        }
        return nil
    }
    

    插件中的逻辑主要是,调用 ks-apiserver 鉴权接口,返回合法用户。

    6. 后端代码结构及逻辑

    6.1 代码目录

    代码路径以 https://github.com/kubesphere/kubesphere/tree/release-3.0 为例。

    • pkg/apiserver/apiserver.go

    通过 ks-apiserver 对外提供的接口,都需要在这里进行注册,配置 GVR 等。

    • pkg/kapis/devops

    KubeSphere 以 /kapis 为前缀提供 API, 在这里还需要对每个 URL 进行更详细的描述和处理,通常会调用到很多 models 中的函数方法。API 文档也是在这里进行描述的。

    • pkg/models/devops

    对于数据层的操作,被聚合在 models 中。这里提供了很多对数据的操作方法。

    • pkg/controller/devopscredential

    credential 的 controller 处理部分。

    • pkg/controller/devopsproject

    devopsproject 的 controller 处理部分。

    • pkg/controller/pipeline

    pipeline 的 controller 处理部分。

    • pkg/simple/client/devops

    上面的代码理解起来相对容易,这部分会有些难度。这里的主要功能是提供对 Jenkins 的操作函数方法。在实现时,抽象了一个 Interface ,希望能够对接不同的编排工具。

    简单说了下代码目录,可能还是不够清晰。下面一起通过调用逻辑看看代码。

    6.2 关于 CRD 类型的代码逻辑

    以创建 DevOps 工程为例。

    1. 前端调用 /kapis/devops.kubesphere.io/v1alpha3/workspaces/liuxin-test/devops/ ,传递参数

    2. 后端处理 https://github.com/kubesphere/kubesphere/blob/release-3.0/pkg/kapis/devops/v1alpha3/register.go#L154 ,通过生成的 client 写入 Etcd

    3. ks-controller-manager 处理 https://github.com/kubesphere/kubesphere/blob/release-3.0/pkg/controller/devopsproject/devopsproject_controller.go#L205 ,这里单步运行,可以缕清整个链路。最终是调用 pkg/simple/client/devops 中的接口,在 Jenkins 中,创建一个文件夹。

    6.3 关于触发类型的代码逻辑

    以触发代码扫描为例。

    1. 前端调用 kapis/devops.kubesphere.io/v1alpha2/devops/test2-projectwvb2n/pipelines/test1-pipeline/scan/ , 传递参数

    2. 后端处理 https://github.com/kubesphere/kubesphere/blob/release-3.0/pkg/kapis/devops/v1alpha2/register.go#L479 ,这是就不需要经过 ks-controller-manager,单步运行调试会发现,依然会进入 pkg/simple/client/devops 然后组装 xml 调用 Jenkins API 。

    7. 前端代码结构和逻辑

    前端的代码结构清晰,非常直观,文件名能与页面对应上。下面以仓库地址 https://github.com/kubesphere/console/tree/release-3.0 为例进行说明。另外,前端采用的是 React 框架。

    7.1 代码目录

    主要的逻辑都在 src/pages/devops 目录。

    • components

    流水线相关的组件。而 src/components 中是整个项目公共的组件。

    • containers

    对应流水线相关的页面。如下图,左侧选项卡与文件夹中文件名一一对应,包括流水线图形化编辑页面。

    • routes

    单页面应用的路由配置。

    7.2 环境搭建

    请参考 https://github.com/kubesphere/console 的文档。