Docker は様々な仮想マシンを用いるためのソフトです。docker run コマンドは仮想マシン定義であるイメージを元にコンテナを作り、渡したコマンドを実行させるコマンドです。
Docker run リファレンス — Docker-docs-ja 20.10 ドキュメント
docker run コマンドは例えば、次の様に使えます。
>docker run alpine:3.15.0 ls Unable to find image 'alpine:3.15.0' locally 3.15.0: Pulling from library/alpine Digest: sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300 Status: Downloaded newer image for alpine:3.15.0 bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
alpine:3.15.0 の ls コマンドの実行例です。今回、注目すべき点はイメージをダウンロードしてきたことを示す以下のメッセージです。
Unable to find image 'alpine:3.15.0' locally 3.15.0: Pulling from library/alpine Digest: sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300 Status: Downloaded newer image for alpine:3.15.0
これは DockerHub からイメージをダウンロードしてきたという意味のメッセージです(docker run localhost:5000/alpine:3.15.0 のようにすると DockerHub 以外のリポジトリもいけます。この記事ではローカルファイルを使いますが、サービスが死なないための冗長化としてプライベートレジストリ等のリポジトリ置き場を別に用意しておくのも全然ありです)。要するに docker run コマンドはローカルのイメージに docker run するためのイメージがなければ外部からイメージをダウンロードしようとする、ということです。
DockerHubにはアクセス回数の制限があります。そうでなくとも自分が Docker を使って運用するサービスと DockerHub は別々であり、DockerHub が使えなくなった時に一緒に自分が運用するサービスも共倒れするのは避けたいです。そこでローカルにイメージがないならばローカルのファイルからイメージを生成、そのイメージを使って docker run する、という流れを作りたくなります。これを作る方法を紹介します。
まずはファイル、イメージ、コンテナを変換するコマンドです。これは次のコマンドらでできます。
# 完成状態のコンテナをイメージ化 # https://docs.docker.com/engine/reference/commandline/commit/ docker commit [コンテナID] [出力後イメージ名] # 例. docker commit e68acd7cd50e cpl/test_docker_img # 実際はもっと意味のある名前を推奨 # 任意のイメージをファイル化 # https://docs.docker.com/engine/reference/commandline/save/ docker save [イメージ名] -o [出力後ファイル名] # 例. docker save cpl/test_docker_img -o cpl-test_docker_img.tar # 任意のファイル化されたイメージから Docker 内で使えるイメージを作成 # https://docs.docker.com/engine/reference/commandline/load/ docker load -i [イメージになる元のファイル名] # 例. docker load -i cpl-test_docker_img.tar # 任意のイメージからコンテナを作成して使用 docker run [実行時に欲しいオプション色々] [イメージ名] [実行時に欲しいコマンド色々]
あらかじめ docker save で必要なイメージをファイル化しておき、加えて次のフローで処理を実行することで目的を達成できます。
if(docker run 対象のイメージがないならば true){
docker load を実行
}
docker run を実行
仮に PHP で実装するならば次になります。
<?php
/**
* Symfony の Process コンポーネントで外部コマンドを実行
* @see https://symfony.com/doc/current/components/process.html
*/
use Symfony\Component\Process\Process;
require_once __DIR__ . '/vendor/autoload.php';
/** docker run 対象のイメージがないならば true */
function dockerImageExists(string $dockerImageTag): bool
{
// タグを渡すと一致するイメージのみを表示する
// ヘッダを消すための --format オプション
$p = new Process(['docker', 'images', $dockerImageTag, '--format', '{{.ID}}']);
echo $p->getCommandLine() . "\n";
$p->run();
if($p->getExitCode() !== 0) {
throw new \RuntimeException($p->getErrorOutput());
}
// イメージが存在するならば出力が存在する。無ければ空文字列
return $p->getOutput() !== '';
}
/** Docker イメージをファイルからロード。 */
function dockerImageLoad(string $dockerImageFile): void
{
// カレントディレクトリ直下にあるイメージと同名の tar.zip ファイルからイメージをロード
// 実務で使うなら、どこか適当なディレクトリにイメージをまとめるとか
$p = new Process(['docker', 'load', '-i', $dockerImageFile]);
echo $p->getCommandLine() . "\n";
$p->run();
if($p->getExitCode() !== 0) {
throw new \RuntimeException($p->getErrorOutput());
}
}
/** コマンドを実行 */
function processRun(array $command): void
{
$p = new Process($command);
echo $p->getCommandLine() . "\n";
$p->run();
if($p->getExitCode() !== 0) {
throw new \RuntimeException($p->getErrorOutput());
}
echo $p->getOutput() . "\n";
}
/** メイン処理 */
function main(string $dockerImageTag, string $dockerImageFile, array $command): void
{
// docker run 対象のイメージがないならば true
if(!dockerImageExists($dockerImageTag)) {
// docker load を実行
dockerImageLoad($dockerImageFile);
}
// docker run を実行
processRun($command);
}
// あらかじめ docker save alpine:3.15.0 -o alpine_3.15.0.tar でイメージをファイル化しておく
main(
'alpine:3.15.0',
'alpine_3.15.0.tar',
['docker', 'run', 'alpine:3.15.0', 'ls']
);
これにより、誤って docker run に使うローカルの Docker イメージを削除し、その Docker イメージがリンク切れとなっている様な状況でも docker run が正常に動作します。