【Docker】Dockerのドキュメントを読んで知らなかったことをメモ_ベストプラクティス編
雰囲気でDocker, docker-composeを使っているので一度ドキュメントを読んでみて怪しかったところをメモ
コピペ+所感という内容なので記事としての価値はないです(メモ書き)
イメージを小さく
適切なベースイメージ
適切なベースイメージで始めましょう。たとえば、 JDK が必要なら、一般的な ubuntu イメージに openjdk をインストールするのではなく、公式 openjdk イメージをベースにするのを検討します。
これはわかる。超軽量なイメージをつくる!みたいな記事はたまに見るけど、そっちの方向に頑張ることはあまりしそうもない
レイヤをへらす
RUN命令をまとめることでレイヤを減らす
みんなオシャレでやってるのかと思ってた
RUN apt-get -y update RUN apt-get install -y python
↓
RUN apt-get -y update && apt-get install -y python
因みにレイヤを増やすのはRUN, COPY, ADD命令だけ
命令における複数行にわたす引数の並びはabc順
https://docs.docker.jp/develop/develop-images/dockerfile_best-practices.html#id13
abc順が作法的に間違えにくく良いらしい。考えたことなかった
パッケージの重複指定の危険が減るとか言われてみれば確かに
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion
マルチステージ・ビルド
https://docs.docker.jp/develop/develop-images/multistage-build.html
知らなかった
ビルドする環境と実行する環境を分けて最終的なイメージのサイズを小さくできる
多分他にもいろいろやれそう
ビルドコンテクスト
docker build
の -f
オプションでdockerfileのある場所を指定するんだけど、
そのdockerfileがあるところ、現在作業しているディレクトリをビルドコンテクストと呼ぶらしい
以下のように指定すればdockerfileと資材のディレクトリを分けられる
mkdir -p dockerfiles context mv Dockerfile dockerfiles && mv hello context docker build context -f dockerfiles/Dockerfile
stdinを通してDockerfileをパイプ
普通のやつ
https://docs.docker.jp/develop/develop-images/dockerfile_best-practices.html#stdin-dockerfile
以下は同じ
echo -e 'FROM busybox\nRUN echo "hello world"' | docker build -
docker build -<<EOF FROM busybox RUN echo "hello world" EOF
docker build -
のハイフンでDockerfileのPATHに代わりその内容を渡すような感じ
あんまり使うことはなさそう
gitリポジトリの資材を使う
-f
でgitを指定しつつ、そこにハイフンを付け足して -f-
とすることで、
gitをcloneしてその資材をstdinで流し込むDockerfile(の構文)の中で利用できる
docker build -t myimage:latest -f- https://github.com/docker-library/hello-world.git <<EOF FROM busybox COPY hello.c . EOF
こんなややこしいことをする機会があるのかわからない
git利用しない方含め、1枚のshellscriptファイルで色々完結させたい時には使うかもしれない
.dockerignore
https://docs.docker.jp/develop/develop-images/dockerfile_best-practices.html#dockerignore
あんまり書いたことないかも
node_modulesあたりは除外するんだろう
ビルドキャッシュ
https://docs.docker.jp/develop/develop-images/dockerfile_best-practices.html#leverage-build-cache
あんま考えてなかった
キャッシュを使いたくない時は
docker build
に --no-cache=true
を指定
ADD, COPYコマンドはファイルの内容についてチェックサムを計算
それ以外の場合はコマンド文字列そのものがキャッシュの一致判断に利用される(ファイルは見ない)
ということを思うとcurlだのapt-getして取得してくるものが古いとか、そういう時に考えることになりそう
Dockerfile
LABEL
イメージには複数ラベルが設定でき、昔はLABELコマンドで余分なレイヤの追加を避けるために1行で設定していたらしいけど、
いまは1行である必要はない
でも1行で書くのはまだサポートされてるし見栄え的にもそっちの方がよさそう
LABEL vendor=ACME\ Incorporated \ com.example.is-beta= \ com.example.is-production="" \ com.example.version="0.0.1-beta" \ com.example.release-date="2015-02-12"
RUN
apt-get自体のupgradeはダメ
RUN apt-get upgrade
やdist-upgrade
の実行は避けてください。ベース・イメージに含まれる重要パッケージは、権限が与えられていないコンテナ内ではほとんど更新できないからです。 (中略) foo というパッケージを更新する必要があれば、 apt-get install -y foo を利用してください。
知らなかった!
apt-get update と apt-get install は同一RUNで実行すること
RUN apt-get update と apt-get install は、同一の RUN コマンド内にて同時実行するようにしてください。たとえば以下のようにします。
RUN apt-get update && apt-get install -y \
package-bar \
package-baz \
package-foo
知らなかった!
キャッシュの問題。詳しくは直接ドキュメント見たほうがいい
https://docs.docker.jp/develop/develop-images/dockerfile_best-practices.html#apt-get
aptキャッシュを消す
RUN apt-get update && apt-get install -y \
aufs-tools \
automake \
build-essential \
curl \
dpkg-sig \
libcap-dev \
libsqlite3-dev \
mercurial \
reprepro \
ruby1.9.1 \
ruby1.9.1-dev \
s3cmd=1.1.* \
&& rm -rf /var/lib/apt/lists/*
最後にaptキャッシュを消してイメージサイズを小さくしている
公式のDebianとUbuntuのイメージは自動でこれやってるから明示的にやる必要はなし
パイプを利用する処理でエラーを検知
RUN wget -O - https://some.site | wc -l > /number
これだとwgetが失敗してもwc -l
が成功すればコマンドが失敗とはならずビルドが通ってしまう
パイプ内のどの段階でもエラー即コマンド失敗にするためには set -o pipefail &&
を使う
(シェルが-o pipefail
オプションに対応してないとダメだけど)
RUN set -o pipefail && wget -O - https://some.site | wc -l > /number
CMD
CMDはイメージ内に含まれるソフトウェアを実行するために用いるもの
CMD ["実行モジュール名", "引数1", "引数2" …]
の形式
CMD ["apache2","-DFOREGROUND"]
みたいな
RUNとの使い分けをあんま考えてなかった!
ENV
環境変数の設定にもPATHの設定にも使うというのはよくある話
ENV PATH /usr/local/nginx/bin:$PATH ENV PASSWORD="xxx"
アプリケーションのバージョン固定にも使うと便利とのこと
ENV PG_MAJOR 9.3 ENV PG_VERSION 9.3.4 RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && … ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
ENVによって中間レイヤが作成されるため、環境変数をセットしたのと別の行でunsetしても値は保持されてしまう
それに対応するには~...というのがドキュメントに書いてあるけどあんまり使う機会はなさそう
迂闊にunsetしてるコードを見たら思い出したい
https://docs.docker.jp/develop/develop-images/dockerfile_best-practices.html#apt-get
ADDとCOPY
使い分け
- COPY:ローカルからコンテナにコピーするだけ
- ADD:ローカルでのtar展開やリモートURLサポートもついてる!
ややこしいからCOPYを優先して使う
ADDを使うのはローカルのtarを展開してイメージに書き込むときだけ
ADD rootfs.tar.xz /
複数ステップで異なるファイルをコピー
まとめずに個別にコピーすると、個々のステップのキャッシュのビルドが最低限に抑えられる
# こっちより COPY . /tmp/ RUN pip install /tmp/requirements.txt # こっちのほうが望ましい COPY requirements.txt /tmp/ RUN pip install /tmp/requirements.txt COPY . /tmp/
上の例だとローカルのコンテクストの中で差分があったら、COPYコマンドのキャッシュが無効化される
下の例ならrequirements.txtに差分があればそこのCOPYだけキャッシュ無効化、それ以外のコンテクストに差分があればそっちCOPYだけキャッシュ無効化
ということになる(はず)
リモートURLからパッケージを取得する場合ADDじゃなくてcurlやwget
イメージサイズの問題
こうしておくことで、ファイルを取得し展開した後や、イメージ内の他のレイヤにファイルを加える必要がないのであれば、その後にファイルを削除することができます
消せばイメージのサイズを小さくできるということだと思う(消さないならどっちでもいいのか?レイヤの問題?)
# だめ ADD http://example.com/big.tar.xz /usr/src/things/ RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things RUN make -C /usr/src/things all # よい RUN mkdir -p /usr/src/things \ && curl -SL http://example.com/big.tar.xz | tar -xJC /usr/src/things \ && make -C /usr/src/things all
USER
サービスが特権ユーザでなくても実行できる場合は、 USER を用いて非 root ユーザに変更します。
ユーザとグループを生成するところから始めてください。Dockerfile 内にてたとえば
RUN groupadd -r postgres && useradd -r -g postgres postgres
こうしてユーザとグループを作ったあとで、
USER <user>[:<group>]
or USER <UID>[:<GID>]
を書いておくことで、それから先のRUN, CMD, ENTRYPOINTのコマンドを指定したユーザで実行する
ついでにsudoのインストールと利用は避けよう!とのこと
WORKDIR
絶対パスで書こう!
RUN cd ... && do-somethig
みたいなことをせずWORKDIRを使おう!
さぼりがち
ONBUILD
コマンド自体を知らなかった
親側のイメージに処理を仕込んでおくと、
それをFROMとして作られる子のイメージで処理を実行させられる感じ
ここの例がわかりやすい
https://deeeet.com/writing/2014/03/21/docker-onbuild/
自分で書くことはほぼ無いんじゃないかと思うけど、このコマンドを知っておかないと、これが使われてた時に混乱することになりそう