Yuki's Tech Blog

仕事で得た知見や勉強した技術を書きます。

【さわって学ぶクラウドインフラ docker 基礎からのコンテナ構築】第5章 コンテナ内のファイルと永続化で知らなかったことをざっくりまとめてみた

目次

概要

コンテナを破棄すれば、その中にあるファイルは自ずと失われます。今回は、コンテナを破棄してもファイルを残すための方法、そして、バックアップの方法について学びます。

2つのhttpdコンテナを1つのDockerホスト内で起動させてみる

マウントをせず、2つのhttpdコンテナ(web01とweb02)を起動させます。

作業手順

手順1:
1つ目のhttpdコンテナを起動します。

docker run -dit --name web01 -p 8080:80 httpd:2.4

手順2:
2つ目のhttpdコンテナを起動します。

docker run -dit --name web02 -p 8081:80 httpd:2.4

手順3:
http://18.183.203.210:8081/」と「http://18.183.203.210:8081/」にアクセスすると、どちらにも接続でき「It works!」と表示されます。つまり、この2つのコンテナは完全に互いに独立していることが分かりました。そして、1台のDockerホストに2台のWebサーバーを同居させることができました。

コンテナの中のファイルを変更する

コンテナの中のファイルを変更するには、docker execコマンドで/bin/bashを起動してコンテナの内部に入り、そこでvimを起動して、/usr/local/apache2/htdocs/index.htmlを編集するという方法が考えられます。しかし、httpdイメージはファイルサイズを小さくするために、Vimなどは含まれていません。入っていない場合は、aptコマンドでVimをインストールすればいいのですが、結構めんどくさいです。 この場合、docker cpコマンドを使用します。docker cpコマンドを使うことで、 DockerホストとDockertコンテナ間でファイルやディレクトリをコピーすることができます。docker cpコマンドは、コンテナが稼働中でも停止中でも、どちらの場合でもファイルやディレクトリをコピーできます。

↓ ホスト→コンテナの向きでファイルをコピーする場合

docker cp オプション コピー元のパス名 コンテナ名またはコンテナID:コピー先のパス名

↓ コンテナ→ホストの向きにコピーする場合

docker cp オプション コンテナ名またはコンテナID:コピー元のパス名 コピー先のパス名

docker cpコマンドは、パーミッションをそのままコピーします。また、ディレクトリも再帰的にコピーします。

(注) docker cpでは、/proc、/sys、/dev、tmpfs配下のような、システムファイルはコピーできません。こうしたファイルをコピーしたいときは、標準入出力経由でコピーします。あと、コピー先のパスのファイル名は自由につける事ができます。

pushdコマンド・popdコマンド

pushdコマンドは、シェルにおいて、現在のカレントディレクトリの状態を保存した上で、パスで指定した場所にカレントディレクトリを移動させます。保存したカレントディレクトリの位置まで戻るには、popdと入力します。

Image from Gyazo

index.htmlをDockerホストに作り、それをコンテナにコピーしてみる

docker cpコマンドを使うことで、 Dockerホスト上のファイルをコンテナにコピーできます。

作業手順

手順1:
pushdコマンドでカレントディレクトリを/tmpにします。その後、/tmp配下にweb01コンテナ用のindex.htmlを作成します。

pushd /tmp

手順2:
index.htmlファイルを、以下のコマンドで、コンテナweb01の/usr/local/apache2/htdocs/にコピーします。

docker cp /tmp/index.html web01:/usr/local/apache2/htdocs/index.html

(注) コピー先のパスのファイル名は自由につける事ができます。

ブラウザで「http://Dockerホスト:8080/」に接続すると、index.htmlの内容が表示されていることがわかります。 Image from Gyazo

手順3:
Dockerホストのファイルが本当にコンテナにコピーされたかを、コンテナに入って確認します。起動中のコンテナに入るためには、起動しているコンテナの中のbashを実行すれば良いので、以下のコマンドを実行します。

docker exec -it web01 /bin/bash

/usr/local/apache2/htdocs/に移動してlsコマンドを実行すると、index.thmlが存在することを確認できました。catコマンドで内容を確認すると同じであることも分かりました。

web02コンテナに対しても同じことをします。index02.htmlというファイルを作成します。index02.htmlの内容は、web02に関する内容にします。またDockerホストからweb02コンテナにコピーするときは、コンテナ上でのファイル名をindex.htmlとしてコピーします。

docker cp /tmp/index02.html web02:/usr/local/apache2/htdocs/index.html

手順4:
popdコマンドで、カレントディレクトリに戻します。

コンテナの破棄をすると、コピーしたファイルが失われる

コンテナを停止して再開しても、Dockerホストからコンテナにコピーしたファイルは失われません。しかし、コンテナを破棄してコンテナを立ち上げると、DockerホストからDockerコンテナにコピーしたファイルは失われています。

 docker run -dit --name web01 -p 8080:80 httpd:2.4

これは起動前のコンテナと削除後に起動したコンテナが別物であることを意味します。実際にdocker psコマンドでコンテナIDを確認すると、違う事がわかります。コンテナIDが違うので、別のコンテナであることがわかります。

大事なことは コンテナを作り直すと、別のコンテナが作成されるので、不注意にコンテナを削除してファイルを削除しないように気をつけましょう。

データを独立させる

docker rmでコンテナを破棄すると、そのコンテナの中にあるデータは失われます。 失ってはならないデータをコンテナに持たせたい場合、Dockerホストからコンテナに対してマウントを行います。

(※) コンテナでは「実行するシステム」と「扱うデータ」は別に管理することが推奨されています。

マウントはディレクトリを同期することなので、ディレクトリを指定します。マウントする場合は、-vオプションを指定します。以下のコマンドでは、Dockerホストのubuntuディレクトリをコンテナのhtdocsディレクトリにマウントしています。

docker run -dit --name web01 -p 8080:80 -v /home/ubuntu/:/usr/local/apache2/htdocs httpd:2.4

Dockerホスト上にあるディレクトリは、コンテナを破棄しても失われることはありません。Dockerホスト上のディレクトリをマウントすることで、コンテナを破棄しても、また同じファイルを持ったコンテナを作成する事ができます。 また、データをDockerホスト側に持つことで、イメージのアップデートに対応するのが容易になります。

Dockerホストのディレクトリを2つ以上のコンテナで同時にマウントする

Dockerホストのディレクトリを2つ以上のコンテナで同時にマウントすることもできます。そうすることで、2つ以上のコンテナ間でファイルの共有をする事ができます。

(※) ボリュームでも同じように2つ以上のコンテナに対して、同時にマウントできます。

バインドマウントとボリュームマウント

バインドマウントとは、 Dockerホストにあらかじめディレクトリを作っておき、それをマウントする方法のことです。 今までやってきた-vオプションのマウント元にパスを指定するのが、バインドマウントです。

ボリュームマウント

ボリュームマウントとは、 ホスト上のディレクトリではなく、Docker Engine上で確保した領域をマウントする方法のことです。確保した場所のことを「データボリューム」または「ボリューム」と呼びます。 ボリュームマウントをする場合、-vオプションのマウント元にボリュームを指定します。または、-vオプションではなく、mountオプションを使用します。

ボリュームマウントをする場合、コンテナを作る前に、ボリュームをあらかじめdocker volume createコマンドを使って作成します。

ボリュームマウントのメリット

ボリュームマウントのメリットは、それぞれのDockerホストのディレクトリ構成を意識せずにマウントが行えることです。また、ボリュームはブラックボックスなので、Dockerホストから変更させたくないようなファイルをマウントするのに適しています。そのため、データベース用のコンテナのデータを保存する場所として、ボリュームは優れています。

(※) ボリュームはデフォルトではDockerホスト上のストレージですが、ボリュームプラグインをインストールすることで、AWSのS3ストレージやNFSなどのネットワークストレージを用いることもできます。

MySQLコンテナを起動させてみる

MySQLがインストールされたコンテナのDockerイメージ Docker Hub

マウントすべきディレクト

DBのデータは/var/lib/mysqlディレクトリに保存されます。ここをボリュームマウント(またはバインドマウント)することで、コンテナを破棄しても、DBの内容が失われないようにします。

rootユーザーのユーザー名、パスワード、既定のデータベース名などの指定方法

DBにアクセスする際のrootユーザーのユーザー名、パスワード、既定のデータベースなどは、環境変数として引き渡します。MYSQL_ROOT_PASSWORDのみ必須で、残りはオプションです。 環境変数を指定するときは、-eオプションを指定します。

作業手順

手順1:
以下のコマンドを実行してボリュームを作成します。

docker volume create --name ボリューム名
docker volume create --name mysqlvolume

作成したボリュームは以下のコマンドで確認できます。

docker volume ls

以下のように表示されます。

ubuntu@ip-10-0-12-65:~$ docker volume ls
DRIVER    VOLUME NAME
local     mysqlvolume

手順2:
ボリュームをマウントしてMySQLのコンテナを起動します。rootユーザーのパスワードを「mypassword」にします。ボリュームマウントをしたい場合、-vオプションのマウント元にボリュームを指定します。

docker run --name db01 -dit -v mysqlvolume:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=mypassword mysql:5.7

手順3:
MySQLコンテナにログインして、新しいDBを作り、適当なデータを書き込みます。その後、コンテナを破棄して新しいコンテナをボリュームマウントして作り直しても、そのデータが破棄されていない事が分かります。

Dockerイメージを使う時の注意点

Dockerイメージを使う場合、そのイメージの制作者が「どのような使い方を想定して作っているのか」「各種設定はどのようにして行えば良いのか」を、記載されているドキュメントから汲み取る必要があります。

データのバックアップ

バインドマウントの場合、Dockerホスト上のファイルをバックアップすれば良いので、Dockerホストで別のディレクトリにコピーするか、tarコマンドでファイルをまとめて保存すればバックアップできます。

ボリュームマウントの場合、ボリュームのバックアップをするには、ボリュームを適当なコンテナに割り当てて、そのコンテナを使ってバックアップを取ります。

tarコマンドとは

以下の記事が分かりやすかったので、引用させていただきます。

↓ 引用(【 tar 】コマンド――アーカイブファイルを作成する/展開する)

 「tar」は、複数のファイルを1つにまとめた“アーカイブファイル”を作成/展開するコマンドです。

(※) アーカイブとは書庫という意味です。tarコマンドはプログラムの複数ファイル群をまとめて保管したり、配布する際に使用します。

ボリュームの詳細情報を確認する

ボリュームの詳細情報は以下のコマンドで確認できます。

docker volume inspect ボリューム名 (またはボリュームID)

Mountpointが実際にコンテナにマウントされている場所です。

ubuntu@ip-10-0-12-65:~$ docker volume inspect mysqlvolume
[
    {
        "CreatedAt": "2022-11-23T01:31:40Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/mysqlvolume/_data",
        "Name": "mysqlvolume",
        "Options": {},
        "Scope": "local"
    }
]
ubuntu@ip-10-0-12-65:~$

ボリュームバックアップの作業手順

適当なLinuxシステムが入ったコンテナを1つ別に起動します。その際、コンテナ内の/tmpなどのディレクトリにパックアップ対象のボリュームをマウントします。その後、tarコマンドでパックアップを作り、そのバックアップをDockerホストで取り出せばバックアップは完了です。リストアするときは逆の手順で戻します。

(注) コンテナの稼働中のバックアップはデータの整合性が取れなくなる可能性があるので、バックアップ中はコンテナ(DBコンテナ等)を停止しておくのが望ましいです。

手順1:
ボリュームを利用中のコンテナが停止中または存在しないかを確認します。

docker ps -a

手順2:
軽量Linuxシステムのbusyboxが入ったコンテナを、mysqlvolumeをマウントして起動します。その際、tarコマンドを実行してアーカイブします。

docker run --rm -v mysqlvolume:/src -v "$PWD":/dest busybox tar czvf /dest/backup.tar.gz -C /src .

このコマンドでは、一つ目のvオプションでバックアップをしたいmysqlvolumeを/srcにボリュームマウントしています。2つ目のvオプションではDockerホストのカレントディレクトリを/destにバインドマウントをしています。最後に tar czvf /dest/backup.tar.gz -C /src .を実行することで、/srcディレクトリの全ファイルが/dest/backup.tar.gzにバックアップしています。/destをDockerホストのカレントディレクトリにマウントしているので、このファイルはDockerホストのカレントディレクトリから参照する事ができます。

(注) 1つのコンテナに対して複数のマウントをすることができるので、その点を忘れないようにしましょう。

ボリュームの削除

以下のコマンドを実行することで、ボリュームを削除できます。

docker volume rm ボリューム名

どのコンテナからもマウントされていないボリュームを削除する場合、以下のコマンドでボリュームをまとめて削除する事ができます。

docker volume prune

ボリュームをリストアする

カレントディレクトリにあるbackup.tar.gzをリストアするためには、ボリュームを一旦削除後、同じ名前のボリュームを再度作り直してリストアします。リストアするためには以下のコマンドを実行します。

docker run --rm -v mysqlvolume:/dest -v "$PWD":/src busybox tar xzf /src/backup.tar.gz -C /dest

このコマンドでは、リストア先のボリューム(mysqlvolume)を/destにボリュームマウント、Dockerホストのカレントディレクトリを/srcにバインドマウントしています。こうすることで、カレントディレクトリに置いたbackup.tar.gzは、/src/backup.tar.gzとして参照する事ができます。その後、tar xzf /src/backup.tar.gzを実行して、ファイルを/destに展開しています。/destはリストア先のボリューム(mysqlvolume)にマウントされているので、結果的にそのボリュームにファイルが展開されます。

参考記事

さわって学ぶクラウドインフラ docker基礎からのコンテナ構築 | 大澤 文孝, 浅居 尚 |本 | 通販 | Amazon