🔬

Tekton Triggerを作成しCurlでビルドを実行してみる

2022/07/14に公開

はじめに

前々回及び前回に引き続きGKE AutopilotとTektonを組合わせてスケーラブルなCIを作っていきます。今まではtaskrunマニフェストを手動で投げる実行していましたが、実際は外部からリクエストされたり、コードがPushされたとき、などイベントに応じてリアクティブに実行したいですよね? 継続的ではないビルドCI(Continuous Untegration) ではありません><

もちろん、Tektonでもそのあたりはサポートしています。今回はまずはCurlでWebhookを叩くサンプルで基本的な挙動を理解し、次回にGitHubへのPushに反応するCIを構築したいと思います。

環境準備

まずは以前と同じくGKE Autopilotで環境を作成します。

追加でTekton Trigerに必要な2つのマニフェストも適用します。

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

最後に前回まではNamespaceはデフォルトにしていましたが、より実用的な構成を目指して今回からはNamespaceを指定します。k8sにおけるNamespaceは簡単に言えばリソースの論理的な仕切りなので、複数のワークロードを一つのクラスタに乗せる時には必須の設定かと思います。
以下のようにNamespaceを作成しデフォルトで利用するようにコンテキストの作成とデフォルトの切替えをします。

kubectl create ns tekton-workers
kubectl config set-context tekton-workers --namespace tekton-workers --cluster $(kubectl config current-context) --user=$(kubectl config current-context)
kubectl config use-context tekton-workers
kubectl config get-contexts

これでtekton-workersというネームスペースが作られ、特に指定しなければこちらにTaskやTriggerといったワーカー系のリソースが登録されます。

$ kubectl get ns
NAME               STATUS   AGE
default            Active   30m
kube-node-lease    Active   30m
kube-public        Active   30m
kube-system        Active   30m
tekton-pipelines   Active   11m
tekton-workers     Active   2m23s

TaskとPipelineの作成

まずは、トリガーで実行する対象を作成します。今回は非常にシンプルな構成で、単一のTaskを持つPipelineで、Taskは渡されたパラメータを元に標準出力にメッセージ吐くだけとなっています。

Taskは以下の内容をtask-myecho.yamlという名前で保存します。

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: my-echo
  description: A simple echo task.
spec:
  params:
  - name: name
    type: string
    description: Your Name for print.
  - name: message
    type: string
    description: This is a message for print.
  steps:
  - name: echo
    image: alpine
    script: |
      #!/usr/bin/env sh
      echo "================================"
      echo "My Echo Task"
      echo "================================"
      echo "Hi, $(params.name)"
      echo "$(params.message)"

続いて上記のTaskを含むPipelineは以下の内容でpipeline.yamlという名前で保存します。

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: pipeline-echo
spec:
  description: |
    This pipeline is a simple echo to STDOUT.
  params:
  - name: name
    type: string
    description: Your Name for print.
  - name: message
    type: string
    description: This is a message for print.
  tasks:
  - name: echo
    taskRef:
      name: my-echo
    params:
      - name: name
        value: "$(params.name)"
      - name: message
        value: "$(params.message)"

以下のようにTaskとPipelineを登録します。

$ kubectl apply -f task-myecho.yaml
$ kubectl apply -f pipeline.yaml

$ tkn task list
NAME      DESCRIPTION   AGE
my-echo                 37 seconds ago
$ tkn pipeline list
NAME            AGE              LAST RUN   STARTED   DURATION   STATUS
pipeline-echo   29 seconds ago   ---        ---       ---        ---

サービスアカウントの作成とRBACの設定

今回は以下の2つのサービスアカウントを利用します。

  • sa-buildbot
  • sa-trigger

sa-buildbotはPipelineやTaskといったビルダーに付与するアカウントとして作成します。通常はGitHubなどのクレデンシャルを紐づけますが、今回は特に意味のある権限は付けません。以下をsa-buildbot.yamlの名前で保存してください。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-buildbot

sa-triggerはTriggerの実行のためのサービスアカウントです。こちらはRBACによりトリガーの実行に必要な各種権限を付けておきます。以下をsa-tekton-el.yamlの名前で保存します。

#
# Service Account
#
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-trigger
---
#
# Role
#
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: role-trigger
rules:
- apiGroups:
  - triggers.tekton.dev
  resources:
  - eventlisteners
  - triggers
  - triggerbindings
  - triggertemplates
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - tekton.dev
  resources:
  - pipelineruns
  - pipelineresources
  verbs:
  - create
- apiGroups:
  - ""
  resources:
  - configmaps
  verbs:
  - get
  - list
  - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: triggers-role-binding
subjects:
  - kind: ServiceAccount
    name: sa-trigger
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: role-trigger
---
#
# Cluster Role
#
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: clusterrole-trigger
rules:
- apiGroups: ["triggers.tekton.dev"]
  resources: ["clustertriggerbindings", "clusterinterceptors"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: clusterbinding-trigger
subjects:
- kind: ServiceAccount
  name: sa-trigger
  namespace: tekton-workers
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: clusterrole-trigger

上記のマニフェストを適用してサービスアカウントを確認します。

$ kubectl apply -f sa-buildbot.yaml
$ kubectl apply -f sa-tekton-el.yaml

$ kubectl get sa -n tekton-workers
NAME          SECRETS   AGE
default       1         160m
sa-buildbot   1         15s
sa-trigger    1         10s

トリガー/イベントリスナーの登録

いよいよ本題のトリガーの登録です。今回使うのは以下の3つです。

  • TriggerTemplate
  • TriggerBinding
  • EventListener

TriggerTemplateはPipelineを実行する実態やコンテキストを書きます。TaskRunやPipelineRunに似ていますね。TriggerBindingはHTTPで受けとった値をTektonのパラメータに変換する事が出来ます。最後にEventListenerは、前者2つをマッピングした常駐のサービスです。

今回は最終的には、こちらのtrigger-curl.yamlに1ファイルに統合しています。

TriggerTemplate

TriggerTemplateではPipelineを実行するためのPipelineRunやその名前の自動生成、実行するサービスアカウントを記述する事が出来ます。resourcetemplates側でパラメータを参照する際にtt(trigger template).を付ける必要があることに注意をしてください。

apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerTemplate
metadata:
  name: template-curl-trigger
spec:
  params:
    - name: name
      description: Your Name for print.
    - name: message
      description: This is a message for print.
      default: hello, world
  resourcetemplates:
    - apiVersion: tekton.dev/v1beta1
      kind: PipelineRun
      metadata:
        # Autogenerate Pipeline Run Name with unique ID as pipeline-run-curl-{Rondom ID}.
        generateName: pipeline-run-curl-
      spec:
        pipelineRef:
          name: pipeline-echo
        params: 
          # `tt` is `trigger template`
          - name: name
            value: $(tt.params.name)
          - name: message
            value: $(tt.params.message)
        serviceAccountName: sa-buildbot

TriggerBinding

TriggerBindingではHTTPで受け取った値をTektonのパラメータにマッピングします。下記では以下のようなJSONをBody要素にとった場合に変換しています。

{
  "user": {
    "name": "koduki"
  },
  "greeting": {
    "message": "Hello"
  }
}

マッピングするさいはシンプルに.で繋げるだけです。Body以外にもHeader情報なども取得でき、詳しくは公式ページを参考にすると良いと思います。

apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerBinding
metadata:
  name: binding-curl-trigger
spec:
  params:
    # Binding from HTTP Rquests to Tekton values
    - name: name
      value: $(body.user.name)
    - name: message
      value: $(body.greeting.message)

EventListener

EventListenerはTriggerTemplateとBindingをマッピングし、イベントを受け付けるServiceを作成します。このマニフェストから自動的にel-{EventListener Name}でServiceも作られるのでHTTPリクエスト等はそこに投げる事になります。

apiVersion: triggers.tekton.dev/v1alpha1
kind: EventListener
metadata:
  name: curl-for-echo
spec:
  serviceAccountName: sa-trigger
  triggers:
    - bindings:
        - ref: binding-curl-trigger
      template:
        ref: template-curl-trigger

トリガーの登録

さて、それでは登録していきましょう。3つのマニフェストファイルを作ってそれぞれ実行しても良いのですが関連性が深いのでtrigger-curl.yamlとして1ファイルにまとめています。

$ kubectl apply -f trigger-curl.yaml

$ tkn eventlistener list
NAME            AGE              URL                                                             AVAILABLE
curl-for-echo   11 minutes ago   http://el-curl-for-echo.tekton-workers.svc.cluster.local:8080   True

$ tkn triggerbinding list
NAME                   AGE
binding-curl-trigger   11 minutes ago

$ tkn triggertemplate list
NAME                    AGE
template-curl-trigger   12 minutes ago

tkn-cliで各種登録された事が分かりますね! また以下のようにel-{イベントリスナー名}でサービスが作成されているのも分かります。

$ kubectl get services -n tekton-workers
NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
el-curl-for-echo   ClusterIP   10.71.202.119   <none>        8080/TCP,9000/TCP   16m

これでトリガーのための準備は完了です。

curlでWebhookを実行

それではいよいよ、curlでWebhookをキックしてみます。まず、サービスのURLをアクセス可能にしてやる必要がありますので今回はkubectlでport-fowardします。本来はEgressなどの設定を行い外部から見えるようにしてやる必要があります。

$ kubectl port-forward service/el-curl-for-echo 8081:8080 -n tekton-workers
Forwarding from 127.0.0.1:8081 -> 8080
Forwarding from [::1]:8081 -> 8080

続いてcurlを実行します。以下のようなJSONをデータとして持ちそれが先ほどのTriggerBindingのインプットとなります。

$ curl -X POST -H 'Content-Type: application/json' http://localhost:8081 -d '{"user":{"name": "koduki"},"greeting":{"message": "Hello"}}'
{"eventListener":"curl-for-echo","namespace":"tekton-workers","eventListenerUID":"7ae72c57-a190-45e9-90d4-d736d216e2db","eventID":"6308c090-b1f7-4d46-ac33-b95cfe3ae988"}

$ tkn pipeline list
NAME            AGE              LAST RUN                  STARTED          DURATION   STATUS
pipeline-echo   48 minutes ago   pipeline-run-curl-9cd9f   36 seconds ago   ---        Running

無事pipeline-echoが実行中になりました。pipelinerunの名前がpipeline-run-curl-{自動ID}と先ほど指定したルール通りに作成されているのが分かります。

実行が完了すると以下のように表示されます。ちゃんとcurlで指定したJSONの中身が反映されている事が分かりますね。

$ tkn pipeline logs
================================
My Echo Task
================================
Hi, koduki
Hello

まとめ

今回はTekton Triggerを使って外部からのリクエストを受け取ってビルドをする方法に関して記載しました。これが出来れば大抵の仕組みは作りこみが出来そうなので、社内システムやマイナーなパッケージとの連携もしやすくなると思います。
とはいえ 「簡単なことを簡単に」 のポリシーで良く使うGithubとの連携などは最初から用意された仕組みがあります。次回はその辺を記事に出来ればと思います。

それではHappy Hacking!

参考

https://tekton.dev/docs/triggers/
https://blog.mosuke.tech/entry/2021/04/06/tekton-trigger/

Discussion