云原生在京东丨基于 Tekton 打造下一代云原生 CI 平台

去年10月份,京东研发效能部紧跟云原生潮流,开始调研并引入 Tekton ,在内部尝试基于 Tekton 打造下一代云原生 CI 平台。java

云原生概念自2015年最初被说起后, 其生态在不断壮大。与此同时,支持云原生的开源工具如雨后春笋般出现。在众多开源工具中咱们把目光聚焦在了tekton上, 不只仅由于她是K8s“亲”生, 还由于与其余工具相比较,它更加轻量、更加灵活扩展,并支持多云环境, 拥有活跃的社区。Tekton虽然仍是一个挺新的项目,可是已经成为 Continuous Delivery Foundation (CDF) 四个初始项目之一。node

在不到一年的时间里,咱们经过对tektoncd/pipeline 工程的学习和验证,建设了jbuild等组件,并部署到业务生产环境的 Kubernetes 集群里,支持京东内部日均近万次应用构建任务。mysql

本文将分享如何使用 Tekton 推动CI平台往云原生方向发展和落地,同时分享一些在推动过程当中遇到的问题以及解决方案。git

Tekton是什么?

Tekton 是一个功能强大且灵活的 Kubernetes 原生开源框架,用于建立持续集成和交付(CI/CD)系统, 实现了CI/CD 中流程的控制。经过抽象底层实现细节,用户能够跨多云平台和本地系统完成构建、测试,、部署等环节。github

Tekton Pipeline中定义了几类对象,核心理念是经过定义yaml定义构建过程。下面咱们来介绍在实践和落地过程当中最经常使用的5类对象:sql

• Task:一个任务的执行模板,用于描述单个任务的构建过程;docker

• TaskRun:定义TaskRun设置具体须要运行的Task;shell

• Pipeline:包含多个Task, 对task进行编排(串 / 并 行);ubuntu

• PipelineRun:定义PipelineRun设置具体须要运行的Pipeline;api

• PipelineResource:可用于input和output的对象集合。

现状——老编译平台

image.png

架构简图

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平台实践&落地

image.png

架构简图

jbuildService

image.png

讲解:

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产生镜像并推送到镜像仓库, 这个实例相对简单, 所以直接串行之行各个步骤便可, 展现了两种可行方案;

image.png

example1

示例2: 一个java应用编译后出两个产物(image、pkg), 产物分别推送到镜像仓库、云存储,而后分别部署到不一样的环境中,部署完后通知测试人员。此示例与上面例子不一样的是包含了并行的逻辑, 缩短了总体运行时间;

image.png

example2

通过上面的两个示例发现能够根据具体的业务进行对task进行自由编排。示例2中含有并行执行的逻辑在缩短总体运行时间的同时增长了参数传递、数据传递的复杂度, jbuild成功地对用户进行了屏蔽, 用户只须要专心关注业务便可。

watchService

image.png

和 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运行时长的对比分析:

image.png

将来规划

海纳百川

“海纳百川, 有容乃大”, 咱们将来将会融入更多优秀的工具, 利于开发、测试、运维同窗能够方便快捷地完成需求,提升工做效率,加强工做的快感和生活的幸福感。

• 丰富代码扫描功能, 能够快速定位代码问题, 作到早发现早解决;

• 完善单元测试组件, 能够知足不通语言不一样架构的须要;

• 支持更多环境的编译, 知足不一样语言, 不一样业务的编译流程;

• 加强服务监控功能, 能够经过监控数据对服务的健康度以及使用状况进行很好的展现;

• 增长线上不一样环境的部署, 打通测试-开发-部署全流程。

在编译上咱们致力于提高编译率, 同时完善编译失败的信息提示, 最终作到根据错误直接给出对应的解决方案。

在部署上咱们将支持更多的发布方式(蓝绿部署, 金丝雀部署等), 知足用户在不一样的场景下能够更加轻松的进行发布同时下降回滚率。

点-线-面

打造全方面的平台, 既能够单独部署开源服务(好比, 快速部署一个mysql、tomcat),又能够知足开发在不一样开发阶段的不一样需求, 同时能够知足测试同窗和运维同窗需求。

image.png

总结

在不到一年的时间里tekton也在快速发展,、不断完善, 咱们的服务架构也会随之迭代。而在打造此服务期间, 咱们也意识到项目得以顺利进行与在开源社区中获得了许多帮助息息相关, 咱们将持续关注并为社区尽微薄之力。同时,咱们也将致力于为研发同窗打造一款助力工做提高工做快感的平台。

原文连接

相关文章
相关标签/搜索