社内で専門のインフラ関係の担当の方がいらっしゃって、環境などはよしなにしてくださっていたのですが、流石にそろそろDocker周りを自分でも勉強しておかないとまずいだろう・・ということで、最近Dockerにやっと入門したので、色々と学んだことを備忘録も兼ねてまとめたり試したりしていこうと思います。
経歴的にインフラ周りなど馴染みの薄いお仕事を長くしてきていたので、知識的に荒い(浅い)ところなどは色々ご容赦ください。
私も初心者ですし、あまりインフラとかに馴染みのない方向けの記事となります。
Windows環境(Docker for Windows)でLinuxやPython関係を動かしたりを中心に進めていきます。
この記事で触れること
- コンテナなどの概要やメリット
- Docker for Windowsのインストール・設定
- コンテナとイメージ関係の基礎
- Docker Hubやタグについて
- Dockerfileの基礎
- 実際にUbuntuにPythonなどを絡めたイメージのビルド
この記事で触れないこと
記事が長くなりすぎるので主に以下は触れません。
※機会があれば別の記事で対応します・・
- ネットワークやファイルシステム周り
- Docker Compose周り
- SwarmやKubernetes関係など
- サーバーへのデプロイ周りなど
そもそもコンテナって?Dockerを使うと何が嬉しいの?
コンテナーとは、ホストOS上に論理的な区画(コンテナー)を作り、アプリケーションを動作させるのに必要なライブラリやアプリケーションなどを1つにまとめ、あたかも個別のサーバーのように使うことができるようにしたものです。ホストOSのリソースを論理的に分離し、複数のコンテナーで共有して使います。
コンテナーとは? Kubernetesとは? 導入や運用、ユースケースを解説
これだけだとイメージが付きづらいので、画像を踏まえてかみ砕いていきます。
例として、Linuxのサーバーを使ったDjangoやFlaskなどでのwebサイトを考えます。
開発をしていく場合、ローカルのWindowsなどのPCで本番に合わせるためにLinuxを動かす必要があり、そこにPythonやらDjangoなどのライブラリやらwebサイトのコードを載せたりが必要になってきます。
このWindows上でLinuxなどを動かす技術が仮想環境(仮想マシン)などと呼ばれ、昔からVirtualBoxやVMWareといったツールやサービスなとを使うことで利用することができていました。
Windows PCの中に、もう一つパソコンが入っているようなイメージでしょうか。
これはこれで本番環境に近い形の環境がローカルのPCに用意できて、便利に使うことができます。
ただ、メモリやCPUなどを各仮想環境に割り振っていく必要があったりと、大元のWindows PCのリソースがどんどん持っていかれてしまってリソースの無駄が多かったり、仮想環境の起動時間が長かったりと、辛い面がありました。
例えがちょっと微妙ですが、複数のデザインツール(Photoshop, Illustratorなど)を使いたいときに例えてみます。
当たり前ですが、これらのツールを使うときには「PCを起動」→「Photoshop起動」→「Illustratorを起動」→「...」みたいな感じで、一度だけPCを起動すれば済みますし、ソフトの起動も連続して行えます。
一方で、VirtualBoxなどのケースで先ほどのツールで例えると、「PCを起動」→「別のPCを起動」→「Photoshopを起動」→「別のPCを起動」→「Illustratorを起動」みたいなことが必要でした。
この場合、通常のケースと比べると各OSが起動する時間を待たないといけなかったり、OSに必要なメモリなども持っていかれてしまったりで無駄が多くなってしまって辛いところです。
コンテナ(Docker)技術を使うと、前述のOSを起動してから複数のソフトを連続して起動するように、無駄無く環境を起動することができます(厳密にはコンテナ関係もDocker以外にも色々ありますが、今回は触れません)。
また、まるでソフトの1つのようにLinuxやそれに乗っかるPython、使うライブラリ、webサイトのコードを1つのコンテナーとして起動することができます。
無駄無くソフトを起動するような感覚で使えるため、VirtualBoxなどで仮想環境を起動するよりも起動時間が大分短くなり、且つ本番に近い形でWindows上で他のOSの隔離された環境を扱うことができるようになります。
大雑把に図にすると以下のようになります。
その他にも、複数人でのローカル環境の統一がやりやすいとか、CI/CDとかで快適とか、デプロイが楽になるとか色々メリットがあります。
複数人でアプリケーションを開発する場合、開発者のマシンの数だけ開発環境を立てる必要があります(VDIでというのは無視)。まあ、大変ですよね
...
これが dockerのサービス/デーモンさえ動いていれば、1つの共通 Docker Imageを挿すだけで全く同じ環境が、開発者全員に行き渡ります。
...
開発で動いたものがまるっきりそのまま本番、しかもCIもその開発で使っていたImageなので、如何に安定性・リスク回避からも有利であることがわかると思います。
2018年なぜ私達はコンテナ/Dockerを使うのか
環境構築や共有なども楽になる
また、1つ1つのコンテナーを構築するのはDockerfileと呼ばれるファイルのコード(テキスト)で作られるため、構築の手順の再現が容易という点もメリットとして挙げられます(Infrastructure as Codeなどと呼ばれます)。
今までVirtualBoxなどで環境を作る際、手順や必要なライブラリのインストールなど、結構辛いものがありました(順番が違うとか、共有するにしてもファイルサイズが巨大など)。
Dockerでは、必要なOSやライブラリ、コードなどの指定をテキストファイルで行われるため、構築は楽ですし構築したものの共有・配布なども楽になっています。
Docker for Windowsのインストール
さっそくWindowsにDockerをインストールしていきます。
まず前提として、Windows10が必要になります(Windows7もサポートが切れたので多くの方がWindows10だと思いますが、Win7だとDockerが使えません)。
Hyper-VとContainersの有効化
Windowsの設定でHyper-VとContainersの機能を有効にする必要があります。
「Windowsの機能」から設定ができます。
スタートメニューで「アプリと機能」と検索します。
開かれる設定画面の右の方にある「プログラムと機能」を選択します。
左メニューにある、「Windowsの機能の有効化または無効化」を選択します。
開かれるWindowで、CotnainersとHyper-Vにチェックを入れます。
OKを押したら一度PCを再起動します。
Docker Desktop for Windowsのダウンロードとインストール
Docker Desktop for Windowsのページからインストーラーのダウンロードなどを行います。
Dockerのアカウントが必要になります(後述しますが、ダウンロード時以外でもアカウントが必要になってきます)。
アカウントの作成が終わったら、ダウンロードしてそちらのインストーラーを使ってインストールしていきます(特に迷うことなく、そのままインストーラーの指示通りに進めていけばとりあえず動きます)。
ファイルが1GB近い、結構大きなファイルなので注意してください。
インストールが終わったら、一度サインアウトもしくは再起動します。
再起動後、Dockerは自動的に起動します。
右下の常駐アプリ表示の部分をクリックすると、クジラのアイコンが表示されていることか確認できます。
コマンドラインを開いて、dockerが動くか確認するため、バージョンを表示するコマンドを実行してみます。
$ docker version
Client: Docker Engine - Community
Version: 19.03.5
API version: 1.40
Go version: go1.12.12
Git commit: 633a0ea
Built: Wed Nov 13 07:22:37 2019
OS/Arch: windows/amd64
Experimental: false
Server: Docker Engine - Community
Engine:
Version: 19.03.5
API version: 1.40 (minimum version 1.12)
Go version: go1.12.12
Git commit: 633a0ea
Built: Wed Nov 13 07:29:19 2019
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: v1.2.10
GitCommit: b34a5c8af56e510852c35414db4c1f4fa6172339
runc:
Version: 1.0.0-rc8+dev
GitCommit: 3e425f80a8c931f88e6d94a8c831b9d5aa481657
docker-init:
Version: 0.18.0
GitCommit: fec3683
問題なく表示されました。
Linuxを扱うための切り替えを行う
Docker for Windowsでは、WindowsのPCでWindowsのDocker環境を使うか、Windows用のPCでLinuxの環境を使うかが切り替えができます。
今回はWindows上でLinuxを動かしたいので、Linuxの方に切り替えます。
左下の常駐アプリのクジラのアイコンを右クリックします。
表示されるメニューで、Switch to Linux containers...を選択します(初めて表示する際などには少し時間がかかったりするようです。その場合には右クリック後少し放置します)。
もしSwitch to Windows containers...という表示になっていたら、もうLinux環境に切り替わっているので特に操作は不要です。
警告が出ますが、特に問題ないのでSwitchを選択します。
数秒待つと、Linux環境が有効化されます。
イメージってなに?
コンテナはイメージと呼ばれるものを使って起動されます。
停止している仮想マシン(もしくは停止しているコンテナ)のようなものとか、テンプレートのようなものに近いイメージになります。
どんな風なコンテナなの?という情報やデータが色々詰まっています。
公開されているイメージをダウンロードしてみる
自分でイメージを作ることもできますが、まずは他人が公開しているものを落としてきてみます。
Githubなどでリポジトリのファイルを落としてくるように、他人が公開しているものを落としてきたり、自分で公開したりすることができます。
コマンドもgitのコマンドのようなフォーマットになっています。
例えば、Ubuntuのイメージを落としてくるには以下のようなimage pull
のコマンドで対応できます。
$ docker image pull ubuntu:latest
latest: Pulling from library/ubuntu
5c939e3a4d10: Pull complete
c63719cdbe7a: Pull complete
19a861ea6baf: Pull complete
651c9d2d6c4f: Pull complete
Digest: sha256:8d31dad0c58f552e890d68bbfb735588b6b820a46e459672d96e585871acc110
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest
ubuntuの箇所に落としたいイメージの名前、latestの部分はタグと呼ばれバージョンの指定などに使います。
(バージョン番号などの他に、必ずしも最新版を意味するものではありませんが、安定版の最新版などとしてのlatestという単語が良く使われます。)
ローカルに存在するイメージは、Linuxのlsコマンド(ディレクトリのファイル一覧などを表示するコマンド)と同じような感覚でイメージの一覧を確認することができます。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest ccc6e87d482b 4 days ago 64.2MB
落としてきたイメージを起動してみる
pullしてきたUbuntuのイメージを使ってコンテナを起動してみます。
docker container run
コマンドでコンテナを起動できます。
$ docker container run -it ubuntu:latest bash
ubuntu:latest
部分は起動したいイメージ(と対象のタグ)を指定します。
末尾のbash
部分は、Ubuntuで起動したいプログラムを指定します。
VirtualBoxなどでサーバー用Linuxを起動するとコンソールの黒い画面になりますが、それと同じように今回はUbuntu上でコマンドを実行できるようにしたいので、bashを指定しています。
-it
の引数を指定すると、Windows上のコマンドプロンプトに対して起動したUbuntu上のbashを割り当てる指定となります(Ubuntu以外のLinuxだとbash以外が必要になったりする点にはご留意ください)。
割り当てる、と書くとなんだか分かりづらいですが、Linuxにログインした際のターミナルにWindows上のコマンドプロンプトが切り替わる、といった具合です。
コマンドプロンプトを見てみると、Ubuntuのrootユーザー(root@75ae40e15c7f:/#
といったユーザー表示)に切り替わっているのが分かります(私は最初これに触れた際に、最初はなんだか結構不思議な感覚になりました)。
この状態では、Windows上で動かしているにも関わらず実際にUbuntu用のコマンドが実行できます。ls
コマンドを実行してみると、Ubuntuで見慣れたディレクトリたちが表示されることが分かります。
root@75ae40e15c7f:/# ls
bin dev home lib64 mnt proc run srv tmp var
boot etc lib media opt root sbin sys usr
まるでUbuntuを起動してログインしたような感覚です。
また、コンテナのメリットとして、起動が速いという点も前述しましたが、私の環境で上記のコマンドでUbuntuのコンテナが立ち上がってbashの入力ができるようになるまでほんの数秒で移行できています。VirtualBoxやVMWareで起動するよりかはやっぱり大分速い・・・という印象です。
普段開発環境で毎日起動することを考えると、この速さはいいですね。
なお、-it
引数は、-i
と-t
の引数の組み合わせのようで、それぞれで以下のような設定のようです。
-iは、Keep STDIN open even if not attached
標準入力を開き続ける。-tは、Allocate a pseudo-TTY
疑似ttyを割りあてる。標準入力を開き続け、そこを操作出来るようにする。
→手元の環境で、docker内入力ができるようにする
docker exec -itって実際は何をしてるの?【90日目】
元のWindowsのコマンドプロンプトに戻る
Ubuntu上のbashから元のWindowsのコマンドプロンプトに戻りたい場合にはexitとコマンドを実行するか、Ctrl + P → Ctrl + Qとキーボードで入力すると戻れます。
exitコマンドを実行した場合には、条件によってはコンテナ自体が停止します(それしかプロセスが動いていない場合など?)。Ctrl + P → Ctrl + Qとキーボードを入力して戻った場合には、Ubuntuのコンテナは動いたままとなります。
サーバからログアウトする感覚でexitコマンドを実行すると、起動したコンテナも停止してしまうので注意してください。
Dockerコンテナの作成、起動〜停止まで
起動中のコンテナの一覧を確認する
イメージのlsコマンドと同様に、コンテナのlsコマンド(docker container ls
)で起動中のコンテナの一覧を表示することができます。
試しにUbuntuのコンテナを起動して、exitではなくCtrl + P → Ctrl + Qのショートカット(起動したままbashを抜ける)を使って、そののちにlsコマンドで確認してみます。
$ docker container run -it ubuntu:latest bash
※Ctrl + P → Ctrl + Qを入力してWindowsのコマンドプロンプトに戻る。
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e8f5c7aadcb5 ubuntu:latest "bash" About a minute ago Up About a minute xenodochial_hodgkin
先ほど起動したUbuntuのコンテナ情報が表示されました。
起動中のコンテナを停止する
停止するにはdocker container stop <コンテナ識別用のパラメーター>
を指定します。
前述のdocker container ls
コマンドで表示された、CONTAINER IDもしくはNAMESが指定できます。
NAMESの値は、コンテナ起動時に指定しなかった場合は自動で何らかの値が設定されます。
たとえば、先ほど起動したコンテナに対してIDを指定して停止する場合には以下のようになり、
$ docker container stop e8f5c7aadcb5
NAMESを指定して停止する場合には以下のようになります。
$ docker container stop xenodochial_hodgkin
停止処理後再度lsコマンドで起動中のコンテナを確認してみると、ヘッダーのみの表示で、起動中のコンテナが無くなっていることが確認できます。
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
コンテナに名前を付けて起動する
特に名前を指定しなくてもコンテナを起動することはできますが、特定の名前を明示しておくとスクリプトなどで制御する際に便利です。
名前を指定するには--name <コンテナ名>
と指定します。例えば、ubuntutestという名前をコンテナに指定して起動したい場合は以下のようになります。
$ docker container run --name ubuntutest -it ubuntu:latest bash
Ctrl + P, Ctrl + Qでコンテナを起動したままWindowsのコマンドプロンプトに戻って、lsコマンドで確認すると、NAMESの値に引数で指定したubuntutestという名前が設定されていることが分かります。
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9c1141a99d07 ubuntu:latest "bash" About a minute ago Up About a minute ubuntutest
一旦、上記のコンテナは停止させて次に進みます。
$ docker container stop 9c1141a99d07
停止しているコンテナのリストを表示する
通常のdocker container ls
コマンドでは起動しているコンテナしか表示されません。
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
しかし、停止したコンテナはローカルから削除されるわけではありません。
-a
のオプションを加えると停止済みのコンテナも表示することができます。
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9c1141a99d07 ubuntu:latest "bash" 10 hours ago Exited (0) 43 seconds ago ubuntutest
e8f5c7aadcb5 ubuntu:latest "bash" 11 hours ago Exited (0) 11 hours ago xenodochial_hodgkin
39c583048c7f ubuntu:latest "bash" 23 hours ago Exited (0) 23 hours ago nervous_swanson
...
コンテナを取り除く
停止しているコンテナに対して、docker container rm <対象のコンテナのIDや名前>
というフォーマットのコマンドで対象のコンテナを取り除くことができます。
$ docker container rm ubuntutest
lsコマンドで確認してみると、一番上にあったubuntutestという名前のコンテナ(ID=9c1141a99d07)が消えていることが確認できます。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e8f5c7aadcb5 ubuntu:latest "bash" 11 hours ago Exited (0) 11 hours ago xenodochial_hodgkin
39c583048c7f ubuntu:latest "bash" 23 hours ago Exited (0) 23 hours ago nervous_swanson
...
なお、起動しているコンテナに関しては基本的に削除は実行できず、一度停止させる必要があります。試しにrunning_ubuntuという名前でコンテナを起動し、Ctrl + P → Ctrl + Qで起動したままコマンドプロンプトに戻り、削除を試してみます。
$ docker container run --name running_ubuntu -it ubuntu:latest bash
※Ctrl + P → Ctrl + Qを押す。
$ docker container rm running_ubuntu
以下のように怒られます。
Error response from daemon: You cannot remove a running container 95586a709576d33521072c1b40c2d84e5c3881a6d6468b485958e62f6d2a3440. Stop the container before attempting removal or force remove
-f
オプションを追加すると一応強制的に取り除くことができます。ただ、基本的には停止 → 削除の流れに沿う形での対応が好ましそうな印象があります。
$ docker container rm -f running_ubuntu
同一名を指定してコンテナで複数回起動するとどうなるのか
名前(--name引数)を指定せずにコンテナを起動すると毎回別のコンテナが起動します。
では、名前を指定しつつ、複数回同じ名前で起動しようとするとどうなるのでしょうか?
same_name_ubuntuという名前で、コンテナを起動してみます。
$ docker container run --name same_name_ubuntu -it ubuntu:latest bash
Ctrl + P, Ctrl + Qで起動したままbashからWindowsのコマンドプロンプトに戻ります。
もう一度同じ名前を指定してコンテナを起動してみます。
$ docker container run --name same_name_ubuntu -it ubuntu:latest bash
そうすると以下のように、別のコンテナが立ち上がることは無くエラーで弾かれ、名前はユニークになっている必要があることが分かります。
docker: Error response from daemon: Conflict. The container name "/same_name_ubuntu" is already in use by container "9abe70cdbc98350f18b780880fd3b2ba23d0202b1cdb464c290dfe929924188b". You have to remove (or rename) that container to be able to reuse that name.
See 'docker run --help'.
停止しているコンテナを再開させる
停止しているコンテナを再度スタートさせるには、docker start <コンテナのIDもしくは名前>
というフォーマットでコマンドを実行します。docker container run
でも無いですし、docker container start
でもなく、docker start
というフォーマットになります。
一度試しに動いているコンテナを停止させます :
$ docker container stop same_name_ubuntu
スタートさせます :
$ docker start same_name_ubuntu
lsコマンドを実行すると、ちゃんと起動しているのが確認できます。
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9abe70cdbc98 ubuntu:latest "bash" 35 minutes ago Up 4 minutes same_name_ubuntu
なお、このコマンドではコンテナが起動するだけでbashとかに切り替わってくれたりはしません。
起動しているコンテナに対してもう一度bashで入るには?
Ctrl + P, Ctrl + Qとかで起動したままbashを抜けた場合や、docker start
コマンドでコンテナを起動した後に、再度bashに切り替えたり、もしくは他の任意のコンテナ内のアプリを起動したい場合には、docker container exec
コマンドで実行できます。
$ docker container exec -it same_name_ubuntu bash
コマンドプロンプトの表示がroot@9abe70cdbc98:/#
といったような表示になり、bashに切り替えられたのが確認できます。
イメージを削除する
イメージもコンテナと同様の感覚で削除できます。
CONTAINER IDの代わりにIMAGE IDという値がlsコマンドで確認できるため、そちらを指定します。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest ccc6e87d482b 6 days ago 64.2MB
ただし、該当のイメージをいずれかのコンテナで利用していると弾かれます。
$ docker image rm ccc6e87d482b
Error response from daemon: conflict: unable to delete ccc6e87d482b (cannot be forced) - image is being used by running container 9abe70cdbc98
起動しているコンテナで使われていて削除できないよ、といったエラー内容ですが、コンテナを停止させてもエラーで怒られます。
試しに起動しているコンテナをlsコマンドで確認してから、そのコンテナを停止させてからもう一度削除コマンドを実行してみます。
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9abe70cdbc98 ubuntu:latest "bash" 14 hours ago Up 14 hours same_name_ubuntu
$ docker container stop 9abe70cdbc98
$ docker image rm ccc6e87d482b
Error response from daemon: conflict: unable to delete ccc6e87d482b (must be forced) - image is being used by stopped container 75ae40e15c7f
エラー内容がimage is being used by stopped container
となり、停止しているコンテナで使われていても弾かれることが分かります。
コンテナと同様、-f
オプションを付ければ一応は強制的に削除ができます。ただし、コンテナ側などに悪影響が出たりするので、実行時には要注意です。
$ docker image rm -f ccc6e87d482b
Untagged: ubuntu:latest
Untagged: ubuntu@sha256:8d31dad0c58f552e890d68bbfb735588b6b820a46e459672d96e585871acc110
Deleted: sha256:ccc6e87d482b79dd1645affd958479139486e47191dfe7a997c862d89cd8b4c0
なお、ID以外にも<イメージ名>:<タグ名>
の指定で特定のイメージを削除することも可能です。
$ docker image rm ubuntu:latest
大元のイメージを削除してしまったりと、影響を受けたコンテナをこのまま残しておくのも微妙なので、次の節で一括で停止中のコンテナを削除しておきます。
停止中のコンテナを一括で削除する
利用は気を付ける必要がある(消したくないコンテナまで消してしまったりなど)感じではありますが、docker container prune
コマンドで停止中のコンテナを一括で削除できます。
lsコマンドで確認してみると、色々な停止中のコンテナが残っていることが分かります。
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9abe70cdbc98 ccc6e87d482b "bash" 15 hours ago Exited (0) 23 minutes ago same_name_ubuntu
e8f5c7aadcb5 ccc6e87d482b "bash" 26 hours ago Exited (0) 26 hours ago xenodochial_hodgkin
39c583048c7f ccc6e87d482b "bash" 38 hours ago Exited (0) 38 hours ago nervous_swanson
...
pruneコマンドを実行してみます。
$ docker container prune
警告が出るのでyと入力してEnterを押します。
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N]
Deleted Containers:
9abe70cdbc98350f18b780880fd3b2ba23d0202b1cdb464c290dfe929924188b
e8f5c7aadcb54cbddb1b9857ffc6c9ae16d389023f094fd8fb52385ad1195c47
39c583048c7f63c425c5bcb1e88cbf10fa5a6e69f736e1f3db235678cbac089e
...
Total reclaimed space: 33B
lsコマンドで確認すると、ちゃんと削除されていることが分かります。
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
コンテナは停止させても内部のデータが削除されるわけではない
docker container stop
でコンテナを停止させても、追加した内部のファイルなどのデータはそのまま残ります。
試しに、Ubuntuのコンテナを追加して、bashからファイルを追加し、停止したのちに再度起動してファイルが残っていることを確認してみます。
前節まででコンテナなどを一通り削除していたので、もう一度docker container run
コマンドでコンテナを追加・起動します。今回はfile_persistency_testという前を付けました。
なお、イメージも削除済みですが、docker container run
コマンドで、指定したイメージがローカルて見つからなければ同時にpullしてくれます。便利。
$ docker container run --name file_persistency_test -it ubuntu:latest bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
5c939e3a4d10: Pull complete
c63719cdbe7a: Pull complete
19a861ea6baf: Pull complete
651c9d2d6c4f: Pull complete
Digest: sha256:8d31dad0c58f552e890d68bbfb735588b6b820a46e459672d96e585871acc110
Status: Downloaded newer image for ubuntu:latest
Ubuntuのbashに切り替わるので、cdコマンドでtmpフォルダに移動して、確認用のファイルを保存してみます。
root@d289b867492e:/# cd tmp
test_file.txt
というファイル名で書き込みます。
root@d289b867492e:/tmp# echo "吾輩は猫である" >> test_file.txt
catコマンドで、書き込んだ内容を確認してみます。
root@d289b867492e:/tmp# cat test_file.txt
吾輩は猫である
無事書き込めているようなので、一旦コンテナを停止させて再度起動します。
exitでそのままbashを抜けてstopさせてもいいのですが、起動プロセスの都合で止まらないことがあるので普通にCtrl + P, Ctrl + Qで抜けてからコマンドでstopさせます。
$ docker container stop file_persistency_test
もう一度起動します。
$ docker start file_persistency_test
bashに切り替えます。
$ docker container exec -it file_persistency_test bash
tmpフォルダに移動して、ファイルが残っていることを確認します。
root@d289b867492e:/# cd tmp
root@d289b867492e:/tmp# ls
test_file.txt
root@d289b867492e:/tmp# cat test_file.txt
吾輩は猫である
無事表示されました。コンテナを停止してもファイルが残ることが確認できました。
Docker Hubについて
説明無くイメージのpullなどを行っていましたが、これらのイメージはDocker HubというDocker公式のサーバーから落とされてきています。
後で触れますが、他人のイメージをpullしたりする以外にも、自分でイメージをアップして公開することもできます。
Docker Hub以外のサーバーでもイメージが公開されていて、落としてきて利用することもできますが、基本的にはDocker Hubがメインとなります。
特殊なケースを除いて、基本的にはDocker Hubを使うことになります。
イメージを検索する
必要になったイメージを探したいときには、Googleでdocker hub <対象のイメージのキーワード>
みたいな感じで探せば見つかると思います。
例えば、MongoDBのイメージを探したいときにはdocker hub mongodb
といった具合です。
pull用のコマンドや、Tagsのタブを見ると各タグの情報やそのタグでのpullのコマンドを調べることができます。
その他にも、docker search
コマンドを使うことで、コマンドプロンプト上で検索することもできます。
$ docker search mongo
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mongo MongoDB document databases provide high avai… 6519 [OK]
mongo-express Web-based MongoDB admin interface, written w… 600 [OK]
tutum/mongodb MongoDB Docker image – listens in port 27017… 229 [OK]
bitnami/mongodb Bitnami MongoDB Docker Image 108 [OK]
mongoclient/mongoclient Official docker image for Mongoclient, featu… 79 [OK]
mongooseim/mongooseim Small docker image for MongooseIM - robust a… 19
frodenas/mongodb A Docker Image for MongoDB 18 [OK]
cvallance/mongo-k8s-sidecar Kubernetes side car to setup and maintain a … 14 [OK]
STARSの数が多い、人気のイメージのリポジトリから降順で25件程度表示されます。
もしそれ以上の件数を確認したい場合には、--limitのオプションを指定することで、最大100件まで表示することができます。
$ docker search --limit 50 mongo
公式と非公式のイメージ(リポジトリ)について
先ほどのdocker search
コマンドでは、検索結果にOFFICIALというカラムがありました。
NAME ... OFFICIAL AUTOMATED
mongo ... [OK]
mongo-express ... [OK]
tutum/mongodb ... [OK]
bitnami/mongodb ... [OK]
mongoclient/mongoclient ... [OK]
[OK]と付いているリポジトリと空になっているリポジトリがあることが分かります。
これは、Docker側で公式として扱われているリポジトリかどうかの判定として利用することができます。
[OK]と付いていないリポジトリは、個人だったり公式とは関係無い第三者によるリポジトリとなります。
それらは便利なケースも多くある一方で、ものによってはアップデートが止まっていたり、脆弱性が放置されていたり、バグが残ったままになっていたりするかもしれません。
利用する場合には自己責任となります。
もちろん公式でもそういった脆弱性が見つかることもあり、公式だからといって安全というものでもないですが、そういった際にちゃんとアップデートがされるかどうかなどで差が出る可能性があります。
公式のリポジトリだけ検索する
--filter "is-official=true"
と引数を加えると、公式リポジトリのみに検索を制限したりすることができます。
$ docker search mongo --filter "is-official=true"
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mongo MongoDB document databases provide high avai… 6519 [OK]
mongo-express Web-based MongoDB admin interface, written w… 600 [OK]
イメージの詳細をinspectコマンドで確認する
docker image inspect <イメージ名:タグ もしくはID>
のフォーマットのコマンドで、対象のイメージの詳細を確認することができます。
表示される情報は、設定されているタグやユニーク判定用のハッシュ値、生成日時や設定値、実行されるコマンド、OS、サイズ、ネットワーク情報、マウント(ファイルシステム)など様々です。
$ docker image inspect ubuntu:latest
[
{
"Id": "sha256:ccc6e87d482b79dd1645affd958479139486e47191dfe7a997c862d89cd8b4c0",
"RepoTags": [
"ubuntu:latest"
],
"RepoDigests": [
"ubuntu@sha256:8d31dad0c58f552e890d68bbfb735588b6b820a46e459672d96e585871acc110"
],
"Parent": "",
"Comment": "",
"Created": "2020-01-16T01:20:34.28326882Z",
"Container": "8fadb7780c5426c7a294cc9fdb50294da831d550a3ecaf8382737d6b141b3054",
"ContainerConfig": {
"Hostname": "8fadb7780c54",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"/bin/bash\"]"
],
"ArgsEscaped": true,
"Image": "sha256:d73776eba985c9fbd43227fbf32eeb85baf88775dc0c27db7edb0c8ddeb72b26",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {}
},
"DockerVersion": "18.06.1-ce",
"Author": "",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/bash"
],
"ArgsEscaped": true,
"Image": "sha256:d73776eba985c9fbd43227fbf32eeb85baf88775dc0c27db7edb0c8ddeb72b26",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"Architecture": "amd64",
"Os": "linux",
"Size": 64194748,
"VirtualSize": 64194748,
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/f03c69f5d7637556772ae1b2c650e1872f8a574db88e45f3911315729db0a59c/diff:/var/lib/docker/overlay2/1c79f0978277afcdf1c118571885a43fe9be6d979eeaafd5b27196e757628da0/diff:/var/lib/docker/overlay2/0109eef00cd0de560fdbdf6c680e1cf89511736fb8b24180c9ef889e37611e59/diff",
"MergedDir": "/var/lib/docker/overlay2/e902d5b0ebae48707cca044bdbf7b1397c560bd58b397c841e426b20de6f5333/merged",
"UpperDir": "/var/lib/docker/overlay2/e902d5b0ebae48707cca044bdbf7b1397c560bd58b397c841e426b20de6f5333/diff",
"WorkDir": "/var/lib/docker/overlay2/e902d5b0ebae48707cca044bdbf7b1397c560bd58b397c841e426b20de6f5333/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:43c67172d1d182ca5460fc962f8f053f33028e0a3a1d423e05d91b532429e73d",
"sha256:21ec61b65b20ec53a1b7f069fd04df5acb0e75434bd3603c88467c8bfc80d9c6",
"sha256:1d0dfb259f6a31f95efcba61f0a3afa318448890610c7d9a64dc4e95f9add843",
"sha256:f55aa0bd26b801374773c103bed4479865d0e37435b848cb39d164ccb2c3ba51"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
イメージのレイヤーの話
各イメージは、複数のレイヤーという要素で構成されています。
各イメージでレイヤーが何個で構成されているかは前述のdocker image inspect
コマンドで確認することができます。
Ubuntuのイメージで確認してみると、以下のように4つのレイヤーで構成されていることが分かります(バージョンによってレイヤー数はある程度前後します)。
$ docker image inspect ubuntu:latest
...
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:43c67172d1d182ca5460fc962f8f053f33028e0a3a1d423e05d91b532429e73d",
"sha256:21ec61b65b20ec53a1b7f069fd04df5acb0e75434bd3603c88467c8bfc80d9c6",
"sha256:1d0dfb259f6a31f95efcba61f0a3afa318448890610c7d9a64dc4e95f9add843",
"sha256:f55aa0bd26b801374773c103bed4479865d0e37435b848cb39d164ccb2c3ba51"
]
},
...
例えば自分でイメージを作る際に、Linuxのイメージに加えて、Python関係を色々入れたり・・・と、要素を追加していくとレイヤーがどんどん増えていきます。
一方で、メタデータのみの追加であれば特にレイヤーは追加されません(詳細は後述しますが、例えばDockerfileにて指定する、コンテナ起動時に実行して欲しいコマンドの指定のためのCMD
の設定などは、メタデータの追加のみのためレイヤーは追加されません)。
なぜレイヤーが分かれているのか
いくつか理由がありますが、大きな点の1つとしてイメージをpullする際に、すでにローカルに該当のイメージで必要な特定のレイヤーが存在すれば、そのレイヤーのダウンロードはスキップされ、処理時間が短縮されるというメリットがあります。
たとえば、特定のイメージでレイヤーを4つ使っているとします。次のマイナーバージョンが公開され、その際に4つのレイヤーのうち1つが更新されているとします。
ユーザーがアップデートのためpullすると、新バージョンのイメージ全体のダウンロードが実行されるのではなく、更新された1レイヤーのみがダウンロードされ、該当バージョンのイメージが利用可能になります。
この複数のイメージ間でレイヤーが共有されることによって、ローカルのディスクサイズの節約やダウンロード時間の節約に繋がります。
この辺りはpullコマンドの際に確認することができます。
ダウンロードがレイヤーごとに実行されるため、すでにローカルに該当のレイヤーが存在すればAlready exists
という表示になり、pullがスキップされていることが確認できます。
$ docker image pull mongo:4.2.2
5c939e3a4d10: Already exists
c63719cdbe7a: Already exists
19a861ea6baf: Already exists
651c9d2d6c4f: Already exists
85155c6d5fac: Pull complete
85fb0780fd97: Pull complete
85b3b1a901f5: Pull complete
6a882e007bb6: Pull complete
f7806503a70f: Pull complete
e23d5068c270: Pull complete
56eb708963d7: Pull complete
fc4def32f081: Pull complete
ea1dc19faea9: Pull complete
Digest: sha256:a1a71d8659ef2f944469f9eb876a44b7ae7c05153f356c18c165fd485fc60f2b
Status: Downloaded newer image for mongo:4.2.2
docker.io/library/mongo:4.2.2
コンテナの自動の再起動設定(restart policy)
コンテナが何らかの要因で終了してしまったり、Docker自体を再起動した際に自動で再起動するようにする機能があります。
なんらかの要因でプロセスが死んでしまって、手動で再起動したりせずに済むようになります。
設定はコンテナごとに行えます。docker container run
コマンドに--restart
オプションを指定することで設定ができます。
--restart
オプションには以下のいずれかの値を指定します。
- always: 一通りの条件でコンテナが再起動するようになる。
- on-failure: コンテナのプロセスがエラーで終了した場合や、Docker再起動時などに正常に終了しなかった場合などに再起動するようになる。
- unless-stopped: 明示的にstopコマンドでコンテナが停止している場合にDockerを再起動などしてもコンテナは再起動しない。それ以外はalwaysと同様大体再起動する。
※明示的に対象のコンテナをstopコマンドで手動で止めた場合にはalwaysでも再起動しません(そこで再起動してしまうと止めれなくなりますしね・・・)。ただし、停止させている状態でDocker自体を再起動した際などには該当コンテナも再起動します。
実際に試してみます。実験のため、一旦起動しているコンテナを全て停止し、lsコマンドの結果が空になるようにしておきます(起動中のものがあればdocker container stop
コマンドで止めておきます)。
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
まずは--restart always
から試していきます。
always_restart_ubuntu
という名前で進めていきます。
$ docker container run -it --restart always --name always_restart_ubuntu ubuntu:latest bash
Ctrl + P, Ctrl + Qで起動したままbashを抜けます。
lsコマンドで、該当のコンテナが起動していることを確認します。
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
eb83d8b715ed ubuntu:latest "bash" 8 seconds ago Up 7 seconds always_restart_ubuntu
Dockerを再起動してみて、該当のコンテナも再起動してくれることを確認します。
Dockerの再起動は左下の常駐アプリのクジラのアイコンを右クリックした際に表示されるコンテキストメニューの「Restart...」から実行できます。
警告が出ますが特に問題ないのでそのまま進めます。
再起動中、クジラのアイコンにマウスオーバーすると、再起動中ですよ、と表示されます。少し待つとDockerの再起動が完了します。
lsコマンドで起動中のコンテナを表示してみます。
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
eb83d8b715ed ubuntu:latest "bash" 7 minutes ago Up About a minute always_restart_ubuntu
該当の--restart
設定をしていたコンテナが無事起動していることが分かります。
また、CREATED
が約7分前になっている一方で、STATUS
の方が約1分前の起動と表示されていることから、Docker再起動と共に停止され、Dockerに合わせてコンテナも新たに起動したことが分かります。
Dockerfileとは
Dockerfileとは、コンテナのイメージを作るときの指示書のようなものです。
自分でイメージを作るときに「こんな感じのコンテナのイメージにしてね」とDockerに伝えるために使用します。
決められたフォーマットがあるのでそちらに準じて書いていった後に、docker image build
コマンドでイメージを作ることができます。
自分でイメージを作って(ビルドして)みる
自分でDockerfileを用意してみて、buildコマンドでビルドしてみます。
最初なので、最小限の構成で、Ubuntuのみ指定する形でまずは進めてみます。
任意の空のフォルダを用意します。今回はbuild_test_1という名前で進めます。
そちらのフォルダにDockerfileという名前のファイルを追加します(拡張子は設定しません)。
FROM ubuntu:18.04
FROM
は、ベースとなるイメージの指定です。OSのイメージなど、基本的なものを指定します(今回はUbuntuを指定しています)。
Dockerfileの先頭に記述します。
また、タグはlatestだと将来のビルドタイミングで以前と異なるバージョンのUbuntuになってしまうので、固定のバージョンを指定しています。
なお、Dockerのベストプラクティスの資料では、公式のイメージのリポジトリをFROMに指定することが推奨されています。
可能であれば、自分のイメージの元として現在の公式レポジトリを使います。私たちは Debian イメージ を推奨します。これは、非常にしっかりと管理されており、ディストリビューションの中でも最小(現在は 100 MB 以下)になるよう維持されているからです。
Dockerfile のベストプラクティス
ビルド実行の前に、Windowsのコマンドプロンプトで、このDockerファイルが設置されているフォルダに移動します。
$ cd build_test_1
dirコマンド(Windowsにおけるlsコマンドに近いコマンド)でファイルを一応確認しておきます。
$ dir
2020/01/22 17:34 19 Dockerfile
ビルドはdocker image build -t <イメージに付ける名前>:<タグ名> <Dockerfileが設置されているパス>
のフォーマットのコマンドで実行します。
今回は、イメージ名はubuntu_build_test_1という名前にし、タグ名は0.0.1とします。
Dockerfileが設置されているパスに関してはDockerfile設置ディレクトリにまで移動済みなので、直下のパスを指定します。
$ docker image build -t ubuntu_build_test_1:1.0.0 ./
エラー無く終了しました。
Sending build context to Docker daemon 2.048kB
Step 1/1 : FROM ubuntu:18.04
18.04: Pulling from library/ubuntu
Digest: sha256:8d31dad0c58f552e890d68bbfb735588b6b820a46e459672d96e585871acc110
Status: Downloaded newer image for ubuntu:18.04
---> ccc6e87d482b
Successfully built ccc6e87d482b
Successfully tagged ubuntu_build_test_1:1.0.0
ただ、最後に警告が出ています。
SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.
ふむ・・・WindowsでLinuxをビルドすると、すべてのファイルのパーミッションが固定の値になるようで。センシティブなファイルやディレクトリはちゃんとパーミッション変えておいてね、といった具合のようです。
実務で使う際には要チェックですね。
今回はお試しなので一旦そのまま進めます。
lsコマンドを実行してみると、自分でビルドしたイメージがちゃんと追加になっていることが分かります。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
...
ubuntu_build_test_1 1.0.0 ccc6e87d482b 6 days ago 64.2MB
もちろん、今まで試してきた各dockerのコマンドも使えます。
以下のように、起動することができます。
$ docker container run -it ubuntu_build_test_1:1.0.0 bash
DockerfileにPythonのインストール関係を追記する
前節でビルドしたUbuntuのイメージでPythonコマンドを実行してみると、Pythonが入っていないことが分かります。
root@762ce425ca58:/# python -V
bash: python: command not found
これは、恐らく「各コンテナは無駄をそぎ落として小さくしておくべし」的なベストプラクティスに準じているのかな、という印象です。その分、公式のUbuntuなどのイメージは大分小さくなっています。
Dockerfile で定義されたイメージを使って作成されるコンテナは、可能ならばエフェメラル(短命;ephemeral)にすべきです。私たちの「エフェメラル」とは、停止・破棄可能であり、明らかに最小のセットアップで構築して使えることを意味します。
...
複雑さ、依存関係、ファイルサイズ、構築階数をそれぞれ減らすために、余分ないし不必要な「入れた方ほうが良いだろう」というパッケージは、インストールを避けるべきです。
Dockerfile のベストプラクティス
そこで、Python関係のインストールをUbuntuのイメージに追加してみたいと思います。実務の場合にはpipenvやらpoetryとか色々絡んでくると思いますが、Dockerの記事なので今回はその辺りは割愛します。
後でDockerfileに記載しますが、以下のようなコマンドが最低限必要になります。Dockerfileに記載する前に、直接1つずつ実行してみて、コマンドが通ることを確認してみます。
[1]. Ubuntuでインストール可能なパッケージの一覧を更新する
apt-get install
コマンドなどでPythonをインストールする際に、事前に1回実行が必要になります。
root@762ce425ca58:/# apt-get update
Get:1 http://security.ubuntu.com/ubuntu bionic-security InRelease [88.7 kB]
Get:2 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB]
Get:3 http://security.ubuntu.com/ubuntu bionic-security/restricted amd64 Packages [23.7 kB]
...
Get:17 http://archive.ubuntu.com/ubuntu bionic-backports/main amd64 Packages [2496 B]
Get:18 http://archive.ubuntu.com/ubuntu bionic-backports/universe amd64 Packages [4241 B]
Fetched 17.5 MB in 58s (303 kB/s)
Reading package lists... Done
[2]. Pythonインストール用のコマンドを実行する
-y
の指定が無いとインストールする?しない?と聞かれてしまうので、Dockerfileに移すときのことを考えて-y
のオプションを追加しておきます。結構時間がかかるので実行後少し放置します。
root@762ce425ca58:/# apt-get install -y python3.7 python3.7-dev
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
file libc-dev-bin libc6-dev libexpat1 libexpat1-dev libmagic-mgc libmagic1 libmpdec2 libpython3.7 libpython3.7-dev libpython3.7-minimal
libpython3.7-stdlib libreadline7 libsqlite3-0 libssl1.1 linux-libc-dev manpages manpages-dev mime-support python3.7-minimal readline-common xz-utils
zlib1g-dev
...
Setting up python3.7 (3.7.5-2~18.04) ...
Setting up libpython3.7:amd64 (3.7.5-2~18.04) ...
Setting up libpython3.7-dev:amd64 (3.7.5-2~18.04) ...
Setting up python3.7-dev (3.7.5-2~18.04) ...
Processing triggers for libc-bin (2.27-3ubuntu1) ...
特にエラーなく通ったようです。
正常に動いていることを確認するため、バージョンを確認したり、Pythonのインタラクティブシェルでprintだけ動かしてみます。
root@762ce425ca58:/# python3.7 -V
Python 3.7.5
root@762ce425ca58:/# python3.7
Python 3.7.5 (default, Nov 7 2019, 10:50:52)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('Hello Python!')
Hello Python!
動いてますね。問題なさそうです。
前述までのコマンドをDockerfileに記載していきます。
Dockerfile内でコマンドを指定するには先頭にRUNと記載します。
複数のコマンドを記載するには、複数のRUNを書くか、&&で繋げられるようです。&&で繋げた場合、1行が長くなるので適宜見やすいようにバックスラッシュ(\)などで改行を加えます。
※複数のRUNを記載する方法に関して、RUNを書くたびにイメージのレイヤーが増えるのと、ベストプラクティスではレイヤー数を最小にするように、との記載があります。RUNを複数書いた方がある程度読みやすくなるケースが出そうですが、レイヤー数が多くなりすぎると好ましくない点が出てきます。
レイヤの数を最小に
Dockerfile の読みやすさと、使用するイメージレイヤーの数を最小化。両者のバランスを見つける必要があります。戦略的に注意深くレイヤー数を使います。
Dockerfile のベストプラクティス
RUNはなるべくまとめる
RUNでも不要なイメージのレイヤーが作成されることを防ぐため、なるべくひとつのRUN文に必要な処理を書いてしまいます。
Dockerfileを書くときに気をつけていること10選
軽く調べてみた感じ、レイヤー数が多くなると、パフォーマンスと容量を消費する、といった感じでしょうか。
レイヤ数の削減(最小化)
• Dockerの古いバージョンでは、確実に性能を出すため イメージ・レイヤ数の最小化が非常に重要だった
• 現在は、RUN COPY ADD 命令時のみレイヤを作成し、容量を消費
Dockerfile を書くためのベストプラクティス解説編 33ページより引用。
今回は&&を使う形で記載していってみます。
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y \
python3.7 \
python3.7-dev
ビルドしています。タグのバージョン部分を1.0.0
から1.0.1
に切り替えています。
$ docker image build -t ubuntu_build_test_1:1.0.1 ./
なんだか若干の警告が出ましたが、今回は問題ないだろうと判断して進めます。
...
debconf: delaying package configuration, since apt-utils is not installed
...
update-alternatives: warning: skip creation of /usr/share/man/man1/lzma.1.gz because associated file /usr/share/man/man1/xz.1.gz (of link group lzma) doesn't exist
...
Successfully built e82c7cfa3b67
Successfully tagged ubuntu_build_test_1:1.0.1
起動してPythonが使えるか確認してみましょう。こちらもタグの部分を1.0.1
と切り替える必要があるので注意してください。
$ docker container run -it ubuntu_build_test_1:1.0.1 bash
root@2902e7eb95df:/# python3.7 -V
Python 3.7.5
ちゃんと入っています。簡単で素晴らしいですね・・・。
pythonのエイリアスをDockerfileに設定する
このままだとPythonを動かしたりする際にpython
ではなくpython3.7
と入力する必要があります。
入力が増えたり、バージョンを変えたときにアプリのコマンドも変更しないとなので、pythonと入力したらインストールされているPythonが起動するようにしておきます。Linuxのエイリアスを使います。
まずは今まで同様に、Dockerfileに追記する前に、bash上で動作を確認していきます。
単純にaliasのコマンドを実行しただけではコンテナをスタートさせた際に有効になってくれないので、bashrcに追記する形を考えます。
且つ、一度停止して再度スタートさせてみたりしても問題ないことを確認するため、一度ubuntu_alias_test
という名前をコンテナに付けて起動しておきます。
$ docker container run -it --name ubuntu_alias_test ubuntu_build_test_1:1.0.1 bash
root@c2f056bf6daa:/# echo 'alias python="/usr/bin/python3.7"' >> /root/.bashrc
root@c2f056bf6daa:/# source /root/.bashrc
python3.7ではなくpythonでコマンドが実行できることを確認します。
root@c2f056bf6daa:/# python -V
Python 3.7.5
大丈夫そうです。
一度、containerを停止させてから、再度起動してみてもエイリアスが効いていることを確認します。
exitコマンドでbashを抜けると共に、containerを停止させます。
root@c2f056bf6daa:/# exit
再度起動してbashに切り替えます。
$ docker start ubuntu_alias_test
$ docker container exec -it ubuntu_alias_test bash
エイリアスが効いているか確認します。
root@c2f056bf6daa:/# python -V
Python 3.7.5
大丈夫そうなので、Dockerfileに前述のコマンドを追記していきます。
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y \
python3.7 \
python3.7-dev \
&& echo 'alias python="/usr/bin/python3.7"' >> /root/.bashrc
タグのバージョンを上げてビルドしてみます。
$ docker image build -t ubuntu_build_test_1:1.0.2 ./
ビルドしたイメージで起動します。
$ docker container run -it ubuntu_build_test_1:1.0.2 bash
aliasが効いているか確認します。
root@50c41d6289ca:/# python -V
Python 3.7.5
大丈夫そうです。
タグについてもう少し詳しく
タグは、一つのイメージに対して複数設定することができます。
ただし、同じタグを複数のイメージに付けることはできません。例えば、latestというタグを複数のバージョンのイメージに付けたりはできません。
少し試してみます。
前述までの処理でビルドされた、ubuntu_build_test_1:1.0.2
というイメージとubuntu_build_test_1:1.0.1
という二つのイメージを使っていきます。
タグを追加するにはdocker tag <イメージID> <新しいイメージ名>:<追加するタグ名>
というフォーマットでコマンドを実行します。イメージ名を変える必要が無い場合は<新しいイメージ名>
部分はそのままの値を指定します。
lsコマンドでイメージIDを調べます。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu_build_test_1 1.0.2 f2b89ec36f84 14 minutes ago 241MB
ubuntu_build_test_1 1.0.1 e82c7cfa3b67 12 hours ago 241MB
試しに、1.0.1のタグが付いている方のイメージにlatestというタグを追加してみます。
$ docker tag e82c7cfa3b67 ubuntu_build_test_1:latest
もう一度lsコマンドで内容を確認してみます。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu_build_test_1 1.0.2 f2b89ec36f84 17 minutes ago 241MB
ubuntu_build_test_1 1.0.1 e82c7cfa3b67 12 hours ago 241MB
ubuntu_build_test_1 latest e82c7cfa3b67 12 hours ago 241MB
IMAGE IDの列を見ると同じIDのイメージが2行あり、1つのイメージに複数のタグが設定できていることが分かります。
続いて、追加で1.0.2のタグが設定されているイメージの方にlatestのタグを追加するコマンドを実行するとどうなるでしょう?試してみます。
$ docker tag f2b89ec36f84 ubuntu_build_test_1:latest
$ docker image ls
ubuntu_build_test_1 1.0.2 f2b89ec36f84 13 hours ago 241MB
ubuntu_build_test_1 latest f2b89ec36f84 13 hours ago 241MB
ubuntu_build_test_1 1.0.1 e82c7cfa3b67 24 hours ago 241MB
1.0.1の方のイメージに付けていたlatestのタグが剥がされていることが分かります。このように同一のタグは複数のイメージに設定できないため、古い方のイメージに設定されているタグは無くなります。
イメージからタグを削除する
イメージからタグを削除するにはdocker rmi <イメージ名>:<タグ名>
のフォーマットのコマンドを利用します。
latestタグを消してみます。
$ docker rmi ubuntu_build_test_1:latest
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu_build_test_1 1.0.2 f2b89ec36f84 13 hours ago 241MB
ubuntu_build_test_1 1.0.1 e82c7cfa3b67 24 hours ago 241MB
1.0.2のイメージに付いていたlatestタグが消えました。
イメージにタグが1つも設定されていないとどうなるのか
先ほどは2つタグの付いているイメージから1つタグを取り除きましたが、1つしかタグを持っていないイメージからタグを取り除くとどうなるのでしょう?こちらも試してみます。
1.0.1のタグを消してみます。
$ docker rmi ubuntu_build_test_1:1.0.1
Untagged: ubuntu_build_test_1:1.0.1
Deleted: sha256:e82c7cfa3b67a7c1354c7640ce53e0000a1d500176a63d2e86270581c3efbe46
Deleted: sha256:cb5593f33928c36ae1b90ce2fb1c469dbeb49617b100a7ddebf7bd164f7dbacf
どうなったのか確認してみます。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu_build_test_1 1.0.2 f2b89ec36f84 24 hours ago 241MB
rmi
コマンドの場合、イメージごと消えたようです。
では、latestタグだけ設定されているイメージで、別のイメージにlatestを設定した場合(latestタグが剥がされるケース)ではどうなるのでしょうか。
試しに、別途イメージをビルドしてみて試してみます。
Dockerfileの内容が一緒のままだと、1.0.2と同じイメージとして扱われてしまうので、一時的に末尾に意味の無いコマンドを追加してみます。
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y \
python3.7 \
python3.7-dev \
&& echo 'alias python="/usr/bin/python3.7"' >> /root/.bashrc \
&& echo 'temporary'
latestタグを付けてビルドします。
$ docker image build -t ubuntu_build_test_1:latest ./
1.0.2とは別のイメージになっていることを、IMAGE IDで確認します。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu_build_test_1 latest 734018f9903b 44 seconds ago 241MB
ubuntu_build_test_1 1.0.2 f2b89ec36f84 24 hours ago 241MB
1.0.2側にlatestタグを設定してみます。
$ docker tag f2b89ec36f84 ubuntu_build_test_1:latest
どうなったか確認します。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 734018f9903b 8 minutes ago 241MB
ubuntu_build_test_1 1.0.2 f2b89ec36f84 24 hours ago 241MB
ubuntu_build_test_1 latest f2b89ec36f84 24 hours ago 241MB
今度はタグやリポジトリの表示が<none>
となりました。
他のイメージにタグが移ってタグが0件になった場合には、このように削除ではなく<none>
表示となります。
pipをDockerfileに追加する
少しPythonライブラリ関係も試してみようと思いますので、pip関係を追加していこうと思います。
今まで同様、コンテナで直接コマンドを打って通るかどうか確認します。
また、インストール順などでぼちぼち躓いたので、Ubuntuのイメージから順番に進めて検証します(この辺り、古い情報だとpip側で色々更新されていて躓きますね・・・)。
※参考にさせていただきました : Python3.7 + Pipenv環境をUbuntu18.04 LTSに構築
$ docker container run -it ubuntu:18.04 bash
root@d6c0ea41779a:/# apt-get update
root@d6c0ea41779a:/# apt-get install -y python3.7-dev
root@d6c0ea41779a:/# apt-get install -y python3.7
root@d6c0ea41779a:/# apt-get install -y python3-distutils
root@d6c0ea41779a:/# apt-get install -y wget
root@d6c0ea41779a:/# echo 'alias python="/usr/bin/python3.7"' >> /root/.bashrc
root@d6c0ea41779a:/# cd tmp/
root@d6c0ea41779a:/# wget https://bootstrap.pypa.io/get-pip.py
root@d6c0ea41779a:/# python3.7 get-pip.py
変更点として、
- get-pip.pyを経由するようにしたのでwgetなどを先にインストールした。
- python3-distutilsが無いとpipインストールで怒られるのでPython3.7インストールの後にインストールした(先だと別のPythonバージョンのみにインストールされたりと、pipインストール時に弾かれた)。
といったところです。
ビルドしてみます。
コマンドで実行してみて問題なさそうなので、Dockerfileを編集します。
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y \
python3.7-dev \
python3.7 \
python3-distutils \
wget \
&& echo 'alias python="/usr/bin/python3.7"' >> /root/.bashrc \
&& cd tmp / \
&& wget https://bootstrap.pypa.io/get-pip.py \
&& python3.7 get-pip.py
$ docker image build -t ubuntu_build_test_1:1.0.3 ./
ビルド自体は問題なく通ったようです。ただ、過程で警告が出ています。
WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip.
Please see https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue.
To avoid this problem you can invoke Python with '-m pip' instead of running pip directly.
該当のリンク見てみましたが、なんとも今回のケースで解決できなさそう(そもそもget-pip.py経由なのに-m pip
してくれという感じだと解決できなさそう)なのと、ネットで調べている感じほとんど情報がヒットしないので、一旦このまま進めます・・・(なんだか将来のアップデートで自動的によしなになりそうな気がする)
起動して、pipコマンドを試してみます。
$ docker container run -it ubuntu_build_test_1:1.0.3 bash
root@c1beaf56d656:/# pip -V
pip 20.0.2 from /usr/local/lib/python3.7/dist-packages/pip (python 3.7)
無事pipが入ったようです。少々、この辺りまだまだ洗練されてシンプルになるといいな・・・とは思いました(エラーやら警告やら、新旧情報が色々あって迷う点など・・・)。
(毎年のようにPython環境構築記事が出ますが、似たような感じでまだまだ改善途中という印象が・・・)
イメージのビルドのキャッシュについて考える
DockerfileのRUN部分はまとめたほうがいいと先ほどは記述しました。
ただし、分けたほうが好ましいケースがあり、そのうちの一つにビルドのキャッシュ面があります。
イメージのビルド処理では、Dockerfileの記述が上から順番に実行され、もし途中まで以前実行したビルドと同じ内容であればそこはキャッシュが参照され、ビルドの短縮化が実行されます。
一方で、RUNコマンドをまとめていると、そこに新たに記述を追加したりするとそのRUNコマンドの一通りの実行が必要になってしまいます。
そのため、よく更新されるものなどはDockerfile内で下の方に、且つRUNコマンドなどは分ける形で記述しておくと日々のビルドが短くなります。
試しに、さきほど更新したDockerfileの最後に、別のRUNコマンドでライブラリ(Django)をインストールする記述を追加します(今回はrequirement.txtなどでのライブラリ管理はスキップします)。
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y \
python3.7-dev \
python3.7 \
python3-distutils \
wget \
&& echo 'alias python="/usr/bin/python3.7"' >> /root/.bashrc \
&& cd tmp / \
&& wget https://bootstrap.pypa.io/get-pip.py \
&& python3.7 get-pip.py
RUN pip install \
Django==3.0.2
ビルドしてみます。
$ docker image build -t ubuntu_build_test_1:1.0.4 ./
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM ubuntu:18.04
---> ccc6e87d482b
Step 2/3 : RUN apt-get update && apt-get install -y python3.7-dev python3.7 python3-distutils wget && echo 'alias python="/usr/bin/python3.7"' >> /root/.bashrc && cd tmp / && wget https://bootstrap.pypa.io/get-pip.py && python3.7 get-pip.py
---> Using cache
---> 09019f05baa6
Step 3/3 : RUN pip install Django==3.0.2
---> Running in e485f302a7f7
Collecting Django==3.0.2
Downloading Django-3.0.2-py3-none-any.whl (7.4 MB)
Collecting asgiref~=3.2
Downloading asgiref-3.2.3-py2.py3-none-any.whl (18 kB)
Collecting pytz
Downloading pytz-2019.3-py2.py3-none-any.whl (509 kB)
Collecting sqlparse>=0.2.2
Downloading sqlparse-0.3.0-py2.py3-none-any.whl (39 kB)
Installing collected packages: asgiref, pytz, sqlparse, Django
Successfully installed Django-3.0.2 asgiref-3.2.3 pytz-2019.3 sqlparse-0.3.0
Removing intermediate container e485f302a7f7
---> 1d076e3f7bb9
Successfully built 1d076e3f7bb9
Successfully tagged ubuntu_build_test_1:1.0.4
SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.
最後のDjangoインストール以外同じ内容なため、ビルドが大分短時間で終わりました。
コマンド実行時の出力を少しよく見てみると、キャッシュが使われている点などが確認できます。
...
Step 2/3 : RUN apt-get update && apt-get install -y python3.7-dev python3.7 python3-distutils wget && echo 'alias python="/usr/bin/python3.7"' >> /root/.bashrc && cd tmp / && wget https://bootstrap.pypa.io/get-pip.py && python3.7 get-pip.py
---> Using cache
---> 09019f05baa6
...
このように、RUNコマンドなどは無理して少なくするというものでもなく、例えば「Pythonやpipのバージョンはたまにしか更新しない」「ライブラリはそれなりの頻度でバージョンの更新や追加などを行うので分ける」「コード部分なども分ける」といった感じに適宜調整すると、ビルドが快適になります。
参考文献・サイトまとめ
- Docker Deep Dive
- コンテナーとは? Kubernetesとは? 導入や運用、ユースケースを解説
- docker exec -itって実際は何をしてるの?【90日目】
- Dockerコンテナの作成、起動〜停止まで
- 2018年なぜ私達はコンテナ/Dockerを使うのか
- [docker] コンテナを一括削除
- Start containers automatically
- Dockerのrestart policyごとの違いを表でまとめてみる
- Dockerfileの書き方と使い方
- Dockerfileの書き方, 利用する命令, 作成手順
- Dockerfile のベストプラクティス
- Linux Ubuntu — apt-get update / upgrade の違い
- Python3.7 + Pipenv環境をUbuntu18.04 LTSに構築
- fnndsc/ubuntu-python3
- Dockerfileを書くときに気をつけていること10選
- Dockerfile を書くためのベストプラクティス解説編
- How do I alias python2 to python3 in a docker container?
- 【Docker】イメージ名とタグ名を変更する方法
- Docker imageにtagを追加・削除する方法
- wgetのインストール
- Docker上のCentOSにPython3と、関連ライブラリpip, virtualenvとフレームワークDjango, bottle, Flaskのインストール!これらをまとめたDockerfile付き!
- Python3.7 + Pipenv環境をUbuntu18.04 LTSに構築