Nginx/React/MongoDB on Docker の構築 その6 Reactアプリをコンテナイメージに

前回作ったアプリをNginxのDockerに埋め込んで、イメージを作成する

React アプリをビルド

ビルドコマンドを使う。

npm run build

ビルドに成功するとbuildフォルダが以下のように作成される。

# ls -la build/
total 20
drwxr-xr-x. 3 root root  130 Mar 30 00:41 .
drwxr-xr-x. 6 root root  119 Mar 30 00:40 ..
-rw-r--r--. 1 root root  196 Mar 30 00:41 asset-manifest.json
-rw-r--r--. 1 root root 3870 Mar 30 00:40 favicon.ico
-rw-r--r--. 1 root root  756 Mar 30 00:41 index.html
-rw-r--r--. 1 root root  317 Mar 30 00:40 manifest.json
-rw-r--r--. 1 root root 3164 Mar 30 00:41 service-worker.js
drwxr-xr-x. 4 root root   27 Mar 30 00:41 static

このbuildフォルダをDockerイメージにコピーをする必要がある。
まずは、コンテナからホストにdocker cpコマンドでアプリをコピーする。

docker cp 885fba123646:/root/demo/frontend/ .

Dockerfile を編集

今回作成するDockerコンテナ用フォルダを作成する

mkdir frontendapp

以下の必要なものを作成したフォルダにコピーする。

  • default.conf
    nginxの設定ファイル
  • frontend
    frontendのアプリが入ったディレクトリ

default.confの中身は簡単に以下のように設定

server {
    listen       80 default_server;
    root /var/www/frontend;
    server_name  localhost;

    access_log  /var/log/nginx/host.access.log  main;
    index index.html index.htm;
}

Dockerfileはnginxのものをベースに以下を追加

RUN mkdir /var/www/
COPY ./default.conf /etc/nginx/conf.d/default.conf
COPY ./frontend/build/ /var/www/frontend

最終的なDockerfileはこんな感じ。

FROM centos:7
MAINTAINER "yuki"

RUN yum update -y && yum clean all
RUN touch /etc/yum.repos.d/nginx.repo
RUN echo '[nginx]' >> /etc/yum.repos.d/nginx.repo
RUN echo 'name=nginx repo' >> /etc/yum.repos.d/nginx.repo
RUN echo 'baseurl=http://nginx.org/packages/centos/$releasever/$basearch/' >> /etc/yum.repos.d/nginx.repo
RUN echo 'gpgcheck=0' >> /etc/yum.repos.d/nginx.repo
RUN echo 'enabled=1' >> /etc/yum.repos.d/nginx.repo
RUN yum install -y nginx && yum clean all

RUN mkdir /var/www/
COPY ./default.conf /etc/nginx/conf.d/default.conf
COPY ./frontend/build/ /var/www/frontend

# Enable nginx
RUN systemctl enable nginx

# Start init daemon
CMD ["/sbin/init"]

準備ができたのでコンテナイメージの作成

docker build -t yuki/frontend:0.1 .

コンテナイメージができたことを確認

$ docker images
REPOSITORY                                TAG                 IMAGE ID            CREATED             SIZE
yuki/frontend                             0.1                 72463995b2c5        5 minutes ago       304.5 MB

起動テスト

作ったコンテナイメージのテスト

docker run -d --name frontend -p 10080:80 yuki/frontend:0.1

ホストの10080ポートを割り当てて、10080ポートにアクセスすると前回作成したTODOアプリの画面がでる。

おわり

Dockerfile内でファイルのコピーを記述し、イメージ作成と起動ができた。
次回はenvsubstを使って、環境変数を渡す練習をしてみる。

Nginx/React/MongoDB on Docker の構築 その5 Reactからバックエンドへの接続

先日作ったバックエンドに対して、Reactから情報を取るところまで。
追加とか削除は同じようにロジック追加するだけなので割愛。

準備

nginxのDockerを立ち上げて、Reactをインストールしその上で作業。
以前Reactのインストールをnginx上で行ったので、そのDockerを使った。
もちろん、Docker上で試す必要はない。

React-Bootstrapのインストール

見た目とか色々いじるのはめんどくさいので、簡単なBootstrapを使用。
React-Bootstrapなるものがあるらしく、Reactのアプリを作成後にインストール。
いつも通り公式のGetting Startedを参考に

create-react-app frontend
cd frontend
npm install --save react react-dom
npm install --save react-bootstrap

この状態でnpm startすると前回のデモアプリと同じ以下の画面が出るはず。

PORT=80 npm start

React App

続いて、Bootstrapを使うためにCSSを追加する。

vi public/index.html

# <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
# を<head>内に追加

TODOを表示するテーブルを作成

src/App.jsにTODOリストのテーブルを追加。
todosに後々取得したデータが入る予定で、各行の表示はsrc/EntryRow.jsに記載。

import React, { Component } from 'react';
import { Table } from 'react-bootstrap';
import EntryRow from './EntryRow';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {todos: ''};
  }
  entryRow(){
    if(this.state.todos instanceof Array){
      return this.state.todos.map(function(object, i){
          return <EntryRow obj={object} key={i} />;
      })
    }
  }
  render() {
    return (
      <div className="App">
        <Table striped bordered condensed hover>
          <thead>
            <tr>
              <th>Id</th>
              <th>Name</th>
            </tr>
          </thead>
          <tbody>
            {this.entryRow()}
          </tbody>
        </Table>
      </div>
    );
  }
}
export default App;

続いて、src/EntryRow.jsにTODOリストの各行を追加するロジックを追加。

import React, { Component } from 'react';

class EntryRow extends Component {
  render() {
    return (
        <tr>
          <td>
            {this.props.obj._id}
          </td>
          <td>
            {this.props.obj.name}
          </td>
        </tr>
    );
  }
}

export default EntryRow;

この状態で画面にテーブルができる。もちろん、todosの中は空なので何も表示されない。

todo-table1

TODOのリストを取得して表示

HTTPのリクエストにはaxiosを使用。
追加でaxiosもインストール。

npm install --save axios

src/App.jsに前回作成したフロントエンドに対してリクエストを投げるロジックを追加する。
頭にモジュールのインポートを追加。

import axios from 'axios';

リクエストのロジックをconstructorとrenderの間くらいに挿入する。 IPアドレスやポートなどは環境に合わせて。

  componentDidMount(){
    axios.get('http://<ip>:3000/api/tasks')
    .then(response => {
      this.setState({ todos: response.data });
    })
    .catch(function (error) {
      console.log(error);
    })
  }

動作確認

PORT=80 npm start

以前追加したタスクがあれば、画面に表示されるはず。

todo-table2

ちなみに、オリジン間リソース共有ができない設定では正しく表示されない。
テストなので、ChromeにAllow-Control-Allow-Origin: *などの拡張を入れて対応も可能。

おわり

やっと通して動くようになった。
次は、Dockerのイメージとして動かせるようにする。

Nginx/React/Express/MongoDB on Docker の構築 その4 バックエンドサーバ構築 Express on Docker

今回はExpressを使って、バックエンドを構築。
具体的には、Expressを使って、MongoDBを操作するAPIのサーバを構築する。

Expressは小さくて柔軟なwebフレームワーク。
今回は、GET/POST/DELETEに合わせてDBの取得/追加/削除のロジックを構築する。

CentOSのDockerを作成

まずはいつも通りCentOSのDocker作成から

docker run -itd --privileged -p 3000:3000 --name express centos:7 /sbin/init
docker exec -it express /bin/bash

3000番ポートを外部に公開する。

node.jsのインストール

前回と同様にnode.jsのインストール。

yum install -y epel-release
yum install -y nodejs npm

Expressのインストール

ここを参考に、新規アプリケーションを作成していく。

create-react-appでReactのアプリケーションを作成。npm initのとき色々聞かれるけど、空白のままで。

mkdir backend
cd backend/
npm init

続いて、expressのモジュールをインストール

npm install express --save

このコマンドを実行すると、package.jsonの中にexpressのパッケージが追加される。

他に必要なパッケージの追加

  • body-parser
    POSTパラメータをJsonで取得するのに必要

  • mongoose
    MongoDBにつなぐためのモジュール

npm install body-parser --save
npm install mongoose --save

これでpackage.jsonは以下の通り3つのモジュールが追加されている。

{
  "name": "backend",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.18.2",
    "express": "^4.16.3",
    "mongoose": "^5.0.10"
  }
}

動作確認

とりあえず動かしてみる。
チュートリアルのExampleにあるコードをコピーしてindex.jsとして保存。

var express = require('express')
var app = express()

app.get('/', function (req, res) {
  res.send('Hello World!')
})

app.listen(3000)

expressの起動

node ./index.js

その後、Dockerの外からホストの3000宛にHTTPリクエストを投げて確認。

$ curl -X GET http://localhost:3000
Hello World!

APIの設計

今回はTODOアプリを想定。

DB

MongoDBにはタスク用のDBが用意されていると想定する。
中身はシンプルで、tododbという名前のDB内にtasksテーブルを作成して、nameのフィールドを持つエントリが増えていく形。

  • tododb
    • tasks
      • name

API

  • GET /api/tasksでタスクリストの取得
  • POST /api/tasksでタスクリストの追加
  • DELETE /api/tasks/:idでタスクリストの削除

Expressの実装

Mongooseを使っているので、この簡単なチュートリアルをコピペして、DBとのやりとりを作る。
index.jsにロジックを追加していく。勉強用なので、MongoDBのアクセス先もべたがき。
アドレスは 172.17.0.4 で、ポートはデフォルトの 27017

var express = require('express');
var bodyParser = require('body-parser');
var mongoose   = require('mongoose');

var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

var router = express.Router();
mongoose.connect('mongodb://172.17.0.4/tododb');

const Task = mongoose.model('Task', { name: String });

router.use(function (req, res, next) {
  console.log('Time:', Date.now())
  next()
})

router.route('/tasks')
  .post(function(req, res) {
    var task = new Task();
    task.name = req.body.name;
    task.save(function(err) {
      if (err)
        res.send(err);
      res.json({ message: 'task created' });
    });
  })
  .get(function(req, res) {
    Task.find(function(err, tasks) {
      if (err)
        res.send(err);
      res.json(tasks);
    });
  });

router.route('/tasks/:id')
  .delete(function(req, res) {
     Task.remove({
       _id: req.params.id
     }, function(err, task) {
       if (err)
         res.send(err);
       res.json({ message: 'task deleted' });
     });
  });


app.use('/api', router);
app.listen(3000);

APIサーバの動作確認

上記ファイルを作成するとbackendフォルダ内が以下のようになる。

$ ls -l
total 16
-rw-r--r--.  1 root root 1071 Mar 14 02:08 index.js
drwxr-xr-x. 67 root root 4096 Mar 14 01:39 node_modules
-rw-r--r--.  1 root root  342 Mar 14 01:42 package.json

Expressを起動する

node ./index.js

外部から動作確認。
ちなみに、テスト環境では172.17.0.4のDocker上でMongoDBがデフォルトポート(27017)で待ち受けている前提。

最初は何もタスクがない状態。GETしても何も得られない。

$ curl -X GET http://localhost:3000/api/tasks
[]

新しいタスクを追加してみる。OKのメッセージが返ってきたら取得して確認。

$ curl --data-urlencode "name=task1" -X POST "http://localhost:3000/api/tasks"
{"message":"task created"}

$ curl -X GET http://localhost:3000/api/tasks
[{"_id":"5aa8846f3a07580112286a66","name":"task1","__v":0}]

ちゃんと追加されているので、削除して確認。

$ curl -X DELETE http://localhost:3000/api/tasks/5aa8846f3a07580112286a66
{"message":"task deleted"}

$ curl -X GET http://localhost:3000/api/tasks
[]

おわり

CRUDのUは実装していないけど、APIサーバとしては今は十分。
次はReactからこのAPIサーバにリクエストしてページに表示するところ。

Nginx/React/Express/MongoDB on Docker の構築 その3 React on Nginx on Docker

React

Reactはwebアプリのフロントエンド開発用ライブラリ。
公式によると宣言型で効率的かつ柔軟なJavascriptライブラリらしい。
インフラエンジニアのイメージとしては、クライントサイドでの実装を簡単に作れるライブラリという勝手なイメージ。
Webページにあるフォームのチェックやサジェスト機能を実装できる。

React 開発用Dockerを作成

前回作成したnginxのイメージからDockerを建てる

docker run --name nginx -d -p 10080:80 ytsuboi/nginx:1.0

node.jsのインストール

yumを使って、node.jsのインストールを行う。これでnpmコマンドが使用可能になる。
npmはjavascript用のパッケージ管理ツールで、RubyのRubyGems。PythonのPipにあたる。

yum install -y epel-release
yum install -y nodejs npm

create-react-appでReactのアプリケーションを作成

これでnodejsを使う準備ができたので、React用のライブラリをインストールして、初期アプリケーションを作成する。
初期アプリケーションの作成にはcreate-react-appを利用する。
この手順の通り、Reactの新規アプリケーションを作成する際に使えるツール。

npm install -g create-react-app
create-react-app <application name>

<application name>でフォルダが作成されると思うが、その中のファイルを編集してReactのアプリを作成していく。
今回はこのままでアプリを動かしてみる。
ちなみに、buildせずにnpm startで動かすには、まずnginxを止めてから80ポートで起動してあげれば外部から10080ポートでアクセスできる。

sysatemctl stop nginx
PORT=80 npm start

アプリケーションのビルド

実環境で動作させる場合には、開発したReactのアプリケーションをビルドする必要がある。
npm run buildを行うと、/build/フォルダが作成される。
このフォルダをnginx上で公開することでReactのwebアプリケーションにアクセスができるようになる。

npm run build

ビルドしたアプリケーションを/var/www/<application name>にコピーする。

mkdir /var/www
cp -r build /var/www/<application name>

nginx上で動かす

nginx上でVirtualHostを作成する。
編集するファイルは/etc/nginx/conf.d/default.confを用いる。
公開するフォルダを/var/www/<application name>とするとnginxの設定は以下のようになる。

server {
    listen       80 default_server;
    root /var/www/<application name>;
    server_name  localhost;

    access_log  /var/log/nginx/host.access.log  main;
    index index.html index.htm;

}

今回は簡単な設定だけを記載。
80ポートに来たHTTPのリクエストは/var/www/<application name>配下のファイルを返す。
ファイル名を指定しない場合には、デフォルトでindex.htmlまたはindex.htmを返す。

設定が完了したらnginxを再起動する。

systemctl restart nginx

外部からホストのIPとポートでアクセスして次の画面が出れば成功。

React App

おわり

nginx上で公開する方法がわかったので、次はNode.jsを使ってmongoDBにアクセスするところをやっていこう。
これらの設定やアプリケーションをコンテナ化するのはまたその後で。

FlutterでiPhoneアプリ 入門

Flutterが気になったので、iPhoneアプリの開発ができるところまで試した。

Flutter

FlutterはGoogleの新しいUIフレームワーク。iOSとAndroid上で動作するネイティブアプリを
作成することができる。現在はベータバージョン。

https://flutter.io/

インストール

環境

  • macOS Sierra 10.12.6
  • Xcode 9.2

DARTのインストール

Homebrewを使って、まずDARTをインストールする。

brew tap dart-lang/dart
brew install dart

VSCodeのインストール

Editorは Android StudioとVSCodeの2つがサポートされている。
ここでは、VSCodeを使用。
VSCodeはこのサイトからダウンロードしてインストールするだけ。

必要なソフトウェアのインストール()

brew update
brew install --HEAD libimobiledevice
brew install ideviceinstaller
brew install ios-deploy
brew install cocoapods

CocopPodのセットアップを実施

pod setup

もし、以下のようなエラーが出た場合にはキャッシュを削除して試す。

Setting up CocoaPods master repo
[!] Failed to connect to GitHub to update the CocoaPods/Specs specs repo - Please check if you are offline, or that GitHub is down

キャッシュを一時/tmp/に移動

mv ~/.cocoapods/ /tmp/
mv ~/Library/Caches/CocoaPods/ /tmp/

Flutter SDKのインストール

ここからFlutterをインストールしていく。
基本的にここを参照。

# 今回インストール場所に $HOME/sw/flutter を使用
cd $HOME/sw/
git clone -b beta https://github.com/flutter/flutter.git

FlutterをPATHに登録するため、.bashrcまたは.zshrcなどに以下を登録

export PATH=$HOME/sw/flutter/bin:$PATH

Flutter の条件確認

ガイドにあるとおり、環境の確認を行う。

$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (on Mac OS X 10.12.6 16G29, locale en-JP, channel beta)
[✗] Android toolchain - develop for Android devices
✗ Unable to locate Android SDK.
Install Android Studio from: https://developer.android.com/studio/index.html
On first launch it will assist you in installing the Android SDK components.
(or visit https://flutter.io/setup/#android-setup for detailed instructions).
If Android SDK has been installed to a custom location, set $ANDROID_HOME to that location.
[✓] iOS toolchain - develop for iOS devices (Xcode 9.2)
[✗] Android Studio (not installed)
[✓] VS Code (version 1.20.1)
[!] Connected devices
! No devices available

! Doctor found issues in 3 categories.

FlutteriOS toolchaingVS CodeにチェックがついていればiPhoneアプリには十分。
ソフトウェアが足りない場合には、以下のようなエラーが出るので、従うこと。

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (on Mac OS X 10.12.6 16G29, locale en-JP, channel beta)
[✗] Android toolchain - develop for Android devices
✗ Unable to locate Android SDK.
Install Android Studio from: https://developer.android.com/studio/index.html
On first launch it will assist you in installing the Android SDK components.
(or visit https://flutter.io/setup/#android-setup for detailed instructions).
If Android SDK has been installed to a custom location, set $ANDROID_HOME to that location.
[!] iOS toolchain - develop for iOS devices (Xcode 9.2)
✗ libimobiledevice and ideviceinstaller are not installed. To install, run:
brew install --HEAD libimobiledevice
brew install ideviceinstaller
✗ ios-deploy not installed. To install:
brew install ios-deploy
[✗] Android Studio (not installed)
[!] VS Code (version 1.12.1)
[!] Connected devices
! No devices available

Editorのセットアップ

VS Codeを開いて’Dart Code’をインストールする。

新規Flutterプロジェクトの作成

VS Codeのメニュー View>Command Palette… を選択して
Flutter: New Projectを選択し、任意の場所にプロジェクトを作成する。

作成が完了したら、/ios/Runner.xcworkspaceをXcodeで開いて、
SiningBundle Identifierを設定する。

open myflutterapp/ios/Runner.xcworkspace

でも起動できる。

動作確認

iOS Simulaterアプリケーションを起動する。

open -a Simulator

メニューの Hardware>Device から好きなシミュレータを起動。

VSCodeに戻って、main.dartを開いた状態でF5を押すとアプリケーションが動きだす。
成功すると以下のような画面になる。

所感

コードに変更を加えて、F5を押すだけですぐ試せるのは本当に嬉しい。
まだベータ版なので、どこまで動くのか?という疑問はあるが、おもったよりドキュメントがあるので色々試せそう。