DynamoDB LocalのDockerイメージを使う際の「WARNING: [sqlite] cannot open DB[*]: com.almworks.sqlite4java.SQLiteException: [*] unable to open database file」の解決方法3つ

## はじめに
dynamonodb-localのDockerイメージを使用する際に、データの永続化のためにvolumesを指定したが、以下のようにwarningが出てホスト側のマウント先に書き込みができずに困った。今回はその解決策について備忘録を残す。
```console
dynamodb-local    | WARNING: [sqlite] cannot open DB[1]: com.almworks.sqlite4java.SQLiteException: [14] unable to open database file
dynamodb-local    | Apr 30, 2022 12:02:35 PM com.almworks.sqlite4java.Internal log
dynamodb-local    | SEVERE: [sqlite] SQLiteQueue[shared-local-instance.db]: error running job queue
dynamodb-local    | com.almworks.sqlite4java.SQLiteException: [14] unable to open database file
dynamodb-local    |     at com.almworks.sqlite4java.SQLiteConnection.open0(SQLiteConnection.java:1480)
dynamodb-local    |     at com.almworks.sqlite4java.SQLiteConnection.open(SQLiteConnection.java:282)
dynamodb-local    |     at com.almworks.sqlite4java.SQLiteConnection.open(SQLiteConnection.java:293)
dynamodb-local    |     at com.almworks.sqlite4java.SQLiteQueue.openConnection(SQLiteQueue.java:464)
dynamodb-local    |     at com.almworks.sqlite4java.SQLiteQueue.queueFunction(SQLiteQueue.java:641)
dynamodb-local    |     at com.almworks.sqlite4java.SQLiteQueue.runQueue(SQLiteQueue.java:623)
dynamodb-local    |     at com.almworks.sqlite4java.SQLiteQueue.access$000(SQLiteQueue.java:77)
dynamodb-local    |     at com.almworks.sqlite4java.SQLiteQueue$1.run(SQLiteQueue.java:205)
dynamodb-local    |     at java.lang.Thread.run(Thread.java:748)
```

※調べた所、少なくとも以下の3通りのやり方があるようなので、それぞれのアプローチについて1つずつ見ていく。

1. `user: root`を指定し、rootユーザーで起動する
1. あらかじめ手動でvolumesで指定しているホスト側のマウント先になるディレクトリを作成し`chmod 777`に変更する
1. 自分で新しくイメージを作成し、Dockerのvolumeにマウントする

## 実際の解決策を見ていく前に
### DynamoDB Localについて
公式にDockerイメージを使う場合のやり方が書かれているのでそちらを参照。

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html

※Docker Hubでいうと[amazon/dynamodb-local](https://hub.docker.com/r/amazon/dynamodb-local/)にイメージがある。


### そもそもなぜホスト側のマウント先に書き込みができないのか？
`amazon/dynamodb-local`の[Dockerイメージの定義](https://hub.docker.com/layers/dynamodb-local/amazon/dynamodb-local/latest/images/sha256-083b4cb70bd24421db50271342405a8879cc931cc58cdffe4e96259a13a933a5?context=explore)を見てみると、`USER dynamodblocal`となっており、dynamodblocalというユーザーでDynamoDBを起動・実行している事が分かる。

そして、[AWSに書かれてるようなdocker-compose.yaml](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.DownloadingAndRunning.html)のvolumesで指定しているホスト側のディレクトリの所有者は以下の通り、rootユーザーになっている（docker-compose up でコンテナを起動すると、自動でホスト側のdataディレクトリ以下が作成されるが、そのディレクトリの所有者がrootになってしまうという事）。

```console
[study@localhost node-express]$ tree . -I node_modules
.
...
├── data
│   └── dynamodb

[study@localhost data]$ ls -l
合計 0
drwxr-xr-x. 2 root root 38  4月 30 21:30 dynamodb
```

つまり、ユーザーの違いによるパーミッションの問題が発生し、volumesで指定しているホスト側のディレクトリ（今回だと`./data/dynamodb`）に書き込みができず、データのファイル（`shared-local-instance.db`）も作成できないので読み込みもできず（`unable to open database file`）…という事が起きている。

## 解決策
### その1　`user: root`を指定し、rootユーザーで起動・実行する
これはrootユーザーが所有者なのでDynamoDBのプロセスもrootユーザーにしてしまい、パーミッションの問題を発生させないようにするアプローチ。この設定をすると、以下の動画の通り、warningが表示されず、データの永続化ができている事が分かる（`shared-local-instance.db`というファイルがホスト側に作成されているので）。
![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/55aa58ef-72dc-6c42-1fea-d11a7faa1dbf.gif)

※ソースコード全体は以下。

https://github.com/yuta-katayama-23/node-express/commit/1c94f50c34eaa8f80e7d1cb558d5d966e1bf4336

- 参考：[Dockerfile reference USER](https://docs.docker.com/engine/reference/builder/#user)（日本語は[ここ](https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/builder/#user)）

### その2　あらかじめ手動でvolumesで指定しているホスト側のマウント先になるディレクトリを作成し`chmod 777`に変更する
次に見ていく方法としては、dockerのvolumesで指定しているホスト側のディレクトリを手動で作成し、そのパーミッションを777（すべてのユーザーで全ての操作(読み取り・書き込み・実行)を許可する）に変更する（Linuxのパーミッションについては参考に示した記事等を参照）。
これによりdynamodb-localの実行ユーザーdynamodblocalでもホスト側のディレクトリに書き込みができるようになる。
![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/3cf7fe08-8203-a7f0-709f-51a73d263860.gif)

```console
[study@localhost node-express]$ mkdir -p ./data/dynamodb
[study@localhost node-express]$ chmod 777 ./data/dynamodb
[study@localhost node-express]$ cd data
[study@localhost data]$ ls -l
合計 0
drwxrwxrwx. 2 study study 6  5月  2 16:35 dynamodb

[study@localhost node-express]$ cd data/dynamodb/
[study@localhost dynamodb]$ ls -l
合計 16
-rw-r--r--. 1 1000 1000 16384  5月  2 16:35 shared-local-instance.db
```

- 参考：[Linuxの権限確認と変更(chmod)（超初心者向け）](https://qiita.com/shisama/items/5f4c4fa768642aad9e06)
- 参考：[[小ネタ] DockerでlocalDynamoDB起動する時の注意点](https://qiita.com/etnk/items/fff02e8fbf6901e013f9)

### その3　自分で新しくイメージを作成し、Dockerのvolumeにマウントする
最後の方法は、dynamodb-localのイメージの実行ユーザーであるdynamodblocalでdataディレクトリを作成するように、Dockerfileを自分で定義し、Dockerのvolumes（`/var/lib/docker/volumes`以下）にマウントする、という方法。

まずは以下のようにDockerfileを作成し、amazon/dynamodb-localのイメージを上書きする。

```dockerfile
FROM amazon/dynamodb-local:latest

WORKDIR /home/dynamodblocal

RUN whoami; \ # <- 実行ユーザーを確認するためにわざと書いている（本来は不要）
    mkdir data;
```

あとは、docker-compose.yamlの方を以下のように書き換えればOK。

```yaml:docker-compose.yaml
version: '3.9'
services:
  dynamodb-local:
    command: -jar DynamoDBLocal.jar -sharedDb -dbPath ./data
    image: my-dynamodb-local
    build:
      context: ./build
      dockerfile: Dockerfile
    container_name: dynamodb-local
    ports:
      - '8000:8000'
    volumes:
      - dynamodb-data:/home/dynamodblocal/data
    working_dir: /home/dynamodblocal

volumes:
  dynamodb-data:
```

1点注意として、[公式](https://docs.docker.com/compose/compose-file/#volumes-top-level-element)にも書かれている通り、トップレベル要素のvolumesではそのキー以下は空でもよいが、キーになっていない（`:`がない）と`In file './docker-compose.yaml', volume must be a mapping, not a string.`というエラーがでる。

> An entry under the top-level volumes key can be empty, in which case it uses the platform’s default configuration for creating a volume.（トップレベルのボリュームキーの下のエントリーは空でもよく、その場合、ボリュームを作成するためのプラットフォームのデフォルトの設定が使用されます）

上記のように設定すると、以下の動画の通りwarningは出なくなる。
![ezgif.com-gif-maker (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/e0e14779-dca7-4462-eb48-b1f9e1aaf39e.gif)

ちなみに、永続データがどこに保存されるか？だが、`/var/lib/docker/volumes`というディレクトリにデフォルトの設定でディレクトリが作成され、そこにマウントされる。

```console
[root@localhost volumes]# pwd
/var/lib/docker/volumes
[root@localhost volumes]# ls -l
合計 32
brw-------. 1 root root 253, 0  4月 30 16:54 backingFsBlockDev
-rw-------. 1 root root  65536  5月  2 17:04 metadata.db
drwx-----x. 3 root root     19  5月  2 17:04 node-express_dynamodb-data
[root@localhost volumes]# cd node-express_dynamodb-data
[root@localhost node-express_dynamodb-data]# ls -l
合計 0
drwxr-xr-x. 2 1000 1000 38  5月  2 17:17 _data
[root@localhost node-express_dynamodb-data]# cd _data/
[root@localhost _data]# ls -l
合計 16
-rw-r--r--. 1 1000 1000 16384  5月  2 17:17 shared-local-instance.db
```

また、ちなみに上記のuser/groupの`1000`についてだが、dynamodblocalのuser/groupのidが1000であり、dynamodblocalによりホスト側のvolume（今回だとnode-express_dynamodb-data）が作成されている事が分かる。

```console
[study@localhost node-express]$ docker-compose up
Creating volume "node-express_dynamodb-data" with default driver
Building dynamodb-local
...
Step 3/3 : RUN whoami;     id;     mkdir data;
 ---> Running in e82a742db0d7
dynamodblocal
uid=1000(dynamodblocal) gid=1000(dynamodblocal) groups=1000(dynamodblocal) # <- idコマンドの実行結果
Removing intermediate container e82a742db0d7
 ---> ca5db73e1d11
...
```

※ソースコード全体は以下。

https://github.com/yuta-katayama-23/node-express/commit/ebe806aade8ae5acc46c02bfa2187853108684a8

- 参考：[Running Dynamodb local with Docker and Persistent Data Storage](https://www.codingcrossroads.com/running-dynamodb-local-with-docker-and-persistent-data-storage/)

## まとめとして
あまり手動で何をやるというのは避けたいという気持ちもあるので、その場合には解決策のその1を使うのがいいような気がするが、rootユーザーに引っかかる場合には、その2やその3に挙げたような方法で解決する事になるのだろうと思う。