TECHSTEP

ITインフラ関連の記事を公開してます。

Tektonに入門する

はじめに

最近Kubernetes上でCI/CDパイプラインを構築するTektonというツールについて調べる機会があったので、本記事で簡単にまとめておきます。

Tektonとは

TektonKubernetes NativeなCI/CDソリューションとして開発され、Kubernetesクラスター上でのアプリケーションのビルド・テスト・デプロイなどを目的としたパイプラインの構築を実現します。

Tektonは主に4つのコンポーネントを利用しています。

  • Pipelines: CI/CDワークフローの基礎となるブロック。後述のTaskPipelineなどを含む。Tektonのコア機能や他の機能の基盤を提供する。
  • Triggers: CI/CDワークフロー向けのイベントトリガー機能を提供。
  • CLI: TektonのCI/CDワークフローを管理するCLIツール(tkn)を提供。
  • Dashboard: Pipeline向けWeb UIを提供。

Tektonのコンセプト

Pipelinesは複数のコンセプトで構成されています。こちらはConcept Modelとして公式ドキュメントでも記載されていますが、以下のようなコンセプトから成り立っています。

  • step:CI/CDワークフローでの操作はstepという単位で実行されます。stepでは、ユーザーの指定したコンテナイメージを利用してコマンドを実行します。

  • task:複数のStepをまとめたものがTaskになります。各TaskはPod内で実行され、stepはコンテナとして実行されます。TaskをPodとして実行することで、各stepに対して共通の環境を提供することができます。また、Taskで利用するVolumeを用意すれば、各stepからvolumeを利用し、ファイルの共有などが実現できます。

  • pipeline:複数のTaskをまとめたものがPipelineになります。複数のTaskPodを作成して、各Podを順番に実行します。Pipelinesは各Stepコンテナにentrypointバイナリを注入し、ユーザーの指定したコマンドを実行します。各Pipelineでのワークフローの実行や状態は、Kubernetes annotationsを利用して実行・追跡をします。Annotationsは各Stepコンテナにおいて、Kubernetes downward APIのファイルフォームで導入されます。entrypointバイナリは導入されたファイル群をチェックし、特定のannotationがファイルに出現した時のみコマンドを実行します。

concepts

Tekton公式ドキュメントより

Tekton Pipelinesでは複数のCustom Resourceを組み合わせてCI/CDパイプラインを構築します。利用する主なCustom Resourceは以下の通りです。

Custom Resource名 利用用途の概要
Task Taskを定義する。各Stepの内容や使用するコンテナイメージ、パラメータなどを指定する
TaskRun 定義したTaskを実行するために利用する。指定のTask内にあるStepを順番に実行し、処理が完了・失敗するまで動作する。TaskRunでは、実行するTaskの指定やTask内で利用するInput/Outputリソースを指定する
Pipeline Pipelineを定義する。Pipelineで利用するStepやパラメータなどを指定する
PipelineRun 定義したPipelineを実行するために利用する。指定したPipeline内のTaskに含まれるStepを順番に実行し、処理が完了・失敗するまで動作する。PipelineRunでは、実行するPipelineやPieline内で利用するInput/Outputリソースなどを指定する
PipelineResource Task・Pipelineで利用するリソースを定義する。利用できるリソース種別としてはgit image cluster storageなど ※7月15日時点でalphaのみ提供

※参考ドキュメント:


Tektonを利用する流れ

Tektonを利用する際の大きな流れとしては以下のようになります。各CRを作成後にTaskRun PipelineRunを実行することで、Taskを実行するPodが順次作成され、処理が実行されます。

f:id:FY0323:20200718153849p:plain

Tekton Custom Resource間の関連性

次に、CRの定義ファイル間でどのような関連があるかを図示しておきます。ここではPipelineResourceを利用する・しない場合で分けております。なお、ここでは各リソースの定義ファイルの例を載せておりますが、書き方は複数あるのであくまで一例です。また内容は説明用のものなので、実際のリソース作成に使うことはできません。

PipelineResourceを利用する場合(v1alpha1

TaskRunを利用する場合、まずはPipelineResource Taskを作成します。PipelineResourceには利用するリソースURLなどを、Taskには実行する各Stepでのコンテナイメージやコマンドを設定します。

PipelineResource Taskを作成後はTaskRunリソースを作成し、Taskを実行します。実行中はPodの状態やtknコマンドを利用することで状況を確認することができます。

f:id:FY0323:20200718122014p:plain

次にPipelineRunを利用する場合、PipelineResource Taskに加えてPipelineリソースを作成します。Pipelineでは利用するTaskやTask中で使用するリソースなどを指定します。

3種類のリソースを作成した後はPipelineRunを作成し、Pipelineを実行します。こちらも実行中はPodの状態やtknコマンドにより状況を確認することができます。

f:id:FY0323:20200718122125p:plain

PipelineResourceを利用しない場合(v1beta1

PipelineResourceを利用しない場合も、大きな流れは変わりません。TaskRunを利用する場合は事前にTaskを作成しておき、TaskRunによってそれを実行します。Taskで利用するGitHub URLなどのパラメータはTask TaskRunそれぞれで指定することが可能です。例えばTaskで利用するのと同じ名称のパラメータをTaskRunで指定することで、同じTaskを別々のTaskRunで利用する際にTaskRun側で必要なパラメータを指定することができます。

f:id:FY0323:20200718122449p:plain

PipelineRunを利用する場合も同様で、Task Pipelineリソースをあらかじめ作成してからPipelineRunによって実行します。

f:id:FY0323:20200718122712p:plain

Task/Pipelineの構成

今回は以下のようなPipelineの構築を行います。大まかな流れは以下の通りです。

  1. GitHubからソースコードをclone
  2. ソースコードをDockerイメージにbuild
  3. buildしたDockerイメージをDocker Hubにpush
  4. Docker Hubのイメージをpull
  5. pullしたイメージをKubernetes上にdeploy

f:id:FY0323:20200718124007p:plain

今回はPipelineResourcesは利用せず、各CR内で利用するリソースのURL等を直接指定しています。

また上図中にWorkspaceというものが見えます。Workspaceは複数のTaskが共有することのできる領域を表しており、例えばKubernetesSecretConfigMapPersistentVolumeClaimなどを共有することができます。これにより、あるTaskで取得した情報(今回で言えばGitHubから取得したソースコードなど)を別のTaskに渡すことが可能です。

Workspaceを利用する場合は、PersistentVolumeClaim emptyDir ConfigMap SecretのいずれかのKubernetesリソースを指定する必要があります。

TektonにはWorkspaceと似たような機能としてVolumeというものが存在しますが、あるTaskが複数のWorkspaceを利用する場合、TaskRun PipelineRunから、どのWorkspaceに対してどの保存領域(PersistentVolumeClaimなど)を利用するかを指定することができます。そのため、Taskの再利用を考えた場合はWorkspaceのほうが使いやすい機能となります。

なお、今回利用しているPipelineの構成やyamlファイルの多くはこちらの記事を参考にしている部分が多いため、合わせて読んでいただければと思います。

TektonのTasks/Pipelineの利用方法

構築環境情報

今回構築で利用した環境は以下の通りです。Pipeline中で利用するアプリケーションは、簡単なJSONのメッセージを返すだけのWebアプリです。

今回はGitHub、Docker Hubともにパブリックなものを利用しましたが、プライベートリポジトリを利用する場合は以下のリンク先を参照してください。


※参考ドキュメント:


Tektonの構築

まずはTektonの構築を行います。またTekton Pipelinesを利用するうえで必要となる設定も合わせて行います。なお構築・検証等で利用した定義ファイルはこちらに置いてあります

Tekton Controller / CRDのデプロイ

まずはTektonを利用するうえで必要なController Webhook Deployment、各種CustomResourceDefinitionなどをKubernetesクラスター上にデプロイします。Tektonのデプロイは、以下のコマンドを実行するだけで完了します。

# Tekton pipelineのデプロイ
$ kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
namespace/tekton-pipelines created
podsecuritypolicy.policy/tekton-pipelines created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-controller-cluster-access created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-controller-tenant-access created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-webhook-cluster-access created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-leader-election created
role.rbac.authorization.k8s.io/tekton-pipelines-controller created
role.rbac.authorization.k8s.io/tekton-pipelines-webhook created
serviceaccount/tekton-pipelines-controller created
serviceaccount/tekton-pipelines-webhook created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller-cluster-access created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller-leaderelection created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller-tenant-access created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-webhook-cluster-access created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-webhook-leaderelection created
rolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller created
rolebinding.rbac.authorization.k8s.io/tekton-pipelines-webhook created
customresourcedefinition.apiextensions.k8s.io/clustertasks.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/conditions.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/images.caching.internal.knative.dev created
customresourcedefinition.apiextensions.k8s.io/pipelines.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/pipelineruns.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/pipelineresources.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/tasks.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/taskruns.tekton.dev created
secret/webhook-certs created
validatingwebhookconfiguration.admissionregistration.k8s.io/validation.webhook.pipeline.tekton.dev created
mutatingwebhookconfiguration.admissionregistration.k8s.io/webhook.pipeline.tekton.dev created
validatingwebhookconfiguration.admissionregistration.k8s.io/config.webhook.pipeline.tekton.dev created
clusterrole.rbac.authorization.k8s.io/tekton-aggregate-edit created
clusterrole.rbac.authorization.k8s.io/tekton-aggregate-view created
configmap/config-artifact-bucket created
configmap/config-artifact-pvc created
configmap/config-defaults created
configmap/feature-flags created
configmap/config-leader-election created
configmap/config-logging created
configmap/config-observability created
deployment.apps/tekton-pipelines-controller created
service/tekton-pipelines-controller created
deployment.apps/tekton-pipelines-webhook created
service/tekton-pipelines-webhook created


# 実行後の確認
$ kubectl get pods -n tekton-pipelines
NAME                                           READY   STATUS    RESTARTS   AGE
tekton-pipelines-controller-548c8fb7b5-hbb9m   1/1     Running   0          15s
tekton-pipelines-webhook-9cfc4b4df-vw8ss       1/1     Running   0          15s

Docker HubへPushするためのSecret作成

KubernetesからDocker HubへコンテナイメージをPushするため、Docker Hubのログイン情報をSecretとして用意します。ここで作成したSecretを利用するには、Service Account・各Pipelineリソースのどちらかに関連付ける必要があります。今回作成したSecretは、後ほどService Accountを作成する際に指定することで、Service Accountと関連付けます。

annotationsに記載しているtekton.dev/docker-0: https://index.docker.io/v1/は、このSecretで指定している秘匿情報がどのWebアドレスに属したものかを表します。

apiVersion: v1
kind: Secret
metadata:
  name: basic-user-pass
  annotations:
    tekton.dev/docker-0: https://index.docker.io/v1/
type: kubernetes.io/basic-auth
stringData:
  username: <username>
  password: <password>
# Secretの作成
$ kubectl apply -f docker-secret.yml
secret/basic-user-pass created


# 作成後の確認
$ kubectl describe secret basic-user-pass
Name:         basic-user-pass
Namespace:    default
Labels:       <none>
Annotations:  tekton.dev/docker-0: https://index.docker.io/v1/

Type:  kubernetes.io/basic-auth

Data
====
password:  <byte number>
username:  <byte number>

※参考ドキュメント:


Tekton Pipeline実行用Service Accountの作成とRoleの付与

次にTekton Pipelineを実行するためのService Accountを用意します。作成の定義ファイルはこちらのファイルを利用しました(Service Accountと関連付けるSecretのみ変更しています)。

今回使用するpipeline-accountというService Accountには、合わせてSecret Roleを付与します。SecretにはAPIアクセスの認証に利用するトークンの情報を、Roleにはtekton-pipelinesNamespaceに含まれるリソースに対して操作する権限を追加し、RoleBindingを用いてService Accountと紐づけます。

# Service Account作成
$ kubectl apply -f pipeline-account.yml
serviceaccount/pipeline-account created
secret/kube-api-secret created
role.rbac.authorization.k8s.io/pipeline-role created
rolebinding.rbac.authorization.k8s.io/pipeline-role-binding created



# 作成後の確認
$ kubectl describe sa pipeline-account
Name:                pipeline-account
Namespace:           default
Labels:              <none>
Annotations:         Image pull secrets:  <none>
Mountable secrets:   basic-user-pass
                     pipeline-account-token-sg475
Tokens:              kube-api-secret
                     pipeline-account-token-sg475
Events:              <none>

Workspace用のPersistent Volume Claim作成

次に、Pipelineで利用するWorkspaceのためのストレージ領域を用意します。今回は、Alibaba Cloudで利用できるStorage Classのうちalicloud-disk-ssdを利用します。Persistent Volume Claim作成時に、Storage Classとしてalicloud-disk-ssdを指定することでPersistent Volumeが作成され、Kubernetesの提供する永続ストレージを利用することができます。

なおAlibaba Cloud DiskをPVCで利用する場合は、20Gi以上のサイズを指定する必要があります。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: tekton-volume
spec:
  accessModes: 
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: alicloud-disk-ssd
# 利用可能なStorage Class
$ kubectl get sc
NAME                       PROVISIONER                       AGE
alicloud-disk-available    diskplugin.csi.alibabacloud.com   3h28m
alicloud-disk-efficiency   diskplugin.csi.alibabacloud.com   3h28m
alicloud-disk-essd         diskplugin.csi.alibabacloud.com   3h28m
alicloud-disk-ssd          diskplugin.csi.alibabacloud.com   3h28m
alicloud-disk-topology     diskplugin.csi.alibabacloud.com   3h28m



# 20Gi未満を指定した場合
$ kubectl apply -f tekton-pvc.yml
$ kubectl get pvc
NAME            STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS        AGE
tekton-volume   Pending                                      alicloud-disk-ssd   5s
$ kubectl describe pvc tekton-volume

(中略)

Message: disk size is not supported., PVC defined storage should equal/greater than 20Gi
  Normal  ExternalProvisioning  0s (x3 over 19s)  persistentvolume-controller  waiting for a volume to be created, either by external provisioner "diskplugin.csi.alibabacloud.com" or manually created by system administrator



# 20Gi以上を指定した場合
$ kubectl apply -f tekton-pvc.yml
persistentvolumeclaim/tekton-volume created

$ kubectl get pvc
NAME            STATUS   VOLUME                   CAPACITY   ACCESS MODES   STORAGECLASS        AGE
tekton-volume   Bound    d-6we0n5028zulr8h5qwzo   20Gi       RWO            alicloud-disk-ssd   3s

※参考ドキュメント:


Task/TaskRunの利用方法

ここから実際のTask/Pipelineの利用方法を紹介します。

まずはTaskRunを利用する方法について紹介します。ここでは3つのTaskのうち一つ目のTaskのみを利用します。

まずはTaskについてです。TaskはTektonのGitHub RepositoryでCatalogとして紹介されているこちらの定義ファイルを利用しました。

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: git-clone
spec:
  workspaces:
    - name: output
      description: The git repo will be cloned onto the volume backing this workspace
  params:
    - name: url
      description: git url to clone
      type: string
    - name: revision
      description: git revision to checkout (branch, tag, sha, ref…)
      type: string
      default: master
    - name: refspec
      description: (optional) git refspec to fetch before checking out revision
      default: ""
    - name: submodules
      description: defines if the resource should initialize and fetch the submodules
      type: string
      default: "true"
    - name: depth
      description: performs a shallow clone where only the most recent commit(s) will be fetched
      type: string
      default: "1"
    - name: sslVerify
      description: defines if http.sslVerify should be set to true or false in the global git config
      type: string
      default: "true"
    - name: subdirectory
      description: subdirectory inside the "output" workspace to clone the git repo into
      type: string
      default: ""
    - name: deleteExisting
      description: clean out the contents of the repo's destination directory (if it already exists) before trying to clone the repo there
      type: string
      default: "false"
    - name: httpProxy
      description: git HTTP proxy server for non-SSL requests
      type: string
      default: ""
    - name: httpsProxy
      description: git HTTPS proxy server for SSL requests
      type: string
      default: ""
    - name: noProxy
      description: git no proxy - opt out of proxying HTTP/HTTPS requests
      type: string
      default: ""
  results:
    - name: commit
      description: The precise commit SHA that was fetched by this Task
  steps:
    - name: clone
      image: gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.12.1
      script: |
        CHECKOUT_DIR="$(workspaces.output.path)/$(params.subdirectory)"
        cleandir() {
          # Delete any existing contents of the repo directory if it exists.
          #
          # We don't just "rm -rf $CHECKOUT_DIR" because $CHECKOUT_DIR might be "/"
          # or the root of a mounted volume.
          if [[ -d "$CHECKOUT_DIR" ]] ; then
            # Delete non-hidden files and directories
            rm -rf "$CHECKOUT_DIR"/*
            # Delete files and directories starting with . but excluding ..
            rm -rf "$CHECKOUT_DIR"/.[!.]*
            # Delete files and directories starting with .. plus any other character
            rm -rf "$CHECKOUT_DIR"/..?*
          fi
        }
        if [[ "$(params.deleteExisting)" == "true" ]] ; then
          cleandir
        fi
        test -z "$(params.httpProxy)" || export HTTP_PROXY=$(params.httpProxy)
        test -z "$(params.httpsProxy)" || export HTTPS_PROXY=$(params.httpsProxy)
        test -z "$(params.noProxy)" || export NO_PROXY=$(params.noProxy)
        /ko-app/git-init \
          -url "$(params.url)" \
          -revision "$(params.revision)" \
          -refspec "$(params.refspec)" \
          -path "$CHECKOUT_DIR" \
          -sslVerify="$(params.sslVerify)" \
          -submodules="$(params.submodules)" \
          -depth "$(params.depth)"
        cd "$CHECKOUT_DIR"
        RESULT_SHA="$(git rev-parse HEAD | tr -d '\n')"
        EXIT_CODE="$?"
        if [ "$EXIT_CODE" != 0 ]
        then
          exit $EXIT_CODE
        fi
        # Make sure we don't add a trailing newline to the result!
        echo -n "$RESULT_SHA" > $(results.commit.path)

定義ファイルの各項目について簡単に紹介します。

  • spec.workspaces: ここではoutputというWorkspaceを指定しており、Cloneしたソースコードの置き場所として主に利用します。また実行後のSHAの値をファイルに出力し、Workspaceに保存しています。

  • spec.results: resultsを指定すると、Taskの実行結果をファイルに出力することができます。ここではspec.steps内で利用し、Taskの実行結果を$(results.commit.path)に保存しています。

  • spec.steps: ここではscriptを設定してStepコンテナ内で実行するコマンドを指定しています。コンテナはTektonが用意したgit-initというイメージを利用し、指定したGitHubリポジトリからのcloneと実行結果のSHAを保存する処理を実行します。

続いてTaskRunについてです。TaskRunはTekton GitHub Repository中のこちらの定義ファイルを参考にしました。元の定義ファイルはapiVersion: v1alpha1となっているので、apiVersion: v1beta1に合わせて一部修正しております。

apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
  name: git-clone-run
spec:
  workspaces:
    - name: output
      emptyDir: {}
  params:
    - name: url
      value: https://github.com/FY0323/tekton-test.git
  taskRef:
    name: git-clone
  • spec.params: ここではTask中で利用するパラメータを指定しています。今回はclone元のGitHub RepositoryのURLを指定しています。
  • spec.taskRef: taskRefには、TaskRun中に利用するTaskの名称を指定します。

※参考ドキュメント:


上記定義ファイルを利用してTaskを実行します。

# Taskの作成
$ kubectl apply -f tk_task/task1.yml
task.tekton.dev/git-clone created

$ kubectl get tekton-pipelines
NAME                        AGE
task.tekton.dev/git-clone   17m



# TaskRunの作成
$ kubectl apply -f taskrun1.yml
taskrun.tekton.dev/git-clone-run created

# 実行中
$ kubectl get tekton-pipelines
NAME                        AGE
task.tekton.dev/git-clone   18m

NAME                               SUCCEEDED   REASON    STARTTIME   COMPLETIONTIME
taskrun.tekton.dev/git-clone-run   Unknown     Running   4s


# 完了後
$ kubectl get tekton-pipelines
NAME                        AGE
task.tekton.dev/git-clone   18m

NAME                               SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
taskrun.tekton.dev/git-clone-run   True        Succeeded   13s         6s


$ kubectl get pods
NAME                      READY   STATUS      RESTARTS   AGE
git-clone-run-pod-8827g   0/1     Completed   0          27s

$ tkn taskrun logs git-clone-run --last -f
[clone] + CHECKOUT_DIR=/workspace/output/
[clone] + '[[' false '==' true ]]
[clone] + test -z
[clone] + test -z
[clone] + test -z
[clone] + /ko-app/git-init -url https://github.com/FY0323/tekton-test.git -revision master -refspec  -path /workspace/output/ '-sslVerify=true' '-submodules=true' -depth 1
[clone] {"level":"info","ts":1594704754.7358825,"caller":"git/git.go:136","msg":"Successfully cloned https://github.com/FY0323/tekton-test.git @ d33aa9b539e788c4f7b90ca8c4879664effd306c (grafted, HEAD, origin/master) in path /workspace/output/"}
[clone] {"level":"info","ts":1594704754.7876565,"caller":"git/git.go:177","msg":"Successfully initialized and updated submodules in path /workspace/output/"}
[clone] + cd /workspace/output/
[clone] + git rev-parse HEAD
[clone] + tr -d '\n'
[clone] + RESULT_SHA=d33aa9b539e788c4f7b90ca8c4879664effd306c
[clone] + EXIT_CODE=0
[clone] + '[' 0 '!=' 0 ]
[clone] + echo -n d33aa9b539e788c4f7b90ca8c4879664effd306c

※参考ドキュメント:


Pipeline/PipelineRunの利用方法

次にPipelineの利用方法についてです。

Pipelineで利用する3つのTaskは先ほどのtask1.ymlに加え、こちらこちらの定義ファイルをそれぞれ利用しました。

task2.yml

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: kaniko
spec:
  params:
  - name: IMAGE
    description: Name (reference) of the image to build.
  - name: DOCKERFILE
    description: Path to the Dockerfile to build.
    default: ./Dockerfile
  - name: CONTEXT
    description: The build context used by Kaniko.
    default: ./
  - name: EXTRA_ARGS
    default: ""
  - name: BUILDER_IMAGE
    description: The image on which builds will run
    default: gcr.io/kaniko-project/executor:latest
  workspaces:
  - name: source
  results:
  - name: IMAGE-DIGEST
    description: Digest of the image just built.

  steps:
  - name: build-and-push
    workingDir: $(workspaces.source.path)
    image: $(params.BUILDER_IMAGE)
    # specifying DOCKER_CONFIG is required to allow kaniko to detect docker credential
    # https://github.com/tektoncd/pipeline/pull/706
    env:
    - name: DOCKER_CONFIG
      value: /tekton/home/.docker
    command:
    - /kaniko/executor
    - $(params.EXTRA_ARGS)
    - --dockerfile=$(params.DOCKERFILE)
    - --context=$(workspaces.source.path)/$(params.CONTEXT)  # The user does not need to care the workspace and the source.
    - --destination=$(params.IMAGE)
    - --oci-layout-path=$(workspaces.source.path)/$(params.CONTEXT)/image-digest
    # kaniko assumes it is running as root, which means this example fails on platforms
    # that default to run containers as random uid (like OpenShift). Adding this securityContext
    # makes it explicit that it needs to run as root.
    securityContext:
      runAsUser: 0
  - name: write-digest
    workingDir: $(workspaces.source.path)
    image: gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/imagedigestexporter:v0.11.1
    # output of imagedigestexport [{"key":"digest","value":"sha256:eed29..660","resourceRef":{"name":"myrepo/myimage"}}]
    command: ["/ko-app/imagedigestexporter"]
    args:
    - -images=[{"name":"$(params.IMAGE)","type":"image","url":"$(params.IMAGE)","digest":"","OutputImageDir":"$(workspaces.source.path)/$(params.CONTEXT)/image-digest"}]
    - -terminationMessagePath=$(params.CONTEXT)/image-digested
  - name: digest-to-results
    workingDir: $(workspaces.source.path)
    image: stedolan/jq
    script: |
      cat $(params.CONTEXT)/image-digested | jq '.[0].value' -rj | tee /tekton/results/IMAGE-DIGEST

task3.yml

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: deploy-using-kubectl
spec:
  workspaces:
    - name: git-source
      description: The git repo
  params:
    - name: pathToYamlFile
      description: The path to the yaml file to deploy within the git source
    - name: imageUrl
      description: Image name including repository
    - name: imageTag
      description: Image tag
      default: "latest"
    - name: imageDigest
      description: Digest of the image to be used.
  steps:
    - name: update-yaml
      image: alpine
      command: ["sed"]
      args:
        - "-i"
        - "-e"
        - "s;__IMAGE__;$(params.imageUrl):$(params.imageTag);g"
        - "-e"
        - "s;__DIGEST__;$(params.imageDigest);g"
        - "$(workspaces.git-source.path)/$(params.pathToYamlFile)"
    - name: run-kubectl
      image: lachlanevenson/k8s-kubectl
      command: ["kubectl"]
      args:
        - "apply"
        - "-f"
        - "$(workspaces.git-source.path)/$(params.pathToYamlFile)"

Pipelineにはこちらのファイルを利用しています。

pipeline1.yml

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: build-and-push-pipeline
spec:
  workspaces:
    - name: git-source
      description: The git repo
  params:
    - name: gitUrl
      description: Git repository url
    - name: gitRevision
      description: Git revision to check out
      default: master
    - name: pathToContext
      description: The path to the build context, used by Kaniko - within the workspace
      default: src
    - name: imageUrl
      description: Image name including repository
    - name: imageTag
      description: Image tag
      default: "latest"
    - name: pathToYamlFile
      description: The path to the yaml file to deploy within the git source
  tasks:
    - name: clone-repo
      taskRef:
        name: git-clone
      workspaces:
        - name: output
          workspace: git-source
      params:
        - name: url
          value: "$(params.gitUrl)"
        - name: revision
          value: "$(params.gitRevision)"
        - name: subdirectory
          value: "."
        - name: deleteExisting
          value: "true"
    - name: source-to-image
      taskRef:
        name: kaniko
      runAfter:
        - clone-repo
      workspaces:
        - name: source
          workspace: git-source
      params:
        - name: CONTEXT
          value: $(params.pathToContext)
        - name: IMAGE
          value: $(params.imageUrl):$(params.imageTag)
    - name: deploy-to-cluster
      taskRef:
        name: deploy-using-kubectl
      runAfter: 
        - source-to-image
      workspaces:
        - name: git-source
          workspace: git-source
      params:
        - name: pathToYamlFile
          value: $(params.pathToYamlFile)
        - name: imageUrl
          value: $(params.imageUrl)
        - name: imageTag
          value: $(params.imageTag)
        - name: imageDigest
          value: $(tasks.source-to-image.results.IMAGE-DIGEST)
  • spec.tasks: Pipeline中のTaskにrunAfterを設定することで、Task間の実行順を設定することができます。ここではclone-repo source-to-image deploy-to-clusterという順で実行されるようrunAfterを設定しています。

最後にPipelineRunこちらのファイルを利用しています。PipelineRunではPipelineで設定したパラメータやWorkspaceに利用するPersistent Volume Claimなどを設定します。

pipelinerun1.yml

apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: build-and-push-pieline
spec:
  pipelineRef:
    name: build-and-push-pipeline
  params:
    - name: gitUrl
      value: https://github.com/FY0323/tekton-test.git
    - name: pathToContext
      value: .
    - name: pathToYamlFile
      value: "test-app.yml"
    - name: imageUrl
      value: fy0323/tekton-test
    - name: imageTag
      value: "1"
  serviceAccountName: pipeline-account
  workspaces:
    - name: git-source
      persistentVolumeClaim:
        claimName: tekton-volume
  • spec.pipelineRef: ここには実行するPipelineを指定します。
  • spec.serviceAccountName: ここではPipelineを実行する際に利用するService Account(ここではpipeline-account)を指定します。
  • spec.workspaces: ここではPipeline中に利用するWorkspaceと、それに利用するKubernetesリソースを指定します。ここではPersistent Volume Claimを利用します。

※参考ドキュメント:


それでは用意した定義ファイルを用いて各リソースを作成します。

# Taskの作成
$ kubectl apply -f tk_task/
task.tekton.dev/kaniko created
task.tekton.dev/deploy-using-kubectl created

$ kubectl get tekton-pipelines
NAME                                   AGE
task.tekton.dev/deploy-using-kubectl   7s
task.tekton.dev/git-clone              18h
task.tekton.dev/kaniko                 18h



# Pipelineの作成
$ kubectl apply -f pipeline1.yml
pipeline.tekton.dev/build-and-push-pipeline created

$ kubectl get tekton-pipelines
NAME                                          AGE
pipeline.tekton.dev/build-and-push-pipeline   1m

NAME                                   AGE
task.tekton.dev/deploy-using-kubectl   2m
task.tekton.dev/git-clone              19h
task.tekton.dev/kaniko                 19h



# PipelineRunの作成(Pipelineの実行)

$ kubectl apply -f pipelinerun1.yml
pipelinerun.tekton.dev/build-and-push-pieline created

$ tkn pipelinerun logs --last -f
[clone-repo : clone] + CHECKOUT_DIR=/workspace/output/.
[clone-repo : clone] + '[[' true '==' true ]]
[clone-repo : clone] + cleandir
[clone-repo : clone] + '[[' -d /workspace/output/. ]]
[clone-repo : clone] + rm -rf /workspace/output/./Dockerfile /workspace/output/./image-digest /workspace/output/./image-digested /workspace/output/./pipeline-account.yml /workspace/output/./pipeline1.yml /workspace/output/./pipelinerun1.yml /workspace/output/./sa_clusterrolebinding.yml /workspace/output/./src /workspace/output/./taskrun1.yml /workspace/output/./test-app.yml /workspace/output/./tk_resource /workspace/output/./tk_task
[clone-repo : clone] + rm -rf /workspace/output/./.git
[clone-repo : clone] + rm -rf '/workspace/output/./..?*'
[clone-repo : clone] + test -z
[clone-repo : clone] + test -z
[clone-repo : clone] + test -z
[clone-repo : clone] + /ko-app/git-init -url https://github.com/FY0323/tekton-test.git -revision master -refspec  -path /workspace/output/. '-sslVerify=true' '-submodules=true' -depth 1
[clone-repo : clone] {"level":"info","ts":1594779966.749329,"caller":"git/git.go:136","msg":"Successfully cloned https://github.com/FY0323/tekton-test.git @ 3d34a47b3de078961ebf8c7b71143bf9ea17f2ec (grafted, HEAD, origin/master) in path /workspace/output/."}
[clone-repo : clone] {"level":"info","ts":1594779966.8057873,"caller":"git/git.go:177","msg":"Successfully initialized and updated submodules in path /workspace/output/."}
[clone-repo : clone] + cd /workspace/output/.
[clone-repo : clone] + git rev-parse HEAD
[clone-repo : clone] + tr -d '\n'
[clone-repo : clone] + RESULT_SHA=3d34a47b3de078961ebf8c7b71143bf9ea17f2ec
[clone-repo : clone] + EXIT_CODE=0
[clone-repo : clone] + '[' 0 '!=' 0 ]
[clone-repo : clone] + echo -n 3d34a47b3de078961ebf8c7b71143bf9ea17f2ec

[source-to-image : build-and-push] INFO[0002] Retrieving image manifest golang:latest
[source-to-image : build-and-push] INFO[0004] Retrieving image manifest golang:latest
[source-to-image : build-and-push] INFO[0008] Built cross stage deps: map[]
[source-to-image : build-and-push] INFO[0008] Retrieving image manifest golang:latest
[source-to-image : build-and-push] INFO[0010] Retrieving image manifest golang:latest
[source-to-image : build-and-push] INFO[0014] Executing 0 build triggers
[source-to-image : build-and-push] INFO[0014] Unpacking rootfs as cmd COPY src/main.go /go/src/work/ requires it.
[source-to-image : build-and-push] INFO[0038] EXPOSE 8080
[source-to-image : build-and-push] INFO[0038] cmd: EXPOSE
[source-to-image : build-and-push] INFO[0038] Adding exposed port: 8080/tcp
[source-to-image : build-and-push] INFO[0038] COPY src/main.go /go/src/work/
[source-to-image : build-and-push] INFO[0038] Taking snapshot of files...
[source-to-image : build-and-push] INFO[0038] WORKDIR /go/src/work
[source-to-image : build-and-push] INFO[0038] cmd: workdir
[source-to-image : build-and-push] INFO[0038] Changed working directory to /go/src/work
[source-to-image : build-and-push] INFO[0038] No files changed in this command, skipping snapshotting.
[source-to-image : build-and-push] INFO[0038] RUN go get -u github.com/gin-gonic/gin && go build main.go
[source-to-image : build-and-push] INFO[0038] Taking snapshot of full filesystem...
[source-to-image : build-and-push] INFO[0045] cmd: /bin/sh
[source-to-image : build-and-push] INFO[0045] args: [-c go get -u github.com/gin-gonic/gin && go build main.go]
[source-to-image : build-and-push] INFO[0045] Running: [/bin/sh -c go get -u github.com/gin-gonic/gin && go build main.go]
[source-to-image : build-and-push] INFO[0104] Taking snapshot of full filesystem...
[source-to-image : build-and-push] INFO[0109] CMD ["./main"]


[source-to-image : digest-to-results] + cat ./image-digested
[source-to-image : digest-to-results] + jq .[0].value -rj
[source-to-image : digest-to-results] + tee /tekton/results/IMAGE-DIGEST
[source-to-image : digest-to-results] sha256:b5248ad0c8ff878ad795c17fdad3b0a842cf38e3dc7df97f66012511af803c5a


[deploy-to-cluster : run-kubectl] deployment.apps/test-app configured
[deploy-to-cluster : run-kubectl] service/test-app created



# 実行後の確認
$ kubectl get tekton-pipelines
NAME                                          AGE
pipeline.tekton.dev/build-and-push-pipeline   37m

NAME                                            SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
pipelinerun.tekton.dev/build-and-push-pieline   True        Succeeded   14m         10m

NAME                                   AGE
task.tekton.dev/deploy-using-kubectl   39m
task.tekton.dev/git-clone              19h
task.tekton.dev/kaniko                 19h

NAME                                                                SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
taskrun.tekton.dev/build-and-push-pieline-clone-repo-hwc7g          True        Succeeded   14m         13m
taskrun.tekton.dev/build-and-push-pieline-deploy-to-cluster-n9wdw   True        Succeeded   11m         10m
taskrun.tekton.dev/build-and-push-pieline-source-to-image-jmqg8     True        Succeeded   13m         11m


$ kubectl get pods
NAME                                                       READY   STATUS      RESTARTS   AGE
build-and-push-pieline-clone-repo-hwc7g-pod-flpw6          0/1     Completed   0          6m7s
build-and-push-pieline-deploy-to-cluster-n9wdw-pod-ttp7l   0/2     Completed   0          3m9s
build-and-push-pieline-source-to-image-jmqg8-pod-zfrxg     0/3     Completed   0          5m49s
test-app-6b47db66b5-cfgjz                                  1/1     Running     0          2m51s

$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP   172.21.0.1      <none>        443/TCP          45h
test-app     NodePort    172.21.15.253   <none>        8080:30008/TCP   3m11s

なお、実行後のPersistent Volumeにアクセスすると、先ほどのPipelineで作成したファイルなどが確認できます。

# Persistent VolumeへアクセスするPodを作成
$ kubectl apply -f testpod.yml
pod/mypod created



# Podへログイン
$ kubectl exec -it mypod /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
root@mypod:/#


# Persistent Volumeの内容確認
root@mypod:/# pwd
/
root@mypod:/# ls -l /var/www/html
total 48
-rw-r--r-- 1 root root  174 Jul 15 02:26 Dockerfile
drwxr-xr-x 3 root root 4096 Jul 15 02:28 image-digest★
-rw-r--r-- 1 root root  146 Jul 15 02:28 image-digested★
-rw-r--r-- 1 root root  951 Jul 15 02:26 pipeline-account.yml
-rw-r--r-- 1 root root 2099 Jul 15 02:26 pipeline1.yml
-rw-r--r-- 1 root root  676 Jul 15 02:26 pipelinerun1.yml
-rw-r--r-- 1 root root  283 Jul 15 02:26 sa_clusterrolebinding.yml
drwxr-xr-x 2 root root 4096 Jul 15 02:26 src
-rw-r--r-- 1 root root  464 Jul 15 02:26 taskrun1.yml
-rw-r--r-- 1 root root  601 Jul 15 02:29 test-app.yml
drwxr-xr-x 2 root root 4096 Jul 15 02:26 tk_resource
drwxr-xr-x 2 root root 4096 Jul 15 02:26 tk_task

root@mypod:/# cat /var/www/html/image-digested
[{"key":"digest","value":"sha256:b5248ad0c8ff878ad795c17fdad3b0a842cf38e3dc7df97f66012511af803c5a","resourceRef":{"name":"fy0323/tekton-test:1"}}]

root@mypod:/# ls -l /var/www/html/image-digest
total 12
drwxr-xr-x 3 root root 4096 Jul 15 02:28 blobs
-rwxr-xr-x 1 root root  259 Jul 15 02:28 index.json
-rwxr-xr-x 1 root root   37 Jul 15 02:28 oci-layout

root@mypod:/# cat /var/www/html/image-digest/oci-layout
{
    "imageLayoutVersion": "1.0.0"
}

root@mypod:/# cat /var/www/html/image-digest/index.json
{
   "schemaVersion": 2,
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1738,
         "digest": "sha256:b5248ad0c8ff878ad795c17fdad3b0a842cf38e3dc7df97f66012511af803c5a"
      }
   ]
}

root@mypod:/# ls -l /var/www/html/image-digest/blobs/sha256
total 401604
-rw-r--r-- 1 root root  51827493 Jul 15 02:28 5573c4b3094956931fd68c310ae92c9eb1a787f0c77ac2730be9d16cce172d5e
-rw-r--r-- 1 root root  98813395 Jul 15 02:28 56269221df77fbedf580f9a2caacbcb7dc4e7ea2d33922cd95973f70323e13a6
-rw-r--r-- 1 root root       125 Jul 15 02:28 56c4bef2c0bbf3cff5165f41fc838bc6eb019e04a44afcb8e6e7d5ab1da0f0c9
-rw-r--r-- 1 root root      5836 Jul 15 02:28 8ea17da6b6f374412f284f9d00ec18ee7926f87456ae16f30f5c7e3cfb37de2b
-rw-r--r-- 1 root root 123712482 Jul 15 02:28 90833167b3a65b4f9c43490478a2b69758cd005193ac60dbb81b20717ba4a5a0
-rw-r--r-- 1 root root   7811709 Jul 15 02:28 989e6b19a265d6b8b7934e7ddd7dc07f6e2fc945b3a28dda9b8aecb12cdb30e0
-rw-r--r-- 1 root root   9996168 Jul 15 02:28 af14b6c2f8785723bceb5964c5dec1f0489b7750e9d4ec671e49bfba15d80a39
-rw-r--r-- 1 root root      1738 Jul 15 02:28 b5248ad0c8ff878ad795c17fdad3b0a842cf38e3dc7df97f66012511af803c5a
-rw-r--r-- 1 root root       365 Jul 15 02:28 bda1f29e2d9758bd7e97f0e8cd6855a27f1a5015ed64b5d9ef763f5ba88c762e
-rw-r--r-- 1 root root  68650396 Jul 15 02:28 d4020e2aa747ca4d0ca2e87305f22f089fd57a696fd19bfa30182316d51b089a
-rw-r--r-- 1 root root  50389504 Jul 15 02:28 e9afc4f90ab09248d75c8081b6dfba749a7f7efdac704ced7e0ceb506e02fa4a

なお上記mypodで利用した定義ファイルはこちらになります。

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: nginx
      volumeMounts:
      - mountPath: "/var/www/html"
        name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: tekton-volume

参考ドキュメント