ギークなエンジニアを目指す男

機械学習系の知識を蓄えようとするブログ

【初心者向け】実際に動かしながらDockerを学ぶ〜前編〜

f:id:taxa_program:20190218023230p:plain
 

こんにちは。takapy(@takapy0210)です。

本記事は、転職カウントダウンカレンダー 2日目の記事です。

www.takapy.work

はじめに

次の職場ではDockerを利用してサービス運用をしているため、事前に知識を蓄えておいた方が良いだろうということで勉強しています。機械学習エンジニアでも学習基盤の知識・インフラ面の知識という部分は身につけておいて損はないと思っていたりします。

本記事では、Dockerのインストールから、Dockerイメージの作成、DockerHubへのプッシュ、コンテナのシェルへの接続方法などについて記載しています。内容が多くなりそうなので、数回に分けてまとめようと思います。

初心者ゆえ内容に誤りがある可能性もありますが、ご了承ください。

ホスト型仮想化とコンテナ型仮想化の違い

仮想化のオーバーヘッド

従来の仮想化(ホスト型仮想化)

リソース(CPUや使用率など)の面でオーバーヘッドが多く、起動や停止にも時間がかかります。

コンテナ型仮想化

コンテナはアプリケーション実行に必要なものだけを含み、ホストOSのカーネルを使用するため、動作が早くリソースの使用率も少なくて済みます。

アプリケーション実行の再現性

従来の仮想化(ホスト型仮想化)

仮想マシンの環境の違いにより、アプリケーションが動作しなくなる可能性があります。

コンテナ型仮想化

特定のアプリケーションを動作させるために必要なものは、後述するDockerイメージにまとまっており、同じDockerイメージからコンテナを起動する限り、環境が異なっていても同様に動作します。ステージング環境では動作するけど、本番環境で動作しないという心配事がなくなるのはとてもメリットが大きいと感じました。

OSの自由度

従来の仮想化(ホスト型仮想化)

仮想マシン上で任意のOSを動作させることができます。(Windows上の仮想環境にLinuxをインストールするなど)

コンテナ型仮想化

コンテナはホストOSのカーネルを使用して動作するので、WindowsOS上で直接Linuxコンテナは動作させることはできません。また、反対にLinuxOS上で直接Windowsコンテナも動作させることはできません。(これは一般的なコンテナ型仮想化の話でDockerの話ではありません)

分離レベル

従来の仮想化(ホスト型仮想化)

ハードウェアレベルで仮想化されており、ホストOSや仮想マシン間の分離レベルが高く、それぞれが影響を受けにくいいです。

コンテナ型仮想化

OS上の1プロセスとして動作するコンテナ型仮想化は、従来の仮想化に比べて分離レベルは低いです。要求されるセキュリティレベルの度合いによっては1つの懸念点となる可能性もありそうですが、ホスト型仮想化・コンテナ型仮想化のいずれにせよ、侵入されにくい設定や構成にする必要があります。

Docker for Macについて

DockerはそもそもLinux環境で動くのものであり、MacOSやWindowsOS上ではVirtualBoxなどVMを立てて動かすものです。MacでDockerをサポートするツールという位置付けでDocker for Macがあります。

インストール

Docker公式HPからインストールが行えます。この辺の記事はWeb上にたくさんあるので省略します。

Docker for Mac上でコンテナがどのように動作しているのか

冒頭でも述べましたが、MacOS上で直にコンテナが動作するのではなく、実際にはHyperKitというMacネイティブの仮想化環境が裏で動いています。こいつが軽量なLinuxを動作させ、その上でコンテナが動作しています。

Dockerコンテナの実行

hello-worldコンテの実行

下記コマンドをターミナル上かた実行し、hello-worldというイメージを取得してコンテナを起動してみます。

$ docker run hello-world

~~~~
Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.
~~~~

上記ステップを図で表すと下記のようになります。

f:id:taxa_program:20190217210157p:plain
docker run コマンドの実行時動作イメージ

初回実行時、「hello-world」イメージはローカルに存在していないため、Docker Hubまで取得しにいきます。2回目以降の実行ではローカルにイメージがあるため、②、③の手順は省略されます。

docker runコマンドは何をしているのか

$ docker runコマンドは、分割すると下記3つのコマンドを実行していることと同じになります。

$ docker pull:イメージの取得
$ docker create:コンテナの作成
$ docker start:コンテナの起動

Docker Hubとは

Dockerイメージのレジストリサービスで、Dockerイメージの公開や検索、ダウンロードが行えます。

hub.docker.com

Dockerイメージとは

コンテナ実行に必要なファイルをまとめたファイルシステムです。phpやruby、pythonの実行環境が必要となった場合に、これらのイメージからコンテナを起動すれば、すぐに動作環境を構築できたりします。

DockerイメージはAUFSという特殊なファイルシステムが使用されています。また、イメージ上のデータはレイヤで構成され、すべて読み取り専用となっています。

f:id:taxa_program:20190217211906p:plain
Dockerイメージのファイルシステム

Dockerイメージダウンロードの動作

例としてwhalesayコンテナを実行しながら、Dockerイメージのダウンロード動作について見てみます。

whalesayコンテナとは、引数で渡した文字列をクジラのAAに喋らせることができるものです。

$ docker run docker/whalesay cowsay Hello!

Unable to find image 'docker/whalesay:latest' locally
latest: Pulling from docker/whalesay
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 run docker/whalesay cowsay Hello!

ここでのcowsay Hello!は、whalesayコンテナ内で呼び出すコマンドを表しています。このように記述することで、コンテナが立ち上がった後にコマンドを実行することができます。

Unable to find image 'docker/whalesay:latest' locally

今回指定しているdocker/whalesay:latestのイメージがローカルに存在していないことを表しています。

latest: Pulling from docker/whalesay

latestタグのイメージをDockerHubのdocker/whalesayリポジトリからPullしていることを表しています。

ローカルにあるDockerイメージ一覧を確認する

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              fce289e99eb9        6 weeks ago         1.84kB
docker/whalesay     latest              6b362a9f73eb        3 years ago         247MB

Dcokerイメージを複製する

イメージを複製しても、IMAGE IDは変化しません。つまり、同じイメージということです。

$ docker tag docker/whalesay my_whalesay
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              fce289e99eb9        6 weeks ago         1.84kB
docker/whalesay     latest              6b362a9f73eb        3 years ago         247MB
my_whalesay         latest              6b362a9f73eb        3 years ago         247MB

また、:tag名を付与することで、異なるTAGをつけてイメージを複製することもできます。

$ docker tag docker/whalesay my_whalesay:ver1
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              fce289e99eb9        6 weeks ago         1.84kB
docker/whalesay     latest              6b362a9f73eb        3 years ago         247MB
my_whalesay         latest              6b362a9f73eb        3 years ago         247MB
my_whalesay         ver1                6b362a9f73eb        3 years ago         247MB

ローカルのイメージを削除する

すでにイメージからコンテナを生成してしまっている場合は、コンテナの削除をした後にイメージを削除するか、-fオプションを付与して強制削除する必要があります。

削除時も、特にTAG指定しなかった場合はlatestTAGが付いているイメージが削除されます。

$ docker rmi docker/whalesay
Untagged: docker/whalesay:latest
Untagged: docker/whalesay@sha256:178598e51a26abbc958b8a2e48825c90bc22e641de3d31e18aaf55f3258ba93b

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              fce289e99eb9        6 weeks ago         1.84kB
my_whalesay         latest              6b362a9f73eb        3 years ago         247MB
my_whalesay         ver1                6b362a9f73eb        3 years ago         247MB

自分でDockerイメージファイルを作成する

イメージを作成するには、Dockerfileというイメージの定義ファイルを作成する必要があります。Dockerfileからイメージを作成することをイメージビルドと言ったりします。

まずはDockerfileを下記のような内容で作成します。ファイル名はDockerfileとします。

FROM docker/whalesay:latest
RUN apt-get -y update && apt-get install -y fortunes
CMD /usr/games/fortune | cowsay
  • FROM
    イメージを作成する際に、基となるイメージを指定するコマンドです。基のイメージのレイヤの上に新しいレイヤをカスタマイズして追加することができます。

  • RUN
    イメージに新しいパッケージをインストールしたりできます。

  • CMD
    コンテナ起動時に動作させたいコマンドを指定することができます。

ここまでで、イメージをビルドさせる準備ができました。

実際にイメージからビルドするコマンドは下記となります。

$ docker build -t docker-whale .

-t docker-whaleの部分はイメージ名を指定しており、末尾の.はビルドコンテキストを指定しています。

ビルドコンテキストとは、イメージを作成する際にアクセスできるディレクトリやファイルの範囲を指定するものです。(上記ではカレントディレクトリを指定しています)イメージ内に含めたいファイルがある場合は、このビルドコンテキスト内からコピーすることが可能になります。
また、このビルドコンテキストで指定したディレクトリにあるDockerfileから、イメージを作成することになるので、Dockerfileの作成場所にも注意が必要です。

今回のDockerfileの内容だと、fortunesパッケージをインストールしたレイヤが1つ、cowsayコマンドを実装したレイヤが1つ追加されるようになります。

イメージビルド後、docker imagesコマンドで確認してみます。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
docker-whale        latest              ff32c5edd7f9        About a minute ago   278MB
hello-world         latest              fce289e99eb9        6 weeks ago          1.84kB
docker/whalesay     latest              6b362a9f73eb        3 years ago          247MB
my_whalesay         latest              6b362a9f73eb        3 years ago          247MB
my_whalesay         ver1                6b362a9f73eb        3 years ago          247MB

docker-whaleというイメージが作成されていることが分かります。

このイメージからコンテナを起動して、動作を確認してみます。

$ docker run docker-whale
 _________________________________________
/ P.S. Perl's master plan (or what passes \
| for one) is to take over the world like |
| English did. Er, *as* English did...    |
|                                         |
| -- Larry Wall in                        |
\ <199705201832.LAA28393@wall.org>        /
 -----------------------------------------
    \
     \
      \
                    ##        .
              ## ## ##       ==
           ## ## ## ##      ===
       /""""""""""""""""___/ ===
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
       \______ o          __/
        \    \        __/
          \____\______/

ビルドキャッシュについて

Dockerfileによりイメージビルドすると、コマンドがキャッシュされます。このビルドキャッシュによって2回目以降のイメージビルドを高速に行うことができます。

しかし、例えば2回目以降に同じDockerfileでイメージビルドを行うとそのキャッシュが使用され、Dockerfileで記述したコマンド(例えばapt-get -y updateなど)が実行さない、という問題が発生する可能性があります。

それを防ぐためには、--no-cacheオプションをつける必要があります。

$ docker build --no-cache -t docker-whale .

DockerHubにイメージをPushする

リポジトリにイメージを追加してみます。

DockerHubにターミナルからログイン

$ docker login

DockerHubにおけるタグ付けのルール

下記のようにタグ付けを行います。(タグ名は省略可能)

<Docker ID>/<イメージ名>:<タグ名>

実際にやってみます。

$ docker tag docker-whale takapy0210/docker-whale:ver1
$ docker images
REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
docker-whale              latest              ff32c5edd7f9        28 minutes ago      278MB
takapy0210/docker-whale   ver1                ff32c5edd7f9        28 minutes ago      278MB
hello-world               latest              fce289e99eb9        6 weeks ago         1.84kB
docker/whalesay           latest              6b362a9f73eb        3 years ago         247MB
my_whalesay               latest              6b362a9f73eb        3 years ago         247MB
my_whalesay               ver1                6b362a9f73eb        3 years ago         247MB

takapy0210/docker-whaleというリポジトリが作成されていることがわかります。

Push

Pushのコマンドは下記です。

$ docker push <Docker ID>/<イメージ名>:<タグ名>

実践してみます。

$ docker push takapy0210/docker-whale:ver1

DockerHub上でリロードすると、確かにPushできていることが確認できます。

f:id:taxa_program:20190218005602p:plain:w400

コンテナを起動してみる

nginxのDockerHubリポジトリ

$ docker run --name test-nginx -d -p 8080:80 nginx

test-nginxはコンテナ名を設定しています。また、8080はホスト側のポート番号、80はコンテナ側のポート番号を表しています。

起動後、ブラウザからhttp://localhost:8080/にアクセスすることにより、nginxが起動していることが確認できるかと思います。

f:id:taxa_program:20190218011914p:plain
簡略化したネットワーク図

ホスト上のディレクトリをコンテナにマウントする

nginxのDockerHubリポジトリに記載のあるように、下記コマンドで静的ページをマウントしてnginxで動作させることができます。

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

-vの引数でホスト側のディレクトリとコンテナ側のマウンポイントを指定しています。

f:id:taxa_program:20190218013236p:plain
バインドマウントのイメージ図

このようにファイルマウントすることで、手元でコンテンツの更新を行ったり、ソースコードをコンテナに含めずに、Githubなどで管理することができるようになります。

実際にやってみます。まずは、htmlファイルをローカルに作成します。(フルパス:/Users/takapy/Documents/03_docker/docker-tutrial/html)

<!DOCTYPE html>
<html>
    <body>
        <h1>MyFirst Heading</h1>
        <p>My first paragraph.</p>
    </body>
</html>

この状態で、下記コマンドを実行します。

$ docker run --name first-nginx -v /Users/takapy/Documents/03_docker/docker-tutrial/html/:/usr/share/nginx/html:ro  -d -p 8080:80 nginx

ブラウザからhttp://localhost:8080/にアクセスすると、上記htmlの内容が表示されていることが確認できると思います。

f:id:taxa_program:20190218014500p:plain:w300

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

Dockerのステータスは下記のようなコマンドで確認できます。

$ docker ps
$ docker ps -a

f:id:taxa_program:20190218020908p:plain
コンテナのステータス遷移のイメージ図

コンテナのシェルへの接続

下記の2パターンで接続することが可能です。

docker attachを使用する

$ docker attach <コンテナ名またはコンテナID>

※シェル接続できるのはコンテナでシェルを実行している場合のみです。また、exitでシェルを抜けるとコンテナも停止してしまいます。Ctrl+P → Ctrl+Q でコンテナを停止させることなくシェルを抜けることができます。

docker execを使用する

$ docker exec -it <コンテナ名またはコンテナID> /bin/bash

※/bin/bashを実行していますが、bashがなければ別のシェルを指定してください。

それでは実際に起動中のコンテナに接続してみます。例としてubuntuのコンテナを実行してみます。

$ docker run --name connect-test -it -d ubuntu /bin/bash

$ docker attach connect-test
-- または下記
$ docker exec -it connect-test /bin/bash

上記のようにしてubuntuへ接続することができます。

コンテナを停止・削除する

停止させる場合は docker stop <CONTAINER IDまたはNAME>で停止できます。また、停止させたあとであればdocker rm <CONTAINER IDまたはNAME>でコンテナを削除することもできます。

$ docker stop b7f098b3b774

$ docker rm b7f098b3b774

参考

ゼロからはじめる Dockerによるアプリケーション実行環境構築

以上。