この記事はコンテナを自作してdocker やコンテナそのものの仕組み理解する試みのものです。
コンテナといえばdocker のイメージですが、実はdocker なしでもコンテナを作ることはできます。
なにかを理解するには、手を動かすことが近道なことが多いと思うので、今回はコンテナをLinux の標準コマンドだけで作成したいと思います。
今回はコンテナの構成要素であるNamespaceについて理解を進めます。
また、この記事は基本このサイトを参考にしています、ほぼ翻訳版です。一部私の実行環境のために変更を加えたところもあります。
コンテナの正体#
コンテナとはLinuxの標準機能であるNamespace の集まりで、Docker はNamespace に加えて、Cgroup、overlayfs の技術を使用して実現しています。
それぞれの機能は以下の通りです。
- Namespace: システムリソースの隔離
- Cgroup: リソースの制限
- overlayfs: コンテナごとのファイルシステムの隔離
Namespace とは#
Namespace(ネームスペース/名前空間)は、Linux カーネルの標準機能の一つで、一言で説明すると「プロセスに対して、システムのリソース(部屋)を隔離して見えなくする壁」のような仕組みです。例えば、ネットワークのルーティングやホスト名ドメイン名を独立させ、ホストには影響させないようにできます。
Namespace にはいくつか種類があり、プロセスを独立させるPID、ネットワークを独立させるNet などがあります。逆説的ですが、「Namespace = 部分的なコンテナ」と考えるとわかりやすいかも知れません。
コンテナとはNamespace の寄せ集めでDocker はそれらを一元管理しています。
実行環境#
VM でUbuntu を入れて動かしました。
# $ cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04.5 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.5 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo準備#
作るコンテナではUbuntu を使うので以下のをインストールします。
sudo apt update
sudo apt install -y debootstrapまた、ラズパイを仕様している場合などは(アーキテクチャーがaarch64なので)QEMUのエミュレータもインストールし、rootfs 内に qemu のバイナリをコピーします。
# Ubuntu/Debianの場合
sudo apt install -y qemu-user-static
sudo cp /usr/bin/qemu-aarch64-static ./rootfs/usr/bin/Step 1: コンテナのファイルシステムを作成#
まずは適当にディレクトリを作って、そこでコンテナの元になるファイルシステムをダウンロードします。(ちょっと時間かかる)
mkdir ~/my-container
cd ~/my-container# 最小のUbuntuをダウンロード
sudo debootstrap jammy rootfs http://archive.ubuntu.com/ubuntu/
# arch 系の場合は --arch=amd64 を追加
ls rootfs/
# ファイルシステムがある、そして bin, usr, etc, varとかがあるはずStep 2: プロセスIDの独立(PID Namespace)#
プロセスIDを独立させ、ホストのプロセスが見れなくなっていることを確認します。
# まずはホストが抱えてるプロセスの数を確認します。おそらく100以上あるはずです
ps aux | wc -l# unshare コマンドはNamespaceを使ってプロセスを隔離させるコマンドです
# 引数で --pid を指定しプロセスを独立させる、このとき--fork も必須
sudo unshare --pid --fork --mount bash
# /proc をマウントし 'ps' コマンドでプロセスを数えられるようにする
mount -t proc proc /proc
ps aux
# 自分自身(コンテナ)のIDと今のコマンドのみが表示される
# PIDが1であることもわかる
echo $$
exitStep 3: ファイルシステムの独立(chroot)#
chrootを使ってコンテナの中に入ります。そして、コンテナの外は見れなくなります。
sudo unshare --pid --fork --mount bash -c "
mount -t proc proc /proc
# Change root to our container filesystem
chroot $PWD/rootfs /bin/bash
"作ったコンテナの中で以下のコマンドを叩いてみましょう。
pwd # /
ls # コンテナ内のファイルシステム
cat /etc/os-release # Ubuntu 22.04 LTSStep 4: ホスト名の独立(UTS Namespace)#
ホスト名を独立させる場合は--utsを使います。
sudo unshare --pid --fork --mount --uts bash -c "
mount -t proc proc /proc
hostname my-container
chroot $PWD/rootfs /bin/bash
"Step 5: ネットワークの独立(Network Namespace)#
ネットワークの独立をさせる際は--netの引数を使います。
sudo unshare --pid --fork --mount --uts --net bash -c "
mount -t proc proc /proc
hostname my-container
ip link show
# 'lo' だけが表示されます
chroot $PWD/rootfs /bin/bash
"Step 6: 全部盛り#
ここまでプロセス、ファイルシステム、ホスト名、ネットワークを独立させてきました。
シェルにして全部盛りにします。
ROOTFS="$PWD/rootfs"
echo "Starting container..."
sudo unshare --pid --fork --mount --uts --ipc --net bash -c "
# 必要なファイルシステムをマウント
mount -t proc proc $ROOTFS/proc
mount -t sysfs sys $ROOTFS/sys
mount -t tmpfs tmpfs $ROOTFS/tmp
hostname my-container
chroot $ROOTFS /bin/bash
# exit時にもろもろ消去
umount $ROOTFS/proc 2>/dev/null
umount $ROOTFS/sys 2>/dev/null
umount $ROOTFS/tmp 2>/dev/null
"実行 ↓
chmod +x run-container.sh
sudo ./run-container.shテストで以下を実行してみます。
hostname # "my-container"
ps aux # コンテナプロセスのみ
ip a # ループバックのみネットののNamespaceを使用しましたが、そのネットワークになにも繋いでないので外にでることはできません。その辺についてはこの記事を参考にしてください。
