KVM (libvirt) で cloud-init を使って IP アドレスとホスト名を自動設定

libvirt な KVM のホストに、ベースとなるイメージからゲストをダバーっと作りたかったんですけど、IP アドレスやホスト名の設定を個別にやるのは流石にしんどいと思ったので cloud-init を使ってみたメモ。

最初は libvirt のネットワークの DHCP でどうにかしようと思いましたが、ゲストのネットワークをブリッジにしたかったので諦めました。

## ベースイメージの cloud-init

ベースイメージのゲストに cloud-init をインストールします。

```sh
yum -y install cloud-init cloud-utils-growpart
```

次のように設定ファイルを作成します。

```sh
cp -a /etc/cloud/cloud.cfg{,.orig}

cat <<EOS | sudo tee /etc/cloud/cloud.cfg
datasource_list:
  - NoCloud

cloud_init_modules:
  - write-files
  - bootcmd
  - growpart
  - resizefs
  - set_hostname
  - update_hostname

cloud_config_modules:
  - timezone
  - runcmd

cloud_final_modules:
  - scripts-per-once
  - scripts-per-boot
  - scripts-per-instance
  - scripts-user
  - final-message

system_info:
  distro: rhel
EOS
```

cloud-init のモジュールは使いそうなやつだけ有効にしています。また、データソースには [NoCloud](http://cloudinit.readthedocs.io/en/latest/topics/datasources.html#no-cloud) を指定します。

終わったらゲストはシャットダウンしておきます。

```sh
shutdown -h now
```

## ゲストを作成（ひとつ）

ゲストを 1 つだけ作成してみます。

ベースイメージからスナップショットで新たしいイメージを作成します。

```sh
lvcreate -s /dev/vg1/base -n ore-no-server -L 5G
```

メタデータファイルを作成します。とりあえず `instance-id` だけあればなんとかなりました。

```sh
cat <<EOS> meta-data
instance-id: $(uuidgen)
EOS
```

ユーザーデータファイルを作成します。cloud-init で I/F を設定するうまい方法がわからなかったので ifcfg-eth0 を作成して ifdown/ifup しています。

```sh
cat <<EOS> user-data
#cloud-config

hostname: ore-no-server
timezone: "Asia/Tokyo"

write_files:
  - path: /etc/sysconfig/network-scripts/ifcfg-eth0
    owner: root:root
    permissions: '0644'
    content: |
      TYPE=Ethernet
      DEVICE=eth0
      ONBOOT=yes
      BOOTPROTO=none
      IPADDR=192.168.8.10
      PREFIX=24
      GATEWAY=192.168.8.1
      DNS1=192.168.8.1
      DEFROUTE=yes
      PEERDNS=yes
      PEERROUTES=yes
      IPV4_FAILURE_FATAL=no
      IPV6INIT=no

bootcmd:
  - ifdown eth0
  - ifup eth0
EOS
```

これら 2 つのファイルを含む ISO を作成します。

```sh
genisoimage -output ore-no-server.iso -volid cidata -joliet -rock user-data meta-data
```

ゲストを作成します。このとき↑で作成した ISO もアタッチします。

```sh
virt-install \
  --name ore-no-server \
  --disk path=/dev/vg1/ore-no-server \
  --disk path="$PWD/ore-no-server.iso,bus=ide,device=cdrom" \
  --network bridge=br1 \
  --hvm --virt-type kvm \
  --vcpus 1 --ram 1024 --arch x86_64 \
  --os-type linux --os-variant rhel7 \
  --boot hd --graphics none --serial pty --console pty \
  --import --noreboot
```

ゲストを開始します。

```sh
virsh start ore-no-server
```

IP アドレスやホスト名の設定ができているか確認します。

```sh
ssh root@192.168.8.10 uname -n
```

意図した通りに表示されれば成功です。

## ゲストの作成（たくさん）

ゲストをたくさんダバーっと作成してみます。

作成するゲストのホスト名と IP アドレスの一覧を配列に入れます。

```sh
guests=(
  sv01:192.168.8.11
  sv02:192.168.8.12
  sv03:192.168.8.13
  sv04:192.168.8.14
  sv05:192.168.8.15
  sv06:192.168.8.16
  sv07:192.168.8.17
  sv08:192.168.8.18
)
```

イメージを作成します。

```sh
for guest in "${guests[@]}"; do
  name=${guest%:*}
  lvcreate -s /dev/vg1/base -n "$name" -L 5G
done
```

メタデータとユーザーデータの ISO を作成します。

```sh
for guest in "${guests[@]}"; do
  addr=${guest#*:}
  name=${guest%:*}

  cat <<EOS> meta-data
instance-id: $(uuidgen)
EOS

  cat <<EOS> user-data
#cloud-config

hostname: $name
timezone: "Asia/Tokyo"

write_files:
  - path: /etc/sysconfig/network-scripts/ifcfg-eth0
    owner: root:root
    permissions: '0644'
    content: |
      TYPE=Ethernet
      DEVICE=eth0
      ONBOOT=yes
      BOOTPROTO=none
      IPADDR=$addr
      PREFIX=24
      GATEWAY=192.168.8.10
      DNS1=192.168.8.1
      DEFROUTE=yes
      PEERDNS=yes
      PEERROUTES=yes
      IPV4_FAILURE_FATAL=no
      IPV6INIT=no

bootcmd:
  - ifdown eth0
  - ifup eth0
EOS

  genisoimage -output "$name.iso" -volid cidata -joliet -rock user-data meta-data
done
```

ゲストを作成します。

```sh
(
  set -eux
  for guest in "${guests[@]}"; do
    name=${guest%:*}
    virt-install \
      --name "$name" \
      --disk path="/dev/vg1/$name" \
      --disk path="$PWD/$name.iso,bus=ide,device=cdrom" \
      --network bridge=br1 \
      --hvm --virt-type kvm \
      --vcpus 1 --ram 1024 --arch x86_64 \
      --os-type linux --os-variant rhel7 \
      --boot hd --graphics none --serial pty --console pty \
      --import --noreboot

    virsh start "$name"
  done
)
```