↓ メインコンテンツへスキップ

Namespace を使った超ミニコンテナの作成

·1879 文字·4 分
わて
著者
わて

この記事はコンテナを自作して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 を入れて動かしました。

os-release
# $ 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 $$

exit

Step 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 LTS

Step 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: 全部盛り
#

ここまでプロセス、ファイルシステム、ホスト名、ネットワークを独立させてきました。
シェルにして全部盛りにします。

run-container.sh
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を使用しましたが、そのネットワークになにも繋いでないので外にでることはできません。その辺についてはこの記事を参考にしてください。