前の記事に引き続き、今回はコンテナでNode.jsのアプリケーションを動かすことに挑戦してみました。
#実装
実装は以下の手順で行いました。
- Node.jsアプリの作成
- Dockerfileの作成
- Dockerfileのビルド
- コンテナの起動
- ブラウザとアプリの接続
##Node.jsアプリの作成
localhost:8080
を開いたときにHi there
がブラウザに表示されることを目指します。
package.json
とindex.js
の中身は以下のようにします。
{
"dependencies": {
"express": "*"
},
"scripts": {
"start": "node index.js"
}
}
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hi there');
});
app.listen(8080, () => {
console.log('Listening on port 8080');
});
##Dockerfileの作成
Dockerイメージをビルドしたとき、ファイルシステムにアプリに必要なプログラム(dependencies)をインストールし、メタ情報にアプリの起動コマンド(npm start
)を導入することを目指します。
前の記事でredisをコンテナで立ち上げたときのように、試しにalpineのベースイメージを使ってDockerfileを作成してみます。
FROM alpine
RUN npm install
CMD ["npm", "start"]
これをビルドすると、RUN npm install
の部分でこけます。
なぜかというと、alpineイメージは軽量であるがゆえに、node
もnpm
もプリインストールされていないからです。
そのため、ベースイメージにはnode
やnpm
がプリインストールされているnodeイメージなどを選択する必要があります。
ただ、nodeイメージにはnpmやnode以外にもテキストエディタツールなどの余計なプログラムもプリインストールされているので、軽量なalpineにnpm
とnode
がプリインストールされたnode:alpine
やnode:slim
などの最適化されたベースイメージを使うのがベストプラクティスです。
ベースイメージをnode:alpine
に変えて再度ビルドしてみます。
FROM node:alpine
RUN npm install
CMD ["npm", "start"]
このビルドもこけます。
なぜなら、node:alpine
のファイルシステムにpackage.json
が含まれておらず、インストールするプログラムが不明だからです。
もしたまたまハードドライブにpackage.json
が含まれていたとしても、コンテナに割り当てられたハードドライブのセグメントにファイルがあるわけではないので、npm install
は実行できません。
ここでDockerfile内に、RUN
の前にファイルコピーするための記述COPY
を加えます。
COPY ./ ./
とすることで、ローカルのカレントディレクトリのファイル(package.json, index.js)をコンテナ内のファイルシステムにコピーすることができます。
FROM node:alpine
WORKDIR /usr/app
COPY ./ ./
RUN npm install
CMD ["npm", "start"]
*WORKDIR /usr/app
という記述がありますが、これはファイルのコピー先(ワーキングディレクトリ)を指定しています。詳細は後述します。
これでようやくビルドが成功します。
(base) [9:42:15] → docker build -t suzuki0430/simpleweb:latest . ~/Programs/docker/simpleweb
Sending build context to Docker daemon 4.096kB
Step 1/5 : FROM node:alpine
---> b3dce3e0529f
Step 2/5 : WORKDIR /usr/app
---> Using cache
---> d995d6f0fd23
Step 3/5 : COPY ./ ./
---> 8b999fe7dc6e
Step 4/5 : RUN npm install
---> Running in 52b539c857e3
added 50 packages, and audited 50 packages in 3s
found 0 vulnerabilities
npm notice
npm notice New minor version of npm available! 7.0.8 -> 7.6.1
npm notice Changelog: <https://github.com/npm/cli/releases/tag/v7.6.1>
npm notice Run `npm install -g npm@7.6.1` to update!
npm notice
Removing intermediate container 52b539c857e3
---> 5f7e4d45df44
Step 5/5 : CMD ["npm", "start"]
---> Running in e8085a051d01
Removing intermediate container e8085a051d01
---> e76e6a91dc2b
Successfully built e76e6a91dc2b
Successfully tagged suzuki0430/simpleweb:latest
コンテナの作成・起動を行います。
(base) [9:43:11] → docker run suzuki0430/simpleweb ~/Programs/docker/simpleweb
> start
> node index.js
Listening on port 8080
コンテナの起動も正常にできたみたいなのでこれで成功!といきたいところなのですが、ブラウザからアプリが開けません...えっ?
###ポートマッピング
アプリが開けなかったのは、コンテナのポート8080とローカルホストのポート8080が接続されていなかったためです。
ブラウザからlocalhost:8080
にアクセスする際、localhost(ローカルPC)のポートに接続しようとします。
ただ、今回はコンテナの中でNode.jsのアプリケーションを実行しているため、コンテナのポートと接続する必要があります。
ローカルPCとコンテナのポートを紐付けることをポートマッピングと呼びます。
ポートマッピングを実行するためにはdocker run -p 8080:8080 [image id]
コマンドを実行します。
コンテナの作成・起動とポートマッピングを同時に実行してくれます。
###キャッシュの利用
アプリは開けるようになったのですが、気になる部分が1つあります。
それは今のDockerfileの内容だと、ファイルを更新してイメージをビルドする際にnpm install
が必ず実行されてしまうことです。
最終的にビルドは完了するので問題がないといったらないのですが、ビルドの待ち時間はできるだけ避けたいものです...
そこでキャッシュの利用を考えます。
DockerfileをRUN npm install
の前にpackage.json
のみをコピーするように書き換えます。
FROM node:alpine
WORKDIR /usr/app
COPY ./package.json ./
RUN npm install
COPY ./ ./
CMD ["npm", "start"]
すると、package.json
の中身が同じであれば、RUN npm install
までの手順をキャッシュによってスキップすることができるようになります。
Step 3/6 : COPY ./package.json ./
---> Using cache
---> 1e7f34ded131
Step 4/6 : RUN npm install
---> Using cache
---> 8f0ae8a2f7f6
###ワーキングディレクトリの指定
Dockerfile内にWORKDIR /usr/app
という記述がありますが、ここではRUN
, CMD
, ENTRYPOINT
, COPY
, ADD
, docker run
, exec
で実行するコンテナプロセスのワーキングディレクトリを指定しています。
WORKDIR /
と指定することももちろんできるのですが、ルートディレクトリにはいろんなファイルやフォルダが含まれているので、ファイル名が同じだったりすると干渉が起こってしまいます。そのため、コンテナプロセス用のディレクトリを指定してあげる必要があります。
コンテナ起動時にシェルを開くと、確かにワーキングディレクトリが/usr/app
となっています。
(base) [11:06:07] → docker run -it suzuki0430/simpleweb sh ~/Programs/docker/simpleweb
/usr/app #
#おわりに
Node.jsをコンテナで動かすことができるようになりました。次はDocker-composeについて学習します。
#参考資料