[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']という感じで指定するんですね。

次回はなにかな?

[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版