Kubernetesユーザーは待っていた!?コンテナネイティブなワークフローエンジンArgoを試してみた

## [Argo](https://applatix.com/open-source/argo/)とは？

ArgoはコンテナネイティブなOSSのワークフローエンジンです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/62029/1d85d1e0-1e59-4554-1036-c0bc3d07652a.png)

**2018-04-25 追記**

```
Argoはプロジェクトが分割されているようです。
この記事はその中の`Argo Workflows`について記載しています。
その他のプロジェクトはこちら 「これからと期待」に少しだけ記載しています。
```

ワークフローエンジンだと色々な意味が含まれるため、イメージを掴むため類似するプロダクトを挙げると`Airflow`、`Digdag`、`Luigi`となります。

名前の由来はギリシア神話に登場する巨大な船の名前のアルゴ船？かも

ArgoはKubernetes（以後k8s）上に構築することにより、k8sの機能を利用できる大きなアドバンテージを獲得しています。
例えばワークフローの定義はCustom Resource Definition（以後CRD）で定義できます。
つまりYAMLで定義できるため、k8sのマニフェスト管理と同じにように`helm`、`ksonnet`を使用できます。

その他にもk8sリソースの`Secret`や`Persistent volume`にアクセスすることも可能です。
k8sを利用しているユーザーには馴染みやすい定義方法となる点は大きな魅力です。
表題の通り個人的には「こういうの待ってた！」という気持ちです。
<img src="https://qiita-image-store.s3.amazonaws.com/0/62029/6bb8b393-8f01-92a9-b45b-4c623f17fcb7.png" width="300px"/>

先日はコミュニティの法人会員（corporate members）にGoogleとNVIDIAの参加がアナウンスされ、これからの成長が楽しみです。

## `hello-world`で実行までの流れ！

どんなことができるかを説明する前に、まずは`hello-world`なワークフローを実行し、イメージを掴んでいきます。

ワークフローの定義はこちら。

```yaml:hello.yaml
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: hello-
spec:
  # 実行するテンプレートを指定
  entrypoint: hello

  # テンプレートの定義
  templates:
  - name: hello
    container:
      image: docker/whalesay:latest
      command: [cowsay]
      args: ["hello world"]
```

CRDで定義された`kind: Workflow`を使うため、k8sのマニフェストを管理している人には馴染みやすいですね。

そして実行方法は以下のようになります。

```shell
# ワークフローを作成
$ kubectl create -f hello.yaml
workflow "hello-7wlnt" created

# Podが作成され、ワークフローが実行されている
$ kubectl get pod hello-7wlnt
NAME          READY     STATUS      RESTARTS   AGE
hello-7wlnt   0/2       Completed   0          5m

# 実行内容を見てみる（コンテナ名はmainとなる）
$ kubectl logs -c main hello-7wlnt
 _____________
< hello world >
 -------------
    \
     \
      \
                    ##        .
              ## ## ##       ==
           ## ## ## ##      ===
       /""""""""""""""""___/ ===
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
       \______ o          __/
        \    \        __/
          \____\______/

```

k8sと同じ感覚で使えてとっても簡単ですね。
また上の例では`kubectl`のみでワークフローが実行できており、既存のエコシステムとの連携も簡単そうです。

Webコンソールで結果をみるとこんな感じです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/62029/dddd2642-91e4-a077-6a08-c6ddb2b3068b.png)



## どんなワークフローを定義ができる？（v2.1.0-beta2）

それではもう少しどのようなワークフローが定義できるか見ていきます。

公式のドキュメントはまだ少ないのですが、サンプルと合わせて読み取った機能を一問一答の方式で書きます。

#### 処理ごとにパラメーターの変更はできる？
- `spec.arguments.paramters`で定義したパラメーターを`argo submit --parameters`で指定できるよ。

#### CPUとMemoryの指定はできる？
- k8sと同じように`resources`で指定できるよ。

#### Gitからコードをチェックアウトできる？
- `spec.templates[].inputs.artifacts[].git`で指定できるよ。

#### 同じワークフローの中でファイルデータの共有はできる？
- k8sの`Persistent Volume`を使って共有できるよ。

#### 同じワークフローの中でパラメーターの引渡しはできる？
- パラメーターは`spec.templates[].outputs.parameters[].(name|globalName)`で引き渡せるよ。

#### 定期実行はできる？
- 現状はできないけど、k8sの`CronJob`で実現できると思うよ。

#### （Source）入力のソースとしては何が使える？
- Git、HTTP、S3が標準では用意されているよ。
    - GCSもS3互換APIを有効にすれば対応できるよ。

#### （Sink）アーティファクトの保存には何が使える？
- S3が標準では用意されているよ。
    - GCSもS3互換APIを有効にすれば対応できるよ。

#### 既存の監視設定に組み込みたいから、Podにラベルをつけたいけどできる？
- `metadata`の情報としてアノテーションとラベルを付与できるよ。

#### ワークロードが違うから実行するノードも分けたい。Argoで実行するPodってノードセレクターを指定できる？
- `spec.templates.nodeSelector`が使えるよ。

#### 並列に実行したいけどできる？
- もちろんできるし、`spec.parallelism`で並列数の上限も指定できるよ。

#### フローを分岐できる？
- `when`を使って処理をスキップできるから、擬似的に分岐できるよ。
- `when`の条件には終了ステータスコード、標準出力の内容などが使えるよ。

#### Slackに通知できる？
- ネイティブの機能はないので、自分で用意する必要があるよ。
- `curl`を使ってSlackに通知できるので簡単に実現できるよ。
- メールに通知したい場合は`Sendgrid`のようなサービスを使えば簡単に実現できるよ。

#### ワークフローの全体の処理結果によって通知先を変更ってできる？
- `spec.onExit`を使えばできるよ。

#### 失敗した処理を再実行できる？
- `spec.templates[].retryStrategy`で指定できるよ。
- 回数制限もできるし、成功するまで実行することもできるよ。

#### k8sの`Secret`って読み込める？
- 環境変数、またはファイルとして参照できるよ。

#### 実行するワークフローに必要なデーモンがあるんだけど、通信させる方法はある？
- `Sidecar`もできるし、`Daemon`の実行もできるからできるよ。

#### Dockerイメージをビルドってできる？
- Docker in docker（sidecar）できるよ。
- [kaniko](https://github.com/GoogleContainerTools/kaniko)を使ってもいいかもね。

#### 既存の`Persistent Volume`って参照できる？
- できるよ。

#### タイムアウトって設定できる？
- できるよ。

#### CI/CDとして利用できる？
- 大きなトピックになりそうだから、次の章で！！

## CI/CDはできるのか

> Continuous integration is a popular appication for workflows. Currently, Argo does not provide event triggers for automatically kicking off your CI jobs, but we plan to do so in the near future. Until then, you can easily write a cron job that checks for new commits and kicks off the needed workflow, or use your existing Jenkins server to kick off the workflow.

Argoのドキュメントに書かれている通り、まだトリガー機能が実装されていないので**Argo Workflow単体では実現できません**。

JekinsやBotkitなどでトリガーを実装するか、[Argo-CI](https://github.com/argoproj/argo-ci)、[Argo-CD](https://github.com/argoproj/argo-cd)を使ってみるのもありだと思います。
`Argo-CI`と`Argo-CD`は名前の通りまさにCIとCDをするためのツールです。
ただし`Argo-CI`は欠けているトリガー機能を提供してくれますが、`ARLY DEVELOPMENT`の状態です。
また`Argo-CD`は`ksonnet`を使うことが前提のため、既存システムによっては使いにくいツールかなと感じています（将来的に分離して欲しいな）。

## これからと期待
v2.1ではDAGモデルもサポートされます。
これでワークフローエンジンとしては便利に使えると思いますが、ワークフローの定義内容がGUIからは確認できなため、事前定義や再利用の機能が入ればいいなと思っています。

また次のコミュニティミーティングでは以下のプロジェクトについて話し会われるようです。

- Argo Workflows
- Argo CI
- Argo CD
- Argo Events

CI/CDツールとして使える日が近づきそうです！

## おまけ

### インストール手順（for MAC）
- kubernetes: v1.9.6(Docker for MAC)
    - Namespace: `kube-system`にArgoのコンポーネントがインストールされる
- Argo: v2.1.0-beta2

```shell
# インストールパスの適当に設定
$ install_path=/usr/local/bin

# バージョンを指定
$ version=2.1.0-beta2

# バイナリをダウンロード
# brewであればstableなバージョンをインストールできます
# brew install argoproj/tap/argo
$ curl -sSL -o ${install_path}/argo https://github.com/argoproj/argo/releases/download/v${version}/argo-darwin-amd64

# 実行権限を追加
$ chmod +x ${install_path}/argo

# バイナリインストール確認
$ argo version
argo: v2.1.0-beta2
  BuildDate: 2018-03-29T20:49:33Z
  GitCommit: fe23c2f651a61a2d7aa877a86edff9802d7b5b47
  GitTreeState: clean
  GitTag: v2.1.0-beta2
  GoVersion: go1.9.3
  Compiler: gc
  Platform: darwin/amd64

# インストール
$ argo install
Installing Argo v2.1.0-beta2 into namespace 'kube-system'
Proceeding with Kubernetes version 1.9.6
CustomResourceDefinition 'workflows.argoproj.io' created
ServiceAccount 'argo' created
ClusterRole 'argo-cluster-role' created
ClusterRoleBinding 'argo-binding' created
Deployment 'workflow-controller' created
ServiceAccount 'argo-ui' created
ClusterRole 'argo-ui-cluster-role' created
ClusterRoleBinding 'argo-ui-binding' created
Deployment 'argo-ui' created
Service 'argo-ui' created

# インストール確認
$ kubectl -n kube-system get pod
NAME                                         READY     STATUS    RESTARTS   AGE
argo-ui-b94f57cc5-lc7g8                      1/1       Running   0          1h
...
workflow-controller-6ff45f57b7-b4s42         1/1       Running   0          1h
...

# argo-uiにアクセスできるようにserviceの内容を変更
# アクセスできれば何でもOK
$ kubectl patch svc argo-ui -n kube-system -p '{"spec":{"$setElementOrder/ports":[{"port":8001}],"ports":[{"port":8001,"protocol":"TCP","targetPort":8001},{"$patch":"delete","port":80}],"type":"LoadBalancer"}}'

# ポート確認
$ kubectl get svc argo-ui -n kube-system -o yaml
kubectl get svc argo-ui -n kube-system
NAME      TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
argo-ui   LoadBalancer   10.105.20.7   localhost     8001:30780/TCP   3h

# Webコンソールにアクセス
$ open http://localhost:8001/

# 試しに実行してみる
$ argo submit https://raw.githubusercontent.com/argoproj/argo/master/examples/hello-world.yaml
Name:                hello-world-svlpd
Namespace:           default
ServiceAccount:      default
Status:              Pending
Created:             Mon Apr 23 16:35:04 +0900 (29 seconds from now)

# 実行コンテナの内容確認
$ argo list
NAME                STATUS      AGE    DURATION
hello-world-svlpd   Succeeded   16s    5s

# 実行内容確認
$ argo get hello-world-svlpd
Name:                hello-world-svlpd
Namespace:           default
ServiceAccount:      default
Status:              Succeeded
Created:             Mon Apr 23 16:35:04 +0900 (3 seconds from now)
Started:             Mon Apr 23 16:35:04 +0900 (3 seconds from now)
Finished:            Mon Apr 23 16:35:09 +0900 (8 seconds from now)
Duration:            5 seconds

# 実行ログの内容確認
$ argo logs hello-world-svlpd
 _____________
< hello world >
 -------------
    \
     \
      \
                    ##        .
              ## ## ##       ==
           ## ## ## ##      ===
       /""""""""""""""""___/ ===
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
       \______ o          __/
        \    \        __/
          \____\______/


```