KVM環境のフックについて調査

はじめに

KVM環境でポートフォワードを行おうと思って調べてみたところ、どうもlibvirtのフックの仕組みを利用してiptablesを実行するのがよいらしい。 そのフックの概要を忘れないうちに残しておく。

フックのスクリプト

フックはスクリプトとして実装する。 言語はbashPythonなど、何でもよい。 機能別に呼び出されるスクリプトが分かれており、スクリプトのファイル名は固定である。

スクリプトファイル 対応する機能
/etc/libvirt/hooks/daemon libvirtデーモン
/etc/libvirt/hooks/qemu QEMUのゲストであり、KVMの普通のVM
/etc/libvirt/hooks/lxc LXCコンテナ
/etc/libvirt/hooks/libxl XenVM
/etc/libvirt/hooks/network ネットワーク

フックの呼び出しのタイミング

各対象の起動や停止などのライフサイクルの変化が発生した場合に呼び出される。 具体的なライフサイクルについては、各フックの仕様を参照。

フックのパラメータ

フックには、次のパラメータが渡される。

引数 内容 具体値
第1引数 object VM名, ネットワーク名, など
第2引数 operation "started", "stopped", など
第3引数 sub-operation "begin", "end", など
第4引数 extra argument "SIGHUP", など

各引数について、「値なし」の場合は"-"が引き渡される。 具体的な値の種類と組み合わせは、各フックの仕様を参照。

第1引数objectの詳細情報

第1引数objectの詳細情報は、標準入力からXML形式で取得できる。 例えば、/etc/libvirt/hooks/networkには以下のようなXMLが引き渡されてきた。

<hookData>
  <network>
    <name>default</name>
    <uuid>2cc82eef-28f4-463f-9324-a0f0f3d0578c</uuid>
    <forward mode='nat'/>
    <bridge name='virbr0' stp='on' delay='0'/>
    <mac address='52:54:00:3e:72:43'/>
    <ip address='192.168.8.1' netmask='255.255.255.0'>
      <dhcp>
        <range start='192.168.8.128' end='192.168.8.254'/>
      </dhcp>
    </ip>
  </network>
</hookData>

フックの戻り値

フックのスクリプトが戻り値として0を返却すると成功、0以外を返却すると失敗と判定される。

ログ出力

フックのスクリプト内で標準エラーに出力した内容は、ログファイルに記録される。

実験

フックの挙動を確認するため、引数と標準入力をダンプするスクリプトを作成してみた。 どのフックも処理内容は同じなので、スクリプトはひとつだけ作成して、シンボリックリンクを張ることにした。

フックのスクリプトの実体は/etc/libvirt/hooks/hook-logger.pyとし、次の内容で作成。

#!/usr/bin/python3

import datetime
import fcntl
import os
import os.path
import sys

logfile = os.path.join(os.path.dirname(os.path.abspath(__file__)), "log.txt")

with open(logfile, "a+") as f:
    fcntl.flock(f, fcntl.LOCK_EX)
    try:
        f.write(str(datetime.datetime.today()) + " " + str(sys.argv) + "\n")

        cr = True
        for line in sys.stdin:
            cr = line.endswith("\n")
            f.write(line)
        
        if not cr:
            f.write("\n")

    finally:
        fcntl.flock(f, fcntl.LOCK_UN)

このスクリプトに実行権限を付与して、各フックとしてシンボリックリンクを作成する。

$ sudo chmod a+x hook-logger.py
$ sudo ln -s hook-logger.py daemon
$ sudo ln -s hook-logger.py qemu
$ sudo ln -s hook-logger.py lxc
$ sudo ln -s hook-logger.py libxl
$ sudo ln -s hook-logger.py network

各対象のライフサイクルに変化があると、ファイル/etc/libvirt/hooks/log.txtに、例えば次のような情報が出力される。

2019-02-17 05:15:12.414193 ['/etc/libvirt/hooks/daemon', '-', 'shutdown', '-', 'shutdown']
2019-02-17 05:16:01.398895 ['/etc/libvirt/hooks/daemon', '-', 'start', '-', 'start']
2019-02-17 05:16:04.189531 ['/etc/libvirt/hooks/network', 'default', 'start', 'begin', '-']
<hookData>
  <network>
    <name>default</name>
(略)
2019-02-17 05:16:05.459755 ['/etc/libvirt/hooks/network', 'default', 'started', 'begin', '-']
<hookData>
  <network>
    <name>default</name>
(略)
2019-02-17 05:34:23.456336 ['/etc/libvirt/hooks/qemu', 'my-vm', 'prepare', 'begin', '-']
<domain type='kvm' id='2'>
  <name>my-vm</name>
  <uuid>8fee996e-99c0-4334-97be-07abe2d9e089</uuid>
(略)
2019-02-17 05:34:23.919914 ['/etc/libvirt/hooks/network', 'default', 'plugged', 'begin', '-']
<hookData>
  <interface type='network'>
    <mac address='52:54:00:a0:91:82'/>
(略)

参考