Tuesday, June 13, 2023

ホストとコンテナを繋ぐ: Docker cp の詳細解説

Dockerは、アプリケーションを隔離された環境で実行するための強力なプラットフォームです。この「隔離」はセキュリティと再現性の観点から非常に重要ですが、一方でホストマシンとコンテナ間でデータをやり取りする必要性が頻繁に生じます。設定ファイルの投入、ログファイルの取得、生成されたアーティファクトの取り出しなど、その用途は多岐にわたります。この課題を解決するための最も直接的なコマンドラインツールが docker cp です。

docker cp は、Linuxの cp コマンドに似た使い勝手で、ホストのファイルシステムとコンテナのファイルシステム間でファイルやディレクトリを簡単にコピーできる機能を提供します。しかし、その単純な見た目の裏には、知っておくべき挙動の細かなルールや、他のデータ管理手法との適切な使い分けが存在します。本記事では、docker cp の基本的な使い方から、その動作原理、詳細なオプション、実践的なユースケース、そしてボリュームやDockerfileのCOPY命令との比較まで、深く掘り下げて解説します。

docker cp の基本構文と動作原理

まず、docker cp コマンドの基本的な構文を理解しましょう。コピーの方向(ホストからコンテナへ、またはコンテナからホストへ)によって、引数の順序が変わります。

構文

コマンドの形式は常に同じです: docker cp [オプション] ソースパス ターゲットパス

  • ホストからコンテナへコピーする場合:
    docker cp [OPTIONS] HOST_SRC_PATH CONTAINER:CONTAINER_DEST_PATH
  • コンテナからホストへコピーする場合:
    docker cp [OPTIONS] CONTAINER:CONTAINER_SRC_PATH HOST_DEST_PATH

ここで重要なのは、コンテナ内のパスを指し示す際の CONTAINER:PATH という記法です。CONTAINER の部分には、対象コンテナの名前またはコンテナIDを指定できます。


# コンテナ名 `my-nginx` を使用
docker cp ./index.html my-nginx:/usr/share/nginx/html/index.html

# コンテナID `a1b2c3d4e5f6` を使用
docker cp a1b2c3d4e5f6:/var/log/nginx/access.log ./nginx_logs/

動作原理: Tarアーカイブによる転送

docker cp は、単純なファイルコピー以上のことを内部で行っています。コマンドが実行されると、Dockerデーモンは以下のステップを踏みます。

  1. ソースのアーカイブ化: Dockerはソースパス(ファイルまたはディレクトリ)を単一の .tar アーカイブにまとめます。ディレクトリをコピーする場合、その中身が再帰的にアーカイブに含まれます。
  2. アーカイブの転送: 作成されたtarアーカイブは、Docker APIを介してストリームとしてコンテナ(またはホスト)に転送されます。
  3. 宛先での展開: 受け取った側で、tarアーカイブが指定された宛先パスに展開されます。

このtarベースのアーキテクチャにより、docker cp は単一のファイルだけでなく、複雑なディレクトリ構造も一度のコマンドで効率的にコピーできます。また、この仕組みを理解することは、後述するパスの挙動やシンボリックリンクの扱われ方を理解する上で助けになります。

パス指定による挙動の詳細

docker cp の挙動は、ソースパスと宛先パスの指定方法、特に宛先が存在するかどうか、ディレクトリかファイルか、そして末尾にスラッシュが付いているかによって微妙に変化します。これらのルールを正確に把握することが、意図しない結果を避ける鍵となります。

ケース1: ソースがファイルの場合

  • 宛先が存在しない:

    宛先パスで指定された名前のファイルが新規に作成され、ソースファイルの内容がコピーされます。もし宛先パスの親ディレクトリが存在しない場合、Dockerはそれらを自動的に作成します。

    # ホストの app.conf を、コンテナの /etc/new_dir/new_app.conf としてコピー (new_dirも作成される)
    docker cp app.conf my-container:/etc/new_dir/new_app.conf
  • 宛先がファイルの場合:

    既存の宛先ファイルが、ソースファイルの内容で上書きされます。

  • 宛先がディレクトリの場合:

    ソースファイルが、そのディレクトリ内に元のファイル名でコピーされます。

    # ホストの app.conf を、コンテナの /etc/nginx/conf.d/ ディレクトリ内に app.conf としてコピー
    docker cp app.conf my-nginx:/etc/nginx/conf.d/

ケース2: ソースがディレクトリの場合

ディレクトリのコピーは少し複雑です。特に、ソースパスの末尾に /. を付けるかどうかで挙動が変わる点に注意が必要です。

  • 宛先が存在しない:

    宛先パスで指定された名前のディレクトリが作成され、そのにソースディレクトリの内容がコピーされます。

    # ホストの `config` ディレクトリの中身を、コンテナ内に新しく作成される `/app/settings` ディレクトリにコピー
    docker cp ./config my-container:/app/settings

    この場合、/app/settings/file1.txt のようになります。/app/settings/config/file1.txt ではありません。

  • 宛先がディレクトリの場合:

    ソースディレクトリ自体が、宛先ディレクトリの中にコピーされます。

    # ホストの `config` ディレクトリを、コンテナの `/app` ディレクトリの中にコピー
    # 結果: /app/config/file1.txt
    docker cp ./config my-container:/app

テクニック: ディレクトリの中身だけをコピーする

ソースディレクトリ自体ではなく、その中身だけを既存の宛先ディレクトリにコピーしたい場合があります。例えば、./config の中身を /app/config に直接展開したい(/app/config/config/... となってほしくない)場合です。この場合、ソースパスの末尾に /. を追加します。

# ./config の中身 (`file1.txt`, `file2.txt`) をコンテナの `/app/config` にコピーする
# 結果: /app/config/file1.txt, /app/config/file2.txt
docker cp ./config/. my-container:/app/config/

この /. は、tarコマンドがカレントディレクトリの内容をアーカイブする際の挙動を利用したもので、非常に便利なテクニックです。

オプションの徹底解説

docker cp には、コピーの挙動を制御するための2つの主要なオプションがあります。

-a または --archive : 所有権を保持する

デフォルトでは、docker cp でコンテナ内にコピーされたファイルの所有者(UID:GID)は、コンテナ内でコマンドを実行するユーザー(多くの場合root、UID=0, GID=0)に設定されます。

-a (--archive) オプションを使用すると、コピー元のファイルのUIDとGIDが可能な限り保持されます。これは、パーミッションが重要な設定ファイルやアプリケーションファイルをコピーする際に非常に重要です。

例:

ホスト上に appuser (UID=1001) が所有する data.db があるとします。


# ホスト側
$ id -u appuser
1001
$ sudo -u appuser touch data.db
$ ls -l data.db
-rw-r--r-- 1 appuser appuser 0 Dec 25 12:00 data.db

このファイルをオプションなしでコピーした場合と、-a を付けてコピーした場合の違いを見てみましょう。


# オプションなしでコピー
docker cp data.db my-container:/app/data.db
docker exec my-container ls -l /app/data.db
# 出力: -rw-r--r-- 1 root root 0 Dec 25 12:00 /app/data.db  <-- 所有者が root になっている

# -a オプション付きでコピー
docker cp -a data.db my-container:/app/data.db
docker exec my-container ls -l /app/data.db
# 出力: -rw-r--r-- 1 1001 1001 0 Dec 25 12:00 /app/data.db <-- 所有者(UID:GID)が保持されている

コンテナ内にホストと同じUID/GIDを持つユーザーが存在しない場合でも、数値としてのUID/GIDは保持されます。アプリケーションが特定のUIDで実行されるように設計されている場合、このオプションは不可欠です。

-L または --follow-link : シンボリックリンクを解決する

ソースパスにシンボリックリンクが含まれている場合、-L オプションはその挙動を制御します。

  • デフォルトの挙動 (-L なし): シンボリックリンク自体がそのままコピーされます。リンク先の実体はコピーされません。リンクがコンテナ内で無効なパスを指している場合、そのリンクは壊れた状態になります。
  • -L オプション使用時: シンボリックリンクをたどり、リンク先のファイルやディレクトリの実体をコピーします。

例:


# ホスト側で設定ファイルへのシンボリックリンクを作成
$ touch /etc/real_config.conf
$ ln -s /etc/real_config.conf ./latest.conf
$ ls -l latest.conf
lrwxrwxrwx 1 user user 22 Dec 25 12:10 latest.conf -> /etc/real_config.conf

# デフォルト (リンクをコピー)
docker cp latest.conf my-container:/opt/
docker exec my-container ls -l /opt/latest.conf
# 出力: lrwxrwxrwx 1 root root 22 Dec 25 12:10 /opt/latest.conf -> /etc/real_config.conf
# このリンクはコンテナ内では無効 (壊れている)

# -L オプション使用 (リンク先の実体をコピー)
docker cp -L latest.conf my-container:/opt/
docker exec my-container ls -l /opt/latest.conf
# 出力: -rw-r--r-- 1 root root 0 Dec 25 12:10 /opt/latest.conf
# `real_config.conf` の内容が `latest.conf` という名前でコピーされた

実践的なユースケースとコード例

docker cp は、日常的なDockerの操作において様々な場面で役立ちます。

1. 実行中のアプリケーションに設定ファイルを反映させる

アプリケーションを停止せずに設定を更新したい場合に便利です。ただし、アプリケーションが設定ファイルの変更を動的に検知する仕組みを持っている必要があります。


# Nginxの設定ファイルを更新し、コンテナにコピー
docker cp nginx.conf my-nginx-container:/etc/nginx/nginx.conf

# Nginxに設定を再読み込みさせる
docker exec my-nginx-container nginx -s reload

2. コンテナからログファイルや生成物を取得する

コンテナ内で生成されたログファイルや、バッチ処理によって生成されたデータファイルなどをホストに取得する際に最も一般的な使い方です。


# アプリケーションログを取得
docker cp my-app-container:/var/log/app.log ./logs/

# データベースコンテナからバックアップファイルを取得
docker exec my-db-container pg_dump -U user -d dbname > backup.sql
docker cp my-db-container:/backup.sql ./db_backups/

3. 停止したコンテナからデータを救出する

何らかの理由でコンテナが起動しなくなってしまった場合でも、そのコンテナが削除されていなければ、docker cp を使って内部のデータを救出できます。これは非常に強力なデバッグ・リカバリ手段です。


# 起動に失敗したコンテナIDを確認
docker ps -a

# 失敗したコンテナから重要なデータをコピー
docker cp dead-container-id:/data/important.json ./recovered_data/

4. シェルスクリプトでの自動化

docker cp はスクリプトに組み込むことで、定型的な作業を自動化できます。


#!/bin/bash

CONTAINER_NAME="my-prod-app"
TIMESTAMP=$(date +"%Y%m%d-%H%M%S")
BACKUP_DIR="/path/to/backups"

echo "Backing up logs from ${CONTAINER_NAME}..."

# コンテナ内のログディレクトリをホストにコピー
docker cp "${CONTAINER_NAME}:/app/logs" "${BACKUP_DIR}/logs-backup-${TIMESTAMP}"

if [ $? -eq 0 ]; then
  echo "Backup successful: ${BACKUP_DIR}/logs-backup-${TIMESTAMP}"
else
  echo "Backup failed."
  exit 1
fi

他のデータ管理手法との比較

docker cp は便利ですが、万能ではありません。Dockerには他にもデータを扱うための仕組みがあり、用途に応じて最適なものを選択することが重要です。

docker cp vs ボリューム(Volumes) / バインドマウント(Bind Mounts)

ボリュームとバインドマウントは、コンテナのファイルシステムの一部をホストのファイルシステムに永続的にマッピングする仕組みです。

特徴 docker cp ボリューム / バインドマウント
主な用途 一時的、アドホックなファイル転送(ログ取得、設定投入、デバッグ) 永続的なデータ管理、ホストとのリアルタイムなファイル共有(DBデータ、ソースコード開発)
持続性 コピーされたデータはコンテナの一部。コンテナを削除するとデータも消える。 コンテナのライフサイクルから独立。コンテナを削除してもデータはホスト上に残る。
パフォーマンス ファイルI/Oが頻繁な用途には不向き。都度アーカイブと転送のオーバーヘッドがある。 ホストのファイルシステムに直接アクセスするため、高パフォーマンス。
設定タイミング コンテナの実行中または停止中にいつでも実行可能。 コンテナの作成時(docker run -v ...)に設定が必要。

使い分けの指針:

  • データベースのデータ、ユーザーがアップロードしたファイル、アプリケーションの状態など、永続化が必要なデータには必ずボリュームを使いましょう。
  • 開発中にソースコードの変更を即座にコンテナに反映させたい場合は、バインドマウントが最適です。
  • 一度きりのデータ取得や、緊急のデバッグ目的でのファイル操作には、docker cpが適しています。

docker cp vs Dockerfileの COPY / ADD 命令

Dockerfile内のCOPYADD命令もファイルをコンテナにコピーしますが、その目的とタイミングはdocker cpと全く異なります。

  • タイミング: COPY/ADDイメージのビルド時に使用されます。これにより、アプリケーションのコードや依存関係がイメージのレイヤーとして焼き付けられます。一方、docker cpコンテナのランタイム(実行時)に使用されます。
  • 不変性: Dockerfileによるイメージ構築は、不変なインフラ(Immutable Infrastructure)の原則に従います。必要なものは全てイメージに含めるべきです。docker cpによる実行時のファイル変更は、この原則から外れるため、本番環境での状態変更には使うべきではありません。
  • 用途:
    • COPY/ADD: アプリケーションのソースコード、ライブラリ、静的な設定ファイルなど、イメージの構成要素として必須のファイルを組み込むために使います。
    • docker cp: 実行時に生成された動的なデータ(ログ、出力ファイル)を取得したり、コンテナの状態を外部から調査・デバッグしたりするために使います。

注意点とベストプラクティス

  1. 本番環境での利用は慎重に: 本番環境で実行中のコンテナに対してdocker cpで安易にファイルを変更することは、コンテナの状態を追跡不可能にし、再現性を損なう原因となります。設定変更は、イメージの再ビルドと再デプロイを通じて行うのが原則です。
  2. デバッグツールとして活用する: docker cpが最も輝くのはデバッグの場面です。コンテナ内の特定の設定ファイルの内容を確認したり、コアダンプファイルを取得したりするのに非常に役立ちます。
  3. セキュリティリスクを認識する: ホストからコンテナへファイルをコピーする際は、信頼できないソースからのファイルを不用意にコピーしないように注意してください。悪意のあるスクリプトやバイナリをコンテナ内に持ち込んでしまう可能性があります。
  4. パーミッション問題: ファイルの所有者やパーミッションが原因でアプリケーションが正常に動作しないことがあります。前述の-aオプションを適切に利用し、コンテナ内でのファイルの所有権が期待通りになっているか確認しましょう。
  5. 大きなファイル/ディレクトリのコピー: 非常に大きなデータをdocker cpでコピーすると、tarの作成と展開に時間がかかり、Dockerデーモンのリソースを消費する可能性があります。そのような場合は、共有ボリュームを介したデータ転送を検討する方が効率的なことがあります。

まとめ

docker cpは、Dockerコンテナの隔離された壁を越えてホストとデータをやり取りするための、シンプルかつ強力なコマンドです。その基本的な構文は直感的ですが、パス指定のルールやオプションの挙動を正確に理解することで、より効果的に、そして安全に活用することができます。

重要なのは、docker cpを適切な文脈で使うことです。アドホックなデータ転送やデバッグには最適なツールですが、データの永続化やアプリケーションの構成管理といった目的には、ボリュームやDockerfileといった、より体系的なアプローチを選択するべきです。これらのツールの特性を理解し、状況に応じて正しく使い分けることが、堅牢で管理しやすいDocker環境を構築するための鍵となります。


0 개의 댓글:

Post a Comment