Argo Events と Workflow の連携

# Argo Events と Workflow の連携

Argo Eventは、GitHub への git push によって発信される WebHook を受けて、Argo Workflow などへアクションを実行することができる。その連携について検証してみる。

アルゴイベントのアーキテクチャは以下のようになる。ゲートウェイとセンサーのコントローラーからなり、これらのコントローラーに、イベント源に対応したイベントソース、ゲートウェイ、センサーの3つのマニフェストを適応する。そうすることで、対応するポッドを起動する。 ゲートウェイコントローラーによって起動されるポッドでイベントを受ける。その後、ゲートウェイは Cloud Events の形式に変換して、センサーコントローラーから起動されるポットへイベントが伝達され、そのテンプレートに記述されたスペックで、Argoワークフローが開始する。

![screenshot-argo-events.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/2ba99cba-ec42-f915-2957-cf506ef0acc6.png)


ワークフローとイベントを分離することで、イベントを様々な処理に繋ぐことができるので、無限に拡張することができる。


## Argo Event のインストール

Argo Event の設定変更を簡単にできるように、ローカル環境にクローンしておいて、インストールを実施する。インストールして実行できるリリースのコミットはタグに登録されている。そこで、`-b stable` を指定してクローンする。
このオプションを設定しないで、クローンもできるが、そうすると、開発途上の最新コードが落ちてくるので、動かないことも多々なので、開発に参加する人以外は、オプションの設定をお勧めする。

~~~console:
$ git clone -b stable https://github.com/argoproj/argo-events
~~~

名前空間 argo-events を設定してインストールを実施する。名前空間に閉じて利用するパターンと、Kubernetesクラスタ ワイドにインストールするパターンを選択できる。ここではクラスタワイドに使用可能なパターンでインストールする。 

~~~console:
$ kubectl create ns argo-events
$ kubectl apply -f install.yaml -n argo-events
~~~

しばらくすると、二つのコントローラーが起動する。これで Argo Event を利用するためのインストールは完了だけど、これだけでは動かない。これからが長いので頑張ろう！

~~~console:
$ kubectl get pod -n argo-events
NAME                                  READY   STATUS    RESTARTS   AGE
gateway-controller-7b88d47999-b5rwv   1/1     Running   0          2m7s
sensor-controller-6df5cd5d86-svvq5    1/1     Running   0          2m7s
~~~

Argo Event は、Gatwayで イベント源からの何らかなの通知を受け取り、Cloud Event 形式に変換、Sensor で実行をリクエストするのが役割だ。そのため、実行をリクエストする部分がセンサーというのは違和感を感じる。今回は Argo workflow と連携させる。



## Argo workflow のインストール

Argo workflow のインストールは、過去二つの記事を書いた。一つ目の記事は、Docker CE をコンテナランタイムに持つ Kubernetesクラスタの上で動かすもので、執筆時点では、この構成がArgo workflowのフル機能が使える。しかし、containerd や cri-o がコンテナランタイムの主流となっている現在は、それでは本番システムで使えないので、２番目の記事を書いた。今回は２番目の記事をベースに、Argo Events と Argo Workflow を連携させる。

* [Argo workflow を動かしてみた](https://qiita.com/MahoTakara/items/9ca1e9bb4d13c06d5090)
* [IBM Cloud で Argo workflow を動かす時の課題とOperatorによる対処法](https://qiita.com/MahoTakara/items/a2d4e041cf3b6a295869)

そこで、PostgreSQLサーバーとオブジェクトストレージが必要になる。 PostgreSQLはオペレーターを利用して起動し、オブジェクトストレージは AWS S3 または IBM Cloud オブジェクトストレージを利用する。


## Postgres オペレータを利用した PostgresSQLのサービス環境構築

最初に、Postgres専用の名前空間(namespace)を作成しておく。

~~~console:
$ kubectl create ns postgres
~~~

[オペレータハブ](https://operatorhub.io/)に登録されているデータベースのオペレーターから、[Postgres-Operator](https://operatorhub.io/operator/postgres-operator)を利用することにした。
どれが一番良いか解らないので、他のオペレーターを試した人は、是非、コメント蘭に使用報告を入れてもらえるとありがたい。

~~~console:
$ git clone https://github.com/zalando/postgres-operator
~~~

PostgresSQLは、専用の名前空間 postgres で管理したいので、オペレーターのマニフェストに変更を加える。そのためにマニフェストのあるディレクトリへ移動する。

~~~console:
$ cd postgres-operator/manifests
~~~

名前空間(Namespace)を変更を自動化するために、kustomization.yamlに変更を加える。
リソース(resources)の次に 'namespace:' を追加する。 これによって、リソース対象にファイルに、名前空間の指定があった場合、自動的に置き換えてくれる。

~~~yaml:kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- configmap.yaml
- operator-service-account-rbac.yaml
- postgres-operator.yaml
- api-service.yaml

namespace: postgres
~~~

Kustomization APIを利用するために `-k` でデプロイする。 ほんとに Kustomizeation は便利で良いね。

~~~console:
$ kubectl create -k .
~~~

デプロイ後の実行状態を確認しておく。これも `-k` オプションを利用する。

~~~console:
maho:manifests maho$ kubectl get -k .
NAME                               SECRETS   AGE
serviceaccount/postgres-operator   1         37s

NAME                                                      AGE
clusterrole.rbac.authorization.k8s.io/postgres-operator   38s
clusterrole.rbac.authorization.k8s.io/postgres-pod        38s

NAME                                                             AGE
clusterrolebinding.rbac.authorization.k8s.io/postgres-operator   38s

NAME                          DATA   AGE
configmap/postgres-operator   39     38s

NAME                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/postgres-operator   ClusterIP   172.21.130.241   <none>        8080/TCP   38s

NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/postgres-operator   1/1     1            1           38s
~~~

以上で PostgreSQL をサービスのように利用できるオペレーターの環境ができた。
データベースインスタンスの作成は、Argo workflow のインスタンス起動と合わせて実施する。


## kubectl デフォルトのコンテキストの変更

名前空間 argo-events をデフォルトにするために、コンテキスト argo-events を設定する。

~~~console:
$ kubectl config get-contexts | grep `kubectl config current-context`
*         default   k8s-vpc-jp/brvsrolt0sihofiav90g   takara@jp.ibm.com/753c62201245d3d333a3033556c66d71/iam.cloud.ibm.com-identity   default

$ kubectl config set-context argo-events --namespace=argo-events --cluster=k8s-vpc-jp/brvsrolt0sihofiav90g --user=takara@jp.ibm.com/753c62201245d3d333a3033556c66d71/iam.cloud.ibm.com-identity
~~~

これで `use-context` でデフォルトを切り替えられる。

~~~console:
$ kubectl config use-context argo-events
$ kubectl config current-context
argo-events
~~~

## Argo Workflow のマニフェスト取得

Arog Workflow の動作する名前空間を argo から　argo-events に変更すること、Kubernetes クラスタ内の PostgreSQL と接続すること、クラウドのオブジェクトストレージに結果を保存することから、インストールするマニフェストを変更する。そのため、Argo Workflow リリース v2.9.3 のコードをクローンして編集するが、この記事で紹介するのは大変なので、編集済のコードをGitHubに登録しておいた。

~~~console:
$ git clone -b v2.9.3 https://github.com/argoproj/argo
~~~

そして、このソースツリーの中かから `argo/manifests/quick-start-postgres.yaml`を元にして、作成したマニフェスト https://github.com/takara9/argo-workflow を使用する。

~~~console:
$ git clone https://github.com/takara9/argo-workflow
~~~

この中から、icos-cred.yaml と postgresql-cred.yaml を作成して、`kubectl apply -k .`コマンドでデプロイする。


## オブジェクトストレージの設定

クラウドで、オブジェクトストレージを作成して、パブリックのエンドポイント、バケット、アクセスキー、シークレットキーを取得する。
次のYAMLファイルを argo-workflow ディレクトリに追加して、`accesskey` と `secretkey` にセットする。

~~~yaml:icos-cred.yaml 
apiVersion: v1
kind: Secret
metadata:
  labels:
    app: argo
  name: my-icos-cred
stringData:
  accesskey: 20a**************************139
  secretkey: 090******************************************b12
type: Opaque
~~~

オブジェクトストレージのバケットとエンドポイントを artifact-repositories-configmap.yaml に設定する。acceessKerySecretは以下の項目は、上記でシークレットに保存するので、変更してはいけない。

~~~yaml:artifact-repositories-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: artifact-repositories
data:
  minio: |
    s3:
      endpoint: s3.jp-tok.cloud-object-storage.appdomain.cloud  <--- エンドポイントURL
      bucket: argo-workflow-artifacts <-- バケット名
      insecure: false
      accessKeySecret:
        name: my-icos-cred
        key: accesskey
      secretKeySecret:
        name: my-icos-cred
        key: secretkey

~~~


## データベースの設定

オペレーター[Postgres-Operator](https://operatorhub.io/operator/postgres-operator)に指示を与え、PostgreSQLのサーバーを起動するためのマニフェストを作成する。このファイルは argo-workflowのディレクトリに postgresql-cluster.yamlとして作成する。

これを適用することで、二つのインスタンスと対応する永続ボリュームがプロビジョニングされるので、永続ボリュームの利用料金が発生するので注意して欲しい。このマニフェストのポイントとして、`patroni` というキーの項目下にある `pg_hba` がある。これは接続を許すクライアントのリストを設定するもので、これが無いとサーバーに接続できないので注意だ。

~~~yaml:postgresql-cluster.yaml
apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
  name: postgresql-cluster
spec:
  databases:
    argodb: argo
  numberOfInstances: 2
  postgresql:
    version: '11'
  teamId: POSTGRESQL
  users:
    argodb_user: []
    argo:
      - superuser
      - createdb
  volume:
    size: 5Gi
  patroni:
    pg_hba:
      - host all all 0.0.0.0/0 md5
~~~

編集が終わったら適用する。

~~~console:
$ vi postgresql-cluster.yaml
$ k apply -f postgresql-cluster.yaml -n postgres
~~~

しばらくすると、PostgreSQLサーバーのポッドが起動する。

~~~console:
$ k get pod -n postgres 
NAME                                 READY   STATUS    RESTARTS   AGE
postgres-operator-85978df8cf-xjvkf   1/1     Running   0          7m15s
postgresql-cluster-0                 1/1     Running   0          4m9s
postgresql-cluster-1                 1/1     Running   0          2m5s
~~~

パスワードは、以下の方法で取得できるので、メモっておく。

~~~console:
$ export PGPASSWORD=$(kubectl get secret -n postgres postgres.postgresql-cluster.credentials -o 'jsonpath={.data.password}' | base64 -d)
$ echo $PGPASSWORD
hlqEyuVdlGi7fh096MUp84Oe9qgy0U56ptodyjFLKRhuhXv2PmeOjuf2mbxXJGld
~~~

次に PostgreSQLの認証情報をシークレットに保存するマニフェストを作成する。

~~~yaml:postgresql-cred.yaml
apiVersion: v1
kind: Secret
metadata:
  name: argo-postgres-config
  labels:
    app: argo
stringData:
  username: postgres
  password: hlqEyuVdlGi7fh096MUp84Oe9qgy0U56ptodyjFLKRhuhXv2PmeOjuf2mbxXJGld
type: Opaque
~~~


## Argo Workflow のインストールと起動

`git clone https://github.com/takara9/argo-workflow` で作成されたディレクトリの kustomization.yaml に変更を加えて適用する。変更箇所は `namespace:`の値で、今回は名前空間 `argo-events`にデプロイするので、下記のように１行を加える。

~~~yaml:kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - workflow-controller-configmap.yaml
  - artifact-repositories-configmap.yaml
  - ServiceAccount_RBAC.yaml
  - icos-cred.yaml
  - postgresql-cred.yaml
  - CustomResourceDefinition.yaml
  - argo-server.yaml
  - workflow-controller-metrics.yaml

namespace: argo-events
~~~

以下のコマンドで適用する。しばらくすると、argoサーバーとworkflowコントローラーが起動する。

~~~console:
$ kubectl apply -k .
$ kubectl get po -w
NAME                                  READY   STATUS    RESTARTS   AGE
argo-server-7d7644fbd8-9vwjq          0/1     Running   0          6s
gateway-controller-7b88d47999-b5rwv   1/1     Running   0          67m
sensor-controller-6df5cd5d86-svvq5    1/1     Running   0          67m
workflow-controller-9ddc4456d-54bww   1/1     Running   0          6s
~~~

## Agro Workflow ウェブ画面の起動

上記のArgoサーバーのポッドのポート番号を指定して、プロキシを設定する。 本格的に動かす場合は、Ingress などの設定や認証を加えると良い。

~~~console:
$ kubectl -n argo-events port-forward argo-server-7d7644fbd8-9vwjq 2746:2746
Forwarding from 127.0.0.1:2746 -> 2746
Forwarding from [::1]:2746 -> 2746
~~~

これで`http://localhost:2746/` で Argo workflow のコンソールが起動される。動作確認として、hello-world.yamlをサブミットして結果を確認する。もし、この時点で問題があれば解決しておく。

~~~yaml:hello-world.yaml
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: hello-world-
  namespace: argo-events
spec:
  entrypoint: whalesay
  templates:
    - name: whalesay
      container:
        image: 'docker/whalesay:latest'
        command:
          - cowsay
        args:
          - hello world
~~~


## Argo Eventの動作テスト

Argo Eventで Webhook のイベントを受けて、Argo workflow へコードの実行をリクエストするには、次の3つのマニフェストを提供する必要がある。このマニフェストは `argo-events/examples` にある。

~~~console:
kubectl apply -f examples/event-sources/webhook.yaml
kubectl apply -f examples/gateways/webhook.yaml
kubectl apply -f examples/sensors/webhook.yaml
~~~

イベントソースには、エンドポイントのディレクトリとポート番号、HTTPリクエストを指定する。今回、event-sources/webhook.yaml は、特に修正する必要はない。

~~~yaml:event-sources/webhook.yaml
apiVersion: argoproj.io/v1alpha1
kind: EventSource
metadata:
  name: webhook-event-source
spec:
  type: webhook
  webhook:
    # gatewayは複数のHTTPサーバーを動作できる。
    example:
      # ポート 120000 で起動する
      port: "12000"
      # エンドポイントは /example に設定する
      endpoint: /example
      # 許可されるHTTP リクエスト
      method: POST
~~~

イベントソースで受けたリクエストはセンサーのURLへ中継する。この理由は Cloud Event に変換して、センサーへ渡すためである。このYAMLの最終行にセンサーのURLがある。名前空間を含むURLなので、必要に応じて修正する。

~~~yaml:gateways/webhook.yaml
apiVersion: argoproj.io/v1alpha1
kind: Gateway
metadata:
  name: webhook
spec:
  replica: 1
  type: webhook
  eventSourceRef:
    name: webhook-event-source
  template:
    serviceAccountName: argo-events-sa
  service:
    ports:
      - port: 12000
        targetPort: 12000
  subscribers:
    http:
      - "http://webhook-sensor.argo-events.svc:9300/"
~~~

次のマニフェストの中で、Argo Workflow にポッドを起動する時のサービスアカウントを変更する必要がある。無修正で argo-events-sa で実行した場合、ログを書き込めないため、ワークフローが失敗する。そこで、argo workflow で使用しているサービスアカウント default に変更する。 変更箇所は template の `k8s/source/spec/templates/[0]/name/serviceAccountName: default` である。

~~~yaml:sensors/webhook.yaml
apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
  name: webhook
spec:
  template:
    serviceAccountName: argo-events-sa
  dependencies:
    - name: test-dep
      gatewayName: webhook
      eventName: example
  subscription:
    http:
      port: 9300
  triggers:
    - template:
        name: webhook-workflow-trigger
        k8s:
          group: argoproj.io
          version: v1alpha1
          resource: workflows
          operation: create
          source:
            resource:
              apiVersion: argoproj.io/v1alpha1
              kind: Workflow
              metadata:
                generateName: webhook-
              spec:
                entrypoint: whalesay
                arguments:
                  parameters:
                  - name: message
                    # the value will get overridden by event payload from test-dep
                    value: hello world
                templates:
                - name: whalesay
                  serviceAccountName: default # <---  このサービスアカウントをdefaultに変更
                  inputs:
                    parameters:
                    - name: message
                  container:
                    image: docker/whalesay:latest
                    command: [cowsay]
                    args: ["{{inputs.parameters.message}}"]
          parameters:
            - src:
                dependencyName: test-dep
              dest: spec.arguments.parameters.0.value
~~~

この3つのマニフェストを適用すると、`webhook-` から始まる二つのポッドが起動する。

~~~console:
$ kubectl get po -w
NAME                                    READY   STATUS      RESTARTS   AGE
argo-server-7d7644fbd8-9vwjq            1/1     Running     0          10m
gateway-controller-7b88d47999-b5rwv     1/1     Running     0          77m
hello-world-fg4ls                       0/2     Completed   0          3m8s
sensor-controller-6df5cd5d86-svvq5      1/1     Running     0          77m
webhook-gateway-s86m5-78cc89b7f-b9n5v   2/2     Running     1          20s
webhook-sensor-prlq4-7875b484b6-4x74q   1/1     Running     0          19s
workflow-controller-9ddc4456d-54bww     1/1     Running     0          10m
~~~

このポッドにプロキシーを設定する。

~~~console:
kubectl -n argo-events port-forward webhook-gateway-s86m5-78cc89b7f-b9n5v 12000:12000
~~~

このアドレスに、curlでポストする。

~~~console:
curl -d '{"message":"this is my first webhook"}' -H "Content-Type: application/json" -X POST http://localhost:12000/example
~~~

~~~console:
$ kubeclt get wf
NAME                                STATUS      AGE
webhook-7dgnq                       Succeeded   15m
~~~

Argo workflow のコンソール `http://localhost:2746/` から 実行結果を確認することができる。

![screenshot-argo-wf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/8a4dcb80-a339-2671-ffe9-ed0a68769bdd.png)

以下はオブジェクトストレージに保存された実行結果である。

~~~console:
 _________________________________________ 
/ {"context":{"id":"39373962623637332d663 \
| 739352d346362632d386332332d616136653364 |
| 333138336230","source":"webhook","specv |
| ersion":"0.3","type":"webhook","dataCon |
| tentType":"application/json","subject": |
| "example","time":"2020-07-22T14:07:37Z" |
| },"data":"eyJoZWFkZXIiOnsiQWNjZXB0IjpbI |
| iovKiJdLCJDb250ZW50LUxlbmd0aCI6WyIzOCJd |
| LCJDb250ZW50LVR5cGUiOlsiYXBwbGljYXRpb24 |
| vanNvbiJdLCJVc2VyLUFnZW50IjpbImN1cmwvNy |
| 42NC4xIl19LCJib2R5Ijp7Im1lc3NhZ2UiOiJ0a |
\ GlzIGlzIG15IGZpcnN0IHdlYmhvb2sifX0="}   /
 ----------------------------------------- 
    \
     \
      \     
                    ##        .            
              ## ## ##       ==            
           ## ## ## ##      ===            
       /""""""""""""""""___/ ===        
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~   
       \______ o          __/            
        \    \        __/             
          \____\______/   
~~~


## まとめ

アルゴイベントとワークフローを連携させて動作させることに成功した。

本記事では、プロキシー設定でアルゴワークフローの画面を表示して、WebHookを受ける部分もプロキシーで代用した。本格的に動作させるには、アルゴワークフローのWebコンソールは、IngressやLoadBalancerを利用するのが良い。そして、アルゴワークフローの認証機能を有効化して併用するのが良いと思われる。また、アルゴイベントのイベントソースについても、GitHubと連携するには、TLS暗号が必要となるので、Ingressイングレスを併用して、GitHubからのWebHookを受けると良さそうだ。

次は、DevOpsパイプラインとして、GitHubと連動して、コンテナのビルド、テストKubernetes環境へのデプロイなどを検証してみたい。