kentana20 技忘録

技術ネタを中心に、セミナー、勉強会、書籍、会社での出来事を綴っていきます。不定期更新。

Railsでpaperclipを使ってリサイズしつつS3へ画像をアップロードする

RubyonRailsアプリで、画像アップロード機能を作るにあたって、paperclipを試すことにしたので、技忘録。

想定するユースケース

  • エンドユーザ向け画面/管理画面で画像アップロード機能を作りたい
  • アップロードした画像はいくつかのサイズにリサイズして使うことを想定する
  • 画像はWebサーバ内部ではなく、オンラインストレージを使用して保存する
  • アップした画像はWebアプリから参照したい
  • アップロードはWebアプリからのみ許可したい
  • なるべくシンプルに作りたい

構成

これらをOSX Yosemite上で実行し、オンラインストレージにはAmazon S3を使います。

前提

  • RubyRailsは既にインストール済みであること
  • 何らかのRailsアプリが動く状態にあること
  • 画像を追加するモデル"Hoge"は既に定義されていること
  • AWSアカウントが既にあること

事前準備

画像のリサイズにはimagemagickを使うのでbrewでインストール

$ brew install imagemagick

:warning: CentOSとかならyumでインストールしてください

  • Gem追加

paperclipとaws-sdk, rmagickのGemを追加して

gem 'paperclip'
gem 'aws-sdk'
gem 'rmagick'

bundle install します

$ bundle install

Gemfile.lockが更新されます。

Model

  • Modelに画像保存用の設定を追加する
class Hoge < ActiveRecord::Base
  has_attached_file :image,
                    :styles => {
                        :thumb  => "90x60",
                        :medium => "180x120",
                        :large => "600x400"
                    },
                    :storage => :s3,
                    :s3_permissions => :private,
                    :s3_credentials => "#{Rails.root}/config/s3.yml",
                    :path => ":attachment/:id/:style.:extension"

  validates_attachment_content_type :image, :content_type => ["image/jpg", "image/jpeg", "image/png"]

  def authenticated_image_url(style)
    image.s3_object(style).url_for(:read, :secure => true)
  end

end
  • Migrationを作る
$ rails g migration AddAttachmentImageToHoges
  • migrationファイルの中身はこんな感じ
class AddAttachmentImageToHoges < ActiveRecord::Migration
  def change
    change_table :hoges do |t|
      t.has_attached_file :image
    end

    drop_attached_file :hoges, :image
  end
end
  • Migrate
$ rake db:migrate

これで

  • image_file_name
  • image_content_type
  • image_file_size
  • image_updated_at

の4つのカラムがDBに追加されます。

Controller, View

次にControllerとViewを作っていきます。

$ rails g controller hoges index show new edit

Controller

class HogesController < ApplicationController
  before_action :set_hoge, only: [:show, :edit]

  def index
    @hoges = Hoge.all
  end

  def new
    @hoge = Hoge.new
  end

  def show
  end

  def edit
  end

  # とりあえず、createだけ追加
  def create
    @hoge = Hoge.new(hoge_params)

    respond_to do |format|
      if @hoge.save
        format.html { redirect_to @hoge, notice: 'Hoge was successfully created.' }
        format.json { render action: 'show', status: :created, location: @hoge }
      else
        format.html { render action: 'new' }
        format.json { render json: @hoge.errors, status: :unprocessable_entity }
      end
    end
  end

  private
    def set_hoge
      @hoge = Hoge.find(params[:id])
    end

    def hoge_params
      params.require(:hoge).permit(:name, :image)
    end

end

View

  • 新規登録画面
  <div class="field">
    <%= f.label :name %>
    <%= f.text_field :name %><br>
    <%= f.label :image %>
    <%= f.file_field :image %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
  • 一覧画面
    <% @hoges.each do |hoge| %>
      <tr>
        <td><%= hoge.id %></td>
        <td><%= image_tag(hoge.authenticated_image_url(:thumb)) if hoge.image.present? %></td>
        <td><%= link_to 'show', hoge %></td>
        <td><%= link_to 'edit', edit_hoge_path(hoge) %></td>
        <td><%= link_to 'destroy', hoge, method: :delete, data: { confirm: 'are you sure?' } %></td>
      </tr>
    <% end %>
  • 部分テンプレート
  <%= @hoge.name %>
  <%= image_tag(@hoge.authenticated_image_url(:large)) if @hoge.image.present? %>

Routes

hogesへのrouteを追加します。

  resources :hoges

S3

Bucketを作る

AWSのManagementConsole上でS3のBucketを作ります。

f:id:kentana20:20141202230827j:plain

f:id:kentana20:20141202230840j:plain

接続情報

  • config/s3.yml として、S3への接続情報を記述します
bucket: 'bucket_name'
access_key_id: '12345678'
secret_access_key: '12345678901234567890'
s3_host_name: 's3-ap-northeast-1.amazonaws.com' # tokyoリージョンの場合

これで準備は完了です。

実行

Railsアプリを動かして /hoges/ へアクセスして、"new hoge"リンクを押下して画像データを追加します。

CreateしたらAWSのコンソール上でS3のBucketの中身を確認します。

f:id:kentana20:20141202230910j:plain

所感

  • paperclip便利
    • めちゃ便利です
    • S3、ImageMagickとの連携が非常に手軽に行えます
  • 静的コンテンツをWebサーバに置かなくていいのはラク
    • 静的コンテンツをWebサーバ内に置かなくていいのは非常に良いですね
    • S3ならそのままCloudFrontでCDN対応もできます
    • Webサーバをスケールアウトしても静的コンテンツはそのままで良いし、運用もラクです
  • 画像ファイルのURL生成をRails側で処理することについては...
    • 一方、画像URL生成をRailsで処理させることになるので、ここはフラグメントキャッシュを使って、キャッシュした方が良い部分かもしれません
  • CarrierWave
    • 対抗馬と目される、Carrierwaveも試してみようと思います

参考

Python mini Hack-a-thon #49に行ってきた

先日、友人の @shinyorke さんに飲み会でお誘いいただいて、Python mini Hack-a-thon #49に行ってきたのでレポート。

f:id:kentana20:20141221200407p:plain

  • 日時: 2014.12.20 11:00~19:00 (実際の作業時間は11:30~17:30くらい)
  • 場所: ビープラウドさん(新宿)

ビープラウドさんはPythonの受託開発や、イベントサイトのconnpassでもお馴染みの会社さんですね。


注: 生まれてはじめてPythonを触ったのでいろいろ誤りがあるかもです

環境構築

OSXPython環境を整える

brewpythonをインストール

brewで入れるので以下を実行

$ brew update
$ brew upgrade
$ brew install fontforge --use-gcc --without-python
$ brew install python

brew upgradeFontForgeとXQuartzのエラーが出たのでXQuartzをインストールして、brew install fontforge --use-gcc --without-pythonして、リトライして成功

パッケージ管理pipのインストール

インストールしたらPATHを追加

# python
export PATH=/usr/local/bin:$PATH
export PATH=/usr/local/share/python:$PATH

pipのインストール

$ curl -kL https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python

とりあえず、ここまででお昼へ。

virtualenvのインストール

Pythonの実行環境を仮想的に作れて、「ローカルマシンを汚さなくて済むので、学習ならこれ良いのでは」とPythonistaの方に教わったので、pipでインストール。

$ pip install virtualenv

Flask

Rubyで言うところのSinatra的存在と聞いたので、Flaskを使ってチュートリアルしながら、カンタンなWebアプリを作る。

仮想環境

virtualenvを使って仮想環境を作る

$ cd hogehoge # 適当な作業ディレクトリに移動
$ virtualenv env
$ source env/bin/activate

これで、カレントディレクトリに env というディレクトリが作られて、シェル上に (env) という「仮想環境で動いてるよ」的なサインが出る

Screen Shot 2014-12-20 at 2.55.26 PM.png

Flaskチュートリアル

  • まずはHello, world
  • カンタンなDBアクセス
  • www.ikyu.com のコンテンツをスクレイピングして表示するアプリ

といった感じでやってみる。

Hello, world Flask

まずはFlaskのインストール

(env)$ pip install flask
........ # ログがゲロゲロ出て、flaskがインストールされる
(env)$ touch hello.py
(env)$ vim hello.py

hello.pyには下記のように書く

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return "hello world"

if __name__ == '__main__':
    app.run()
  • flaskをインポート
  • appインスタンス作る
  • route書く
  • このモジュールがmainで実行された場合の if __name__ == '__main__': にはappインスタンスをローカルサーバ(デフォルトは localhost:5000)で実行する

という感じです。

書いたら、実行して、ブラウザで http://localhost:5000/ を開いて、hello worldが表示されれば :ok: です

(env)$ python hello.py

* Running on http://127.0.0.1:5000/
127.0.0.1 - - [20/Dec/2014 14:29:44] "GET / HTTP/1.1" 200 -

Tutorial

Flaskチュートリアルをひたすらやるだけ。

ひと通り書いたら、動かしてみてentriesにデータができてるトコを確認。

(env)$ python flaskr.py

* Running on http://127.0.0.1:5000/
* Restarting with reloader
127.0.0.1 - - [20/Dec/2014 16:14:19] "GET /logout HTTP/1.1" 302 -
127.0.0.1 - - [20/Dec/2014 16:14:19] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2014 16:14:20] "GET /login HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2014 16:14:31] "POST /login HTTP/1.1" 302 -
127.0.0.1 - - [20/Dec/2014 16:14:31] "GET / HTTP/1.1" 200 -

Screen Shot 2014-12-20 at 4.14.21 PM.png

起動出来てます

Screen Shot 2014-12-20 at 4.14.37 PM.png

Webからデータの登録も出来ていて

hoge.jpg

SQLite側からも確認できています。

ここまでで夕方16:20..あと1hちょい。時間がねー!!

BeautifulSoup

install

pipでインストール

(env)$ pip install beautifulsoup

(トレーニング) 一休.comのタグをいくつかとってFlaskアプリで表示する

def show_ikyu():
    url = 'http://www.ikyu.com/'
    html = urllib.urlopen(url).read()
    soup = BeautifulSoup(html)
    return "title: " + soup.find('h1').string

美女をスクレイピングする

いつぞやの開発合宿でNode.jsでチャレンジしたときと同じく、美女暦から美女をスクレイピングしようと試みるもここで無念のタイムアップ。美女を取得できず。。5時間くらいの作業時間で

までを実施。もうちょいやりたかった...


所感

  • 新しい言語楽しい
    • 書き方戸惑うとこあるけど、まぁなんとか読める
  • virtualenv便利
    • ちょっと試す程度なら、virtualenv便利ですね @shinyorke ありがとうございます
  • pip
    • 最近の言語では当たり前になりましたが、エコシステムが発達していてカンタンにモジュールが追加できるのは便利
  • Framework
    • 定番ものがよくわからないので、ちょっと戸惑った
    • やはり Django ..? 
      • 成果発表でプレゼンしたときに、このメモを見せたら「いや、Pyramidですね( ー`дー´)キリッ」と被せ気味にアドバイスいただいたので、Pyramidにもチャレンジしたいなと思いますw
  • Flask
    • SinatraライクでカンタンにWebアプリ作れる
    • 便利なんだが、Latest release(v0.10.1)が2013/06で止まってるのがちょっと気になる
  • BeautifulSoup
    • 時間足らなかったので、ノーコメント
  • スキル不足と準備不足
    • 環境くらいは作ってこようと思っていたが、結局来てから作った
    • 時間かかりすぎ..orz
  • 次回も参加したい
    • ほぼ月イチで定期開催らしいので、次回も参加して続きをやろうと思います
    • 最後に全員成果を発表するのはプレッシャーにもなるので、いい感じ
    • 思ったより、カジュアルな雰囲気で、Pythonガリガリ書いてるエンジニアから自分のようなPythonビギナーまで、気軽に楽しめる感じでオススメです

参考

環境構築

Webフレームワーク

BeautifulSoup

越境を続ける

この記事はDevLOVE Advent Calendar 2014 「越境」の14日目の記事です。

まずは、このようなイベントを企画してくださったDevLOVEの皆様に御礼申し上げます。DevLOVEのAdventCalendarには去年に「モダンな現場にするために」というタイトルで参加したので、2回目となります。

自己紹介

まずは自己紹介です。はじめまして、 @kentana20 と申します。ソーシャル上やGithubではだいたい @kentana20 でやってます。株式会社一休という会社でエンジニアをしていて、早いもので、もう8年以上会社に在籍しています。入社してから長く、一休.comという宿泊予約サイトの開発・運用・保守を続けてきましたが、今年の春からサービス開発からは一歩引いて、「開発業務改善プロジェクト」を立ち上げて、開発現場を改善する毎日を過ごしています。

自分にとっての越境

さっそくですが、越境の話をします。

  • 今までとは異なる役割にチャレンジする
  • 新しいプロジェクトにチャレンジする
  • 新しいジョブにチャレンジする

などなど、カタチは人によって様々だと思いますが、自分が「越境」という言葉を聞いて思い浮かぶことは「それまでの自分の世界を飛び越えること」です。いまの自分に満足せずに、より高みを目指す過程のなかで、今までに経験したことがない部分にチャレンジをすることが「越境」だと思っています。

越境の連続だった2014年

自分にとって、今年はまさに「越境」の年だったと思っています。特に、以前のブログで書いた、開発業務改善プロジェクトが始まってからの半年で社内でも社外でもイロイロな越境がありましたので、少し紹介させていただきます。

  • 開発業務改善プロジェクトが進み始めた

今年の象徴的な出来事です。id:naoya という心強いアドバイザーとともに一休の現場を改善しています。まだまだ課題は多いですが、チャレンジを続けて、強くて楽しい現場にしていきたいと考えています。そのために、自分も努力を続けて力を付けなくては、と日々感じながら毎日を過ごしています。

  • DevLOVEで登壇した

DevLOVE主催で8月に行われたDevLOVE現場甲子園東日本大会というイベントで登壇する機会をいただき、一休で実施している現場改善のお話をさせていただきました(発表資料はこちら)。はてなブックマークにもホットエントリし、本当に多くの方に見ていただけたみたいで、その後、勉強会やイベントで「見ました〜」とお声がけいただいたり、それがきっかけで交流が深まった社外のエンジニアもいるので、登壇して本当によかったと思っています。

12/6(土)に開催されるDevLOVE現場甲子園日本シリーズにも登壇が決まっていて、8月のお話のリバイバル+αというカタチでお話をさせていただきますので、ご興味のある方はぜひお越しくださいませ!

  • 社外でもスタートアップサービスに関わった

社外の活動として、スタートアップサービスに関わる機会をいただいて、土日でお手伝いみたいなことをしています。1人でやるより、勉強になることも多くて、週末もとても充実しています。

これから越境しようとしていること

DevLOVEだけでなく、他のイベントにも主体的に関わっていきたいと思っています。

  • CROSS2015

@niftyとJAIPAの共催で来年1月に開催されるCROSS2015というイベントにスタッフ兼セッション登壇者というカタチで関われないかと思い、実行委員長の山口さんにアタックして、スタッフとして参加させていただくことになりました。セッションについてはまだ調整中なので、なんとも言えませんが開催できるように動いていきたいと思っています。

翔泳社主催で来年2月に目黒雅叙園で開催されるDevelopers Summit(通称デブサミ)で一休での取組みについてお話させていただきたいと考えて、公募セッションに申込みました。応募倍率が高そうなので、どうなるかはわかりませんが、「当選しますように」という願いを込めて書いておこうと思います。当たりますように!

  • ほか

詳しくは書けないのですが、これ以外にも越境しようとしていることはたくさんあります。

越境はサイコーに楽しい

自分は結構飽きっぽい性格なので、飽きることなく、チャレンジし続ける「越境」はサイコーに楽しいです。もちろん、うまくいくことばかりではなく、課題や問題がバンバン起こります。「なんだよ、もう」と思うこともたくさんありましたが、すべてがうまくいっていたらこんなに充実した時間は過ごせていないと思うので、これからもハードルがあるものにどんどんトライして越境を続けていきたいと思っています。

思いがあれば誰でも越境できる

取り留めのない話をいろいろ書いてきましたが、最後は単純に。自分は「思いが越境につながる」と思っています。現場での不満や自分の欲求、理想とする未来とのギャップなど、思いを強くもつことができれば、誰でも越境できると思います。現に自分はちょうど1年前の今頃に「 id:naoya と一休の現場を良くしよう」と考えて、思いを伝えた結果、プロジェクトがスタートして越境が始まりました。 きっかけはホントにちょっとしたことだと思うので、ぜひ「いま、何をすべきなのか」「何をしたいのか」など、自分の思いをふりかえって次の越境へ向かってほしいと思います。

次回

次回は11/22(土)にDevLOVEでとってもお世話になっている、新井 剛さんとなります。新井さん、よろしくお願いします!!

ISUCON本戦に出場して、惨敗してきた

11/8(土)に渋谷のLINEで開催された、ISUCONの本戦に出場してきました。

まず、とっても楽しいイベントを開催してくださった運営のLINE、Cookpad、テコラスさんに御礼申し上げます。ホントに楽しい1日でした。ありがとうございました!

めっっっっっっっっっっっっっっっっっっっっっっっっっっっっっっっっっっっちゃ、悔しい思いをしたので、この悔しさを忘れずに来年へ向けて気を引き締めるために、技忘録。

結果

まず、結果から。30チーム中、29位と惨敗しました。failしてないチームの中ではダンラスです。

f:id:kentana20:20141112005853j:plain

優勝チームの61万点から比べたら、う◯こみたいなもんですね。

事前準備

本戦のレポートの前に、事前にやった準備について少し触れておきます。予選が終わってから チームメートである @_taketake と @zimathon の3人でふりかえりを行い、以下の点について準備をしようと話していました。

  • 前日飲みに行かない
  • 事前に役割分担決める
  • 前日にある程度タイムスケジュールを作って共有しておく
  • レギュレーションを熟読する
  • 短い単位で共有しながら都度軌道修正して進める
  • ルールをしっかりと確認する
  • ファシリテーター決める
  • 挑戦する言語を絞っておく
  • ベンチの特性を早めに知る
  • failを恐れない大胆な判断を本戦でもする
  • ボトルネックが何かをしっかり確認する
  • キリの良いタイミングで再起動する
  • 予選ど同様のフォーメーション(に近い形)で作業する
  • 2013の本戦と今回の予選まとめを読みながら、手を動かして復習する
  • 計測ツールとか計測の仕方とか決める(環境次第)
  • 構成を把握する時間をはじめに取って、その後共有して役割分担する
  • 今回の予選の他チームのやったことを情報収集する

くらいのTryが上がっていたのですが、事前に準備する時間がほとんど取れず、結果的にできたのは

  • 前日飲みに行かない
  • 今回の予選の他チームのやったことを情報収集する
  • ルールをしっかりと確認する

くらいで、ほぼ達成できませんでした。この時点で悔しいです!!

当日・開始まで

モニタを持ち込む必要があったのと、事前準備ができていない自覚はあったので、当日朝8:30に会社に集合して、持ち物を準備しつつ作戦会議をしました。直前すぎますね。

そして、集合時間の10:00に間に合うように会社を出て渋谷ヒカリエへモニタを持って移動。移動中にDSub-Thunderboltのケーブルを忘れるという失態を犯し、開店直後のビックカメラへ突撃して調達してから会場であるLINEのカフェテリアへ入りました。ここからして既に平常心ではなかったかもしれないです。

本戦のシステム、サーバ構成

  • 動画広告配信システム
  • サーバは3台構成でどう使っても良い。使わなくても良い。
  • ISUCONで強いと言われているオンメモリDB(Redis)を既に使っていて、RDBは使っていない
  • サーバはAWSのt2.micro相当で結構貧弱
  • 予選同様にRubyを選択

といった感じ。

午前

競技開始の11:00からお昼の13:00までの間は

  • まずは構成を把握する
  • レギュレーションを把握する
  • マニュアルを精読する
  • サーバのスペックを確認する
  • 初期実装でベンチを動かしてスコアを見る
  • OS設定、ミドルウェアの設定を確認する

といった足回り的な部分を見つつ、サクッとできる対応をやろうと話していました。ただ、出題側の @mirakui さんはじめ、参加者の皆さんもブログで書かれている通り、用意されたサーバ3台はすべてメモリが1GBしかなくて、且つデータがRDBではなく、オンメモリDBであるRedisに載っていて、更に5MBくらいある動画データもRedisに入るという初期実装だったので、ベンチマーカーが走るとメモリがスワップしてしまい、繰り返しベンチを実行するとスコアが安定しないというトラップがあって「初期実装でベンチを動かす」という部分でスコアが安定せずに「う〜ん。。。。。」となってしまいました。

OSのファイルディスクリプタの設定や、ユーザポートの設定なども予選ではできていましたが、うまくできませんでした。ちょっとキンチョーしてたのかもしれないです。

午後

弁当食べながらも「う〜〜〜〜〜ん。。。」は続いていて、これを安定させないと、対応方針が決まらないなぁ、と考えながらもちょっとずつコードを見て手を入れていこうと思い、 @_taketake とベンチを安定させるアクションをしつつ、 @zimathon とコードを見るみたいな状態で中盤は過ごしていました。

予選ではしっかりコミュニケーションをとりつつ、対応策をバーっと出した上で「じゃあ、これ俺やります」とかって話しつつ、施策を試しつつ、1箇所でベンチを走らせるみたいなアクションができていたのですが、本戦ではほぼコミュニケーションはとらずに各自悩む、みたいな感じになってしまって、チーム競技のアンチパターンみたいな状態でした。

直前会議では自分がファシリテーターをすることになっていたので、これは完全に自分に責任があります。チームメートには本当に申し訳ないと思っていますし、反省点です。

結局、手を入れはじめたのは17:30頃

結局、ベンチは安定しなかったので

  • Redisに乗っている動画データを別箇所に配置
  • ファイルI/Oで管理していたログファイルをRedisかメモリに載せる

といった施策をコードに入れはじめたのが夕方以降になってしまい、その頃にちょうど準優勝チームの「33万点」がランキングに現れ、自分たちがうまく行っていなかったこともあって、ちょっと「もう、厳しいなぁ」と思ってしまい、攻めきることができませんでした。

懇親会で @tagomoris さんに話を聞いたら、「あの33万点が出たことで、ネットワーク帯域を使わずにスコアを伸ばす方法があるはず」と確信して、秘孔探しを最後まで諦めなかったってお話を聞いて、さすがはV2を達成するチームのメンバーだと思いました。

結局、ベンチの実行オプションもベストの指定ができずに、初期実装を下回る超低空飛行なスコアで終戦しました。

まとめ

  • 経験のないシステムをしっかり把握することは難しい

「動画広告配信システム」と聞いて、「動画!?」とか「広告!?」という印象を抱いてしまいましたが、本戦が終わってから落ち着いて考えてみると、なんのことはない、管理画面からの広告入稿と、動画≒静的コンテンツを配信するだけのシステムでした。

  • 本戦独特の雰囲気に飲まれてしまった

予選はオンラインだったので、会社でいつも仕事してる感覚で対応できたので、いつもどおりの平常心で作業ができましたが、本戦はアウェーゲームという感覚で、ちょっとキンチョーしてました。これは本戦に1度でも参加しないとわからないことだと思います。

  • 低スペックのサーバとベンチのスコアに惑わされた

自分の中では、これが一番キツかったです。メモリスワップによって、ベンチが安定せず、「う〜〜〜〜ん。。。」を繰り返してしまいました。先にできることがあったのに、ムダな努力で時間を費やしてしまって、本当に悔しいです。

  • 準備大事

アクセスログを解析するツールや、サーバのスペックを出力するツール、各種ミドルウェアの設定ファイルなど、事前に準備できるものはたくさんあったのですが、しっかり準備ができませんでした。来年へ向けて社内用のGitHubリポジトリを作って整理していこうという話になっています。

  • コードの問題に気づいても、対応策をパパっと書けない

初期実装状態でアプリのコードの問題には感づいたが、どう直せばいいのか、パッと出てきmせんでした。会社ではWindowsメインでいじっているので、普段からOSSに囲まれた構成でやっていないと、パパっとコマンドが出てないです。

  • Cache-Control

ベンチマーカーに対してキャッシュが効くとはまったく考えていませんでした。動画広告を受けるクライアントであることを考えれば、思いついた気もしますが、あの雰囲気でちょっとキンチョーした状態でその発想は、正直いまの自分の実力では厳しいなぁ、と思いました。完敗です。

いや〜〜、ホントに悔しいのですが、自分の実力を知る良い機会になりました。こんなに悔しい気持ちは久々なので、1年間、修行を積んで基礎力をつけて、来年ISUCONに再挑戦します。絶対本戦に出場します。

TravisCIを使ってHerokuへ自動デプロイする

前回のエントリで RubotyをHerokuにデプロイして、Slackで動かす についてまとめました。

今回はGitHubリポジトリを変更した際に、TravisCIを使ってGitHubに変更があったときに自動でHerokuへデプロイされるようにしたいと思います。この手のお話は結構エントリがあるので、今更感もありますが、自分的に整理しておきたいので技忘録。

f:id:kentana20:20141103210518p:plain

前提・準備

デプロイするアプリケーション

TravisCIの設定

該当リポジトリをTravisCIの実行対象にする

  • TravisCIにログインしたら、Accountsメニューから該当リポジトリのスライダーをonに変更します

f:id:kentana20:20141103204414p:plain

travis gem のインストール

  • GitHubとTravisCIを連携するためのファイルとして .travis.yml を作る必要があるのですが、TravisCI周りの設定をCUIからいい感じにできるGemがあるので利用します
$ gem install travis

.travis.ymlの作成

  • ローカルにCloneしたリポジトリルートへ移動して、以下のコマンドを実行します
$ travis init
Shell completion not installed. Would you like to like to install it now? |y| y
Detected repository as kentana20/ruboty-template, is this correct? |yes| yes
Main programming language used: |Ruby| Ruby
.travis.yml file created!

これで、 .travis.yml が作成されます。Credentialのエラーが出た場合は travis login —auto コマンドを実行して認証をしてから再度実行してみてください。travis report を実行するとエラーの詳細が確認できます。

.travis.ymlにHerokuの設定を追加する

  • 今度は作成した .travis.yml にHerokuの設定を追加します。これもtravis gemの機能を利用してCUIで行います
$ travis setup heroku
Heroku application name: |ruboty-template| $heroku_app_name
Deploy only from kentana20/ruboty-template? |yes| yes
Encrypt API key? |yes| yes

Heroku application nameはHeroku上のアプリケーション名を入力します。これで、 .travis.yml にHerokuの設定が追加されます。出来上がったファイルは以下。

language: ruby
rvm:
- 2.0.0
deploy:
  provider: heroku
  api_key:
    secure:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  app: $heroku_app_name
  on:
    repo: kentana20/ruboty-template

設定ファイルができたらGitHubへpushしておきます。

$ git push origin master

これでTravisCIの設定は完了です。

動作確認

Pull RequestでTravisCI

  • まずはmasterブランチから別のブランチへ作業ブランチを切り替えて、何らかの作業をします。今回は単なる bundle update でGemfile.lockを更新してPull Requestを作成します
$ bundle update
$ git add .
$ git commit -m “gem update”
$ git push origin f/bundle_update

GitHubでPull Requestを作成すると、以下のようにコミットしたリビジョンに対してTravisCIが走り、実行結果をGitHub上で見ることができます。

f:id:kentana20:20141103204529p:plain

これで、master merge前にCIを走らせて、masterに毒が仕込まれるのを防ぐことができます。また、このときのCIジョブはPull Request起因なので、Herokuへのデプロイは行っていません。

f:id:kentana20:20141103204544j:plain

master mergeでTravisCI

  • 先ほどのPull Requestをmergeすると、もう1度TravisCIが動いて、今度はHerokuへのデプロイも実行されます。

f:id:kentana20:20141103204612j:plain

これで、前回のエントリで紹介したHerokuへのデプロイ git push heroku master をすることなく、masterにmergeするだけで、Herokuへのデプロイが完了します。便利ですね〜。

所感

  • Travis Gem超便利
    • Travis Gem、今まで知らなかったので手動で .travis.yml 作ってましたが、これは使わない手はないですね。便利です。Herokuの設定に関してはAPI KeyのEncryptもやってくれるし、スグレモノです。
  • TravisCIはちょっと遅い
    • TravisCIは変更を検知してからの実行が気持ち遅いかな、と感じます。無料版なので贅沢は言えないですが、もう少しサクサク動くことを期待したいです。
  • master mergeからの自動デプロイはラク
    • 会社でも少しは自動化していますが、やはりmaster mergeをフックにデプロイができると良いですね。デプロイ運用に関わっているメンバーの心理的負担も減らせますし、お決まりの作業はどんどん自動化するべきだと強く感じます。
  • コミットリビジョン毎にTravisの実行結果が見られるのも良い
    • リビジョン単位でCIを走らせることで「このPull RequestはCIが通っている」と一目でわかるので、安心してmergeができてこれも心理的負担を軽くできます。メンバーの人数やCIジョブのボリュームによっては「CI待ち」みたいな状態を作りかねないので注意が必要ですが、スピードアップを目指せば良いわけで、これも会社のスタンダードにしていきたいです。
  • ブランチ毎にデプロイ先を分けて運用なども
    • 参考にしたOnishiさんのエントリにもありましたが、devブランチはStaging環境へ、masterブランチはProduction環境へデプロイする、みたいな設定を作っておけば、どちらもデプロイを自動化できて非常にラクにデプロイ運用を行う事ができてTravis便利だと思いました。
  • CircleCIはどうなんだろう
    • 最近はCircleCIの方が流行りっぽいので、今後はCircleCIも試してみたいと思いました。
  • ForkしたリポジトリはPull Requestの送り先に注意
    • 完全に本題とは外れますが、個人的な反省の意味を込めて。今回のデプロイ対象リポジトリid:r7kamuraさんのリポジトリをForkしてるんで、Pull Requestの送り先を間違えるとFork元に飛んでしまいます。今回は実際にPull Requestを飛ばしてしまって「間違ってるよ」と指摘をいただいてしまいました。。反省です。

f:id:kentana20:20141103204658p:plain

これで自動デプロイの設定までできたので、もうちょいためになる挙動を足していこうと思います。個人的にBrowserStackが気になっているので、次回は「Rubotyに話しかけてBrowserStackの結果をSlack上で確認する」をお届けしようと思います。

参考