Dockerの開発環境作成メモの2つ目。Dockerfileについてのメモ。
Dockerfile のベストプラクティス
http://docs.docker.jp/engine/articles/dockerfile_best-practice.html
前回ではalpineイメージを作成して、OSのコンテナ展開したり、コンテナ内に入ったりする例をさらっと記載しました。今回は開発環境としてNode.jsを題材としてもう少し自分なりの実践的な使い方をまとめておきます。
Node.jsのようなミドルウェアをコンテナ化する理由としては、自分としてはバージョンの問題だと思ってます。
今動作しているアプリはその当時の最新バージョンだったりしますが、時が経つごとに古くなってきます。新しいバージョンと入れ替えたくなりますが、切り替えることにより動作しなくなるアプリも出てくるので、結局古いまま使用していることがモヤモヤしてしまいます。
ミドルウェアをコンテナ化して、ローカルで管理しているソースやデータを永続データとしてコンテナにマウントして実行できれば、アプリケーション毎にバージョン切り替えが簡単になるはずです。もし新しいバージョンのコンテナだと動作しない場合、対策とるまではそのアプリだけ古いバージョンのまま利用することも簡単になるはずです。
このあたりをまずはゴールとしてどうしたらいいのか検討&メモっていきます。
そもそもミドルウェアのイメージってどうなっているのか?
現時点でのNode.jsの最新安定化バージョンは12.13.1
です。Docker Hubを見ていると、PC版とArm版などがあります。ただ同じバージョンでもstretch
やbuster
やらいろいろあります。これはDebianのバージョン名で、stretchがDebian9(2020年6月まで)、busterがDebian10(2024年?)のことです。
例えば、node : 12.13.1-stretch
、node : 12.13.1-buster
の詳細を見ると以下になります。
# 12.13.1-stretch
ADD file:152359c10cf61d80091bfd19e7e1968a538bebebfa048dca0386e35e1e999730 in /
:
COPY file:238737301d47304174e4d24f4def935b29b3069c03c72ae8de97d94624382fce in /usr/local/bin/
# 12.13.1-buster
ADD file:9b7d9295bf7e8307ba4e81ce20770256b964da70dea966568b3515ad026d0b27 in /
:
COPY file:238737301d47304174e4d24f4def935b29b3069c03c72ae8de97d94624382fce in /usr/local/bin/
/usr/local/bin
にコピーしている箇所は同じで、おそらくNode.js関連のモジュール群だと思います。ADD
している箇所のIDが異なりここがDebianの最小構成のモジュール群であり、バージョンが違うので、IDが異なっているんだと思います。
確認するために今度は、debian
のイメージの詳細を見てみます。2行しかないです。
# stretch
ADD file:152359c10cf61d80091bfd19e7e1968a538bebebfa048dca0386e35e1e999730 in /
CMD ["bash"]
# buster
ADD file:9b7d9295bf7e8307ba4e81ce20770256b964da70dea966568b3515ad026d0b27 in /
CMD ["bash"]
ほかにもslim版を見てみると、やはりIDは一致します。つまり、Node.jsのイメージの中身はDebianの最小限のモジュールとNode.jsのモジュールで構成されていることがわかります。これらのイメージからコンテナに展開すると、コンテナ内にはDebianとNode.jsだけの最小環境が展開されることがわかります。
ちなみにslim版と通常版のイメージの違いは、内部でapt-get update
をやるかやらないかの違いだと思います。
Node.js用開発環境のイメージを作成する
> cd node-dev
> ls
.dockerignore Dockerfile test.js
.dockerignore
はDockerfileからイメージをビルドするときに余計なフォルダやファイルが混入しないようにするための定義ファイルです。.gitignore
と同じようなものです。
#.dockerignore
node_modules
npm-debug.log
test.js
はまずは実行すると、標準出力に1行出力するだけのソースとします。
console.log("hello node-dev");
Dockerfile
はまずは以下とします。node:12.13.1-alpine
のイメージから作成して、ソースtest.js
を/src
フォルダを作成してコピーするだけです。
FROM node:12.13.1-alpine
WORKDIR /src
COPY ./test.js ./
> docker build -t node-dev:1.0 .
:
Sending build context to Docker daemon 8.704kB
Step 1/3 : FROM node:12.13.1-alpine
---> 3fb8a14691d9
Step 2/3 : WORKDIR /src
---> Running in d3bc9d74160f
Removing intermediate container d3bc9d74160f
---> be1f3e9150cd
Step 3/3 : COPY ./test.js ./
---> ca7e16c4ee23
Successfully built ca7e16c4ee23
Successfully tagged node-dev:1.0
イメージを確認
> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
node-dev 1.0 ca7e16c4ee23 23 seconds ago 80.2MB
コンテナ展開を実行すると、最後にコンテナ内部でnode
が実行されることがわかる。
> docker run -it --name dev1 node-dev:1.0
Welcome to Node.js v12.13.1.
Type ".help" for more information.
>
いったん終了してコンテナを確認すると、コンテナが作成されて停止していることがわかる。docker start dev1
で起動することはもちろんできる。
> docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
83b9242bd9e0 node-dev:1.0 "docker-entrypoint.s…" About a minute ago Exited (0) 3 seconds ago dev1
docker run
を実行するときに、後ろにnodeのコマンドを指定すると、node {ソースファイル}
を実行するコンテナが作成される。(コンテナはプログラムを実行し終わると停止する)
> docker run -it --name dev2 node-dev:1.0 test.js
hello node-dev
> docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
71c65b658be1 node-dev:1.0 "docker-entrypoint.s…" 11 seconds ago Exited (0) 10 seconds ago dev2
docker start
でこのコンテナを再度開始すると、node test.js
が再度実行されることになる。(バックグラウンドで)
> docker start dev2
> docker logs dev2
hello node-dev <-- docker run
hello node-dev <-- docker start
最後にnode
を実行するのではなく、シェルを開始したい場合は、使用するシェルを指定してCMD
を上書きしてしまえばいい。また、コンテナを使い捨てしたい場合は、--rm
を付けるとプロセス終了時に停止したコンテナが自動削除される。
> docker run -it --rm node-dev:1.0 /bin/ash
/src # node --version
v12.13.1
/src # exit
> docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
(停止したコンテナすら存在しない)
継続して使い続けるのであれば、--rm
を付与せずにコンテナ作成する。-d
も付与してバックグラウンドで実行しても良い。
> docker run -it -d --name dev4 node-dev:1.0 /bin/ash
c8271bae35b9b3a9c10741103ff3d930e9e4c7c7d4ec2cf397ce292d1f718ce4
> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c8271bae35b9 node-dev:1.0 "docker-entrypoint.s…" 5 seconds ago Up 5 seconds dev4
コンテナ内に入って、nodeプログラムを実行する。コンテナから外に出て、プロセスを確認すると、コンテナが起動したままになる。(バックグラウンド実行しているので)
> docker exec -it dev4
/src # node test.js
hello node-dev
/src # exit
> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c8271bae35b9 node-dev:1.0 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes dev4
コンテナを停止する場合はdocker stop
、開始してまたプログラムを実行したければdocker start
する
> docker stop dev4
> docker start dev4
> docker exec -it dev4 /bin/ash
次にDockerfileを以下のように、最後にCMD
で、このプログラムを実行するところまで記述するとどうなるのか?
FROM node:12.13.1-alpine
WORKDIR /src
COPY ./test.js ./
CMD [ "node", "test.js" ]
> docker build -t node-dev:1.1 .
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
node-dev 1.1 fdfbeb24b0f7 5 seconds ago 80.2MB
node-dev 1.0 ca7e16c4ee23 11 minutes ago 80.2MB
test.js
が実行されるコンテナが作成される。実行されてると停止しているのがわかる。
> docker run -it --name dev5 node-dev:1.1
hello node-dev
> docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4e8cb429b4dd node-dev:1.1 "docker-entrypoint.s…" 7 seconds ago Exited (0) 6 seconds ago dev5
開始するとまたプログラムは実行されるがすぐに停止するので、中に入れないことは留意しておく。
> docker start dev5
dev5
> docker logs dev5
hello node-dev
hello node-dev
プログラムを開始するだけのコンテナであれば、DockerfileのCMD
にプログラムを開始する指定をすればいいが、開発環境を提供するコンテナであれば最後はシェルを起動しておくのが良さそう。