【AWS メモ⑧】Elastic Beanstalk 自分用にまとめてみた

## Elastic Beanstalk とは
Webアプリケーションをアップロードすると、運用に必要な環境の作成、デプロイ、ロードバランシングまで自動で構築できるPaaSである。
環境は、ウェブサーバー環境、ワーカー環境の2種類が用意されている。([公式サイト - Elastic Beanstalk 概念](https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/concepts.html))

また、複数コンテナを動作させるDocker環境の構築も可能。コンテナの管理はECSらしい。([公式サイト - 複数コンテナの Docker 環境](https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/create_deploy_docker_ecs.html))

## 使ってみる
Elastic Beanstalk をターミナルから使用するためのツールをインストールする。

```console
# とりあえず brew をアップデート
$ brew update

# ツールをインストール
$ brew install awsebcli
```

## HelloWorldしてみる

Elastic Beanstalk はアプリケーションと環境に分かれている。
アプリケーションには、アプリケーション名、キーペア名、ランタイムバージョン、リージョンを設定する。
環境には、オートスケーリング、ELBの設定をする。
 `アプリケーション`という枠の中にWebアプリケーションを実行する`環境`が複数存在するイメージ。

### アプリケーションの作成

```console
$ mkdir HelloWorld
$ cd HelloWorld

# アプリケーションを作成する(profileは適宜変更)
$ eb init --profile eb-demo
```

`eb init` を実行して以下の設定値で作成

- リージョン　　　　　：us-west-2
- アプリケーション名　：(デフォルト)
- ランタイム　　 　　　：PHP
- PHPバージョン　 　　：7.2
- キーペア　　　　　　：eb-demo


設定値が完了すると、`HelloWorld`ディレクトリの中に以下のファイル、ディレクトリが作成される。

```console
./
├── .gitignore
└── .elasticbeanstalk
    └── config.yml
```

`HelloWorld/.elasticbeanstalk/config.yml`の中身には、アプリケーションの情報（アプリケーション名、キーペア名、ランタイムバージョン、リージョン）が設定されている。

```console
branch-defaults:
  default:
    environment: null
    group_suffix: null
global:
  application_name: HelloWorld
  branch: null
  default_ec2_keyname: aws-eb
  default_platform: PHP 7.2
  default_region: us-west-2
  include_git_submodules: true
  instance_profile: null
  platform_name: null
  platform_version: null
  profile: null
  repository: null
  sc: null
  workspace_type: Application
```

### 環境の作成

環境を作成する。
コマンド実行後、しばらくすると環境が立ち上がる。
デフォルトでは、オートスケーリンググループ、EC2、ELB、セキュリティグループが作成される。

```console
# ebの作成
# 静的なページを返すだけの設定で作成する
$ echo "Hello World" > index.html

# dev-env という名前で環境を作成する
$ eb create dev-env
```

### ステータスの確認

環境のステータスは`eb status`で確認できる。

``` console
$ eb status

Environment details for: dev-env
  Application name: HelloWorld
  Region: us-west-2
  Deployed Version: app-200503_044529
  Environment ID: e-snihahmjcb
  Platform: arn:aws:elasticbeanstalk:us-west-2::platform/PHP 7.2 running on 64bit Amazon Linux 2/3.0.0
  Tier: WebServer-Standard-1.0
  CNAME: dev-env.eba-ijnmvfx5.us-west-2.elasticbeanstalk.com
  Updated: 2020-05-02 19:47:39.052000+00:00
  Status: Ready
  Health: Green
```

### ヘルスの確認

`eb health`で環境のヘルスチェックもできる。

![スクリーンショット 2020-05-03 4.49.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180939/58e60db5-7270-ee16-8ef1-ccb2cdc6f909.png)

### ログの確認

`eb logs`で環境ないのログを確認できる。

```console
$ eb logs

============= i-087965f9152f39898 ==============
----------------------------------------
/var/log/eb-engine.log
----------------------------------------
2020/05/02 19:47:24.930642 [INFO] Running command /bin/sh -c systemctl stop php-fpm.service
2020/05/02 19:47:24.935046 [INFO] Executing instruction: FlipApplication
2020/05/02 19:47:24.936947 [INFO] create soft link from /var/app/current/ to /var/www/html
2020/05/02 19:47:24.936981 [INFO] Executing instruction: start X-Ray
2020/05/02 19:47:24.936986 [INFO] X-Ray is not enabled.
2020/05/02 19:47:24.936990 [INFO] Executing instruction: start php-fpm
2020/05/02 19:47:24.936997 [INFO] No plugin in cfn metadata.
2020/05/02 19:47:24.937076 [INFO] Running command /bin/sh -c systemctl show -p PartOf php-fpm.service
2020/05/02 19:47:24.942972 [INFO] Running command /bin/sh -c systemctl daemon-reload
2020/05/02 19:47:25.012288 [INFO] Running command /bin/sh -c systemctl reset-failed
2020/05/02 19:47:25.016430 [INFO] Running command /bin/sh -c systemctl show -p PartOf php-fpm.service
2020/05/02 19:47:25.021426 [INFO] Running command /bin/sh -c systemctl is-active php-fpm.service
2020/05/02 19:47:25.024860 [INFO] Running command /bin/sh -c systemctl start php-fpm.service
2020/05/02 19:47:26.366513 [INFO] Executing instruction: start proxy with new configuration
2020/05/02 19:47:26.366550 [INFO] Running command /bin/sh -c /usr/sbin/nginx -t -c /var/proxy/staging/nginx/nginx.conf
2020/05/02 19:47:26.398655 [ERROR] nginx: the configuration file /var/proxy/staging/nginx/nginx.conf syntax is ok
nginx: configuration file /var/proxy/staging/nginx/nginx.conf test is successful

・
・
・

----------------------------------------
/var/log/nginx/access.log
----------------------------------------
172.31.36.8 - - [02/May/2020:19:50:05 +0000] "GET / HTTP/1.1" 200 12 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36" "180.56.80.253"
172.31.36.8 - - [02/May/2020:19:50:06 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://dev-env.eba-ijnmvfx5.us-west-2.elasticbeanstalk.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36" "180.56.80.253"


----------------------------------------
/var/log/nginx/error.log
----------------------------------------
2020/05/02 19:50:06 [error] 3758#0: *26 open() "/var/www/html/favicon.ico" failed (2: No such file or directory), client: 172.31.36.8, server: , request: "GET /favicon.ico HTTP/1.1", host: "dev-env.eba-ijnmvfx5.us-west-2.elasticbeanstalk.com", referrer: "http://dev-env.eba-ijnmvfx5.us-west-2.elasticbeanstalk.com/"
```

### ファイルを更新してデプロイしてみる

`index.html`を内容を書き換えてデプロイしてみる。
`eb deploy`で環境にデプロイできる。

```console
ファイルを書き換える
$ echo "Hello World 2" > index.html

# デフォルトだと、1回にすべてのインスタンスに対してデプロイを実行する(All at once)
# All at once だとダウンタイムが発生するので注意
$ eb deploy
```

ちなみに作成、デプロイ時に使用したソースコードと設定ファイルは、s3に保管される。
バケット名はデフォルトだと、`elasticbeanstalk-リージョン名-ランダムな文字列`となる。

### 環境変数を加えてみる
`eb setenv key=name` で環境変数を設定できる。

```console
$ eb setenv ENABLE_COOL_NEW_FEATURE=true
```


### アプリケーションを終了する

`eb terminate`で環境を終了できる。(環境に関する全てのサービスを削除する)

```console
# このコマンドで終了できる
# コマンドを実行後、環境名を入力してエンター（今回の例では dev-env）
$ eb terminate
```

## 設定ファイルの生成とカスタマイズ
起動しているアプリケーションの、設定ファイルを保存することができる。

```console
$ eb config save dev-env --cfg prod
```

コマンドを実行すると、`HelloWorld/.elasticbeanstalk/saved_configs/prod.cfg.yml`が作成される。
中身は、ELBの設定やオートスケーリングの設定等。

```console
EnvironmentConfigurationMetadata:
  Description: Configuration created from the EB CLI using "eb config save".
  DateCreated: '1588449138000'
  DateModified: '1588449138000'
Platform:
  PlatformArn: arn:aws:elasticbeanstalk:us-west-2::platform/PHP 7.2 running on 64bit Amazon Linux 2/3.0.0
OptionSettings:
  aws:elasticbeanstalk:command:
    BatchSize: '30'
    BatchSizeType: Percentage
  aws:elb:policies:
    ConnectionDrainingEnabled: true
  aws:elb:loadbalancer:
    CrossZone: true
  aws:elasticbeanstalk:environment:
    ServiceRole: aws-elasticbeanstalk-service-role
  aws:elasticbeanstalk:healthreporting:system:
    SystemType: enhanced
  aws:autoscaling:launchconfiguration:
    IamInstanceProfile: aws-elasticbeanstalk-ec2-role
    EC2KeyName: eb-demo
  aws:autoscaling:updatepolicy:rollingupdate:
    RollingUpdateType: Health
    RollingUpdateEnabled: true
EnvironmentTier:
  Type: Standard
  Name: WebServer
AWSConfigurationTemplateVersion: 1.1.0.0
```

### 設定ファイルから環境を起動してみる
`eb put`で設定ファイルから環境を起動できる。

```console
# prod はファイル名の.cfgの前の部分
$ eb config put prod
```

### 設定ファイルに設定を追加する
`HelloWorld/.elasticbeanstalk/saved_configs/prod.cfg.yml`にオートスケーリングの設定を追加する。

```console
# OptionSettingsの下に追加
  AWSEBAutoScalingScaleUpPolicy.aws:autoscaling:trigger:
    UpperBreachScaleIncrement: '2'
  AWSEBCloudwatchAlarmLow.aws:autoscaling:trigger:
    LowerThreshold: '20'
    MeasureName: CPUUtilization
    Unit: Percent
  AWSEBCloudwatchAlarmHigh.aws:autoscaling:trigger:
    UpperThreshold: '50'
```

設定を反映して起動してみる。
デプロイが完了するとオートスケーリンググループのスケーリングポリシーが更新される。

```console
$ eb config put prod
$ eb config dev-env --cfg prod
```

## デプロイのタイミングで任意の操作を実行
アプリケーションデプロイのタイミングで、インスタンス上で任意のコマンドを実行する仕組みがある。Beanstalkのアプリケーションバンドル直下に`.ebextensions`というディレクトリを作り、その中に拡張子`.config`が付いた設定ファイルを配置することで制御できる。
(内部では、cloudformationを実行して各種設定を反映している。、AWSコンソール画面上からも確認できる。)

今回の例では、こんなかんじ。

```console
HelloWorld
├── .ebextensions
├── .elasticbeanstalk
│   ├── config.yml
│   └── saved_configs
│       ├── prod.cfg.yml
│       └── initial-configuration.cfg.yml
├── index.html
└── .gitignore
```

### オートスケーリングの設定を制御してみる
`autoscaling.config`という名前で、オートスケーリンググループのconfigを作成する。

設定は以下の通り。
`option_settings`にオートスケーリングの設定等追加できる。

```console
# docs 1 https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options.html#configuration-options-precedence
# docs 2 https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/ebextensions-optionsettings.html

option_settings:
  # docs 3 https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options-general.html#command-options-general-autoscalingasg
  aws:autoscaling:asg:
    # 最小 1台
    MinSize: 1
    # 最大 10台
    MaxSize: 10
    # スケーリングのクールダウン 240秒
    Cooldown: 240

  # docs 4 https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options-general.html#command-options-general-autoscalinglaunchconfiguration
  aws:autoscaling:launchconfiguration:
    InstanceType: t2.micro
    # ImageId: ...
    # SecurityGroups: ...
    # InstanceIamProfile: ....
```

以下のコマンドでデプロイする。

```console
# 初回はこちら
$ eb create prod-env --cfg prod

# すでにアプリケーションが起動中ならこちら
$ eb deploy
```

### DynamoDBの作成と接続情報をデプロイ時に設定してみる
`dynamodb.config`という名前で、DB作成から接続情報を設定するconfigを作成する。

設定は以下の通り。
`Resources`、`Outputs`を使用して他のサービスの作成ができる。(CloudFormationと同じ構文)
`Resources`で作成したリソースの情報を`options_settings`で設定している。

```console
Resources:
  DynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      KeySchema:
         HashKeyElement:
           AttributeName: id
           AttributeType: S
      # create a table with the least available rd and wr throughput
      ProvisionedThroughput:
         ReadCapacityUnits: 1
         WriteCapacityUnits: 1

  NotificationTopic:
    Type: AWS::SNS::Topic

Outputs:
  NotificationTopicArn:
    Description: Notification topic ARN
    Value: { "Ref" : "NotificationTopic" }

option_settings:
  aws:elasticbeanstalk:application:environment:
    # デプロイ時に設定を環境変数に登録
    NOTIFICATION_TOPIC: '`{"Ref" : "NotificationTopic"}`'
    DYNAMODB_TABLE: '`{"Ref" : "DynamoDBTable"}`'
    AWS_REGION: '`{"Ref" : "AWS::Region"}`'
```

設定を反映して起動してみる。

```console
$ eb deploy
```

デプロイ後、サーバー内で以下のコマンドを実行すると環境変数に接続情報が設定されていることが確認できる。

```console
$ /opt/elasticbeanstalk/bin/get-config environment -k NOTIFICATION_TOPIC

$ /opt/elasticbeanstalk/bin/get-config environment -k DYNAMODB_TABLE

$ /opt/elasticbeanstalk/bin/get-config optionsettings
```


### デプロイ時にコマンドを実行してみる
`commands.config`という名前で、configを作成する。
`commands`と`container_commands`の違いとしては、前者はアプリケーションのデプロイ前なのでアプリケーション内のファイルに変更を加えることができない。
後者は、アプリケーションがデプロイされたあとなのでアプリケーション内のファイルに変更を加えることができる。

```
# docs 1 https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html#linux-commands 
commands:
  create_hello_world_file:
    command: touch hello-world.txt
    cwd: /home/ec2-user

# docs 2 https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html#linux-container-commands
container_commands:
  modify_index_html:
    # アプリケーション内のファイルを触れるので、中身を書き換えてみる
    command: 'echo " - modified" >> index.html'

  database_migration:
    command: 'echo "TODO: database migration"'
    # 以下の設定で他のcontainer_commandsよりも優先しての実行ができる
    leader_only: true
```

設定を反映して起動してみる。

```console
$ eb deploy
```

動作を確認してみる。

```console
$ curl http://prod-env.hogehogefugafuga.us-west-2.elasticbeanstalk.com
Hello World
 - modified
```

## デプロイポリシーについて
アップデート方法は以下の5通りある。（[公式サイト - Elastic Beanstalk 環境へのアプリケーションのデプロイ](https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/using-features.deploy-existing-version.html)）

文章だけだとわかりづらいので図説してみる。

### All at once

デフォルト設定ではこの方式をとる。
起動中のアプリケーションに一括でデプロイを行う。
デプロイ完了までの時間が短いが、ダウンタイムが発生する。
![スクリーンショット 2020-05-05 19.30.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180939/30f408bd-4e33-4fae-735d-6fcf30c78bde.png)

デプロイに失敗した場合、自動ロールバックにより古いアプリケーションを再度デプロイする。
![スクリーンショット 2020-05-05 19.30.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180939/39309ca0-1dd8-2121-03fd-7c5d0ba47457.png)


### ローリング（Rolling）

バッチサイズ分のインスタンスをELBからデタッチして、デプロイを行う。
デプロイが完了後、アタッチする。これを全てのインスタンスが新しいアプリケーションになるまで繰り返す。
注意点としては、デタッチするインスタンス分のリソースが環境から減ることになるので、一時的にインスタンス一台あたりの負荷が上昇する点。
また、古いアプリケーションと新しいアプリケーションが混在する時間が存在する。
![スクリーンショット 2020-05-04 20.02.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180939/0238686c-8e78-e2df-8693-65d5cdc6affa.png)

デプロイに失敗した場合、初回バッチの場合は、自動ロールバックにより古いアプリケーションを再度デプロイするだけでロールバックが完了するが、
初回バッチ以外で失敗した場合、した図のようにバージョンが混在した状態になってしまうので手動による再デプロイが必要になる。
![スクリーンショット 2020-05-04 20.02.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180939/d40c79b1-e8fb-ab1f-fd52-90365ac8e449.png)


### 追加バッチとローリング（Rolling with additional batch）

`ローリング`と違い、バッチサイズ分のインスタンスを追加するので、リクエストを受け付けるインスタンスの数は変わらない。
バッチサイズ分のインスタンスを追加して、新しいアプリケーションをデプロイする。デプロイ成功後、ELBから新しいアプリケーションをELBにアタッチして、デプロイしたインスタンス分、古いアプリケーションが起動しているインスタンスをデタッチ、終了する。
注意点としては、`ローリング`と同じく古いアプリケーションと新しいアプリケーションが混在する時間が存在する点。

![スクリーンショット 2020-05-05 19.43.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180939/e9f68eac-e65d-15e4-709c-2c2f83db69c9.png)

デプロイに失敗した場合、`ローリング`の場合と同じく初回バッチの場合は、自動ロールバックにより古いアプリケーションを再度デプロイするだけでロールバックが完了するが、初回バッチ以外で失敗した場合、した図のようにバージョンが混在した状態になってしまうので手動による再デプロイが必要になる。
![スクリーンショット 2020-05-05 19.43.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180939/1a59f29d-d053-1d92-9576-f892f932a8d7.png)

### 変更不可（Immutable）

新しいアプリケーションをデプロイするための Auto Scaling Group を作成して、動作の確認が取れたインスタンスから古いアプリケーションと同じ Auto Scaling Group に追加する。全てのインスタンスの追加完了後、デプロイ用に作成した Auto Scaling Group の削除と古いアプリケーションが起動しているインスタンスの終了を行う。
注意点としては、古いアプリケーションと新しいアプリケーションが混在する時間が存在する点。

![スクリーンショット 2020-05-05 19.54.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180939/06a60d14-2fab-b7d7-cb57-90861810fb3d.png)

デプロイに失敗した場合、新しいアプリケーションが起動しているインスタンスの終了とデプロイ用に作成した Auto Scaling Group の削除を行う。

![スクリーンショット 2020-05-05 19.53.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180939/a737904b-8d10-85f3-1d41-4c3ca3d39460.png)

### ブルーグリーンデプロイ（Blue/Green）

Route53を用意すれば、ブルーグリーンデプロイもサポートしている。（もはやElastic Beanstalkの機能では無い気がするけど）
トラフィックの重み付け等、設定が可能。（カナリアデプロイが利用できる）

下の図はカナリアデプロイの場合

![スクリーンショット 2020-05-05 20.00.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180939/e9249acc-976c-4b26-fa2d-ae8adb154d15.png)

デプロイに失敗した場合、環境の破棄とトラフィックの重み付けの変更を行うことでロールバックが可能。

![スクリーンショット 2020-05-05 20.00.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180939/6d77f781-97c7-812b-9d6d-8823a805132e.png)

## 解決できなかったこと

東京リージョン（とかオハイオとか）では以下のコマンド実行時にエラーが発生した。AWS何もわからない。

```
$ eb config put prod
ERROR: NotFoundError - Elastic Beanstalk can't find a platform version that matches "PHP 7.4 running on 64bit Amazon Linux 2".
```