Amazon SageMaker でカナリアデプロイをサクッと！

# はじめに
[Amazon SageMaker 推論エンドポイントに新しいデプロイガードレールが追加](https://aws.amazon.com/jp/about-aws/whats-new/2021/11/new-deployment-guardrails-amazon-sagemaker-inference-endpoints/) されました。ブルー/グリーンデプロイ機能に canary と linear の 2つのトラフィックシフトモードが追加され、CloudWatch アラームを事前指定してレイテンシーやエラーレートなどのアラームが作動した場合、自動的にデプロイをブルーフリートにロールバックすることができます。

[こちらにサンプルノートブックがある](https://github.com/aws/amazon-sagemaker-examples/tree/master/sagemaker-inference-deployment-guardrails) ので、こちらを見ながら使い方を確認したいと思います。

サンプルノートブックは Amazon SageMaker Studio で実行しました。SageMaker Studio のセットアップ方法は [こちらの記事](https://qiita.com/mariohcat/items/d8688d2a73517a0d8e1c) で紹介しています。

# Amazon SageMaker 推論エンドポイントのためのデプロイガードレール

まず、デプロイガードレールというのは Amazon SageMaker の推論エンドポイントのオプション群であり、本番環境にデプロイされた機械学習モデルを更新する際に利用するフルマネージドの機能です。自動ロールバックなどのセーフガードが組み込まれており、モデルの更新に問題があれば即座に今までのモデルに戻すことが可能です。

設定可能なトラフィックシフトモードは以下の 3種類です。トラフィックシフトモードとは、推論エンドポイントへのトラフィックが、古いモデル（がデプロイされたエンドポイント）から新しいモデルにどのように切り替わっていくかを指定する設定です。

- All-At-Once Traffic Shifting : すべてのエンドポイントトラフィックをブルーフリートからグリーンフリートへ移行します。トラフィックがグリーンフリートに移行すると、事前に指定した Amazon CloudWatch のアラームがグリーンフリートの監視を一定時間開始します（この期間を「ベイキング期間」と呼ぶ）。ベイキング期間中にアラームがトリガーされなかった場合、新しいモデルの利用に問題がないと見なしブルーフリートが終了します。
- カナリアトラフィックシフト : トラフィックのごく一部（「カナリア」）をグリーンフリートへ移行し、ベイキング期間中にそれを監視します。カナリアが問題なく動作した場合、残りのトラフィックをブルーフリートからグリーンフリートへ移行し、ブルーフリートが終了します。
- リニアトラフィックシフト：トラフィックシフトのステップ数や、各ステップでシフトさせるトラフィックの割合などのカスタマイズが可能なモードです。カナリアシフトでは、カナリア -> 残りすべて の 2ステップでトラフィックを移行しますが、リニアシフトではこれを nステップに拡張し、直線的な間隔で移行することができます。

# カナリアデプロイのサンプルを試してみる
[こちらのサンプルノートブック](https://github.com/aws/amazon-sagemaker-examples/blob/master/sagemaker-inference-deployment-guardrails/Update-SageMaker-Inference-endpoint-using-canary-traffic-shifting.ipynb) を見てみます。学習済みの XGBoost の顧客離反予測モデルをデプロイし、その後モデルを更新するシナリオです。デプロイされるモデルとコンテナイメージ（使用する XGBoost のバージョン）の組み合わせは以下の通りです。

|#|モデル|コンテナイメージ|
|-|-|-|
|1|Model1|xgboost 0.90-1|
|2|Model2|xgboost 1.2-1|
|3|Model2|xgboost 0.90-2|

## ベースとなるモデルのデプロイと Amazon CloudWatch メトリクスの確認

ひとつ目のモデルをデプロイしたあと、333行のテストデータを使って推論を実施します。その後、Amazon CloudWatch に記録されているエンドポイントのログのうち、Invocations、Invocation4XXErrors、Invocation5XXErrors、ModelLatency、OverheadLatency のグラフを表示してエンドポイントの状況を確認しています。初めのエンドポイントに関しては、エラーは発生しておらず、ModelLatency と OverheadLatency が時間の経過とともに減少していく様を確認できました。

## Amazon CloudWatch アラームの作成
次に、エンドポイントの状況を監視するために CloudWatch アラームを作成します。Invocation5XXErrors と ModelLatency を監視対象にします。

```python
error_alarm = f"TestAlarm-5XXErrors-{endpoint_name}"
latency_alarm = f"TestAlarm-ModelLatency-{endpoint_name}"

# alarm on 1% 5xx error rate for 1 minute
create_auto_rollback_alarm(
    error_alarm, endpoint_name, "AllTraffic", "Invocation5XXErrors", "Average", 1
)
# alarm on model latency >= 10 ms for 1 minute
create_auto_rollback_alarm(
    latency_alarm, endpoint_name, "AllTraffic", "ModelLatency", "Average", 10000
)
```

## エンドポイントの更新（エラー発生と自動ロールバックを試す）
いよいよエンドポイントを更新します。このサンプルではカナリアトラフィックシフトオプションを指定しているので、エンドポイントに変なモデルがデプロイされてアプリケーションが異常動作してしまう悲劇を防ぐことができます。

以下は、カナリアデプロイをするためのコードです。`TrafficRoutingConfiguration` の `Type` に `CANARY` を設定し、カナリアのサイズを `CanarySize` で指定しています。また、新しいモデルに不具合があった際に自動でロールバックをするために、`AutoRollbackConfiguration` にあらかじめ作成しておいた CloudWatch アラームを設定しています。`update_endpoint` の `EndpointConfigName` に、#2の組み合わせ（Model2、xgboost 1.2-1）のモデルを設定しています。

```python
canary_deployment_config = {
    "BlueGreenUpdatePolicy": {
        "TrafficRoutingConfiguration": {
            "Type": "CANARY",
            "CanarySize": {
                "Type": "INSTANCE_COUNT",  # or use "CAPACITY_PERCENT" as 30%, 50%
                "Value": 1,
            },
            "WaitIntervalInSeconds": 300,  # wait for 5 minutes before enabling traffic on the rest of fleet
        },
        "TerminationWaitInSeconds": 120,  # wait for 2 minutes before terminating the old stack
        "MaximumExecutionTimeoutInSeconds": 1800,  # maximum timeout for deployment
    },
    "AutoRollbackConfiguration": {
        "Alarms": [{"AlarmName": error_alarm}, {"AlarmName": latency_alarm}],
    },
}

# update endpoint request with new DeploymentConfig parameter
sm.update_endpoint(
    EndpointName=endpoint_name,
    EndpointConfigName=ep_config_name2,
    DeploymentConfig=canary_deployment_config,
)
```

エンドポイントの更新が完了するまでに数分程度かかりますが、その間もエンドポインへ推論リクエストを送ることが可能です。

推論リクエストを投げてみると、このモデルはエラーが発生しているようです。モデル学習時と異なる XGBoost のバージョンのコンテナイメージを使用しているので想定通りのエラーです。以下は、エンドポイントアップデート中に推論リクエストを投げた際のログです。E がエラー発生を示しています。

```bash
Sending test traffic to the endpoint DEMO-Deployment-Guardrails-Canary-2021-12-30-12-26-04. 
Please wait...
.................................................................
.................................................................
.................................................................
..E.EE....EE.E..E................................................
................................................................
..........
Done!
```

CloudWatch メトリクスのグラフを見てみると、`Invocation5XXErrors` が発生していることがわかります。
![スクリーンショット 2021-12-31 20.00.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315319/40626af7-b4e7-abca-bc82-318fe3b8aab1.png)

![スクリーンショット 2021-12-31 20.01.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315319/3841e693-392b-e691-4f6e-c523b8fdee0e.png)

エラーが発生しているので、エンドポイントが自動ロールバックします。Amazon SageMaker コンソールでエンドポイントの画面を見てみると、アラームが発生したので自動ロールバックしたよ、と表示されていました。正常に動作していたモデルにロールバックされたので、エンドポイント自体は `InService` になっています。

![スクリーンショット 2021-12-31 20.04.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315319/b4849729-1597-c04c-1472-6b2d2750bcf2.png)

## エンドポイントの更新（エンドポイント更新の成功を確認する）
最後に、モデルとライブラリのバージョンが正しいモデルを使ってエンドポイントを更新します。

以下の図は、推論リクエストがどの設定のエンドポイントに送られたかを示していて、ひとつ目の山は先ほどの、モデルとライブラリのバージョンがあっていないモデルをデプロイしようとした時のものです。EpConfig-1 から EpConfig-2 にリクエストが切り替わりましたが、途中で再度全てのリクエストが EpConfig-1 に戻っています。ふたつ目の山は正しいモデルを使っているため、推論リクエストが EpConfig-1 から EpConfig-3 に切り替わり、そのまま全てのリクエストが EpConfig-3 に流れるようになりました。

![スクリーンショット 2021-12-31 20.29.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315319/e284ae60-86b1-12b5-046a-4caa633000b0.png)

# カナリアデプロイのしくみ
最後に、[Amazon SageMaker 開発者ガイド](https://docs.aws.amazon.com/ja_jp/sagemaker/latest/dg/deployment-guardrails-blue-green-canary.html) の図を見ながら、どのようにエンドポイントの更新が進んでいくのかを確認します。

こちらは、初めに既存のモデルがデプロイされたブルーフリート（インスタンスは 4つ）があり、うち 1台のインスタンスをカナリアとして設定してエンドポイントの更新を実行した例です。カナリアの割合は 50% 以下に設定する必要があります。図の左上では、全てのトラフィックがブルーフリートに流れており、上段中央で 25% のトラフィックがグリーンフリート（カナリア）に流れています。その後ベイキング期間に CloudWatch アラームが設定に応じてブルー/グリーンフリートを監視します（図の右上）。何事もなければ全てのトラフィックがグリーンフリートに移動し（図の左下）、再度ベイキング期間になります（下段中央）。CloudWatch アラームが発生しなければ、全てのトラフィックがグリーンフリートに流れるようになり、ブルーフリートが削除されます（図の右下）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315319/4c8e5218-bb5b-fa8b-896f-2d47b15e88d2.png)


# まとめ
Amazon SageMaker 推論エンドポイントのためのデプロイガードレールの機能のうち、カナリアデプロイと自動ロールバックを試してみました。Amazon CloudWatch アラームを設定して自動ロールバックを有効にすることで、異常なモデルを意図せずデプロイしてしまいアプリケーションがダウンしてしまうという悲劇を回避することができそうです。