共计 6200 个字符,预计需要花费 16 分钟才能阅读完成。
去年 10 月份,京东研发效力部紧跟云原生潮流,开始调研并引入 Tekton,在外部 尝试基于 Tekton 打造下一代云原生 CI 平台。
云原生概念自 2015 年最后被提及后, 其生态在一直壮大。与此同时,反对云原生的开源工具如雨后春笋般呈现。在泛滥开源工具中咱们把眼光聚焦在了 tekton 上, 不仅仅因为她是 K8s“亲”生, 还因为与其余工具相比拟,它 更加轻量、更加灵便扩大 ,并 反对多云环境, 领有 沉闷的社区。Tekton 尽管还是一个挺新的我的项目,然而曾经成为 Continuous Delivery Foundation (CDF) 四个初始我的项目之一。
在不到一年的工夫里,咱们通过对 tektoncd/pipeline 工程的学习和验证,建设了 jbuild 等组件,并 部署到业务生产环境的 Kubernetes 集群里,反对京东外部日均近万次利用构建工作。
本文将分享如何应用 Tekton 推动 CI 平台往云原生方向倒退和落地,同时分享一些在推动过程中遇到的问题以及解决方案。
Tekton是什么?
Tekton 是一个功能强大且灵便的 Kubernetes 原生开源框架,用于创立继续集成和交付(CI/CD)零碎, 实现了 CI/CD 中流程的管制。通过形象底层实现细节,用户能够跨多云平台和本地零碎实现构建、测试,、部署等环节。
Tekton Pipeline 中定义了几类对象,核心理念是通过定义 yaml 定义构建过程。上面咱们来介绍在实际和落地过程中最罕用的 5 类对象:
• Task:一个工作的执行模板,用于形容单个工作的构建过程;
• TaskRun:定义 TaskRun 设置具体须要运行的 Task;
• Pipeline:蕴含多个 Task, 对 task 进行编排(串 / 并 行);
• PipelineRun:定义 PipelineRun 设置具体须要运行的 Pipeline;
• PipelineResource:可用于 input 和 output 的对象汇合。
现状——老编译平台
架构简图
jeciService 介绍
jeci 编译平台: jenkins(2.89.3) + k8sCluster(1.13)
2017 年年中,咱们开始对 jenkins 的 pipeline 性能进行验证,应用此性能能够间接对接 k8s。master 从工作节点转扭转成仅做工作转发的节点, 理论编译工作将会在设置的 k8sCluster 中启动对应的 pod 进行编译。pod 的生命周期等同于一次编译的生命周期。此性能很快就对线上提供了服务, 节俭了一批 master 节点。
服务运行近两年中, 在理论应用过程中遇到一些问题, 例如:
• jenkinsfile 并不能很好的反对 shell 脚本, 有些符号须要进行转换;
• 须要独自学习 jenkinsfile 的语法;
• 新减少插件服务须要重启;
• jenkins master 节点上因 job 数量太多, 导致关上界面超级慢;
• 新减少新 master 须要进行从新的配置;
• 当编译量大的时候 jenkins 与 k8s 之间的调度偶发的会呈现问题;
云原生 CI 平台实际 & 落地
架构简图
jbuildService
解说:
BusinessScene: 解决各种业务场景的业务逻辑;
TemplateEngine: 形象 yaml 模版, 依据不同的业务场景抉择对应的模版进行渲染;
Tekton pipeline 提供了 PipelineResource、Task、Pipeline、TaskRun 和 PipelineRun 等几个 CRD,其中 Task/Pipeline 作为模板类型,占据十分重要的位置。pipeline 具备对 tasks 进行编排的能力, 此性能是 Tekton pipeline 的外围性能之一, 也是 TemplateEngine 解决的次要问题之一。
Tekton 对一个工作的执行分为三个阶段:
• 资源、参数输出,包含 git 代码库,task/pipeline/pipelineRun 之间参数的传递等;
• 执行逻辑,如 mvn clean package、docker build 等;
• 定义资源输入,docker push 等。
此服务对上述的三个阶段简单的逻辑进行屏蔽, 向外透出简略的接口, 用户无需关怀 task/pipeline/pipelineRun 之间参数传递, 无需关怀 pipeline 如何对 task 如何进行编排。
示例
示例 1: Java 利用构建实现后应用 kaniko 产生镜像并推送到镜像仓库, 这个实例绝对简略, 因而间接串行之行各个步骤即可, 展现了两种可行计划;
example1
示例 2: 一个 java 利用编译后出两个产物(image、pkg), 产物别离推送到镜像仓库、云存储,而后别离部署到不同的环境中,部署完后告诉测试人员。此示例与下面例子不同的是蕴含了并行的逻辑, 缩短了整体运行工夫;
example2
通过下面的两个示例发现能够依据具体的业务进行对 task 进行自在编排。示例 2 中含有并行执行的逻辑在缩短整体运行工夫的同时减少了参数传递、数据传递的复杂度, jbuild 胜利地对用户进行了屏蔽, 用户只须要分心关注业务即可。
watchService
和 K8s 其它的 CRD 一样,tektoncd/pipeline 所有的 CRD 申明、实例都存储在 K8s 体系内的 etcd 组件里。这样的设计带来了历史运行数据长久化的问题, 同时 tekton 对曾经运行过的 CRD 没有主动删除的性能, 当历史数据越来越多时资源将被耗尽。
watchService 解决了下面的所有问题:
• 清理历史资源;
• 长久化日志信息;
• 统计运行数据等;
问题 & 解决方案
tekton 版本 0.9.0; k8s 集群版本 1.13;
1:tekton 装置后 controller 无奈失常启动
问题形容:
0.9.0 版本中 controller 默认监听是 K8s 集群内所有的 namespace 下的 pod。如果配置的 K8s config 中认证不是针对所有 namespace 都有权限的场景中会导致 install te k ton 时 controller 无奈失常启动。
解决办法:
批改在 controller/main.go 源码, 减少了只容许监听 tekton-pipelines 这个 namespace。
减少代码如下:
ctx := injection.WithNamespaceScope(signals.NewContext(), system.GetNamespace())
sharedmain.MainWithContext(ctx, ControllerLogKey,
taskrun.NewController(images),
pipelinerun.NewController(images),
)
注: 高版本修复了此问题, 减少了启动参数能够进行指定, 默认监听所有 namespace。
namespace 参数原文解释:
Namespace to restrict informer to. Optional, defaults to all namespaces.
2: 统计运行时长
提供了两种解决方案, 能够依据理论业务需要进行抉择, 两种计划均已验证:
计划一: 采纳 watch 机制
采纳 K8s 的 watch 个性, 提供一个服务做相应的业务解决, 这就须要具体业务具体分析。
事件:ADDED, MODIFIED, DELETED, ERROR
计划二: 在工作的 container 中减少回调性能
因业务场景须要, 须要对执行命令的工夫进行较为精确的计算, 在计划一中发现采纳这种形式会有肯定工夫的延时, 因而进行了计划二的设计。
通过对 tekton 源码的学习理解 entrypoint 管制了每个 container 什么时候开始执行命令, 因而咱们正在 entrypoint 中减少 callback 接口, 在每次执行前与执行后回调用用户指定的 API(此 API 是通过环境变量的传入), 这样计算出的时长相对来说更加精确。
3:pipelineRun 沉积
批改 DefaultThreadsPerController 的值而后从新对 controller 生成镜像即可; 此参数源码注解如下:
// DefaultThreadsPerController is the number of threads to use
// when processing the controller’s workqueue. Controller binaries
// may adjust this process-wide default. For finer control, invoke
// Run on the controller directly.
4: 命令被提早执行
测试场景形容:
简略的一个 maven 类型我的项目的编译, 蕴含的步骤有代码下载、代码编译。一次编译对应一个 pod, pod 中蕴含两个 container 别离是 代码下载、代码编译。两个 container 串行执行。
三个节点的 K8s 集群, 在资源短缺的状况下, 应用 jmeter 进行压测, 对下面的编译场景启动 500 次编译(会启动 500 个 pod)。
景象形容:
在 500 个 pod 中会呈现几个 pod 的运行时长显著长于其余 pod, 别离进入两个 container 中查看, 发现第一个 container(代码下载的 pod, 500 个 pod 应用的是同一个代码库)的运行工夫比失常 pod 中第一个 container 的运行工夫要长出 20s-70s 不等, 有的甚至更长。
注:
1) 以上场景反复很屡次并不是每次都会呈现提早执行的景象
2) 如果对源码中启动 DefaultThreadsPerController(默认为 2)没有进行批改, 则可能会出新 pipelineRun 沉积的状况, 属于失常景象, 能够通过增大次参数的值靠近沉积的问题(批改后须要对 controller 进行从新生成镜像)。
通过对源码和对应 pod 的 yaml 文件剖析能够发现 tekton 应用了 K8s 中的 downwardAPI 机制, 将 pod 中的信息以文件的模式挂载到 container 中, 例如上面的 yaml 能够发现:
1) 名字为 clone 的 container 应用 volumeMounts 挂载了 downward, 其余的 container 并没有挂载 downward。因而 clone 容器是想获取 pod 中 tekton.dev/ready 的内容。
# 伪 yaml
kind: Pod
metadata:
…
annotations:
tekton.dev/ready: READY
spec:
volumes:
– name: downward
downwardAPI:
items:
– path: ready
fieldRef:
apiVersion: v1
fieldPath: ‘metadata.annotations[”tekton.dev/ready”]’
defaultMode: 420
containers:
– name: clone
image: ubuntu
command:
– /tekton/tools/entrypoint
args:
– ‘-wait_file’
– /tekton/downward/ready
– ‘-wait_file_content’
– ‘-post_file’
– /tekton/tools/0
– ‘-entrypoint’
– /ko-app/git-init
– ‘–‘
– ‘-url’
– ‘https://github.jd.com/test/te…’
– ‘-revision’
– “master”
– ‘-path’
– /workspace/test
volumeMounts:
– name: downward
mountPath: /tekton/downward
…
2) clone container 中的 command 为 /tekton/tools/entrypoint, args 中 -wait_file -wait_file_content -post_file -entrypoint 均为 entrypoint 的参数(因为在定义 task 时并未写这些参数, 同时也能够看 entrypoint 的源码也能够发现), 在查看 entry point 源码时看到如下逻辑
/*
file 为 wait_file 所对应的值
expectContent 为 wait_file_content 的值,默认为 false;
wait_file_content 此参数在对 task 的 step 进行编排时判断如果是第一个 step 则增加, 后续 step 不会增加此参数
*/
func (*realWaiter) Wait(file string, expectContent bool) error {
…
for ; ; time.Sleep(waitPollingInterval) {
log.Printf(“1. wait for file, time: %v”, time.Now())
_/*_
获取此文件的属性,判断文件大小如果大于 0 则期待完结
或者 expectContent 为 false 期待完结
*/
if info, err := os.Stat(file); err == nil {
if !expectContent || info.Size() > 0 {
log.Printf(“2. finish wait for file, time: %v”, time.Now())
return nil
}
}
…
因而下面在测试中呈现的问题能够发现 clone 容器在执行的时候 file 中的内容为 0, 因而统一在 for 循环无奈退出, 直到 file 文件外面有内容才会跳出 for 循环开始前面执行自定义的命令。
解决办法:
通过上述的形容, 对 task 的 step 进行编排时第一个容器去掉 wait_file_content 即可。
老编译平台 VS 云原生 CI 平台
云原生 CI 平台上线后在编译提速上有了很可观的改善;仅仅应用三台 K8sCluster 的 node 节点, 便反对了老编译中个别的日编译流量;大大减少了对 jenkins 的保护工作, 因而无需再有内部的服务时刻监控 jenkins master 是否为可用状态;同时,应用新的工具插件时, 无需再重启 master, 一个镜像便能够搞定, 不便又快捷。
下图为新老编译平台 job 运行时长的比照剖析:
将来布局
海纳百川
“海纳百川, 有容乃大”, 咱们将来将会融入更多优良的工具, 利于开发、测试、运维同学能够方便快捷地实现需要, 进步工作效率, 加强工作的快感和生存的幸福感。
• 丰盛代码扫描性能, 能够疾速定位代码问题, 做到早发现早解决;
• 欠缺单元测试组件, 能够满足不通语言不同架构的须要;
• 反对更多环境的编译, 满足不同语言, 不同业务的编译流程;
• 加强服务监控性能, 能够通过监控数据对服务的衰弱度以及应用状况进行很好的展现;
• 减少线上不同环境的部署, 买通测试 - 开发 - 部署全流程。
在编译上咱们致力于晋升编译率, 同时欠缺编译失败的信息提醒, 最终做到依据谬误间接给出对应的解决方案。
在部署上咱们将反对更多的公布形式(蓝绿部署, 金丝雀部署等), 满足用户在不同的场景下能够更加轻松的进行公布同时升高回滚率。
点 - 线 - 面
打造全方面的平台, 既能够独自部署开源服务(比方, 疾速部署一个 mysql、tomcat), 又能够满足开发在不同开发阶段的不同需要, 同时能够满足测试同学和运维同学需要。
总结
在不到一年的工夫里 tekton 也在疾速倒退,、不断完善, 咱们的服务架构也会随之迭代。而在打造此服务期间, 咱们也意识到我的项目得以顺利进行与在开源社区中失去了许多帮忙非亲非故, 咱们将继续关注并为社区尽微薄之力。同时,咱们也将致力于为研发同学打造一款助力工作晋升工作快感的平台。
原文链接