はじめに
以前、Packer + chef-soloでカスタムAMI(AmazonMachineImage)を作るという記事を書きましたが、今回はVagrant用のbox(VirtualBox用)を作ります。でもって、ローカルで持ってても自分しか使えないので、S3にboxファイルをアップロードします。
Packerは非常にシンプルなJSONの定義で動くのですが、いきなり0から作ろうと思うと、フィードバックループに時間もかかるしそれなりに大変なので、Chef社から提供されているbentoをsubmoduleとし、かつtemplateをカスタムする形にしました。
bentoの他にも、いくつかテンプレートを提供してくれている方々がいらっしゃるのでいくつかご紹介します。
- https://github.com/box-cutter
- https://github.com/misheska/basebox-packer
- https://github.com/shiguredo/packer-templates
- https://github.com/hnakamur/my-packer-template-files
- https://github.com/nickchappell/packer-templates
また、Packerコマンドの実行とS3へのアップロードはRakeで行います。Go言語が得意な方はPackerのプラグインを自作したほうがスマートかもしれません。
前提条件
- Packerインストール済み
- VirtualBoxインストール済み
- AWSアカウント作成済み
- S3にアップロード権限のあるIDとKEYを発行済み
- Chef導入済み
- chef-rbenvインストール済み
ゴール
- Ruby2.1.5をビルドしたVagrant用のBoxをS3にアップロードする
実施
Gemfileを用意する
source "https://rubygems.org"
gem "rake"
gem "dotenv"
gem "aws-sdk-core"
Gemをインストールする
$ bundle install --path vendor/bundle
bentoの置き場を作る
$ mkdir vendor/templates
bentoをsubmoduleとする
$ cd vendor/template
$ git submodule add https://github.com/opscode/bento.git bento
bentoのモジュールにシンボリックリンクを貼る
$ ln -s vendor/templates/bento/packer/vagrantfile_templates vagrantfile_templates
$ ln -s vendor/templates/bento/packer/scripts scripts
$ ln -s vendor/templates/bento/packer/http http
$ ln -s vendor/templates/bento/packer/floppy floppy
テンプレートファイルを用意する
{
"variables": {
"centos_version": "6.6",
"chef_version": "11.16.4",
"mirror": "http://ftp.iij.ad.jp/pub/linux/centos",
"ruby_version": "2.1.5",
"box_output_dir": ""
},
"builders": [
{
"type": "virtualbox-iso",
"boot_command": [
"<tab> text ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/centos-{{user `centos_version`}}/ks.cfg<enter><wait>"
],
"boot_wait": "10s",
"disk_size": 40960,
"guest_additions_path": "VBoxGuestAdditions_{{.Version}}.iso",
"guest_os_type": "RedHat_64",
"http_directory": "http",
"iso_checksum": "08be09fd7276822bd3468af8f96198279ffc41f0",
"iso_checksum_type": "sha1",
"iso_url": "{{user `mirror`}}/{{user `centos_version`}}/isos/x86_64/CentOS-{{user `centos_version`}}-x86_64-bin-DVD1.iso",
"output_directory": "packer-centos-{{user `centos_version`}}-x86_64-virtualbox",
"shutdown_command": "echo 'vagrant'|sudo -S /sbin/halt -h -p",
"ssh_password": "vagrant",
"ssh_port": 22,
"ssh_username": "vagrant",
"ssh_wait_timeout": "10000s",
"vboxmanage": [
[
"modifyvm",
"{{.Name}}",
"--memory",
"480"
],
[
"modifyvm",
"{{.Name}}",
"--cpus",
"1"
]
],
"virtualbox_version_file": ".vbox_version",
"vm_name": "packer-centos-{{user `centos_version`}}-x86_64"
}
],
"post-processors": [
{
"type": "vagrant",
"only": ["virtualbox-iso"],
"output": "{{user `box_output_dir`}}/centos-{{user `centos_version`}}-x86_64-ruby-{{user `ruby_version`}}-{{timestamp}}.box"
}
],
"provisioners": [
{
"type": "shell",
"only": ["virtualbox-iso"],
"environment_vars": [
"CHEF_VERSION={{user `chef_version`}}"
],
"execute_command": "echo 'vagrant' | {{.Vars}} sudo -S -E bash '{{.Path}}'",
"scripts": [
"scripts/centos/fix-slow-dns.sh",
"scripts/common/sshd.sh",
"scripts/common/vagrant.sh",
"scripts/common/vmtools.sh",
"scripts/common/chef.sh",
"scripts/centos/cleanup.sh",
"scripts/common/minimize.sh"
]
},
{
"type": "chef-solo",
"install_command": "curl -L https://www.opscode.com/chef/install.sh | {{if .Sudo}}sudo{{end}} bash -s -- -v {{user `chef_version`}}",
"cookbook_paths": ["/path/to/chef/cookbooks"],
"run_list": ["ruby_build", "rbenv::system"],
"json": {
"rbenv" : {
"global": "{{user `ruby_version`}}",
"rubies": [
"{{user `ruby_version`}}"
]
}
},
"prevent_sudo": false
},
{
"type": "shell",
"inline": [
"sudo chmod -R 775 /usr/local/rbenv/"
]
}
]
}
S3へのアップロードに必要な環境変数を定義する
AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID"
AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY"
S3_BUCKET = "S3_BUCKET"
REGION = "ap-northeast-1"
Rakeファイルを用意する 1
require "rake"
require "aws-sdk-core"
require "dotenv"
Dotenv.load
# テンプレートとするコンフィグ
TEMPLATE_JSON = "template.json"
# 設定ファイルに渡せる変数
VARIABLES = %w[
chef_version
centos_version
ruby_version
]
# box出力用ディレクトリ
BOX_OUTPUT_DIR = "./build"
namespace :packer do
namespace :vb do
desc "Run packer validate for virtualbox-iso"
task :validate do
exec_packer(
builder_type: "virtualbox-iso",
sub_command: "validate"
)
end
desc "Run packer build for virtualbox-iso"
task :build do
exec_packer(
builder_type: "virtualbox-iso",
sub_command: "build"
)
upload_box_to_s3
end
desc "Run s3 upload for virtualbox-iso"
task :s3upload do
upload_box_to_s3
end
end
# packerコマンドを実行します
# - builder_type ビルドタイプ String
# - sub_command サブコマンド String
#
# Returns 実行したコマンド(debug用)
def exec_packer(builder_type:, sub_command:)
if Dir.exist?(BOX_OUTPUT_DIR)
# 念のためboxの出力先をクリーンしておく
Dir.glob("#{BOX_OUTPUT_DIR}/*").each { |f| File.unlink(f) }
else
Dir.mkdir(BOX_OUTPUT_DIR)
end
# packerコマンドを生成して実行する
command = "packer #{sub_command} -var 'box_output_dir=#{BOX_OUTPUT_DIR}' "
VARIABLES.each do |var|
if ENV.has_key?(var.upcase)
command << "-var '#{var}=#{ENV[var.upcase]}' "
end
end
command << "--only=#{builder_type} #{TEMPLATE_JSON}"
sh command
# debug
command
end
# 生成されたboxファイルをS3にアップロードします
#
# Returns none
def upload_box_to_s3
# boxファイルは1件しかない想定。念のため1件以外の時は例外飛ばしておく。
box_file_path =
if Dir.glob("#{BOX_OUTPUT_DIR}/*").size == 1
Dir.glob("#{BOX_OUTPUT_DIR}/*").first
else
raise "box files can only do not expect one"
end
# ビルドしたboxをS3に転送する
Aws.config[:s3] = {
region: ENV["REGION"],
credentials: Aws::Credentials.new(ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_ACCESS_KEY"])
}
resp = Aws::S3::Client.new.put_object(
bucket: ENV["S3_BUCKET"],
body: File.open(box_file_path),
key: "vagrant/virtualbox/#{File.basename(box_file_path)}",
acl: "public-read"
)
puts "output vagrant box endpoint:"
puts " #{resp.context.http_request.endpoint.to_s}"
# 転送したらboxファイルは不要なので削除
File.unlink(box_file_path)
end
end
templateファイルにエラーがないかチェック
$ bundle exec rake packer:vb:validate
ビルドを実行する
$ bundle exec rake packer:vb:build
私の環境(MacBook Air (11-inch, Mid 2011), CPU: 1.8 GHz Intel Core i7, メモリ: 4 GB 1333 MHz DDR3
)では1337.60 real 64.32 user 19.57 sys
で完了しました。
備考
S3のアップロードにコケた場合、以下のコマンドでS3へのアップロードのみ再実行する
$ bundle exec rake packer:vb:s3upload
リポジトリ
GitHub
おわりに
これでVagrant up時の時間を短縮することができて、ストレスも軽減された
-
キーワード引数を利用しているため、Ruby2.1以降でないとエラーとなります ↩