ドリコムを支えるデータ分析基盤がTD+AWSに移行した話
はじめに
これは ドリコムAdventCalendar の7日目です
6日目は、keiichironaganoさんによる iTunes 使用許諾更新のとき一旦キャンセルしてほしい話 です
【その2】ドリコム Advent Calendar 2015 もあります
自己紹介
去年の ドリコムを支えるデータ分析基盤 に引き続き、今年もドリコムのデータ分析基盤を担当しています。
分析基盤をTreasure Dataに移行
オンプレ環境の Hadoop からTreasure Data に移行しました。
また、ジョブ管理ツールやBIツールといったサーバーもAmazon EC2 に移行しており、 徐々にオンプレ環境を離れつつあります。
背景
オンプレ環境で Hadoop を運用して3年も経つと考えなければならないのが HW の寿命です。
さてどうしようかとなった時に、ほぼ迷いなく外部サービスに移行するという選択を取りました。
Treasure Data、BigQuery、Amazon Redshift のようなサービスがある中で HW の交換作業をしてまでこのままオンプレで運用し続けるメリットはあまり感じられなかったためです。
サービス選定
候補として上で挙げた Treasure Data、BigQuery、Amazon Redshift の3サービスを実際に触ってみて最終的に Treasure Data を使うことに決めました。
Redshift はちょっと触っただけなのですが、実際に触ってみた所感の比較です。 ※ 2015.1 時点
Treasuredata presto | BigQuery | Amazon Redshift | |
---|---|---|---|
リソースのスケール | フルマネージ | フルマネージ | ユーザがノード変更 |
同時実行 | 4〜 | 20 | 5〜 |
課金 | 固定額。お高め | データサイズ+クエリ数+クエリスキャンサイズ+etc 。全体的にお安い。データ抽出サイズに気をつけないとウン百万の請求も | 固定額+etc |
パフォーマンス | 速い (件数依存、対象が数十億ならHiveにする) | 件数に関係なく速い | 速い (件数に依存) |
テーブル(分散) 設計 | 不要 | 不要 | 必要 |
スキーマレス | ○ | 固定 (json構造ならレス) | 固定 (json構造ならレス) |
データ投入 | 実質入れ放題 | 制約有り (回避方法あり) | S3経由 (大量データのインポートが遅い) |
サポート | ◎ | △ | ○ |
総評 | バランスが良い。専任で人をつけなくても安定した価格、性能で運用できそう | パフォーマンスを保つため、制約が多いので注意が必要 (制約条件が突然変わったら爆死する可能性)。節約したい時、件数が多くてどうしようもなくなった時に使えるかも | 安価で始められるDWH。大規模になるほどチューニングが必要になってくる。 スモールスタート。データマート向き。変更に弱い。 |
他にも比較する部分が色々あるのですが、あんまり細かくなってもしょうがないので、ざっくりとした所感で書きました。
また、弊社の分析基盤としては職人をできるだけ作らないような体制を目指しているので一番運用が楽で安定しそうな Tresure Data を使うことになりました。
新データ分析基盤全体図
大きなところは Hadoop が Treasure Data に変わり、AWS のサーバーを徐々に使い始めているというところで、それ以外のところは去年の ドリコムを支えるデータ分析基盤 とほぼ変わっていません。
DBのスナップショットはS3へ
Hadoop の時は、アプリのログもDBから取ってきたスナップショット(tsv) もHDFS に置いていたのですが、Treasure Data にはデータ件数の制限があるので、無闇に全部 TD に置くということはせずに TD 上で集計が必要なもの以外は S3 に置くことにしています。
また、ログもTD上には直近x日まで保存して、古いものはS3に逃がすということもログ件数調整の一環としてやっています。
闇のスクレイピング技術は今も健在
去年のエントリの闇スクレピングは今も元気に動いています。。。地味に便利なのが憎らしい。。。
まとめ
・安定した運用、職人芸を少しでも減らすために Treasure Data を分析基盤に
・特定の条件下であれば、BigQuery、Redshift はあり
・金を出した分だけ楽ができる
8日目は @sazae657 さんです
ドリコムを支えるデータ分析基盤
はじめに
これは ドリコムAdventCalendar の4日目です
3日目は、@arihh さんによる 3年くらいお菓子神社運営してきた です
自己紹介
ドリコムに新卒で入社し、Android開発、BtoBtoC のwebサービス開発を経て、現在は弊社アプリのログ収集から集計、可視化、その他周辺ツールといった分析基盤の面倒を見ています
本日はそのデータ基盤の話を書きます
データ分析基盤全体図
弊社では Hadoop をオンプレで運用していて、そこにログや分析用のデータを置いています
メリット
運用コストが安い
Treasure Data、Big Query、Amazon Redshift 等の外部サービスを使うよりは安く済みます
自由度が高い
各サービスには容量をはじめ色々と制限があったり、こちらの要求仕様にマッチしない部分が少なからずありますが、自前の場合その辺は融通がききやすいです
分析する人が生データにアクセスしやすい
後述しますが、分析チームの人が R言語 で処理しやすいように、Hadoop にデータを集積しています
なので、基本的に HDFS には gzip 圧縮した tsv ファイル等を置いていて、SequenceFile は置いていません
分析用のサーバーに Fuse で mount しているので通常のファイルシステムのように扱えます
デメリット
メンテナンスが辛い
HDDに障害があったり、運用年数が経ってくると新しいものに入れ替えたり、リカバリ作業をする必要があります
また、HadoopやOSのバージョンアップをしようと思ったら、NameNode や DataNode 全台、その他すべて含めると結構な台数になってくるので精神がガリガリ削られていきます
HDFS全体の容量とかブロックの数とか気にしてあげないといけない点も結構あります
データ取得・保存
ログ送信
ログの送信は fluentd を使用しています
鉄板ですね
図では端折ってますが、web → log collector → Hadoop のように一旦ログを集約してから Hadoop に送信するようにしています
テスト環境のログ排除
テスト環境のログが Hadoop に送られてしまうと集計値がくるってしまうので、ホスト名に test, staging のような文字列が入っている場合 Hadoop に送信しないようにしています
ログ送信成否の確認
webサーバー追加したけど設定ミスでログ送れてませんでしたー!等を突き止めやすくするために、web → collector に送る時にホスト名を送るようにしています
事前処理
送られてきたログに対して、集計に必要な付加情報を加える処理をしています
ここでフォーマットのチェックも行っていて、エラーとなったログは集計対象から除外されます
集計は hive を使用しており、事前処理が終わったらロードされます
スナップショット取得
アプリのDBにSQLを投げて、その結果をファイルとしてHDFSに保存しています
DBのフェイルオーバー対策
DBにクエリを投げる際、SLAVE に投げるわけですが、DBがフェイルオーバーするとSLAVEのIPアドレスも変わってしまいます
そこで、SLAVE のIP情報を管理用のサーバーのDBに入れておき、アプリのDBがフェイルオーバーすると管理用DBにあるIPも書き換わるという仕組みが用意してあります
この仕組みにより常にSLAVEへとクエリを投げることが可能になっています
データの持ち方としてはこんな感じ
CREATE TABLE IF NOT EXISTS `slavedb` ( `app_key` varchar(64) NOT NULL , `host_name` varchar(64) NOT NULL , `slave_ipaddress` varchar(64) NOT NULL , `db_name` varchar(64) NOT NULL , `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`app_key`,`host_name`,`db_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
帯域への配慮
結構な数のクエリを投げたり、結果がでかいものがあったりで何も考えずに一気に流すとネットワークの帯域を食いつぶす可能性があった (実際やばかった) ので、
一度に流すクエリの量を抑える仕組みを作って安全に配慮してます
クエリの管理
アプリのDBへ投げるクエリは ActiveAdmin で管理しています
また、ここで管理されているクエリは管理画面から Explain を実行してその結果を見ることができるように手を加えています
クエリのシンタックスエラーのチェックもできて地味に便利
バックアップ
ログ、スナップショットファイルは Hadoop に取り込まれると同時にバックアップサーバにコピーされます
集計
前述のとおり集計はみんな大好き hive を使っています
presto はテスト的に使ってみたりしてます
集計したデータは可視化のため MySQL へ
集計単位
- hourly
- dayily
- monthly
ジョブ数
集計以外の全ジョブ含め約600個
可視化
有料BIツールを使っていますが、パフォーマンス、アカウント制限等々の理由で結局BIツール自作しました
黒背景にするとなんかカッコよく見えますね!
分析チーム
弊社にはアプリの分析を行う分析専門のチームがいて、
ハイスペックな分析用サーバ数台に Hadoop を Fuse マウントして自由に Hadoop 上のファイルを使えるような環境を用意しています
基本的に R で分析を行い、彼らの定期ジョブは分析サーバーで Jenkis で実行しています
少し前まで cron を使っていましたが、さすがに管理が辛くなってきました
闇スクレイピング
ログの集計値の正当性を担保するために、プラットフォームの管理画面で見ることができる数字(売上等)を取ってこなければいけません
(ちなみにあまりにも誤差があると、ログと管理画面からDLできる課金明細をつき合せていくことになります...)
しかし、プラットフォームによってサイトの作りがまちまちです
単純にスクレイピング用ライブラリではすべてを解決できませんでした (そのへんのAPIは提供されていない)
そこで色々もがいた結果、以下のような環境が出来上がりました
使用しているもの
- Xwindow
- firefox
- Grease moneky
Grease monkey とは firefox のプラグインのひとつで、特定のURLに対して任意のコード (javascript) を実行できます
仕組み
- Linux に作った GUI 環境から指定のURLに firefox でアクセスする
- グリモンで目的のページまでたどり着いたら、そのページをテキストファイルとしてダウンロードします
- そのテキストファイルから正規表現で必要な部分を抜いてDBに入れます
もはやスクレイピングでもなんでもないですね
最高に力技ですが、この方法でURLの末尾にランダムなハッシュがつくサイトも非同期処理をするサイトでもなんでも抜いてこれるようになりました!
サンプルコード (雰囲気だけ感じてもらえれば...)
// ==UserScript== // @name kani_scraper // @namespace imperial_cross // @include https://developer.hogee.jp/ // @include https://developer.hogee.jp/* // ==/UserScript== (function() { if(location.href == "https://developer.hogee.jp/"){ document.getElementsByName("login_id")[0].value = login_id; document.getElementsByName("password")[0].value = password; document.getElementsByName("form")[0].submit(); }else if(location.href == "https://developer.hogee.jp/home"){ // http://d.hatena.ne.jp/a_bicky/20110718/1311027391 参照 writeHtmlFile = function () { function writeToLocal (filename, content){ var ua = navigator.userAgent.toLowerCase(); try { if (ua.indexOf('firefox') != -1) { // Firefox //filename = (ua.indexOf('windows') != -1 ? 'C:\\tmp\\' : '/tmp/') + filename; if (ua.indexOf('windows') != -1){ filename = "C:\\tmp\\" + filename; } else { filename = path + filename; } unsafeWindow.netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); // ファイルコンポーネントの取得+ローカルファイル操作用のインターフェイスの取得; var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile); file.initWithPath(filename); var fileStream = Components .classes['@mozilla.org/network/file-output-stream;1'] .createInstance(Components.interfaces.nsIFileOutputStream); // ファイルが存在しない場合は664の権限で新規作成して書き込み権限で開く // cf. https://developer.mozilla.org/en/NsIFileOutputStream // http://www.oxymoronical.com/experiments/apidocs/interface/nsIFileOutputStream; fileStream.init(file, 0x02 | 0x08, // 0x01: 読み取り専用, 0x02: 書き込み, 0x03: 読み書き, 0x08: 新規作成, 0x10: 追記 0664, // mode 0 // 第4引数は現在サポートしていないとか ); // cf. http://www.oxymoronical.com/experiments/apidocs/interface/nsIConverterOutputStream var converterStream = Components .classes['@mozilla.org/intl/converter-output-stream;1'] .createInstance(Components.interfaces.nsIConverterOutputStream); converterStream.init(fileStream, 'UTF-8', content.length, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); converterStream.writeString(content); converterStream.close(); fileStream.close(); //alert('書き込みが完了しました!'); } else if (ua.indexOf('chrome') != -1) { // Google Chrome // 起動オプションに --unlimited-quota-for-files --allow-file-access-from-files をつける必要あり function errorCallback(e) { alert('Error: ' + e.code); } function fsCallback(fs) { fs.root.getFile(filename, {create: true}, function(fileEntry) { fileEntry.createWriter(function(fileWriter) { fileWriter.onwriteend = function(e) { alert('書き込みが完了しました!'); }; fileWriter.onerror = function(e) { alert('Failed: ' + e); }; var bb = new WebKitBlobBuilder(); bb.append(content); var output = bb.getBlob('text/plain'); fileWriter.write(output); }, errorCallback); }, errorCallback); } // 現時点ではたぶん第1引数はPERSISTENTもTEMPORARYディレクトリ名が異なるだけだし、 // 第2引数は極端な話0でもOK webkitRequestFileSystem(PERSISTENT, 1024, fsCallback, errorCallback); } else if (ua.indexOf('msie')) { // MS IE filename = 'C:\\tmp\\' + filename; // インターネットオプションで「スクリプトを実行しても安全だとマークされていない // ActiveX コントロールの初期化とスクリプトの実行(セキュリティで保護されていない)」 // を有効にする必要あり var fso = new ActiveXObject('Scripting.FileSystemObject'); // ファイルを新規作成して書き込みモードで開く (文字コードはUTF-16) // cf. http://msdn.microsoft.com/ja-jp/library/cc428044.aspx // http://msdn.microsoft.com/ja-jp/library/cc428042.aspx var file = fso.OpenTextFile(filename, 2, // 1: 読み取り専用, 2: 書き込み, 8: 追記 true, // ファイルが存在しなければ新規作成するかどうか -1 // -2: OSのデフォルト文字コード, -1: UTF-16, 0: ASCII ); file.Write(content); file.Close(); alert('書き込みが完了しました!'); /* * ADODB.Stream を使う場合(レジストリをいじっても何故か書き込めない・・・) */ // var adodbStream = new ActiveXObject('ADODB.Stream'); // adodbStream.type = 2; // テキストファイル(バイナリは1) // adodbStream.charset = 'UTF-8'; // adodbStream.open(filename); // adodbStream.writeText(content); // adodbStream.saveToFile(filename, 2); // 上書き保存(1だと新規作成のみが対象) // adodbStream.close(); } else { alert('エラー: ローカルファイルへの書き込み方がわかりません・・・'); } } catch (e) { alert('Error: ' + e); } } writeToLocal('test1.txt', "hogehoge"); } } setTimeout( writeHtmlFile, 20000);//20秒後にページを閉じる })();
闇っぽい雰囲気だけ感じてください
Hadoop まわりの運用
Hadoop、hive での運用で気を使っている点や以前困っていた点を紹介します
細かいファイルは集約する
HDFS のデフォルトブロックサイズは 64MB と大きく、小さいファイルをたくさん置くのはあまり効率が良くありません
サイズの小さいものは1日単位などに集約すると結構ブロック数を減らせます
簡単に再集計できる仕組み
再集計をかけるタイミングが必ず存在します。しかも意外に高い頻度で
ジョブスケジューラで簡単にできればそれがベストですが、
なんらかの事情やそんな機能がなかった場合は、再集計するコードを書いておくと幸せになります
hive の json カラムには気をつけろ
hive には json 型があり、スキーマレスっぽい感覚で使えます
しかし使えるからと言って何でもつっこんで、1行なのに画面いっぱいの json で埋まるみたいなことをされるとさすがに hive さんも落ちます気を付けてください
まとめ
次は id:GUSSAN さんです。
-- 追記 --
HDFS 上のファイル集約のくだりですが、ご指摘を頂いたのでほそくすると、
容量を節約したいからではなく、NameNode のメモリが節約できるということです
あと、mapタスクの数を減り mapの起動コストも減ることになります (こっちは失念してました!)
カニでもできるRailsでアクセスログ実装
こんにちはこんにちは
Rails 4 でさくっとアクセスログ出そうと思ったけど、思ったよりさくっといかなかったので実装方法を残しておく
ログの項目
これくらいの簡単な内容
- 時間
- ユーザー名
- リクエストURI
- USER AGENT
独自ログの設定
まず、Railsのログじゃなくて独自のログを出したかったので、
config/environment.rb
# Load the Rails application. require File.expand_path('../application', __FILE__) class AccessLogger < ::Logger class NoHeaderLogDevice < ::Logger::LogDevice def add_log_header(file) end end class AccessFormatter < ::Logger::Formatter def call(severity, timestamp, progname, msg) "#{msg}\n" end end def initialize(logdev, shift_age = 0, shift_size = 1048576) super(nil, shift_age, shift_size) @formatter = AccessFormatter.new if logdev @logdev = NoHeaderLogDevice.new( logdev, :shift_age => shift_age, :shift_size => shift_size ) end end end # Initialize the Rails application. Kaniapp::Application.initialize!
解説
ソースの中身を解説すると、
ログファイル作成時に出る邪魔なヘッダー(# Logfile created on ...)を抹殺
class NoHeaderLogDevice < ::Logger::LogDevice
def add_log_header(file)
end
end
ログフォーマットを指定
class AccessFormatter < ::Logger::Formatter
def call(severity, timestamp, progname, msg)
"#{msg}\n"
end
end
config/environments/production.rb に
config.access_logger = AccessLogger.new('log/skv_access.log')
を追加
呼び出し
ApplicationController から
before_filter { access_log if Rails.env.production? }
private
def access_log
return if session['warden.user.user.key'].nil?
@user = User.find_by_id(session['warden.user.user.key'][0][0])
return if @user.nil?
now = Time.now.to_s.gsub(/ \+0900/,'')
request_uri = URI.unescape(request.env['REQUEST_URI'])
remote_ip = request.env['HTTP_X_FORWARDED_FOR'] || request.remote_ip
user_agent = request.env['HTTP_USER_AGENT']
row = "#{now}\t#{@user.username}\t#{request_uri}\t#{remote_ip}\t#{user_agent}"
Kaniapp::Application.config.access_logger.info(row)
end
このやり方で君だけの最強のアクセスログを作ろう!!
devise_ldap_authenticatable でグループ制限したい人生だった (Rails 4)
とあるアプリ (でLDAP認証を実現するために、 cschiewek/devise_ldap_authenticatable · GitHub を使っていて幸せに暮らしていたけど、ある日突然○○グループのみ認証を通したくなったので、設定ファイルをいじったら簡単にできるやろって思ったらできなかったので、ちょこっと頑張ったお話。
devise_ldap_authenticatable でグループ認証したい人向けのお話です
先に結論から
で完了
gemのバージョンたち
net-ldap (0.5.1)
devise (3.1.1)
devise_ldap_authenticatable (0.8.1) # 現時点で rubygems 的に最新のgemのバージョン
モンキーパッチを当てる
config/initializers/devise_ldap_authenticatable_customizer.rb
module DeviseLdapAuthenticatableCustomizer
begin
class Railtie < ::Rails::Railtie
config.after_initialize do
Devise::LDAP::Connection.class_eval do
def initialize(params = {})
ldap_config = YAML.load(ERB.new(File.read(::Devise.ldap_config || "#{Rails.root}/config/ldap.yml")).result)[Rails.env]
ldap_options = params
ldap_config["ssl"] = :simple_tls if ldap_config["ssl"] === true
ldap_options[:encryption] = ldap_config["ssl"].to_sym if ldap_config["ssl"]
@ldap = Net::LDAP.new(ldap_options)
@ldap.host = ldap_config["host"]
@ldap.port = ldap_config["port"]
@ldap.base = ldap_config["base"]
@attribute = ldap_config["attribute"]
@ldap_auth_username_builder = params[:ldap_auth_username_builder]
@group_base = ldap_config["group_base"]
@check_group_membership = ldap_config.has_key?("check_group_membership") ? ldap_config["check_group_membership"] : ::Devise.ldap_check_group_membership
# この行を追加
@check_group_membership_without_admin = ldap_config.has_key?("check_group_membership_without_admin") ? ldap_config["check_group_membership_without_admin"] : ::Devise.ldap_check_group_membership_without_admin
@required_groups = ldap_config["required_groups"]
@required_attributes = ldap_config["require_attribute"]
@ldap.auth ldap_config["admin_user"], ldap_config["admin_password"] if params[:admin]
@login = params[:login]
@password = params[:password]
@new_password = params[:new_password]
end
def in_group?(group_name, group_attribute = LDAP::DEFAULT_GROUP_UNIQUE_MEMBER_LIST_KEY)
in_group = false
if @check_group_membership_without_admin
group_checking_ldap = @ldap
else
group_checking_ldap = Connection.admin
end
unless ::Devise.ldap_ad_group_check
group_checking_ldap.search(:base => group_name, :scope => Net::LDAP::SearchScope_BaseObject) do |entry|
#元のコード
#if entry[group_attribute].include? dn
if entry[group_attribute].include? dn.gsub(/uid=([^,]+),.*$/,'\1')
in_group = true
DeviseLdapAuthenticatable::Logger.send("User #{dn} IS included in group: #{group_name}")
end
end
else
# AD optimization - extension will recursively check sub-groups with one query
# "(memberof:1.2.840.113556.1.4.1941:=group_name)"
search_result = group_checking_ldap.search(:base => dn,
:filter => Net::LDAP::Filter.ex("memberof:1.2.840.113556.1.4.1941", group_name),
:scope => Net::LDAP::SearchScope_BaseObject)
# Will return the user entry if belongs to group otherwise nothing
if search_result.length == 1 && search_result[0].dn.eql?(dn)
in_group = true
DeviseLdapAuthenticatable::Logger.send("User #{dn} IS included in group: #{group_name}")
end
end
unless in_group
DeviseLdapAuthenticatable::Logger.send("User #{dn} is not in group: #{group_name}")
end
return in_group
end
end
end
end
rescue Exception(err_msg)
puts (" --- error => #{err_msg} --- ")
end
end
LDAP設定ファイル
config/ldap.yml グループとかLDAPのホストとかは適宜変えてください
## Authorizations
# Uncomment out the merging for each environment that you'd like to include.
# You can also just copy and paste the tree (do not include the "authorizations") to each
# environment if you need something different per enviornment.
authorizations: &AUTHORIZATIONS
group_base: ou=groups,dc=test,dc=com
## Requires config.ldap_check_group_membership in devise.rb be true
# Can have multiple values, must match all to be authorized
required_groups:
# If only a group name is given, membership will be checked against "uniqueMember"
- cn=admins,ou=groups,dc=test,dc=com
- cn=users,ou=groups,dc=test,dc=com
# If an array is given, the first element will be the attribute to check against, the second the group name
- ["moreMembers", "cn=users,ou=groups,dc=test,dc=com"]
## Requires config.ldap_check_attributes in devise.rb to be true
## Can have multiple attributes and values, must match all to be authorized
require_attribute:
objectClass: inetOrgPerson
authorizationRole: postsAdmin
## Environment
development:
host: ldap.network.kani.dc
port: 389
attribute: uid
base: ou=users,dc=kani,dc=co,dc=jp
ssl: false
group_base: ou=groups,dc=kani,dc=co,dc=jp
check_group_membership: true
check_group_membership_without_admin: true
required_groups:
- ["memberuid", "cn=hogegroup,ou=groups,dc=kani,dc=co,dc=jp"]
test:
host: ldap.network.kani.dc
port: 389
attribute: uid
base: ou=users,dc=kani,dc=co,dc=jp
ssl: false
production:
host: ldap.network.kani.dc
port: 389
attribute: uid
base: ou=users,dc=kani,dc=co,dc=jp
ssl: false
group_base: ou=groups,dc=kani,dc=co,dc=jp
check_group_membership: true
check_group_membership_without_admin: true
required_groups:
- ["memberuid", "cn=hogegroup,ou=groups,dc=kani,dc=co,dc=jp"]
解説
LDAP認証の流れ
- 個人アカウントでLDAP認証してオブジェクト取得
- そのオブジェクトを使ってグループ認証
ざっくりこんな感じ
2 を行う際、ldap.yml に check_group_membership_without_admin がないと、グループ認証する際に、入力されたユーザーではなく admin のアカウントとパスワードでバインドしてしまうので、invalid bind みたいな怒られ方をします。
そんな設定なかった
で、config に書くんですが、rubygemsから落とせる最新版 (0.8.1) にはその設定がなく、githubのmasterブランチ (0.8.3) にはありました。
ただ、認証系のgemをmasterから取ってくるのはイヤだったので、0.8.1にモンキーパッチを当てることにしました。
ポイントは2か所
Devise::LDAP::Connection の initialize で ldap.yml の設定を読んでいたので、check_group_membership_without_admin を読ませる
グループに属しているかチェックしている in_group? メソッドで、uid で認証して欲しかったので、正規表現で切り取って認証
手を加えたのは Devise::LDAP::Connection の中の2行だけ
これでグループ認証があなたのもとへ、やったね!
カニでもわかるWindows 7新規インストール
windows機のHDDが突然お亡くなりになったので、新規HDDにwindows DSP版を入れて若干四苦八苦したところをメモる。
環境
マシン
ThinkPad Edge E420
HDD
壊れたHDD
ST9750420AS
http://www.tigerdirect.com/applications/SearchTools/item-details.asp?EdpNo=6324326
調べたら非AFTらしい。
AFTってなんじゃい
このへんを見ました。
http://www.pc-master.jp/jisaku/aft-hdd.html
なる、ほ、ど。。。
わからんが、AFTのものを買った方が良さそう。それに合わせてwindows7 SP1 適応済みにしたほうがよさげ
新しく買ったHDD
WD7500BPVX
http://shop.tsukumo.co.jp/goods/4515479660908/
これはちょっと調べるのに苦労したけど、AFTっぽい
OS
Microsoft Windows7 Home Premium 64bit Service Pack 1 日本語 DSP版 DVD LCP 【紙パッケージ版】
http://www.amazon.co.jp/gp/product/B00HSC9E78/ref=oh_details_o00_s00_i00?ie=UTF8&psc=1
なんかよくわかんないけど、一番安そうなやつ買いました
インストール
インストール自体は何の問題もなく、ディスクを入れて電源を入れたら勝手にインストール画面になったので、キーボードとか色々設定をポチポチしていくだけ
ここからが本当の地獄だ...
さてとりあえず chrome 入れるかと思って IE 立ち上げたらネットつながんねーわ
ディスプレイも外部ディスプレイ検出しねーわ
Bluetooth ヘッドセットも接続できねーわ etc...
なんか色々できない!
デバイスドライバを見ると、とりあえずネットワークアダプターねーじゃんよ
まずはインターネットから
ぐぐってたらレノボのサポートサイトから必要なデバイスドライバーを入れてみることに
http://support.lenovo.com/ja_JP/research/hints-or-tips/detail.page?DocID=HT037710
幸いメインPCはMacさんなのでそっちからネットワークドライバーさんを落として、USBメモリからwin機に入れたらインターネットできました、素敵!!
ディスプレイ
ディスプレイもドライバー入れたら検出してくれました
最後の難関 Bluetooth ヘッドセット
これはほぼ自分用
MW600 を使ってるんですが、PCと接続しようとするとデバイス認証するところまではできた。
でも接続はできない
なぜだ
ぐぐったり、製品のQ&Aを見に行ったりして悶絶してたら、電源ボタン+電話マークのボタン (何て言うんだこれ) 同時長押しで何かがリセットされたっぽくて、接続できるようになりました。
というわけで、ちょっと悶絶しましたが無事 windows 使えるようになりました
カニでもわかるRails 3 (nginx + unicorn) 開発環境構築
今まで Rails 1系だけ触った事があったのですが、Rails 3系をやる機会があったので、開発環境構築手順をまとめました。
手順として、
という感じでいきます。
rvm インストール
ちなみにサーバーは Debian (squeeze) です。
今回は開発環境なので、自分だけ使う前提で自分のアカウントでrvmをインストールします。
curl -L get.rvm.io | bash -s stable
# パスを通す
source ~/.rvm/scripts/rvm
# ruby インストール
rvm install 1.9.3
# gemset 作成
rvm gemset create kanipan_rails
# kanipan_rails gemset を使う
rvm gemset use kanipan_rails
gemset use してgem installすると、
/home/kanipan/.rvm/gems/ruby-(version)@(gemset名)/gems/
以下にgemが入ります。
Rails インストール
今回は最新版 (3.2) をインストールします。
gem install rails
#バージョン確認
rails -v
Rails 3.2.11
# rails プロジェクト作成 databaseをmysqlに指定
rails new kanipan_project --database=mysql
unicornのインストールと設定
vim Gemfile
Gemfileに以下を追加
gem 'unicorn'
Gemfile保存後、
bundle install
# unicornの設定ファイル作成
vim config/unicorn.rb
サンプルとして
listen "/tmp/unicorn.sock" pid "/tmp/unicorn.pid" worker_processes 1 preload_app true if ENV['RAILS_ENV'] == 'production' shared_path = "/var/www/shared" stderr_path = "#{shared_path}/log/unicorn.stderr.log" stdout_path = "#{shared_path}/log/unicorn.stdout.log" end # ログ stderr_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT']) stdout_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT']) preload_app true before_fork do |server, worker| defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! old_pid = "#{ server.config[:pid] }.oldbin" unless old_pid == server.pid begin Process.kill :QUIT, File.read(old_pid).to_i rescue Errno::ENOENT, Errno::ESRCH end end end after_fork do |server, worker| defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection end
unicornを起動します。
# -D : デーモンとして動かす, -E : Railsの環境, -c : unicorn設定ファイルのパス
unicorn_rails -D -E development -c config/unicorn.rb
unicornはmasterプロセスと複数のwokerプロセスから成り立っています。 unicornを停止するにはmasterのプロセスを止めます。
pidはunicorn.rbにパスを指定しているので、
kill -9 `cat /tmp/unicorn.pid`
nginx インストールと設定
sudo apt-get install nginx
sudo vim /etc/nginx/ngin
# kanipan_project用の設定ファイル
sudo vim /etc/nginx/sites-available/kanipan_project
以下サンプル
# statements for each of your virtual hosts upstream kanipan_project{ server unix:/tmp/unicorn.sock; } server { listen 80; server_name localhost; access_log /var/log/nginx/kanipan_project/access.log combined2; error_log /var/log/nginx/kanipan_project/error.log warn; root /var/www/kanipan_project/current/public; #rails params用 proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k; proxy_temp_file_write_size 256k; location / { proxy_pass http://kanipan_project; }
kanipan_project用設定へのシンボリックリンクを sites-enabled以下に置きます。
sudo ln -s /etc/nginx/sites-available/kanipan_project /etc/nginx/sites-enabled/kanipan_project
ドキュメントルートにkanipan_projectのpublicフォルダへのシンボリックリンクを置きます。
# /var/wwwにシンボリックリンクをはる
sudo ln -s kanipan_project/public /var/www/current/kanipan_project
これで準備完了!