PackerでのイメージビルドをCircleCIで実行する

PackerでEC2のAMIをビルドするのはともかく、どこで誰がビルドするのかは悩ましいポイントです。 今回はみんな大好きCircleCIでAMIのビルドとAMI IDの管理する方法を考えてみました。

流れ

  1. GithubのPRを出す
  2. CirleCIで packer validate を実行する
  3. テストにパスしたら実際にPRをマージ
  4. CircleCIが packer build を実行する
  5. ビルドに成功したら作成されたAMI IDをリポジトリ内の images.yml にAMI IDを追記し、Githubへpushする

マージする度にビルドを実行する必要はないため、 作成したAMIのIDを images.yml に保存し、次回以降はすでにIDの記載がある場合は packer build を実行しないようにしています(そのIDが最新かどうかは確認しない)。

テンプレートを編集したあと、同時にビルドを実行したい場合はID部分を削除することで ビルドを実行するようにします。

今回はRubyでこの処理を自動化する packer build のラッパーを書きました。

実装例

https://github.com/nabeken/circleci-packer-example

ディレクトリ構成

https://github.com/chef/bento 同様に packer ディレクトリ以下にテンプレートとスクリプト を用意しました。

CI用のスクリプトは ci ディレクトリ以下へ用意しています。

circle.yml

特に特別なことはしていませんが、 dependenciesセクションでpackerのバイナリをキャッシュしています。

testは packer/*.json に対して packer validate を実行しています(ci/packer-validate.sh)。

deploymentセクションでは2つのスクリプトを実行しています。

packer-ami.rb

今回のメインです。

packerでAMIをビルドするとログの最後にAMI IDが出力されます。 packer-ami.rbpacker build を実行し、 ビルドが成功した場合のみログからAMI IDを取り出し、images.yml を更新するラッパースクリプトです。

例: 引数にはテンプレート名(ファイル名から .json を取ったもの)を与えて実行します。

$ ./ci/packer-ami.rb ec2-ubuntu-14.04-docker
// ...通常のPackerのログ...
==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:
ap-northeast-1: ami-9201f992

$ cat images.yml
---
ec2-ubuntu-14.04-docker: ami-9201f992

push_to_github.sh

images.yml に差分が発生している場合はマージ先へpushするだけのスクリプトです。

作業例

AMIにGoをインストールしてみます。作業前の images.yml は上の状態です。

まず、PRを作ります。 ここではテンプレートでAMI名を修正(重複しているとエラーになる)し、Goをインストールする処理を追加しています。 最後にビルドをトリガーするため、 images.yml からIDを削除しました。

CircleCIでテスト が通ったのを確認し、マージします。

https://circleci.com/gh/nabeken/circleci-packer-example/4 でマージ後のdeploymentとしてビルドの実行と images.ymlの更新が確認できます。

今後の課題とか

Dockerfileを使ってDockerコンテナをビルドしているとあるプロジェクトでは、 タグにGitのコミットIDを使うことで、CIする時にタグがDockerHubに存在しなければ自動的にリビルドし、 pushする運用をしています。こうすることでチーム内で誰かがビルド職人になることを防いでいます。

同じようなことを今回にも適用するとすると、AMIのタグにGitのコミットIDなどから生成した識別子を付与すれば images.yml なしに 運用できそうです。

あと、複数のリージョンを扱う場合ももうすこし工夫が必要だと思われますが、現状は必要ないのでこのままになっています。