本番稼働中のシステムでダウンタイム無しでECS on EC2からFargateに移行

## 稼働中のECS on EC2システムの構成

- クライアントリクエストはALBとターゲットグループでECSにルーティングしている
- クラスター内にサービスは２つあって1つはNginxコンテナとPHP-FPMコンテナが格納されているタスクが動いている
- もう1つはバックグラウンドで非同期処理を行うワーカープロセスのコンテナが格納されているタスクが動いている
- セッション管理はElastiCacheのRedisを使っている
- DBはRDSを使っている
- Route53でシステムのFQDNにALBのエイリアスを設定している
- アプリケーションはPHP, Laravelで実装している
- 他にもSESやSNSやS3など色々使っているがそれらリソースへのアクセス許可を付与したアプリケーション用IAMユーザを作成してアプリケーションに設定し、各リソースを利用するためのパッケージをインストールして動かしている

## 移行概要

一言でいうと、ロードバランサーとアプリケーションサーバー部分だけ新しく作成してDNSで切り替える。

以下の流れで進めることにより、ダウンタイム（移行のためのシステムメンテナンスとしてシステム停止する時間）無しでFargateに移行する。

1. Fargate移行のためにNginxの設定を更新する
1. Fargate用に新しいクラスターとタスク定義を作成する
1. 新しいALBとターゲットグループを作成する
1. サービスに割り当てるセキュリティグループを作成する
1. RedisとRDSのセキュリティグループに作成したサービス用セキュリティグループからのアクセスを許可する
1. 新しいECSサービスを作成する
1. ALBのDNS名にブラウザでアクセスして正常に動作することを確認する
1. Route53でシステムのFQDNに新しく作成したALBのエイリアスを設定して更新する

以上で、まさに移行中に利用しているクライアントもセッションが切れることなくシステムを使い続けることができる。
けっこうインフラをゴソッと入れ替えるので勇気が要るが何の問題も無く移行できた。

※当然、いきなり本番環境で実行するのではなく、同じ構成で作られたステージング環境で実行して必要なテストを行った上で本番環境で実行する

## 手順

### 1. Fargate移行のためにNginxの設定を更新する
Fargateではコンテナ間通信をする際は `127.0.0.1` を指定するとあるので、Nginxのconf設定で `fastcgi_pass php-fpm:9000;` となっている箇所を `fastcgi_pass 127.0.0.1:9000;` に更新する。
ECS on EC2ではコンテナ間通信をする際はコンテナにリンクを設定する必要があったが、Fargateではこれで通信出来るのでリンクを設定しなくてよい。

既存のECRリポジトリに更新したイメージをPushしても良いが、切り戻しのことを考えて新たなリポジトリを作成してイメージをPushする。

### 2. Fargate用に新しいクラスターとタスク定義を作成する
**クラスター作成**
クラスターはFargate用のものでなくてはならないため新しく作成するしかない。既存のECS on EC2のクラスターをFargate用に変更することはたぶん出来ない。おそらく。きっと。

- 既存のVPC内で使いたいため、一緒に新しいVPCは作らない
- CloudWatch Container Insightsは便利なのでクラスター作成時に有効にして作成する

**タスク定義作成**
タスク定義もFargate用のものでなくてはならないため新しく作成するしかない。既存のECS on EC2のタスク定義をFargate用に変更することはたぶん出来ない。おそらく。きっと。

- SESやS3などのリソースへのアクセス許可はアプリケーション用IAMユーザを作成してアプリケーションに設定しているためタスクロールは何も選択しない
- タスク実行ロールはAWSが用意してくれているecsTaskExecutionRoleを選択
- タスクメモリとCPUは適切な値を選択
- コンテナの定義は基本的に既存と一緒で相違点は以下
  - Nginxのコンテナのイメージは新規作成したリポジトリを指定
  - Nginxのコンテナで設定しているPHP-FPMコンテナと通信するためのリンク設定はしなくてよい
  - アクセス先が切り替わったことを確認するためにロググループ名は既存のものとは別にする

### 3. 新しいALBとターゲットグループを作成する
- 設定内容は基本的に既存と一緒で良くてセキュリティグループも既存で使っているものを割り当てる
- ターゲットグループのターゲット種類はインスタンスではなくIPを選択する
- ターゲットの登録でIPの許可される範囲は空白のまま

### 4. サービスに割り当てるセキュリティグループを作成する
- 既存と同じVPCに作成する
- インバウンドルールではALBからのアクセスを許可する

### 5. RedisとRDSのセキュリティグループに作成したサービス用セキュリティグループからのアクセスを許可する
- Redisのセキュリティグループのインバウンドルールにて手順4で作成したセキュリティグループからのアクセスを許可する
- RDSのセキュリティグループのインバウンドルールにて手順4で作成したセキュリティグループからのアクセスを許可する

### 6. 新しいECSサービスを作成する
- 起動タイプはFARGATEを選択する
- タスク定義は手順2で作成したタスク定義を選択する
- タスクの数は適切な数を設定する
- セキュリティグループは手順4で作成したセキュリティグループを選択する
- ロードバランサーは手順3で作成したALBを選択してロードバランサーにNginxのコンテナを追加してターゲットグループは手順3で作成したターゲットグループを選択する
- ワーカープロセス用のサービスを作成するときはロードバランサーはなし

### 7. ALBのDNS名にブラウザでアクセスして正常に動作することを確認する
ALBに設定されているDNS名をコピーしてブラウザからアクセスして正常に動作することを確認する、、、が一発で上手くはいかない。
このデバッグが少し大変。

まずはタスクが正常に動いているかどうかを確認する。
タスクのステータスがRUNNINGになった後にSTOPしてまた新しいタスクが立ち上がって、、ということを繰り返している場合はSTOPしたタスクの詳細をみてエラーメッセージから原因を調査する。
たいていALBからのヘルスチェックが上手くいってなかったりする。

エラーメッセージが記載されてない場合があって困る。

CloudWatchのログをみて、Nginxにヘルスチェックのアクセスログがなかったらコンテナに到達する以前に問題が起きているということ。
セキュリティグループの設定が足りていなかったりするのでその辺りを見直す。

CloudWatchのログをみて、Nginxにヘルスチェックのアクセスログがあるなら通信がコンテナに到達しているということ。
コンテナに到達しているのに正常に動作しない場合は200以外のステータスコードを返している場合が多いので、ステータスコードは何になっているのかエラーメッセージには何と記載されているのかを確認して対応する。

アクセスログでステータスコードが500になっているとアプリケーション内で問題があるということ。
エラーメッセージが出力されている場合はそこから原因を調査する。
場合によってはローカルで同じようにイメージをビルドして再現調査をしたりする。

頑張ってデバッグして正常に動作させる。

### 8. Route53でシステムのFQDNに新しく作成したALBのエイリアスを設定して更新する
- 反映に時間がかかる場合はTTLをいったん短くするなどして切り替えを早める
- digコマンドでIPが新しいALBのIPに変わったことを確認
- CloudWatchでも新しいコンテナの方にアクセスが流れてきていることを確認

## EC2インスタンスの削除と切り戻しの備え
使われなくなったEC2インスタンスは利用料金がもったいないので、CloudWatchで既存コンテナにリクエストが無くなったことを確認してから削除する。

Auto Scaling グループでインスタンスを管理しているので、Auto Scaling グループの希望するインスタンス数を0で更新すると勝手にインスタンスが終了する。
EBSが残ってしまう場合は忘れずに削除する。

インスタンスが終了しているということは当然ECSのタスクも0になっている。
このままでも良いがサービスの必要タスク数も0で更新しておく。

また、何か問題が発生して切り戻したくなった場合に備えて切り戻し手順を用意しておく。

1. Auto Scaling グループでインスタンスの数を適切な数で設定して更新する
1. ECSのサービスを更新してタスク数を適切な数で設定して更新する
1. ALBのDNS名にアクセスして問題ないことを確認する
1. Route53でエイリアス設定を元のALBに戻す

## 監視設定
CloudWatchで最低限の監視設定をしておく。

- 死活監視的な目的でALBのHealthyHostCountでしきい値設定する
- タスク監視的な目的でRunningTaskCountでしきい値設定する

## 後片付け
EC2インスタンスは簡単に復活できるのですぐに削除しても良いが、既存で使っていた以下のリソースに関しては念の為1ヶ月くらい様子を見守ったあとに削除する。
削除したら切り戻しはできない。（出来るけどけっこう時間がかかってしまう）

- NginxのECRリポジトリ
- タスク定義
- クラスター、サービス
- ALB、ターゲットグループ
- セキュリティグループ
- RDSとRedisのセキュリティグループのインバウンドルールにあるEC2インスタンスからのアクセス許可
- Auto Scaling グループ、起動テンプレート
- CloudWatchのアラーム