LoginSignup
3
4

More than 5 years have passed since last update.

Packer + chef-solo + RakeでVagrant用のboxを作ってS3にアップロードする

Last updated at Posted at 2015-01-25

はじめに

以前、Packer + chef-soloでカスタムAMI(AmazonMachineImage)を作るという記事を書きましたが、今回はVagrant用のbox(VirtualBox用)を作ります。でもって、ローカルで持ってても自分しか使えないので、S3にboxファイルをアップロードします。

Packerは非常にシンプルなJSONの定義で動くのですが、いきなり0から作ろうと思うと、フィードバックループに時間もかかるしそれなりに大変なので、Chef社から提供されているbentoをsubmoduleとし、かつtemplateをカスタムする形にしました。

bentoの他にも、いくつかテンプレートを提供してくれている方々がいらっしゃるのでいくつかご紹介します。

また、Packerコマンドの実行とS3へのアップロードはRakeで行います。Go言語が得意な方はPackerのプラグインを自作したほうがスマートかもしれません。

前提条件

  • Packerインストール済み
  • VirtualBoxインストール済み
  • AWSアカウント作成済み
  • S3にアップロード権限のあるIDとKEYを発行済み
  • Chef導入済み
  • chef-rbenvインストール済み

ゴール

  • Ruby2.1.5をビルドしたVagrant用のBoxをS3にアップロードする

実施

Gemfileを用意する

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

テンプレートファイルを用意する

template.json
{
  "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へのアップロードに必要な環境変数を定義する

.env
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

Rakefile
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時の時間を短縮することができて、ストレスも軽減された


  1. キーワード引数を利用しているため、Ruby2.1以降でないとエラーとなります 

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4