kubernetes原生CI/CD工具:Tekton探祕與上手實踐

引子

若是有關注過Knative社區動態的同窗,可能會知道最近發生了一件比較大的新聞,三大組件之一的build項目被人提了一個很殘忍的Proposal(https://github.com/knative/build/issues/614),而且專門在項目Readme的開頭加了個NOTE:git

🚨 NOTE: There is an open proposal to deprecate this component in favor of Tekton Pipelines. If you are a new user, consider using Tekton Pipelines, or another tool, to build and release. If you use Knative Build today, please give feedback on the deprecation proposal.
複製代碼

這個Proposal的目的是想要廢棄Knative的build模塊,Knative只專一作serverless,而將build模塊表明的CI/CD功能全盤交出,讓用戶本身選擇合適的CI/CD工具。Knative只負責將鏡像運行,同時提供serverless相關的事件驅動等功能,再也不關心鏡像的構建過程。
雖然目前爲止,該Proposal還在開放徵求社區的意見,不過,從留言來看,build模塊將來仍是大機率會被deprecate。由於Knative build的替代者Tekton已經展露頭腳,表現出更強大的基於kubernetes的CI/CD能力,Tekton的設計思路其實也是來源於Knative build的,現有用戶也能夠很方便的從build遷移至Tekton。github

Tekton是什麼

Tekton是一個谷歌開源的kubernetes原生CI/CD系統,功能強大且靈活,開源社區也正在快速的迭代和發展壯大。google cloud已經推出了基於Tekton的服務(https://cloud.google.com/Tekton/)。web

其實Tekton的前身是Knative的build-pipeline項目,從名字能夠看出這個項目是爲了給build模塊增長pipeline的功能,可是你們發現隨着不一樣的功能加入到Knative build模塊中,build模塊愈來愈變得像一個通用的CI/CD系統,這已經脫離了Knative build設計的初衷,因而,索性將build-pipeline剝離出Knative,搖身一變成爲Tekton,而Tekton也今後致力於提供全功能、標準化的原生kubernetesCI/CD解決方案。spring

Tekton雖然仍是一個挺新的項目,可是已經成爲 Continuous Delivery Foundation (CDF) 的四個初始項目之一,另外三個則是大名鼎鼎的Jenkins、Jenkins X、Spinnaker,實際上Tekton還能夠做爲插件集成到JenkinsX中。因此,若是你以爲Jenkins過重,不必用Spinnaker這種專一於多雲平臺的CD,爲了不和Gitlab耦合不想用gitlab-ci,那麼Tekton值得一試。docker

Tekton的特色是kubernetes原生,什麼是kubernetes原生呢?簡單的理解,就是all in kubernetes,因此用容器化的方式構建容器鏡像是必然,另外,基於kubernetes CRD定義的pipeline流水線也是Tekton最重要的特徵。
那Tekton都提供了哪些CRD呢?shell

  • Task:顧名思義,task表示一個構建任務,task裏能夠定義一系列的steps,例如編譯代碼、構建鏡像、推送鏡像等,每一個step實際由一個Pod執行。
  • TaskRun:task只是定義了一個模版,taskRun才真正表明了一次實際的運行,固然你也能夠本身手動建立一個taskRun,taskRun建立出來以後,就會自動觸發task描述的構建任務。
  • Pipeline:一個或多個task、PipelineResource以及各類定義參數的集合。
  • PipelineRun:相似task和taskRun的關係,pipelineRun也表示某一次實際運行的pipeline,下發一個pipelineRun CRD實例到kubernetes後,一樣也會觸發一次pipeline的構建。
  • PipelineResource:表示pipeline input資源,好比github上的源碼,或者pipeline output資源,例如一個容器鏡像或者構建生成的jar包等。
    他們大概有以下圖所示的關係:

上手實踐

部署

Tekton部署很簡單,理論上只需下載官方的yaml文件,而後執行kubectl create -f 一條命令就能夠搞定。可是因爲在國內,咱們沒法訪問gcr.io鏡像倉庫,因此須要自行替換官方部署yaml文件中的鏡像。
運行起來後能夠在Tekton-pipelines namespace下看到兩個deployment:api

# kubectl -n Tekton-pipelines get deploy
NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
Tekton-pipelines-controller   1/1     1            1           10d
Tekton-pipelines-webhook      1/1     1            1           10d
複製代碼

這就是運行Tekton所需的全部服務,一個控制器controller用來監聽上述CRD的事件,執行Tekton的各類CI/CD邏輯,一個webhook用於校驗建立的CRD資源。
webhook使用了kubernetes的admissionwebhook機制,因此,在咱們kubectl create一個taskRun或者pipelineRun時,apiserver會回調這裏部署的Tekton webhook服務,用於校驗這些CRD字段等的正確性。緩存

構建一個Java應用

部署完Tekton以後,咱們就能夠開始動手實踐了,下面以構建一個springboot工程爲例。tomcat

假設咱們新開發了一個名爲ncs的springboot項目,爲了將該項目構建成鏡像並上傳至鏡像倉庫,咱們能夠梳理一個最簡單的CI流程以下:安全

  1. 從git倉庫拉取代碼
  2. maven編譯打包
  3. 構建鏡像
  4. 推送鏡像

固然在CI流程以前,咱們先須要在項目中增長dockerfile,不然構建鏡像無從談起。

0. 添加dockerfile

FROM hub.c.163.com/qingzhou/tomcat:7-oracle-jdk-rev4
ENV TZ=Asia/Shanghai LANG=C.UTF-8 LANGUAGE=C.UTF-8 LC_ALL=C.UTF-8
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone WORKDIR /usr/local/tomcat RUN rm -rf webapps/* COPY setenv.sh $CATALINA_HOME/bin/ COPY ./target/*.war webapps/ ENTRYPOINT ["catalina.sh", "run"] 複製代碼

一個示例如上所示,dockerfile的邏輯比較簡單:引用一個tomat的基礎鏡像,而後把maven構建完生成的war包複製到webapps目錄中,最後用腳本catalina.sh運行便可。
固然這裏有個頗有用的細節,咱們會項目中添加一個名爲setenv.sh的腳本,在dockerfile裏會COPY$CATALINA_HOME/bin/。setenv.sh腳本里能夠作一些tomcat啓動以前的準備工做,例如能夠設置一些JVM參數等:

export NCE_JAVA_OPTS="$NCE_JAVA_OPTS -Xms${NCE_XMS} -Xmx${NCE_XMX} -XX:MaxPermSize=${NCE_PERM} -Dcom.netease.appname=${NCE_APPNAME} -Dlog.dir=${CATALINA_HOME}/logs"
複製代碼

若是你也研究過catalina.sh腳本,就會發現腳本里默認會執行setenv.sh,實際上這也是官方推薦的初始化方式。

elif [ -r "$CATALINA_HOME/bin/setenv.sh" ]; then
  . "$CATALINA_HOME/bin/setenv.sh"
fi
複製代碼

1. 從git倉庫拉取代碼

添加完dockerfile以後,咱們能夠正式開始研究如何使用Tekton構建這個ncs項目了。
首先第一步,須要將代碼從遠程git倉庫拉下來。
Tekton中可使用pipelineresource這個CRD表示git倉庫遠程地址和git分支,以下所示:

apiVersion: Tekton.dev/v1alpha1
kind: PipelineResource
metadata:
 name: ncs-git-source
spec:
 type: git
 params:
 - name: url
 value: https://github.com/ethfoo/test.git
 - name: revision
 value: master
複製代碼

其中的revision可使用分支、tag、commit hash。 實際上git拉取代碼這種通用的操做,只須要咱們定義了input的resource,Tekton已經默認幫咱們作好了,不須要在task中寫git pull之類的steps。目前咱們的task能夠寫成以下所示:

apiVersion: Tekton.dev/v1alpha1
kind: Task
metadata:
 name: ncs
spec:
 inputs:
 resources:
 - name: gitssh
 type: git
複製代碼

git拉取代碼還存在安全和私有倉庫的權限問題,基於kubernetes原生的Tekton固然是採用secret/serviceaccount來解決。
對於每一個項目組,能夠定義一個公共的私有ssh key,而後放到secret中,供serviceaccount引用便可。

apiVersion: v1
kind: ServiceAccount
metadata:
 name: nce-qingzhou
 namespace: Tekton-test
secrets:
 - name: ncs-git-ssh
---
apiVersion: v1
kind: Secret
metadata:
 name: ncs-git-ssh
 namespace: Tekton-test
 annotations:
    Tekton.dev/git-0: g.hz.netease.com
type: kubernetes.io/ssh-auth
data:
 ssh-privatekey: LS0tLS1CRUd...
 known_hosts: W2cuaHoub...
複製代碼

最後,這個serviceaccount要怎麼使用呢,咱們接着往下看。

2. maven編譯打包

拉下來項目代碼以後,開始進入使用maven編譯打包階段。而這個階段就須要咱們本身定義task的steps來實現各類CI/CD的步驟了。
實際的原理也很簡單,定義的一個steps實際上就是新建一個pod去執行自定義的操做。
對於maven編譯來講,咱們首先須要找一個安裝有maven的鏡像,而後在容器的command/args里加上mvn編譯的命令。示例以下:

spec:
 inputs:
 resources:
 - name: ncs-git-source
 type: git
 params:
      # These may be overridden, but provide sensible defaults.
 - name: directory
 description: The directory containing the build context.
 default: /workspace/ncs-git-source

 steps:
 - name: maven-install
 image: maven:3.5.0-jdk-8-alpine
 workingDir: "${inputs.params.directory}"
 args:
        [
          "mvn",
          "clean",
          "install",
          "-D maven.test.skip=true",
        ]

 volumeMounts:
 - name: m2
 mountPath: /root/.m2
複製代碼

因爲Tekton會給每一個構建的容器都掛載/workspace這個目錄,因此每個steps步驟裏均可以在/workspace中找到上一步執行的產物。
git拉取代碼能夠認爲是一個默認的steps,這個steps的邏輯裏Tekton會把代碼放到/workspace/{resources.name}中。上面咱們定義的PipelineResource名爲ncs-git-resource,因此ncs這個工程的代碼會被放在/workspace/ncs-git-resource目錄中。
因此在maven-install這個steps中,咱們須要在/workspace/ncs-git-resource中執行mvn命令,這裏咱們可使用workingDir字段表示將該目錄設置爲當前的工做目錄。同時爲了不寫死,這裏咱們定義爲一個input的變量params,在workingDir中使用${}的方式引用便可。

實際的使用中,因爲每次構建都是新起容器,在容器中執行maven命令,通常都是須要將maven的m2目錄掛載出來,避免每次編譯打包都須要從新下載jar包。

 steps:
 - name: maven-install
      ...
 volumeMounts:
 - name: m2
 mountPath: /root/.m2
 volumes:
 - name: m2
 hostPath:
 path: /root/.m2
複製代碼

3. docker鏡像的構建和推送

Tekton標榜本身爲kubernetes原生,因此想必你也意識到了其中很重要的一點是,全部的CI/CD流程都是由一個一個的pod去運行。docker鏡像的build和push固然也不例外,這裏又繞不開另一個話題,即如何在容器中構建容器鏡像。 通常咱們有兩種方式,docker in docker(dind)和docker outside of docker(dood)。實際上二者都是在容器中構建鏡像,區別在於,dind方式下在容器裏有一個完整的docker構建系統,可直接在容器中完成鏡像的構建,而dood是經過掛載宿主機的docker.sock文件,調用宿主機的docker daemon去構建鏡像。
dind的方式可直接使用官方的dind鏡像(https://hub.docker.com/_/docker),固然也能夠採用一些其餘的開源構建方式,例如kaniko,makisu等。docker in docker的方式對用戶屏蔽了宿主機,隔離和安全性更好,可是須要關心構建鏡像的分層緩存。
dood的方式比較簡單易用,只須要掛載了docker.sock,容器裏有docker客戶端,便可直接使用宿主機上的docker daemon,因此構建的鏡像都會在宿主機上,宿主機上也會有相應的鏡像分層的緩存,這樣也便於加快鏡像拉取構建的速度,不過同時也須要注意定時清理冗餘的鏡像,防止鏡像rootfs佔滿磁盤。
若是是在私有云等內部使用場景下,可採用dood的方式。這裏以dood的方式爲例。
首先要在task中加一個input param表示鏡像的名稱。

spec:
 inputs:
 params:
 - name: image
 description: docker image
複製代碼

而後在task的steps中加入鏡像的build和push步驟。

 steps:
 - name: dockerfile-build
 image: docker:git
 workingDir: "${inputs.params.directory}"
 args:
        [
          "build",
          "--tag",
          "${inputs.params.image}",
          ".",
        ]
 volumeMounts:
 - name: docker-socket
 mountPath: /var/run/docker.sock

 - name: dockerfile-push
 image: docker:git
 args: ["push", "${inputs.params.image}"]
 volumeMounts:
 - name: docker-socket
 mountPath: /var/run/docker.sock
 volumes:
 - name: docker-socket
 hostPath:
 path: /var/run/docker.sock
 type: Socket

複製代碼

瞭解kubernetes的同窗必定對這種yaml聲明式的表述不會陌生,實際上上面的定義和一個deployment的yaml十分相似,這也使得Tekton很容易入門和上手。

4. 構建執行

在Tekton中task只是一個模版,每次須要定義一個taskrun表示一次實際的運行,其中使用taskRef表示引用的task便可。

apiVersion: Tekton.dev/v1alpha1
kind: TaskRun
metadata:
 generateName: ncs-
spec:
 inputs:
 resources:
 - name: gitssh
 resourceRef:
 name: ncs-git-source
 taskRef:
 name: ncs
複製代碼

這裏的taskrun須要注意的是,inputs.resources須要引用上文定義的PipelineResource,因此resourceRef.name=ncs-git-source,同時reources.name也須要和上文task中定義的resources.name一致。
這裏還有另一種寫法,若是你不想單獨定義PipelineResource,能夠將taskrun裏的resources使用resourceSpec字段替換,以下所示。

 inputs:
 params:
 - name: image
 value: hub.c.163.com/test/ncs:v1.0.0
 resources:
 - name: ncs-git-source
 resourceSpec:
 params:
 - name: url
 value: ssh://git@netease.com/test/ncs.git
 - name: revision
 value: f-dockerfile
 type: git
 serviceAccount: nce-qingzhou
 taskRef:
 name: ncs
複製代碼

固然,別忘記把上面建立的serviceaccount放到taskrun中,不然沒法拉取私有git倉庫代碼。
最後,咱們能夠把上面的文件保存,使用kubectl create -f ncs-taskrun.yml來開始一段taskrun的構建。
還須要提醒的是,taskrun只表示一次構建任務,你沒法修改taskrun中的字段讓它從新開始,因此咱們沒有在taskrun的metadata中定義name,只加了generateName,這樣kubernetes會幫咱們在taskrun name中自動加上一個hash值後綴,避免每次手動更名建立。

pipeline流水線

既然Tekton是一個CI/CD工具,咱們除了用它來編譯和構建鏡像,還能夠作更多,例如,加入一些自動化測試的流程,對接其餘kubernetes集羣實現容器鏡像的更新部署。
固然,這一切都放到task裏的steps也何嘗不可,可是這樣沒法抽象出各類task進行組織和複用,因此Tekton提供了更高一級的CRD描述,Pipeline和PipelineRun,Pipeline中能夠引用不少task,而PipelineRun可用來運行Pipeline。Pipeline的yaml模版和task大同小異,這裏暫不詳述,相信你看一遍官方文檔也能很快上手。

總結

雖然Tekton還很年輕,咱們網易雲輕舟團隊已經開始在內部嘗試實踐,使用tekton做爲內部服務的鏡像構建推送平臺。

隨着雲原生浪潮的到來,Kubernetes已經成爲事實上的標準,Tekton正脫胎於這股浪潮之中,基於CRD、controller設計思想從一出生就註定會更適合kubernetes。相比其餘老牌的CI/CD項目,Tekton還沒那麼的成熟,不過套用一句如今流行的話:一代人終將老去,但總有人正年輕
看着目前的趨勢,將來可期。

參考:
1. https://kurtmadel.com/posts/cicd-with-kubernetes/Tekton-standardizing-native-kubernetes-cd/
2. https://developer.ibm.com/tutorials/knative-build-app-development-with-Tekton/
複製代碼
相關文章
相關標籤/搜索