LoginSignup
3
4

More than 3 years have passed since last update.

コンテナ技術 ハンズオン Vol.2 Dockerイメージ編

Last updated at Posted at 2019-08-27
1 / 48

本題の前に


位置付け・前提

本記事は、社内ワークショップ のために用意したものです。そのため、次の前提で進めることをご理解ください。

また、本稿はこちらの続きとなります。

コンテナ技術 ハンズオン Vol.1 Docker操作編

目的や前提なども上記の内容に準拠します。


docker ハンズオン環境

今回も Play with Docker を活用してハンズオンを行います。

playwithdocker.png

※要:Docker Hub アカウント

注意点として、この環境は4時間の制限があります。4時間経過すると環境が削除されてしまう点を注意して下さい。

もちろんローカル環境でも構いません。


参加者の前提知識

Dockerfile および Docker レイヤーの考え方を理解していることが前提とします。参考までに下記の資料を一読しておくことをお奨めします。

Dockerfile を書くためのベストプラクティス解説編

HTMLページが良ければ下記でもOKです。

Dockerfile のベストプラクティス — Docker-docs-ja 1.9.0b ドキュメント


ハンズオンの前に


前回の問題

前回のハンズオンで最後に出した問題はこちら。

  1. 起動しているコンテナはホストのどのディレクトリにあるでしょうか。
  2. dockerイメージはどこにあるでしょうか。
  3. dockerのプロセスはどれか。

うまく ググれカス 答えられましたでしょうか。


前回の問題の解答

解答1. コンテナの実態ディレクトリの調べ方はこちら。

$ docker inspect {NAME|ID}

このコマンドを実行して、表示されるJSONデータのうち、下記の該当箇所を確認すればわかります。

"HostnamePath": "/var/lib/docker/containers/8c059e84473033b3a0474b8a9a4beb8f848f003492d87ba6865f93863ce889b2/hostname",
"HostsPath": "/var/lib/docker/containers/8c059e84473033b3a0474b8a9a4beb8f848f003492d87ba6865f93863ce889b2/hosts",
"LogPath": "/var/lib/docker/containers/8c059e84473033b3a0474b8a9a4beb8f848f003492d87ba6865f93863ce889b2/8c059e84473033b3a0474b8a9a4beb8f848f003492d87ba6865f93863ce889b2-json.log",

この場合、/var/lib/docker/containers/8c059e84473033b3a0474b8a9a4beb8f848f003492d87ba6865f93863ce889b2 配下が実態のディレクトリとなります。


前回の問題の解答

解答2. イメージの場所は解答1から予想が付きます。

$ ls /var/lib/docker/image
overlay2

しかし、これは外れです。メタデータなどしかありません。

実態のデータはこちらにあります。

/var/lib/docker/overlay2

配下に複雑な文字列のディレクトリがならんでいて、その中に実態となるデータが保存されます。


前回の問題の解答

解答3. 例として、2つインスタンスが起動しているdockerのプロセスを見てみます。

PID   USER     TIME  COMMAND
    8 root      0:14 dockerd
   35 root      0:02 containerd --config /var/run/docker/containerd/containerd.toml --log-level debug
 2860 root      0:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.co
 2877 root      0:00 /bin/sh
 7628 root      0:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.co
 7648 root      0:00 /bin/bash

PID が 8 と 35 は Docker エンジン自体のプロセスです。

一方、 PID 2866 と 2877、7628 と 7648 は別のインスタンスです。

変な感じですが、OSから直接見たプロセス(の実行コマンド)には差が無いように見えます。

この辺は、今後 学習を進めていく中で疑問が解消できると良いかと思います。


ハンズオン


今回のゴール

  • docker インスタンスから新たなイメージを作成して、そのインスタンスを作成できるようになる。
  • dockerfile から新たなイメージを作成すること
  • dockerコンテナ同士を連携させること

今回やらないこと

  • コンテナ/イメージを分析したり、たくさん操作したりはしません。
  • kubernetes / docker-compose のようなオーケストレーションツールの使いません。

これから進める中で必ず理解しておいて欲しいこと

Docker イメージはレイヤーになっている

Dockerコンテナ実行とは、イメージ内のプログラムを実行

この概念は、イメージを作成していく上で重要な概念となります。理解できていない人は、上の画像をクリックして該当箇所を学習した方が良いでしょう。


イメージ・レイヤ作成


インスタンスからイメージ・レイヤを作成する

Docker イメージ・レイヤを作成するイメージとコマンドは下記です。

Dockerイメージを作る"docker commit"

利用したコンテナからイメージを生成します。

docker commit {コンテナ名/インスタンス名} {イメージ名}

このとき、実態としてイメージからコンテナ化した後の作業・設定などを反映した状態がイメージとして保存されます。


docker commit ハンズオン

下記の通り、docker コンテナを作成および操作します。※コメントは無視して入力してください。

# 軽量Linux "Alpine Linux"の最新版コンテナを pull して、起動(run)する
docker run -it --name alpine-linux-test alpine:latest

# パッケージ群を最新化する
apk update

# 開発に便利なパッケージを追加する
apk --update add python3 python3-dev zsh vim tmux git tig wget

# Pythonのパッケージ管理ツールを最新化する
pip3 install --upgrade pip

# Pythonでよく使うモジュールをインストールする
pip install flask requests

# Alpineパッケージのキャッシュデータを削除する(軽量化)
rm -rf /var/cache/apk/*

ここまで実行したら、Ctrl-p Ctrl-q でデタッチして、ホストのシェルに戻ってインスタンス状態を確認します。

$ docker ps      
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
d813f16ccdf5        alpine              "/bin/sh"           7 minutes ago       Up 7 minutes                            alpine-linux-test

この状態のインスタンスから、現時点のイメージを生成します。

docker commit alpine-linux-test mydev-tools

そうした上で、Docker イメージの状態を確認します。

$ docker images
REPOSITORY       TAG        IMAGE ID            CREATED             SIZE
mydev-tools      latest     50ad99bbf7b5        34 seconds ago      158MB

このまま、新たにインスタンスを生成して、今度はバックグラウンド起動します。

$ docker run -it -d --name dev-env1 mydev-tools
4d68811389f344265f82c923d00d264524b14fb507d42b199b241be6f1f73ec3

再度インスタンスの一覧を表示します。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
589bdf4b45c4        mydev-tools         "/bin/sh"           15 seconds ago      Up 14 seconds                           dev-env1
d813f16ccdf5        alpine              "/bin/sh"           14 minutes ago      Up 14 minutes                           alpine-linux-test

これでインスタンスを複製することに成功しました。
NAMES値だけ変えれば大量のインスタンスを短時間に生成することも可能です。


補足:docker commit コマンド

$ docker commit --help 

Usage:  docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

Create a new image from a container's changes

Options:
  -a, --author string    Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
  -c, --change list      Apply Dockerfile instruction to the created image
  -m, --message string   Commit message
  -p, --pause            Pause container during commit (default true)

実際には、-a で作成者を、-m で作成時の記録を残すのが実用的ですね。


dockerイメージを別サーバへ移す

別のサーバへ移行して、コンテナを動作させたい場合があるはずです。

そのような場合、下記の手順で実行します。

  1. dockerインスタンスを export する
  2. exportした1のデータを移行先に移動/複製する
  3. 新たなイメージとして、2で配置したデータを import する
  4. import したイメージからコンテナを生成・起動する

export / import の書式はこちらです。

docker export [Container ID | NAMES] -o [出力ファイル名].tar
docker import [ファイル名|URL] [REPOSITORY[:TAG]]

docker export / import ハンズオン

ここでは、「docker commit ハンズオン」で生成したイメージを export / import してみます。

まず、イメージを export します。

docker export dev-env1 -o dev-env.tar

本来であれば、scp や sftp などでサーバ間でデータを移動させるはずですが、今回は同じ docker 内で実施します。

次に、異なる名前でイメージとしてインポートします。

docker import dev-env.tar dev-env-copy

再度イメージを確認すると、下記のとおりにインポートに成功しています。

$ docker images
REPOSITORY       TAG        IMAGE ID            CREATED             SIZE
dev-env-copy     latest     98d5d7aeebb1        10 seconds ago      158MB
mydev-tools      latest     50ad99bbf7b5        34 minutes ago      158MB

ここからインスタンスを生成すれば、そのまま異なるサーバの docker で同じ内容のインスタンスを動作させることが可能です。


補足:docker export コマンド

$ docker export --help

Usage:  docker export [OPTIONS] CONTAINER

Export a container's filesystem as a tar archive

Options:
  -o, --output string   Write to a file, instead of STDOUT

実際には、-o 以外は指定するものがほぼありません。


補足:docker import コマンド

$ docker import --help

Usage:  docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]

Import the contents from a tarball to create a filesystem image

Options:
  -c, --change list      Apply Dockerfile instruction to the created image
  -m, --message string   Set commit message for imported image

オプション無しでも十分に動作しますが、実用的には -m で記録を残すのが良いでしょう。


補足:docker save コマンド

$ docker save --help  

Usage:  docker save [OPTIONS] IMAGE [IMAGE...]

Save one or more images to a tar archive (streamed to STDOUT by default)

Options:
  -o, --output string   Write to a file, instead of STDOUT

こちらの記事によると、export との違いはこちらだそうです。

・save 上記のレイヤーやタグといったメタ情報含めてコンテナをtarでまとめる。
・export ファイルシステムを愚直にtarでまとめ、メタ情報は無視される。

また、上記の主語として、save はイメージ、export はインスタンス(コンテナ)が対象になります。

通常は export の方が容量が少なくて良いですが、より正確さ(再現性?)を求めるなら save が良さそうです。


補足:docker load コマンド

$ docker load --help

Usage:  docker load [OPTIONS]

Load an image from a tar archive or STDIN

Options:
  -i, --input string   Read from tar archive file, instead of STDIN
  -q, --quiet          Suppress the load output

save したデータは load コマンドでしかインポートできません。


練習問題

どれかインスタンスを save した後、
同じホストの docker で load すると、
どのようなことが起こるか確認してください。


イメージを設計書から作る


dockerfile = イメージの設計書

dockerfile とは、下記のように説明されています。

Docker Image は Dockerfile というファイルを記述し、そのファイルを元にビルドすることでスナップショットの作成ができます。

dockerfile のイメージ

引用元: dockerfile - 入門 Docker

つまり、端的にはイメージを作る元ファイルと考えましょう。


dockerfile の書き方

サンプルとして、すべての命令を書いた dockerfile をご覧ください。

dockerfile
# ベースとなるイメージを指定する
# ローカルになければ Docker Hub から Pull
FROM ubuntu:latest

# 生成するイメージの作者を設定する
MAINTAINER syamane

# apt で最新化後、vim をインストールコマンドを実行する(シェル方式)
RUN apt update && apt install -y vim

# vim の設定ファイルを作成(JSON形式で指定)
RUN ["/bin/bash", "-c", "echo", "set", "nowrap", ">", "/root/.vimrc"]

# リッスンするポートを指定する
EXPOSE 5900

# 環境変数を設定する
ENV PATH=/root:$PATH

# 命令実行時の作業ディレクトリを指定する
WORKDIR /root

# ローカルファイルをコンテナ内にコピーする(今回はコメントアウト)
# ダウンロード元URL もしくは 実行ディレクトリ配下のパスを指定可
# 圧縮ファイルを指定すれば自動解凍する
# ADD *.txt /root/

# ローカルファイルをコンテナ内にコピーする(今回はコメントアウト)
# 実行ディレクトリ配下のみ指定可
# COPY *.log /root/

# vim をヘルプが開いた状態でフォアグラウンド起動する
# シェルを指定すれば、シェルでの操作ができる
CMD ["vim", "-c", "help"]

他にも VOLUME / LABEL / USER / ARG / ONBUILD / STOPSIGNAL / HEALTHCHECK / SHELL がありますが、書かなくてもイメージは作れます。(もちろん書けた方が良いですが)

各命令の詳細な解説は、下記を参考にしましょう。
* Dockerfile リファレンス — Docker-docs-ja 1.9.0b ドキュメント
* Dockerfile のベストプラクティス — Docker-docs-ja 1.9.0b ドキュメント
* Dockerfile を書くためのベストプラクティス解説編


dockerイメージのビルド

dockerfile と イメージに ADD/COPY したいファイルがあるディレクトリで、下記のコマンドを実行します。

docker build -t testimage:1.0 .

この書式は下記の通りです。

docker build [-t {イメージ名}[:{タグ名}]] {Dockerfileのあるディレクトリ}

最後のディレクトリ指定ですが、「.」つまりカレントディレクトリを指定することが多いです。

このコマンドを実行して、エラーが出なければ下記のように成功を示すメッセージを出力して完了です。

$ docker build -t test:1.0 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM alpine:latest
 ---> 961769676411
Step 2/2 : CMD echo hello
 ---> Running in e1c85ea06aba
Removing intermediate container e1c85ea06aba
 ---> 73e67f49d233
Successfully built 73e67f49d233
Successfully tagged test:1.0

$$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test                1.0                 73e67f49d233        47 seconds ago      5.58MB

dockerfile 書き方あるある


docker build や docker run が上手くいかない場合は?

下記の記事を参考に問題を調査してみましょう。


補足:docker build コマンド

非常に沢山のオプションがあって複雑です。

$ docker build --help

Usage:  docker build [OPTIONS] PATH | URL | -

Build an image from a Dockerfile

Options:
      --add-host list           Add a custom host-to-IP mapping (host:ip)
      --build-arg list          Set build-time variables
      --cache-from strings      Images to consider as cache sources
      --cgroup-parent string    Optional parent cgroup for the container
      --compress                Compress the build context using gzip
      --cpu-period int          Limit the CPU CFS (Completely Fair Scheduler) period
      --cpu-quota int           Limit the CPU CFS (Completely Fair Scheduler) quota
  -c, --cpu-shares int          CPU shares (relative weight)
      --cpuset-cpus string      CPUs in which to allow execution (0-3, 0,1)
      --cpuset-mems string      MEMs in which to allow execution (0-3, 0,1)
      --disable-content-trust   Skip image verification (default true)
  -f, --file string             Name of the Dockerfile (Default is 'PATH/Dockerfile')
      --force-rm                Always remove intermediate containers
      --iidfile string          Write the image ID to the file
      --isolation string        Container isolation technology
      --label list              Set metadata for an image
  -m, --memory bytes            Memory limit
      --memory-swap bytes       Swap limit equal to memory plus swap: '-1' to enable unlimited swap
      --network string          Set the networking mode for the RUN instructions during build (default "default")
      --no-cache                Do not use cache when building the image
      --pull                    Always attempt to pull a newer version of the image
  -q, --quiet                   Suppress the build output and print image ID on success
      --rm                      Remove intermediate containers after a successful build (default true)
      --security-opt strings    Security options
      --shm-size bytes          Size of /dev/shm
  -t, --tag list                Name and optionally a tag in the 'name:tag' format
      --target string           Set the target build stage to build.
      --ulimit ulimit           Ulimit options (default [])

単純にイメージを生成する以外に、リソースを制御することも可能です。


dockerfile からイメージを作るハンズオン

「docker commit ハンズオン」で作成したコンテナと同じイメージを作る dockerfile は下記です。

Dockerfile
FROM alpine:latest
CMD apk update \
 && apk --update add python3 python3-dev zsh vim tmux git tig wget \
 && pip3 install --upgrade pip \
 && pip install flask requests \
CMD rm -rf /var/cache/apk/*
COPY test.txt /test.txt

なお、イメージ内にホスト上のファイルを追加する記述をいれましたので、そのファイルを作成します。

test.txt
DOCKER HANDSON TEST

先程の dockerfile と test.txt を同じディレクトリに配置して、ビルドします。

docker build -t dev-tools-img .

実行するとこんな感じです。

Sending build context to Docker daemon  3.072kB
Step 1/5 : FROM alpine:latest
 ---> 961769676411
Step 2/5 : CMD apk update  && apk --update add python3 python3-dev zsh vim tmux git tig wget  && pip3 install --upgrade pip  && pip install flask requests CMD rm -rf /var/cache/apk/*
 ---> Running in 8e625e558d82
Removing intermediate container 8e625e558d82
 ---> eb9b1153bdd2
Step 3/5 : COPY test.txt /tmp/
 ---> 90ff3bf6c49c
Step 4/5 : CMD cat /test.txt
 ---> Running in 297faf85f943
Removing intermediate container 297faf85f943
 ---> 24bce8916152
Step 5/5 : CMD echo "Complete!"
 ---> Running in 93d43c43f9a9
Removing intermediate container 93d43c43f9a9
 ---> 600d60d001e1
Successfully built 600d60d001e1
Successfully tagged dev-tools-img:latest

実際にインスタンスを作ってみましょう。

$ docker run --name dev-env3 dev-tools-img
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
7b29bbf49e46        mydev-tools         "/bin/sh"           29 minutes ago      Up 29 minutes                           dev-env1

これでオリジナルのイメージを作成して、そこからインスタンスを生成することができました。

あとは、どんな dockerfile を書くかにかかっているわけです。


dockerfile ハンズオン


dockerfile に慣れるハンズオン

とにかく沢山の Dockerfile からイメージを作って、コンテナを作成してみましょう。


意味のない ubuntu イメージ

Dockerfile
FROM ubuntu
COPY hello.txt /tmp/hello.txt
CMD ["cat", "/tmp/hello.txt"]

もちろんビルドします。

docker build -t hello .

出典:dockerfile - 入門 Docker


サービスをいくつか追加した ubuntu イメージ

Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server

今回は dockerfile を指定してみます。

docker build -t build:sample -f dockerfile .

追加しただけで、普通のOSと同じ状態でサービス起動は設定してません。

出典:Dockerfileを使った基本的なDockerの動作 - デベロッパー・コラボ


ApacheでHTTPサーバを実行する。

Dockerfile
FROM centos
MAINTAINER Admin <admin@admin.com>
RUN echo "now running..." \
    yum update \
    yum install -y httpd
EXPOSE 80
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]

※元の内容を一部改変しました。

ビルドします。

docker build -t samplehttp .

実際にインスタンスを作ってみます。

docker run --name ap1 -d -p 80:80 samplehttp

80番ポートにアクセスして、動作できればコンテナを停止しましょう。

出典:Dockerfileを書いてみる - Qiita


ip-api.com へのリバースプロキシ

Dockerfile
FROM centos:6
RUN set -x && \
    yum install -y epel-release && \
    yum install -y nginx && \
    sed -i -e "s/index  index.html index.htm/proxy_pass http:\/\/ip-api.com\/json/" \
        /etc/nginx/conf.d/default.conf && \
    ln -sf /dev/stdout /var/log/nginx/access.log && \
    ln -sf /dev/stderr /var/log/nginx/error.log
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

キチンとビルドします。

docker build -t reverse-proxy .

実際に走らせてみましょう。

docker run --name rp1 -d -p 80:80 reverse-proxy

80番ポートにアクセスして、動作できればコンテナを停止しましょう。(前回のキャッシュが残っていないかは要注意)

出典:効率的に安全な Dockerfile を作るには - Qiita


複雑な nginx サーバーのイメージ

下記の2つのファイルを作成します。

default.conf
server {
        listen 8080 default_server;
        listen [::]:8080 default_server;

        root /app;

        location / {
        }
}
index.html
<html>
<body>
<h1>TEST PAGE</h1>
sample index file
</body>
</html>
Dockerfile
FROM alpine:3.6

# nginxのインストール
RUN apk update && \
    apk add --no-cache nginx

# ドキュメントルート
ADD app /app
ADD default.conf /etc/nginx/conf.d/default.conf

# ポート設定
EXPOSE 8080

RUN mkdir -p /run/nginx

# フォアグラウンドでnginx実行
CMD nginx -g "daemon off;"

ビルドします。

docker build -t alp_nginx .

インスタンスを作って、実行してみます。

docker run -it --name nginx2 -p 80:8080 alp_nginx

80番ポートに接続して、動作できればコンテナを停止しましょう。(前回のキャッシュが残っていないかは要注意)

出典:お気楽にnginxのDockerイメージを作ってみる - Qiita


実用的な開発環境のイメージ

下記の記事の内容を dockerfile で用意してみます。

Dockerでサクッと使い捨ての開発環境を用意する | DevelopersIO

Dockerfile
FROM ubuntu:latest
RUN apt update -y \
 && apt install curl vim git net-tools build-essential -y
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \
 && apt install nodejs -y \
 && node -v
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt update -y && apt install yarn -y
RUN yarn -v

ビルドします。結構長い時間かかります。

docker build -t dev_container .

インスタンスを作って、実行してみます。

docker run -it --name devcon1 dev_container

無事に開発環境に入れたら、Node.js のバージョンを確認してみます。

node -v

バージョンが表示されたらOKです。


NGINX公式イメージ

GitHubの下記のリポジトリから dockerfile をダウンロードして実行してみます。

nginxinc/docker-nginx: Official NGINX Dockerfiles

git clone https://github.com/nginxinc/docker-nginx.git
cd docker-nginx/stable/alpine/
docker build -t ngx1 .

あとはインスタンスを作って、実行してみます。

docker run -it -d --name n1 -p 80:80 ngx1

無事に80番ポートにアクセスできたらOKです。

このように、GitHub にはたくさんの Dockerfile が公開されています。ぜひ参考にしてみて下さい。

Search · Dockerfile - GitHub


問題(宿題)

CentOS を最新状態にアップデートして、nginx をインストール&起動した状態のDockerイメージ「nginx1」を作成してください。

また、完了した後は下記を調べて、後始末をしましょう。

  1. 起動しているインスタンスを一括停止するコマンドは?
  2. 起動しているインスタンスを一括削除するコマンドは?
  3. 起動しているイメージを一括削除するコマンドは?

さらに Dockerfile 力をレベルアップするためには

下記のようなコンテンツを読むと良いかもしれません。


おわりに

今回は docker イメージを作成する方法について、ハンズオンをしました。

ここまで自由にイメージが作れるようになると、だんだん楽しくなってきたのではないでしょうか。

こうして作成したイメージから生成するコンテナの真骨頂は、大量のコンテナ操作定型的な運用操作の効率化にあります。今後はDocker-compose や Kubernetes といったツールを学んでいきたいところですが、

なお、次はコンテナを管理していく方法について、ハンズオンを行っていきます。

コンテナ技術 ハンズオン Vol.3 Docker Compose編


参考

これまでに紹介した記事以外に、本ハンズオン資料を作成する上で下記の記事を参考にさせていただきました。

大変ありがとうございました。

3
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4