ちょっと話題の記事

ChefでS3からファイルを取得する

2013.06.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、植木和樹です。ここ最近ChefとCloudFormation漬けの毎日をすごしています。

さてChefを使ってサーバ環境を構築・保守するにあたって様々なファイルをあつかっていることと思いますが、ファイルの置き場所としてS3が使えたらいろいろ便利ですよね。cookbookと一緒のリポジトリで管理するのがふさわしくない設定ファイルや、サイズが大きくてcookbookに入れたくないオレオレRPMファイルなんかをS3に置いておいてchefレシピから扱えたら・・・今日はそんな課題に取り組んでみたいと思います。

s3_fileリソース

検索サイトで"chef s3"で検索するとs3_fileという、chefからS3を扱うためのライブラリを配布されている方がいました。

https://gist.github.com/peplin/470321

さらにこのページのコメント欄を読むと、AWS SDK for Rubyを利用した版もあるようです。

https://gist.github.com/DavidAllison/5288249

AWS SDK for Rubyを使うとS3の認証にIAM Roleを使うことができ、各EC2インスタンスにアクセスキーなどを配布する必要がありません。事前設定なくchefだけでS3が使えます!!

そこで今回はAWS SDK for Rubyバージョンのs3_fileリソースを使ってみることにしました。

s3_fileリソースを複数のcookbookから使う

ライブラリファイルは、それぞれのcookbookのlibrariesディレクトリに保存しておくことで自動的に読み込まれるようになっています。ただこのやり方だとs3_fileリソースを使いたいcookbookすべてにライブラリファイルをコピーする必要があり好ましくありません。

そこでs3_fileリソースはs3_fileというcookbookで管理し、s3_fileリソースを使いたいレシピではinclude_recipeでs3_file cookbookのデフォルトレシピを読み込むようにします。後ほど説明しますが、s3_fileのレシピに細工をすることでcookbookをまたいでライブラリが使用できるようにします。

cookbooks/oreorerpm/recipes/download.rb

include_recipe "s3_file"
work_dir = "/home/ec2-user/work"

directory work_dir do
  action :create
end

%w{
  oreore.rpm
  app.properties
}.each do |file|

  s3_file "#{work_dir}/#{file}" do
    source "s3://my-repository/#{file}"
    owner  "ec2-user"
    group  "ec2-user"
    mode   0644
    action :create_if_missing
  end
end

s3_fileを使うためのcookbookを用意する

ChefのRubyにAWS SDKをインストールする

サーバにインストールするchefはOmnibus Installerでインストールされたものを想定しています。Omnibus Installerはchefと一緒にchefを実行するrubyのバイナリも(/opt/chef以下に)インストールします。このchef用rubyはシステムにインストールされるrubyとは別物なので、s3_fileでAWS SDKを使うにはこのchef用rubyの環境(/opt/chef以下)にインストールされなければいけません。

chef用rubyにgemパッケージをインストールするには"chef_gem"リソースを使います。"gem_package"リソースとは異なり、"chef_gem"パッケージでインストールされたgemは/ops/chef以下に入ります。

それではs3_fileリソースを有効にするためのcookbook作成から始めましょう。作業ディレクトリで"s3_file"というcookbookを作ります。

$ knife cookbook create s3_file -o cookbooks
** Creating cookbook s3_file
** Creating README for cookbook: s3_file
** Creating CHANGELOG for cookbook: s3_file
** Creating metadata for cookbook: s3_file

cookbooks/s3_file/recipes/default.rb を編集して、このs3_fileに必要なaws-sdkがインストールされるようにしましょう。

chef_gem "aws-sdk" do
  action :install
end

で、ここで問題が発生しました。aws-sdk をインストールするにはコンパイルする必要がありgccやruby-develといったパッケージが必要になります。そこで

%w{gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel}.each do |name|
  package name do
    action [:install]
  end
end

chef_gem "aws-sdk" do
  action :install
end

としてコンパイルに必要なパッケージを先にインストールしようとしたのですが、これもNG。どうも"chef_gem"リソースは「すべてのリソースに先立って最優先でインストールされる」ようで、gcc等がインストールされる前にaws-sdkをインストールしようとするのでビルドエラーになってしまいます。必要なライブラリがインストールされたら、chef_gemをキックしたい・・・

試行錯誤のすえ、subscribes を使ったところうまくいきました。

cookbooks/s3_file/recipes/default.rb

require File.expand_path("../../libraries/s3_file.rb", __FILE__)

%w{gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel}.each do |name|
  package name do
    action [:install]
  end
end

%w{nokogiri aws-sdk}.each do |name|
  chef_gem name do
    action :nothing
    subscribes :install, "package[libxslt-devel]", :immediately
  end
end

chef_gemは action :nothing でひとまずスルーしておき、gccなどの必要パッケージ(配列最後のlibxslt-devel)がインストールされるとsubscribesが検知しchef_gemのインストールをキックしてくれます。

※aws-sdkがXMLを扱うのでnokogiriも必要でした。

s3_file cookbookのdefaultレシピは、requireでlibraries/s3_file.rbを読み込んでいます。

require File.expand_path("../../libraries/s3_file.rb", __FILE__)

こうすることでcookbook毎にs3_fileを置かなくても、s3_fileをinclude_recipeするだけでs3_file cookbookのlibrariesを他のcookbookからも使えるようになりました。

s3_file.rbをs3_file/librariesディレクトリにコピーする

先ほどのURLからライブラリコードをダウンロードし、s3_file cookbookのlibrariesディレクトリにs3_file.rbというファイル名で保存します。Amazon Linuxで使用するときにはダウンロードしたファイルを少し修正し、"rubygems"もrequireする必要がありました。

cookbooks/s3_file/libraries/s3_file.rb

      def fetch_from_s3(source)
        begin
          require  'rubygems'
          require  'aws-sdk'
          protocol, bucket, name = URI.split(source).compact

S3 ReadOnlyのIAM Roleを作成する

chef-soloを実行するEC2インスタンスには、S3からファイルを取得できるよう適切なIAM Roleを割り当てておきましょう。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:Get*",
        "s3:List*"
      ],
      "Resource": "*"
    }
  ]
}

実行したときのログ

chefを実行すると、最初のchef_gemはaction nothingでスルーされ、libxslt-develがインストールされたら改めてchef_gemのinstall actionがキックされていることがわかります。その後、s3_fileリソースによってファイルをS3からローカルの~ec2-user/workディレクトリに保存しています。

Starting Chef Client, version 11.4.4
Compiling Cookbooks...
Recipe: s3_file::default
  * chef_gem[nokogiri] action nothing (up to date)
  * chef_gem[aws-sdk] action nothing (up to date)
Converging 11 resources
  * package[gcc] action install
    - install version 4.6.3-3.10.amzn1 of package gcc

  * package[gcc] action upgrade (up to date)
  * package[ruby-devel] action install
    - install version 1.8.7.371-2.26.amzn1 of package ruby-devel

  * package[ruby-devel] action upgrade (up to date)
  * package[libxml2] action install (up to date)
  * package[libxml2] action upgrade (up to date)
  * package[libxml2-devel] action install
    - install version 2.7.8-10.26.amzn1 of package libxml2-devel

  * package[libxml2-devel] action upgrade (up to date)
  * package[libxslt] action install (up to date)
  * package[libxslt] action upgrade (up to date)
  * package[libxslt-devel] action install
    - install version 1.1.26-2.7.amzn1 of package libxslt-devel

  * chef_gem[nokogiri] action install
    - install version 1.6.0 of package nokogiri

  * chef_gem[aws-sdk] action install
    - install version 1.11.3 of package aws-sdk

  * package[libxslt-devel] action upgrade (up to date)
  * chef_gem[nokogiri] action nothing (up to date)
  * chef_gem[aws-sdk] action nothing (up to date)
Recipe: oreorerpm::download
  * directory[/home/ec2-user/work] action create
    - create new directory /home/ec2-user/work
  * s3_file[/home/ec2-user/work/oreore.rpm] action create_if_missing

  * s3_file[/home/ec2-user/work/app.properties] action create_if_missing

Chef Client finished, 2 resources updated
$ ls -1 ~/work/
app.properties
oreore.rpm

まとめ

s3_fileライブラリとIAM Roleを使うことで、chefで簡単にS3からファイルをダウンロードできるようになりました。冒頭にも書いたとおり、cookbookと一緒に管理できない(したくない)設定ファイルを配布する場合や、ソースファイルが詰まったサイズの大きいtar+gzファイルをS3からダウンロードしてきてexecuteでビルド+インストールなんてこともできます。S3という容量無制限で信頼性の高いストレージを扱えることで、いろいろな使い道がありそうですね。