参考
自前のイメージを作って動かす手順
基本的には Flask込みのDockerイメージを作る方法のブログ
で記載された内容で問題ない。 自身の環境向けにアレンジは必要。
Dockerfileとビルド
Dockerfileは Flask込みのDockerイメージを作る方法のブログ
の通り。
1 2 3 4 5 6 7 8 9 10 $ mkdir -p ~/Sources/docker_flask/simple $ cd ~/Sources/docker_flask/simple $ cat << EOF > Dockerfile FROM ubuntu:latest RUN apt-get update RUN apt-get install python3 python3-pip -y RUN pip3 install flask EOF
ビルドコマンドは自分の環境に合わせた。
1 $ sudo docker build . -t dobachi/flask:1.0
インタラクティブにつなげる例も自分の環境に合わせた。
1 $ docker run -it --rm dobachi/flask:1.0 /bin/bash
アプリ作成と実行
自分の環境に合わせた。 Flask込みのDockerイメージを作る方法のブログ
では、
/bin/bashを起動してから、python3 <アプリ >としていたが、
ここでは直接指定して実行することにした。
1 2 3 4 5 6 7 8 9 10 11 $ mkdir apps $ cat << EOF > apps/app.py from flask import Flask app = Flask(__name__) @app.route('/') def index(): return "Hello world!!" EOF $ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask:1.0 flask run --host 0.0.0.0
なお、もともとの Flask込みのDockerイメージを作る方法のブログ
では、以下のようにPythonコマンドから直接実行するように記載されていたが、
今回は Flask公式
に従い、FlaskのCLIを用いることとした。
1 sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps dobachi/flask:1.0 python3 /usr/local/apps/app.py
その際、手元の環境ではLANG等の事情により、いくつか環境変数を設定しないとエラーを吐いたので、
上記のようなコマンドになった。
公式手順に則ったシンプルな例
上記で作ったDockerイメージを流用する。
準備
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 $ mkdir -p ~/Sources/docker_flask/getting_started $ cd ~/Sources/docker_flask/getting_started $ mkdir apps $ cat << EOF > apps/app.py from flask import Flask, url_for, request, render_template app = Flask(__name__) @app.route('/') def index(): return 'index' @app.route('/user/<username>') def profile(username): return '{}\'s profile'.format(username) @app.route('/post/<int:post_id>') def post(post_id): # show the post with the given id, the id is an integer return 'Post %d' % post_id @app.route('/path/<path:subpath>') def subpath(subpath): # show the subpath after /path/ return 'Subpath %s' % subpath @app.route('/projects/') def projects(): return 'The project page' @app.route('/about') def about(): return 'The about page' @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': return do_the_login() else: return show_the_login_form() def do_the_login(): return 'Do log in' def show_the_login_form(): return 'This is a login form' @app.route('/hello/') @app.route('/hello/<name>') def hello(name=None): return render_template('hello.html', name=name) # Test for URLs with app.test_request_context(): print(url_for('index')) print(url_for('login')) print(url_for('login', next='/')) print(url_for('profile', username='John Doe')) EOF $ mkdir -p apps/templates $ cat << EOF > apps/templates/hello.html <!doctype html> <title>Hello from Flask</title> {% if name %} <h1>Hello {{ name }}!</h1> {% else %} <h1>Hello, World!</h1> {% endif %} EOF
実行
1 $ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask:1.0 flask run --host 0.0.0.0
フォームを使う例
上記で作ったDockerイメージを流用する。
準備
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 $ mkdir -p ~/Sources/docker_flask/form $ cd ~/Sources/docker_flask/form $ mkdir apps $ cat << EOF > apps/app.py from flask import Flask, url_for, request, render_template, redirect, session app = Flask(__name__) # Set the secret key to some random bytes. Keep this really secret! app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' @app.route('/login', methods=['POST', 'GET']) def login(): error = None if request.method == 'POST': if valid_login(request.form['username'], request.form['password']): session['username'] = request.form['username'] return redirect(url_for('welcome')) else: error = 'Invalid username/password' # the code below is executed if the request method # was GET or the credentials were invalid return render_template('login.html', error=error) @app.route('/welcome') def welcome(): if 'username' in session: return render_template('welcome.html', username=session['username']) else: return redirect(url_for('login')) def valid_login(username, password): if username == 'hoge' and password == 'fuga': return True else: return False EOF $ mkdir -p apps/templates $ cat << EOF > apps/templates/welcome.html <!doctype html> <title>Hello from Flask</title> <h1>Hello {{ username }}!</h1> EOF $ cat << EOF > apps/templates/login.html <!doctype html> <title>Question from Flask</title> {% if error %} <h1>Please use valid username and password</h1> {% endif %} <form action="/login" method="post"> <div>username: <input type="text" name="username"></div> <div>password: <input type="password" name="password"></div> <input type="submit" value="Send"> <input type="reset" value="Reset"> </form> EOF
実行
1 $ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask:1.0 flask run --host 0.0.0.0
ファイルのアップロード
上記で作ったDockerイメージを流用する。
準備
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 $ mkdir -p ~/Sources/docker_flask/file_upload $ cd ~/Sources/docker_flask/file_upload $ mkdir apps $ cat << EOF > apps/app.py from flask import Flask, request, render_template, redirect, session, url_for from werkzeug.utils import secure_filename app = Flask(__name__) # Set the secret key to some random bytes. Keep this really secret! app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' @app.route('/upload', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': f = request.files['the_file'] session['filename'] = secure_filename(f.filename) f.save('/tmp/' + session['filename']) return redirect(url_for('done')) elif request.method == 'GET': return render_template('upload.html') @app.route('/done') def done(): return render_template('done.html') EOF $ mkdir -p apps/templates $ cat << EOF > apps/templates/upload.html <!doctype html> <title>Fileuploader by Flask</title> <form action="/upload" method="post" enctype="multipart/form-data"> <div>file: <input type="file" name="the_file"></div> <input type="submit" value="Send"> <input type="reset" value="Reset"> </form> EOF $ cat << EOF > apps/templates/done.html <!doctype html> <title>Fileuploader by Flask</title> <h1>The file: {{ filename }} was uploaded</h1> <a href="/upload">Got to the first page</a> EOF
実行
1 $ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask:1.0 flask run --host 0.0.0.0
クッキー
上記で作ったDockerイメージを流用する。
準備
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 $ mkdir -p ~/Sources/docker_flask/cookie $ cd ~/Sources/docker_flask/cookie $ mkdir apps $ cat << EOF > apps/app.py from flask import Flask, url_for, request, render_template, redirect, make_response app = Flask(__name__) # Set the secret key to some random bytes. Keep this really secret! app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' @app.route('/login', methods=['POST', 'GET']) def login(): error = None if request.method == 'POST': if valid_login(request.form['username'], request.form['password']): username = request.form['username'] resp = make_response(render_template('success.html')) resp.set_cookie('username', username) return resp else: error = 'Invalid username/password' elif request.method == 'GET': if 'username' in request.cookies: return redirect(url_for('welcome')) else: return render_template('login.html', error=error) @app.route('/logout') def logout(): resp = make_response(render_template('logout.html')) resp.delete_cookie('username') return resp @app.route('/welcome') def welcome(): if 'username' in request.cookies: return render_template('welcome.html', username=request.cookies.get('username')) else: return redirect(url_for('login')) def valid_login(username, password): if username == 'hoge' and password == 'fuga': return True else: return False EOF $ mkdir -p apps/templates $ cat << EOF > apps/templates/welcome.html <!doctype html> <title>Hello from Flask</title> <h1>Hello {{ username }}!</h1> <a href="/logout">logout</a> EOF $ cat << EOF > apps/templates/success.html <!doctype html> <title>Login Success</title> <h1>Login!</h1> <META http-equiv="Refresh" content="3;URL=/welcome"> <p>Jump in 3 seconds</p> EOF $ cat << EOF > apps/templates/logout.html <!doctype html> <title>Logout Success</title> <h1>Logout!</h1> <META http-equiv="Refresh" content="3;URL=/login"> <p>Jump in 3 seconds</p> EOF $ cat << EOF > apps/templates/login.html <!doctype html> <title>Question from Flask</title> {% if error %} <h1>Please use valid username and password</h1> {% endif %} <form action="/login" method="post"> <div>username: <input type="text" name="username"></div> <div>password: <input type="password" name="password"></div> <input type="submit" value="Send"> <input type="reset" value="Reset"> </form> EOF
実行
1 $ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask:1.0 flask run --host 0.0.0.0
ロギング
上記で作ったDockerイメージを流用する。
準備
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 $ mkdir -p ~/Sources/docker_flask/logging $ cd ~/Sources/docker_flask/logging $ mkdir apps $ cat << EOF > apps/app.py from flask import Flask, url_for, request, render_template, redirect, session app = Flask(__name__) # Set the secret key to some random bytes. Keep this really secret! app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' @app.route('/login', methods=['POST', 'GET']) def login(): error = None if request.method == 'POST': if valid_login(request.form['username'], request.form['password']): session['username'] = request.form['username'] return redirect(url_for('welcome')) else: app.logger.warning('Failed to login as:' + request.form['username']) error = 'Invalid username/password' # the code below is executed if the request method # was GET or the credentials were invalid return render_template('login.html', error=error) @app.route('/welcome') def welcome(): if 'username' in session: return render_template('welcome.html', username=session['username']) else: return redirect(url_for('login')) def valid_login(username, password): if username == 'hoge' and password == 'fuga': return True else: return False EOF $ mkdir -p apps/templates $ cat << EOF > apps/templates/welcome.html <!doctype html> <title>Hello from Flask</title> <h1>Hello {{ username }}!</h1> EOF $ cat << EOF > apps/templates/login.html <!doctype html> <title>Question from Flask</title> {% if error %} <h1>Please use valid username and password</h1> {% endif %} <form action="/login" method="post"> <div>username: <input type="text" name="username"></div> <div>password: <input type="password" name="password"></div> <input type="submit" value="Send"> <input type="reset" value="Reset"> </form> EOF
実行
1 $ sudo docker run -it --rm -p 5000:5000 -v $(pwd)/apps:/usr/local/apps -e "FLASK_APP=app.py" -w /usr/local/apps -e "LC_ALL=C.UTF-8" -e "LANG=UTF-8" dobachi/flask:1.0 flask run --host 0.0.0.0
JSON形式でやり取りする例
基本的には、 DockerコンテナでFlaskを起動し,
JSONデータのPOSTとGET の通り。
Dockerfile
dockerfileを作る。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ mkdir -p ~/Sources/docker_flask/post_json $ cd ~/Sources/docker_flask/post_json $ cat << EOF > Dockerfile fROM python:3.6 aRG project_dir=/app/ # ADD requirements.txt \$project_dir aDD reply.py \$project_dir wORKDIR \$project_dir rUN pip install flask # RUN pip install -r requirements.txt cMD ["python", "reply.py"] eOF
アプリ
アプリを作る。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $ cat << EOF > reply.py from flask import Flask, jsonify, request import json app = Flask(__name__) @app.route("/", methods=['GET']) def hello(): return "Hello World!" @app.route('/reply', methods=['POST']) def reply(): data = json.loads(request.data) answer = "Yes, it is %s!\n" % data["keyword"] result = { "Content-Type": "application/json", "Answer":{"Text": answer} } # return answer return jsonify(result) if __name__ == "__main__": app.run(host='0.0.0.0',port=5000,debug=True) eOF
ビルドする。
1 $ sudo docker build . -t dobachi/flask_json:1.0
動作確認
ポート5000で起動する。
1 $ sudo docker run --rm -p 5000:5000 -it dobachi/flaskjson:1.0
動作確認する。
1 $ curl http://localhost:5000/reply -X POST -H "Content-Type: application/json" -d '{"keyword": "Keffia"}'
nginxと組み合わせて動かす
gitHub:
uwsgi-nginx-flask-docker を参考にする。 gitHub:
uwsgi-nginx-flask-dockerのクイックスタート を見ながら試す。
Dockerfile、アプリなど
1 2 $ mkdir -p ~/Sources/docker_flask/nginx $ cd ~/Sources/docker_flask/nginx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ cat << EOF > Dockerfile fROM tiangolo/uwsgi-nginx-flask:python3.7 cOPY ./app /app eOF $ mkdir app $ cat << EOF > app/main.py from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World from Flask" if __name__ == "__main__": # Only for debugging while developing app.run(host='0.0.0.0', debug=True, port=80) eOF
ビルド
1 $ sudo docker build -t dobachi/nginx-flask:1.0 .
実行
1 $ sudo docker run --rm -it -p 8080:80 dobachi/nginx-flask:1.0
nginxと組あわせてSPA
gitHub:
uwsgi-nginx-flask-dockerのSPA を参考に進める。
Dockerfileとアプリ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 $ mkdir -p ~/Sources/docker_flask/nginx_spa $ cd ~/Sources/docker_flask/nginx_spa $ cat << EOF > Dockerfile fROM tiangolo/uwsgi-nginx-flask:python3.7 eNV STATIC_INDEX 1 cOPY ./app /app eOF $ mkdir app $ cat << EOF > app/main.py import os from flask import Flask, send_file app = Flask(__name__) @app.route("/hello") def hello(): return "Hello World from Flask" @app.route("/") def main(): index_path = os.path.join(app.static_folder, 'index.html') return send_file(index_path) # Everything not declared before (not a Flask route / API endpoint)... @app.route('/<path:path>') def route_frontend(path): # ...could be a static file needed by the front end that # doesn't use the `static` path (like in `<script src="bundle.js">`) file_path = os.path.join(app.static_folder, path) if os.path.isfile(file_path): return send_file(file_path) # ...or should be handled by the SPA's "router" in front end else: index_path = os.path.join(app.static_folder, 'index.html') return send_file(index_path) if __name__ == "__main__": # Only for debugging while developing app.run(host='0.0.0.0', debug=True, port=80) eOF
1 2 3 4 5 6 7 8 9 10 11 12 13 $ mkdir app/static $ cat << EOF > app/static/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Index</title> </head> <body> <h1>Hello World from HTML</h1> </body> </html> eOF
ビルド
1 $ sudo docker build -t dobachi/nginx-flask-spa:1.0 .
実行
1 $ sudo docker run --rm -it -p 8080:80 dobachi/nginx-flask-spa:1.0
構造的なプロジェクト
gitHub:
構造的なプロジェクトの例 に記載の例を示す。 example-flask-package-python3.7.zip
をダウンロードしてビルドしてみる。
アーカイブをダウンロード
1 2 3 4 $ mkdir -p ~/Sources/docker_flask/ $ wget https://github.com/tiangolo/uwsgi-nginx-flask-docker/releases/download/v0.3.10/example-flask-package-python3.7.zip $ unzip example-flask-package-python3.7.zip $ cd example-flask-package-python3.7
ビルド
1 $ sudo docker build -t dobachi/nginx-flask-package:1.0 .
実行
1 $ sudo docker run --rm -it -p 8080:80 dobachi/nginx-flask-package:1.0
一部修正してみる
apiを追加する。
ファイルを修正 1 2 3 $ cat example-flask-package-python3.7/app/app/api/api.py from .endpoints import user from .endpoints import get_ip
ファイルを追加 1 2 3 4 5 6 7 8 9 10 11 $ cat example-flask-package-python3.7/app/app/api/endpoints/get_ip.py from flask import request from flask import jsonify from ..utils import senseless_print from ...main import app @app.route("/get_ip", methods=["GET"]) def get_ip(): return jsonify({'ip': request.remote_addr}), 200
ビルドし、実行して、/get_ipにアクセスすると、接続元のIPアドレスが表示される。
flask +
NginxでSSLを使用(ここがうまく動かない)
アドホックな対応
flask
+ NginxでのSSL対応に関する記事 に記載された方法で試す。
また上記で使ったexample-flask-package-python3.7の例を用いてみる。
まずDockerfileを以下のように変更した。
1 2 3 4 5 6 7 $ cat Dockerfile froM tiangolo/uwsgi-nginx-flask:python3.7 run apt-get update run pip install pyopenssl copY ./app /app
main.pyを以下のように修正し、アドホックなSSL対応を試せるようにする。
1 2 3 4 5 6 7 8 9 10 11 12 $ cat app/app/main.py from flask import Flask app = Flask(__name__) from .core import app_setup if __name__ == "__main__": # Only for debugging while developing # app.run(host='0.0.0.0', debug=True, port=80) app.run(host='0.0.0.0', debug=True, ssl_context='adhoc')
以上の修正を加えた上で、Dockerイメージをビルドし実行した。
1 2 3 $ sudo docker build -t dobachi/nginx-flask-package:1.0 . $ sudo docker run --rm -it -p 8080:80 dobachi/nginx-flask-package:1.0 /bin/bash
gitHub:
uwsgi-nginx-flask-dockerのアーキテクチャ
gitHub:
uwsgi-nginx-flask-dockerのテクニカル詳細 で書かれている通り。
以下の役割分担。
ウェブサーバ:Nginx
wSGIのアプリサーバ:uWSGI
またアプリはFlaskで書かれていることを前提とし、
プロセスはSupervisordで管理される。
アプリはコンテナ内の/app以下に配備されることを前提とし、その中にとりあえずは動くuwsgi.iniも含まれる。
そのため、Dockerfile内でuwsgi.iniを上書きさせるようにして用いることを想定しているようだ。
Supervisord化について
以下のように/bin/bashを起動して確認した。
1 $ sudo docker run --rm -it -p 8080:80 dobachi/nginx-flask-package:1.0 /bin/bash
/etc/supervisor/conf.d/supervisord.confを確認する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # cat /etc/supervisor/conf.d/supervisord.conf [supervisord] nodaemon=true [program:uwsgi] command=/usr/local/bin/uwsgi --ini /etc/uwsgi/uwsgi.ini --die-on-term --need-app stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 [program:nginx] command=/usr/sbin/nginx stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 # graceful stop, see http://nginx.org/en/docs/control.html stopsignal=QUIT
uwsgiとnginxを起動させていることがわかる。
続いて、nginxの設定において、uwsgiと連携しているのを設定している箇所を確認。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # cat /etc/nginx/conf.d/nginx.conf server { listen 80; location / { try_files $uri @app; } location @app { include uwsgi_params; uwsgi_pass unix:///tmp/uwsgi.sock; } location /static { alias /app/static; } }
uwsgiでは、/appディレクトリ以下のuwsgi.iniと/etc/uwsgi/uwsgi.iniの両方を確認するようだ。
1 2 3 4 5 6 7 # cat /etc/uwsgi/uwsgi.ini [uwsgi] socket = /tmp/uwsgi.sock chown-socket = nginx:nginx chmod-socket = 664 # graceful shutdown on SIGTERM, see https://github.com/unbit/uwsgi/issues/849#issuecomment-118869386 hook-master-start = unix_signal:15 gracefully_kill_them_all
1 2 3 4 5 # cat /app/uwsgi.ini [uwsgi] module = app.main callable = app