CentOS Stream 9でproduction-readyなKVM/QEMUを構築する:ブリッジネットワーク、SELinux、firewalldの正しい設定方法

CentOS tutorial - IT technology blog
CentOS tutorial - IT technology blog

「動けばいい」仮想化と「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、CLI virsh を提供する。
  • 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

パッケージグループ@virtualizationqemu-kvmlibvirtlibvirt-clientvirt-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でブリッジを作成する

最初に物理インターフェース名を確認する(ens3eth0enp2s0 など、マシンによって異なる):

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を使ったストレージプールに移行することだ。しかしそれは、このファウンデーションが固まってからの話だ。

Share: