Bash関数: DockerVolumeでのCopy

Bash関数: DockerVolumeでのCopy

Intro

意外なことに、DockerのVolumeと単体でファイルをやりとりする方法がない
幸い、これはいとも簡単に解決される。

Tweet

というか、Docker公式もこのやり方を取り込めばいいのに……

TL; DR

DockerおよびDocker Volumeの紹介を行う。
さらに、Docker Volume (名前付きVolume) へのファイルコピーを行うBash関数を作成する。

Docker

まず、Dockerの簡単な紹介を軽くしておこう。

Dockerの仮想化のイメージ

  • Dockerは、コンテナ型の仮想化技術を提供するプラットフォームである。
    • これにより、アプリケーションを環境から切り離して実行できる。
    • 新規の環境準備の手間も、既存の環境への汚染も、両方の懸念がなくなる

ファイルでシンプルに管理

  • 移行や導入、カスタマイズが非常に容易になる。
    • 単体のソフトだけではなく、複数のソフトを組み合わせた環境も簡単に構築できる。
    • Dockerfileやdocker-compose.ymlというテキストファイルで管理できるので、環境のカスタマイズも容易で見通しがいい。管理や移行も簡単だ。
    • 複雑なアプリケーションに限らず、さっと新しい言語に触れたり、異なる環境を試すのにも便利
  • AWSやAzure、GCPなどのクラウドサービスでもDockerをサポートしている。

Docker利用ケース

実際に筆者がDockerを利用している/していたケースを紹介する。

導入・運用の手間軽減

  • Minecraftサーバー

ボットおよびスクリプト関連

  • 複数環境でのDiscord Botの安定運用
  • 複数環境でのScript実行

開発およびコンパイル環境

  • 簡単にLaTeXのコンパイル環境を構築
  • Go, Rust, Juliaなどに触れるための環境

動作確認および検証

  • 新規のUbuntuでの動作確認
  • アプリケーションで不具合が生じた際、環境由来の影響を排除するため

Webアプリケーション導入

  • GrowiやCodimdなどのWebアプリケーション導入
  • もともとOnlineで動かしてるphpmyadminのlocal動作検証

一時的な利用環境

  • 研修で一瞬だけ入れたJava環境

Docker Volume

実は、Dockerのコンテナ(VMみたいなもの)は、データの非保持が原則だ。
コンテナを閉じるまではデータが保持されるが、再度コンテナを立ち上げるとデータは消える (Imageの状態に初期化される)。

それに対する解決策は、以下の通り。

Alt text

  1. DockerコンテナのImageを都度、再作成する
    • 内部データ永続化のためのイメージ作成はおすすめしない
      • Dockerは処理ごとに差分バックアップをとっているので、この手法を続けると、容量が膨れ上がる
    • なにより、DockerのImageの利用方法として不適切だ。

Bind Mount

  1. ホストOSのディレクトリをマウントする (Bind Mount)
    • この方法は、コンテナ内のデータをホストOSのディレクトリと同期することで、データの永続化を実現する。
    • 常にホストOS側でデータにアクセスできるので、管理も簡単。
    • ただ、この処理はDockerコンテナの動作を非常に低速化する
    • 少数のファイルや簡単な処理の場合は問題ない
      • LaTeXのコンパイルや、簡単なスクリプトの実行など

mountの概略図

  1. Docker Volume (正確には名前付きVolume) を利用する
    • 一般的なVMにおけるStorageとしてとらえるとよい
      • コンテナにattachされる形で、コンテナのデータを保持する

Tweet

G, GitHub Copilotくん、どうした……?
GitHub Copilotの暴走

Docker Volume in script

docker runの際に、-vまたは--mountオプションを利用することで、Docker Volumeを利用できる。docker docsより引用。

1
2
3
4
5
6
7
8
9
10
11
12
# -v option
docker run -d \
--name devtest \
-v myvol2:/app \
nginx:latest

# --mount option
# 長くなるが、こちらの方が推奨されている
docker run -d \
--name devtest \
--mount source=myvol2,target=/app \
nginx:latest

Docker Volume in docker-compose.yml

今回はあまり触れていないが、Docker ComposeというDockerの拡張を利用すると、複数のDockerサービスや実行時のオプションを一つのYAMLファイルで管理できる。
以下はMincecraft Bedrock Serverを立ち上げるdocker-compose.ymlの例。

docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
services:
bds:
image: itzg/minecraft-bedrock-server
container_name: bedrock_server
environment:
EULA: "TRUE"
SERVER_NAME: ServerName
LEVEL_NAME: LevelName
GAMEMODE: survival
DIFFICULTY: normal
TZ: Asia/Tokyo
ALLOW_CHEATS: TRUE
ONLINE_MODE: FALSE
SERVER_PORT: 19132
PLAYER_IDLE_TIMEOUT: 0
ports:
- 19132:19132/udp
- 19133:19133/udp
volumes:
- type: volume
source: dbs
target: /data
stdin_open: true
tty: true
volumes:
dbs:
external: true
name: minecraft_data

Docker Composeの説明 by ChatGPT

Docker Composeの説明 by ChatGPT

Docker Composeは、複数のDockerコンテナを定義・管理し、まとめて起動・停止するためのツールです。docker-compose.ymlという設定ファイルを用いて、アプリケーションの各サービスを記述し、一括して管理します。これにより、複雑なアプリケーションの環境を簡単に設定し、再現することができます。

Docker Composeのメリット

  • 簡単な設定管理:

    • docker-compose.ymlファイルにより、複数のコンテナ設定を一つのファイルで管理可能。
  • 一括操作:

    • 複数のコンテナを一度に起動・停止・再起動できるため、操作が効率的。
  • 開発環境の再現性:

    • 同じ設定ファイルを使用することで、開発環境やテスト環境を簡単に再現可能。
  • 依存関係の管理:

    • サービス間の依存関係を明確に定義でき、起動順序の制御が容易。
  • ネットワーキングの簡素化:

    • 複数のコンテナ間のネットワーク設定が自動で行われ、手動でのネットワーキング設定が不要。

これらのメリットにより、Docker Composeは開発者や運用担当者にとって非常に有用なツールとなっています。

不満点: 単体でCopyができない

で、DockerやDocker Volumeのすばらしさが伝わったところで、何が不満なのか。

Docker Volume単体へのCopyができない。

かみ砕いて説明しよう。
いかにもcopyをしそうなdocker cpは、コンテナとホストOS間のファイルコピーを行うコマンドだ。通常のcpと似た感覚で利用できる。

1
2
# mcsという名前のコンテナにworldsというフォルダをコピーする
docker cp worlds mcs:/data/worlds

これはコンテナとのファイルコピーであり、Docker Volumeとのファイルコピーではない

当然、Docker Volumeがコンテナにマウントされている場合は、コンテナ内のファイルをDocker Volumeにコピーすることができるが、いつでもDocker Volumeがマウントされているわけではない

たとえば、MinecraftサーバーのためのVolumeの場合、サーバー本体立ち上げ前にVolumeにワールドデータをコピーしておきたいものだろう。
Minecraft Bedrock Serverのフォルダ内容の例

Tweet

ところで、Windowsのペイントにレイヤー機能が復活してる!
Windows 7の頃の記憶以来だなぁ。

Windowsペイントのレイヤー機能

まじめに編集する場合はPhotoshop, Gimp / Illustrator, Inkscapeを使うとはいえ、
一瞬編集したいときのペイントでのレイヤー復活は素直にうれしい。

Windowsペイントのレイヤーの右クリックメニュー

レイヤー複製ができたのはいいが、レイヤーごとの透過度の設定もしたかった……。
ていうか、Windowsペイントで透過度の概念自体ない……?

解決策

情報元

さて、すべてを簡単に解決してしまう情報元はGitHub Issueである。

1
2
3
docker container create --name dummy -v myvolume:/root hello-world
docker cp c:\myfolder\myfile.txt dummy:/root/myfile.txt
docker rm dummy

あるいは、日本語のこちらのBlogを閲覧した人も多いだろう。(情報元は同じ)

つまり一度コンテナを作ってしまえばdocker cpが使えるので解決できるわけです。
コンテナを作って削除すれば、volumeだけ残るので結果的にやりたいことが実現できるわけですね。
またホストからコピーしたvolumeを作るだけなのでdocker runでわざわざ起動せずとも、docker container createで済みます。

作成されたScript

さて、すでに問題は解決しているのだが、これをScript化することで、より簡単にファイルのコピーを行うことができる。

~/.bash_aliases
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
alias dockerVolCp="_DockerVolume_Copy"
function _DockerVolume_Copy(){
container_name="tmp_volcp"$(date +%s)

# :が含まれている方がDocker Volumeのpath
if [[ "$1" == *":"* ]]; then
mode="from"
virtual_path="$1"
host_path="$2"
else
mode="to"
host_path="$1"
virtual_path="$2"
fi
volume_name=$(echo $virtual_path | cut -d: -f1)
virtual_dest=$(echo $virtual_path | cut -d: -f2)
host_path="$1"
virtual_path="$2"
# file_name=$(basename $file_path)
docker container create --name $container_name -v $volume_name:/root hello-world
if [[ $mode == "from" ]]; then
docker cp $host_path $container_name:/root/$virtual_dest
else
docker cp $container_name:/root/$virtual_dest $host_path
fi
docker rm $container_name
}

実際の使用例

1
2
3
4
5
6
7
8
# あらかじめ、Docker Volumeを作成しておく
docker volume create test

# Docker Volumeへcopyする
dockerVolCp tmp.dat test:tmp.dat

# 逆にDocker Volumeからホストにcopyする
dockerVolCp test:tmp.dat tmp.dat

今回のScriptの技術的な解説 by ChatGPT

今回のScriptの技術的な解説 by ChatGPT

以下のスクリプトは、Dockerボリュームとホスト間でファイルをコピーするためのものです。新しいスクリプトでは、ファイルのコピー方向(ホストからボリューム、またはボリュームからホスト)を判断するためにmode変数を追加しています。これにより、どちらの方向にもファイルをコピーできるようになります。

スクリプトの解説

  1. エイリアスの設定

    1
    alias dockerVolCp="_DockerVolume_Copy"
    • dockerVolCpというエイリアスを設定し、このエイリアスが呼ばれると_DockerVolume_Copy関数が実行されます。
  2. 関数の定義

    1
    function _DockerVolume_Copy(){
    • dockerVolCpコマンドが実行されると、この関数が動作します。
  3. 一時的なコンテナ名の作成

    1
    container_name="tmp_volcp"$(date +%s)
    • 現在の時刻を使ってユニークなコンテナ名を作成します。
  4. 引数の解析とコピー方向の判断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if [[ "$1" == *":"* ]]; then
    mode="from"
    virtual_path="$1"
    host_path="$2"
    else
    mode="to"
    host_path="$1"
    virtual_path="$2"
    fi
    • 最初の引数に「:(コロン)」が含まれている場合、Dockerボリュームからホストへコピーする(mode="from")。
    • コロンが含まれていない場合、ホストからDockerボリュームへコピーする(mode="to")。
  5. ボリューム名とパスの設定

    1
    2
    volume_name=$(echo $virtual_path | cut -d: -f1)
    virtual_dest=$(echo $virtual_path | cut -d: -f2)
    • virtual_pathからボリューム名と、ボリューム内のパスを分割して取得します。
  6. 一時的なコンテナの作成

    1
    docker container create --name $container_name -v $volume_name:/root hello-world
    • Dockerボリュームをマウントした一時的なコンテナを作成します。
  7. ファイルのコピー

    1
    2
    3
    4
    5
    if [[ $mode == "from" ]]; then
    docker cp $host_path $container_name:/root/$virtual_dest
    else
    docker cp $container_name:/root/$virtual_dest $host_path
    fi
    • mode変数に基づいて、適切な方向にファイルをコピーします。
      • mode="from"の場合、ホストからコンテナへコピーします。
      • mode="to"の場合、コンテナからホストへコピーします。
  8. 一時的なコンテナの削除

    1
    docker rm $container_name
    • コピーが終わったら、一時的なコンテナを削除します。

使い方の例

  • ホストからDockerボリュームへコピー

    1
    dockerVolCp /path/to/local/file volume_name:/path/in/volume
  • Dockerボリュームからホストへコピー

    1
    dockerVolCp volume_name:/path/in/volume /path/to/local/file

このスクリプトを使うことで、Dockerボリュームとホスト間のファイルコピーが簡単に行えるようになります。

まとめ

Docker Volumeは、DockerコンテナとホストOS間でデータを共有するための便利な機能である。しかし、DockerコマンドにはDocker Volume単体へのファイルコピー機能がないため、スクリプトを作成することでこの問題を解決した。

Closing

なんとなく放置してたdocker volume cpの関数化をようやく行った。まだ改善の余地はあるが、手間がかかるのでまたそのうち……。

コメント