2021

5

25

DockerでDjango+Vue+NginxのSPA作ってやんよ!!!

タグ: | | |

Pocket
LINEで送る
Facebook にシェア
reddit にシェア
LinkedIn にシェア

Docker composeの復習がてらSPAサイトの雛形コンテナ構成を最速ビルドしてみる

事前準備

可能な限り初期ファイル構成はシンプルかつ最小限で。

docker_spa/
  ┣━ back/
  ┃  ┣━ scripts/
  ┃  ┃  ┣━ django-appstart.sh  # Django新規アプリ作成用スクリプト
  ┃  ┃  ┣━ django-settings.sh  # Djangoセットアップ用スクリプト
  ┃  ┃  ┗━ start.sh  # コンテナ起動関連スクリプト
  ┃  ┣━ Dockerfile  # バックエンド用Dockerfile
  ┃  ┗━ requirements.txt   # pythonパッケージ一覧
  ┣━ front/
  ┃  ┣━ scripts/
  ┃  ┃  ┣━ start.sh  # 「settings.py」修正用スクリプト
  ┃  ┃  ┗━ vue-settings.sh  # vue用スクリプト
  ┃  ┗━ Dockerfile  # フロントエンド用Dockerfile
  ┣━ web/
  ┃  ┣━ conf/
  ┃  ┃  ┗━ app_nginx.conf  # アプリ用Nginx設定ファイル
  ┃  ┣━ logs/nginx
  ┃  ┃  ┗━ access.log  # サーバーアクセスログ
  ┃  ┃  ┗━ error.log  # サーバーエラーログ
  ┃  ┣━ Dockerfile  # webサーバー用Dockerfile
  ┃  ┗━ uwsgi_params  # アプリサーバー設定ファイル
  ┃    
  ┗━ docker-compose.yml

docker-compose.yml

version: '3.7'

services:
  web:
    container_name: web-container
    build:
      context: ./
      dockerfile: ./web/Dockerfile
    restart: always
    ports:
      - "8000:8000"
    environment:
      TZ: "Asia/Tokyo"
    volumes:
      - ./web/conf:/etc/nginx/conf.d  
      - ./web/logs/nginx/:/var/log/nginx/ 
      - ./web/uwsgi_params:/etc/nginx/uwsgi_params 
      - static_volume:/usr/src/app/static
      - media_volume:/usr/src/app/media
    networks:
      - django_net
    depends_on:
      - back

  back:
    container_name: back-container
    build:
      context: ./back
      dockerfile: ./Dockerfile
    command: 'sh /code/scripts/start.sh'
    restart: always
    volumes:
      - ./back:/usr/src/app
      - static_volume:/usr/src/app/static
      - media_volume:/usr/src/app/media
    expose:
      - "8001"
    networks:
      - django_net

  front:
    container_name: front-container
    build:
      context: ./front
      dockerfile: ./Dockerfile  # ./front/Dockerfileを参照
    ports:
      - 8080:8080
    expose:
      - "3000"
    command: 'sh /code/scripts/start.sh'
    volumes:
      - ./front:/code
      - ./front:/app/:cached
      - ./front/node_modules:/app/node_modules
    networks:
      - django_net
    depends_on:
      - back

networks:
  django_net:
    driver: bridge

volumes:
  django_statics:
    driver: local
  static_volume:
  media_volume:
SPA構成イメージ

詳細な導線は異なるところはあると思いますが、上記がComposeのコンテナ構成イメージです。

  • ページアクセスのリクエストをwebサーバーコンテナ内のNginxがプロキシサーバーとして中継
  • バックエンドコンテナ内のアプリサーバーのuWSGIでバックエンドとNginxとの疎通
  • POSTリクエストをAPIサーバーとしてDjango(REST Framework)で処理
  • Vueのaxiosのプロキシサーバーと連結してレスポンスオブジェクトをVueの非同期で受け取る

こんな流れで最小限のSPA環境をビルドします。
今回はwebアプリとしてVueからしか繋いでないのに無駄に複雑な構成になってますが、専用コンテナをcomposeに追加してそこからのリクエストをAPIサーバーに処理してもらってNginx経由でアクセスを振り分けてもらえば将来的にネイティブアプリやデスクトップアプリなどクロスプラットフォーム対応した際にもコンテナ同士の構造を意識しなくても良くなる・・・ハズ・・・。

バックエンド側

APIサーバー処理用のバックエンド側のコンテナ構成

Dockerfile

FROM python:3.8
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
EXPOSE 8000

# 開発ディレクトリ設定
ENV WORKDIR /usr/src/app
WORKDIR ${WORKDIR}
COPY ./scripts/ ${WORKDIR}/

# コンテナ内でパッケージを一括インストール
COPY requirements.txt ${WORKDIR}
RUN pip install --upgrade pip && pip install -r requirements.txt && \
pip install coreapi django-cors-headers python-jose

COPY . ${WORKDIR}/

コンテナ起動関連スクリプト「start.sh」

  • 初回ビルド時にappフォルダ&新規プロジェクトを作成
  • 同時に各種セットアップのスクリプトを実行
  • migrationとuWSGIサーバーを起動(※2回目以降はここのみ実行)
#!/bin/bash
set 'echo -e -o pipefail'

unix_today=$(date +'%s')
unix_today=$((unix_today+32400))
jst_ymd_today=$(date '+%Y/%m/%d %H:%M:%S' --date "@$unix_today")

# メインプロジェクトフォルダが無ければセットアップ実行
if [ ! -d /code/app ]; then
    echo "----- ${jst_ymd_today} | Project initialization -----"
    mkdir app && cd app && django-admin startproject config .
    sleep 10
    # djangoセットアップ&サーバー起動
    bash /usr/src/app/scripts/django-settings.sh
    bash /usr/src/app/scripts/django-appstart.sh api
    # python manage.py createsuperuser
    echo "----- ${jst_ymd_today} | Initialization completed -----"
else
    echo "----- ${jst_ymd_today} | container update -----"
fi

cd app
python manage.py makemigrations
python manage.py migrate
# python manage.py collectstatic

# 静的ファイルディレクトリをstatic指定ディレクトリに集約コピー(--noinputで対話プロンプトを無視)
python manage.py collectstatic --noinput
# configアプリuWSGIに接続。「--py-autoreload 1」でファイル等に変更があった際は自動リロード
uwsgi --socket :8001 --module config.wsgi --py-autoreload 1 --logto /tmp/uwsgi.log

# python manage.py runserver 0.0.0.0:8001

Djangoセットアップ用スクリプト「django-settings.sh

  • 「config/settings.py」の各種セットアップ
    • タイムゾーンと言語を日本語対応
    • REST Framework設定を追記
    • staticディレクトリ設定
  • 「config/url.py」のセットアップ
  • 静的ファイル用「template」ディレクトリ作成
#!/bin/sh
# シェル実行が失敗したら終了してエラー出力
set 'echo -e -o pipefail'

# ルートディレクトリ取得
ROOT_DIR=$(cd $(dirname $0)/..;pwd)
echo ${ROOT_DIR}

# 各種モジュールインポートを追記
if ! grep -q "import os" "${ROOT_DIR}/app/config/settings.py" ;then
    sed -i -e "s/from pathlib import Path/from pathlib import Path \nimport os\nimport json\nfrom six.moves.urllib import request\nfrom cryptography.x509 import load_pem_x509_certificate\nfrom cryptography.hazmat.backends import default_backend/" ${ROOT_DIR}/app/config/settings.py
fi

# BASE_DIR変更
sed -i -e "s/BASE_DIR = Path(__file__).resolve().parent.parent/BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))/" ${ROOT_DIR}/app/config/settings.py

# INSTALLED_APPSにrest_frameworkとrest_framework_jwtとcorsheadersを追加
if ! grep -q "'rest_framework'," "${ROOT_DIR}/app/config/settings.py" ;then
    sed -i -e "s/'django.contrib.staticfiles',/'django.contrib.staticfiles',\n    'rest_framework', \n    'rest_framework_jwt', \n    'corsheaders',/" ${ROOT_DIR}/app/config/settings.py
fi

# MIDDLEWAREにcorsheadersミドルウェアを追記
if ! grep -q "'corsheaders.middleware.CorsMiddleware'," "${ROOT_DIR}/app/config/settings.py" ;then
sed -i -e "s/'django.middleware.common.CommonMiddleware',/'corsheaders.middleware.CorsMiddleware', \n    'django.middleware.common.CommonMiddleware', \n/" ${ROOT_DIR}/app/config/settings.py
fi

# テンプレートディレクトリ設定
sed -i -e "s/'DIRS': \[\],/'DIRS': \[os.path.join(BASE_DIR, \"app\/templates\")\],/" ${ROOT_DIR}/app/config/settings.py

# 言語をjaに変更
sed -i -e "s/LANGUAGE_CODE = 'en-us'/LANGUAGE_CODE = 'ja'/" ${ROOT_DIR}/app/config/settings.py

# タイムゾーンをAsia/Tokyoに変更
sed -i -e "s/TIME_ZONE = 'UTC'/TIME_ZONE = 'Asia\/Tokyo'/" ${ROOT_DIR}/app/config/settings.py

# DATEBASEパス変更
sed -i -e "s/'NAME': BASE_DIR \/ 'db.sqlite3',/'NAME': BASE_DIR+'\/app\/db.sqlite3',/" ${ROOT_DIR}/app/config/settings.py

# ALLOWED_HOSTSにlocalhostと127.0.0.1追加
sed -i -e "s/ALLOWED_HOSTS = \[\]/ALLOWED_HOSTS = \[\n  '127.0.0.1', \n    'localhost', \]/" ${ROOT_DIR}/app/config/settings.py

# STATIC_ROOT追記
sed -i -e "s/STATIC_URL = '\/static\/'/STATIC_URL = '\/static\/'\nSTATIC_ROOT = os.path.join(BASE_DIR, 'static')/" ${ROOT_DIR}/app/config/settings.py

# REST_FRAMEWORK設定追記
if ! grep -q "REST_FRAMEWORK = {" "${ROOT_DIR}/app/config/settings.py" ;then
echo "# REST_FRAMEWORK設定
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),
}

# クロスオリジン許可
CORS_ORIGIN_WHITELIST = (
    'http://localhost:8080',
)
" >> ${ROOT_DIR}/app/config/settings.py
fi

# importにincludeモジュールを追加&napp_name設定
sed -i -e "s/from django.urls import path/from django.urls import path, include \nfrom rest_framework.documentation import include_docs_urls\n/" ${ROOT_DIR}/app/config/urls.py
# APIドキュメントルート追加
sed -i -e "s/path('admin\/', admin.site.urls),/path('admin\/', admin.site.urls),\n    path('docs\/', include_docs_urls(title='API Document')),\n/" ${ROOT_DIR}/app/urls.py

# templates&staticフォルダ階層作成
mkdir ${ROOT_DIR}/app/templates
mkdir ${ROOT_DIR}/app/static && mkdir ${ROOT_DIR}/app/static/css ${ROOT_DIR}/app/static/js ${ROOT_DIR}/app/static/images

# base.html作成
cat > ${ROOT_DIR}/app/templates/base.html << "EOF"
<!DOCTYPE html>
{% load tz %}
{% load static %}
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}{% endblock %}</title>
    {% block style %}{% endblock %}
</head>
<body>
    {% if messages %}
    <ul class="pl-0 ml-3">
    {% for message in messages %}
        <li class="alert alert-{{ message.tags }}">{{ message }}<button type="button" class="close" data-dismiss="alert" aria-label="Close"> <span aria-hidden="true">×</span> </button></li>
    {% endfor %}
    </ul>
    {% endif %}
    {% block content %}
    {% endblock %}
    {% block script %}{% endblock %}
</body>
</html>
EOF

# index.html作成
cat > ${ROOT_DIR}/app/templates/index.html << "EOF"
{% extends "base.html" %}
{% load static %}
{% block title %}ページタイトル{% endblock %}
{% block style %}
{% endblock %}
{% block breadcrumb %}
{% endblock %}
{% block content %}
インデックスページのコンテンツを表示中
{% endblock %}
{% block script %}
{% endblock %}
EOF

# template.html作成
cat > ${ROOT_DIR}/app/templates/template.html << EOF
{% extends "base.html" %}
{% load static %}
{% block title %}ページタイトル{% endblock %}
{% block style %}
{% endblock %}
{% block breadcrumb %}
{% endblock %}
{% block content %}
コンテンツテンプレートです
{% endblock %}
{% block script %}
{% endblock %}
EOF

#*********************************************************
echo "----- django-settings.sh finish -----"
#*********************************************************

pythonパッケージ一覧

Django==3.2
psycopg2-binary>=2.8
scrapy-djangoitem==1.1.1
scrapy==2.4
scrapyd==1.2.0
djangorestframework==3.12.4
djangorestframework-jwt==1.11.0
django-cors-headers==3.7.0
django-webpack-loader==1.0.0
uwsgi==2.0.19.1
markdown==3.3.4
django-filter==2.4.0
Pygments==2.9.0

新規アプリ作成用スクリプト「django-appstart.sh

#!/bin/sh
# シェル実行が失敗したら終了してエラー出力
set -e -o pipefail

# 実行ディレクトリ取得
ROOT_DIR=$(cd $(dirname $0)/..;pwd)
echo $ROOT_DIR

# "新規アプリ作成"
# 新規ディレクトリ&アプリ作成
mkdir $ROOT_DIR/app/${1}
django-admin startapp ${1} $ROOT_DIR/app/${1}

# INSTALLED_APPSに新規アプリを追加
sed -i -e "s/'rest_framework',/'rest_framework', \n    '${1}', /" ${ROOT_DIR}/app/config/settings.py

# websiteルーティング追加
sed -i -e "s/\]/    path('${1}\/', include('${1}.urls')),\n\]/" ${ROOT_DIR}/app/config/urls.py


# APP_NAME/urls.py作成
cat > $ROOT_DIR/app/${1}/urls.py << EOF
from django.urls import include, path
# from . import views
from .views import *
app_name = '${1}'

urlpatterns = [
    path('api/jp', JPTestAPI.as_view()),
    path('api/us', USTestAPI.as_view()),
    path('api/br', BRTestAPI.as_view()),
    path('', index, name='index'),
]
EOF

# APP_NAME/views.py修正
cat > $ROOT_DIR/app/${1}/views.py << "EOF"
from uuid import uuid4
from urllib.parse import urlparse
from django.core.validators import URLValidator
from django.core.exceptions import ValidationError
from django.views.decorators.http import require_POST, require_http_methods
from django.shortcuts import render
from django.utils import timezone
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import ListView, TemplateView, DetailView
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework import authentication, permissions
from rest_framework import status, viewsets, filters
from rest_framework import permissions
from rest_framework.response import Response
# from django.db.models import Q
import pytz
from datetime import datetime

# REST API 通信テスト用
class JPTestAPI(APIView):
    permission_classes = (permissions.AllowAny,)
    def get(self, request, format=None):
        # return Response(data={'status': dt_now}, status=status.HTTP_200_OK)
        jp = pytz.timezone('Asia/Tokyo')
        return JsonResponse(data={'status': datetime.now(tz=jp)}, status=status.HTTP_200_OK)

class USTestAPI(APIView):
    permission_classes = (permissions.AllowAny,)
    def get(self, request, format=None):
        us = pytz.timezone('US/Eastern')
        return JsonResponse(data={'status': datetime.now(tz=us)}, status=status.HTTP_200_OK)

class BRTestAPI(APIView):
    permission_classes = (permissions.AllowAny,)
    def get(self, request, format=None):
        br = pytz.timezone('Brazil/East')
        return JsonResponse(data={'status': datetime.now(tz=br)}, status=status.HTTP_200_OK)

def index(request):
    return render(request, 'index.html'
EOF

# "テンプレート作成"
# templates&staticフォルダ階層作成
mkdir $ROOT_DIR/app/templates/${1}

# "「config/urls.py」にルーティングを追加"
sed -i -e "s/]/    path('${1}', include('${1}.urls')),\n]/" $ROOT_DIR/config/urls.py

#*********************************************************
echo "----- django-appstart.sh finish -----"
#*********************************************************

初回実行時に「main」ディレクトリを作成するスクリプトですが、他にも新規でアプリディレクトリを追加したい場合は
以下のコマンドでAPPNAMEを好きなアプリ名で実行するとスクリプトが実行され、新規ディレクトリが作成されます

$ docker-compose run back bash /code/scripts/django-appstart.sh APPNAME
  • 新規ディレクトリにアプリファイル一覧を作成
  • app/config/settings.py 」の INSTALLED_APPS にアプリ名設定追記
  • app/config/urls.py 」の urlpatterns にアプリルーティング追記
  • app/templates/ 」に新規アプリ名ディレクトリを作成
  • urls.py 」に設定追記。(※テスト用のパターン記述は削除)
  • views.py 」に設定追記。(※テスト用の関数は削除)

フロントエンド側

webアプリとしてのフロントエンド側のコンテナ構成

Dockerfile

FROM node:16.2.0

# 開発ディレクトリ設定
ENV WORKDIR /code
WORKDIR ${WORKDIR}
COPY ./scripts/ ${WORKDIR}/

# コンテナ内でパッケージを一括インストール
RUN npm update npm && npm install -g @vue/cli && \
    npm install -g @vue/cli-init

COPY . ${WORKDIR}/

コンテナ起動関連スクリプト「start.sh」

  • 初回ビルド時は新規Vueプロジェクト作成コマンドを催促
  • 初回以降にappディレクトリ作成済の場合は各種スクリプト実行
#!/bin/bash
set 'echo -e -o pipefail'

unix_today=$(date +'%s')
unix_today=$((unix_today+32400))
jst_ymd_today=$(date '+%Y/%m/%d %H:%M:%S' --date "@$unix_today")
app_name='app'

# プロジェクトディレクトリが存在していればサーバー起動
if [ -d /code/${app_name} ]; then
    # vueプロジェクト設定済みではなかったら
    if [ ! -f /code/${app_name}/vue.config.js ]; then
        echo "${jst_ymd_today} | Vue directory set up"
        # vueテンプレート作成
        bash /code/scripts/vue-settings.sh
        # axiosインストール
        npm install axios vue-router

        echo "----- Restart the container with the 'docker-compose up' command again -----"
    else
        echo "${jst_ymd_today} | Start the project server"
        # プロジェクトディレクトリに移動してサーバー起動
        cd ${app_name}
        npm run serve
    fi
else
    echo "「${jst_ymd_today}」|Create a new project"
    mkdir /code/${app_name}

    echo "----- Run the command 'docker-compose run front vue create ${app_name} .' on the host machine -----"
fi

Vue.js設定用スクリプト「vue-settings.sh」

  • 「HelloWorld.vue」にテスト用コード追記
  • 「main.js」にaxios設定追記
#!/bin/bash
set 'echo -e -o pipefail'

# ルートディレクトリ取得
ROOT_DIR=$(cd $(dirname $0)/..;pwd)
app_name='app'

#***********************************************************************
echo "----- Vue axios設定ファイル作成 -----"
#***********************************************************************
cat > ${ROOT_DIR}/${app_name}/vue.config.js << "EOF"
module.exports = {
    devServer: {
        proxy: {
            '^/api/': {
                target: 'http://localhost:8000',
                logLevel: 'debug',
                pathRewrite: { "^/api/": "/api/" }
            }
        }
    }
}
EOF

#************************************************************************************************
echo "----- 「HelloWorld.vue」修正 -----"
#************************************************************************************************

sed -i -e "s/<h1>{{ msg }}<\/h1>/<h1>{{ msg }}<\/h1>\n<h2>現在の時間:{{ result }}<\/h2>\n<button @click=\"getAPI()\">クリック!<\/button>/" ${ROOT_DIR}/${app_name}/src/components/HelloWorld.vue

sed -i -e "N;s/}\n<\/script>/, data () {\n    return {\n      result: 'No Result',\n      url: 'http:\/\/localhost:8000\/api\/'\n    }\n  },\n  methods: {\n    getAPI () {\n      this.\$axios.get(this.url).then(response => {\n        this.result = response.data.status\n      })\n    }\n  }\n}\n<\/script>/" ${ROOT_DIR}/${app_name}/src/components/HelloWorld.vue

#************************************************************************************************
echo "----- main.js修正 -----"
#************************************************************************************************
cat > ${ROOT_DIR}/${app_name}/src/main.js << "EOF"
import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'
import router from './router.js'

Vue.config.productionTip = false
Vue.prototype.$axios = axios

new Vue({
  router,
  render: h => h(App),

}).$mount('#app')
EOF

#**********************************************************
echo "----- コンテンツテンプレート作成 -----"
#**********************************************************
cat > ${ROOT_DIR}/${app_name}/src/router.js << "EOF"
import Vue from 'vue'
import Router from 'vue-router'
import top from '@/components/top'
import page2 from '@/components/page2'
import page3 from '@/components/page3'


Vue.use(Router)
export default new Router({
    mode: 'history',
    routes: [
        {
            path: '/',
            component: top
        },
        {
            path: '/page2',
            component: page2
        },
        {
            path: '/page3',
            component: page3
        }
    ]
})
EOF

cat > ${ROOT_DIR}/${app_name}/src/components/top.vue << "EOF"
<template>
    <div>
        <p>ここはトップページです。</p>
        <h2>現在の日本時間:{{ result }}</h2>
        <button @click="getAPI()">クリック!</button>
    </div>
</template>

<script>
export default {
    name: 'jp-time',
    props: {
        msg: String
    },
    data () {
        return {
        result: '不明',
        url: 'http://localhost:8000/api/jp'
        }
    },
    methods: {
        getAPI () {
            this.$axios.get(this.url).then(response => {
                this.result = response.data.status
            })
        }
    }
}
</script>
<style>
</style>
EOF

cat > ${ROOT_DIR}/${app_name}/src/components/page2.vue << "EOF"
<template>
    <div>
        <p>ここは2ページ目です。</p>
        <h2>現在のアメリカ時間:{{ result }}</h2>
        <button @click="getAPI()">クリック!</button>
    </div>
</template>

<script>
export default {
    name: 'us-time',
    props: {
        msg: String
    },
    data () {
        return {
        result: '不明',
        url: 'http://localhost:8000/api/us'
        }
    },
    methods: {
        getAPI () {
            this.$axios.get(this.url).then(response => {
                this.result = response.data.status
            })
        }
    }
}
</script>
EOF

cat > ${ROOT_DIR}/${app_name}/src/components/page3.vue << "EOF"
<template>
    <div>
        <p>ここは3ページ目です。</p>
        <h2>現在のブラジル時間:{{ result }}</h2>
        <button @click="getAPI()">クリック!</button>
    </div>
</template>

<script>
export default {
    name: 'br-time',
    props: {
        msg: String
    },
    data () {
        return {
        result: '不明',
        url: 'http://localhost:8000/api/br'
        }
    },
    methods: {
        getAPI () {
            this.$axios.get(this.url).then(response => {
                this.result = response.data.status
            })
        }
    }
}
</script>
EOF

#*********************************************************
# "----- 「App.vue」修正 -----"
#*********************************************************

cat <<EOF > ${ROOT_DIR}/${app_name}/src/App.vue
<template>
  <div id="app">
    <div id="header">
      <!--リンクタグを生成します。-->
      <router-link to="/">top</router-link>41
      <router-link to="/page2">2</router-link>
      <router-link to="/page3">3</router-link>
    </div>
    <router-view></router-view>
  </div>
</template>

<!--コンポーネントの名前を定義します。-->
<script>
export default {
  name: 'App'
}
</script>

<!--スタイルの指定をします-->
<style>
body {
  margin: 0;
}

#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
#header {
  height: 40px;
  background: white;
  box-shadow: 0px 3px 3px rgba(0,0,0,0.1);
  display: flex;
  justify-content: center;
  align-items: center;
}

#header a {
  text-decoration: none;
  color: #2c3e50;
  margin: 0 10px;
  padding: 3px 10px;
  background: #5ccebf;
}
</style>
EOF


Webサーバー

すべてのアクセスをさばくプロキシサーバー役のコンテナ構成

Dockerfile

FROM nginx:1.11.7

RUN mkdir /var/www
RUN mkdir /var/www/app
RUN mkdir /var/www/static

アプリ用Nginxファイル

upstream back {
  ip_hash;
  server back:8001;  # uWSGIでDjangoとnginxとが通信するためのポート
  server front:3000;     # Vueとnginxとが通信するためのポート
}

server {
  listen      8000;     # 待ち受けポート
  server_name 127.0.0.1;
  charset     utf-8;

  client_header_buffer_size 1k;
  large_client_header_buffers 8 32k;

  add_header Strict-Transport-Security 'max-age=31536000';
  add_header X-Frame-Options DENY;
  add_header X-XSS-Protection "1; mode=block";

  error_page 500 502 503 504 /50x.html;

  location /static {
    alias /var/www/static;
  }

  # バックエンドサーバー
  location / {
    include /etc/nginx/uwsgi_params;
    uwsgi_pass back;  # バックエンド
    proxy_redirect     off;
    proxy_set_header   Host $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Host $server_name;
    proxy_read_timeout 86400s;
    proxy_send_timeout 86400s;
  }

  # バックエンド adminサーバー
  location /admin/ {
    include /etc/nginx/uwsgi_params;
    uwsgi_pass back;
    proxy_redirect     off;
    proxy_set_header   Host $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Host $server_name;
  }
  location = /50x.html {
    root /usr/share/nginx/html;
  }
  client_max_body_size 75M;
}
server_tokens off;

uWSGI設定ファイル

uwsgi_param  QUERY_STRING       $query_string;
uwsgi_param  REQUEST_METHOD     $request_method;
uwsgi_param  CONTENT_TYPE       $content_type;
uwsgi_param  CONTENT_LENGTH     $content_length;

uwsgi_param  REQUEST_URI        $request_uri;
uwsgi_param  PATH_INFO          $document_uri;
uwsgi_param  DOCUMENT_ROOT      $document_root;
uwsgi_param  SERVER_PROTOCOL    $server_protocol;
uwsgi_param  REQUEST_SCHEME     $scheme;
uwsgi_param  HTTPS              $https if_not_empty;

uwsgi_param  REMOTE_ADDR        $remote_addr;
uwsgi_param  REMOTE_PORT        $remote_port;
uwsgi_param  SERVER_PORT        $server_port;
uwsgi_param  SERVER_NAME        $server_name;

コンテナ起動

まずコンテナ全体を起動。

$ cd docker_spa
$ docker-compose up -d

バックエンドにappディレクトリが作成&Djangoアプリ全体の設定ディレクトリ「config」とメインアプリディレクトリの「main」が作成されます。

webサーバー側のNginxも立ち上がるのでhttp:localhost:8000にアクセスで「インデックスページのコンテンツを表示中」のテキストが表示されていればDjangoとNginxの疎通はOK

$ docker-compose run front vue create app .
? Please pick a preset: Default (Vue 2) ([Vue 2] babel, eslint)
? Pick the package manager to use when installing dependencies: NPM

続いて上記コマンドで新規Vueプロジェクトは初期設定の対話コマンドがあるので「Vue2」と「npm」を選択してインストールします。

結構時間かかりますがインストールが完了したら再度docker-compose -dでビルドするとフロント側のセットアップスクリプトが実行されます

docker_spa/
  ┣━ back/
  ┃  ┣━ app/
  ┃  ┃  ┣━ config/
  ┃  ┃  ┣━ main/
  ┃  ┃  ┣━ static/
  ┃  ┃  ┣━ templates/
  ┃  ┃  ┣━ db.sqlite3
  ┃  ┃  ┗━ manage.py
  ┃  ┣━ media/
  ┃  ┣━ scripts/
  ┃  ┣━ static/
  ┃  ┣━ Dockerfile
  ┃  ┗━ requirements.txt
  ┣━ front/
  ┃  ┣━ app/
  ┃  ┃  ┣━ node_modules 
  ┃  ┃  ┣━ public/ 
  ┃  ┃  ┣━ src/
  ┃  ┃  ┣━ babel.config.js
  ┃  ┃  ┣━ package-lock.json
  ┃  ┃  ┣━ package.json
  ┃  ┃  ┗━ vue.config.js  # proxy設定ファイル
  ┃  ┣━ scripts/
  ┃  ┣━ Dockerfile
  ┃  ┣━ package-lock.json
  ┃  ┗━ package.json
  ┣━ web/
  ┃  ┣━ conf/
  ┃  ┃  ┗━ app_nginx.conf
  ┃  ┣━ logs/nginx
  ┃  ┃  ┗━ access.log
  ┃  ┃  ┗━ error.log
  ┃  ┣━ static/
  ┃  ┣━ Dockerfile
  ┃  ┗━ uwsgi_params
  ┃    
  ┗━ docker-compose.yml

上手くいけば上記の構成が出来上がってると思います。
http:localhost:8080にアクセスするとVueのトップページが表示されます。

Vueアクセスページ

「top」「2」「3」でページが切り替わり、「クリック!」ボタンをクリックするとバックエンドとの通信を行い、現在の時刻を表示します。

Pocket
LINEで送る
Facebook にシェア
reddit にシェア
LinkedIn にシェア

トップへ