【AWS】Elastic BeanstalkによるEC2へのPython Webアプリのデプロイ


## Elastic Beanstalkとは
Elastic Beanstalkは、アプリケーションのデプロイとスケーリングに関するインフラストラクチャの詳細な設定や管理について心配することなく、簡単かつ迅速にアプリケーションをデプロイできるサービス。これにより、Lambdaの15分制限や料金制限で、EC2へWebアプリをデプロイする必要になった際に、開発者は面倒な環境設定を気にせずにアプリをEC2へデプロイできる。

下記、公式リンク

https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/GettingStarted.CreateApp.html

#### デプロイの流れ
![ElasticBeanstalk.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483556/dadb2b08-9974-f2f2-ce0f-e0d2615612a2.png)

## 料金
**Elastic Beanstalk に対する追加料金はなく**、アプリケーションが使用する基になる AWS リソースに対してのみ料金がかかる。

## Elastic Beanstalkによる環境構築
1. Elastic Beanstalkコンソールを開きます。
1. 「アプリケーションの作成」を選択します。
1. 「アプリケーション名」に「getting-started-app」と入力します。
1. 必要に応じてアプリケーションのタグを追加します。
1. 「プラットフォーム」を選択します。
1. 「次へ」を選択します。
1. 「サービスアクセスの設定」ページが表示されます。
1. 「既存のサービスロールを使用する」を選択します。
1. 次に、EC2インスタンスプロファイルのドロップダウンリストに焦点を当てます。このドロップダウンリストに表示される値は、以前に環境を作成したかどうかによって異なる場合があります。
     - EC2インスタンスプロファイルのドロップダウンリストに「aws-elasticbeanstalk-ec2-role」と表示される場合、それを選択します。
    :::note alert
     ここで正しいIAM Roleを選択しないと以下のエラーが出ます。
    【ERROR】 **The instance aws-elasticbeanstalk-ec2-role associated with the environment does not exist.** 
    上記エラーが出た場合、なぜかaws-elasticbeanstalk-ec2-roleがIAM ロールに作成されていないので、下記を参照してマニュアルでEC2のロールを三つのポリシーを追加して作成する。
     - AWSElasticBeanstalkWebTier
     - AWSElasticBeanstalkWorkerTier
     - AWSElasticBeanstalkMulticontainerDocker
    
    https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/concepts-roles-instance.html
1. 「サービスアクセスの設定」ページで「レビューにスキップ」を選択します。
    これにより、オプションの手順がスキップされます。
1. 「レビュー」ページには、選択した内容の要約が表示されます。
1. ページの一番下にある「送信」を選択します。

サンプルアプリケーションでは、以下のAWSサービスが作成される
 - EC2
 - Instance security group
 - S3 bucket
 - CloudWatch alarms
 - CloudFormation stack
 - Domain name ※Route53では作成されてない

## Elastic BeanstalkによるWebアプリのデプロイ
1. 環境のプラットフォームに一致するサンプル アプリケーション(https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/samples/python.zip) をダウンロードする
1. Elastic Beanstalk コンソールを開く
1. ナビゲーションペインで、[環境]を選択し、リストから環境の名前を選択する
1. [アップロードしてデプロイ]を選択する
1. ダウンロードしたzipをアップロードする
:::note alert
  Zipファイルは、親ディレクトリを含めてはいけません。ファイルのみを圧縮したものをアップロードして下さい。
:::
1. 「デプロイ」を選択する

## Webアプリを編集してデプロイする


```Python
import logging.handlers

# Create logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# Handler 
LOG_FILE = '/tmp/sample-app.log'
handler = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes=1048576, backupCount=5)
handler.setLevel(logging.INFO)

# Formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# Add Formatter to Handler
handler.setFormatter(formatter)

# add Handler to Logger
logger.addHandler(handler)

welcome = """
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <!--
    Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved.

    Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at

        http://aws.Amazon/apache2.0/

    or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
  -->
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Welcome</title>
  <style>
  body {
    color: #ffffff;
    background-color: #E0E0E0;
    font-family: Arial, sans-serif;
    font-size:14px;
    -moz-transition-property: text-shadow;
    -moz-transition-duration: 4s;
    -webkit-transition-property: text-shadow;
    -webkit-transition-duration: 4s;
    text-shadow: none;
  }
  body.blurry {
    -moz-transition-property: text-shadow;
    -moz-transition-duration: 4s;
    -webkit-transition-property: text-shadow;
    -webkit-transition-duration: 4s;
    text-shadow: #fff 0px 0px 25px;
  }
  a {
    color: #0188cc;
  }
  .textColumn, .linksColumn {
    padding: 2em;
  }
  .textColumn {
    position: absolute;
    top: 0px;
    right: 50%;
    bottom: 0px;
    left: 0px;

    text-align: right;
    padding-top: 11em;
    background-color: #1BA86D;
    background-image: -moz-radial-gradient(left top, circle, #6AF9BD 0%, #00B386 60%);
    background-image: -webkit-gradient(radial, 0 0, 1, 0 0, 500, from(#6AF9BD), to(#00B386));
  }
  .textColumn p {
    width: 75%;
    float:right;
  }
  .linksColumn {
    position: absolute;
    top:0px;
    right: 0px;
    bottom: 0px;
    left: 50%;

    background-color: #E0E0E0;
  }

  h1 {
    font-size: 500%;
    font-weight: normal;
    margin-bottom: 0em;
  }
  h2 {
    font-size: 200%;
    font-weight: normal;
    margin-bottom: 0em;
  }
  ul {
    padding-left: 1em;
    margin: 0px;
  }
  li {
    margin: 1em 0em;
  }
  </style>
</head>
<body id="sample">
  <div class="textColumn">
    <h1>Congratulations</h1>
    <p>Your first AWS Elastic Beanstalk Python Application is now running on your own dedicated environment in the AWS Cloud</p>
    <p>This environment is launched with Elastic Beanstalk Python Platform</p>
  </div>
  
  <div class="linksColumn"> 
    <h2>What's Next?</h2>
    <ul>
    <li><a href="http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/">AWS Elastic Beanstalk overview</a></li>
    <li><a href="http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/index.html?concepts.html">AWS Elastic Beanstalk concepts</a></li>
    <li><a href="http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/create_deploy_Python_django.html">Deploy a Django Application to AWS Elastic Beanstalk</a></li>
    <li><a href="http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/create_deploy_Python_flask.html">Deploy a Flask Application to AWS Elastic Beanstalk</a></li>
    <li><a href="http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/create_deploy_Python_custom_container.html">Customizing and Configuring a Python Container</a></li>
    <li><a href="http://docs.amazonwebservices.com/elasticbeanstalk/latest/dg/using-features.loggingS3.title.html">Working with Logs</a></li>

    </ul>
  </div>
</body>
</html>
"""


def application(environ, start_response):
    path = environ['PATH_INFO']
    method = environ['REQUEST_METHOD']
    if method == 'POST':
        try:
            if path == '/':
                request_body_size = int(environ['CONTENT_LENGTH'])
                request_body = environ['wsgi.input'].read(request_body_size)
                logger.info("Received message: %s" % request_body)
            elif path == '/scheduled':
                logger.info("Received task %s scheduled at %s", environ['HTTP_X_AWS_SQSD_TASKNAME'],
                            environ['HTTP_X_AWS_SQSD_SCHEDULED_AT'])
        except (TypeError, ValueError):
            logger.warning('Error retrieving request body for async work.')
        response = 'This is my APP!'
    else:
        response = welcome
    start_response("200 OK", [
        ("Content-Type", "text/html"),
        ("Content-Length", str(len(response)))
    ])
    return [bytes(response, 'utf-8')]
```


以下のコマンドでPOSTリクエストを送り、正常にレスポンスが返ってくるか確認する。
```
curl -X POST -d {\"aba\":\"aaa\"} http://getting-started-app-env.eba-******.ap-northeast-1.elasticbeanstalk.com/
```

## Python開発環境の構築
下記リンク記事を参照

https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-apps.html

デプロイしたapplication.pyは以下の場所にある。
```
/var/app/current/application.py
```

デプロイのデバッグの方法は、Cloud Watchログに出力して確認する

1. 環境 -> 作成した環境を選択 -> 設定 -> 更新、モニタリング、ログ記録の編集を押す
1. CloudWatch ログへのインスタンスログのストリーミングをアクティブにする
1. 適当にデプロイして、エラーを出させる
1. CLoudWatchのロググループを開き、下記のようなロググループを開く
    ```
    /aws/elasticbeanstalk/Getting-started-app-env/var/log/web.stdout.log
    ```

Pythonのデバッグは、loggerで出力したログを確認する。
```
tail /tmp/sample-app.log 
```

環境変数の設定は、Elastic Beanstalkコンソールから行える。
1. 環境 -> 作成した環境を選択 -> 設定 -> 更新、モニタリング、ログ記録の編集を押す
1. 環境プロパティで環境変数の設定をする

AWSサービス(S3やDynamoDB)へのアクセス権限は、サービスロールへ付与する
```
arn:aws:iam::*********:role/service-role/aws-elasticbeanstalk-service-role
```

APIのTimeout設定は、Nginxの場合、以下のように設定する。
```
cd /etc/nginx/conf.d/elasticbeanstalk
sudo vi 00_application.conf

location / {
    proxy_pass          http://127.0.0.1:8000;
    proxy_http_version  1.1;

    proxy_set_header    Connection          $connection_upgrade;
    proxy_set_header    Upgrade             $http_upgrade;
    proxy_set_header    Host                $host;
    proxy_set_header    X-Real-IP           $remote_addr;
    proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
    proxy_read_timeout 1000;
}

sudo service nginx restart
```

## 参考リンク

https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/tutorials.html