OpenShift Pipelines (Tekton) は、OpenShift 上で CI/CD パイプラインを構築するためのツールです。開発者としては、パイプラインのエラーや失敗が発生したら困るので、リアルタイムで知りたいと思います。そこで、Tekton Catalog を参考にしてエラー通知を Slack に送信する仕組みを構築してみました。
前提条件
- OpenShift 4 クラスターが構築済みであること
- OpenShift Pipelines Operator がインストールされていること
- oc CLI がインストール済みであること
- OpenShift Pipelines でパイプラインが構築済みであること
- パイプラインの構築については以前の記事を参考
- Slack に通知を送信するワークスペース、チャンネルが用意されていること
CloudEvents について
CloudEvents は、イベントデータの標準化を目的とした CNCF のプロジェクトです。OpenShift Pipelines は CloudEvents をサポートしており、パイプラインの状態をイベントとして発行することができます。OpenShift Pipelines の CloudEvents は下記リンクのようになっています。
- https://tekton.dev/docs/pipelines/events/
- https://tekton.dev/docs/pipelines/additional-configs/#configuring-cloudevents-notifications
CloudEvents の HTTP ヘッダーの一部は下記のような形で送られてきます。PipelineRun が Failed の時、”Ce-Type” が “dev.tekton.event.pipelinerun.failed.v1” で送られてくる仕様なので、これを利用したいと思います。
"Ce-Id": "77f78ae7-ff6d-4e39-9d05-b9a0b7850527",
"Ce-Source": "/apis/tekton.dev/v1beta1/namespaces/default/taskruns/curl-run-6gplk",
"Ce-Specversion": "1.0",
"Ce-Subject": "curl-run-6gplk",
"Ce-Time": "2021-01-29T14:47:58.157819Z",
"Ce-Type": "dev.tekton.event.taskrun.unknown.v1",
構築の概要
図のような構成を構築したいと思います。まず初めに、Pipeline Run からの CloudEvents を EventListener で受け取ります。次に EventListener は Pipeline Run が失敗したことを検知して TriggerBinding と TriggerTemplate から Slack に Webhook を送信するような Task Run を動作させます。Slack の指定したチャンネルに Pipeline Run 失敗通知が届けば動作完了です。
Slack App の準備
Slackに通知を送信するためには、Slack App を作成し、適切な権限を付与する必要があります。Slack App の Webhook URL を取得し、OpenShift Pipelines で使用できるようにします。
Slack App のページから Create New App をクリックします。
From scratch をクリックします。
App Name を入力して、Slack App を追加するワークスペースを選択します。
Basic Information が表示されれば完了です。次に Incoming Webhooks をクリックします。
デフォルトでは Activate Incoming Webhooks は Off になっているので、クリックして On にします。
On になれば完了です。Add New Webhook to Workspace をクリックします。
Slack App が Slack ワークスペースに投稿する権限を与えます。投稿することのできる投稿先チャンネルを指定して許可します。
Webhook URL が作成されれば完了です。この Webhook URL は後に使うので Copy して保存しておきます。
Slack の通知を送信したいチャンネルに作成した Slack App が追加されていることを確認します。
Task の作成
Tekton Catalog を参考に Slack にメッセージを送信するための Task を作成します。この Task はシンプルなもので、Slack の Webhook URL に対して POST リクエストを送信します。
Secret を作成します。stringData の url に前項で保存した Slack API の Webhook URLを入力します。
webhook-url.yaml
kind: Secret
apiVersion: v1
metadata:
name: webhook-secret
stringData:
# 下記に前項で保存した Slack API の Webhook URLを入力
url: https://hooks.slack.com/services/
下記コマンドを実行してWebhook URLを持つシークレットを作成します。
$ oc apply -f webhook-url.yaml
secret/webhook-secret created
下記コマンドを実行してシークレットが作成されていることを確認します。
$ oc get secret
NAME TYPE DATA AGE
︙
webhook-secret Opaque 1 17s
Task の編集をします。params の中の bot-name の default を作成した Slack App の名前に置き換えます。
send-to-webhook-slack.yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: send-to-webhook-slack
labels:
app.kubernetes.io/version: "0.1"
annotations:
tekton.dev/pipelines.minVersion: "0.12.1"
tekton.dev/categories: Messaging
tekton.dev/tags: messaging
tekton.dev/displayName: "Send message to Slack Channel"
tekton.dev/platforms: "linux/amd64,linux/s390x,linux/ppc64le"
spec:
description: >-
These tasks post a simple message to a slack channel.
This task uses Incoming Webhooks of slack to send the message.
params:
- name: webhook-secret
type: string
description: secret name of the slack app webhook URL (key is url)
- name: message
type: string
description: plain text message
default: 'Webhook-test'
- name: bot-name
type: string
description: plain text message
# 下記を作成した Slack App の名前に変更
default: 'sample app'
- name: icon-emoji
type: string
description: plain text message
default: ':ロボット:'
steps:
- name: post
image: docker.io/curlimages/curl:7.70.0@sha256:031df77a11e5edded840bc761a845eab6e3c2edee22669fb8ad6d59484b6a1c4 #tag: 7.70.0
script: |
#!/usr/bin/env sh
MESSAGE=$(echo "${MESSAGE}" | sed -e 's/\"/\\\\"/g')
BOTNAME=$(echo "${BOTNAME}" | sed -e 's/\"/\\\\"/g')
JSON="{\"text\": \"${MESSAGE}\", \"username\": \"${BOTNAME}\", \"icon_emoji\": \"${EMOJI}\"}"
curl -X POST -H 'Content-Type: application/json' --data "${JSON}" "${URL}"
env:
- name: URL
valueFrom:
secretKeyRef:
name: $(params.webhook-secret)
key: url
- name: MESSAGE
value: $(params.message)
- name: BOTNAME
value: $(params.bot-name)
- name: EMOJI
value: $(params.icon-emoji)
下記コマンドを実行してタスクを作成します。
$ oc create -f send-to-webhook-slack.yaml
task.tekton.dev/send-to-webhook-slack created
下記コマンドを実行してタスクが作成されていることを確認します。
$ tkn task ls
NAME DESCRIPTION AGE
send-to-webhook-slack These tasks post a ... 43 seconds ago
TriggerBinding の作成
TriggerBinding は、イベントデータを Task に渡すための設定です。パイプラインのエラー情報を Task に渡すための TriggerBinding を作成します。
下記コマンドを実行して TriggerBinding を作成します。
$ oc apply -f trigger_binding.yaml
triggerbinding.triggers.tekton.dev/failure-trigger-binding created
trigger_binding.yaml
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerBinding
metadata:
name: failure-trigger-binding
spec:
params:
- name: message
value: "Failed pipeline $(body.pipelineRun.metadata.name)"
下記コマンドを実行して TriggerBinding が作成されていることを確認します。
$ tkn triggerbinding ls
NAME AGE
failure-trigger-binding 3 minutes ago
TriggerTemplate の作成
TriggerTemplate は、イベントに応じて実行するリソースのテンプレートです。Task を実行するための TriggerTemplate を作成します。
下記コマンドを実行して TriggerTemplare を作成します。
$ oc apply -f trigger_template.yaml
triggertemplate.triggers.tekton.dev/failure-trigger-template created
trigger_template.yaml
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata:
name: failure-trigger-template
spec:
params:
- name: message
- name: bot-name
resourcetemplates:
- apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
generateName: failure-handler
spec:
params:
- name: webhook-secret
value: webhook-secret
- name: message
value: $(tt.params.message)
- name: bot-name
value: $(tt.params.bot-name)
taskRef:
name: send-to-webhook-slack
下記コマンドを実行して TriggerTemplate が作成されていることを確認します。
$ tkn triggertemplate ls
NAME AGE
failure-trigger-template 4 minutes ago
EventListener の作成
OpenShift Pipelines で発生した CloudEvents を受け取るための EventListener を作成します。この EventListener は、interceptors の cel を利用して CloudEvents の内容を判別し、 HTTP ヘッダーの ”Ce-Type” が “dev.tekton.event.pipelinerun.failed.v1” の場合に Slack にメッセージを送信する Task を実行します。
下記コマンドを実行して EventListener を作成します。
$ oc apply -f failure-event-listener.yaml
eventlistener.triggers.tekton.dev/failure-event-listener created
failure-event-listener.yaml
apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
name: failure-event-listener
spec:
serviceAccountName: pipeline
triggers:
- name: failure-trigger
interceptors:
- ref:
name: cel
params:
- name: "filter"
value: "header.match('ce-type', 'dev.tekton.event.pipelinerun.failed.v1')"
bindings:
- ref: failure-trigger-binding
template:
ref: failure-trigger-template
resources:
kubernetesResource:
spec:
template:
spec:
serviceAccountName: pipeline
containers:
- resources:
requests:
cpu: "250m"
memory: "64Mi"
limits:
cpu: "500m"
memory: "128Mi"
下記コマンドを実行して EventListener が作成されていることを確認します。
$ tkn eventlistener ls
NAME AGE URL AVAILABLE
failure-event-listener 1 minute ago http://el-failure-event-listener.ci-pipeline.svc.cluster.local:8080 True
下記コマンドを実行して EventListener をルートとして公開します。
$ oc expose svc el-failure-event-listener
route.route.openshift.io/el-failure-event-listener exposed
下記コマンドを実行して EventListener がルートとして公開されていることを確認します。
$ oc get route
NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
el-failure-event-listener el-failure-event-listener-ci-pipeline.apps.xxx el-failure-event-listener http-listener None
下記コマンドを実行して EventListener の webhook-url を確認します。この webhook-url は次項の CloudEvents の設定で使用します。
$ echo "URL: $(oc get route el-failure-event-listener --template='http://{{.spec.host}}')"
URL: http://el-failure-event-listener-ci-pipeline.apps.xxx
CloudEvents の設定
cloud_events_config.yaml の data.sink パラメータに前項で取得した EventListener の webhook-url を入力します。
cloud_events_config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: config-events
namespace: openshift-pipelines
labels:
app.kubernetes.io/instance: default
app.kubernetes.io/part-of: openshift-pipelines
data:
formats: tektonv1
# ここに前項で取得した EventListener の webhook-url を入力
sink: http://el-failure-event-listener-ci-pipeline.apps.xxx
下記コマンドを実行して CloudEvents 設定をします。
$ oc apply -f config-events.yaml
configmap/config-events configured
下記コマンドを実行して CloudEvents 設定が反映されていることを確認します。
$ oc describe configmaps config-events -n openshift-pipelines
Name: config-events
Namespace: openshift-pipelines
︙
formats:
----
tektonv1
sink:
----
http://el-failure-event-listener-ci-pipeline.apps.xxx
動作確認
最後に、実際にパイプラインを実行して、エラーが発生した場合に Slack に通知が届くことを確認します。
以前の記事のようにアプリケーションリポジトリに空のコミットを push します。
$ git commit -m "empty-commit" --allow-empty
$ git push
このままだと成功してしまうのでメッセージは送信されません。
パイプラインを少し編集してパイプラインが失敗するようにしてみます。
パイプラインの定義が記述されている pipeline.yaml 内の fetch-repository タスクの params を下記のように編集してこのタスクが失敗するようにします。
pipeline.yaml の詳細は以前の記事を参照してください。
tasks:
- name: fetch-repository
taskRef:
name: git-clone
kind: ClusterTask
workspaces:
- name: output
workspace: shared-workspace
- name: ssh-directory
workspace: ssh-creds
params:
- name: url
# URL を適当な文字列に変更
value: "test"
- name: subdirectory
下記コマンドを実行して編集を反映させます。
$ oc apply -f pipeline.yaml
空のコミットをもう一度pushします。
$ git commit -m "empty-commit" --allow-empty
$ git push
少し経った後、OpenShift コンソール上でパイプラインが失敗したことを確認します。
webhookを許可したSlackのチャンネルを確認してメッセージが送信されていれば完了です。
まとめ
OpenShift Pipelines にエラー通知をSlackに送信する仕組みを Tekton Catalog を参考にして構築してみました。OpenShift Pipelines では OpenShift コンソール上の GUI で通知機能を設定するような実装はありませんが、CloudEvents と EventListener を組み合わせて実装することが出来ました。次回は CD 部分である OpenShift GitOps のエラー通知機能を実装してみたいと思います。
参考文献
https://github.com/tektoncd/catalog/blob/main/task/send-to-webhook-slack/0.1/README.md
https://tekton.dev/docs/pipelines/events/
https://tekton.dev/docs/pipelines/additional-configs/#configuring-cloudevents-notifications