AWS Elastic Beanstalk Docker -コンテナごとFlaskアプリをデプロイ-

#AWS#docker#EB

前回は素のFlaskアプリケーションをデプロイしました。 今回はDockerコンテナを動かしてみます。 ちょうどAzure Web App for Containersと同じようなものです。 http://roy29fuku.com/infra/azure/web-app-for-containers/   EC2より自由度は下がりますが管理が楽になり、 EBに単純にアプリケーションをデプロイするよりは自由度が上がります。 それなりに学習コストも高くなりますが、かけただけの時間を将来削減できると信じて勉強します。   今回は単一コンテナのDocker環境構築を行います。

Elastic Beanstalkとは

Elastic Beanstalkではユーザはアプリケーションをデプロイするだけです。 ソースコードを自動でEC2に配置し、ウェブサーバを設定し、さらにロードバランサーやオートスケーリングなどの面倒もElastic Beanstalkが管理してくれます。 Elastic BeanstalkにDockerコンテナイメージをデプロイする場合は、EC2の中にさらにDockerコンテナを起動し、外部とのポートの接続を行ってくれます。

出展: How to Deploy Docker apps to Elastic Beanstalk · hopsoft/relay Wiki

  詳しい説明は公式へ。 早速始めていきます。

EB CLIインストール

AWSマネジメントコンソールでポチポチやるのではなく、CUIでカタカタやっていきます。 EB CLIツールが必要になるのでインストールします。 詳細は公式へ。

brew install aws-elasticbeanstalk

仮想環境の構築

仮想環境を用意し、有効にします。 前回やったので詳しくはこちら

pip install virtualenv
virtualenv ~/YOUR_DIRECTORY
source ~/YOUR_DIRECTORY/bin/activate

サンプルアプリを作成

とりあえず動かしたい人はこちらのREADMEに従えば動くはずです。

  自分で作りたい人は続きへ。 適当なディレクトリを作成しapplication.pyを作成します。 (ファイル名はapplication.pyにしましょう。WSGIが見てるので)

mkdir my-app
cd my-app
vi application.py

  application.pyはこんな感じ。

from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()

  requirements.txtを作成します。EB内ではこのファイルを元にライブラリをインストールします。 多ければデプロイに時間がかかるので余計なものは入れないようにしましょう。 プロジェクトのディレクトリ毎に仮virtualenvで仮想環境を作るのがいいと思います。 とりあえず今回のrequirements.txtには、flaskとだけ記入しましょう。

echo flask > requirements.txt

Dockerfile作成

今回は単一のDockerコンテナを利用するため、公式の以下のページを参考にします。

  EBに乗せるDockerコンテナに関しては、AWS Elastic Beanstalk Docker ベースイメージを使用する必要があります。 公式の案内では書いていませんでしたが、EXPOSEオプションがないと怒られます。

FROM amazon/aws-eb-python:3.4.2-onbuild-3.5.1
EXPOSE 8080

  Pythonのバージョンは3.4なので依存関係がある場合は注意してください。

  amazon/aws-eb-python:3.4.2というコンテナイメージを作っているDockerfileはおそらくこちらのGithubで公開されているものです。多分。 一応Officialと書いていますが、AWS側からのリンクがないので利用は自己責任で。

  どうしても別バージョンが使いたかったらDockerfile内で設定すれば使えるんでしょうか?ご存知の方がいたら教えてください。

ローカルで確認(Dockerコンテナイメージ作成)

docker buildコマンドでイメージを作成し、

docker build -t my-app .

  runコマンドで試しにローカルで起動してみます。 コンテナは8080番経由でアプリケーションを公開しています。

docker run -it --rm -p 3000:8080 my-app-image

  ブラウザからhttp://localhost:3000/にアクセスしてみましょう。 お馴染みの文言が表示されればOKです。   公式にはDockerfileからDockerfile.localにファイル名を変更するように書かれていましたが、不要です。 むしろDockerfile.localにするとElastic Beanstalkが認識しなくなります。   eb initしていれば以下のアプリケーションをローカルで確認するのようにeb local runコマンドで確認できます。

Elastic Beanstalkを開始する

eb init --prifile YOUR_PROFILE

  Elastic Beanstalkデプロイに関してはこちらを参照にしてください。   適当に質問を答えていくと、素のFlaskプロジェクトでは聞かれなかったことを聞かれます。 Dockerfileを認識したようですね。

Select a default region
1) us-east-1 : US East (N. Virginia)
2) us-west-1 : US West (N. California)
3) us-west-2 : US West (Oregon)
4) eu-west-1 : EU (Ireland)
5) eu-central-1 : EU (Frankfurt)
6) ap-south-1 : Asia Pacific (Mumbai)
7) ap-southeast-1 : Asia Pacific (Singapore)
8) ap-southeast-2 : Asia Pacific (Sydney)
9) ap-northeast-1 : Asia Pacific (Tokyo)
10) ap-northeast-2 : Asia Pacific (Seoul)
11) sa-east-1 : South America (Sao Paulo)
12) cn-north-1 : China (Beijing)
13) us-east-2 : US East (Ohio)
14) ca-central-1 : Canada (Central)
15) eu-west-2 : EU (London)
(default is 3):3
Select an application to use
1) EXISTING_PROJECT
2) EXISTING_PROJECT
3) [ Create new Application ]
(default is 3): 3
Enter Application Name
(default is "my-app"): my-app
Application my-app has been created.
It appears you are using Docker. Is this correct?
(Y/n): Y
Select a platform version.
1) Docker 17.06.2-ce
2) Docker 17.03.2-ce
3) Docker 1.12.6
4) Docker 1.11.2
5) Docker 1.9.1
6) Docker 1.7.1
7) Docker 1.6.2
8) Docker 1.5.0
(default is 1): 1
Cannot setup CodeCommit because there is no Source Control setup, continuing with initialization
Do you want to set up SSH for your instances?
(Y/n): Y
Select a keypair.
1) YOUR_KEY_PAIR
2) YOUR_KEY_PAIR
3) [ Create new KeyPair ]
(default is 2): 1

CodeCommitについて① gitなどでソース管理していない場合

以下の質問をされます。Yで答えると引き続きkeypairについて聞かれます。

Cannot setup CodeCommit because there is no Source Control setup, continuing with initialization
Do you want to set up SSH for your instances?
(Y/n):
Select a keypair.
1) YOUR_KEY_PAIR
2) [ Create new KeyPair ]
(default is 1):

CodeCommitについて② gitなどでソース管理している場合

AWSのCodeCommitを使うかどうか聞かれます。 デフォルトではno、つまり自分が使っているGithubなどを引き続き使用します。 この場合はkeypairについて聞かれません。

Note: Elastic Beanstalk now supports AWS CodeCommit; a fully-managed source control service. To learn more, see Docs: https://aws.amazon.com/codecommit/
Do you wish to continue with CodeCommit? (y/N) (default is n): n

Elastic Beanstalkに環境を作成する

環境を作成し、開きます。

eb create YOUR_ENV_NAME

  しばらく実行ログが出力されます。

アプリケーションをウェブブラウザで確認する

環境の作成が完了したら、以下のコマンドでウェブブラウザを開きます。 5秒ぐらいかかるので待ちます。

eb open

  アプリケーションが動いているのがわかります。   WebサーバがDockerfileで公開した8080番ポートに繋いでいる感じです。 多分。若干のブラックボックス感が怖いですが。ひとまず動かすことができました。   公式の情報に不足があったのでこちらがかなり参考になりました。

アプリケーションをローカルで確認する

eb local runコマンドでローカルで確認することができます。 内部処理的には普通にDockerコンテナbuildしてrunしてるっぽいです。

eb local run --port 8080

  2,3分待ったらブラウザでアクセスしてみます。

  確認が終わったらcmd + cで終了。 初回こそ時間がかかりますが、2回目からは迅速に確認できます(Docker側の設定をいじっていなければ)。

変更を反映する

その後アプリケーションや設定ファイルを変更した場合、反映するにはeb deployコマンドを用います。

eb deploy

  古い情報だとgit aws.pushなどが紹介されていますが、こちらは廃止になりました。

SSH接続する

Elastic Beanstalkが管理しているEC2インスタンスにSSH接続する

Elastic BeanstalkはアプリケーションをEC2インスタンスで動かしています。 eb sshコマンドでSSH接続することが可能です。

eb ssh

  デプロイ周りでこけている場合はこの中のログを漁ってみると原因が掴めるかもしれません。 (ちなみにデプロイ時のログはAWSマネジメントコンソールでも確認できます)

EC2が起動しているDockerコンテナに入る

Elastic Beanstalk Dockerでは、EC2の中でDockerコンテナがアプリケーションを動かしています。

出展: How to Deploy Docker apps to Elastic Beanstalk · hopsoft/relay Wiki

  すなわち、アプリケーションの実行環境を確認するためにはEC2にSSH接続したのちDockerコンテナに入る必要があります。 docker psでコンテナIDを取得します。

# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2181f0bf271e b7b9beb74e15 "/uwsgi-start.sh" 14 hours ago Up 14 hours 8080/tcp youthful_heisenberg

  docker execコマンドでDockerの中に入りましょう。

docker exec -it 2181f0bf271e bash
root@2181f0bf271e:/var/app# ls

  すると/var/app/に自分がデプロイしたアプリケーションが配置されていることがわかります。 また、このディレクトリにはbin/ディレクトリが新たに追加されています。これがユーザがrequirements.txtで定義したライブラリをインストールしたPython仮想環境です。

DockerのPython環境を確認する

/var/app/ディレクトリにあるbin/ディレクトリを使って仮想環境を有効化します。

root@2181f0bf271e:/var/app# source bin/activate
(app)root@2181f0bf271e:/var/app#

  pipによるライブラリの管理状況を確認してみます。 requirements.txtに定義した通りのライブラリがインストールされているはずです。

pip freeze
EB==0.1.5
Flask==0.12.2
Jinja2==2.10
....

Flaskのログを見る

ローカルのDockerコンテナで問題なく動いていればebでも問題なく動くはず、というかそれがDockerで開発するメリットのはずですが、現実は無情である。 ローカルでは動いていたのにebでは動かないときはサーバのFlaskのログを見たくなりますよね。 amazon/aws-eb-python:3.4.2-onbuild-3.5.1イメージのDockerfileを見てみると、/var/log/uwsgi/uwsgi.logにuWSGIのログを吐いていることがわかります。   上記の方法でDockerコンテナまで入ったら、tailコマンドでログを見ましょう。

tail -f /var/log/uwsgi/uwsgi.log

  これでアプリケーションにアクセスすればログが取れます。

Nginxのファイルアップロード制限を解除する

Nginxに対するファイルアップロードは制限値が設けられています。 client_max_body_sizeで定義され、デフォルトでは1MBです。

/etc/nginx/nginx.confに設定を追記することで制限を解除できますが、Elastic Beanstalk Dockerの場合デプロイの度に初期設定に戻されてしまいます。 Elastic Beanstalkでは.ebextensions/ディレクトリに設定ファイルを配置することでソフトウェアの設定を変更できます。 詳しくは公式を。

アプリのディレクトリに.ebextensions/ディレクトリを作成して.config拡張子のファイルに以下の記事のように記述をすればアップロード制限が10MBになります。

files:
"/etc/nginx/conf.d/client-max-body-size.conf":
mode: "000644"
owner: root
group: root
content: "client_max_body_size 10m;"

不要になったリソースを削除する

Elastic Beanstalkにデプロイする際、ソースは一旦S3にzip形式で保存されます。 これらのファイルはデプロイの度に作成されますが、eb terminateコマンドやAWSマネジメントコンソール上での環境やアプリケーションの削除を行っても残ります。 大きなプロジェクトをデプロイするときはS3を逼迫していないか注意しましょう。

おまけ

Githubにcloneしてそのまま使えるリポジトリ公開しました。 多分なかったと思うので。他に公開してあったら消すので教えてください。

  使い方はREADMEに書いておきます。

エラー集

Cannot setup CodeCommit because there is no Source Control setup, continuing with initialization

作成済みのebプロジェクト内の.git/ディレクトリを途中で削除した時などに発生します。 改めてgit initするなり、元のgitの設定を復元しましょう。

ERROR: Failed to build Docker image aws_beanstalk/staging-app: command ‘/bin/sh -c if [ -f /var/app/requirements.txt ]; then /var/app/bin/pip install -r /var/app/requirements.txt; fi’ returned a non-zero code: 1. Check snapshot logs for details.

requirements.txtを用いたpipインストールでエラーが発生しています。 eb local runを行うとより詳細な原因がわかります。   どうやらgrpcioのインストールでこけているみたいです。

No distributions matching the version for grpcio==1.8.2 (from -r /var/app/requirements.txt (line 23))
Storing debug log for failure in /root/.pip/pip.log
The command '/bin/sh -c if [ -f /var/app/requirements.txt ]; then /var/app/bin/pip install -r /var/app/requirements.txt; fi' returned a non-zero code: 1
ERROR: CommandError - None

  該当の部分をコメントアウト。 改めてeb local run。Dockerの起動まではできました。 しかし、コメントアウトしたライブラリを捨て置くわけにもいきません。   docker execコマンドでコンテナに入り、source bin/activateで仮想環境に入ります。 件のライブラリをインストールしてみます。

pip install grpcio
Requirement already satisfied (use --upgrade to upgrade): grpcio in ./lib/python3.4/site-packages
Requirement already satisfied (use --upgrade to upgrade): six>=1.5.2 in ./lib/python3.4/site-packages (from grpcio)
Requirement already satisfied (use --upgrade to upgrade): protobuf>=3.5.0.post1 in ./lib/python3.4/site-packages (from grpcio)

  どうやら既にインストール済みのようです。 requirements.txtにはgrpcio==1.8.2と書かれていましたが、こちらはどうでしょう。

pip freeze | grep grpcio
grpcio==1.8.1

  どうやらDocker内のpipでは1.8.2を認識していないため、そんなバージョンはないと怒られていたみたいですね。 Dockerfileでpipのアップデートを指示できますが、amazonのコンテナイメージを使っており、どうやらrequirements.txtを読み込む方が早そうです。 requirements.txt内の記述をgrpcio==1.8.2からgrpcio==1.8.1に変更しました。   eb local runで確認したのち、eb deployで解決しました。

ERROR: NotSupportedError - You can use “eb local” only with preconfigured, generic and multicontainer Docker platforms.

久しぶりにEBプロジェクトでeb local run --port 8080を実行したらエラーが発生しました。 単一のコンテナでは実行できない?できなくなった?以前はこのプロジェクトを使ってローカルで確認できていたと思うんですが。。。 こうして記事にも残っているし。 謎ですが。調査して追記します。

次回

そもそもなんでこんなことになったかと言うと、 Flaskアプリケーションに全文検索使ったオートコンプリートを導入したい! Elasticseachがいいらしい!使ってみよう! Python環境でEC2で作りたくない!Elastic Beanstalkでお手軽デプロイだ! EBでElasticsearchどうやって動かすん? Elastic Beanstalk Dockerならいけるんでは!?(見切り発車)   って感じです。 Elastic Cloudも考えたけど、高い。パッと作るアプリに月45$も払えない。   これ調べてるうちに色々知ることができたので別の方法でデプロイするかもしれませんが、勉強ということで。   選択肢としては、こんな感じ

Flaskアプリケーション

  • AWS EC2: 環境構築が面倒、使い回せない、諸々の設定を自分でしなくてはいけない
  • AWS Elastic Beanstalk: 楽チンだけど、あまりいじれない、諸々AWSが管理してくれる
  • AWS Elastic Beanstalk Docker: Dockerfileに記述できるのでInfrastructure as Codeが実現できる
  • AWS Elastic Container Service: EB Dockerより自由度が高い

検索エンジン

  • Elasticsearch: Flaskと同一サーバ内にインストール、多分一番早い、EC2でもEB Dockerでもそこそこ設定が面倒そう
  • Ealstic Cloud: $45/月、高い
  • Amazon Elasticsearch Service: $13/月ぐらい(一番安いインスタンスで)
  • Algoria: 10Kレコードまで無料

参考