📝

【Kubernetes】Cloud NativeなCICDツールTektonの使い方まとめ

2023/02/05に公開

内容

Kubernetes上で動作するCloud NativeなCICDツールTektonに入門したので、簡単な使い方やセットアップについてまとめます。

Tektonとは

https://tekton.dev/

Tektonはk8s上で動作するCICDツールです。k8s上で動作するため、kubernetesクラスターに対してTektonのCRDをinstallし、yamlファイルにてCICDパイプラインを構築していきます。

Tektonを使うメリット

Tektonを使うメリットを紹介します。

自由度の高さ

自分たちが運用するKubernetes上で自由にPipelineを構築できるため、自由にPipelineをカスタマイズすることができます。

セキュリティ

Kubernetes上でPipelineを実行するため、Productionで動作するWorkflowを同じクラスター内で実行できます。外部のCICDツールを利用する場合、Production環境へのアクセス権を外部ツールに付与しないといけないため、セキュリティリスクが向上します。

スケーラビリティ

Kubernetes上で実行環境を用意するため、nodeを追加するだけでスケールします。

コスト面

ちゃんと運用した場合に限りますが、Kubernetes上でリソースをシェアして使えるので、managedサービスにお金を払う場合に比べて、コストが低くなる可能性があります。

カタログ

Tekton Catalog ( https://tekton.dev/docs/catalog/ ) を利用することで、自分でコーディングすることなく、様々なコンポーネントを組み合わせてPipelineを構成することができます。

Tektonを使うデメリット

もちろん、github actionsといった完全マネージドなCICDツールを使用する場合に比べて、Tektonを使うデメリットも存在します。

情報の少なさ

github actionsやCircleCIと比べ、Tektonに関する情報は少ないです。特に日本語の情報はほぼ見当たりません。

運用コストの高さ

Tektonのメリットである自由度の高さに付随してついてくるデメリットが運用コストの高さです。
github actionsの場合、基盤のアップデートをgithubサイドに任せることが出来ますが、Tektonを使用する場合、Tektonの実行環境であるKubernetesを始め、Tekton自体の運用を自分たちで行わなければなりません。

Tektonの動作イメージ

Tekton

大雑把にTektonの動作フローを図示すると上記のようになります。(参考: https://tekton.dev/docs/getting-started/ )

Tektonの構成要素

上記図に表記されている要素を解説します。

Step

Tektonを構成する最小要素で、一つの実行単位を表します。例えば、「githubからソースをcloneする」「CloneしたソースをDocker Buildする」といった一つの処理の実行単位がStepです。

Task

Taskは複数のStepを一つのまとまりにしたものです。

  • githubからソースをcloneする
  • CloneしたソースをDocker Buildする
  • BuildしたDocker ImageをDocker HubにPushする

例えば、上記3つのStepの順番を制御し、実行する単位がTaskです。

Kubernetes上で、TaskはPodとして管理され、StepはそのPodの中のContainerとして管理されます。

Pipeline

複数のTaskを束ねたものがPipelineになります。

  • githubからソースをcloneして、Buildして、Docker HubにPushするTask
  • Docker HubにPushされたImageをProduction環境にDeployするTask
  • Deploy後、正常に動作しているかテストするTask

こういった連続性が必要な複数のTaskの順番を制御し実行する単位がPipelineです。 Task間での値の受け渡しも可能です。

Shared Workspace

一つのPipelineを通して、複数のTaskで共通のファイルを参照できるようにする機能がWorkspaceです。 Kubernetes上ではPersistent Volumeとして管理されています。

TaskRun

Taskの実行を制御する要素です。Taskを作成しただけだとTaskは実行されず、TaskRunをKubernetes上に作成することで、対象のTaskを実行することができます。また、TaskRunでTaskに渡したいParameterを指定することで、汎用的なTaskを様々なTaskRunで使い回すことが可能です。

PipelineRun

TaskRun同様に、Pipeleinの実行を制御する要素です。 Schedule機能もあるため、毎日17時に集計バッチ用のPipelineを実行するといったことも可能です。

Tektonの周辺ツール

Tektonを使いやすくするツール群を紹介します。

Tekton CLI

Tektonの操作をしやすくするためのCLI(TektonはKubernetesAPIを操作する形で実行可能なので、CLIがなくても、kubectl等で構築・実行可能です。)

install方法

$ brew install tektoncd-cli

Tekton Dashboard

https://tekton.dev/docs/dashboard/

Tektonで実行したPipelineのログ等を可視化して表示できるツール

Tekton Triggers

https://tekton.dev/docs/triggers/

github上のソースが変更された際に、githubからTektonで公開されたendpointをキックして、TektonのPipelineを実行する。といった、ユースケースを可能にするのがTekton Triggersです。

Tektonのinstall方法

TektonをKubernetesクラスターにinstallします。

Tekton Pipeline

Tektonを使用するために必要なコアファイルをinstallします。

$ kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml

Tekton Triggers

TektonのEvent周りを制御するために必要なmanifestファイルをinstallします。

$ kubectl apply --filename https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml
$ kubectl apply --filename https://storage.googleapis.com/tekton-releases/triggers/latest/interceptors.yaml

Tekton Dashboard

Tektonで実行したPipeline等をUI上で確認するためのツールをinstallします。

$ kubectl apply --filename https://storage.googleapis.com/tekton-releases/dashboard/latest/release-full.yaml

ブラウザで確認する方法

$ kubectl port-forward -n tekton-pipelines service/tekton-dashboard 9097:9097

# http://localhost:9097 にてアクセス可能

実際にTektonでPipelineを構築してみる

実際にTektonでPipelineを構築します。

Taskの作成と実行

標準出力に Hello! という文字列を出力するTaskを作成します。

hello-task.yaml

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: hello-task
spec:
  steps:
    - name: echo-hello
      image: alpine
      script: |
        #!/bin/sh
        echo "Hello!"

alpineのベースイメージ上でshellを実行するシンプルなTaskです。

$ kubectl apply -f hello-task.yaml
task.tekton.dev/hello-task created

$ kubectl get task
NAME         AGE
hello-task   26s

正常にTektonのTaskが作成されました。次にこのTaskを実行するためにTaskRunを作成します。

hello-taskrun.yaml

apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
  generateName: hello-taskrun-
spec:
  taskRef:
    name: hello-task

generateNameを使用することで、suffixにランダム文字列を追加することができます。これを付与することで、同じTaskRunを何回実行しても名前が重複して実行できないということを防ぐことができます。

$ kubectl create -f hello-taskrun.yaml
taskrun.tekton.dev/hello-taskrun-nbqtb created

$ kubectl get taskrun
NAME                  SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
hello-taskrun-nbqtb   True        Succeeded   29s         19s

正常にTaskRunが作成されたので、hello-taskが実行されているはずです。正常に実行されたことを確認するために、logを確認します。

$ kubectl logs --selector=tekton.dev/taskRun=hello-taskrun-nbqtb
Defaulted container "step-echo-hello" out of: step-echo-hello, prepare (init), place-scripts (init)
Hello!

標準出力にHello!という文字列が出力されています!

Taskにparamsを渡す

次に先ほど作成したTaskにparamsを渡せるようにし、Hello!ではなく、指定した文字列を標準出力に出力できるようにします。

hello-task.yaml

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: hello-task
spec:
  params:
    - name: username
      type: string
      description: The name of user
      default: Tom
  steps:
    - name: echo-hello
      image: alpine
      script: |
        #!/bin/sh
        echo "Hello $(params.username)!"

このTaskはusernameというparamsを引数に取り、その値を標準出力に出力します。
このTaskにusername paramを渡して呼び出せるようにTaskRunを修正します。

apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
  generateName: hello-taskrun-
spec:
  taskRef:
    name: hello-task
  params:
    - name: username
      value: Yossy
# apply task
$ kubectl apply -f hello-task.yaml

# create task run
$ kubectl create -f hello-taskrun.yaml
taskrun.tekton.dev/hello-taskrun-7c472 created

$ kubectl get taskrun
NAME                  SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
hello-taskrun-59v8r   True        Succeeded   20s         13s

$ kubectl logs --selector=tekton.dev/taskRun=hello-taskrun-59v8r                   
Defaulted container "step-echo-hello" out of: step-echo-hello, prepare (init), place-scripts (init)
Hello Yossy!

これで、paramsに値を受け取って、その値を処理に利用する汎用的なTaskが作成できました。

複数のTaskをPipelineで接続する

次に、複数のTaskをPipelineでまとめて、順に実行していきます。

まずは、Helloではなく、Goodbyeと出力するTaskを作成します。

goodbye-task.yaml

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: goodbye-task
spec:
  params:
    - name: username
      type: string
      description: The name of user
      default: Belle
  steps:
    - name: echo-goodbye
      image: alpine
      script: |
        #!/bin/sh
        echo "GoodBye $(params.username)!"
$ kubectl apply -f goodbye-task.yaml 
task.tekton.dev/goodbye-task created

次に、2つのTaskを順番に実行できるようにPipelineを作成します。

$ touch pipeline.yaml

pipeline.yaml

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: pipeline
spec:
  params:
    - name: hello-name
      type: string
      description: hello name
      default: Bob
    - name: goodbye-name
      type: string
      description: goodbye name
      default: Kate
  tasks:
    - name: hello
      taskRef:
        name: hello-task
      params:
        - name: username
          value: "$(params.hello-name)"
    - name: goodbye
      taskRef:
        name: goodbye-task
      params:
        - name: username
          value: "$(params.goodbye-name)"
      runAfter:
        - hello

$ kubectl apply -f pipeline.yaml 
pipeline.tekton.dev/pipeline created
$ touch pipeline-run.yaml

pipeline-run.yaml

apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  generateName: pipelinerun-
spec:
  pipelineRef:
    name: pipeline

$ kubectl create -f pipeline-run.yaml
pipelinerun.tekton.dev/pipelinerun-655k2 created

$ kubectl get pipelinerun
NAME                SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
pipelinerun-655k2   True        Succeeded   52s         15s

$ kubectl describe pipelinerun pipelinerun-655k2
Name:         pipelinerun-655k2
Namespace:    default
Labels:       tekton.dev/pipeline=pipeline
Annotations:  <none>
API Version:  tekton.dev/v1
Kind:         PipelineRun
Metadata:
  Creation Timestamp:  2023-01-27T09:04:26Z
  Generate Name:       pipelinerun-
  Generation:          1
...
Spec:
  Pipeline Ref:
    Name:  pipeline
  Task Run Template:
    Service Account Name:  default
  Timeouts:
    Pipeline:  1h0m0s
Status:
  Child References:
    API Version:         tekton.dev/v1beta1
    Kind:                TaskRun
    Name:                pipelinerun-655k2-hello
    Pipeline Task Name:  hello
    API Version:         tekton.dev/v1beta1
    Kind:                TaskRun
    Name:                pipelinerun-655k2-goodbye
    Pipeline Task Name:  goodbye
  Completion Time:       2023-01-27T09:05:03Z
  Conditions:
    Last Transition Time:  2023-01-27T09:05:03Z
    Message:               Tasks Completed: 2 (Failed: 0, Cancelled 0), Skipped: 0
    Reason:                Succeeded
    Status:                True
    Type:                  Succeeded.
...
Events:
  Type    Reason     Age                From         Message
  ----    ------     ----               ----         -------
  Normal  Started    16m                PipelineRun  
  Normal  Running    16m                PipelineRun  Tasks Completed: 0 (Failed: 0, Cancelled 0), Incomplete: 2, Skipped: 0
  Normal  Running    15m (x2 over 15m)  PipelineRun  Tasks Completed: 1 (Failed: 0, Cancelled 0), Incomplete: 1, Skipped: 0
  Normal  Succeeded  15m                PipelineRun  Tasks Completed: 2 (Failed: 0, Cancelled 0), Skipped: 0

2つのTaskが実行されていることが確認できました。

Taskの結果を他のTaskに引き継ぐ

次に、連続したTaskで、先に実行したタスクの結果を次のTaskに引き渡す実装を行います。
Taskの結果を他のTaskに引き継ぎたい場合、resultをつかいます。

hello-taskの結果をresultで出力する

hello-task.yaml

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: hello-task
spec:
  results:
    - name: output-value
      description: Output Value
  params:
    - name: username
      type: string
      description: The name of user
      default: Tom
  steps:
    - name: echo-hello
      image: alpine
      script: |
        #!/bin/sh
        echo "Hello $(params.username)!" | tee $(results.output-value.path)

上記のような形で、reusltsparamsを追加し、results.output-value.pathに出力したい値を代入することで、他のタスクにこのタスクの結果を引き渡すことができます。
これで、hello-taskの結果を他のタスクに引き渡す準備が出来ました。Pipelineの設定を変更して、出力内容をgoodbye-taskに引き渡します。

pipeline.yaml

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: pipeline
spec:
  params:
    - name: hello-name
      type: string
      description: hello name
      default: Bob
    - name: goodbye-name
      type: string
      description: goodbye name
      default: Kate
  tasks:
    - name: hello
      taskRef:
        name: hello-task
      params:
        - name: username
          value: "$(params.hello-name)"
    - name: goodbye
      taskRef:
        name: goodbye-task
      params:
        - name: username
          value: $(tasks.hello.results.output-value)
      runAfter:
        - hello

goobey-taskのusernameのvalueをhello-taskの出力に変更しました。この状態でこのPipelineを実行します。

$ kubectl apply -f hello-task.yaml 
task.tekton.dev/hello-task configured

$ kubectl apply -f pipeline.yaml 
pipeline.tekton.dev/pipeline configured

$ kubectl create -f pipeline-run.yaml 
pipelinerun.tekton.dev/pipelinerun-jjjrx created

$ kubectl get pipelinerun   
NAME                SUCCEEDED   REASON     STARTTIME   COMPLETIONTIME
pipelinerun-dbkkq   True        Succeeded  22s         10s

# pipelineのgoodbyeタスクのlogを確認する
$ kubectl logs pipelinerun-dbkkq-goodbye-pod
Defaulted container "step-echo-goodbye" out of: step-echo-goodbye, prepare (init), place-scripts (init)
GoodBye Hello Bob!
!

以上のような、helloタスクの出力結果がgoodbyeタスクに引き渡されていることがわかります。

Dashboardで確認する

最後に、今回実験した内容をDashboardで確認します。

$ kubectl port-forward  svc/tekton-dashboard -n tekton-pipelines 9097:9097

port-forwardでDashboardにローカルから接続できるようにします。

Tekton
Tekton
Tekton

上記のように実行結果がブラウザで確認できます。(テスト実行したものがたくさんあってすみません...)

まとめ

CloudNativeなCICDパイプラインTektonの基本的な使い方を紹介しました。
github actionsやCircleCIといったマネージドなCICDツールと違い、自分でサーバーを管理する手間が増えますが、自分達でサーバーの面倒を見るからこそ得られる自由度の高さがTektonの魅力だと思います。
CICDパイプラインは開発効率に大きく影響する重要な工程です。最適なツールを選定することが重要かと思います。

note

勉強法やキャリア構築法など、エンジニアに役立つ記事をnoteで配信しています。

https://note.com/ring_belle/membership

Discussion