セッション分離せずAuto Scalingできなかった話

## はじめに

とあるシステムがありました。概要図は下記です。


```mermaid
flowchart LR

%%外部要素のUser
OU1[User]

%%グループとサービス
subgraph GC[AWS]
    subgraph GV[VPC]
        subgraph GS1[public subnet]
            NW1{{"ALB"}}
        end
        subgraph GS2[private subnet]
            subgraph ASG[ASG]
                CP1("AP1")
                CP2("AP2")
            end
            DB1[("DB")]
        end
    end
end

%%サービス同士の関係
OU1 --> NW1
NW1 --> CP1
NW1 --> CP2
CP1 --> DB1
CP2 --> DB1

%%グループのスタイル
classDef SGC fill:none,color:#345,stroke:#345
class GC SGC

classDef SGV fill:none,color:#0a0,stroke:#0a0
class GV SGV

classDef SGPrS fill:#def,color:#07b,stroke:none
class GS2 SGPrS

classDef SGPuS fill:#efe,color:#092,stroke:none
class GS1 SGPuS

classDef AsgStyle fill:none,color:#000,stroke:#777,stroke-dasharray:8
class ASG AsgStyle


%%サービスのスタイル
classDef SOU fill:#aaa,color:#fff,stroke:#fff
class OU1 SOU

classDef SNW fill:#84d,color:#fff,stroke:none
class NW1 SNW

classDef SCP fill:#e83,color:#fff,stroke:none
class CP1 SCP
class CP2 SCP

classDef SDB fill:#46d,color:#fff,stroke:#fff
class DB1 SDB

classDef SST fill:#493,color:#fff,stroke:#fff
class ST1 SST
```


* ALB: Application Load Balancer
* AP1, AP2: アプリケーションインスタンス
* DB: データベースインスタンス 
* ASG: Auto Scaling Group


## 課題

APインスタンスはASGに含まれていますが、負荷に応じたスケールイン・スケールは行わず、インスタンスの台数の指定のみでした。

これでは従量課金制によるコスト最適化というクラウドのメリットを活かせません。

現状、セッションをAPインスタンスの中に保持しているため、スケールインを行うと、シャットダウンされるインスタンスに割り振られていたユーザは急にログアウトされてしまうことになります。

これはユーザが入力していた内容が消えてしまうことや再ログインの不便に繋がるため許容できません。

また、アプリケーションは別チームが管理しているため、セッションを分離してAPインスタンス外に保存するようにするのとは別に何か方法が無いか調査することにしました。


※一度ログインしたら同じインスタンスにリクエストが割り振られるようにするため、スティッキーセッションを使っています

https://e-words.jp/w/%E3%82%B9%E3%83%86%E3%82%A3%E3%83%83%E3%82%AD%E3%83%BC%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3.html

https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/sticky-sessions.html


## 調べたこと

### 予想
シャットダウンする前に指定時間待つようにするための、deregistration delayというターゲットグループの機能によって、次のようなことが実現可能なのではと考えました。

* スケールイン対象のインスタンスには、
  * 新規セッションを割り当てない
  * 既存セッションが切れるくらい待ってからシャットダウンする
* これによって、スケールイン時にセッションが切れることを予防する（一定時間後にログアウトしてしまうのは許容する）


https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/load-balancer-target-groups.html#deregistration-delay


### 現実

ASGのサイズを手動で増減させて実験を行いました。

その結果、スケールイン対象になったインスタンスには新規セッションだけでなく既存セッションも割り振られないので、スケールインが始まったタイミングで問答無用でログアウトされてしまうということが分かりました。残念

deregistration delayはリクエストを返す前にシャットダウンしてしまってエラーになることを防ぐための機能なので仕方ないですね。


## 結論

素直にセッションをAPインスタンス外に保存するようにしてからスケールイン・アウトを実装することになりました

ベストプラクティスには従えということですね


## おわりに

アドベントカレンダー始まりましたね。去年に引き続きトップバッターを務めました。

去年はポエムっぽい記事でしたが今回は技術記事にしました。

去年の記事↓

https://qiita.com/kabik/items/53655f8fc81c7844f055


## 参考

図を描く時の参考にしました

https://qiita.com/b-mente/items/b17275090176d63d1d69



## おまけ

Auto Scalingによってスケールイン・アウトを実現するときに使えそうなものをメモしたものをこちらにも残しておきます。

誰かの助けになれば幸いです。


### 料金
https://aws.amazon.com/jp/autoscaling/faqs/#:~:text=AWS%20Auto%20Scaling%20%E3%81%AF%E7%84%A1%E6%96%99%E3%81%A7%E4%BD%BF%E7%94%A8%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99

* ASG自体は無料
* CloudWatchやEC2インスタンスやロードバランサなどの料金がかかるだけ


### ターゲット追跡スケーリング（Target tracking scaling）
https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/as-scaling-target-tracking.html

* メトリクスによって動的にスケーリングするための機能
* 複数は指定できない


### 予測スケーリング（Predictive scaling）
https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/ec2-auto-scaling-predictive-scaling.html

* 「過去 14 日間の CloudWatch メトリクスデータのパターンを検出して、次の 48 時間の時間ごとの予測を作成します。」と書いてあるので、月初め・終わりにリクエストが増減するワークロードは学習してくれない模様
    * 平日だけリクエストが多いようなワークロードには使えそう
* 予測のみモードにすると、予測の結果によってはスケーリングしない
    * 予測の結果はグラフで見れる
    * 予測と実際の値のデータをグラフで比較できる
        * これによって、予測の結果を確認して使えそうだったら予測の結果でスケーリングするように設定、みたいなことが可能
    * [ベストプラクティス](https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/ec2-auto-scaling-predictive-scaling.html)


### スケジュールされたスケーリング（Scheduled Scaling）
https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/ec2-auto-scaling-scheduled-scaling.html

予測スケーリングで対応できない、月初め・終わりにリクエストが増減するワークロードはこっちで対応すると良さそう


### warm up
https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/ec2-auto-scaling-default-instance-warmup.html

* 起動時のメトリクスの変化によってスケーリングが実施されないようにするためのもの
    * 例: 起動時にCPU使用率が上がってスケールアウトされるのは意図と異なる挙動。warm up時間を設定すれば回避可能
* リクエスト数でスケーリングする場合は関係なかった


### スティッキーセッション
https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/sticky-sessions.html

* アプリのCookieを使うか、ALBのCookieを使うか選べる
* セッションのタイムアウト時間を設定可能


### Termination Policy（終了ポリシー）
https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/ec2-auto-scaling-termination-policies.html

* どのインスタンスからシャットダウンするかを決めるポリシー
* 複数のポリシーを順番を決めて指定し、順番通りに評価する
* 気になったポリシーのみメモ
    * OldestInstance
        * 最も古いインスタンスからシャットダウンする
        * 本番環境で使えそう
    * NewestInstance
        * 最も新しいインスタンスからシャットダウンする
        * テスト時に使えそう
    * OldestLaunchTemplate
        * 最も古い起動テンプレートを使用しているインスタンスからシャットダウンする
        * OldestInstanceとの違いがよく分からない。組み合わせて使うもの？