去年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也在疾速倒退,、不断完善, 咱们的服务架构也会随之迭代。而在打造此服务期间, 咱们也意识到我的项目得以顺利进行与在开源社区中失去了许多帮忙非亲非故, 咱们将继续关注并为社区尽微薄之力。同时,咱们也将致力于为研发同学打造一款助力工作晋升工作快感的平台。
原文链接