Nginx + Unicorn + Redmine

環境移設中にうまく動かなくなったので,追記.
ユーザの環境変数を引き継いでbundle exec ... は動くのに,crontab で @reboot から起こそうとすると,unicorn が上がらない場合,私の環境では次の2つが原因であった.

  • bundleコマンドを––user–localオプションつけてインストールしたから,rootからは見えなかった.
    ⇒フルパスで対応
  • bundlerを––user–localオプションつけてインストールしたから,bundle exec unicorn_railsの中でエラー吐いて動かなかった.
    sudo su - で完全にrootとしてログインして,gem install bundlerする.

これで解決したので,これ以上は調べてないよん.

途中.nginx から unicorn の呼び出しは設定完了済,設定ファイルは現行環境を見て,適宜,パースする(予定).

$cd tmp
$ wget http://www.redmine.org/releases/redmine-2.6.0.tar.gz
$ tar xzf redmine-2.6.0.tar.gz
$ mv redmine-2.6.0 ..
$ cd ~/redmine-2.6.0
$ sudo gem install bundler
$ vi database.yml
$ vi Gemfile.local
+ gem "unicorn"
$ bundle install --without development test rmagick postgresql sqlite
$ cp /oldfile/databasefile.sqlite3 ./db/
$ cp /oldfile/unicorn.rb ./config/
$ sudo bundle exec unicorn_rails -D -E production -c config/unicorn.rb # テスト
$ sudo kill (pid) # 停止

追記 2015/4/6
unicornまわりのコマンド系

起動
# bundle exec unicorn_rails -D -E production -c config/unicorn.rb

–D でデーモン化
–E hogehoge で hogehoge 環境を起動
–c hogehoge で hogehoge というファイルをコンフィグファイルに指定

停止
# kill -QUIT PID

unicorn.rb とかで pid ファイルを指定していると思うので,そいつを cat するか,ps aux | grep unicorn とかで探す.

再起動
# kill -HUP PID

完全にコンフィグとか読み直して欲しいんだけど,なんか反映されないことがあるので,その場合はQUIT→bundle execとする

新規に起動
# kill -USR2 PID

前のプロセスを残して起動.この後,QUIT すれば,サービスを止めずにプロセスを再起動できるという理屈だが,QUIT するのをよく忘れるので,あんま使ってない.

サブディレクトリで運用

アプリに依るんだろうけど,けっこう手間がかかるから,VirtualHost で運用できるなら,そっちのが楽.
redmineについては,そのうちまとめる.

python で実行時間計測

何度も繰り返す必要のある短いコードの測定

import timeit
t=timeit.Timer("""script""",'import clause')
print t.timeit()

1回の実行に長時間を要するため,1度で評価したい場合など.

import time
def tm(t0=None):
    if isinstance(t0,float): return time.time()-t0
    else: return time.time()

time を使うべきか datetime を使うべきか

import datetime
def tm(t0=None):
    if isinstance(t0,datetime.datetime): return datetime.datetime.today()-t0
    else: return datetime.datetime.today()
import timeit
t=timeit.Timer("""t0=tm.tm()
t1=tm.tm(t0)""",'import tm')
print t.timeit()
t=timeit.Timer("""t0=tm2.tm()
t1=tm2.tm(t0)""",'import tm2')
print t.timeit()

結果は,
0.602358818054
7.44828104973
圧倒的に time

SD カード上に入れた gnupack で python numpy を使う

SD カード上の gnupack (gnupack_devel-10.00) で、python numpy を apt-cyg から入れても動かない。

>>> import numpy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/site-packages/numpy/__init__.py", line 137, in <module>
    import add_newdocs
  File "/usr/lib/python2.6/site-packages/numpy/add_newdocs.py", line 9, in <module>
    from lib import add_newdoc
  File "/usr/lib/python2.6/site-packages/numpy/lib/__init__.py", line 13, in <module>
    from polynomial import *
  File "/usr/lib/python2.6/site-packages/numpy/lib/polynomial.py", line 17, in <module>
    from numpy.linalg import eigvals, lstsq
  File "/usr/lib/python2.6/site-packages/numpy/linalg/__init__.py", line 48, in <module>
    from linalg import *
  File "/usr/lib/python2.6/site-packages/numpy/linalg/linalg.py", line 23, in <module>
    from numpy.linalg import lapack_lite
ImportError: No such file or directory
>>>

web を検索すると、こういう情報が出てくる。→
numpyでlapack_fileを読み込んでくれないとき – 放置演算子
このサイトで言っているのは、必要なライブラリにパスが通ってないということだが、このサイトの対応方法では新しいバージョンを上書きインストールした時に、使っているライブラリとズレが出て、将来にハマる可能性がありそう。
なので、リンクを張ることにするが、やっぱ怒られる。

>>> import numpy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/site-packages/numpy/__init__.py", line 137, in <module>
    import add_newdocs
  File "/usr/lib/python2.6/site-packages/numpy/add_newdocs.py", line 9, in <module>
    from lib import add_newdoc
  File "/usr/lib/python2.6/site-packages/numpy/lib/__init__.py", line 13, in <module>
    from polynomial import *
  File "/usr/lib/python2.6/site-packages/numpy/lib/polynomial.py", line 17, in <module>
    from numpy.linalg import eigvals, lstsq
  File "/usr/lib/python2.6/site-packages/numpy/linalg/__init__.py", line 48, in <module>
    from linalg import *
  File "/usr/lib/python2.6/site-packages/numpy/linalg/linalg.py", line 23, in <module>
    from numpy.linalg import lapack_lite
ImportError: Exec format error
>>>

しょうがないので、調べてみる。

# cygcheck.exe /lib/python2.6/site-packages/numpy/linalg/lapack_lite.dll
G:\gnupack_devel-10.00\app\cygwin\cygwin\lib\python2.6\site-packages\numpy\linalg\lapack_lite.dll
  G:\gnupack_devel-10.00\app\cygwin\cygwin\bin\cygwin1.dll
    C:\WINDOWS\system32\KERNEL32.dll
      C:\WINDOWS\system32\ntdll.dll
  G:\gnupack_devel-10.00\app\cygwin\cygwin\bin\cyglapack-0.dll  G:\gnupack_devel-10.00\app\cygwin\cygwin\bin\libpython2.6.dll
chgcheck: G:\gnupack_devel-10.00\app\cygwin\cygwin\bin\cyglapack-0.dll is a symlink instead of a DLL

シンボリックリンクはダメ、らしい。
じゃぁ、とハードリンクを張ろうとするが、Fat32 でフォーマットされている SD カード上ではハードリンクが張れない。
ということで、SD カードを NTFS でフォーマットし直す。→SDカードをNTFSフォーマットする
で、その SD カード上でハードリンクを張れば、ちゃんと動く。

# cd /usr/bin
# ln /usr/lib/lapack/cygblas-0.dll cygblas-0.dll
# ln /usr/lib/lapack/cyglapack-0.dll cyglapack-0.dll

AutoFollowersBlocker

GAE 上で動作し, cron で呼び出されて, twitter アカウントの follower を監視し,
プロファイルの文字列によって自動ブロッキングをするスクリプトを作成した.
きっかけは,昔むかし,山のように spammer からフォローされまくって,鬱陶しかったため.
当時の記事はこちら → twitterのspammerのfollowが多い

で,作ってみたのが, twitter を OAuth 認証でアクセスするためのクラス twit_oauth.py
そして,呼び出し元の AFBsample.py
いずれも分量が多いので,ソース自体はリンク先を参照されたい.

また,GAEで使うためのyamlファイルもついでに貼り付けしておく.

application: afb-sample
version: 10
runtime: python
api_version: 1

handlers:
# handling mode
- url: /cron
  script: AFBsample.py
  login: admin
# viewer mode
- url: /.*
  script: AFBsample.py
cron:
- description: polling job
  url: /cron
  schedule: every 5 minutes

さてこれで,次の4つを GAE へ放り込めば動く.
GAE の使い方については,web を参照されたいが,以前に hadacchi が調べたメモもある → twitterのspammerのfollowが多い
これでも,多少は参考になるかも知れない.

  • AFBsample.py
  • twit_oauth.py
  • app.yaml
  • cron.yaml

さて,今回のハマりポイントは,呼出 URL による処理の振り分けだった.

handlers:
# handling mode
- url: /cron
  script: AFBsample.py
  login: admin
# viewer mode
- url: /.*
  script: AFBsample.py
application = webapp.WSGIApplication([
    ('/cron', AutoFollowerBlocker),
    ('/', AFBview)
    ])

def main():
    wsgiref.handlers.CGIHandler().run(application)

当初は app.yaml の url 行を修正すれば呼び出し URL に応じて処理を振り分けできると思っていたが,
呼び出される側の python スクリプト内の webapp.WSGIApplication() の意味に気付いていなかった.
こいつの引数は,長さ 2 のタプルのリストであるが,タプルの第 1 要素は呼び出しURLになっていて,
ここでも呼び出し URL に応じて処理クラスを振り分けできる.
きちんと GAE の処理を読んでいないが,多分 app.yaml による振り分けは login 行におけるパーミッション制御や,
スクリプトファイルを分けることにより,クロスサイトスクリプティングみたいなアタックに対してある程度の
対処をしたい場合などに使うのが良いのかも知れない.

今回の構成では,
http://pjname.appspot.com/ へのアクセスで前回の実行結果を表示し,
http://pjname.appspot.com/cron へのアクセスでフィルタを手動実行する.

他の工夫としては,運用面で使い易くするため,自動 BLOCK 後に手動で UNBLOCK したユーザは対象から除外するようにしたり,
AND/OR 検索で重複マッチを走らせないようにしたり,
空フィールドに対して None が返却されるので事前に置換しておいたり,(デバッグの時にはjsonファイルを直接食わせる関係から,
マッチ関数内にもNoneの処理は残しているが…)
といった小細工は少ししてみた.

今後の課題としては,プロファイルを 1 call で 100 id 分まで抜ける API を見落としていたことが
分かったので,この API へ対応させた上で, follower が 10000 を越えるユーザでも TwitterAPI の
呼出制限にかからないよう初期化する実装を行なうことがある.
と難しく言ってみたものの,既チェック ID をパスするロジックなので, API の回数制限にかかる手前で
ループを break させてしまえば良いだけである.

参考にした web site は, Python アプリケーション設定 - Google App Engine - Google Code などの GAE のオンラインドキュメントと,
REST API Resources | Twitter Developers などの Twitter のオンラインドキュメントに加え,
前回までの記事 twitterでspammerのfollowを自動block 及び twitterのspammerのfollowが多い 内で参照している各ページである.

twitterでspammerのfollowを自動block

-- 追記2
ごめん,バグあった.文字列操作まわりのencodingがかなり怪しい.
空の時,Noneが入ってくるのは空文字列に置き換えるべきか.

-- 追記
きっと誰も AND/OR 検索を実装してくれないので,実装した.
ついでに, twit_oauth.py を更新して,フォロワだけでなくフォローしてる人の一覧も取れるようにした.

先の記事twitterのspammerのfollowが多いでも書いた,spammerのキーワード抽出について.
GMail からの PUSH 通知をトリガとした動作がうまく実装できなかったので (なぜか python2.5.xでは,imaplib2 がうまく動かない)
とりあえずトリガーは何も考えてません.cron でも,IMAP/IDLE でも,好きに実装してください.

twitter を変なライブラリなしで OAuth 認証で動かすクラスは,こんな感じで実装してます → twit_oauth.py
で,こいつを使って,下記のように使うとスパムフィルタのできあがり.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import simplejson,re
from twit_oauth import twit_oauth

__KEYWORDS = [(re.compile(u'キーワード1'),),
              (re.compile(u'キーワード2-1'),re.compile(u'キーワード2-2'))
             ]

# プロファイルリスト取得関数
def getProfiles(followers,pobj):
    profiles = {}
    for f_id in followers:
        profs=simplejson.loads(pobj.callAPI('profile',`f_id`))
        if profs['name']==None: name=''
        else: name=''.join(profs['name'].split('\n'))
        if profs['description']==None: desc=''
        else: desc=''.join(profs['description'].split('\n'))
        profiles[`f_id`]=(name,desc)
    return profiles

# テキストマッチ関数
def filterByKeys(profiles):
    fil_list = []
    for (id,prof) in profiles.items():
        # __KEYWORDS のいずれかの要素1つでも合致すればブロック (OR)
        for keys in __KEYWORDS:
            fil_list.append(id)
            # __KEYWORDS の1要素の中の,全てのマッチが成功すればブロック (AND)
            for k in keys:
                if not k.search(prof):
                    del fil_list[-1]
                    break
            # 重複登録回避
            if len(fil_list) != 0 and fil_list[-1] == id: break
    return fil_list


pobj= twit_oauth('xxxxxxxxxxxxxxxxxx','xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
                 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx','xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')


# 自分の username を入れて,フォロワーを取得
followers = simplejson.loads(pobj.callAPI('getfollowers','username'))
# フォロワーのプロファイルを取得
profiles  = getProfiles(followers,pobj)
# キーワードマッチでフィルタ対象を取得
fil_list  = filterByKeys(profiles)
# ブロック
for fid in fil_list:
    print pobj.callAPI('block',fid)
# report_spam 自動処理で報告してしまうので,非推奨
# for fid in fil_list:
#    print pobj.callAPI('report_spam',fid)

report_spam を呼んでないのは,巻き添えレポートを防ぐため.
キーワードに絶対の自信があれば,使ってください.
and検索,or検索したい場合は,k.search(prof) の所を工夫してください.
実装済.

twitterのspammerのfollowが多い

級にspammerが来始めたので,なんか炎上してた人をフォローしたせいか,大手画像サイトとかをフォローしたせいだろうなぁ.
で,私のfollowerリストから更に他の人に飛び火すると悪いので(もう遅いかも知れんが…),一旦非公開アカウントにしました.
spammerの自己紹介や行動には,一目見て分かるほど特徴が沢山あるので,非公開にしているうちに,自動blockするスクリプトを作ることにした.
これまでの経験上,誰かが私をfollowするのは,頻度が高くても1人/週程度なので,1週間を目処に解決を目指そう.

前提

  • 今借りているレンタルサバ上でwebに溢れているサンプルコードを動かそうとすると,モジュールが足りない
  • pythonのモジュールは,同じディレクトリに置けば動かんこともないが,twitter関係の操作に必要なモジュールを1から入れようとすると,依存関係のため入れるべきモジュールが多い上,それぞれのimport文に対応するよう配置しないといけないので大変すぎる.
  • 自宅PCは最近は1~2回/週しか立ち上げないので,それではspammerのやりたい放題

つーことで,新たな常時起動しているサバを用意するか,コードをある程度自作する必要があることが分かった.
とか思考を全部書いていくと時間が足りないので結論だけ書く.
Google AppEngineでpythonコードを動かすか,標準モジュールだけでpythonコードを動かすかの両睨みができる方法を取ることにする.
具体的には,標準モジュールだけでTwitter APIを操作できるようにしながらも,GAEの開発環境は用意し呼出し部は2パターン作った.

で,cronを使うよりも,twitterでfollowされる度に飛んでくる通知メールをGMailで受け取って,それをトリガーに動作させてみたいと思い,GAEに挑戦することにした.
追記: GAEだもの,GMail くらい直接触れるでしょ,なんて思い込みがあったが,触れないことが分かった.オーノー

GAEについて

pythonしか試していないのでpythonのことを書く.
Google AppEngine より,登録してSDKを落とす.
Windows版はmsiファイル,Linux版はただのzipファイル.
Win版は,C:\Program Files (x86)\Google\google_appengineへインストールされる.Linux版は,unzipして展開するだけ.
python2.5.x でないと動かない.

$ python (PATH)dev_appserver.py demos/guestbook

と実行すれば,サンプルが動作する.

guestbookと同じように,app.yamlとそこから呼び出されるpyファイルを配置すればOK.
制限をかけたい場合は,app.yamlの制限したいscriptを定義しているscript行の下あたりに,login: required (Googleアカウントでログインしてないとダメ) とか, login: admin (GAEの該当プロジェクトの管理者権限がないとダメ) とか書く.
GAEへアップロードするには,

$ python appcfg.py update project_name

とかする.
アクセス先は GAE の dashboard の application settings にあるが,http://application_name.appspot.com とかだと思う.
サンプルみたいに wsgiref.handlers.CGIHandler().run(application) で呼び出してもいいが,デバッグしたい時は,from google.appengine.ext.webapp import utilとインポートしておいて,util.run_wsgi_app(application)とすると,ブラウザ上にデバッグメッセージが表示される.
どうせ開発環境では,dev_appserver.pyを呼び出している窓にも同じメッセージが表示されるけど.
詳しくは,ユーティリティ関数 - Google App Engine - Google Codeとかを参照のこと.
あと,simplejsonとか,ちょっと欲しいモジュールは入っていたりするけど,oauthtwitterとかニッチなのはない.
外部ファイルからクラスをインポートしたい時は,同じフォルダにpyファイルを置いてimportすれば,開発環境では動く.GAE上では試してないので不明.

twitterAPIについて

仕様がよく変わるので,公式の最新のもの(英語)Documentation | Twitter Developersの REST API を見た方が早い.
BASIC認証はもう使えないらしいので,OAuth認証を使う覚悟をする.
情報取得系はGETで叩き,情報更新系はPOSTで叩くRESTful I/F.
応答はJSONかXML.
アクセストークンの発行が死ぬほど面倒くさいので,頑張る.私は,やる夫と Python で学ぶ Twitter の OAuth - YoshioriのBlogを参考にした.
どうせReadもWriteもやりたくなるので,Read/Writeは少なくともつけておく.
QUERYをトークンをキーに符号化したものを署名としてHTTPヘッダへ埋め込み,肝心のQUERYは上記の通りGETかPOSTで投げるので,二重に必要なことを忘れない.

で,上に記載のやる夫と Python で学ぶ Twitter の OAuth - YoshioriのBlogのソースコードを改変して作ったのがこんなクラス → py_twit.py 更新しました → twit_oauth.py
無駄に書いてるimportとかはそのうち消します.
なお,screen_nameをuser_idとする場合,idは文字列で渡すこと.
また,statuses/updateは同じ発言を2回書くと403を返す,など応答については各々調べること.

あんま関係ないが,pythonのこと調べてると,大学時代の同級生がたいてい出てくるので,よくビビる.
今回の案件では,コードの美しさというよく分からん評価軸について語っていたので,反映しなかった.

python へ パッケージをインストール

[hadacchi_blog : memcachedとpython (修正追記)](/p_blog/article.php?id=706)で書いた記事に更に追記.
cygwinのpythonにパッケージを追加したくなったので,やってみた.

  1. [setuptools](http://pypi.python.org/pypi/setuptools#downloads)のソースをDL.
  2. 次のコマンドでsetuptoolsをインストール.
    $ tar xzf setuptools-0.6c9.tar.gz # ファイル解凍
    $ cd setuptools-0.6c9
    $ ./setup.py build
    $ ./setup.py install # なんで,buildとinstallがいるのかよく分からん
  3. [python-memcache](http://www.tummy.com/Community/software/python-memcached/)をダウンロード.
  4. setuptoolsと同じ要領でインストール.
    $ ./setup.py build; ./setup.py install # 1行にまとめただけ