コンテナ入門
大学の共有サーバーで機械学習させるときにsingularityを使うことを推奨していたので、導入がスムーズになるように研究室向けに書きました。
コンテナの導入と使い方、 docker/podman のイメージ作成、singularity のイメージ作成と docker/podman のコンテナから singularity で使える形式への変換について記載します。
singularity のコンテナファイルをローカルでビルドするために docker/podman を使用するので、docker/podman についての解説もしています。
なお、注釈は完全な余談です。
singularity Quick Start
- CLI 操作のみ記述 (GUI を言葉で説明するのは難しい...)
-
windows でコンテナを使えるようにする
→ singularity 向けコンテナイメージのビルド環境の構築
→ singularity の操作
の順に読むと singularity についてはすぐに使えます
# 対話シェルの起動: --nvオプションでGPUの使用
singularity shell --containall --nv <name>.sif
# コンテナでコマンドを実行: --nvオプションでGPUの使用
singularity exec --containall --nv <name>.sif <cmd>
# ホストファイル・ディレクトリのマウント (パスは絶対パス)
singularity shell --containall -B /path/to/file/or/dir <name>.sif
# ,で繋げると複数マウント
singularity shell --containall -B /path/to/file/or/dir,/path/to/file/or/dir <name>.sif
# マウントする場所を指定する (read onlyにする場合、末尾に:roを追加)
singularity shell --containall -B /path/to/host:/path/to/container,/example:/mnt:ro <name>.sif
docker/podman Quick Start
- CLI 操作のみ記述 (GUI を言葉で説明するのは難しい...)
- ローカルマシンで開発や検証をしたい人向け
- docker を使っているなら
podman
をdocker
に読み替えてください
# コンテナイメージのpull (<username> = <image_name>のときはどちらか一方だけでいい)
podman pull (<remote>/)<username>/<image_name>:<tag>
# ex) podman pull docker.io/ubuntu/ubuntu:22.04
# コンテナのバックグラウンド起動
podman run -id --name <name> <username>:<tag>
# コンテナにホストのファイルやディレクトリをマウントしてバックグラウンド起動
podman run -id -v /absolute/path/to/host:/absolute/path/to/container --name <name> <username>/<image_name>:<tag>
# コンテナでシェルの起動
podman exec -it <name> bash
# コンテナの終了
podman stop <name>
# 起動コンテナの確認
podman ps
# 作成した全てのコンテナの確認
podman ps -a
# コンテナの破棄
podman rm <name>
# コンテナイメージの破棄
podman rmi <username>/<image_name>:<tag>
用語など
- 説明にあるコマンドと実行結果の見方
# コメントアウト
<cmd> # 実行コマンド ($を含めないで実行する)
> foobar # 実行結果
- Linux ディストリビューション
Linux は思想の違いで様々な形で配布されています。
特にパッケージ管理の仕方が大きく違います。
ubuntu・debian で使われているapt
について知っておけばとりあえず大丈夫
コンテナとは
A container is a standard unit of software that packages up code and
all its dependencies so the application runs quickly and
reliably from one computing environment to another.
(https://www.docker.com/resources/what-container/ より)
あるコードとそれを動かすための依存関係をパッケージ化して、どのマシン環境でも関係無く動かすための技術です。
現在主流のコンテナ1は、基本的には 1 サービス 1 コンテナで動かします。
使う分には実験(使う機械学習モデル)ごとにホストと隔離された root が使える環境を作ってその中で作業するんだなくらいで大丈夫です。
大分余談: docker/podmanとsingularity
docker は Docker Inc が、podman は Redhat が開発しているコンテナです。
どちらもコンテナの業界標準である OCI (Open Container Initiative)に準拠しています。
細かい仕様の違い2はありますが、コマンドとオプションは高い互換性があります。
singularity は HPC (High Perfomance Computer)上で実行することを想定し開発されたコンテナです。
環境をファイルに出力し、より可搬性の高いコンテナ環境を提供します。
また、コンテナ内外で同じユーザーで実行されます。 (docker/podman は設定しなければコンテナ内では root ユーザー)
OCI とは互換性がないため docker/podman で作成した環境を直接 singularity で使うことはできません。
singularity のセットアップ
Ubuntu on WSL2にsingularityをインストールします。
WSL2のセットアップ
-
コマンドラインから
- 管理者権限で
powershell
もしくはwindows terminal
を開きwsl --install
を実行 - 一般ユーザーで
powershell
もしくはwindows terminal
を開き直してwsl --install Ubuntu
を実行 -
wsl -d Ubuntu
で起動 (2. の時点でユーザーの登録を求められたらここはスキップ) - 初回セットアップを行う (ユーザーの登録とパスワードの設定)
- 管理者権限で
-
windows storeから
- windows storeを開き
wsl
で検索 -
Windows Subsystem for Linux
をインストール -
ubuntu
で検索 -
Ubuntu 22.04 LTS
をインストール後、Openで起動 (ターミナルまたはpowershellが開く) - 初回セットアップを行う (ユーザーの登録とパスワードの設定)
- windows storeを開き
singularityのインストール
-
依存パッケージのインストール
sudo apt update sudo apt install -y libfuse2 uidmap squashfs-tools-ng
-
singularityのインストール
- 導入したubuntuのバージョンが22.04であることが前提
- 異なるバージョンはjammyを適切なものに変更
VERSION=4.0.1 wget https://github.com/sylabs/singularity/releases/download/v$VERSION/singularity-ce_$VERSION-jammy_amd64.deb sudo dpkg -i ./singularity-ce_$VERSION-jammy_amd64.deb rm ./singularity-ce_$VERSION-jammy_amd64.deb
作業ディレクトリの作成
- 作業ディレクトリの下に`containersというディレクトリを作成
-
Dockerfile
とrecipe.def
はこの後で作成
workdir
└── recipe.def
定義ファイル(recipe.def
)の作成
- pytorch と tensorflow に加え
git
とwget
が使えるコンテナを作成する
BootStrap: docker
From: pytorch/pytorch:2.0.0-cuda11.7-cudnn8-devel
%setup
%files
%environment
export DEBIAN_FRONTEND=noninteractive
export LANG=ja_JP.UTF-8
%post
apt update
apt install -y \
git \
wget \
locales
conda install \
pip
pip install \
tensorboard
定義ファイルの詳細
必須
- Bootstrap
- ベースイメージが存在する外部リポジトリを定義、基本的には docker 、他は公式の Docs 参照
- From
- 使用するイメージ。docker であればDockerHubから探す
- %post
- 必要なパッケージのインストールや git リポジトリのクローンなど
- シェルスクリプトを書くのと同様に
良く使う
- %setup
- コンテナ外つまりホスト上で行なう操作を記述
- シェルスクリプトを書くのと同様に
- 非推奨
- %files
- ホストに存在するファイルをコンテナにコピーする
- GitHub にない自作スクリプトやデータセットをコンテナで使うときに使用
-
<source> <target>
のように記述: source=ホストのデータへのパス、target=コンテナ内のパス、Linux のディレクトリ構造と同じ- Ex. README.md /workspace/README.md
- ビルドを実行したディレクトリからの相対パスで記述可能
- %environment
- 環境変数の定義
-
export foo=var
のように記述
使わなくても困らない
- %test
- ファイルが適切にクローンやコピーされたかなどのテストプログラムを定義
- シェルスクリプトを書くのと同様に
- %startscript
- ビルド時のみに実行されるスクリプトの定義
- %runscript
- コンテナを呼び出した時に必ず実行されるスクリプトの定義
- jupyter の起動などに
- %labels
- メタデータの定義
-
name value
で定義
環境のビルドと確認
- 環境のビルド
singularity build --fakeroot sample.sif recipe.def
- 環境の確認
singularity run sample.sif
docker/podmanイメージからのビルド
docker/podmanで作成したイメージをsingularityで使える形式へビルドします。
これは、singularityとdocker/podmanが同一のインスタンス(ここではUbuntu-22.04 on WSL2)で実行されていることが前提です。
-
dockerの場合
singularity build --fakeroot test.sif docker-daemon://<name>(/<imagename>:<tag>)
-
podmanの場合
- rootlessで動作している場合:
--docker-host unix:///var/run/user/<user_id>/podman/podman.sock
- rootfullで動作している場合:
sudo
を付けて--docker-host unix:///var/run/podman/podman.sock
singularity build --docker-host unix:///var/run/<path/to/each/user>/podman/podman.sock --fakeroot test.sif docker-daemon://<name>(/<imagename>:<tag>)
- rootlessで動作している場合:
windows でコンテナを使えるようにする
ここでは podman-desktop3を使って windows マシンでコンテナを使えるようにします。
共用のマシンがあると思いますが、以下の点から導入することをオススメします。
- 共用マシンには root 権限が付与されていないが、自分のマシンでは root 権限を持てる。
- 手元のマシンだと環境の構築とテストがしやすい。
- 共用マシンの OS のバージョンに左右されずに作業ができる。
- Linux の勉強がしやすい。
導入手順
windows 10 version 2004 以上が必要です。
-
podman-desktop のダウンロードページからダウンロード
- winget を使ってるなら
winget install -e --id RedHat.Podman-Desktop
- winget を使ってるなら
-
podman-desktop を起動
- WSL2の設定など必要なコンポーネントが無いときは
podman-desktop
の指示に沿って設定すればいい
- WSL2の設定など必要なコンポーネントが無いときは
-
windows-terminal を起動 (スタートメニューで
terminal
と入力すると出てくる)- windows のバージョンによってはインストールされていないことがあります。
その時は powershell を起動してください。
- windows のバージョンによってはインストールされていないことがあります。
-
podman -v
と入力してバージョンが出てくれば OK
docker/podman の操作
singularityはコンテナイメージを固定するために柔軟な変更が困難です。
そのため、docker/podmanを使って環境をテストしてからsingularityで固定すると良いでしょう。
まだ、
コンテナイメージ の pull
コンテナを使うには、コンテナイメージを持ってくる必要があります。
今回は ubuntu 22.04 を使用します。
別のイメージを使う場合はubuntu:22.04
を置き換えます。
-
podman-desktop
-
CLI
-
podman pull ubuntu/ubuntu:22.04
と入力 - 選択肢が出てきたら
docker.io
を選択
-
解説
ubuntu:22.04
を見るとname/image_name:tag
の形式になっています。
name は image_name を管理している人や団体、image_name は使いたいソフトや Linux ディストリビューションの名前、tag でバージョンを指定します。
image はDockerHubから検索します。
tag は指定しないことも可能です。
その場合、最新のバージョンが pull されます。
実験環境を再現可能なものにするため tag の利用を強く勧めます。
コンテナの起動
Container Name は任意の文字列です。
指定しないと適当な文字列が割り振られます。
-
podman-desktop
-
CLI
podman run -id --name HelloWorld ubuntu:22.04
-
podman exec -it HelloWorld bash
root@<hash>:/#
という表示が出れば OK
ホストのファイルやディレクトリをマウントしての起動
ホストのファイルやディレクトリは起動時にオプションで指定することでコンテナにマウントしてコンテナ内から使用できるようにします。
データセットはコンテナに含めることによるコンテナの肥大化を防ぐため、またモデルの学習結果をホストから利用するためにマウントを利用します。
-
podman-desktop
-
CLI
-
-v, --volume
オプションをpodman run
に追加する ex)podman run -id -v /home/user/study/exp1:/tmp/exp1 --name HelloWorld ubuntu:22.04
-
解説
デスクトップからの操作はそのままなので割愛します。
podman run
でコンテナを起動します。
コンテナイメージで常駐するようなアプリ(web サーバーなど)が指定されていない場合、起動したコンテナはすぐにストップします。
これを防ぐためにオプション-i, --interactive
で起動状態を維持します。
また、バックグラウンドで起動させるためにオプション-d, --detach
を指定しています。
起動したコンテナ環境に入るにはpodman exec
を使います。
podman exec
ではコマンドが実行されるとコンテナから弾き出されます。
今回は bash を起動してそのままコンテナに留まりたいので、podman run
の時と同じようにオプション-i, --interactive
を指定して維持しています。
また、-t, --tty
を指定してシェルで対話できるようにしています。
その他のオプションはpodman run --help
、podman exec --help
で出力できます。
大体は
podman run -id --name <name> <image>:<tag>
でコンテナを起動、
podman exec -it <name> bash
でコンテナ環境に入るので事足りると思います。
また、podman run --rm -it <image>:<tag> bash
使い捨ての環境を作成できます。(オプション--rm
は remove)
これはコンテナの bash から Exit した時に起動したコンテナも破棄します。
コンテナの確認
-
podman-desktop
-
CUI
コンテナの停止と破棄、コンテナイメージの破棄
実験が一通り終わり次の実験に移るときはコンテナを破棄することが良いでしょう。
コンテナは起動するほどディスク容量を消費します。
- podman-desktop
- CLI
-
podman stop HelloWorld
でコンテナを停止 -
podman rm HelloWorld
でコンテナを破棄 podman rmi ubuntu:22.04
-
コンテナへパッケージを追加
image の pullとコンテナの起動を元に ubuntu:22.04 のコンテナで bash が実行されていることを前提に進めます。
始めてコンテナに入るとless
やunzip
などのコマンドが無いことに気付くでしょう。(less -h
やunzip -h
を実行してみてください。)
コンテナは必要最小限の環境であり、パッケージを追加することで実験環境を構築します。
ここではpytorch
やtensorflow
のベースイメージで使われるapt
についてのみ説明します。
なお、文献によってはapt-get
と書かれていることがありますが、直近の ubuntu ではapt
に変更されました。
リモートリポジトリのアップデート
apt update
パッケージのインストール
-y
を付けるとパッケージをインストールするかの確認がスキップされる
apt install -y less
# 複数パッケージを一度に追加 (末尾に\で繋ぐ)
apt install -y \
git \
python-is-python3
キャッシュの削除
apt clean
パッケージの検索
あいまい検索でヒットしたものが列挙される。
あまり使わない。
web で docs に書いてある方法や先人のやっているインストールパッケージに従った方が早い。
apt search libsound
> Sorting... Done
> Full Text Search... Done
> libsoundio-dev/jammy 2.0.0-2 amd64
> cross platform audio input and output library (development files)
>
> libsoundio2/jammy 2.0.0-2 amd64
> cross-platform audio input and output library
> ...
ppa の追加
ubuntu や debian のリモートリポジトリには古くて安定しているバージョンが載せられている。
最新バージョンを使いたい場合、そのパッケージが独自にリモートリポジトリを持っていてそこから利用できることがある。
# neovimの最新バージョンをインストールする
# ppa追加の準備
apt install -y software-properties-common
# ppaの追加
add-apt-repository ppa:neovim-ppa/stable
apt update
apt install -y neovim
コンテナの保存
実行中のコンテナからコンテナイメージを作成します。
podman commit <container-hash or name> <name>(/<imagename>:<tag>)
Dockerfile を使ったコンテナイメージの作成
ここまでで CLI から環境を作ることができます。
しかし、コミットを忘れるとその環境は全て破棄され再現できません。
再現可能な環境を作るためにはDockerfile
を用いて環境を定義することが望ましいです。
ここではpython
とconda
が使える環境を作りながら説明します。
下のようなディレクトリ構造にします。
example
├── Dockerfile
└── requirement.txt
Dockerfile の作成
適当なエディタで下のファイルを作成
pip
とconda
が混在していますが、説明用ということで...。
FROM docker.io/ubuntu/ubuntu:22.04
RUN apt update && \
apt install -y \
wget && \
apt clean
RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh \
bash Miniconda3-latest-Linux-x86_64.sh -b -p /opt/miniconda3 && \
rm -r Miniconda3-latest-Linux-x86_64.sh
ENV PATH /opt/miniconda3/bin:$PATH
WORKDIR /workdir
COPY requirement.txt requirement.txt
RUN conda update -n base -c defaults conda && \
pip install -r requirement.txt && \
conda install -c conda-forge \
matplotlib
CMD ["/usr/bin/env", "bash"]
解説
-
FROM
: コンテナのベースイメージを指定。
podman pull
と同じように<remote>/<username>/<image_name>:<tag>
で指定。 -
RUN
: コマンドを実行。
&&
で複数コマンドを繋げて列挙していく。
\
で改行を解釈させる。(複数行にして人に見やすいようにしている。)apt update && \ # && はコマンドの実行が成功したら次のコマンドを実行するという意味 apt install -y \ # 改行。apt install -y wgetと同義で解釈される。 wget
-
ENV
: 環境変数を定義。
ENV 変数名 値
-
WORKDIR
: 指定したパスに移動する。存在しない場合は作成する。
新しくWORKDIR
が指定されるまでそのパスの元でコマンドが実行される。 -
COPY
: ホストのファイルをコンテナにコピーする。
COPY path/to/host path/to/container
-
CMD
:podman run
で実行されるコマンド
podman run <image_name> <cmd>
で上書き可能
上記を知っていれば大体は対応できます。
対応できないときはリファレンスの参照や都度知らべてください。(特に何をベースイメージにするかなど)
コンテナイメージのビルド
- podman-desktop
- CLI
-
podman build -t example/python:22.04
.
-
解説
-t, --tag
でコンテナイメージに名前を付けます。
Dockerfile が存在するディレクトリを指定して実行します。
コンテナの起動は先に解説した通りです。
Dockerfile 作成の Tips
コンテナイメージのサイズを小さくするために、RUN
をできるだけ少なくすることが推奨されます。
コードの視認性が落ちること、デバッグがしにくくなるので慣れてから意識するといいでしょう。
例えば、先のDockerfile
は下のように書き直されます。
FROM docker.io/ubuntu/ubuntu:22.04
ENV PATH /opt/miniconda3/bin:$PATH
WORKDIR /workdir
COPY requirement.txt requirement.txt
RUN apt update && \
apt install -y \
wget && \
apt clean && \
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && \
bash Miniconda3-latest-Linux-x86_64.sh -b -p /opt/miniconda3 && \
rm -r Miniconda3-latest-Linux-x86_64.sh && \
conda update -n base -c defaults conda && \
pip install -r requirement.txt && \
conda install -c conda-forge \
matplotlib
CMD ["/usr/bin/env", "bash"]
余談: Linux の操作
コンテナは何かしらの Linux ディストリビューションなので操作は UNIX コマンドで行います。
pwd, env, echo, ls, find, mkdir, cp, ln, chmod, chown, grep, egrep, xargs
とパイプ・リダイレクトについては覚えておいて損はないでしょう。
下に簡単な例と実行結果をまとめました。
image の pullとコンテナの起動を元に ubuntu:22.04 のコンテナで bash が実行されていることを前提に進めます。
ディレクトリの作成
# mkdir -p <path/to/dir>
mkdir workdir # workdirを作成
mkdir -p workdir/foo/bar # 親ディレクトリが無い時、親ディレクトリも作成する
current dir の移動
# cd <dir name>
cd workdir
空ファイルの作成
# touch <file name>
touch foo.txt
touch .hidden.txt # .から始めると隠れファイル(ディレクトリ)となってls では表示されない
ファイル・ディレクトリの列挙
ls # 列挙
ls -a # .から始まる隠れファイル(ディレクトリ)も列挙
シンボリックリンクの作成
シンボリックリンクは別のパスにあるファイルやディレクトリを参照します。
散けているファイルを容量を圧迫せずに別のディレクトリにまとめることができます。
ln -sf foo.txt foobar.txt
# 参照元が→で表わされている
ls -l
> lrwxrwxrwx 1 root root 7 Jul 4 05:47 foobar.txt -> foo.txt
ファイル・ディレクトリの検索
find ./ -name "foo.txt" # "foo.txt"を配下のディレクトリ全てから検索
find ./ -name "*.txt" # 拡張子が ".txt"であるファイルを配下のディレクトリ全てから検索
find ./ -name "foo.txt" -maxdepth 2 # "foo.txt"を配下のディレクトリのうち深さ2までで検索
find ./ -type f # 配下のディレクトリにある全てのファイルを検索
find ./ -type d # 配下のディレクトリにある全てのディレクトリを検索
パイプとリダイレクト
Linux ではパイプやリダイレクトを使うことでコマンドの実行結果を他のコマンドに与えることができます。
複数ファイルに対して同じ操作を行なうときに便利です。
- リダイレクト
# >がリダイレクト (echoの結果をbar.txtに出力)
echo "Hello World !" > bar.txt
cat bar.txt # bar.txtの内容を表示
> Hello World !
# bar.txtに追記
echo "This is container tutorial." >> bar.txt
# bar.txtを新しい出力で置き換える
echo "Replace !" >| bar.txt
# EOFが再度出現するまでの文字列をsample.shに出力
# 簡単なスクリプトならエディタが無くても書ける
cat <<EOF > sample.sh
echo $FOO
FOO="Hello World"
echo $FOO
>>EOF
bash sample.sh
> BAR
> Hello World
- パイプ
# catの出力をgrepに渡している
# grep <語>で語にヒットする行を出力
cat sample.sh | grep Hello
> FOO="Hello World"
特定の文字列を含むファイルの検索
例えば、numpy
を import しているファイルを知りたいというような状況で役立ちます。
egrep -r echo ./ # 配下のファイル全てに対してechoを含むファイルを検索
文字列の置換
sed
を用います。
正規表現を使って文字列の置換をします。
文字列の挿入や特定の行に対しての置換もできます。
# s/置換前/置換後/g (置換前の語にヒットした全てに対して置換する)
cat sample.sh | sed -e 's/Hello/New/g'
echo $FOO
FOO="New World"
echo $FOO
# 置換して上書き (-i オプション)
sed -i -e 's/Hello/New/g' sample.sh
# 最終行に"echo END"を追記 ($a<追加文字列>)
sed -i '$aecho END' sample.sh
複数ファイルの操作
xargs
で並列処理ができる。
拡張子の一括変更や複数ファイルの移動に便利
# ファイル名だけ表示
find ./*.txt -type f | xargs baesname
# -I% は渡された結果を%がある位置に置くオプション
# 検索でヒットしたファイルを別のディレクトリにコピーする
find ./*.txt -type f | xargs -I% cp % ./foo/bar
# 拡張子の一括変更
find ./*.txt -type f | xargs -I% sh -c 'cp % ./foo/bar/$(basename % | sed -e "s/.txt/.md/g")'
ls ./foo/bar
> foo.txt bar.txt foo.md bar.md
参考文献
- 【Dockerfile】Docker に Conda 環境を構築し、仮想環境を Activate する
- LINUX パイプとリダイレクト
- とほほの正規表現入門
- Dockerfile リファレンス
- apt コマンドチートシート
-
Linux Containers (lxc)のようにコンテナを超軽量の VM として用いる場合もあります。
(docker のようなアプリケーションコンテナと対比してシステムコンテナと呼ばれます。) ↩ -
docker はデーモンが常駐(実行ユーザーに root 権限が必要)しますが、podman はデーモンレス・ルートレスコンテナとして実行できます。
自分のマシンで使う分にはあまり問題ではありませんが...。 ↩ -
docker-desktopがよく使われますが、私が普段使っているのが podman なので布教も兼ねて podman-desktop を選択しました。2023/06/30 現在 v1.1 がリリースされているので使用に耐えうると判断したのも要因です。
え?docker-compose が使いたい?podman-compose もあるし podman pod もあるよ...。 ↩