terraform-provider-libvirtを使ってみた

# はじめに

Terraformを使ってローカル環境でVMをデプロイしてみたのでその記録を残します。利用したバージョンは下記になります。Ubuntu 20.04LTSでの記録になります。

```shell-session
$ terraform version
Terraform v0.14.5
+ provider registry.terraform.io/dmacvicar/libvirt v0.6.3
```

# Terraform?

公式ページ https://www.terraform.io/

Infrastructure as Code (IaC) のツールという紹介がよくされていると思います。Codeつまりテキストファイルに書かれたロジックに基づいてインフラを構築するCLIツールです。インフラと言っても当然物理をどうこうできるわけがありませんので、基本的にはクラウド上でのインフラ構築と考えればいいと思います。ローカル環境でKVMやdockerを使った構成を構築することもできます。

# Terraformのインストール

[ダウンロードページ](https://www.terraform.io/downloads.html)から、下記のプラットフォーム向けのTerraformを入手できます。

- macOS
- FreeBSD
- Linux
- OpenBSD
- Solaris
- Windows

クラウド環境から実行できる環境[Terraform Cloud](https://www.terraform.io/cloud)もあるようです。

Ubuntuであれば`sudo apt-get install terraform`でインストールすることもできます。他もおそらくパッケージシステムでインストールできると思います。

# どんなふうに記述して何を構築するのか

https://learn.hashicorp.com/terraform
ここを読んでおいてください、だと投げやりすぎるのですが、読んでいただけるのならそのほうが以下のしょぼくれた説明よりしっかりしているので、安心できるかと思います。

というわけで雰囲気を味わいたい人向けに、どんなふうに書いたら何が構築できるのか、その一例を紹介します。

## ディレクトリを用意する

Terraformの構築対象はディレクトリで管理されます。ここでは試しに`tf-test`というディレクトリを用意してみます。Codeをこのディレクトリに置くことになります。

```
mkdir tf-test
cd tf-test
```

## *.tf ファイルを用意する

Terraformが読んで実行するコードはsuffixを.tfとします。.tfの中身つまり記述言語は、Hashicorp Configuration Language (HCL) というそうです。

下記はオフィシャルのチュートリアルムービーから拝借しました。AWSのEC2インスタンスを構築する例になります。ここでは`main.tf`としておきます。suffixをみてファイルを見つけるので、`main`である必要は特にありません。

```HCL:main.tf
provider "aws" {
  access_key = "ACCESS_KEY"
  secret_key = "SECRET_KEY"
  region = "us-east-1"
}

resource "aws_instance" "example" {
  ami = "ami-2757f631"
  instance-type = "t2.micro"
}
```

見て解ると思いますが、これはこのままでは実際に動かすことはできませんので、眺めるだけにとどめておいてください。

## Terraformを動かしてみる

まず最初に一回だけ、初期化が必要です。

```
terraform init
```

tfファイルの内容が正しいかをチェックできます。(してもしなくても、間違っていれば実行時にエラーが出ます)

```
terraform validate
```

なにをどんなふうに変更することになるのかの確認もできます。

```
terraform plan
```

実行すると、上記例であればAWS EC2インスタンスが作成されます。

```
terraform apply
```

インスタンスを削除することもできます。

```
terraform destroy
```

# さまざまなprovider

例示したtfファイルにも書きましたが、Terraformでは`provider`を指定して、その`provider`のリソースとしてどういうものを用意するのかを記述していきます。例で提示した`"aws"`の他にも様々な`provider`が用意されています。

- `"aws"`
- `"azurerm"`
- `"google"`
- `"kubernetes"`
- ...

数が多くて紹介しきれませんので、https://registry.terraform.io/browse/providers こちらで検索してみてください。

providerは、下記に分類されます。

- HashiCorpのTerraform registryから提供されていて自動的にダウンロードして利用できるもの
    - official, verified, communityの3種に分かれます
- Terraform registryにないもの

Terraform registryで提供されているものは、`provider "それ"` と書けば利用できます。ローカルになければ自動でダウンロードされます。提供されていないものは手動でダウンロードするなどの作業が必要になります。

# terraform-provider-libvirt

[terraform-provider-libvirt](https://github.com/dmacvicar/terraform-provider-libvirt)は、名前から想像できますが、`libvirt`を使ってVM環境を構築するTerraform providerです。Terraform registryでは提供されていませんので、自動ダウンロードや自動インストールは失敗します。そのため、インストールと利用には少し手間がかかります。

## terraform-provider-libvirtのインストール

さまざまなインストール方法がありますので、公式ページの[README.md](https://github.com/dmacvicar/terraform-provider-libvirt/blob/master/README.md)を読んで、好きな方法でインストールしてください。

たとえばstable packageをUbuntuにインストールする場合は、README.mdからリンクを手繰って[この手順](https://software.opensuse.org/download/package?project=systemsmanagement:terraform&package=terraform-provider-libvirt)に従います。

インストール後、もうひと手間必要です。
https://github.com/dmacvicar/terraform-provider-libvirt/blob/master/docs/migration-13.md
こちらのドキュメントのとおりに、ファイルを移動させてください。Terraform 0.13以降のバージョンでは、これをやっておかないと`provider "libvirt"`が使えません。

# provider "libvirt" でVMを構築する

## 用意しておくもの

### ディスクイメージ

構築するVM用のディスクイメージが必要です。構築時に(`virt-install`によって)複製されます。なんでもいいのですが、ここではたまたま手元にあった`debian10-gns3.qcow2`を使うことにします。([ここ](https://docs-v1.gns3.com/appliances/debian10-min.html)から拾えます)

```shell
$ ls -lh
total 2.6G
-rw-rw-r-- 1 masaru masaru 2.6G Nov 11 16:07 debian10-gns3.qcow2
```

### libvirt, qemu

当然ですが`libvirt`や`qemu`をインストールしておく必要があります。操作しているアカウントを`libvirt`グループに追加するのもお忘れなく。

### `/etc/libvirt/qemu.conf` の編集

`security_driver = "none"` にしておかないと、VMの起動に失敗します。編集しておいてください。編集後は `sudo systemd restart libvirt-bin` を実行してください。グループ周りの反映もあるので一度リブートするのでもいいかと思います。

## tfファイルの編集

ディレクトリを作って、.tfファイルを編集します。(viである必要はもちろんありません)

```
mkdir libvirt-test
cd libvirt-test
vi main.cf
```

いきなり`provider`と書き始めたいところですが、.tfファイルの先頭におまじないが必要です。

```HCL:main.tf
terraform {
  required_version = ">= 0.13"
  required_providers {
    libvirt = {
      version = ">= 0.6.2"
      source = "dmacvicar/libvirt"
    }
  }
}
```

この記述によって、先ほど場所を移動した`terraform-provider-libvirt`を見つけることができます。これがない場合、Terraform registryに探しに行ってしまい、**`hashicorp/libvirt`がないんじゃが?** と言われエラーになってしまいます。

では続けていよいよ`provider`以降をつらつら書きます。

```HCL:main.tf
provider "libvirt" {
  uri = "qemu:///system"
}

resource "libvirt_volume" "os-image" {
  name = "testvm-disk.qcow2"
  source = "debian10-gns3.qcow2"
  format = "qcow2"
}

resource "libvirt_domain" "domain-testvm" {
  name = "debian"
  memory = 2048
  vcpu = 1

  disk { volume_id = libvirt_volume.os-image.id }

  network_interface {
    network_name = "default"
  }

  console {
    type = "pty"
    target_port = "0"
  }

  graphics {
    type = "spice"
    listen_type = "address"
    autoport = "true"
  }
}
```

ざっくり解説すると
- `pervider "libvirt"`でlibvirt providerの利用を宣言 (以降で`libvirt_`ごにょごにょというリソースが使えるようになる)
- `resource "libvirt_volume" "os-image"` でボリューム(ストレージ)を定義。
- `resource "libvirt_domain" "domain-testvm"`でドメイン(VMインスタンス)を定義。

最後に、このディレクトリに`debian10-gns3.qcow2`を置けば準備完了です。

## いよいよ実行

```
terraform init
terraform validate
terraform apply
```

`terraform apply` を実行すると、実行するか問い合わせがありますので `yes` と答えます。

<details><summary>実行ログ (たまたまですが裏で別の重いタスクを動かしてる関係で時間がかかっています)</summary><div>

```shell-session
$ terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of dmacvicar/libvirt from the dependency lock file
- Installing dmacvicar/libvirt v0.6.3...
- Installed dmacvicar/libvirt v0.6.3 (unauthenticated)

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
$ terraform validate
Success! The configuration is valid.
$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # libvirt_domain.domain-testvm will be created
  + resource "libvirt_domain" "domain-testvm" {
      + arch        = (known after apply)
      + disk        = [
          + {
              + block_device = null
              + file         = null
              + scsi         = null
              + url          = null
              + volume_id    = (known after apply)
              + wwn          = null
            },
        ]
      + emulator    = (known after apply)
      + fw_cfg_name = "opt/com.coreos/config"
      + id          = (known after apply)
      + machine     = (known after apply)
      + memory      = 2048
      + name        = "debian"
      + qemu_agent  = false
      + running     = true
      + vcpu        = 1

      + console {
          + source_host    = "127.0.0.1"
          + source_service = "0"
          + target_port    = "0"
          + type           = "pty"
        }

      + graphics {
          + autoport       = true
          + listen_address = "127.0.0.1"
          + listen_type    = "address"
          + type           = "spice"
        }

      + network_interface {
          + addresses    = (known after apply)
          + hostname     = (known after apply)
          + mac          = (known after apply)
          + network_id   = (known after apply)
          + network_name = "default"
        }
    }

  # libvirt_volume.os-image will be created
  + resource "libvirt_volume" "os-image" {
      + format = "qcow2"
      + id     = (known after apply)
      + name   = "testvm-disk.qcow2"
      + pool   = "default"
      + size   = (known after apply)
      + source = "debian10-gns3.qcow2"
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

libvirt_volume.os-image: Creating...
libvirt_volume.os-image: Still creating... [10s elapsed]
libvirt_volume.os-image: Still creating... [20s elapsed]
libvirt_volume.os-image: Still creating... [30s elapsed]
libvirt_volume.os-image: Still creating... [40s elapsed]
libvirt_volume.os-image: Still creating... [50s elapsed]
libvirt_volume.os-image: Still creating... [1m0s elapsed]
libvirt_volume.os-image: Still creating... [1m10s elapsed]
libvirt_volume.os-image: Still creating... [1m20s elapsed]
libvirt_volume.os-image: Still creating... [1m30s elapsed]
libvirt_volume.os-image: Still creating... [1m40s elapsed]
libvirt_volume.os-image: Still creating... [1m50s elapsed]
libvirt_volume.os-image: Still creating... [2m0s elapsed]
libvirt_volume.os-image: Still creating... [2m10s elapsed]
libvirt_volume.os-image: Creation complete after 2m18s [id=/var/lib/libvirt/images/testvm-disk.qcow2]
libvirt_domain.domain-testvm: Creating...
libvirt_domain.domain-testvm: Still creating... [10s elapsed]
libvirt_domain.domain-testvm: Creation complete after 15s [id=33e01c13-97a9-4ec0-ae7a-51853096ad94]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
masaru@tama:~/src/libvirt-test$ 
```
</div></details>

`terraform apply`を実行した後、実際にVMが起動しているか確認してみます。

```
$ virsh list
 Id   Name     State
------------------------
 37   debian   running

$ 
```

`virt-viewer`でコンソール画面と接続することができます。今回使ったイメージではbannerに表示がありますが`root/debian`あるいは`debian/debian`でログインできます。

## 削除

`terraform destroy` で構築した環境がまるっと削除されます。ディスクイメージも削除されますので、VMにログインしていろいろファイルを変更などしていてもすべてが無に帰します。ご注意ください。

## エラーで困ったら

`/var/lib/libvirt/qemu.conf`の編集を忘れていると、`terraform apply`の実行で最後の最後に`Permission denied`と言われてコケます。`/var/lib/libvirt/images`にアクセスできないのでこれかな? と思う方もいらっしゃるかわかりませんが、そこはそのままでOKです。`virsh pool-edit default`とかやればオーナーグループパーミッションどれも変更できますが、それでは解決しません。上で書いたとおり、`qemu.conf`を編集してください。

この状態で`terraform destroy`を実行しても半端にlibvirt domainが残っているので、次の`terraform apply`が失敗します。`terraform destroy`の後にdomainを削除することでこの問題を回避できます。

```
virsh undefine debian
```

# さいごに

Terraformには`module`という概念があって便利なのですが、これについては別途解説したいと思います。`terraform-provider-libvirt`のネットワーク周りについても解説できればと思います。