KVM に Kernel Samepage Merging (KSM)

いわゆる自宅サーバーの機器を更新し、これを機にソフトも KVM の上に載せてみることにしました。

用途と向きによって仮想サーバーを切り分けます。ホストも複数のゲストも、ぜんぶ Debian で同じバージョンです。ネットの検索によって得た知識でも何とかなります。

当初気づかずに、後から「こういうのがあるのか」と気づいたのが、KSM (Kernel Samepage Merging) でした(解説はたとえば Linux カーネル共有メモリーの徹底調査)。

KVM on Debian Squeeze – My Notes を参考に設定しました。

ホストとゲストの両方で /etc/rc.local を編集し、次を追加します。

echo 1 > /sys/kernel/mm/ksm/run
echo 300 > /sys/kernel/mm/ksm/sleep_millisecs

再起動後、/sys/kernel/mm/KSM/pages_sharing がゼロより大きな値になっていれば、有効になっています。

KSM は使わないほうがいい?

さて、はじめ気づかなかった KSM についての情報に行き当たったのは、とりあえずしばらく動かしていると、ゲストOSが徐々にメモリを食いつぶしていくという現象をどうにかしたいと思って調べてみたからです。 そこで上記のように KSM を有効にしてみたのですが、引き続き検索していると、「KVMで複数VMを起動してVM間の相互作用を減らしたいときに考えること 」という記事を見つけました。
  • メモリはオーバーコミットしない
    • メモリをオーバーコミットするとろくなことがありません。なので要るだけ積みあげます。安いし。
      • 特にゲストOSがlinuxの場合、メモリはあるだけ使おうとする。本来だとキャッシュとしての有効活用になるキャッシュを積極的に使う戦略が、メモリオーバーコミット環境下ではswapを多発させる原因になってしまう

うーむ。これが原因かもしれません。個々のゲストはさほど深刻な負荷があるわけではなし、一方が急に働くことになっても他方はそれほどでもないことがほとんどなので、個々のゲストのメモリ割り当てを、ほぼ実メモリと同じくらい、としていました。すなわち、ゲストに割り当てたメモリを合計すると実メモリの数倍になっていました。しかし、この記述によると

    • メモリは足りてるはずなのでKSMは停止する

確かに KSM はホストに負荷がかかるので、動かさずに済むならそのほうがいいかもしれません。

もうしばらく様子を見ながら(まだ理解できていないことも多いし)、考えたいと思います。

第何何曜日 再び

昨年末に「2013年に読まれた記事トップ10」を書く際にサイト統計を見たのですが、圏外すぐに「第何何曜日」がありました。古い記事ですが、それなりによく読まれています。

ところでいま調べなおしてみたら、PHP の strtotime() には便利な機能があるではありませんか。前の記事を書くときには見落としていたのか……。前の記事の脚注を見ると PHP 4 と書いているから、まだ使えなかったのかな。

というわけで、いま風に書き直してみます。

Y 年 m 月の第 i の w 曜日は、その月の d 日か

PHP ならこれはもう簡単で、そのまま書くだけです。

Y, m, i, w という記号では書きにくいので、具体的に「2013年12月の第3木曜日」を例にすると、

$d = date("d", strtotime("third Thursday of Dec 2013"));

です。「今月の第3木曜日」なら “third Thursday of this month” ですし、「次の第3木曜日」なら “third Thursday” だけです[1]。なんて簡単なんだ。

ここに日本語が使えると便利なのですが、さすがにそこまではできないようです。というのは、「おととい」を指定するのが、英語だと “day before yesterday” で、まどろっこしいからです。日本語だと今日の前後2日までは短い言葉があるのに[2]、英語だと前後1日までで、その先は持って回ったような言い方になってしまいます。話はやや違いますが、PostgreSQL の SQL でも “today”, “yesterday”, “tomorrow” のキーワードは使えるのですが(ほかの DB はどうなんでしょう?)、「おととい」をしょっちゅう入力したい私はしかたなく日付を使っていて、どうにかしたいなあと思っているのでした。

さて、前の記事の際の動機はゴミの収集日でした。「第 2, 4 月曜日」だとすると、次のゴミの日は

$gomi = date("Y-m-d", min( strtotime("second Monday"), strtotime("fourth Monday") ) );

で済みます。

ちょうど当該日にこれを実行すると、その日になります。しかしゴミの収集時刻 14:00 を過ぎていたら次の収集日を出力してほしいですね。

$base = ( date("G") <= 14 ) ? strtotime("today") : strtotime("tomorrow");
$gomi = date("Y-m-d", min( strtotime( "second Monday", $base ), strtotime( "fourth Monday", $base ) ) );
}

ちょっときれいじゃない気がするけど、こんなのでいいのかなあ。

  1. なお、この書式でも「第5週」の場合は注意が必要です。
  2. その先も「さきおととい」とか「しあさって」あたりまでは、たまに耳にします。

WordPress のプラグインを公式リポジトリと GitHub に公開する

昨年末に公開したプラグイン wp_mail to XMPP は、Git で管理しつつ作ってみました。ひととおりできたところで GitHub に公開。それから公式リポジトリを申請して、こちらは SVN なので、git-svn で送ろうとしてもなんだかうまくいかず、とりあえず手動で(ローカルの別の場所にコピーして、svn で。つまり Git とは完全に切り離して)作業しました。

それからゆっくり見なおしてみました。

WordPress公式プラグインリポジトリに登録しているプラグインをGitHubで管理する」や「公式リポジトリのWordPressプラグインをGitHubでバージョン管理できる『wp-plugin-in-github』使ってみたよ」を見て、これは便利そうだと思ったのですが、tai さんと同じく readme と本体のバージョンが合わないエラーとか、

  • 手元で、勉強がてら git flow を導入していたので、そのままでは構成が合わない
  • 翻訳ファイルの作業は自前の Makefile と重複する

など、そのままでは使えませんでした。そこでその中身を個々にやるような形ですが、次のようにやってみました。参考にしたページは

です。

手順

出発点は

  • 既に公式リポジトリに何らかがある状態
  • 公式リポジトリの branches は使っていない
  • 手元では git-svn がインストールされている状態

という中途半端なところなので、どれほど意味があるのかわかりませんが、自分のメモとして。そのため、例とするプラグインは wp_mail to XMPP です。

公式リポジトリと連携

まず、trac で見るなどして、はじめと終わりのリビジョン番号を調べておきます。

それと、内容が

mako09 = Mako N <mako@pasero.net>
plugin-master = Mako N <mako@pasero.net>

というファイル(名前は何でもいいですが、ここではauthor.txt とします)を作っておきます。

次に、ローカルの何もない場所で、

git svn clone --no-minimize-url -s -A author.txt -r829275:829448 http://plugins.svn.wordpress.org/wp-mail2xmpp/

を行います。-r(はじめ):(終わり)を付けます。この -r ではじめと終わりを指定すれば膨大な時間がかかることもありません。-r(はじめ) だけでは、はじめのリビジョンは申請によって向こうで(plugin-masterに)場所だけ作ってもらったものなので、空っぽのディレクトリだけになります。

手元で作業していた git リポジトリで、このコマンドを実行してもうまくいきませんでした。これ自体はうまくいっているように見えるのですが、後段の git svn rebase が “Unable to determine upstream SVN information from working tree history” と言うのです。何もない場所でやるとうまくいきます。

git の設定

git config user.name などの設定を行います。git flow を使うため、

git flow init

も行います。

ブランチの作成

git checkout -b svn

で、svn という名前のブランチを作ります。

svn → 公式、master → gitHub で、develop は手元、という構成です。svn と master の違いは、今回の例では公式には置かないファイル(Makefile, LINGUAS)だけです。

git svn fetch
git svn rebase

で、同期させておきます。

ローカルから取り込み

既に作業していたローカルのリポジトリから

git checkout master
git remote add temp file:///...
git pull temp master
git checkout develop
git pull temp develop
git remote remove temp

で、取り込みます。

GitHub との連携

git checkout master
git remote add origin git@github.com:mako09/wp-mail2xmpp.git

として、

git pull origin master

で、同期させておきます。GitHub には何もなくてこれからという場合は、むしろ git push することになります。

以上で、GitHub とも公式リポジトリとも連携した git リポジトリが手元に出来ました。これまで作業してきた手元の git リポジトリは不要になります。

その先に作業してきた git リポジトリに、git-svn で svn リポジトリを連携させることができればよかったのですが、上述したようにうまくいきませんでした。このへんの仕組みはよく理解できていません。そのため、このようなとてもまわりくどい手順になってしまいました。

以後の作業

以後は、手元の git リポジトリで作業します。master が完成したら(git checkout master の状態)、GitHub に送ります。以下、まだその場面ではないので十分試していませんが、

git push           # GitHub に送られる

続いて、ブランチ svn に移って、svn に送ります。

git checkout svn
git merge master   # --squash をつけるか、merge ではなく個々のファイルを checkout のほうがいいかも
git svn fetch      # 念のため
git svn rebase
git svn dcommit    #公式リポジトリに送られる

git のタグは(ブランチ master で)

git tag 0.9

のようにします。これを GitHub に送るには

git push origin 0.9

です。

svn にタグをつけるのは(ブランチ svn で)

git svn tag 0.9

のようにします。メッセージは自動的に “Create tag 0.9” のようになります。このコマンドで公式側にタブができます。

wp-login.php への攻撃に対処する

サーバーにログインして作業しようとしたら非常に反応が悪く、どうしたことかと見てみると(普段と違って、コマンドを入力してから応答があるまで数十秒も待たされるのでヒヤヒヤしましたが)、大量の apache プロセスが走っていて、ログには wp-login.php への大量のアクセスが記録されていました。最近よく耳にする wp-login.php へのブルートフォースアタックです。

1つの IP アドレスから5,6回のアクセスがあり、すぐに(あるいは同時に)別の IP アドレスからのアクセスがあります。結局、2時間ほどのあいだに数千回のアクセスがあり、その後沈静化しました。

特に対策らしい対策をしていなかったので、パスワードの強度のみで耐えたことになりますが、実際目の当たりにすると気持ちのいいものではありません。それにどちらかというとサーバーに対する高負荷のほうが心配です。

そこでいまさらではありますが、少し手を打つことにしました。

強いパスワードにする

今回行った対策ではありませんが、そこそこ強いパスワードにしていたので、攻撃に気づくまでの30分間ほどはこのことだけで侵入を防ぎました。

.htaccess で制限する

攻撃に気づいてすぐ、 .htaccess に次のように書いて wp-login.php へのアクセスを制限しました。これは効果てきめんで、すぐにサーバーの負荷は通常の範囲まで下がりました。

<Files wp-login.php>
Order deny,allow
Deny from all
Allow from xxx.xxx.xxx.xxx
</Files>

前述のように攻撃側の IP アドレスは絞り切れないので、Deny に列挙することができません。

さて当面はこれでしのいだとしても、正当に管理画面にアクセスするのが複数の人間で、それぞれの接続元の IP アドレスが固定していなかったりすると、Allow に列挙するのもやっかいです。

WordPress のロックダウン

IP アドレスでふるい分けられないとすれば、wp-login.php のところで何らかの制限をかける方法が考えられます。たとえば Force Email Login は、中心となる機能は「ログインをユーザー名ではなくメールアドレスで」という点なのですが、そのほかに

  • ログインに失敗すると、ログイン処理に対して10秒間 wp_die() を発火させて膨大な量のブルートフォースアタックによるサーバー負荷を軽減します。

ということも行います。

こちらの問題のサイトでは「ログインをユーザー名ではなくメールアドレスで」という機能は必要ないので(というか、ようやく「ユーザー名」が何かを理解するくらいの人たちにもう一度ログイン方法を周知徹底するのが面倒すぎるので)、その部分を削ぎ落とすことにしました。

ただ、これだと誰か(攻撃者)がログインに失敗するとその後10秒間は誰も(正当なユーザーも)ログインできません。たとえば5秒に1回、1週間にわたってログインを失敗し続けるという、もはや「ブルートフォース」とは呼べないようなぬるい攻撃(?)で、そのあいだ正当なユーザーのログインを妨害することができるという欠点があります。

やはり「IP アドレスごとに」という機能をつけざるを得ないのかもしれません。そこで、Limit Login Attempts in WordPress … のようなものにすることにしました。これだと、同一 IP アドレスから1秒間にn回のログインの失敗があったらその後m秒間、その IP アドレスからのアクセスを禁止します[1]。ユーザー名/パスワードを試みる回数は、IP アドレスをチェックしないものよりは増えますが、何も対策しないよりはずいぶん減らせるでしょう。

さて、たとえば1秒間に2か所からそれぞれ2回、3回の攻撃があった場合の対応は、この対策を行わない場合、

  • wp-login.php にアクセス → データベースでユーザー名とパスワードを調べる → 失敗
  • wp-login.php にアクセス → データベースでユーザー名とパスワードを調べる → 失敗
  • wp-login.php にアクセス → データベースでユーザー名とパスワードを調べる → 失敗
  • wp-login.php にアクセス → データベースでユーザー名とパスワードを調べる → 失敗
  • wp-login.php にアクセス → データベースでユーザー名とパスワードを調べる → 失敗

となっていたわけですが、これが

  • wp-login.php にアクセス → データベースで IP アドレスごとの失敗回数を調べる → データベースでユーザー名とパスワードを調べる → 失敗
  • wp-login.php にアクセス → データベースで IP アドレスごとの失敗回数を調べる → 失敗(403)
  • wp-login.php にアクセス → データベースで IP アドレスごとの失敗回数を調べる → データベースでユーザー名とパスワードを調べる → 失敗
  • wp-login.php にアクセス → データベースで IP アドレスごとの失敗回数を調べる → 失敗(403)
  • wp-login.php にアクセス → データベースで IP アドレスごとの失敗回数を調べる → 失敗(403)

となります。いずれにせよデータベースにはアクセスするわけで、1回の試行の負荷はどれほど違い、全体での負荷はどれほど違ってくるのでしょうか。そこまでは調べていません。

apache のモジュールで制限する

データベースへのアクセス前に制限できれば負荷を小さくできるのは明らかなので、前述の .htaccess によるものに代わる、apache のモジュールはないか探してみました。

mod_bw は回数制限ではない、mod_dosdetector は特定ページ(wp-login.php)のみに限定できない、mod_dosblock は IP アドレスごとではない……と、どれも一長一短のようです。ModSecurity だと要求にかなっていそうですが、これだけのためには機能が豊富すぎてルールが難しく、まだ理解できていません。

mod_evasive というのは、IP アドレスごとのアクセス回数を制限できますが特定ページの指定はできないようで、 mod_dosdetector と同じです。しばらくのあいだ、これを試してみることにしました。


管理するいくつかのサイトで、実情に合わせて上述の対策のいずれかをとることにしました。

  1. 実際は、1回の失敗ですぐ制限をかけるようにするため、このページのコードを少し修正しました。

Debian Jessie での git-completion.bash のある場所

git の状態に応じて bash のプロンプトを変更するようにしていたつもりが、ふと気がつくと、Debian の バージョン Jessie (2103年9月現在では testing) の環境では、機能していない。

何しろしょっちゅう使っている訳でもないので、久しぶりだといろいろと状況が変わっていて、以前うまくいっていたものがそうでなくなっていることがある。

Debian Wheezy 以前なら /etc/bash_completion.d/git があったのだが、Debian Jessie の環境ではそのディレクトリを見ると、git はなくなっており、代わりに git-prompt というのになっている。

どういう訳でなくなったのだろう、と検索しているうちに、git: /etc/bash_completion.d/git gone という情報を見つけた。/usr/share/doc/git/NEWS.Debian.gz に説明があり、要は、以前の /etc/bash_completion.d/git/usr/share/bash-completion/completions/git に移動したので、自分の .bashrc でこれを読み込むようにしている場合は変更せよ、ということだった。

プラグインやテーマの国際化を少し楽に

すっかり忘れていたのですが

と思い出させてもらったので、ここに書いておきましょう。

これは世界で人気のプラグインやテーマの開発者向けの情報です。もとより私はプラグインもテーマも書かないし、したがって世界中から言語ファイルを送られて「大変だー」なんて思いをしたことはないのですが、たまたまその声を聞いて Makefile を miya さん に贈り、それがここで使われています。

WordPress の国際化

おさらいです。WordPress では gettext ライブラリおよびツールを使用して国際化します

プラグインやテーマの開発者は、その中の翻訳されるべきメッセージに __()_e() などのマークを付けておきます。こうしたプラグインやテーマから、マークされた文字列を抽出して POT ファイルと呼ばれる、翻訳者にとって原本となるものを作成します。この作業は WordPress の本家で配布されているスクリプト wordpress-i18n tools を入手して、これで行います。

翻訳者はこの POT ファイルを元に、メッセージをそれぞれの言語に翻訳した PO ファイルと呼ばれるものを作り、これを開発者に送ります。

開発者は PO ファイルを、実行時にすばやく読み込まれるようにバイナリ化された MO ファイルと呼ばれるものに変換して、これをプラグインやテーマに同梱して配布することになります。

少し楽に

完全に出来上がっていてメッセージが不変で、たまに「新しい言語に翻訳したよ」と送られてくるものを追加するだけなら、手作業でもさほどでもないのでしょう。しかし現時点で既に各言語の翻訳ファイルがあるところに、元のプラグインやテーマに手を加えてメッセージが変わってしまい POT が変更になって、これをいちいち各言語の PO に反映して、MO を作りなおすのがメンドクサー、という工程を自動化するのがこの Makefile です。

プラグインやテーマの開発者にとって詳しい説明は不要でしょうから、あとは簡単に紹介します。

POT の生成には上述の wordpress-i18n tools が必要です。

make pot
で POT を生成します。あとは外部ファイル LINGUAS に作成したい言語の locale を書いておき、適宜
make

で、必要な MO までが生成されます。


POT 生成の部分は本家のスクリプトですし、後半の部分も gettext 関連のところでよく見かけるものから拝借して、前半に WordPress 特有の設定を加えたものです。私ができるのはこの程度です。あとは誰かすばらしいプラグインやテーマを作ってくださいね。

Firefox に新しいプロトコルを教える

前の記事のようにして、WordPress の記事に xmpp: のリンクを書けるようになりました。しかし、それを読む側のブラウザが「そんなの知らん」と言っては役に立ちません。

手元の環境は Linux (Debian) 上の Firefox (Debian では Iceweasel という名前) ですので、それについて書きます。その他の環境については、wiki.xmpp.org に情報があります (ただし Firefox の例を見てもやや古い情報のようです)。

ブラウザ

Firefox について、mozillaZine の Register protocol を参考にしました。

  1. ロケーションバーに about:config と入力します。
  2. 右クリック→新規作成→真偽値 とします。
  3. 「設定名」を network.protocol-handler.expose.xmpp とし、値を false とします。
  4. 次にこの xmpp: のリンクをクリックしたときに、どのアプリケーションでこのリンクを開くか、聞いてきます。これはあとで 編集→設定→プログラム のところで変更できます。

Chromium (Chrome) については、常用していないので詳しくありません。ヘルプを見ると、設定→高度な設定→コンテンツの設定 の「ハンドラ」あたりでどうにかするのでしょうか。

XMPPクライアント

ところで、この方法ではアプリケーションに URL が渡されるのですが、それを受け取って開きながら起動できる XMPP アプリケーションを2つ紹介します。

ひとつは Gajim です。ただし gajim-remote handle_uri %s という形で呼び出すのですが、Firefox から URL 以外の引数を渡せないようなので、ラッパーを介することにしました。

もうひとつは、ウェブアプリの Jappix です。Jappix の オプション→一般→XMPPリンク の「JappixでXMPPリンクを開く」をクリックすると、Firefox の、どのアプリケーションでこのリンクを開くかという選択肢に Jappix が入ります。