2020-06-11

東京都 新型コロナウイルス感染症対策サイトを読む その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.jsonyarn.lock だけをコピーして yarn install してるのはなぜ?

先に package.jsonyarn.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.jsonyarn.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に関しては学ばなければならないことが多そうだ