コンテナの再起動・再作成とデータの保持
イメージの設定、コンテナの寿命、volumeの寿命、ホスト側ポートの対応を分けて説明します。
説明
コンテナの起動時には環境変数や公開ポートを指定できます。ファイルの保存先には、コンテナ自身の書込み層と、コンテナとは別に管理するボリュームがあります。停止・再起動と、削除・再作成でデータの残り方がどう変わるかを調べます。
作成済みのversion=oneのイメージを使います。環境変数の上書き、書込み層とvolumeへの保存、再起動、再作成、読取り専用mountを順に確認します。最後に8080で動かすHTTP処理を127.0.0.1の44810へ公開し、実際の応答を取得します。
イメージの設定、コンテナの寿命、volumeの寿命、ホスト側ポートの対応を分けて説明します。
1イメージにあるポート設定
version=oneを表示するlbox-cl10:v1を使います。まずイメージに設定されているポートと環境変数を確認します。
docker image inspect lbox-cl10:v1 --format '{{json .Config.ExposedPorts}}イメージのポート宣言を読む {{json .Config.Env}}イメージ既定の環境を読む'8080/tcpとAPP_MODE=demoがあります。ExposedPortsはイメージのメタデータで、ホストの待受けポートを割り当てた結果ではありません。
2起動時だけ環境変数を上書きする
APP_MODEをprodにして一度実行します。
docker run --rm --network none -e APP_MODE=prod今回のコンテナへ渡す環境を上書きする lbox-cl10:v1version=one mode=prodです。イメージを作り直さず、今回のプロセスへ渡す環境を変更しました。イメージの既定値を保存し直した操作ではありません。
3ボリュームを接続して起動する
lbox-cl10-dataという名前のボリュームを作り、コンテナ内の/dataから利用できるように接続(マウント)します。sleepを実行してコンテナを起動したままにし、後の操作でファイルを書き込みます。
docker volume create名前付きvolumeを作る --label org.lbox.fixture=CL-10 lbox-cl10-data
docker run -d --name lbox-cl10-c1 --network none --mount type=volume,src=lbox-cl10-data,dst=/data/dataへvolumeを接続する --entrypoint sh lbox-cl10:v1 -c 'exec sleep 3600' > container.id
docker inspect lbox-cl10-c1起動状態とmountを確認する --format '{{.State.Running}} {{range .Mounts}}{{.Type}} {{.Destination}}{{end}}'Runningのtrueは起動中であることを示します。volume /dataは、/dataにボリュームをマウントしたという意味です。この構成では/tmpへの書込みはコンテナの書込み層へ、/dataへの書込みはボリュームへ保存されます。
4二つの保存先へ書く
一時ファイル/tmp/session.txtと、保持したいデータ/data/state.txtを作ります。二つの内容を表示して、変更前の値を確認します。
docker exec lbox-cl10-c1 sh実行中のコンテナ内で処理する -c 'printf "session=local\n" > /tmp/session.txtコンテナの書込み層へ保存する; printf "counter=1\n" > /data/state.txtvolumeへ保存する; cat /tmp/session.txt /data/state.txt'session=localとcounter=1を保存できました。次に、同じコンテナの再起動と、新しいコンテナへの作り直しで、この二つがどうなるかを確認します。
5停止と再起動なら両方残る
同じコンテナを停止し、再び起動して読みます。
docker stop lbox-cl10-c1同じコンテナを停止する
docker start lbox-cl10-c1停止したコンテナを再起動する
docker exec lbox-cl10-c1 cat二つの保存先を読み直す /tmp/session.txt /data/state.txt二つとも残っています。停止はコンテナの削除ではないため、同じコンテナの書込み層も保持されています。
6削除して再作成すると書込み層は別になる
c1を削除してc2を作り、同じ名前付きvolumeを接続します。
docker rm -f lbox-cl10-c1元のコンテナを削除する
docker run -d --name lbox-cl10-c2同じイメージから新しいコンテナを作る --network none --mount type=volume,src=lbox-cl10-data,dst=/data --entrypoint sh lbox-cl10:v1 -c 'exec sleep 3600' > container.id
docker exec lbox-cl10-c2 sh -c 'test -f /tmp/session.txt旧コンテナの一時ファイルがないことを確認する || echo "session missing"; cat /data/state.txt同じvolumeの内容を読む'session missingですが、counter=1は残ります。新しいコンテナには前の書込み層がなく、同じvolumeを付け直すことで永続データを読めています。
7読取り専用で接続すると書き込めない
同じvolumeをreadonlyで付けた別の一時コンテナから、内容を読んで書換えを試します。
docker run --rm --network none --mount type=volume,src=lbox-cl10-data,dst=/data,readonly同じvolumeを読取り専用で接続する --entrypoint sh lbox-cl10:v1 -c 'exec 2>&1; cat /data/state.txt; echo changed > /data/state.txt書込みが拒否されることを確認する'counter=1は読めますが、書込みはRead-only file systemで拒否されます。コンテナ内のユーザー権限だけでなく、mountの読取り専用条件も操作を制限します。
8コンテナ側とホスト側のポートを対応させる
ncで8080番ポートへの接続を受け、printfで固定のHTTP応答を返します。Content-Length: 14は本文の改行を含む14バイトを表します。-pでホストの127.0.0.1:44810をコンテナの8080へ転送します。
docker run -d --name lbox-cl10-web -p 127.0.0.1:44810:8080ホストのloopbackへポートを対応させる --entrypoint sh lbox-cl10:v1 -c 'while true; do printf "HTTP/1.1 200 OK\r\nContent-Length: 14\r\nConnection: close\r\n\r\ncontainer web\n" | nc -l -p 80808080で接続を受けて固定のHTTP応答を送る > /dev/null; done' > web.id
python3 wait_http.pyHTTPの準備完了を確認する
docker port lbox-cl10-webホスト側の公開先を表示する8080/tcp -> 127.0.0.1:44810と表示されます。EXPOSEはポートの宣言、ncはコンテナ内の待受け、-pはホスト側の公開設定です。この例では接続のたびに同じ本文を返します。
9ホスト側の公開先から取得する
127.0.0.1:44810へHTTPで接続し、コンテナから固定のHTTP応答を取得します。
curl -sS http://127.0.0.1:44810/ホストの公開ポートからHTTP応答を取得するcontainer webが返りました。ホスト側の44810へ送った要求がコンテナの8080へ届き、そこで返したHTTP応答の本文をcurlが表示しています。
まとめ
環境変数の上書きとイメージの作り直しは別の操作です。
再起動では書込み層が残り、再作成では同じvolumeを付け直すことで保存データを引き継げます。
ポート宣言、コンテナ内の待受け、ホストへの公開をそれぞれ確認します。
操作履歴と解説を振り返れます。
Docker Engine 29.7.2 / Linux arm64
docker image inspect lbox-cl10:v1 --format '{{json .Config.ExposedPorts}} {{json .Config.Env}}'{"8080/tcp":{}} ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","APP_MODE=demo"]docker run --rm --network none -e APP_MODE=prod lbox-cl10:v1version=one mode=prod
docker volume create --label org.lbox.fixture=CL-10 lbox-cl10-data
docker run -d --name lbox-cl10-c1 --network none --mount type=volume,src=lbox-cl10-data,dst=/data --entrypoint sh lbox-cl10:v1 -c 'exec sleep 3600' > container.id
docker inspect lbox-cl10-c1 --format '{{.State.Running}} {{range .Mounts}}{{.Type}} {{.Destination}}{{end}}'lbox-cl10-data true volume /data
docker exec lbox-cl10-c1 sh -c 'printf "session=local\n" > /tmp/session.txt; printf "counter=1\n" > /data/state.txt; cat /tmp/session.txt /data/state.txt'session=local counter=1
docker stop lbox-cl10-c1
docker start lbox-cl10-c1
docker exec lbox-cl10-c1 cat /tmp/session.txt /data/state.txtlbox-cl10-c1 lbox-cl10-c1 session=local counter=1
docker rm -f lbox-cl10-c1
docker run -d --name lbox-cl10-c2 --network none --mount type=volume,src=lbox-cl10-data,dst=/data --entrypoint sh lbox-cl10:v1 -c 'exec sleep 3600' > container.id
docker exec lbox-cl10-c2 sh -c 'test -f /tmp/session.txt || echo "session missing"; cat /data/state.txt'lbox-cl10-c1 session missing counter=1
docker run --rm --network none --mount type=volume,src=lbox-cl10-data,dst=/data,readonly --entrypoint sh lbox-cl10:v1 -c 'exec 2>&1; cat /data/state.txt; echo changed > /data/state.txt'counter=1 sh: can't create /data/state.txt: Read-only file system
docker run -d --name lbox-cl10-web -p 127.0.0.1:44810:8080 --entrypoint sh lbox-cl10:v1 -c 'while true; do printf "HTTP/1.1 200 OK\r\nContent-Length: 14\r\nConnection: close\r\n\r\ncontainer web\n" | nc -l -p 8080 > /dev/null; done' > web.id
python3 wait_http.py
docker port lbox-cl10-webHTTP ready on 127.0.0.1:44810 8080/tcp -> 127.0.0.1:44810
curl -sS http://127.0.0.1:44810/container web
演習完了