「動けばいい」仮想化と「production-ready」な仮想化は全く別物だ
サーバー上のKVM構成でよく見かけるパターンがある:SELinuxをpermissiveにして、ファイアウォールを完全に無効化し、VMはlibvirtのデフォルトNAT virbr0 を使用するというものだ。理由は?「とりあえず動かして後で考える」。問題は、その「後で」が最悪のタイミングで来ることだ。VMが社内DHCPサーバーからIPを取得できないとき、セキュリティ監査のとき、あるいはファイアウォールの設定が正しく見えるのにVM内のサービスが外部からアクセスできないときなど。
我々の会社にはまだCentOS 7のサーバーが数台あり、AlmaLinuxへの移行は既に完了している。それと並行して、CentOS Stream 9上でKVMインフラをゼロから再構築した。本物のブリッジネットワーク、正しいSELinuxラベル付け、完全なfirewalld FORWARDルールを備えた構成だ。この記事はその過程で得た知見をまとめたものだ。VMが「沈黙」したままIPを取得できず、原因がファイアウォールの設定が1行足りないだけだったこともあった。
仮想化スタック:KVM、QEMU、Libvirt — それぞれの役割
よくあるのが、インストールは完了したものの、エラーが発生したときにどの層が問題なのかわからないケースだ。スタックを事前に理解しておくと、トラブルシューティングが大幅に速くなる:
- KVM:LinuxカーネルモジュールでCPU x86をハイパーバイザーに変える。CPUとメモリの仮想化のみを担当する。
- QEMU:I/Oを処理するエミュレーター(ディスク、ネットワークカード、USBなど)。KVM + QEMUの組み合わせでほぼネイティブに近い性能を発揮する。
- Libvirt:管理レイヤー。統一APIとデーモン
libvirtd、CLIvirshを提供する。 - Virt-Manager:libvirt用のGUIフロントエンドで、SSH経由でリモート接続できる。ラップトップから複数のVMを管理するのに便利。
CentOS Stream 9はRHEL 9のアップストリームだ。今日CS9に入った変更は、数ヶ月後にRHEL 9.xに反映される。Kernel 5.14.x、完全なKVMパッチ、長いライフサイクル。このスタックは本格的なサポートを受けている。
KVMと関連ツールのインストール
ステップ1:CPUがハードウェア仮想化をサポートしているか確認
# vmxフラグ(Intel)またはsvm(AMD)を確認する
grep -E --color=auto 'vmx|svm' /proc/cpuinfo | head -3
# またはvirt-host-validateで包括的にチェックする
virt-host-validate
grepコマンドの出力が表示されない場合は、BIOSでVT-x(Intel)またはAMD-Vを有効にしてから試してほしい。ハードウェア仮想化がなければKVMは動作しない。回避策は存在しない。
ステップ2:仮想化パッケージグループのインストール
# 仮想化パッケージグループをフルインストール
dnf install -y @virtualization
# ホストにGUIがある場合またはローカルマシンから使用する場合はVirt-Managerもインストール
dnf install -y virt-manager virt-viewer
# VMのディスクイメージ管理ツール
dnf install -y libguestfs-tools guestfs-tools
パッケージグループ@virtualizationはqemu-kvm、libvirt、libvirt-client、virt-installと全依存関係を一緒にインストールする。コマンド1つで完了だ。
ステップ3:libvirtdの有効化とKVMモジュールの確認
systemctl enable --now libvirtd
systemctl status libvirtd
# KVMモジュールがロードされている必要がある
lsmod | grep kvm
# 期待される出力:kvm_intel(Intel)またはkvm_amd(AMD)+ kvm
ブリッジネットワークの設定 — 最も重要なステップ
libvirtのデフォルトNATネットワーク(virbr0)は個人のラボ環境にしか使えない。NAT配下のVMは社内DHCPサーバーからIPを取得できず、サービスポートも外部から直接アクセスできない。本番環境にはブリッジネットワークが必要だ。VMが物理ネットワーク上に実マシンと同様に現れ、ルーターやスイッチのDHCPから直接IPを取得できる。
nmcliでブリッジを作成する
最初に物理インターフェース名を確認する(ens3、eth0、enp2s0 など、マシンによって異なる):
nmcli device status
物理インターフェースがens3の場合:
# ブリッジインターフェースbr0を作成
nmcli connection add type bridge con-name br0 ifname br0
# 物理NICをブリッジに追加
nmcli connection add type ethernet slave-type bridge \
con-name br0-ens3 ifname ens3 master br0
# ブリッジに静的IPを設定(実際のIPアドレスに変更してください)
nmcli connection modify br0 \
ipv4.addresses "192.168.1.100/24" \
ipv4.gateway "192.168.1.1" \
ipv4.dns "8.8.8.8,8.8.4.4" \
ipv4.method manual
# スイッチループがない場合はSTPを無効化(ブリッジの起動を高速化)
nmcli connection modify br0 bridge.stp no
# 有効化
nmcli connection up br0
nmcli connection up br0-ens3
# 確認
nmcli connection show --active
ip addr show br0
ブリッジトラフィック用のfirewalld設定
ここが最もよく見落とされる部分だ。ブリッジを作成した後、firewalldにこのインターフェースがどのゾーンに属するかを認識させる必要がある。さらに重要なのは、VMと外部ネットワーク間のトラフィックを通すFORWARDルールが必要なことだ:
# br0をゾーンに割り当て(ネットワークの信頼度に応じてpublicまたはtrusted)
firewall-cmd --permanent --zone=public --add-interface=br0
# FORWARDルール — これがないとVMから外部にpingできない
firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 \
-i br0 -o br0 -j ACCEPT
# VMがホスト経由でインターネットにNAT接続する場合(ブリッジ先にインターネットがない場合)
firewall-cmd --permanent --zone=public --add-masquerade
firewall-cmd --reload
firewall-cmd --list-all --zone=public
ブリッジ設定後に最もよく発生するエラーは、VMがIPを取得できているのに外部にpingできないというものだ。すぐにFORWARDルールを確認してほしい。10件中9件はこれが原因だ。
SELinuxとKVM:無効化ではなく正しく設定する
SELinuxを無効にする気持ちはわかる。急いでいるときに何かブロックされると、setenforce 0が最初の反射になってしまう。しかしハイパーバイザーの場合はリスクが全く異なる。VMからプロセスエスケープが発生した場合、SELinuxが攻撃者とホスト全体の間に立つ最後の防壁になる。これを無効にするとその保護を失う。auditログを読むのに5分もかからない。
VMディスクイメージのSELinuxコンテキスト
Libvirtはデフォルトで/var/lib/libvirt/images/にイメージを保存し、コンテキストvirt_image_tは既に正しく設定されている。別のディレクトリを使用する場合:
# 例:/data/vm-imagesを使用する場合
mkdir -p /data/vm-images
semanage fcontext -a -t virt_image_t "/data/vm-images(/.*)?"
restorecon -Rv /data/vm-images
# コンテキストを確認
ls -Z /data/vm-images
エラー発生時のSELinux denial対処法
# qemu-kvmに関連する最近のdenialを確認
ausearch -c 'qemu-kvm' --raw | audit2why
# denialからポリシーモジュールを作成(SELinuxを無効にする代わりに)
ausearch -c 'qemu-kvm' --raw | audit2allow -M my-qemukvm
semodule -X 300 -i my-qemukvm.pp
# KVMで役立つSELinux Boolean
setsebool -P virt_use_nfs 1 # VMにNFSストレージを使用する場合
setsebool -P virt_use_samba 1 # Sambaを使用する場合
VMの作成とVirt-Managerによる管理
KVMホストがヘッドレスで動作していても問題ない。ラップトップからVirt-ManagerでSSH接続すれば十分だ:
# GUIのあるローカルマシンで実行
virt-manager --connect qemu+ssh://[email protected]/system
複数のVMを同時に起動したい場合(例えばテスト環境に10台など)、CLIはGUIをひとつひとつクリックするより格段に速い:
virt-install \
--name centos9-vm1 \
--memory 2048 \
--vcpus 2 \
--disk path=/var/lib/libvirt/images/centos9-vm1.qcow2,size=20,format=qcow2 \
--cdrom /tmp/CentOS-Stream-9-latest-x86_64-dvd1.iso \
--network bridge=br0 \
--os-variant centos-stream9 \
--graphics vnc,listen=127.0.0.1 \
--noautoconsole
よく使うvirshコマンド
# VM一覧
virsh list --all
# 起動 / グレースフルシャットダウン / 強制停止
virsh start centos9-vm1
virsh shutdown centos9-vm1
virsh destroy centos9-vm1
# VMのリソース情報を確認
virsh dominfo centos9-vm1
# アップデート前にスナップショットを作成
virsh snapshot-create-as centos9-vm1 snap-before-update \
"Before system update" --disk-only
# コンソール接続(VM内でシリアルコンソールが必要)
virsh console centos9-vm1
よくあるエラーの素早いトラブルシューティング
エラー:”error: Failed to connect socket to ‘/var/run/libvirt/libvirt-sock'”
# サービスを確認
systemctl status libvirtd
# ユーザーはlibvirtグループに属している必要がある
usermod -aG libvirt $(whoami)
# ログアウトして再ログイン、または:
newgrp libvirt
エラー:VMがIPアドレスを取得できない
# ブリッジに物理インターフェースが追加されているか確認
brctl show br0
# firewalldを確認
firewall-cmd --list-all --zone=public
# libvirtのログで原因を確認
journalctl -u libvirtd --since "30 minutes ago" | grep -i 'error\|warn'
まとめ
CentOS Stream 9へのKVMインストール自体は複雑ではない。しかしラボ構成と本番構成の違いは3つの点に集約される:NATではなくブリッジネットワーク、SELinuxを無効にせず正しくラベルを設定すること、そしてVMトラフィックを通すfirewalld FORWARDルール。この3点を追加しても、デフォルト設定に比べて余分にかかる時間は約20分程度だ。しかし後のデバッグ時間を何時間も節約できる。
さらに深く進みたい場合の次のステップ:VMグループ間のトラフィックを分離するためにブリッジ上にVLANを追加するか、I/Oパフォーマンスを改善するためにファイルイメージではなくLVMを使ったストレージプールに移行することだ。しかしそれは、このファウンデーションが固まってからの話だ。

