Edited at

[Docker] 初心者が知っておくと便利かもしれない18の知識

初心者の状態から、既存サービスをDocker化するまでに学んだ知識のメモです。

※ 随時更新していきます

検証環境
Version

Docker
18.09.1

Docker Compose
1.23.2

Compose file format
3.0

以下の内容は、他の記事で分かりやすく説明されているのでここでは省きます。


  • Docker / Docker Compose のインストール方法

  • 基本的な使い方(といっても、私が知らなかっただけで一般的な使い方を挙げているかもしれませんがそこはご了承ください

また、dockerコマンドオプションとdocker-compose.ymlのオプション指定は適宜読み替えてください。

では早速行きましょう!


Docker コマンド


1. build 時にキャッシュを使わない

Dockerfileを編集しても内容が反映されないときは試してみてください。

$ docker build -t hoge:v1 . --no-cache


2. <none>:<none> の image のみ抽出する

これまで grep 使ってましたが、ちゃんとドキュメントにありました。

$ docker images --filter "dangling=true"

↑を使って消す時は、

$ docker rmi $(docker images --filter "dangling=true" --format {{.ID}})

# もっと簡単に
$ docker rmi $(docker images --filter "dangling=true" -q)


3. コンテナ内のタイムゾーンを設定する

環境変数のTZAsia/Tokyoを設定すればOKです。

$ docker run -it -e TZ=Asia/Tokyo ruby:2.4 bash

Dockerfileに書いてももちろん問題ありません。


4. -itオプションってなに?

次のようなコマンドをよく見ます。私も大抵はこう使います。

$ docker exec -it [コンテナID] bash

この -itオプションは、-i-tをまとめたもので、それぞれのざっくりな意味合いは



  • -i:コンテナ内の標準出力とホスト側の出力をつなげる


  • -t:ホスト側の入力をコンテナの標準出力につなげる

だそうです。試してみましょう。

$ docker run -i ruby:2.4 bash

(なにも操作ができない

$ docker run -t ruby:2.4 bash
root@05cda9646908:/#

(コンテナには入れるが、入力しても表示されない

コンテナ内でゴニョゴニョしたい場合は、2つのオプションをセットで使う必要があります。


5 --no-trunc をつけると、情報を省略せずに表示される


--no-trunc なし

 $ docker ps --format "{{.Command}}"

"irb"
"docker-entrypoint.s…"
"drakov -f api.md -p…"
"/usr/local/bin/run …"
"/usr/local/bin/run …"
"/usr/local/bin/run …"
"docker-entrypoint.s…"
"docker-entrypoint.s…"


--no-trunc あり

$ docker ps --format "{{.Command}}" --no-trunc

"irb"
"docker-entrypoint.sh mysqld"
"drakov -f api.md -p 3031 --autoOptions --public"
"/usr/local/bin/run npm run start"
"/usr/local/bin/run bundle exec sidekiq -q default -q mailers -q pull -q push"
"/usr/local/bin/run bundle exec rails s -p 3000 -b 0.0.0.0"
"docker-entrypoint.sh postgres"
"docker-entrypoint.sh redis-server"


Dockerfile / docker-compose.yml


6. RUNを多用する時は

一行ごとにRUNを書くのではなく、次のようにつなげて書きましょう。

できるだけRUNを呼ぶ回数を減らすことで、可読性・メンテナンス性が向上します。また、buildのStep数削減で多少の時間短縮ができます。

RUN apt-get update \

&& apt-get install -y --no-install-recommends \
build-essential \
curl \
git \
zlib1g-dev \
libssl-dev \
libreadline-dev \
libyaml-dev \
libxml2-dev \
libxslt-dev \
libmysqlclient-dev


7. 不要な命令は減らす

Dockerfileで一通り動作する環境が出来たら、次は最適化していきましょう。

Quickstart: Compose and Rails を見ると、こんな記述があります。

RUN mkdir /myapp

WORKDIR /myapp

myappディレクトリを作成して、作業用ディレクトリとするためcd myappをした感じです。

このWORKDIRですが、指定するディレクトリがもし存在しない場合は作成してくれますので、

WORKDIR /myapp

だけでよいことになります。


8. ADDとCOPYってどう使い分けるの?

基本的にはローカルのファイルをコンテナにコピーする役割で使用します。

ただし、


  • ADD: コピーするソースが gzip、bzip2、xz の場合、指定ディレクトリに展開される

  • COPY:ただコピーするだけ

という違いがあります。

使い分けとして、以下のように何をする命令なのか明確にするという点で使用するといいと思います。

ADD db_data.tar.gz /tmp/dump/

COPY Gemfile /app/Gemfile


9. EXPOSEって必要?

弊社で開発環境をDocker化しているリポジトリを覗いてみると、7割のDockerfileで、EXPOSEが記述されていました。

公式でEXPOSEを見てみると、


EXPOSE 命令は、特定のネットワーク・ポートをコンテナが実行時にリッスンすることを Docker に伝えます。 EXPOSE があっても、これだけではホストからコンテナにアクセスできるようにしません。


とあります。

つまり、この命令で何かが実行されるわけではありませんが、どのポートに接続するのかを明示しておく、という点で書いておいたほうが親切かもしれません。


10. RUNとCMDの違い

先ほどもでてきたRUNですが、これはシェル形式でコマンドを実行するものであり、Dockerfile内に複数回出てきても問題のないものです。

それに比べてCMDは、Dockerfileには原則1つのみ記述します。

もし複数の記述がある場合は、最後のCMDが実行されます。

また、CMDは、コンテナ実行時のデフォルトを提供するそうです。

例として、Rubyの公式イメージを使用してみます。

$ docker run -it ruby:2.4

irb(main):001:0>

irbの待受状態になりました。

これは、Rubyの公式イメージを見るとわかりますが、Dockerfileの最後に

CMD [ "irb" ]

と書いてあるためです。

RUNとCMDの使い分けは、Railsのアプリケーションで例えると、

RUN bundle install \

&& rails db:create \
&& rails db:migrate

CMD ["rails", "s", "-b", "0.0.0.0"]

という感じでしょうか。

これでコンテナが実行されるとき、同時にRails Serverが起動します。


11. DBコンテナであらかじめユーザを作成しておきたい

MySQLやPostgreSQLを使用する時、事前にDBやユーザを作成しておきたい、なんてことがあると思います。

その時は、あらかじめ拡張子が.shまたは.sqlのファイルを用意し、/docker-entrypoint-initdb.d/ディレクトリへ設置しておくだけで、コンテナ実行時に自動で行ってくれます。

具体的にはこんな感じです。


init.sh

psql -v ON_ERROR_STOP=1 -U postgres <<-EOSQL

CREATE USER hoge_user WITH CREATEDB;
CREATE DATABASE hoge_development OWNER hoge_user;
EOSQL


Dockerfile.postgres

FROM postgres:9.3.16

COPY init.sh /docker-entrypoint-initdb.d/

ファイルの実行順を指定したい場合は、1_init.sh2_restore.sqlとすればOKです。


12. cachedを使う

マウント遅い問題ですが、Docker 17.04.0-ceからcachedが使えるようになり、多少は改善されたようです。


docker-compose.yml

app:

build: .
volumes:
- .:/myapp:cached

ただし、読み込みは速くなったものの、ホストからの書き込みがコンテナ内で反映されるまで時間がかかる場合もあるそうです。

ちなみに、cachedの他にもconsistentdelegatedが追加されていました。


  • consistent:ホストとコンテナの整合性を保つ(デフォルト)

  • delegated:コンテナ内でのマウントは速いものの、コンテナで行われた書き込みがホストからの読み込みに遅延が生じることがある

私は基本的にcached指定ですが、適宜変更していく形がいいかと思います。


13. 複数のDockerfileをディレクトリに含める

複数のDockerfileを扱おうとすると、リポジトリ直下が汚くなったり、Dockerfileの名前をDockerfile.dbとして扱うのはちょっとかっこ悪いので、例えば.dockerというディレクトリにまとめて管理したいとします。


docker-compose.yml

version: '3'

services:
web:
build:
context: .
dockerfile: .docker/containers/app/Dockerfile
...

buildのcontextdockerfileを指定します。

contextを変えれば、docker-compose.yml.dockerディレクトリに含めることも可能ですが、その場合はdocker-composeコマンドでファイルオプションをつける必要があるため、ymlだけはディレクトリ直下に置いています。


14. EC2でDockerコンテナ内のcronが動かない

ローカルでは問題なく動いていたのに…

詳しくは http://tanksuzuki.com/post/docker-pam-error/ をご覧ください。

解決方法として、docker-compose.ymlを以下のように修正します


docker-compose.yml

app:

build: .
cap_add:
- audit_control
...


15. RUN 命令のキャッシュ

RUN、COPY、ADDコマンドではレイヤを作成し、RUN は文字列が変わらない限りレイヤーを cache します。

COPY ADD したファイルはチェックサムを計算し、以前と異なる場合は、それ以降の RUN の cache が破棄されるそうです。

よって、Lockfile などを先にコピーしておくことにより、効率的にレイヤーキャッシュを活用できるそうです。

...

# Gemfile, Gemfile.lock が更新されない限り、bundle install はキャッシュされる
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install


16. 意図しない改行を防ぐ

コンテナ内のコンソールを操作中に意図しないところで改行してしまうことがあります。

そのときは、以下の環境変数を設定しましょう。

$ docker-compose exec -e COLUMNS=$COLUMNS -e LINES=$LINES -e TERM=$TERM app bash


その他


17. no space left on device

docker-compose buildをすると、さっきまで何もなかったのに、急に以下のメッセージで通らなくなってしまうことがあります。

... no space left on device

これは、VMの容量が足りないときに発生するようです。

そんなときは、使用していないイメージとコンテナを削除してみましょう。

$ docker rm CONTAINER [CONTAINER…]

$ docker rmi IMAGE [IMAGE...]


18. Docker関連をまっさらに

イメージ、コンテナ、ボリュームをキレイに消せる便利なコマンドがあります。

https://github.com/ZZROTDesign/docker-clean

ただ、Dockerのステータスを理解しておかないと望んでいないコンテナを削除してしまう恐れもある(経験あり)ので、使用には気をつけてください。

[追記] @AkihiroSuda さんよりコメントいただきました

$ docker system prune

というものがあるそうで、実際に試してみると

$ docker system prune --all

WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- all build cache
Are you sure you want to continue? [y/N]
.
.
.

Total reclaimed space: 14.8GB

と親切に警告も出してくれます。スッキリしました。


おわりに

まだまだ勉強中のため、「お、そこ違うかもよ?」という点があればいつでもコメントお待ちしております!

誰かのお役に立てば幸いです。