AWS Beanstalk環境における、Blue/Greenデプロイの実装によるデプロイ作業の簡略化

 - Beanstalkで構築した環境で動くRailsアプリのデプロイを毎回手作業で行っていた
 - コンソール上でデプロイする場合、いちいちアプリを圧縮し手作業でUPしないといけない
 - デプロイだけなら上記手順で済むが、環境そのものの設定を変更したいときは一から作り直し。。。
 - CodeCommitのリポジトリを参照してBeanstalkをビルドし、デプロイ処理を実行するパイプラインを作れば、デプロイ自動化できる！ということを教えていただきましたので備忘録として書き記します
 - AWSに関してはかなり初心者（片手間で3ヵ月くらいしか触ってません）なので、おかしな部分があればご指摘いただければ幸いです


```
利用したAWSサービス：
 - Elastic Beanstalk
 - CodePipeline
 - CodeCommit
 - CodeBuild
 - CloudFormation
 ```

## Blue/Greenデプロイとは
```
In place: インスタンスはそのままに、新しいリビジョンのアプリのみをその場で反映させる
Blue/Green: 新しいリビジョンのアプリ用に、新しいインスタンスを構築して入れ替える

そして、実現方法とは別の軸で、反映のスピードで大まかに以下の3つの分類ができます。

All at once: 全台を一斉に新しいリビジョンでデプロイする
One by one: 1台ずつ新しいリビジョンをデプロイする
Batch: 数台(例えば半数)ずつ新しいリビジョンをデプロイする
```
参考：
https://aws.typepad.com/sajp/2015/12/what-is-blue-green-deployment.html
https://dev.classmethod.jp/cloud/operation-blue-green-deployment/

- 今回利用したのは、Blue/GreenによるAll at Onceの方式です
- 新しいインスタンスを構築し、全てのインスタンスにおいて一斉にデプロイを行う流れになります

## デプロイの流れ
1. 何らかのアプリを修正or環境（インスタンスタイプとか）を修正
2. CodeCommitにPushする
3. Pushされたリポジトリを元にCodeBuildを実行
4. 新しい環境でアプリが立ち上がる
 
## 手順
### 事前準備
- アプリが入ったディレクトリと、環境設定ファイルが入ったディレクトリを用意する
※※※-APPと※※※-ENVってなってるやつ
<img width="1298" alt="スクリーンショット 2020-01-09 18.00.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/425007/242abe36-021b-23af-d6a5-2826d34acb1b.png">

- APPの方はアプリを入れるだけ
- ENVの方は、.ebextensionsフォルダを作成し、環境の設定をしておく。
- buildspec.ymlを作成する（CodeBuildするときに実行される）

```buildspec.yml
version: 0.2

phases:
  install:
    runtime-versions:
      python: 3.7 

  pre_build:
    commands:
      - echo awsebcli install
      - pip install --upgrade pip
      - pip install awsebcli
      - pip --version
      - eb --version
  build:
    commands:
      - echo Build started on `date`
      - mkdir app_build
      - cd app_build
      # プラットフォームを指定
      - eb init api-dev1 --platform "ruby-2.6-(puma)" --region ap-northeast-1
      # SourceAPP（アプリが入ってるディレクトリ）をコピー
      - cp -a ${CODEBUILD_SRC_DIR_SourceAPP}/* ./
      - cp -a ../dev1/apiweb/.ebextensions ./../dev1/apiweb/.ebextensions/00.vpc.config > ./.ebextensions/00.vpc.config
      - cat .ebextensions/00.vpc.config
      - touch public/alive.html
      - envpre=`aws elasticbeanstalk describe-environments --application-name api-dev1 | jq '.Environments[] | select((.Status != "Terminated") and (.CNAME == "api-dev1-pre.ap-northeast-1.elasticbeanstalk.com"))' | jq -r '.EnvironmentName'`
      - echo ${envpre}
      # 既にBeanstalk環境が作成されていた場合、その環境をterminateし、新しく作り直す。
      # 詳しくは後ほど解説
      - if [ "te" != "te"${envpre} ]; then eb terminate ${envpre} --force; fi
      - envdate=`date +"%Y%m%d%H%M%S" --date='9 hours'`
      - eb create api-${envdate} -c api-dev-pre --timeout 30
```

- CodeCommitでリポジトリを作成し、クローンする
- 参考：https://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/setting-up-ssh-unixes.html 
- クローンしたディレクトリにそれぞれアプリと環境ファイルを入れ、Pushする
![スクリーンショット 2020-01-15 11.33.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/425007/403bcaa5-d7b3-daa1-e6b6-491f751a94b3.png)
こんな感じで、`アプリ用リポジトリ`と`環境用リポジトリ`で2つ分ける

### 1. CloudFormationのテンプレートでCodePipeLineとCodeBuildを作る
- CloudFormationを使う前提となります
  - CodePipelineのIAMを設定する（かなり多いので割愛）
  - CodeBuildのIAMを設定する（こちらも割愛）
  - デプロイ設定を記載する（下記参照）

```deployment.yml

# デプロイ設定

  CodeBuildImage:
    Type: String
    Default: "aws/codebuild/amazonlinux2-x86_64-standard:1.0"
    Description: Image used for CodeBuild project.

  CodeBuildApiWeb:
    Description: Creating AWS CodeBuild project
    Type: AWS::CodeBuild::Project
    Properties:
    #Artifactのタイプを指定する
      Artifacts:
        Type: CODEPIPELINE
      Description: !Sub Building stage for apiweb.
      # Buildするコンテナを指定する
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: !Ref CodeBuildImage
        Type: LINUX_CONTAINER
      Name: codebuild-dev-apiweb
      ServiceRole: !GetAtt BuildIam.Arn
      # Buildspec（ビルドの際に実行される処理が書かれたymlテンプレート）の場所を指定
      Source:
        BuildSpec: dev/apiweb/buildspec.yml
        Type: CODEPIPELINE
  
  PipelineApiWeb:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      ArtifactStore:
      //Artifactのストアを指定
        Location: !Ref ArtifactBucket
        Type: S3
      Name: !Sub codepipeline-dev-apiweb
      RestartExecutionOnUpdate: false
      RoleArn: !GetAtt PipeLineIam.Arn
      Stages:
        -
          Name: Source
          # ビルドするソースを指定する
          Actions:
            -
              Name: Source1
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeCommit
                Version: 1
              OutputArtifacts:
                -
                # 先にbeanstalkの環境を設定するファイルを持つリポジトリを指定
                  Name: SourceENV
              Configuration:
                PollForSourceChanges: false
                RepositoryName: dev-Env
                BranchName: master
              RunOrder: 1
            -
              Name: Source2
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeCommit
                Version: 1
              OutputArtifacts:
                -
                  Name: SourceAPP
              Configuration:
                PollForSourceChanges: false
                RepositoryName: dev-App
                BranchName: master
              RunOrder: 1
        -
        ＃CodeBuildの設定
          Name: Build
          Actions:
            -
              Name: Build
              InputArtifacts:
                -
                  Name: SourceENV
                -
                  Name: SourceAPP
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: 1
                Provider: CodeBuild
              Configuration:
                ProjectName: !Ref CodeBuildApiWeb
                PrimarySource: SourceENV
              RunOrder: 1
```

### 2. CloudFormationでスタックを作成する

### 3. CodePipelineが出来上がると同時に、CodeBuildが実行される
* この時点でCodeBuildが失敗した場合、yamlファイルの修正を行い再度スタックを作成すれば、新しいスタックが作られ古いスタックは削除される
* スタックには問題がなくて、アプリ側の問題（アプリの設定がおかしくてデプロイできない・環境設定の`builspec.yml`がおかしい、など。おそらく`builspec.yml`がおかしいパターンが多い）の場合は、スタックを再度作成するのではなく、以下の動きとなる
    1. エラーを修正してCodeCommitにPush
    2. CodePipelineで`変更をリリースする`を押下
    3. CodeBuildが実行される

### 4. beanstalkの環境がbuildspecに基づき、.ebextensionの設定値で作成される 
- この段階で作成される環境のURL名にはBlue/Greenデプロイ的な使い方をするために`pre`という名前がついている（以下`pre環境`とする）

### 5. 出来上がったpre環境をクローンする
- クローンする際に、`pre環境`のURLから、`pre`という文字を省いてURLを指定する
- 出来上がった`pre`のついていない環境を`本番環境`とする
![環境のクローンを作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/425007/c8019ffe-a031-bd18-13a1-334aafa13ef1.png)
![スクリーンショット 2020-01-31 16.41.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/425007/84142891-be67-93d2-569a-a84a34b5a906.png)


### 6. 本番環境が出来上がったら、pre環境を削除する
- これで本番環境のみがBeanstalk上に存在する状態となる

## 運用時のデプロイ手順
* 基本的には上述の手順と同じ

#### 1. アップデートしたアプリor環境設定ファイルをリポジトリにPUSHする

#### 2. codepipelineで、`変更をリリースする`を選択

#### 3. リポジトリが読み込まれ、新しいEBの環境が作成される
* この段階で作成された環境は、`pre環境`となる

#### 4. pre環境にて、動作確認を行う

#### 5. `URLをスワップ`で、本版環境のURLとpre環境のURLを入れ替える
![スクリーンショット 2020-01-31 16.41.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/425007/d6cd5b47-3240-20dc-4c29-f43d84a0a9ab.png)

#### 6. 問題なければ入れ替わったpre環境を削除する

#### 7. デプロイ作業完了

てな感じ

## 所感
#### メリット
- アプリ運用中でも、URLのスワップでほとんどダウンタイムなし（2~3秒程度）で入れ替えを行うことができる
- スワップ後に問題が発生した場合でも、再度URLをスワップすれば元の状態に戻せる
- Beanstalk環境を壊すだけでクリアな状態になるので、やり直しが楽
 
#### デメリット
- pre環境を残しておいたままにすると、当然ながら2倍の料金がかかるので注意
- Beanstalkの立ち上げに時間がかかる（約15分〜20分）ので、緊急時の対応に若干の時間がかかる可能性がある
- 大規模アーキテクチャだと運用は難しい？

#### 備考
- おかしな記述や不明瞭な部分があればコメントいただければ幸いです！