インドカレーファンクラブ

パソコン、カメラ

【Docker】Dockerのドキュメントを読んで知らなかったことをメモ_ベストプラクティス編

雰囲気でDocker, docker-composeを使っているので一度ドキュメントを読んでみて怪しかったところをメモ

docs.docker.jp

コピペ+所感という内容なので記事としての価値はないです(メモ書き)

イメージを小さく

適切なベースイメージ

適切なベースイメージで始めましょう。たとえば、 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

知らなかった
ビルドする環境と実行する環境を分けて最終的なイメージのサイズを小さくできる
多分他にもいろいろやれそう

ビルドコンテクスト

https://docs.docker.jp/develop/develop-images/dockerfile_best-practices.html#understand-build-context

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 upgradedist-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キャッシュを消してイメージサイズを小さくしている

公式のDebianUbuntuのイメージは自動でこれやってるから明示的にやる必要はなし

パイプを利用する処理でエラーを検知

RUN wget -O - https://some.site | wc -l > /number

wgetした行数をhoge(ファイル)に吐く

これだと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じゃなくてcurlwget

イメージサイズの問題

こうしておくことで、ファイルを取得し展開した後や、イメージ内の他のレイヤにファイルを加える必要がないのであれば、その後にファイルを削除することができます

消せばイメージのサイズを小さくできるということだと思う(消さないならどっちでもいいのか?レイヤの問題?)

# だめ
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/

自分で書くことはほぼ無いんじゃないかと思うけど、このコマンドを知っておかないと、これが使われてた時に混乱することになりそう