一覧CL-10-2 · コンテナイメージと実行

コンテナの再起動・再作成とデータの保持

イメージの設定、コンテナの寿命、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:v1

version=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

1
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"]
2
docker run --rm --network none -e APP_MODE=prod lbox-cl10:v1
version=one mode=prod
3
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
4
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
5
docker stop lbox-cl10-c1 docker start lbox-cl10-c1 docker exec lbox-cl10-c1 cat /tmp/session.txt /data/state.txt
lbox-cl10-c1
lbox-cl10-c1
session=local
counter=1
6
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
7
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
8
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-web
HTTP ready on 127.0.0.1:44810
8080/tcp -> 127.0.0.1:44810
9
curl -sS http://127.0.0.1:44810/
container web

演習完了