業務で使用している OpenStack のプライベートクラウド環境では、 LDAP 設定や共通で使うパッケージ/ユーザが入ったイメージを Packer で作り、新規にサーバを構築する際にそのイメージから VM を立ち上げています。一旦イメージを作ってしまえば、それ以降立ち上げる VM は同じ設定が入った状態になっているので、その後の運用方法を統一できて良いですね。

しかし、管理対象に素の cloud-image から立ち上げた VM や物理サーバが混ざってくるとちょっと厄介です。 Chef や Ansible などの他のプロビジョニングツールを適用する前に、 Packer で行ったのと同じ変更を適用しておきたいケースが出てきました。物理サーバのためだけにレシピを書き足しメンテナンスするのもだるい。

"provisioners" で使っているシェルスクリプトを、対象のサーバに全部アップロードして直接実行してしまえば良いのですが、手運用が入ってしまうのはちょっとイケてない。

  "provisioners": [
    {
      "type": "shell",
      "execute_command": "sudo -S sh '{{.Path}}'",
      "override": {
        "openstack": {
          "scripts": [    ## <- ここで指定しているスクリプト達な
            "./scripts/01_base.sh",
            "./scripts/02_create_user.sh",
            ...

Packer でイメージを作る時は builder で VM 立ち上げ → スクリプトを適用 → イメージ化 が順に行われるわけですが、最後のイメージ化だけ行わなければ、 Packer の枠組みを使いつつ既存のサーバも同じ状態に揃えられるんじゃね? と思いました。つまり、Packer をプロビジョニングツール的に動作させ、 "provisioners" のスクリプトを指定したサーバに適用する道具として使うというイメージです。

前置きが長くなりました。 Packer の Null Builder とやらを使うと、想定した動作が実現できました。

Null Builder

https://www.packer.io/docs/builders/null.html

指定したサーバに SSH で入って provisioners を走らせ、成果物としてイメージは作らない builder のようです。今回の目的を達成できそうな雰囲気。本来の使用用途としてはデバッグ目的っぽいですが。

今回は物理サーバにスクリプトを適用し、 Packer イメージから立ち上げた VM と同様の設定が入った状態を作ってみます。

どういう構成にしたか

Packer で使っているファイルが入ったリポジトリに、VM イメージ作成用とは別に、物理サーバに provisioners を適用するための json ファイルを作成しました。builderstype"null" を指定します。

{
  "variables": {
    "ssh_host": "",
    "ssh_user": "",
    "ssh_key": ""
  },
  "builders": [
    {
      "type": "null",
      "communicator": "ssh",
      "ssh_pty": "true",
      "ssh_host": "{{user `ssh_host`}}",
      "ssh_username": "{{user `ssh_user`}}",
      "ssh_private_key_file": "{{user `ssh_key`}}"
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "execute_command": "sudo -S sh '{{.Path}}'",
      "override": {
        "null": {
          "scripts": [
            "./scripts/physical/01_base.sh",
            "./scripts/10_create_user.sh",
            "./scripts/11_hogehoge.sh",
            "./scripts/12_hugahuga.sh"
          ]
        }
      }
    }
  ]
}

複数サーバにに繰り返し適用する際に、一々 json を書き換えるのは面倒なので、SSH 接続に必要な情報はユーザ変数として外出しし Packer 実行時にコマンドライン引数で渡すようにしました。ループでホスト名を変えつつ実行することを想定しています。

実行時のコマンドはこんな感じ。

packer build \
  -var "ssh_host=prd-web01.example.com" \
  -var "ssh_user=ope_user" \
  -var "ssh_key=$HOME/.ssh/ope_user.pem" \
  physical_server.json

実行すると、指定したサーバに SSH 接続しに行き、 provisioners のスクリプトが順番に実行されていきます。 build 終了後はイメージ作成などは行われず Packer が終了します。

==> null: Provisioning with shell script: ./scripts/12_hugahuga.sh
Build 'null' finished.

==> Builds finished. The artifacts of successful builds are:
--> null: Did not export anything. This is the null builder

これで Packer の枠組みを使いつつ、 build コマンド実行で既存サーバにもスクリプト適用ができるようになりました。

今回用意したスクリプトは、元々 VM 作成時に 1 度だけ実行されることを想定したシェルスクリプトのため、冪等性は保証されていないものでした。実行されるスクリプトに状態チェックのコードが書かれていなければ、繰り返し packer build することは出来ないという点に注意しなければなりません。

元々はイメージ作成のためのツールなので、プロビジョニング目的だけに新たに Packer を導入するようなことはしないほうが良いかと思います。