golang
openstack
packer

Packerで作ったイメージを世代管理

More than 1 year has passed since last update.

この記事は Livesense - 自 Advent Calendar 2017 4日目の記事です。
本日はインフラストラクチャーグループの@nashioxがお送りします。

インフラストラクチャーグループの「自」といえばもちろん「自動化」ですね。

Packerでイメージ作成を自動化する

リブセンスではPackerを使って「AMI」「OpenStackイメージ」などを作っています。
イメージの作成そのものはCIツールを利用してGitリポジトリのPush/Margeをフックしてpacker buildを実行すれば良いわけですが、気をつけなければいけないのがイメージの削除です。

Packer only builds images. It does not attempt to manage them in any way.

Packerの公式ドキュメントにも書いてありますが、Packerはイメージのビルドのみを行います。
そのため、何度も何度も自動でビルドされていくうちにたくさんのイメージが出来上がっていくわけです。
イメージが積み重なっていくとどれが最新のイメージかわからなくなったり、AMIの場合だとそれだけでお金がどんどんかかっていくことになってしまいます。
せっかくイメージの作成を自動化しても、削除は手動では旨味が少なくなってしまいます。

Packerを使ってイメージ作成の自動化を考えていく上ではイメージの削除・世代管理を考えることが重要になってきます。

イメージの削除・世代管理を考える

イメージの削除・世代管理を考えるのが重要というのはわかりましたが、Packerそのものに機能がない以上どうすれば良いのか。
今回は「AMI」と「OpenStackイメージ」について考えてみました。

AMIの場合

AMIの場合はすでに同じことを考えていた人がいらっしゃいました。
PackerのAMI手動管理を卒業するプラグインを作ってみた

Packerのプラグイン機構を利用して、packer build後のpost-processorとしてイメージの削除・世代管理を行ってくれるというものです。
https://github.com/wata727/packer-post-processor-amazon-ami-management

インストール

インストールは簡単。
~/.packer.d/plugins/以下にダウンロードしてきて配置するだけです。

### 無ければ作る
$ mkdir -p ~/.packer.d/plugins

### ダウンロード(今回はLinux amd64)
$ curl -L -O https://github.com/wata727/packer-post-processor-amazon-ami-management/releases/download/v0.2.0/packer-post-processor-amazon-ami-management_linux_amd64.zip

### 解凍
$ unzip packer-post-processor-amazon-ami-management_linux_amd64.zip

### 配置
$ mv packer-post-processor-amazon-ami-management ~/.packer.d/plugins/

設定

Packerの設定ファイルに以下のようにpost-processors"type": "amazon-ami-management"を設定することで世代管理されます。

{
  "builders": [{
    "type": "amazon-ebs",
    "region": "ap-northeast-1",
    "source_ami": "ami-da9e2cbc",
    "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": "ap-northeast-1",
    "identifier": "packer-example",
    "keep_releases": "3"
  }]
}

buildersの段階でtagsに任意のAmazon_AMI_Management_Identifierを設定しておけばidentifierで世代管理するイメージをフィルターすることが可能です。
作成日時の新しい順にkeep_releasesで設定した数で世代管理されます。

これで手動でイメージをポチポチ消していく必要がなくなります。

OpenStackの場合

OpenStackの場合は色々調べてみましたが、良い方法を見つけることが出来ませんでした。

なのでpacker-post-processor-amazon-ami-managementにインスパイアされて、OpenStack版も作ってみました。
https://github.com/nashiox/packer-post-processor-openstack-image-management

インストール

~/.packer.d/plugins/以下にダウンロードしてきて配置するだけです。

### 無ければ作る
$ mkdir -p ~/.packer.d/plugins

### ダウンロード(今回はLinux amd64)
$ curl -L -O https://github.com/nashiox/packer-post-processor-openstack-image-management/releases/download/v0.0.1/packer-post-processor-openstack-image-management_linux_amd64.zip

### 解凍
$ unzip packer-post-processor-openstack-image-management_linux_amd64.zip

### 配置
$ mv packer-post-processor-openstack-image-management_linux_amd64/packer-post-processor-openstack-image-management ~/.packer.d/plugins/

設定

Packerの設定ファイルに以下のようにpost-processors"type": "openstack-image-management"を設定することで世代管理されます。

{
  "builders": [{
    "type": "openstack",
    "identity_endpoint": "http://<destack-ip>:5000/v3",
    "tenant_name": "admin",
    "domain_name": "Default",
    "username": "admin",
    "password": "<your admin password>",
    "region": "RegionOne",
    "ssh_username": "root",
    "image_name": "sample-image",
    "source_image": "<image id>",
    "flavor": "m1.tiny",
    "insecure": "true"
  }],
  "provisioners":[{
    "type": "shell",
    "inline": [
      "echo 'running...'"
    ]
  }],
  "post-processors":[{
    "type": "openstack-image-management",
    "identity_endpoint": "http://<destack-ip>:5000/v3",
    "tenant_name": "admin",
    "domain_name": "Default",
    "username": "admin",
    "password": "<your admin password>",
    "region": "RegionOne",
    "insecure": "true",
    "identifier": "sample-image",
    "keep_releases": "2"
  }]
}

identifierにはイメージ名を利用しています。
作成日時の新しい順にkeep_releasesで設定した数で世代管理されます。

プラグインの実装

Packerプラグインの実装に関しては詳しくは公式ドキュメントを御覧ください。
今回は作成したpacker-post-processor-openstack-image-managementの仕組みについて簡単に紹介します。

プラグインの起動

プラグインの起動はmain関数で以下のようにします。

package main

import (
    "github.com/hashicorp/packer/packer/plugin"
    openstackimagemanagement "github.com/nashiox/packer-post-processor-openstack-image-management/openstack-image-management"
)

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

    server.RegisterPostProcessor(new(openstackimagemanagement.OpenStackPostProcessor))
    server.Serve()
}

github.com/hashicorp/packer/packer/pluginを利用してイメージを作成しています。
今回はイメージ作成後、古いイメージを削除していきたいのでpost-processorの仕組みを利用します。
server.RegisterPostProcessorでプラグインを登録します。

イメージリストの取得

イメージのリストは以下のように取得しています。

var imageList []images.Image

log.Println("Describing images for generation management")
pager := images.List(p.conn, images.ListOpts{Name: p.config.Identifier})
pager.EachPage(func(page pagination.Page) (bool, error) {
    imgs, err := images.ExtractImages(page)
    if err != nil {
        return false, err
    }

    imageList = append(imageList, imgs...)
    return true, nil
})

sort.Slice(imageList, func(i, j int) bool {
    return imageList[i].CreatedAt.After(imageList[j].CreatedAt)
})

images.ListOptsでフィルターを設定することが出来ます。
今回はイメージ名でフィルターをするのでName: p.config.Identifierを設定しています。

images.Listでイメージリストのページャーが取得出来るので、pager.EachPageでページごとにイメージを取得、imageListSliceに追加をしていきます。

そしてsort.Sliceを使って、CreatedAtが新しい順にソートします。

Go言語でSliceのソートをするのは結構面倒な印象だったのですが、sort.SliceがGo 1.8から追加されて非常に書きやすくなっていました。
https://github.com/golang/go/commit/22a2bdfedb95612984cec3141924953b88a607b7

イメージの削除

イメージは以下のように削除・世代管理しています。

for i, img := range imageList {
    if i < p.config.KeepReleases {
        continue
    }

    ui.Message(fmt.Sprintf("Deleting image: %s", img.ID))
    log.Printf("Deteting image (%s)", img.ID)
    if result := images.Delete(p.conn, img.ID); result.Err != nil {
        return nil, true, result.Err
    }
}

KeepReleasesで世代数を確認し、images.Deleteで対象となるイメージを削除しています。

まとめ

Packerを利用するとイメージの作成が自動化されてすごく便利でしたが、削除まわりで今一歩感がありました。
しかし今回Packerのプラグイン機構を利用すればそれも簡単にできるようになりました。

ただ、Packerのプラグイン機構はまだあまり一般的ではないのか、探すのが大変でした。
Goの1バイナリを置けば動いてしまうのは簡単で良いですが、GitHubの広い海を探して回るのはなかなか大変なので何かいい方法をご存知の方がいれば教えてください。