MyCSS

2011/11/25

ちょっとだけテレワークしてみてわかったこと はてなブックマークに追加

テレワーク/在宅勤務を開始してから1ヶ月半が経過し、少しずつリズムが出てきました。ちょっとだけ見えてきたこともあるので、ここにメモしておきます。

通勤からの解放
  • 一日が4時間増えましたw
    • 都内に通勤していた頃は、片道2時間弱かかっていました。往復4時間。これが一気に5秒まで短縮されました。著しいRTTの改善です。そして、朝昼晩の三食すべて家族で食卓を共にするという、今まであり得なかったシチュエーションに最近ようやく慣れてきたところです(笑)でも、これができることは大変にありがたいことです。
    • 平日父親不在の核家族でしんどい思いをしてきた妻と、毎朝出勤時に玄関先で「またあしたねー」と言っていた息子に少しずつ借りを返せればいいかな?と思う今日この頃だったりします… それにしても、仕事が終わった直後に「あそんでモード」全開のテンション高い息子とつきあうのは、まだまだ修行が足りないようです(笑)
  • 読書について
    • 通勤時間は読書タイムという方は多いと思います。私自身も例外ではなく、電車の中で技術書を読んで勉強していたクチです。通勤がなくなると、いままで自然にあった読書タイムがなくなります。これはあえて作らないといけません。また、会社帰りに書店によって書籍を物色、という行為が出来ません。これも今になってみたらすごく大きかったなぁ、と思います。自宅の近くには使える書店がないので、あえて街中まで出ないといけません。図書館も近所にないし、ここはやり方を少し考えないとなぁ、と思っています。

地方で東京の仕事をするということ
  • SkypeとGoogle Docsの存在
    • 会社とのコミュニケーションは基本的にEメールとSkypeです。本社で仕事をしているときも隣に座っているのに連絡はEメール、なんていう事も多かったですから(ゴメンネその1)、感覚的には悲しいかな?はっきり言って違和感ありません。
    • そんなことよりもやはり、Skypeの存在はすごく大きいですね。先日も3時間、延々と本社と会議しましたが、感覚的にはその場にいるのと違いがありませんでした。良くも悪くも(笑)。ただ、当然の事ながら紙の資料をその場で配布、という訳には行きませんから、Google Docsでドキュメントを共有しました。これも非常に便利です。
  • 東京本社との時差??
    • こちらは自然と勤務時間が9時-5時になります。まぁ、5時はちょっと言い過ぎですけど、要するに東京本社が本格的に動き出すまで数時間のズレが出てきます。人より朝早く出勤して早く帰る感覚ですかね?
  • 業務の効率

    • 在宅勤務で生産性が向上することが科学的に証明されました」という記事が話題になってましたけど、これは人それぞれだと思います。自分は幸運にも仕事に集中できる部屋をキープできて、かつ家族の協力が得られたので明らかに効率がアップしてます。会社に出るまで通勤と戦わなくて良いというのはとても大きいです。あと重要なのが椅子ですね。IT仕事だとPCにへばりついている事が多い訳ですが、コーディングに夢中になると気がつけば辺りは真っ暗だったりします。引越した当初はマトモな椅子がなくて腰痛がひどくなりましたが、ここはきっちり投資すべきと考えて思い切ってレビーノチェアを買っちゃいました。アーロンチェア みたいな流行のメッシュは寒いので却下(ここは北海道)、肘掛けとヘッドレスト、ランバーサポートは必須という条件であれこれ調べてコレに決めました。会社の椅子もひどかったので(ゴメンネその2)、椅子が違うとこんなにも違うものか!と驚いている次第です。ほんと、椅子は重要!!

在宅勤務
  • 運動不足
    • 都内に通勤していた頃は少なくとも合計1時間は歩いていましたが、それすらなくなりますw でもここは雪の多い札幌。冬場は毎朝の雪かきが良い運動です。マジで有酸素運動ですww
  • あえて外に出る
    • 在宅勤務はへたすりゃ引きこもりまっしぐらです。ここも意識して外に出る事をやらないといけません。最近はTwitterやFacebookなどのソーシャルメディア、またUstreamやWebinarなんかもしょっちゅうあるので実に良いのですが、やはりリアルな世界に出て人と会わないと人間ダメになります(笑)
    • 先日JavaFestaというイベントが開催されたので、無理を言ってスタッフをやらせてもらいました。もちろんUst係ですw 翌日はJAWS-UGの札幌支部勉強会があったので、こちらにも参加しました。これから徐々に札幌近郊で行われている勉強会やイベントには顔を出そうと思っています。
  • Amazonでの買い物
    • 札幌だとAmazonで「お急ぎ便」にしても翌日には着きません。したがって、Amazonプライムの意味が殆どありません。実際に津軽海峡をわたってみて、あらためて北海道の遠さを実感したので理解できますが、ここは一つ地産地消という事で?なるべく地元でお買い物をしようと思っています。

北海道に移住しました! はてなブックマークに追加

遅ればせながらご報告です
前回のエントリで宣言した通り、東日本を車で縦断し、はるばる北海道までやってきました。

荷物満載状態で神奈川県逗子市を出発して山梨、長野を経由。新潟、山形、秋田、青森と日本海側をまわって津軽海峡を渡り、紅葉真っ只中の北海道を走って札幌に無事到着しました。移動距離1564km。7泊8日の旅でした。

道中はこんな感じでした
長野県の青木湖
長野県北安曇郡小谷村 道の駅小谷
http://otarimura.co.jp/
新潟県長岡市 和島オートキャンプ場の展望台からの日本海
http://www.kizuna-camp.com/umi.html
山形県温海の道の駅からみた日本海
秋田県男鹿半島 なまはげオートキャンプ場(シーズンオフで貸し切り状態だったw)
http://www.namahage.co.jp/camp/
あきた白神駅に止まる五能線、リゾート白神を激写w
青森県白神山地にある十二湖
http://ja.wikipedia.org/wiki/十二湖
青森県つがる市のリンゴ畑
青函フェリーから見た青森港
津軽半島に沈む夕陽
ようやく函館フェリーターミナルに到着
北海道 洞爺湖
真狩村の国道から羊蹄山を激写w
まずは、このような大移動に楽しんでついてきてくれた妻と息子に感謝!
秋から冬にかけての移動という事で、子連れキャンプ&長距離移動は無謀だったか?と一瞬思いましたが、まぁなんとかなりましたw

飛行機を使えば羽田ー新千歳が1時間半。やっぱり遠かった…でも、もうこんな事は一生できないだろうな。良い経験させてもらいました!


中山峠より羊蹄山を望む


道中に立ち寄らせていただいた農家さん
山梨県北杜市の畑山農場さん、山形県遊佐町の尾形さん、秋田県大潟村のみすず農場さんには大変お世話になりました。家族一同感謝申し上げます!

山梨の畑山農場さんの味のある玄関を激写。
ご主人はなんと同じ大学出身。奥様は以前逗子にすんでいたということで、話題がミョーにローカルに(笑)
畑を見学させてもらいつつ、一晩泊めてもらいました。
ブログはこちら。http://blog.livedoor.jp/hatayamaorganicfarm/
山形の尾形さん宅の裏。
川は鳥海山から流れ出てくる澄み切った水。
やっぱりうまい米は水なんだなぁ、と思いました。
こちらにも一泊させてもらい、うまうまな食事を頂きました。ホント感動した…
秋田県大潟村のみすず農場さんの田んぼ。すでに稲刈り終了で寒いw
八郎潟で有名な広大な干拓地に圧倒。
これだけの農地を管理するってものすごい労力だな、と思う。

とてもおいしいとれたて野菜&お米をごちそうしてもらい、大変ありがたかったです。通販もやっているので、ぜひみなさん注文してみてね!

2011/10/10

北海道に移住します はてなブックマークに追加

色々な方にすでにお伝えしているのですが、ここで改めてご報告いたします。

この度、北海道に移住する事となりました。

お仕事は現在のままで印刷会社のエンジニアとして続行します。いわゆるテレワークというスタイルになります。

北海道に帰るべきか否か?
自分は大学入学を機に故郷を離れ、卒業(っていうか大学院中退)後、都内で働いておりました。いわゆるWindows95あたりの「ご家庭に一台パソコンを」という時代からお仕事をやり始めて、最終的に落ち着いたのが欧文印刷株式会社でのエンジニアのお仕事です。結局11年ほど勤務しております。そして気がつけば来年不惑の歳でございます。(プログラマーとしてはとっくに定年ですがなにか?)

いつかは北海道に帰ろうと思っていました。道産子なら結構そういう意識が強いんじゃないかな?自分もそういう思いを抱えつつ、気がつけば日頃の忙しさにかまけてついつい忘れがちになっておりました。そうした中クラウドと出会った訳ですが、これで本当にどこでも仕事ができるな、と考え、昨年からJAWS-UGの縁もあって札幌の勉強会に遊びにいったりもしておりました。ただ、その時もまだ、踏ん切りがつかない感じで、チャンスがあればいいなぁ、という「受け身」の姿勢であった事は間違いないです。そもそも北海道に行くべき理由が見当たらなかった。当時はそんな感じでした。

移住を決断した理由
しかし、直接的な契機となったのは3.11の東日本大震災と、同時期に起きた身内の問題でした。後者の方は公にするのもアレなんで控えますが、もう少し近くに、少なくとも陸続きの所に(?)住んでいた方良い事情が出来てしまいました。これには代替手段も考えられたのですが、なんせ「不惑」ですからここは決断しろよ、と思し召しを頂いたのだと解釈しました(笑)
まぁ、それも大きな理由なのですが、家族全員にとっての3.11の経験がもう完全に移住フラグを立てたと言っても過言ではありません。こちらに当時の様子をメモってありますが、要するに

今動かずにいつ動くのだ!

という思いでいっぱいになったからです。いとも簡単に津波に蹂躙されてしまった東北の地、電気がなくなって改めて分かる現代生活の不合理さ、問題が収束しない原発と、その周辺にわき上がってくる洪水のような不確定情報、放射能問題… これらの混乱をどう自分の中で消化するべきか、そして父親としてどういう決断をすれば最良なのか?社会人としてどのような仕事をしていけば今後に繋げられるのか… もう待ったなしというか、今までおざなりに考えてきた事に大して、「不惑よ決断せよ」と言われているような気がしたんです(笑)

ライフスタイルを変えるのだ!
半年間いろいろ考えつつ、妻とも色々と話した(時には激論した)結果、ライフスタイルを少しずつ変えていかない事には、子供たちが生きる未来につなげる事が出来ないのではないか?という結論に至りました。
クラウドでワークスタイルを見直す
このプレゼンテーションは、JAWS-UG宮崎の勉強会でお話ししたものです
もう、受け身な生き方ではより良い人生を送れない時代だと思います。これは社会人としてより良い仕事をする、という意味もありますが、我々は社会人の前に一人の人間であり、その人間は服を着たり食べたり寝たりするわけです。そういった一番ベーシックな部分を「他人任せにしすぎていやしないか?」という素朴な疑問がどうしても頭をよぎって離れなかったんです。 もちろん、そうはいっても極端な行動はとれません。いきなり自給自足的に農業に従事するのはあまりに非現実的ですし、そもそもそういうマイナス面からの動機で動いた所でうまくいかないのは当然です。それよりも、自分たちの手の届く範囲で、1%で良いから、人間が行きて行く為の最もベーシックな部分を少しでも生み出す事が出来る生活がしたい、という考えがベースにあります。 理想は自分のプロフィールにもあるとおり半農半ITです。とは言うものの現実的に言って半農とは言うものの家庭菜園レベルで、自分達家族分位が当面の目標です。それでも良いと思っています。自分達の食べ物を自分達で作る事が出来ればそれだけもかなり違います。

仕事について
そのような結論が出たところで、そのようなライフスタイルを実現出来そうな場所、ということで、自然と自分の生まれ故郷である北海道が浮かびました。それには冬の生活という独特の問題がありますが、私自身道産子でありますし、妻もその点は不安要素はあるものの、これまでに様々な出逢いもあり、最終的には合意してくれました。
あとは仕事をどうするかという大問題があります。普通逆だろ?仕事を決めてからだろ?とおじさんたちから諭される事もままありますが、そこは「そうですよねー」と合図地を打って華麗にスルーしておきますw

仕事が大事か?家庭が大事か?などという三文芝居的な何かはそもそも問題ではなく、両方大事なのです。要は自分達はどう生きたいのか?と言う極めて自然な問題に対して素直に考える事が大事で、仕事か、家庭か、なんて言う切り口は意味が無いと考えています。

ちょっと話が呑み屋の説教オヤジくさくなってしまったので軌道修正しますねw

実際問題、弊社には「テレワーク」なんていうイマドキな制度はありませんでした。ましてや「印刷会社」なので、出社しないなんて事はハナから頭にないです。ですが、幸いにも会社には事情を勘案していただいて、テレワークを認めてくれました。決め手となったのは以下の二つの事実です。


あとは、打ち合わせは基本的にSkypeです。これも社内でSkypeを使う習慣のある人間がいなかった事もあり、夏の間に一週間ほど「在宅勤務」をやってみて試してみました。会議にもSkypeで出席しましたが、コミュニケーションを取るうえでも問題もなく、日常の細かい打ち合わせなどもSkypeの画面共有等を駆使して行えば問題なく進められるという実感をお互いに持つことができました。

とはいえ、実際にやってみたら色々問題も出てくるかもしれません。それでも地方でクラウドを中心としたワークスタイルを実現する事を念頭において、仕事もこれまで以上に張り切ってやっていきたいと思っています!

というわけで、今までいろんな方々に大変お世話になりました。そしてこれからも末永くどうぞよろしくお願いいたします。 北海道にいらした際はお気軽にお声がけください。タイミングが合えば空港までお迎えにあがりますのでwww

ちなみに
家族全員が「北の大地の遠さ」を実感する為に、あえて「車」で移動いたします。子供がまだ小さいので、一日の移動距離は少なめで7泊8日の予定です。そして、もう寒くなりつつありますが、基本キャンプしながらです。でも、途中で堪え兼ねてコテージなどに変更するかもw

2011/07/20

AWS SDK for Ruby はてなブックマークに追加

「七夕記念企画/雲(AWS)に願いを!」という事で、お願いしたらあっという間に願いがかなったわけでして。


【AWS発表】 AWS SDK for Rubyを提供開始 - Amazon Web Services ブログ

大変喜ばしい!というわけで、ちょっとだけ試してみたメモです。

AWS::SNSを試してみる
一年以上前に、Amazon SNSのRubyライブラリをRightAwsを元に作ったんですが、もう不要になりました。万歳!

SDKの導入方法はこの辺を見ていただくとして、例えばTopicを全て総ざらいして、その中のSubscriptionを取得するにはこんな感じになります。
# -*- coding: utf-8 -*-
require 'rubygems'
require 'aws-sdk'

AWS.config(:access_key_id => 'アクセスキー', 
  :secret_access_key => 'シークレットキー')
  
sns = AWS::SNS.new
sns.topics.each do |topic|
  puts topic.name
  topic.subscriptions.each do |sub|
    puts sub.arn
  end
end
実にいい感じですね。

Messageをpublishするには、Topicオブジェクトにpublishしてあげるだけです。
topic.publish('デフォルトメッセージ',
  :subject => 'Test',
  :email => 'ほげほげ')

送信先がメールの場合、:subject に日本語をそのまま与えるとエラーになりますんで、例のごとくJISエンコードしてあげます。
subject = '=?ISO-2022-JP?B?' + Kconv.tojis('日本語サブジェクト')
  .split(//,1).pack('m').chomp + '?='
topic.publish('デフォルトメッセージ',
  :subject => subject,
  :email => 'ほげほげ')

~を含むとpublishに失敗するぞ!
まだちゃんと検証していないのですが、問題を発見しましたので報告しておきますw
先のpublishですが、メッセージの本文に~(0x007E)があると、AWS::SNS::Errors::SignatureDoesNotMatchになってしまいます。
※ちなみに \(~0~)/ を試して気がつきましたw
topic.publish('デフォルトメッセージ',
  :subject => 'Test',
  :email => '~') 

以下、エラーメッセージ
E, [2011-07-20T20:45:38.321580 #3504] ERROR -- : [AWS SNS 403 0.870669] publish {:message_structure=>"json", :topic_arn=>"arn:aws:sns:us-east-1:XXXXXXXXXXXX:XXXXXXXX", :message=>"{\"default\":\"デフォルトメッセージ\",\"email\":\"~\"}", :subject=>"Test"} AWS::SNS::Errors::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.

これってAWS SDKの問題って言うよりはRubyの問題なのかな?それともJSONエンコード?ちなみに試した環境は以下です。
$ ruby --version
ruby 1.8.7 (2009-06-12 patchlevel 174) [universal-darwin10.0]

2011/07/15

第9回 AWS User Group - Japan 東京勉強会で、ひさびさにLTしてきました はてなブックマークに追加



↑自分は12:00位からです

こんにちは。フォロアー泥棒の@dateofrockです。

思い起こせば、第一回でSQSネタを、第二回でSNS自爆ネタを発表してからはや一年が経ちました。その間に、QCon Tokyo 2010とか、女子会とか、札幌支部の勉強会でしゃべったりと、結構いろんなところでお話しさせていただく機会をもらい、大変ありがたく思います。(沖縄行きてー)

AndroidでTwitter follower数をCustom Metricsに突っ込む
お約束の「続きはWebで」の件ですが、まずJMXを使ったCustom Metricsはこちらをご覧ください。
で、これをAndroidからやるためには、cron的な動作をさせる事が出来るAlarmManagerを使うのが便利です。

Main.java

package com.dateofrock.android.test;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class Main extends Activity {

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  // 監視開始ボタン
  Button startWatchButton = (Button) findViewById(R.id.startWatchButton);
  startWatchButton.setOnClickListener(new View.OnClickListener() {
   public void onClick(View v) {
    long interval = 60 * 1000;// 1分間隔

    // Intent作成
    PendingIntent pendingIntent = createPendingIntent();

    // AlarmManagerに登録
    AlarmManager am = (AlarmManager) Main.this
      .getSystemService(ALARM_SERVICE);
    am.setRepeating(AlarmManager.ELAPSED_REALTIME, 1000, interval,
      pendingIntent);
   }
  });

  // 監視終了ボタン
  Button stopWatchButton = (Button) findViewById(R.id.stopWatchButton);
  stopWatchButton.setOnClickListener(new View.OnClickListener() {
   public void onClick(View v) {
    PendingIntent pendingIntent = createPendingIntent();
    AlarmManager am = (AlarmManager) Main.this
      .getSystemService(ALARM_SERVICE);
    am.cancel(pendingIntent);
    Toast.makeText(getBaseContext(), "Twitterの監視をやめますた", 10).show();
   }

  });
 }

 private PendingIntent createPendingIntent() {
  Intent intent = new Intent(getBaseContext(),
    TwitterWatchBroadcastReceiver.class);
  PendingIntent pendingIntent = PendingIntent.getBroadcast(
    getBaseContext(), 0, intent, 0);
  return pendingIntent;
 }
}

res/layout/Main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent" android:layout_height="fill_parent"
 android:orientation="horizontal">
 <Button android:id="@+id/startWatchButton"
  android:layout_height="wrap_content" android:layout_width="wrap_content"
  android:text="@string/start_watch_string"></Button>
 <Button android:layout_height="wrap_content"
  android:layout_width="wrap_content" android:id="@+id/stopWatchButton"
  android:text="@string/stop_watch_string"></Button>
</LinearLayout>

極限まで研ぎすまされた無駄のないユーザーインターフェース


Main.javaは、アプリケーションを起動した時に最初に表示されるActivityです。スタートボタンが押されるとAlarmManager#setRepeating()するのですが、ここで渡されるPendingIntentはBroadcastReceiverを継承したTwitterWatchBroadcastReceiverを起動させるものになります。この中で、Twitter APIに現在のフォロワー数を聞きに行って、それをCloudWatchにputMetricDataするだけです。
上記のコードでは1分間隔で監視していますが、これは結構電池を消耗するのでおススメしません。今回はどれだけ短時間でフォロワーが増えるかみたかったので、あえて1分にしています。

TwitterWatchBroadcastReceiver.java

package com.dateofrock.android.test;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.format.DateUtils;

import com.dateofrock.android.test.TwitterClient.APIResult;

public class TwitterWatchBroadcastReceiver extends BroadcastReceiver {

 @Override
 public void onReceive(Context context, Intent intent) {
  // Twitterにお伺い
  TwitterClient twitter = new TwitterClient();
  APIResult result = twitter.getResult("dateofrock");

  // 結果をCloudWatchに送信
  CloudWatchClient cloudWatch = new CloudWatchClient();
  cloudWatch.sendMetrics("dateofrock", result);

  // Notify
  doNotification(context);
 }

 private void doNotification(Context context) {
  NotificationManager notificationManager = (NotificationManager) context
    .getSystemService(Context.NOTIFICATION_SERVICE);

  String text = "CloudWatchにTwitter情報を登録しました("
    + DateUtils.formatDateTime(context, System.currentTimeMillis(),
      DateUtils.FORMAT_SHOW_TIME) + ")";
  Notification notification = new Notification(
    android.R.drawable.btn_star, text, System.currentTimeMillis());
  notification.flags = Notification.FLAG_AUTO_CANCEL;

  Intent intent = new Intent(context, Main.class);
  PendingIntent contentIntent = PendingIntent.getActivity(
    context.getApplicationContext(), 0, intent, 0);
  notification.setLatestEventInfo(context, "TwitterWatch", text,
    contentIntent);
  notificationManager.notify(R.string.app_name, notification);
 }

}


CloudWatchClient.java

package com.dateofrock.android.test;

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;
import com.dateofrock.android.test.TwitterClient.APIResult;

public class CloudWatchClient {

 public void sendMetrics(String twitterUser, APIResult result) {
  if (result.isError()) {
   return;
  }

  Dimension dimension = new Dimension().withValue(twitterUser).withName(
    "User");

  // リクエストの組み立て
  PutMetricDataRequest request = new PutMetricDataRequest()
    .withNamespace("Twitter").withMetricData(

      new MetricDatum().withMetricName("Follower")
        .withDimensions(dimension)
        .withUnit(StandardUnit.Count.toString())
        .withValue((double) result.followersCount),

      new MetricDatum().withDimensions(dimension)
        .withMetricName("Friends")
        .withUnit(StandardUnit.Count.toString())
        .withValue((double) result.friendsCount),

      new MetricDatum().withDimensions(dimension)
        .withMetricName("Status")
        .withUnit(StandardUnit.Count.toString())
        .withValue((double) result.statusCount)

    );

  // CloudWatchにデータ送信
  AWSCredentials cred = new BasicAWSCredentials("アクセスキー", "シークレットキー");
  AmazonCloudWatchClient client = new AmazonCloudWatchClient(cred);
  client.setEndpoint(CloudWatchEndPoint.US_EAST);
  client.putMetricData(request);
 }

}

CloudWatchEndPoint.java

package com.dateofrock.android.test;

public class CloudWatchEndPoint {

 public static final String US_EAST = "monitoring.us-east-1.amazonaws.com";
 public static final String US_WEST = "monitoring.us-west-1.amazonaws.com";
 public static final String EU_WEST = "monitoring.eu-west-1.amazonaws.com";
 public static final String AP_SOUTHEAST = "monitoring.ap-southeast-1.amazonaws.com";
 public static final String AP_NORTHEAST = "monitoring.ap-northeast-1.amazonaws.com";

}

TwitterClient.java

package com.dateofrock.android.test;

import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.User;

public class TwitterClient {

 public APIResult getResult(String twitterUser) {
  APIResult result = new APIResult();
  Twitter twitter = new TwitterFactory().getInstance();
  User user;
  try {
   user = twitter.showUser(twitterUser);
   result.followersCount = user.getFollowersCount();
   result.friendsCount = user.getFriendsCount();
   result.statusCount = user.getStatusesCount();
  } catch (TwitterException e) {
   result.error = true;
  } finally {
   return result;
  }

 }

 public class APIResult {
  int followersCount, friendsCount, statusCount;
  private boolean error = false;

  public boolean isError() {
   return this.error;
  }
 }
}

TwitterWatchBroadcastReceiver#onReceive()で起動されるTwitterClientですが、Twitter APIのGET users/showが幸い認証が不要なので、非常に簡素に書く事ができます。ただし、Twitterの状況によっては取得に失敗する事もありますし、ましてやケータイは圏外になる事もよくありますので、例外はキャッチして取得に失敗したという状況が分かるようにして(APIResult.error=true)値を返すようにしています。というわけで、エラー処理は適当ですw

結果発表

で、どのくらいTwitterのフォロワーが増えたかを貼っておきますとこんな感じでした。わずか3分で純増70弱です。協力していただいた皆様、本当にありがとうございます!
あれから半日以上経過しましたが、若干の増減はあるものの、今のところキープですww

ちなみに、所々グラフが切れている部分がありますが、これはTwitterAPIやCloudWatchAPIの呼び出しに失敗したからだと思います。原因はほぼ間違いなくAndroid端末の通信状況です。移動中でトンネルの中に入っていたりすると圏外になってしまい、失敗します。

ちなみに、CloudWatchではあるしきい値を超えるとAmazon SNS経由でメールを飛ばす事が出来るのですが、それもしゃべっている間に来ておりましたw
実際に来たメール(クリックで拡大)
Alarmの設定画面(クリックで拡大)


CloudWatch Custom Metricsの注意点
5分という制限時間ではお伝えしきれなかった事ををここにメモしておきます。※2番目、3番目はCustom Metricsに限った話ではありません。
  1. 消す手段が無いw

  2. 時刻表記がUTCのみ
    これは、Management Consoleで見た場合に非常に分かりにくいのでなんとかして欲しいところです < AWS様
  3. グラフデータを画像として取得できない
    ただし、APIのGetMetricStatisticsを叩けば、DataPointがドバドバっと取得できますので、自力でグラフを描く事は可能です。ただ、どうせなら絵にして欲しいところです < AWS様


まとめ
今回はネタとしてわざとAndroidとかTwitterなどを出してみましたが、これはCloudWatchのCustom Metricsって使い勝手があるよ、という事を強調したかったからです。(というか、フォロワーを増やしてみたかっただけ?)
通常OSやアプリケーションレベルのメトリクスをとりたい場合、Nagios、Zabbix、Muininあたりが出てくるのでしょうけれども、クラウド環境でそれらを動かすってのもある意味芸が無いというか、そもそも監視サーバのお守りもなんだかなぁ、と思うので、CloudWatchに全て統合できれば嬉しいな、という個人的見解です。

もう、ここまで来るとIaaSとかPaaSっていうレイヤー分けが意味が無い気がする今日この頃でした・・





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

2011/04/20

S3とIAMを組み合わせたお手軽ファイル共有 はてなブックマークに追加

AWSを使ったお手軽ディザスタリカバリ対策を社内で構築したお話を書きましたが、その続きです。

ファイル共有としてS3を使おうとした経緯
ファイルをやり取りする時など、イントラにあるファイルサーバー(SambaやAFP、NFSなど)を経由させるのが一番お手軽な方法だと思います。実際、最近のNASは安いですし、RAIDも簡単に組めてそこそこ信頼性もあります。ちなみに弊社ではBuffaloのTera Stationを利用しています。

ですが、このファイルサーバーをクラウドに持って行くとなると結構大変です。弊社の場合ですと、エンジニアと印刷オペレーターとでファイルをやり取りする機会も多いので、エンジニア以外の人たちでも簡単に扱える仕組みが必要となります。AWSでこの手の「ファイル共有」プロトコルを喋らせるにはEC2を仕立ててあげなければなりませんが、今回はそのような時間もお金もかける事が出来ません。

AWS最強のサービスS3
AWSにはS3という容量無制限のストレージサービスがあります。CloudBerry ExplorerCyberduckを使えば、FTPライクに扱う事が出来ますし、AWS純正のAWS Management Consoleはブラウザで利用出来るので、基本的なPCリテラシーがある人であれば扱えるものです。
※ただし、UIの日本語化が行われていませんので、場合によっては問題になるかもです。

そこで、ファイル共有の代わりにこの辺のツールを使う事にして、あとはファイルの書き込み、読み込みなどの権限管理をIAM(Identity and Access Management)を利用して設定してみました。

IAMに関しては、サーバーワークス小倉さんがJAWS-UGの勉強会で発表してくださった超わかりやすいスライドがありますので、そちらをご覧ください。

IAMまわりのツール
今の所IAMの設定は、AWS Management Consoleから扱えません。しかし、日本にはCloudworksという神サービス(笑)があり、こちらでIAMをサポートしています。また、FirefoxエクステンションのIAM Foxもあるので、コマンド叩くの嫌だ!という人でもだいぶ敷居が低くなっています。ただ、細かい所をいじるには、やはりコマンド、もしくはSDKを使ってAPIを叩かなければなりません。

また、設定自体はAccess Policy Languageと呼ばれる形式(中身はJSON)で書く必要があるので、それなりに大変です。ですが、JSONを生成してくれるAWS Policy Generatorが用意されているので、イチから手書きするよりも、こちらを使う方が良いかもしれません。

この記事では Policy GeneratorでJSONを作成 → 少々手直し → Cloudworksにて設定
いうやり方で行いました。その他、GUIのツールでサポートしていない部分はコマンドラインを利用しました。

概要
考え方としてはこのような感じです。
  • ファイルサーバーとして利用するバケットを一つ作ります。リージョンはもちろん東京です。このバケットを仮に「共有バケット」と呼ぶ事にします。バケット名は「s3-shared」とします。
  • 印刷部門の人間には、この共有バケットに対してのみ、自由に読み書きが出来ます。
  • その他バケットは、管理者以外は読み書き出来ない事とします。

IAMで「admin」と「s3-user」という二つのグループを作ります。adminは管理者権限、s3-userはS3の共有バケットの読み書きのみ出来るという意味です。adminグループにはadminというユーザー、s3-userグループにはkenとsachikoというユーザーがいるとします。(名前に他意はありませんよw)



CloudworksでIAMを設定する
まずはユーザーを作らないと何も出来ません。コマンドでのやり方はググれば沢山出てくるので、Cloudworksでのやり方を説明してみます。

Cloudworksにログインし、左のIAMユーザーを選択、右上の「IAMユーザーの追加」をクリックします。admin、ken、sachikoというユーザーを作成します。



次にadminグループとs3-userグループを作成します。

adminグループ配下にユーザーadminを、s3-userグループ配下にkenとsachikoを入れます。グループを選択して、「IAMユーザーをグループに追加」をクリックします。以下の画面は、s3-userを選択した場合です。

s3-userグループにkenを追加します。追加するユーザーは、ちゃんとプルダウンで選択出来るようになっているので便利ですね!

同様にsachikoも追加して、s3-userグループの設定が完了しました。

ポリシーの記述
さて、ここからJSONでポリシーの記述を行います。ここではAWS Policy Generatorを使ってみます。

AWS Policy Generator

まずは、adminからやってみます。

adminはS3だけではなく、その他のサービスも全て利用出来るものとしますので、All Servicesにチェックを入れるだけです。

「Add Statement」をクリック、「Generate Policy」をクリックすると、このようにJSONが生成されます
このJSONをCloudworksにコピペするという訳です。

Cloudworksに戻ります。adminグループのIAMグループポリシーの追加をクリックします。ポリシー名は何でも良いのですが、判りやすくadmin-policyとでもしておきましょう。

次に、s3-userのポリシーを作成します。Policy Generatorに戻って、以下の事をやります。
  1. Type of PolicyはIAM Policyを選択
  2. EffectはAllow、AWS ServiceはAmazon S3を選択。以下の操作も同じ。
  3. ActionsでListAllMyBucketsを選択。Amazon Resource Name(ARN)には、 arn:aws:s3:::* と記入。「Add Statement」をクリック。
  4. ActionsでListBucketGetBucketLocationを選択。ARNは arn:aws:s3:::s3-shared と記入。「Add Statement」をクリック。
  5. ActionsでAll Actionsをチェック。ARNは arn:aws:s3:::s3-shared/* と記入。「Add Statement」をクリック。
結果、このような感じになります。

出来上がったポリシーはこんな感じです。
{
  "Statement": [
    {
      "Sid": "Stmt1303292722962",
      "Action": [
        "s3:ListAllMyBuckets"
      ],
      "Effect": "Allow",
      "Resource": "arn:aws:s3:::*"
    },
    {
      "Sid": "Stmt1303292848143",
      "Action": [
        "s3:GetBucketLocation",
        "s3:ListBucket"
      ],
      "Effect": "Allow",
      "Resource": "arn:aws:s3:::s3-shared"
    },
    {
      "Sid": "Stmt1303292879145",
      "Action": "s3:*",
      "Effect": "Allow",
      "Resource": "arn:aws:s3:::s3-shared/*"
    }
  ]
}

まず、IAMのデフォルトポリシーは全てDenyです。Allowを付け加えて行くやり方で考えています。

ListAllMyBucketsとは、S3バケットの一覧を表示するかどうかです。これはDenyしても良さそうですが、Management Consoleでは左側にバケット一覧が表示される為、Allowしておく必要があるみたいです。(情報求む)

共有バケット名は「s3-shared」です。これをARNで書くと arn:aws:s3:::s3-shared となります。ここに対してGetBucketLocationとListBucketをAllowしてやると、このバケット内のオブジェクト一覧を取得する事が出来ます。

さらに、このバケット内部を読み書きする為には、arn:aws:s3:::s3-shared/* に対して全てのActionをAllowしてやります。これは文字通り全てのアクションを許可してしまうので、例えばファイルのパーミッションにあたるACL(Access Control List)の変更もs3-userグループの人が出来てしまいます。この辺はどこまで制限するかによるでしょう。

このJSONをCloudworksにコピペします。

これでIAMの設定は終わりました。

s3-userでAWS Management Consoleを使う場合
通常、Management Consoleにアクセスするには、
  • https://console.aws.amazon.com/s3/home
に行くと思いますが、ここで入力するのはAWSアカウントのEメールとパスワードです。せっかくIAMを設定したのに、これではログイン出来ません(笑)

IAMでManagement Consoleにログインする為には、特別なURLが用意されています。(参考:AWSドキュメント
  • https://your_AWS_Account_ID.signin.aws.amazon.com/console/s3
your_AWS_Account_IDは、AWSアカウントに設定されている12桁の数字です。ただ、この数字を覚えている人は極めてまれだと思いますので、AWSアカウントIDのエイリアスを作って覚えやすいURLに変更してしまいましょう。下記のコマンドを打ちます。

$ iam-accountaliascreate -a hogehoge
Alias: hogehoge
Direct Signin Link: hogehoge.signin.aws.amazon.com

これでめでたく、https://hogehoge.signin.aws.amazon.com/console/s3でログインする事が出来るようになりました。

さて、ユーザー名とパスワードを入力してログインしてみましょう…って、パスワードってどこかで設定しましたっけ?(笑)

実は、このパスワードの設定はCloudworksでは出来ません。しかたがないので、コマンドを打ちましょう。

$ iam-useraddloginprofile -u ken -p PASSWORD

これでようやくログインする事が可能になります。


ログインするとおなじみのコンソール画面が出て、バケット一覧が表示されます。試しに共有していないバケットをクリックしてみるとこんな感じでアクセス出来ません。うまく設定出来ているようです。

共有バケットは、自由にフォルダを作ったり、ファイルをアップロード/ダウンロードが出来ます。sachikoユーザーでも全く同じ事が出来ます。

s3-userグループは、S3の共有バケットの操作のみ許可されていますので、もちろんEC2は触れません。
もちろん、Elastic Beanstalkも無理です。

こんな感じで、共有バケットの操作のみに権限を絞ったIAMユーザーに対して、AWS Management Consoleを使ってもらう準備ができました。めでたしめでたし。

CyberduckやCloudBerry Explorerの場合
これらのツールを使う場合は、先ほど作ったIDとパスワードでは出来ません。従来通りアクセスキーとシークレットアクセスキーを使う必要があります。これはユーザーごとに発行します。

Cloudworksではこのようにやります。
アクセスキーを発行したいユーザーを選択し、アクセスキーの追加ボタンをクリックします。
そうすると、このようにキーが発行されますので、メモっておきます。シークレットアクセスキーは、この画面でしか表示されません。あとで確認する手段がないので、もし忘れてしまったらキーの再発行となります。(これはAWS側の仕様です。コマンドラインでやっても同じ事です。)

Cyberduckでは、ユーザー名にアクセスキーを、パスワードにシークレットアクセスキーを入力します。

共有バケットへはアクセス出来ますし、それ以外はアクセスが拒否される事が判ります。

まとめ
なんか、思いがけず長文エントリになってしまいましたが、ここで紹介している事は非常に単純なものです。ポリシーは時間帯でIP制限をかけるなど、きめ細かく記述出来ますし、そもそもS3だけでなく、EC2などのサービスにも適用出来ます。(私は全然使いこなしていませんがw)
また、Cloudworksのように周辺のツールを組み合わせれば、色々と作業が楽になります。

手軽でセキュアな無制限ストレージが格安で手に入る時代になりました。
いやー、S3って、ほんといいもんですね!