ノートPCでLinuxの仮想化

いままではノートPCではWindowsを使ってきた. なんだかんだで使い慣れて*1いたり, あるいはハードウェア機能をフルに活かすためには他に選択肢が無かったりした. でも最近はペンタブ搭載機でもLinuxがふつうに動いたりするし, Windowsを使っていてもどうせ仮想OSとしてLinuxを動かしていて, Windows上のLinuxをべんりに使うためのバッドノウハウが溜まってしまいつつあったので, 思い切ってWindowsを使うのをやめることにした*2.

ノートPCでカジュアルにLinuxを使うなら, できるだけ新しいソフトウェアが動いてほしい. Debianならunstableを入れたい. でもなんかアップデートして急に動かなくなったりしたら困る. そこで仮想化ですよ! ただ, ノートPCの仮想化にはいろいろ問題点もあるので, 整理して解決策をまとめておく.

仮想OSの運用方法

ノートPCで仮想化すると言っても, サーバの仮想化のようにゲストOSをたくさん立ち上げて役割ごとに何かさせたいとかではなく, 基本的にはゲストOSは1つだけで, 実際にふだん使うのもこのOS. ゲストOSに何かトラブルがあったときのために, ホストOS側でスナップショットを取るなど, メンテナンスのためだけに仮想化したい. となると, ふつうのサーバ用の仮想化のベストプラクティスとは異なってくると思う. 重要なポイントは以下の通り.

ネットワークはNAT接続で

ブリッジ接続にしてしまうとノートPCをどこかのネットワークに接続したときに, 2つのホストにIPアドレスが割り当たることになってあまり行儀がよくない. かと言って, 普段はゲストOSを使うのでホストオンリー接続だと不便. なので事実上NAT接続にするしかない.

ホストOSのシャットダウン時にゲストOSをサスペンド

これは必須ではないけれど, 個人的にはEmacsをデーモン化してずっと起動しておきたいので, ゲストOSはサスペンドする運用にしたい.

PCを起動したらゲストOSのログイン画面を表示

PCを起動してホストOSにログイン後にゲストOSの画面をウィンドウに開いて使うというのでは, Windowsを使っているのと大差ないので, 起動後に直接ゲストOSにログインしたい. ホストOSの存在を普段は意識したくない.

KVMを使う

ゲストOSのログイン画面を表示するために, XDMCPを用いてホストOSのXサーバとゲストOSのXクライアントを接続することにする. ということはホストOSにXサーバが必要となる. だったらホストOSとしてもふつうにインストールしたLinuxを使うのが楽. 今回はDebian wheezyを使うことにした.

Xenなんかだときっと全く別のソリューションがあるのだと思うけど, ふつうの知識でできそうなKVMを使っておく.

ノートPCで仮想化する上での問題点

具体的な仮想化設定の前に, 上記の運用で発生する問題点と, おおよその解決方法を書いておく.

電源管理

実際にはPCがバッテリ動作していても, ゲストOS側からは電源ケーブルが接続されているように見えてしまう. これではバッテリ残量がわからなくて困るし, バッテリが残りわずかになったときにアラートしてもらえない. 手っ取り早い解決策としては, ホストOS側の電源管理アプリをssh経由で起動する. X転送を有効にしておけばホストOS側の電源アイコンがきちんとゲストOS側のデスクトップに表示される.

もう一つの問題はシャットダウン. ゲストOS側のデスクトップ画面ではどう操作しても電源を切れないので, 本体の電源ボタンを押すのが早い(ACPIによきにはからってもらう). サスペンドも同様.

ネットワーク管理

NAT接続だと実際に外部のネットワークに接続するのはホストOSで, ゲストOSからは(外部のネットワークの有無にかかわらず)有線接続のLANが存在するようにしか見えない. これでは無線LANの接続設定ができなくて困る. この場合も電源管理と同様に, ネットワーク管理のアプリをssh経由で起動するのが楽. ただし, 無線LANに接続する場合のパスワードの入力時に, D-Busで認証マネージャを起動できない*3ために入力欄が表示されないかもしれない. その場合の対処は最後のセクションで触れる.

音量調節

まずふつうにインストールするとおそらくゲストOSでは音が鳴らない. きちんと設定を行なう必要がある.

音が出るようになっても, Debian wheezyのバージョンのqemu-kvmではゲストOS側で音量調節ができない. なのでホストOS側の音量調節アプリを遠隔実行するくらいしか手がない.

ちなみに, ゲストOSから音量調節をするしくみ自体は存在していて, Debian wheezyのバージョンのqemu-kvmでも./configure--enable-mixemuをつけてコンパイルすれば有効になる. さらに後のバージョンではデフォルトで有効化されるようなので, 今だけの問題.(追記: よく確認してみるとこのパッチだけ取り込まれていない.)

その他のハードウェア設定

他にもハードウェア設定などをやりたい場合はホストOS側の設定アプリを遠隔起動する必要がある.

時刻がずれる

ゲストOSをサスペンドして, しばらく後にレジュームすると, なんとゲストOSの時刻がずれる. たとえゲストOSにNTPを設定していても時刻が補正されるまでに時間がかかってしまう(か, ずれ幅が大きすぎると補正をあきらめてしまう). なのでゲストOSのレジューム時に時刻を調整する必要がある.

ちなみに, ゲストOSをシャットダウンした後に一から起動する場合は自動的に時刻が調整される.

sshで特定のコマンドのみを実行

ホストOS上のアプリをゲストOSから遠隔実行することで問題を解決することが多く, その性質上rootで起動すべきだったりもして, あらゆるコマンドを無条件に実行できるようにしてしまうとさすがにまずい. そこで, sshのauthorize_keysを使って1つのコマンドだけを実行できる鍵を登録することにする. 具体的には~/.ssh/authorized_keysに

command="hoge foo bar",options ssh-rsa ABCD...

のように書くと, ABCD...の鍵による接続ではコマンドhoge foo bar(foo, barはコマンド引数)しか実行できないようになり, 対話的に接続しようとしてもこのコマンドが実行される. さらにoptionsのところに設定を書くことで端末を無効にする, ポート転送を無効にするというようなこともできる.

この手の設定をいちいち手で書くのは面倒なので, 遠隔コマンドを設定するためのスクリプトを作った: https://gist.github.com/4108520#file-auth-command-sh

使い方

ゲストOS側で

$ ./auth-command.sh add HOST SOME-NAME COMMAND ARG1 ARG2 ...

とすると, 通常通りHOSTに接続されるので, パスワードを打つなどしてログインすると, COMMAND ARG1 ARG2...SOME-NAMEという名前で登録される. 以降はゲストOSで

$ ./auth-command.sh run SOME-NAME

とすると, 登録されたホストの登録されたコマンドが実行される.

ユーザ名の指定やX転送などその他のオプションについては

$ ./auth-command.sh help

を参照.

qemuのフック

サンスペンドからの復帰時の時刻調整をするには, サスペンドから復帰する瞬間に何らかのスクリプトを実行する必要がある. libvirtを使っている場合はホストOSに/etc/libvirt/hooks/qemuというスクリプトを置いておけば, 各種イベント発生時にこのスクリプトが呼ばれる. ゲストOSの起動時に呼ばれるイベントstartのハンドラを書いておけば大丈夫そう(マニュアルを見るとstartedという, ゲストOSの起動後に発生するイベントがあるけれど, 新し目のバージョンでしかサポートされていないので注意).

ただし, 注意が2点.

  • startイベントはゲストOSの起動時に発生するので, スクリプト実行時にはまだ完全に起動しきっていない
  • 実際の時刻調整はゲストOS上でやる必要があるので, ホストOSからゲストOSのコマンドを呼ぶ必要がある

この辺りをよきにはからうスクリプトを一から作るのは面倒なので, startイベント時にゲストOSの起動を待ってからコマンドを遠隔実行するように設定するスクリプトを作った: https://gist.github.com/4169176#file-virt-start-hook-sh

使い方

ホストOS側で

$ ./virt-start-hook.sh add GUEST GUEST-NAME/SOME-FEATURE COMMAND ARG1 ARG2 ...

とすると, startイベント時にGUEST上でCOMMAND ARG1 ARG2 ...が実行されるようになる. GUEST-NAMEはゲストOSのlibvirt上の登録名で, SOME-FEATUREはこのイベントハンドラにつける名前(複数のハンドラを登録したときの区別のため).

自動設定スクリプト

auth-command.shvirt-start-hook.shをもってしても, この後の設定はややこしい. なので, auth-command.shvirt-start-hook.shをダウンロードして各問題に対処するための設定を自動的に行なうスクリプトを作った: https://gist.github.com/4169765#file-virt-laptop-sh

これはゲストOS側で(通常のユーザとして)実行するスクリプトで,

$ ./virt-laptop.sh HOST FEATURE

というように使う. HOSTはホストOS, FEATUREはインストールする設定で以下のいずれか(省略時はすべてインストール).

ntp
ゲストOS起動時の時刻の自動調整
audio
ゲストOSで音が出るようにする設定, およびオーディオミキサをメニューに登録
gnome-control-center
ホストOSのgnome-control-centerをメニューに登録
network-manager
ホストOSのネットワーク管理アプレットをログイン時の自動起動に登録
power-manager
ホストOSの電源管理アプレットをログイン時の自動起動に登録

必要なパッケージを自動インストールする都合上Debian限定. メニューの登録やタスクトレイの都合上GNOME限定. さらに, ゲストOSのホスト名はlibvirtの登録名と一致している必要がある.

設定方法

例としてホストOSとゲストOSのホスト名をそれぞれkokojonahとする.

ホストOSのセットアップ

まずはホストOSをふつうにインストール. ここではDebian wheezyをインストールしたと仮定. ただし, 必要ないのでGraphical desktop environmentは選択しない. LaptopSSH serverは必ず選択する.

続いて必要なものをインストール.

koko# aptitude install  network-manager alsa xserver-xorg ntp virtinst virt-top qemu-kvm libvirt-bin

ゲストOSのホスト名を設定.

koko# vi /etc/hosts
+192.168.122.10 jonah

シャットダウン時にゲストOSをサスペンドするようにする.

koko# vi /etc/default/libvirt-guests
+ON_SHUTDOWN=suspend
ゲストOSのセットアップ

通常の仮想OSのセットアップ手順に従う. たとえば以下のようにする. セットアップ時にはデスクトップ環境(GNOME)もインストールする. またGRUBをインストールしたものとする.

koko# mkdir -p /var/vm/jonah
koko# cd /var/vm/jonah

koko# qemu-img create -f qcow2 jonah.img 20G
koko# qemu-img create -f qcow2 jonah.swap.img 4G

koko# virsh net-autostart default
koko# virsh net-start default
koko# virt-install --connect  qemu:///system \
      --name jonah --ram=3072 --vcpus=4 \
      --disk path=./jonah.img,format=qcow2 \
      --disk path=./jonah.swap.img,format=qcow2 \
      --network network=default \
      --os-type linux --os-variant debianwheezy \
      --graphics none \
      --autostart \
      --location='http://d-i.debian.org/daily-images/amd64/' \
      --extra-args='console=ttyS0,115200n8'

koko# virsh destroy jonah # セットアップ後, 一旦停止

ゲストOSのIPアドレスを固定する.

koko# virsh net-destroy default
koko# virsh net-edit default
 <ip address='192.168.122.1' netmask='255.255.255.0'>
   <dhcp>
     <range start='192.168.122.2' end='192.168.122.254'/>
+    <host mac='52:54:**:**:**:**' ip='192.168.122.10'/>
   </dhcp>
 </ip>
koko# virsh net-start default

52:54:**:**:**:**の部分はゲストOSのMACアドレスを指定する(virsh edit jonahすればdevices/interface[@type='network']/macで確認できる).

以下, 起動後のゲストOS設定.

koko# virsh start jonah

必要に応じてunstableにアップグレード.

jonah# sed -i 's/wheezy main/sid main non-free contrib/g' /etc/apt/sources.list
jonah# aptitude update
jonah# aptitude full-upgrade

GRUBのメニューの表示時間を短くして, 起動途中のメッセージが表示されるようにしておく.

jonah# vi /etc/default/grub
-GRUB_TIMEOUT=5
+GRUB_TIMEOUT=1
 ...
-GRUB_CMDLINE_LINUX=""
+GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200n8"
jonah# update-grub

ホストOSのホスト名を設定する.

jonah# vi /etc/hosts
+192.168.122.1  koko
X設定(ホストOS側)

ゲストOSのXクライアントがXサーバに接続できるようにする.

koko# vi /etc/X0.hosts
+192.168.122.10

グラフィックが存在しないとGDMのプロセスがパニックを起こすようなので, 使わなくてもVNCの設定をしておく(ゲストOSのリスタートが必要).

koko# virsh edit jonah
 <devices>
   ...
+  <graphics type='vnc' port='-1' autoport='yes' listen='192.168.122.1'/>
 </devices>

起動時にゲストOSのログイン画面を表示する.

koko# vi /etc/inittab
 id:2:initdefault:
 ...
+x:2:respawn:/usr/bin/X -once -query jonah

initdefaultがデフォルトランレベルなのでxランレベルはそれに合わせる.

X設定(ゲストOS側)

XDMCPを有効にする.

jonah# vi /etc/gdm3/daemon.conf
 [xdmcp]
+Enable=true
電源管理

ゲストOS上で以下を実行するだけ.

jonah$ wget -O - http://raw.github.com/gist/4169765/virt-laptop.sh | sh -s install -v koko power-manager

-vの代わりに-tとすると裏側で実行されるコマンドが表示される.

GNOME3からは単体の電源管理アプレットがなくなってしまったので, Xfceの電源管理アプレットを使うように設定される. ホストOSにxfce4-power-managerがインストールされて, ゲストOSの現在のユーザのログイン時にアプレット自動起動するように設定される.

ネットワーク管理

ゲストOS上で以下を実行するだけ.

jonah$ wget -O - http://raw.github.com/gist/4169765/virt-laptop.sh | sh -s install -v koko power-manager

GNOMEのネットワーク管理アプレットを使うように設定される. 具体的には, ホストOSにnetwork-manager-gnomeがインストールされて, ゲストOSの現在のユーザのログイン時にホストOS上のnm-applet自動起動するように設定される.

音量調節

まず, ゲストOSの音声デバイスを有効にしておく(ゲストOSのリスタートが必要).

koko# virsh edit jonah
 <devices>
   ...
+  <sound model='ac97'/>
 </devices>

あとはゲストOS上で以下を実行するだけ.

jonah$ wget -O - http://raw.github.com/gist/4169765/virt-laptop.sh | sh -s install -v koko audio

まず, ホストOSでの音量調整を可能にするためにalsa-utilsがインストールされる. また, そのままではしばしばミュート状態になっているので, 自動的にミュート解除も行なう.

次に, qemuの音声デバイスを有効にする設定が行なわれる. 具体的には, /etc/libvirt/qemu.confを

 # vnc_allow_host_audio = 0
+vnc_allow_host_audio = 1

と書き換えることと, ユーザlibvirt-qemuaudioグループに追加すること.

最後に, ゲストOSのメニューに, ホストOS上のalsa-mixerを起動するためのアイテムが追加される.

その他の設定

ゲストOS上で以下を実行すると, ホストOS上のgnome-control-centerを開くメニューが追加される.

jonah$ wget -O - http://raw.github.com/gist/4169765/virt-laptop.sh | sh -s install -v koko gnome-control-center
時刻調整

ゲストOS上で以下を実行するだけ.

jonah$ wget -O - http://raw.github.com/gist/4169765/virt-laptop.sh | sh -s install -v koko ntp

まず, ゲストOSにntpがインストールされて, /etc/ntp.confに

+tinker panic 0

の設定がされる(時刻に大幅なずれがあっても修正されるようになる).

さらに, qemustartイベントでゲストOSの/etc/init.d/ntp restartが実行されるようになる.

その他の細かな問題

ネットワーク管理アプレットの認証

無線LANのパスワードなどが要求されるところで入力画面が表示されずに固まってしまうので, あらかじめ接続設定を開いておく. アプレットのドロップダウンメニューでアクセスポイントを選択すると「ワイヤレス」のタブに項目が追加されるので, 設定を編集して「セキュリティ」のタブでパスワードを入力すれば, 接続される.

アプレットのテーマ

遠隔で起動しているアプリにはゲストOS側のテーマ設定が適用されない. これをなんとかする場合は, ホストOS側の/etc/gtk-2.0/gtkrcを設定したいテーマのものにする. たとえば

koko# aptitude install murrine-themes
koko# cd /etc/gtk-2.0/
koko# ln -s /usr/share/themes/Albatross/gtk-2.0/gtkrc

とすると, 遠隔起動したアプリのテーマはAlbatrossになるので, ゲストOS側のGTK2, GTK3のテーマもAlbatrossにすればテーマを統一できる.

ログイン中のシャットダウン

ホストOSをシャットダウンしたときにゲストOSをサスペンドする場合は, ログインしたままシャットダウンするとよくわからないことになる(レジューム後に同じセッションに再接続できない)ので, きちんとログアウトした方がいい.

ちなみにログイン中にホストOSをサスペンドしてみたところ, 何事もなくうまくいった(けれどやるときは自己責任で).

GDMのエラー

ごく稀に, ログアウトしたときやPCの起動時(GDMが立ち上がるとき)に, "Oh no! Something has gone wrong."という画面(いわゆる黒画面)が出るときがある. たとえばXDMCPのクエリで-onceをつけないとより頻発するので, プロセスが多重に起動してしまっているのが原因らしい. /etc/gdm3/daemon.confでDisplaysPerHost=2とかすると多少改善するかも知れない(未確認).

GNOME 3 Shell

GNOME 3 Shellは3Dハードウェアアクセラレーションを必要としていて, 仮想化環境はサポートされていない*4ので, 現状ではfallback modeになってしまう. ただし頑張れば実はソフトウェアレンダリングでも動くという話もあるし, それに基づいてfallback modeの提供をやめるべきかという議論もあるので, そのうちふつうにGNOME 3 Shellが動くようになるかも知れない. (個人的にはfallback modeの方が使いやすいので動かなくていいのだけど......)

USB

USBデバイスをゲストOSにpass throughすればうまくいく, ように思うのだけど, なんかうまくいかなかった. うまいやり方を求む.

*1:昔々Windowsアプリを作ったりしていたのでWin32 APIなんかはだいたいわかっていて, APIから見たOSのアーキテクチャに馴染みがある

*2:と言っても普段使いのノートPCでWindowsを使うのをやめただけでWindows機を完全になくしたわけではない

*3:D-Busはホストをまたいで動作するようには設計されておらず, D-Busの接続を転送してみてもうまくいかなかった

*4:PCI passthroughとかいろいろやっても無理