MyCSS

2011/05/23

right_awsのマルチバイトでの謎挙動に関して はてなブックマークに追加

最近、EC2やEBSなどに「日本語」のタグをつけられる事が解りまして(笑)、マネジメントコンソールからポチポチNameタグをつけ直していたのですが、最近リージョンの引っ越し等を行っている関係で、プログラムから制御させようと思い立ちました。

この手の管理プログラムは、いちいちJavaを立ち上げるのも気が重いので、Rubyのright_awsを利用してボチボチ作り始めました。right_awsのバージョンは2.1.0です。

require 'rubygems'
require 'right_aws'

AWS_ACCESS_KEY = 'アクセスキー'
AWS_SECRET_KEY = 'シークレットキー'

client = RightAws::Ec2.new(AWS_ACCESS_KEY, AWS_SECRET_KEY, :server => 'ap-northeast-1.ec2.amazonaws.com')
client.create_tags('vol-12345678', {'Name' => '日本語!'}) 

で、実行するとSignatureが合わないというエラーでコケます。
/Library/Ruby/Gems/1.8/gems/right_aws-2.1.0/lib/awsbase/right_awsbase.rb:545:in `request_info_impl': SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details. (RightAws::AwsError)
 from /Library/Ruby/Gems/1.8/gems/right_aws-2.1.0/lib/ec2/right_ec2.rb:140:in `request_info'
 from /Library/Ruby/Gems/1.8/gems/right_aws-2.1.0/lib/ec2/right_ec2_tags.rb:79:in `create_tags'
 .....
ところが、{'Name' => 'HogeHoge!!'}のようにシングルバイトの文字列を与えた場合はうまく行きます。

おっかしい…マルチバイト関連の問題だろう…めんどくさー、とか思いつつ、自分を奮い立たせる為にツイートしていたら(笑)なんと@junyaさんも同様の現象にあったとの事で、サポートフォーラムにも投稿したとの事。




Java SDKではうまく行くとの事で、これはRubyそのものの問題か、もしくはright_aws側の問題だろうなーと思いつつ、Java SDKで同じ事をやったら確かに問題なく行きます。

package test1;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.CreateTagsRequest;
import com.amazonaws.services.ec2.model.Tag;

public class CreateTagsTest {

    public static void main(String[] args) {
 final String AWS_ACCESS_KEY = "アクセスキー";
 final String AWS_SECRET_KEY = "シークレットキー";

 AWSCredentials cred = new BasicAWSCredentials(AWS_ACCESS_KEY, AWS_SECRET_KEY);
 AmazonEC2Client client = new AmazonEC2Client(cred);
 client.setEndpoint("https://ec2.ap-northeast-1.amazonaws.com");
 client.createTags(new CreateTagsRequest().withResources("vol-12345678")
  .withTags(new Tag().withKey("Name").withValue("日本語!")));
    }

}

仕方が無いので、Java SDKとright_awsの両方のソースを掘ってみたところ、やはりSignatureの生成のところで違いがありました。

Java側のクラスはcom.amazonaws.auth.QueryStringSignerです。下記、一部抜粋。

private String calculateStringToSignV2(URI endpoint,
            Map parameters) throws AmazonClientException {
        StringBuilder data = new StringBuilder();
        data.append("POST").append("\n");
        data.append(getCanonicalizedEndpoint(endpoint)).append("\n");
        data.append(getCanonicalizedResourcePath(endpoint)).append("\n");
        data.append(getCanonicalizedQueryString(parameters));
        return data.toString();
    }
POST」という文字がハードコーディングされていますねw

では、Ruby側はどうなっているかというと、create_tagsメソッドがright_ec2_tags.rbに定義されています。※構造がJavaと異なるので単純比較が出来ないのです。

module RightAws
  class Ec2

    (中略)

    def create_tags(resources, tags, options={})
      default = options[:default].nil? ? '' : options[:default]
      params = amazonize_list("ResourceId", resources)
      params.merge! amazonize_list(['Tag.?.Key', 'Tag.?.Value'], tags, :default => default)
      link = generate_request("CreateTags", params)
      request_info(link, RightBoolResponseParser.new(:logger => @logger))
    rescue Exception
      on_exception
    end

    (中略)

  end
end

問題は、このメソッドの中でgenerate_request("CreateTags", params)しているところです。これはright_ec2.rbに定義されおりまして、びっくりする事が書いてありました。
module RightAws
  class Ec2 < RightAwsBase
    include RightAwsBaseInterface

    (中略)

    def generate_request(action, params={}) #:nodoc:
      generate_request_impl(:get, action, params )
    end

    (中略)

  end
end
get決め打ちキタコレ! というわけで、うまく動作するJavaに習って、right_aws側をこのようにしてみました。

require 'rubygems'
require 'right_aws'

AWS_ACCESS_KEY = 'アクセスキー'
AWS_SECRET_KEY = 'シークレットキー'

client = RightAws::Ec2.new(AWS_ACCESS_KEY, AWS_SECRET_KEY, :server => 'ap-northeast-1.ec2.amazonaws.com')
def client.generate_request(action, params)
  verb = :get
  if action.start_with? 'Create' then
    verb = :post
  end  
  generate_request_impl(verb, action, params)
end
client.create_tags('vol-12345678', {'Name' => '日本語!'}) 

generate_requestの挙動をCreateなんちゃらの時にPOSTに置き換えるという付け焼き刃対応ですw
結果的にこれでうまく行きました。

シングルバイトの時はgetでうまく行くのがどうにも納得いかないのですが…一応、ご報告です。

追記
Ruby 1.8.7で試した結果です。Ruby 1.9.2ではうまく行かないとの報告がありました。

2011/05/19

JavaのJXMをCloudWatchのカスタムメトリクスに突っ込んでみた件(四番煎じ) はてなブックマークに追加

CloudWatchのカスタムメトリクス
AWSのリソース監視、アラート発信が出来るCloudWatchですが、EC2に関しては正直今ひとつ使いにくいなーと思っておりました。理由は、OSより内側の情報が取れないので、ディスクの空きスペースを監視したり、Tomcatのメモリ状況等の情報を取得する事が出来なかったからです。

お仕事では状況のモニタリングにMuninを使っていますが、その為にインスタンスを1台立てたりして結構メンドイ…っていうか、クラウドっぽくない(笑)訳で、おもわず
Muninの類とCloudWatchって統合できるようにならないのかなー?
とつぶやいた訳ですが、ソリューションアーキテクトの荒木さんに
Zabbix Senderとくっつけてる人とかいますよ!
と教えていただき、なるほどと思いつつ、やりたい事は逆なんだよなーとか思っていたら、じつは一週間ほど前にそのようなリリースがあったんですね…完全に見逃しておりました。

【AWS発表】 クラウド監視サービスAmazon CloudWatchでカスタムのメトリクスが使用可能に - Amazon Web Services ブログ

また、すでに試されている方々もおられます。
これはイケル!と思いつつ、一番やりたいのはJavaアプリのモニタリングなんだよなー、という訳で、
JMXから直接CloudWatchのカスタムメトリクスに投げられるものなんてまだないよね…
とつぶやいたら、これまたソリューションアーキテクトの大谷さんに
無ければ作るだけなのではないかw
と突っ込まれましたので、簡単に作ってみましたw
実行には、AWS SDK for Javaの最新版が必要になります。公式サイトからダウンロードしても良いですし、Mavenのリポジトリより適時落としてください。

使用例
JXMを使ってヒープメモリの状況を取り出し、それを一分間隔でCloudWatchに投げる例です。
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.cloudwatch.AmazonCloudWatchClient;
import com.amazonaws.services.cloudwatch.model.Dimension;
import com.amazonaws.services.cloudwatch.model.MetricDatum;
import com.amazonaws.services.cloudwatch.model.PutMetricDataRequest;
import com.amazonaws.services.cloudwatch.model.StandardUnit;

public class JMXTest1 {

    public static void main(String[] args) throws Exception {
 while (true) {
     MemoryMXBean mBean = ManagementFactory.getMemoryMXBean();
     // ヒープメモリ状況取得
     MemoryUsage heapUsage = mBean.getHeapMemoryUsage();

     // Dimension
     Dimension dimension = new Dimension().withName("Server").withValue("001");

     // カスタムメトリクスデータのリクエストを生成
     PutMetricDataRequest request = new PutMetricDataRequest()
      .withNamespace("MyJVMTest01")
      .withMetricData(
       // Used
       new MetricDatum().withDimensions(dimension)
        .withMetricName("Used")
        .withUnit(StandardUnit.Bytes.toString())
        .withValue((double) heapUsage.getUsed()),
       // Max
       new MetricDatum().withDimensions(dimension)
        .withMetricName("Max")
        .withUnit(StandardUnit.Bytes.toString())
        .withValue((double) heapUsage.getMax()),
       // Committed
       new MetricDatum()
        .withDimensions(dimension)
        .withMetricName("Committed")
        .withUnit(StandardUnit.Bytes.toString())
        .withValue((double) heapUsage.getCommitted())

      );

     AWSCredentials cred = new BasicAWSCredentials("アクセスキー", "シークレットキー");
     AmazonCloudWatchClient client = new AmazonCloudWatchClient(cred);

     // CloudWatchにデータ送信
     client.putMetricData(request);

     Thread.sleep(60 * 1000);
 }
    }
}

で、結果がこれ。(画像クリックで拡大)

というわけで、めでたしめでたし!

ついでに要望など
カスタムメトリクスはとても嬉しい機能なのですが、グラフの時間を自分のロケールで表示できればパーフェクトですね… という訳で顧客というのはトコトンわがままな訳ですが(笑)とても良いサービスなのでついつい言ってしまいます!

2011/05/16

「サーバ/インフラエンジニア養成読本」に記事が再録されました はてなブックマークに追加

サーバ/インフラエンジニア養成読本 [現場で役立つ知恵と知識が満載!] (Software Design plus)
2011年4月9日発売
Software Design編集部 編
B5判/228ページ
定価1,974円(本体1,880円)
ISBN 978-4-7741-4600-3
Amazon.co.jpで見る

発売されてからちょっと時間がたってしまいましたが、ご報告いたします。インフラエンジニア向けのムックに弊社のAWS事例を載せていただきました!これは、昨年のSoftware Design 2010年11月号に書いた記事に加筆修正したものです。

インフラエンジニアでもなんでもない自分が、この手の本に寄稿できるなんて今まで考えもしませんでしたが、まさに空からサーバーが降ってくるクラウドのおかげでこのような事になりました(笑)

特に資本の少ない中小企業やベンチャーには、クラウド環境は既に必要不可欠な存在だと思います。これはIT企業は当然として、その他の業種でも少なからずITの力を使う必要がある場合、クラウドをうまく使いこなす事で様々なメリットが得られるはずです。

また、クラウドはAmazonだけ、という訳でもなく(笑)、適材適所でGoogleやSalesForce、Azureなどがマッチする場合もあると思います。とにかく、どのクラウドも「タダで始められる」所がほとんどですから、まずは自分で試してみることが何よりも大事です!!

運用環境構築に障壁がなくなった現在、極端に言えばソースコード一本で飯が食える(かもしれないw)時代になってまいりました。楽しい時代ですねw