专栏首页测试技术圈Kubernetes原生CI/CD工具Tekton探秘与上手实践

Kubernetes原生CI/CD工具Tekton探秘与上手实践

如果有关注过Knative社区动态的同学,可能会知道最近发生了一件比较大的新闻,三大组件之一的build项目被人提了一个很残忍的Proposal[1],并且专门在项目Readme的开头加了个NOTE:

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。

Tekton是什么

Tekton是一个谷歌开源的Kubernetes原生CI/CD系统,功能强大且灵活,开源社区也正在快速的迭代和发展壮大。Google Cloud已经推出了基于Tekton的服务[2]。

其实Tekton的前身是Knative的build-pipeline项目,从名字可以看出这个项目是为了给build模块增加pipeline的功能,但是大家发现随着不同的功能加入到Knative build模块中,build模块越来越变得像一个通用的CI/CD系统,这已经脱离了Knative build设计的初衷,于是,索性将build-pipeline剥离出Knative,摇身一变成为Tekton,而Tekton也从此致力于提供全功能、标准化的原生Kubernetes CI/CD解决方案。

Tekton虽然还是一个挺新的项目,但是已经成为Continuous Delivery Foundation(CDF)的四个初始项目之一,另外三个则是大名鼎鼎的Jenkins、Jenkins X、Spinnaker,实际上Tekton还可以作为插件集成到JenkinsX中。所以,如果你觉得Jenkins太重,没必要用Spinnaker这种专注于多云平台的CD,为了避免和GitLab耦合不想用gitlab-ci,那么Tekton值得一试。

Tekton的特点是Kubernetes原生,什么是Kubernetes原生呢?简单的理解,就是all in Kubernetes,所以用容器化的方式构建容器镜像是必然,另外,基于Kubernetes CRD定义的Pipeline流水线也是Tekton最重要的特征。

那Tekton都提供了哪些CRD呢?

  • 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:

# 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之后,我们就可以开始动手实践了,下面以构建一个Spring Boot工程为例。

假设我们新开发了一个名为ncs的Spring Boot项目,为了将该项目构建成镜像并上传至镜像仓库,我们可以梳理一个最简单的CI流程如下:

  1. 从Git仓库拉取代码
  2. Maven编译打包
  3. 构建镜像
  4. 推送镜像

当然在CI流程之前,我们先需要在项目中增加Dockerfile,否则构建镜像无从谈起。

添加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

从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要怎么使用呢,我们接着往下看。

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

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镜像[3],当然也可以采用一些其他的开源构建方式,例如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很容易入门和上手。

构建执行

在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://github.com/knative/build/issues/614
  2. https://cloud.google.com/Tekton/
  3. https://hub.docker.com/_/docker

原文链接:https://juejin.im/post/5d629c1a5188254628236b69

转自:Docker公众号

本文分享自微信公众号 - V社 北京社(SoftwareTesters)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • SSD物体检测模型Keras版

    本文是基于论文SSD: Single Shot MultiBox Detector,实现的keras版本。

    机器学习AI算法工程
  • 张高兴的 .NET Core IoT 入门指南:(五)PWM 信号输出

    在解释 PWM 之前首先来了解一下电路中信号的概念,其中包括模拟信号和数字信号。模拟信号是一种连续的信号,与连续函数类似,在图形上表现为一条不间断的连续曲线。数...

    张高兴
  • 计算机视觉领域最好用的开源图像标注工具

    它来自下面的项目:https://github.com/wkentaro/labelme

    机器学习AI算法工程
  • 基于 Vue 和 TS 的 Web 移动端项目实战心得

    来源:https://juejin.im/post/5d759f706fb9a06afa32adec

    ConardLi
  • NLP学习思维导图,非常的全面和清晰

    nlp-roadmap 是 Natural Language Processing 的路线图(思维导图),以及为对学习NLP感兴趣的同学准备的一些关键字。这个路...

    AI科技大本营
  • 最近学到的Git知识,大厂的Git机制还是很方便的

    •这里由于提交自己的代码第一次提交到A分支,第二次提交B分支,然后报错了,这里报错以后,会提示一个百度自己内部的链接,你点击链接就可以照着提示去修改,可以说还是...

    用户2769421
  • 【Kubernetes系列】第9篇 CI/CD之全流程实践

    注: 本次示例使用的gitlab项目地址为:http://gitlab.hanker.com/colynn/hanker-hello.git

    HankerCloud
  • 带你少走弯路:强烈推荐的Keras快速入门资料和翻译(可下载)

    上次写了TensorFlow和PyTorch的快速入门资料,受到很多好评,读者强烈建议我再出一个keras的快速入门路线,经过翻译和搜索网上资源,我推荐4份入门...

    材ccc
  • 2016-2018年机器学习大赛TOP开源作品汇总

    本次大赛要求参赛者基于提供的讯飞 AI 营销云的海量广告投放数据,通过人工智能技术构建来预测模型预估用户的广告点击概率。比赛提供了 5 类数据,包括基础广告投放...

    机器学习AI算法工程
  • 在 Kubernetes 上使用 Tekton 快速实现应用自动发布

    Tekton 是一个功能强大的 Kubernetes 原生开源框架,用于创建持续集成和交付系统。 通过抽象底层实现细节,用户可以跨多云平台和本地系统进行构建、...

    iMike

扫码关注云+社区

领取腾讯云代金券