1. kasayu

    Posted

    kasayu
Changes in title
+Vue + Flask on Docker
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,386 @@
+ニフティグループ Advent Calendar 2019 5日目の記事です。
+昨日は@snowwhite2019さんで、[「会社でvscode活用していますか?」](https://qiita.com/snowwhite2019/items/74c78d076c69ff551b7f)でした。VScodeを使いこなしたいと思っていたため、とても参考になりました。
+
+# はじめに
+「VueとPython使ってアプリ開発したいな〜でも環境構築面倒だな〜」
+ということで、今回は実験的にVueとFlaskの開発環境をDocker上で構築します。(どちらもDockerで開発する、という記事が意外となかったのもあります。)
+nginxやmysqlは使いません。とりあえずコンテナ間でリクエスト・レスポンスのやりとりができるようなものができれば良しとします。
+最終的にはFlaskで作ったAPIを叩いてVueで表示させます。
+
+Docker・Vue・Flaskとは、のような説明であったり、文法についての詳しい説明は行いません。
+また、Docker for Macはインストール済みとして説明していきます。
+
+## 環境
+- macOS Mojava10.14.5
+- Docker:2.0.0.3
+- node v8.16.0
+- npm 6.4.1
+- @vue/cli 4.1.1
+- Python 3.6.9
+- Flask 1.1.1
+
+## 構築の流れ
+- 環境構築
+ - ディレクトリ作成
+ - Dockerfile作成
+ - docker-compose.yml作成
+ - Dockerイメージの作成とコンテナの作成
+- ソースコード変更
+- 動作確認
+
+# 環境構築
+## ディレクトリ
+ディレクトリの初期状態。
+
+```
+.
+├── Dockerfile_python
+├── Dockerfile_vue
+└──docker-compose.yml
+```
+
+最終的なディレクトリ構成は次の通り。
+
+```
+.
+├── Dockerfile_node
+├── Dockerfile_python
+├── backend
+│   └── app.py
+├── docker-compose.yml
+└── frontend
+   ├── README.md
+  (略)
+```
+
+## Dockerfile
+pythonイメージ・nodeイメージを作成するためのDockerfileを作成します。
+
+### Dockerfile_python
+```Dockerfile_python
+FROM python:3.6
+
+WORKDIR /usr/src/app
+
+RUN pip install flask
+```
+
+### Dockerfile_node
+```Dockerfile_node
+FROM node:8.16.0-alpine
+
+WORKDIR /usr/src/app
+
+RUN yarn global add @vue/cli && yarn global add @vue/cli-init
+```
+
+## docker-compose.yml
+
+Docker composeを使って、FlaskとVueのコンテナをまとめて管理します。volumesで二つのコンテナがホスト側の同じディレクトリをマウントするように指定しています。Vueでビルドしたindex.htmlをFlask側で参照する必要があるためです。
+
+```docker-compose.yml
+version: '3'
+services:
+ flask:
+ build:
+ context: .
+ dockerfile: Dockerfile_python
+ container_name: flask_container
+ tty: true
+ volumes:
+ - ./:/usr/src/app
+ ports:
+ - "5000:5000"
+ vue:
+ build:
+ context: .
+ dockerfile: Dockerfile_node
+ image: vue_img
+ container_name: vue_container
+ tty: true
+ volumes:
+ - ./:/usr/src/app
+ ports:
+ - "8080:8080"
+```
+
+## Dockerイメージの作成とコンテナの生成
+```
+$ docker-compose build
+$ docker-compose up -d
+```
+
+環境構築はこれで終了です。
+あとはVueからFlaskのAPIを叩けるようにソースコードを書き換えていきます。
+
+# ソースコード変更
+- vue_containerでの変更
+- flask_containerでの変更
+
+## vue_containerでの作業
+1. プロジェクト作成
+2. frontend/config/index.js の変更
+3. frontend/src/router/index.js の変更
+4. frontend/src/components/Home.vue の作成
+
+### 1. プロジェクト作成
+vue_containerに入り、vue init でfrontendプロジェクトを作成します。(5分ほどかかる)
+コマンドを叩いてすぐに色々聞かれるが、全てYes(Enter)でオーケーです。
+
+```
+$ docker-compose exec vue /bin/sh
+/usr/src/app # vue init webpack frontend
+
+/bin/sh: git: not found
+? Project name frontend
+? Project description A Vue.js project
+? Author
+? Vue build standalone
+? Install vue-router? Yes
+? Use ESLint to lint your code? Yes
+? Pick an ESLint preset Standard
+? Set up unit tests Yes
+? Pick a test runner jest
+? Setup e2e tests with Nightwatch? Yes
+? Should we run `npm install` for you after the project has been created? (recommended) npm
+
+----------中略----------
+
+# Project initialization finished!
+
+```
+
+プロジェクト作成完了後、frontendディレクトリが作られていることが分かります。
+
+```
+/usr/src/app # ls
+Dockerfile_python Dockerfile_node docker-compose.yml frontend
+```
+
+### 2. frontend/config/index.js の変更
+試しにvue_containerだけでビルドとサーバの立ち上げを行い、テンプレートが表示されるか確認してみましょう。
+この時、vue initでプロジェクトの作成を行った場合、frontend/config/index.js で設定されているhostのアドレスを変更しないと、http://localhost:8080 へアクセスできません!
+
+```frontend/config/index.js
+ dev: {
+
+ // Paths
+ assetsSubDirectory: 'static',
+ assetsPublicPath: '/',
+ proxyTable: {},
+
+ // Various Dev Server settings
+ // host: 'localhost', // can be overwritten by process.env.HOST ★★★コメントアウト★★★
+ host: '0.0.0.0', // ★★★追加★★★
+ port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
+ autoOpenBrowser: false,
+```
+
+修正したところで、ビルドとサーバの立ち上げを行います。
+
+```
+/usr/src/app # cd frontend/
+/usr/src/app/frontend # npm run build
+/usr/src/app/frontend # npm run dev
+
+ DONE Compiled successfully in 22310ms
+
+ I Your application is running here: http://0.0.0.0:8080
+```
+![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267904/690a9e4a-5644-a425-4205-9eab1424ccb3.png)
+
+表示できました。
+
+### 3. frontend/src/components/index.js の変更
+このファイルにはURLのパスとそれに対応して出力されるテンプレートが記述されています。
+
+```frontend/src/components/index.js
+import Vue from 'vue'
+import Router from 'vue-router'
+import HelloWorld from '@/components/HelloWorld'
+import Home from '@/components/Home' // ★★★追加★★★
+
+Vue.use(Router)
+
+export default new Router({
+ // パス末尾へ'#'が追加されるのを防ぐ
+ mode: 'history', // ★★★追加★★★
+ routes: [
+ {
+ // '/'に対して、frontend/src/components/ 配下のHelloWorld.vueファイルを参照する
+ path: '/',
+ name: 'HelloWorld',
+ component: HelloWorld
+ },
+// -----追加ここから-----
+ {
+ // '/home'に対して、frontend/src/components/ 配下のHome.vueファイルを参照する
+ path: '/home',
+ name: 'Home',
+ component: Home
+ }
+// -----追加ここまで-----
+ ]
+})
+```
+
+### 4. frontend/src/components/Home.vue の作成
+以下のvueファイルを作成します。
+scriptタグでバックエンド側のAPIを叩き、取得した乱数を変数"randomNum"に代入してtemplateタグに渡して表示しています。
+scriptタグでインポートしているaxiosは、Ajax通信のためのライブラリで、Vueで外部APIを叩くことができます。
+
+#### 注意:vueファイルでは最後に一行空行を入れないとエラーが出ます。
+
+```frontend/src/components/Home.vue
+<!-- HTMLを記述 -->
+<template>
+ <div>
+ <p>Home</p>
+ <button @click="getRandom">占う</button>
+ <p>Random number from backend: {{ randomNum }}</p>
+ <h1 v-if="randomNumber%4==0">Awesome!!!</h1>
+ <h2 v-if="randomNumber%4==1">Good</h2>
+ <h2 v-if="randomNumber%4==2">Bad...</h2>
+ <h1 v-if="randomNumber%4==3">S〇〇ks!!!</h1>
+ </div>
+ </div>
+</template>
+
+<!-- JavaScriptを記述 -->
+<script>
+import axios from 'axios'
+
+export default {
+ data () {
+ return {
+ randomNum: 0,
+ }
+ },
+ methods: {
+ getRandom () {
+ this.randomNum = this.getRandomNum()
+ },
+ getRandomNum () {
+ const path = 'http://localhost:5000/rand'
+ axios.get(path)
+ .then(response => {
+ this.randomNum = response.data.randomNum
+ })
+ .catch(error => {
+ console.log(error)
+ })
+ }
+ },
+ created () {
+ this.getRandom()
+ }
+}
+</script>
+<!--一行空行を入れてください-->
+```
+
+「axiosはもともとVueに入っているのかな?」
+いいえ、入っていないのでインストールしましょう。
+
+```
+/usr/src/app # npm install axios
+```
+
+ファイルの変更・追加が終わったら、ビルドとサーバの立ち上げを行います。
+
+```
+/usr/src/app # cd frontend/
+/usr/src/app/frontend # npm run build
+/usr/src/app/frontend # npm run dev
+```
+
+これでVue側の変更は終わりです。
+
+## flask_containerでの作業
+1. backend/app.pyの作成
+
+### 1. backend/app.py の作成
+
+Flask側ではプロジェクト作成などがないため、こちらでディレクトリとソースコードを用意します。
+frontendとディレクトリを分けるために、backendディレクトリを作成し、その中にソースファイルを作成します。
+Ajax通信を行うためのライブラリ Flask-CORSのインストールも行いましょう。
+
+```
+$ docker-compose exec flask bash
+/usr/src/app# mkdir backend
+/usr/src/app# cd backend && touch app.py
+/usr/src/app# pip install flask-cors
+```
+
+app.py を作成します。
+
+
+```backend/app.py
+# render_template:参照するテンプレートを指定
+# jsonify:json出力
+from flask import Flask, render_template, jsonify
+
+# CORS:Ajax通信するためのライブラリ
+from flask_cors import CORS
+from random import *
+
+# static_folder:vueでビルドした静的ファイルのパスを指定
+# template_folder:vueでビルドしたindex.htmlのパスを指定
+app = Flask(__name__, static_folder = "./../frontend/dist/static", template_folder="./../frontend/dist")
+
+app.config.from_object(__name__)
+
+CORS(app)
+
+# 任意のリクエストを受け取った時、index.htmlを参照
+@app.route('/', defaults={'path': ''})
+@app.route('/<path:path>')
+def catch(path):
+ return render_template("index.html")
+
+# '/rand'が叩かれた時、乱数を生成
+@app.route('/rand')
+def random():
+ response = {
+ 'randomNum': randint(1,100)
+ }
+ return jsonify(response)
+
+# app.run(host, port):hostとportを指定してflaskサーバを起動
+if __name__ == '__main__':
+ app.run(host='0.0.0.0', port=5000)
+```
+
+app.py を実行してサーバを起動し、localhost:5000 にアクセスします。
+
+```
+/usr/src/app# python app.py
+ * Serving Flask app "app" (lazy loading)
+ * Environment: production
+ WARNING: This is a development server. Do not use it in a production deployment.
+ Use a production WSGI server instead.
+ * Debug mode: off
+ * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
+```
+
+## 動作確認
+それでは、vue_containerとflask_cointainerのサーバが立ち上がった状態で、localhost:5000/home と localhost:8080/home にアクセスし、APIを叩いてみましょう。
+
+![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267904/66348df3-7841-ac4d-d002-3dc090d13a2e.png)
+
+![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267904/02b35a1e-94b4-678f-b64e-43dd36ba1e75.png)
+
+
+Vue_container、flask_container どちらからもアクセスし、APIを叩くことができました!!!
+
+# あとがき
+ 以上、Docker composeからフロント・バックエンドのコンテナを作成し、連携させることができました。今回の構成に意味があるのかと言われると怪しいです(コンテナ行ったり来たりが面倒...)。しかし、Dockerの各種ファイルを作成して数コマンド叩けば環境ができるので、アプリ開発に集中しやすい思います。やはりDockerは便利ですね。
+ 詰まったところも多くあったので、別の機会に紹介したいと思います。
+
+ 明日は@yt_hattereneさんがスクラムマスターについて書いてくれるそうです!お楽しみに!!
+
+# 参考文献
+参考にさせていただきました。
+[Vue.js(vue-cli)とFlaskを使って簡易アプリを作成する【前半 - フロントエンド編】](https://qiita.com/mitch0807/items/2a93d93adbf6b5fc445c)
+[Vue.js(vue-cli)とFlaskを使って簡易アプリを作成する【後半 - バックエンド編】]
+(https://qiita.com/mitch0807/items/c2e84beee6c9a61e86cd)