📝

tektonのGetStartedをやりました

2025/02/11に公開

tektonとは

tektonはk8s上でCICD環境を構築することができるミドルウェア。
以下のような概念がある。

  • task: 最小の実行単位で
  • pipeline: taskを束ねる単位
  • trigger: Webhookエンドポイントで待ち受けて、それを契機にpipelineを実行する

なお、taskやpipelineを実行する場合は、taskrunやpipelinerunのリソースを作ることで実行される。そのrun系リソースは実行ステータスやログなどを管理している。
詰まることろ、毎回pipeline本体を作成するとかそういうことはしないということ。

また、GithubActionsと比べると、以下のようになると思います。

  • step: step
  • task : job
  • pipeline: workflow

tekton実行環境を構築

操作コマンドをインストール

yay -S tkn

ローカルクラスタを起動

kind create cluster

tekton pipelineに必要なリソース一式をデプロイする

kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
kubectl get pods --namespace tekton-pipelines --watch
❯ kubectl get pods --namespace tekton-pipelines
NAME                                           READY   STATUS    RESTARTS   AGE
tekton-events-controller-6d5d95b4-wplsd        1/1     Running   0          6m12s
tekton-pipelines-controller-585bbf56fb-82k9p   1/1     Running   0          6m12s
tekton-pipelines-webhook-5886749497-sdppr      1/1     Running   0          6m12s

Taskを実行

echoするだけのタスクを作成

cat <<EOF > hello-world.yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: hello
spec:
  steps:
    - name: echo
      image: alpine
      script: |
        #!/bin/sh
        echo "Hello World"
EOF
kubectl apply --filename hello-world.yaml
$ kubectl get task hello
NAME    AGE
hello   5m8s
cat <<EOF > hello-world-run.yaml
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
  name: hello-task-run
spec:
  taskRef:
    name: hello
EOF
kubectl apply --filename hello-world-run.yaml

taskrunを見ると、Successedになっている。

$ kubectl get taskrun
NAME             SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
hello-task-run   True        Succeeded   2m52s       75s

logを見ると、Hello Worldと出ている。

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

Pipelineを実行

2つめのタスクを追加で作成

cat <<EOF > goodbye-world.yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: goodbye
spec:
  params:
  - name: username
    type: string
  steps:
    - name: goodbye
      image: ubuntu
      script: |
        #!/bin/bash
        echo "Goodbye \$(params.username)!"
EOF
kubectl apply --filename goodbye-world.yaml
$ kubectl get task
NAME      AGE
goodbye   33s
hello     15m
cat <<EOF > hello-goodbye-pipeline.yaml
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: hello-goodbye
spec:
  params:
  - name: username
    type: string
  tasks:
    - name: hello
      taskRef:
        name: hello
    - name: goodbye
      runAfter:
        - hello
      taskRef:
        name: goodbye
      params:
      - name: username
        value: \$(params.username)
EOF
cat <<EOF > hello-goodbye-pipeline-run.yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: hello-goodbye-run
spec:
  pipelineRef:
    name: hello-goodbye
  params:
  - name: username
    value: "Tekton"
EOF

pipelineを実行する

kubectl apply --filename hello-goodbye-pipeline-run.yaml
$ kubectl get pipelineruns.tekton.dev
NAME                SUCCEEDED   REASON    STARTTIME   COMPLETIONTIME
hello-goodbye-run   Unknown     Running   3s
tkn pipelinerun logs hello-goodbye-run -f -n default

ログを見ると実行ログが確認できる

$ tkn pipelinerun logs hello-goodbye-run -f -n default
[hello : echo] Hello World

[goodbye : goodbye] Goodbye Tekton!

Triggerの実行

このようなイメージ。

  1. EventListenerがEventを待ち受ける

  2. TriggerTemplateが発生したときに、PipelineRunを構成する

  3. TriggerBindingは、TriggerTemaplateによって作成されたPipelineRunにデータを渡す

TektonTriggersのインストール

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
kubectl get pods --namespace tekton-pipelines --watch
$ kubectl get pods --namespace tekton-pipelines
NAME                                                READY   STATUS    RESTARTS   AGE
tekton-events-controller-6d5d95b4-wplsd             1/1     Running   0          50m
tekton-pipelines-controller-585bbf56fb-82k9p        1/1     Running   0          50m
tekton-pipelines-webhook-5886749497-sdppr           1/1     Running   0          50m
tekton-triggers-controller-7d8cf85c9f-g66k5         1/1     Running   0          9m21s
tekton-triggers-core-interceptors-ff9879694-gzlhq   1/1     Running   0          9m20s
tekton-triggers-webhook-cdc9d576f-9m7hp             1/1     Running   0          9m21s

TriggerTemplateの作成

cat <<EOF > trigger-template.yaml
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata:
  name: hello-template
spec:
  params:
  - name: username
    default: "Kubernetes"
  resourcetemplates:
  - apiVersion: tekton.dev/v1beta1
    kind: PipelineRun
    metadata:
      generateName: hello-goodbye-run-
    spec:
      pipelineRef:
        name: hello-goodbye
      params:
      - name: username
        value: \$(tt.params.username)
EOF
kubectl apply -f trigger-template.yaml
$ kubectl get triggertemplates.triggers.tekton.dev
NAME             AGE
hello-template   11s
cat <<EOF > trigger-binding.yaml
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerBinding
metadata:
  name: hello-binding
spec: 
  params:
  - name: username
    value: \$(body.username)
EOF
kubectl apply -f trigger-binding.yaml
$ kubectl get triggerbindings.triggers.tekton.dev
NAME            AGE
hello-binding   12s

EventListenerの作成

cat <<EOF > event-listener.yaml
apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
  name: hello-listener
spec:
  serviceAccountName: tekton-robot
  triggers:
    - name: hello-trigger 
      bindings:
      - ref: hello-binding
      template:
        ref: hello-template
EOF
cat <<EOF > rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tekton-robot
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: triggers-example-eventlistener-binding
subjects:
- kind: ServiceAccount
  name: tekton-robot
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: tekton-triggers-eventlistener-roles
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: triggers-example-eventlistener-clusterbinding
subjects:
- kind: ServiceAccount
  name: tekton-robot
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: tekton-triggers-eventlistener-clusterroles
EOF
kubectl apply -f rbac.yaml

Triggerの起動

EventListnerの作成

kubectl apply -f event-listener.yaml

ポートフォワーディング

kubectl port-forward service/el-hello-listener 8080
$ kubectl port-forward service/el-hello-listener 8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

Triggerの実行

curlを投げる

curl -v \
   -H 'content-Type: application/json' \
   -d '{"username": "Tekton"}' \
   http://localhost:8080

出力結果

$ curl -v \
   -H 'content-Type: application/json' \
   -d '{"username": "Tekton"}' \
   http://localhost:8080
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
* using HTTP/1.x
> POST / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.11.0
> Accept: */*
> content-Type: application/json
> Content-Length: 22
>
* upload completely sent off: 22 bytes
< HTTP/1.1 202 Accepted
< Content-Type: application/json
< Date: Mon, 10 Feb 2025 00:52:30 GMT
< Content-Length: 164
<
{"eventListener":"hello-listener","namespace":"default","eventListenerUID":"234e23dd-b8ae-4edc-bb7c-8b071a55a83b","eventID":"88e68f48-3f6d-4be2-98d9-3569f6059e11"}
* Connection #0 to host localhost left intact

hello-goodbye-run-w5mhsが新しく作成され実行された

$ kubectl get pipelineruns
NAME                      SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
hello-goodbye-run         True        Succeeded   21m         20m
hello-goodbye-run-w5mhs   True        Succeeded   95s         82s

ログ出力もされている

$ tkn pipelinerun logs hello-goodbye-run-w5mhs -f
[hello : echo] Hello World

[goodbye : goodbye] Goodbye Tekton!

Supply Chain

private registoryが必要なのようで、チュートリアルどおり、minikubeを利用する。

minikube start --insecure-registry "10.0.0.0/24"
minikube addons enable registry

tekton pipelineをインストール

kubectl apply --filename \
https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
❯ kubectl get po -n tekton-pipelines
NAME                                           READY   STATUS    RESTARTS   AGE
tekton-events-controller-74c57cd84d-4sc6p      1/1     Running   0          18m
tekton-pipelines-controller-595966dff6-s2s5l   1/1     Running   0          18m
tekton-pipelines-webhook-76796bd7bf-ksjbf      1/1     Running   0          18m

tekton chainをインストール

kubectl apply --filename \
https://storage.googleapis.com/tekton-releases/chains/latest/release.yaml
❯ kubectl get po -n tekton-chains
NAME                                       READY   STATUS    RESTARTS   AGE
tekton-chains-controller-8454f7d7d-s5gvq   1/1     Running   0          14m

metadataの構成

kubectl patch configmap chains-config -n tekton-chains \
-p='{"data":{"artifacts.oci.storage": "", "artifacts.taskrun.format":"in-toto", "artifacts.taskrun.storage": "tekton"}}'

pipeline.yamlを作成

apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: build-push
spec:
  params:
  - name: image-reference
    type: string
  results:
  - name: image-ARTIFACT_OUTPUTS
    description: Built artifact.
    value:
      uri: $(tasks.kaniko-build.results.IMAGE_URL)
      digest: sha1:$(tasks.kaniko-build.results.IMAGE_DIGEST)
  workspaces:
  - name: shared-data
  tasks: 
  - name: dockerfile
    taskRef:
      name: create-dockerfile
    workspaces:
    - name: source
      workspace: shared-data
  - name: kaniko-build
    runAfter: ["dockerfile"]
    taskRef:
      name: kaniko
    workspaces:
    - name: source
      workspace: shared-data
    params:
    - name: IMAGE
      value: $(params.image-reference)
---
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: create-dockerfile
spec:
  workspaces:
  - name: source
  steps:
  - name: add-dockerfile
    workingDir: $(workspaces.source.path)
    image: bash
    script: |
      cat <<EOF > Dockerfile
      FROM alpine:3.16
      RUN echo "hello world" > hello.log
      EOF      
---
apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: kaniko
  labels:
    app.kubernetes.io/version: "0.6"
  annotations:
    tekton.dev/pipelines.minVersion: "0.17.0"
    tekton.dev/categories: Image Build
    tekton.dev/tags: image-build
    tekton.dev/displayName: "Build and upload container image using Kaniko"
    tekton.dev/platforms: "linux/amd64,linux/arm64,linux/ppc64le"
spec:
  description: >-
    This Task builds a simple Dockerfile with kaniko and pushes to a registry.
    This Task stores the image name and digest as results, allowing Tekton Chains to pick up
    that an image was built & sign it.    
  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
      type: array
      default: []
    - name: BUILDER_IMAGE
      description: The image on which builds will run (default is v1.5.1)
      default: gcr.io/kaniko-project/executor:v1.5.1@sha256:c6166717f7fe0b7da44908c986137ecfeab21f31ec3992f6e128fff8a94be8a5
  workspaces:
    - name: source
      description: Holds the context and Dockerfile
    - name: dockerconfig
      description: Includes a docker `config.json`
      optional: true
      mountPath: /kaniko/.docker
  results:
    - name: IMAGE_DIGEST
      description: Digest of the image just built.
    - name: IMAGE_URL
      description: URL of the image just built.
  steps:
    - name: build-and-push
      workingDir: $(workspaces.source.path)
      image: $(params.BUILDER_IMAGE)
      args:
        - $(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)
        - --digest-file=$(results.IMAGE_DIGEST.path)
      # 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-url
      image: docker.io/library/bash:5.1.4@sha256:c523c636b722339f41b6a431b44588ab2f762c5de5ec3bd7964420ff982fb1d9
      script: |
        set -e
        image="$(params.IMAGE)"
        echo -n "${image}" | tee "$(results.IMAGE_URL.path)"
❯ kubectl get service --namespace kube-system
NAME       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.96.0.10      <none>        53/UDP,53/TCP,9153/TCP   20m
registry   ClusterIP   10.110.186.77   <none>        80/TCP,443/TCP           15m

pipelinerun.yamlを作成
spec.params.valueのIPアドレスをregistryのCLUSTER-IPに置き換える

apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  generateName: build-push-run-
spec: 
  pipelineRef:
    name: build-push
  params:
  - name: image-reference
    value: 10.110.186.77/tekton-test
  workspaces:
  - name: shared-data
    volumeClaimTemplate:
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi

パイプラインをデプロイ

kubectl apply -f pipeline.yaml

パイプラインを実行

kubectl create -f pipelinerun.yaml
❯ tkn pr logs build-push-run-vz9xj

task kaniko-build has failed: "step-build-and-push" exited with code 1
[kaniko-build : build-and-push] error building image: parsing dockerfile: Dockerfile parse error line 3: unknown instruction: EOF

failed to get logs for task kaniko-build : container step-build-and-push has failed  : [{"key":"StartedAt","value":"2025-02-11T06:01:52.884Z","type":3}]
Tasks Completed: 2 (Failed: 1, Cancelled 0), Skipped: 0

scriptセクションが変に解釈されていたみたいなので、editで直した。

❯ k get task create-dockerfile -o yaml
apiVersion: tekton.dev/v1
kind: Task
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"tekton.dev/v1","kind":"Task","metadata":{"annotations":{},"name":"create-dockerfile","namespace":"default"},"spec":{"steps":[{"image":"bash","name":"add-dockerfile","script":"cat \u003c\u003cEOF \u003e Dockerfile\n  FROM alpine:3.16\n  RUN echo \"hello world\" \u003e hello.log\nEOF      \n","workingDir":"$(workspaces.source.path)"}],"workspaces":[{"name":"source"}]}}
  creationTimestamp: "2025-02-11T05:50:49Z"
  generation: 5
  name: create-dockerfile
  namespace: default
  resourceVersion: "8645"
  uid: 13c70d78-8019-4500-bf71-9ae84f1a9027
spec:
  steps:
  - computeResources: {}
    image: bash
    name: add-dockerfile
    script: |
      cat <<EOF > Dockerfile
      FROM alpine:3.16
      RUN echo "hello world" > hello.log
      EOF
    workingDir: $(workspaces.source.path)
  workspaces:
  - name: source

修正後、再度pipelinerunを実行したところ、Trueで完了した。

❯ k get pipelineruns.tekton.dev
NAME                   SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
build-push-run-cm9bx   True        Succeeded   87s         62s

ログも正しく出力されている

❯ tkn pr logs build-push-run-cm9bx

[kaniko-build : build-and-push] INFO[0000] Retrieving image manifest alpine:3.16
[kaniko-build : build-and-push] INFO[0000] Retrieving image alpine:3.16 from registry index.docker.io
[kaniko-build : build-and-push] INFO[0003] Built cross stage deps: map[]
[kaniko-build : build-and-push] INFO[0003] Retrieving image manifest alpine:3.16
[kaniko-build : build-and-push] INFO[0003] Returning cached image manifest
[kaniko-build : build-and-push] INFO[0003] Executing 0 build triggers
[kaniko-build : build-and-push] INFO[0003] Unpacking rootfs as cmd RUN echo "hello world" > hello.log requires it.
[kaniko-build : build-and-push] INFO[0006] RUN echo "hello world" > hello.log
[kaniko-build : build-and-push] INFO[0006] Taking snapshot of full filesystem...
[kaniko-build : build-and-push] INFO[0006] cmd: /bin/sh
[kaniko-build : build-and-push] INFO[0006] args: [-c echo "hello world" > hello.log]
[kaniko-build : build-and-push] INFO[0006] Running: [/bin/sh -c echo "hello world" > hello.log]
[kaniko-build : build-and-push] INFO[0006] Taking snapshot of full filesystem...
[kaniko-build : build-and-push] INFO[0006] Pushing image to 10.110.186.77/tekton-test
[kaniko-build : build-and-push] INFO[0009] Pushed image to 1 destinations

[kaniko-build : write-url] 10.110.186.77/tekton-test

参考

https://tekton.dev/docs/installation/pipelines/

Discussion