こんにちは。Product Team の竹原です。
先日、社内用の小さな Web アプリを追加開発する機会がありまして、その際に CI/CD パイプラインの作成も行いました。
CI/CD パイプライン構築のために Argo Workflows を使ってみたところ非常に感触がよかったので、感想などを記載しておこうと思います。
※ 今回は概要の紹介程度となります。実際に作成したワークフローはまた次回ご紹介します。
Argo Workflows とは
Argo Workflows はタイトルで書いたとおりワークフローエンジンです。
エンジニアの身近なところで言えば近いのは Jenkins とか GitHub Actions とかでしょうか。
公式サイト では以下のように紹介されています。
Argo Workflows is an open source container-native workflow engine for orchestrating parallel jobs on Kubernetes.
Argo Workflows は、Kubernetes 上で並列ジョブをオーケストレーションするための、オープンソースのコンテナネイティブなワークフローエンジンです。
ここで重要なのは、
- Kubernetes 上で動作する
- コンテナネイティブ
というところだと思っています。
ワークフローを YAML で書いて Argo Workflows に与えると、GUI 上からそのワークフローを実行することができます。
公式のワークフローサンプル
https://argoproj.github.io/argo-workflows/walk-through/steps/
↑ ワークフロー上で3回 Hello world する公式サンプルです。
apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: steps- spec: entrypoint: hello-hello-hello # This spec contains two templates: hello-hello-hello and whalesay templates: - name: hello-hello-hello # Instead of just running a container # This template has a sequence of steps steps: - - name: hello1 # hello1 is run before the following steps template: whalesay arguments: parameters: - name: message value: "hello1" - - name: hello2a # double dash => run after previous step template: whalesay arguments: parameters: - name: message value: "hello2a" - name: hello2b # single dash => run in parallel with previous step template: whalesay arguments: parameters: - name: message value: "hello2b" # This is the same template as from the previous example - name: whalesay inputs: parameters: - name: message container: image: docker/whalesay command: [cowsay] args: ["{{inputs.parameters.message}}"]
spec.entrypoint
が hello-hello-hello
なので、spec.templates.name
が hello-hello-hello
であるワークフローがこのワークフローで実行されるもの、となります。
このワークフローを GUI から実行すると以下のようになります。
ワークフロー内のステップ hello2a
と hello2b
が縦に積まれているのは、並列実行されているからです。
spec: templates: steps: - - name: hello1 (中略) - - name: hello2a (中略) - name: hello2b
↑ 一部抜粋ですが、並列処理は上記のように書きます。
これは hello1
が実行完了したら hello2a
と hello2b
を並列実行する、という書き方です。
ハイフン2つ - -
の step に続いてハイフン1つ -
の step を書くと、それらが並列実行されます。
Jenkins に Groovy ファイルを与えて動かすパイプラインとかだと並列処理時は parallel { }
で括る必要があったりして、行が増えたりインデントが増えたり面倒ですが、Argo Workflows ではシンプルに並列処理を書けます。
上記のサンプルだけでは「コンテナを動かせるのは分かったけど、例えばシェルスクリプト書く場合はどうすれば?」という疑問があると思いますので、Git のチェックアウトだけをするステップの例を示します。
spec: templates: - name: checkout inputs: parameters: - name: repo script: image: alpine/git command: [sh] source: | ls /src || mkdir /src cd /src git clone {{inputs.parameters.repo}}
こんな感じでシェルスクリプトを書けます。この例だと「認証情報はどう渡せば?」「volume に保存するには?」という追加の疑問があるかと思いますがそれはまた次回に...
Argo Workflow ならではの便利さとか工夫とか
私は Argo Workflows を CI/CD パイプラインの基盤として使用しました。
そのなかでの便利さや工夫などをご紹介します。
利用するツールや動作環境のバージョンをコントロールしやすい
当然といえば当然なのですが、Argo Workflows ではワークフローが 100% コンテナ上で動作するので、java
・node
・mvn
・jq
・skaffold
といった各種ツール・動作環境のバージョン指定はすべて Dockerfile 上で行えます。
共通サーバ上に Jenkins を立てていたりすると、「他のプロジェクトで使ってる mvn
のバージョンが古すぎて、うちのプロジェクトだと使えないけど、最新の mvn
を PATH
に含めちゃうと他プロジェクトが壊れる」といった事象があったりなかったりすると思います。
Argo Workflows ではそのような「バージョン差を生んでしまい、他が壊れる」といったことは気にしなくて良いんです。
Kubernetes のサービス名で名前解決ができるので、E2E テストがやりやすい
今回私が追加開発したアプリケーション (CI/CD の対象) は、フロントエンド (Vite 製) が1つ、バックエンド (Flask 製) が1つ、という構造でした。
そのどちらにも E2E テストが存在し、その E2E テストを流すためには、テスト対象のアプリケーションと、テスト対象から通信が走る先のモック (Wiremock) を起動し、テスト対象からモックに通信ができる必要があります。
E2E 実行環境に直接フロントエンドとバックエンドをデプロイしているなら、http://localhost:16000
など localhost に向けた通信で事足りるのですが、
私達は「E2E の実行環境はできるだけ本番環境と同じにする」という考えで Kubernetes 上にテスト対象をすべてデプロイしています。(もちろん本番環境のアプリはすべて Kubernetes 上で動いてます)
そのため、私達の E2E 実行環境はいつも以下のように構成されます。
しかし Argo Workflows のワークフローはそれ自体が Kubernetes 上で動くので、以下のようになります。
覚えづらかったり変わったりする可能性のある Node IP ではなく、Kubernetes の名前解決の仕組みを使ってテスト対象にアクセスできるので非常に便利です。
並列処理をシンプルに書ける
先に紹介したように、ハイフン2つ - -
の記法により並列処理をシンプルに書くことができます。
しかし並列処理には更に別の記法もあり、以下の YAML は
- - name: hello2a # double dash => run after previous step template: whalesay arguments: parameters: - name: message value: "hello2a" - name: hello2b # single dash => run in parallel with previous step template: whalesay arguments: parameters: - name: message value: "hello2b"
以下のように書いても同様の挙動になります。
- - name: hello2a # double dash => run after previous step template: whalesay arguments: parameters: - name: message value: "{{item.message}}" withItems: - { message: "hello2a" } - { message: "hello2B" }
withItems
は、引数の個数分だけそのステップを並列実行してくれます。そのため、上記例の2つは全く同じ動きをします。
つまり処理をテンプレート化しておけば、「複数のリポジトリの並列チェックアウト」「Skaffold を使ってフロントエンドとバックエンドをビルド」といった似たような処理の繰り返しは簡単に書けます。
ステップをテンプレート化できる(Workflow Templates)
上で紹介した hello-hello-hello
の例では1ファイル内にすべてを記述してありましたが、ファイル分割することもできます。
例えば以下の YAML で1ファイル作っておくと、
apiVersion: argoproj.io/v1alpha1 kind: WorkflowTemplate metadata: name: say spec: templates: - name: whalesay inputs: parameters: - name: message container: image: docker/whalesay command: [cowsay] args: ["{{inputs.parameters.message}}"]
上記を他ファイルから以下のように参照できます。
apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: steps- spec: entrypoint: hello-hello-hello templates: - name: hello-hello-hello steps: - - name: hello1 templateRef: name: say template: whalesay arguments: parameters: - name: message value: "hello1"
Cluster Workflow Templates と Workflow Templates
上記ではステップ whalesay
をテンプレート化しました。
これは kind: WorkflowTemplate
として定義しましたので、Workflow Template という括りのテンプレートになります。
この他に kind: ClusterWorkflowTemplate
として定義することができ、これは Cluster Workflow Template になります。
Workflow Template として定義すると、Kubernetes 内の同一 namespace からしかそのテンプレートを参照できませんが、
Cluster Workflow Template として定義すると、別の namespace からもそのテンプレートを参照することができます。
そのため、Git の clone や commit -> push、Skaffold を使ったアプリのビルド、といった他のワークフローにも転用できそうなテンプレートは Cluster Workflow Template にしておくと良さそうです。
おわりに
「Jenkins と大差なくない?」と思いながら使い始めた Argo Workflows でしたが、新しめのツールだけあって細かいところで便利さが発揮されていて、かなり使い心地がよかったです。
次回がもしあれば、実践編ということでワークフローの具体的な YAML についてご紹介しようと思います。