Dockerは、2013年3月にDocker, Inc.からリリースされたオープンソースのコンテナ型の仮想化ソフトウェアです。
当初は、Linuxコンテナベース、その後、更に発展したというDockerコンテナをベースとしているとのこと。
コンテナ型仮想化の原点には、Linuxのchroot、これが機能拡張された恰好のFreeBSDのjail、これに似たLinuxコンテナ(LXC)があり、他方でスタンドアローン型の仮想マシンやハイパーバイザー型仮想化があります。
chroot(CHange root)は、上のパスに移動できない(とあるプロセスの)ルートディレクトリを指定した他の場所に設定(変更)することができ、デバイスを含む必要なファイル類をそこにコピーしてから実行、その後は、その仮のルートディレクトリ内のディレクトリやファイルを扱うことはできますが、ネットワークや1つのプロセス内にあるため、プロセスを制御することはできないchroot監獄とも呼ばれるプロセス空間です。
jail(監獄)は、chrootでできることに加え、root権限を降格させつつ、外部のネットワークやプロセスも制御でき、ルータの実装などにも有用で実用化もされていますが、別の仮想OSを起動させることはできません。
LXCは、cgroup(cgroups)の隔離機能を軸にjailでできることに加え、仮想OSを動かすこともでき、この時、OSのカーネル、物理マシンのCPUやRAMを共有、効率的に資源配分するため、負荷が少なく軽量、高速に動作しますし、その構造上、物理マシン上だけではなく、仮想マシン上やクラウド上でも機能しますが、カーネルを共有するため、様々なLinux環境を構築できるものの、Linux以外の他のOS環境を構築することはできません。
また、LXCは、他から見えない状態で隔離されることもあり、複数の仮想OSを一元管理することは想定されていません。
仮想マシンには、VMware Player/VMware WorkstationやVirtualBoxなどのスタンドアローン型やVMware ESXi、Citrix Xen Server、Microsoft Hyper-V、LinuxのKVMなどのハイパーバイザ型があります。
スタンドアローン型の仮想マシンは、ホストとなる物理マシンのOS上に仮想的なコンピュータ環境を作り、その上に仮想的なOS環境を作るので一般にCPUには左右されるものの、ホストOSとは別の種類のOS環境も構築できますが、物理マシンへの負荷が非常に大きく、物理マシンのスペックにもよるとはいえ、一度に複数の仮想マシンを起動させることは、あまり、現実的ではありません。
一方、ハイパーバイザ型は、物理マシンの資源を自動的に配分し、複数の仮想マシンを起動・制御できることを意図して考案されたものでホストOSと異なる種類のOSも動かすことができますが、スタンドアローン型に比べれば、低資源で済むものの、やはり、物理マシンへの負荷は大きく、相応のスペックが要求されます。
コンテナ型仮想化であれば、少ない資源で仮想化を実現でき、同じ物理マシン上でも、というか、ある程度、低スペックなマシンでも複数の仮想OSを起動させることができるため、注目されています。
そのコンテナ仮想化の中でもLXCベースから誕生したDockerは、短期間でコンテナ仮想化の代表格とも言えるほどの知名度となっています。
なぜ、LXCではなく、Vagrantでもなく、Dockerだったのかと言えば、Linux用に留めることなく、macOSやWindows用のデスクトップ版をリリースしたこともあるでしょうが、やはり、単なるテキスト文書であるDockerfileに手順を書いておきさえすれば、同じ環境を何度でもいくつでも正確に、高速に自動的に作ってくれることが大きいものと思われます。
Dockerfileは、それさえあれば、外国だろうが、地球外だろうが?同じ環境を簡単に構築でき、ChefやPuppet、Ansibleなどのレシピツールと同様ですが、専用だけあって一貫性があって親和性が高いと言えるでしょう。
このようにDockerfileがあれば、同じ環境をすぐに正確に構築できるため、言葉は悪いですが、失敗したら、即捨てて、構築し直すのも容易なので何らかの環境を初めて構築する場合や試行錯誤することが予想される場合などにおいては、特に作業効率向上は、絶大です。
また、複数の仮想マシンの制御を行なうDocker-Composeや更に機能豊富でスケールも大きいSwarm(Docker Swarm、swarm mode)やKubernetesとの連携で個人やLinuxパソコン教室などはもちろん、システム開発現場でも、この上なく重宝するでしょうし、クラウド環境や大企業、企業グループのサーバ群にも十分対応できます。
Dockerには、CE/Community EE/Enterprise Editionがありますが、執筆時点においては、選択肢は、次のようにホストとなるOSやプラットフォームに依存します。
尚、About Docker CEにあるようにCE版には、stable(安定版)とTest(開発後テスト中のプレリリース版)、Nightly(開発継続中の最新版)の3つがあるとのこと。
Dockerのインストールについては、下記リンク先(日本語)やそこから張られたリンク先(英語)に詳細に書いてあり、OS別に丁寧に書いてあるので迷うことはないでしょう。
Dockerには、Docker-Hubという公式リポジトリがあり、公式イメージの他、ユーザーがアップしたイメージもあります。
また、Dockerには、プライベートリポジトリもあるようです。
ストレージ容量との相談もありますが、もちろん、ローカルに保存しておくこともできます。
Dockerでは、イメージを元にコンテナを作り、コンテナ(にパッケージされたOS)を起動して作業することになります。
Dockerはデフォルトでは、管理者権限を要しますが、Dockerインストール時に作成されるdockerグループにユーザーを追加して再ログインしておくとホスト側において当該ユーザーは、sudoなしでdockerコマンドを実行できます。
DockerのHello World![$ docker run hello-world]は、結果がわかりづらいため、ここでは、敢えてスルーすることにします。
最も簡単なコンテナの起動は、端末からこのようにタイプして実行するだけです。
たった、これだけで公式イメージであるubuntuを公式リポジトリから持ってきて環境を整え、CLIベースながらubuntuをすぐに使えるようになります。
もちろん、相応に構成すれば、デスクトップ環境を含め、GUIアプリの操作も可能です。
コンテナを終了する場合は、exitします。
尚、ここで指定したubuntuとは、元になるイメージファイルであり、ローカルになければ、公式リポジトリDocker-Hub内を探し、存在すれば、自動的にダウンロードされ、コンテナが起動します。
また、-itオプションは、-iと-tオプションであり、個別でも組み合わせても、順不同でも良いですが、これらセットで起動後のコンテナ内でshellを使う指定、コンテナを残したくない場合には、--rmオプションを使い、もし、コンテナを残したい場合には、--nameオプションでコンテナ名を付けることができ、--rmも--nameオプションもつけなかった場合には、コンテナ名が、自動的に付けられます。
先の例では、docker runが自動的に全てやってくれたわけですが、本来、順を追って手作業でやると...
docker pullで公式リポジトリからubuntuイメージをダウンロード、docker runでubuntuイメージを元にコンテナを起動...という流れになります。
この場合、前述のようにコンテナからexitした後は、コンテナ名が自動的に付与され、コンテナが残りますが、残ったコンテナの扱いについては後述します。
公式イメージは、Docker-HubのExplore Official Repositoriesから探すこともできますし、docker searchコマンドで探すこともできます。
ただ、Docker-Hubには、公式イメージだけでなく、ユーザーがアップしたイメージもあり、[--filter=is-official=true]を付けると公式イメージのみ、falseにすると公式以外のイメージ一覧、 このオプション自体付けないと公式イメージとユーザーイメージを含めた一覧が出力されます。
docker search KEYWORD結果の内、一部例外もありますが、基本的に公式イメージでないものは、スラッシュで区切られたREPOSITORY/TAGという表記になっています。
docker pull IMAGEやdocker run -it IMAGEするとローカルにイメージがダウンロードされ、後者の場合、コンテナが起動されると共にコンテナが作成されます。
イメージは、docker imagesやdocker image lsなどとすると存在するイメージを確認できます。
コンテナは、docker ps -aやdocker container lsのようにすると作成済みのコンテナ一覧を得ることができ、前者は、状態にかかわらず全ての一覧が表示されます。
コンテナは、イメージから作られ、docker create -it IMAGEなどとして起動はせずにコンテナを作成しておくこともできます。
既にローカルにあるイメージを指定して、こんな風にdocker runを実行すると前述の要領で自動でIMAGEから作成されたコンテナが起動します。
docker ps -aなどで確認すると起動中は、コンテナのSTATUSがUPに、起動後、exitやCtrl+p Ctrl+qした場合は、Exitedとなります。
Exited状態のコンテナを起動する場合は、docker ps -aなどで表示される情報を参考にCONTAINER IDかNAMESを指定してdocker startします。
もし、stopしてないよといった旨のエラーが出た場合は、次のようにdocker stopしてから、docker startします。
docker ps -aなどでSTATUSがUP状態のコンテナは、他の端末やそのタブで起動している可能性がありますが、それを止める場合や前述のようにstopしていないエラーが表示された場合は、docker stopします。
コンテナからexit、docker stop、doker startしたコンテナは、docker attachすることで再度、コンテナ内に入ることができ、exitする以前にインストールしたパッケージや作成したファイルなどの情報も保持されます。
このようにattachできる状態のコンテナがある場合、ExitedやUPなどの状態にかかわらず(UPなら他の端末やタブから)、docker commitすると、そのコンテナの状態を反映した新たなイメージを作成することができます。
docker commitする際には、既存のCONTAINER IDと、識別するためのREPOSITORY名とTAG名をコロンでつないで指定し、現在のイメージの状態を確定します。
自身には、その意味を見出すことができないものの、REPOSITORY:TAGを付けずにdocker commitを実行することもできますが、docker imageした際、それらの項目は、<none>となります。
ちょっと待つと出来上がって[sha256:....]と表示されます。
このREPOSITORY:TAGとは、前述した公式リポジトリにあったREPOSITORY/TAGと同じです。
REPOSITORY名とTAG名はローカル上では、何でも良いのですが、公式リポジトリDocker-Hubにアップロードする場合、ユーザー用イメージは、公式イメージと区別するために一意となるREPOSITORY名と自由に付けられるTAGの組み合わせを付与することになっています。
よってDocker-Hubに上げる際は、REPOSITORY名は、必然的に他と区別できる名称で、かつ、実質、自分のものだとわかる名称を付けることになるでしょう。
尚、Docker-Hubからダウンロードした公式イメージは、変更しなければ、ローカルでは、REPOSITORY名は、Docker-Hub上のもの、TAGは、latestなどになります。
docker commitしたイメージをdocker imagesなどで確認、docker runでそれを元にコンテナを作り、起動するとインストールしたパッケージが入っていることを確認できます。
docker stop後、Exited状態のコンテナは、docker rmで削除できます。
もし、stopしていないから削除できない旨のエラーが表示された場合は、docker stop後、docker rmしてみるとよいでしょう。
すべてのコンテナを一気に削除したい場合には、このようにします。
ただし、STATUSが[UP]状態では、削除したり、再接続したりできないので削除する場合は、先の通り、docker stopでExited状態にしてから、Exited状態のコンテナに再接続する場合は、doker startでSTART状態にしてから行ないます。
また、削除する場合、後述しますが、既存のコンテナを元に新たにコンテナを作ることもできるのですが、差分で成り立っているので、その場合には、新たに作ったコンテナを先に削除しないと元のコンテナは削除できません。
イメージも、それを使ったコンテナが存在しなければ、[$ docker images]などでIDや名前を確認してdocker rmiで削除することができます。
Dockerfileについても懇切丁寧に解説されています。
よって詳細は、リンク先に譲りますが、先と同じようなことをDockerfileを使ってやる場合、
任意のディレクトリを作り、その中にDockerfileという名前のファイルを置き、
最低限という意味では、FROM行だけでも構いませんが、リポジトリと同一のイメージを作るだけでは意味がないのでFROM行に元になるイメージ名ubuntu、RUN行に例えば、gitパッケージを追加インストールするものとして、この順でこのように書いておきます。
端末上で先のDockerfileのあるディレクトリにいることを確認しておきます。
[.]は、Dockerfileのあるパスで、この場合、カレントディレクトリを指しており、ドットを忘れないようにdocker buildを実行します。
この時、-t IMAGEを付けないとローカルに同名ファイルがない前提でDockerfileで指定したイメージ名と同名のイメージが作成され、-t IMAGEを付けるとIMAGEという名のイメージファイルが作成されます。
ある程度環境が整ったら、やることをすべてDockerfileに書いておけば、1から自動でイメージを作ってくれるので便利です。
というわけで任意のディレクトリを作り、このような内容のDockerfileを配置します。(==でバージョン指定もできます。)
当該ディレクトリに移動して...とすると自動的に、これらがインストールされた状態のubuntuが起動します。
尚、メンテナンス性向上のため、Dockerfileでは、区切れる部分は、適度にバックスラッシュ付きで1行ずつ書くことが推奨されています。
作成、生成、起動してみて満足がいけば、それまでに使った検証用のコンテナやイメージを削除しておくとよいでしょう。(前述のように他から依存されている状態のものは、依存を解消(依存しているものを先に削除)しないと削除できません。)
ここでは、GUIアプリもインストールしましたが、CLIベースのOSにおいて、これらを起動するための手順は、他にもあるので後述します。
尚、マニュアル、いわゆるmanを参照する場合、dockerfileなどを除き、各コマンドごとにハイフンでつなげた[docker-*]という書式になります...と思ったら、ハイフンなしというか、空白に替えても大丈夫っぽいです。
Dockerインストール後、いろいろいじっているとJupyter Notebookやグラフ描画などGUIアプリを使いたい衝動に駆られました。
Docker内でGUIを使おうという場合、X11を導入する情報が多い中、ソケット通信によりローカルで完結して実現するというDockerコンテナの中でGUIアプリケーションを起動させるの情報に惹かれました。
やってみたら、面倒もなく、Firefoxもjupyter notebookもホスト側の画面を利用して(ホスト側のアプリと同様にホスト側で)起動するに至りました。
また、Jupyter Notebookの起動にあたっては、もう1つ手順が必要でDocker上のjupyter notebookが起動できなくなったらが役立ちました。
Firefoxを直接起動する場合には、Jupyter Notebookのように、このような手順は必要ありませんでした。
具体的には、こんな風に実行します。"#(docker)"は便宜的な架空のプロンプトです。
このあたりでDockerfileをまとめてみると、こんな感じになりました。
ENV DISPLAY=":0"については、ENV DISPLAY :0 でも良く、前者はイコールで代入した変数をスペース区切りで複数指定可、後者は1行1つ。
Dockerでは、前者が推奨とのことです。
尚、以下の行も入れてみましたが、ホスト側でxhost:localをせずにdocker buildしたからか、そもそもビルド時点ではダメなのか、/tmp/.X11-unix/:のみで/tmp/.X11-unix/X0がありませんでした。
VOLUME /tmp/.X11-unix/:/tmp/.X11-unix/
あ、公式ドキュメントによれば、
VOLUME /tmp/.X11-unix/
か、
VOLUME ["/tmp/.X11-unix/"]
のようにするらしいです...が、ホスト側はコマンドラインから指定するのか?そうだとしたら、ここで設定する意味はどこに....?
docker run前にホスト側で[xhost local:](コンテナ側ホスト名も添えればベターとのこと)してrunに-v /tmp/.X11-unix/:/tmp/.X11-unix/としたら、/tmp/.X11-unix/X0ができ、jupyter notebook --allow-rootなどホスト側画面を使ってGUI起動させることができました。(DockerでTensorFlow/Keras開発環境を作る参照。)
また、元イメージは、Dockerが推奨するDocker-Hubのdebianにしようと思ったのですが、リポジトリにブラウザが見当たらなかったため、ubuntuとしました。
ホストでダメだったので当たり前ですが、試しにtensorflowとkerasを共に最新にしてみたところ、Illegal errorとなったため、安定して機能したバージョンを指定することにしました。