emahiro/b.log

日々の勉強の記録とか育児の記録とか。

Leaning Docker - part1

Overview

Docker について今更ながら入門したのでその記録をつらつらと書いていく。

Summary

Docker について雰囲気でしか理解しておらず、実務でちゃんと使えるレベルになかったので Udemy の「ゼロから始める Docker によるアプリケーション実行環境構築」 を見ながら、今更 Docker の基礎中の基礎について再入門しました。

Docker Hub

Docker向けのコンテナ共有サービス「Docker Hub」

Dokcer Image とは

Docker コンテナの実行に必要なファイルをまとめたファイルシステム(AUFS) OSのライブラリ、アプリケーション、ミドルウェアがすでにインストールされている(RubyPHPを含) Webサーバーを作る場合は nginx や apatch がすでにインストールされている、というイメージ。 イメージのデータはレイヤで構成され read only

Docker コンテナは軽量であることが求められる。 過去のレイヤーのファイルは消えない。削除を実行したコンテナレイヤー上でファイルは削除されるが、履歴は残り続けるのであるレイヤーでファイルを追加した処理は残り続ける。 1度大きなファイルを追加してイメージ化したものをあとのレイヤで削除したとしても、イメージにはその過去追加した大きなファイルは残り続けるのでイメージ全体が肥大化する。 Dockerコンテナはなるべく軽量であることが求められているので、なるべく無駄なファイルがイメージの中に存在しないようにイメージを作成することが大事。 Dockerイメージがデカイ -> イメージのDL, Docker Hub 等へのアップロードに時間がかかる、ということ。 イメージを作成するときはサイズに注意。

Docker コンテナでのコマンドの実行

docker run docker/whalesay cowsay Hello を実行する。 ※ docker/whalesay は Docker 社が公開してる公式のアスキーアートのイメージ。docker run でコマンドを実行する練習をすることができる。

docker run $ImageName {$command} {$args} で docker コンテナ内部で指定したコマンドを実行できる。

$ docker run docker/whalesay cowsay Hello
Unable to find image 'docker/whalesay:latest' locally
latest: Pulling from docker/whalesay
Image [docker.io/docker/whalesay:latest](http://docker.io/docker/whalesay:latest) uses outdated schema1 manifest format. Please upgrade to a schema2 image for better future compatibility. More information at [https://docs.docker.com/registry/spec/deprecated-schema-v1/](https://docs.docker.com/registry/spec/deprecated-schema-v1/)
e190868d63f8: Pull complete
909cd34c6fd7: Pull complete
0b9bfabab7c1: Pull complete
a3ed95caeb02: Pull complete
00bf65475aba: Pull complete
c57b6bcc83e3: Pull complete
8978f6879e2f: Pull complete
8eed3712d2cf: Pull complete
Digest: sha256:178598e51a26abbc958b8a2e48825c90bc22e641de3d31e18aaf55f3258ba93b
Status: Downloaded newer image for docker/whalesay:latest
_______
 < Hello >
  -------
     \
      \
       \
                     ##        .
               ## ## ##       ==
            ## ## ## ##      ===
        /""""""""""""""""___/ ===
   ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
        \______ o          __/
         \    \        __/
           \____\______/

こんな感じ。

Docker Imageの管理

docker images を叩く。
現在 local に存在する image の一覧を確認できる。

イメージのリポジトリ名を変更する

docker tag docker/whalesay emahiro_whalesay で local のイメージに新しくタグを追加することができる。

$docker images
emahiro_whalesay latest 6b362a9f73eb 4 years ago 247MB

こんな感じ。

タグを打つ

docker tag docker/whalesay emahiro_whalesay:emahiro で元々のイメージにタグを打って分けることが可能になる。

$ docker images
emahiro_whalesay emahiro 6b362a9f73eb 4 years ago 247MB
emahiro_whalesay latest 6b362a9f73eb 4 years ago 247MB

こんな感じ。

イメージをビルドする

イメージを構築するには Dockerfile (イメージの定義ファイル) Dockerfile からイメージを構築することを イメージビルド と言う。 Dockerfile には 命令 引数 と言う形式で構築手順を記載していく。 ex. FROM docker/whalesay:latest は新しいイメージをビルドするときの大元(タネ)になるイメージに docker/whalesay イメージの latest タグを使う、と言うことを表現しています。

ビルド時に指定する命令1つあたりが1レイヤーとして元となるベースイメージに追加される、と言う意味になる。

よくわかってなかったDocker build のあれこれ

  • CMD 命令 -> コンテナが作成された後に実行するコマンドを指定する命令。
  • -t optison: ビルドした Docker イメージに名前をつけるオプション。ex. docker build -t emahiro_whalesay .
  • ビルドコンテキスト: イメージを作成する際にアクセスできるディレクトリやファイルの範囲を示す。
    • 特に指定がなければ . でビルドしたい Dockerfile と同じ階層のカレントディレクトリを指定する。
    • イメージ内に含めたいファイルやディレクトリを参照する場合は、ビルドコンテキスト内であればコピーすることができる。ビルドコンテキスト外のファイルはイメージ構築時に参照することができない。
    • イメージビルド時にビルドコンテキストないのファイルやディレクトリはまとめて Docker deamon に送信される。大きなファイルがビルドコンテキストに含まれる場合に、ファイルの転送処理に時間がかかるので、不要なファイルはビルドコンテキストに含まない方が吉。
    • ビルドコンテキストに含まれるファイルが全てイメージに取り込まれる、と言うわけではなく、イメージビルドした際に一時的に Docker deamon に転送されるだけで COPYADD と言った命令で明示的にイメージ内にファイルをコピーする命令を使わなければイメージ内にビルドコンテキスト上のファイルが保存されることはない。
    • Docker build コマンドはデフォルトでビルドコンテキスト状にある Dockerfille が読み込まれる。

サンプル

$ docker build -t emahiro-whale .
Sending build context to Docker daemon 2.048kB

Sending build context to Docker daemon 2.048kB はビルドコンテキストを Docker deamon に転送している処理。

ビルドキャッシュ

各ステップのビルドの結果をキャッシュとして保持したもの。 Dockerfile の内容が同じであればキャッシュが使用されて、実際の命令はスキップされる。

$ docker build -t emahiro-whale .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM docker/whalesay:latest
---> 6b362a9f73eb
Step 2/3 : RUN apt-get -y update && apt-get install -y fortunes
---> Using cache # キャッシュを使う
---> 2a92623b6954

※ 毎回動作が変わるようなコマンドには注意が必要 ( ex. apt-get -y update など)

なぜならば、apt-get -y update を記載した本人は毎回パッケージリストを更新することを期待しているかもしれないが、Dockerfile 上はコマンドが変わらないのでビルドキャッシュが利用されてしまい、実際の動作では package リストが更新されない、と言う問題が発生するからである。 --no-cache を使うとキャッシュが使用されずに全てのステップが実行される。

Image をアップロードする

学習なので Docker Hub にアップロードする。このイメージのアップロード先については Google Cloud Registry や Amazon Elastic Container Registory などクラウドベンダーが用意してるコンテナイメージのホスティングサービスもある。 ローカルからイメージをビルドしてアップロードする場合、リモートのホスティング環境にすでに同名のイメージが存在するケースではタグを切らない場合にイメージが上書きされてしまうので注意が必要である。

  1. Docker image をホスティングしてるサーバーへのログインする。事前にログインをしておく必要がある。
    1. docker login -> デフォルトで Docker Hub へのログインをする。
    2. タグ付のルール -> $DockerID/$Image:$Tag ex. docker tag emahiro-whale emahiro/emahiro-whalesay:v1
  2. Docker Hub へ Push する。 docker push emahiro/emahiro-whalesay:v1

※ Docker Hub では事前にリポジトリを作成しておかなくても、$DockerID/$Image:$Tag のフォーマットで push すれば自動でリポジトリが作成されます。

Web サーバーを Docker で構築する

Nginx) のイメージを使用して Web サーバーを立ててみる。

Nginx イメージについて

使用するのは Exposing external port に記載されてる docker run コマンド。

$ docker run --name $ContainerName -d \
    -p $HostPortNum:$ContainerPortNum \
    $ImageName

$ContainerName で指定したコンテナ名はコンテナを識別したり、コンテナ操作を行う上で必要になるのでできるだけ設定するようにしておくのが吉。

-p はコンテナのポートをコンテナ外に公開する設定。前が外部に公開する番号で、後ろがコンテナにマッピングされているポート番号。

docker run --name emahiro-nginx -d -p 8080:80 nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
afb6ec6fdc1c: Pull complete
b90c53a0b692: Pull complete
11fa52a0fdc0: Pull complete
Digest: sha256:30dfa439718a17baafefadf16c5e7c9d0a1cde97b4fd84f63b69e13513be7097
Status: Downloaded newer image for nginx:latest
24e6863779c15bcd1580808b9efcd02ac8c871827de1f0fae12455a6bb840c06 # 起動したコンテナのID

コンテナを起動・停止・削除をする時には末尾のコンテナIDかコンテナ名を使用する。

-d はデタッチモード。デタッチモードの場合に foreground でコンテナが動作するわけではないので、コンテナを立ち上げたまま他の操作をすることが可能。

デタッチモードを指定しないと foreground で nginx のコンテナが動作しているので Ctnl + C でコンテナを停止すると nginx の動作も停止します。

ホスト上の HTML を Nginx コンテナで公開する(バインドマウント)

Docker Hub の Nginx) に記載されている Hosting some simple static content に習って進める。

Sample.

$ docker run --name some-nginx -v /some/content:/usr/share/nginx/html:ro -d nginx

  • -v : Volume の v を意味している。
  • 静的なHTMLファイルを Nginx で公開するものを想定している。
$ docker run --name $ConteinarName -d \
    -v $HostDir:$ContainerMountedPoint:$Option \
    -p $HostPortNum:$ContainerPortNum \
    $ImageName

-v の引数がコロン区切りで3つに分かれている。

  • $HostDir: コンテナにマウントするホスト側のディレクト
  • $ContainerMountPoint :コンテナ側のディレクトリ。Nginx コンテナにおいては /usr/share/nginx/html が公開対象のドキュメントルートになる。
  • $Options : 権限を指定できる。 ex. ro マウント先のコンテナに読み取り専用でマウントする指定。なくても動作が可能。

※ 引数の指定は 絶対path で指定することが必要。

pwd コマンドを使えば現在いるディレクトリの絶対パスを表示することが可能。(だからよくサンプルとかで $PWD と言う環境変数がよく出てきたのか、と言うことを今更理解した。)

Sample

docker run -it --name emahiro-nginx \
-v $PWD/assets/html:/usr/share/nginx/html:ro \
-d -p 8080:80 \
nginx
6af418943c7300bb2ac9e03f47aab91eb6cf3dbbc0fe87c0796ffa408e10ab43

COPY, ADD 命令

ホストマシン上のファイルをイメージ内にコピーする命令。

サンプルとして、 Nginx の修正した設定ファイルをコンテナ内にコピーしてみる。

編集する設定ファイルを取り出す

docker run --name emahiro-nginx --rm -d nginx

--rm : コンテナを停止した時点で自動でコンテナの削除まで行うオプション。コンテナは通常停止しても停止状態で残り続けてしまう。これにより、同名のコンテナを作成するときなどに名前がバッティングして起動できなくないので、すでに同名のコンテナが存在する場合は削除する必要がある。毎回削除するのは手間なので --rm オプションをつけることで、停止した時にコンテナ自体も削除することで名前がバッティングしたりするケースを避けることができる。

Nginx のコンテナを起動し、設定ファイルを取り出すことのみをするので、デタッチモードで起動する。

デタッチモードで起動したら Nginx の設定ファイルを取得する。

docker cp

ホストマシン → コンテナ へのコピー

$ docker cp $FilePathOnHost \
    $ContainerName(or $ContainerID):$PathToFileOnContainer

コンテナ内のファイル → ホストマシン へのコピー

$ docker cp $ContainerName(or $ContainerID):$PathToFileOnContainer \ 
    $PathToCopyOnHost

設定ファイルをコンテナにCOPYする

Docker ファイルで以下のようにコンテナに転送するファイルを指定します。

FROM nginx:latest
COPY nginx_conf/default.conf /etc/nginx/conf.d/default.conf

そしてビルドコンテキストを . に指定してイメージをビルドします。

$ docker build -t emahiro-nginx:v1 .

ADD 命令と COPY コマンドの違い

COPY コマンドは単純にファイルをコピーする。

ADD 命令は COPY 命令の機能に加えて tar でアーカイブされたファイルをコピー時に自動で展開したり、 COPY 元にURLを指定した場合は、URLからダウンロードしてコピー先に転送するといった動作を行う。

ADD 命令の動作は一見すると名前から類推しづらい動作をするため、ベストプラクティスとしては通常は COPY 命令を使い、ADD 命令に備わった機能が必要な場合のみ ADD 命令を使うことが推奨されている。

コンテナのライフサイクル

前提

  • コンテナとは Docker Image のファイルシステムを元にして作られる一種の仮想環境。
  • コンテナが起動してる間は、コンテナはホストマシンの1プロセスとして動作する。
  • コンテナが作成されてから削除されるまでにいくつものステータスがある。

Created Status

コンテナが作成されてスタートされる前の状態。

docker create を使ってコンテナを作成した状態がこのステータス。

docker create --name status-test -it alpine /bin/sh

-i : コンテナの標準入力を取得して、双方向に接続できるようにするオプション。

-t : コンテナ内にtty を割り当てる。コンテナでシェルを実行して、foreground で実行状態のままにしておきたい時によく使う。

-it の組み合わせてよく使われる。

docker create をしただけでは実行中ではないので docker ps コマンドでは出力されない。 docker ps -a コマンドを利用する。

docker ps -a
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS                    PORTS               NAMES
d1ed34aa3314        alpine                    "/bin/sh"                3 minutes ago       Created                                       status-test

Running Status

コンテナが作成され、Created 状態になった後に Start されると Runninng ステータスになる。

docker run コマンドは create → start がセットになったもの。docker ps で表示さえる Status には起動時間が表示される(=起動時間が表示されているのは Running status)

docker inspect でも起動状態を確認できる。

$ docker inspect status-test
[
    {
        "Id": "abf9fb1cecaa8bdd08e643a41065c2048c072e401823a962997a2fdbedbdf3b0",
        "Created": "2020-05-25T10:20:21.487691823Z",
        "Path": "/bin/sh",
        "Args": [],
        "State": {
            "Status": "running", # これ。
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 2629,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2020-05-25T10:23:29.494591462Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
#略

Paused Status

docker pause コマンドで一時停止させた時にこのステータスになる。

docker pause status-test
status-test
➜  docker git:(il/docker) docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                  PORTS               NAMES
abf9fb1cecaa        alpine              "/bin/sh"           5 minutes ago       Up 2 minutes (Paused)                       status-test

docker inspect でも確認可能。

$ docker inspect status-test
[
    {
        "Id": "abf9fb1cecaa8bdd08e643a41065c2048c072e401823a962997a2fdbedbdf3b0",
        "Created": "2020-05-25T10:20:21.487691823Z",
        "Path": "/bin/sh",
        "Args": [],
        "State": {
            "Status": "paused", #これ
            "Running": true,
            "Paused": true,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 2629,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2020-05-25T10:23:29.494591462Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
#略

Paused Status のときは起動中のコンテナにリクエストを投げたりしても応答ができない場合がある。

(ってことはクライアントのタイムアウトのテストの時とかに使える?)

docker unpause で解除できる。

Restarting Status

コンテナの再起動中のステータス。

通常はすぐに起動しちゃうのでこのステータスを見ることは稀。

Exited Status

コンテナは終了したのに、コンテナが残り続けてるとこのステータスになる。

$ docker ps -a
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS                      PORTS               NAMES
892fdf2e3fb8        hello-world               "/hello"                 15 seconds ago      Exited (0) 15 seconds ago                       recursing_williamson

foreground で動作が終了したコンテナや、 docker stop で停止させたコマンドがこの Exited ステータスになります。

Removing Status

コンテナ削除中のステータス。すぐに削除されちゃうのでこのステータスをみることは稀。

Dead Status

正常に終了できずにゾンビ化したコンテナがこのステータスになる。この状態のコンテナは削除するしかない。通常の docker rm で削除できないので docker rm -f で削除する。

コンテナのシェルに接続する

docker attach を使用する

docker attach $ContainerName(or $ContainerID)

コンテナの pid:1 の標準入出力に接続する。コンテナ起動時のコマンドでシェルを起動した場合は attach するとそのシェルに接続することができる。

ただし起動時にシェルでなく、デーモンを起動していた場合はデーモンの標準入出力に接続してしまう。

起動時に --it オプションを使うことがセットになる。

docker exec を使用する

docker exec は起動してるコンテナ内で任意のコマンドを実行するためのコマンド。

シェルに接続するには -it オプションつきでシェルを実行する。接続したシェルを抜けるには exit コマンドを使います。

$ docker exec -it $ContainerName(or $ContainerID) /bin/bash

docker attach と違い、 exit で抜けてもコンテナが停止することはない。誤ってコンテナを停止させてしまうことを考えると docker exec コマンドの方が安全。

Example

$ docker run --name connect-test -it -d ubuntu /bin/bash
f612509fd241330357446a8d3a275a09c73968ac3fbd3743dee282305b4c9791
$ docker attach connect-test
exit
$ docker ps -a
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS                        PORTS               NAMES
104fb2fb5779        ubuntu                    "/bin/bash"              18 seconds ago      Exited (127) 1 second ago                         connect-test

# docker exec のケース
$ docker exec -it connect-test /bin/bash
root@42933e32d725:/# exit
exit
$ docker ps -a
CONTAINER ID        IMAGE                     COMMAND                  CREATED              STATUS                        PORTS               NAMES
42933e32d725        ubuntu                    "/bin/bash"              About a minute ago   Up About a minute                                 connect-test

exec コマンドのケースではコンテナが停止していないことが確認できます。

Docker commit

docker commit : 存在するコンテナを対象に docker commit を実行した新しく保存するイメージ名、タグ名を指定する → docker commit $ContainerName $ImageName:$Tag

コンテナの状態をイメージとして保存することができる。

docker commit コマンドで何かしら操作をしたコンテナからイメージを作成した場合、コンテナ内で行われた作業はどこにも明確な記録として残らなくなってしまうこと。

通常、Docker Image を作成する際に Dockerfile で実行された各コマンドはイメージの各レイヤーとして何のコマンドが実行されたかが記録されています。それを docker history で確認することが可能ですが、コンテナ内部で操作したコマンドは docker history コマンドの結果には出力されません。

Dockerfile で Image をビルドした場合は Dockerfile からもどういったコマンドを実行して作成したイメージなのかをあとから確認することもできる。

どのような操作で作成されたイメージなのかわからなくなってしまうと、何が含まれていて、どのような変更が加えられているのか把握しづらくなる。つまり使いづらい Docker Image になってしまう。

通常は Dockerfile を使うのが吉。

# ubuntuをベースに tempfile を加えたイメージを作成する。 
$ docker run --name commit-test -it --rm -d ubuntu /bin/bash
e61b85a3f4cd6cbf457e0b4bd25d40e7ffe1cbd430f25c8a4b4dd811b22ac585
$ docker exec -it commit-test /bin/bash
root@e61b85a3f4cd:/# cd tmp
root@e61b85a3f4cd:/tmp# dd if=/dev/zero of=tempfile bs=1M count=10
10+0 records in
10+0 records out
10485760 bytes (10 MB, 10 MiB) copied, 0.00829059 s, 1.3 GB/s
root@e61b85a3f4cd:/tmp# ll
total 10248
drwxrwxrwt 1 root root     4096 May 25 16:42 ./
drwxr-xr-x 1 root root     4096 May 25 16:40 ../
-rw-r--r-- 1 root root 10485760 May 25 16:42 tempfile
root@e61b85a3f4cd:/tmp# exit # 一旦コンテナを抜ける。
$ docker commit commit-test commit-test:v1 # tempfile を作成した状態でイメージを作成する。

docker history

コンテナでどんな処理を行ったのかイメージのレイヤーが順に出力される。上から順に新しく追加されたレイヤーになっている。

docker history commit-test:v1
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
af6d001d6055        2 minutes ago       /bin/bash                                       10.5MB # 10M を追加したレイヤー
1d622ef86b13        4 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           4 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B
<missing>           4 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   811B
<missing>           4 weeks ago         /bin/sh -c [ -z "$(apt-get indextargets)" ]     1.01MB
<missing>           4 weeks ago         /bin/sh -c #(nop) ADD file:a58c8b447951f9e30…   72.8MB  

CREATED BY には bin/bash の情報しか載っておらず、10Mを増やすのにどんな処理をしたのかは、このレイヤーを追加した人にしかわからない。

Infrastructure as code の観点からも、Docker においては Dockerfile を使うことが重要とされている。

Docker link

--link オプションはいずれ廃止される可能性があるので詳細は割愛。

-e : 引数の環境変数をコンテナに設定するオプション。

$ docker run --name $ContainterName -e ENV_VALUE="Hoge" -d $ImageName

$ContainerName で作成してコンテナ内で ENV_VALUEHoge という値がわかっていることになる。

$ docker run --name emahiro-ubuntu -it --rm -e EMAHIRO="emahiro" -d ubuntu /bin/bash
1e5d343ce111d9c5e26cfab553faa5d91c95a66fc8cfe9592581cd5f52f05b3b
$ docker exec -it  emahiro-ubuntu /bin/bash
root@1e5d343ce111:/# echo $EMAHIRO
emahiro

-e オプションで指定した環境変数がコンテナで設定されてることがわかる。