東京都 新型コロナウイルス感染症対策サイトを読む その4
続きをやっていく
ちょっとDockerの知識を今一度再確認してまとめる必要があると思う出来事があったので、今回はDocker周りを見ていこうと思う
まずDockerfileとは
イメージ作成の手順が書かれた設定ファイル
Dockerクライアントのbuildサブコマンドを実行すると、対象ディレクトリにあるDockerfileに書かれている命令に従ってイメージが作成される
Dockerfileは命令行、コメント行、空行から構成されている
コロナ対策サイトのDockerfile
まずはDockerfileから見ていく
↓がコロナ対策サイトのDockerfile
FROM node:10.19-alpine
ENV PROJECT_ROOTDIR /app/
WORKDIR $PROJECT_ROOTDIR
COPY package.json yarn.lock $PROJECT_ROOTDIR
RUN yarn install
COPY . $PROJECT_ROOTDIR
EXPOSE 3000
ENV HOST 0.0.0.0
CMD ["yarn", "dev"]
とてもシンプルでよい
1行1行みていく
FROM node:10.19-alpine
FROM
は作業のベースとなるイメージが指定されている
FROM
は最低限書かれていなければならない命令で、最初に記述されるべき命令でもある
docker hubを参照していて、これを使っている
リポジトリ名(:タグ名)
というフォーマットになっていて、 node
リポジトリの 10.19-alpine
というタグであることがわかる
見たまんまnode.jsのバージョン10.19を使えるイメージをベースに構築しているということ
なお、 FROM scratch
という命令を書くと、ベースとなるイメージを使わず、全くのゼロからイメージを作っていくこともできる
ENV PROJECT_ROOTDIR /app/
ENV HOST 0.0.0.0
ENV
はイメージ作成時やコンテナ実行時の環境変数を設定している
ここで設定した環境変数は、以降のDockerfileの処理時に有効になる
また、作成したイメージから起動したコンテナでも有効になる
WORKDIR $PROJECT_ROOTDIR
WORKDIR
はイメージ作成時やコンテナ実行時のカレントディレクトリを指定している
コンテナでコマンドを実行する際のカレントディレクトリは /app/
となる
COPY package.json yarn.lock $PROJECT_ROOTDIR
COPY . $PROJECT_ROOTDIR
COPY
はホスト側からイメージ内の指定ディレクトリにファイルやディレクトリをコピーしている
コピーするファイルはDockerfileがあるディレクトリを起点とした相対パスで指定する
ファイルの代わりにディレクトリを指定すると、指定したディレクトリ内のファイルやサブディレクトリがまとめてコピーされる
また、*
などのワイルドカードを使って、複数のファイルをまとめてコピーすることもできる
コピー先のディレクトリは絶対パスまたは相対パスで指定する
相対パスの場合は WORKDIR
などで指定したカレントディレクトリを起点として解釈される
指定したディレクトリが存在しない場合は、その親のディレクトリも含めて自動的に作成される
コピーしたファイルなどの所有者とグループは管理者(root)になる(ユーザーIDとグループIDが共に0となる)
アクセス権限は元ファイルのままコピーされる
ちなみに、似た命令で ADD
というものがあるが、COPYの機能に加えて
- URLを指定してリモートにあるファイルもコピーできる
- コピーするファイルが圧縮(tarなど)されたファイルの場合、イメージ内で自動的に展開する
という2つの機能が有効になっているという点が異なる
なお、コピー先指定の末尾の /
には注意が必要で
コピー先指定の末尾に /
をつけるとディレクトリとして解釈され、 /
をつけないとファイル名として解釈される
ここではコピー先が /app/
なので、appディレクトリへコピーされていることがわかる
(これがもし /app
だった場合は、 package.json
などが /app
ファイルとしてコピーされる)
コピーするのがディレクトリの場合、コピー先指定の末尾に /
をつけると、コピー元ディレクトリがコピー先ディレクトリのサブディレクトリとしてコピーされる
/
がついていない場合は、コピー元ディレクトリのファイルやサブディレクトリだけ(中身だけ)がコピー先ディレクトリにコピーされる
RUN yarn install
RUN
は作業用コンテナ内で実行するコマンドを指定している
CMD
コマンドのようにブラケット([])を使った書き方もできる
EXPOSE 3000
EXPOSE
はコンテナが通信を待ち受ける通信ポート番号が指定されている
TCPとUDPを指定できるが、特に指定がないのでTCPが使われている
CMD ["yarn", "dev"]
CMD
はコンテナ起動時に最初に実行するコマンドを指定している
ここでの指定のように、配列っぽく [ ]
で指定されている場合、シェルを使わずに直接コマンドが実行される
なのでシェルが存在しないイメージでもコマンドを実行することが可能
Dockerfileを見てみて
- 先に
package.json
とyarn.lock
だけをコピーしてyarn install
してるのはなぜ?
先に package.json
と yarn.lock
だけをコピーして yarn install
してるのは、キャッシュを使ってビルド速度を高めるため
もし↓のように全コピーしてから yarn install
を実行させると、毎回パッケージのダウンロードが走ってしまう(参考)
FROM node:10.19-alpine
ENV PROJECT_ROOTDIR /app/
WORKDIR $PROJECT_ROOTDIR
COPY . $PROJECT_ROOTDIR
RUN yarn install
EXPOSE 3000
ENV HOST 0.0.0.0
CMD ["yarn", "dev"]
でもなんで全コピーだとダメで、先に package.json
と yarn.lock
だけを転送した場合はOKなんだろう
ちょっとハッキリと理解できていない(誰か教えて)
docker-compose.yml
でホスト側カレントディレクトリとコンテナ側のappディレクトリをマウントしているにも関わらず、DockerfileでファイルのCOPYをしているのはなぜ?
Dockerfile単体での利用を想定しているため(参考)
素のdockerでもdocker-composeでも起動できるようになっている
Docker-compose
続いてdocker-composeを見ていく
docker-composeは複数のコンテナを組み合わせて1つのアプリケーションとして構成を定義するファイル
↓がコロナ対策サイトのdocker-compose.yml
コロナ対策サイトでは1つのコンテナしか立ち上げないので、docker-composeの利点は分かり辛いかもしれない
version: "3"
services:
app:
container_name: covid19
build:
context: .
dockerfile: Dockerfile
tty: true
ports:
- 3000:3000
volumes:
- .:/app
- node_modules:/app/node_modules
volumes:
node_modules: {}
これも1行1行見ていく
version: "3"
docker-composeは当然dockerを使うので、バージョン〇〇のdocker-composeは、どのバージョンまでのdockerのリリースをサポートしているのか?というのが、このバージョン指定でわかるらしい
具体的にはここに対比表がある
バージョン3なので、Docker engine1.13.0のリリースまでサポートしているということ?
それとも1.13.0以上じゃないと動かないってこと?
services
docker-composeで動かすコンテナを指定している
ここでは app
コンテナしか存在しないが、複数のコンテナを起動する場合はここに必要な分だけ設定を追加していく
container_name: covid19
そのまんまコンテナに名前をつけられる
docker-compose ps
とかで一覧になったときにわかりやすくなる
指定しなかった場合 covid19_app_1
という名前になる
build/context/dockerfile
コロナ対策サイトでは、独自にDockerfileを書いてそれを使っているので
context
でDockerfileが置いてあるディレクトリを指定し、dockerfile
でDockerfileの名前を指定している
コロナ対策サイトのディレクトリ構成/ファイル名の場合、多分 context
とか細かく指定しなくても大丈夫なので
単に build: .
だけでOKだと思われるのでissueを立ててコントリビュートしてきた
tty: true
どうやらこいつをtrueにすることで、docker-composeを起動しっぱなしにすることができるらしい
でも最終的に yarn dev
が実行されるので、指定しなくてもそもそも起動しっぱなしになる気がする
多分不要
ports
コンテナが公開するポートを指定する
ホストのポート番号:コンテナのポート番号
か、コンテナのポート番号だけを指定する2つのパターンがある
ここではホストとコンテナの3000番ポートがマッピングされている
また、ホストマシンへはポートを公開せず、リンク機能によって連携するコンテナ間でのみポートを公開する必要がある場合は expose
で指定する
volumes
volumeとは、コンテナのライフサイクルが終了した後でもデータを保管しておけるデータ領域のことで、明示的に破棄されない限り、volumeの中のデータは保持される(永続化される)
特定のコンテナ専用のvolumeだけでなく、複数のコンテナ間から参照できるvolumeも作成できる
また、ホスト側のディレクトリをvolumeとしてコンテナ内にマウントできる
app
コンテナでは
- ホスト側のカレントディレクトリ(docker-compose.ymlのあるディレクトリ)を、コンテナの
/app
にマウント - トップレベルで定義した
node_modules
volumeをコンテナの/app/node_modules
にマウント
している
コンテナを削除するたびに node_modules
が消えると、コンテナ立ち上げる時にまたダウンロードが走って無駄に時間消費するからマウントしている
Rails開発していると、他にDBとかbundlerとかでvolumeが作られているのをよく見かける
とりあえずざっくり確認してきたけど、まだまだDockerに関しては学ばなければならないことが多そうだ