不格好エンジニア

wordpress.comから引っ越しました。

【あの有名サービスが】コードリーディングで学ぶAngularJS【丸裸に】

コードリーディング

AngularJS初心者です。
コードリーディングという形で、AngularJSで実装されたWebサービスソースコードを題材に使って、その動きや機能がどのように実装されているのかを、ソースコードのレベルで解析していこうと思います。

※時間のある時に、随時追記していきます。

題材「FMTube 」について

コードリーディングの題材とするのはこちら。
ゆーすけべーさんが、AngularJSの勉強中に作成したサービスだそうです。

FMTube!

アーティストの名前を入れると、楽曲リストを順番に再生してくれます。
素晴らしいです。勉強させて頂きます。

ソースコード

ソースコードですが、Webサービスとして公開しているので、当然、Webブラウザから確認することができます。
メインロジックは、こちらに記述されています。

http://yusukebe.github.io/FMTube/js/app.js

参考資料

正直、全然わからない。。。この辺りを参考にしながら進めていきます。

初心者向けAngularJS - その1 - albatrosary's blog

Service, Factory, Providerが何の事か分からない方はこちら。

AngularJs Service,Factory,Providerなどなど - senta.me/blog

エントリポイント

var app = angular.module('fmtube', ['ng']);

ここでは、このアプリケーションを初期化して、依存するモジュールを登録します。

view

次に、view部分をざっと眺めます。
#content, #listあたりに注目すれば処理内容を把握できそうです。

<html ng-app="fmtube" ng-controller="controller" class="ng-scope"><head><style type="text/css">@charset "UTF-8";[ng\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\:form{display:block;}.ng-animate-start{border-spacing:1px 1px;-ms-zoom:1.0001;}.ng-animate-active{border-spacing:0px 0px;-ms-zoom:1;}</style>
    <meta charset="utf-8">
    <title ng-bind="title" class="ng-binding">Eis essen by GReeen - FMTube!</title>
    <link href="css/bootstrap.min.css" rel="stylesheet">
    <link href="css/typeahead.js-bootstrap.css" rel="stylesheet">
    <link href="css/app.css" rel="stylesheet">
    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
      <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
    <![endif]-->
    <script id="www-widgetapi-script" src="https://s.ytimg.com/yts/jsbin/www-widgetapi-vflAiKzF-/www-widgetapi.js" async=""></script><script src="http://www.youtube.com/iframe_api"></script><script id="twitter-wjs" src="http://platform.twitter.com/widgets.js"></script><script async="" src="//www.google-analytics.com/analytics.js"></script><script src="js/jquery.js"></script>
    <script src="js/typeahead.min.js"></script>
    <script src="js/bootstrap.min.js"></script>
    <script src="js/angular.min.js"></script>
    <script src="js/angular-resource.min.js"></script>
    <script src="js/app.js"></script>
    <script>
      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
      ga('create', 'UA-492497-67', 'yusukebe.github.io');
      ga('send', 'pageview');
    </script>
  </head>
  <body style="" data-twttr-rendered="true">
    <div class="container">
      <div id="header" class="clearfix">
        <h1><a href="http://yusukebe.github.io/FMTube/">FMTube!</a></h1>
      </div>
      <!-- /header -->
      <div class="row">
        <div id="form" class="col-md-4">
          <form role="form" ng-submit="submit(true)" class="ng-valid ng-dirty">
            <div class="form-group">
              <span class="twitter-typeahead ng-valid ng-dirty" style="position: relative; display: inline-block; direction: ltr;"><input class="tt-hint" type="text" autocomplete="off" spellcheck="off" disabled="" style="position: absolute; top: 0px; left: 0px; border-color: transparent; box-shadow: none; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255);"><input type="text" class="form-control typeahead tt-query" placeholder="Artist name" ng-model="artist" autocomplete="off" spellcheck="false" dir="auto" style="position: relative; vertical-align: top; background-color: transparent;"><span style="position: absolute; left: -9999px; visibility: hidden; white-space: nowrap; font-family: 'Alegreya Sans', sans-serif; font-size: 14.2857141494751px; font-style: normal; font-variant: normal; font-weight: 400; word-spacing: 0px; letter-spacing: 0px; text-indent: 0px; text-rendering: auto; text-transform: none;">GReeen</span><span class="tt-dropdown-menu" style="position: absolute; top: 100%; left: 0px; z-index: 100; display: none; right: auto;"><div class="tt-dataset-artist" style="display: block;"><span class="tt-suggestions" style="display: block;"><div class="tt-suggestion" style="white-space: nowrap; cursor: pointer;"><p style="white-space: normal;">Greeen Linez</p></div><div class="tt-suggestion" style="white-space: nowrap; cursor: pointer;"><p style="white-space: normal;">GReeen</p></div><div class="tt-suggestion" style="white-space: nowrap; cursor: pointer;"><p style="white-space: normal;">Greeen</p></div><div class="tt-suggestion" style="white-space: nowrap; cursor: pointer;"><p style="white-space: normal;">Dr.Greeen</p></div><div class="tt-suggestion" style="white-space: nowrap; cursor: pointer;"><p style="white-space: normal;">Al Greeen</p></div></span></div></span></span>
              <button type="submit" class="btn btn-default"><b>Play!</b></button>
            </div>
          </form>
        </div>
      </div>
      <div id="social-buttons">
        <span style="margin-right:4px;">
          <iframe class="hatena-bookmark-button-frame" title="このエントリーをはてなブックマークに追加" frameborder="0" scrolling="no" width="130" height="20" src="javascript:false" style="width: 111px; height: 20px;"></iframe><script type="text/javascript" src="http://b.st-hatena.com/js/bookmark_button.js" charset="utf-8" async="async"></script>
        </span>
        <iframe id="twitter-widget-0" scrolling="no" frameborder="0" allowtransparency="true" src="http://platform.twitter.com/widgets/tweet_button.d58098f8a7f0ff5a206e7f15442a6b30.en.html#_=1416154421769&amp;count=horizontal&amp;id=twitter-widget-0&amp;lang=en&amp;original_referer=http%3A%2F%2Fyusukebe.github.io%2FFMTube%2Findex.html&amp;size=m&amp;text=FMTube!&amp;url=http%3A%2F%2Fyusukebe.github.io%2FFMTube%2F" class="twitter-share-button twitter-tweet-button twitter-share-button twitter-count-horizontal" title="Twitter Tweet Button" data-twttr-rendered="true" style="width: 109px; height: 20px;"></iframe>
        <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
        <span style="margin-left:-20px;">
          <iframe src="//www.facebook.com/plugins/like.php?href=http%3A%2F%2Fyusukebe.github.io%2FFMTube%2F&amp;width=150&amp;layout=button_count&amp;action=like&amp;show_faces=true&amp;share=true&amp;height=21&amp;appId=640667512644377" scrolling="no" frameborder="0" style="border:none; overflow:hidden; height:21px; width:150px;" allowtransparency="true"></iframe>
        </span>
      </div>

      <hr>

      <div class="row">
        <div id="content">
          <iframe id="player" frameborder="0" allowfullscreen="1" title="YouTube video player" width="600" height="400" src="https://www.youtube.com/embed/5sveP1gSXGA?autoplay=1&amp;rel=0&amp;enablejsapi=1&amp;origin=http%3A%2F%2Fyusukebe.github.io"></iframe>
        </div>
        <div id="list">
          
          <!-- ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix list-active" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Eis essen</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="http://userserve-ak.last.fm/serve/34s/97541019.jpg" class="pull-left" src="http://userserve-ak.last.fm/serve/34s/97541019.jpg">
              <b class="pull-left ng-binding">Gesundes Ego</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Eis essen - JuliensBlogContest</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="http://userserve-ak.last.fm/serve/34s/92538915.jpg" class="pull-left" src="http://userserve-ak.last.fm/serve/34s/92538915.jpg">
              <b class="pull-left ng-binding">Ein gesundes Ego</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="http://userserve-ak.last.fm/serve/34s/92538915.jpg" class="pull-left" src="http://userserve-ak.last.fm/serve/34s/92538915.jpg">
              <b class="pull-left ng-binding">Nur das Beste</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="http://userserve-ak.last.fm/serve/34s/92538915.jpg" class="pull-left" src="http://userserve-ak.last.fm/serve/34s/92538915.jpg">
              <b class="pull-left ng-binding">Die Blüte meiner Selbst</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="http://userserve-ak.last.fm/serve/34s/92538915.jpg" class="pull-left" src="http://userserve-ak.last.fm/serve/34s/92538915.jpg">
              <b class="pull-left ng-binding">Blauer Planet</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">JottBeBe Qualifikation</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="http://userserve-ak.last.fm/serve/34s/92538915.jpg" class="pull-left" src="http://userserve-ak.last.fm/serve/34s/92538915.jpg">
              <b class="pull-left ng-binding">Hovercraft</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Lächeln Exklusiv</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Hippie 2.0</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Scheiss doch einfach drauf (feat. Der Rote)</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Hab Wörter für dich</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">JottBeBe Winin Battle</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Vaubetee Quali</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Pinselstrich für die Ohren</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Abseits der Norm</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Ich bin hier</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">Frauen</b>
            </a>
          </div><!-- end ngRepeat: track in tracks --><div class="list-item ng-scope" ng-repeat="track in tracks">
            <a class="clearfix" ng-click="click($index)" ng-class="active_class($index)">
              <img ng-src="" class="pull-left">
              <b class="pull-left ng-binding">VauBeTee Qualifikation - Instrumental</b>
            </a>
          </div><!-- end ngRepeat: track in tracks -->
        </div>
      </div>
      
      <hr>
      <div id="footer">
        <address>
          © <a href="http://yusukebe.github.io/">yusukebe</a>,
          <b>FMTube</b> using Last.fm Web API, YouTube API, jQuery, AngularJS, Twitter Bootstrap and GitHub!
        </address>
      </div>
    </div>
</body></html>

run

またjs/app.jsに戻って、処理内容を確認します。

app.run(function(){
  var tag = document.createElement('script');
  tag.src = "http://www.youtube.com/iframe_api";
  var first_tag = document.getElementsByTagName('script')[0];
  first_tag.parentNode.insertBefore(tag, first_tag);
});

runって何ですか。。。?
StackOverflowさんによると、constantsやinstanceはrunブロックでしか注入できないらしい。

[参考]

breeze - AngularJS app.run() documentation? - Stack Overflow

Factory, Service, Providerについて

Service,Value,Factory,Provider ってなんぞ?そこについて前提知識がないと、以降のコードを理解するのは難しそうです。

先ず、サービスとは…
Angularサービスはシングルトンオブジェクト、またはWebアプリケーション共通の特定のタスクを実行する関数です。 Angularは、サーバにリクエストを送るブラウザのXMLHttpRequestオブジェクトにアクセスする$httpサービスのような、 いくつかの組み込みサービスを持ちます。 他のコアなAngular変数と識別子と同様に、組み込みのサービスは常に$から始まります。(前述した$httpのように) また、独自のカスタムサービスを作ることも可能です。
とのこと。 (http://js.studio-kingdom.com/angularjs/guide/understanding_services)
このサービスをDIに登録する方法が複数あり、大分とまどいました。

[参考]
AngularJs Service,Factory,Providerなどなど - senta.me/blog

DI

この際、ぶっちゃけると「DIとか何それおいしいの?」な人もたくさんいらっしゃる事でしょう。
僕もその1人です。こちらを読むと何となくわかった気になれます。


要するに DI って何なのという話 - 猫型の蓄音機は 1 分間に 45 回にゃあと鳴く


明日から、ドヤ顔で新人君に説明できますね。どや!

今日はここまで。また追記します。。。

Unicorn(Rackサーバ)+Nginx(リバースプロキシ)の構成が多用されている理由について、調べてみた話

使ってますか? Unicorn

Unicornは本番環境用に広く使われているRackサーバの一つです。位置づけとしてはMongrelやThinに近いものです。お仕事ではPhusion Passengerを使用していますが、今回はUnicornについて学んだ事をまとめてみました。

Unicornの特徴

  • 高速なクライアントからのレスポンスに対し、短時間の処理をすばやく返す
  • 運用コストが低い(Rainbows!やPumaと比較して枯れており、ノウハウが充実している)

ここでいう「高速なクライアント」とは、帯域をめいっぱい使えるクライアントの事です。
例えば、LAN上もしくは同一ホスト上のクライアントを指しています。
比較して、「低速なクライアント」とは、スマートフォンなど低速なネットワークを利用しているクライアントのことです。

Unicornアーキテクチャ

Unicornのmasterプロセスはリクエストを処理する為、設定に応じた数のworkerをforkします。
1つのworkerプロセスは、同時に複数リクエストを処理する事はできません。
そのため、低速なクライアントからのリクエストは、プロセスを長時間占有してしまうという問題が発生します。

Unicorn(Rackサーバ)+Nginx(リバースプロキシ)の構成が多用されている理由

先ほど書いた様に、低速なクライアントからのリクエストを単体で処理するのには不向きなアーキテクチャですが、Nginxをリバースプロキシとして用いる事でこの問題を解決できます。

具体的には、レスポンスデータが出来上がったら、クライアントへのデータ送信を(Unicornではなく)リバースプロキシから行います。
リバースプロキシにレスポンス送信を委譲する事によって、workerは別のリクエスト処理に移る事ができます。

これが、UnicornをRackサーバとして使用する際に、Nginxなどのリバースプロキシを併用する理由の一つだと思われます。このあたりについては、WEB+DB PRESS Vol.83|技術評論社が詳しいです。


【参考】
Everything You Need to Know About Unicorn
https://blog.engineyard.com/2010/everything-you-need-to-know-about-unicorn

サービス特性に合ったRackサーバを選ぼう
http://gihyo.jp/magazine/wdpress/archive/2014/vol83

「納品をなくせばうまくいく」を読みました

「納品」をなくせばうまくいく ソフトウェア業界の“常識

※旧ブログからの転載記事です。

購入のきっかけ
IT業界の課題、開発トレンドやエンジニアの働き方に対する問題意識から「納品のない受託開発」というビジネスモデルを提唱された倉貫さんの著書です。

受託開発専業のSIerから転身して、ここ2年ほど、Webサービス/モバイルアプリをコア事業とするチームで勤務しております。
倉貫氏は受託開発、私は自社サービス開発と、ややドメインは異なりますが、「開発、リリースして終わりではなく、リリース後も改善が必要となる」という意味では、参考にできる部分も多いのではないかと思い、購入しました。

メモ

覚えておきたい部分をいくつかメモしておきたいと思います。

・扱う技術を統一化する

例えば、WebアプリケーションのフレームワークRails、サーバはAWS上に構築する、と決定してしまいます。こうする事で、ノウハウの共有や社内での助け合いも容易になりそうです。

個々のエンジニアにとって考えてみると、「学習コストを下げる事により、より深いレベルに、より早く到達できる」為、メリットの大きい方法ではないでしょうか。なお、この視点は航空業界のLCCを参考にしたものだそうです。

・幅広いスキルを備えたエンジニアが要件定義から開発・運用まで兼務する

ソニックガーデンのエンジニアは顧客との対話、設計、プログラミング、サーバ運用まで全てをこなします。これにより伝言ゲームをなくし、無駄を最小限にします。オフショアやコストメリットの大きい新興国のエンジニアに対抗するヒントになる考え方だと思います。

今後、エンジニアとして生き残るには、ワンストップで要件定義から運用までこなせる、1人でやる事でコミュニケーションコストを最小化できる事がポイントになりそうです。

・自動化と合理化

機械に出来る事は機械にさせましょう、という基本を徹底されているようです。弊社でもまだまだ人力に頼る部分が多くあり、これからも業務改善を進めて行こうと気持ちを新たにしました。

ただ、「自動化による開発/運用工数の削減」というのはその効果が見えにくい場合もあり、どうしても「機能追加、新規プロダクトの開発」が優先されやすい傾向にあります。「業務改善のための工数」をどうやって確保するか、これは現在の僕にとっては非常に難しい問題です。このあたりは近道はなく、対話を繰り返す事、個々の取り組みによるビジネスインパクトを可視化していく事が重要だと考えています。

・「属人性の排除」より、「人を大事にする」

個人的に最も感銘を受けたのが、この部分です。1人のエンジニアが担当する領域が増えるほど、実は1人のエンジニアに対する依存度は高くなります。この点に関しては、細かいノウハウはあるにしても、マネジメントや技術的な対策よりも、長く一緒に働ける関係性を維持する方が健全だと述べられています。

エンジニアの退職リスクに対する対応って、小手先の方法論で語られる事が多くて、「そもそも人が簡単に辞めないようにしよう」という方向性で語られる事って少なかったように思います。こういう姿勢は大事ですね。。。

感想
このビジネスモデルを提唱するに至った背景が、ご自身の言葉で丁寧に描かれています。
倉貫さん自身がエンジニアでもあり、共感できる部分が多くありました。

【5分で学べる】Vagrant上にRailsをAnsibleでかんたんクッキング(CentOS6, MySQL, Rails4, Unicorn)

概要

VagrantとAnsibleでRailsの開発環境を構築したら便利すぎて鼻血吹きました。 ソースコードを公開しておりますので、ご自由にお使い下さい。

https://github.com/tjnet/vagrant_sakuravps_rails

最低限のシンプルな構成になっており、把握/カスタマイズしやすいと思います。 今後は、Production環境として用いるVPS(さくらVPS)の環境構築やデプロイも実装して自動化したいと考えています。

想定している読者様

・サーバ構築の自動化に取り組みたい小規模チームの開発者
・AnsibleとかVagrantとか使ったことないけど「5分で習得したい」人
・シンプルなVagrant+CentOS+Railsの開発環境を構築して、VPS/AWS上でも動かしたい人

※開発環境(vagrant)用のものであり、nginx, capistrano,production環境(さくらVPS)用のplaybookは未完成です。今後、実装予定です。 ※playbook_vagrant.ymlをコピーして少しカスタマイズすれば、さくらVPSAWSに転用できるはずです。

経緯

とある事情から、プロダクション環境をHerokuからVPSに移す事になりました。 その為の構築手順をブログやWikiにドキュメント化し、それをコピー&ペーストするのが前時代的でダルくなってきました。 ここでは手作業で行っていた内容をAnsibleで「InfraStructure as Code」にしていく過程をご紹介します。

なぜAnsibleを使うのか?

AnsibleではChefと異なり、構築サーバ側に何かをインストールする必要は、ほぼありません。 また、僕自身はChefやPuppetを使用した事はありませんが、動作がシンプルゆえに学習コストが低いと言われています。現時点でのオフィシャルドキュメントは十分に充実しており、シンプルな構成や小規模な構成での運用事例はググればすぐに見つかると思います。

検証環境

OS X 10.9上にてコマンドを実行し、Vagrant上で開発サーバを構築しています。

Virtual Box, Vagrant, Ansibleの導入

vagrantのインストールは、ここを確認して行って下さい。

最新版Ansibleのインストールは、次のようにHomebrewを利用します。

$ brew update
$ brew install ansible

詳細はこちらをご確認ください。

仮想マシン構築の為の準備

まずは、こんな感じで任意の場所にサブディレクトリを作って、そこに仮想サーバのひな形をcloneします。

mkdir -p VM/projects
cd VM/projects
git clone https://github.com/tjnet/vagrant_sakuravps_rails.git

ディレクトリ名が長過ぎてイケてないので、任意の名前に変更します。

mv vagrant_sakuravps_rails myapp
cd myapp

ここで、仮想マシンの設定を記述したVagrantfileを確認します。

Vagrant.configure("2") do |config|

  config.vm.define :web do |web_config|
    web_config.vm.box = "centos64"
    #web_config.vm.box_url = "http://files.vagrantup.com/precise64.box"
    web_config.vm.box_url = "http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.5-x86_64-v20140110.box"
    web_config.vm.network :private_network, ip: "33.33.33.33"
    web_config.vm.network :forwarded_port, guest: 3000, host: 8080

    web_config.vm.hostname = "develop-centos"

    web_config.vm.provider :virtualbox do |vb|
      vb.memory = 1024
    end

    config.vm.provision :ansible do |ansible|
      ansible.playbook = "provision/playbook_vagrant.yml"
      ansible.inventory_path = "provision/dev_hosts"
      ansible.limit = 'all'
      ansible.verbose = 'vvv'
    end
  end

end

主要部分について、少し補足します。

    web_config.vm.box_url = "http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.5-x86_64-v20140110.box"
    web_config.vm.network :private_network, ip: "33.33.33.33"
    web_config.vm.network :forwarded_port, guest: 3000, host: 8080

まず、この仮想マシンはCentOS6.5を使用します。ホストOS(Mac)のポート番号8080へのアクセスは、ゲストOS(VM上のCentOS)3000へ転送されます。

    config.vm.provision :ansible do |ansible|
      ansible.playbook = "provision/playbook_vagrant.yml"
      ansible.inventory_path = "provision/dev_hosts"
      ansible.limit = 'all'
      ansible.verbose = 'vvv'
    end

また、VagrantはAnsibleのPlaybookを用いて構成管理する機能を提供しており、provision/playbbok_vagrant.ymlを用いてサーバの構成管理を行う事がわかります。 ansible.verbose = 'vvv'は、Ansibleによるサーバ構築時に、デバッグログを詳細に出力する為の設定です。

Ansibleで構成管理するための対象ホストは、provision/dev_hostsに記載しています。 []付きでグループ名を記述することが可能です。グループを指定することで、複数のサーバを同時に構築する事が可能です。

[dev_server]
33.33.33.33

PlayBookについて

AnsibleのPlaybookは、構築するサーバの構成内容をYAML形式で記述したものです。 Chefでいうレシピにあたるものです。 見れば何となく理解できますが、インストールするパッケージや処理、設定をrolesでグルーピングして記述していくと、記述されたroles内の処理(task:)が実行されていきます。

ねっ、学習コスト低いでしょ? (^^)/

- name: setting rails to server
  hosts: dev_server
  user: vagrant
  sudo: yes
  vars:
    app_name: myapp
    environtment: vagrant
    mysql_port: 3306
    home: "/home/{{user}}"
    user: vagrant
    src_dir: '/usr/local/src'
    ruby_version: '2.1.2'

    rails:
      dir: /var/www/rails/
  roles:
    - common
    - mysql
    - ruby
    - rails
    #- nginx

各記述項目の意味は次の通りです。

項目 説明
hosts 対象のサーバグループ
user 対象サーバで実行するユーザ
sudo 対象サーバでsudoコマンドを使用して実行するか
roles 各taskを任意の名前でグルーピングしたものです。各roleの処理はroles/role_name/tasks/main.ymlに記述されています。

PlayBook:最低限のサーバ設定を行う

provision/roles/common/tasks/main.ymlで、下記の処理を行っています。
・ルートログインの禁止
・パスワード認証の禁止
・EPELを追加して、パッケージの種類を増やす
・パッケージのインストール(よくわからないパッケージはyum info package_nameで確認)

PlayBook:MySQLのインストールを行う

provision/roles/mysql/tasks/main.ymlで、下記の処理を行っています。
MySQLのインストール

PlayBook:Rubyのインストールを行う

provision/roles/ruby/tasks/main.ymlで、下記の処理を行っています。 ・Rubyのバージョン確認
Rubyソースコード入手
Rubyソースコードを解凍
Rubyソースコードコンパイル
Rubyをインストール
・gemをアップデート
・bundlerをインストール

PlayBook:Railsのインストールを行う

provision/roles/rails/tasks/main.ymlで、下記の処理を行っています。
Railsアプリケーションを作成する為のDirを作成
・/etc/resolv.confに追記(これをしないとRailsのインストールが異様に遅くなります)
※詳細はVagrant+VirtualBox(CentOS6)で「gem install rails」がすっごい遅い時の対処法とか、 Slow networking (due to IPv6?) on CentOS 6.x #1172をご確認ください。
JavaScriptのランタイムがないと、おこられるのでNode.jsを導入

いよいよ実行

ここまでPlayBookの構成について書いてみました。では実際にVagrant上でサーバ構築を行ってみましょう。

vagrant up 
vagrant provision web
vagrant upでvagrantを起動します。このコマンドで、VagrantはCentOS6のboxをダウンロードする為、初回は少し時間がかかるかもしれません。 。お茶でもすすってお待ちください^^。 ちなみに、ここで下記のエラーが出てるかもしれませんが、Railsの導入や開発作業自体には支障はありません。
Failed to mount folders in Linux guest. This is usually because
the "vboxsf" file system is not available. Please verify that
the guest additions are properly installed in the guest and
can work properly. The command attempted was:

mount -t vboxsf -o uid=`id -u vagrant`,gid=`getent group vagrant | cut -d: -f3` vagrant /vagrant
mount -t vboxsf -o uid=`id -u vagrant`,gid=`id -g vagrant` vagrant /vagrant
vagrant provision webで、ゲストOS上のサーバ構築を行います。 Node.jsの導入には、少し時間がかかるかもしれません。 このVMには、
vagrant ssh
でアクセスできます。 その後は、 ・パーミッションの調整
rails new でwebアプリのベースを作成
MySQLを起動
・必要なポートをあける
iptablesの再起動
http://localhost:8080にアクセスする

と、順次設定すると、いつものRailsのトップページが表示されます。Congratulations! 詳細はREADMEをご確認ください。

TODO(今後やりたい事)

Capistranoでのデプロイ
Unicornの導入
・Nginxの導入
・プロダクション環境(さくらVPS)用のPlaybookを用意する
・厳密な冪等性を保つにはどうしたら良いのか、勉強する
・DockerとかImmutable Infrastractureに触れてみる

HerokuにDeploy Hooks を導入してデプロイ通知をHipChatで受け取る

※旧ブログからの転載記事です。

Deploy Hooksを導入する事により、Herokuへのデプロイ通知を受け取る事が可能になります。実際には、メール、BaseCamp、任意のHTTPエンドポイント等、いくつかの通知先を設定できます。

通知手順は、HipChatの公式ドキュメントが最も参考になりました。手順は次の通りです。

まず、ターミナルでアプリケーションのルートディレクトリへ移動します。

ルームIDとauth_tokenを入力します。


heroku addons:add deployhooks:http \
url="https://api.hipchat.com/v1/webhooks/heroku?auth_token=<token>&room_id=<room>"

<token>は.ご自分のAPI auth_tokenで置き換えて下さい。

<room>はメッセージを送信したい部屋のnameもしくはIDで置き換えて下さい。

API

あとは、デプロイしてHipChatのroomを確認してテストを行ってみて下さい。

[参考]

http://help.hipchat.com/knowledgebase/articles/64392-heroku-integration

RubyでTopCoderの問題を解いてみる(幅優先探索)

TopCoder SRM453.5 Div2 Level2を解いてみました。

※旧ブログからの転載記事です。

幅優先探索をはじめて実装しました。長くて見通しがよくない気がする。。。リファクタが必要。

[問題要約]
友人のjimに迷路を解かせたいと思っています。Jimの動き方は、move_row, move_colで指定されています。
障害物は'X'で表し、通行できる場所は'.'で表します。
出口をなるべくスタート地点から遠いところに置いて、出口に到達するまでのステップ数を返せ。
到達できない時は-1を返せ。


# coding: utf-8
# SRM453.5 Div2 Level2
# 幅優先探索
# 迷路を解いたり、最短経路のステップ数を知ることもできるアルゴリズム
# step1:ある出発点から1つ進んだ経路をすべて求める
# step2:これらの経路から1つ進んだ経路をすべて求める
# setp3:上記の作業をゴールに至るまで繰り返す

require 'pp'
class MazeMaker

def longest_path(maze, start_row, start_col, move_row, move_col)
max = 0 # 最大ステップ数
width = maze[0].size # 幅
height = maze.size # 高さ

board = Array.new(height).map{Array.new(width, -1)} # 盤面(すべての経路を初期化)

# スタート地点を選ぶ
#boardのスタート地点には0と書いておく、次の地点には1, その次の地点には2,...と順次増やす
board[start_row][start_col] = 0

# queueを用意する
queue_x = Array.new
queue_y = Array.new

queue_x.push(start_col)
queue_y.push(start_row)

while queue_x.size > 0 do
# 1つの地点を取り出す
x = queue_x.pop
y = queue_y.pop

# 選んだ地点から1つずつ進んだ経路を調べる
for i in 0..(move_row.size-1) do
next_x = x + move_col[i]
next_y = y + move_row[i]

# 以下の条件なら探索を続ける(queueに格納する)
# 盤面上にある場所
# まだ行ったことがない
# 通行可能な場所
#pp "next_x = #{next_x}, next_y = #{next_y}, x=#{x}, y=#{y}" # debug

next unless (0 <= next_x && next_x < width && 0 <= next_y && next_y < height)
next unless board[next_y][next_x] == -1
next unless maze[next_y][next_x] == '.'

board[next_y][next_x] = board[y][x] + 1 #移動先は、現在の地点から1加算した値を書いておく

queue_x.push(next_x)
queue_y.push(next_y)

end

end # end while

# 一通り、盤面を眺めて、まだ行ってない地点があるとしたら、それは到達できない地点だと言う事
for i in 0..(height-1) do
for j in 0 ..(width-1) do
if maze[i][j] == '.' && board[i][j] == -1 #到達していない点があった
return -1
else
max = [max, board[i][j]].max
end
end
end
return max

end
end


#
#== example 0
#
maze = [
['.', '.', '.'],
['.', '.', '.'],
['.', '.', '.']
]
start_row = 0
start_col = 1
move_row = [0,0,-1,1]
move_col = [-1,1,0,0]

ret = MazeMaker.new.longest_path(maze, start_row, start_col, move_row, move_col)
pp "correct!" if ret == 3

#
#== example 2
#
#
maze = [
['X','.','X'],
['.','.','.'],
['X','X','X'],
['X','.','X'],
]

start_row = 0
start_col = 1
move_row = [1,0,-1,0]
move_col = [0,1,0,-1]

ret = MazeMaker.new.longest_path(maze, start_row, start_col, move_row, move_col)
pp "correct!" if ret == -1

Herokuを用いて、ひとりでWeb APIを構築した時にやった事、やるべき事

※旧ブログからの転載記事です。

まえがき
最近、個人でもWebAPIと連携するiPhoneアプリの開発を進めており、サーバサイドではHerokuを利用していました(現在は、さくらVPSに移行しています)。これから、個人開発や小規模チームでの開発を始めようと思っている方に、必要となりそうな情報や、
記事をまとめてみました。

まずHerokuで公開してみる

プロトタイプでも何でも、まずは数時間程度で、サクッと公開できないとモチベーションが上がりません。
環境構築で悩みすぎると、Herokuのメリットも感じられません。
とりあえず、この2つの記事とgoogle先生を駆使して、サンプルアプリをherokuで公開するところまでやってみると、取っつきやすいのではないでしょうか。

HerokuでWebアプリ開発を始めるなら知っておきたい10のこと
紹介されているリンクがすばらしいです。Herokuに少し触れ始めた時に、読んで、非常に為になりました。

Ruby on Rails 4 でアプリを新規作成してherokuで公開するまで
Railsにある程度、触れている方なら、問題なく環境構築できるかと思います。

Getting Started With Heroku
Herokuの公式チュートリアルです。運用上、必要なHerokuの独自コマンドなども記載されています。一通り目を通しておくと良いと思います。
heroku login, heroku open, などなど。。。

個人開発でHerokuを使うメリット
 インフラやミドルウェアの導入にかかる経費(というより時間)が圧倒的に削減できる事がメリットになると思います。
個人的には、ソースから色々コンパイルしてミドルウェアを導入してみたりする事も、勉強にはなると思います。
そういう勉強も必要だとは思います。
 ただ、やってみるとわかりますが、ちょっとした環境構築だけでも、ハマると半日くらい費やしてしまいます。こうなると、個人開発のスピード感は大きく失わます。
 このような背景があり、優秀なインフラエンジニアをひとり雇うような感覚で、Herokuの採用に踏み切りました。また、社内では制約が大きくて触れられないような技術にも、いち早く触れる事ができるため、「その技術で何が出来るのか」考えるきっかけになるように思います。

ソースコードの公開について
 基本的には、サーバサイドのソースコードや、サーバと連携するようなネイティブアプリのソースコードGitHubで公開しないようにしています。
configファイルなど、おいそれと公開できないものもあるためです。
 機密情報のみ、非公開にしてGitHubに公開するような手法も色々とあるようですが、そういった作業に慣れていない為、現在はこのようなスタンスで開発を進めています。

無料で、どこまでの機能を使用できるのか
 基本的には、個人開発で小規模なWebアプリケーションを作成して、ひとつのdynoで動かす分には、ほぼ無料で使用できます(2014年8月現在)。
ただ、スケジューラによるバッチ処理や本番環境でのコマンド実行を行いまくっていると、知らず知らずのうちに課金される事もあるようです。
このあたりは注意が必要です。

学習コスト
Dyno, Slugなどの用語はHeroku独自のものだとおもいますが、それ以外のものは、Heroku独自の用語や技術というものは、あまりないように思います。ですから、別のフレームワークやプラットフォームでお仕事をする際にも、勉強した事は無駄にはならないと思います。

優良アドオン
未使用のものも含まれます。良さげなものをリストアップしてみました。

Deploy Hooks
これは指定したサービスのデプロイ時に通知を行うものです。通知方法はHipChat, IRC, Email, HTTP Postなどが選べるようです。

New Relic
モニタリング、トラブルシュート、チューニングなどに用います。

Logentries
ログをWeb上でグラフィカルにエラー表示をしたり、検索機能を提供してくれるようです。

https://addons.heroku.com/scheduler
事前登録した処理を指定した時間帯に実行させるためのスケジューラです。バッチ処理の実行に用いています。

BitbucketとWerckerを用いた継続的インテグレーション
Bitbucketと連携できて、しかも無料、セットアップが簡単なCIサービスを探していたところ、こちらの記事にたどり着きました。手順もわかりやすく記載されているので、おすすめです。

Githubのプライベートリポジトリでも無料で使えるCI、Werckerを使ってrails newからHerokuのデプロイまでやってみる


今後、やっていきたい事
独自ドメイン
Memcachedの使用
・ステージング環境の構築