最近ちょっとDockerを入門しました。
アウトプットも兼ねて、ハンズオンっぽくまとめてみたので公開したいと思います。
まだ「Dockerを触ったことない」という方の入門に使って頂けたら幸いです。
※出力結果のすべては記載できていません。(ハンズオンで一緒に操作して確認しながら進める想定だったため)
目次
- 事前準備(環境構築)
- Docker概要
- ハンズオン1:基本操作
- ハンズオン2:マウント
- ハンズオン3:Dockerfile
- ハンズオン4:マルチステージビルド
- おわり
事前準備(環境構築)
公式ドキュメントなど参考に構築してください。
Docker Documentation - Get started with Docker
Docker Hubアカウントをお持ちであれば、Play with Dockerが便利です。
インストールなしでDockerを試すことができます。Login、Start、ADD NEW INSTANCEするだけです。
参考:Docker 入門にはインストールなしで使える「Play with Docker」がいいと思う
Windowsであれば、自分も以下の記事を書いています。良ければ参考にしてみてください。
VirtualBoxとVagrantでDocker環境構築
※以降の内容は、上記記事で環境構築した前提で進めています。
※別の方法で構築した場合は IPアドレス など適宜読み替えてください。
Docker概要
ハンズオンに入る前にDockerとは?を説明したいと思います。
とはいえ、Dockerで検索すると良記事がたくさん見つかるので大雑把にざっくりと説明します。
Dockerとは?
”コンテナ”を利用した基盤ソフトウェアです。
コンテナ型仮想化技術と言われたりします。
基盤ソフトウェアとは、
高度なアプリケーションを構築するための基盤となるソフトウェアのことです。
(オペレーティング・システムやミドルウェア、言語処理系など)
コンテナとは?
OSの基本コマンドやアプリケーションなどの実行環境全体をパッケージ化して、
それらを分離された空間で実行する技術です。
ホストOS上で独立したプロセスとして実行されます。
コンテナはDocker独自の技術ではなく、Linuxカーネルのcgroupやnamespaceなどを使用して実現しています。
cgroup:プロセスグループのリソース(CPU、メモリ、ディスクI/Oなど)の利用を制限・隔離する機能。
namespace:名前空間内のプロセスに対して、専用のリソースを持っているかのように見せる仕組み。
コンテナを利用したソフトウェアはDocker以外にもいくつか存在します。
例:Linuxコンテナ(LXC)、LXD、Virtuozzo、OpenVZ
物理基盤、ハイパーバイザー型、コンテナの比較
簡単にですが比較した図を用意してみました。
ざっくりなので省略してしまっている箇所もあるかもですが、イメージをつかんでもらえればと思います。
Dockerの構成要素
Dockerの構成要素がざっくりわかるように図にしてみました。
こちらも省略している箇所がありますが、イメージをつかんでもらえればと思います。
以下簡単にですが、各要素の説明です。
・Dockerエンジン:アプリケーションのパッケージ化やコンテナを実行する。Dockerの主機能。
・Dockerクライアント:Dockerを操作するためのコマンドライン・インターフェース(CLI)。Dockerエンジンに含まれる。
・Dockerイメージ:OSやアプリケーションを含んだテンプレート。コンテナ作成のもとになる。
・Dockerコンテナ:アプリケーションの実行するための分離された空間。イメージから作成される。
・レジストリ:イメージを公開・共有する為のイメージ保管場所。リポジトリという単位で保管されている。
・リポジトリ:同じ種類のDockerイメージの集まり(タグが付与されバージョン分けされたDockerイメージの集まり)
レジストリにはDocker公式のDocker Hub以外にも
Amazon Elastic Container Registry、Google Container Registry、プライベートレジストリなどがあります。
Dockerに向くシステム、向かないシステム
参考にした本にDockerに向くシステム、向かないシステムについて記載があったので紹介したいと思います。
Dockerに向くシステム
・システムの改変、開発が頻発
・比較的、可用性を厳密に求めない
・サポートのないOSSを利用する
・システムの問題を自社で解決できる
例:
オンラインゲームサーバー
サーバーホスティング
広告・検索ビッグデータ基盤
Dockerに向かないシステム
・システムの改変をほとんど行わない
・商用のOSとアプリが稼働し保守が必要
・可用性、信頼性、機密性が求められる
・システム停止が社会的影響を及ぼす
例:
原子力発電所制御システム
銀行・証券オンライン基盤
航空・鉄道オンライン基盤
ざっくりですが、以上でDocker概要は終わりにしたいと思います。
次項からハンズオンをやっていきます。
#Dockerコマンドについて
Dockerコマンドには新旧で多くの違いがあります。このハンズオンでは新しいコマンドを紹介します。
そのため、過去にDockerを学んだことがある方には若干の違和感があるかと思います。
コンテナ一覧表示
$ docker container ls # 新
$ docker ps # 旧
以下はコマンドラインを表しています。]$
、]#
以降がコマンドになります。
[vagrant@handson ~]$
[root@test01 /]#
以下のようにコマンド後の#
はコメントです。実行時は入力不要です。
[vagrant@handson ~]$ docker search centos # イメージの検索
以下のようにコマンドが長い場合は\
で改行しています。
[vagrant@handson ~]$ docker container stats -a --no-stream \
--format "table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemPerc}}\t{{.MemUsage}}\t{{.PIDs}}"
ハンズオン1:基本操作
Dockerコマンドを使用したイメージの取得やコンテナの起動など、基本操作をやっていきます。
イメージの検索、取得、一覧表示
まずは、コンテナのもとになるイメージの取得と取得したイメージの確認からやっていきます。
[vagrant@handson ~]$ docker search --filter "is-official=true" centos # イメージの検索
[vagrant@handson ~]$ docker image pull centos # イメージの取得
[vagrant@handson ~]$ docker image ls # 取得したイメージ一覧表示
[vagrant@handson ~]$ docker image ls --no-trunc # イメージIDを短縮せず表示
[vagrant@handson ~]$ docker image ls -q # イメージIDのみ表示
[vagrant@handson ~]$ docker image ls --no-trunc -q # 同時に指定可能
docker search
では--filter "is-official=true"
をオプションで指定することでDocker Official Imagesのみ表示しています。
イメージ削除、イメージ取得(タグ指定あり)
docker search
では表示されませんが、イメージにはタグ(TAG)が付けられています。
docker image pull
でタグを指定せずに取得するとイメージの最新版(latest)が取得されます。
タグはDockerHubで検索すると確認できます。
イメージを削除する場合はdocker image rm <イメージ名>:<タグ名>
で削除します。
[vagrant@handson ~]$ docker image rm centos:latest # イメージの削除
[vagrant@handson ~]$ docker image pull centos:7 # タグを指定してイメージ取得
イメージは同じでタグが違うものを削除した場合は、タグのみが削除されます。
イメージの削除はスペース区切りで複数指定可能です。
コンテナを起動してコマンド実行
[vagrant@handson ~]$ docker container run -i -t centos:7 date # コンテナを起動しコマンド実行
[vagrant@handson ~]$ docker container run -it centos:7 date -d tomorrow # `-i`と`-t`は`-it`でも可
[vagrant@handson ~]$ docker container run -it centos:7 date "+%Y/%m/%d" -d "30 days"
-i:コンテナの STDIN にアタッチ。標準入力を受け付ける。
-t:疑似ターミナル (pseudo-TTY) を割り当て。
コンテナ一覧表示(停止中のコンテナを含む全コンテナ一覧)
[vagrant@handson ~]$ docker container ls -a # コンテナ一覧表示
コマンド実行に使用したコンテナが表示されます。
コンテナを名前付きで起動
コンテナ一覧で表示されるNAMES
は指定することができます。
[vagrant@handson ~]$ docker container run -it --name test01 centos:7 date # コンテナを名前付きで起動
[vagrant@handson ~]$ docker container ls -a
[vagrant@handson ~]$ docker container run -it --name test01 centos:7 date # 使用されているコンテナ名を指定するとエラーになる
##停止中のコンテナ削除
docker container rm <コンテナ名 or CONTAINER ID>
で停止中のコンテナを削除できます。
docker container prune
を使用すると停止中の全コンテナを削除できます。
[vagrant@handson ~]$ docker container rm test01 # 停止中のコンテナの削除 (スペース区切りで複数指定可能)
[vagrant@handson ~]$ docker container ls -a
[vagrant@handson ~]$ docker container prune # 停止中の全コンテナの削除
Are you sure you want to continue? [y/N] y # y を応答
[vagrant@handson ~]$ docker container ls -a
コンテナ起動(削除オプション付き)
コマンドを実行するだけの一時的なコンテナを毎回削除するのはめんどうです。
コンテナが停止すると自動でコンテナを削除する--rm
オプションがあります。
[vagrant@handson ~]$ docker container run --rm -it \
--name rmtest01 centos:7 cat /etc/redhat-release # コンテナ停止で削除オプション付き
[vagrant@handson ~]$ docker container ls -a # 起動したコンテナが存在しないことを確認
コンテナ起動、ログイン
これまではコンテナを起動しコマンドを実行するのみでしたが、
次はコンテナにログインしてみたいと思います。
[vagrant@handson ~]$ docker container run --rm -it --name test01 centos:7 /bin/bash # コンテナを起動しログイン
[root@dd6149b65301 /]# # コンソールが表示される
[root@bad50470fb4f /]# hostname # DockerによってコンテナIDがホスト名になる
bad50470fb4f
[root@dd6149b65301 /]# exit # コンテナから離脱、コンテナは停止する
ホスト名を指定してコンテナ起動、ログイン
ホスト名を指定しない場合、コンテナIDがホスト名になりますが、
ホスト名は起動時に指定することができます。
ログイン後にいくつかコンテナ内でコマンドを実行してみます。
[vagrant@handson ~]$ docker container run -it --name test01 -h test01 centos:7 /bin/bash
[root@test01 /]#
[root@test01 /]# hostname # ホスト名が起動時に指定したtest01になっている
test01
[root@test01 /]# echo "Docker container test01" > /test01.txt # ファイルを追加してみる。
[root@test01 /]# cat /test01.txt
Docker container test01
[root@test01 /]# ping -c 3 192.168.10.10 # Dockerのホストマシン(Vagrantで起動したゲストマシン)と通信できる
[root@test01 /]# ping -c 3 google.com # 外部サイトにもアクセスできる
[root@test01 /]# ip addr # IPアドレスの確認コマンドがインストールされていない
bash: ip: command not found
[root@test01 /]# yum install -y iproute # yumでインストール可能
[root@test01 /]# ip addr # コンテナのIPアドレスを確認
ログイン中のコンテナを起動した状態で離脱
ログインしたコンテナでexit
を実行するとコンテナが停止してしまいます。
Ctrl + p
を入力後 Ctrl + q
を入力することでコンテナを起動した状態で離脱できます。
`Ctrl + p` を入力後 `Ctrl + q` を入力しコンテナから離脱
[vagrant@handson ~]$ docker container ls # 起動中のコンテナを表示
コンテナでの作業ログ確認
コンテナ内で作業した内容を確認することができます。
[vagrant@handson ~]$ docker container logs test01 | less # lessの終了は q を入力
[vagrant@handson ~]$ docker container logs -t test01 | less # 時刻付きで表示
ログの実態はホストOS上の以下のファイルに格納されています。コンテナを削除すると消えます。
/var/lib/docker/containers/<CONTAINER ID>/<CONTAINER ID>-json.log
コンテナからイメージ作成
コンテナからイメージを作成することができます。
コンテナ上で作業した内容を含んだ状態のイメージが作成できます。
「ホスト名を指定してコンテナ起動、ログイン」で起動したコンテナからイメージを作成してみます。
[vagrant@handson ~]$ docker container commit test01 centos:test01 # コンテナから新しいイメージ作成
[vagrant@handson ~]$ docker image ls # 新しいコンテナが作成されていることを確認
[vagrant@handson ~]$ docker container run -it --name test02 -h test02 centos:test01 /bin/bash # 作成したイメージでコンテナ起動
[root@test02 /]# cat /test01.txt # test01のコンテナで作成したファイルが存在するか確認
Docker container test01
[root@test01 /]# ip addr # test01でインストールしたコマンドも存在するか確認
[root@test02 /]# echo "Docker container test02" > /test02.txt # ファイルを追加してみる。
[root@test02 /]# cat /test02.txt
Docker container test02
[root@test02 /]# exit
[vagrant@handson ~]$ docker container ls -a
[vagrant@handson ~]$ docker container commit test02 centos:test02 # 停止したコンテナからもイメージ作成可能
[vagrant@handson ~]$ docker image ls
[vagrant@handson ~]$ docker container run -it --name test03 -h test03 centos:test02 /bin/bash
[root@test03 /]# cat /test01.txt test02.txt # test01,test02で作成したファイルが存在することを確認
Docker container test01
Docker container test02
[root@test03 /]# exit
起動中のコンテナの削除
停止済みのコンテナについてはオプションなしで削除できます。
起動中のコンテナは-f
を付けることで強制的に削除できます。
[vagrant@handson ~]$ docker container ls -a
[vagrant@handson ~]$ docker container rm test01 test02 test03 # 停止しているコンテナのみ削除され、起動中のコンテナは失敗する
test02
test03
Error response from daemon: You cannot remove a running container <CONTAINER ID>. Stop the container before attempting removal or force remove
[vagrant@handson ~]$ docker container ls -a # 削除に失敗したコンテナが残っている
[vagrant@handson ~]$ docker container rm -f test01 # 起動中のコンテナを強制削除
[vagrant@handson ~]$ docker container ls -a
停止中コンテナの起動、再接続
停止中のコンテナは再度起動することができます。
また、起動したコンテナには再接続することもできます。※/bin/bash
が動いている場合
[vagrant@handson ~]$ docker container run -it --name test04 -h test04 centos:test02 /bin/bash
[root@test04 /]# exit # exitでログアウトするとコンテナは停止する
[vagrant@handson ~]$ docker container ls -a
[vagrant@handson ~]$ docker container start test04 # 停止したコンテナの起動
[vagrant@handson ~]$ docker container attach test04 # コンテナに再接続
[root@test04 /]# exit
イメージの強制削除
コンテナのもとになっているイメージはオプションなしでは削除できません。
-f
を付けることによって強制的に削除することができます。(コンテナ停止中の場合)
[vagrant@handson ~]$ docker image ls
[vagrant@handson ~]$ docker image rm centos:test02 # 存在するコンテナのもとになっているイメージは削除が失敗する
Error response from daemon: conflict: unable to remove repository reference "centos:test02" (must force) - container <CONTAINER ID> is using its referenced image <IMAGE ID>
[vagrant@handson ~]$ docker image ls # イメージが削除されていないことを確認
[vagrant@handson ~]$ docker image rm -f centos:test02 # 存在するコンテナのもとになっているイメージを強制削除する
[vagrant@handson ~]$ docker image ls # イメージが削除される
[vagrant@handson ~]$ docker container ls -a # コンテナ自体は残る
[vagrant@handson ~]$ docker container start test04 # 起動できる
[vagrant@handson ~]$ docker container attach test04 # 接続もできる
[root@test04 /]#
[root@test04 /]# exit
[vagrant@handson ~]$ docker container rm test04
起動中のコンテナのイメージ強制削除、コンテナの停止
起動中のコンテナのもとになっているイメージは削除することができません。
※強制削除してもイメージ自体は残る。
コンテナを停止することで削除することができます。
[vagrant@handson ~]$ docker container run -it --name test05 -h test05 centos:test01 /bin/bash
`Ctrl + p` を入力後 `Ctrl + q` を入力しコンテナから離脱
[vagrant@handson ~]$ docker image ls
[vagrant@handson ~]$ docker image rm -f centos:test01
Untagged: centos:test01
[vagrant@handson ~]$ docker image ls # コンテナ起動中のイメージを削除するとREPOSITORY、TAGが<none>のイメージが残り、イメージ自体は残る
[vagrant@handson ~]$ docker container ls
[vagrant@handson ~]$ docker container stop test05 # コンテナの停止
[vagrant@handson ~]$ docker image rm -f <IMAGE ID> # 停止することで削除できる
[vagrant@handson ~]$ docker image ls
[vagrant@handson ~]$ docker container ls -a
[vagrant@handson ~]$ docker container rm test05
離脱した状態でコンテナ起動
コンテナにログイン中はCtrl + p
を入力後 Ctrl + q
を入力するとコンテナから離脱できますが、
-d
を指定することで離脱した状態でコンテナを起動できます。
[vagrant@handson ~]$ docker container run -itd \
--name testd01 -h testd01 centos:7 /bin/bash # コンテナをバックグラウンドで起動する
[vagrant@handson ~]$ docker container ls
起動中のコンテナでコマンド実行
exec
を使うと起動中のコンテナでコマンドが実行できます
[vagrant@handson ~]$ docker container exec -it testd01 ps -ef # コンテナでコマンド実行
[vagrant@handson ~]$ docker container exec -it testd01 /bin/bash # exec でログインもできる
[root@test05 /]#
[root@test05 /]# ps -ef
[root@test05 /]# exit
exit
[vagrant@handson ~]$ docker container ls # exitで抜けてもrunで起動した /bin/bash とは別のためコンテナは起動したままになる
[vagrant@handson ~]$ docker container rm -f testd01
attach
を使うことで起動中のコンテナに再接続できますが、exit
で離脱するとコンテナを誤って停止してしまうことがあります。
上記の例であるようにexec
で/bin/bash
を起動して接続するのがおすすめです。
/bin/bash
を新たに起動してログインするため、/bin/bash
が起動していないコンテナにもログインできます。
コンテナ情報表示
inspect
でコンテナの詳細情報を確認することができます。
環境変数やIPアドレスなども確認することができます。
[vagrant@handson ~]$ docker container run -itd --name test01 -h test01 centos:7 /bin/bash
[vagrant@handson ~]$ docker container inspect test01 # コンテナの情報表示
[vagrant@handson ~]$ docker container rm -f test01
test01
コンテナのリソース確認
stats
でコンテナのリソース状況を確認することができます。
[vagrant@handson ~]$ docker container run -itd --name test01 -h test01 centos:7 /bin/bash
[vagrant@handson ~]$ docker container stats -a --no-stream
[vagrant@handson ~]$ docker container stats -a --no-stream \
--format "table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemPerc}}\t{{.MemUsage}}\t{{.PIDs}}"
[vagrant@handson ~]$ docker container rm -f test01
オプションや表示のフォーマットについてはドキュメント参照
https://docs.docker.com/engine/reference/commandline/stats/
ホストOSとコンテナ間のファイルやディレクトリのコピー
ホストOSと起動中のコンテナ間でファイルやディレクトリのコピーができます。
コンテナからホストOSへのコピー:
docker container cp <コンテナ名>:<コンテナ内コピー対象パス> <ホストOSコピー先パス>
ホストOSからコンテナへのコピー:
docker container cp <ホストOSコピー対象パス> <コンテナ名>:<コンテナ内コピー先パス>
[vagrant@handson ~]$ docker container run -itd --name test01 -h test01 centos:7 /bin/bash
[vagrant@handson ~]$ docker container cp test01:/etc/redhat-release ./ # test01コンテナからホストOSにファイルコピー
[vagrant@handson ~]$ ls
[vagrant@handson ~]$ docker container cp test01:/etc/redhat-release ./hoge.txt # test01コンテナから名前指定でホストOSにファイルコピー
[vagrant@handson ~]$ ls
[vagrant@handson ~]$ docker container cp ./hoge.txt test01:/
[vagrant@handson ~]$ docker container exec -it test01 ls / # コピーしたhoge.txtが確認できる
[vagrant@handson ~]$ docker container rm -f test01
ハンズオン2:マウント
マウント機能を利用することで、ホストOSのフォルダをコンテナと共有できたり、コンテナ間でボリュームを共有することができます。
マウントのタイプは以下の3種類あります。それぞれ簡単にですが紹介します。
bind
ホストOS上の任意のデバイスファイルやディレクトリをコンテナに見せることができます。
コンテナ起動時に以下のオプションを指定することでマウントできます。
--mount type=bind,src=<ホストOS上のディレクトリ>,dst=<コンテナ上のディレクトリ>
各ディレクトリは絶対パスを指定します。(ホストOS、コンテナともに)
[vagrant@handson ~]$ mkdir hostdir
[vagrant@handson ~]$ echo "Create file in host." > hostdir/hostfile01.txt
[vagrant@handson ~]$ ls hostdir/
hostfile01.txt
[vagrant@handson ~]$ cat hostdir/hostfile01.txt
Create file in host.
[vagrant@handson ~]$ docker container run -it --rm --name testbind01 -h testbind01 \
--mount type=bind,src=/home/vagrant/hostdir,dst=/ctdir01 centos:7 /bin/bash
[root@testbind01 /]#
[root@testbind01 /]# ls /ctdir01/
hostfile01.txt
[root@testbind01 /]# cat /ctdir01/hostfile01.txt # ホストOSで作成したファイルが参照可能
Create file in host.
[root@testbind01 /]# echo 'Create file in container.' > /ctdir01/containerfile01.txt # コンテナでファイル作成可能
[root@testbind01 /]# ls /ctdir01/
containerfile01.txt hostfile01.txt
[root@testbind01 /]# cat /ctdir01/containerfile01.txt
Create file in container.
[root@testbind01 /]# mkdir /ctdir01/dir01 # ディレクトリも作成可能
[root@testbind01 /]# ls ctdir01/
containerfile01.txt dir01 hostfile01.txt
[root@testbind01 /]# echo 'Create file in dir01 in container.' > /ctdir01/dir01/containerfile01.txt
[root@testbind01 /]# cat /ctdir01/dir01/containerfile01.txt
Create file in dir01 in container.
[root@testbind01 /]# rm -rf /ctdir01/dir01 # 削除可能
[root@testbind01 /]# ls /ctdir01/
containerfile01.txt hostfile01.txt
[root@testbind01 /]# exit
exit
[vagrant@handson ~]$ cat hostdir/hostfile01.txt hostdir/containerfile01.txt
Create file in host.
Create file in container.
readonly
を付けることで読み込み権限でマウントすることもできます。
[vagrant@handson ~]$ docker container run -it --rm --name testbind02 -h testbind02 \
--mount type=bind,src=/home/vagrant/hostdir,dst=/ctdir01,readonly centos:7 /bin/bash
[root@testbind02 /]#
[root@testbind02 /]# ls /ctdir01/
containerfile01.txt hostfile01.txt
[root@testbind02 /]# cat ctdir01/hostfile01.txt ctdir01/containerfile01.txt
Create file in host.
Create file in container.
[root@testbind02 /]# rm -rf ctdir01/
rm: cannot remove 'ctdir01/hostfile01.txt': Read-only file system
rm: cannot remove 'ctdir01/containerfile01.txt': Read-only file system
[root@testbind02 /]# rm -f /ctdir01/hostfile01.txt
rm: cannot remove '/ctdir01/hostfile01.txt': Read-only file system
[root@testbind02 /]# rm -f /ctdir01/containerfile01.txt
rm: cannot remove '/ctdir01/containerfile01.txt': Read-only file system
[root@testbind02 /]# exit
exit
inspect
でコンテナ情報表示することで、マウント情報も確認できます。
後続のvolume、tmpfsでも同様に確認してみてください。
[vagrant@handson ~]$ docker container run -itd --name testbind03 -h testbind03 \
--mount type=bind,src=/home/vagrant/hostdir,dst=/ctdir01,readonly centos:7 /bin/bash
[vagrant@handson ~]$ docker container inspect testbind03 |grep -A10 -e Mounts # inspectでマウント情報が確認できる
[vagrant@handson ~]$ docker container rm -f testbind03
testbind03
volume
ホストOS上のDockerで管理されるボリューム(/var/lib/docker/volumesに作成される)をコンテナに提供することができます。
コンテナ起動時に以下のオプションを指定することでマウントできます。(無い場合は作成されマウントされる)
--mount type=volume,src=<ボリューム名>,dst=<コンテナ上のディレクトリ>
[vagrant@handson ~]$ docker container run -itd --name testvol01 -h testvol01 \
--mount type=volume,src=vol01,dst=/ctdir01 centos:7 /bin/bash
[vagrant@handson ~]$ docker volume ls # ボリュームを確認
DRIVER VOLUME NAME
local vol01
[vagrant@handson ~]$ docker container exec -it testvol01 \
bash -c "ls | grep ctdir01"
ctdir01
[vagrant@handson ~]$ docker container exec -it testvol01 \
bash -c "echo 'File in container volume' > /ctdir01/testvolfile01.txt"
[vagrant@handson ~]$ docker container exec -it testvol01 \
cat /ctdir01/testvolfile01.txt
File in container volume
[vagrant@handson ~]$ sudo ls /var/lib/docker/volumes/
metadata.db vol01
[vagrant@handson ~]$ sudo ls /var/lib/docker/volumes/vol01/_data
testvolfile01.txt
[vagrant@handson ~]$ sudo cat /var/lib/docker/volumes/vol01/_data/testvolfile01.txt
File in container volume
readonlyを付けることで読み込み権限でマウントすることもできます。
また、作成されたボリュームは別のコンテナでもマウントすることができます。
[vagrant@handson ~]$ docker container run -itd --name testvol02 -h testvol02 \
--mount type=volume,src=vol01,dst=/ctdir02,readonly centos:7 /bin/bash
[vagrant@handson ~]$ docker container exec -it testvol02 \
cat /ctdir02/testvolfile01.txt # testvol01で作成したファイルが参照できる
File in container volume
[vagrant@handson ~]$ docker container exec testvol02 rm -f /ctdir02/testvolfile01.txt
rm: cannot remove '/ctdir02/testvolfile01.txt': Read-only file system
[vagrant@handson ~]$ docker container exec -it testvol02 \
bash -c "echo 'File2 in container volume' > /ctdir02/testvolfile02.txt"
bash: /ctdir02/testvolfile02.txt: Read-only file system
[vagrant@handson ~]$ docker container inspect testvol01 |grep -A10 -e Mounts
[vagrant@handson ~]$ docker container inspect testvol02 |grep -A10 -e Mounts
[vagrant@handson ~]$ docker container ls -a
[vagrant@handson ~]$ docker container rm -f testvol01 testvol02
testvol01
testvol02
[vagrant@handson ~]$ sudo cat /var/lib/docker/volumes/vol01/_data/testvolfile01.txt # コンテナを消してもボリュームは消えない
File in container volume
--volumes-from
オプションでもコンテナのボリュームを共有することができます。
コンテナ内のマウント先は、共有するボリュームのコンテナと同じ場所です。(今回の場合は/ctdir01
)
[vagrant@handson ~]$ docker container run -itd --name testvol01 -h testvol01 \
--mount type=volume,src=vol01,dst=/ctdir01 centos:7 /bin/bash
[vagrant@handson ~]$ docker container run -itd --name testvol03 -h testvol03 --volumes-from testvol01 centos:7 /bin/bash
[vagrant@handson ~]$ docker container exec -it testvol03 \
bash -c "echo 'File in container volume. use volumes-from.' > /ctdir01/testvolfile03.txt"
[vagrant@handson ~]$ docker container exec -it testvol03 cat /ctdir01/testvolfile03.txt
File in container volume. use volumes-from.
[vagrant@handson ~]$ docker container exec -it testvol01 cat /ctdir01/testvolfile03.txt
File in container volume. use volumes-from.
[vagrant@handson ~]$ docker container rm -f testvol01 testvol03
testvol01
testvol03
作成したボリュームは以下のコマンドで削除できます。
[vagrant@handson ~]$ docker volume ls
[vagrant@handson ~]$ docker volume rm vol01
vol01
tmpfs
ホストOSのメモリの一部をファイルシステムとしてコンテナに提供することができます。
インメモリのファイルシステム(tmpfs)を提供できるため、非常に高速なファイルシステムを実現できます。
ただし、メモリ上にあるデータは、一時的な利用に限ります。(消えてしまう?)
コンテナ起動時に以下のオプションを指定することでマウントできます。
--mount type=tmpfs,dst=<コンテナ上のディレクトリ>
[vagrant@handson ~]$ docker container run -it --rm --name testtmpfs01 -h testtmpfs01 \
--mount type=tmpfs,dst=/ctdir01 centos:7
[root@testtmpfs01 /]#
[root@testtmpfs01 /]# ls -l | grep ctdir01 # tmpfs-modeオプションを指定しない場合、1777(world-writable)になる。
drwxrwxrwt. 2 root root 40 Jul 20 07:13 ctdir01
[root@testtmpfs01 /]# exit
tmpfs-mode
でフォルダの権限を変更できます。
tmpfs-mode
以外にサイズを制限できるオプション(tmpfs-size
)もあったりします。(今回は紹介のみ)
[vagrant@handson ~]$ docker container run --rm -it --name testtmpfs02 -h testtmpfs02 \
--mount type=tmpfs,dst=/ctdir01,tmpfs-mode=1755 centos:7
[root@testtmpfs02 /]#
[root@testtmpfs02 /]# ls -l | grep ctdir01
drwxr-xr-t. 2 root root 40 Jul 20 08:06 ctdir01
[root@testtmpfs01 /]# exit
メモ:
drwxrwxrwtはスティッキービットがついています。(実行権部分の「t」)
ファイルやディレクトリを書き込めますが、所有者だけ(rootは除く)しか削除できません。
参考:スティッキービット(Sticky Bit) - 特殊なアクセス権
マウント補足
上記3つのマウント方法の説明では--mount
を使用しましたが、-v (--volume)
や--tmpfs
でもマウントすることができます。
明示的であることやサポートしているオプションの違いなどから、ドキュメントでは--mount
の使用が推奨されているようです。
ハンズオン3:Dockerfile
コンテナから新しいイメージが作成できることをハンズオン1で試しました。
ベースイメージからファイルやコマンドを追加した新しいイメージが作成できることが確認できたと思います。
Dockerfileを使用すると、それら新しいイメージ作成作業を自動化することができます。
イメージ作成にはdocker image build
コマンドを使用します。
参考:docker image build (日本語)
ハンズオン3:Dockerfileでは、
Dockerfileを使ってDockerイメージをいくつか作成してみます。
※以降で作成するファイルは**「文字コード:UTF-8、改行コード:LF」**で作成してください。
Dockerfile01:簡単な例
まずは、ハンズオン1で作成したイメージを、Dockerfileを使用して作成してみたいと思います。
ハンズオン1ではcentoos:7
のイメージをベースイメージとして、test01.txt
の作成とiproute
のインストールをしていました。
Dockerfileの作成
Dockerfileを以下のように作成します。
install_iproute
└ Dockerfile01
FROM centos:7
WORKDIR /home
RUN echo "Dockerfile test01" > /home/test01.txt \
&& yum install -y iproute \
&& rm -rf /var/cache/yum/* \
&& yum clean all
Dockerfileでは様々なDockerfile命令を使用してイメージを作成しており、今回は以下の命令を使用しています。
FROM:
ベースイメージを指定します。
使用するイメージについてドキュメントではAlpine imageを推奨しているようです。
ここでは、ハンズオン1と合わせるためにcentos:7
を使用しています。
WORKDIR:
Dockerfileで記述される命令の作業ディレクトリを設定します。
指定したディレクトリが存在しない場合は作成されます。
Dockerfileで複数回使用でき、相対パスが指定されている場合は前のWORKDIR命令のパスに相対します。
RUN:
ベースイメージに対してコマンドを実行します。
Dockerではレイヤという概念があり、RUN命令を複数実行するとレイヤが増えます。
レイヤの増加はDockerイメージサイズの肥大化につながるため、
&&
(1つ目のコマンドが正常終了した場合に2つ目のコマンドを実行する)や\
(改行)を使用して一つのRUN命令で実行しています。
rm
やyum clean
はDockerイメージの容量を減らすため、不要ファイルなどを削除しています。
Dockerfileからイメージのビルド。コンテナ起動。
[vagrant@handson install_iproute]$ docker image build --no-cache -f ./Dockerfile01 -t centos:df01 .
[vagrant@handson install_iproute]$ docker image ls
[vagrant@handson install_iproute]$ docker container run -it --rm --name dftest01 -h dftest01 centos:df01 /bin/bash
[root@dftest01 home]# ip a
[root@dftest01 home]# exit
####ビルドに使用したコマンドについて補足
ビルドコマンドはdocker build [OPTIONS] PATH
の形式で実行します。(PATHの部分にはURLが入る場合もあります)
--no-cache
:ビルドにキャッシュを使わない。
-f <Dockerfile名>
:使用するDockerfile名を指定します。指定がない場合はPATH/Dockerfile
が使用されます。
-t <リポジトリ>:<タグ>
:ビルドして作成されるイメージに付与するリポジトリとタグです。
PATH:「ビルドで使用するファイルが保存されている場所」を指定します。
Dockerfile02:データベースコンテナ作成
Dockerfileを使用してテスト用のデータベースを作成してみます。
コンテナを起動するとテスト用のデータベース、テーブルが作成された状態にしたいと思います。
また、設定ファイルも配置して日本語に対応した状態にします。
※データベースにそこまで詳しくないため、こういうこともできるという参考程度に。。
Dockerfileとその他ファイル作成
Dockerfileとその他使用するファイルを以下のように作成します。
mariadb
└ sql
└ 01_create_table_goods.sql
└ 02_insert_into_goods.sql
└ Dockerfile02
└ server.cnf
└ envfile.txt
FROM mariadb:10.4
ENV LANG=C.UTF-8
COPY server.cnf /etc/mysql/mariadb.conf.d
COPY sql/* /docker-entrypoint-initdb.d/
[mysqld]
character-set-server=utf8mb4
[client]
default-character-set=utf8mb4
create table test_db.goods
(
goods_id bigint not null auto_increment,
name varchar( 255 ) not null,
category varchar( 255 ),
memo varchar( 255 ),
index (goods_id),
primary key (goods_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into test_db.goods (name, category) values ("PC0001", "PC");
insert into test_db.goods (name, category) values ("PC0002", "PC");
insert into test_db.goods (name, category) values ("MB0001", "MobilePhone");
insert into test_db.goods (name, category) values ("MB0002", "MobilePhone");
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=test_db
MYSQL_USER=master
MYSQL_PASSWORD=master
ベースイメージとしてDocker Official Imagesのmariadbを使用しています。
既存のイメージを使用できるのはDockerのメリットです。
ただし、ドキュメントでは可能な限りOfficial Imagesを使用することが推奨されています。
Dockerfile02で新たに使用したDockerfile命令はENV命令とCOPY命令です。
ENV:
環境変数を設定します。ここではLANGを設定しています。
設定した環境変数はコンテナ起動時に--env
オプションで変更することもできます。
COPY:
ファイルまたはディレクトリをコピーし、ファイルシステムに追加します。
使用したmariadbのイメージでは、指定のディレクトリにsqlファイルを配置することで、
コンテナ起動時に実行することができるので、COPY
命令を使用してsqlファイルを配置しています。
COPY
命令と似た命令にADD命令があります。
ADD
命令の場合、圧縮ファイルを解凍しつつコピーできるなど動作に違いがあります。
ベストプラクティスとして、コピーするだけであればシンプルなCOPY
命令の使用がドキュメントでは推奨されています。
Dockerfileからイメージのビルド。コンテナ起動
[vagrant@handson mariadb]$ docker image build --no-cache -f ./Dockerfile02 -t mariadb:db01 .
[vagrant@handson mariadb]$ docker image ls
[vagrant@handson mariadb]$ docker container run --name mariadb01 -h mariadb01 \
-p 3306:3306 \
--mount type=volume,src=mysql,dst=/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-e MYSQL_DATABASE=test_db \
-e MYSQL_USER=master \
-e MYSQL_PASSWORD=master \
-d mariadb:db01
[vagrant@handson mariadb]$ docker container ls
[vagrant@handson mariadb]$ docker volume ls
[vagrant@handson mariadb]$ docker container exec -it mariadb01 /bin/bash
root@mariadb01:/# mysql -u master -p
Enter password: <MYSQL_PASSWORD で指定した値を入力>
MariaDB [(none)]> show variables like "chara%";
MariaDB [(none)]> show databases;
MariaDB [(none)]> use test_db
MariaDB [test_db]> show tables;
MariaDB [test_db]> select * from goods;
MariaDB [test_db]> exit
root@mariadb01:/# exit
mariadbイメージでは環境変数を指定することで、コンテナ起動時にデータベースを作成したりすることができます。
環境変数は--env
または-e
オプションで指定できます。今回は以下の環境変数を指定します。
MYSQL_ROOT_PASSWORD
必須の変数です。MariaDBのrootユーザーに設定されるパスワードを指定します。
MYSQL_DATABASE
イメージの起動時に作成されるデータベースの名前を指定できます。
MYSQL_USER、MYSQL_PASSWORD
新しいユーザーを作成し、そのユーザーのパスワードを設定します。
MYSQL_DATABASEで指定されたデータベースのスーパーユーザー権限が付与されます。
ユーザーを作成するには、両方の変数が必要です。
マウント機能(--mountオプション)を使用してコンテナの外でデータディレクトリを管理しています。
コンテナで作成したファイルなどはコンテナが削除された際に一緒に削除されてしまいます。
mariadbイメージでは--mountオプションを指定しない場合、デフォルトでランダムな文字列でボリューム作成されるようです。
-p <ホストOS側ポート>:<Docker側ポート>
:ポートを紐づけています。
上記では環境変数を一つずつ設定していますが、--env-file
オプションを使用するとファイルで指定することができます。
上記で作成したコンテナ、ボリュームを削除します。
[vagrant@handson mariadb]$ docker container rm -f mariadb01
[vagrant@handson mariadb]$ docker volume rm mysql
[vagrant@handson mariadb]$ docker container run --name mariadb01 -h mariadb01 \
-p 3306:3306 \
--mount type=volume,src=mysql,dst=/var/lib/mysql \
--env-file ./envfile.txt \
-d mariadb:db01
[vagrant@handson mariadb]$ docker container ls
[vagrant@handson mariadb]$ docker volume ls
[vagrant@handson mariadb]$ docker container exec -it mariadb01 /bin/bash
root@mariadb01:/# mysql -u master -p
Enter password: <MYSQL_PASSWORD で指定した値を入力>
MariaDB [(none)]> show variables like "chara%";
MariaDB [(none)]> show databases;
MariaDB [(none)]> use test_db
MariaDB [test_db]> show tables;
MariaDB [test_db]> select * from goods;
MariaDB [test_db]> exit
root@mariadb01:/# exit
Dockerfile03:Dockerfile02で作成したデータベースに接続するWebサーバー
ここではDockerfile02で作成したデータベースに接続するWEBサーバーを作成してみたいと思います。
今回はDockerの開発言語でもあるGolangで、DBにアクセスして表示するだけの簡単なWEBサーバーを作成してみます。
※Golangも入門中のため、コードは参考程度でよろしくお願いします。
Dockerfileとその他ファイル作成
Dockerfileとその他の使用するファイルを以下のように作成します。
simple_server
└ goapp
└ server.go
└ tmpl01.html
└ Dockerfile03
FROM golang:1.13.5
COPY goapp /go/src/goapp
WORKDIR /go/src/goapp
RUN go get -u github.com/go-sql-driver/mysql
ENTRYPOINT ["go","run","server.go"]
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"html/template"
"net/http"
)
type Goods struct {
goodsId int `db:goods_id`
name string `db:name`
category string `db:category`
memo string `db:memo`
}
func main() {
fmt.Println("Start Sample Server")
server := http.Server{
Addr: ":8080",
}
http.HandleFunc("/goods", goods)
server.ListenAndServe()
}
func goods(w http.ResponseWriter, r *http.Request) {
goodsSelectQuery := "SELECT * FROM goods;"
goodsRows := selectQuery(goodsSelectQuery)
var goodsSlice []Goods
for goodsRows.Next() {
var goods Goods
goodsRows.Scan(&goods.goodsId, &goods.name, &goods.category, &goods.memo)
goodsSlice = append(goodsSlice, goods)
}
fmt.Println(goodsSlice)
t, err := template.ParseFiles("tmpl01.html")
if err != nil {
panic(err)
}
err = t.Execute(w, goodsSlice)
if err != nil {
panic(err)
}
}
func selectQuery(query string) *sql.Rows {
// DB接続
db, err := sql.Open("mysql", "master:master@tcp(192.168.10.10:3306)/test_db?charset=utf8mb4")
if err != nil {
panic(err.Error())
}
defer db.Close()
// ステートメント作成
stmt, err := db.Prepare(query)
if err != nil {
panic(err.Error())
}
defer stmt.Close()
// クエリの実行
rows, err := stmt.Query()
if err != nil {
panic(err.Error())
}
return rows
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>sample server</title>
</head>
<body>
<ul>
{{ range . }}
<li>{{ . }}</li>
{{ else }}
データなし
{{ end}}
</ul>
</body>
</html>
ベースイメージとしてDocker Official Imagesのgolangを使用しています。
Dockerfile03で新たに使用したDockerfile命令はENTRYPOINT命令のみです。
ENTRYPOINT:
コンテナを起動した際に、実行されるコマンドを設定します。
今回はGolangで作成したWebサーバーを起動するコマンドを設定しています。
ENTRYPOINT命令と似た命令にCMD命令があります。
詳細について以下の記事が分かりやすかったので、ここでは紹介のみで詳細は省きたいと思います。
[docker] CMD とENTRYPOINT の違いを試してみた
Dockerfileからイメージのビルド。コンテナ起動。
[vagrant@handson simple_server]$ docker image build --no-cache -f ./Dockerfile03 -t golang:ss01 .
[vagrant@handson simple_server]$ docker image ls
[vagrant@handson simple_server]$ docker container run -d \
--name ss01 -h ss01 \
-p 8080:8080 \
golang:ss01
[vagrant@handson simple_server]$ docker container ls
[vagrant@handson simple_server]$ curl localhost:8080/goods
以下のURLにホストPCのブラウザからアクセスして表示を確認できます。
(IPアドレスはVagrantfileで指定した仮想環境のアドレス)
http://192.168.10.10:8080/goods
ハンズオン4:マルチステージビルド
イメージのサイズは小さい方が良いとされています。
AWS、GCP、Azureなどが提供するリポジトリはイメージ容量が課金対象になるため、容量削減がコスト削減にもつながります。
マルチステージビルドを使うと、必要なファイルのみイメージに含めることが可能になるため容量削減に便利です。
Dockerfile03ではgo run
コマンドでソースコードをビルドせずに実行していました。
Golangはコンパイル言語のため、ビルド後はソースファイルなどが不要になり容量削減が可能です。
Dockerfile03の例を変更して、ビルドしたファイルのみを含めむDockerイメージを作成してみたいと思います。
また、今回は容量がとても小さなAlpine imageで作成してみます。
Dockerfileとその他ファイル作成
Dockerfile03のフォルダに今回使用するDockerfile03bを作成しています。
その他のファイルは同じファイルを使用します。
simple_server
└ goapp
└ server.go
└ tmpl01.html
└ Dockerfile03 ← 今回は使用しない
└ Dockerfile03b ← 追加
FROM golang:1.13.5-alpine AS gobuild01
COPY goapp /go/src/goapp
WORKDIR /go/src/goapp
RUN apk update &&\
apk upgrade &&\
apk add --no-cache git &&\
go get -u github.com/go-sql-driver/mysql &&\
go build server.go
FROM alpine:3.10.3
COPY --from=gobuild01 /go/src/goapp/server /goapp/
COPY --from=gobuild01 /go/src/goapp/tmpl01.html /goapp/
WORKDIR /goapp
ENTRYPOINT ["./server"]
マルチステージビルドではステージを分けてDockerイメージを作成します。
FROM命令を複数記述しそれぞれがステージになります。異なるベースイメージも使用できます。
今回は2つのステージに分けています。
第1ステージがGolangコードのビルド、第2ステージが最終的なDockerイメージ作成です。
第1ステージでビルドで作成したファイルなどを、第2ステージで作成するDockerイメージにコピーすることで、
必要なファイルのみが含まれるDockerイメージを作成しています。
別ステージからのコピーはCOPY命令を使用します。
COPY --from=<コピー元別名(またはステージ番号)> <コピー元ステージのファイル、フォルダ> <コピー先ステージのファイル、フォルダ>
第1ステージのFROM命令ではAS
を使って別名を付けています。
別名を付けなかった場合は、Dockerfile内の各ステージが上から順に 0 から始まる整数番号になります。
別名を付けることでDockerfile内のステージの順番を後で並べ替えてもCOPYが壊れずにすみます。
Dockerfileからイメージのビルド。コンテナ起動。
[vagrant@handson simple_server]$ docker image build --force-rm --no-cache -f ./Dockerfile03b -t golang:ss02 .
[vagrant@handson simple_server]$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
golang ss02 dc35acf77661 18 minutes ago 16.1MB
<none> <none> ad6f9e36afc6 18 minutes ago 390MB
golang ss01 f24356734000 2 hours ago 806MB
[vagrant@handson simple_server]$ docker container run \
-d --name ss02 -h ss02 \
-p 8081:8080 \
golang:ss02
[vagrant@handson simple_server]$ docker container ls
[vagrant@handson simple_server]$ curl localhost:8081/goods
サイズが16.1MBの小さなイメージが作成できました。
以下のURLにホストPCのブラウザからアクセスして表示を確認できます。(IPアドレスはVagrantfileで指定した仮想環境のアドレス)
http://192.168.10.10:8081/goods
##マルチステージビルド補足
###補足1:ごみイメージの削除
マルチステージビルドの場合、最終的に作成するDockerイメージ以外が<none>
という名前で残ってしまいます。
以下のようにdocker image pruneコマンドで簡単に削除できます。
$ docker image ls --filter dangling=true # 対象の表示
$ docker image prune # 削除
###補足2:特定のビルドステージで止める
docker image build
で--target
オプションを指定することで、特定のステージで止めることができます。
[vagrant@handson simple_server]$ docker image build --target gobuild01 \
--force-rm --no-cache -f ./Dockerfile03b -t golang:ssb02 .
[vagrant@handson simple_server]$ docker container run \
-it \
--rm \
--name ssb02 -h ssb02 \
-p 8082:8080 \
golang:ssb02 \
/bin/sh
/go/src/goapp # ls
server server.go tmpl01.html
※alpineイメージでは/bin/bash
が入っていないので/bin/sh
または/bin/ash
でログインしています。
###補足3:外部イメージをステージとして使用
Dockerfileで記述したステージ以外にも、既に作成済みのイメージがステージとして使用できます。
使用方法はCOPY --from=
でローカルまたはDockerレジストリのDockerイメージを指定するだけです。
※今回は紹介のみ。
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
###補足4:apkコマンド
Alpineではyum、aptではなくapkコマンドを使用してパッケージをインストールします。
コマンドをいくつか紹介します。
$ apk --version # apkコマンドのバージョン表示。
$ apk --help # apkコマンドのヘルプ表示。
$ apk update # 利用可能なパッケージのインデックス(パッケージ一覧)を更新。
$ apk upgrade # インストール済みパッケージの更新。
$ apk search # パッケージの検索。
$ apk add # パッケージのインストール。--no-cacheでキャッシュを使用しない。
$ apk del # パッケージのアンインストール。
$ apk info # インストール済みパッケージ一覧表示。パッケージ名指定でパッケージの詳細表示。
参考:Alpine Linux package management
#おわり
Dockerではコンテナで動かす対象の理解も必要になるので学ぶことが多いですね。
まだまだ学習中の内容も多く触れられなかった内容も多々ありますが、今回は以上です。
参考
Docker実践ガイド 第2版 (impress top gear) 古賀政純
コンテナはなぜ安全(または安全でない)なのか
Dockerでプログラマが最低限知るべきことが、最速でわかるチュートリアル
VM・Dockerといった仮想化技術を理解する
DockerHubのイメージのタグ一覧をコマンドで取得する
「それコンテナにする意味あんの?」迷える子羊に捧げるコンテナ環境徹底比較 #cmdevio2019
docker container / image コマンド新旧比較
Wikipedia - ハイパーバイザ
Docker Documentation
Docker Hub
Docker Commandline
Best practices for writing Dockerfiles
Dockerfile を書くベスト・プラクティス
Alpine Linux入門 -内部構造とapkでパッケージインストール編-
Docker内部で利用されているLinuxカーネルの機能 (namespace/cgroups)
いまさらだけどDockerに入門したので分かりやすくまとめてみた
MySQLの文字コード事情