イベント駆動ワークフローエンジン stackstorm を試す
概要
stackstorm (以下 st2) は python 製の OSS のイベント駆動型ワークフローツールです。
Linux Foundation project の一部であり、trusted by the enterprise には Cisco, NASA, Netflix などの有名企業が名を連ねています。Github star 数は現時点で 5.8 k となっており利用者も多いことがわかります。
イベント駆動型というのは、例えば github に PR が建てられた等や監視対象のサーバがダウンした等のイベントを起点として処理を開始するような仕組みのことを指します。同様の web サービスでは IFTTT が有名であり、st2 もよく比較されています。
ワークフローという面に注目すると今までの記事で紹介した concourse や tekton などと同じカテゴリに位置づけられます。ただ concourse や tekton がクラウドネイティブな CI/CD を実現するツールであるのに対し、stackstorm は複数サービスの連携や運用、対応の自動化といった面に焦点を当てています (もちろん使いようによっては CI/CD に組み込むことも可能)。
インストール
インストール方法は ドキュメント にまとまっています。st2 自体はクラウドネイティブなプロジェクトではないためマシンに直接インストールする方法が多いですが、helm を使って k8s クラスタにデプロイする 方法があるのでこちらで k8s クラスタへインストールします。
まずは helm repository を登録して values.yml を出力します。
helm repo add stackstorm https://helm.stackstorm.com/
helm show values st2/stackstorm-ha > values.yml
k8s でのインストールでは、後述する pack
を共有するための方法として Custom st2 packs に 2 種類の方法が記載されています。今回は k8s persistentVolumeClaim を使用して runner 間で pack を共有する方法を使うため、values.yml の中身を以下のように書き換えます。
- enabled: false
+ enabled: true
- packs: {}
+ packs:
+ persistentVolumeClaim:
+ claimName: st2-pack
< virtualenvs: {}
+ virtualenvs:
+ persistentVolumeClaim:
+ claimName: st2-virtualenv
- configs: {}
+ configs:
+ persistentVolumeClaim:
+ claimName: st2-config
ここで使用する persistentVolumeClaim リソースを事前に作成しておきます。
storage size はユースケースにより異なりますが、ここでは適当にそれぞれ 5 GB を割り当てます。
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: st2-pack
namespace: st2
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: st2-config
namespace: st2
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: st2-virtualenv
namespace: st2
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 5Gi
また、これに対応する persistentVolume も作成する必要があります。
手動で作成しても良いですが、今回は事前に k8s クラスタに openEBS をデプロイしておき、pvc が pod に割り当てられた際に動的に pv を作成するようにします。
準備ができたらデプロイします。
helm install st2 -n st2 st2/stackstorm-ha -f values.yml
デプロイが完了すると st2 コンポーネントに対応するかなりの数の pod が作成されます。
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
st2-job-st2-apikey-load-mtxkn 0/1 Completed 0 42h
st2-job-st2-key-load-q7p6x 0/1 Completed 0 42h
st2-job-st2-register-content-nqg4v 0/1 Completed 0 42h
st2-mongodb-0 1/1 Running 0 42h
st2-mongodb-1 1/1 Running 0 42h
st2-mongodb-2 1/1 Running 0 42h
st2-rabbitmq-0 1/1 Running 0 42h
st2-rabbitmq-1 1/1 Running 0 42h
st2-rabbitmq-2 1/1 Running 0 42h
st2-redis-node-0 2/2 Running 0 42h
st2-redis-node-1 2/2 Running 0 42h
st2-redis-node-2 2/2 Running 0 42h
st2-st2actionrunner-d4988dffc-466dq 1/1 Running 0 179m
st2-st2actionrunner-d4988dffc-9dj9m 1/1 Running 0 179m
st2-st2actionrunner-d4988dffc-dnxwj 1/1 Running 0 179m
st2-st2actionrunner-d4988dffc-kdgw9 1/1 Running 0 179m
st2-st2actionrunner-d4988dffc-t6j9q 1/1 Running 0 179m
st2-st2api-6c5874d95c-hrw5n 1/1 Running 0 42h
st2-st2api-6c5874d95c-vf25v 1/1 Running 0 42h
st2-st2auth-774dfddc4c-cc2ln 1/1 Running 0 42h
st2-st2auth-774dfddc4c-v57xc 1/1 Running 0 42h
st2-st2client-5fb9c79dcd-snlfr 1/1 Running 0 42h
st2-st2garbagecollector-6c64ff5bc9-x8gvd 1/1 Running 0 42h
st2-st2notifier-55d5df6976-dsdq7 1/1 Running 0 42h
st2-st2notifier-55d5df6976-tflts 1/1 Running 0 42h
st2-st2rulesengine-54dcc8dd9f-4td89 1/1 Running 0 42h
st2-st2rulesengine-54dcc8dd9f-xfw7h 1/1 Running 0 42h
st2-st2scheduler-597c9b488f-j7mxc 1/1 Running 4 (42h ago) 42h
st2-st2scheduler-597c9b488f-ll7xn 1/1 Running 4 (42h ago) 42h
st2-st2sensorcontainer-6949b97fb6-rmxzb 1/1 Running 0 42h
st2-st2stream-7b9878447-42ztf 1/1 Running 0 42h
st2-st2stream-7b9878447-vqqb5 1/1 Running 0 42h
st2-st2timersengine-844999d648-xllrb 1/1 Running 0 42h
st2-st2web-7b695b6d59-ftwz4 1/1 Running 0 42h
st2-st2web-7b695b6d59-vprsb 1/1 Running 0 42h
st2-st2workflowengine-86bbb589f4-7xgtl 1/1 Running 0 42h
st2-st2workflowengine-86bbb589f4-fmgfn 1/1 Running 0 42h
svc
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
st2-mongodb-headless ClusterIP None <none> 27017/TCP 42h
st2-rabbitmq ClusterIP 10.105.250.253 <none> 5672/TCP,4369/TCP,25672/TCP,15672/TCP 42h
st2-rabbitmq-headless ClusterIP None <none> 4369/TCP,5672/TCP,25672/TCP,15672/TCP 42h
st2-redis ClusterIP 10.108.249.198 <none> 6379/TCP,26379/TCP 42h
st2-redis-headless ClusterIP None <none> 6379/TCP,26379/TCP 42h
st2-st2api ClusterIP 10.103.84.133 <none> 9101/TCP 42h
st2-st2auth ClusterIP 10.109.68.142 <none> 9100/TCP 42h
st2-st2stream ClusterIP 10.105.237.199 <none> 9102/TCP 42h
st2-st2web NodePort 10.102.51.79 <none> 80:30341/TCP 42h
svc の st2web が web UI に対応しており、username は st2admin
, 以下のコマンドで取得した password でログインできます。
kubectl get secrets -o yaml st2-st2-auth | yq -r ".data.ST2_AUTH_PASSWORD" | base64 -d
使ってみる
実際にワークフローを作って動作を確認します。
st2 における action, workflow, pack の概念
ワークフローを作る前に st2 における action や workflow リソースの概念について簡単に説明しておきます。
Action
st2 では action と呼ばれるリソースが処理の最小単位になります。tekton や concourse における task オブジェクトに相当します。
action は 2 つのファイルから構成されます。
- metadata: action の定義や入力パラメータを記載する yaml ファイル
- script: 実際の処理を記述するファイル
script は Available Runner に使用可能な形式が記載されています。シェルスクリプトや python スクリプトの他に実行可能な linux コマンドも対応しているので、executable なバイナリが生成できるプログラム言語でも記述できそうです。
また、Ansible のようにリモートマシン上でスクリプトを実行する形式も指定可能となっています。
Workflow
workflow は複数の action を組み合わせて一連のワークフローとして実行するリソースであり、st2 内では Orquesta という用語で定義されています。
こちらも action と同様に 2 つのファイルから構成されます。
- metadata: workflow の定義や入力パラメータを記載する yaml ファイル
- workflow: workflow 内の処理記述する yaml ファイル
st2 における処理の実行単位は action であるため、ワークフロー処理では複数の action を組み合わせて好みの動作を実現します。その他、action 間での変数の受け渡しや action の実行結果に応じて後続の action を実行する条件分岐などを記述できます。
pack
st2 では複数の action や workflow をまとめて pack という単位で管理します。
例えばデフォルトでインストールされている linux
pack では cp, rsync, service などの基本的な linux コマンドに対応する action や workflow が格納されています。
action や workflow はいずれかの pack 配下に登録する必要があります。自作の action 等を開発する場合はまず自作の pack を定義し、その中に pack の趣旨に沿った action, workflow を作成して登録するという流れになります。
ワークフローの作成
st2 での処理単位について簡単に紹介したところで、実際にワークフローを作成してみます。
今回は、今までのワークフロー処理でおなじみの git repository から dockerfile を含むコードを clone し、コンテナイメージを build, push, ビルド結果を slack に投稿するような一連のワークフロー処理を実現してみたいと思います。
st2 でこの動作を実現する場合、以下のような workflow と action を作成する必要があります。
- action
- git clone する処理
- イメージを build, push する処理
- slack に webhook でメッセージを投稿する処理
- workflow
- 上記を一連で実行するワークフロー
それぞれの作り方について以下で見ていきます。
action の作成
はじめに Action metadata を参考に、git clone を行う action の metadata を作成します。
description: 'Git clone action'
enabled: true
entry_point: git-clone.sh
name: git-clone
parameters:
repository_url:
type: string
description: "Repository url"
required: true
position: 0
branch:
type: string
description: "Branch"
default: main
position: 1
username:
type: string
description: "Username"
position: 2
password:
type: string
description: "Password"
position: 3
tls_verify:
type: boolean
description: "Check if TLS verify"
default: true
position: 4
runner_type: "local-shell-script"
この action が呼ばれた際に実行されるシェルスクリプトを記述します。
オプションはいろいろ省いて単に入力パラメータで指定された repository から git clone するだけのシンプルな構成にします。
#!/bin/bash
set -ue
repository_url=$1
branch=$2
username=$3
password=$4
tls_verify=$5
repository_url="https://${username}:${password}@${repository_url#https://}"
last_element=$(basename "$repository_url")
destination=$(echo "$last_element" | sed 's/\.git$//')
if [[ ${tls_verify} == "false" ]]; then
git config --global http.sslVerify false
fi
# Remove the previous directory
share_dir=/opt/stackstorm/virtualenvs/tmp
rm -rf ${share_dir}/${destination}
# Run git clone
git clone \
${repository_url} \
-b ${branch} \
${share_dir}/${destination}
なお、action は基本的に別の st2actionrunner pod で実行されるため、デフォルトでは action 間でのファイルやディレクトリの共有ができません。
今回は pvc で共有している /opt/stackstorm/virtualenvs/
以下に clone したディレクトリを保存することで後続の action で使用可能にしています。
ちゃんと作るのであれば共有用の pvc を別に用意し、runner pod 内でマウントするようにマニフェストを書き換えるのが良いでしょう。
次にディレクトリの Dockerfile からコンテナイメージを build, push する action を作ります。
ただし、諸々の事情からデフォルトでインストールされる st2actionrunner ではコンテナイメージの build が実行できないため、独自でビルドしたイメージを使用します。詳細は buildah が実行可能な st2actionrunner イメージの作成 を参照。
これに合わせて buildah を使ってイメージを build する action を作ります。
description: 'Build and push an image action'
enabled: true
entry_point: build.sh
name: build
parameters:
image_name:
type: string
description: "Image name"
required: true
position: 0
image_tag:
type: string
description: "Image tag"
default: latest
position: 1
directory:
type: string
description: "Path to the directory where Dockerfile exists"
required: true
position: 2
dockerfile_name:
type: string
description: "Dockerfile filename"
default: Dockerfile
position: 3
registry_host:
type: string
description: "Registry host [host]:[port]"
required: true
position: 4
tls_verify:
type: string
description: "Check if TLS verify"
default: "true"
position: 5
runner_type: "local-shell-script"
buildah を実行するシェルスクリプト
#!/bin/bash
set -ue
IMAGE_NAME=$1
IMAGE_TAG=$2
DIRECTORY=$3
DOCKERFILE_NAME=$4
REGISTRY_HOST=$5
TLS_VERIFY=$6
buildah build \
-t ${IMAGE_NAME}:${IMAGE_TAG} \
-f ${DIRECTORY}/${DOCKERFILE_NAME}
buildah tag ${IMAGE_NAME}:${IMAGE_TAG} ${REGISTRY_HOST}/${IMAGE_NAME}:${IMAGE_TAG}
buildah push \
--tls-verify=${TLS_VERIFY} \
${REGISTRY_HOST}/${IMAGE_NAME}:${IMAGE_TAG}
最後に build 結果を slack に post する action を準備します。
これに関しては、いろいろな pack が事前に公開されている stackstorm exchange にある slack
pack が利用できます。
pack をインストールするには st2client pod 上で st2 pack install slack
を実行します。
kubectl exec -it st2-st2client-5fb9c79dcd-snlfr -- st2 pack install slack
これにより slack pack に含まれる様々な action が使用可能になります。webhook で slack channel にメッセージを post するにはこの中の post_message
action が使えます。
$ st2 action list -p slack
...
| slack.pins.add | slack | Pins an item to a channel. |
| slack.pins.list | slack | Lists items pinned to a channel. |
| slack.pins.remove | slack | Un-pins an item from a channel. |
| slack.post_message | slack | Post a message to the Slack channel. |
...
workflow の作成
次に、上記で作成した action を一連で実行するワークフローを作成します。
まず action と同様に workflow のメタデータを作成します。
---
name: build-workflow
description: Clone git repository, build an image and push it to a remote registry.
runner_type: orquesta
entry_point: workflows/build-workflow.yml
enabled: true
parameters:
repository_url:
type: string
description: "Git repository url such as https://..../repo.git"
required: true
branch:
type: string
description: "Gir branch"
default: main
username:
type: string
description: "Git username"
password:
type: string
description: "Git password"
image_name:
type: string
description: "Image name"
required: true
image_tag:
type: string
description: "Image tag"
default: latest
dockerfile_name:
type: string
description: "Dockerfile filename"
default: Dockerfile
registry_host:
type: string
description: "Registry host where the image to be pushed. [host]:[port]"
required: true
tls_verify:
type: string
description: "TLS Check"
default: "true"
メタデータでは runner_type: orquesta
を指定することで内部的に workflow と認識されます。
次に実際に実行されるワークフローを作成します。
ワークフローファイルではトップレベルの tasks
で実行される action を定義します。例えば、git-clone action を実行するための記述は以下のようになります。
tasks:
git_clone:
action: cicd.git-clone
input:
repository_url: "{{ ctx().repository_url }}"
branch: "{{ ctx().branch }}"
username: "{{ ctx().username }}"
password: "{{ ctx().password }}"
tls_verify: "{{ ctx().tls_verify }}"
next:
- when: "{{ succeeded() }}"
publish:
- directory: "/opt/stackstorm/virtualenvs/tmp/{{ ctx().repository_url.split('/') | last | replace('.git', '') }}"
do: build_push
- when: "{{ failed() }}"
publish:
- build_status: Fail
- result_message: "{{ result().stderr }}"
do: post_result
action には [pack_name].[action_name]
形式で実行する action を指定します。
input 以下には action に受け渡すパラメータを key-value 形式で指定できます。
ワークフロー全体で input の変数や task の実行結果は ctx()
という変数に格納され、例えば以下のいずれかの形式で変数 a
の値を取得できます。
jinja 形式: "{{ ctx().a }}"
yaql 形式: <% ctx().a %>
詳細については Workflow Runtime Context を参照。
どちらの形式でも ok ですが、1 つのワークフロー内で両者の形式を混ぜて使用することはできません。
上記の input ではワークフロー実行時に指定したパラメータを取得し、git-clone アクションの入力パラメータに指定しています。
next ではこの action が終了した際の次の動作を list 形式で指定します。参考
Key | Description |
---|---|
when | 適用条件 |
publish | 以降の task で使用する変数と値の指定。指定した変数は ctx() に格納される |
do | when に指定した条件を満たす際に次に実行される task 名 |
when では指定した式が true となる時に設定が適用されます。タスクが成功した際は "{{ succeeded() }}"
, 失敗した際は "{{ failed() }}"
がそれぞれ true となるので、上記の例では以下の動作となります。
- action が成功 (シェルスクリプトの終了コードが 0) した場合
- build_push タスクを実行する。
- action が失敗 (シェルスクリプトの終了コードが 0 以外) した場合
- post_result タスクを実行する。
- result_message 変数に stderr に出力されたメッセージを設定する。
後は同じようにコンテナイメージのビルド、slack への結果のポストを行うタスクを定義します。
build_push:
action: cicd.build
input:
image_name: "{{ ctx().image_name }}"
image_tag: "{{ ctx().image_tag }}"
directory: "{{ ctx().directory }}"
dockerfile_name: "{{ ctx().dockerfile_name }}"
registry_host: "{{ ctx().registry_host }}"
tls_verify: "{{ ctx().tls_verify }}"
next:
- when: "{{ succeeded() }}"
publish:
- build_status: Success
do: post_result
- when: "{{ failed() }}"
publish:
- build_status: Fail
- result_message: "{{ result().stderr }}"
do: post_result
post_result:
action: slack.post_message
input:
message: |
Build_statue: {{ ctx().build_status }}
Repository: {{ ctx().repository_url }}
Image: {{ ctx().registry_host }}/{{ ctx().image_name }}:{{ ctx().image_tag }}
{% if ctx().result_message is not none %}
message:
```
{{ ctx().result_message }}
```
{% endif %}
channel: "#general"
username: bot_test
webhook_url: "https://hooks.slack.com/xxxxxxxxxxxxxxxxxxxx"
なお、ワークフロー全体で python のテンプレートエンジン jinja の表式が使用できます。
上記の post_message のように文字列の抽出、置換や、if 文を使って特定の条件を満たす場合に文字列を追加するなどの柔軟な表現ができます。
その他、Getting started の例に沿ってワークフローの定義に必要なフィールドなどを追加すると、最終的なワークフローの中身は以下のようになります。
version: 1.0
description: Build image and push.
input:
- repository_url
- branch
- username
- password
- image_name
- image_tag
- directory
- dockerfile_name
- registry_host
- tls_verify
vars:
- build_status: null
- result_message: null
output:
- result: null
tasks:
git_clone:
action: cicd.git-clone
input:
repository_url: "{{ ctx().repository_url }}"
branch: "{{ ctx().branch }}"
username: "{{ ctx().username }}"
password: "{{ ctx().password }}"
tls_verify: "{{ ctx().tls_verify }}"
next:
- when: "{{ succeeded() }}"
publish:
- directory: "/opt/stackstorm/virtualenvs/tmp/{{ ctx().repository_url.split('/') | last | replace('.git', '') }}"
do: build_push
- when: "{{ failed() }}"
publish:
- build_status: Fail
- result_message: "{{ result().stderr }}"
do: post_result
build_push:
action: cicd.build
input:
image_name: "{{ ctx().image_name }}"
image_tag: "{{ ctx().image_tag }}"
directory: "{{ ctx().directory }}"
dockerfile_name: "{{ ctx().dockerfile_name }}"
registry_host: "{{ ctx().registry_host }}"
tls_verify: "{{ ctx().tls_verify }}"
next:
- when: "{{ succeeded() }}"
publish:
- build_status: Success
do: post_result
- when: "{{ failed() }}"
publish:
- build_status: Fail
- result_message: "{{ result().stderr }}"
do: post_result
post_result:
action: slack.post_message
input:
message: |
Build_statue: {{ ctx().build_status }}
Repository: {{ ctx().repository_url }}
Image: {{ ctx().registry_host }}/{{ ctx().image_name }}:{{ ctx().image_tag }}
{% if ctx().result_message is not none %}
message:
```
{{ ctx().result_message }}
```
{% endif %}
channel: "#general"
username: bot_test
webhook_url: "https://hooks.slack.com/xxxxxxxxxxxxxxxxxxxx"
pack の作成
最後に、作成した action と workflow を分類するための独自 pack cicd
を作成します。
packs の作成は簡単で、pack の定義を行うメタデータファイル pack.yml
を作成します。
---
name: cicd
description: my CICD custom pack
keywords:
- cicd
version: 1.0.0
python_versions:
- "3"
author: aaa
email: info@local.com
key | description |
---|---|
name | pack の名前 |
description | pack の説明 |
keywords | pack に関連するワードを list 形式で指定 |
version | pack のバージョン |
python_versions | 対応する python version |
author | author 名 |
author email |
そして今まで作成したファイル群を以下のよう配置します。
cicd
├── actions
│ ├── build.sh
│ ├── build-workflow.yml
│ ├── build.yml
│ ├── git-clone.sh
│ ├── git-clone.yml
│ └── workflows
│ └── build-workflow.yml
└── pack.yaml
上記の cicd ディレクトリを st2client pod 内の /opt/stackstorm/packs
に配置し、st2 pack install file:///opt/stackstorm/packs/cicd
を実行することで独自 pack が登録できます。
root@st2-st2client-5fb9c79dcd-snlfr:/opt/stackstorm/packs# st2 pack install file:///opt/stackstorm/packs/cicd
[ succeeded ] init_task
[ succeeded ] download_pack
[ succeeded ] make_a_prerun
[ succeeded ] get_pack_dependencies
[ succeeded ] check_dependency_and_conflict_list
[ succeeded ] install_pack_requirements
[ succeeded ] get_pack_warnings
[ succeeded ] register_pack
+-------------+---------------------+
| Property | Value |
+-------------+---------------------+
| ref | cicd |
| name | cicd |
| description | my CICD custom pack |
| version | 1.0.0 |
| author | aaa |
+-------------+---------------------+
登録すると webUI の Actions からも追加した pack 内の action が確認できます。
packs を登録する際は開発環境から st2client pod 内にディレクトリ全体をコピーする必要があるのがやや面倒です。この部分は今後 k8s CRD で対応できるようにしてほしいところ。
ワークフローの実行
web UI から作成したワークフローを実行します。
web UI の Actions > build で build-workflow を選択してワークフローに渡すパラメータを入力を行います。この画面ではワークフローのメタデータで定義した parameter が表示されており、* が点いているものが必須パラメータとなります。また、notify や trace_tag など自動で追加されているパラメータもあります。
RUN を押すとワークフローの実行がキューに予約され、内部的にいずれかの st2actionrunner pod に割り当てられて実行されます。すべてのタスクが正常に完了すると、slack の該当 channel に build_status: success
としてメッセージが投稿されます。
入力パラメータに不備があり途中のタスクが失敗した際は build_status: fail
として失敗した理由が message
に表示されます。これは失敗した action の stderr のエラーメッセージが表示されるようにしています。以下の例では git repository の URL を間違って指定したために git clone に失敗していることがわかります。
また、タスが完了した場合は registry UI よりコンテナイメージが正常に build & push されていることも確認できます。
ワークフローやアクションの実行履歴は web UI の History から確認できます。ここでは同じワークフローを入力パラメータを変更して実行などができます。
action の準備などが結構大変でしたが、他のワークフローツールと同様にユーザ独自で作成した一連の CI/CD ワークフローが実行可能なことが確認できました。
イベントの受信
st2 はイベント駆動型であるのでもちろん git repository に変更が生じた際に上記のワークフローを実行するように設定できます。今回の範囲では触れていませんが、st2 では以下のコンポーネントでイベントに対応します。
-
sensor, trigger
- webhook 等のイベント受信を対応するコンポーネント
-
Rule
- 受信したイベントの中身を分析、フィルタリングする。
- 特定の条件を満たす際に action や workflow を実行する。
tekton, concourse との比較
OSS ワークフローツールはたくさんあり、今までの記事でも tekton, concourse といったクラウドネイティブなワークフローツールを扱ってきました。それでは各種ワークフローツールと st2 はどれを使えば良いのでしょうか。
もちろんそれぞれの使い勝手は目的やユースケース、環境の規模等により異なり単純な比較は難しいため、いくつかの観点から主観と独断に基づいて比較します。
ワークフローの可読性
ワークフロー自体はいずれも yaml 形式で記述できるため、それぞれの記法の差はあれど可読性に関してはそこまで差はないと思います。
強いて言うなら、 st2 では入力パラメータ等のメタデータと実際の処理を行うワークフロー部分が別ファイルに分離している一方、concourse や tekton では 1 つの yaml にまとめて記述する部分がそれぞれの特徴です。それを良いと捉えるかどうかは人によりますが。
機能の豊富さ
tekton, concourse はスクリプト形式で自由に処理を記述でき、必要に応じて独自の処理を行うコンテナイメージを作成して使用することができます。
stackstorm では処理自体は action としてシェルスクリプトや python スクリプトで記述できます。そのため、目的に合った処理を自作できるという点ではいずれも差は無いと考えられます。
tekton, concourse は処理自体をコンテナイメージで作成するので環境の依存性をある程度分離できますが、st2 はスクリプトで実装するため処理内容によってはその部分がネックになるかもしれません。
ワークフローの処理に関しては、st2 ではタスクのリトライ処理や条件分岐、並列などの機能がデフォルトで存在するので制御がやや楽になっています。また jinja2 の表式が使用できるので、本文の slack post action で使ったように特定の変数がセットされている場合のみ値を追加したり、if 文を使って input に渡すパラメータを変更したりするといった処理が楽に実装できます。これを tekton 等で実装しようとすると条件分岐を 1 つ 1 つ記述する必要があるので、記述量を削減しつつより多くのことを実現できるという点では st2 ワークフローの方が表現力は高いと言えます。
Web UI の使いやすさ
どのツールにも web UI が備わっていますが、stackstorm の webUI では登録済みの action, pack, ワークフローの可視化、実行済みワークフローをパラメータを変更して再実行が可能であるため、他のツールと比べると使いやすいと感じます。
また、st2 では Role-Based Access Control (RBAC) によるユーザ管理機能があり webUI へのログインにも user, password の入力が求められますが、tekton ではその部分の対応は自分で行う必要があります。
学習コスト
stackstorm ではワークフローを使うまでに action, pack の概念や記述形式など覚えることが多いです。ワークフロー内の変数の受け渡し等でも yaql や jinja2 に基づいた独特な記法を用いるため、使いこなすまでに少し慣れが必要になります。
tekton, concourse でもワークフローの記述形式を覚える必要はありますが、一般的な yaml 記法で表現可能なため st2 と比較すると学習コストは低いかと思います。
上記の理由から学習コストに関しては上記の大小関係としました、
運用・管理の容易さ
stackstorm はマイクロサービスアーキテクチャであり、必然的にコンポーネント数も多いため運用・管理の面では結構な負担になりそうです。特に本格的な運用を意識する場合、st2 固有のコンポーネントだけでなくメッセージキュー (rabbitmq) やデータベース (mongoDB) の可用性等も考える必要があります。
コンポーネント数の多さが運用・管理の大変さに直結することを考えると st2 が最も管理が大変であると考えられるため上記の大小関係になるかと思います。
その他
st2 のアーキテクチャ
helm で st2 をインストールすると pod がたくさん作成されますが、これらは st2 アーキテクチャの各コンポーネントに対応しています。st2 はマイクロサービスアーキテクチャで設計されているため、役割毎にコンポーネントが分離されています。
また、helm でのインストールは High Availability 構成としてインストールされます。こちらについては https://docs.stackstorm.com/reference/ha.html にコンポーネント間の連携や役割がまとまっています。
st2 CLI を pod 外から実行する
st2 client CLI は st2client pod にデフォルトでインストールされていますが、pip を使えば他のマシン インストールすることもできます。
pip install st2client
client では st2 user の認証情報、および各種エンドポイントの設定が必要です。
username, password は webUI ログインに使用する認証情報が使用できます。
各種エンドポイントは helm install 時に svc として作成されており、この ClusterIP を使えばアクセスできます。
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
st2-st2api ClusterIP 10.103.84.133 <none> 9101/TCP 3d18h
st2-st2auth ClusterIP 10.109.68.142 <none> 9100/TCP 3d18h
st2-st2stream ClusterIP 10.105.237.199 <none> 9102/TCP 3d18h
client CLI は linux 実行ユーザのユーザディレクトリにある ~/.st2/config
の情報を読み取るのでこれを作成します。
[credentials]
username = st2admin
password = [パスワード]
[api]
url = http://[st2api の ip]:9101
[auth]
url = http://[st2auth の ip]:9100
[stream]
url = http://[st2stream の ip]:9102
このように設定することで、pod 外部からでも st2 の各種 API を実行できます。
$ st2 pack list
+---------+---------+-------------------------+---------+------------------+
| ref | name | description | version | author |
+---------+---------+-------------------------+---------+------------------+
| chatops | chatops | ChatOps integration | 3.8.0 | StackStorm, Inc. |
| | | pack | | |
| cicd | cicd | my CICD custom pack | 1.0.0 | aaa |
| core | core | Basic core actions. | 3.8.0 | StackStorm, Inc. |
| default | default | Default pack where all | 3.8.0 | StackStorm, Inc. |
| | | resources created using | | |
| | | the API with no pack | | |
| | | specified get saved. | | |
| git | git | Git SCM | 2.0.0 | StackStorm, Inc. |
| linux | linux | Generic Linux actions | 3.8.0 | StackStorm, Inc. |
| packs | packs | Pack management | 3.8.0 | StackStorm, Inc. |
| | | functionality. | | |
| slack | slack | Slack Chat integrations | 2.1.0 | StackStorm, Inc. |
+---------+---------+-------------------------+---------+------------------+
buildah が実行可能な st2actionrunner イメージの作成
ワークフローの中でコンテナイメージをビルドするにはビルドコマンドを実行する action を作成する必要があります。
しかし、k8s では st2actionrunner pod 内で action が実行されるため、docker や containerd のような docker engine 等の動作を必要とするコマンドでビルドすることができません。
また、スクリプトとして実行されるため、tekton の際に使用した kaniko を使ってビルドすることもできません。
そのため、docker engine 等の daemon を必要とせずコマンドラインか実行可能なビルドツール buildah を使ってイメージをビルドすることを考えます。
もちろん st2actionrunner にはデフォルトで入っていないのでインストールする必要があるのですが、ここでもう一つ問題があります。helm でインストールする場合の st2actionrunner pod で使用されているイメージは st2-docker のレポジトリの Dockerfile から作成されており、ベースイメージには ubuntu focal (20.04) が使用されています。一方で buildah は ubuntu 22.10 移行のバージョンをサポートしておりそのままではインストールできません。
じゃあベースイメージを ubuntu 20.10 移行にした st2actionrunner をビルドすればいいのでは思いますが、現時点で st2 本体が ubuntu 20.04 までしか対応してなく、実際 ubuntu 22.04 で Dockerfile のビルドを試してみると apt で st2 をインストールする箇所で失敗します。
色々見た結果、rockylinux 8 系であれば st2 本体、buildah の両方をサポートしているので、ベースイメージを rockylinux 8 系にして st2actionrunner を自前でビルドすれば使えるようになります。
ただし上記の Dockerfile は ubuntu を前提としているため、rockylinux で動作するように Dockerfile の中身を書き換える必要があります。
書き換えた内容は以下の github に置いてあります。
また、このままだと pod 内で buildah build を実行した際に以下のエラーが発生します。
Error: mount /var/lib/containers/storage/overlay:/var/lib/containers/storage/overlay, flags: 0x1000: permission denied
WARN[0000] failed to shutdown storage: "mount /var/lib/containers/storage/overlay:/var/lib/containers/storage/overlay, flags: 0x1000: permission denied"
おそらく pod の security context 的に弾かれているので、deployment のマニフェストに securityContext を追加します。
privileged: true に設定するのはセキュリティ的によろしくないですが、今回は検証目的なので良しとします。
spec:
...
+ securityContext:
+ privileged: true
問題はもう一つあります。st2actionrunner pod 内は通常 uid=1000 の stanley ユーザで action が実行されますが、これで buildah を実行すると以下のエラーが発生します。
WARN[0000] running newgidmap: exit status 1: newgidmap: write to gid_map failed: Operation not permitted
WARN[0000] /bin/newgidmap should be setgid or have filecaps setgid
WARN[0000] Falling back to single mapping
WARN[0000] Error running newuidmap: exit status 1: newuidmap: write to uid_map failed: Operation not permitted
WARN[0000] /bin/newuidmap should be setuid or have filecaps setuid
WARN[0000] Falling back to single mapping
...
Error: creating build container: copying system image from manifest list: writing blob: adding layer with blob "sha256:96526aa774ef0126ad0fe9e9a95764c5fc37f409ab9e97021e7b4775d82bf6fa": processing tar file(potentially insufficient UIDs or GIDs available in user namespace (requested 0:42 for /etc/shadow): Check /etc/subuid and /etc/subgid if configured locally and run podman-system-migrate: lchown /etc/shadow: invalid argument): exit status 1
おそらく実行ユーザの権限諸々で弾かれているので、root ユーザで action を実行するように変更します。
st2 では st2.conf
という config ファイルで様々な設定を制御でき、system_user セクションで実行ユーザを指定できます。config ファイルの例
helm でインストールすると上記設定ファイルに相当する configmap が作成され、st2actionrunner の pod にマウントされる形式になっています。
そのため、実行ユーザを変更する configmap を作成し、st2actionrunner pod にマウントの設定を追加すればよいということになります。
configmap
apiVersion: v1
data:
st2.user.conf: |
[system_user]
user = root
[api]
allow_origin = '*'
kind: ConfigMap
metadata:
annotations:
description: Custom StackStorm config which will apply settings on top of default st2.conf
meta.helm.sh/release-name: st2
meta.helm.sh/release-namespace: st2
labels:
app: st2
app.kubernetes.io/managed-by: Helm
chart: stackstorm-ha-0.110.0
heritage: Helm
release: st2
tier: backend
vendor: stackstorm
name: st2-user-config
namespace: st2
deployment pod でマウントする
volumeMounts:
- mountPath: /etc/st2/st2.user.conf
- name: st2-config-vol
+ name: st2-user-config-vol
subPath: st2.user.conf
volumes:
+ - configMap:
+ name: st2-user-config
+ name: st2-user-config-vol
まとめ
以上をまとめると、st2actionrunner 用のコンテナイメージをビルドし、既存の st2actionrunner deployment マニフェストを以下のように編集すれば動くようになります。
spec:
containers:
- ....
- image: stackstorm/st2actionrunner:3.8
+ image: myactionrunner:1.0
...
+ securityContext:
+ privileged: true
...
volumeMounts:
- mountPath: /etc/st2/st2.user.conf
- name: st2-config-vol
+ name: st2-user-config-vol
subPath: st2.user.conf
...
volumes:
+ - configMap:
+ name: st2-user-config
+ name: st2-user-config-vol
長い道のりでしたが、これでようやくワークフロー内でコンテナイメージのビルドが実行できるようになります。
おわりに
st2 を k8s クラスタにインストールしてワークフローを実行する手順についてまとめました。
st2 は自動化の機能が豊富である半面、学習コストや管理の複雑さが大きいといった印象を感じます。
tekton や concourse と同じ一連の処理を実行するワークフローツールというカテゴリには分類されますが、それぞれ得意とする処理が異なるため必要に応じて使い分けていくのが良いでしょう。
例えばコンテナイメージのビルドを含む CI/CD プロセスや k8s クラスタへのデプロイに関しては、CI/CD に特化したクラウドネイティブな tekton や concourse を使う方が楽かと思います。
一方で、より汎用的なワークフロー処理は Automation に特化した st2 の得意領域であるため、st2 で検知や対応処理を組むのが良いと思われます。
Discussion