オリジナルの AMI を作りたいなーと思ったら今は Packer が一択なんでしょうか?
どうも AMI 職人見習いのかっぱ@inokara)です。



はじめに

  • オリジナルの VM イメージを作る場合に Packer という優れたツールがあるようなのでまずは試してみる
  • その昔 veewee でおんなじことやろうとして挫折しているので今回はどうなることやら…
  • とりあえず今回のゴールは AWS にオリジナルの AMI を作るところまで


Packer とは

ちょっとうんちく。


ざっくり言うと

  • OS インストールの自動化ツール
  • 設定ファイルに従って異なる環境で稼働する OS イメージを作成する
  • プラグイン機構を備える
  • 実行に際しては以下のようなフェーズがあり各々のフェーズでプラグインを組み合わせて処理を行うことが出来る


構成要素

こちらを超意訳してみます。(powered by Google 翻訳


Artifacts

  • ビルドの結果
  • AWS の場合には AMI IDAMI そのものを指す


Builds

  • 一つのプラットフォーム用のイメージを生成するタスク
  • 並列の実行も可能


Builders

  • イメージを生成するタスク(build)から呼び出される構成要素
  • 作成するイメージのもととなるイメージを起動する
  • VirtualBoxVMWare や…もちろん AWS も含まれる


Commands

  • packer コマンドのサブコマンド
  • プラグイン形式でも提供される


Post-processors

  • ビルドの後処理を行う(超超意訳)
  • 例えば生成されたイメージをアップロードしたり圧縮したりとか


Provisioners

  • イメージを生成する際にイメージ内の設定を行う構成要素
  • シェルスクリプトや ChefPuppet がその類


Templates

  • イメージの定義ファイル
  • JSON ファイル
  • これを書くことから Packer は始まる(のかな?)


Hello Packer

いい加減なうんちくはこれ位にして簡単な AMI を生成してみたいと思います。


Packer のインストール

つい最近 Mac 使いに昇格した自分としてはこちらに従って作業を進めます。


まずは brew tap

brew tap homebrew/binary

一応、brew info で確認しましょう。

kappa% brew info packer
packer: stable 0.6.0
http://www.packer.io
Not installed
From: https://github.com/homebrew/homebrew-binary/commits/master/packer.rb


brew install

インストールしましょう。

brew install packer

しばし待ちましょう。

01

簡単ですね。


EC2 の AMI を作る

ここは泣く子も黙る AWSEC2 イメージ(AMI)を作ってみたいと思います。
尚、この作業もこちらを見ながら進めます。


必要なもの

AMI を作る場合には以下のような情報を事前に用意しておきましょう。

  • アクセスキー(取得しておきましょう)
  • シークレットアクセスキー(取得しておきましょう)
  • AMI を生成したいリージョン(今回は us-east-1
  • 素になる AMIID(今回は ami-eb6b0182
  • インスタンスタイプ(今回も t1.micro
  • ssh のユーザー名(とりあえず root
  • 生成する AMI の名前(hello_ami


template を作成する

ま、まずは適当なディレクトリをこさえましょう。

mkdir -p ~/packer/hello_ami

ディレクトリをこさえたら、ディレクトリ以下に以下のように template ファイルを作成しましょう。

{
  "variables": {
    "aws_access_key": "AKxxxxxxxxxxxxxxxxxxxxxxxx",
    "aws_secret_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  },
  "builders": [{
    "type": "amazon-ebs",
    "access_key": "{{user `aws_access_key`}}",
    "secret_key": "{{user `aws_secret_key`}}",
    "region": "us-east-1",
    "source_ami": "ami-eb6b0182",
    "instance_type": "t1.micro",
    "ssh_username": "root",
    "ami_name": "hello_ami {{timestamp}}"
  }]
}

ファイル名はなんでもオッケー。今回は hello_ami.json とでもしておきましょう。


早速作ってみましょう…のその前に…

AMI の生成は上記のうんちくの通り packer コマンドを利用しますので、packer コマンドのオプションとか見てみましょう。

packer --version

以下のようにバージョン番号が表示されました。続いて、–help を叩いてみます。

packer --help

以下のように各種オプションが表示されますが Unknown command: –help と出るのはちょっとびっくりします。

02

これはサブコマンド(build 等)と合わせ技で利用するようです。

packer build --help

build コマンドのヘルプが確認出来ました。

03


今度こそ、作ってみましょう

packer build hello_ami.json

ちょっと時間がかかりますが以下のように finished の文字が踊ります。

04


確認してみましょう

何度も言っていますが、Management Console で確認していいのは…(略

05

aws-cli でも確認してみましょう。

aws ec2 describe-images --filters "Name=name,Values=hello_ami*"

上記を実行すると以下のように AMI の情報が JSON で返ってきます。

{
    "Images": [
        {
            "ProductCodes": [
                {
                    "ProductCodeId": "abcdefghijklmnopqrstvwxyz",
                    "ProductCodeType": "marketplace"
                }
            ],
            "Name": "hello_ami 1402691138",
            "VirtualizationType": "paravirtual",
            "Hypervisor": "xen",
            "ImageId": "ami-12345678",
            "RootDeviceType": "ebs",
            "State": "available",
            "BlockDeviceMappings": [
                {
                    "DeviceName": "/dev/sda",
                    "Ebs": {
                        "DeleteOnTermination": false,
                        "SnapshotId": "snap-12345678",
                        "VolumeSize": 8,
                        "VolumeType": "standard"
                    }
                }
            ],
            "Architecture": "x86_64",
            "ImageLocation": "1234567890123/hello_ami 1402691138",
            "KernelId": "aki-12345678",
            "OwnerId": "1234567890123",
            "RootDeviceName": "/dev/sda1",
            "Public": false,
            "ImageType": "machine"
        }
    ]
}

ちゃんと作られていますね。良かった、良かった。



手心を加えた AMI を作る

先ほどは素の AMI を単純にコピーしただけでオリジナル AMI と謳ってしまっていましたので、いささか物足りなさを感じてきました。ということで少し手心を加えた AMI をこさえたいと思います。


手心は Provisioners にあり

手心を加えるには Provisioners にお手伝いいただきます。この Provisioners は先述の通り shellchefpuppet 等でイメージ生成の過程で色々とイメージ内部の設定をやってくれます。ということで、今回は以下のようなシナリオをもとに AMI を作ってみたいと思います。

  • yum -y update を実行する
  • Apache をインストールする
  • motdtegokoro ami という文字で置き換える

また、AMI を作り終えたら、その AMI を起動してみてちゃんとシナリオどおりに設定が施されているかを Serverspec を使ってテストしてみたいと思います。


手心を加えるのに一番簡単な方法

手心を加えるのに一番簡単な方法は先ほどの hello_ami.json に以下のような記述を加えるだけです。

  "provisioners": [{
    "type": "shell",
    "inline": [
      "sleep 30",
      "yum -y update",
      "yum -y install httpd",
      "cp /etc/motd /etc/motd.original"
      "echo 'tegokoro ami' > /etc/motd"
    ]
  }]

簡単ですね。ところで、sleep 30 についてはこちらで以下のように言及されています。

The sleep 30 in the example above is very important. Because Packer is able to detect and SSH into the instance as soon as SSH is available, Ubuntu actually doesn’t get proper amounts of time to initialize. The sleep makes sure that the OS properly initializes.

ざっくり言うと「ちゃんと OS が起動して SSH がつながるまでしばし余裕を持たせています」という感じでしょうか。Ubuntu に特化したことなのかもしれませんが念のために実行させることにします。


いざ tegokoro ami

先ほど利用した hello_ami.json に以下のように手心を加えておきます。

    "source_ami": "ami-eb6b0182",
    "instance_type": "t1.micro",
    "ssh_username": "root",
    "ami_name": "hello_ami {{timestamp}}"
  }],
  "provisioners": [{
    "type": "shell",
    "inline": [
      "sleep 30",
      "yum -y update",
      "yum -y install httpd",
      "cp /etc/motd /etc/motd.original",
      "echo 'tegokoro ami' > /etc/motd"
    ]
  }]
}

そして、満を持して packer build を実行します。

packer build hello_ami.json

しばし、お茶でもたてて待ちましょう。

06

途中 yum -y update 等で出力が流れてしまいましたが、上記のように Apache のインストールまで行って終了です。AMI そのものが出来ているについては…今更見なくても良さそうですので、実際の中身がちゃんと手心が加わっているかを確認してみたいと思います。


AMI から作ったインスタンスにログインできない問題

早速 AMI から作ったインスタンスにログインしてみたいのですが、インスタンスを立ち上げた際に指定した鍵での認証では失敗してしまいます…orz。こちらを参考にさせて頂いて provisioners のスクリプトの最後に以下のような手心を改めて加えます。(原因等の詳細につきましては参考サイトをご確認ください。)

rm -f /root/.ssh/authorized_keys

ちなみに packer build の最後のステップで…

07

てっきり Deleting temporary keypair… で消してるものとばかり…。


改めて AMI をビルド

packer build hello_ami.json

しばし待ちましょう。


完成した AMI からインスタンスを起動しましょう。

08

ここは aws-cliSDK からプログラマブルにやりたいところ…。


ではテスト

Serverspec を使って AMI から起動したインスタンスをテストします。以下のような spec ファイルを用意しました。(※事前に Serverspec が利用出来るように準備しておきましょう)


check_spec.rb

require 'spec_helper'

describe package('httpd') do
  it { should be_installed }
end

describe file('/etc/motd') do
  its(:content) { should match /tegokoro ami/ }
end


テスト実行

以下のようにテストを実行しましょう。

rake spec SPEC_OPTS=”-fd”

以下のように出力されました。テストは成功です。

09

AMI も意図した通りに作成出来たと判断して良いと思います!



自動化へのアプローチ

今回は全ての作業を手動で行いましたが、AMI を定期的に更新したい場合にはこれらのタスクを自動化したいところです。


ちょっとした Rakefile で

乏しい Ruby の知識をフルに発揮して以下のような Rakefile を作成してみました。

rake コマンドで AMI の生成からインスタンスの起動、Serverspec によるテスト、インスタンスのターミネートまでを行えるようにしてあります。(コードの汚さはご勘弁を…)



まとめ


今回やったこと

  • Packer をちょっと調べた
  • Packer を使って AMI を手動で作成した
  • 少し手心を加えた AMI を作成した
  • 作成した AMI でインスタンスを起動して Serverspec を使ってテストした


次回は…

  • ProvisionersChef-solo を使って、さらに手心を加えた AMI を作ってみたいと思います!