PackerでRIaaSのテンプレートイメージを管理する (VMware vSphereとOpenStackの事例紹介)

# はじめに

この記事は、[Rakuten Advent Calendar 2016](http://qiita.com/advent-calendar/2016/rakuten)の7日目 兼 [OpenStack Advent Calendar 2016](http://www.adventar.org/calendars/1739) の9日目の記事です。

楽天でサーバサイドのエンジニアをしていますtkakと申します。楽天社内には、RIaaSと呼ばれるプライベートクラウド環境があり、その上で多くのサービスが動いています。私が今いる部署では、Packerというツールを使って、RIaaSで使われているテンプレートイメージを管理しています。今回は、そのPackerの使い方などを紹介したいと思います。

# RIaaSとテンプレートイメージ管理

RIaaSは、VMware vSphereをベースにしたプラットフォームと、KVMをベースにしたOpenStackプラットフォームの２種類があります。使っているテンプレートイメージは、用途に合わせていくつか種類があります。

* OSの初期設定のみの"minimal"
* 楽天のインフラ環境用の設定が入った"base"
* DB向けの"db"

![mockup_image.png](https://qiita-image-store.s3.amazonaws.com/0/2725/923c0935-2b23-33d9-858d-0ccf873dec2d.png)

また、ローカル開発環境用にVagrant(VirtualBox)のイメージも用意しています。ローカル環境で開発したアプリケーションをRIaaSに持っていっても動くように、ベースの部分は極力設定を合わせるように作っています。

# Packerとは

Packerは、テンプレートイメージを自動生成・管理するためのコマンドラインツールです。Hashicorpが、OSSとして開発・メンテナンスしています。

昨今、様々なクラウド環境や仮想環境が存在していますが、より早くアプリケーションをデプロイするためには、ゴールデンイメージのような環境に合わせたテンプレートイメージの管理が必要です。ただ、取り扱う環境やOSの種類が増えるごとに、テンプレートイメージの管理は、煩雑になる傾向があります。そんな時、Packerを使うことで、様々なプラットフォームのテンプレートイメージを、一つの設定ファイルに集約し、かつビルド作業を自動化することができます。

弊社では、Packerを導入する前は、テンプレートイメージごとに手作業によるGUI作業と秘伝のスクリプトを駆使して、温かみのある運用をしていました。しかし、仮想環境やOSの種類が少ない間は、それでも運用できていたのですが、徐々に仮想環境や運用するOSが増えるにつれて、運用が回らなくなっていきました。そこで、Packerを導入し、Jenkinsと組み合わせることで、ほぼテンプレートイメージビルド作業を自動化することができました。さらに、コード化することでテンプレートイメージの内部設定を共有しやすくしたり、Pull requestによる改修フローができたりといった、Infrastructure as Codeの恩恵も得ることができました。

# テンプレートイメージをビルドする仕組み

では、実際のテンプレートイメージをビルドする仕組みを紹介したいと思います。ディレクトリ構成は以下のようにしています。

```
.
├── CHANGELOG.md
├── README.md
├── Vagrantfile
├── chef
│   ├── Berksfile
│   ├── Berksfile.lock
│   ├── client.rb
│   ├── cookbooks
│   └── node.json
├── http
│   ├── qemu
│   ├── virtualbox
│   └── vmware
├── openstack
├── rakuten-centos-6-x86_64.json
├── rakuten-centos-7-x86_64.json
├── rakuten-rhel-5-x86_64.json
├── rakuten-rhel-6-x86_64.json
├── rakuten-rhel-7-x86_64.json
├── scripts
│   ├── common
│   └── rhel
├── test
│   └── vsphere
└── vsphere
    ├── deploy.sh
    ├── target-v6.list
    └── target.list
```

JSONファイルがPackerの設定ファイルです。イメージの作成は、引数に設定ファイルを指定し、`packer build`というコマンドで行います。例えば、以下のようになります。

```
$ packer build rakuten-centos-7-x86_64.json
```

このコマンドを実行した後、しばらく待つとイメージが出来上がります。この時の処理の流れは、基本的には以下のようになっています。

1. OS ISOイメージのダウンロード
2. 仮想マシンの作成と起動
3. VNC経由でbootコマンドを実行し、OSインストールを開始(kickstart)
4. OSインストール完了後に再起動
5. SSH経由で任意のコマンドを実行
6. シャットダウン
7. イメージの配布

Packerはこれらの処理を自動的に行います。Packerの設定ファイルは、`builders`や`provisioners`、`post-processors`などのセクションに分かれていて、それぞれ次のような用途になっています。

```
{
  "builders": [
    ...
    ...
  ],
  "provisioners": [
    ...
    ...
  ],
  "post-processors": [
    ...
    ...
  ]
}
```

buildersは、仮想マシンの作成・起動を行います。上記のイメージビルドの流れでいうと1から4までが、buildersで取り扱う設定になります。AWSやAzure、GCP、DigitalOcean、OpenStack、Dockerなど、色々なプラットフォームをサポートしています。

provisionersは、起動した仮想マシンの中で、任意のコマンドやスクリプト、Chef、Ansibleなどを行います。上記のイメージビルドの流れでいうと5がprovisionersで取り扱う設定になります。

post-processorsは、ビルドが終わったマシンイメージを、tarに固めたり、どこかにアップロードしたりする処理を行います。上記のイメージビルドの流れでいうと7がpost-processorsで取り扱う設定になります。

より詳細な使い方などは、[公式サイト](https://www.packer.io/docs/)を参照してください。それでは、以下でVMware vSphereとOpenStack向けのビルド環境をそれぞれ紹介します。

## VMware vSphere向けのビルド環境

まず、VMware vSphere用のビルド環境ついて紹介します。全体の流れは次のようになります。

![mockup_vmware-iso-builder.png](https://qiita-image-store.s3.amazonaws.com/0/2725/2950a7b8-7a43-bec6-e25c-36b8597d99ba.png)

1. Gitにpushすると、web hookが行われ、Jenkinsのジョブが起動
2. packerを実行
    3. ISOをダウンロード&ESXiに転送
    4. ESXiにVMを作成&起動。ISOをマウント
    5. packerが内部的にhttpサーバを立てる(httpディレクトリ配下にあるks.cfgを公開)
    6. build_commandをVNCで流し込む
    7. kickstartによるOS自動インストール
    8. 再起動したら、provisionersでスクリプトを実行
    9. post-processorsで、配布用スクリプトを実行
        10. [ovftool](https://www.vmware.com/support/developer/ovf/)で、仮想マシンデータをローカルにダウンロード&いろいろとカスタマイズ
        11. [govc](https://github.com/vmware/govmomi/tree/master/govc)で、仮想マシンを各vCenterにばらまく
12. Hipchatに通知

### builders

VMware vSphere用のbuilders設定ファイルは以下のようにしています。

```json
{
  "builders": [
    {
      "name": "rhel-7.3-vmware-base",
      "type": "vmware-iso",
      "vm_name": "rakuten-rhel-7.3-x86_64-base",
      "headless": "true",
      "disk_size": "30720",
      "disk_type_id": "thin",
      "guest_os_type": "rhel7-64",
      "iso_url": "{{user `iso_base_url`}}/rhel-server-7.3-x86_64-dvd.iso",
      "iso_checksum": "120acbca7b3d55465eb9f8ef53ad7365f2997d42d4f83d7cc285bf5c71e1131f",
      "iso_checksum_type": "sha256",
      "ssh_username": "{{user `ssh_username`}}",
      "ssh_password": "{{user `ssh_password`}}",
      "ssh_wait_timeout": "30m",
      "shutdown_command": "shutdown -h now",
      "output_directory": "rhel-7.3",
      "remote_type": "esx5",
      "remote_host": "{{user `esxi_host`}}",
      "remote_username": "{{user `esxi_username`}}",
      "remote_password": "{{user `esxi_password`}}",
      "remote_datastore": "{{user `esxi_datastore`}}",
      "format": "ovf",
      "http_directory": "http",
      "boot_command": [
        "<esc><wait>",
        "vmlinuz initrd=initrd.img inst.geoloc=0 rd.driver.blacklist=dm-multipath net.ifnames=0 biosdevname=0 cxgb4.force_init=1 cxgb4.force_old_init=1 ip=eth0:dhcp ",
        "ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/vmware/rhel-7/ks.cfg",
        "<enter>"
      ],
      "vmx_data": {
        "cpuid.coresPerSocket": "1",
        "memsize": "2048",
        "numvcpus": "2",
        "virtualhw.version": "10",
        "ethernet0.present": "true",
        "ethernet0.startConnected": "true",
        "ethernet0.virtualDev": "e1000",
        "ethernet0.networkName": "{{user `esxi_network`}}"
      }
    },
    ...
    ...
}
```

buildersでは、使う環境に合わせて"type"を選択します。VMwareの場合、"vmware-iso"と"vmware-vmx"の２つが用意されています。以下のような違いがあります。

* "vmware-iso": 一から仮想マシンを作り、OSをインストールする(ビルドに時間がかかる)
* "vmware-vmx": すでにある仮想マシン(vmx)から新しい仮想マシンを作る

今のところは、ビルドに時間がかかりますが一から作ることができる"vmware-iso"をメインで使っています。

ここで注意したいのが、Packerの`vmware-iso`タイプは、vSphereではなくESXi用ということです。最初試した時にネットワーク設定をvSphereでしかサポートされていない"分散仮想スイッチ"に設定してしまい、いくら試してもネットワークが繋がらず、どハマりしたことがありました。ネットワーク設定は___"スタンダードネットワーク"___を指定しなければなりません（ESXiなんだから当たり前ではありますが・・・。）

あと、事前にESXiホストで、以下のコマンドを実行する必要があるので注意しましょう（公式ドキュメントに記載あり）

```
esxcli system settings advanced set -o /Net/GuestIPHack -i 1
```

これは、PackerがESXiに接続してコマンドを実行するために必要な物です。中で、実際にどんなコマンドを使っているかは、[こちら](https://github.com/mitchellh/packer/blob/master/builder/vmware/iso/driver_esx5.go)を見ればわかります。

また、VNC接続用にポート(デフォルトだと5900から6000)の解放も必要なので、適宜設定してください。VNC用のポートを追加するには、ESXiにログインしxmlファイルを変更する必要があります。詳細なやり方は以下を参考にしてください。

* [Enable VNC on VMware Deployments](https://platform9.com/support/enable-vnc-on-vmware-deployments/)

### provisioners

provisionersでは、スクリプトとChefを使っています。スクリプトでは、仮想環境ごとに異なる設定(VMware toolsのインストールなど)や、カーネルアップデート、Chefクライアントのインストールなどを行なっています。Chefの方では、楽天インフラ独自の設定を行っています。ただ、PackerからChefを使う際は、Packerで用意されている"chef-client"や"chef-solo"といったタイプは使わずに以下のようにコマンドから実行するようにしています。(その方が、失敗した時などにデバッグがしやすいため)

```json
...
...
  "provisioners": [
    ...
    ...
    {
      "only": [
        "centos-7.2.1511-virtualbox-base",
        "centos-7.2.1511-vmware-base",
        "centos-7.2.1511-vmware-base-v6",
        "centos-7.2.1511-openstack-base"
      ],
      "source": "chef",
      "destination": "/var/tmp",
      "type": "file"
    },
    {
      "only": [
        "centos-7.2.1511-virtualbox-base",
        "centos-7.2.1511-vmware-base",
        "centos-7.2.1511-vmware-base-v6",
        "centos-7.2.1511-openstack-base"
      ],
      "inline": [
        "/usr/local/chef/bin/chef-client -z -c /var/tmp/chef/client.rb -j /var/tmp/chef/node.json",
        "rm -rf /var/tmp/chef"
      ],
      "type": "shell",
      "override": {
        "centos-7.2.1511-virtualbox-base": {
          "execute_command": "echo 'vagrant' | {{.Vars}} sudo -S -E bash '{{.Path}}'"
        },
        "centos-7.2.1511-openstack-base": {
          "execute_command": "{{.Vars}} sudo -E bash '{{.Path}}'"
        }
      }
    },
    ...
    ...
```

また、"override"パラメータを使うことで、builderごとにパラメータを上書きすることが出来るので便利です。builderごとにprovisionerを書く必要がなくなります。

### post-processors

post-processorsでは、出来上がったマシンイメージのVMXファイルをカスタマイズしたり、各vCenterに配布したりする処理を行っています。配布するにあたって、一度マシンイメージをダウンロードする必要があるのですが、ovftoolというコマンドラインツールを使っています。

また、VMXファイルのカスタマイズとマシンイメージの配布は、govcというツールを使っています。
結構、力業でやっているのでpackerの設定ファイルに書くのではなく、スクリプトの方で行なっています。

```json
  ...
  ...
  "post-processors": [
    ...
    ...
    {
      "only": [
        "rhel-7.3-vmware-minimal",
        "rhel-7.3-vmware-base",
        "rhel-7.3-vmware-minimal-v6",
        "rhel-7.3-vmware-base-v6"
      ],
      "type": "shell-local",
      "inline": [
          "vsphere/deploy.sh ${PACKER_BUILD_NAME}"
      ]
    }
  ]
}
```

## OpenStack向けのビルド環境

次にOpenStack用のビルド環境について紹介します。全体像は次のようになります。

![mockup_qemu-builder.png](https://qiita-image-store.s3.amazonaws.com/0/2725/562624de-c793-8129-9b40-3ebeb8ee3548.png)


1. Gitにpushすると、web hookが行われ、Jenkinsのジョブ(slave)が起動
2. packerを実行(QEMU+KVMサーバにjenkins slaveとpackerをインストール)
    3. ISOをダウンロード
    4. QEMU+KVM上にVMを作成&起動。ISOをマウント
    5. packerが内部的にhttpサーバを立てる(httpディレクトリ配下にあるks.cfgを公開)
    6. build_commandをVNCで流し込む
    7. kickstartによるOS自動インストール
    8. 再起動したら、provisionersでスクリプトを実行
9. glanceコマンドで、qcow2ファイルを各OpenStackに配布
10. Hipchatに通知

### builders

```json
...
    {
      "name": "centos-7.2.1511-qemu-minimal",
      "type": "qemu",
      "qemuargs": [
        [ "-m", "2048m" ]
      ],
      "iso_url": "{{user `iso_base_url`}}/CentOS-7-x86_64-DVD-1511.iso",
      "iso_checksum": "4c6c65b5a70a1142dadb3c65238e9e97253c0d3a",
      "iso_checksum_type": "sha1",
      "output_directory": "images",
      "format": "qcow2",
      "headless": true,
      "accelerator": "kvm",
      "http_directory": "http",
      "ssh_port": 22,
      "ssh_username": "{{user `ssh_username`}}",
      "ssh_password": "{{user `ssh_password`}}",
      "ssh_wait_timeout": "10000s",
      "disk_size": 10240,
      "shutdown_command": "shutdown -P now",
      "vm_name": "centos-7.2.1511",
      "boot_wait": "4s",
      "boot_command": [
        "<tab> text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/centos-7/ks.cfg<enter><wait>"
      ]
    },
    ...
    ...
```

OpenStack用と言っても、実際はQEMU+KVM向けの設定です。"qemu"タイプで`packer build`を実行すれば、QEMUのイメージが作成できます。

### provisioners

provisionersでは、スクリプトとChefを実行するようにしています。ほとんどvSphereと一緒なので、割愛します。

### post-processors

post-processorsでは、マシンイメージの整形と配布を行っています。出来上がったテンプレートイメージには、MACアドレスなどの邪魔な情報が残っているため、[virt-sysprep](https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/7/html/Virtualization_Deployment_and_Administration_Guide/sect-Guest_virtual_machine_disk_access_with_offline_tools-Using_virt_sysprep.html)コマンドを実行して、それらを削除します。

```bash
sudo -E virt-sysprep -a images/${image_name}
```

その後、`glance`コマンドでOpenStackにイメージを配布しています。

```bash
$ glance image-create --name ${image_name}-`date "+%Y%m%d"` \
  --disk-format qcow2 --container-format bare \
  --progress --file images/${image_name}
```



# 終わりに

RIaaSで使われているテンプレートイメージのビルド環境について簡単に紹介しました。Packerを導入したことで、煩わしいビルド作業を自動化し、コード化することで属人性を減らすことができました。今後としては、現状一回のビルドに１時間くらいかかってしまっているので、ビルド時間を短縮させたいです。また、他の組織でもPackerを使っていけるように布教活動なども行なっていきたいと思います。

VMware周りは色々と落とし穴が多かったので、本エントリが今後VMwareでPackerを導入する方のお役に立てれば幸いです。

# 参考文献

* https://www.packer.io/
* https://thinkit.co.jp/story/2015/04/13/5875
* https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/6/html/Installation_Guide/ch-kickstart2.html
* https://github.com/mitchellh/packer
* http://qiita.com/skuroki@github/items/3afd4adfa798acf35093