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については,そのうちまとめる.

回線の死活監視

自宅の回線がよく切れるので,いい加減にムカついてきた.
とはいえ,どの程度切れているのかデータがないので計測することにする.

参考にしたのは,Perlでフラッシュする方法: 小粋空間

ただし,このままだと最後のperlの出力をファイルリダイレクトした時に,バッファリングされてしまう.
きっちりCtrl-dなんかで落とせばいいんだろうけど,うっかりCtrl-cで落とすと,バッファリングされている出力が電子の藻屑と化してしまう.
そこで,以下のように$|への代入でバッファリングを無効化するようコマンドを改造した.
あと細かいとこでいえば,24時間監視を1週間くらいしたく,200msでpingを打つと怒られそうなので,ちょっとパラメータもいじった.

$ ping hogehoge.destination.address -i 60 | perl -MPOSIX -ne "local $|=1;print strftime('%Y-%m-%d %H:%M:%S ',localtime), $_" > output.filename

ちなみに,多段のパイプやリダイレクトについては【bash】リダイレクトとパイプを理解する(3) - 私は素人サーバ管理者が参考になったものの,今回はそんな複雑なことにはならなかった.

linux サーバをいくつも越えて

直接接続できないリモートホストにsshとかscpとかする話.
基本的には,SSH力をつけよう - SlideShare なんだけど,いくつかできない点があったのでメモ.

ログインできないけど socks proxy として動作する linux を通過

Host HOSTA
    User         [A_USER]
    HostName     [A_HOST_FROM_SOCKSHOST]
    Port         [A_PORT]
    ProxyCommand nc -x [SOCKSHOST]:[SOCKSPORT] %h %p

ブラケット内を変更する.A_* は A の,って意味.FROM_* は,* から見て,という意味.
netcat は netcat-traditional では -x オプションが使えないので,netcat-openbsd にしないとだめポ

socks でアクセスできる host からなら届く linux

Host HOSTB
    User         [B_USER]
    HostName     [B_HOST_FROM_A]
    Port         [B_PORT_FROM_A]
    ProxyCommand ssh -W %h:%p HOSTA

多分,nc でも同じことできるけど,ssh のオプションでトンネル.

これを Windows (WinSCP) でやる

socks の設定は「プロキシ」へ,踏み台の設定は「トンネル」へ.
多分,putty も似たような設定すればいいけど,私の環境だと wakeonlan とかの関係で一度 HOSTA に入るので,そんな設定は使うことがなく,よく知らない.

Rlogin という名前の ssh クライアント

作者のページはここ → rlogin/telnet/ssh(クライアント)ターミナルソフト
タブ型sshクライアントとして使っていて,画面分割してターミナルを表示できるのが便利かなと思って入れてみた.
結局,接続先が1つの場合は使い勝手がtmux には及ばず,かつ tmux のコマンドが使用不能となってしまったため,使用を断念した.

不満点

  • tmux ではペーンの移動を上下左右に自由にできるが,Rlogin では順に送ることしかできない
  • 送りコマンドもTabや↑を要するなど,アルファベットキーの範囲内で操作できない
  • 窓内で tmux を使おうにも,窓のサイズ変更に割り当てている Ctrl-t,Ctrl-h のコンビネーションが効かない

puttyのいけてないところは,公開鍵が独自フォーマットなところなのだが,設定の手間さえかければ,それ以降は気にせず使えるので,どちらかというと putty に軍配があがった.

debian でファームウェアインストール

-- 追記(8/27)
やっぱ直ってなかった。

症状

  • sshしてると10秒~5分の間で、なんかセッションが切断される。
  • 切断された時は、新しくsshのセッションを開こうとしても接続に失敗する。
  • winscpを使っている分には、切断を挟んでいても気付かない。
  • サーバ側のログにはクライアントがタイムアウトしたと出ている。
  • クライアント側では、「Network Error: Software caused connection abort」とログが出る。
    (↑このエラーは、Windowsが吐く一般的なNWエラーらしく、あんまり手掛かりにならなさそう?)
    参照:10.14 ‘Network error: Software caused connection abort’
  • ブログアクセスなど負荷のかかる動作をさせると高確率で切れる。

やったこと

直感的に、ログ出力の変更とか関係ない気もするが、不思議とmysqlのログを追加することでblogアクセスによる切断が生じなくなったので、因果はともかくとしてメモっておく。

  • とにかく/var/logに出ているエラーを消す。
    • bind9のv6を動作させなくする。
      /etc/bind/named.conf.optionslisten-on-v6 {none;}; では不十分で、/etc/default/bind9 を書き換え。

      - OPTIONS="-u bind"
      + OPTIONS="-u bind -4"
      
    • sambaのprinter_cups関連の設定を無効化。
      + load printers = no
      + printing = bsd
      + printcap name = /dev/null
      

      どれが欠けてもエラーは継続。

  • sshまわりの設定修正
    • sshdの設定を多少変更 (ClientAliveInterval を少し伸ばした)
    • sshクライアントからkeepaliveを発出
    • ログを単独出力
      - SyslogFacility AUTH
      + SyslogFacility LOCAL5
      
      - auth,authpriv.*;(hogehoge)                      /var/log/auth.log
      + auth,authpriv.*;(hogehoge);local5.*             /var/log/auth.log
      + local5.*                                        /var/log/sshd.log
      
  • mysqlまわりの設定修正
    • slowlog の出力

-- 追記ここまで

自宅の debian マシンのネットワーク接続が、突然不安定になった。
syslog や、他のログを見ていても原因はよく分からなかったが、dmesg などで NIC の firmware が見付からないというエラーが出ていた。
r8169 0000:03:00.0: firmware: agent aborted loading rtl_nic/rtl8168e-2.fw (not found?)
みたいな感じ。

ファームウェアの名前からベンダを調べて、上記の場合は Realtek なので、

# aptitude search firmware # ← firmware のパッケージを探す
# aptitude install firmware-realtek

と単純に実行するだけで、なんとなく解決したっぽいので良しとする。

MVNO+docomo スマホで USB tether

ICS になって APN 設定をアプリから変更できなくなったらしく,GB で使っていた FixAPN というアプリが使えなくなった.
で,FoxFi と PdaNet で実現することになった.

  • FoxFi (WiFi Tether w/o Root) を google play から tethering router となるスマホに導入.(無料版の無印で良い)
  • PdaNet+ -- Internet Sharing for Android を PC に導入.
  • スマホの開発者オプションから USB debug を on にしてから,FoxFi の Activate USB Mode をオン
  • PC からPdaNet を起動して USB で接続する.

netcat-traditional

Debian wheezy を入れて,Proxy 越しに ssh をしようとしたら,それまでの .ssh/config では通らなかった.

nc: invalid option -- 'x'
nc -h for help
ssh_exchange_identification: Connection closed by remote host

理由は,netcat-traditional というパッケージが入っていて,nc コマンドに proxy を指定する -x オプションがないから.
netcat-openbsd とかを入れればOK

apache のエラーログを真面目に読んでみた

最近、サイト運営の時間がとれず、放置していたので、たまにはログをチェックしようかと三が日からログを読んでいた。
ログは error.log (うちのサーバの場合、ログファイルはvhost毎に変えてるので、標準のログファイル名で書いておく。以下同様。) を眺めて、サイトのコンテンツ絡みの場合は、man もしくは google 先生、明らかにアタックの場合は該当アクセスの IP アドレスを /var/log 内全検索するなどして、分析した。
分かったのは、思ったより脆弱性の入ったサーバやスクリプトが巷には溢れていて、下に列挙していない file does not exist に、大量にそのキーワードが含まれているということ。
自分が入れているツールへのアタックかも、と感じたら、その IP でログの全検索をかけることと、そのツールが最新版か確認することはした方がよさそうだ。

んでわ、さっそく。

PHP Warning: Unknown: failed to open stream: Permission denied in Unknown on line 0
PHP Fatal error: Unknown: Failed opening required 'FILEPATH' (include_path='.:FILEPATHES') in Unknown on line 0
2行でセット。
旧サイトの投稿スクリプトのパーミッションを 000 にしていたために、出ていた。
パーミッションを変更したら(投稿フォームを無効化してから)、直った。
Directory index forbidden by Options directive: FILEPATH
index ファイルを設置してなくて、Indexes をセットしてないディレクトリへのアクセスがあったログ。
index.htm を置けば OK。
no acceptable variant: FILEPATH
コンテンツネゴシエーションに失敗。具体的には、MultiView の設定に絡むファイルしかなかった。
だが、telnet アクセスで再現しなかったので、切り分けはこれ以上はできなかった。うーむ…
アクセスログによれば、Majestic-12 とかいうプロジェクトの bot らしい。
File does not exist シリーズ
ほとんどが、旧コンテンツのメタタグとかで読み込む設定にしていた旧faviconなど。
あといくつかが、色々な脆弱性へのアタック。目についたのだけ、ピックアップする。
File does not exist: FILEPATH/w00tw00t.at.blackhats.romanian.anti-sec:)
phpMyAdmin の脆弱性アタック。
この辺りが詳しい → phpMyAdminを狙った攻撃観察 - ろば電子が詰まっている
そんな軟派なものは入れていないので、関係なし。
w00tw00t.at.hogehoge 自体は、他にも色んな種類があるらしいので、他のアクセスが出てきたらそれはそれで注意が必要ぽい。
File does not exist: FILEPATH/notify-NotifyUser_NONE_071217
なんか日本語サイトだと、楽天とか伊藤忠のイントラネットから web アクセスする時に、業務と関係ないサイトにアクセスする時に使う proxy が notify-NotifyUser1 だってネタがひっかかる。
ただ、web サーバにそんなファイルアクセスを起こす意味が分からないので、なんか違うんじゃないかという気がしている。
なおうちへのアクセスでは、AhrefsBot/5.0 という bot によるものらしい。ポートスキャンという説もあるが、うちのサイトでは、web クローラとしての動作のみが記録されていた。外側の FW で落ちているだけかも知れない。
client sent HTTP/1.1 request without hostname (see RFC2616 section 14.23): /w00tw00t.at.ISC.SANS.DFind:)
これは DFind のアタックを受けた痕跡らしい。
DFind については、この辺を見たらいいのかな… → Hacktool.DFind | シマンテック 日本
んで、対策は色々と書かれているんだけど、だいたい多いのは iptables の string モジュールで文字列マッチをしてパケットをドロップさせろ、というもの。
(参照: Blocking w00tw00t scans - Myatu's, GET /w00tw00t.at.ISC.SANS.DFind:) HTTP/1.1)
日本語のサイトでは、400 が返ってたら心配ないよ、って書かれてて、多分その元ネタは後者の参照ページあたりだと思うのだけど、なんで you should not worry about it なのかが、見付けられなかった。
少なくとも Hacktool.DFind が正しいのであれば、apache と関係ない SQL サーバへのアタックも含まれているのだから、apache のログだけ見て心配しなくて良い理由が不明。
いちおう、サーバの外側の FW で弾いた上で、サーバ内のソフトウェア FW でも弾くようにしているので、apache 以外のログには残っていないのだと信じたい。
script not found or unable to stat シリーズ
4割が、過去に google calendar に登録していたが現在ではもう登録していない URL へのもの。
他は、色々な php システムのバグへのアタックくさい。
Invalid URI in request GET HTTP/1.1 HTTP/1.1
なんか、アタックの最初に "GET HTTP/1.1 HTTP/1.1" をつけてくるツールがあるっぽい。
google の連文節検索(という名前は私が勝手につけたが、よーはハイフンで単語を繋げる検索)で、http-1.1-http-1.1 として検索すれば、大量のログが出てくるので、自分のログと類似のものを探せばよい。
このログが理解できなくて、telnet してたら自鯖の設定ミス(ServerTokens を設定していない)のために HTTP ヘッダに OS と apache のバージョンが流れていることに気付いたので、修正した。
ServerSignature を未設定または Off にすると、エラーレスポンスの末尾の OS や apache バージョンが入ったシグネチャが消えるので安心していたら、HTTP レスポンスで返していたとは…
ちゃんとヘッダまで確認していなかった自分の甘さに反省。
PHP Notice:
Undefined variable: comment_field_name in FILEPATH/comment_form.tpl.php on line 64
多分、P_BLOG のバグ。reply.php の中で、エラーメッセージ内の .tpl.php を呼ぶ前に、add.php とかみたく $comment_field_name をセットしていないのが原因。

特定ドメインでアクセスされた場合のみ、移転後ページへのリンクを張った移転メッセージを表示

前使っていたドメインから現在のドメインへリダイレクトをしていたが、どうにもアクセスが完全には途絶えていないらしい。
んで、リダイレクトしているホストやリファラから、古いドメインへリンクしているサイトを特定するために、また移転したよメッセージを表示するために、ワンクッションはさむことにした。←ほぼ検索エンジンか更新されていない掲示板等であることが判明したので,rewriteはやめた
なんとなくのイメージだけで作業開始したものの、実際に作業してみるとハマりポイントがいくつかあったため、メモに残す。

やったこと

  • 旧ドメイン配下の任意ページ(存在の有無によらず)へのアクセスがあった場合、移転メッセージを提示する。
  • 同居する他のドメイン名で80番ポートにアクセスが飛んだ時は、明らかに存在するはずのルートへのリクエストのみ403を返し、他のリクエストの場合は404を返す。
  • 移転メッセージには、http[s]://新ドメイン/元リクエストのパス?元リクエストのQUERY_STRING へのリンクを貼っておく。
  • アクセスログを mysql に残す。

ハマりポイント

任意ページへのアクセスの受け方
分かりにくいけど,これはもう使ってない方法.
任意ページへのアクセスを、上の処理を行なうスクリプトへ元のGET文字列も含めて飛ばすのは意外と難しい。
.htaccess を使って、Rewrite でリクエストを書き換えると簡単にできる。ただし REFERER はこの時点で死ぬので、REFERER を取りたい場合は他の手段を講じる必要がある。
具体的な記述は、例えば

RewriteEngine On
RewriteCond %{HTTP_HOST} ^(old\.domain\.com)(:80)?
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ http://redirect.domain.com/?$1 [QSA,R=301,L]

といった感じ。
QSA は QUERY_STRING を最後に付与する指示。

php での HTTP ヘッダの返し方
php で HTTP ヘッダを返して、特定のアクセスの場合に 404 とか 403 を返す方法はいくつかある。
詳しくは php 公式ページのリファレンスを見るとして、私の環境では次のコードがうまく動いた。
あらゆる出力 (HTML の DOCTYPE や XML 宣言を含む) より前に記述しないと意味がないことに注意。

header("HTTP",true,404);
header("Content-type: text/html; charset=iso-8859-1");
include('./404.php');

404.php は、httpd の 404 ドキュメントを模したファイルで、上の .htaccess を用いた場合は QUERY_STRING に アクセスパスがついてくるので、展開して使えば、それっぽく表示できる。
apache2 の場合、404 や 403 はブラウザの設定に依らず charset が iso-8859-1 で返っていたので、ヘッダに入れた。

今時のmysql

なんか php 公式のリファレンスに mysql は古いから mysqli を使え、と書かれていたので、使うことにしたが、ちょっと梃摺った。

$mysqli = new mysqli("DBHOST", "DBUSER", "DBPASS", "DBNAME");
$mysqli->query($query);

mysqli では、特段の理由がない限りはセッションの close をしないらしい。
$query は使いたい SQL をそのまま書いたもの。

いざ文書にしてみると、迫力のない tips だな。
他にも元アクセスのパターンによって何度か print デバッグしないといけないと思うけど、頑張れ!

ubuntu とか centos で resolv.conf が勝手に書き換えられるのを止める

最近は linux を使っていても PC 起動のタイミングで resolv.conf が勝手に書き換えられるようになってしまった。
普通にインストールすると /etc/resolv.conf は symlink なのだが、通常ファイルに置き換えてもダメ。
昔は dhclient.conf の書き換えで直っていたのだが、最近はそれも効かなくなり苦労していた。

[ Fedora ] resolv.conf が勝手に書き換えられていた - NetworkManager を無効にする: Fomalhaut of Piscis Australis
がドンピシャの記事だったので、メモる。
CentOS の場合は載っているが、一応転載しつつ、ubuntu の場合も書いておく。

CentOS の場合: # yum erase NetworkManager
ubuntu の場合: # apt-get purge network-manager

注意
このままだと、resolv.conf も interfaces も設定しなければならないので、この状態で遠隔操作のままリブートしないこと!!
interfaces は最近、編集する機会が減り、検索結果にも出にくくなってきた気がする。
eth0 inet static とかで検索したら出るよ。

なお、 ubuntu だと記事の CentOS のように network-manager-pptp とか関連っぽいものは消えてくれなかった。
記事では erase を使っている辺りに憎しみを感じるが、大変よく分かります。