はじめに
#1では、肥大化するDockerのストレージの対策としてSSDの増設と/var/lib/docker
ディレクトリからの移行を紹介しました。
今回は4TBのSSDを増設しましたが、容量は決して無限ではありません。特にML系のイメージはサイズが大きく、作成したDockerオブジェクト(イメージ、コンテナ、ボリューム、etc.)を放置すると数年後には同じ運命を辿ってしまいます。
本記事では、持続可能*1なDockerオブジェクトの管理を目的として、管理者ラベルの自動付与スクリプトの設定を紹介します。
1. 要件定義
目的は、研究室サーバー上で作成されたDockerイメージ・コンテナの管理者を明確にすることです。
そもそも、#1でDockerのストレージ容量がひっ迫してしまった原因は主に以下の2点だと考えられます。
- 単純に使用済みのイメージ・コンテナを消し忘れる
- 自分が作成したイメージ・コンテナかどうか怖くて削除できない
1.については、人間なので仕方ないと思います。今は使わないイメージ・コンテナがあっても、近いうちに利用する可能性があれば消さずに取っておきたいものですし、研究に集中しすぎると研究以外のことはなおざりになってしまいがちです。
問題があると考えたのは、2.です。研究室のNotionには、以前からDockerイメージ・コンテナ作成時のテンプレートのようなものが紹介されていました。何割かの学生はテンプレートに沿って自身のユーザー名をコンテナ名に含めていましたが、一方でイメージの管理者が不明であったり、ルールが十分に浸透していなかったりという不完全さがありました。
手っ取り早い対策として、Notion上のドキュメントを完全なものにする(e.g., イメージ名とコンテナ名にユーザー名を含めるように明記する)ということも考えられますが、ターミナルとNotionを頻繁に行き来するのは依然として面倒、かつ認知負荷が高いままです。
また、ルールを策定したとしても浸透率が100%になることはないですし、ルールに違反するイメージ・コンテナを監視してSlackに通知する方法はアラート疲れの原因となりそうです。
多くのアイデアを比較検討した結果、2章で紹介するラベルをメタデータとして、3章で紹介するエイリアスによって自動的に付与する方法を採用しました。4章では、それでもカバーできないケースの対応を行っています。
2. Dockerオブジェクトのラベル
ラベルは、 Dockerオブジェクトに対してメタデータを設定する仕組みです。
ラベルは名前やタグと違って、そのオブジェクトが存在する間は不変なので、意図せず上書きされることも無さそうです。
以下はコンテナにラベルを付与する例です。
$ docker run -d --name busybox-container --label "foo=bar" busybox sleep 3600 5e19a20ba247ccaee2c6bbf18a853b3cc0717650f8b764376a25754aa09ae7b1 $ docker inspect busybox-container | jq '.[0].Config.Labels' { "foo": "bar" } $ docker ps --filter "label=foo=bar" CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5e19a20ba247 busybox "sleep 3600" About a minute ago Up About a minute busybox-container
イメージについては、docker build
コマンドの--label
オプションだけでなく、Dockerfileに以下のように記述することでラベルを付与することもできます。
LABEL foo=bar
フィルタリング機能を利用すれば、CLI上で特定のユーザーが作成したイメージ・コンテナを手軽に確認できるだけでなく、Docker Engine SDKsを利用してより複雑な条件で絞り込むこともできそうです。
3. ラベル自動付与のスクリプト
ラベルを付与すると、簡単にフィルタリングできて便利だということが分かりました。しかし、先述したラベルの付与方法をドキュメント(e.g., Notion)に記載し、全員に意識してもらうのはベストプラクティスとは程遠いと考えました。
ChatGPTとの長きにわたる壁打ちの結果、以下のようなスクリプトでdocker
コマンドのラッパーを作成することにしました。
#!/bin/bash USER_NAME=$(whoami) if [[ $1 == "build" ]]; then shift exec docker build --label "maintainer.xxxlab=$USER_NAME" "$@" elif [[ $1 == "run" ]]; then shift exec docker run --label "maintainer.xxxlab=$USER_NAME" "$@" else exec docker "$@" fi
maintainer
キーは単純で重複する可能性が高いので、maintainer.xxxlab
をキーとしました。名前の衝突を避けるため、一般的には接頭辞に逆DNS表記が利用される*2そうです。
今回は、イメージ作成時のdocker build
コマンドとコンテナ作成・実行時のdocker run
コマンドを対象として--label
オプションを追加しています。
引数を1つずらすshift
コマンドや、"$1" "$2" …
と等価である"$@"
は使用したことがなかったので勉強になりました。
先述のスクリプトを/usr/local/bin/docker-wrapper
として保存し、実行権限を付与します。それから、ユーザー全体に適用されるようにスタートアップファイル*3として追加します。
$ chmod +x /usr/local/bin/docker-wrapper $ echo "alias docker=/usr/local/bin/docker-wrapper" > /etc/profile.d/docker_alias.sh $ exit
再度シェルにログインして、明示的にラベルは付与せずにDockerイメージ・コンテナを1つずつ作成してみます。
$ alias alias docker='/usr/local/bin/docker-wrapper' # (省略) $ docker build -t my-ubuntu - <<EOF FROM ubuntu:latest RUN apt-get update && apt-get install -y curl CMD ["bash"] EOF $ docker run -d --name busybox-container --label "foo=bar" busybox sleep 3600
次に、ユーザー名でフィルタリングを行います。
$ docker images --filter "label=maintainer.xxxlab=azuma" REPOSITORY TAG IMAGE ID CREATED SIZE my-ubuntu latest f538b2998179 3 minutes ago 125MB $ docker ps --filter "label=maintainer.xxxlab=azuma" CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e330e1ee116c busybox "sleep 3600" About a minute ago Up About a minute busybox-container
無事にユーザー名のラベルを自動付与することができました。
4. ラベルが自動付与されないケースの対処
先述したdocker
コマンドラッパーを数週間ほどテストしてみましたが、どうやら指定ラベルが付与されていないイメージ・コンテナが作成されているようでした。
原因を調査したところ、そのようなオブジェクトはDocker Composeを利用して作成されていました。完全に考慮し忘れていました。
また、一般的にエイリアスはシェルスクリプトのような非インタラクティブシェルで展開されません。つまり、シェルスクリプトを利用してdocker
コマンドを実行しているユーザーは自動ラベル付与の恩恵を受けられないということです。man bash
の中に以下のような一文があります。
Aliases are not expanded when the shell is not interactive, unless the expand_aliases shell option is set using shopt (see the description of shopt under SHELL BUILTIN COMMANDS below).
スクリプトにshopt -s expand_aliases
を追加するか、bash -o expand_aliases
とオプション実行することで非インタラクティブモードでも無理矢理エイリアスを展開することができます。さらに、bash
コマンドに-l
オプションを付けることで、Bashをログインシェルとして起動したかのように動作させる(/etc/profile
を読み込ませる)ことができます。
makeでdocker
コマンドを実行しているユーザーの場合は、さらにMakefileにSHELL=/bin/bash -l
を追記してシェルを指定しなければいけません。
これは完全にオーバーエンジニアリングであり、避けるべきです。
docker
コマンドをインタラクティブシェルで実行しているユーザーには自動ラベル付与を提供し、それ以外のユーザーにはシンプルに以下の一行をDockerfileに追加してもらうことにしました。
LABEL maintainer.xxxlab=<Linuxユーザー名>
結局2章の方法に回帰してしまい悔しいですが、全てのケースに対応するのは想定よりも遥かに難しいタスクだったため断念しました。
まとめ
持続可能なDockerオブジェクトの管理を目標に掲げ、(一般的なdocker
コマンドのユーザーには)ユーザー名を値とした管理者ラベルを自動で付与することができました。
完全なツールを作成することはできませんでしたが、オーバーエンジニアリングにならない範囲でDockerオブジェクトの運用方針を周知していこうと思います。
Bashをはじめとするシェルは非常に奥が深く、勉強になりました。
*1:持続可能なエンジニアリングの象徴として、各記事の冒頭には見覚えのある雰囲気のアイコンが掲載されています