Argo workflow を動かしてみた

Argo workflow の言葉のイメージから、人と人が連携して仕事を熟すためのツールと間違えそうだが、そうではなく、コンテナ化されたバッチアプリケーションのジョブコントロールシステムと言っても良いと思う。このOSSの実態を掴むために、軽く動かして見ただけだけど、これは良いツールという印象だった。


## Argo workflow のインストール

#### K8sクラスタのセットアップ

Argo workflowは、コンテナ化されたバッチジョブのステップを実行するので、コンテナのエクゼキューターを指定できるようになっている。
デフォルトの設定では、dockerになっており、ソケットのパスが決まっている。 これらをdocker以外に変更しても動くが、[成果物データをストレージに保存出来ない](https://github.com/argoproj/argo/issues/1667)IssueがOpenとなっている。しかし、dockerと言っても、containerdをアクセスするだけのようなので、ソケットのパスが解れば解決するかもしれない。 

~~~yaml:workflow-controller-configmap.yaml
    # Specifies the container runtime interface to use (default: docker)
    # must be one of: docker, kubelet, k8sapi, pns
    containerRuntimeExecutor: docker

    # Specifies the location of docker.sock on the host for docker executor (default: /var/run/docker.sock)
    # (available since Argo v2.4)
    dockerSockPath: /var/someplace/else/docker.sock
~~~

クラウドの環境で動かすのは、いったん保留として、パソコン環境下で Kubernetes を動かして、Argo workflow を動かすことにした。普段は触りたくないが、こういう時は、自由自在に変更できる アップストリームの Kubernetes環境は重宝する。

GitHubからクローンするが、バージョン 1.16 のブランチを使用する。いったん、Vagrantfile を編集して、ワーカーノードのコア数を 4コア、メモリを 8GB する。それから、コンテナのランタイムエンジンを docker に変更する。 リソースの増強は、パソコンにリソースがあればの話であるが....

~~~
$ git clone -b 1.16 https://github.com/takara9/vagrant-kubernetes k8s-1.16
$ cd k8s-1.16
$ vi Vagrantfile
~~~

以下の node1 と node2 となる行の cpu: 4, memory: 8192 が変更箇所 

~~~Vagrantile抜粋
vm_spec = [
  { name: "master", cpu: 2, memory: 2048,
    box: linux_os,
    private_ip: "172.16.20.11",
    public_ip: "192.168.1.91",
    storage: [], playbook: "install_master.yml",
    comment: "Master node" },

  { name: "node1", cpu: 4, memory: 8192,
    box: linux_os,
    private_ip: "172.16.20.12",
    public_ip: "192.168.1.92",
    storage: [], playbook: "install_node.yml",
    comment: "Worker node #1" },

  { name: "node2", cpu: 4, memory: 8192,
    box: linux_os,
    private_ip: "172.16.20.13",
    public_ip: "192.168.1.93",
    storage: [], playbook: "install_node.yml",
    comment: "Worker node #2" },
]
~~~

それから、Ansible playbook の以下の部分で、コンテナエンジンを docker-ce にする。

~~~yaml:k8s-1.16/playbook/kubernetes/defaults/main.yml抜粋
# どちらか一方を選んで、有効化してください。
# Chose either one and enable it. docker-ce or containerd
container_engine: docker-ce
#container_engine: containerd
~~~

`vagrant up` してから、起動後に、マスターノードにログインして、以下の結果を得られたら K8sクラスタの起動は成功である。

~~~console:
maho:k8s-1.16 maho$ vagrant ssh master
<中略>
vagrant@master:~$ kubectl get node
NAME     STATUS   ROLES    AGE   VERSION
master   Ready    master   18m   v1.16.8
node1    Ready    <none>   15m   v1.16.8
node2    Ready    <none>   12m   v1.16.8
~~~


#### Argo workflow のインストール

マスターノードで引き続き、Argo workflow のインストールを実施する。 名前空間 argo を作成して、その名前空間にインストールする。

~~~
$ kubectl create ns argo
$ kubectl apply -n argo -f https://raw.githubusercontent.com/argoproj/argo/stable/manifests/quick-start-postgres.yaml
~~~

これは Argo workflow のクイックインストール用の構成ファイルで、UIを提供する argo-server と バッチジョブのコントロールを担う workflow-controller を起動するだけでなく、データベース postgress と 実効結果の成果物データを保存するオブジェクトストレージ minio を起動してくれる。 クラウドで動かしていれば、クラウドのデータベースサービスとオブジェクトストレージに繋ぐのだが、今回は、オブジェクトストレージだけ AWS S3 を利用ことにする。次は、起動直後のポッドの起動状況である。

~~~
$ kubectl get pod -n argo
NAME                                  READY   STATUS    RESTARTS   AGE
argo-server-787995cbf5-f7v2c          1/1     Running   3          2m8s
minio                                 1/1     Running   0          2m8s
postgres-69559bd989-9krjj             1/1     Running   0          2m8s
workflow-controller-f5958489f-gfk9x   1/1     Running   3          2m8s
~~~

#### ArgoのCLIコマンドのインストール

本格的に活用する時には、CLIで操作できるのが便利だと思うので、インストールしておく。

~~~
$ curl -sLO https://github.com/argoproj/argo/releases/download/v2.9.2/argo-linux-amd64
$ chmod +x argo-linux-amd64
$ sudo mv ./argo-linux-amd64 /usr/local/bin/argo
$ argo version
argo: v2.9.2
  BuildDate: 2020-07-08T23:54:33Z
  GitCommit: 65c2bd44e45c11e0a0b03adeef8d6800b72cd551
  GitTreeState: clean
  GitTag: v2.9.2
  GoVersion: go1.13.4
  Compiler: gc
  Platform: linux/amd64
~~~


#### CLIコマンドからのジョブのサブミット

Argo workflow の構成ファイルを指定して、サブミットすれば、バッチ処理を実行して、結果をオブジェクトストレージに保存してくれる。
[Kubernetes Job](https://kubernetes.io/docs/concepts/workloads/controllers/job/) を本格的に利用する場合には、いろいろなツールを手作りする必要があったが、Argo workflow は便利である。

ジョブ（ワークフロー）のサブミット

~~~
$ argo submit -n argo --watch https://raw.githubusercontent.com/argoproj/argo/master/examples/hello-world.yaml
~~~

結果表示

~~~
$ argo list -n argo
NAME                STATUS      AGE   DURATION   PRIORITY
hello-world-qbsfr   Succeeded   1m    42s        0

$ argo get -n argo @latest
Name:                hello-world-qbsfr
Namespace:           argo
ServiceAccount:      default
Status:              Succeeded
Conditions:          
 Completed           True
Created:             Sun Jul 12 13:54:39 +0000 (1 minute ago)
Started:             Sun Jul 12 13:54:39 +0000 (1 minute ago)
Finished:            Sun Jul 12 13:55:21 +0000 (58 seconds ago)
Duration:            42 seconds
ResourcesDuration:   22s*(1 cpu),22s*(100Mi memory)

STEP                  TEMPLATE  PODNAME            DURATION  MESSAGE
 ✔ hello-world-qbsfr  whalesay  hello-world-qbsfr  40s         
~~~

次はログ表示で、これは、オブジェクトストレージにも保存される。

~~~
$ argo logs -n argo @latest
hello-world-qbsfr: 2020-07-12T13:55:19.44861898Z  _____________ 
hello-world-qbsfr: 2020-07-12T13:55:19.448653193Z < hello world >
hello-world-qbsfr: 2020-07-12T13:55:19.4486569Z  ------------- 
hello-world-qbsfr: 2020-07-12T13:55:19.448659054Z     \
hello-world-qbsfr: 2020-07-12T13:55:19.448661275Z      \
hello-world-qbsfr: 2020-07-12T13:55:19.448663282Z       \     
hello-world-qbsfr: 2020-07-12T13:55:19.448665338Z                     ##        .            
hello-world-qbsfr: 2020-07-12T13:55:19.448667331Z               ## ## ##       ==            
hello-world-qbsfr: 2020-07-12T13:55:19.448669252Z            ## ## ## ##      ===            
hello-world-qbsfr: 2020-07-12T13:55:19.448671178Z        /""""""""""""""""___/ ===        
hello-world-qbsfr: 2020-07-12T13:55:19.448673284Z   ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~   
hello-world-qbsfr: 2020-07-12T13:55:19.448675217Z        \______ o          __/            
hello-world-qbsfr: 2020-07-12T13:55:19.448677205Z         \    \        __/             
hello-world-qbsfr: 2020-07-12T13:55:19.448679348Z           \____\______/   
~~~

## 書き込み先を minio から AWS S3 へ変更

AWS S3 にバケットを作成して、アクセス権を付与して、結果の書き込みができるようにする。

~~~
$ export mybucket=argo-tkr-bucket249
$ cat > policy.json <<EOF
> {
>    "Version":"2012-10-17",
>    "Statement":[
>       {
>          "Effect":"Allow",
>          "Action":[
>             "s3:PutObject",
>             "s3:GetObject"
>          ],
>          "Resource":"arn:aws:s3:::$mybucket/*"
>       }
>    ]
> }
> EOF
~~~

バケットの作成

~~~
$ aws s3 mb s3://$mybucket
make_bucket: argo-tkr-bucket249
~~~

権限の付与

~~~
$ aws iam create-user --user-name $mybucket-user
$ aws iam put-user-policy --user-name $mybucket-user --policy-name $mybucket-policy --policy-document file://policy.json
$ aws iam create-access-key --user-name $mybucket-user > access-key.json
~~~

アクセスキーとシークレットキーの表示

~~~
$ cat access-key.json 
{
    "AccessKey": {
<以下省略>
~~~

これらは、以下の設定ファイルには、オブジェクトストレージをアクセスするためのシークレット my-minio-cred が設定されているので、my-s3-cred を作って参照先を変更する。

~~~
$ kubectl get configmap -n argo
NAME                            DATA   AGE
artifact-repositories           1      9h
workflow-controller-configmap   4      9h
~~~

それから、エンドポイント、バケット名、リージョンもセットする。minioではTLSが使わないが、S3は暗号化されるので、それに合わせて変更もしておく。

~~~yaml:workflow-controller-configmap抜粋
    artifactRepository:
      archiveLogs: true

      s3:
        endpoint: s3.amazonaws.com
        bucket: bucket-tkr-9949
        region: us-west-2
        insecure: false
<中略>
        accessKeySecret:
          name: my-s3-cred
          key: accesskey
        secretKeySecret:
          name: my-s3-cred
          key: secretkey
~~~

それぞれのコンフィグマップは適用するだけで、それぞれのポッドは、新しい設定で動作するので、特にポッドの再起動は必要ない。

それから、参照先のシークレットも作成する。

~~~yaml:my-s3-cred.yaml 
apiVersion: v1
kind: Secret
metadata:
  labels:
    app: s3
  name: my-s3-cred
stringData:
  accesskey: access-key.jsonから該当の値をコピペ
  secretkey: 同上
type: Opaque
~~~

これで、オブジェクトストレージ minio から S3 への切り替えが完了した。


## Argo UI のアクセス

本格的に動かすならば、SSOや認証環境の考慮も必要であるが、パソコンの中で完結して動かすので、認証なしで簡単にアクセスできるようにする。

次のコマンドで、実行中のオブジェクトを直接編集して、nodeport としてアクセスできるようにする。

~~~console:
$ kubectl edit svc argo-server -n argo
~~~

編集箇所は、2つで、nodePort の行を追加、typeの値を NodePort へ変更する。

~~~yaml:
spec:
  clusterIP: 10.32.0.124
  externalTrafficPolicy: Cluster
  ports:
  - name: web
    nodePort: 30007
    port: 2746
    protocol: TCP
    targetPort: 2746
  selector:
    app: argo-server
  sessionAffinity: None
  type: NodePort
~~~

これで、Argo workflow のブラウザ画面を表示できるようになった。

## ブラウザ画面のアクセス

サービスをNodePortで公開したので、マスターノードやワーカーノードのIPアドレス:30007 でブラウザ画面を開ける。

http://172.16.20.12:30007 にアクセスすれば、以下のワークフローの結果リストにアクセスできる。

![workflow-top.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/1b8efb7b-c013-ec66-8a80-0158e35e3b82.png)


この画面の左上、「＋SUBMIT NEW WORKFLOW」からサンプルのワークフロー構成ファイルを指定してジョブを実行できる。サンプルは、
https://github.com/argoproj/argo/tree/master/examples に沢山あるので、試してみると良い。

## ワークフローの実行結果

ワークフローを確認してみる。最初に実行した Hello-world は、タスクが一つしか無いので、一個だけが表示されている。このミドリのアイコンをクリックすると、右側に概要、コンテナ、出力成果物を選択して参照することができる。

![workflow-job-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/24b6765c-4306-a6c5-dd32-2ac2db1d2280.png)


次のステップが沢山あり並列処理があるサンプルも見ておく。

![workflow-job-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/b4d15787-4978-a276-98a8-1052cd8025a9.png)

このジョブの構成ファイルが次である。ここでは、A,B,C,D のタスクがネストされて実行されている。
テンプレート、依存関係、受け渡す値などが表現されている。

~~~yaml:dag-nested.yaml
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: dag-nested-
spec:
  entrypoint: diamond
  templates:
  - name: echo
    inputs:
      parameters:
      - name: message
    container:
      image: alpine:3.7
      command: [echo, "{{inputs.parameters.message}}"]
  - name: diamond
    dag:
      tasks:
      - name: A
        template: nested-diamond
        arguments:
          parameters: [{name: message, value: A}]
      - name: B
        dependencies: [A]
        template: nested-diamond
        arguments:
          parameters: [{name: message, value: B}]
      - name: C
        dependencies: [A]
        template: nested-diamond
        arguments:
          parameters: [{name: message, value: C}]
      - name: D
        dependencies: [B, C]
        template: nested-diamond
        arguments:
          parameters: [{name: message, value: D}]
  - name: nested-diamond
    inputs:
      parameters:
      - name: message
    dag:
      tasks:
      - name: A
        template: echo
        arguments:
          parameters: [{name: message, value: "{{inputs.parameters.message}}A"}]
      - name: B
        dependencies: [A]
        template: echo
        arguments:
          parameters: [{name: message, value: "{{inputs.parameters.message}}B"}]
      - name: C
        dependencies: [A]
        template: echo
        arguments:
          parameters: [{name: message, value: "{{inputs.parameters.message}}C"}]
      - name: D
        dependencies: [B, C]
        template: echo
        arguments:
          parameters: [{name: message, value: "{{inputs.parameters.message}}D"}]
~~~


## まとめ

Argo workflow の言葉のイメージから、人と人が連携して仕事を熟すためのツールと間違えそうだが、ジョブコントロールシステムと言っても良いと思う。いろいろ課題もあるが、深層学習などのデータ前処理、学習処理などにも便利なツールであるが、バッチ中心の業務システムなどでも使えそうに思う。


# 参考資料
* チュートリアル https://github.com/argoproj/argo/blob/master/docs/quick-start.md
* ワークフローのサンプル, https://argoproj.github.io/argo/examples/
* インストール, https://argoproj.github.io/argo/installation/
* https://github.com/argoproj/argo
* https://blog.argoproj.io/artifacts-management-in-container-native-workflows-part-1-f6efa838666b