Docker の volume は、コンテナが使うデータを永続化するための仕組みで、これを使うことでコンテナのライフサイクルとは別にデータを管理することができる。
また、network という機能を使うことで、コンテナ間で通信ができるようになる。
この記事では、volume と network の基本的な使い方を見ていく。
コンテナを削除すればそのなかにあるデータも削除されてしまう
基本的に Docker のコンテナは、一度作ったものを長く大切に使い続けるのではなく、作成と破棄を繰り返して使うことが多い。
そしてコンテナを破棄すれば、コンテナのなかにあるデータも当然失われてしまう。
MySQL のコンテナで試してみる。
まずコンテナを作り起動させる。
% docker run --name test_db -dit -e MYSQL_ROOT_PASSWORD=password mysql:8
以下のコマンドを入力するとパスワードを求められるので、先程MYSQL_ROOT_PASSWORD
として設定したpassword
を入力する。
% docker exec -it test_db mysql -p
そうすると MySQL に接続できるので、sample_db
というデータベースを作ってみる。
mysql> CREATE DATABASE sample_db;
Query OK, 1 row affected (0.01 sec)
mysql> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sample_db |
| sys |
+--------------------+
5 rows in set (0.01 sec)
mysql> exit
Bye
その後、test_db
コンテナを一度停止させてから再度起動してアクセスしても、sample_db
は存在する。
% docker stop test_db
% docker start test_db
% docker exec -it test_db mysql -p
mysql> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sample_db |
| sys |
+--------------------+
5 rows in set (0.01 sec)
だがコンテナを削除し新しく作り直してからその中身を確認すると、sample_db
は存在しない。
% docker stop test_db
% docker rm test_db
% docker run --name test_db -dit -e MYSQL_ROOT_PASSWORD=password mysql:8
% docker exec -it test_db mysql -p
mysql> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.00 sec)
今見ているコンテナは、名前こそtest_db
ではあるが最初に作成したコンテナとは別のコンテナなのだから、sample_db
が存在しないのは当然といえる。
このように、コンテナを削除してしまうと、コンテナに対して行った操作やそれによって生まれたデータは、全て失われてしまう。
volume を使ってデータを永続化する
Docker が提供する volume という機能を使うことで、データを永続化できる。
volume を使うと、コンテナではなくホストマシンにデータを保存するようになる。そしてそれでいて、コンテナのなかにデータが存在するかのように扱うことができるので、コンテナから簡単にデータを利用することができる。
まず、先ほど作成したtest_db
はもう使わないので、停止し削除する。
% docker stop test_db
% docker rm test_db
volume を使うためにはまず、docker volume create volumeの名前
で volume を作る必要がある。
今回はmysql_volume
という volume を作ることにする。
% docker volume create mysql_volume
そしてコンテナを作る際に、-v ボリューム名:コンテナの記憶領域のパス
というオプションをつければいい。
% docker run --name first_db -dit -e MYSQL_ROOT_PASSWORD=password -v mysql_volume:/var/lib/mysql mysql:8
上記の例では、-v mysql_volume:/var/lib/mysql
というオプションをつけて、first_db
という名前のコンテナを作っている。
「コンテナの記憶領域のパス」として/var/lib/mysql
を指定しているが、こうすることで、volume に保存されているデータを、コンテナの/var/lib/mysql
に存在するかのように扱うことができる。シンボリックリンクのようなもので、/var/lib/mysql
には実体は存在せず、あくまでも volume にデータが保存される。そして volume はコンテナではなくホストマシンに存在するため、このコンテナが破棄されたところで、データは残り続ける。
MySQL コンテナの場合、データは/var/lib/mysql
に保存される。そのため SQL でデータに対して何らかの操作を行うと、/var/lib/mysql
に対して操作を行うことになり、それはつまり volume に対して操作を行うことになる。
実際に操作を行って確認してみる。
まずfirst_db
に接続してsample_db
というデータベースを作る。
% docker exec -it first_db mysql -p
mysql> CREATE DATABASE sample_db;
Query OK, 1 row affected (0.01 sec)
mysql> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sample_db |
| sys |
+--------------------+
5 rows in set (0.00 sec)
次にfirst_db
を削除する。
% docker stop first_db
% docker rm first_db
そしてsecond_db
という新しいコンテナを作る。この際、先程と同様にmysql_volume
をコンテナと紐付けるようにする。
% docker run --name second_db -dit -e MYSQL_ROOT_PASSWORD=password -v mysql_volume:/var/lib/mysql mysql:8
そうすると、既にsecond_db
のなかにsample_db
が存在している。
% docker exec -it second_db mysql -p
mysql> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sample_db |
| sys |
+--------------------+
5 rows in set (0.01 sec)
first_db
に対して行った操作(sample_db
の作成)はmysql_volume
という volume に保存され、そのデータはfirst_db
とは別に管理されている。そのため、first_db
が削除されても残り続ける。
そしてsecond_db
を作成する際にmysql_volume
と紐付けたので、既にsecond_db
にはsample_db
が存在しているのである。
network によるコンテナ間の通信
network という機能を使うと、コンテナ間で通信ができるようになる。
例として Nginx のコンテナと Node.js のコンテナを用意し、両者間で通信できるようにしてみる。
Nginx コンテナの準備
まずnginx
ディレクトリとnode
ディレクトリを作り、そこに必要なファイルを入れていくことにする。
% mkdir nginx
% mkdir node
そして以下のコマンドを実行し、nginx/user
ディレクトリにデータを用意する。このデータを Nginx コンテナにコピーして使うことになる。
% mkdir nginx/user
% echo "{\"id\": 1, \"name\": \"Alice\"}" > nginx/user/1.json
% echo "{\"id\": 2, \"name\": \"Bob\"}" > nginx/user/2.json
% echo "{\"id\": 3, \"name\": \"Carol\"}" > nginx/user/3.json
最後に、以下の内容のnginx/Dockerfile
を作成。
FROM nginx:latest
WORKDIR /usr/share/nginx/html
COPY ./user ./user
Dockerfile の書き方については特に説明しないので、下記を参照。
numb86-tech.hatenablog.com
そして以下のコマンドを実行し、sample_nginx_image
というイメージを作る。
% docker build -t sample_nginx_image ./nginx
そしてそのイメージから、sample_nginx
というコンテナを作る。
% docker run --name sample_nginx -d -p 8084:80 sample_nginx_image
8084:80
でポートマッピングしたので、ホストマシンの8084
ポートを経由して、Nginx コンテナの80
ポートにアクセスできる。
% curl localhost:8084/user/1.json
{"id": 1, "name": "Alice"}
% curl localhost:8084/user/2.json
{"id": 2, "name": "Bob"}
% curl localhost:8084/user/3.json
{"id": 3, "name": "Carol"}
% curl localhost:8084/user/4.json
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.21.3</center>
</body>
</html>
だがこれは、ホストマシンとコンテナの通信である。
繰り返しになるが、network を使うことで、コンテナとコンテナで通信できるようになる。
Nginx コンテナが問題なく動いていることを確認できたので、sample_nginx
は、停止、削除しておく。
% docker stop sample_nginx
% docker rm sample_nginx
network の作成
docker network create ネットワークの名前
で、ネットワークを作成できる。
% docker network create sample_network
そして作成されたネットワークとコンテナを紐付けることで、同一のネットワークに紐付けられているコンテナ同士で通信できるようになる。
まず、sample_nginx_image
を元にしたコンテナを、sample_network
と紐付ける形で作成する。
--net ネットワークの名前
オプションを付けることで、そのネットワークと紐付ける形でコンテナを作れる。
% docker run --name sample_nginx -d --net sample_network sample_nginx_image
Node.js コンテナの作成
あとは、sample_network
と紐付ける形で、Node.js のコンテナを作ればよい。
以下の内容のnode/index.js
とnode/package.json
を作る。
import fetch from "node-fetch";
import http from "http";
http
.createServer(async function (req, res) {
let result;
try {
const fetchResponse = await fetch("http://sample_nginx:80/user/1.json");
result = await fetchResponse.json();
} catch (e) {
result = e.message;
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(`${JSON.stringify(result)}\n`);
})
.listen(3000);
{
"type": "module",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"node-fetch": "3.2.3"
}
}
重要なのはnode/index.js
のfetch
の部分。
ここで、リクエスト先のホストをsample_nginx
としている。こうすることで、sample_nginx
コンテナと通信が行われる。
最後にnode/Dockerfile
を作成し、そこからsample_node_image
というイメージを作成する。
FROM node:16
WORKDIR /scripts
COPY package.json ./
RUN npm i -G yarn
RUN yarn install
COPY index.js ./
CMD ["yarn", "run", "start"]
% docker build -t sample_node_image ./node
そして、sample_network
と紐付ける形でコンテナを作成、起動する。
% docker run --name sample_node --net sample_network -d -p 8080:3000 sample_node_image
sample_nginx
もsample_node
もsample_network
に紐付けられているため、両者間で通信ができるようになり、fetch
で情報を取得できるようになった。
curl で Node.js コンテナにアクセスしてみると、sample_nginx
から取得したデータが返されていることを確認できる。
% curl http://localhost:8080
{"id":1,"name":"Alice"}
Node.js コンテナと MySQL コンテナを接続する
最後により実践的な内容として、Node.js コンテナと MySQL コンテナの間でやり取りできるようにしてみる。また、MySQL コンテナには volume を紐付け、データが永続化されるようにする。
まずは volume と network の作成。
% docker volume create app_volume
% docker network create app_network
そして、これらと紐付けた形で、MySQL コンテナを作成する。
% docker run --name app_db -dit -e MYSQL_ROOT_PASSWORD=password -v app_volume:/var/lib/mysql --net app_network mysql:8
そして作成されたコンテナの MySQL に接続し、動作確認用のテーブルやデータを用意する。
% docker exec -it app_db mysql -p
mysql> CREATE DATABASE sample_db;
Query OK, 1 row affected (0.00 sec)
mysql> USE sample_db;
Database changed
mysql> CREATE TABLE users (id INT AUTO_INCREMENT, name TEXT NOT NULL, PRIMARY KEY (id));
Query OK, 0 rows affected (0.02 sec)
mysql> INSERT INTO users(name) VALUES('Alice');
Query OK, 1 row affected (0.02 sec)
具体的には、sample_db
というデータベースを作成し、そのなかにusers
というテーブルを作成、そしてそこに 1 件のレコードを作成している。
mysql> SELECT * FROM users;
+----+-------+
| id | name |
+----+-------+
| 1 | Alice |
+----+-------+
1 row in set (0.00 sec)
Node.js コンテナからこのデータを取得できるようにするのが、ゴールである。
あとで必要になるのでポート番号も確認しておく。
mysql> show variables like 'port';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| port | 3306 |
+---------------+-------+
1 row in set (0.01 sec)
MySQL 側の準備はこれで完了。
次は、Node.js コンテナの準備。
以下の内容でindex.js
、package.json
、Dockerfile
を作成する。
import fetch from "node-fetch";
import http from "http";
import mysql from "mysql2";
http
.createServer(async function (req, res) {
let result;
try {
const connection = mysql.createConnection({
host: "app_db",
port: 3306,
user: "root",
password: "password",
database: "sample_db",
});
connection.connect();
result = await connection
.promise()
.query("SELECT * FROM users;")
.then(([rows]) => {
return rows;
})
.catch((err) => err);
} catch (e) {
result = e.message;
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(`${JSON.stringify(result)}\n`);
})
.listen(3000);
{
"type": "module",
"license": "MIT",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"mysql2": "2.3.3",
"node-fetch": "3.2.3"
}
}
FROM node:16
WORKDIR /scripts
COPY package.json ./
RUN npm i -G yarn
RUN yarn install
COPY index.js ./
CMD ["yarn", "run", "start"]
index.js
のなかで MySQL との接続を行っているが、その際にhost
としてapp_db
を指定することで、app_db
コンテナと通信できるようになる。もちろん、Node.js コンテナとapp_db
コンテナが同一のネットワーク(今回の場合はapp_network
)に紐付いていることが前提となる。
イメージを作成し、そこからコンテナを作成、起動する。
$ docker build -t app_node_image .
$ docker run --name app_node -d -p 8080:3000 --net app_network app_node_image
無事、Node.js を経由して MySQL からデータを取得することができた。
% curl http://localhost:8080
[{"id":1,"name":"Alice"}]`