2015年1月21日水曜日

はてなブログに移行しました!

ブログ担当の阿達です。 この度はてなブログに移行しました。 新しいブログはこちら 『UZABASE Tech Blog』 引き続きUZABASE開発者ブログをよろしくお願いします。 Twitterではブログの更新情報や勉強会のお知らせをツイートしています。 @Uzabase_Tech こちらもフォローよろしくお願いします!

2015年1月14日水曜日

SPEEDAに導入して分かった、CDNの劇的な効果

インフラチームの金屋です。
ユーザベースのインフラを担当しています。

今回は身近に利用しているけど、使っていることに気付かない
インターネットの影の立役者であるCDNについて書きます。

CDNとは?

Contents      Delivery   Network
コンテンツを  配信する  ネットワーク

簡単に言うと、次の機能を持っています
  • Webコンテンツ(サイト、画像、動画等)を
  • 全世界のインターネットへ配信する
  • 専用に整備された高速ネットワーク

CDNは何のためにあるのか?

地理的・物理的に遠隔地からのアクセスをキャッシュ、経路最適化で高速化するのが目的です。

(イメージ)
[通常] 日本 <=======10秒=======> 米国
[CDN] 日本 <==CDN(2秒)==> 米国 500%高速化

なんで遠隔地だと遅くなるのか?

インターネット上のWebサイトへのアクセスの流れは
    1. www.abc.com を名前解決してグローバルIPに変換
    2. グローバルIP で接続する
    3. ルーターがパケットを中継して、宛先のグローバルIP(サーバ)まで届ける
    一般的に遠いほど、中継点(ホップ)が多く、通信に時間(コスト)が掛かり、遅延します。
    日本から近いサイト(Yahoo Japan)と遠いサイト(米Yahoo) で比べてみます。

    近いサイト(Yahoo Japan)へのping応答

    C:\>ping www.yahoo.co.jp
    www.g.yahoo.co.jp [182.22.70.251]に ping を送信しています32 バイトのデータ:
    182.22.70.251 からの応答:バイト数 =32 時間 =8ms TTL=53
    182.22.70.251 からの応答: バイト数 =32 時間 =7ms TTL=53
    182.22.70.251 からの応答: バイト数 =32 時間 =7ms TTL=53
    182.22.70.251 からの応答: バイト数 =32 時間 =7ms TTL=53
    182.22.70.251 の ping 統計:    パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、 
    ラウンド トリップの概算時間 (ミリ秒):  最小 = 7ms、最大 = 8ms、平均 = 7ms


    遠いサイト(米Yahoo)へのping応答

    C:\>ping www.yahoo.com
    fd-fp3.wg1.b.yahoo.com [106.10.139.246]に ping を送信しています 32 バイトのデータ:
    106.10.139.246 からの応答: バイト数 =32 時間 =72ms TTL=49
    106.10.139.246 からの応答: バイト数 =32 時間 =72ms TTL=49
    106.10.139.246 からの応答: バイト数 =32 時間 =72ms TTL=49
    106.10.139.246 からの応答: バイト数 =32 時間 =72ms TTL=49
    106.10.139.246 の ping 統計:    パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、 
    ラウンド トリップの概算時間 (ミリ秒):  最小 = 72ms、最大 = 72ms、平均 = 72ms


    ※ラウンドトリップ:通信の往復にかかる時間
    遠いサイトの方が約10倍時間が掛かっています。

    実際の経路を比べてみましょう。

    近いサイト(Yahoo Japan)の経路→11回経由
     C:\>tracert www.yahoo.co.jp
      www.g.yahoo.co.jp [182.22.39.242] へのルートをトレースしています経由するホップ数は最大 30 です:
    1     1 ms     2 ms     1 ms  10.10.5.126
     2     1 ms     1 ms     1 ms  10.0.0.254
     3     3 ms     3 ms     3 ms  106.162.241.131
    4     3 ms     3 ms     3 ms  106.162.241.161
    5     4 ms     3 ms     4 ms  sjkBBAR001-2.bb.kddi.ne.jp [106.162.241.141]
    6     7 ms     7 ms     6 ms  sjkBBAC06.bb.kddi.ne.jp [182.248.174.1]
    7    11 ms     3 ms     4 ms  otejbb206.int-gw.kddi.ne.jp [210.234.225.5]
    8     4 ms     4 ms     4 ms  cm-ote257.int-gw.kddi.ne.jp [118.155.197.52]
    9     5 ms     4 ms     4 ms  125.29.25.94
    10     7 ms     7 ms     7 ms  118.151.224.242
    11     7 ms     7 ms    13 ms  182.22.34.86
    12    10 ms    10 ms     8 ms  f4.top.vip.ssk.yahoo.co.jp [182.22.39.242]

    ※全て日本国内                
     遠いサイト(米Yahoo)の経路→19回経由
    C:\>tracert www.yahoo.com
    fd-fp3.wg1.b.yahoo.com [106.10.139.246] へのルートをトレースしています経由するホップ数は最大 30 です:
    1     1 ms    <1 ms     1 ms  10.10.5.126 2     1 ms     1 ms     1 ms  10.0.0.254
    3     3 ms     4 ms     3 ms  106.162.241.131
    4     3 ms     3 ms     4 ms  106.162.241.161
    5     4 ms     3 ms     4 ms  sjkBBAR001-2.bb.kddi.ne.jp [106.162.241.141]
    6     8 ms     4 ms     8 ms  sjkBBAC06.bb.kddi.ne.jp [182.248.174.1]
    7     6 ms     4 ms     3 ms  otejbb206.int-gw.kddi.ne.jp [210.234.225.85]
    8    10 ms    17 ms    17 ms  ix-ote212.int-gw.kddi.ne.jp [106.187.6.134]
    9    15 ms    17 ms     4 ms  Vlan527.ihar1.OVC-Tokyo.as6453.net [116.0.90.65]
    10     4 ms    19 ms    13 ms  Port-channel2.ihar2.OVC-Tokyo.as6453.net [116.0.91.1]
    11     5 ms     4 ms     4 ms  if-3-1-0-11.tcore1.TV2-Tokyo.as6453.net [180.87.180.21]

    ※1~12:日本国内でラウンドトリップが少ない

    12     *        *        *     要求がタイムアウトしました。
    13    70 ms    75 ms    70 ms  if-6-2.tcore1.SVW-Singapore.as6453.net [180.87.12.109]
    14    70 ms    71 ms    70 ms  if-2-2.tcore2.SVW-Singapore.as6453.net [180.87.12.2]
    15    77 ms    81 ms    82 ms  180.87.15.122
    16    71 ms    91 ms    71 ms  ae-5.msr1.sg3.yahoo.com [203.84.209.87]
    17    78 ms    78 ms    77 ms  ae-2.clr2-a-gdc.sg3.yahoo.com [106.10.128.5]
    18    71 ms    71 ms    71 ms  et-18-25.fab3-1-gdc.sg3.yahoo.com [106.10.128.25] 19    88 ms    88 ms    88 ms  po-11.bas1-1-prd.sg3.yahoo.com [106.10.128.69]
    20    73 ms    72 ms    72 ms  ir1.fp.vip.sg3.yahoo.com [106.10.139.246]

    ※13~20:日本国外でラウンドトリップが多い         
    (地理的・物理的に遠く、相手先のネットワーク環境に依存)
    →この遅延をキャッシュと経路最適化で高速化し、解消する


    通常経路とCDNの概念図


    • 通常経路だと、遅延が大きくなる(経由が多く、各国の通信環境に依存)
    • CDNだと、エッジサーバのキャッシュと最適経路で安定した高速ネットワークで遅延が小さい

    CDN経由でのアクセス概念図

    • 日本国内のPCから、米国に存在するホスト「abc.com」へアクセスすることを想定
    • エッジサーバが応答することで、高速化+ホスト負荷軽減のメリットもある

    改めてCDNとは?

    Contents      Delivery   Network
    コンテンツを  配信する  ネットワーク

    これまでの内容を合わせると、次のように言うことができます。
    • Webコンテンツ(サイト、画像、動画等)
    • 世界中に分散配置されたエッジサーバのキャッシュを利用して
    • 専用に経路最適化された高速なネットワーク網を通じて
    • 全世界のインターネットへの配信を高速化する仕組み

    SPEEDAでの効果

    当社サービスのSPEEDAで検証した結果が下記の通りです。


    • どの地域でも200%以上高速化されている(日本国内も含めて)
    • 特に中国(右端)が顕著
      • 中国特有の環境が影響
        • 金盾(グレートファイアウォール)という中国国内の情報検閲システムや、 南北問題(中国国内でもアクセスが遅い)などでネットワーク遅延が大きい


    CDN導入後の一般的な変更点・注意点

    • アクセス元IPアドレスがエッジサーバのIPになる
      • 本来のリモートホストはHTTPヘッダで付与
        • 「X-forwarded-for」や「Origin-IP」など
      • Webサーバでのアクセス制限やプログラム側での処理に注意
    • 静的ファイルがキャッシュされる
      • ファイル名に日付を付けるなど、ユニーク化しないとキャッシュ有効期限まで反映されない等の問題が発生する可能性がある

    まとめ

    以上、一般的なCDNの効果と仕組みを説明しました。
    もちろんメリットだけでなく費用やアプリケーション改修も発生するので、
    しっかりと検証して、費用対効果を見極めることが重要です。

    2014年12月24日水曜日

    第1回 えびスタ! ~ 恵比寿スタートアップ勉強会 ~ レポート

    先日12月18日、恵比寿にある UZABASE 新オフィスにてエンジニア向け勉強会「えびスタ! 」第 1 回を開催させて頂きました。

    移転したばかりの新オフィスで初めて開催する勉強会ということで不安な点もありましたが、50 人という定員枠に対してなんと 50 人オーバー近い参加登録を頂き、盛況のうちにイベントを終えることが出来ました。参加者の皆さん、ありがとうございました!


    本記事では当日の様子をレポートしたいと思います。




    今回のアジェンダは以下のとおりです。
    前半はクックパッド、Retty、VASILY、 UZABASE から 4 名のエンジニアによるプレゼンテーション。後半は皆でわいわいエビスビール(恵比寿だけに)やピザをつまみながらの QA セッション + 懇親会でした!

    時間
    発表者
    内容
    19:00 ~ 19:20
    小野 大器(クックパッド株式会社)
    マイクロサービスアーキテクチャについて
    19:20 ~ 19:40
    梅田 昌太(Retty 株式会社)
    Dev が AWS と出会って DevOps を目指した話
    19:40 ~ 20:00
    今村 雅幸(株式会社 VASILY)
    プッシュ通知大戦争
    20:00 ~ 20:20
    文字 拓郎(株式会社 UZABASE)
    NewsPicks を支える技術と怖い話
    20:30 ~ 21:15
    -
    QA セッション
    21:15 ~ 22:00
    -
    懇親会


    <プレゼンテーション>

    1.マイクロサービスアーキテクチャについて     小野 大器 様(クックパッド)

    クックパッドからは小野様に、クックパッドが何故マイクロサービスアーキテクチャを採用したかということと、実際に行っている取組みについてご紹介頂きました。


    クックパッドがマイクロサービスアーキテクチャを採用するにあたった経緯として


    ・ビジネスの変化(ブラウザからモバイルへの変遷といった環境の変化)
    ・巨大化するモノリシックなアプリではテストやデプロイに時間がかかり過ぎる

    などの理由を挙げられていました。





    えびスタ以前から「最近マイクロサービス気になる…!」という声を周りで聞いていたので、そのような方にとってはとても勉強になるセッションだったのはないでしょうか。


    2.Dev が AWS と出会って DevOps を目指した話  梅田 昌太 様(Retty)

    Retty からは 梅田様に、サービスの成長とユーザーの増加に伴う問題にへの対応と今後、そしてアプリケーションエンジニア出身の DevOps として AWS とどう関わっているのかについてお話頂きました。


    ユニークユーザーが 10 万人以下のサービス黎明期から 200 万~ 400 万人の人気サービスにまで成長していく過程で 、インフラアーキテクチャをどのように変化させて対応していったのかついて取り上げていらっしゃいました。






    またインフラで利用されている ElasticBeanstalk についての詳しい説明もされていました。
    ElasticBeanstalk は AWS から無料で提供されている アプリケーション管理サービスです。AWS内のアプリケーションに対してデプロイの詳細を自動的に設定してくれます。
    参加者には ElasticBeanstalk を利用されている方はほとんどいなかったのですが、このセッションを聞いて気になったという方も多かったのではないでしょうか。


    3.プッシュ通知大戦争  今村 雅幸 様(VASILY)

    VASILY からは今村様に、iQON で行われているアプリのプッシュ通知に対しての取り組みをご紹介頂きました。(ちなみに VASILY さんは UZABASE と同じビルの 1 フロア下のご近所さんです)


    プッシュ通知のメリットとそれに付随する課題にはじまり、代表的な ASP サービスの比較から、自前でプッシュ通知サービスを実装するにあたりおこなった様々な工夫(配信の最適化・スケーラビリティーの確保の仕方・通知の分析など)まで、プッシュ通知まわりでのノウハウについてお話頂きました。


    テスト配信における秘話などタメになる話が満載でした。




    4.NewsPicks を支える技術と怖い話  文字 拓郎(UZABASE)

    弊社からは 3 ヶ月前に NewsPicks チームに join したばかりの文字が、急成長を続けるサービスの裏側がどのようなアーキテクチャで構成されており、どのような工夫を行っているのか、実際にあった事件なども交えてご紹介。

    今までにあった怖い話として、荒ぶる Redis、暴走する Phantom JS、互換性肥満などといった事件の原因及び、解決策としてどのような工夫を行ってきたのかについてお話しました。




    プレゼンテーションのあとはエビスビールとピザを頂きながら、しばし休憩を挟んで、後半の QA セッションへ。




    <QAセッション>

    QA セッションでは本日登壇された 4 名によるカジュアルな質疑応答が行われました。ガリガリにテック寄りの質疑応答が行われたわけではなく、ゆるい雰囲気の中で、ビール片手に冗談を交えながらの進行でした。
    質問は、会場にいる参加者全員に対して、スマートフォンを用いて 4 択のアンケートで回答をとり、その結果をスクリーンに表示して共有する、という形で行われました。弊社杉浦恒例のリアルタイム参加型です!


    1418904633198.jpg


    登壇者の方には代表して、各自選んだ回答に対しての意見や、なぜその回答を選んだのか、などのお話をしていただきました。ユーモラスな回答も多かったのですが、時には登壇者の方の熱いお話を聞くこともできました。

    質問内容は、好きなエディタは何かという質問から、普段聞きづらいような攻めた質問まで様々で、エンジニアジョークが飛び交うセッションでした。登壇者の意見を聞いて、他のエディタや他の言語に浮気してみようかな、なんて思った方もいたかもしれませんね。


    また、登壇者以外の参加者がアンケートページで入力したコメントもスクリーンに表示されるようになっていて、そのコメントに対しての笑いも起こり、会場全体で盛り上がっていました。


    その後は同じ会場内で懇親会を行いました。参加者の方と主催企業エンジニアの垣根なく、皆様思い思いに交流されていました。
    集合写真を撮れなかったのが心残りです…
    1418905278274.jpg


    1418906038626.jpg
    1418906110029.jpg

    幸いなことに「次回はいつですか」というお声も多数頂けたので、是非次回も開催したいと思います!恵比寿オフィス引っ越し後まもなくの勉強会で到らないところも多々ありましたが、次回の勉強会では今回の反省を活かしてより良いものにしたいと思います!


    みなさまご参加ありがとうございました!

    2014年12月18日木曜日

    オフショア開発 @ Framgia Vietnam

    SPEEDA QAチームの藤田です。

    オフショアでのテスト自動化PJ立ち上げのために11月中旬より1か月間 Framgia Vietnam に来ています。

    Framgia さんには、以前からUzabaseの社内システムの開発を依頼していましたが、今回は社内システムだけでなくSPEEDAの自動テストコードを実装いただくべく一緒にベトナムで仕事しております。

    せっかくなのでちょっと今までの投稿とは異色ですが、Framgiaさんとのオフショア開発についてまとめようと思います。
    今後オフショアを考えてる方には参考になる?とうれしいです。

    信頼できるオフショアパートナー ベトナムのFramgiaさん

    「なぜFramgiaさんと一緒に開発するのか?」という理由はいろいろありると思うのですが、
    ベトナムという国としての特性とFramgiaさんの会社としての魅力が大きいと感じています。
    オフショア開発を進めるポイントとなるベトナムの基本情報。
    • 物価、労働コストが日本の3~4分の1
    • 平均年齢が若い(27~28才)
    • 親日
    • IT業種はとても人気がある
    • みんなポジティブで明るく、面倒みの良い国民性

    Framgiaさんの場合、平均年齢が27才以下らしく、実際に170人くらいの社員みなさんが若くパワーあふれる感じです。
    優秀な学生のリクルートには力を入れているとのことで、国費留学で海外で学んだ超エリートもいるとのこと。
    Framgiaさんではラボ型開発を推進しています。ラボ型は、契約した期間で担当メンバーを確保することができ優秀なエンジニアを常に確保することができます。

    日本と2Hの時差のあるベトナムでは、日本に合わせてみなさん1日は基本的に以下のスケジュールで仕事しています。
    稼動時間が同じなので、いつでもオンラインでチャットや会議をできます。
    AM 7:45 全社の朝礼
    朝は、日本のクライアントに合わせて早いです。
    毎日1人ずつモーニングスピーチを英語でし、最後は日本のあいさつの練習を全社で実施。やる気UP!

    AM 8:00 チームの朝会
    どのチームも、最後は「モー・ハイ・バー わっしょい!」の掛け声で終了。みんな同じ時間に朝会をするので掛け声が社内のそこら中から聞こえます。やる気UP!2回目。

    AM 8:10~PM17:00? 開発集中
    チームは席が近く、いつでもコミュニケーションはその場ですぐできるのでミーティングが少なく仕事に集中できる時間が長い。

    モチベーションやチームビルディングはどこでも大きな課題ですが、ここではそんな悩みないのでは?と思えるくらいにみなさん明るく仲良しで、とても仕事に集中しやすい会社の文化があります。

    もちろん技術力UPにも熱心で、チーム対応Hackathon、コードコンテストなどのイベントが企画されていたり、トレーニングルームが用意されておりプロジェクトに携わるメンバーが必要スキルを短期で習得できる体制ができています。

    ということで、長期で一緒に仕事をしこれからのオフショア開発の可能性をほぼ毎日感じ、更にベトナムとFramgiaが好きになってしまいました。(暖かいし、食べ物もめっちゃおいしいし。。。)

    立上げはやっぱりオンサイト!
    私は、春からオフショア開発に参加しましたが、今までを振り返って思うのはやはりコミュニケーションが難しいという部分です。
    テスト自動化PJの立ち上げでは、メンバーが自分たちで開発環境をつくり、コードを書いていけるように、
    「コンセプトを共有 > 必要最小限のドキュメントを作成  実際に一緒にやってみる」繰り返しました。
    その中でよりワークしそうなフローを一緒に相談したり、躓いている部分を共有しました。
    割とスムーズに自動テストの実装を依頼できる体制ができたのも、オンサイトだったからこそ。

    また、ベトナムでFramgiaメンバーと仕事がしたい!!


    自動化を手伝ってくれたFramgiaエンジニア Ngocさん
    with 社内Hackathonの告知ポスター!

    2014年12月16日火曜日

    NewsPicks(iOS)の設計思想

    NewsPicksチームインターン生の保田です。
    主にiOSアプリ開発のお手伝いをしております。
    iOSアプリを作っていて悩ましいな、と思うのが、同じ機能を持つがiPadとiPhoneで見た目が違うページをどう設計していくか、ということです。

    ちょうど僕が関わった、NewsPicksの特徴的な機能である「ニュース中間ページ」をどのように設計したかについてお届けいたします。



    NewsPicks上でニュースをみるとき、上図の左から右の流れで画面を切り替えます。
    この中の真ん中の画面(ニュース中間ページ)の設計についてご説明していきます。


    Season1. 単一仕様時代

    当初は、次のような単純な仕様でした
     - 「フォローしているユーザー」セクションで、コメントを表示
     - 「その他のユーザー」セクションで、コメントを表示


    ただし、並び順に関してはサーバー側で『時系列順』『Like順』でソートされたものを受信して、そのまま表示していました。

    当時は次のようなクラス図になっていました(メソッドは省略)。


    ControllerにはiPhone, iPadで共通の処理が双方に書かれていたため、メンテナンス性が低いものになっていました。また、NewsTableViewにはコメントが表示されるのですが、コメントの有無の判定、データの保持などの処理も行っており、ViewとLogicが切り分けられていない典型的なアンチパターンになってしまっていました。


    Season2. 人気コメントの誕生

    次に、人気ニュース一覧からニュース中間ページに遷移した場合、Likeがたくさん付いているコメントを「人気コメント」というセクションで上位に表示するという仕様が産声を上げました。


    仕様を整理すると、下記のようになります。
    そこで、以下のような設計に変更しました。


    Commentsというクラスを作成し、NewsTableViewから表示するコメントの表示内容、コメントのソートなどを切り離しました。これで、ViewからLogicを少し切り離すことができました。
    CommentsWithTrendingCommentsはCommentsを継承した、人気コメントを表示するためのクラスです。NewsTableViewがどうCommentsを生成すれば良いか知っていて、それをControllerが知っているという設計です。実装の詳細を知らないと使えないクラスはイケてません。


    Season3. 検索機能の実装と、連載企画のスタート

    NewsPicksオリジナル連載企画がはじまり、中間ページに以下のようなページが仲間入りしました。

    上のほうに連載名が書いていたり、「記事に登場するユーザー」というカテゴ リが追加されたりしました。
    また、検索機能が追加され、コメント検索の結果から遷移すると「ヒットした コメント」というカテゴリを表示することとなりました。
    仕様を整理すると、下記になります。
    それに伴い大幅な設計変更を行い、以下の様な設計になりました。
    

    大工事です。
    5個しか無かったクラスが
    20 個近くになりました。緑色のクラスが実際に呼び出されるクラスです。
    共通部分は
    Abstract に集約し、コメントのロジックは PickerCommentsCategorizeLogic が責務を持 ち 、 データはCategorizedComments で授受されそのまま表示すると正しいセクションが表示されます。
    また、
    Factory Method パターンを導入したことによって呼び出し側は Abstract など実装の詳細は一切知りません。 しかし、まだ改善の余地があります。『AbstractNewsSummaryController』の 親クラスが iPad, iPhone によって分かれてしまっていて、全く同じ処理が iPhone, iPad のそれぞれに書かれてしまっています。 
    Season4. 更なるリファクタリング Season3 で残ってしまった重複コードを削除すべく、リファクタリングを 行いました。iPad だけが継承している TransitionController は、Controller の遷移方法に関する責務を持っていました。これをを Helper として存在さ せ、標準の UIViewController を継承できるようにしました。
    こうすることで共通部分を抽象クラスへ追いやることができ、具象クラスの重複コードが撲滅できました。
    ここで、season3でFactory Method パターンを導入していたため、呼び出し側では全くコードの変更が必要ありませんでした。GoF さまさまです。
    まとめ
    NewsPicks では、同じ機能をもつが iPad iPhone で見た目が違うページを、下記のような戦略でメンテナンス性の高い設計で実装しています。 - View, Logic, Controller を切り分ける
    - 具象クラスに点在する共通処理を抽象クラスに追いやる
    - Factory Method パターンでインスタンスの生成方法を隠すことにより、後々の変更に強くする
    サービスローンチ時に、様々な試行錯誤を行いながら徐々にサービスを成長させる過程で、負債コードをためてしまうのは致し方ないことです。しかし、適切な手順で設計を変更すれば必ずコードは綺麗になります!
    NewsPicks では更なるグロースに向けて、一緒に戦ってくれるエンジニアを募集しております!
    インターン生でも設計から携わらせていただけるのでとても勉強になります!
    興味をお持ちいただいた方は Wantedly などからご応募ください!!

    
    

    2014年12月12日金曜日

    俺の Docker イメージ

    SPEEDA 開発チームの緒方です。

    最近話題に上ることも多い Docker ですが、UZABASE でもチームによっては積極的に使っています。
    (現在は主に開発用途。)

    他のコンテナ型仮想化技術と比較した場合の Docker の良さとして、
    • Docker Hub など、レジストリに登録されているイメージを利用できる
    • Dockerfile を使用したイメージ構築の自動化
    などが挙げられると思います。

    Docker Hub をざっと見渡しただけでも、Ubuntu や CentOS など環境だけを提供するものから redis や MySQL などそのままアプリケーションが実行できるものまで様々なイメージを見つけることができ、その良さを垣間見ることができます。

    さて、その便利な Docker のイメージですが、実体がどのようになっているかご存知でしょうか。
    Ubuntu や CentOS が丸ごと入っているくらいだからさぞかし難解なものだろうと思われる方もいらっしゃるかもしれませんが、作成の手順はとても単純です。

    ということで、試しに作ってみることにしました。

    とりあえず簡単なものということで、
    • フルスクラッチから作る
    • busybox を動かすだけの最小のイメージ
    ということを目標とします。
    イメージの作り方にも色々な方法があるのですが、今回はイメージのファイル構成を含んだアーカイブを作成して docker import するというやり方でいきます。

    アーカイブの最終的なファイル構成は次のようになります。
    bin/
    bin/busybox (※busybox-x86_64 へのシンボリックリンク)
    bin/busybox-x86_64
    
    話がややこしくならないように、busybox はスタティックリンク版を利用します。
    (ダイナミックリンクしているバイナリを実行するためにはライブラリの配置や /etc/ld.so.conf の設定などが必要です。)

    以下手順です。

    環境:
    Docker 1.3.2 / Debian sid (Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.7-2 (2014-11-06) x86_64 GNU/Linux)
    $ mkdir -p busybox-static/bin
    $ cd busybox-static/bin
    $ wget -P . http://www.busybox.net/downloads/binaries/latest/busybox-x86_64
    $ chmod +x busybox-x86_64
    $ ln -s busybox-x86_64 busybox
    $ cd ..
    $ tar zcf busybox-static.tar.gz bin
    
    これで、busybox を含んだ bin ディレクトリだけの最小の Docker イメージができました。
    作成したアーカイブを docker import します。
    $ cat busybox-static.tar.gz | docker import - busybox-static
    
    うまくいったようです。
    REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
    busybox-static      latest              85e373c46d80        27 seconds ago      973.2 kB
    
    では、run してみます。
    $ docker run busybox-static /bin/busybox ls -a
    .
    ..
    .dockerenv
    .dockerinit
    bin
    dev
    etc
    proc
    sys
    
    これも成功です。目標達成!

    ところで、アーカイブには bin ディレクトリしか含まれていなかったはずですが、いくつかファイルやディレクトリが追加されています。
    これらは、デバイスなどホストのリソースを利用できるように、Docker が勝手に作ってコンテナに追加してくれているものです。

    このような感じで、単純なイメージであれば非常に簡単に作ることができます。
    Docker コンテナ内でプログラムを実行するには何が必要であるか、コンテナからホストのリソースはどのように見えているかなど勉強になることも多いので、一度スクラッチからのイメージ作成をやってみても面白いかと思います。

    2014年12月11日木曜日

    PhantomJSを使ってみる

    Techチームの遠藤です。

    コンテンツのグローバル展開に向けて、いくつかの地点での画面表示の速度測定のためにphantomjsを使用し計測しました。
    全部ではないですが初めの部分だけ記述します。

    PhantomJSはまぁ仮想ブラウザを立ち上げてHPを操作していくものですね。
    自動テストなどに使えるものです。

    PhantomJS
    http://phantomjs.org/

    DownloadでそれぞれのOSにあったものをDLしてください

    私はwindowsでやってるのでwindowsのみ記述します。
    といっても大したことしてません。

    windowsの場合はDLしてきたzipファイルを解凍し、任意の場所に配置してから環境変数にそのフォルダを指定すると使えるようになります。

    さっそく使ってみます。

    jsファイルを書いて、コマンドプロンプトで
    phantomjs XXX.js

    phantomjsコマンドと実行するファイルを指定するだけでできます。

    まず手始めにgoogleへアクセスしてみます。

    var page = require('webpage').create();
    page.open('http://www.google.co.jp/', function () {
            page.render('sample.png');
            phantom.exit();
    });
    

    新しく作ったJSファイルに上記を記述して実行。

    アクセスできました。
    sample.pngというピクチャが保存されています。

    ↓こんな感じ



    実際に使うにはどんどんページ遷移していきたいので、、、
    下記ページを参考に遷移する先をtask化していきます。

    var page = require('webpage').create();
    
    function next(){
      if (tasklist.length == 0) {
        phantom.exit();
      }
      var task = tasklist.shift();
    
      page.address = task.path;
      page.resources = [];
    
      page.open(page.address, function(status) {
        if (status !== 'success') {
          console.log('FAIL to load the address');
          phantom.exit(1);
        } else {
          var imagename = 'sample2_' + ('0' + task.id).slice(-2);
          page.render(imagename + '.png');
    
          next();
        }
      });
    }
    
    var tasklist = [
    {
      id:1,
      path:'http://www.yahoo.co.jp/'
    },{
      id:2,
      path:'http://www.google.co.jp/'
    }];
    
    next();
    
    こんな感じに...


    実行してみるとちゃんとアクセスされているようです。
    sample2_01.png、sample2_02.pngと画像が保存されているので順にアクセスされてる模様です。

    さて、意味もなくやってるわけじゃなくて仕事で使うので自社サイトのアクセスをするわけですが。。。
    自社サイトでは認証があるのでそれを実装します。

    var page = require('webpage').create();
    
    function next(){
      if (tasklist.length == 0) {
        phantom.exit();
      }
      var task = tasklist.shift();
    
      page.address = task.path;
      page.resources = [];
    
      page.open(page.address, function(status) {
        if (status !== 'success') {
          console.log('FAIL to load the address');
          phantom.exit(1);
        } else {
          var imagename = 'sample3_' + ('0' + task.id).slice(-2);
          page.render(imagename + '.png');
          if(task.operation == null){
            next();
          } else {
            task.operation(page);
          }
        }
      });
    }
    
    var tasklist = [
    {
      id:1,
      path:'http://www.ub-speeda.com/',
      operation:
        function(page){
          page.evaluate(function() {
            //ここは各サイトで実装が異なります
            document.forms[0].username.value = 'dummy_username';
            document.forms[0].password.value = 'dummy_password';
            document.forms[0].submit();
          });
    
          //弊社サイトではログイン数管理をしており、それを超過した場合、古いログインを追い出すなどがあるため確認メッセージを出し、再度ログインボタンを押してもらう仕様のため、その対応です
          setTimeout(function() {
            if (page.url.indexOf('top/welcome') > 0) {
              page.evaluate(function() {
                document.forms[0].submit();
              });
              setTimeout(function() {
                next();
              }, 3000);
            } else {
              next();
            }
         }, 3000);
        }
    },{
      id:2,
      path:'http://www.ub-speeda.com/company/companyinformation/cid/JPN3O1U73D3VNGWO'
    }];
    
    next();
    

    operationという変数を作ってそこにログイン処理を放り込んでみました。
    operationが実装されていれば実行する感じですね。
    これを応用すればHP内の操作を追加していけるかと思います。
    こちらは実際動作するのですがID/Pass等が必要なため試していただける方は少ないかと思います。(ゴメンナサイ

    次にファイルのアクセスに関しての情報をファイルに記述します。
    ここではページのロード時間しか記述しませんが、ちょこっと検索すればより詳細な情報を取得できるサンプルコードがあるところがあるので詳細はそちらを参考にしてください。

    まず、ファイルに書きだす方法ですが公式を参考に。。。

    var fs   = require('fs');
    fs.write(ファイル名, 内容, 'w');

    で書き出せます。

    組み込んでみましょう。

    var page = require('webpage').create();
    var fs   = require('fs');
    
    page.onLoadStarted = function() {
      page.startTime = new Date();
    };
    
    function next(){
      if (tasklist.length == 0) {
        phantom.exit();
      }
      var task = tasklist.shift();
    
      page.address = task.path;
      page.resources = [];
    
      page.open(page.address, function(status) {
        if (status !== 'success') {
          console.log('FAIL to load the address');
          phantom.exit(1);
        } else {
          page.endTime = new Date();
    
          var logname = 'sample4_' + ('0' + task.id).slice(-2);
          store(logname, page.endTime, page.startTime);
    
          if(task.operation == null){
            next();
          } else {
            task.operation(page);
          }
        }
      });
    }
    
    function store(logname, endTime, startTime){
      page.render(logname + '.png');
      fs.write(logname + '.log', endTime - startTime , 'w');
    }
    
    var tasklist = [
    {
      id:1,
      path:'http://www.ub-speeda.com/',
      operation:
        function(page){
          page.evaluate(function() {
            document.forms[0].username.value = 'dummy_username';
            document.forms[0].password.value = 'dummy_password';
            document.forms[0].submit();
          });
    
          setTimeout(function() {
            if (page.url.indexOf('top/welcome') > 0) {
              page.evaluate(function() {
                document.forms[0].submit();
              });
              setTimeout(function() {
                next();
              }, 3000);
            } else {
              next();
            }
         }, 3000);
        }
    },{
      id:2,
      path:'http://www.ub-speeda.com/company/companyinformation/cid/JPN3O1U73D3VNGWO'
    }];
    
    next();
    

    さて、これで
    sample4_01.log
    sample4_02.log
    というファイルが出来ました。

    中身は「endTime - startTime 」という内容が示すとおり殺伐とした数字だけです。
    sample4_01.logの中身
    ---
    219
    ---

    sample4_02.logの中身
    ---
    2448
    ---

    これでページを開く時間がわかるようになりました。
    実際にはこれをより詳細にHAR形式で出力するなどして、使えるものに仕上げていってます。
    他にも、ダウンロードやajaxでの表示待ちなどを組み込みながら作ったりします。

    こんな感じで日々仕事してます。。。