初心者から中級者のためのDocker理解

2021-09-12
2021-09-12

Docker をなんとなく使っていたけど、改めて勉強したいと思い自分の気になったことを中心に調べてみました。 間違い等あるかもしれないのでご注意ください。

Docker を使用するまでの流れ

ここでは、Dockerfile の記述から実際に使うまでどういう流れになっているのかを俯瞰します。 Docker のインストール方法は公式サイトに委ねます。

まず、Docker は基本的に build→run という手順で使います。

  1. docker build
    • Dockerfile と Context からコンテナイメージを作る
  2. docker run
    • コンテナイメージを実際に実行する

その他開発でよく使うのはdocker pushです。

Docker は作成したイメージを Docker Hub に代表されるレジストリに保管することができます。 その際に使うのがdocker pushです。

逆にレジストリからイメージを取得するにはdocker pullで可能です。

以下 2 つはに関連する言葉をメモしたものです。

  • ビルドコンテキストとは
    • ビルド時にカレントディレクトリもしくは指定されたフォルダ配下にあるフォルダやファイルがビルドコンテキストとして Docker デーモンに送られます。
  • .dockerignore
    • ビルドコンテキストに含めたくないファイルやフォルダを指定できます。これによりビルド時間の短縮が期待できます。書き方は.gitignore とほぼ一緒です。

Docker のレイヤについて

Docker でいうレイヤとは Dockerfile に記述される命令(簡単にいうと行)のことを指します。 Docker イメージはレイヤの集まりとも言えます。

そして肝心なのは、各レイヤは直前のレイヤの変更差分だけを保持します。

以下は Hello World を出力するだけの Dockerfile です。

# 親イメージ(ベース)
FROM python:3.8.12-slim

# 作業用ディレクトリを指定
WORKDIR /usr/src/app

# ホスト上のファイルを現在の場所にコピー
COPY hello_world.py .

# コンテナ内でコマンドを実行
CMD [ "python", "hello_world.py" ]

これをビルドしてコンテナイメージを作成します。 以下のコマンドで build してみます。(Dockerfile があるフォルダで実行した場合)

docker build . -t hello

ターミナル の出力を見ると各レイヤごとに 処理されているのがみて取れます。

例えば、以下のレイヤを CMD 行の前に増やしてみます。

COPY hello_world.py .

再度ビルドコマンドを実行すると以下のような出力が得られます。

Step 1/5 : FROM python:3.8.12-slim
 ---> 0b4039cc52c9
Step 2/5 : WORKDIR /usr/src/app
 ---> Using cache
 ---> 3941c721c3f5
Step 3/5 : COPY hello_world.py .
 ---> Using cache
 ---> 757901c00e9f
Step 4/5 : COPY add_layer.py .
 ---> 5ab5c6c6c477
Step 5/5 : CMD [ "python", "hello_world.py" ]
 ---> Running in dbde703d1433
Removing intermediate container dbde703d1433
 ---> f3c7f4146400
Successfully built f3c7f4146400
Successfully tagged hello:v2

追加行より以前のレイヤではキャッシュが使われており、新しい行がレイヤとして追加されたのがわかります。 試しに追加行をCOPY hello_world.py .より前にすると、以下のようになります。

Step 1/5 : FROM python:3.8.12-slim
 ---> 0b4039cc52c9
Step 2/5 : WORKDIR /usr/src/app
 ---> Using cache
 ---> 3941c721c3f5
Step 3/5 : COPY add_layer.py .
 ---> 6a85eb4aef06
Step 4/5 : COPY hello_world.py .
 ---> 2c4f11da0ed9
Step 5/5 : CMD [ "python", "hello_world.py" ]
 ---> Running in c0b1523aa464
Removing intermediate container c0b1523aa464
 ---> 42c4156c2564
Successfully built 42c4156c2564
Successfully tagged hello:v3

以前とは異なり、COPY hello_world.py .の部分でキャッシュが使われていません。 このように前のレイヤで変更があった場合はそれ以降のレイヤでは仮に処理が同じでもキャッシュが利用されません。

つまり、開発等でコンテナに取り込むファイルが頻繁に変更される場合は後ろのレイヤに組み込むことがビルド時間の短縮のためには重要です。

Docker Hub を使ってみる

続いて先ほどのイメージを利用して Docker Hub にイメージを push してみます。

Docker Hub の登録などの事前準備は公式サイトを参考にしてください。

イメージの push は以下の手順で行います。

  1. push したい Docker イメージの作成
  2. 必要があればイメージ名を変更
    • push するにはイメージ名が<Docker Hubのユーザ名>/イメージ名とする必要がある
  3. docker push <Docker Hubのユーザ名>/イメージ名で push

上記ではタグをつけてませんが、タグをつけることも可能です。

Buildkit とは

Buildkit とは Docker18.09 以降に使えるようになった機能です。

基本的にはイメージのビルド時間を短縮してくれる機能です。

具体的にはビルド処理の並列化やビルドコンテキストの差分転送の最適化などが行われています。

使ってみると劇的にビルドが早くなるので使わないてはないです。

特にビルドコンテキストの転送が信じられないくらい早くなります。 学習済みモデルなどの重たいファイルをビルドコンテキストに含めているとビルドに毎回時間がかかってイライラしましたが、 Buildkit の利用でその問題が解消されました。

使うにはビルド時のコマンドの前にDOCKER_BUILDKIT=1をつけるだけです。

DOCKER_BUILDKIT=1 docker build -f docker/Dockerfile . -t django:0.1

マルチステージビルド

Docker ではレイヤが増えるほど、イメージのサイズが多くなり扱いづらくなります。

従来は、Dockerfile のRUN句をなるべく一つにまとめてレイヤを少なくする工夫が行われていました。

今はコンテナイメージのサイズを抑える方法としてマルチステージビルドがあります。

こちらは公式サイトでも Docker 開発のベストプラクティスとして紹介されています。

例として Django を動かす Dockerfile を作ってみました。

FROM python:3.8-slim

ENV PYTHONUNBUFFERED 1
ENV PYTHONPATH /workspace/

WORKDIR /workspace/

RUN pip install poetry

COPY init_django.sh pyproject.toml poetry.lock /workspace/

RUN poetry config virtualenvs.create false \
    && poetry install

COPY backend /workspace/

CMD [ "sh", "init_django.sh"]

今回使うプロジェクトは手元にあった機械学習系アプリなのでかなりサイズが大きいです。

マルチステージ前のサイズが 3.18GB です。

これを以下のようにマルチステージに書き換えてみます。

FROM python:3.8-slim as builder

WORKDIR /workspace/

RUN pip install poetry

COPY pyproject.toml poetry.lock /workspace/

RUN poetry config virtualenvs.create false \
    && poetry install

FROM python:3.8-slim

ENV PYTHONUNBUFFERED 1
ENV PYTHONPATH /workspace/

WORKDIR /workspace/

COPY --from=builder /usr/local/lib/python3.8/site-packages /usr/local/lib/python3.8/site-packages

COPY backend /workspace/
COPY init_django.sh /workspace/

CMD [ "sh", "init_django.sh"]

ビルドして image サイズを見てみると 2.25GB!! 1GB ほど小さくなりました!

Docker でよく使うコマンド

操作コマンド備考
起動中のコンテナ確認docker psなし
コンテナイメージビルドdocker build -t <イメージ名>:0.1-t名前:タグを指定できる
コンテナ実行docker run --name コンテナ名 <イメージ名>--nameオプションでコンテナに名前をつけられます
イメージ削除docker rmi <image id>なし
使ってない Docker のイメージ、コンテナ、ネットワークを削除docker system pruneボリュームを削除したい場合は --volumesフラグをつける