Docker に関する面白い事柄と備忘録。
コンテナからホストマシンの IP アドレスを参照する #
MacOS ホスト + Docker Desktop の場合
docker.for.mac.host.internalという名前は、MacOS にインストールされた仮想 NIC の IP アドレスに自動的に解決され、設定は不要です。もちろん VM 層(https://github.com/linuxkit/linuxkit/blob/master/examples/docker-for-mac.md)は依然として存在しますが、少なくとも実際の TCP を配送する IPv4 ルーティングは機能します。Linux ホスト + Docker engine の場合
docker runおよびcreateコマンドは、--add-host=name:<ip>フラグを受け取り、コンテナの/etc/hostsに<ip> nameという行を追加します。<ip>には特殊なリテラル値host-gatewayを指定でき、ホストの Linux カーネルにある仮想 NIC の IP アドレスを参照できます(Docker engine が設定します)。最終的には、172.0.0.1 nameのような行が得られます。
コンテナ内の GUI からホストの X11 サーバーソケットに接続する例:
docker run --name gugu -it --entrypoint bash --user jose --add-host=hostgg:host-gateway -v $AA:$AA -v $BB:$CC -e DISPLAY=:0 -e XAUTHORITY=$CC kk
# in the container
getent hosts hostgg
172.17.0.1 hostggdocker compose の yaml でコンポーネントを再利用する #
docker compose の yaml ファイルで DRY 原則を実践する方法は 2 つあります。
extendsキーワードを使用する- docker compose yaml で再利用可能な設定部分に YAML のアンカー/マージ構文を使用する
networks:
gugunet:
driver: bridge
enable_ipv6: false
ipam:
driver: default
config:
- subnet: 99.99.99.0/24
gateway: 99.99.99.1
x-hdfs-commons: &hdfs-commons
# hadoop launcher scripts requires bash, don't use alpine.
image: ubuntu:latest
volumes:
- ../rofiles/zulu17.58.21-ca-jdk17.0.15-linux_aarch64:/opt/jdk:ro
- ../rofiles/hadoop-3.4.1:/opt/hadoop:ro
- ./core-site.xml:/opt/hadoop/etc/hadoop/core-site.xml:ro
# - ./hdfs-site.xml:/opt/hadoop/etc/hadoop/hdfs-site.xml:ro
- ./hadoop-logs:/tmp/hadoop-logs
environment:
- JAVA_HOME=/opt/jdk
- PATH=$PATH:/opt/jdk/bin:/opt/hadoop/bin
- HADOOP_LOG_DIR=/tmp/hadoop-logs
services:
hdfs-name-node:
<<: *hdfs-commons
networks:
gugunet:
ipv4_address: 99.99.99.100
command: bash -c "hdfs namenode -format && hdfs --daemon start namenode && sleep infinity"
hdfs-data-node-1:
<<: *hdfs-commons
command: bash -c "hdfs --daemon start datanode && sleep infinity"
networks:
gugunet:
ipv4_address: 99.99.99.101Dockerfile の変数置換規則 #
- Dockerfile は
$VARと${VAR}の両方の構文を使用します。 VARは、以前のENV(自身またはベースイメージの Dockerfile で定義されたもの)またはARGでなければなりません。- シェル変数を参照することはできません。ゲストのシェルからもホストのシェルからも不可能です(設計上の分離によるものです)。
$PATH を実際に参照できる場合があるのは、ゲストのシェルの init rc が評価されるからではなく、ベースイメージの Dockerfile に ENV PATH が記述されているためです。
ゲストのシェルでのシェル評価を必要とする動的な処理は実行できません。つまり、環境変数の値をゲスト内のコマンドで計算する必要がある場合、ENV では対応できません。以下を参照してください。
環境変数の設定 #
原則:
- 「Dockerfile の変数置換規則」を読み、それで問題なければ (1) と (2) を使用します。
- それでは不十分で、エントリポイントプロセスとその子プロセスから環境変数を参照できるようにしたい場合は、(3) と (4) に進みます。
- それでは不十分で、Docker コンテナを永続的に実行し、
docker execで接続して操作したい場合は、(5) を使用します。
Dockerfile の
ENVコマンドこの方法では、コンテナの一部として実行される任意のプロセスを fork-exec する際に、
containerdが指定された環境変数を追加します。そのため、エントリポイントプロセスと実行中のコンテナに対するdocker execの両方で機能し、シェルやシェル rc とは無関係です。ENVがサポートする変数置換はARGと他のENVのみです。そのため$PATHは機能しません(その他のよく知られた Unix 環境変数も同様です)。docker run -eまたはcompose.yamlの environment仕組みは Dockerfile の
ENVコマンドと同じです。非シェルのエントリポイントは環境変数を参照でき、非シェルのexecでも同様です。ただし、次の点が異なります。- 使い捨てコンテナと、後からそのコンテナに対して実行するすべての
docker execに有効です。 - すべての展開はホストのシェルで行われます。ホストのシェル変数以外の変数は使用できません。
- 使い捨てコンテナと、後からそのコンテナに対して実行するすべての
Dockerfile の
RUNコマンドでシェル rc を変更する
長所:
- 実際のゲストマシンのシェル環境にいるため、環境変数の値をプロセスの出力にできます。たとえば rc に
export foo="$(...)"の行を追加できます。これは 1 と 2 のENVではできません。(例はこちら)。 docker execコマンドとコンテナのエントリポイントが両方ともシェルであれば、同じ環境変数を参照できます。
短所:
- 新しいイメージをビルドする必要があります。
- エントリポイントまたは exec コマンドがシェルでない場合は機能しません。
ほとんどの Linux ディストリビューションの Docker イメージは非ログイン bash を使用します。profile ではなく、適切なシステムレベルまたはユーザーレベルの rc を変更してください。
- エントリポイントの init スクリプトで
exportする
これは 3 の機能を弱めたものです。export された環境変数を参照できるのは、その init スクリプトの子プロセスだけであり、docker exec からは何も見えません。
ただし、変数の可視性を制限することは常に良い設計です。
docker exec -it ... script.sh用に用意した独立スクリプトでexportするこの構成が意味を持つのは、エントリポイントが
sleep infinityのみを実行し、分離された環境でシェルコマンドを入力するためにdocker execでコンテナへ接続する場合です。init スクリプトのすべての子プロセスは環境変数を参照できません(自分で source した場合を除きます)が、sleep infinityには適しています。
Docker ボリュームに関する誤解 #
Docker ボリュームは dockerd が管理する永続ストレージです。
- どのコンテナを終了または削除してもボリュームは削除されません。両者は独立しています。
- コンテナ作成時には、任意のボリュームを任意のコンテナにアタッチできます。ただし設計上、コンテナ作成後にボリュームを追加または削除することはできません。
ボリュームに関する混乱の多くは、docker run|create コマンドの -v フラグに由来します。実際、これは非常に扱いにくい設計です。構文は -v [LHS:]RHS です。
LHS:を省略すると、このフラグは匿名ボリュームを作成し、コンテナ内のパスRHSにマウントします。- それ以外で、
LHSが/または./で始まる文字列の場合、docker runコマンドを入力した時点のホストマシン上の絶対パスまたは PWD からの相対パスとして解釈され、そのディレクトリまたはファイルがコンテナ内のRHSパスにバインドマウントされます。LHSがホスト上に存在しないパスに解決される場合、空のディレクトリが作成されます。これは新しい Docker ボリュームを作成しません。 - それ以外で、
LHSが既存の Docker ボリュームの名前(docker volume lsの結果として確認できるもの)に一致する場合、そのボリュームがコンテナ内のRHSにマウントされます。 - それ以外の場合、新しい Docker ボリュームが作成され、
LHSという名前が付けられた後、コンテナ内のRHSにマウントされます。
新しいボリュームを作成するのは 1 と 4 のみです。3 は既存のボリュームを再利用し、2 は Docker ボリュームとは無関係です。それでも、これらはすべて -v フラグで実行されます。より明示的な指定になるため、現代の Docker では -v を -mount type=..., フラグに置き換えることが推奨されます。
以下はボリュームではなく、ボリュームを作成しません。
LHSが/または./で始まる場合のdocker run -v LHS:RHSdocker run --mount type=bind,...- 新しいコンテナを作成すると、Docker 内部に書き込みレイヤーが作成されますが、これはボリュームではありません。ホストマシン上の場所は
docker inspect <container>で確認でき、Linux では通常/var/lib/docker/overlay2/配下にあります。このストレージはボリュームではなく、Docker の実装詳細です。
以下はボリュームに関係します。
- 明示的に新しいボリュームを作成する:
docker volume create LHSが/または./で始まらない場合、またはLHS:を省略した場合のdocker run -v LHS:RHSdocker run --mount type=volume,src=...,dest=...
/tmp の tmpfs
#
Docker はデフォルトでは、コンテナ内の /tmp にメモリ上の tmpfs を割り当てません。必要な場合は明示的に指定してください。
- compose ファイルの場合は、サービス下の tmpfs キーワードを使用します。
- docker run / create コマンドの場合は、繰り返し指定可能な
--tmpfs /MOUNTING/POINTフラグを使用します。
読み取り専用コンテナ rootfs #
rootfs が読み取り専用のコンテナを作成するには、次のいずれかを使用します。
docker run / createコマンドの--read-onlyフラグ- compose ファイルのサービスにおける
read_only: true
特定のソフトウェアの挙動を慎重に観察したい場合や、コンテナがリモート SSH を提供する場合にユーザーを制限したい場合に役立ちます。
これは rootfs のみを読み取り専用にします。/proc 上の tmpfs や /sys 上の sysfs など、他のマウント済みファイルシステムは通常どおり読み書き可能です。カーネルの動作には変更可能である必要があるため、これは理にかなっています。ただし、これで十分な場合が多いでしょう。特に /tmp も読み取り専用になります。書き込み可能な /tmp が必要な場合は、明示的に /tmp に tmpfs をマウントしてください。
--read-only コンテナ内で特定のパスを読み書き可能にしたい場合は、自分で Docker ボリュームをマウントしてください。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)