こんにちは。Dockerは素晴らしいですね。
@say3no くんと湯バードくんからDockerを教えてもらったのがきっかけです。本当に感謝しています。
目次
はじめに
さてとあるプロジェクトでNode.jsのソースコードを管理したい、ということになりました。
デプロイやテスト環境、コードの公開を考慮する必要がありました。
そんな時に「Node.jsのコードってどうやってDockerで管理すれば良いの?」というお話になりました。
調べたところ DockerでのNodeアプリ構築で学んだこと (元: Lessons from Building a Node App in Docker)
というページがあったのでこちらを参考にデプロイや公開に向けての手順をなぞってみました。
とりあえずNode.jsのパッケージをDockerfile付きで公開する手順
今回なぞった箇所についてを簡易版として切り出しました。
こちらを使ってみます。
サンプルのアプリケーションを作っておきます。
$ mkdir -p ~/tmp/some-nodejs-app/; cd ~/tmp/some-nodejs-app
$ echo 'console.log("hello world!")' > index.js
tar玉をディレクトリを掘らずに手元に展開します。
一旦ダウンロードして展開してもOKです。またこのやり方だとpackage.jsonを上書きしてしまうので、上書きしたくない場合には下の方法で除外します。
$ curl -L -s -o - https://github.com/yousan/nodejs-docker-deploy/archive/0.1.tar.gz | tar zxv --strip=1
package.jsonを展開せずに手元にダウンロードする方法です。
$ curl -L -s -o - https://github.com/yousan/nodejs-docker-deploy/archive/0.1.tar.gz | tar zxv --strip=1 --exclude="package.json"
展開したファイル中にあるyourappnamehere
をアプリケーション名にします。
$ sed -i '' -e 's/yourappnamehere/myapp/g' *
動作を確認します。
$ docker-compose up
あとはここにあるDockerfileやdocker-composeなどを公開すれば良いです。
docker-compose.ymlやpackage.jsonなどは適宜調整してください。
参考記事の内容をたどる
続いて参考記事にあった内容をフォローしていきます。
アプリケーション名の決定と作業ディレクトリの確保
今回のアプリケーション名は元記事に従って「chat」としています。
作業ディレクトリを作ってGit化しておきます。
$ mkdir ~/git/nodejstest
$ git init
$ echo 'node_modules/' > .gitignore
$ git add -A; git commit -m 'first commit'
テストアプリケーションの配置、Dockerfileとdocker-compose.ymlの作成
Dockerfileを作ります。元記事の手順を少し進めた形で作ってます。
FROM node:4.3.2
RUN useradd --user-group --create-home --shell /bin/false app && \
npm install --global npm@3.7.5
ENV HOME=/home/app
COPY package.json npm-shrinkwrap.json $HOME/chat/
RUN chown -R app:app $HOME/*
USER app
WORKDIR $HOME/chat
RUN npm install
USER root
COPY . $HOME/chat
RUN chown -R app:app $HOME/*
USER app
CMD ["node", "index.js"]
続いてdocker-compose.yml を作ります。
chat:
build: .
command: node_modules/.bin/nodemon index.js
volumes:
- .:/home/app/chat
- /home/app/chat/node_modules
ports:
- '3000:3000'
テスト用のNode.jsのアプリケーションを置いておきます。
const express = require('express');
const app = express()
app.get('/', function (req, res) {
res.send('Hello World!!')
});
app.listen(3000, function () {
console.log('Example app listening on port 3000!')
});
https://github.com/jdleesmiller/docker-chat-demo
このDockerfileではpackage.json npm-shrinkwrap.jsonをホスト側からDockerイメージにコピーすることになっていますが、それがないためにビルドに失敗します。
元記事ではDockerfileにCOPYがない状態でnpm shrinkwrapを行っていますが、横着をして空っぽのpackage.jsonとnpm-shrinkwrap.jsonを作ってコピーしています。
$ echo '{}' > package.json; echo '{}' > npm-shrinkwrap.json
ここまでの結果をコミットしておきます。
$ git add -A; git commit -m 'add initial files.'
実際に動かしてみます。
$ docker-compose up --build
動きましたね。しかしnodejsではエラーが出ています。
動作にはexpressが必要で、元の記事にある通り、expressをインストールします。
$ docker-compose run --rm chat /bin/bash -c 'npm install --save express@4.10.2'
再度動かします。
$ docker-compose up
動きました!
ブラウザでアクセスするとHello Worldが見えます。
ファイル変更の検出
「ローカル側で開発する」ということが前提ですので、index.jsの変更があった場合、Dockerのコンテナ側で再読込をしてもらいます。
そのためにnodemonというパッケージを使っています。試してみましょう。
res.send('Hello World2!!')
ブラウザでアクセスするとたしかに変わっています。
ポイントなど
npm shrinkrwapを使ってバージョンを固定する
インフラ周りを担当していると脆弱性や性能の観点から「基本的に最新版を使え」、という鉄の掟を教え込まれていました。
しかしNode.js周りは-カオス-開発がさかん ですので、バージョンを固定しないと依存関係が動かないことが多々あります。
そのため「確実に動くバージョン」を固定して管理する、ということが重要そうでした。
(このあたりはRubyやPython周りでイヤというほど悩まされた問題です…)
リポジトリに含めるファイル
標準のNode.jsのファイルに加え、Dockerfile
、docker-compose.yml
、package.json
、npm-shrinkwrap.json
を含めると良いようでした。
パッケージの追加などのnpmコマンド
この方法では基本的にnpmコマンドはコンテナ内で実行させます。
node_modulesもコンテナにのみ存在させますので、Dockerホスト側からは動かすことを想定していません。
その為パッケージのインストールなどはdocker-compose runにて動かします。
例
$ docker-compose run chat /bin/bash -c 'npm install --save naname000/hubot-webshot'
$ docker-compose run chat /bin/bash -c 'npm test'
基本的にはこのやりかたでよさそうです。
ただしこのやり方のデメリットとして、どうしてもコンテナ内ですので遅延が発生してしまいます。
またnode_modulesのファイルが無いためにライブラリの依存が解決できません。
その為ローカル側でnpm installなどを行い、node_modulesの実ファイルをDockerホスト側にも置いてしまう、というやり方もアリかなと思いました。
$ npm install --save naname000/hubot-webshot
$ docker-compose run chat /bin/bash -c 'npm install'
まとめ
まだNode.jsになれていませんが、デプロイやそもそものコード管理が非常に大変だなぁ、どうしたら良いんだろうと思っていました。(というかみんなどうしているんでしょうか?)
今回参考にした記事はそういった中で先人のベストプラクティスとして大変ありがたい存在でした。
DockerやNode.jsについて「こんなやりかたやってるよ」とか「ここ間違ってるよ」ということがありましたらぜひともコメントで教えて欲しいです。