第9章:仮想化からコンテナへ - 隔離技術の進化

9.1 はじめに:なぜ隔離が必要なのか

あなたがアパートの大家だとしましょう。10部屋あるアパートに10組の家族が住んでいます。各家族にはプライバシーが必要で、他の家族の生活に干渉されたくありません。

コンピュータの世界でも同じです。一つの物理サーバーで複数のアプリケーションを動かすとき、それぞれが独立した環境で動作する必要があります。この「隔離」をどう実現するか。その答えの進化が、仮想化からコンテナへの道のりです。

9.2 物理サーバーの非効率性から仮想化、そしてコンテナへ

物理サーバー時代の課題

1990年代~2000年代初頭の状況

物理サーバーA:メールサーバー(CPU使用率 5%)
物理サーバーB:Webサーバー(CPU使用率 10%)
物理サーバーC:データベース(CPU使用率 20%)

問題:
- ハードウェアの使用率が低い(平均15%)
- 各サーバーの購入・維持コストが高い
- 新しいアプリケーションには新しいサーバーが必要
- 障害時の復旧に時間がかかる

仮想化技術の登場

VMware、Xen、KVMによる革命

物理サーバー(1台)
├─ 仮想マシン1:メールサーバー
├─ 仮想マシン2:Webサーバー
└─ 仮想マシン3:データベース

メリット:
- ハードウェアの有効活用(使用率60〜80%)
- 迅速なプロビジョニング
- スナップショット、ライブマイグレーション

仮想マシンの仕組み

Linux環境では、KVM (Kernel-based Virtual Machine)という仮想化技術が広く利用されています。 KVMはLinuxカーネルに組み込まれたハイパーバイザー(仮想化ソフトウェア)であり、専用のツール群(libvirtなど)と組み合わせて利用することで、効率的に仮想マシンを管理できます。

以下は、Linuxホスト上で virt-install コマンドを使ってKVM仮想マシンを作成する基本的な例です。(このコマンドは、通常 sudo を付けて実行します。)

# KVMを使用した仮想マシンの作成例
$ sudo virt-install \
    --name vm1 \
    --ram 2048 \
    --disk path=/var/lib/libvirt/images/vm1.qcow2,size=20 \
    --vcpus 2 \
    --os-type linux \
    --network bridge=br0 \
    --graphics none \
    --console pty,target_type=serial \
    --location 'https://archive.ubuntu.com/ubuntu/dists/jammy/main/installer-amd64/'

仮想マシンの限界

仮想マシンの限界

問題点

  • 各VMに完全なOSが必要(メモリ・ディスク消費)
  • 起動時間が長い(数十秒~数分)
  • オーバーヘッドが大きい

コンテナの革新

コンテナの革新

利点

  • 軽量(数十MB):OS全体ではなく、アプリケーションと最小限のライブラリのみをパッケージ化するため
  • 高速起動(数秒):完全なOSを起動するのではなく、ホストOSのカーネルを共有するため
  • 効率的なリソース使用:ホストOSのカーネルを共有し、無駄なリソース消費を抑えるため

9.3 名前空間とcgroupsによる軽量隔離

Linuxカーネルの隔離機能

Linuxの名前空間(namespace)は、プロセスやネットワーク、ファイルシステムなどの「見える範囲」を分離することで、他の環境から隔離された空間を提供する仕組みです。これに対してcgroups(control groups)は、CPUやメモリ、ディスクI/Oなどの「使える量」を制限する機能です。この2つを組み合わせることで、同じホストOS上に軽量な隔離環境、すなわちコンテナを構築できます。

名前空間(Namespaces)- 見える範囲の隔離

Linuxの名前空間は、プロセスが見ることができるシステムリソースを制限します:

# 利用可能な名前空間の種類
Mount   (mnt)   - ファイルシステムのマウントポイント
Process (pid)   - プロセスID
Network (net)   - ネットワークインターフェース、ルーティング
IPC     (ipc)   - プロセス間通信
UTS     (uts)   - ホスト名、ドメイン名
User    (user)  - ユーザーID、グループID
Cgroup  (cgroup)- コントロールグループ
Time    (time)  - システム時刻(Linux 5.6以降)

名前空間の実験

# 新しいネットワーク名前空間を作成
$ sudo unshare --net bash

# 新しい名前空間内では、ネットワークインターフェースが見えない
$ ip link show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN

# 元の名前空間では通常通り
$ exit
$ ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500

PID名前空間の隔離

# pid_namespace_demo.sh
cat > pid_namespace_demo.sh << 'EOF'
#!/bin/bash

echo "=== PID Namespace Demo ==="
echo "Parent PID: $$"
echo "Parent sees these processes:"
ps aux | head -5

echo
echo "Creating new PID namespace..."
sudo unshare --pid --fork --mount-proc bash -c '
    echo "Child PID in new namespace: $$"
    echo "Child sees these processes:"
    ps aux
'
EOF

cgroups(Control Groups)- リソースの制限

cgroupsの階層構造

補足(cgroup v2の制約):

  • cgroup.subtree_control は、子cgroupで利用可能にするコントローラーを有効化するためのファイルである
  • cgroup.subtree_control への書き込みは、書き込み対象のcgroupが内部プロセスを持たないこと(cgroup.procs が空)を要求する cgroup.procs が空でない場合は Device or resource busy になりやすい
  • systemd管理下では cpu/memory 等が既に有効化されていることが多い 本章の例は root cgroup(/sys/fs/cgroup)で対象コントローラーが有効化済みであることを前提とする 未有効の場合は環境側で有効化が必要(root cgroup が空なら echo "+cpu" | sudo tee /sys/fs/cgroup/cgroup.subtree_control のように有効化可能)
# cgroup v2の確認
$ mount | grep cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2

# 利用可能なコントローラー
$ cat /sys/fs/cgroup/cgroup.controllers
cpu io memory pids

# CPU使用率を50%に制限(前提: root cgroup で cpu コントローラーが有効)
$ grep -qw cpu /sys/fs/cgroup/cgroup.subtree_control || \
    echo "NOTE: cpu コントローラーが root cgroup の subtree_control で有効化されていません(この節はスキップしてください)"
$ sudo mkdir /sys/fs/cgroup/mylimit
$ echo "50000 100000" | sudo tee /sys/fs/cgroup/mylimit/cpu.max
# 100000マイクロ秒中50000マイクロ秒使用可能 = 50%

# 親シェル自身ではなく、CPUを使う子プロセスを cgroup に追加
$ yes > /dev/null &
$ STRESS_PID=$!
$ echo $STRESS_PID | sudo tee /sys/fs/cgroup/mylimit/cgroup.procs
$ sleep 5
$ kill $STRESS_PID
$ wait $STRESS_PID 2>/dev/null || true
$ sudo rmdir /sys/fs/cgroup/mylimit

メモリ制限の実装

# memory_limit_demo.sh
cat > memory_limit_demo.sh << 'EOF'
#!/bin/bash

# 前提: root cgroup で memory コントローラーが有効になっている環境を対象とする
if ! grep -qw memory /sys/fs/cgroup/cgroup.controllers; then
    echo "memory コントローラーが利用できません(cgroup.controllers に memory がありません)。" >&2
    exit 1
fi
if ! grep -qw memory /sys/fs/cgroup/cgroup.subtree_control; then
    echo "memory コントローラーが root cgroup の subtree_control で有効化されていません。" >&2
    echo "(root cgroup の cgroup.procs が空の環境なら、echo \"+memory\" | sudo tee /sys/fs/cgroup/cgroup.subtree_control で有効化できます)" >&2
    exit 1
fi

# メモリ制限付きのcgroup作成
sudo mkdir -p /sys/fs/cgroup/memlimit
echo "100M" | sudo tee /sys/fs/cgroup/memlimit/memory.max

# メモリを大量に使用するプログラム
cat > memory_hog.c << 'C'
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    size_t size = 200 * 1024 * 1024; // 200MB
    char *buffer = malloc(size);
    if (buffer) {
        printf("Allocated 200MB\n");
        memset(buffer, 0, size);
        printf("Memory filled\n");
        sleep(10);
    }
    return 0;
}
C

gcc memory_hog.c -o memory_hog

# cgroup内で実行
./memory_hog &
HOG_PID=$!
echo $HOG_PID | sudo tee /sys/fs/cgroup/memlimit/cgroup.procs
wait $HOG_PID || true  # OOM Killerでkillされる場合がある(環境/overcommit設定で挙動が変わる)
# 例: malloc失敗で終了する場合もある。確認: dmesg / journalctl -k
rm -f memory_hog memory_hog.c
sudo rmdir /sys/fs/cgroup/memlimit
EOF

実際のコンテナ作成(手動)

# manual_container.sh - 手動でコンテナ環境を作成
cat > manual_container.sh << 'EOF'
#!/bin/bash
set -euo pipefail

# 1. ルートファイルシステムの準備
ROOTFS="/tmp/container_root"
cleanup() {
    sudo rm -rf -- "$ROOTFS" alpine.tar.gz
}
trap cleanup EXIT
mkdir -p "$ROOTFS"

# 最小限のLinux環境をコピー(Alpine Linuxを使用)
ALPINE_VERSION="3.18.0"
ALPINE_ARCH="x86_64"
ALPINE_BASE="alpine-minirootfs-${ALPINE_VERSION}-${ALPINE_ARCH}.tar.gz"
wget -O alpine.tar.gz "https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/${ALPINE_ARCH}/${ALPINE_BASE}"
# 検証時点の SHA256 は Alpine 公式 release ページで確認してから更新する
echo "cb107eb5a1ab71aa2ae788a9c014480e003272ef2e7f76a2936ce9acca4218f1  alpine.tar.gz" | sha256sum -c -
tar -xzf alpine.tar.gz -C "$ROOTFS"

# 2. 名前空間を分離してchrootで起動
sudo unshare --mount --uts --ipc --net --pid --fork \
    --mount-proc="$ROOTFS/proc" \
    chroot "$ROOTFS" /bin/sh -c '
    # コンテナ内での作業
    hostname container
	    echo "Welcome to manual container!"
	    echo "Hostname: $(hostname)"
	    echo "Process list:"
	    if command -v ps >/dev/null 2>&1; then
	        ps aux 2>/dev/null || ps
	    else
	        echo "(ps is not installed)"
	    fi
	    echo "Network interfaces:"
	    if command -v ip >/dev/null 2>&1; then
	        ip addr show
	    else
	        echo "(ip is not installed)"
	    fi
	'
EOF

9.4 開発と本番環境の一致、迅速なデプロイ

「動作環境の違い」問題

従来の問題

開発者:「私の環境では動いています」
運用者:「本番環境では動きません」

原因:
- OSのバージョン違い
- ライブラリのバージョン違い
- 設定ファイルの違い
- 環境変数の違い

コンテナによる解決

Dockerfileによる環境の定義

# Dockerfile
FROM python:3.11-slim

# 依存関係のインストール
COPY requirements.txt .
RUN pip install -r requirements.txt

# アプリケーションのコピー
COPY app.py .

# 環境変数の設定
ENV FLASK_APP=app.py
ENV FLASK_ENV=production

# ポートの公開
EXPOSE 5000

# 起動コマンド
CMD ["flask", "run", "--host=0.0.0.0"]

イメージのビルドと配布

# 開発環境でビルド
docker build -t myapp:v1.0 .

# レジストリにプッシュ
docker push registry.example.com/myapp:v1.0

# 本番環境でプル&実行
docker pull registry.example.com/myapp:v1.0
docker run -d -p 80:5000 registry.example.com/myapp:v1.0

迅速なデプロイメントの実現

従来のデプロイメント

1. サーバーのプロビジョニング(30分)
2. OSのインストール・設定(1時間)
3. 依存関係のインストール(30分)
4. アプリケーションのデプロイ(15分)
5. 設定・テスト(30分)
合計:2時間45分

コンテナベースのデプロイメント

1. コンテナイメージのプル(1分)
2. コンテナの起動(5秒)
合計:1分5秒

実践的なCI/CDパイプライン

# .gitlab-ci.yml の例
stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

test:
  stage: test
  script:
    - docker run --rm $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA pytest

deploy:
  stage: deploy
  script:
    - kubectl set image deployment/myapp myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - kubectl rollout status deployment/myapp --timeout=180s
    - kubectl get deployment myapp -o jsonpath='{.spec.template.spec.containers[0].image}'

9.5 演習:手動でコンテナの仕組みを再現

演習1:名前空間の分離を体験

# namespace_isolation.sh
cat > namespace_isolation.sh << 'EOF'
#!/bin/bash

echo "=== Namespace Isolation Demo ==="

# 1. UTS名前空間(ホスト名の分離)
echo "1. UTS Namespace:"
echo "   Current hostname: $(hostname)"
sudo unshare --uts bash -c '
    hostname isolated-container
    echo "   Isolated hostname: $(hostname)"
'
echo "   Original hostname still: $(hostname)"

# 2. PID名前空間(プロセスの分離)
echo
echo "2. PID Namespace:"
echo "   Current process count: $(ps aux | wc -l)"
sudo unshare --pid --fork --mount-proc bash -c '
    echo "   Isolated process count: $(ps aux | wc -l)"
    ps aux
'

# 3. ネットワーク名前空間
echo
echo "3. Network Namespace:"
echo "   Current interfaces:"
ip -brief addr show
sudo unshare --net bash -c '
    echo "   Isolated interfaces:"
    ip -brief addr show
    echo "   Setting up lo interface..."
    ip link set lo up
    ip -brief addr show
'
EOF

chmod +x namespace_isolation.sh

演習2:cgroupsによるリソース制限

# resource_limits.sh
cat > resource_limits.sh << 'EOF'
#!/bin/bash

echo "=== Resource Limitation Demo ==="

# CPU制限のデモ
echo "1. CPU Limitation:"

# CPU負荷生成プログラム
cat > cpu_stress.c << 'C'
#include <stdio.h>
int main() {
    printf("Starting CPU stress...\n");
    while(1) {
        // 無限ループでCPU使用
    }
    return 0;
}
C

gcc cpu_stress.c -o cpu_stress

# cgroup設定
sudo mkdir -p /sys/fs/cgroup/cpu_demo
cleanup() {
    if [ -n "${PID:-}" ] && kill -0 "$PID" 2>/dev/null; then
        kill "$PID"
        wait "$PID" 2>/dev/null || true
    fi
    rm -f cpu_stress cpu_stress.c
    sudo rmdir /sys/fs/cgroup/cpu_demo 2>/dev/null || true
}
trap cleanup EXIT INT TERM

# この演習では、すでにroot cgroupでcpuコントローラーが有効になっている環境のみを対象とする
if ! grep -qw cpu /sys/fs/cgroup/cgroup.controllers; then
    echo "cpu コントローラーが利用できません(cgroup.controllers に cpu がありません)。" >&2
    exit 1
fi
if ! grep -qw cpu /sys/fs/cgroup/cgroup.subtree_control; then
    echo "cpu コントローラーが root cgroup の subtree_control で有効化されていないため、このデモを中断します。" >&2
    echo "(root cgroup の cgroup.procs が空の環境なら、echo \"+cpu\" | sudo tee /sys/fs/cgroup/cgroup.subtree_control で有効化できます)" >&2
    exit 1
fi

# 20% CPU制限
echo "20000 100000" | sudo tee /sys/fs/cgroup/cpu_demo/cpu.max > /dev/null

# バックグラウンドで実行
./cpu_stress &
PID=$!

# 制限なしで5秒間測定
echo "Without limit:"
sleep 5
ps -p $PID -o %cpu | tail -1

# cgroupに追加
echo $PID | sudo tee /sys/fs/cgroup/cpu_demo/cgroup.procs > /dev/null

# 制限ありで5秒間測定
echo "With 20% limit:"
sleep 5
ps -p $PID -o %cpu | tail -1

kill $PID
wait $PID 2>/dev/null || true
EOF

確認の目安:

  • ps -p "$PID" -o %cpu= の値が制限前後で変化することを確認すると、cgroup の CPU 制御が効いているかを判断しやすくなります。
  • sudo test ! -d /sys/fs/cgroup/cpu_demo が真になることを確認すると、演習用 cgroup が cleanup で消えているかを確認できます。

後始末:

  • rm -f cpu_stress cpu_stress.c を追加して、演習用のバイナリとソースを残さないようにします。

演習3:最小限のコンテナランタイム実装

# mini_container.sh - 最小限のコンテナランタイム
cat > mini_container.sh << 'EOF'
#!/bin/bash

CONTAINER_ID="mini_$(date +%s)"
ROOTFS="/tmp/containers/$CONTAINER_ID"

cleanup() {
    sudo umount "$ROOTFS/proc" 2>/dev/null || true
    sudo umount "$ROOTFS/sys" 2>/dev/null || true
    sudo rm -rf -- "$ROOTFS"
}

trap cleanup EXIT INT TERM

# コンテナイメージの作成関数
create_rootfs() {
    mkdir -p $ROOTFS/{bin,lib,lib64,etc,proc,sys,dev,tmp}
    
    # 必要なバイナリをコピー
    for bin in sh ls cat echo ps mount umount; do
        cp /bin/$bin $ROOTFS/bin/ 2>/dev/null || \
        cp /usr/bin/$bin $ROOTFS/bin/ 2>/dev/null
    done
    
    # 共有ライブラリをコピー
    for bin in $ROOTFS/bin/*; do
        ldd $bin 2>/dev/null | grep -oE '/[^ ]+' | while read lib; do
            mkdir -p $ROOTFS$(dirname $lib)
            cp $lib $ROOTFS$lib 2>/dev/null
        done
    done
    
    # 基本的な設定ファイル
    echo "container" > $ROOTFS/etc/hostname
    echo "127.0.0.1 localhost" > $ROOTFS/etc/hosts
}

# コンテナ実行関数
run_container() {
    echo "Starting container $CONTAINER_ID..."
    
    sudo unshare --mount --uts --ipc --net --pid --fork \
        bash -c "
        # procとsysをマウント
        mount -t proc proc $ROOTFS/proc
        mount -t sysfs sys $ROOTFS/sys
        
        # ルートファイルシステムを変更
        chroot $ROOTFS /bin/sh -c '
            hostname \$(cat /etc/hostname)
            echo \"Welcome to mini container!\"
            echo \"Container ID: $CONTAINER_ID\"
            echo \"Hostname: \$(hostname)\"
            echo \"Processes:\"
            ps aux 2>/dev/null || ps
            /bin/sh
        '
        
        # クリーンアップ
        umount $ROOTFS/proc
        umount $ROOTFS/sys
    "
}

# メイン処理
create_rootfs
run_container
EOF

Verify: 終了後に findmnt "$ROOTFS/proc"findmnt "$ROOTFS/sys" が空であること、test -d "$ROOTFS" が偽になることを確認すると、一時 rootfs と mount namespace の後始末を確認しやすくなります。

演習4:コンテナネットワークの構築

# container_network.sh
cat > container_network.sh << 'EOF'
#!/bin/bash
set -euo pipefail

cleanup() {
    sudo ip link del br0 2>/dev/null || true
    sudo ip netns del container1 2>/dev/null || true
    sudo ip netns del container2 2>/dev/null || true
}
trap cleanup EXIT INT TERM

echo "=== Container Network Setup ==="

# ネットワーク名前空間の作成
sudo ip netns add container1
sudo ip netns add container2

# 仮想イーサネットペアの作成
sudo ip link add veth1 type veth peer name br-veth1
sudo ip link add veth2 type veth peer name br-veth2

# ブリッジの作成と設定
sudo ip link add br0 type bridge
sudo ip link set br-veth1 master br0
sudo ip link set br-veth2 master br0

# コンテナに仮想インターフェースを移動
sudo ip link set veth1 netns container1
sudo ip link set veth2 netns container2

# IPアドレスの設定
sudo ip netns exec container1 ip addr add 172.20.0.2/24 dev veth1
sudo ip netns exec container2 ip addr add 172.20.0.3/24 dev veth2
sudo ip addr add 172.20.0.1/24 dev br0

# インターフェースを有効化
sudo ip link set br0 up
sudo ip link set br-veth1 up
sudo ip link set br-veth2 up
sudo ip netns exec container1 ip link set veth1 up
sudo ip netns exec container2 ip link set veth2 up

# 接続テスト
echo
echo "Testing connectivity:"
echo "Container1 -> Container2:"
sudo ip netns exec container1 ping -c 3 172.20.0.3

echo
echo "Container2 -> Host:"
sudo ip netns exec container2 ping -c 3 172.20.0.1

echo
echo "Network setup complete. Cleanup runs automatically on exit."
EOF

確認の目安:

  • sudo ip netns listcontainer1 / container2 が出ていることを確認すると、名前空間の作成結果を把握しやすくなります。
  • ip -br link | grep -E 'br0|br-veth1|br-veth2' の結果で、bridge 側リンクが UP になっていることを確認すると、疎通失敗の切り分けがしやすくなります。

後始末:

  • 終了後に sudo ip netns listcontainer1 / container2 が消えていること、ip -br link | grep -E 'veth1|veth2|br-veth1|br-veth2' が空になることを確認すると、演習で作成したリンクが残っていないかを確認できます。

演習5:コンテナイメージのレイヤー構造理解

# layer_demo.sh
cat > layer_demo.sh << 'EOF'
#!/bin/bash

echo "=== Container Image Layers Demo ==="

WORK_DIR="/tmp/layer_demo"
mkdir -p "$WORK_DIR"
cd "$WORK_DIR"

cleanup() {
    if mountpoint -q overlay/merged 2>/dev/null; then
        sudo umount overlay/merged
    fi
    rm -rf -- "$WORK_DIR"
}

trap cleanup EXIT INT TERM

# レイヤー1:ベースシステム
echo "Creating Layer 1: Base System"
mkdir -p layer1/{bin,lib,etc}
echo "Base Linux System" > layer1/etc/os-release
echo "Layer 1 size: $(du -sh layer1 | cut -f1)"

# レイヤー2:ランタイム追加
echo
echo "Creating Layer 2: Runtime"
cp -r layer1 layer2
mkdir -p layer2/usr/bin
echo "#!/bin/sh\necho 'Python 3.11 runtime'" > layer2/usr/bin/python3
chmod +x layer2/usr/bin/python3
echo "Layer 2 size: $(du -sh layer2 | cut -f1)"
echo "Layer 2 diff: $(diff -r layer1 layer2 | wc -l) changes"

# レイヤー3:アプリケーション
echo
echo "Creating Layer 3: Application"
cp -r layer2 layer3
mkdir -p layer3/app
echo "print('Hello from container!')" > layer3/app/main.py
echo "Layer 3 size: $(du -sh layer3 | cut -f1)"
echo "Layer 3 diff: $(diff -r layer2 layer3 | wc -l) changes"

# OverlayFSのデモ(簡易版)
echo
echo "Demonstrating OverlayFS:"
mkdir -p overlay/{lower,upper,work,merged}
cp -r layer1/* overlay/lower/
cp -r layer2/* overlay/upper/

# 実際のOverlayFSマウント(要root)
sudo mount -t overlay overlay \
    -o lowerdir=overlay/lower,upperdir=overlay/upper,workdir=overlay/work \
    overlay/merged

echo "Merged filesystem contents:"
ls -la overlay/merged/
EOF

Verify: findmnt overlay/merged -o FSTYPE,TARGEToverlay が見えていること、終了後に同じコマンドが空になることを確認すると、OverlayFS の mount / unmount が正しく行われたかを判断しやすくなります。

9.6 コンテナ技術の実際

本番環境での考慮事項

セキュリティ

# 固定タグまたは digest を指定して脆弱性スキャン
docker scout cves registry://myimage:v1.0.0
# より厳密に固定したい場合の例
docker scout cves registry://myimage@sha256:<digest>

注記: latest のような可変タグは、検証記録や障害調査で同じイメージを再現しにくくなります。スキャン結果を保存する場合は、固定タグか digest を使ってください。

Verify: スキャン結果を記録する場合は、docker image inspect myimage:v1.0.0 | jq -r '.[0].RepoDigests[0]' などで、その時点の digest を一緒に残してください。固定タグで運用していても、registry 側の再 push やミラー差分があると、後から同じイメージを特定しにくくなります。

# 非rootユーザーでの実行
FROM node:20
RUN useradd -m appuser
USER appuser
COPY --chown=appuser:appuser . /app

ログ管理

# コンテナログの確認
docker logs container_name

# ログドライバーの設定
docker run --log-driver=syslog \
    --log-opt syslog-address=tcp://192.168.1.100:514 \
    myapp:v1.0.0

監視

# リソース使用状況
docker stats

# ヘルスチェック
HEALTHCHECK --interval=30s --timeout=3s \
    CMD curl -f http://localhost/health || exit 1

9.7 まとめ:隔離技術がもたらす革新

コンテナ技術の本質

本章で学んだコンテナ技術は、以下の革新をもたらしました:

  1. 軽量な隔離:カーネルを共有しながら独立した環境を実現
  2. 移植性:開発から本番まで同じ環境を保証
  3. 効率性:リソースの最適な利用
  4. 俊敏性:秒単位でのデプロイメント

技術の進化の流れ

物理サーバー(1990年代)
    ↓ リソースの無駄
仮想マシン(2000年代)
    ↓ オーバーヘッドが大きい
コンテナ(2010年代)
    ↓ オーケストレーションが必要
Kubernetes(現在)

次章への展望

コンテナの基本的な仕組みを理解したところで、次章では実際のコンテナランタイムである「Podman」について学びます。

なぜDockerではなくPodmanなのか。それは、よりセキュアで、より柔軟な、次世代のコンテナ技術だからです。デーモンレス、ルートレスといった革新的な機能を持つPodmanの世界を探求していきましょう。

章末演習問題

問題1:基本理解の確認

以下の文章の空欄を埋めてください。

  1. コンテナ技術で使用されるLinuxカーネルの機能は、(     )と(     )です。
  2. 仮想マシンは(    )を仮想化し、コンテナは(    )を仮想化します。
  3. PID名前空間を使用すると、コンテナ内のプロセスはPID( )から始まります。

問題2:概念の理解

次の質問に答えてください。

  1. 仮想マシンとコンテナの違いを、リソース使用効率、起動速度、セキュリティの観点から比較してください。
  2. Linuxの名前空間(namespace)の種類を5つ挙げ、それぞれの役割を説明してください。
  3. cgroupsがコンテナ技術において果たす役割を具体例を交えて説明してください。

問題3:実践的な課題

以下のシナリオに対する解決策を示してください。

  1. コンテナ内でメモリを100MBまでに制限する方法
  2. 2つのコンテナ間でのみ通信可能なネットワークを構築する方法
  3. ホストのディレクトリをコンテナ内にマウントする際のセキュリティ考慮事項

問題4:コンテナ作成

以下の要件を満たすDockerfileを作成してください。

  • ベースイメージ:Ubuntu 22.04
  • Nginx Webサーバーをインストール
  • カスタムindex.htmlを/usr/share/nginx/html/に配置
  • 非rootユーザー(www-data)で実行
  • ヘルスチェックを実装

問題5:名前空間の実験

以下の動作を確認するスクリプトを作成してください。

#!/bin/bash
# namespace_experiment.sh

# 要件:
# 1. 新しいUTS名前空間を作成し、ホスト名を変更
# 2. 新しいPID名前空間を作成し、プロセス一覧を表示
# 3. 新しいネットワーク名前空間を作成し、インターフェースを確認
# 4. 各名前空間での変更がホストに影響しないことを確認

# ここにコードを記述

問題6:パフォーマンス比較

仮想マシンとコンテナのパフォーマンスを比較する実験を設計してください。

比較項目:

  • 起動時間
  • メモリ使用量
  • CPU使用効率
  • ディスクI/O性能

問題7:トラブルシューティング

コンテナが起動しない場合の段階的なトラブルシューティング手順を作成してください。

確認項目:

  • イメージの存在
  • リソースの制限
  • ネットワーク設定
  • 権限設定

問題8:発展的課題

  1. コンテナのセキュリティを強化するための技術(seccomp、AppArmor、SELinux)について、それぞれの役割と実装方法を説明してください。

  2. マイクロサービスアーキテクチャにおいて、コンテナ技術が果たす役割と課題について論じてください。