不格好エンジニア

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

【怠惰な】Swiftを学ぶにあたって最適なオープンソース【Objective-C経験者向け】

筆者のスペック

業務やプライベートプロジェクトでObjective-C を1年半ほど、経験しました。最近、サンプルコードやライブラリもSwiftを用いたものが増えて来て、少し焦っています。Swift勉強しなくちゃ。

困っていること

チュートリアルをチマチマ進めていくのが性に合わない

Railsチュートリアルは非常によく出来ていて、一連のストーリーが有ったので、進めていくのは苦痛ではありませんでした。ただ、Swiftに関しては、既にObjective-Cを経験したエンジニアであれば、いきなり小規模なアプリのソースコードをリファレンス引きながら読み進めて行く方が理解が早そうですね。

共有されているサンプルアプリが色々動かなくなっている

Swiftが発表され話題を集めた頃、ギークな方達が、たくさんのサンプルアプリをGitHubに晒して下さいました。ところが、これらのGitHubで共有されているコードがXcode6.1で動作しない事がよくあります。

学習に適したアプリ

とりあえず、いくつか試してみて、筆者の環境(Xcode6.1)で動作確認できたソースはこちら。


amitburst/HackerNews · GitHub

HackerNewsは、Objective-Cの教材としてもお世話になったプロダクトです。

Newsアプリは、内部動作も把握しやすいと思います。業務に応用できる部分も多いかと思いますので、こちらを読み進めていきます。

ちょっと読んでみた


AppDelegate.swiftに目を通してみます。

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    // MARK: Properties
    
    var window: UIWindow?

    // MARK: UIApplicationDelegate
    
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
        return true
    }

}

@UIApplicationMainの@は意味がわかりませんが、いったん後回しにしても良さそうです。

    var window: UIWindow?

この部分はプロパティ宣言ですね。後ろについている?は、nilを許容するかどうかを明示的に示しているそうです。このあたり、後で調べておきます。

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
        return true
    }

この部分は、下のObjective-Cソースコードと比較すれば、何となく理解できますね。問題なさそう。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    return YES;
}

AppDelegateは、何となくわかりました。
この調子で、読み進めていけそうです。

【あの有名サービスが】コードリーディングで学ぶ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