GitLabからWebhookでTekton Pipelineを実行する

前回構築したTektonHelmで構築したGitLab を連携させてみます。
全てオンプレで構築したセルフマネージドな環境です。

やりたいこと

GitLabのpushイベントをトリガーにTektonのEventListenerにwebhookを送信します。
そのwebhookをトリガーにGitLabからDockerfileをcloneしてbuild、その後GitLabと連携しているprivate container registryにビルドしたイメージをpushするパイプラインを実行します。

公式ドキュメントのHow-to Guides に git cloneやコンテナビルドやプッシュの手順が紹介されているのでこれを参考にします。

GitLab側の準備

今回テスト用にtekton-testというプロジェクトを作成しました。

tekton_gitlab_01.png

リポジトリ直下に以下のDockerfileだけプッシュしておきます。
内容はタイムゾーンを変えているだけです。

FROM nginx:latest

ENV TZ Asia/Tokyo
RUN echo "${TZ}" > /etc/timezone \
   && dpkg-reconfigure -f noninteractive tzdata

また、前回構築時は有効化していませんでしたが、今回GitLabでContainer Registryを使えるようにしました。

tekton_gitlab_02.png

認証情報の作成

Tektonのタスク内でgit cloneやdocker pullするために認証情報が必要となります。
今回はひとまずGitLabのrootユーザーで認証しようと思います。

git clone の認証方法はいくつかあるようですが、
ガイドと同じssh-directoryで認証してみます。
鍵を作成します。

ubuntu@k8s1:~$ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/ubuntu/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/ubuntu/.ssh/id_rsa
Your public key has been saved in /home/ubuntu/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:JjmEBeWbkzewCYvZQOXhJ4ZfaWrBILZfVIxOgUwCP40 ubuntu@k8s1
The key's randomart image is:
+---[RSA 3072]----+
|+o=o==B.         |
|.+o@.B o         |
| .E &.O          |
|  .O.% O         |
|  o.* @ S        |
|   .   * .       |
|                 |
|                 |
|                 |
+----[SHA256]-----+

公開鍵をGitLabのrootユーザーに登録します。

ubuntu@k8s1:~$ cat .ssh/id_rsa.pub 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCVpofTvETduq9g/L2n9zmn9mfHyZXtd4IbUaY2Gq8JFsDi+zPmgaJ1u75XHzyD5HBktG7xFWvmFmLt77Wq7pWhNAuZz9+hG6d8PyxfrhfP7eTe7i17VswwgMr+RQ96CKzlyU4bMZaFNNKafA0tpPML1pe4+LTxX669vZDpBs204l4IQNHK5uREyaQHp06JODnVgKcxCb2Z2QbI75aXZ3/G/mFypFDGOv7wv/mF+G83blEZiZ/LRJJq4B2tcI7BKvgWyo0r5S2NXsU34FSkxEymUPwkH6+YMZ7hiTE/UY9mKT2TpXYL8JolvpdkDTlgDc4/+VOTTFNxQzDPAYA5Hx+oFtemWKOR7nRbRUw2Rbbd611ll4P+zKlYxXDXBqsNTuYL9PT01gz9u9jGDARz3wUxVtemr7SQDwAFGCfMFz8UxsqALUup/Ihu0/YSTQFXIjVeIV949dY2Otm2YCSHM5x9mnfv3UtLs6XGAH7WeRQT7LIQStTOXQ71UxqNUIAD/+8= ubuntu@k8s1
tekton_gitlab_03.png

configファイルを.sshディレクトリに作成します。

ubuntu@k8s1:~$ cat .ssh/config 
Host gitlab.example.tsuchinokometal.com
  HostName gitlab.example.tsuchinokometal.com
  IdentityFile ~/.ssh/id_rsa
  User root

接続テストをします。

ubuntu@k8s1:~$ ssh -T git@gitlab.example.tsuchinokometal.com
The authenticity of host 'gitlab.example.tsuchinokometal.com (192.168.0.225)' can't be established.
ECDSA key fingerprint is SHA256:WVrpzH92P+iSYjYrUX5RayZymxlf+c/FWlgmrm6e+ik.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'gitlab.example.tsuchinokometal.com,192.168.0.225' (ECDSA) to the list of known hosts.
Welcome to GitLab, @root!
ubuntu@k8s1:~$ ll .ssh/
total 28
drwx------  2 ubuntu ubuntu 4096 Sep 24 16:00 ./
drwxr-xr-x 13 ubuntu ubuntu 4096 Sep 24 10:56 ../
-rw-------  1 ubuntu ubuntu  574 Sep 24 16:00 authorized_keys
-rw-rw-r--  1 ubuntu ubuntu  127 Sep 23 17:45 config
-rw-------  1 ubuntu ubuntu 2590 Sep 24 11:00 id_rsa
-rw-r--r--  1 ubuntu ubuntu  565 Sep 24 11:00 id_rsa.pub
-rw-r--r--  1 ubuntu ubuntu  444 Sep 24 11:44 known_hosts

ファイルが揃ったのでsecretを作成します。
今回tekton-testというNamespaceでパイプラインを作成します。

ubuntu@k8s1:~$ kubectl create ns tekton-test
namespace/tekton-test created
ubuntu@k8s1:~$ kubectl create secret generic --save-config git-credentials --from-file=.ssh/id_rsa --from-file=.ssh/known_hosts --from-file=.ssh/config -n tekton-test
secret/git-credentials created

次にContainer Registry用の認証情報を作成します。
こちらもガイドと同じくconfig.jsonを利用します。

ubuntu@k8s1:~$ docker login registry.example.tsuchinokometal.com
Username: root
Password: 
WARNING! Your password will be stored unencrypted in /home/ubuntu/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
ubuntu@k8s1:~$ kubectl create secret generic --save-config docker-credentials --from-file=.docker/config.json -n tekton-test
secret/docker-credentials created
ubuntu@k8s1:~$ kubectl get secret -n tekton-test
NAME                 TYPE     DATA   AGE
docker-credentials   Opaque   1      40s
git-credentials      Opaque   3      61s

Tekton pipelineの準備

まずTekton hubから git-clonekaniko の Taskをインストールします。
kanikoはコンテナの中でコンテナイメージをビルドすることができるツールらしいです。

$ kubectl apply -f https://raw.githubusercontent.com/tektoncd/catalog/main/task/git-clone/0.6/git-clone.yaml -n tekton-test
task.tekton.dev/git-clone created
$ kubectl apply -f https://raw.githubusercontent.com/tektoncd/catalog/main/task/kaniko/0.6/kaniko.yaml -n tekton-test
task.tekton.dev/kaniko created
$ tkn task list -n tekton-test
NAME        DESCRIPTION              AGE
git-clone   These Tasks are Git...   32 seconds ago
kaniko      This Task builds a ...   11 seconds ago

次にこれらのタスクを実行するパイプラインを作成します。
workspaceに先ほど作成したgitとdockerの認証情報をマウントしています。
pipeline.yaml

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: clone-build-push
  namespace: tekton-test
spec:
  description: |
    This pipeline clones a git repo, builds a Docker image with Kaniko and
    pushes it to a registry    
  params:
  - name: repo-url
    type: string
  - name: image-reference
    type: string
  workspaces:
  - name: shared-data
  - name: git-credentials
  - name: docker-credentials
  tasks:
  - name: fetch-source
    taskRef:
      name: git-clone
    workspaces:
    - name: output
      workspace: shared-data
    - name: ssh-directory
      workspace: git-credentials
    params:
    - name: url
      value: $(params.repo-url)
  - name: build-push
    runAfter: ["fetch-source"]
    taskRef:
      name: kaniko
    workspaces:
    - name: source
      workspace: shared-data
    - name: dockerconfig
      workspace: docker-credentials
    params:
    - name: IMAGE
      value: $(params.image-reference)

テストのために手動実行用のpipelinerunを作ります。
workspaceのボリュームを動的プロビジョニングしたいため、longhornをインストールしました。
gitリポジトリやdocker registryのURLはここで指定しています。
pod内で名前解決できるようにしてください。
pipelinerun.yaml

apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  generateName: clone-build-push-run-
  namespace: tekton-test
spec:
  pipelineRef:
    name: clone-build-push
  podTemplate:
    securityContext:
      fsGroup: 65532
  workspaces:
  - name: shared-data
    volumeClaimTemplate:
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
        storageClassName: longhorn
  - name: git-credentials
    secret:
      secretName: git-credentials
  - name: docker-credentials
    secret:
      secretName: docker-credentials
  params:
  - name: repo-url
    value: git@gitlab.example.tsuchinokometal.com:root/tekton-test.git 
  - name: image-reference
    value: registry.example.tsuchinokometal.com/root/tekton-test/my_nginx:latest

リソースを作成します。
pipelinerunを作成すると指定したパラメータでパイプラインが実行されます。

$ kubectl apply -f pipeline.yaml 
pipeline.tekton.dev/clone-build-push created
$ kubectl create -f pipelinerun.yaml 
pipelinerun.tekton.dev/clone-build-push-run-4bsm7 created

無事パイプラインが走り切りました。

tekton_gitlab_04.png

Container Registryにイメージもpushされています。

tekton_gitlab_05.png

良さそうですね!

リソースを確認すると以下が作成されていますね。
これらはpipelinerunを削除すると合わせて削除されます。

$ kubectl get pod,pv,pvc -n tekton-test
NAME                                              READY   STATUS      RESTARTS   AGE
pod/clone-build-push-run-4bsm7-build-push-pod     0/2     Completed   0          10m
pod/clone-build-push-run-4bsm7-fetch-source-pod   0/1     Completed   0          11m

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                        STORAGECLASS   REASON   AGE
persistentvolume/pvc-ae42b42e-18d8-41e2-878e-7229c427061f   1Gi        RWO            Delete           Bound    tekton-test/pvc-379cd0b737   longhorn                11m

NAME                                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/pvc-379cd0b737   Bound    pvc-ae42b42e-18d8-41e2-878e-7229c427061f   1Gi        RWO            longhorn       11m

Tekton Triggerの準備

Webhookからパイプラインを実行するためにTriggerリソースを作成します。
まずEventListenerからTektonリソースを作成するためのサービスアカウントを準備します。

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-trigger
  namespace: tekton-test
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: role-trigger
  namespace: tekton-test
rules:
- apiGroups:
  - triggers.tekton.dev
  resources:
  - eventlisteners
  - triggers
  - triggerbindings
  - triggertemplates
  - interceptors
  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
  namespace: tekton-test
subjects:
  - kind: ServiceAccount
    name: sa-trigger
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: role-trigger
---
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-test
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: clusterrole-trigger
$ kubectl apply -f sa-tekton-test.yaml 
serviceaccount/sa-trigger created
role.rbac.authorization.k8s.io/role-trigger created
rolebinding.rbac.authorization.k8s.io/triggers-role-binding created
clusterrole.rbac.authorization.k8s.io/clusterrole-trigger created
clusterrolebinding.rbac.authorization.k8s.io/clusterbinding-trigger created

次にTrigger関連のリソースを作成します。
resourcetemplatesで先ほど手動で実行したPipelineRunを定義しています。
これrefできないんですかね。
ちなみにTriggerBindingでパラメータ指定していますが、使ってません。
とりあえず書いただけです。

apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerTemplate
metadata:
  name: template-tekton-test
  namespace: tekton-test
spec:
  params:
    - name: gitrevision
      description: The git revision
      default: master
  resourcetemplates:
    - apiVersion: tekton.dev/v1beta1
      kind: PipelineRun
      metadata:
        generateName: clone-build-push-run-
        namespace: tekton-test
      spec:
        pipelineRef:
          name: clone-build-push
        workspaces:
        - name: shared-data
          volumeClaimTemplate:
            spec:
              accessModes:
              - ReadWriteOnce
              resources:
                requests:
                  storage: 1Gi
              storageClassName: longhorn
        - name: git-credentials
          secret:
            secretName: git-credentials
        - name: docker-credentials
          secret:
            secretName: docker-credentials
        params: 
          - name: repo-url
            value: git@gitlab.example.tsuchinokometal.com:root/tekton-test.git
          - name: image-reference
            value: registry.example.tsuchinokometal.com/root/tekton-test/my_nginx:latest
---
apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerBinding
metadata:
  name: binding-tekton-test
  namespace: tekton-test
spec:
  params:
    - name: gitrevision
      value: $(body.head_commit.id)
---
apiVersion: triggers.tekton.dev/v1alpha1
kind: EventListener
metadata:
  name: tekton-test
  namespace: tekton-test
spec:
  serviceAccountName: sa-trigger
  triggers:
    - name: trigger-tekton-test
      bindings:
        - ref: binding-tekton-test
      template:
        ref: template-tekton-test

gitlab interceptorを使おうとしましたが、なぜか自己証明書エラーが発生してうまくいきませんでした。

リソースを作成します。

$ kubectl apply -f trigger-tekton-test.yaml 
triggertemplate.triggers.tekton.dev/template-tekton-test created
triggerbinding.triggers.tekton.dev/binding-tekton-test created
eventlistener.triggers.tekton.dev/tekton-test created

EventListenerを作成するとclusterIPのServiceが自動的に作成されていると思います。

$ kubectl get svc -n tekton-test
NAME             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)             AGE
el-tekton-test   ClusterIP   10.43.194.40   <none>        8080/TCP,9000/TCP   7m11s

EventListenerが正常に稼働できているかどうかはtknコマンドでも確認できます。

$ tkn el list -n tekton-test
NAME          AGE             URL                                                        AVAILABLE
tekton-test   7 minutes ago   http://el-tekton-test.tekton-test.svc.cluster.local:8080   True

このままでは外部からアクセスできないので、ingressで公開します。

$ cat ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tekton-test
  namespace: tekton-test
spec:
  rules:
  - host: tekton-test.domain.tld
    http:
      paths:
      - pathType: ImplementationSpecific
        backend:
          service:
            name: el-tekton-test
            port:
              number: 8080
$ kubectl apply -f ingress.yaml 
ingress.networking.k8s.io/tekton-test created

以下のようになりました。
GitLab側で名前解決できるようにする必要があります。

$ kubectl get ingress -n tekton-test
NAME          CLASS   HOSTS                    ADDRESS                     PORTS   AGE
tekton-test   nginx   tekton-test.domain.tld   192.168.0.52,192.168.0.53   80      22s

webhookからpipelineを実行する

では、GitLabからwebhookを飛ばしてみます。
Ingressで指定したhostを指定します。
gitlab interceptorを使えばここで指定したシークレットトークンで検証することができます。

tekton_gitlab_06.png

作成後、テスト実行します。

tekton_gitlab_07.png

ひとまず以下のメッセージが表示されればOKです。

Hook executed successfully: HTTP 202

もし以下のメッセージが表示されたらAdmin areaのネットワーク設定の
Outbound requestsでローカルネットワークを許可してください。

Url is blocked: Requests to the local network are not allowed

Dashboardで実行できているか確認します。
良さそうですね!

tekton_gitlab_08.png

Kubernetesカスタムリソースでパイプラインをyaml管理できるのはいいですね。