Windows for Docker & docker composeにて、top-level volumes option で named volume を定義してPostgreSQLのデータを永続化する

WindowsPostgreSQLを使って開発する際、コンテナを破棄してもデータが残るデータの永続化を考えました。

ただ、macと同じように

version: '3'
services:
  postgres:
    image: postgres:12.2-alpine
    tty: true
    restart: always

    volumes:
      # pgdataをホストに置く
      - ./pgdata:/var/lib/postgresql/data

    stdin_open: true
    ports:
      - "44321:5432"

と、pgdata というローカルのファイルシステム上のフォルダを指定したところ

postgres_1  | running bootstrap script ... 2020-03-27 15:56:59.023 UTC [51] FATAL:  data directory "/var/lib/postgresql/data" has wrong ownership
postgres_1  | 2020-03-27 15:56:59.023 UTC [51] HINT:  The server must be started by the user that owns the data directory.
postgres_1  | child process exited with exit code 1
postgres_1  | initdb: removing contents of data directory "/var/lib/postgresql/data"

というエラーが発生してPostgreSQLが起動しませんでした。

 
そんな中、同僚の @moon_in_nagano よりデータボリュームについて教わったため、メモを残します。

 

目次

 

環境

 
また、PostgreSQLでデータの永続化ができているかを確認するのに、Pythonを使いました。

  • Python 3.8.2
  • SQLAlchemy 1.3.15
  • psycopg2 2.8.4

 
テーブル作成 & データ投入のスクリプトは以下です。

# insert.py

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String)


if __name__ == "__main__":

    engine = create_engine('postgresql://{user}:{password}@localhost:{port}/{database_name}'.format(
        user='postgres',
        password='postgres',
        port=44321,
        database_name='postgres',
    ))
    Base.metadata.create_all(engine)

    Session = sessionmaker(bind=engine)
    session = Session()
    session.add(User(name='foo'))
    session.commit()

 

対応

データボリュームを使うにはコマンドラインのオプションを指定すれば良さそうでした。
Docker の Data Volume まわりを整理する - Qiita

docker-composeでの方法を調べたところ、 top-level volumes option を使えば良さそうでした。
Compose file version 3 reference | Docker Documentation

 

top-level volumes optionの設定

docker-compose.ymlでnamed volumeを設定します。

まずは、トップレベルにvolumeの記載を追加します。今回は pgdata というvolume名とします。

volumes:
  pgdata:

 
次に、serviceのvolumeの中で pgdata を使うように修正します。

services:
  postgres:
    image: postgres:12.2-alpine
    ...
    volumes:
      # ファイルシステムの指定をやめて、PostgreSQLのデータを名前付きボリュームを使って永続化
      # - ./pgdata:/var/lib/postgresql/data
      # top-level volumes optionで指定した "pgdata" を ":" の左側へ設定
      - pgdata:/var/lib/postgresql/data

 
全体はこちら。

# マイナーバージョンを書かないと自動的にマイナーバージョンが"0"になるので、"3.7"と明示的に設定
# https://docs.docker.com/compose/compose-file/compose-versioning/#version-3
version: '3.7'
services:
  postgres:
    image: postgres:12.2-alpine

    environment:
        # エンコーディングを指定しておく
        # initdbで指定できる内容はここで指定可能
        # https://www.postgresql.org/docs/10/static/app-initdb.html を参照。
        POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=ja_JP.UTF-8"
        
        # パスワード
        POSTGRES_PASSWORD: postgres

    # ttyをtrueに設定しておくと、コンテナが起動し続ける
    tty: true
    restart: always

    volumes:
      # ファイルシステムの指定をやめて、PostgreSQLのデータを名前付きボリュームを使って永続化
      # - ./pgdata:/var/lib/postgresql/data
      - pgdata:/var/lib/postgresql/data

    stdin_open: true
    ports:
      - "44321:5432"
        
volumes:
  pgdata:

 

動作確認

あとはいつもどおり起動します。

docker-compose up

 
Pythonスクリプトを流した後、PostgreSQL上にデータが存在することを確認します。

# スクリプトの実行
python insert.py

# コンテナのPostgreSQLを確認
>docker container exec -it temp_postgres_1 sh
/ # psql -U postgres -h 127.0.0.1 -p 5432 postgres
psql (12.1)
Type "help" for help.

postgres=# SELECT * FROM users;
 id | name
----+------
  1 | foo
(1 row)

 
down後に再度upしても、データが残っていることを確認します。

# 停止
docker-compose down

# 起動
docker-compose up

# 確認
## Dockerの中に入ってshを実行
>docker container exec -it temp_postgres_1 sh

## PostgreSQLの中に入る
/ # psql -U postgres -h 127.0.0.1 -p 5432 postgres
psql (12.1)
Type "help" for help.

## SELECT実行
postgres=# SELECT * FROM users;
 id | name
----+------
  1 | foo
(1 row)

 

named volumeの削除

もし不要になった場合は、down時にオプション -v を追加することで、コンテナと同時にさくじょできます。 ( docker volume rm で別々に削除することも可能)

>docker-compose down -v
Stopping temp_postgres_1 ... done                                                                                       Removing temp_postgres_1 ... done                                                                                       Removing network temp_default
Removing volume temp_pgdata

 
再度起動しても、PostgreSQLのデータはありません。

>docker container exec -it temp_postgres_1 sh
/ # psql -U postgres -h 127.0.0.1 -p 5432 postgres
psql (12.1)
Type "help" for help.

postgres=# SELECT * FROM users;
ERROR:  relation "users" does not exist
LINE 1: SELECT * FROM users;

 

その他

手元ではダメだったこと
PostgreSQLのパスからdataを削除する

docker-compose.ymlで、

services:
  postgres:
    image: postgres:12.2-alpine
    volumes:
      # - ./pgdata:/var/lib/postgresql/data
      - ./pgdata:/var/lib/postgresql

と、 ./pgdata:/var/lib/postgresql とやれば動作するというのも見かけました。

ただ、手元では動作はしたものの、データの永続化がなされませんでした。

 

top-level volumes optionで保存先をローカルのファイルシステムにする

このstackoverflowを見ると、top-level volumes optionでもローカルのファイルシステムを指定できそうでした。

そのため、ローカルの D:\temp\pgdata2 に保存するよう

volumes:
  pgdata:
    driver: local
    driver_opts:
        type: none
        device: "/host_mnt/d/temp/pgdata2"
        o: bind

と設定して docker-compose up したところ

postgres_1  | running bootstrap script ... 2020-03-27 22:48:46.370 UTC [50] FATAL:  data directory "/var/lib/postgresql/data" has wrong ownership
postgres_1  | 2020-03-27 22:48:46.370 UTC [50] HINT:  The server must be started by the user that owns the data directory.
postgres_1  | child process exited with exit code 1
postgres_1  | initdb: removing contents of data directory "/var/lib/postgresql/data"

と冒頭と同じようなエラーになりました。ローカルのファイルシステム自体に割り当てるのがダメですね。

 

Windowsにおけるnamed volumeのありか

上記では特にパスを指定していないので、named volumeがどこに置かれるのか分かりませんでした。

調べてみたところ

When running linux based containers on a windows host, the actual volumes will be stored within the linux VM and will not be available on the host's fs, otherwise windows running on windows => C:\ProgramData\Docker\volumes\

Locating data volumes in Docker Desktop (Windows) - Stack Overflow

With Docker for Windows, the volume is saved inside of the VM running Linux which is managed by Docker. It wouldn't be inside of a container, or your Windows box directly.

volume mount point - does not exist (Docker for Windows / WSL) - Course: Docker Fundamentals

とありました。

 
そこで、Docker for WindowsにおけるLinuxコンテナについて調べてみたところ、Microsoftの公式ドキュメントに

- Linux コンテナーを完全な Linux VM で実行する-これは、現在、Docker が行うものです。
- Hyper-v 分離(lcow) を使用して Linux コンテナーを実行する-これは Docker for Windows の新しいオプションです。

https://docs.microsoft.com/ja-jp/virtualization/windowscontainers/deploy-containers/linux-containers

とありました。

手元ではどちらが使われているのかを調べたところ、Hyper-VマネージャーにDockerDesktopVMがいました。

f:id:thinkAmi:20200329090905j:plain:w450

 
また、 C:\Program Files\Linux Containers フォルダ自体がありませんでした。

C:\Program Files>dir Linux*

 C:\Program Files のディレクトリ

ファイルが見つかりません

これらより、手元では Linux コンテナーを完全な Linux VM で実行する の設定でDocker for Windowsが動作しているようです。

 
そのため、Hyper-V上のLinuxコンテナにデータボリュームが保存されており、Windowsファイルシステム上では見れないのだと考えました。