Dockerを使ってみる

更新日:2022年9月5日

Dockerとは

細かい説明はインターネット上に色々あるので、そちらにおまかせするとして・・・。
仮想化とコンテナの違いについてまとめた後、Dockerについて、私の理解をメモします。
(「仮想化」という大きな言葉の配下に、Dockerなどのコンテナも属するかと思いますが、ここでは、KVMやXenなどのことのみを仮想化と呼んでいます)

仮想化の説明

仮想化(完全仮想化、準仮想化)は、サーバーの環境そのものをまるっと、ゲストOSへ提供する。
(余談ですが・・・。完全仮想化は、CPUの実行コード変換も含むものだと思っていましたが、人によって定義が違うみたい??
なので、エミュレーターと完全仮想化を別のものとして捉える人もいる様子ですが、正確な定義はよくわかりませんでした・・・)。
仮想環境を1つ作ると、物理的にサーバーが1台作り上げるのと同じ意味合いとなる。
なので、その仮想環境へOSのインストールが必要だし、そのOSを動かすための設定(ユーザーの作成やネットワークの設定など)も必要となる。
仮想化でサーバーを起動してしまえば、物理サーバーが1台作り上げられたのと同じ状態となる。

結果、得られる利益として・・・。
10台あったサーバーを、1台の高性能なサーバーを用意し、そこに仮想環境を10個用意することで、1台のサーバーに集約できる。
(仮想環境はそれぞれ独立しているため、1つの仮想環境で何かあっても、他の仮想環境に影響が及ぶことがない)
仮想化サーバーにCPUやメモリなどのリソースが空いていれば、すぐにサーバー環境を作れるし、不要なサーバー環境の削除も簡単にすぐにできる。

コンテナの説明

コンテナは仮想化とは異なり、サーバーが作り上げられるわけではない。
そもそもコンテナと仮想化では、達成したい目標が異なる。
仮想化は、ハードウェアを仮想化して、物理的に1台のサーバーの上に、仮想化されたサーバーを何台も作って動かすことが目標。
コンテナは、自分が動かしたいソフトウェアの動作環境を独自に作り出すことが目標(独自に・・・とは、配置されているライブラリや設定ファイルなどのことを差す。メモリ空間とかそういう話では無い。もっと上位の次元の話)。

なので、コンテナという環境を作って、そこでソフトウェアを実行して動かすことが実現したいこととなる。
ソフトウェアを動かすということは、依存ライブラリや設定ファイルが必要となる。
なので、自分が動かしたいソフトウェアと、依存ライブラリ・設定ファイルなどをコンテナ内にまとめて保存する。
この1まとまりのファイル(=コンテナ)を、コンテナを実行する環境に持っていけば、コンテナ内に保存されたソフトウェアが、コンテナ内に保存されている依存ライブラリ・設定ファイルを使って、動かすことができる。
もちろん、コンテナはコピペ可能で、コンテナの実行環境があれば、コンテナのファイルをコピペして持っていけば、実行環境の設定不要で、ソフトウェアを動かすことができる。
(コンテナを実行するイメージとしては、chrootコマンドを使って、ディレクトリアクセスを制限した状態でソフトウェアを動かすのに似ている。もちろん、コンテナはディレクトリアクセスだけでなくプロセスについてもホストOSから切り離されたところで実行されているが)

■注意点
コンテナを実行する際、OSのカーネルは、ホストのものが使われるという点(たとえば、Dockerを動かしているのがCentOSのサーバーであれば、CentOSのカーネルが使われる)。
CentOS上で、ubuntuのコンテナを動かしていても、ubuntuのコンテナ内で動いているアプリケーションから見ると、ubuntuで動いているように見えたとしても、カーネルに関する部分についてはCentOSのものが使われることとなる。
CentOSのカーネルのバージョンが4系で、ubuntuのカーネルのバージョンが5系・・・なんてこともできるかもしれません。
その場合、5系にしか存在しない機能を実行した場合、「予期せぬ結果」が待っていると思います。
コンテナ実行時は、コンテナを動かすホストOSのカーネルが使われる・・・という点が、仮想化との決定的な違いなので、この点は覚えておく必要があるかなと思います。

もちろんですが、コンテナは、コンテナを動かすホストOS上で動くので、Linuxのコンテナ実行環境で、Windowsのコンテナを動かすというのは不可能。
そういうのは仮想化の仕事ですね。
(・・・ただし、Windows上では、WLSを使ってLinuxのコンテナを動かすことができます)

Dockerの説明

コンテナ=Docker・・・というわけではない(Dockerが有名になりすぎて、私は、そういうイメージを持っていました)。
コンテナ技術という考え方があり、その中の1つの実装としてDocker(Docker Engine)がある・・・という感じの様子。

基本的な内容としては、「コンテナの説明」で記載した説明した通りです。
Docker Engineというのがコンテナを実行する本体。
Docker Hubには、様々なコンテナがアップロードされていて、DLして使うことができる便利サービス。
その他いろいろある様子だが、これら全体をひっくるめて「Docker」と呼ぶ様子。

Dockerのドキュメントは、日本語化されており「https://docs.docker.jp/index.html」から見ることができます。

本番運用ではKubernetesなどのツールを使うことが多い様子。

Dockerの使い方

自分のPC(Windows10)にDocker Desktop for Windowsをインストールし、基本的な操作を試してみます。

Docker Desktop for Windowsのインストール

前提としてWindowsはProを使用します。
※一応、Homeでもできる様子ですが、手順が異なってくると思います。
 ここではHomeでの手順は省略します。
 Windowsのエディションによる差が出てくる理由は、DockerをHyper-Vで動かすかどうかという点です。
 Pro以上のエディションでなければ、Hyper-Vが使用できるようになっていないと思いますので・・・。

Docker Desktop for Windowsをダウンロードし、インストールします。
 → https://docs.docker.com/desktop/install/windows-install/
インストールが終わり、「restart」ボタンが表示されるので、「restart」ボタンを押下しWindowsを再起動させます。

PCが再起動後、Dockerのアプリが起動するのですが、WLS2がインストールされていないと、以下のポップアップが表示されます。

この場合、ダイアログに記載されている「https://aka.ms/wsl2kernel」をクリックしてください。
ブラウザが起動し、WSL2のダウンロードリンクが記載されたページに飛びます。
「x64 マシン用 WSL2 Linux カーネル更新プログラム パッケージ」をクリックすると、msiファイルがDLできるので、それをWクリックし、インストールしてください。
インストール完了後、上記ダイアログの「Restart」を押すことで、Dockerのウインドウが起動します。

インストールすると、コマンドプロンプトにもdockerコマンドのパスが通った状態になりますので、コマンドプロンプトからも操作可能な状態となります。
以上で、インストール完了です。

※補足
WSLとは、Windows Subsystem for Linuxというもので、Windows環境でも、Linuxのアプリを実行できるようにしよう・・・という拡張です。
標準ではインストールされていないので、追加でインストールする必要があります。

Dockerの基本的な使い方(コマンド)

DockerのhelloWorldを実行するコンテナがあります(helowWorldを出力し終了する)。
コンテナの中にhellowWorldを出力するアプリケーションが保存されており、そのアプリケーションを実行するものです。
コンテナ起動時には、コンテナ内のどのコマンドを実行するか、dockerコマンドの引数で指定します。
指定された処理(コンテナ実行時に指定されたコマンド)が終わると、コンテナは自動で終了します。

各種コンテナは、DockerHub(https://hub.docker.com/)にて公開されています。
docker pullコマンドを使うことで、コンテナをローカルにDLして使用することができます。
(webサイトを見る場合、アカウントを作らないといけないっぽい?dockerコマンドでコンテナをpullするときは、アカウント無くてもできるのに。。。)

docker pull hello-world

このコマンドを実行すると、hello-worldというコンテナがPCにダウンロードされます。
ダウンロードしたコンテナの一覧表示をやってみます。

C:\Users\admin>docker images コンテナのイメージ一覧表示のコマンドを実行。コンテナ起動時に「REPOSITORY」と「TAG」の名前が必要になる
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
hello-world   latest    feb5d9fea6a5   11 months ago   13.3kB

hello-worldというコンテナには、起動されると「Hello from Docker!」とメッセージを出力するプログラムが格納されています。
コンテナが起動すると、このプログラムを実行するよう、コンテナ内に設定されています。
そのため、このコンテナは起動すると、「Hello from Docker!」とメッセージ出力を行います。

では、「hello-world」コンテナを実行してみます。
正確にいうと、上記でダウンロードしたのはコンテナのイメージです。
コンテナのイメージはテンプレートのようなものなので、変更されることはありません。
コンテナ実行時、イメージを元にコンテナが生成され、実行されます。
そして、処理完了後はコンテナが終了します。
この実行したコンテナは自動では削除されず、残り続けます。
そのため、再実行も可能です。
再実行する予定がない場合、実行時のオプションで終了と共にコンテナの自動削除も可能です。
これらの一連の処理を実行してみます。

C:\Users\admin>docker run --name MyContainer1 hello-world:latest MyContainer1という名前を付与し、コンテナ起動(REPOSITORY+TAGでイメージを指定)

Hello from Docker!
(長いので省略)

C:\Users\admin>docker ps -a 実行中・実行終了両方のコンテナを一覧表示
CONTAINER ID   IMAGE                COMMAND    CREATED          STATUS                      PORTS     NAMES
64b6dc95c649   hello-world:latest   "/hello"   13 seconds ago   Exited (0) 13 seconds ago             MyContainer1

C:\Users\admin>docker run --name MyContainer2 hello-world:latest MyContaner2という名前をつけてコンテナを実行

Hello from Docker!
(長いので省略)

C:\Users\admin>docker ps -a 実行中・実行終了両方のコンテナを一覧表示(実行したコンテナは残り続ける)
CONTAINER ID   IMAGE                COMMAND    CREATED          STATUS                      PORTS     NAMES
93c0bb37683b   hello-world:latest   "/hello"   3 seconds ago    Exited (0) 2 seconds ago              MyContainer2
64b6dc95c649   hello-world:latest   "/hello"   21 seconds ago   Exited (0) 20 seconds ago             MyContainer1

C:\Users\admin>docker start -i MyContainer1 MyContainer1を指定し、再度実行させる

Hello from Docker!
(長いので省略)

C:\Users\admin>docker ps -a 実行中・実行終了両方のコンテナを一覧表示(MyContainer1のステータスの記載が変化している)
CONTAINER ID   IMAGE                COMMAND    CREATED          STATUS                      PORTS     NAMES
93c0bb37683b   hello-world:latest   "/hello"   38 seconds ago   Exited (0) 37 seconds ago             MyContainer2
64b6dc95c649   hello-world:latest   "/hello"   56 seconds ago   Exited (0) 5 seconds ago              MyContainer1

C:\Users\admin>docker rm 93c0bb37683b 64b6dc95c649 コンテナIDを指定し、コンテナを削除する(コンテナIDを複数指定可能)
93c0bb37683b
64b6dc95c649

C:\Users\admin>docker ps -a 実行中・実行終了両方のコンテナを一覧表示(上記ですべて削除したので何も表示されない)
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

C:\Users\admin>docker run --rm --name MyContainer2 hello-world:latest コンテナ実行後、自動削除されるオプション(--rm)をつけて実行

Hello from Docker!
(長いので省略)

C:\Users\admin>docker ps -a --rmオプションをつけているため、終了後に自動削除されるため何も表示されない
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

コンテナ起動時、--nameオプションをつけていますが、これを省略すると、ランダムな名前が設定されます(ランダム文字列ではなく、人の名前っぽい単語が付与されます。最初見たときは、いったい何なのかと思ってしまいます・・・)。
コンテナは再度実行もできますが、消さないと残り続けてHDDの残容量を圧迫します。
コンテナイメージからいつでも起動できるので、あまり残すようなケースは限られている感じがしますので、「--rm」オプションをつけて実行することが多いかもしれません。

ダウンロードしたコンテナのイメージが不要になることもあると思います。
残しておいてものHDDの残容量を圧迫するので、「hello-world」のコンテナイメージを削除してみます。

C:\Users\admin>docker images  ローカルにあるイメージの一覧を表示
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
hello-world   latest    feb5d9fea6a5   11 months ago   13.3kB

C:\Users\admin>docker rmi hello-world:latest イメージの削除(REPOSITORY+TAGでイメージを指定)
Untagged: hello-world:latest
Untagged: hello-world@sha256:7d246653d0511db2a6b2e0436cfd0e52ac8c066000264b3ce63331ac66dca625
Deleted: sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412
Deleted: sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359

C:\Users\admin>docker images ローカルにあるイメージの一覧を表示(削除したので何も表示されていない)
REPOSITORY   TAG       IMAGE ID   CREATED   SIZE

■その他
予測だけで書きますが・・・。
Docker for windowsをHyper-Vで動かす・・・という状態になっているので、「docker pull hello-world」を実行したときの、コンテナのDLしたファイルの保存先は、Hyper-Vの仮想ハードディスクとなるっぽい。
vhdxファイルは「C:\Users\(ユーザー名)\AppData\Local\Docker\wsl\data」の中にあるっぽい。
exploreで「\\wsl$」にアクセスしたときは、おそらく、上記の仮想HDDにアクセスしているのでは・・・と思う。
ちゃんと調べて確証を得たほうが良いが、本番環コンテナを実行方法など境などの大切な環境で使うことが無いので、気にしないことにする。
動けばいいことにする^^;

コンテナのコンソールを使う

DockerHubから、Linuxの各種ディストリビューションのコンテナをDLして、そのコンテナを起動後させれば、簡単にLinuxを操作する環境を作ることができます。

AlmaLinuxのコンテナを使ってみたいと思います。
コマンドラインから以下のコマンドを実行し、コンテナをダウンロードします。
DockerHubのサイトでalmalinuxを検索すると分かるのですが、現時点で「latest」タグがついているのはAlmaLinux8.6の様子です。

docker pull almalinux:latest  almalinuxの「latest」というタグがついたコンテナをダウンロード

その後、このコンテナを起動します。
起動するときに、オプションとして「--name」を使い、名前をつけると便利です(名前を指定しないと、ランダムな名前の文字列が自動でセットされます)。
コマンドを実行すると、コンテナのシェルに自動的に切り替わり、操作ができるようになっているはずです。
終了したい場合はexitでログアウトしてください。コンテナも停止します。

docker run -it --name almaLatest almalinux:latest

再度以下のコマンドでコンテナを起動すれば、前回の状態から復帰することができます。
(コンテナに「almaLatest」と名前をつけた場合)

docker start -i almaLatest

一瞬だけ試したくて、コンテナの保存が不要な場合、以下のように--rmオプションを付与して起動することで、コンテナ終了後、自動的に削除されます。

docker run -it --rm --name almaLatest almalinux:latest

コンテナを自作する(最もシンプルなもの)

「こんにちは!」と表示するだけのシェルを実行する、自作コンテナを作ってみようと思います。
やることとしては、「こんにちは!」と出力するだけのシェルを作成し、それをコンテナ内に配置し、動作させるだけです。

ベースとなるコンテナを選び、そのコンテナに対し、必要なコマンドを実行したり、必要なファイルをコピーしたりすることとなります。
ベースとなるコンテナは、DockerHubにあるものを選ぶことも、自分のオリジナルのものを選ぶでも、どちらも可能です。

DockerHubに、コンテナ内に何も入っていない「scratch」というコンテナがあります。
本当に何も入っておらず、C言語などで作成したアプリケーションの動作に必要な標準ライブラリなども無いので、何もかも自分で用意する必要があります。
何かにこだわりたい時以外は、選択肢として微妙です。

なので、各種LinuxのディストリビューションのコンテナがDockerHubに用意されています(「/etc」や「/lib」などのディレクトリや、コマンドなどがそろっているコンテナ)。
今回は「AlmaLinux」のコンテナをダウンロードしてきて、そこに実行ファイル(自作のシェルファイル)を配置し、コンテナを作成したいと思います。

コンテナを作成するときは、コンテナの中に必要なファイルを手動でコピーしていく・・・というのも可能ですが、Dockerの場合は、Dockerfileという設定ファイルにすべて記載し、自動化することが推奨です。
そのため、Dockerfileを作って、それを元にコンテナを作るということをやっていきます。

まず、PC上のどこかにディレクトリを作成します(以下の例では「D:\tmp\work\container1」)。
次に、そのディレクトリの中に「こんにちは!」を出力するシェルを作成します。
ファイル名は「showMsg.sh」とします。
「showMsg.sh」の中身は、以下の通りです。

#!/bin/sh

echo 'こんにちは!!'

そして、同じように、ディレクトリの中にDockerfileを作成します。
ファイル名は「Dockerfile」とします。
「Dockerfile」の中身は、以下のようにします。

FROM almalinux:9-minimal almalinuxの「9-minimal」のタグのついたコンテナを使用する
WORKDIR / カレントディレクトリを「/」に移動する
RUN mkdir appTmp 「mkdir appTmp」コマンドを実行する
COPY showMsg.sh /appTmp/showMsg.sh 「shoMsg.sh」ファイルを、コンテナに「/appTmp/showMsg.sh」としてコピーする
RUN chmod +x /appTmp/showMsg.sh 「chmod +x /appTmp/showMsg.sh」コマンドを実行する

CMD ["/appTmp/showMsg.sh"] コンテナ起動時に「/appTmp/showMsg.sh」を実行するように設定する

上記のDockerfileの補足・・・。
FROMでベースとなるコンテナを指定します。
WORKDIRは、必須ではないですが、いちいちコマンド実行時にフルパスを記載しなくてもよくなり、便利なことがあるため、こういうこともできる・・・という意図で書いてみました。
RUNコマンドは、コンテナ上でこのコマンドを実行する・・・というものです。mkdirをすれば、コンテナ上にディレクトリが作成されます。
COPYコマンドの第一引数は、自分のPC上にあるファイルのパスを指定します。そして第二引数に、コンテナ上のパスを指定することで、コンテナにファイルを転送します。

最後の行に記載してある「CMD」はコンテナ起動時に実行するコマンドを記載しています。

作業用に作成したディレクトリ(ここでは例として「d:\tmp\work\container1」)の中に、2つファイルを作成したので、ディレクトリの中は、以下のようになっているはずです。

次に、コマンドプロンプトを開き、作業用に作成したディレクトリ(d:\tmp\work\container1)に移動します。
そして、以下のコマンドを実行します。

docker build -t shell_showmsg:tag1 .

最後の「.」を忘れないようにしてください。
このコマンドを実行すると、カレントディレクトリにあるDockerfileを元に、コンテナを作成します。
コンテナには「shell_showmsg」という名前をつけ、タグには「tag1」としう文字列を設定しています。
無事ビルドが終わると、コンテナの一覧を出力をさせるコマンド「docker images」を実行すると、「shell_showmsg」というコンテナイメージが追加されているはずです。

D:\tmp\work\container1>docker images
REPOSITORY      TAG         IMAGE ID       CREATED         SIZE
shell_showmsg   tag1        f56e614a8470   5 seconds ago   118MB

サイズは118MBとありますが、これはベースとしているalmalinuxのコンテナがこのぐらいの容量があるため、サイズが大きくなっています。
では、いざ、実行してみます。
実行後のコンテナは残しておいてもやることがないので、終了後にコンテナを自動削除するオプション(--rm)をつけて、実行してみます。

D:\tmp\work\container1>docker run -it --rm shell_showmsg:tag1
こんにちは!!

「こんにちは!!」が表示されたかと思います。
「-it」オプションをつけているので、コンテナの標準出力がコマンドプロンプト上見えるようになっています。
Dockerfileの作り方次第で、色々なことができます。
細かいことはDockerのリファレンスを参照すると色々書いてあります → https://docs.docker.jp/engine/reference/builder.html

コンテナのデータ保存先について(volume/bind mount/tmpfs)

Dockerでは、コンテナのデータ保存先として使用できるものが、いくつか用意されています。
それぞれ、試していたいと思います。

種類 説明
volume 推奨されている方法。
Dockerを動かしているホスト上に、データ保存用のファイルを作成し、そのファイルをコンテナからマウントするようなイメージ。
DBのトランザクションのように厳密が動作が必要な場合、こちらを使用する(ネイティブなファイルシステムの動作が必要とされる場合)。
また、複数のコンテナで1つのvolumeをマウントし、ファイルの共有などが行える。
bind mount Dockerを動かしているホスト上の、任意のディレクトリを、データ保存先として指定できる。
コンテナからホスト上のディレクトリをマウントするようなイメージ。
tmpfs 一時データを保存するなどの場合に使用する。書き込まれたデータはメモリ上に保存される。
コンテナ終了時、保存されたデータは消える。

volume

volumeはあらかじめ、作っておくこともできますし、コンテナ起動時に使用するvolumeを指定した際、存在しない場合は自動的に作成もしてくれます。
手動でのvolumeの作成と削除を、コマンドの例として以下に記載します。

D:\tmp\work>docker volume create myVol1 「myVol1」という名前のvolumeを作成する
myVol1

D:\tmp\work>docker volume ls volumeを一覧表示する
DRIVER    VOLUME NAME
local     myVol1

D:\tmp\work>docker volume inspect myVol1 「myVol1」の詳細を表示
[
    {
        "CreatedAt": "2022-09-04T09:34:20Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/myVol1/_data",
        "Name": "myVol1",
        "Options": {},
        "Scope": "local"
    }
]

D:\tmp\work>docker volume rm myVol1 「myVol1」を削除する
myVol1

D:\tmp\work>docker volume ls 「myVol1」を削除したのでvolumeは1つも無い状態
DRIVER    VOLUME NAME

上記のinspectの結果を見る限り、データサイズの上限など、そういった設定は無い様子です。

では、これから起動するコンテナに、volumeを指定したディレクトリにマウントさせた状態で起動させる、コマンドの例を以下に示します。
コンテナ起動時にオプションを指定するだけです。
あらかじめvolumeを用意せずとも、無ければ自動でvolumeが作成されます。

docker run --name MyContA -it --rm --mount source=myVol1,target=/shareVol almalinux:latest

「--mount source=myVol1,target=/shareVol」の部分(赤字の部分)が、「myVol1」という名前のvolumeを、「/shareVol」にマウントしてコンテナを実行させるようにしています。
コンテナ起動時の引数に、同じように「--mount source=myVol1,target=/shareVol」をつけて、複数コンテナを起動することで、コンテナ間で「/shareVol」ディレクトリが共有ディレクトリのように使用することができます。

注意点として、volumeは自動的に削除されません。
手動で削除する必要があります。
残り続けてHDDの容量を圧迫する可能性があるので、不要なvolumeは削除するようにしなければなりません。
volume削除時の便利なコマンドとして、使用していないvoluemを一括削除してくれるコマンドがあります。
以下のコマンドを実行すると、Yes/Noの確認後、不要なvolumeを一括削除してくれます。

docker volume prune

bind mount

volumeはdockerに管理された領域でしたが、bind mountは単純にDockerをホストしているOS上のディレクトリを、コンテナから使用できるようにするものです。
複数のコンテナから同じbind mountのディレクトリを共有できます。
とてもシンプルなもので、少し試すぐらいならこちらが便利です(ただ、dockerとしては、volume使用を推奨しています)。
こちらも、コンテナ起動時のオプションで指定することとなります。

docker run --name MyCont1 -it --rm --mount type=bind,source=c:\tmp2,target=/shareVol almalinux:latest

赤字部分でbind mountを指定しています。
「/shareVol」ディレクトリを、Dockerのホスト上のディレクトリパスである「c:\tmp2」をマウントするようにしています。
指定したディレクトリが無い場合は作成してくれます。
Dockerをwindowsで動かしている場合は「c:\tmp2」のようなディレクトリパスになりますが、LinuxでDockerを動かしている場合は「/var/docker/share」などディレクトリのパスの書き方が違ってきます。

tmpfs

tmpfsは、単純にマウント先のディレクトリパスを指定するだけです。
以下の例では「/temp_fs」にtmpfsをマウントするようにしています。

docker run --name MyCont1 -it --rm --mount type=tmpfs,destination=/temp_fs almalinux:latest

また、「tmpfs-size」オプションを指定することで、サイズを制限することもできます(以下の例では1GBまでの制限付き)。

docker run --name MyCont1 -it --rm --mount type=temp_fs,destination=/tmpfs,tmpfs-size=1GB almalinux:latest

MariaDBをコンテナで動かしてみる

DockerHubを見ると、MariaDBなどのDBもコンテナとして提供されています。
MariaDBをコンテナとして起動して、使ってみたいと思います。

コンテナ初回起動時にDB初期化スクリプトが実行されるコンテナを作る

DBというものは、起動しただけだと、テーブルもレコードも無いし、ログインユーザーもありません。
MariaDBでは、初回起動時に、セットアップ用のスクリプトを実行できるようになっています。
今回はそちらを使って、DBの初期セットアップもやってみようとおもいます。

まずは、どこか適当なところにディレクトリを作り(今回の例では「D:\tmp\work\mariadb」)、そこに、以下の内容のsetup.sqlというファイルを作ります(ファイル名の制約は特にありません)。
setup.sqlでは「SampleDB」というDBを作成し、「normalUser」というユーザーを追加しています(パスワードは「password」)。
そして、適当にテーブルを作ってレコードを入れる・・・というサンプルです。

CREATE DATABASE `SampleDB`;
CREATE USER 'normalUser'@'%' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON `SampleDB`.* TO 'normalUser'@'%';



USE SampleDB;

create table SampleTable(
    id int,
    userName varchar(128),
    
    PRIMARY KEY(id)
);

INSERT INTO SampleTable(id, userName) VALUES(1, 'レコードその1');
INSERT INTO SampleTable(id, userName) VALUES(2, 'レコードその2');
INSERT INTO SampleTable(id, userName) VALUES(3, 'レコードその3');

次に、Dockerfileを作成します。
内容は簡単で、以下のように記載します。
mariadbのコンテナの中には「/docker-entrypoint-initdb.d」というディレクトリが用意されているので、そこにsqlファイルをおいておけば、コンテナの初回起動時に実行してくれるようになっています。

FROM mariadb:latest mariadbのlatestタグのついたコンテナをベースに使用します
COPY setup.sql /docker-entrypoint-initdb.d/ コンテナ内の/docker-entrypoint-initdb.dへ、setup.sqlファイルをコピー

作業用に作成したディレクトリ(今回の例では「D:\tmp\work\mariadb」)は、上記の2ファイルを保存すると、以下のようになっているはずです。

コマンドプロンプトを開き、作業用に作成したディレクトリ(今回の例では「D:\tmp\work\mariadb」)まで移動後、以下のコマンドを実行すると、コンテナのイメージが作成されます。
「my_mariadb_env」という名前をつけてタグには「tag1」という文字列をセットしています。

docker build -t my_mariadb_env:tag1 .

実際にDBアクセスできるかを試したいですが、これは次の手順を踏んでから試してみたいと思います。

別のコンテナからのDBアクセスについて(コンテナ間の通信設定)

コンテナ同士を通信させたい時があります。
たとえば、mariadbのサーバーをコンテナで起動し、別途コンテナを起動し、そのコンテナからmariadbをアクセスする・・・などの場合です。
コンテナ同士を通信させるために、Dockerが仮想の通信ネットワークを提供しています。
以下のコマンドを実行すると、Dockerに定義されたネットワークの一覧を見ることができます。

D:\tmp\work\mariadb>docker network ls dockerのネットワークの一覧表示
NETWORK ID     NAME           DRIVER    SCOPE
1133a2a87d03   bridge         bridge    local
69dd60eda7c8   host           host      local
3dc421556e26   none           null      local

これら3つは元々デフォルトで作られるものです。
この状態だと、コンテナ間で通信する場合、IPアドレスを直接指定すれば通信できる様子です。
ただし、その方法だと、IPアドレスを特定しなければならず、IPアドレスを固定値としてどこかに持っておかないといけなくなります。

IPアドレスの代わりに、コンテナの名前で接続しに行くことができます。
そのために、以下のコマンドでネットワークを追加します。

D:\tmp\work\mariadb>docker network create mynetwork mynetworkというネットワークを追加
f69788daf6c5e9f3d7a7ff94a9a25b65f9d6d4a68809cfd0b2e7f53c11f9afb3

D:\tmp\work\mariadb>docker network ls dockerのネットワークの一覧表示
NETWORK ID     NAME        DRIVER    SCOPE
1133a2a87d03   bridge      bridge    local
69dd60eda7c8   host        host      local
f69788daf6c5   mynetwork   bridge    local mynetworkというブリッジが追加されている
3dc421556e26   none        null      local

これでブリッジが追加されました。
すでに、「bridge」というブリッジが存在しますが、名前解決(コンテナ名からIPアドレスを解決してくれる機能)が無いらしい(よく確かめていませんが・・・とりあえず、デフォルトのbridgeでは通信できませんでした)。
なので、独自にブリッジを追加し、それを使用するようにします。
コンテナがどのブリッジを使用するかは、コンテナ起動時のオプションで指定できます。
こちらは、に記載いたします。

MariaDBのコンテナ起動と別コンテナからのアクセス

こちらに記載した、dockerのネットワークに「mynetwork」というブリッジを追加済みの状態を前提とします。
では、こちらで作成した「my_mariadb_env」というコンテナを、以下のコマンドで起動します。

docker run -d --name MyMariaDB1 --rm --network mynetwork  ^
--env MARIADB_ROOT_PASSWORD=rootPass ^
my_mariadb_env:tag1

(windowsのコマンドプロンプトで実行しているので、複数行に分割して記載するために「^」を使っています。Linuxだと「\」になります。)
「MyMariaDB1」という名前でコンテナを起動しています。
「--network」をつけて「mynetwork」というネットワークを使用するようにオプションを指定しています。
「--env MARIADB_ROOT_PASSWORD=rootPass」は、コンテナに設定値として渡しているものです。
mariadbのコンテナはこれを渡すと、rootのパスワードを指定したもの(ここでは「rootPass」)に設定してくれるようです。
上記コマンドでは「-d」オプションをつけて、コンソールをデタッチしています(このコンテナはずっと起動しっぱなしなので、「-d」をつけないと、コマンドプロンプトが入力できない状態になってしまうため)。

これで、mariadbのコンテナが起動できました。

次に、別途コンテナを起動して、mariadbのクライアントから、mariadbに接続してみます。
mariadbのコンテナにはmariadbのクライアントも入っているので、これを使います。
以下のコマンドでコンテナを起動します。

docker run -it --rm --network mynetwork my_mariadb_env:tag1 mysql -h MyMariaDB1 -u normalUser -p

「--network mynetwork」を指定して、mariadbのコンテナと同じネットワークを使用するように設定しています。
「mysql -h MyMariaDB1 -u normalUser -p」の部分は、コンテナで実行するコマンドです。mariadb(MySQL)でおなじみのコマンドです。
ポイントとしては「-h MyMariaDB1」で接続先ホストをコンテナ名で指定しています。
コンテナ名からMyMariaDB1コンテナのIPアドレスに変換して接続してくれます。
「-u normalUser」の部分は、コンテナの初期化処理で追加されているはずの「normalUser」というアカウント(こちらで作成したsetup.sqlの中で作成している)でログインすることをmysqlコマンドに指定しています。
余談ですが、コンテナは「mariadb:latest」でも良いのですが、ここでは「my_mariadb_env:tag1」を指定しています。
これには理由がありまして、「mariadb:latest」は文字コードがUTF-8を使うようになっていないため、日本語の文字列を表示しようとすると文字化けして表示します。
そのため、文字コード設定をmy.cnfの中でUTF-8を使うように設定済みの、「my_mariadb_env:tag1」のコンテナを使っています。

上記コマンドを実行すると、normalUserアカウントのパスワードの入力を求められます。
こちらでpasswordと設定しているので、それを打ち込むとmariadbのsqlクライアントが起動します(MyMariaDB1に接続した状態)。
これでDBに接続しSQLを実行可能です。

D:\tmp\work\mariadb>docker run -it --rm --network mynetwork my_mariadb_env:tag1 mysql -h MyMariaDB1 -u normalUser -p
Enter password: setup.sqlで作成済みのnormalUserのパスワードを入力
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.9.2-MariaDB-1:10.9.2+maria~ubu2204 mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> use SampleDB setup.sqlで作成済みのSampleDBを使用する
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [SampleDB]> select * from SampleTable; setup.sqlで作成済みのSampleTableをselectしてみる
+----+-----------------------+
| id | userName              |
+----+-----------------------+
|  1 | レコードその1 |
|  2 | レコードその2 |
|  3 | レコードその3 |
+----+-----------------------+
3 rows in set (0.001 sec)

Docker以外からコンテナにアクセスしてみる

今までは、Dockerのコンテナ同士での通信しかしていませんでした。
mariadbのコンテナを動かした状態で、外からアクセスしてみたいと思います。
外から・・・といっても、Dockerの世界の外、つまり、同じPC上ですが、別のアプリケーションからmariadbにアクセスしてみます。

mariadbにアクセスするアプリを作る

特にこれにした理由は無いのですが、JavaでEclipse上でmariadbにアクセスするプログラムを作り、アクセスしてみたいと思います。
mavenのpom.xmlは以下の通りです(このpom.xmlを作るのが一番苦労した!)。

<project xmlns="http://maven.apache.org/POM/4.0.0" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>sampleJavaDBAccess</groupId>
	<artifactId>sampleJavaDBAccess</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>sampleJavaDBAccess</name>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.mariadb.jdbc</groupId>
			<artifactId>mariadb-java-client</artifactId>
			<version>2.1.2</version>
		</dependency>
	</dependencies>


	<build>
		<pluginManagement>
			<plugins>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-compiler-plugin</artifactId>
					<version>3.8.0</version>
					<configuration>
						<source>${java.version}</source>
						<target>${java.version}</target>
					</configuration>
				</plugin>

				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-assembly-plugin</artifactId>
					<version>3.0.0</version>
					<configuration>
						<finalName>sampleJavaDBAccess</finalName>
						<descriptorRefs>
							<descriptorRef>jar-with-dependencies</descriptorRef>
						</descriptorRefs>
						<archive>
							<manifest>
								<mainClass>jp.ne.sakura.alctail.JavaMain</mainClass>
							</manifest>
						</archive>
					</configuration>
					<executions>
						<execution>
							<id>sampleJavaDBAccess</id>
							<phase>package</phase>
							<goals>
								<goal>single</goal>
							</goals>
						</execution>
					</executions>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>
</project>

そして、唯一のJavaのクラス「JavaMain」(パッケージは「jp.ne.sakura.alctail」)は以下の通りです。
アクセスするポート番号が13306番になっているのがポイントです。
詳細はで。


package jp.ne.sakura.alctail;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;


public class JavaMain {
	
	public static void main(String[] arvs) {
		showMsg("処理を開始します");
		
		try (Connection connect = DriverManager.getConnection("jdbc:mariadb://localhost:13306/SampleDB", "normalUser", "password")) {
			try(Statement st = connect.createStatement()) {
				try(ResultSet res = st.executeQuery("SELECT id, userName FROM SampleTable ORDER BY id")) {
					// 取得したレコードの値を表示する
					while(res.next()) {
						showMsg("取得レコード:" + res.getInt("id") + "," + res.getString("userName"));
					}
				}
			}
		} catch(SQLException e) {
			showMsg("例外発生:" + e.getMessage());
		}
		
		showMsg("処理を終了します");
	}
	
	private static void showMsg(String str) {
		System.out.println(str);
	}
}

余談ですが、Eclipseでソースを書いてmavenを実行したのですが、エラーとなり実行できませんでした。
どうやら、mavenのセントラルリポジトリがTLS1.2だけしかサポートしなくなったのが原因らしいです。
Eclipse上、mavenを実行するときは、以下のようにオプションを追加してあげると、実行できるようになります。

mariadbのコンテナを起動し、アプリからアクセス(ポートフォワード)

こちらで作成したmariadbのコンテナである「my_mariadb_env:tag1」を、以下のコマンドで起動します。

docker run -d --name MyMariaDB1 --rm --network mynetwork  ^
-p 13306:3306 ^
--env MARIADB_ROOT_PASSWORD=rootPass ^
my_mariadb_env:tag1

ここでのポイントは「-p 13306:3306」です。
Dockerを動かしているPCの、localhost:13306にアクセスした場合、dockerの中の3306にポートフォワードされます(mariadbの標準の待ち受けポートは3306です)。
なので、mariadbのコンテナの設定は変更不要で、外からアクセスする人が13306番ポートにアクセスすればよいこととなります。

コンテナを起動したら、さきほど作成したソースを、実行してみると、mariadbにアクセスし、レコードが取得できていることが分かると思います。
実行結果は以下の通りです。

処理を開始します
取得レコード:1,レコードその1
取得レコード:2,レコードその2
取得レコード:3,レコードその3
処理を終了します

ページの一番上へ