はじめに
ネットワークのテストを実環境でやるのは大変なので、仮想環境で基本の確認をやるという方はそれなりにいらっしゃると思います。
クラウドの世界では割と有名と思われるIaC(Infrastrucure as Code)を提供するTerraformをローカル環境に適用してテスト環境を構築していたのですが、いろいろあちこち罠に引っかかったので備忘録を兼ねて解説したいと思います。
Terraform + libvirt
Terraformは作成したいリソースをresource
として定義しておき、terraform apply
で記述したとおりにリソースを作成あるいは変更してくれます。通常はAWSやAzure, GCPといったクラウドのリソースを指定しますが、libvirtを操作することでVMを作ったり消したりもできます。
libvirtを操作するlibvirt-provider-libvirtですが、現在はTerraform registryに登録されてるので、それほどハマることなく導入できます。ディスクやネットワークなど指定なしなのでOSをブートできませんが、めっちゃシンプルな記述例は下記です。
resource "libvirt_domain" "example" {
name = "example0"
memory = 2048
vcpu = 2
}
複数のVMをつなげる
libvirt_network
でネットワークリソース(ブリッジ)を定義して、libvirt_domain
のnetwork_interface
に指定することで、そのドメイン(VM)がネットワークに参加します。複数のドメインが同じネットワークに参加すれば、VM同士での通信が可能になります。下記は、やっぱりOS起動などできませんが、VM1
とVM2
のあいだをtest_bridge
でつなぐ記述です。
resource "libvirt_network" "test_bridge" {
mode = "none"
dns { local_only = true }
}
resource "libvirt_domain" "VM1" {
name = "VM1"
memory = 2048
vcpu = 2
network_interface {
network_name = "test_bridge"
}
}
resource "libvirt_domain" "VM2" {
name = "VM2"
memory = 2048
vcpu = 2
network_interface {
network_name = "test_bridge"
}
}
だいたいはこれでうまくいきます。
うまく通信できない?
仮想環境あるあるの話です。
昔(といっていいでしょう)流行っていたOpenFlowのようなものでは、どんなMACアドレスのパケットも処理できて、自由にMACアドレスを変更してパケット送信できます。
しかし、仮想環境における通常の仮想ネットワークは外部からMACアドレスの情報などわかることから、無駄な通信をさせないために、想定外のMACアドレスのパケットは全部はじくような動作をすることが多いのです。そのためOpenFlowのテストを仮想環境で実施してみると、うまくいかないことがありました。
実はSONiCのVM版 (SONiC-VS)が、この問題に引っかかってうまく他のホストとBGPセッションを張ることができなくて、しばらく原因がわからず頭を抱えていました。GNS3で組んだらあっさりセッションが張れて、もしかして……と。
通信の問題の解決方法
うまく通信させるにはどうすればいいか。
VMとVMをつなぐものがパケットを素通しにするブリッジ、物理機器で言えば昔あったバカHUB相当の動作をしてくれればいいのですが、このような動作をさせるのによく使われるのがOpen vSwitchです。2つのVMをつなぐとケーブルのように動作します。
libvirt + Open vSwitch
Open vSwitchの公式ドキュメントがあります。
https://docs.openvswitch.org/en/latest/howto/libvirt/
サマリーだけ書くと
-
ovs-vsctl
でOVS bridgeを作成する - libvirt domain作成用のXML内の
<interface>
の中に<virtualport type="openvswitch"/>
を入れる
となります。ん? libvirtでXML書いてOVS bridge作ったりできないの?
Terraform + libvirt + open vSwitch
libvirtからOVS bridgeを作成することは、できません。あらかじめOVS bridgeを作成しておいて、それをlibvirtで指定することになります。
libvirt_network
でなんとかならないか?
結論から言えば、失敗。
OVS公式で書かれていたように手動でovs-vsctl
でブリッジを作っておいて、mode = "bridge"
で指定する方法を試してみましたが、うまく動きませんでした。
provisioner
だッ!
libvirt_network
を使うのはあきらめます。
terraform apply
とterraform destroy
でリソース操作する際に、任意のコマンドを実行させることができます。provisionerです。
provisioner "local-exec"
で、ローカル環境でコマンドを実行できます。command=
で実行するコマンドラインを指定します。他に何も指定しなければ、リソース作成時に実行されます。when = destroy
を書いておくと、その中のコマンドラインはリソース削除時に実行されます。
通常、provisionerはなんらかのリソース(たとえばlibvirt_network
)の作成・削除にともなって実行されますが、とくに関連リソースがない場合は resource "null_resource"
を指定する方法が公式から案内されています。
resource "null_resource" "net" {
provisioner "local-exec" {
command = "sudo ovs-vsctl add-br br0"
}
provisioner "local-exec" {
when = destroy
command = "sudo ovs-vsctl del-br br0"
}
}
XSLT!
実はふつうに記述するだけでは<virtualport type="openswitch"/>
を指定することができません。ではどうやって指定するのか? XMLを加工することができるプログラミング言語XSLT (XSL Transform) を使います。
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/domain/devices/interface[@type='bridge']">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
<virtualport type="openvswitch"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
ややこしいので解説しませんが、ざっくりいうと、<domain><devices><interface type="bridge">'
の内側、最後に<virtualport type="openvswitch"/>
を追加する処理になります。
処理を実行するのに xsltproc
というプログラムが必要です。apt-get install
しておきましょう。
libvirt_domain
での指定方法
XSLTで書かれた処理を、libvirt_domain
の中で指定します。
resource "libvirt_domain" "VM1" {
name = "VM1"
network_interface {
bridge = "br0"
}
xml {
xslt = file("ovs-port.xsl")
}
}
resource "libvirt_domain" "VM2" {
name = "VM2"
network_interface {
bridge = "br0"
}
xml {
xslt = file("ovs-port.xsl")
}
}
上記のmain.tf
と、ovs-port.xsl
、net.tf
を用意してterraform apply
することで、VM1
とVM2
の間をOVS bridge br0
でつなぐことができます。(ネットワーク以外の設定はいろいろ足してください)
おわりに
いろいろ調べてみたのですが、「OVS bridge作れるようにしてほしい。例えばlibvirt_network
でmode="ovs"
」「XSLTで<virtualport type="openvswitch"/>
指定できるようになったからこのissueはclose」と微妙に噛み合わない上に具体例がなくて、しばらく頭を抱えていました。
けっきょくどこにも答えがなくて自力でゴリゴリやったので、もしかするともっとスマートな解決方法があるかも知れません。