LXDコンテナにGPUをパススルーするためのスクリプト

LXDコンテナへのGPUパススルー、LXD-UIを使えば簡単ですが、わざわざLXD-UIにアクセスするまでもない場合のためにスクリプトを用意しました。KonomiTV環境を構築する際などに利用できます。

ここで作成するスクリプトのフロー

  1. lspci でGPU(VGA/3D/Displayコントローラ)を検出し、PCIアドレス・ベンダー/デバイスID・関連する /dev/dri ノードを表示
  2. 複数GPUがあれば番号選択、1つだけなら自動選択
  3. パススルー確認 [y/N]
  4. lxc list でコンテナ一覧を番号付きで表示(状態も表示)
  5. 対象コンテナを番号で選択 → 最終確認
  6. パススルー方式を選択
    • gpu (pci)方式(推奨): 選んだGPUをPCIアドレスで限定してパススルー。複数GPUがあるホストで「このGPUだけ」を渡せる
    • gpu (physical)方式: /dev/dri 配下を丸ごとパススルー。単一GPU環境向け
  7. NVIDIA GPU(vendor 10de)の場合は nvidia.runtime=true の設定も提案
  8. コンテナ起動中なら再起動の確認

補足

  • NVIDIA/AMD/IntelどのGPUでも同じスクリプトで対応できるよう、ベンダー判定で分岐しています
  • 実行には pciutilslspci)が必要です。なければ sudo apt install pciutils
  • パススルー後の動作確認は lxc exec <container> -- nvidia-smils -la /dev/dri で行えます
  • 既存の gpu0 デバイスがあれば自動で gpu1, gpu2… と番号をずらして追加するので、複数GPUを同じコンテナに追加しても安全です

スクリプト(ホストにインストール)

sudo mkdir -p /opt/lxd-data/konomitv-backup
cd /opt/lxd-data/konomitv-backup
sudo nano lxd-gpu-passthrough.sh
sudo bash lxd-gpu-passthrough.sh
#!/usr/bin/env bash
#
# lxd-gpu-passthrough.sh
#
# ホストのGPUをLXDコンテナにパススルーするスクリプト (Ubuntu 26.04 / LXD想定)
#
# 機能:
#   1. ホストで認識しているGPU一覧を表示
#   2. パススルーするGPUを選択 (複数GPUがある場合)
#   3. パススルー実行の確認
#   4. LXDコンテナ一覧を表示し、番号で対象コンテナを指定
#   5. `lxc config device add` で GPUデバイスをコンテナに追加
#
# 注意:
#   - LXDはホストカーネルを共有するため、ドライバ(NVIDIAドライバ等)は
#     ホスト側にインストールされていればコンテナ側でも基本的に共有可能。
#     ただしコンテナ内にも同バージョンのユーザランド(nvidia-utils等)が
#     必要な場合がある。
#   - root権限が必要 (sudo実行を推奨)
#
set -euo pipefail

# ---- 色定義 ----
C_RESET='\033[0m'
C_BOLD='\033[1m'
C_GREEN='\033[1;32m'
C_YELLOW='\033[1;33m'
C_RED='\033[1;31m'
C_CYAN='\033[1;36m'

info()  { echo -e "${C_CYAN}[INFO]${C_RESET} $*"; }
warn()  { echo -e "${C_YELLOW}[WARN]${C_RESET} $*"; }
err()   { echo -e "${C_RED}[ERROR]${C_RESET} $*" >&2; }
ok()    { echo -e "${C_GREEN}[OK]${C_RESET} $*"; }

# ---- 事前チェック ----
if [[ $EUID -ne 0 ]]; then
    err "このスクリプトはroot権限で実行してください (例: sudo $0)"
    exit 1
fi

if ! command -v lxc &>/dev/null; then
    err "lxc コマンドが見つかりません。LXDがインストールされているか確認してください。"
    exit 1
fi

if ! command -v lspci &>/dev/null; then
    err "lspci コマンドが見つかりません。'apt install pciutils' でインストールしてください。"
    exit 1
fi

echo -e "${C_BOLD}=== LXD GPUパススルー設定スクリプト ===${C_RESET}"
echo

# ---------------------------------------------------------------------------
# 1. GPU検出
# ---------------------------------------------------------------------------
info "ホストのGPUをスキャン中..."
echo

# VGA / 3D / Display controller を対象に抽出
mapfile -t GPU_LINES < <(lspci -Dnn | grep -Ei 'VGA compatible controller|3D controller|Display controller')

if [[ ${#GPU_LINES[@]} -eq 0 ]]; then
    err "GPUが検出されませんでした。"
    exit 1
fi

declare -a GPU_PCI_ADDR
declare -a GPU_DESC
declare -a GPU_VENDOR_ID
declare -a GPU_DEVICE_ID

idx=0
for line in "${GPU_LINES[@]}"; do
    # 例: 0000:01:00.0 VGA compatible controller [0300]: NVIDIA Corporation ... [10de:2204] (rev a1)
    pci_addr=$(echo "$line" | awk '{print $1}')
    desc=$(echo "$line" | sed -E 's/^[^ ]+ //')
    ids=$(echo "$line" | grep -oP '\[\K[0-9a-f]{4}:[0-9a-f]{4}(?=\])' | tail -1)
    vendor_id=$(echo "$ids" | cut -d: -f1)
    device_id=$(echo "$ids" | cut -d: -f2)

    GPU_PCI_ADDR+=("$pci_addr")
    GPU_DESC+=("$desc")
    GPU_VENDOR_ID+=("$vendor_id")
    GPU_DEVICE_ID+=("$device_id")

    echo -e "  ${C_BOLD}[$idx]${C_RESET} ${pci_addr}  ${desc}"

    # 関連するドライバ情報も表示 (lspci -k)
    driver_info=$(lspci -ks "${pci_addr#0000:}" 2>/dev/null | grep -E 'Kernel driver in use|Kernel modules' || true)
    if [[ -n "$driver_info" ]]; then
        echo "$driver_info" | sed 's/^/        /'
    fi

    # 関連するレンダーノード / カードノードも表示
    for drm in /dev/dri/by-path/*"${pci_addr#0000:}"*; do
        [[ -e "$drm" ]] && echo "        -> $(readlink -f "$drm" 2>/dev/null || echo "$drm")"
    done

    idx=$((idx+1))
    echo
done

# ---------------------------------------------------------------------------
# 2. パススルーするGPUの選択
# ---------------------------------------------------------------------------
SELECTED_GPU_IDX=0
if [[ ${#GPU_PCI_ADDR[@]} -gt 1 ]]; then
    while true; do
        read -rp "パススルーするGPUの番号を入力してください [0-$((${#GPU_PCI_ADDR[@]}-1))]: " sel
        if [[ "$sel" =~ ^[0-9]+$ ]] && (( sel >= 0 && sel < ${#GPU_PCI_ADDR[@]} )); then
            SELECTED_GPU_IDX=$sel
            break
        fi
        warn "無効な番号です。再入力してください。"
    done
else
    info "GPUが1つのみ検出されたため、これを使用します。"
fi

SEL_PCI=${GPU_PCI_ADDR[$SELECTED_GPU_IDX]}
SEL_DESC=${GPU_DESC[$SELECTED_GPU_IDX]}
SEL_VENDOR=${GPU_VENDOR_ID[$SELECTED_GPU_IDX]}
SEL_DEVICE=${GPU_DEVICE_ID[$SELECTED_GPU_IDX]}
# lxc config device add で使うPCIアドレス表記 (0000:01:00.0 -> 0000:01:00.0 のままでOK)

echo
echo -e "${C_BOLD}選択されたGPU:${C_RESET}"
echo "  PCIアドレス : $SEL_PCI"
echo "  デバイス    : $SEL_DESC"
echo "  Vendor:Device : ${SEL_VENDOR}:${SEL_DEVICE}"
echo

read -rp "このGPUをLXDコンテナにパススルーしますか? [y/N]: " confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    info "キャンセルしました。"
    exit 0
fi

# ---------------------------------------------------------------------------
# 3. LXDコンテナ一覧表示・選択
# ---------------------------------------------------------------------------
echo
info "LXDコンテナ一覧を取得中..."
echo

mapfile -t CONTAINERS < <(lxc list -c n --format csv 2>/dev/null)

if [[ ${#CONTAINERS[@]} -eq 0 ]]; then
    err "LXDコンテナが見つかりませんでした。"
    exit 1
fi

declare -a CT_STATUS
idx=0
printf "  %-4s %-25s %-10s\n" "No." "コンテナ名" "状態"
printf "  %-4s %-25s %-10s\n" "---" "-------------------------" "----------"
for ct in "${CONTAINERS[@]}"; do
    status=$(lxc list "^${ct}\$" -c s --format csv 2>/dev/null)
    CT_STATUS+=("$status")
    printf "  ${C_BOLD}%-4s${C_RESET} %-25s %-10s\n" "[$idx]" "$ct" "$status"
    idx=$((idx+1))
done
echo

while true; do
    read -rp "パススルー先のコンテナ番号を入力してください [0-$((${#CONTAINERS[@]}-1))]: " ct_sel
    if [[ "$ct_sel" =~ ^[0-9]+$ ]] && (( ct_sel >= 0 && ct_sel < ${#CONTAINERS[@]} )); then
        break
    fi
    warn "無効な番号です。再入力してください。"
done

TARGET_CT=${CONTAINERS[$ct_sel]}
TARGET_STATUS=${CT_STATUS[$ct_sel]}

echo
echo -e "${C_BOLD}対象コンテナ:${C_RESET} $TARGET_CT (状態: $TARGET_STATUS)"
echo

read -rp "コンテナ '$TARGET_CT' に GPU ($SEL_DESC) をパススルーします。よろしいですか? [y/N]: " final_confirm
if [[ ! "$final_confirm" =~ ^[Yy]$ ]]; then
    info "キャンセルしました。"
    exit 0
fi

# ---------------------------------------------------------------------------
# 4. パススルー方式の選択 (PCI方式 / GPUデバイス方式)
# ---------------------------------------------------------------------------
echo
echo -e "${C_BOLD}パススルー方式を選択してください:${C_RESET}"
echo "  [0] gpu (pci) デバイス  - 指定したGPUのみをPCIアドレスで限定してパススルー (推奨・複数GPU環境向け)"
echo "  [1] gpu (physical) デバイス - /dev/dri 以下の全GPUデバイスを丸ごとパススルー (単一GPU環境向け)"
echo

while true; do
    read -rp "方式番号を選択 [0/1] (デフォルト: 0): " mode_sel
    mode_sel=${mode_sel:-0}
    if [[ "$mode_sel" == "0" || "$mode_sel" == "1" ]]; then
        break
    fi
    warn "0 か 1 を入力してください。"
done

DEVICE_NAME="gpu0"

# 既存の同名デバイスがあれば確認
if lxc config device show "$TARGET_CT" 2>/dev/null | grep -q "^${DEVICE_NAME}:"; then
    n=1
    while lxc config device show "$TARGET_CT" 2>/dev/null | grep -q "^gpu${n}:"; do
        n=$((n+1))
    done
    DEVICE_NAME="gpu${n}"
fi

echo
info "デバイス名 '$DEVICE_NAME' としてコンテナに追加します。"

if [[ "$mode_sel" == "0" ]]; then
    # PCIアドレス指定方式 (gputype=physical, pci=<addr>)
    # lxc が要求するPCIアドレス形式は "0000:01:00.0" のようなフルアドレス
    info "実行コマンド: lxc config device add \"$TARGET_CT\" \"$DEVICE_NAME\" gpu gputype=physical pci=\"$SEL_PCI\""
    if lxc config device add "$TARGET_CT" "$DEVICE_NAME" gpu gputype=physical pci="$SEL_PCI"; then
        ok "GPU (PCI: $SEL_PCI) をコンテナ '$TARGET_CT' に追加しました (デバイス名: $DEVICE_NAME)"
    else
        err "デバイス追加に失敗しました。"
        exit 1
    fi
else
    # 物理デバイス丸ごと方式
    info "実行コマンド: lxc config device add \"$TARGET_CT\" \"$DEVICE_NAME\" gpu gputype=physical"
    if lxc config device add "$TARGET_CT" "$DEVICE_NAME" gpu gputype=physical; then
        ok "GPU (全/dev/driデバイス) をコンテナ '$TARGET_CT' に追加しました (デバイス名: $DEVICE_NAME)"
    else
        err "デバイス追加に失敗しました。"
        exit 1
    fi
fi

# ---------------------------------------------------------------------------
# 5. NVIDIAドライバの場合の追加情報
# ---------------------------------------------------------------------------
if [[ "$SEL_VENDOR" == "10de" ]]; then
    echo
    warn "NVIDIA GPUが検出されました。NVIDIAドライバを使用する場合は以下も確認してください:"
    echo "  - ホストに 'nvidia-container-toolkit' 相当のCDI設定が必要な場合があります"
    echo "  - LXD 5.x以降では 'nvidia.runtime=true' を設定することでNVIDIAランタイムを"
    echo "    自動的にコンテナへ注入できます:"
    echo "      lxc config set \"$TARGET_CT\" nvidia.runtime=true"
    echo "  - コンテナ起動後、コンテナ内で 'nvidia-smi' が使えるか確認してください"
    echo
    read -rp "nvidia.runtime=true をこのコンテナに設定しますか? [y/N]: " nv_confirm
    if [[ "$nv_confirm" =~ ^[Yy]$ ]]; then
        lxc config set "$TARGET_CT" nvidia.runtime=true
        ok "nvidia.runtime=true を設定しました。"
    fi
fi

# ---------------------------------------------------------------------------
# 6. コンテナ再起動の確認
# ---------------------------------------------------------------------------
echo
if [[ "$TARGET_STATUS" == "RUNNING" ]]; then
    read -rp "設定を反映するためコンテナ '$TARGET_CT' を再起動しますか? [y/N]: " restart_confirm
    if [[ "$restart_confirm" =~ ^[Yy]$ ]]; then
        info "コンテナを再起動中..."
        lxc restart "$TARGET_CT"
        ok "再起動しました。"
    else
        warn "設定を完全に反映するには手動で再起動してください: lxc restart $TARGET_CT"
    fi
else
    info "コンテナは停止中です。次回起動時に設定が反映されます。"
fi

echo
ok "完了しました。"
echo
echo "確認コマンド:"
echo "  lxc config device show $TARGET_CT"
echo "  lxc exec $TARGET_CT -- ls -la /dev/dri"
[[ "$SEL_VENDOR" == "10de" ]] && echo "  lxc exec $TARGET_CT -- nvidia-smi"

パススルーされているかの確認

コマンドで確認してもよいのですが、「EasyLXD」を導入しておくと簡単です。

タイトルとURLをコピーしました