Argo Workflowsをtanzuコマンドでインストールする

Argo Workflowsを`kind: Package`にしてtanzuコマンドからインストールする。\
以下の方針で進めていく。
- Ingressを利用し、Ingress ControllerはContourを利用する
- TLS証明書はcert-managerに任せる
- TLS証明書は自己署名証明書を利用
- デプロイ先はTanzu Kubernetes Grid

## 前提
以下がインストールされていること。
- [Carvelツール](https://carvel.dev/)(kapp, imgpkg, kbld, kapp-controllerを利用)
- tanzuコマンド

tanzuコマンドは[TCE版](https://github.com/vmware-tanzu/community-edition/releases)か製品版を利用する。
  
また、push/pullが可能なContainer Registryが必要となる。\
今回は自前で立てたHarborを利用した。

## ベースとなるManifestの作成
[こちら](https://argoproj.github.io/argo-workflows/quick-start/)のQuick StartだとIngressが含まれない。\
自作してもいいが、面倒なのでHelmからManifestを作成する。bitnami版とコミュニティ版があるが、コミュニティ版の方が更新頻度が高いため、今回はコミュニティ版を利用する。

まず最初に、helmのvalues.yamlを引き抜き、ベースとなるManifestを作りやすくするための値を設定する。

```
helm repo add argo https://argoproj.github.io/argo-helm
helm inspect values argo/argo-workflows > ./argo-workflow-helm-valus.yaml
```

helmのvalues.yamlを修正した結果がこちら。ServiceAccountの作成を有効にしたのと、Ingress周りを修正した。
```diff
--- argo-workflow-helm-valus.yaml.orig	2022-06-30 11:56:08.607490890 +0900
+++ argo-workflow-helm-valus.yaml	2022-06-30 12:21:44.990590637 +0900
@@ -29,7 +29,7 @@
   namespace:
   serviceAccount:
     # -- Specifies whether a service account should be created
-    create: false
+    create: true
     # -- Annotations applied to created service account
     annotations: {}
     # -- Service account which is used to run workflows
@@ -402,7 +402,7 @@
   # ref: https://kubernetes.io/docs/user-guide/ingress/
   ingress:
     # -- Enable an ingress resource
-    enabled: false
+    enabled: true
     # -- Additional ingress annotations
     annotations: {}
     # -- Additional ingress labels
@@ -413,8 +413,8 @@
     # -- List of ingress hosts
     ## Hostnames must be provided if Ingress is enabled.
     ## Secrets must be manually created in the namespace
-    hosts: []
-      # - argoworkflows.example.com
+    hosts:
+    - argoworkflows.example.com

     # -- List of ingress paths
     paths:
@@ -423,25 +423,25 @@
     # -- Ingress path type. One of `Exact`, `Prefix` or `ImplementationSpecific`
     pathType: Prefix
     # -- Additional ingress paths
-    extraPaths: []
+    extraPaths:
       # - path: /*
       #   backend:
       #     serviceName: ssl-redirect
       #     servicePort: use-annotation
       ## for Kubernetes >=1.19 (when "networking.k8s.io/v1" is used)
-      # - path: /*
-      #   pathType: Prefix
-      #   backend:
-      #     service
-      #       name: ssl-redirect
-      #       port:
-      #         name: use-annotation
+      - path: /*
+        pathType: Prefix
+        backend:
+          service:
+            name: ssl-redirect
+            port:
+              name: use-annotation

     # -- Ingress TLS configuration
-    tls: []
-      # - secretName: argoworkflows-example-tls
-      #   hosts:
-      #     - argoworkflows.example.com
+    tls:
+      - secretName: argoworkflows-tls
+        hosts:
+          - argoworkflows.example.com

   clusterWorkflowTemplates:
     # -- Create a ClusterRole and CRB for the server to access ClusterWorkflowTemplates.
```

作成したvalues.yamlを元に、Argo WorkflowのManifestを作成する。
```
helm template -f argo-workflow-helm-valus.yaml argo-workflows argo/argo-workflows --include-crds > ./argo-workflow.yaml
```
`--include-crds`を忘れないこと。

次に作成したManifestを書き換えるoverlay.yamlとパラメタ用values.yamlを作成する。\
Manifestの整理用に、一旦ディレクトリを作成して元のファイルを移動する。
```
mkdir -p package/config
mv argo-workflow.yaml package/config/
cd package/config/
```

## ytt overlayの作成＆動作確認
ベースとなるManifest(argo-workflow.yaml)を書き換えるoverlayを作成する。overlayで追加・修正する項目は以下とする。
- Namespace名の書き換え
- Host名の書き換え
- 証明書のためのIssuer、Certificateの追加
- Ingressにcert-managerを使うためのannotation追加

overlayのyamlは今回は分かりやすいよう分けて作る。\
最初にNamespaceの書き換えを実装する。
```yaml
cat <<'EOF' > ./overlay_namespace.yaml
#@ load("@ytt:overlay", "overlay")
#@ load("@ytt:data", "data")

apiVersion: v1
kind: Namespace
metadata:
 name: #@ data.values.namespace
---
#@overlay/match by=overlay.not_op(overlay.subset({"kind":"Namespace"})),expects="1+"
---
metadata:
 #@overlay/match missing_ok=True
 namespace: #@ data.values.namespace
---
#@overlay/match by=overlay.subset({"kind":"ClusterRoleBinding"}),expects="1+"
---
subjects:
#@overlay/match by="kind"
- kind: ServiceAccount
  namespace: #@ data.values.namespace
---
#@overlay/match by=overlay.subset({"kind":"RoleBinding"}),expects="1+"
---
subjects:
#@overlay/match by="kind"
- kind: ServiceAccount
  namespace: #@ data.values.namespace
EOF
```
最初にNamespaceを作り、Namespace以外のリソースのmetadataにnamespaceを埋め込んでいる。値はvalues.yamlから引っ張ってきたいので、ここではdataから引くようにしている。\
また、ClusterRoleBinding/RoleBindingの`subejects:`でnamespaceを参照している部分は見落としやすいため注意。

次にHost名の書き換えを実装する。
```yaml
cat <<'EOF' > ./overlay_hostname.yaml
#@ load("@ytt:overlay", "overlay")
#@ load("@ytt:data", "data")

#@overlay/match by=overlay.subset({"kind":"Ingress"})
---
spec:
  rules:
  #@overlay/match by=overlay.index(0)
  - host: #@ data.values.hostname
  tls:
  #@overlay/match by=overlay.index(0)
  - hosts:
    #@overlay/match by=overlay.index(0)
    #@overlay/replace
    - #@ data.values.hostname
EOF
```
こちらもHost名を外部から与えられるようdataから引く。

次に、TLS周りのoverlayを作成する。
```yaml
cat <<'EOF' > ./overlay_tls.yaml
#@ load("@ytt:overlay", "overlay")
#@ load("@ytt:data", "data")

apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: argo-workflow-selfsigned-issuer
  namespace: #@ data.values.namespace
spec:
  selfSigned: { }
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: argo-workflow-ca
  namespace: #@ data.values.namespace
spec:
  commonName: argo-workflow-ca
  isCA: true
  issuerRef:
    kind: Issuer
    name: argo-workflow-selfsigned-issuer
  secretName: argo-workflow-ca
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: argo-workflow-ca-issuer
  namespace: #@ data.values.namespace
spec:
  ca:
    secretName: argo-workflow-ca
---
#@overlay/match by=overlay.subset({"kind":"Ingress"})
---
metadata:
  #@overlay/match missing_ok=True
  #@overlay/match-child-defaults missing_ok=True
  annotations:
    cert-manager.io/issuer: "argo-workflow-ca-issuer"
EOF
```
cert-manager用のIssuer、Certificateの作成と、Ingressのannotationに利用するIssuerを指定した。

最後にvalues.yamlを作成する。
```yaml
cat <<'EOF' > ./values.yaml
#@data/values-schema
---
#@schema/desc "Namespace"
namespace: argo-workflow
#@schema/desc "Hostname"
hostname: argo-workflow.hogehoge.com
EOF
```

一旦このタイミングで作成したManifestが正常に動作するか確認する。
```
ytt -f ./ | kapp deploy -a argo-workflow -y -n default  -f -
```
ここでは一旦Ingressが起動するのと、TLS証明書関係のSecretが作成されたことまで確認する。
```
$ kubectl get ingress -n argo-workflow
NAME                    CLASS    HOSTS                        ADDRESS          PORTS     AGE
argo-workflows-server   <none>   argo-workflow.hogehoge.com   10.206.222.235   80, 443   50s
$ kubectl get secret -n argo-workflow |grep tls
argo-workflow-ca                                 kubernetes.io/tls                     3      112s
argoworkflows-tls                                kubernetes.io/tls                     3      112s
```
気になる人はブラウザでのアクセスもしてみると良い。\
動作確認が終わったら削除する。
```
kapp delete -a argo-workflow -y -n default
```


## `kind: Package`, `kind: PackageMetadata`,`kind: PackageRepository`の作成
tanzuコマンドで扱うためのプロセスは以下となる。

1. 先程作ったManifestをimage bundle化してContainer Registoryにupload
2. `kind: Package`, `kind: PackageMetadata`を作成してimage bundle化
3. `kind: PackageRepository`を作成してapply

3が完了した時点で、tanzuコマンドからArgo Workflowをインストール出来るようになる。

### image bundleのupload

まず、image bundle化してContainer Registoryに登録する。
image bundleは以下のような構成を期待する。
```
bundle/
├── .imgpkg/images.yml
└── config/
```
そのため、最初にディレクトリを作成する。
```
cd ..
mkdir .imgpkg
```
次にImagesLockリソースを.imgpkg/images.ymlに作成する。
```
ytt -f config/ | kbld -f - --imgpkg-lock-output .imgpkg/images.yml
```
コマンドが成功したら、以下のようなファイルが見れるはずだ。
```yaml
$ cat .imgpkg/images.yml
---
apiVersion: imgpkg.carvel.dev/v1alpha1
images:
- annotations:
    kbld.carvel.dev/id: quay.io/argoproj/argocli:v3.3.8
  image: quay.io/argoproj/argocli@sha256:41891b95aca23018ba65b320ff3ce10a98ee3cb39261f02fd74867c68414e814
- annotations:
    kbld.carvel.dev/id: quay.io/argoproj/workflow-controller:v3.3.8
  image: quay.io/argoproj/workflow-controller@sha256:6d4b11db67c41352088040830b1ab715d3cec762ac37656459e72bd17e0c452c
kind: ImagesLock
```
これをimgpkgコマンドを使ってContainer Registryにimage bundle形式でuploadする。
```
docker login myharbor.hoge
imgpkg push -b myharbor.hoge/myapp/argo-workflow-bundle -f ./
```
なお、コマンド成功時に出てくる、
```
Pushed 'myharbor.hoge/myapp/argo-workflow-bundle@sha256:7db7f72a79845387b427f7148a7f15db2a71a35159145f0700a414b15eed8a18'
Succeeded
```
↑のPushedから先のイメージ名は後で使うので控えておく。

### `kind: Package`, `kind: PackageMetadata`の作成

ワークディレクトリを変えて、それぞれを作成してく。
```
mkdir ../package-bundle
cd ../package-bundle
```
PackageMetadataを作っていく。nameとdisplayName以外は適当なので、好みで変更すること。
```yaml
cat << EOF > ./argo-workflow-pkgmeta.yaml
apiVersion: data.packaging.carvel.dev/v1alpha1
kind: PackageMetadata
metadata:
  name: argo-workflow.hoge.fuga
spec:
  displayName: "argo-workflow"
  longDescription: "ArgoCD Workflow for TKG user"
  shortDescription: "ArgoCD Workflow"
  providerName: IT Dept.
  maintainers:
  - name: John Doe
  categories:
    - "Argo"
  supportDescription: "Please see https://argoproj.github.io/argo-workflows/"
EOF
```
次にPackageを作るのだが、Manifest内のvaluesSchemaの項目を埋めるのにimage bundle内のvalues.yamlを利用したいため、overlayさせる形式で記載する。
```yaml
cat <<EOF > ./argo-workflow-template.yaml
#@ load("@ytt:data", "data")
#@ load("@ytt:yaml", "yaml")
---
apiVersion: data.packaging.carvel.dev/v1alpha1
kind: Package
metadata:
  name: #@ "argo-workflow.hoge.fuga." + data.values.version
spec:
  refName: argo-workflow.hoge.fuga
  version: #@ data.values.version
  releaseNotes: |
        Initial release of Argo Workflow for TKG user
  valuesSchema:
    openAPIv3: #@ yaml.decode(data.values.openapi)["components"]["schemas"]["dataValues"]
  template:
    spec:
      fetch:
      - imgpkgBundle:
          image: myharbor.hoge/myapp/argo-workflow-bundle@sha256:7db7f72a79845387b427f7148a7f15db2a71a35159145f0700a414b15eed8a18
      template:
      - ytt:
          paths:
          - "./config"
      - kbld:
          paths:
          - "-"
          - ".imgpkg/images.yml"
      deploy:
      - kapp: {}
EOF
```
ここでのimage名は、先程imgpkg pushで控えていたimage名を入れている。

先程作成したimage bundleのvalues.yamlをOpenAPIv3形式に変換し、それを上記Packageの雛形と結合してPackageのManifestを生成する。
```
ytt -f ../bundle/config/values.yaml --data-values-schema-inspect -o openapi-v3 > argo-workflow-schema-openapi.yml
ytt -f ./argo-workflow-template.yaml --data-value-file openapi=./argo-workflow-schema-openapi.yml -v version="3.3.8" > argo-workflow-package-3.3.8.yaml
```
作成したものを、先程image bundle化した手順と同じようにimage bundle化してuploadする。\
ただし、ディレクトリ構成に縛りがあり、packages以下にManifestを置く必要がある。今回は以下のようにする。
```
bundle/
├── .imgpkg/
└── packages/
     └── argo-workflow/
         ├── metadata.yaml
         └── 3.3.8/
             └── package.yaml
```
ディレクトリの作成およびリネーム、移動を実施する。
```
mkdir -p bundle/{.imgpkg,packages/argo-workflow/3.3.8}
mv argo-workflow-package-3.3.8.yaml bundle/packages/argo-workflow/3.3.8/package.yaml
mv argo-workflow-pkgmeta.yaml bundle/packages/argo-workflow/metadata.yaml
kbld -f bundle/packages/ --imgpkg-lock-output bundle/.imgpkg/images.yml
imgpkg push -b myharbor.hoge/myapp/argo-workflow-pkg-bundle -f ./bundle
```

### `kind: PackageRepository`の作成
PackageRepositoryを作成し、tanzuコマンドが認識できるようにする。


```yaml
cat <<EOF > ./argo-workflow-repo.yaml
apiVersion: packaging.carvel.dev/v1alpha1
kind: PackageRepository
metadata:
  name: argo-repo
  namespace: tanzu-package-repo-global
spec:
  fetch:
    imgpkgBundle:
      image: myharbor.hoge/myapp/argo-workflow-pkg-bundle:latest
      secretRef:
        name: myimagerepo
---
apiVersion: v1
kind: Secret
metadata:
  name: myimagerepo
  namespace: tanzu-package-repo-global
  annotations:
    secretgen.carvel.dev/image-pull-secret: ""
type: kubernetes.io/dockerconfigjson
stringData:
  .dockerconfigjson: |
    {
      "auths": {
        "myharbor.hoge": {
          "username": "user",
          "password": "xxxx",
          "auth": ""
        }
      }
    }
EOF
```
なお、今回はどこからでもPackageがインストール出来るよう、配置するNamespaceをGlobal Namespaceにしている。\
非TKG環境などでは名前が異なるため注意。Global Namespaceの確認方法はkapp-controllerの引数から確認できる。
```
$ kubectl get deploy -n tkg-system kapp-controller -o yaml | grep packaging-global-namespace | tail -n 1
        - -packaging-global-namespace=tanzu-package-repo-global
```
また、Secretを設定しているのは、PackageRepositoryのカスタムリソースがimageをpullする際にContainer Registoryへの認証情報を要求するためである。

作成したPackageRepositoryのManifestをapplyし、状態が`Reconcile succeeded`になるまで待つ。
```
$ kubectl get packagerepository -A
NAMESPACE                   NAME             AGE   DESCRIPTION
tanzu-package-repo-global   argo-repo        24s   Reconcile succeeded
tanzu-package-repo-global   tanzu-standard   38d   Reconcile succeeded
tbs-install                 tbs-repository   37d   Reconcile succeeded
tkg-system                  tanzu-core       38d   Reconcile succeeded
```
これで準備はOK。

## tanzuコマンドによるインストール
tanzuコマンドで利用可能なバージョンなどを確認する。
```
$ tanzu package available list
  NAME                           DISPLAY-NAME   SHORT-DESCRIPTION                                                                                           LATEST-VERSION
  argo-workflow.hoge.fuga        argo-workflow  ArgoCD Workflow                                                                                             3.3.8
  cert-manager.tanzu.vmware.com  cert-manager   Certificate management                                                                                      1.5.3+vmware.2-tkg.1
  contour.tanzu.vmware.com       contour        An ingress controller                                                                                       1.18.2+vmware.1-tkg.1
  external-dns.tanzu.vmware.com  external-dns   This package provides DNS synchronization functionality.                                                    0.10.0+vmware.1-tkg.1
  fluent-bit.tanzu.vmware.com    fluent-bit     Fluent Bit is a fast Log Processor and Forwarder                                                            1.7.5+vmware.2-tkg.1
  grafana.tanzu.vmware.com       grafana        Visualization and analytics software                                                                        7.5.7+vmware.2-tkg.1
  harbor.tanzu.vmware.com        harbor         OCI Registry                                                                                                2.3.3+vmware.1-tkg.1
  multus-cni.tanzu.vmware.com    multus-cni     This package provides the ability for enabling attaching multiple network interfaces to pods in Kubernetes  3.7.1+vmware.2-tkg.2
  prometheus.tanzu.vmware.com    prometheus     A time series database for your metrics                                                                     2.27.0+vmware.2-tkg.1
  ```
設定できる値も確認する。
```
$ tanzu package available get argo-workflow.hoge.fuga/3.3.8 --values-schema -n tanzu-package-repo-global


  KEY        DEFAULT                     TYPE    DESCRIPTION
  hostname   argo-workflow.hogehoge.com  string  Hostname
  namespace  argo-workflow               string  Namespace
```
values.yamlを作成する。
```yaml
cat <<EOF > ./values.yaml
---
namespace: argo-workflow-edit
hostname: argo-workflow.fugafuga.com
EOF
```
tanzuコマンドでインストールする前に、`kind: Package`がimage bundleを引くためのSecretを登録する。
```
tanzu secret registry add harbor-registry \
  --username user \
  --password xxxx \
  --server myharbor.hoge \
  --export-to-all-namespaces -n default -y
```
Secretを登録後、packageをインストールする。
```
tanzu package install argo-workflow -n default -p  argo-workflow.hoge.fuga -v 3.3.8 -f ./values.yaml --poll-timeout 30m
```
正常にインストールされると、`tanzu package installed get｀で`Reconcile succeeded`が表示される。
```
$ tanzu package installed get argo-workflow

NAME:                    argo-workflow
PACKAGE-NAME:            argo-workflow.hoge.fuga
PACKAGE-VERSION:         3.3.8
STATUS:                  Reconcile succeeded
CONDITIONS:              [{ReconcileSucceeded True  }]
USEFUL-ERROR-MESSAGE:
```
先程設定したNamespaceにIngressも作成される。
```
$ kubectl get ing -A
NAMESPACE            NAME                    CLASS    HOSTS                        ADDRESS          PORTS     AGE
argo-workflow-edit   argo-workflows-server   <none>   argo-workflow.fugafuga.com   10.206.222.235   80, 443   4m58s
```

ブラウザでログインすると認証方法を聞かれるので、Tokenによるアクセスでログインする。
![1656580848365.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/3b8b30eb-f66f-3e7d-b9fd-523128f3a263.png)


[表示されているリンク先](https://argoproj.github.io/argo-workflows/access-token/#token-creation)のToken取得方法に従ってTokenを出力し、出力結果をログイン画面の入力ウィンドウにコピペする。
```
SECRET=$(kubectl get sa default -n default -o=jsonpath='{.secrets[0].name}')
ARGO_TOKEN="Bearer $(kubectl get secret $SECRET -o=jsonpath='{.data.token}' | base64 --decode)"
echo $ARGO_TOKEN
```

無事ログインできました。
![1656581013893.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/80eb385f-dfde-6811-c174-db2619127776.png)


なお、適当なServiceAccountのTokenでログインすると、Argo Workflowsのカスタムリソースへのアクセス権限がなくて何も出来ないので、実際に使う際は適切なServiceAccountのTokenを取得すること。