[Chef] Cookbook,Recipe,Roleの関係

Chef検証再開。

Chefを設計・運用するうえで、どうしても理解しなければいけないのが、CookbookRecipeとRole`の関係。

とりあえず、それを理解するためにこのスライドを見てみた。

Resouceは設定の最小単位

Resouceは各種設定の最小単位と解釈しました。
ファイルを配置する、ソフトウェアをインストールする、ユーザを作る、等など。

これらの設定はOSによってコマンドはまちまちですが、それぞれのコマンドの違いはPlatformやらProviderやらで吸収されて、ResouceではそれらのOSの違いを意識せずに設定を指定できるところが良いところです。

RecipeはResourceのリスト

スライドに

Recipes are lists of Resources

と書いてありました。

僕は、「Recipeは一つの操作をするために必要なこと(Resource)がまとまったもの」と解釈しました。
例えば、apacheをインストールする場合、以下のような設定をする必要があります。

  • apacheのインストール
  • httpd.confの設定
  • 必要に応じて各種設定

それらを一つのRecipeの中に書くことで、apacheのインストールという一つの操作を完了することができます。

[ Cookbook -ne Role ]

分からなかったのが、CookbookとRoleの区別でした。
Roleは単語の意味からWebServerなどの役割を指定して、そのRoleをNodeに適用するとWebServerの機能が構築されてWebServerとして利用できるものと理解しています。

では、Cookbookは?

Cookbookの役割がよくわからなかったので、試しに[Opscode Public Cookbooks](Opscode Public Cookbooks)からApacheのCookbookをダウンロードして中を見てみました。
Cookbook[“apache”]のRecipeのdefaults.rbはapache本体のインストールでした。
そして、defaultではないその他のRecipeはapache modulesのインストールでした。

これで何となくCookbookが分かって来た気がしますが、さらに理解を深めるためcookbook[MySQL]を見てみました。

cookbook[“MySQL”]のRecipesの中にはdefault.rb,client.rb,server.rbがありました。
Recipeのdefault.rbには一行。

include_recipe "mysql::client"

CookbookはRecipeのグループ

これらの調査から、Cookbookはソフトウェアや設定のグルーピングとして使うという理解に至りました。
特定のソフトウェアとそれに付属するソフトウェアや設定などを一つのCookbookにまとめることができそうです。

Roleを書く

ということでまとめという意味でWebServerというRoleがあったと仮定して、そのRoleのファイルを書いてみます。


name "webserver"
run_list "recipe[common]","recipe[apache2]", "recipe[apache2::mod_proxy]","role[product]", "role[datacenter01]"

まだまだ続く

とりあえずここまでなーんとなく分かった気がします。

でも、まだまだ分からないことばかり。
例えば、attributeとか。
さらに調査を続けます。

もし、この記事の内容に理解に誤りがあれば、是非ご指摘ください。お待ちしています。

AWS S3をJavaで

JavaでAWS S3を試す

AWS S3のパフォーマンス測定を行うため、JavaでS3の操作をしてみました。

準備

事前準備として以下のSDKやらライブラリを事前に用意します。

Java 用 AWS SDKは読んで時のごとく、JavaでAWSを操作するためのSDKです。
Apache HttpComponents Clientは↑を使うために必要です。

次に必要な情報として、AWSのセキュリティ証明書のページでaccess key IDとSecret access keyを用意しておきます。

最後にAWSのManagement ConsoleでBucketを作っておきます。
Bucketはそう繰り返し作るものでもないので、Javaでやらなくても良いでしょ?

Bucketリストを取得

まずは接続テストを兼ねてBucketリストを取得してみます。
以下のようにS3Test.javaを書きます。

package s3test;

import com.amazonaws.auth.PropertiesCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.Bucket;

public class S3Test {

  static void getList(AmazonS3 s3){
    for(Bucket bucket:s3.listBuckets()){
      System.out.println(bucket.getName());
    }
  }
  
  public static void main(String[] args) {
    
    System.out.println("START");
    AmazonS3 s3 = null;
    try {
      s3 = new AmazonS3Client(new PropertiesCredentials(
            S3Test.class.getResourceAsStream("AwsCredentials.properties")));
    }catch(Exception e){
      System.err.println(e.getMessage());
      System.exit(1);
    }
    
    System.out.println("======= Bucket List ========");
    getList(s3);
  }
}

そして、そのjavaファイルと同じディレクトリにAwsCredentials.propertiesというaccesskeyとSecretKeyが書いてあるファイルを置きます。

accessKey = xxxxxxxxxxxxxxxxxxxxxx
secretKey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

適当にコンパイルして、実行してみます。

$ java -jar s3test.jar
START
======= Bucket List ========
your-original-bucket

アップロードするファイルの準備

ファイルアップロードを実装する前にローカルの環境にアップロードするファイルを用意しておきます。
Javaでそこからやっても良いのですが、面倒なので事前に用意することにしました。
種となる10KBのファイルを用意します。

$ i=0; while [ $i -lt 1024 ]; do echo -n '0123456789' >> orig01; i=$(($i+1)); done
$ cat orig01
012345678901234567890123456789012345678901234567890123456789
.
.
.

そしてこの種ファイルで10KBのファイルを量産します。

$ mkdir /var/tmp/files
$ cd /var/tmp/files
$ i=0; while [ $i -lt 300 ]; do cp orig01 test-${i}.txt; i=$(($i+1)); done

とりあえず、test-??.txtというファイルを300個作りました。

ファイルのアップロード

アップロードするファイルができましたのでさっそくファイルのアップロードを実装します。
冒頭で書いた通り、今回の目的はパフォーマンステストなので並列でアクセスできるようにthreadで実装します。

class S3TestThread implements Runnable {
  
  String mode = "";
  int index = 0;
  String localPath = "/var/tmp/files";
  String filePrefix = "test-";
  String bucket = "your-original-bucket";
  String s3Path = "test01/" + filePrefix;
  AmazonS3 s3;
  long start, end;
  
  S3TestThread(AmazonS3 s, String m, int i){
    s3 = s;
    mode = m;
    index = i;
  }
  
  void uploadFile(int i){
    
    // uploadするファイル
    File localFile = new File(localPath + "/" + filePrefix + i + ".txt");
    String s3File = s3Path + i + ".txt";
    
    System.out.println("UploadFile: " + localFile.getName());
    
    // 時間計測用のスタート時間
    start = System.currentTimeMillis();
    
    // upload
    s3.putObject(bucket, s3File, localFile);
    
    //時間計測用終了時間
    end = System.currentTimeMillis();
    
    //処理時間出力
    System.out.println(s3File + ":" + (end - start));
    
    //認証無しでアクセスできるように設定(GETのパフォーマンステスト用)
    s3.setObjectAcl(bucket, s3File, CannedAccessControlList.PublicRead);
    
  }
  
  @Override
  public void run(){
    if(mode.equals("put")){
      uploadFile(index);
    }
  }
}

コンストラクタではs3の接続オブジェクトと、動作モード(今回はput)、そしてthreadのインデックスを渡すようにしました。
動作モードについてはこれからdeleteなども必要になるかもしれないので、そのためのスイッチ。
そして、パフォーマンスを測るためにuploadする前と後で時間を取ります。
最後にGETのパフォーマンス測定はApache JMeterを使いたいのでACLをPublicReadに設定しています。

mainは省略。

実行結果は以下の通り。

$ java -jar s3test.jar 10 put
START
=======put==========
UploadFile: test-0.txt
UploadFile: test-1.txt
UploadFile: test-2.txt
UploadFile: test-3.txt
UploadFile: test-4.txt
UploadFile: test-5.txt
UploadFile: test-6.txt
UploadFile: test-7.txt
UploadFile: test-8.txt
UploadFile: test-9.txt
test01/test-2.txt:1575
test01/test-3.txt:1580
test01/test-8.txt:1584
test01/test-1.txt:1591
test01/test-4.txt:1588
test01/test-6.txt:1588
test01/test-9.txt:1610
test01/test-7.txt:1615
test01/test-5.txt:1621
test01/test-0.txt:1638

まとめ

SDKがあるのでご覧の通りとても簡単。
ちなみにSDKはJavaだけでなくPythonもあるので、機会があればそっちもやってみたいです。

おしまい。



pythonでoauth署名付きリクエスト(1)

スクラッチでごりごり

pythonでoauthの署名付きリクエストを送る必要に迫られて、よく調べもせずにスクラッチでごりごり書いてみました。oauth2というモジュールがあることも知らずに・・・。

oauthに必要なパラメータは以下のように設定しました。

from time import time
import random

oauth_consumer_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
oauth_consumer_secret = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
oauth_timestamp = int(time())
oauth_nonce = random.randint(1000000000, 9000000000)
oauth_version = '1.0'
oauth_signature_method = 'HMAC-SHA1'

oauth_nonceのランダム文字列は適当にrandom.randint(1000000000, 9000000000)にしてしまいました。

次に上記のパラメータを配列に入れます。

sig_param = []
sig_param.append("{0}={1}".format('oauth_consumer_key',oauth_consumer_key))
sig_param.append("{0}={1}".format('oauth_signature_method',oauth_signature_method))
sig_param.append("{0}={1}".format('oauth_timestamp',oauth_timestamp))
sig_param.append("{0}={1}".format("oauth_nonce",oauth_nonce))
sig_param.append("{0}={1}".format('oauth_version',oauth_version))

# 最後にsort
sig_param.sort()

そして、signatureを作ります。

import urllib,base64,hmac,hashlib

# signatureのkeyをconsumer_secretとtokenで作ります。
# 今回はtokenはありません。
signature_key = urllib.quote_plus(oauth_consumer_secret) + '&'

# requestを出すmethodを設定
request_method = 'PUT'

# requestを出すURLを設定
request_url = 'http://api.piyopiyo.com/oauth'

# パラメータを設定
oauth_param = ''
for val in sig_param:
  if len(oauth_param) == 0:
    oauth_param += val
  else:
    oauth_param = "{0}".format(oauth_param, val)

# signatureを作る文字列を作る
signature_string = "{0}".format(urllib.quote_plus(request_method),
                                        urllib.quote_plus(request_url),
                                        urllib.quote_plus(auth_param))


# signatureを作る
signature = urllib.quote_plus(base64.b64encode(hmac.new(
                                                signature_key,
                                                signature_string,
                                                hashlib.sha1).digest())

こんな感じでやってみました。
さくっと書いてしまいましたが結構ハマりました。
ハマりポイントは2つ。

quote_plusを使う

urlencodeにはurllib.quoteurllib.quote_plusがあるのですが、quote_plusを使いましょう。
quoteだと「/」(スラッシュ)がencodeされません。これで小一時間ハマりました。

署名はhmacのdigest()を使う

これは僕だけかもしれませんが、最初hmacのhexdigest()を使ってしまってハマりました。
digest()を使いましょう。

それよりもoauth2を使おう!

oauth2モジュールを使ったらさくっとできそうで、軽くショックを受けました。
次回はoauth2モジュールを使って試してみます。

Pythonスタートブック

初めてのPython 第3版

Chef-Serverを使ってみる(2)

前回は、Recipeの登録と、Roleの登録をしたので、今回はいよいよもう一台サーバを用意して、Recipeの適用をしてみたいと思います。

Nodeの登録

まずはNode登録から。
以下のコマンドでノードを登録します。
ついでにRoleも設定します。

knife node create node01

{
  "chef_environment": "_default",
  "normal": {
  },
  "run_list": [
    "role[front]"
  ],
  "name": "node01"
}

確認してみます。

$ knife node show mode01
Node Name:   node01
Environment: _default
FQDN:        
IP:          
Run List:    role[front]
Roles:       
Recipes:     
Platform:

Node用のClientの作成

Node側の設定の前にNode用のClientを作っておきます。
ちょっと混乱してしまったのですが、Clientとはchef-clientのことで、Chef-Serverとお話しするインタフェースのことです。

  • WebUI用Client
  • knife用Client
  • Node用Client

あたりを用意しておけば良いのかなと思います。
今回はNode用Clientを作ります。
clientの作成はいつものknifeで。

$ export EDITOR=vi
$ knife client create client01 -c /data/chef/.chef/knife.rb
※とくに修正せずに保存
Created client[client02]
-----BEGIN RSA PRIVATE KEY-----
.
.
.
-----END RSA PRIVATE KEY-----

これでClient作成完了。
作成時に出力されたPrivate Keyは大事に保管しておきましょう。

Node側の設定

Chef-ServerにNodeの登録とNode用Clientができたので、ノードのサーバにchef-clientを設定します。
まずはお約束のruby-gemsとruby-develをyumでインストール。

$ sudo yum install -y rubygems.noarch ruby-devel.x86_64

そして、gemでchefをインストール。

$ sudo gem install chef

chef-clientの設定

まずはchef-clientの設定を行います。
knifeを使って設定のひな形を作ります。

$ sudo knife configure client /etc/chef
WARNING: No knife configuration file found
Creating client configuration
Writing client.rb
Writing validation.pem

これで/etc/chefclient.rbvalidation.pemができますので、できたclient.rbを自分の環境に変更します。
僕はChef Configuration Settingsを参照しながら、以下のように修正してみました。

$ sudo vi /etc/chef/client.rb

log_level        :info
log_location     STDOUT
chef_server_url  'http://chef-server:4000'
validation_client_name 'client01'
node_name node01

validation.pemの配置

そして、/etc/chef/validation.pemを配置します。
validation.pemはclient.rbのvalidation_client_nameで設定したclientのPrivate Keyです。
chef-serverでNode用clientのclient01を作成したときに保管したPrivate Keyを/etc/chef/validation.pemというファイル名で配置します。

これで準備完了のはずなので、node01でchef-clientを実行してみます。

$ sudo chef-client
INFO: *** Chef 0.10.8 ***
INFO: Client key /etc/chef/client.pem is not present - registering
INFO: HTTP Request Returned 403 Forbidden: You are not allowed to take this action.
FATAL: Stacktrace dumped to /var/chef/cache/chef-stacktrace.out
FATAL: Net::HTTPServerException: 403 "Forbidden"

「403 Forbidden」と言われてしまった。
chef-server側のログを見てみます。

$ view /var/log/chef/server.log

merb : chef-server (api) : worker (port 4000) ~ Params: {"format"=>nil, "controller"=>"nodes", "action"=>"show", "id"=>"node01"}
merb : chef-server (api) : worker (port 4000) ~ {:action_time=>0.001098, :after_filters_time=>1.6e-05, :before_filters_time=>7.0e-05, :dispatch_time=>0.008263}
merb : chef-server (api) : worker (port 4000) ~
merb : chef-server (api) : worker (port 4000) ~ Started request handling: Fri Apr 27 00:15:40 +0000 2012
merb : chef-server (api) : worker (port 4000) ~ Params: {"controller"=>"clients", "action"=>"create", "name"=>"node01", "admin"=>false}
merb : chef-server (api) : worker (port 4000) ~ You are not allowed to take this action. - (Merb::ControllerExceptions::Forbidden)

なんかNodeをcreateしようとして”You are not allowed to take this action.“と言われている様子。
すでにNodeは登録しているのにcreateするのはよくわかりませんがとりあえず、Node用Clientでadminをtrueに変更します。

$ knife client edit client01

{
  "json_class": "Chef::ApiClient",
  "_rev": "1-3d9d9fefc931326527ab1bb9d5c43f0a",
  "admin": false,
  "public_key": ….,
  "name": "client01",
  "chef_type": "client"
}

これでもう一度chef-clientを実行すると無事、Recipeが適用されました。

おしまい。

Chef-Serverを使ってみる(1)

今回は、前回構築したchef-serverを使ってみようと思います。

chef-serverにレシピを登録

まずはchef-serverにレシピを登録してみます。
最初にchef-soloでもやったchef-repoを作ります。
今回は/dataにchef-repoを作ります。

$ sudo su -
# mkdir /data/chef
# chown chef:chef /data/chef
# cd /data/chef
# su -c 'git clone git://github.com/opscode/chef-repo.git' chef

次にknifeの設定。

# sudo su - chef
$ bash
$ knife configure
WARNING: No knife configuration file found
Where should I put the config file? [~/.chef/knife.rb] /data/chef/.chef/knife.rb
Please enter the chef server URL: [http://starmp-dev-chef-01:4000] http://localhost:4000
Please enter an existing username or clientname for the API: [www] chef
Please enter the validation clientname: [chef-validator] 
Please enter the location of the validation key: [/etc/chef/validation.pem] 
Please enter the path to a chef repository (or leave blank): /data/chef/chef-repo

そしてCOOKBOOKを作成。

$ knife cookbook create Ec2Base -c /data/chef/.chef/knife.rb
** Creating cookbook Ec2Base
** Creating README for cookbook: Ec2Base
** Creating metadata for cookbook: Ec2Base
$ ls /data/chef/chef-repo/cookbooks/
Ec2Base  README.md

レシピを/data/chef/chef-repo/cookbook/Ec2Base/recipes/initial.rbに登録します。
今回登録するレシピは前回作成したものを使います。

作ったCOOKBOOKをサーバにアップロード

ここからが初体験。
作ったCOOKBOOKをchef-serverにアップロードしてみます。

$ knife cookbook upload -a -o /data/chef/chef-repo/cookbooks -c /data/chef/.chef/knife.rb 
ERROR: Your private key could not be loaded from /data/chef/.chef/chef.pem
Check your configuration file and ensure that your private key is readable

はい、だめー。

サーバとの通信用に鍵交換などをする必要があるっぽいです。
ちょっと調べてみるとknife configure -iで設定するっぽいのでもう一度knifeの設定をやり直します。

$ knife configure -c /data/chef/.chef/knife.rb -i
Overwrite /data/chef/.chef/knife.rb? (Y/N) y
Please enter the chef server URL: [http://server-01:4000] http://localhost:4000
Please enter a clientname for the new client: [www] knife
Please enter the existing admin clientname: [chef-webui] 
Please enter the location of the existing admin client's private key: [/etc/chef/webui.pem] 
Please enter the validation clientname: [chef-validator] 
Please enter the location of the validation key: [/etc/chef/validation.pem]  
Please enter the path to a chef repository (or leave blank): /data/chef/chef-repo
Creating initial API user...
Created client[knife]
Configuration file written to /data/chef/.chef/knife.rb

設定に成功していればknife client listでchef-serverのclient一覧が出るようなのでやってみます。

$ knife client list
  chef-validator
  chef-webui
  cilent-test
  knife

できたー。

それではさっそくCOOKBOOKをアップロードします。

$ knife cookbook upload -a -o /data/chef/chef-repo/cookbooks -c /data/chef/.chef/knife.rb 
Uploading Ec2Base             [0.0.1]
upload complete

できました。

knife cookbook listで確認してみます。

$ knife cookbook list
Ec2Base   0.0.1

登録されています。

Roleの登録

次にこのレシピを使うRoleの設定をしてみます。
今回はknifeを使ってchef-serverのrole設定をしてみます。

$ export EDITOR=vi
$ knife role create web

{
  "json_class": "Chef::Role",
  "name": "web",
  "env_run_lists": {
  },
  "description": "Web Server",
  "chef_type": "role",
  "override_attributes": {
  },
  "default_attributes": {
  },
  "run_list": [
    "recipe[initial]"
    "recipe[Ec2Base::initial]"
  ]
}

登録されているか確認します。
※追記:run_listの書き方が間違っていましたので訂正しました。
書式はrecipe[COOKBOOK::RECIPE]でした。
recipeがdefault.rbの場合はrecipe[COOKBOOK]でOK。

$ knife role show web
chef_type:            role
default_attributes:   
description:          Web Server
env_run_lists:        
json_class:           Chef::Role
name:                 web
override_attributes:  
run_list:             recipe[initial]

今回はここまで。
次回はNodeを登録して、実際にRecipeを反映させてみたいと思います。

chefたのしー!knifeサイコー!

chef-server構築

gemでchefをインストール

今度はchefをS/Cで動かすべく、またイチからchefを構築します。
今度の環境はCentOS6.2 x86_64です。

さっそく

sudo gem install chef

を実行。
しかしエラー。
ruby-develが無いようなのでインストール。

sudo yum install -y ruby-devel.x86_64

そして再び

sudo gem install chef

chef-soloでchef-serverをインストール

chef-serverのインストールは
さくらのVPSにchef-serverをインストール(kikumotoのメモ帳)
を参考にさせていただきました。ありがとうございます!
chef-soloでchef-serverをインストールするのがおもしろい。

まずは作業用のディレクトリを作る。
mkdir ~/tmp_chef

そこにsolo.rbを作成。

vi tmp_chef/solo.rb

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

同じく、chef.jsonも作成。

vi tmp_chef/chef.json

{
  "chef": {
    "server_url": "http://localhost:4000",
    "webui_enabled": true,
    "init_style": "init"
  },
  "run_list": [ "recipe[chef::bootstrap_server]" ]
}

そして、chef-soloコマンドでchef-serverをインストール。

$ sudo su -
# chef-solo -c /home/piyo/tmp_chef/solo.rb -j /home/piyo/tmp_chef/chef.json -r http://s3.amazonaws.com/chef-solo/bootstrap-latest.tar.gz

うまく行ったでしょうか。
うまく行けばport4040が開いていて、ブラウザで接続できるはずです。

# netstat -an | grep 4000
tcp        0      0 0.0.0.0:4000                0.0.0.0:*                   LISTEN

http://your-server:4040

デフォルトのユーザ/パスワードはadmin/p@ssw0rd1です。

とりあえず、WEB UIにアクセスすることはできました。
が、よーわからんという感じなので、次回は使い方をやりたいと思います。
せっかくWEB UIがあるのですが、できたらCLIで操作したいなと思ったりもしてます。
続く。

ChefのRecipeを書く(2)

ntpのインストール

引き続きyumでntpのインストールを行います。
cookbook/COOKBOOK/recipes/default.rbに以下の内容を追加します。

package "ntp" do
 action :install
end

で、実行をしてみます。

$ sudo chef-solo -c .chef/solo.rb -j .chef/chef.json
INFO: *** Chef 0.10.8 ***
.
.
.
INFO: Processing package[ntp] action install (base_packages::default line 53)
INFO: package[ntp] installed version 4.2.2p1-15.el5.centos.1
INFO: Chef Run complete in 25.093701 seconds
INFO: Running report handlers
INFO: Report handlers complete

$ rpm -qa | grep ntp
ntp-4.2.2p1-15.el5.centos.1

無事インストールできました!

これで予定していたものは全て終了ですが、物足りないのでntp.confをAWSのS3から取ってくるのをやってみたいと思います。

S3に接続するためのs3cmdの設定から

S3に接続するときにはs3cmdを使います。
s3cmdは始めに

s3cmd --configure

というコマンドで~/.s3cfgという設定ファイルを作るのですが、このコマンドは対話型で使いづらいので、この.s3cfgをテンプレートとして使いたいと思います。

まずは.s3cfgをテンプレート化します。
.s3cfgでaccess keyとsecret keyの部分を以下のようにして変数扱いにします。

access_key = <%= @access_key %>
secret_key = <%= @secret_key %>

そしてcookbooks/COOKBOOK/templates/defaults3cfg.erbという名前で保存します。

次にrecipeでこのテンプレートを使うように定義を加えます。

cookbooks/COOKBOOK/recipes/default.rb

template "/home/piyo/.s3cfg" do
 source "s3cfg.erb"
 owner "piyo"
 group "piyo"
 mode 0600
 variables(
  :access_key => "xxxxxxxxxxxxxxxxxxxxx",
  :secret_key => "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
 )
end

chef-soloを実行し、s3cmdを実行すると無事s3に接続することができました。

Resource s3_fileのセットアップ

このs3_fileですが、chef 11.0.0にアップデートしたら使えなくなりました。調査はしていませんが、取り急ぎ!

これでs3cmdコマンドが使えるようになりました。
が、よくよく調べてみるとs3cmdコマンドなんて使わなくてもs3のファイルのresourceのs3_fileというものがあるらしいのでさっそくそれを使えるようにセットアップします。

githubからs3_file.rbを取得

このs3_fileは公式ではなく、サードパーティなのでgithubとかよくわからないですが、そこからダウンロードしてきます。

S3 File Resource for Chef(gist.github.com)

ダウンロードしたs3_file.rbcookbooks/COOKBOOK/libraries/に置きます。

cookbooks/COOKBOOK/recipes/default.rbには以下を追加します。

s3_file "/etc/ntp.conf" do
 source "s3://your_bucket/directory/ntp.conf"
 owner "root"
 group "root"
 mode 0644
 access_key_id "xxxxxxxxxxxxxxxxxxxx"
 secret_access_key "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
end

実行してみると

LoadError: no such file to load -- aws/s3

と怒られましたのでaws/s3をインストールします。

sudo gem install aws-s3

再び実行すると今度はうまくいきました。
/etc/ntp.confの中を見てみると、、、


<Error><Code>PermanentRedirect</Code>
.
.

むむむ、なんかエラーのようです。どうやらエラーのハンドリングを省いているようで、エラーでもERRORと出ないのか。
どうにかならないもんでしょうか。

それはちょっと置いておいてなぜエラーになったのかを調べます。
どうやらsourceの部分でregionを指定する必要があったみたいです。
なので以下のように書き直します。

s3_file "/etc/ntp.conf" do
 source "s3-ap-northeast-1://your_bucket/directory/ntp.conf"
 owner "root"
 group "root"
 mode 0644
 access_key_id "xxxxxxxxxxxxxxxxxxxx"
 secret_access_key "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
end

再び実行して/etc/ntp.confを見るとうまくできてました!
ちょっと問題はありますが、目的は達成。
これで実用化できそうです。
あと、AWSのaccesskeyとsecretkeyの指定が冗長なのでここは一つの変数にまとめたいところです。

おしまい。

ChefのRecipeを書く(1)

recipeを書いてみる

前回まででとりあえずChef-soloが動くことは確認できたので今回はrecipeを書いてみます。

仕様を決める

recipeを書き始める前に、どのようなことをしたいかを考えたいと思います。
実際の環境はAWSのEC2なのでそれを想定して考えてみるととりあえず以下の通りにしたいと思います。

  • /mnt/logを作る
    • SymLinkで/logにする
  • /mnt/tmpを作る
    • パーミッションを1777にする
    • SymLinkで/tmpにする
  • ntpdをインストール
    • /etc/ntp.confを適当に設定する
    • ntpユーザで動かす

とりあえずざっくりこれを目指したいと思います。

ディレクトリの操作

まずは/mnt/logについて書いていきます。
cookbooks/base_packages/recipes/default.rbに以下を記述。

directory "/mnt/log" do
 owner "root"
 group "root"
 mode "0755"
end

試しに実行してみます。

$ sudo chef-solo -c .chef/solo.rb -j .chef/chef.json
INFO: *** Chef 0.10.8 ***
INFO: Setting the run_list to ["recipe[base_packages]"] from JSON
INFO: Run List is [recipe[base_packages]]
INFO: Run List expands to [base_packages]
INFO: Starting Chef Run for chef-test-01
INFO: Running start handlers
INFO: Start handlers complete.
INFO: Processing directory[/mnt/log] action create (base_packages::default line 20)
INFO: directory[/mnt/log] created directory /mnt/log
INFO: directory[/mnt/log] owner changed to 0
INFO: directory[/mnt/log] group changed to 0
INFO: directory[/mnt/log] mode changed to 755
INFO: Chef Run complete in 0.024623 seconds
INFO: Running report handlers
INFO: Report handlers complete

$ ls /mnt
log/

できましたー。

次に/mnt/logのSymLinkとして/logを作りたいのでさらに以下を記述。

link "/log" do
 to "/mnt/log"
end

実行してみるとエラーとなる。
既に/logというディレクトリがありますよーというエラーが出てしまいました。
確かに/logというディレクトリはもともとあって、今まではそれを一度消してSymLinkを作っていました。
そこまで一気にやってくれることを期待したのですが、やっぱり始めに/logディレクトリを消してSymLinkを作らないといけないようです。
puppetはその辺よろしくやってくれそうな気がするんですが、どうなんでしょうか。

ということで以下のように変更。

directory "/log" do
 action :delete
 only_if "test -d /log"
end

link "/log" do
 to "/mnt/log"
end

これでできました。  
と思ったら、もう一回実行すると
directory “/log”のところでエラーが出てしまいました。
どうやら/logはSymLinkなので「ディレクトリじゃないよ」と怒られている様子。
only_ifが効かないのはなぜ??

原因不明につき、今回はこれまで。

雑感

ここまでやってみて思ったのは、僕はpuppetの方が好みのような気がするということです。何となくですが、puppetは運用目線、Chefは開発者目線のような雰囲気を感じました。
とは言ってもChefの調査を続けます。

追記(2012/4/23)

最後のonly_ifが効かない件、後日再び、そのままの設定で実行したらなんでか分かりませんが問題なく動きました。
なんだったんだろう?

Chef調査

Chef調査開始

chefにはスタンドアロンでも使えるしServer/Clientでも使える。
実際使う時はServer/Clientだけども、まずどんなものかを知るためにスタンドアロンのchef-soloをいじってみることにしました。

で、やり方をググるとさっそくchef-soloで作業環境構築の自動化(ひげろぐ)がヒット。
chef-soloの導入が丁寧に書いてあるので参考にさせていただいた。

既にRuby1.9.2はインストールしてあり(CentOS5.7は1.8.5だったのでSourceを拾ってきてBuildした)、chefもgem installで導入済みだったのでchef-repoのところから始めた。

git clone git://github.com/opscode/chef-repo.git

してChefリポジトリを構築。

.chef/solo.rbと.chef/chef.jsonを作ってさっそくchef-soloを実行してみる。

$ sudo chef-solo -c .chef/solo.rb -j .chef/chef.json
<internal:lib/rubygems/custom_require>:29:in `require': no such file to load -- openssl (LoadError)
    from <internal:lib/rubygems/custom_require>:29:in `require'
    from /usr/local/lib/ruby/1.9.1/net/https.rb:92:in `<top (required)>'
    from <internal:lib/rubygems/custom_require>:29:in `require'
    from <internal:lib/rubygems/custom_require>:29:in `require'
    --(中略)--
    from /usr/local/lib/ruby/gems/1.9.1/gems/chef-0.10.8/lib/chef.rb:25:in `<top (required)>'
    from <internal:lib/rubygems/custom_require>:29:in `require'
    from <internal:lib/rubygems/custom_require>:29:in `require'
    from /usr/local/lib/ruby/gems/1.9.1/gems/chef-0.10.8/lib/chef/application/solo.rb:18:in `<top (required)>'
    from <internal:lib/rubygems/custom_require>:29:in `require'
    from <internal:lib/rubygems/custom_require>:29:in `require'
    from /usr/local/lib/ruby/gems/1.9.1/gems/chef-0.10.8/bin/chef-solo:23:in `<top (required)>'
    from /usr/local/bin/chef-solo:19:in `load'
    from /usr/local/bin/chef-solo:19:in `<main>'

む。何やらエラーの様子。
rubyバージンなのでよくわからないが、opensslがロードできないと言われてそうだ。

openssl-develが無かった

エラーメッセージでググると
require ‘twitter’のエラー(opensslがロードできない)と言われる(Ruby_log)
を発見。
さっそく、書いてある通りにやってみた。

$ cd /path/to/ruby-src/ext/openssl

$sudo ruby extconf.rb 
=== OpenSSL for Ruby configurator ===
=== Checking for system dependent stuff... ===
checking for t_open() in -lnsl... no
checking for socket() in -lsocket... no
checking for assert.h... yes
=== Checking for required stuff... ===
checking for openssl/ssl.h... no
=== Checking for required stuff failed. ===
Makefile wasn't created. Fix the errors above.

openssl/ssl.hがないって言われたのでyumでopenssl-develをインストール。

sudo yum install openssl-devel.x86_64
sudo ruby extconf.rb

通ったのでbuild。
make; make install

node_nameでエラー

これでopensslが入ったと思うので、再度chef-soloを実行してみる。

$ sudo /usr/local/bin/chef-solo -c .chef/solo.rb -j .chef/chef.json 
[Fri, 20 Apr 2012 08:48:45 +0900] INFO: *** Chef 0.10.8 ***
[Fri, 20 Apr 2012 08:48:47 +0900] FATAL: Stacktrace dumped to /tmp/chef-solo/chef-stacktrace.out
[Fri, 20 Apr 2012 08:48:47 +0900] FATAL: Chef::Exceptions::CannotDetermineNodeName: Unable to determine node name: configure node_name or configure the system's hostname and fqdn

chef-soloは動いた。でもまたエラー。
調べてみるとnode_nameという値を設定しないといけないみたい。
.chef/solo.rbに追加する。

vi .chef/solo.rb

file_cache_path "/tmp/chef-solo"
cookbook_path "/home/piyo/chef-repo/cookbooks"
node_name "chef-test-01" ←追加

再度実行して、

FATAL: Chef::Exceptions::CookbookNotFound: Cookbook base_packages not found. If you're loading base_packages from another cookbook, make sure you configure the dependency in your metadata

とエラーが出るけど、レシピを書いていないので、これは想定通り。
これで、初期動作の確認は完了です。

cookbookの作成

続けて、書いてある通り
$ rake new_cookbook COOKBOOK=base_packages
を実行してcookbookを作成。
お約束の「knifeを使いなさい」が出ましたのでそっちもやってみました。

$ knife cookbook create COOKBOOK
WARNING: No knife configuration file found
** Creating cookbook COOKBOOK
ERROR: Errno::EACCES: Permission denied - /var/chef

knife configurationが無いと言われてしまった。
参考資料は先に進んでいるがここは勉強なのでknifeの動作環境の整備をしてみる。

knife configure

Where should I put the config file? [~/.chef/knife.rb] /home/piyo/knife-repo/.chef/knife.rb

configurationファイルの場所を指定する。

Please enter the chef server URL: [http://localhost:4000]

よくわからないのでそのままENTER.

Please enter an existing username or clientname for the API: [www] 

よくわからないのでそのままENTER.

Please enter the validation clientname: [chef-validator] 

よくわからないのでそのままENTER.

Please enter the location of the validation key: [/etc/chef/validation.pem] 

よくわからないのでそのままENTER.

Please enter the path to a chef repository (or leave blank): /home/piyo/chef-repo

先ほど構築したリポジトリのディレクトリを指定。

これで動くでしょうか。
先ほどのcookbook作成をもう一回実行してみます。

$ knife cookbook create COOKBOOK -c .chef/knife.rb 
** Creating cookbook COOKBOOK
** Creating README for cookbook: COOKBOOK
** Creating metadata for cookbook: COOKBOOK
$ ls cookbooks
COOKBOOK/  README.md  base_packages/

できた!でも今はいらないので消しときます。

とりあえずここまで。
次はいよいよレシピ作りに取りかかります。
何やらRuby全開の予感がして、手こずりそうです。

puppetかchefか

サーバ設定管理ツール選択

管理するサーバが増えてくると必ず現れてくる話題です。
僕もそれに漏れず、今までなーんとなく避けてきたこれに着手し始めました。

puppetかchefか

選択肢はもうこの2択でしょう。
今まで避けてきていた理由はこれらの言語がRubyだから。
残念ながら(と言っていいのか分からないが)、puppetもchefもどちらもRubyで書かれているんですね。
必要に迫られた以上、好き嫌いを言っても仕方の無いことなのでどんなものか調べはじめました。

puppetとchefの決定的な違い

それは外部DSLか内部DSLか。
それぞれのソフトウェアの好き嫌いを除けば、双方の違いはこれに尽きるのではないでしょうか。
僕としては最初puppetを選択しました。
それはペパボの宮下さんが使っているから。
あと、Rubyに何となく抵抗があったので内部DSLに拒否反応。
両者の違いを知る上では以下のサイトがとても参考になりました。

puppetを知る

puppetを知る上でとても役に立ったのはやはり宮下さんの以下の資料。

とりあえず、この二つの資料をじっくり読むことでpuppetがどんなツールで、どんな風に使うのに向いていて、何ができるのか理解することができました。

だが結局chef

で、ここまで調べて、開発の技術ディレクターに
「サーバ管理ツールはpuppetとchefと二つあるんですよ。puppetは以前からあって実績は十分で、chefは最近流行ってる感じです。あとこの二つの違いはpuppetは独自言語で、chefはrubyそのものなんです。」
と言ったら「じゃ、chefにしましょう!」だってさ。
ということで明日chefについて調べます。

おまけ

puppet、chefどちらもオープンソースですが、商用版もあるようです。
商用版はWEB GUIとかがついててちょっといい感じですが、まーむりなので深追いはしてません。