去年10月份,京東研發效能部緊跟雲原生潮流,開始調研並引入 Tekton ,在內部嘗試基於 Tekton 打造下一代雲原生 CI 平臺。java
雲原生概念自2015年最初被說起後, 其生態在不斷壯大。與此同時,支持雲原生的開源工具如雨後春筍般出現。在衆多開源工具中咱們把目光聚焦在了tekton上, 不只僅由於她是K8s「親」生, 還由於與其餘工具相比較,它更加輕量、更加靈活擴展,並支持多雲環境, 擁有活躍的社區。Tekton雖然仍是一個挺新的項目,可是已經成爲 Continuous Delivery Foundation (CDF) 四個初始項目之一。node
在不到一年的時間裏,咱們經過對tektoncd/pipeline 工程的學習和驗證,建設了jbuild等組件,並部署到業務生產環境的 Kubernetes 集羣裏,支持京東內部日均近萬次應用構建任務。mysql
本文將分享如何使用 Tekton 推動CI平臺往雲原生方向發展和落地,同時分享一些在推動過程當中遇到的問題以及解決方案。git
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的對象集合。
架構簡圖
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之間的調度偶發的會出現問題;
架構簡圖
講解:
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成功地對用戶進行了屏蔽, 用戶只須要專心關注業務便可。
和 K8s 其它的 CRD 同樣,tektoncd/pipeline 全部的 CRD聲明、實例都存儲在 K8s 體系內的 etcd 組件裏。這樣的設計帶來了歷史運行數據持久化的問題, 同時tekton對已經運行過的CRD沒有自動刪除的功能, 當歷史數據愈來愈多時資源將被耗盡。
watchService解決了上面的全部問題:
• 清理歷史資源;
• 持久化日誌信息;
• 統計運行數據等;
tekton版本0.9.0; k8s集羣版本1.13;
問題描述:
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.
提供了兩種解決方案, 能夠根據實際業務需求進行選擇, 兩種方案均已驗證:
方案一: 採用watch機制
採用K8s的watch特性, 提供一個服務作相應的業務處理, 這就須要具體業務具體分析。
事件:ADDED, MODIFIED, DELETED, ERROR
方案二: 在任務的container中增長回調功能
因業務場景須要, 須要對執行命令的時間進行較爲準確的計算, 在方案一中發現採用這種方式會有必定時間的延時, 所以進行了方案二的設計。
通過對tekton源碼的學習瞭解entrypoint控制了每一個container何時開始執行命令, 所以咱們正在entrypoint中增長callback接口,在每次執行前與執行後回調用用戶指定的API(此API是經過環境變量的傳入), 這樣計算出的時長相對來講更加準確。
修改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.
測試場景描述:
簡單的一個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便可。
雲原生CI平臺上線後在編譯提速上有了很可觀的改善;僅僅使用三臺K8sCluster的node節點,便支持了老編譯中通常的日編譯流量;大大減小了對jenkins的維護工做, 所以無需再有外部的服務時刻監控jenkins master是否爲可用狀態;同時,使用新的工具插件時, 無需再重啓master, 一個鏡像即可以搞定, 方便又快捷。
下圖爲新老編譯平臺job運行時長的對比分析:
「海納百川, 有容乃大」, 咱們將來將會融入更多優秀的工具, 利於開發、測試、運維同窗能夠方便快捷地完成需求,提升工做效率,加強工做的快感和生活的幸福感。
• 豐富代碼掃描功能, 能夠快速定位代碼問題, 作到早發現早解決;
• 完善單元測試組件, 能夠知足不通語言不一樣架構的須要;
• 支持更多環境的編譯, 知足不一樣語言, 不一樣業務的編譯流程;
• 加強服務監控功能, 能夠經過監控數據對服務的健康度以及使用狀況進行很好的展現;
• 增長線上不一樣環境的部署, 打通測試-開發-部署全流程。
在編譯上咱們致力於提高編譯率, 同時完善編譯失敗的信息提示, 最終作到根據錯誤直接給出對應的解決方案。
在部署上咱們將支持更多的發佈方式(藍綠部署, 金絲雀部署等), 知足用戶在不一樣的場景下能夠更加輕鬆的進行發佈同時下降回滾率。
打造全方面的平臺, 既能夠單獨部署開源服務(好比, 快速部署一個mysql、tomcat),又能夠知足開發在不一樣開發階段的不一樣需求, 同時能夠知足測試同窗和運維同窗需求。
在不到一年的時間裏tekton也在快速發展,、不斷完善, 咱們的服務架構也會隨之迭代。而在打造此服務期間, 咱們也意識到項目得以順利進行與在開源社區中獲得了許多幫助息息相關, 咱們將持續關注併爲社區盡微薄之力。同時,咱們也將致力於爲研發同窗打造一款助力工做提高工做快感的平臺。