Edited at

Vue.jsとFlaskのDocker環境を構築してみた

ウェブクルー AdventCalendar 14日目の記事です。

昨日は@DotaKobayashiさんのReactNativeで電車遅延のメールを送信するでした。

本日は@kouchanneが担当します!


背景

社内向けの管理ツールを作る際に、あまり作り込まずにシンプルにアプリを構築する必要がありました。

試行錯誤しながらDocker環境で動くSAPのアプリ環境を構築したので、簡単に最低限の環境が構築できる手順をご紹介したいと思います。

※セキュリティとか細かい設定の部分はまだ課題があるので、今回はHello Worldが動く最低限の実装です。


環境


  • Docker(OSにあわせて準備してください)

  • docker-compose

  • node.js & npm

  • Python3(今回の作業そのものではいらないかもですが…)


作ってみる

今回作ったサンプルはココにあげてあります。


おおまかな構成

アドベントカレンダー.png

※今回のサンプルではとりあえず動けばいいのでログファイルのマウントはやっていません。

フロントエンド(nginxコンテナ)

バックエンド(Pythonコンテナ)

データベース(mysqlコンテナ)

それぞれコンテナを作成し、サーバーですべてのコンテナを起動する形です。


ファイルの作成

SPAAplication/

┣ frontend/
┣ vue/
       ┣ nginx/
  └ work/
┣ backend/
┣ python/
┣ mysql/
┣ docker-compose.yml


フロントエンド(nginxコンテナ)


Vue.jsプロジェクトの作成



  • vue-cliを使ってVue.jsプロジェクトの作成します。

  • 作成したプロジェクトに移動し、 npm run dev をして http://localhost:8080/ にとりあえずサンプルが表示されればOK

  • 試しに npm run build してみて dist ファイルが作成されるか確認してみましょう

npm install -g vue-cli #vue-cliのインストール 

vue init webpack vue #ProjectNameとかいろいろ聞かれるので適当に答えます


nginx.confの作成

まずはnginxで使うconfファイルを作成します。

私は ./frontend/vue/nginx/work の中に作成しました。


nginx.conf

user  nginx;

worker_processes 1;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
#tcp_nopush on;

keepalive_timeout 65;

#gzip on;

upstream uwsgi {
server backend:3031;
}

server {
listen 80;
charset utf-8;

location / {
alias /home/www/contents/;
}

location /api {
include uwsgi_params;
uwsgi_pass uwsgi;
}
}
}


全体像としては上のような感じです。

最低限の処理(とりあえず動いた)しか入れていないので、必要なものは追加してください。

location / {

alias /home/www/contents/;
}

この部分で/でアクセスしてきた場合 /hom/www/contents/ の中身を見るように指定しています。

ここにVue.jsをbuildした際に生成されるdistファイルを配置します。

location /api {

include uwsgi_params;
uwsgi_pass uwsgi;
}

今回同じサーバー内でAPIの処理も判断させるので、リクエストが/api/ だった場合はPythonコンテナへ処理を投げるリバースプロキシの役割を持たせます。


Dockerfileの作成

Vue.jsでbuildされる静的ファイルの生成は、dockerのmultistage-buildを使います。

この方法はVue.jsの公式に書いてあります。

※ciツールなどで静的ファイルを生成する方法もありますが、それだと別途処理を書く必要があるのと管理が複雑になるのでできるだけdockerのみで完結できるようにしました。

##### ビルド環境 #####

FROM node:9.11.1-alpine as build-stage
WORKDIR /app
# vue.jsのProjectをコピーする
COPY ./app ./
RUN npm install
# npm run build で distファイルが生成されるscriptがpackage.jsonにある前提
RUN npm run build

##### 本番環境 #####
FROM nginx:1.13.12-alpine as production-stage

# contentsを配置するディレクトリを作成する
COPY nginx/work/ /home/work
RUN mkdir -p /var/log/nginx/log\
&& mkdir /home/www\
&& mkdir /home/www/contents

# ビルド環境で構築されたdistディレクトリをnignxの該当のディレクトリに配置する
COPY --from=build-stage /app/dist /home/www/contents

# nginx.confファイルを配置する
WORKDIR /home/work
RUN rm -f /etc/nginx/conf.d/*.conf\
&& rm -f /etc/nginx/nginx.conf\
&& cp -i *.conf /etc/nginx

EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;","-c","/etc/nginx/nginx.conf"]

上記のDockerfileにより、Vue.jsプロジェクトのビルドとnginxコンテナの立ち上げができるようになります。

--from=[コンテナ名]で上のvue.jsbuildコンテナで生成されたものを使うことができるようになります。


バックエンド(Pythonコンテナ)

続いて、バックエンドのコンテナを作成していきます。

今回は比較的にシンプルにWebアプリを作成することができるPythonのFlaskを使います。


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

Pythonアプリケーションを動かすにはuwsgiを使います。


uwsgi.iniの作成


uwsig.ini

[uwsgi]

wsgi-file = /var/www/run.py
callable = app
master = true
processes = 1
socket = :3031
chmod-socket = 666
vacuum = true
die-on-term = true
py-autoreload = 1

これは、uwsigを動かす際のオプションが書いてあります。

一番下のpy-autoreload はpythonファイルが更新されると自動的に実行ファイルも更新されるような仕組みです。 Dockerで動かす際は、コンテナ内のファイルを直接編集することはないので0が良いと思います。

※ローカルで開発する際などは、1にしてソースファイルをvolumeマウントしておけば編集しながら都度確認をすることができます。


requirements.txtの作成

pythonでは追加のモジュールなどの管理をpipを使って行います。

pip install hoge でinstallすることができますが、複数ある場合モジュールごとにinstallしてると行けていないのでrequirements.txt にインストールしたいものをかいて上げることでまとめてinstallができます。

Flask == 1.0.2

uwsgi == 2.0.17
mysql-connector-python-rf == 2.2.2

今回は Flaskとuwsgiとmysqlコントローラーを使いたいので上記のように作成しました。

== と書いているのはversionを指定しています。

その他の指定方法


run.pyの作成

Flaskはとてもシンプルなフレームワークなので run.py 一つでアプリを作成することができます。(Djangoなどは最初からパッケージが用意されていますが、Flaskはボリュームが増えてきたら追加していく形です)


run.py

from flask import Flask                           

app = Flask(__name__)

@app.route('/api/')
def hello_world():
return "Hello World!"

if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000, debug=True)


先程、nginx.conf/api/ の場合はアプリケーションサーバーに処理を降ると書いたので

@app.route('/api/hogehoge') で呼ぶことができます


データベース(MySqlコンテナ)

今回は特にMySqlに特別な設定等を加える必要がなかったので、Dockerfileは作らずに、DockerHubにあがっているDockerイメージを使いました。


docker-compose.ymlの作成

最後にこれらのコンテナを起動すためのdocker-composeを作成します。

コンテナをそれぞれ順番に起動すれば起動できますが、コマンドの引数が多くなってしまったり、

環境ごとに引数を変えたいなどの場合は、docker-composeを環境ごとに用意すれば構築ができてしまうという利点からdocker-composeを使います。


docker-compose

version: "3"

services:
backend:
build: ./backend
image: backend
container_name: backend_container
links:
- mysql
ports:
- "3031:3031"
environment:
TZ: "Asia/Tokyo"
# volumes:
# - ./backend/python:/var/www/

frontend:
build: ./frontend
image: frontend
container_name: frontend_container
links:
- backend
ports:
- "80:80"
environment:
TZ: "Asia/Tokyo"
# volumes:
# - ./web/vue/dist:/home/www/contents:ro

mysql:
image: mysql:5.7
container_name: mysql_container
volumes:
- ./mysql/work:/var/lib/mysql
ports:
- 3306:3306
environment:
MYSQL_DATABASE: root
MYSQL_ROOT_PASSWORD: passwd


上記のdocker-composeをプロジェクトのルートにセットします。

ちなみにlinkesを使ってコンテナを指定してあげることで、同じネットワークに属することができるようになります。


動かしてみる

作ってみるの手順を終えたらプロジェクトを立ち上げる準備は完了です。


Dockerfileのビルドとコンテナの立ち上げ

実際に作成したDockerfileをビルドして、起動してみましょう。


実行手順

docker-compose build

docker-compose up

ルートディレクトリで上のコマンドを実行するだけです。


動作確認

そしたら、コンテナが立ち上がるのでブラウザで localhost にアクセスしてみましょう!!!!!

Vue.jsのウェルカムページが表示されるはずです!!!!!!

次にAPI側にアクセスできるかは localhost/api にアクセスしてみます!!!!!

Hello World!が表示されるはずです!!!!!!

これで一通りの環境構築が完了しました。

あとは、Flaskで @app.route('/api/hogehoge')にAPIを作って、Vue.js上で axios などで /api/hogehogeで呼び出して上げれば使うことができます。

また、host名はコンテナ名で解決することができるので、Flaskのmysqlのhost名にmysqlを指定して上げればmysqlコンテナに接続することができます。


さいごに

今回は、最低限のSAPの環境構築方法を紹介しました。

Vue.jsの部分が他の言語になろうが、Pythonの部分が他の言語になろうが基本的な部分は同じなので

導入したい言語で応用が可能だと思いますので、参考にしていただければ幸いです。

また、今回Dockerの右も左もわからない状態で手探りで作ったため、まだまだ足りない部分があると思います。

もっと他にいい方法があるや今回書いている内容で間違っている点があれば教えていただければ幸いです。