なにをしたのか
Docker Composeという、複数のDockerコンテナを管理する機能を使ったサンプルです。目指しているのは、「実用的かつ最小のサンプル(と思っているもの)」です。
nginxコンテナでstaticなファイル(html/js)を配信し、APIのアクセスは後ろにいるnode.jsのアプリケーションコンテナにフォワードして、SPAのWebサービスを構築しています。
基本的に、以下のページを参考に書いています。
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)のための設定を記述しています。
まずは必要なファイルをコピーして
COPY package.json webpack.config.js $HOME/nodeapp/
COPY client $HOME/nodeapp/client
npm install
とnpm run build
を実行します。
USER app
WORKDIR $HOME/nodeapp
RUN npm install
RUN npm run build
package.json
、webpack.config.js
を見るとわかるように、ビルドしたファイルはnodeコンテナ上のout
ディレクトリに出力されます。
{
"scripts": {
"build": "webpack --config webpack.config.js"
}
}
{
output: {
path: __dirname + "/out",
filename: 'bundle.js',
}
}
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)で転送できるようになります。
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する。
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
ディレクトリに出力するように設定する。
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 DBやMySQL、Elasticsearchなども公式のイメージがあるので、自分のサービスに合わせてdocker-compose.yml
に追加してあげれば良い。公式のREADMEは結構充実しているので、それを見ればなんとかなる。
次やること
プロダクションと開発用の設定をわけたい
- フロントをプロダクションビルドしたい
- 開発中は
webpack --watch
したい