本題の前に
位置付け・前提
本記事は、社内ワークショップ のために用意したものです。そのため、次の前提で進めることをご理解ください。
また、本稿はこちらの続きとなります。
目的や前提なども上記の内容に準拠します。
docker ハンズオン環境
今回も Play with Docker を活用してハンズオンを行います。
※要:Docker Hub アカウント
注意点として、この環境は4時間の制限があります。4時間経過すると環境が削除されてしまう点を注意して下さい。
もちろんローカル環境でも構いません。
参加者の前提知識
Dockerfile および Docker レイヤーの考え方を理解していることが前提とします。参考までに下記の資料を一読しておくことをお奨めします。
HTMLページが良ければ下記でもOKです。
Dockerfile のベストプラクティス — Docker-docs-ja 1.9.0b ドキュメント
ハンズオンの前に
前回の問題
前回のハンズオンで最後に出した問題はこちら。
- 起動しているコンテナはホストのどのディレクトリにあるでしょうか。
- dockerイメージはどこにあるでしょうか。
- 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 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イメージを別サーバへ移す
別のサーバへ移行して、コンテナを動作させたい場合があるはずです。
そのような場合、下記の手順で実行します。
- dockerインスタンスを export する
- exportした1のデータを移行先に移動/複製する
- 新たなイメージとして、2で配置したデータを import する
- 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 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 書き方あるある
- Dockerfile の ADD と COPY の違いを結論から書く - Qiita
- DockerのRUNとCMDの違い - Qiita
- Dockerfile内のENTRYPOINTとCMD(とRUN) の使い分け
-
Dockerfileを書く時の注意とかコツとかハックとか | kim hirokuni
- CMDでコンテナをバイナリのように扱う
- CMDとENTRYPOINTの違い
- Dockerで複数CMDを実行する方法 | エンジニアの眠れない夜
docker build や docker run が上手くいかない場合は?
下記の記事を参考に問題を調査してみましょう。
- dockerでコンテナが立ち上がらないときやってみること - Qiita
- Dockerイメージのビルド中にExitedしたコンテナに入る方法 - Namiking.net
- Docker調査 ~ログ編~ - Qiita
補足: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 は下記です。
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
なお、イメージ内にホスト上のファイルを追加する記述をいれましたので、そのファイルを作成します。
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 イメージ
FROM ubuntu
COPY hello.txt /tmp/hello.txt
CMD ["cat", "/tmp/hello.txt"]
もちろんビルドします。
docker build -t hello .
サービスをいくつか追加した ubuntu イメージ
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サーバを実行する。
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番ポートにアクセスして、動作できればコンテナを停止しましょう。
ip-api.com へのリバースプロキシ
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つのファイルを作成します。
server {
listen 8080 default_server;
listen [::]:8080 default_server;
root /app;
location / {
}
}
<html>
<body>
<h1>TEST PAGE</h1>
sample index file
</body>
</html>
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
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 が公開されています。ぜひ参考にしてみて下さい。
問題(宿題)
CentOS を最新状態にアップデートして、nginx をインストール&起動した状態のDockerイメージ「nginx1」を作成してください。
また、完了した後は下記を調べて、後始末をしましょう。
- 起動しているインスタンスを一括停止するコマンドは?
- 起動しているインスタンスを一括削除するコマンドは?
- 起動しているイメージを一括削除するコマンドは?
さらに Dockerfile 力をレベルアップするためには
下記のようなコンテンツを読むと良いかもしれません。
- Dockerfileを改善するためのBest Practice 2019年版
- 【翻訳】いいDockerイメージを構築するには? ーDockerfileのベストプラクティス | POSTD
- docker buildを速くするコツ - keroxpのScrapbox
- 改めてDockerfileのベストプラクティスを振り返ろう|JapanContainerDays|動画 | crash.academy
おわりに
今回は docker イメージを作成する方法について、ハンズオンをしました。
ここまで自由にイメージが作れるようになると、だんだん楽しくなってきたのではないでしょうか。
こうして作成したイメージから生成するコンテナの真骨頂は、大量のコンテナ操作や定型的な運用操作の効率化にあります。今後はDocker-compose や Kubernetes といったツールを学んでいきたいところですが、
なお、次はコンテナを管理していく方法について、ハンズオンを行っていきます。
コンテナ技術 ハンズオン Vol.3 Docker Compose編
参考
これまでに紹介した記事以外に、本ハンズオン資料を作成する上で下記の記事を参考にさせていただきました。
- Dockerコンテナをexportして別のサーバでimportして動作させる - かべぎわブログ
- Dockerハンズオン基礎編:コンテナとイメージのライフサイクルを理解 - Qiita
- Dockerハンズオン資料 - Qiita
- Docker ハンズオン - 基本コマンド編 - Qiita
- こうしたいんだぜ! という時の逆引きdockerコマンド | 株式会社ビヨンド
大変ありがとうございました。