実行中の Docker コンテナにボリュームを動的に追加する方法

実行中の Docker コンテナにボリュームを動的に追加する方法

以前、Docker コンテナの起動後にボリュームをマウントできるかどうか尋ねられたことがあります。mnt 名前空間の動作を考えると、最初はこれを実現するのは難しいだろうと思いました。しかし、今ではそれが達成されたと思います。

  • つまり、実行中のコンテナにディスク ボリュームをマウントするには、次の操作が必要です。
  • nsenter を使用して、このディスク ボリュームを含むファイル システム全体を一時マウント ポイントにマウントします。
  • ディスク ボリュームとして使用する特定のフォルダーからこのディスク ボリュームの場所へのバインド マウントを作成します。

最初の手順で作成した一時マウント ポイントをアンマウントします。

予防

次の例では、入力する必要がある内容とマシンの応答を区別できるように、これがシェル コマンド ライン プロンプトであることを示すために、意図的に $ 記号を含めています。一部の複数行コマンドでは、引き続き > を使用します。これにより、例のコマンドを簡単にコピーして貼り付けることができなくなることに気付きました。コードをコピーして貼り付ける場合は、記事の最後にあるサンプル スクリプトを確認してください。

詳細な手順

次の例では、次のコマンドを使用して charlie という名前の単純なコンテナを起動したことを前提としています。

$ docker run --name charlie -ti ubuntu bash

必要なのは、ホスト フォルダー/home/jpetazzo/Work/DOCKER/dockerコンテナー内の/srcディレクトリにマウントすることです。では、始めましょう。

センター

まず、nsenter と docker-enter ヘルパー スクリプトが必要です。なぜ?コンテナからファイルシステムをマウントしたいからです。セキュリティ上の理由により、コンテナではこれを行うことはできません。 nsenter を使用すると、上記のセキュリティ制限を突破し、コンテナのコンテキスト (厳密には名前空間) 内で任意のコマンドを実行できます。もちろん、これには Docker ホスト上のルート権限が必要です。

nsenter をインストールする最も簡単な方法は、docker-enter スクリプトと組み合わせて実行することです。

$ docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter

詳細については、nsenter プロジェクトのホームページを参照してください。

ファイルシステムを見つける

ホストフォルダ (/home/jpetazzo/Work/DOCKER/docker) を含むファイルシステムをコンテナにマウントします。次に、どのファイル システムにこのディレクトリが含まれているかを調べる必要があります。

まず、ファイルがシンボリック リンクである場合、またはそのパスにシンボリック リンクが含まれている場合は、ファイルを正規化 (または逆参照) する必要があります。

$ readlink --canonicalize /home/jpetazzo/Work/DOCKER/docker
github.com の docker ディレクトリに docker というファイルを作成します。

はは、確かにシンボリックリンクですね!これを環境変数に設定してみましょう:

$ HOSTPATH=/home/jpetazzo/Work/DOCKER/docker
$REALPATH=$(readlink --canonicalize $HOSTPATH)

次に、どのファイルシステムにこのパスが含まれているかを調べる必要があります。私たちは、やや意外なツールである df を使用してこれを行います。

$ df $リアルパス
ファイルシステム 1Kブロック 使用済み 使用可能 使用率 マウント済み
/sda2 245115308 156692700 86157700 65% /home/jpetazzo

-P フラグを使用します (Solaris または BSD に Docker をインストールするときに、特殊な df または他の人の df がある場合に備えて、POSIX 形式を強制します)。また、結果も変数に格納します。

$ FILESYS=$(df -P $REALPATH | tail -n 1 | awk '{print $6}')

ファイルシステムのデバイス(およびサブルート)を見つける

現在、システムにはバインド マウントや BTRFS サブボリュームは存在しないため、/proc/mounts を調べて、/home/jpetazzo ファイル システムに対応するデバイスを見つける必要があります。しかし、私のシステムでは、/home/jpetazzo は BTRFS プールのサブボリュームです。サブボリューム情報 (またはバインド マウント情報) を取得するには、/proc/self/moutinfo を確認する必要があります。

mountinfo について聞いたことがない場合は、proc.txt のカーネルドキュメントを確認してください。

まず、ファイル システム デバイス情報を取得します。

$ 読み取り中 DEV マウント ジャンク
> [ $MOUNT = $FILESYS ] を実行する && break
> 完了 </proc/mounts
$ エコー $DEV
/dev/sda2

次に、サブルート情報 (マウントされたファイル システムへのパス) を取得します。

$ ABC SUBROOT MOUNT JUNK を読み取り中
> [ $MOUNT = $FILESYS ] を実行する && break
> 完了 < /proc/self/mountinfo 
$ echo $サブルート
/jpetazzo

とても良い。これで、 /dev/sda2をマウントする必要があることがわかりました。ファイルシステム内で、/jpetazzo に移動します。ここから、必要なファイル (この例では/go/src/github.com/docker/docker ) への残りのパスを取得できます。
残りのパスを計算してみましょう:

$ SUBPATH=$(echo $REALPATH | sed s,^$FILESYS,,)

注意: この方法は、パスに「,」が含まれていない場合にのみ機能します。パスに「,」があり、この記事の方法でディレクトリをマウントしたい場合は、お知らせください。 (この問題を解決するには、シェル トライアドを呼び出す必要があります: ジェシー、ソウルシェイク、ティアノン?)

コンテナに入る前に最後に行うことは、ブロック デバイスのメジャー番号とマイナー番号を見つけることです。 stat を使用できます:

$ stat --format "%t %T" $DEV
8 2

これら 2 つの数値は 16 進数であることに注意してください。後で 2 進数が必要になります。これは次のように変換できます。

$ DEVDEC=$(printf "%d %d" $(stat --format "0x%t 0x%T" $DEV))

要約する

最後にもう1つのステップがあります。説明できない理由で、一部のファイルシステム (BTRFS を含む) は、複数回マウントされた後に /proc/mounts のデバイス フィールドを更新します。つまり、コンテナ内に /tmpblkdev という一時ブロック デバイスを作成し、それを使用して独自のファイルシステムをマウントすると、ファイルシステムは (ホスト マシン上で!) /dev/sda2 ではなく /tmpblkdev として表示されます。これは無害に思えるかもしれませんが、実際には、ファイル システム ブロック デバイスを取得しようとする後続の試行が失敗する原因になります。

簡単に言うと、コンテナ内のブロック デバイス ノードがホスト マシンと同じパスに配置されていることを確認する必要があります。

次の操作を行う必要があります:

$ docker-enter charlie --sh -c \
> "[ -b $DEV ] || mknod --mode 0600 $DEV b $DEVDEC"

ファイル システムをマウントするための一時マウント ポイントを作成します。

$ docker-enter charlie --mkdir /tmpmnt
$ docker-enter charlie --mount $DEV /tmpmnt

ボリューム マウント ポイントが存在することを確認し、ボリュームをバインド マウントします。

$ docker-enter charlie --mkdir -p /src
$ docker-enter charlie --mount -o bind /tmpmnt/$SUBROOT/$SUBPATH /src

一時マウントポイントを削除します。

$ docker-enter charlie --umount /tmpmnt
$ docker-enter charlie --rmdir /tmpmnt

(デバイス ノードはクリアしません。そもそもデバイスが存在するかどうかを確認するのは冗長かもしれませんが、すでに十分複雑です。)

ミッション完了!

すべてを自動化

次の段落はそのままコピーして貼り付けることができます。

#!/bin/sh
セット-e
コンテナ=チャーリー
HOSTPATH=/home/jpetazzo/Work/DOCKER/docker
CONTPATH=/src

REALPATH=$(readlink --canonicalize $HOSTPATH)
FILESYS=$(df -P $REALPATH | tail -n 1 | awk '{print $6}')

DEV MOUNT JUNKを読みながら
[ $MOUNT = $FILESYS ] を実行し、break する 
完了 </proc/mounts
[ $MOUNT = $FILESYS ] # 正常性チェック!

\ABC SUBROOT MOUNT JUNKを読みながら
\do [ $MOUNT = $FILESYS ] && break
\done < /proc/self/mountinfo 
[ $MOUNT = $FILESYS ] # マウントの健全性チェック!

SUBPATH=$(echo $REALPATH | sed s,^$FILESYS,,)
DEVDEC=$(printf "%d %d" $(stat --format "0x%t 0x%T" $DEV))

docker-enter $コンテナ --sh -c \
   "[ -b $DEV ] || mknod --mode 0600 $DEV b $DEVDEC"
docker-enter $コンテナ --mkdir /tmpmnt
docker-enter $CONTAINER --mount $DEV /tmpmnt
docker-enter $コンテナ --mkdir -p $CONTPATH
docker-enter $CONTAINER --mount -o bind /tmpmnt/$SUBROOT/$SUBPATH $CONTPATH
docker-enter $コンテナ --umount /tmpmnt
docker-enter $コンテナ --rmdir /tmpmnt

ステータスと制限

上記の方法は、ブロック デバイスをベースとしないファイル システムには適用されません。/proc/mounts がブロック デバイス ノードを正しく取得できる場合にのみ機能します (前述のように、常に正しく取得できるとは限りません)。また、私はこれをクラウド インスタンスなどではなく、自分の環境でのみテストしましたが、そこでも当てはまるかどうか興味があります。

以上がこの記事の全内容です。皆様の勉強のお役に立てれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • Docker 学習に関する簡単な説明: Docker データ量 (ボリューム)
  • Docker におけるコンテナデータボリュームとデータ管理の詳細な説明
  • Dockerデータボリューム管理の詳細な説明
  • Dockerボリュームマウントの実装方法
  • Dockerfile命令VOLUMEの簡単な紹介
  • Dockerでボリュームを管理する2つの方法

<<:  cocoscreatorプレハブの詳しい説明

>>:  MySQL ベースのシーケンス実装方法

推薦する

パフォーマンスの最適化を教える 52 個の SQL 文

1. クエリを最適化するには、テーブル全体のスキャンを避けてください。まず、where と orde...

Vue3ナビゲーションバーコンポーネントのカプセル化実装方法

参考までに、Vue3でナビゲーションバーコンポーネントをカプセル化し、スクロールバーのスクロールに合...

CSS で実現される HTML 背景色のグラデーション

エフェクトのスクリーンショット:実装コード:コードをコピーコードは次のとおりです。 <!DOC...

Vue3 手動カプセル化ポップアップ ボックス コンポーネント メッセージ メソッド

この記事では、ポップアップボックスコンポーネントメッセージのVue3手動カプセル化の具体的なコードを...

CentOS 7 で MySQL 5.7 をインストールして設定する

この記事では、以下の環境をテストします。 CentOS 7 64 ビット 最小 MySQL 5.7 ...

Baidu サイト検索が https をサポートしていない問題の解決策 (テスト済み)

最近、携帯電話で https が有効になりました。緑色のロックを取得するには、次の問題を解決する必要...

JavaScript でプライベート メンバーを作成する

目次1. クロージャを使用する2. ES6クラスを使用する3. ES2020提案を使用する4. We...

Javascript 構造化代入の詳細

目次1. 配列の分解2. オブジェクトの分解3. 不完全な解体4. 分割代入を使用して変数交換を実装...

CentOS7 ファイアウォールとポート関連コマンドの紹介

目次1. ファイアウォールの現在の状態を確認する2. ファイアウォールサービスを開始する3. ファイ...

MySQLのint主キーの自己増分の問題を解決する

導入MySQL データベースを使用する場合、int を主キーとして使用し、自動インクリメントに設定す...

mysql5.7.19 zip 詳細なインストールプロセスと構成

MySQL v5.7.19 正式版(32/64 ビットインストール版および zip 解凍版) 1. ...

ウェブページの要素の検査とソースコードの表示の違いについて

Chrome ブラウザで Web ページを開くと、ページを右クリックすると 2 つの非常によく似たオ...

ラジオボタンと複数選択ボタンは画像を使用してスタイル設定されます

ラジオ ボタンや複数選択ボタンにスタイルを追加する方法や、ボタンを大きくする方法を尋ねる人を以前見か...

MySQLクエリの文字セットの不一致の問題を解決する方法

問題を見つける最近、仕事で問題が発生しました。MySQL データベースにテーブルを作成するときに、ラ...

新しく作成された MySQL ユーザーの % には localhost が含まれていますか?

通常の説明%はどのクライアントでも接続できることを意味しますlocalhostはローカルコンピュータ...