Docker에서 React와 MariaDB를 사용하여 웹 서비스의 병아리 만들기

목적


  • 여러가지 소프트 넣어 환경 구축하고 있다고 알 수 없게 된다.
  • Docker에 설정 전부 써서 간단하게 구축할 수 있도록 한다.

  • Python이든이 처리 한 것을 데이터베이스에 저장하고 싶습니다.
  • Pandas에서 MariaDB에 연결하자.

  • 프런트 엔드는 React를 사용하고 싶다.
  • Webpack + Babel을 소개한다.

  • 웹 서버는 어떻게 합니까?
  • 최근 유행의 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
  • 로컬에 Python 실행 환경과 MariaDB 클라이언트를 설치합니다.
  • MariaDB를 초기화한 후 Python을 사용하여 데이터를 전송합니다.
  • Node의 초기 설정은 이미지 작성 후에 npm install에서 실행한다.
  • 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:[email protected]/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를 중지

    노드 컨테이너 작성


    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
    

    브라우저에서의 동작 확인





    우선 React의 컴퍼넌트는 제대로 빌드되어 Nginx도 기능하고 있는 것 같다.
    하지만 데이터베이스와의 연결은 일근줄이 아니었다.

    TODO


  • 뭐든지 Node.js는 데이타베이스만을 취급해 wabpack의 고도의 빌드는 하지 않으면 좋다고 하지만, 전부 하려고 하면 서버 사이드 렌더링등으로 다크 사이드에 떨어져 버린다.
  • 가볍게 만진 느낌으로는 깊이 들어가지 않고 엔지니어는 eslint나 mocha, sass의 쓰는 방법에 주력하는 편이 좋다.
  • React의 세계와 데이터베이스 사이는 얇지만 어둠이 깊다.
  • 여기를 연결하는 것은 Rails인가 Django인가… 아니, 더 얇은 피부로 좋기 때문에 REST API를 시험해 보자.

  • 좋은 웹페이지 즐겨찾기