2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Terraform + libvirt + Open vSwitch

Last updated at Posted at 2021-07-02

はじめに

ネットワークのテストを実環境でやるのは大変なので、仮想環境で基本の確認をやるという方はそれなりにいらっしゃると思います。

クラウドの世界では割と有名と思われるIaC(Infrastrucure as Code)を提供するTerraformをローカル環境に適用してテスト環境を構築していたのですが、いろいろあちこち罠に引っかかったので備忘録を兼ねて解説したいと思います。

Terraform + libvirt

Terraformは作成したいリソースをresourceとして定義しておき、terraform applyで記述したとおりにリソースを作成あるいは変更してくれます。通常はAWSやAzure, GCPといったクラウドのリソースを指定しますが、libvirtを操作することでVMを作ったり消したりもできます。

libvirtを操作するlibvirt-provider-libvirtですが、現在はTerraform registryに登録されてるので、それほどハマることなく導入できます。ディスクやネットワークなど指定なしなのでOSをブートできませんが、めっちゃシンプルな記述例は下記です。

example.tf
resource "libvirt_domain" "example" {
  name = "example0"
  memory = 2048
  vcpu = 2
}

複数のVMをつなげる

libvirt_networkでネットワークリソース(ブリッジ)を定義して、libvirt_domainnetwork_interfaceに指定することで、そのドメイン(VM)がネットワークに参加します。複数のドメインが同じネットワークに参加すれば、VM同士での通信が可能になります。下記は、やっぱりOS起動などできませんが、VM1VM2のあいだをtest_bridgeでつなぐ記述です。

test.tf
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 applyterraform destroyでリソース操作する際に、任意のコマンドを実行させることができます。provisionerです。

provisioner "local-exec"で、ローカル環境でコマンドを実行できます。command=で実行するコマンドラインを指定します。他に何も指定しなければ、リソース作成時に実行されます。when = destroyを書いておくと、その中のコマンドラインはリソース削除時に実行されます。

通常、provisionerはなんらかのリソース(たとえばlibvirt_network)の作成・削除にともなって実行されますが、とくに関連リソースがない場合は resource "null_resource"を指定する方法が公式から案内されています。

net.tf
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) を使います。

ovs-port.xsl
<?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の中で指定します。

main.tf
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.xslnet.tfを用意してterraform applyすることで、VM1VM2の間をOVS bridge br0でつなぐことができます。(ネットワーク以外の設定はいろいろ足してください)

おわりに

いろいろ調べてみたのですが、「OVS bridge作れるようにしてほしい。例えばlibvirt_networkmode="ovs"」「XSLTで<virtualport type="openvswitch"/>指定できるようになったからこのissueはclose」と微妙に噛み合わない上に具体例がなくて、しばらく頭を抱えていました。

けっきょくどこにも答えがなくて自力でゴリゴリやったので、もしかするともっとスマートな解決方法があるかも知れません。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?