平凡な社会人の日記

平凡な社会人の日記です。怠惰な毎日を送っております。

【AWS で遊ぶ】 AWS Elastic Beanstalk でインフラのことを気にせずに簡単デプロイ!

 本稿の対象者

  • AWS でとりあえずアプリを動かしたい
  • 小さく作ってみたい
  • AWS ElasticBeanstalkについて知りたい

本稿の目標

  • AWS Elastic Beanstalk を使って Flask で書かれたアプリケーションをデプロイしてみる
  • AWS Elastic Beanstalk が何をしてくれるかを知る

はじめに

こんにちは。新卒 1 年目エンジニアのさっちゃんです。普段は Spring boot で API の設計・実装をしたり、AWS 上で Python で Lambda 書いたりしてます。使っている AWS のサービスは主に API Gateway, Lambda, Cognito, Neptune, Dynamo, S3 などで、最近は CloudFormation を使ってインフラをコード化することにハマってます。なんでもコードで管理したいお年頃。

さて、最近趣味で AWS ソリューションアーキテクト アソシエイトの勉強をしていると、さまざまなサービスが出てくるので遊んでみたくなります。その中でも「アプリケーションを実行しているインフラストラクチャについて深く意識していなくても、アプリケーションのデプロイとその管理を簡単に行うことができます。」(徹底攻略 AWS 認定ソリューションアーキテクト -アソシエイト教科書 p.90)と紹介されていた AWS Elastic Beanstalk を使えばどれくらい簡単にデプロイできるものなのか、何をしてくれるのか気になったので実際に使ってみました。

AWS Elastic Beanstalk とは

まずは AWS Elastic Beanstalk とはなんなのか、いつも通り公式サイトを見てみましょう。

AWS Elastic Beanstalk は、Java、.NET、PHP、Node.js、PythonRuby、Go および Docker を使用して開発されたウェブアプリケーションやサービスを、Apache、Nginx、Passenger、IIS など使い慣れたサーバーでデプロイおよびスケーリングするための、使いやすいサービスです。

お客様はコードをアップロードするだけで、Elastic Beanstalk が、キャパシティのプロビジョニング、ロードバランシング、Auto Scaling からアプリケーションのヘルスモニタリングまで、デプロイを自動的に処理します。同時に、お客様のアプリケーションが稼動している AWS リソースの完全な制御を維持でき、いつでも基盤となるリソースにアクセスすることができます。

Elastic Beanstalk には追加料金はかかりません。アプリケーションを格納および実行するために必要な AWS のリソースに対してのみお支払いいただきます。

なるほど分からん。とにかく、コードをアップロードすればデプロイやロードバランシング、ヘルスモニタリングをいい感じにやってくれるらしいです。まぁとにかくやってみるかー。

まずはサンプルをデプロイ

まぁとにかく手を動かしてみるのがいいですね。コンソールから Elastic Beanstalk を選択してアプリケーションを作成します。名前は、このアプリを作成することは私にとって偉大な一歩なので GreatApplication にします。

f:id:physics-heibon:20211031012959p:plain
偉大な一歩

言語は Python を選択します。

f:id:physics-heibon:20211031013025p:plain

1 分くらい待つと次のような画面が出ます。なにかよくわかりませんがデプロイされたようです。

f:id:physics-heibon:20211031013106p:plain
がちょんがちょん...。

f:id:physics-heibon:20211031013128p:plain
なんかできた。

greatapplication-env.eba-ruyxxcr5.ap-northeast-1.elasticbeanstalk.com をブラウザで見ると次のように、Your first AWS Elastic Beanstalk Python Application is now running on your own dedicated environment in the AWS Cloud とデフォルト画面が出ます。

f:id:physics-heibon:20211031013148p:plain
おー。実感が湧かないけどデプロイできたみたい。

しかし自分はコードを何も見ていません。ソースコードはどこにあるんでしょうか?チュートリアルおよびサンプル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 = ''
    else:
        response = welcome
    start_response("200 OK", [
        ("Content-Type", "text/html"),
        ("Content-Length", str(len(response)))
    ])
    return [bytes(response, 'utf-8')]

では template の部分を変えてみてデプロイの練習をしてみます。

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>
  <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;
  }
  h1 {
    font-size: 500%;
    font-weight: normal;
    margin-bottom: 0em;
  }
  </style>
</head>
<body>
  <h1>
    猫は偉大。
  </h1>
</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 = ''
    else:
        response = welcome
    start_response("200 OK", [
        ("Content-Type", "text/html"),
        ("Content-Length", str(len(response)))
    ])
    return [bytes(response, 'utf-8')]

書き換えてzip -r sample.zip ./などで zip に固めて「アップロードとデプロイ」からファイルを選択します。そしてもう一度 Greatapplication-env.eba-ruyxxcr5.ap-northeast-1.elasticbeanstalk.com を見てみると、ちゃんと「猫は偉大。」と表示されています。なるほど、コードをアップロードするだけで勝手にデプロイされているらしい。なんかよく分からんけど動くからヨシッ!

f:id:physics-heibon:20211031013242p:plain

もう満足したので環境を削除します。次のように削除は簡単です。

f:id:physics-heibon:20211031013303p:plain

Flask で書かれたアプリケーションをデプロイ

Python だけだと味気ないので Flask で書かれたアプリケーションでもデプロイしてみましょうかね。Flask なんも知らんけど。先程と同様に、アプリケーション名は GreatFlaskApplication として(自分が作るものはなんでも偉大なので)、プラットフォームは Python にします。また、先ほどはアプリケーションコードとして「サンプルアプリケーション」を選択しましたが、今回は「コードのアップロード」を選択します。また、コードについては Deploying a Flask App to AWS Elastic Beanstalk を参考に、次のようなディレクトリ構成をとることにします。

.
|--- .ebextensions
|       |--- app.config
|--- application.py
|--- requirements.txt

中身は次の通りです。

app.config

files:
  "/etc/httpd/conf.d/wsgi_custom.conf":
    mode: "000644"
    owner: root
    group: root
    content: WSGIApplicationGroup %{GLOBAL}

requirements.txt

Flask==1.1.1

application.py

from flask import Flask
application = Flask(__name__)

@application.route("/")
def index():
    return "Your Flask App Works!"

@application.route("/hello")
def hello():
    return "Hello World!"


if __name__ == "__main__":
    application.run(port=5000, debug=True)

これらを zip に固めて「ソースコード元」としてアップロードします。これだけでいいのですが、今回は「より多くのオプションを設定」を選んでどんな設定があるのか見てみましょう。

f:id:physics-heibon:20211031013423p:plain

f:id:physics-heibon:20211031013442p:plain

f:id:physics-heibon:20211031013456p:plain

f:id:physics-heibon:20211031013510p:plain

f:id:physics-heibon:20211031013534p:plain

他にも設定がありますが、そのままで使います。

f:id:physics-heibon:20211031013609p:plain

がちょんがちょん...。CloudWatch アラームだったり Auto Scaling group、EC2 インスタンス、セキュリティグループ、S3 バケットが作成されているみたいです。

f:id:physics-heibon:20211031013626p:plain

できたみたいです。ブラウザで見てみます。

f:id:physics-heibon:20211031013643p:plain

やったー動いた ✌️

自動で作られた AWS サービスを眺める

なるほど、確かに簡単にアプリケーションをデプロイできることは確認できました。しかし、何が作られたのか分からないままではかかる料金も分からないので気になります。新卒はお金がないので、AWS を触る時は料金に敏感です。

コンソール上から色々みると、大体「ウェブサーバー環境」のようになっているみたいです。(Route53 は作成されてません)

f:id:physics-heibon:20211031013656j:plain

また、サブネットやインターネットゲートウェイのアタッチ、ルーティングなど様々なことをやってくれているみたいです。勉強になってありがたい。

最後に

今回は AWS Elastic Beanstalk について、実際にアプリケーションをデプロイする事と自動作成されたサービスを眺めることを目標に書きました。SAA の勉強で見たことが多かったので、「これ進 ◯ ゼミで見たやつ!!」という状態で楽しかったです。次回は EKS か Lambda Layer について書く予定ですが、気まぐれなので好きなことを書きます。

今回に限らず、間違った認識をしていれば指摘してください。勉強になるので。

関係ないですが、パフェが食べたいです。

参考

AWS Elastic Beanstalk

徹底攻略 AWS 認定ソリューションアーキテクト -アソシエイト教科書

チュートリアルおよびサンプル

Deploying a Flask App to AWS Elastic Beanstalk

AWS Elastic Beanstalk デベロッパーガイド ウェブサーバー環境