Knativeのトラフィック管理でBlue/Greenデプロイを実現してみる

# 概要
Knativeは、Kubernetes上でサーバーレスアプリケーションを構築、デプロイ、そして管理を容易にするためのオープンソースプラットフォームです。GoogleやIBMをはじめとする多くの企業が共同で開発し、現在ではCNCF（Cloud Native Computing Foundation）の傘下でプロジェクトが進行しています。

本記事では、Knativeのトラフィック管理機能を活用して、Blue/Greenデプロイを実現する方法について解説します。環境はMinikube上のKubernetesを使用しました。

* 参考リンク
  * https://knative.dev/docs/serving/traffic-management/#procedure
  * https://github.com/jonas/knative-docs/blob/master/serving/samples/blue-green-deployment.md

# サンプルアプリケーション

まず、Blue/Greenデプロイ対象のサンプルアプリケーションを作成します。  

サンプルアプリケーションには`バージョン1`と`バージョン2`が存在し、  
`バージョン1`をBlue(現在稼働中のもの)、`バージョン2`をGreen(新規デプロイ対象)として扱います。

サンプルアプリケーションはDocker Hubで公開しています:  
[https://hub.docker.com/repository/docker/yoshikit/app/general](https://hub.docker.com/repository/docker/yoshikit/app/general)

以下、サンプルアプリケーションの実装およびビルド方法についての記載です。

### サンプルアプリケーションのバージョン1

実装:

```python:app_v1.py
from bottle import route, run
import math

@route('/')
def index():
    return "sample-app-v1"

if __name__ == '__main__':
    run(host='0.0.0.0', port=80)
```

```Dockerfile:Dockerfile.v1
FROM python:3.12-slim

WORKDIR /app
RUN pip install bottle

COPY app_v1.py /app

ENTRYPOINT python app_v1.py
```

```shell:ビルド方法
docker build -t app:v1 -f Dockerfile.v1 .
```

### サンプルアプリケーションのバージョン2

実装:

```python:app_v2.py
from bottle import route, run
import math

@route('/')
def index():
    return "sample-app-v2"

if __name__ == '__main__':
    run(host='0.0.0.0', port=80)
```

Dockerfile:

```Dockerfile:Dockerfile.v2
FROM python:3.12-slim

WORKDIR /app
RUN pip install bottle

COPY app_v2.py /app

ENTRYPOINT python app_v2.py
```

```shell:ビルド方法
docker build -t app:v2 -f Dockerfile.v2 .
```

# バージョン1のデプロイ

まず、バージョン1のイメージをKnativeサービスとしてデプロイします:

```
kn service create app-v1-service \
--image yoshikit/app:v1 \
--port 80
```

次にデプロイしたアプリのリビジョンを取得し、Route.yamlのマニフェスト適用します。

```
$ kubectl get configurations app-v1-service -o=jsonpath='{.status.latestCreatedRevisionName}'
app-v1-service-00001
```

```
# Route.yaml
apiVersion: serving.knative.dev/v1
kind: Route
metadata:
  name: app
  namespace: default
spec:
  traffic:
    - revisionName: app-v1-service-00001
      percent: 100
```

この作業により、2つのURLで作成したwebアプリにhttp経由でアクセスすることができます。

```
$ kubectl get route
NAME             URL                                                READY   REASON
app              http://app.default.127.0.0.1.sslip.io              True    
app-v1-service   http://app-v1-service.default.127.0.0.1.sslip.io   True    
```

# バージョン2の段階的デプロイ

バージョン1の作業と同様にバージョン2のイメージをKnativeサービスとしてデプロイして、  
リビジョンを取得します。

```
kn service create app-v2-service \
--image yoshikit/app:v2 \
--port 80
```

```
$ kubectl get configurations app-v2-service -o=jsonpath='{.status.latestCreatedRevisionName}' 
app-v2-service-00001
```

次に`http://app.default.127.0.0.1.sslip.io`へのリクエストの内、50%がバージョン1、50%がバージョン2に割り当てられるようにRoute.yamlを改修し、再適用します。

```
apiVersion: serving.knative.dev/v1
kind: Route
metadata:
  name: app
  namespace: default
spec:
  traffic:
    - revisionName: app-v1-service-00001
      percent: 50
    - revisionName: app-v2-service-00001
      percent: 50
```

以下はRoute.yaml適用後に`http://app.default.127.0.0.1.sslip.io`へリクエストを1000回投げた結果です。

```
$ for i in {1..1000}; do curl -s 'http://app.default.127.0.0.1.sslip.io' && echo ''; done > /tmp/count.txt
$ cat /tmp/count.txt | grep 'sample-app-v1' | wc -l
482
$ cat /tmp/count.txt | grep 'sample-app-v2' | wc -l
518
```

概ね半々になりました。  
完全にバージョン2に移行するには、Route.yamlを編集して、`app-v2-service-00001`に100%のトラフィックを割り当てる必要があります。

```
apiVersion: serving.knative.dev/v1
kind: Route
metadata:
  name: app
  namespace: default
spec:
  traffic:
    - revisionName: app-v2-service-00001
      percent: 100
```

# 備考

今回の記事ではRoute.yamlを編集することによって、  
トラフィックの割り当てを行いましたが、以下のようにコマンドでトラフィック設定を変更することも可能です。

```
kn service update hello \
--traffic hello-00001=50 \
--traffic @latest=50
```

# おわりに

今回、Knativeを使用したBlue/Greenデプロイの手順を実際に試してみました。

バージョン1からバージョン2への移行を段階的に行い、トラフィックの分割を確認することで、  
Blue/Greenデプロイメントを行いました。