WindowsでPostgreSQLを使って開発する際、コンテナを破棄してもデータが残るデータの永続化を考えました。
ただ、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 よりデータボリュームについて教わったため、メモを残します。
目次
環境
- Windows 10 Pro
- Docker for Windows
- Docker version 19.03.5, build 633a0ea
- PostgreSQL 12.2
- Dockerイメージは
postgres:12.2-alpine
を使用 - docker-compose.ymlは以下を参考・修正
- Dockerイメージは
また、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がいました。
また、 C:\Program Files\Linux Containers
フォルダ自体がありませんでした。
C:\Program Files>dir Linux* C:\Program Files のディレクトリ ファイルが見つかりません
これらより、手元では Linux コンテナーを完全な Linux VM で実行する
の設定でDocker for Windowsが動作しているようです。
そのため、Hyper-V上のLinuxコンテナにデータボリュームが保存されており、Windowsのファイルシステム上では見れないのだと考えました。