読者です 読者をやめる 読者になる 読者になる

SideCI TechBlog

SideCIを作っているアクトキャットのエンジニアによる技術ブログです。

PackerのAMI手動管理を卒業するプラグインを作ってみた

こんにちは、家から捕まえられるポケモンだけを捕まえて僕のポケモンGoは終了しました。@wata727です。

f:id:sideci-dev:20160817154928p:plain

弊社ではインフラ自動化の一環として、AMIの作成にPackerを利用しています。PackerはAMIを作るまでは自動化してくれるものの、作成されたAMIは残り続け、使わなくなった古いAMIは手動で削除しなくてはいけません。
この点に関して、公式*1では、

Packer only builds images. It does not attempt to manage them in any way. After they're built, it is up to you to launch or destroy them as you see fit. If you want to store and namespace images for easy reference, you can use Atlas by HashiCorp.

と述べており、Atlasを使ってくれという立場です。とはいえ、そこまで大げさな話でもないし、毎回手動で削除しないといけないのは面倒臭い、そうだ、Packerにはプラグインの仕組みがあるじゃないか、プラグインを作ろう、となったのが今回の経緯です。

Packerプラグインの仕組み

PackerはGo言語で作られており、本体は単独で実行可能なバイナリとして提供されています。 Packerのプラグインも同様にGo言語で作成し、単独のバイナリとして提供されます。内部的には、RPCを使ってPacker本体とやり取りをすることで、独自の処理を追加することができます。 詳しくは公式のドキュメントに書いてありますが、大まかに言うと以下のような処理をmainに書いて、自作プラグインの処理を登録します。

package main

import (
    "github.com/mitchellh/packer/packer/plugin"
    "github.com/yourname/packer-type-name
)

func main() {
    server, err := plugin.Server()
    if err != nil {
        panic(err)
    }

    server.RegisterPostProcessor(new(yourplugin.PostProcessor))
    server.Serve()
}

github.com/mitchellh/packer/packer/pluginがPackerのプラグインを作る上で必要なものをすべて持っているので、基本的にはこれをimportすれば動作させることができます。 後は追加したい処理をプラグインのインターフェイスを満たすようにゴリゴリ実装します。プラグインの種類はいろいろありますが、今回はAMIを作成したタイミングで、古いAMIを削除できれば良いので、最後に処理を挟むpost-processorプラグインを選択しました。

作ったもの

で、完成したものがこちらになります。

名前が異常に長いですが、packer-post-processorまでは命名規則なのと、他のpost-processor、amazon-importなどにあわせた結果、こんな名前になりました。もうちょっといい名前があったかもしれません...

インストール

ビルド済みのバイナリをダウンロードしてきて~/.packer.d/pluginsに配置するだけで動作します。 最初はpluginsディレクトリが無いこともあるので、その場合には作ってください。

$ wget https://github.com/wata727/packer-post-processor-amazon-ami-management/releases/download/v0.1.0/packer-post-processor-amazon-ami-management_linux_amd64.zip
$ unzip packer-post-processor-amazon-ami-management_linux_amd64.zip
$ mv dist/linux_amd64/packer-post-processor-amazon-ami-management ~/.packer.d/plugins

使い方

post-processorとしてamazon-ami-managementを以下のように指定します。

{
  "builders": [{
    "type": "amazon-ebs",
    "region": "us-east-1",
    "source_ami": "ami-6869aa05",
    "instance_type": "t2.micro",
    "ssh_username": "ec2-user",
    "ssh_pty": "true",
    "ami_name": "packer-example {{timestamp}}",
    "tags": {
        "Amazon_AMI_Management_Identifier": "packer-example"
    }
  }],
  "provisioners":[{
    "type": "shell",
    "inline": [
      "echo 'running...'"
    ]
  }],
  "post-processors":[{
    "type": "amazon-ami-management",
    "region": "us-east-1",
    "identifier": "packer-example",
    "keep_releases": "3"
  }]
}

AMIを作成するときにAmazon_AMI_Management_Identifierをキーに、任意の値のタグを設定しておけば、そのタグの値を指定することで世代管理の対象とすることができます。何世代まで維持するかどうかはkeep_releasesで指定できます。 これでいちいち古いAMIがたまった時点で定期的に削除する必要が無くなって、AWSの料金もきっちり節約できます、うれしいですね!

まとめ

普段、実務ではRubyを使うことが多いのですが、Goでコードを書いていて、特にAWSの通信まわりのモックを書くのに結構苦労しました... 現状はgomockを使ってSDKのモックを自動生成してそれを使っているのですが、もっと良い方法があればぜひ知りたいです。

Packerのプラグイン機構はまだ試験的なものとのことですが、短いコードでかなりいろいろな拡張ができるので、ぜひ便利プラグインを作って配布してください!

参考