ArgoCDで、DBのマイグレーションが完了してからデプロイする

# 背景
私が所属しているプロジェクトのリリースフローは、大まかに以下の順序で行われます。
1. DBのマイグレーション専用ブランチにマージ
2. CloudBuildで、マイグレーション用のシェルが実行される
3. デプロイ専用ブランチにマージ
4. CloudBuildで、k8sのマニフェストが適用される

また先日、DBの接続情報などをSealedSecretを用いてSecretで管理するようになりました。
そこでマイグレーションの実行もk8sのJobで実行したくなったのですが、`マイグレーションの完了を待ってからデプロイするにはどうすればいいか`と疑問に思い、調査した結果、`ArgoCDでうまいことやれそうだ`ということが分かったので、検証することにしました。
しかもArgoCDでGitOpsを実現しようとしていたので、ちょうどよかったです。

# この記事で触れること
- ArgoCDのHookについて

説明で用いるyamlは以下のリポジトリにコミットしてあります。
[istsh/secret-sample-manifests](https://github.com/istsh/secret-sample-manifests)

# この記事では触れないこと
- ArgoCDの導入方法
- GitOpsとはなにか
- デプロイ対象のアプリケーションについての諸々

# ArgoCDのHook
[公式ドキュメント](https://argoproj.github.io/argo-cd/user-guide/resource_hooks/)

### Hookの定義方法
Hookはアノテーションとして定義します。
ここではJobのyamlを紹介します。

```yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: sample-job
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
    argocd.argoproj.io/sync-wave: "2"
```

### Hookの種類
ArgoCDのHookは5種類あります。

Hook| 説明
---|---
PreSync| マニフェストの適用前に実行される。
Sync| すべてのPreSync Hookが成功として完了した後で、マニフェストの適用と同時に実行される。
Skip| マニフェストの適用をスキップする。
PostSync| すべてのSync Hookが成功として完了し、すべてのリソースが正常な状態になった後に実行される。
SyncFail| PreSyncやSync, PostSyncが失敗したときに実行される。(ver1.2以降)

### Hookの削除
Hookを何らかの方法で削除するには、`hook-delete-policy`をアノテーションに定義します。
Policyは3種類あります。

Policy| 説明
---|---
HookSucceeded| Hookが成功した後に削除される。
HookFailed| Hook失敗した後に削除される。
BeforeHookCreation| 次の新しいHookが作成される直前に削除される。(ver1.3以降)

### 同一Hookでの実行順序の指定
`sync-wave`をアノテーションに定義します。
これを定義すると、その値の<font color=red>昇順</font>にHookが実行されます。
ただし、注意点が2つあります。
1つ目は、値は数値ではなく文字列で定義すること。
2つ目は、[公式ドキュメント](https://argoproj.github.io/argo-cd/user-guide/sync-waves/#how-do-i-configure-waves)にもあるように、<font color=red>sync-waveの初期値は0</font>です。同一Hookでsync-waveを設定する場合に、どれかに設定漏れがあると、それが意図せず先に実行されることがあるので注意してください。

# マイグレーションのJobを定義
前提として、JobはSecretから環境変数を読み取る、Secretは[SealedSecret](https://github.com/bitnami-labs/sealed-secrets#sealed-secrets-for-kubernetes)を使用している、とします。

先にSecret(SealedSecret)とJobのyamlを載せます。
使用しているイメージやその引数は、今回の説明には関係ないので無視してください。

```secret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: sample-secret
  namespace: default
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
    argocd.argoproj.io/sync-wave: "1"
spec:
  encryptedData:
    MYSQL_HOST: AgBPhntyTvUOYPtjJrdZk0fbVyljZFQExAVKAHYjE0rtTNsqaNXygninGs5CPPALfIf/BgmONK35xepHnH7kqc+WNm2nt4MueAMHAtrHQ/8SCkzXbQGLuiyUUds2Q2xVu/rBAVwGfWaKkwi9Db8Y7u6UOLE2wjgk2hpfqnf0KCgR4+u4q936tszBlqQrzk6+ZCqn7oPkOHI3iHJIi4Myo+Xh51Cx0ItEcq18huAK04tTgPoNDJgjLZZnWt83eOJcdqs6mNuI2khsi+/sXizA/naRRDJjiZat5//v/e73ejPRYLJkLCxmbiqBkZUmzBcTgj9PNvOPd+Be/VlKV0I4oO23e4f4CTp+xsq/hxje1qo38zL5UEalbYvtWVCAQCHyA5zcacpD7Sbl0hDSn4JCmXgktrtmgp4iADw2dGDqj7yGyvgQt18VUyWwqIzdhdih9a9LLvqK+gEf9pHs6p6j+rSlvIhKWM9k+vp44h9GLv5ViXB0jFD6Ig5kuyrne6OPceJgj1kuoGQbGdwCvd6thjNytWwBuPujKo5AK4VepLUUh7q7zZwqBv0yXMwSkmFDpJMTmMuy6W32HZvmJw1m/ajaAqlltDKl2pbyDClRLJ/K1n9QDiOEgIdLX3t9C5tJL1G9b1bMTpTfqqYcQbTQnDBEJJ7du4eYQRoT9EP4vI0eyab8beuvBvwIdPvsPWwY+uf7TzuyX0ve6/8=
    MYSQL_PASSWORD: AgAV4O7rOmyqL2JtPZ/XtUIMNW5GPgSMhc8zU0zU7LyaNfu5PPFfZrqmyvV4u0XjiNL0nH0rxCEnbm8ZlYtDQCDTMAZMgQkDSWVq7RGnL0txkE3mtsgzctNvIk54lvoVi7fzsBUHXLWsXu58NgQsGhc/VG25zMIq2ycEKd0nEFoa3EK1EoNsb3gKNS4qGM7hqkS5y7ufOcUR5mVuvXlihrfn9nGcucFsRsMbxsTYWUauM8x0wqxrnw7o74EkA0Gyhu2FI43n4mleubC8pzOEYvurfyfd1UmS7H6j/Cm4VuPtiX/uynvKyb2iVJX3vkeyKnmvhP6AcegfI1UYSD1nLg6taDOUFDdScCRsdI97lF0ehYauinLLp94rRQIXOFWYaSqq8VNbQzApXTDAgSzjazdx17p1IVrspEc7xCzD1Dcn3RiiiSpGnYEbG4rF4pFu8AcKH5j/MSydKN4iJgQ2utOgJ7pUXNeShgRrNf/BI67uus44fgeIXnkG8+LG7/WGhsjnYTRsECZD4g+q2vB3tYeBSFQ7ojUFVN5ChsZgTS+uziIp3jzxu3sWfSt4puGx+BlFp+dxv0Zp1YrRKuje+dBWlCkrf9Sa16SR7DCqKdX+1G0W6rwYYEyXwRW9Y0s6lUZwfpd0kOVcYZABohfH3RkWmxgKjF76xFlyFfHILVtdGJbe1nz1144wAMQeE3jvk/ypeAGy+I8lbg==
    MYSQL_USER: AgDjq7qBJR1DDOWapYF4AhtQi1sQgeglSRhu6tZ+JHPTPPdNFBV+zQk3xzKKRldZeeIlXQ5ZquEdc3lMWYKqg5xvL2e21YhgPv4oWADDvsWxWdklZVLDl0SGPQT64v6K+lPVR5Gia6zIpocvv4v3GbMcCXFkFK/b5HeSU6FcxrAhrQ+vdxSWKfkezO8vrS1wJ3MYLgLSwtiRDsibjpYwVTg0sLDcpTlrDk95IEnmcY8bE6qGJ3BO8mUBPhsBnk5yLJO2MZeOofA9IbpUDIpUJSFcaaXc+PK3C+vKxxJ2Ia94Y07UshUOrl72dTrp9aJv1mdkVfzZ7EHQpHh+2k7b2U1nMFVFwK2QV193vJVSQDM21IzIttE4QOxllv0ei48FVMVw3pJ0ABJMKiYta6KAZvKQ5EJkEIVVHtVqRH6plpBgU72dAPVuJAmdlAQ3I844lj1AKwfydylFRJP/olkHegB5Lu3ScwblicPZt4Q+f+IV79YefKR+Bf8zwLB5bd5uFOVx5Jj3dkiSfTN9P60FE6uA1axxyECafqwcgEmxafODtRpP+94rbAUboWCCXTAqjL+KyclCkXg0lKKLDHBmIzhsV3+eDwlApHWfvs1/x2dyW17Rjs7Nqb6vi/xX+YXT0d67TzNEUFfYFABRN9ABN/YSCWsVxIJXvMulg4RpsGD4g5E4GG5gwj3Jjjfr0yEtsu9fS5Zn
  template:
    metadata:
      creationTimestamp: null
      name: sample-secret
      namespace: default


```

```job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: sample-job
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
    argocd.argoproj.io/sync-wave: "2"
spec:
  backoffLimit: 0
  template:
    spec:
      containers:
        - name: sample-job
          image: istsh/secret-sample-app-job:1.0.0
          args: ["success"]
          envFrom:
            - secretRef:
                name: sample-secret
      restartPolicy: Never

```


ここで重要なのは、Jobだけでなく、<font color=red>Secret(SealedSecret)にもPreSyncを定義している</font>ことです。
マイグレーションを実行する場合、SecretからDBの接続情報を取得することになると思いますが、JobだけにPreSyncを定義すると、Jobの実行時にSecretが作成/更新されていないため、実行に失敗します。
SecretにもPreSyncを定義することで、<font color=red>同一HookでSecretも作成</font>されます。

しかし、Secretが作成されるよりも前にJobが実行されてしまうと、DBの接続情報が取得できずに失敗してしまいます。
ここで使えるのが`sync-wave`です。Secretのsync-waveを`"1"`、Jobを`"2"`とすることで、Secretが必ず先に作成されます。

また、`hook-delete-policy`を定義しないと、次回の実行時にHookが残ったままなので、PreSyncそのものが失敗してしまいます。必ず定義しましょう。
`HookSucceeded`は失敗した場合に削除されずに残ってしまいます。`HookFailed`も同様なので、`BeforeHookCreation`が有用なのではないでしょうか。

# Hookの状態を確認
Hookの状態は以下のコマンドで確認できます。

```terminal
$ argocd app get sample-app --show-operation
Name:               sample-app
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          default
URL:                https://localhost/applications/sample-app
Repo:               https://github.com/istsh/secret-sample-manifests.git
Target:             candidate-local
Path:               k8s/overlays/local
SyncWindow:         Sync Allowed
Sync Policy:        Automated
Sync Status:        Synced to candidate-local (2d128f4)
Health Status:      Healthy

Operation:          Sync
Sync Revision:      2d128f4f1a2ee8a773a0505d827c34e28ac13593
Phase:              Succeeded
Start:              2020-09-28 01:24:56 +0900 JST
Finished:           2020-09-28 01:25:09 +0900 JST
Duration:           13s
Message:            successfully synced (all tasks run)

GROUP        KIND          NAMESPACE  NAME                               STATUS     HEALTH   HOOK     MESSAGE
bitnami.com  SealedSecret  default    sample-secret                      Succeeded           PreSync  sample-secret created
batch        Job           default    sample-job                         Succeeded           PreSync  job.batch/sample-job created
             ConfigMap     default    envoy-secret-sample-server-config  Synced                       configmap/envoy-secret-sample-server-config unchanged
             ConfigMap     default    envoy-secret-sample-proxy-config   Synced                       configmap/envoy-secret-sample-proxy-config unchanged
             Service       default    secret-sample-server-preview       Synced     Healthy           service/secret-sample-server-preview unchanged
             Service       default    secret-sample-proxy-active         Synced     Healthy           service/secret-sample-proxy-active unchanged
             Service       default    secret-sample-server-active        Synced     Healthy           service/secret-sample-server-active unchanged
             Service       default    secret-sample-proxy-preview        Synced     Healthy           service/secret-sample-proxy-preview unchanged
argoproj.io  Rollout       default    secret-sample-server               Synced     Healthy           rollout.argoproj.io/secret-sample-server unchanged
argoproj.io  Rollout       default    secret-sample-proxy                Synced     Healthy           rollout.argoproj.io/secret-sample-proxy unchanged
```

# まとめ
ArgoCDのPreSyncを用いることで、`アプリケーションのデプロイ前にマイグレーションを実行する`ことが可能になりました。
マイグレーションに限らず、PreSyncで「デプロイを開始します」、PostSyncで「デプロイが完了しました」、SyncFailで「デプロイが失敗しました」と通知することも可能です。
これ以外におすすめの使い方を知っている方は、コメント等いただけると幸いです。