不格好エンジニア

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

【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の使用
・ステージング環境の構築

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

TopCoder SRM425 Div2 Level1を解いてみました。

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

[問題要約]
東西南北自由に動き回るロボットが指定回数動いたときに、同じ地点に戻ってこない確率を求めよ。

ここでは深さ優先探索を用いてロボットの歩行パターンを列挙し、最後に結果をまとめています。
深さ優先探索を、はじめて実装しました。

class CrazyBot
# class variable
@@grid = Array.new(100).map{Array.new(100, false)}
@@vx = [1,-1,0,0]
@@vy = [0,0,1,-1]
@@prob = Array.new(4).map{0}


#
#= 成功する確率を求める
#
def get_probability(n, east, west, south, north)

@@prob[0] = east / 100.0
@@prob[1] = west / 100.0
@@prob[2] = south / 100.0
@@prob[3] = north / 100.0

pp @@prob

return dfs(50,50,n)

end

#
#= 深さ優先探索
#
def dfs(x, y, n)
return 0 if @@grid[x][y] # 既に通過している
return 1 if n == 0 # 探索しつくした

#pp "dfs x=#{x}, y=#{y}, n=#{n}"

@@grid[x][y] = true # 通過したら印をつける
ret = 0

for i in 0..3 do
ret += dfs(x+@@vx[i], y+@@vy[i], (n-1)) * @@prob[i]
end

@@grid[x][y] = false # 探索が終わって、上に戻ったら印を消す
return ret

end

end

crazy_bot = CrazyBot.new
# example1
pp "correct" if 1.0 == crazy_bot.get_probability(1,25,25,25,25)

# example2
pp "correct" if 0.75 == crazy_bot.get_probability(2,25,25,25,25)

# example3
pp "correct" if 1.0 == crazy_bot.get_probability(7,50,0,0,50)

初心者がiOSアプリと連携する自社サービスを"浅く"チューニングしてみた話

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

最近、お仕事ではiPhoneアプリとそのAPIRailsで作っておりました。

それなりに多数のアクセスが見込まれるフェーズになってきたため、サーバサイドでの高速化に着手し、その中で得られた知見を社内で共有しました。


cocoa podsとparse sdkを使用した時のUndefined symbols for architecture ...を解消した話

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

parseのQuickstart tutorialに従って、CocoaPodsを使用した既存のプロジェクトに Parse ios SDKを組み込んでいたところ、Linker errorが多数発生しました。


Undefined symbols for architecture i386:
"_FBTokenInformationExpirationDateKey", referenced from:
-[PFFacebookTokenCachingStrategy cacheTokenInformation:] in ParseFacebookUtils(PFFacebookTokenCachingStrategy.o)
-[PFFacebookTokenCachingStrategy expirationDate] in ParseFacebookUtils(PFFacebookTokenCachingStrategy.o)
-[PFFacebookTokenCachingStrategy setExpirationDate:] in ParseFacebookUtils(PFFacebookTokenCachingStrategy.o)
"_FBTokenInformationTokenKey", referenced from:
-[PFFacebookTokenCachingStrategy accessToken] in ParseFacebookUtils(PFFacebookTokenCachingStrategy.o)
-[PFFacebookTokenCachingStrategy setAccessToken:] in ParseFacebookUtils(PFFacebookTokenCachingStrategy.o)
"_FBTokenInformationUserFBIDKey", referenced from:
-[PFFacebookTokenCachingStrategy facebookId] in ParseFacebookUtils(PFFacebookTokenCachingStrategy.o)
-[PFFacebookTokenCachingStrategy setFacebookId:] in ParseFacebookUtils(PFFacebookTokenCachingStrategy.o)
"_OBJC_CLASS_$_FBAppCall", referenced from:
objc-class-ref in ParseFacebookUtils(PFFacebookAuthenticationProvider.o)
"_OBJC_CLASS_$_FBRequest", referenced from:
objc-class-ref in ParseFacebookUtils(PFFacebookAuthenticationProvider.o)
"_OBJC_CLASS_$_FBSession", referenced from:
objc-class-ref in ParseFacebookUtils(PFFacebookAuthenticationProvider.o)
"_OBJC_CLASS_$_FBSessionTokenCachingStrategy", referenced from:
_OBJC_CLASS_$_PFFacebookTokenCachingStrategy in ParseFacebookUtils(PFFacebookTokenCachingStrategy.o)
"_OBJC_METACLASS_$_FBSessionTokenCachingStrategy", referenced from:
_OBJC_METACLASS_$_PFFacebookTokenCachingStrategy in ParseFacebookUtils(PFFacebookTokenCachingStrategy.o)
ld: symbol(s) not found for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)

現時点での最善の選択肢は、Facebook SDKをCocoaPodsに追加するか、Objc linker flagを削除するか、どちらかになるそうです。僕は前者を選択してPodfileに下記を追記して、pod installしました。
pod 'Facebook-iOS-SDK'

ありがとう、stackoverflow!

[参考]
How to use cocoa pods and the parse sdk without getting linker errors