piyo の紹介

2013年の12月まではシステムインフラエンジニアとして、AWSでの構築、サーバ構築自動化、pythonで運用ツール作成、zabbixによる監視システム構築などに従事。 2014年1月からはリーマン生活に別れを告げて個人事業主として、システム構築関連の会社のお世話になっています。 今は頭の回りを飛ぶハエを追うのに精一杯ですが、落ち着いたら自分の好きな事を仕事にしていきたいです。(2014/6/16)

chef-soloでchef資産の有効活用

chefがある環境に慣れるともうchefから離れられませんが、chefサーバと通信できない環境にある場合などはchefを活用することができません。
「嗚呼、chefが使えたら!」と叫びたくなりますね。

そんなときはchef-soloです。
chef-soloを使えばchefサーバに蓄えたノウハウを使うことができます。
ただ、chef-soloを使うためにはいくつかやらなければならないことがありますので、それを含めてchef-soloの使い方を解説したいと思います。

cookbookのexport

chef-soloを使うためには、まずはこれまで作ったcookbookをexportする必要があります。
cookbookのexportはknifeを使って行います。

# create work directory
WORK_DIR=/path/to/workdir
if [ ! -d ${WORK_DIR} ]; then
  mkdir -p ${WORK_DIR}
fi

for i in `knife cookbook list| awk '{ print $1 }'`; do
  echo $i
  knife cookbook download $i -d ${WORK_DIR}
done

cd ${WORK_DIR}
for i in `ls -1 ./`; do
  poi=`echo $i | awk -F- '{ print $NF }'`
  n=`echo $i | sed s/\-${poi}//`
  mv $i $n
done

knife cookbook downloadでcookbookをダウンロードします。
cookbookはcookbook-versionというディレクトリ名でダウンロードされますが、そのままですとchef-soloでは使えないのでversion部分を除去する処理を入れています。

このダウンロードしたcookbookをtarで固めてchef-soloを使うサーバに持っていきましょう。

Roleの代わりとなるjsonファイルを用意する

chefを使う場合、Roleはとても便利なものです。
chef-soloの場合、このRoleを使うことができないので、代わりのjsonを作ります。
例えばこんな感じ。

{
  "ntp":{
    "servers":[
      "ntp01.hoge.com",
      "ntp02.hoge.com"
    ]
  },
  "td_agent":{
    "logfile":"/log/httpd/access",
    "servers":["fluentd-01"],
    "log_tag":"http.access"
  },
  "httpd":{
    "htdocs":"/var/www",
    "logdir":"/log/httpd"
  },
  "run_list": [
    "recipe[ntp]",
    "recipe[chef-td-agent]",
    "recipe[httpd]"
  ]
}

このjsonファイルによって、使うrecipeを設定して、recipeで使うattributeの値を設定します。

chef-solo.rbの用意

最後にchef-soloを動かすための環境設定ファイルを用意します。
chef-solo.rbは以下のような感じ。

file_cache_path "/var/tmp/chef-solo"
cookbook_path "/var/tmp/cookbooks"

これだけ。
chefを動かすためのcacheディレクトリの場所と、上で固めたCookbookを展開する場所を指定しています。

chef-soloの実行

以下のコマンドでchef-soloを実行。

chef-solo -c /path/to/chef-solo.rb -j /path/to/hoge.json

実はchef-soloはchefサーバのインストール時にも使っていたのですが、調べたまんまを意味もわからず書いてサーバをインストールしていましたが、改めて必要に迫られて使ってみると、chefサーバインストール時に書いたjsonの内容も納得。

それでは良いchefライフを!

[chef] knifeでroleで使われているrecipeを検索

chefの運用が始まって、サーバの設定のほとんどをchefでやるようになりましたが、その中で不便なことが出てきました。
recipeを変更してそれをサーバに反映させるときに、「はて?このrecipeはどこで使ってたっけ?」となってしまうのです。
今の環境では能動的にサーバの設定変更をしたいので、chef-clientはdaemonで起動しておらず、手動でchef-clientを実行してサーバに反映させています。
その場合にrecipeを変更した後、どのサーバでchef-clientを実行すればよいか探すことになり、場合によっては反映し忘れということも起こってしまいます。

knifeのsearchでrun_listを検索

そこで、knifeのsearch機能に着目してrun_listを検索することにしました。
使い方はこちらを参照。

Dashboard – Chef – Home – Chef Essentials – Search

knifeのsearchは具体的にはsolrのindexの検索です。
こんな感じで、run_listを検索しました。

$ knife search role "run_list:*MySQL*" -i
10 items found

db-server

stg-db
.
.
.

“-i”は検索結果のidのみを表示するオプションです。
これでrecipe[MySQL]を使っているroleを検索することができました。
ちなみに

$ knife search role "run_list:recipe[MySQL]"

としてもエラーが出てしまい検索できませんでしたので、上記のようなクエリーになったわけですが、多分もっとスタイリッシュなクエリーがあるはずです。それにはもっと勉強が必要なので今日はこれまで。

PythonでAWS(EC2)操作

最近は仕事の9割がAWS(Amazon Web Service)環境となっていて、毎日のようにインスタンスを立ち上げてchefでサーバの構築をしています。
数が少ないうちはWEBのManagement Consoleでぽちぽちやっていたんですが、いい加減面倒。なにが面倒ってサーバによってはデータ領域をEBSにしたりSecurityGroupを作ったり。
さらに作業を面倒にしているのが複数のAWSアカウント。プロジェクトによってAWSアカウントを分けていたりして、その度にサインアウトしてサインインして、もう!という感じでした。

環境変数を変えながらec2-api-toolで頑張ってました

僕の作業環境はMacなので、最初のうちは(というか今でも)それぞれのアカウントの認証情報を環境変数に設定するshell scriptを用意して、ec2-api-toolsを使って操作していました。
例えば以下のように。

$ #account-01の認証情報(aws_access_key_idとか)が書いてあるファイルをsourceで読み込む
$ source account-01.rc
$ ec2-describe-instances
   .
   .
   .

SecurityGroupを確認したり設定したりする時はこれが便利。
しかし、Instanceを起動したりEBSを作ったりはこれではまだまだ手間がかかるのでスクリプトで一発で作れるようにしてみました。

botoを使ってpythonでインスタンス起動

スクリプトでやるメリットは

  • インスタンス起動時にcloud-initでホスト名を設定するためにuser_dataを設定する
  • インスタンス起動と同時にtag:Nameにホスト名を設定する
  • インスタンスのホスト名から判断して適切なSecurityGroupを設定する
  • インスタンスのホスト名から判断してEBSが必要な場合はEBSを作って、EBSのTag:Nameに名前を付けて、インスタンスにAttachする

と、こんなところです。
将来的にはchefへの登録もこの流れにのせられるかなと思っています。

実はスクリプトでのインスタンス起動は前からbash+ec2-api-toolでやってたのですが、その時は単独アカウントと1種類のサーバのみだったので、管理アカウントが増えた今回はpythonで作り直すことにしました。

なぜpythonかって?そりゃ好きだからです。
これまではサーバ管理用スクリプトはbashでムリがあるものはperlでやっていたのですが、正直perlにはあまり良い思い出が無く、1年ぐらい前にpythonを使い始めて、それ以降はこの手のスクリプトはpythonで作るようになっています。

まずは上記を実装すべくPython用のEC2のSDKであるbotoを使って作ってみました。
コード全てを載せるのもアレなので、ハマったところやドヤ顔できる部分を抜粋します。

複数アカウントの認証関連情報はiniで管理

今回の目的の一つである複数アカウント対応は、iniファイルを使って認証関連情報を管理することにしました。
僕にpythonを勧めてくれた後輩君に「pythonで設定ファイルなら何が良い?xml?ini?」と聞いたら「ini」と即答。iniはConfigParserで一発で使えるとのこと。確かにラクチンでした。

例えば以下のようなiniファイルを用意します。

$ cat hoge.ini
[account-01]
aws_access_key_id=xxxxxxxxxxxxxxxxxxxxxxx
aws_secret=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
account_id=xxxxxxxxxxx

[account-02]
aws_access_key_id=xxxxxxxxxxxxxxxxxxxxxxx
aws_secret=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
account_id=xxxxxxxxxxx

これをpythonで使うにはこうします。

import ConfigParser

# オブジェクト作成
ini = ConfigParser.SafeConfigParser()

# ini読み込み
ini.read("/path/to/hoge.ini")

# account-01のaws_access_key_idを取り出す
aws_access_key_id = ini.get("account-01","aws_access_key_id")

簡単です。

こんな感じで各アカウントの情報をiniにまとめて引数でそれぞれの情報をセットするようにしました。

botoの基本的な使い方

botoのドキュメントはこちら。http://docs.pythonboto.org/
今回はこの中のEC2を使ってインスタンスの起動をしています。

ec2 APIに接続します。

import boto.ec2
region = "ap-northeast-1"
auth = {
  "aws_access_key_id":ini.get('account-01', 'aws_access_key_id'),
  "aws_secret_access_key": ini.get('account-02',aws_secret_access_key')
}

# region指定でec2に接続
conn = boto.ec2.connect_to_region(region, **auth)

っと、ここまで書いて疲れちゃいましたので続きはまた今度。

[chef] chefサーバのバックアップ

一通りchefの構築を済ませたので、この辺でchefサーバのバックアップを仕掛けることにしました。
chefも構築を始めるとノウハウの塊になってきて消失するとかなり痛いです。

node,role,data bagのバックアップ

node,role,data bagのバックアップはドキュメントに書いてあるchef_server_backup.rbを使うことにしました。

これを使えば、node,role,data bagは$cwd/.chef/chef_server_backupというディレクトリにそれぞれのオブジェクトがjson形式でバックアップされます。

chef_server_backup.rbの使い方

使い方ってほどでもないのですが、水増しのため解説します。

まずはchef_server_backup.rbを入手します。

cd /path/to/working_dir
curl -O https://raw.github.com/jtimberman/knife-scripts/master/chef_server_backup.rb

そして、knife execchef_server_backup.rbを実行します。
もちろんknifeが正常に動くことが大前提です。

knife exec /path/to/chef_server_backup.rb

以上!

でも、これではせっかく時間をかけて作ったCookbookがバックアップされません。
Cookbookはこれとは別にバックアップを取る必要があります。

Cookbookのバックアップは自作した

いろしろ探したのですが、Cookbookのバックアップツールは見つかりませんでした。
chef_server_backup.rbと同じような感じでできるのが一番かっこいいのですが、残念ながらそれをするためのRuby言語のスキルが僕には圧倒的に不足しています。

ということで、いろいろ悩んだ結果pythonでknifeを使って、cookbookのリストを取得し、それをknifeコマンドでdownloadするベタな方法にしました。
何で悩んだかというとcookbookのバージョンです。
ご存知の通りCookbookは複数のバージョンを保持することができます。それを全てバックアップを取ろうとするとどうやっても僕のスキルではbashで実装できなかったので、pythonで作りました。

恥をしのんでそのスクリプトを晒します。

#!/usr/bin/python
# -*- coding:utf-8 -*-
import subprocess,sys,os,os.path,shutil
import datetime,locale

import smtplib
from email.MIMEText import MIMEText
from email.Utils import formatdate

backup_dir = '/data/chef-repo/.chef/cookbook'
subject = 'Cookbook Backup ERROR'
mail_from = 'from@domain'
mail_to = 'to@domain'

# 現在時刻を取得する関数
def getnow():
  return datetime.datetime.today().strftime("%Y/%m/%d %H:%M:%S")

# メール構築
def create_message(f,to,subject,body):
  message = MIMEText(body)
  message['Subject'] = subject
  message['From'] = f
  message['To'] = to
  message['Date'] = formatdate()
  return message

# メール送信
def sendMail(f, to, message):
  s = smtplib.SMTP()
  s.connect()
  s.sendmail(f, [to], message.as_string())
  s.close()
 
# 処理開始
print '---START {0} ----'.format(getnow())

# 作業用ディレクトリを消す
if os.path.isdir(backup_dir):
  print "Remove {0}".format(backup_dir)
  try:
    shutil.rmtree(backup_dir)
  except OSError:
    error_message = "ERROR: {0} can not delete.".format(backup_dir)
    print error_message
    sendMail(mail_from,mail_to,create_message(mail_from,mail_to,subject,error_message))
    sys.exit(1)

# 作業用ディレクトリの作成
try:
  os.mkdir(backup_dir)
except OSError:
  error_message = "ERROR: {0} can not create.".format(backup_dir)
  print error_message
  sendMail(mail_from,mail_to,create_message(mail_from,mail_to,subject,error_message))
  sys.exit(1)

# cookbookリストを取得する
cmdline = ['/usr/bin/knife', 'cookbook', 'list', '-a']
subproc_args = {
  'stdin':subprocess.PIPE,
  'stdout':subprocess.PIPE,
  'stderr':subprocess.STDOUT,
  'close_fds':True,
}

try:
  p = subprocess.Popen(cmdline, **subproc_args)
except OSError:
  error_message = "Failed to execute command. : {0}".format(cmdline.__str__())
  print error_message
  sendMail(mail_from,mail_to,create_message(mail_from,mail_to,subject,error_message))
  sys.exit(1)

cmd_args = {
  'stdin':subprocess.PIPE,
  'stdout':subprocess.PIPE,
  'stderr':subprocess.STDOUT,
  'close_fds':True,
}

# Cookbookごとにdownload
while True:
  line = p.stdout.readline().split()
  if not line:
    break
  cookbook_name = line.pop(0)
  print cookbook_name
  pp = ""

  # 複数バージョンがある場合はそれらをdownload
  for version in line:
    cmdline = "/usr/bin/knife cookbook download {0} {1} -d {2}".format(cookbook_name, version, backup_dir)
    print "Downloading {0} {1}: ".format(cookbook_name, version),
    try:
      ret = os.system(cmdline)
    except OSError:
      print "ERROR"
      error_message = "Failed to execute command.: {0}".format(cmdline.__str__())
      print error_message
      sendMail(mail_from,mail_to,create_message(mail_from,mail_to,subject,error_message))
      sys.exit(1)

    print "SUCSESS"
    
print '---END {0} ----'.format(getnow())

このスクリプトで/path/to/.chef/cookbookに全てのcookbookをダウンロードして、chef_server_backup.rbで取得したオブジェクトのjsonをまとめてtarで固めてバックアップを取ることにしました。

おしまい。

[chef] data bag活用法

data_bagを使う

chefにはdata_bagというものがあります。
attributeとは何が違うのかなーと思っていましたが、良い使い方を見つけましたのでご紹介させていただきます。

ユーザアカウントは全てdata_bagで管理

ユーザアカウント系は全てdata_bagで管理すると楽かも!と思ってやってみました。
例えばログインアカウント。
サーバの機能や場所ごとにroleを作りますが、そのroleの中でログインアカウントを管理するrecipeを入れるとします。
data_bagを使わないとrecpieもしくはroleのattributeにユーザ情報を格納して、recipeのuser resourceでユーザを作成することになると思います。
僕の管理している環境では鍵認証が基本なので公開鍵の情報も管理をしたいです。公開鍵はユーザに紐づいていてどのサーバでも同じ鍵で入れると便利ですよね。
そこで、data_bagを活用。
data_bagはdata_bagそのものと、data_bag_itemという2階層構造になっているようです。
まず、data_bag['users']というdata_bagを作成します。  
そしてその中にdata_bag_item['login_account']というdata_bag_itemを作ります。
data_bag_item[‘login_account’]は配列でユーザ情報が格納されています。
例えばこんな感じ。

{
  "users":[
    {
      "system": [
        "role1",
        "role2"
      ],
      "key": "ssh-rsa xxx",
      "name": "user01",
      "active": true,
      "uid": 10001
    },
    {
      "system": [
        "role1",
        "role2"
      ],
      "key": "ssh-rsa xxx",
      "name": "user02",
      "active": true,
      "uid": 10002
    }
  ],
  "id": "login_account"
}

ユーザ情報は

  • uid
  • account名
  • 公開鍵
  • ユーザを作るシステム
  • 有効フラグ

という情報からなっています。

ユーザ作成のrecipeはこんな感じ。

# User add or delete
for u in data_bag_item('users','login_account')['users']
act = false

  # 適用システムかどうかを確認する
  for role in node["roles"]
    for system in  u["system"]
      if role == system
        act = true
        break
      end
    end
    if act
      break
    end
  end

  if ! act
    next
  end

 # activeがfalseなら削除
 user_act = u["active"] ? "create":"remove"

 # ホームディレクトリの文字列作成
 home_dir = "/home/" + u["name"]

 # useradd
 user u["name"] do
  uid u["uid"]
  gid u["uid"]
  shell "/bin/bash"
  home home_dir
  action user_act
 end

 # .ssh directory
 ssh_dir = home_dir + "/.ssh"
 # authorized_keys
 authorized_keys = ssh_dir + "/authorized_keys"

 # .ssh directory作成
 if u["active"]
  directory ssh_dir do
   owner u["name"]
   group "ssh_users"
   mode  0700
  end
  
  # authorized_keyの作成
  if u["key"]
   file authorized_keys do
    owner u["name"]
    mode  0600
    content u["key"] + " " + u["name"]
   end
  end
 end
end

こんな感じで、各サーバのユーザアカウントを管理しています。
もう少し大規模になればディレクトリサービスとかを使うのですが、そこまででもない規模はこれでいいかなーと。

これをやっていてchefの残念なところを発見。

 # activeがfalseなら削除
 user_act = u["active"] ? "create":"remove"

この部分はactiveを見て、trueならcreate,falseならremoveというactionのパラメータを指定しています。
理想はrecipeからユーザが消えたら自動で削除してくれるのが良いのですが、どうもそうではないようなので、不要になったユーザはactiveをfalseにして、明示的に削除処理をするようにしています。

login_accountの説明でだいぶ長くなってしまいましたが、このdata_bag["users"]はユーザ管理data_bagとして、login_accountの他に、例えばdata_bag_item['mysql']とかdata_bag_item['basic']とかを登録して、ここを見ればユーザ管理が出来るというように使いたいと思っています。

おしまい。

[chef]サーバアカウントの管理と公開鍵の登録

最近は、いよいよchefの本格的な運用に向けてrecipeを書く日々が続いています。
その中から一コマをご紹介。

Roleで登録するユーザを分ける

chefでサーバのアカウント管理をすることになって、どのようにするのが効率的かを考えました。
登録するアカウントは全てのサーバで同じというわけではなく、属性によって異なります。
そこで、属性ごとにRoleを用意して、それぞれユーザをattributeで登録することにしました。
ユーザ登録のRecipeは共通です。

Roleはこんな感じ。

{
  "json_class": "Chef::Role",
  "description": "desc",
  "default_attributes": {
    "users": [
      {
        "active": true,
        "uid": 10001,
        "key": "ssh-rsa xxxxxx",
        "name": "user1"
      },
      {
        "active": true,
        "uid": 10002,
        "key": "ssh-rsa yyyyyy",
        "name": "user2"
      }
    ]
  },
  "env_run_lists": {
  },
  "override_attributes": {
  },
  "name": "group1",
  "run_list": [
    "recipe[add_user]",
  ],
  "chef_type": "role"
}

attributeでusersという配列を用意して、その中に以下の要素を持ったオブジェクトを入れています。

  • 名前
  • UID
  • 公開鍵文字列
  • active flag

forのloopでユーザ登録

RecipeではRoleのattribute[users]を参照しながらresource[user]でユーザを登録します。
usersは配列なのでRecipeの中でforのloopを使います。
このresource[user]ですが、不要になったアカウントを削除すれば勝手にaction :removeがはしるかと期待したんですが、そううまく行かなかったので、activeフラグを用意して、不要になったユーザはそれををfalseに変更するとaction :removeになるようにRecipeを作りました。

作ったrecipeは以下のような感じです。

# User add or delete
for u in node["users"]

 # activeがfalseなら削除
 user_act = u["active"] ? "create" : "remove"

 # ホームディレクトリの文字列作成
 home_dir = "/home/" + u["name"]

 # useradd
 user u["name"] do
  uid u["uid"]
  gid "users"
  shell "/bin/bash"
  home home_dir
  action user_act
 end
end

公開鍵の登録

こんな感じでユーザを登録して、ついでにログインするための公開鍵の登録もします。
公開鍵の情報はあらかじめRoleのattributeに登録しておきます。
authorized_keysを作るresourceはfileを使いました。

 # .ssh directory
 ssh_dir = home_dir + "/.ssh"
 # authorized_keys
 authorized_keys = ssh_dir + "/authorized_keys"

 # .ssh directory作成
 if u["active"]
  directory ssh_dir do
   owner u["name"]
   group "ssh_users"
   mode  0700
  end
  
  # authorized_keyの作成
  if u["key"]
   file authorized_keys do
    owner u["name"]
    mode  0600
    content u["key"] + " " + u["name"]
   end
  end

これは実際はさっきのforの中に入っています。

これで、アカウント登録と公開鍵の登録は完了です。

やっぱりchefを使うメリットは同じ機能のサーバを同じ設定に出来るところ。
こんなのを普通にしこしこやったら絶対に設定漏れが出てしまう良い例だと思う。

[chef] clientとnodeの関係

解りづらいclient

chefの要素でclientとnodeがあります。
いろいろ調べる中で、このことを解説しているところがあるのですが、自分は以下のように解釈していました。
Node = 管理対象サーバ
client = Chef Serverとやり取りするインタフェース

この解釈で概ね誤りは無いようなんですが、一つ大きく勘違いをしているところがありました。

Clientの流用は出来ない

どういうことかというと、一つのclient、もっと言えばClientのPrivate Keyを複数のサーバに配布して「Nodeは増えるけど、Clientは増えない」ということは出来なそうな感じです。

chefを使う理由は関するサーバの台数が増えて来て管理が大変なので、一つ例えば「forServer」というclientを作っておいて、そのprivate keyをchef管理下に入るサーバに置いておいて、clientはそれを使ってもらうということにしたかったのです。

chefの管理下に入るには以下の手順が行われます。

  1. 管理対象サーバにchef-clientをセットアップ
  2. chef-serverのclient[chef-validator]のprivate keyをvalidation.pemとして配置。(通常は/etc/chef/validation.pem
  3. 管理対象サーバでchef-clientコマンドを実行
  4. client[chef-validator]によって、chef-serverにnode登録される
  5. 管理対象サーバ用のclientが作られて、管理対象サーバにはclient.pemが配置される(通常は/etc/chef/client.pem

こんな手順でサーバをchef管理下に入れていきますが、出来れば各サーバごとにclientが増えるのは勘弁してほしいところ。
ちなみにあらかじめ作成したclientのprivate keyを配置してclient.rbに以下の行を指定してもダメでした。

client_key   "/etc/chef/client.pem"

どうにかならないものでしょうか。

[chef] attributeの理解

今回はattributeの話。
お約束ですが、この記事の内容は私が一人で分かったつもりになって書いていることなので、誤認やウソが書いてありましたらご指摘してくださると助かります。

attributeとは

Recipeではできるだけ汎用的に書いた方がいいという雰囲気がありますが、それはその通りだと思います。
その汎用性を持たせる機能を担っているのが、providerとこのattributeでは無いでしょうか。
providerはOSなどの環境を吸収してくれて、attributeは変数として使うことができます。

分からなかったのはあちこちでattributeが設定できること

CookbookやRecipeを調べてたり、ドキュメントを読んでいたりすると、あちこちでdefault_attributesやらoverride_attributesを見かけます。
いったいどこでattributeを設定するのが正しいの?と混乱してしまっていました。

その混乱を鎮めたのがドキュメント以下の記述。

Chef > Home > Chef Essentials > Attributes > Precedence

これを見ると設定箇所によってプライオリティがあり、どこで設定するのが適切かということが分かった気がします。

イメージとしては、CookbookのCOOKBOOK/attribute/default.rbではデフォルト値を設定。
roleのdefault_attributesではそのRoleに適した値を設定。
という感じでしょうか。

具体例

具体例を挙げてみます。
シナリオはいつもの通りAWS EC2でchefを使うという想定です。
複数のAWSアカウントを持っていて、横断的にchefを使いたい。
例としてAWSアカウントの各種認証情報をサーバに保管するRecipeを作ります。

template "/path/to/credencial" do
 source "credencial.erb"
 owner "root"
 group "root"
 mode 0644
 variables(
  :cert      => node["ec2"]["cert"],
  :pk        => node["ec2"]["cert"],
  :awsid     => node["ec2"]["awsid"],
  :accesskey => node["s3"]["access_key_id"],
  :secretkey => node["s3"]["secret_key"],
  :region    => node["ec2"]["region"]
 )
end

これは/path/to/credencialというファイルにAPIの証明書やらaccesskey_idなどの情報を記載したファイルのResourceです。
もちろんCOOKBOOK/templates/default/credencial.erbというテンプレートファイルも用意します。
そして、COOKBOOK/attribute/default.rbに以下のように上記resourceで使っているattributeのデフォルト値を設定します。

# s3 key 
default["s3"]["access_key_id"] = ""
default["s3"]["secret_key"] = ""

# ec2 credencial
default["ec2"]["key_location"] = ""
default["ec2"]["cert"] = ""
default["ec2"]["pk"] = ""
default["ec2"]["awsid"] = ""
default["ec2"]["region"] = "ap-northeast-1"

デフォルト値と言ってもそれぞれ、適切な値を入れないと意味がないので、適当に空に設定してみました。

次にRoleを作ります。
account_aというAWS Accountに紐づいたRole[account_a]というroleを作ります。

name "account_a"
description "AWS account_a account servers"
run_list("recipe[Ec2Init]")
default_attributes({
  "s3" => {
    "access_key_id" => "xxxxxxxxxxxxxxxx",
    "secret_key" =>  "xxxxxxxxxxxxxxxxxxx"
  },
  "ec2" => {
    "key_location" => "hoge",
    "cert" => "cert-xxxx.pem",
    "pk" => "pk-xxxx.pem",
    "awsid" => "xxxx"
  }
})

こんな感じで、attributeを便利に使うことができました。

まとめ

attributeが使えるようになって、汎用性がぐっと広がりました。
やってみて分かったんですが、attributeは最終的にnodeに付与されて、node['hoge']['piyo']という感じで指定するんですね。

次回はなにかな?