Edited at

DockerでReactとMariaDB使ってWebサービスの雛形を作る

More than 1 year has passed since last update.


目的


  • いろいろソフト入れて環境構築しているとわけがわからなくなってくる。


    • Dockerに設定全部書いて簡単に構築できるようにする。



  • Pythonであれこれ処理したものをデータベースに保存したい。


    • PandasでMariaDBに接続しよう。



  • フロントエンドはReactを使いたい。


    • Webpack + Babelを導入する。



  • Webサーバーはどうする?


    • 最近流行のNginxを使おう。




Dockerの導入まで


  • GCPでCentOS7のインスタンスを作成する。

  • dockerとdocker-composeをインストールする。

  • dockerをサービスに登録し、sudoなしでdockerコマンドを実行できるようにする。

sudo yum install -y yum-utils device-mapper-persistent-data lvm2 && \

sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo && \
sudo yum makecache fast && \
sudo yum install -y docker-ce && \
sudo usermod -aG docker $USER && \
sudo systemctl enable docker

sudo curl -L https://github.com/docker/compose/releases/download/1.20.0/docker-compose-`uname \
-s`-`uname -m` -o /usr/local/bin/docker-compose && \
sudo chmod +x /usr/local/bin/docker-compose
# バージョン確認して再起動
docker-compose --version
sudo reboot


コンテナの構成

以下の3つのコンテナを作る。


  • MariaDB

  • Node

  • Nginx


  1. ローカルにPython実行環境とMariaDBのクライアントをインストールする。

  2. MariaDBを初期設定の後、Pythonを使ってデータを流し込む。

  3. Nodeの初期設定はイメージ作成後にnpm installで実行する。

  4. Nginxの設定書いてからDocker-compose upで起動。


Python3のインストール

sudo yum install -y https://centos7.iuscommunity.org/ius-release.rpm && \

yum search python36 && \
sudo yum install -y python36u python36u-libs python36u-devel python36u-pip && \
sudo ln -s /usr/bin/python3.6 /usr/bin/python3 && \
sudo ln -s /usr/bin/pip3.6 /usr/bin/pip3 && \
python3 --version && pip3 --version
sudo pip3 install pip --upgrade && \
sudo pip3 install sqlalchemy && \
sudo pip3 install PyMySQL && \
sudo pip3 install pandas


MariaDBクライアントのインストール

sudo yum install -y mariadb


MariaDBコンテナの作成

mkdir docker && cd docker

mkdir container && mkdir container/mariadb && mkdir container/mariadb/init

cat << EOF > container/mariadb/multibyte.cnf
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
EOF

# Dockerfileの作成とDocker-composeに設定を追記
cat << EOF > container/mariadb/Dockerfile
FROM mariadb

EXPOSE 3306
EOF

cat << EOF > docker-compose.yaml
version: '3'
services:
mariadb:
build:
context: ./container/mariadb
container_name: mariadb
image: mariadb
volumes:
- ./mariadb_data:/var/lib/mysql:z
- ./container/mariadb/multibyte.cnf:/etc/mysql/conf.d/multibyte.cnf
- ./container/mariadb/init:/docker-entrypoint-initdb.d
ports:
- 3306:3306
environment:
- MYSQL_ROOT_PASSWORD=rootpass
- MYSQL_DATABASE=app
- MYSQL_USER=username
- MYSQL_PASSWORD=secret
EOF

docker-compose up


MariaDBの動作確認

別のターミナルから……

cd ~/docker

# テストデータの準備
cat << EOF > test_data.csv
ID,Name,Birthdate,Sex,Occupation,Salary
ID-0001,Abe,1985/1/1,M,Engineer,8422213
ID-0002,Saito,1970/2/11,F,Professor,8222588
ID-0003,Yamada,1975/3/21,M,Doctor,9845288
ID-0004,田中,1980/4/22,F,Sales,8505218
ID-0005,Okamoto,1995/5/25,M,Student,218103
EOF

cat << EOF > test_data.py
import pandas as pd
import sqlalchemy as sa

df = pd.read_csv("test_data.csv")
url = 'mysql+pymysql://username:secret@127.0.0.1/app?charset=utf8'
engine = sa.create_engine(url, echo=True)
df.to_sql('table1', engine, index=False, if_exists='replace')
EOF

python3 test_data.py test_data.csv && rm test_data.csv && rm test_data.py
mysql -u root -prootpass -h 127.0.0.1
> use app
> SELECT * FROM table1;
# UTF8出力チェック
> exit

一度docker-composeを止める


nodeコンテナの作成

mkdir container/node && mkdir app && mkdir app/src && mkdir app/dist && mkdir app/node_modules

cat << EOF > container/node/Dockerfile
FROM node:9

WORKDIR app
RUN npm install -g npm
EOF

cat << EOF >> docker-compose.yaml
node:
build:
context: ./container/node
container_name: my-node
image: my-node
volumes:
- ./app:/app:z
ports:
- "80:8080"
EOF

docker-compose build


npm install

完成package.json用意してのnpm updateは重くて挙動が怪しいので少しずつ入れる。

dev系はサーバーでは不要なものもあるがローカルと設定を共通にするために入れておく。

docker-compose run node npm init -y

docker-compose run node npm i -D webpack webpack-cli webpack-dev-server babel-core babel-loader babel-preset-env babel-preset-react babel-polyfill
docker-compose run node npm i -D uglifyjs-webpack-plugin sass-loader node-sass style-loader css-loader style-loader css-loader url-loader
docker-compose run node npm i -S react react-dom redux react-redux
docker-compose run node npm i -D redux-devtools react-hot-loader
docker-compose run node npm i -D eslint eslint-loader eslint-plugin-node eslint-plugin-react babel-eslint
docker-compose run node npm i -D node-mariadb


設定ファイルの準備

webpack4で設定が簡単になったというが、十分複雑だ。

cat << EOF > update_package_json.py

import json
import collections

decoder = json.JSONDecoder(object_pairs_hook=collections.OrderedDict)
with open('app/package.json') as read_file:
data = decoder.decode(read_file.read())
data["name"] = "react-test"
data["version"] = "0.1.0"
data["author"] = "tibigame"
data["description"] = "react-test"
data["scripts"]["build"] = "webpack"
data["scripts"]["start"] = "babel-node ./src/server.js"
data["scripts"]["dev"] = "webpack-dev-server --hot"
data["scripts"]["watch"] = "webpack -d --watch --progress"
fw = open('app/package2.json','w')
json.dump(data, fw, indent=4)
EOF

python3 update_package_json.py && rm -rf app/package.json && mv app/package2.json app/package.json

cat << EOF > app/webpack.config.js
module.exports = {
mode: 'production',
entry: [
'babel-polyfill',
'./src/index.js',
],
output: {
filename: 'main.js'
},
module: {
rules: [
{
test: /
\.jsx?$/,
use: [
{
loader: 'babel-loader'
}
],
exclude: /node_modules/
},
{
test: /
\.scss/,
use: [
'style-loader',
{loader: 'css-loader', options: {url: false}},
],
}
]
}
};
EOF

cat << EOF > app/.babelrc
{
"presets": [
"react",
["env", {
"targets": {
"browsers": ["last 2 versions"]
},
"modules": false
}
]
],
"plugins": [
"transform-class-properties",
"transform-object-rest-spread",
"react-hot-loader/babel"
]
}
EOF


ReactとSCSSの確認ファイルの準備

cat << EOF > app/dist/index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<style>
body {
background: #eee;
}
#app {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
</style>
<script defer src="main.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
EOF

cat << EOF > app/src/style.scss
$red: #FF3333;
h1 {
color:
$red;
}
EOF

cat << EOF > app/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import "./style.scss"

import {TestComponent} from './test_component';

class App extends React.Component {
render() {
return (
<div>
<h1>Hello React!</h1>
<TestComponent name="My Counter for Babel" />
</div>
);
}
}
console.log('テスト');
ReactDOM.render(<App/>, document.querySelector('#app'));
EOF

cat << EOF > app/src/test_component.js
import React from 'react';

export class TestComponent extends React.Component {
constructor() {
super();
this.state = {
count: 0
};
}

handleClick() {
console.log('クリックされました');
this.setState({
count: this.state.count + 1
});
}

render() {
return (
<div>
<h2>{this.props.name}</h2>
<div>{this.state.count}</div>
<button onClick={this.handleClick.bind(this)}>Add +1</button>
</div>
);
}
}
EOF


Nginxコンテナの作成

ここの設定はよく理解していないが、制限かけておけば開発中でも問題ないだろう。

mkdir container/nginx

sudo yum install -y httpd-tools
htpasswd -c ./container/nginx/.htpasswd http_user
password

cat << EOF > container/nginx/Dockerfile
FROM nginx
EOF

vim container/nginx/default.conf
--------
server {
listen 8080;
server_name localhost;
location /api/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location / {
root /www/app;
}
# IPアドレスによる制限
allow xxx.xxx.xxx.xxx;
deny all;
# BASIC認証による制限
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/conf.d/.htpasswd;
}
--------

cat << EOF > docker-compose.yaml
version: '3'
services:
node:
build:
context: ./container/node
container_name: my-node
image: my-node
command: npm run build
volumes:
- ./app:/app:z

mariadb:
build:
context: ./container/mariadb
container_name: mariadb
image: mariadb
volumes:
- ./mariadb_data:/var/lib/mysql:z
- ./container/mariadb/multibyte.cnf:/etc/mysql/conf.d/multibyte.cnf
- ./container/mariadb/init:/docker-entrypoint-initdb.d
expose:
- 3306
environment:
- MYSQL_ROOT_PASSWORD=rootpass
- MYSQL_DATABASE=app
- MYSQL_USER=username
- MYSQL_PASSWORD=secret

nginx:
build:
context: ./container/nginx
image: nginx
container_name: nginx
ports:
- '80:8080'
volumes:
- ./container/nginx:/etc/nginx/conf.d:ro
- ./app/dist:/www/app:ro
links:
- 'node'
- 'mariadb'
EOF

docker-compose run node npm run build
docker-compose up


ブラウザでの動作確認

a.jpg

とりあえずReactのコンポーネントはちゃんとビルドされてNginxも機能しているようだ。

が、データベースとの接続は一筋縄ではいかなかった。


TODO


  • なんでもNode.jsはデータベースだけを扱ってwabpackの高度なビルドはしないとかだといいが、全部やろうとするとサーバーサイドレンダリングとかでダークサイドに落ちてしまう。

  • 軽く触った感じでは深入りせずにエンジニアはeslintやmocha、sassの書き方に注力したほうがいい。

  • Reactの世界とデータベースの間は薄いけど闇が深い。


    • ここをつなぐのはRailsかDjangoか…いや、もっと薄い皮でいいのでREST APIを試してみよう。