Kubernetesで超簡単にBlue/Green Deploymentを実現する

前置きが長いので、さっさと簡単なBlue/Green Deploymentを読ませろって方は[コチラ](#argo-rolloutsを使ったbluegreen-deployment)

# Kubernetes上のアプリケーションのデプロイ方式
デプロイ方式として筆者が認識しているもので以下の4つが有ります。

- Recreate
- RollingUpdate
- Blue/Green Deployment
- Canary Release

それぞれ一長一短は有りますが、以下のような使い分けが考えられます。

| デプロイ方式 | ユースケース |
|-----------|------------|
| Recreate| DBのマイグレーションを含むような後方互換性の無いアプリケーションのリリース |
| RollingUpdate|  ダウンタイム無しのリリース |
| Blue/Green Deployment| アプリケーションの動作を事前確認しつつ、ダウンタイム無しのリリース |
| Canary Release| A/Bテストや一部のユーザだけに先行公開してリリース |


これは**筆者の主観**ですが、Canary Releaseは多くのアプリ開発の現場では**現状**too muchだと考えています。
限られたユーザに先行的に公開して新機能の価値仮説を検証するというのは、イノベーター理論でいうアーリアダプター層ぐらいまでしか実践できておらず、マス層は安全にリリースできれば良いと考えていると思っています。

つまり、多くのユーザが望むデプロイ方式はBlue/Green デプロイメントだと考えています。
しかし、ここで問題が有ります。

# Kubernetes上にBlue/Green Deploymentでリリース

アプリケーションをKubernetes上にデプロイするためにはK8s標準リソースのDeploymentを使用することが一般的かと思いますが、デプロイ方式としてはRecreateとRollingUpdateしかサポートしておりません。
つまりBlue/Green Deploymentは自前で行う必要が有るのです。

## K8s標準リソースのDeploymentとServiceだけでBlue/Green Deployment

Kubernetesの標準リソースのDeploymentとServiceだけでBlue/Green Deploymentをしようと思うと意外と手順が煩雑です。
インタラクティブにmanifestを書き換える必要があります

例えば、Ver1からVer2にアプリケーション(コンテナ)をアップデートする際にBlue/Greenデプロイメントでやりたいとします。
詳細な手順は[こちらの記事](http://neos21.hatenablog.com/entry/2019/04/28/080000)を確認して頂きたいですが、簡単には以下のようになります。

1. Ver2のアプリをデプロイ
2. Ver2のアプリに内部からだけアクセスするためのServiceをデプロイする。
3. Ver2のアプリの動作に問題ないことを確認する
4. Ver1のアプリにルーティングさせているSerivceのSelectorをVer2に切り替える
5. Ver1のアプリを削除する。

確かにレガシーなシステムでBlue/Green Deploymentを実現することに比べると格段に楽ではありますが、
以下の様な辛みが残っています。

- 一時的にVer1とVer2のDeploymentを2つ管理しないといけない
- DeploymentのlabelでアプリのVerを管理しないといけない
- ServiceのSelectorを書き換える必要がある
- 使い終わったVer1の片づけをしないといけない

本当にやりたいことは、DeploymentにRollingUpdateのStrategyを書くように、宣言的にBlue/Green DeploymentのStrategyを書き、後はDeploymentのコンテナのタグをVer1⇒Ver2に変更するだけで、Kubernetesに宜しく上記の手順を自動でやってほしいのです。

これを実現してくれる素晴らしいソリューションが有ります。

## Argo Rolloutsを使ったBlue/Green Deployment

[Argo Rollouts](https://argoproj.github.io/argo-rollouts/)です。
簡潔にはDeploymentのStrategyにBlue/Green DeploymentとCanary Releaseを対応するように拡張したCRDです。

### インストール方法
以下のコマンドでRolloutリソースが扱えるようになります。

```bash
$ kubectl create namespace argo-rollouts
$ kubectl apply -n argo-rollouts -f https://raw.githubusercontent.com/argoproj/argo-rollouts/stable/manifests/install.yaml
```
### 使用方法
既存のDeploymentを以下のように書き換えます。

```yaml
  
apiVersion: argoproj.io/v1alpha1 #apps/v1から書き換え
kind: Rollout #Deploymentから書き換え
# -----この行から下は元のDeploymentと一緒------
metadata:
  name: rollout-bluegreen
spec:
  replicas: 1
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollout-bluegreen
  template:
    metadata:
      labels:
        app: rollout-bluegreen
    spec:
      containers:
      - name: rollouts-demo
        image: gcr.io/heptio-images/ks-guestbook-demo:0.2
        imagePullPolicy: Always
        ports:
        - containerPort: 80
  strategy:
# -----この行から上は元のDeploymentと一緒------
# -----この行から下はRolloutの拡張部分------
    blueGreen: 
      # 本番アクセス用のServiceの名前
      activeService: rollout-bluegreen-active
      # Previewアクセス用のServiceの名前
      previewService: rollout-bluegreen-preview
      # 手動昇格の有無
      autoPromotionEnabled: true
      # オプション Preview環境限定でreplicasの上書き
      previewReplicaCount: 1
      # オプション ReplicaSetが準備完了になってから自動昇格するまでの時間
      autoPromotionSeconds: 300
      # オプション 新Verにルーティングを切り替えた後に旧VerのPodのスケールダウンを開始させるまでの時間
      scaleDownDelaySeconds: 30
```

後は、Previewアクセス用と本番アクセス用のServiceを用意するだけです。
これでRollout内のimageを書き換えれば自動でBlue/Green Deploymentができます。

```yaml
kind: Service
apiVersion: v1
metadata:
  name: rollout-bluegreen-active
spec:
  selector:
    app: rollout-bluegreen
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
---
kind: Service
apiVersion: v1
metadata:
  name: rollout-bluegreen-preview
spec:
  selector:
    app: rollout-bluegreen
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
```

### デモ
Serviceと新旧Verのアプリケーションのルーティングの関係を分かりやすくするために、ArgoCDのGUIを使用して説明します。
ArgoCDについては以前[ハンズオンしてみた記事](https://qiita.com/ttr_tkmkb/items/ff4d88f3a91db982b0ab)を書いたので宜しければ参照ください。
今回使用したmanifestは[こちらのリポジトリ](https://github.com/ssbtn/argo-rollouts-handson)に有ります。

#### Ver1のアプリケーションのデプロイ
Rolloutリソースを使用してアプリケーション(Ver1)と、本番アクセスとPreviewアクセス用のServiceをデプロイ。
アプリケーション(Ver1)は本番アクセス用のServiceからすぐにルーティングされます。

![Cut2019_1116_1149_52.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411298/8257c9ac-3393-d500-f8d1-f097c5ee0747.jpeg)


#### Ver2のアプリケーションのデプロイ
Rolloutリソース内のコンテナイメージのタグをVer2に変更して再デプロイ。

まずは、Ver1のアプリケーションはそのままで、Ver2のアプリケーションはPreview用のサービスから自動でルーティングされるようになります。

![Cut2019_1116_1152_26.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411298/2f3ed000-753d-ce4c-0aed-7f3c91cd60e0.jpeg)

そして、Ver2のアプリケーションのReplicaSetがReadyになり次第、自動で本番アクセス用のServiceにルーティングが一括で切り替わります。最後にVer1のアプリケーションがシュリンクされます。

![Cut2019_1116_1153_26.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411298/20b44472-6820-a583-4ac7-a115d7c982aa.jpeg)


ReplicaSetがReadyになった時、自動で本番アクセス用のServiceにルーティングされるようになりますが、Rolloutsに以下のように設定することで手動昇格にすることも可能です。

```yaml
  strategy:
    blueGreen: 
      autoPromotionEnabled: false
```

デモ手順でいうと、Ver2のアプリケーションがPreview用のサービスからルーティングされるようになった後に停止点が設けられます。

例えばこの手動停止時に、Previe用のアクセス経路からE2Eテスト等を実施することで一般ユーザに公開前に動作確認ができます。

手動昇格する際は、Rolloutsリソースの中のpauseフラグをfalseにします。

```bash
$ kubectl patch rollout example-rollout --type merge -p '{"spec": {"paused": false}}'
```

更にArgoCDに関してはArgo Project繋がりからかGUIから手動昇格が可能になっており、ArgoCDによるGitOpsと相性が良いです。

# 終わりに
当初はGitOpsでBlue/Green Deploymentをしたかったのですが、インタラクティブなデプロイ方式だと不便だったので、都合の良いソリューション探していたら見つかりました。
同じような悩みを持つ誰かの役に立ちましたら幸いです。