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 是访问服务的 API 入口。在 3.0 中,ks-apigateway、ks-account 被合并到了 ks-apiserver。因此,ks-apiserver 承载了这两个组件的功能。
在 3.0 中,DevOps 依然与 KubeSphere Core 代码紧密耦合,没有运行单独的 Operator 处理相关的 CRD 资源。DevOps 所有 CRD 资源的处理都在 ks-controller-manager 中进行。
2.2 Jenkins 流水线
Jenkins 采用的是 Helm 进行安装和维护,相关的配置可以查看 GitHub 上 ks-installer 仓库 。
其中 Jenkins 镜像使用的是官方的,没有进行任何定制。
uc 是提供给 Jenkins 下载插件的服务。有两方面的原因需要 uc: 一方面是适配离线环境,同时线上官方地址下载慢;另一方面是有自行开发的插件需要集成。
uc 提供的只是一个 Nginx 下载服务,相关的镜像内容也只是为了存储 Jenkins 插件而已。
3. 如何搭建流水线的开发环境
3.1 本地安装 Go、Kubebuilder 基础环境
在此不会详细描述,仅以 OS X 为例。
brew install golang
查看版本
go version
go version go1.14.4 darwin/amd64
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 安装并配置本地访问
推荐安装工具 Kubekey。现在主流的集群安装工具是基于 Kubeadm 的二次封装。Kubekey 的优势是国内安装快,配置简单。使用 Kubeadm 半小时的工作量,Kubekey 两分钟解决,还集成了不少插件。这也符合我提倡的,技能文档化,文档工具化,工具产品化,产品服务化 的想法。
在安装时,需要注意一个参数。
controlPlaneEndpoint:
domain: k2
由于 kube-apiserver 采用的是 https 通信,这里的 domain 会被设置到 certSANs 中生成证书。通过 certSANs 中地址进行地访问才是合法的。
完成安装 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
简单一点,可以直接拷贝服务器的文件到本地保存。这里提供一份脚本,可以快速切换多个环境。注意,替换 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 root@$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/kubernetes-admin@cluster.local'/$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 本地运行测试服务
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 中,可以设置一下变量,通过 {{ VAR_NAME }}
的形式可以引用,十分方便。下面有两类 API 的调用示例:
一种是 CRD 资源的增删改查,带上 Token 即可。
另一种是透传 Jenkins API ,不仅需要带上 Token,还需要带上 Jenkins Crumb 。这些参数通过页面访问时,在 Cookies 中都可以拿到。
为了避免干扰,需要先暂停集群中的 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
make ks-apiserver
docker build -f build/ks-apiserver/Dockerfile -t shaowenchen/ks-apiserver:latest .
docker push shaowenchen/ks-apiserver:latest
执行命令,更新镜像为 shaowenchen/ks-apiserver:latest 即可。
kubectl -n kubesphere-system edit deploy ks-apiserver
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 等。
KubeSphere 以 /kapis
为前缀提供 API, 在这里还需要对每个 URL 进行更详细的描述和处理,通常会调用到很多 models 中的函数方法。API 文档也是在这里进行描述的。
对于数据层的操作,被聚合在 models 中。这里提供了很多对数据的操作方法。
- pkg/controller/devopscredential
credential 的 controller 处理部分。
- pkg/controller/devopsproject
devopsproject 的 controller 处理部分。
pipeline 的 controller 处理部分。
上面的代码理解起来相对容易,这部分会有些难度。这里的主要功能是提供对 Jenkins 的操作函数方法。在实现时,抽象了一个 Interface ,希望能够对接不同的编排工具。
简单说了下代码目录,可能还是不够清晰。下面一起通过调用逻辑看看代码。
6.2 关于 CRD 类型的代码逻辑
以创建 DevOps 工程为例。
前端调用 /kapis/devops.kubesphere.io/v1alpha3/workspaces/liuxin-test/devops/ ,传递参数
后端处理 https://github.com/kubesphere/kubesphere/blob/release-3.0/pkg/kapis/devops/v1alpha3/register.go#L154 ,通过生成的 client 写入 Etcd
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 关于触发类型的代码逻辑
以触发代码扫描为例。
前端调用 kapis/devops.kubesphere.io/v1alpha2/devops/test2-projectwvb2n/pipelines/test1-pipeline/scan/ , 传递参数
后端处理 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 目录。
流水线相关的组件。而 src/components 中是整个项目公共的组件。
对应流水线相关的页面。如下图,左侧选项卡与文件夹中文件名一一对应,包括流水线图形化编辑页面。
单页面应用的路由配置。
7.2 环境搭建
请参考 https://github.com/kubesphere/console 的文档。
refer to https://www.chenshaowen.com/blog/the-development-guide-of-kubesphere-devops-3-0-pipeline.html