Operator SDKとEnvoyのgo-control-planeを使って、Blue-Greenデプロイメント機能を備えたCustom Resourceを作ってみた

BlueとGreenの２世代のDeploymentとEnvoy PodをまとめてデプロイするOperatorを作成し、Envoyの構成を動的に制御するxDSサーバの機能もそのOperatorに組み込めば、Blue-Green Deploymentを標準装備したOperatorが作れそうです。
Operatorのフレームワーク（[Operator SDK](https://github.com/operator-framework/operator-sdk)）とxDSサーバのフレームワーク（[go-control-plane](https://github.com/envoyproxy/go-control-plane)）がGitHubに公開されており、どちらもGo言語なので頑張れば一体化できるかもしれません。
...ということで作ってみました。Operatorの構造を下図に示します。
![architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/65aab08b-c43f-2bc9-d9ae-18a7af33b775.png)


作成したコードは[GitHub](https://github.com/takeyan/bgdeploy-xds2)に公開しました。そのコードをダウンロードしてビルドし、Blue-Green Deploymentのシナリオを実行する手順をここに書き残しておきます。
ビルド済のコンテナイメージがDockerhubにpublicで登録されているので、単に動作を再現するだけなら以下の「実行環境構築」の1,3,4はスキップしても大丈夫です。

ここではOperatorやxDSサーバの実装の詳細には言及しませんが、興味のあるかたは上記のArchitecture図をたよりにソースコードを覗いてみてください。Go初心者のad hocなプログラムなので、Goプログラミングの参考にはなりませんが、動いたという事実と処理の流れくらいは参考になると思います。

動作確認環境は次のとおりです。

+ OS: Ubuntu 18.04.1 LTS (Bionic Beaver)
+ [Golang: go1.15 linux/amd64](https://golang.org/dl/)
+ [Operator SDK: v0.17.2](https://sdk.operatorframework.io/docs/installation/install-operator-sdk/)
+ [Docker: 19.03.12](https://docs.docker.com/engine/install/)
+ Kubernetes: お好みで （この記事ではIBM Cloud IKSを使用）

Operator SDKはテンプレート（ソースコード）生成時にv0.17.2を使用した都合上、v0.17.2を前提としています（v0.18以上では互換性の問題が出るかもしれません）。他は最新バージョンでいいと思います。
go-control-planeはライブラリとして使用していますが、明示的にインストールする必要はありません。



## 実行環境構築


### 1. 前提ソフトウェアをインストール
上記の動作確認環境に必要なソフトウェアをインストールします（手順は各リンク先参照）。
Operator SDKは```RELEASE_VERSION=v0.17.2```とする他はリンク先の手順のとおりで大丈夫です。

### 2. GitHubリポジトリをダウンロード
```
git clone https://github.com/takeyan/bgdeploy-xds2.git
```

### 3. Operatorのコードおよびコンテナイメージをビルド
```bash
cd bgdeploy-xds2
export OPERATOR_IMAGE={image name and tag for your container registry}
operator-sdk build $OPERATOR_IMAGE
docker login to your registry
docker push $OPERATOR_IMAGE
```

### 4. Operatorのマニフェストファイルを編集
deploy/operator.yaml内でOperatorのイメージ名を参照しているので、上記で指定した名前に変更します。（初期状態ではtakeyan/bgdeploy-oprator:0.1.0となっています）

```bash
vi deploy/operator.yaml
```

### 5. 以下のマニフェストを適用
```bash
kubectl create -f deploy/service_account.yaml 
kubectl create -f deploy/role.yaml
kubectl create -f deploy/role_binding.yaml 
kubectl create -f deploy/crds/swallowlab.com_bgdeploys_crd.yaml
kubectl create -f envoy-configmap.yaml
```


## 動作確認シナリオ実行

Operatorはカスタムリソースのマニフェストを定期的にモニターし、マニフェストが変化したらマニフェストの定義に合わせてリソースの状態を変更します。マニフェストのtransitは起動するDeploymentの数を指示しており、OFFの時にはactiveに指定した方のDeploymentとServiceを、ONではblueとgreenの両方とも起動します。activeはEnvoyがリクエストを転送する相手も指示しています。
下図にシナリオの流れを示します。
![verification_scenario.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/0a1ee7d9-23cd-3a90-df01-4c7a29725161.png)


### 1. Operatorをデプロイ
```bash
kubectl create -f deploy/operator.yaml 
```

### 2. BGDeployカスタムリソースをデプロイ


```bash
kubectl create -f bgdeploy_phase1.yaml
```
Operatorをデプロイした後初めてカスタムリソースをデプロイするタイミングでは、EnvoyのPodとService、およびOperatorのgrpcポートとhttpポートを公開するためのServiceが起動し、これらはその後稼動し続けます。さらにマニフェストの指定に従ってblueのDeploymentとServiceが起動します。

### 3. bgdeploy-svc-envoyのNodePort番号を確認

```bash
kubectl get svc

# kubectl get svc
NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                           AGE
bgdeploy-svc-envoy      NodePort    172.21.229.116   <none>        10000:32478/TCP                   15m
```

K8sクラスタ外部からEnvoyに対してHTTP Getを送付するのにNodePortを使用します。EnvoyのhttpリスニングポートにHTTPリクエストを送付すると、EnvoyがblueまたはgreenのServiceにそのリクエストを転送し、応答を返してくれます。


### 4. HTTP Getリクエストを送付
```bash
curl {worker nodeのIPアドレス}:{EnvoyのNodePort番号}/api/echo

# curl 10.242.0.5:32478/api/echo
{"api":"echo","nodename":"10.243.0.4","pod_ip":"172.17.5.151","podname":"bgdeploy-dep-blue-68bb89dbf4-glwkd","query_string":[""],"timestamp":"2020-08-27T11:44:31.500561"}
```

サンプルアプリは/api/echoというパスでHTTP Getリクエストを受け取ると、Pod名を含むJSONフォーマットのメッセージを返す仕掛けになっています。Pod名に含まれる"blue"または"green"で、Envoyがblue/greenのどちらに振り分けたかが確認できます。

以下のターミナル画面の上段ではcurlを、下段ではkubectl get podとkubectl get service（getall.sh内で呼び出し）をそれぞれ2秒間隔で実行し、マニフェストの更新によってpodとserviceの増減やアプリ応答が変化する様子をモニターします。以下の画面ではblueのpod（bgdeploy-dep-blue-xx-xx）とservice（bgdeploy-svc-blue）が起動しており、curlにはblueが応答していることがわかります。
![iks-bgdeploy-phase1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/10f0db20-675f-7aee-98ba-e07d7ee58037.png)



### 5. マニフェストを更新し、応答がblueからgreenに変わることを確認
##### マニフェストのtransitをONに変更します。

```bash
kubectl apply -f bgdeploy_phase2.yaml
```

![iks-bgdeploy-phase2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/9efc0de1-2983-009b-a737-dd70bc4bb108.png)
bgdeploy-phase2.yamlの適用によりtransit=ON, active=blueになると、greenのpodとserviceが起動し、blueとgreenの共存状態となりました。curlには引き続きblueが応答しています。

##### マニフェストのactiveをgreenに変更します

```bash
kubectl apply -f bgdeploy_phase3.yaml
```

![iks-bgdeploy-phase3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/73491a99-e4d8-5c4b-0f5c-f01a25689aa5.png)
bgdeploy-phase3.yamlの適用によりtransit=ON, active=greenになると、curlに対してgreenが応答するようになりました。podとserviceは引き続きblueとgreenが共存しています。

##### マニフェストのtransitをOFFに変更します

```bash
kubectl apply -f bgdeploy_phase4.yaml
```

![iks-bgdeploy-phase4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/210ad038-d7fc-de8c-e69e-7b5554628ad9.png)
bgdeploy-phase4.yamlの適用によりtransit=OFF, active=greenになると、blueのpodとserviceが停止しました。

## まとめ
この記事ではBGDeployというカスタムリソースによるBlue-Green Deploymentの流れををご紹介しました。
OperatorとEnvoyの欲しいところだけをコンパクトに実装したいというニーズは、EdgeコンピューティングとEdge向け軽量K8sの普及に伴って広がってくると思います。どちらもプログラミングするとなると難しそうな印象ですが、フレームワークのおかげで敷居はずいぶん低くなっていると感じました。

この記事がOperatorやEnvoyに興味を持っている方の参考になれば幸いです。


以上です。