AWS CodeDeploy デプロイ時ApplicationStopのフックスクリプトが動作しない

## 問題
CodeDeployがデプロイする時にApplicationStopのフックスクリプトが実行されず、アプリの停止や古いファイルの削除などクリーンアップがされていなくてデプロイが失敗しました。

## TL;DR
ApplicationStopで実行されるスクリプトは<font color="HotPink">前回</font>デプロイ成功したリビジョンのスクリプトです。初回はCodeDeploy以外の方法（手動、CloudFormation、AutoScalingGroup）などでデプロイし、以降をCodeDeployでデプロイする場合、<font color="HotPink">前回</font>デプロイ成功したリビジョンがないため、ApplicationStopのフックスクリプトは実行されません。

## ワークアラウンド
クリーンアップスクリプトをBeforeInstallイベントにフックします

## 問題の詳しい説明
例えばこのようなAppSpecがあります

``` AppSpec.yml
version: 0.0

os: linux

files:
  - source: source/rank1app.tar.gz
    destination: /var/webapps

permissions:
  - object: /var/webapps
    owner: webapp
    group: webapp
    type: 
      - directory

hooks:
  ApplicationStop:
    - location: Scripts/StopApp.sh
      runas: ubuntu
  AfterInstall: 
    - location: Scripts/Preparation.sh
      runas: ubuntu
  ApplicationStart:
    - location: Scripts/StartApp.sh
      runas: ubuntu
```

AppSpecではCodeDeployの各ライフサイクルイベントの動作をコントロールしています。デプロイアプリケーションのライフサイクルは図示のようになります
![alt](https://docs.aws.amazon.com/codedeploy/latest/userguide/images/lifecycle-event-order-in-place.png)

このインプレイスデプロイ用AppSpecのhooks部分をのタスクを説明します
1. ApplicationStop:すでに走っているアプリプロセスを止めて、バイナリなどを削除します
2. Install:隠れイベント、ダウンロードされたリビジョンのバンドルファイルがあり、CodeDeployAgentが指定したパッケージファイルを指定したパスにコピーします
3. AfterInstall: 指定パス内のパッケージファイルを解凍し、魔法をかけます
4. ApplicationStart:アプリプロセスを起動します

ApplicationStopのフックスクリプトはオフィシャルのドキュメントが勧めた通り

> This deployment lifecycle event occurs even before the application revision is downloaded. You can specify scripts for this event to gracefully stop the application or remove currently installed packages in preparation of a deployment. The AppSpec file and scripts used for this deployment lifecycle event are from the previous successfully deployed application revision.

[原文URL](https://docs.aws.amazon.com/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html)

アプリの停止、古いファイルの削除などをさせています。しかし、初回のデプロイがCodeDeploy経由か他の手段経由かによるとフックスクリプトが実行されない可能性があります

### 初回からCodeDeployでデプロイする場合
- 初回リビジョンをデプロイします。クリーンな環境のため（アプリプロセスはいません）、上記のタスク１は実行されず、タスク２から実行されます。デプロイ成功
- 二回目以降のリビジョンをデプロイする時、前回成功したリビジョンファイル（特にApplicationStopのフックスクリプト）があるため、タスク１から実行され、無事デプロイできます

### 初回は別手段（手動、CloudFormation、AutoScalingGroupなど）でデプロイし、以降CodeDeployでデプロイする場合
- 初回デプロイする時、成功します
- 以降CodeDeployでデプロイする時、初回リビジョンであるため、想定してる前回リビジョンのApplicationStopのフックスクリプトがないため、クリーンアップできず、デプロイ失敗します
- 今回のリビジョン内に使いたいフックスクリプトがあるのにフックされません。説明は

> Note
> An AppSpec file does not exist on an instance before you deploy to it. For this reason, the ApplicationStop hook does not run the first time you deploy to the instance. You can use the ApplicationStop hook the second time you deploy to an instance.

[ドキュメント](https://docs.aws.amazon.com/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html)を見ると、ライブサイクルイベントを説明する所に上記の補足があります。

## 解決方法
クリーンアップスクリプトをBeforeInstallイベントにフックすれば、現リビジョンのスクリプトを使えるため、AppSpecを修正すれば動きます

``` AppSpec.yml
version: 0.0

os: linux

files:
  - source: source/rank1app.tar.gz
    destination: /var/webapps

permissions:
  - object: /var/webapps
    owner: webapp
    group: webapp
    type: 
      - directory

hooks:
  BeforeInstall:
    - location: Scripts/StopApp.sh
      runas: ubuntu
  AfterInstall: 
    - location: Scripts/Preparation.sh
      runas: ubuntu
  ApplicationStart:
    - location: Scripts/StartApp.sh
      runas: ubuntu
```