Chienomi

Linuxで使いやすい仮想環境をさくっと使う (QEMU/KVM, Systemd nspawn)

Live With Linux::software

まえがき

この記事は「systemd-nspawnでManjaro LinuxからArch Linuxする」のoverrideでもある。

はじめに

Manjaro Linuxユーザーでもいくつかの理由により別環境を必要とすることもある。

典型的にはサーバー系ソフトウェアを試すときだ。もちろん、多彩なパッケージにAURまで備えるManjaro Linuxであればきっとサーバーソフトウェアを導入することもできるはずだが、設定を試したりするにも、あるいはその変更があくまでリハーサルであり最終的にdiscardするためにも、普段使っている環境から独立・隔離された場所で試したいと思うこともあるはずだ。 また、テストなどにおいて設定などが干渉しないようにしたいこともあるだろう。

一般的にこのようなケースで活躍するのが仮想化だ。 ここでは環境を用意するのが容易で、優れた点も多いQEMU/KVMと、Systemd-nspawnの話をしようと思う。

QEMU/KVM

概要

QEMUは極めて多彩なハードウェアをエミュレーションする強力な仮想マシンだ。 その柔軟性は驚くべきものだが、その反面性能はあまり高くない。特にVMWareやVirtualBoxと比べると、レガシー環境では優れているが、性能面ではだいぶ劣ってしまう。

それを補うのがKVMだ。KVMはLinuxのカーネルモジュールであり、Linuxカーネルをハイパーバイザーとして動作させるものである。 これを使うことでQEMUをハイパーバイザー型仮想環境として利用することができる。

そのポイントは、コマンドラインから操作するものの、非常に簡便で、なおかつ「KVMを使うか否か」の差がオプションの有無に留まることだ。

インストール

Manjaro Linuxの場合はqemuパッケージでQEMUがインストールされる。

他のアーキテクチャ(32bit Intel(IA32)を含む)を必要とする場合はqemu-arch-extraが必要だ。

さらに、KVMを使う場合はlibvertもインストールする。

QEMUのGUIフロントエンドも存在はするものの(例えばvirt-manager)、最低限コマンドが打てるなら煩雑になるだけである可能性が高い。

ディスクを作る

QEMUのネイティブなディスク形式はQCow2である。

$ qemu-img create -f foo-linux.qcow2 30G

これで30GBのfoo-linux.qcow2ディスクが出来上がる。 実際にはディスクに使用に従って容量が増加するので、いきなり30GBのファイルが出来上がるわけではない。

ISOイメージから起動する

仮にUbuntuのISOイメージをブートしてみよう。

$ qemu-system-x86_64 -m 8G -cdrom ubuntu-ja-19.10-desktop-amd64.iso

ここでは-mオプションでメモリ8GBで起動している。メモリ4GBでは厳しかった。

ディスクを接続した状態で起動する

-driveオプションでディスクイメージを指定する。

$ qemu-system-x86_64 -m 8G -cdrom ubuntu-ja-19.10-desktop-amd64.iso -drive file=ubuntu.qcow2

通常はこれで良いのだが、複数ディスクがあり、なおかつブータブルになっている場合にどのディスクからブートするかという問題が生じてしまうかもしれない。このような場合は、全て-driveオプションで指定する必要がある。

$ qemu-system-x86_64 -m 8G -drive file=ubuntu-ja-19.10-desktop-amd64.iso,index=0,media=cdrom -drive file=ubuntu.qcow2,index=1,media=qcow2

インストール後は単純にディスクを指定して起動すれば良い。

KVMを使う

CPUが仮想化支援を持っており、カーネルがKVMを有効にしており、KVMがモジュールである場合ロードされており、libvertが導入されている場合、単純に-enable-kvmオプションによってKVMが有効になる。

$ qemu-system-x86_64 -enable-kvm -m 8G -cdrom ubuntu-ja-19.10-desktop-amd64.iso

レガシーOSを起動する

単にi386エミュレータで起動すれば良いのだが、より厳しい場合は細かな指定が必要かもしれない。

qemu-system-i386 -m 128M -drive file=$HOME/rh7.qcow2,index=2,format=qcow2 -drive file=RH7-1.iso,index=0,media=cdrom -drive file=RH7-2.iso,index=1,media=cdrom -vga cirrus -machine pc-i440fx-2.8 -no-acpi -no-hpet

主にはOSからハードウェアが認識できるように古いチップセット、古いビデオカードとして見せている。 その分性能は落ちる。

だが、私が試した限り、Red Hat Linux 7はこれでもX Window Systemがスピンしてしまうし、Vine Linux 2はAnacondaが「ディスクにスペースがない」と認識してしまう。

ネットワーク

デフォルトではゲストOS側から見るとブリッジ接続された状態になっており、IPアドレスを持つインターフェイスが見える。 この状態で外部に通信するとちゃんと通信は届くし、ホストに対して通信することもできる。

ホストから通信したい場合、もっとも手っ取り早いのはデフォルトで起動し、ゲスト側からssh -Rをかけてホストのポートからゲストのポートへ転送することである。

QEMUのネットワーク設定はなかなか複雑なので、簡単に処理するならこの方法が勧めやすい。

また、例えばホストのTCP/5555をゲストのTCP/22に転送するには-device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:22のようなオプションを書くことで実現することができる。

Systemd nspawn

概要

systemd-nspawnはSystemdに備わっているコンテナ仮想化機能である。 Linuxの名前空間分離機能であるcgroupsを利用したものであり、LXCなどと同じタイプのものであるが、Systemdがある環境ならばコマンド一発のお手軽さである。

仕組みは簡単だ。コマンドの引数としたディレクトリをルートディレクトリとするファイルツリー構造上で名前空間を分離し(より適切な言い方なら、その分離した名前空間のうちファイル空間のルートディレクトリを指定したディレクトリにし)、その名前空間上でdefault.target Systemdユニットを起動する。 Systemdのプログラムそのものもコンテナ側のものを使用する。default.targetを起動するのはコンテナのSystemdなので、Systemd nspawnはコンテナを作ったらSystemdを起動するというところまでが仕事だ。

このため、ホスト環境、コンテナ環境ともにSystemdが存在する必要がある。 一方、カーネルはホスト環境で実行中のカーネルだから、コンテナ環境にLinuxカーネルが存在することは必須ではない。 言い換えると、コンテナ環境が実行中のカーネルとかけ離れたカーネルに基づいてビルドされた環境である場合(ABI的な理由で)一部はうまく動作しないかもしれない。

もっとも、この説明は話を簡略化しすぎている。 systemd-nspawnにはもっと色々と使いみちがあるし、別にコンテナ側にSystemdがなくても使うことは可能だからだ。

basestrap

Arch LinuxにおいてはArch Linuxの基本的な環境を構築するpacstrapというコマンドがある。 これはインストールにも使用するものだが、簡単に言えばchroot環境上にpacmanでパッケージをインストールするものである。

# pacstrap -i -c ~/Container base base-devel

Manjaro Linuxでもpacstrapを使っていたが、Arch Linuxとは独立のツールであることを強調するためか、コマンド名はbasestrapに改められた。 パッケージもpacstrapパッケージからmanjaro-toolsパッケージグループに含まれるようになった。

# basestrap -i -c ~/Container base base-devel

起動と終了

コンテナ環境を起動するには、systemd-nspawnを使用する。

# systemd-nspawn -b -D ~/Container

終了するにはコンテナ環境上でSystemdを用いてシャットダウンする。

# systemd poweroff

ネットワーク

デフォルトでは1systemd-nspawnはネットワーク名前空間を分離せず、そのままホストのインターフェイスを共有することになる。

サーバーのテストをしたい場合など、それで都合が悪い場合は、systemd-nspawn-nオプションを追加すると、ネットワークの名前空間が分離され、ホストとコンテナは仮想的なイーサネットケーブルで直接接続された状態になる (1:1だが、PPPではなくEthernetである)。

自動的にIPアドレスが設定されたりはしないので、まずはip linkを使ってデバイスを確認する。

# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: host0@if5: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 72:61:3d:4a:44:1b brd ff:ff:ff:ff:ff:ff link-netnsid 0

iproute2を使ってhost0ネットワークデバイスにアドレスをセットし、upする。

# ip addr add 192.168.65.2/24 dev host0
# ip link set up host0

同様に、ホスト側のve-*ネットワークデバイスもセットすれば、1対1で通信できるようになる。

なお、Archwikiには、コンテナはmymachinesとしてホスト名で名前解決できるとあるが、コンテナ側でhostnamectlを使って設定してもうまくいかなかった。