Edited at

Dockerでnginx+node.jsのSPA構成を試す

More than 3 years have passed since last update.


なにをしたのか

Docker Composeという、複数のDockerコンテナを管理する機能を使ったサンプルです。目指しているのは、「実用的かつ最小のサンプル(と思っているもの)」です。

nginxコンテナでstaticなファイル(html/js)を配信し、APIのアクセスは後ろにいるnode.jsのアプリケーションコンテナにフォワードして、SPAのWebサービスを構築しています。

docker.png

基本的に、以下のページを参考に書いています。

DockerでのNodeアプリ構築で学んだこと | POSTD

リポジトリはこちら。

docker-compose-test


環境


  • Docker 1.12.1

  • docker-compose 1.8.0

  • node.js 4.6.0

  • nginx 1.11.4


動かす

Docker for macがインストールされていることを前提として。

$ git clone https://github.com/KeitaMoromizato/docker-compose-test

$ cd docker-compose-test
$ sudo docker-compose build
$ sudo docker-compose up


内容


Dockerとは

Dockerは、コンテナ型の仮想環境です。

簡単に言うと、Infrastructure as Code(インフラのコード化)の一種です。DockerFileという設定ファイルに、どういうサーバーを構築したいかを書いておき、docker runコマンドを実行するだけで、その環境のコンテナが即起動できます。

また、そのDockerFileから構成されるコンテナが、Docker Imageという形で世の中(DockerHubというサイト)に公開されています。なので、例えば公式のWordpressイメージをダウンロードして、docker runするだけで、簡単にWordpressの環境が入ったコンテナが作れるようになっています。


Docker Composeとは

Dockerは、その特徴から1コンテナ1アプリケーションという思想が基本です。1つのコンテナにすべての機能を詰め込まず、複数のコンテナを起動して、協調してシステムを構成しましょうという考えです。

例えば、リバースプロキシのnginx、バックエンドのアプリケーション、データベースを、それぞれ別のコンテナで起動します。それを簡単に実現するのが、Docker Composeです。

docker-compose.ymlという設定ファイルに、どのコンテナを立ち上げるか、どのコンテナ同士が通信するのかといったことを記述し、これまたdocker-compose upというコマンド1つで、必要なサービスがすべて立ち上がります。


目的

プロダクションで使うメリットは、まだ使っていないのでパス。

今回使った目的は、手元の開発環境の管理のため。(プライベートも含め)いろいろな開発をしていると、やれnode.jsのバージョンが違うだとか、やれGo langのパスが通ってないとか、なんだか色々と面倒なことが多い。

Dockerを使えば、バックエンドアプリケーションを動かす環境をDockerFileという形で明示でき、かつnginxやDBなどをローカルで個別に立ち上げるのではなく、Docker Composeを使うことでより本番環境に近いものが、簡単に再現できます。

という訳で、普段ちょっとしたものを作るときによく使うスタック、node.js+SPA(Webpackビルド)の環境を構築してみました。


ディレクトリ構成

主要なものだけで、こんな感じ。

|-- Dockerfile

|-- docker-compose.yml
|-- client
| |-- index.html
| |-- index.js
|
|-- server
| |-- index.js
|
|-- nginx
| |-- default.conf

名前
役割

Dockerfile
node.jsアプリのコンテナの設定。コンテナを最初に構築するときに、何をするのかなど。

docker-compose.yml
各コンテナの詳細設定。今回は、nodeコンテナとnginxコンテナの設定が書いてある

client
静的配信するファイルのコンパイル前データ。これをWebpackでビルドして、outフォルダに格納される

server
nodeアプリ側のコード

nginx
nginxの設定ファイル。静的配信と、nodeアプリへフォワードする設定が書いてある


詳細


Dockerfile

Docke Composeを使う場合でも、Singleコンテナの場合でも、基本となるのはDockerfileです。ベースとなるコンテナ(今回で言うと、nodeアプリケーション)の設定を書いていきます。

ここでは、主にnpm installとフロントのビルド(Webpack)のための設定を記述しています。

まずは必要なファイルをコピーして


Dockerfile

COPY package.json webpack.config.js $HOME/nodeapp/

COPY client $HOME/nodeapp/client

npm installnpm run buildを実行します。


Dockerfile

USER app

WORKDIR $HOME/nodeapp
RUN npm install
RUN npm run build

package.jsonwebpack.config.jsを見るとわかるように、ビルドしたファイルはnodeコンテナ上outディレクトリに出力されます。


package.json

{

"scripts": {
"build": "webpack --config webpack.config.js"
}
}


webpack.config.js

{

output: {
path: __dirname + "/out",
filename: 'bundle.js',
}
}


docker-compose.yml

docker-compose.ymlには、起動するすべてのコンテナの設定を書きます。


docker-compose.yml

nodeapp:

build: .
container_name: "nodeapp"
command: node index.js
ports:
- '3000:3000'
environment:
- PORT=3000
volumes:
- ./server:/home/app/nodeapp
- /home/app/nodeapp/node_modules
- www:/home/app/nodeapp/out
nginx-proxy:
image: nginx
container_name: "nginx"
ports:
- '8080:8080'
volumes:
- ./nginx:/etc/nginx/conf.d:ro
- www:/www/app:ro
links:
- 'nodeapp'


image

DockerHubにおいてあるイメージを選択します。nodeのイメージはDockerfileの方に書いてあるので、ここではnginxのイメージのみ。

DockerHubからイメージを探すときは、できるだけOFFICIALなものを選びましょう。


environment

コンテナに設定する環境変数を指定します。


volumes

volumesの記述には、いくつか目的があります。例えば、以下の記述は「ホスト上の.serverディレクトリを、コンテナ上の/home/app/nodeappにマウントする`設定です。これにより、コンテナ上で任意のアプリケーションを動かせるようになります。

  volumes:

- ./server:/home/app/nodeapp

また、以下の記述は「node_modulesディレクトリをボリュームとして作成する」設定です。ボリュームは、コンテナとは別のライフサイクルを持っています。ここでは、コンテナを落とす度にnpm installしたモジュールが消えてしまうと困るので、別途ボリュームとして定義しています。

  volumes:

- /home/app/nodeapp/node_modules

また、ボリュームはコンテナ間で共有することもできます。以下の記述は「コンテナ上のディレクトリを、名前付きボリューム(www)として他のコンテナと共有できるようにする」設定です。ここでは、nodeコンテナでビルドしたファイルの出力ディレクトリ(out)にwwwwという名前を付けています。

  volumes:

- www:/home/app/nodeapp/out

そして、そのボリュームを、nginxコンテナから参照し、静的配信しています。後ろにroを付けると、そのコンテナからは読み取り専用(ReadOnly)になります。

  volumes:

- www:/www/app:ro


links

コンテナが通信できる、別のコンテナの名前を記述します。ここでは、nginx-proxyがリクエストをフォワードするため、nodeappコンテナを見えるようにしています。


nginxの設定

nginxのコンテナでは、静的ファイルの配信と、APIへのリクエストをnodeコンテナにフォワードする設定をします。

静的ファイルは、先のdocker-compose.ymlの設定で、outディレクトリを/www/appにマウントしているため、そこをrootとして設定します。

APIへのリクエストは、proxy_passでnodeコンテナにフォワードします。ここでは、linksで設定した名前(nodeapp)で転送できるようになります。


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;
proxy_pass http://nodeapp:3000/;
}

location / {
root /www/app;
}
}



nodeアプリ

普通のexpressアプリ。上記のnginxの設定だと、URLの/api部分が省かれて転送されるので、こちらでは/itemsでLISTENする。


server/index.js

const app = require('express')();

const PORT = 3000;

app.use((req, res, next) => {
console.log(req.url);
next();
});

app.get('/items', (req, res) => {
res.send([
'hoge', 'huga'
]);
});

app.listen(PORT, () => console.log(`LISTEN:${PORT} on docker`));



フロント

実装はReactだけど何でも良いので省略。Webpackでビルドしたものと、HTMLファイルをoutディレクトリに出力するように設定する。


webpack.config.js

module.exports = {

entry: {
js: "./client/index.js",
html: "./client/index.html",
},
output: {
path: __dirname + "/out",
filename: 'bundle.js',
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react'],
},
},
{
test: /\.html$/,
loader: 'file?name=[name].[ext]'
},
],
},
}


まとめ

一度慣れてしまうと、開発環境は基本的にこれで構築したほうが良いと思うくらい、楽。

あとは、Mongo DBMySQLElasticsearchなども公式のイメージがあるので、自分のサービスに合わせてdocker-compose.ymlに追加してあげれば良い。公式のREADMEは結構充実しているので、それを見ればなんとかなる。


次やること

プロダクションと開発用の設定をわけたい

* フロントをプロダクションビルドしたい

* 開発中はwebpack --watchしたい