仙石浩明の日記

システム構築・運用

2011年7月31日

OpenVZ な ServersMan@VPS で独自 OS を動かしてみた

国内に (自宅以外で) IPv6 が使えるサーバが欲しかったので、 ServersMan@VPS を使ってみた。 ServersMan@VPS は仮想化プラットフォームが OpenVZ なので今まで敬遠していたのだが、 Osukini サーバ (Xen) も、 さくらの VPS (KVM) も、 いまのところネイティブな IPv6 はサポートしていない (さくらの IPv6 は 6rd) ので、 やむなく契約してみた次第。

Xen や KVM などの完全仮想化と異なり、 OpenVZ はホストOS のカーネルがゲスト OS のカーネルとしても使われる。 つまり VPS (Virtual Private Server) サービスのユーザが別のカーネルを立ち上げることができない (ServersMan@VPS Perfect は完全仮想化だが月額 3150円と高いのでここでは考えない)。 OS は CentOS, Debian, ubuntu から選ぶことになる。

しかしながら、 私が個人的に管理しているサーバは、 全て OS を独自の 「my distribution」 (便宜上ここでは GCD OS と呼ぶ) に統一していて、 全サーバでディスクの内容を同期させている。 つまりある特定のサーバ固有の設定情報も、 全サーバが共有している。 共有していないのは各サーバのホスト名と、 秘密鍵などごく一部の情報だけ。 だからサーバが壊れた場合も、 新しいマシンを用意して他のサーバからディスク内容を丸ごとコピーするだけで済む。

私個人で管理しているサーバ (もちろん会社で運用しているサーバは除く) は、 仮想環境も含めると 20台ほどになるので、 異なる OS はできれば管理したくない。 ServersMan@VPS で提供されるカーネルを使うのは (OpenVZ の仕組み上) 仕方がないとして、 ディスクの内容は GCD OS と入れ替えることにした。

稼働中のサーバをいじるとき重要なのが、 トラブった時に 「元に戻せるか?」 ということ。 元に戻せるなら多少の失敗を恐れることはない。 ほとんどの VPS サービスは、 最悪の事態に陥っても初期化すれば元通りになるので、 操作をミスっても一からやり直せばいいのだが、 OS を入れ替えようとする場合は (当然) 新しい OS 一式を送り込む必要があり、 初期化するとこれが消えてしまうので再度転送する羽目になる。 GCD OS は最小セットで 7GB 近くあるので、 何度も転送を繰り返したりすると VPS サービスの契約ネットワーク帯域を使いきってしまう。

幸い、 ServersMan@VPS に帯域制限は無いが、 一日に 何十GB も転送していたら、 きっと管理者に睨まれるはず。 そこで、 どんなに失敗しても、 再起動すれば ssh でログインできる状態に戻るような OS 入れ替え手順を考えてみた。 ssh でログインできさえすれば後は何とでも修復できる。 逆に言うと ServersMan@VPS のようなコンソールが提供されない VPS サービスでは ssh でログインできなくなると万事休す、 残された復旧手段は初期化しかない。

まず ServersMan@VPS Standard プランを契約 (月額 980円) して、 Ubuntu(64bit) の最小構成 (シンプルセット) を選択。 ssh でログインして netstat でソケットを開いているデーモンを調べ、 片っ端から dpkg --purge (アンインストール) する。 もちろん sshd (ssh サーバ) だけは purge してはいけない。 syslog や cron などフツーは決して purge しないデーモンも遠慮無く purge する。 ps したとき sshd のプロセスのみが表示されるような状態になるまで purge しまくる。 で、 一通り purge し終わったら念のため再起動してみる。 もしここで ssh でログインできなくなってしまっていたら、 初期化して振出しに戻る。

この時、 sshd が 22番ポートで listen している場合は、 GCD OS が起動する sshd と衝突するので、 22番ポート以外に変えておく。 幸い ServersMan@VPS の場合は sshd が初めから 3843番ポートで listen する設定になっていた (なぜ?) ので、 そのままにしておく。

次に、 デーモン類以外のパッケージも、 残しておくとディスクの肥やしになるだけなので、 できるだけ purge する。 とはいっても、 多くのパッケージが数MB 以下で容量的には誤差の範囲なので、 無理に purge して再起不能状態に陥るリスクを冒すより、 ディスクの肥やしにしておいた方がマシ。 で、 一通り purge し終わったら念のため再起動してみる。 もしここで ssh でログインできなかったら、 初期化して振出しに戻る。

こうして netstat -nap しても ps axf しても sshd 以外のプロセスが一切残っていない状態になったら、 いよいよ GCD OS 一式を送り込む。

senri:/ # mirror -v -r /usr/local/gcd -e 'ssh -p 3843' 'core64[183.181.54.38]' > /tmp/serversman &

mirror というのはサーバ間で GCD OS の同期を行なうためのスクリプト。 64bit (x86_64) の最小セットをコピーするために引数として 「core64」 を指定した。 このスクリプトは、 同期すべきファイルのリストを作成して rsync を呼び出す。 「-e 'ssh -p 3843'」 オプションは、 そのまま rsync に渡される。 このコマンド列で GCD OS 最小セット約 7GB が VPS の /usr/local/gcd ディレクトリ以下へ丸ごとコピーされる。 7GB の転送にどのくらいかかるかと思っていたら、 わずか 10分弱で終わってしまった。 100Mbps 以上の帯域ということになる。 結構すごい。

ホスト名を chiyoda.gcd.org に設定し、 このサーバ専用の秘密鍵を作成すれば GCD OS のインストールが完了。 あとは、 /usr/local/gcd 以下へ chroot して GCD OS を起動するだけ。 次のような GCD OS 起動スクリプト /etc/init.d/chroot を書いてみた。

#!/bin/sh
root=`echo $0 | sed -e 's@/etc/init.d/chroot$@@'`
if [ ! -d $root ]; then
   echo "Can't find root: $root"
   exit 1
fi
mount -obind /lib/modules $root/boot/lib/modules
chroot $root sh <<EOF
mount -a
svscanboot &
/etc/rc.d/rc.M
EOF

このスクリプトも GCD OS 一式をコピーするときに /usr/local/gcd/etc/init.d/chroot へコピーされる。 で、/usr/local/gcd/etc/init.d/chroot を、 とりあえず手動で実行。 手動で実行するところがミソで、 もしこのスクリプトにバグがあって異常事態に陥っても、 再起動すれば元に戻る。

上記 /usr/local/gcd/etc/init.d/chroot は、 chroot /usr/local/gcd sh を実行して、 chroot 環境下で svscanboot と /etc/rc.d/rc.M を実行する。 svscanboot は daemontools の起動スクリプト。 GCD OS のほとんどのデーモン類は daemontools の管理下で起動される。 一方 /etc/rc.d/rc.M は、 GCD OS のブートスクリプトで、 ファイアウォールなどネットワークまわりの設定や、 個々のサーバ特有の設定、 および一部のデーモン類の起動を行なう。

普通の Linux OS だと init から直接起動されるデーモンもあるが、 GCD OS の場合 init が起動するのは /etc/rc.d/rc.S と /etc/rc.d/rc.M および svscanboot だけ。 /etc/rc.d/rc.S は、 OpenVZ 環境下では不要な処理ばかりなので実行する必要はない。

こうして chroot 環境下で GCD OS が起動したら、 chroot /usr/local/gcd sh などと実行して GCD OS 環境へ入ることができる。 各種設定が正しく機能しているか、 デーモン類が正しく動いているか確認する。

ServersMan@VPS で使われているカーネルが、 2.6.18-194.3.1.el5.028stab069.6 と、 かなり古い (2.6.18 などという 5年も昔のカーネルを使い続けないでほしい > RHEL) ので、 GCD OS をきちんと動かすには問題があった。 特に困ったのが、 iptables の owner モジュールが使えない点:

chiyoda:/ # uname -rv
2.6.18-194.3.1.el5.028stab069.6 #1 SMP Wed May 26 18:31:05 MSD 2010
chiyoda:/ # iptables -t nat -j REDIRECT -p udp --dport 53 --to-port 2053 -A dnscache.lo -s 127.0.0.1 -d 127.0.0.1 -m owner ! --uid-owner Gdnscache
iptables: No chain/target/match by that name.

このエラーは、 カーネルに CONFIG_NETFILTER_XT_MATCH_OWNER の設定がないのが原因。 NETFILTER_XT_MATCH_OWNER が導入されたのは 2.6.25 以降なので、 そもそも元から 2.6.18 には存在しない。

なぜ --uid-owner が必要かと言えば、 GCD OS では tinydns と dnscache を、 同じ IP アドレスで動かすことが基本になっているから。 つまり、 通常の名前解決に 127.0.0.1 の dnscache を利用するのだが、 dnscache (Gdnscache 権限で動作) が 127.0.0.1 に問合わせる時に限り mydns が返事をするようにしたい。

仕方がないので、 iptables に --uid-owner を指定してエラーになる場合は、 --uid-owner 抜きで iptables を再実行するように修正した:

nsredirect="-t nat -j REDIRECT --dport 53 --to-port $PORT"
chain=dnscache.lo
iptables -t nat -N $chain 2>/dev/null || iptables -t nat -F $chain
nsinner="$nsredirect -A $chain -s 127.0.0.1 -d 127.0.0.1 \
	-m owner ! --uid-owner Gdnscache"
iptables -p udp $nsinner
if [ $? -ne 0 ]; then
    nsinner="$nsredirect -A $chain -s 127.0.0.1 -d 127.0.0.1"
    iptables -p udp $nsinner
fi
iptables -p tcp $nsinner

このような修正を、 chiyoda.gcd.org だけでなく、 GCD OS をインストールしている全サーバで一斉に行なう点がミソ。 サーバごとにファイルの内容が微妙に異なっていたりしたら、 GCD OS を使う意味がない。

当然、 --uid-owner 抜きで iptables を実行した場合は、 dnscache が 127.0.0.1 の mydns に問合わせをすることができないが、 127.0.0.1 以外、 つまり 183.181.54.38 あるいは 2001:2e8:634:0:2:1:0:2a ならば mydns に問合わせることができるので問題無い。

じゃ、 なんのために、 わざわざ --uid-owner を使って 127.0.0.1 の mydns に問合わせられるようになっているかというと、 127.0.0.1 以外の IP アドレスが動的に変わるサーバ (ノートPC など) も想定しているから。 GCD OS は VirtualBox や Xen などの完全仮想化環境だけでなく、 coLinux や今回の OpenVZ など、 仮想化環境としてはやや異質なものもサポートしている。

以上のような細かい修正を行なっていって、 GCD OS が問題無く立ち上がるようになったら、 /usr/local/gcd/etc/init.d/chroot の呼び出しを (ubuntu の) /etc/rc.local に追加して、 VPS の起動時に自動的に GCD OS が起動されるようにする。

GCD OS が正しく立ち上がれば、 外部から 22番ポートに対して ssh ログインして GCD OS が使える。 22番ポートでログインできることが確認できたら、 3843番ポートの sshd は止めてもよい。 chroot 環境からはいつでも脱出できるので、 3843番ポートを止めても本来の (chroot する前の) ubuntu 環境にアクセスすることが可能:

senri:~ # ssh chiyoda.gcd.org
Enter passphrase for key '/root/.ssh/id_rsa':
Last login: Sat Jul 30 09:14:54 2011 from 2409:82:5fff:0:5542:d84e:971a:9656
Linux 2.6.18-194.3.1.el5.028stab069.6.
chiyoda:~ # ls -F /					↓ GCD OS
bin@   dev/  ftp/   lib/    proc/  run/   sys/  usr/
boot/  etc/  home/  lib64@  root/  sbin@  tmp/  var/
chiyoda:/ # chroot_escape /bin/bash			← chroot から脱出
groups: cannot find name for group ID 11
groups: cannot find name for group ID 14
root@chiyoda:/# ls -F --color=never			↓ ubuntu
aquota.group@  boot/  fastboot  lib32/  mnt/   sbin/     sys/  var/
aquota.user@   dev/   home/     lib64@  proc/  selinux/  tmp/
bin/           etc/   lib/      media/  root/  srv/      usr/
root@chiyoda:/# cat /etc/debian_version
squeeze/sid
root@chiyoda:/# lsb_release -r
Release:	10.10
root@chiyoda:/# exit
chiyoda:~ # 						↓ GCD OS

「chroot_escape /bin/bash」 が、 chroot 環境から脱出するコマンド。 脱出して、 「本来の」 root 環境 (この例では ubuntu) 下で、 引数のコマンド 「/bin/bash」 を実行する。 この bash を使って ubuntu の操作ができて、 exit すると元の GCD OS へ戻る。 まるで chroot 下の GCD OS の方が 「主」 で、 本来の root が 「従」 のように見える ;-)。

なお、 chroot_escape コマンド実行時の 「groups: cannot find name for group ID 〜」 というエラーは、 GCD OS の /etc/group と ubuntu の /etc/group が異なるため。 つまり GCD OS の root は ID 11 と 14 のグループに属しているが、 ubuntu には ID が 11 と 14 のグループが存在しないため、 このようなエラーが表示される。

ここまでやるなら、 chroot といわず root に GCD OS をインストールしてしまえば? という声が聞こえてきそうであるが、 ServersMan@VPS ではコンソールが利用できないため、 トラブったときのために何らかの 「バックドア」 は残しておきたい。 GCD OS がどのような状況に陥っても、 3843番ポートの sshd (init から起動されるので kill しても再起動する) にログインできれば、 ubuntu 環境で GCD OS の修復が可能。

というわけで一週間ほど ServersMan@VPS Standard プランを使っているが、 意外に (失礼!) 使えるので驚いた。 実は、 OpenVZ な 512MB ということであまり期待していなかった。 OpenVZ は swap を使えないので、 メモリ 512MB だと、 ちょっと重い処理をさせるだけですぐ OOM Killer が動き出すのだろうと思っていた。 ところが、

chiyoda:~ $ free
             total       used       free     shared    buffers     cached
Mem:       2097152     425020    1672132          0          0          0
-/+ buffers/cache:     425020    1672132
Swap:            0          0          0

512MB というのは実メモリ? の割当量のようで、 見かけ上は 2GB のメモリがある。 OpenVZ な VPS サービスによっては、 これをメモリ 2GB と言い張るところもあるんじゃないかと思うので、 ServersMan@VPS は良心的。

どのくらいのパフォーマンスなのか、 この日記 (WordPress を使用) の処理時間を測ってみる。 senri.gcd.org から ApacheBench でアクセスすると:

senri:~ $ /usr/apache2/bin/ab -n 10 http://chiyoda.gcd.org/blog/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
	...
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        6    7   0.3      7       7
Processing:   992 1116  94.1   1139    1259
Waiting:      305  403  78.3    380     561
Total:        998 1122  94.1   1146    1265

これを、 Linode Xen 512MB プラン (fremont.gcd.org) と比較する。 fremont.gcd.org は米国西海岸にある VPS なので、 同じく西海岸にある prgmr.gcd.org から ApacheBench でアクセスすると:

prgmr:~ $ /usr/apache2/bin/ab -n 10 http://fremont.gcd.org/blog/
	...
              min  mean[+/-sd] median   max
Connect:        2    3   0.1      3       3
Processing:   996 1081  73.8   1084    1197
Waiting:      354  391  30.5    388     450
Total:        999 1084  73.8   1086    1200

両者は、 ほとんど同等のパフォーマンスであることが分かる。 Linode Xen 512MB プランは、 ディスク 20GB で月額 $19.95 (約 1600円) なので、 ServersMan@VPS Standard プラン (ディスク 30GB, 月額 980円) の方がコストパフォーマンスが良い。

もちろん、 ServersMan@VPS Standard には、 カーネルのバージョンが古すぎ、という問題点があるし、 メモリ 512MB を超える部分のパフォーマンスは、 ホストOS 側の負荷状況に左右されると思われるので、 単純に比較すべきではない。

Filed under: IPv6,システム構築・運用 — hiroaki_sengoku @ 10:48
2011年7月24日

フレッツ 光ネクストの IPv6 IPoE 接続 (ネイティブ方式) を使ってみた

IPv6 閉域網であることを今までさんざん dis られてきたフレッツ網 (NGN, 次世代ネットワーク) が、 ついに 7月21日からインターネットに接続できるようになった。 これでもう 「NGN と IPv6 インターネットは併用できない」 などとは言わせない。 私は IPv6 を既に PPPoE 接続で使っているのだが、 トンネル方式 (PPPoE) よりネイティブ方式 (IPoE) のほうがいいにきまってる、 ということでさっそく申し込んでみた

NGN のサービス情報サイト (NGN からでないとアクセスできない) のページで 「サービス申込受付」 をクリック。 「お客様ID」 と 「アクセスキー」 を入力してログインすると、 「フレッツ・v6オプション」 を申し込むことができる (このページから申し込むと無料)。

申込み後、 NGN から流れてくる IPv6 ルータ広告 (RA, Router Advertisement) を tcpdump で監視していたら、 34分経過した頃にプレフィックスが突然変わった。 NGN の契約以来、 今まで一度も変わったことがなかったプレフィックス 2408:82:5fff:86a::/64 が、 2408:282:5fff::/64 になった。 これはきっとインターネットと通信できるプレフィックスに違いないと思って、 他のサイトから ping6 を打ってみた。 が、 NGN からは RA 以外は何も流れてこない。 うーん。 ちなみに RA の送信元 (NGN のエッジ・ルータ) は fe80::21e:13ff:fec2:69c2 のまま変わらず。

その後 1時間ほど放置してみたが何も状況が変化しなかったので、 「フレッツ・v6オプション」 って何? と思い直して (サービス内容をよく確認せずに申し込んでしまっていた)、 あらためて FAQ を見ると、

 Q
「フレッツ・v6オプション」を利用してインターネットへの接続はできますか?
 A
「フレッツ・v6オプション」 は、NTT東日本が構築するNGN内での通信が可能ですが、 「フレッツ・v6オプション」 のみではインターネットへの接続はできません。 インターネットへの接続には、 別途プロバイダサービスをお申し込みいただく必要があります。

インターネットへ接続するには、 フレッツ・v6オプションとプロバイダ契約の両方が必要らしい。 では、 どのプロバイダの、 どんなサービスを申し込めばいいんだろう? と思って、 フレッツ 光ネクスト IPv6 IPoE対応プロバイダを調べてみると、 神奈川県だと現時点で対応しているのは IIJmio だけだった (神奈川県だけでなく他県も同様)。 IIJ は今まで契約したことがなかったが、 他に選択肢が無いのであれば仕方がない。 さくっとクレジットカード番号を入力して IIJmio FiberAccess/NF を契約した (月額 2100円)。 これで半固定 IPv6 アドレスによる IPoE サービスと、 動的割当て IPv4 アドレスによる PPPoE サービスが利用できる。

Ether 上に IP を流すのはごくごく普通のことなので、 あらたまって 「IPoE」 (IP over Ether) と表現されると何だか妙な感じ。 「半固定」 というのも微妙な表現だが、 注意書きには 「お客様の移転、フレッツ回線の品目変更やNTTのメンテナンスにより、 変更になる場合があります」 と書いてあるので、 普通に使ってる限りプレフィックスが変わることは無さそう。

mio FiberAccess/NFサービスは、 インターネットマルチフィード株式会社が提供する 「transix (トランジックス)」 サービスを利用して、 NTT東西の 「インターネット(IPv6 IPoE)接続」 に対応したIPv6接続を提供します。 IPv6接続に加えて、 PPPoE接続方式によるIPv4接続もあわせて提供するため、 お客様は一つのサービスでIPv6、 IPv4の両方の接続環境を利用することができます。
IIJmio FiberAccess/NF概要 から引用

IPv6 接続は全て transix が担っていて、 IIJmio はネットワーク的には何の役割も果たしていない。 IIJmio がやってるのは課金などユーザ管理関連だけ。 プロバイダである IIJmio が絡むから、 IPv4 接続も提供するなどという抱き合わせ商法になる。 動的割当の IPv4 PPPoE 接続サービスなんて要らないから、 もう少し安いプランがあればいいのにと思う。

本来、 ネイティブ方式の IPv6 接続サービスは、 NTT東西だけで提供するのが自然な形だった。 ところが、 NGN を持っていて地域独占な NTT東西が接続サービスまで提供してしまっては、 他のプロバイダの出る幕がなくなると猛反発されて、 BBIX, JPNE, インターネットマルチフィード の三社が接続サービスを提供することになった。 なぜ三社だけかといえば、 プロバイダを増やすと経路情報の処理量が膨大になって NGN の各ルータの処理能力の限界を超えてしまうから。

ところが、 三社だけでは少なすぎると他のプロバイダ達が反対したので、 その他大勢の中小プロバイダにも参入の余地を無理矢理作ったということなのだろう。 でも、 やってることは単なるユーザ管理なので、 通信事業者じゃなくてもできる簡単なお仕事。 この期に及んで業界保護みたいなことはやめてほしい。 インターネット接続サービスは既にコモディティ化しているのだから、 体力のないところは淘汰されるべき。

FiberAccess/NF を契約してから 1時間が経過したとき、 NGN から流れてくる RA のプレフィックスが 2409:82:5fff::/64 に変わった。 今度こそ疎通したかと思い、 senri.gcd.org に IPv6 アドレス 2409:82:5fff::3c20:55dc を割当てて、 他のサイトから 2409:82:5fff::3c20:55dc に対して ping6 を打ってみる。 すると無事、NGN IPoE 経由で senri.gcd.org に ICMPv6 パケットが届いた。

ただし、 まだ senri.gcd.org の routing 設定を行なっていないので、 返りパケットは PPPoE 経由で OCN 側へ行ってしまう (おそらく OCN 内部で捨てられる)。 そこで、 とりあえずの対策として NGN から届いたパケットは NGN へ返すように、 policy routing rule を設定した:

senri:/ # ip -6 rule add from 2409:82:5fff::/64 table 100 pref 25600
senri:/ # ip -6 route add default table 100 via fe80::21e:13ff:fec2:69c2 dev eth1

つまり、 送信元アドレスが 2409:82:5fff::/64 なパケットは routing table 100 を参照するようにして、 routing table 100 において default route を、 fe80::21e:13ff:fec2:69c2 (NGN のエッジ・ルータ) へ向ける。

これで ping に対して応答できるようになった。

fremont:~ $ ping6 -c 3 2409:82:5fff::3c20:55dc
PING 2409:82:5fff::3c20:55dc(2409:82:5fff::3c20:55dc) 56 data bytes
64 bytes from 2409:82:5fff::3c20:55dc: icmp_seq=1 ttl=49 time=122 ms
64 bytes from 2409:82:5fff::3c20:55dc: icmp_seq=2 ttl=49 time=122 ms
64 bytes from 2409:82:5fff::3c20:55dc: icmp_seq=3 ttl=49 time=122 ms

--- 2409:82:5fff::3c20:55dc ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 122.479/122.526/122.591/0.289 ms
fremont:~ $ tcpspray 2409:82:5fff::3c20:55dc
Transmitted 102400 bytes in 0.491628 seconds (203.406 kbytes/s)

RTT (Round Trip Time, 往復所要時間) が 122ms もかかってるのは、 fremont.gcd.org が米国の西海岸にあるため。 2400:400d:100::3c20:55dc (OCN の PPPoE 接続) 宛の場合↓ と比べると、 transix は RTT で 50ms ほど速く、 帯域 (tcpspray による簡易測定) で倍くらい広い。

fremont:~ $ ping6 -c 3 2400:400d:100::3c20:55dc
PING 2400:400d:100::3c20:55dc(2400:400d:100::3c20:55dc) 56 data bytes
64 bytes from 2400:400d:100::3c20:55dc: icmp_seq=1 ttl=49 time=174 ms
64 bytes from 2400:400d:100::3c20:55dc: icmp_seq=2 ttl=49 time=174 ms
64 bytes from 2400:400d:100::3c20:55dc: icmp_seq=3 ttl=49 time=174 ms

--- 2400:400d:100::3c20:55dc ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 174.046/174.386/174.593/0.539 ms

fremont:~ $ tcpspray 2400:400d:100::3c20:55dc
Transmitted 102400 bytes in 0.905523 seconds (110.433 kbytes/s)

参考までに IPv4 60.32.85.216 (OCN の PPPoE 接続) の場合も測ってみた。 すると RTT も帯域も transix と同程度だった。 つまり OCN の IPv6 PPPoE だけが突出して遅く、 帯域も狭い。

fremont:~ $ ping -c 3 60.32.85.216
PING 60.32.85.216 (60.32.85.216) 56(84) bytes of data.
64 bytes from 60.32.85.216: icmp_req=1 ttl=51 time=130 ms
64 bytes from 60.32.85.216: icmp_req=2 ttl=51 time=130 ms
64 bytes from 60.32.85.216: icmp_req=3 ttl=51 time=129 ms

--- 60.32.85.216 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 129.377/129.903/130.202/0.559 ms

fremont:~ $ tcpspray 60.32.85.216
Transmitted 102400 bytes in 0.518052 seconds (193.031 kbytes/s)

米国西海岸から transix までの IPv6 の経路はこんな感じ:

fremont:~ $ tracepath6 2409:82:5fff::3c20:55dc
 1?: [LOCALHOST]                        0.021ms pmtu 1500
 1:  2600:3c01:ffff:0:ca4c:75ff:fef5:d63f                  0.558ms
 1:  2600:3c01:ffff:0:ca4c:75ff:fef5:d63f                  0.480ms
 2:  10gigabitethernet2-3.core1.fmt1.he.net                3.149ms
 3:  10gigabitethernet1-2.core1.sjc2.he.net               11.042ms
 4:  equi6ix.sv.iij.com                                    1.834ms asymm  5
 5:  sjc002bf00.iij.net                                    9.384ms asymm  7
 6:  2001:48b0:bb00:8016::71                             120.588ms asymm  7
 7:  tky009bb11.IIJ.Net                                  120.730ms asymm  8
 8:  tky009ip50.IIJ.Net                                  121.048ms
 9:  2001:240:bb5c:1008::cafe                            120.365ms
10:  2404:8e00:feed:101::2                               121.852ms
11:  no reply
12:  no reply
13:  no reply
14:  no reply
15:  no reply
16:  2409:82:5fff::3c20:55dc                             127.409ms reached
     Resume: pmtu 1500 hops 16 back 49

「IIJmio はネットワーク的には何の役割も果たしていない」 が、 日米間の回線は IIJ が担っているようだ (あれっ? ^^;)。 日本に上陸後 (6 以降) はほとんど遅延していない。

OCN の PPPoE の場合だと、こんな感じ:

fremont:~ $ tracepath6 2400:400d:100::3c20:55dc
 1?: [LOCALHOST]                        0.047ms pmtu 1500
 1:  2600:3c01:ffff:0:ca4c:75ff:fef5:d63f                  3.935ms
 1:  2600:3c01:ffff:0:ca4c:75ff:fef5:d63f                  0.852ms
 2:  10gigabitethernet2-3.core1.fmt1.he.net                7.978ms
 3:  10gigabitethernet1-2.core1.sjc2.he.net                2.532ms
 4:  xe-0.equinix.snjsca04.us.bb.gin.ntt.net               2.018ms asymm  5
 5:  as-1.r21.osakjp01.jp.bb.gin.ntt.net                 168.074ms asymm 11
 6:  ae-0.ocn.osakjp01.jp.bb.gin.ntt.net                 167.792ms asymm 14
 7:  2001:380:8060:6::1                                  159.446ms asymm 13
 8:  2001:380:8170:4::1                                  167.630ms asymm 14
 9:  2001:380:8030:16::1                                 168.401ms asymm 15
10:  2001:380:8110:d::1                                  170.344ms asymm 14
11:  2001:380:8110:f::2                                  178.503ms asymm 13
12:  2001:380:8130:11::13                                178.131ms asymm 13
13:  2001:380:8270:8::2                                  172.448ms
14:  2001:380:4d:101::2                                  179.036ms
15:  2001:380:4d:182::2                                  181.317ms
16:  no reply
17:  2001:380:4d:181::2                                  173.127ms pmtu 1454
17:  senri.v6.gcd.org                                    183.023ms reached
     Resume: pmtu 1454 hops 17 back 49

日米間に 160ms もかかっている上に、 日本に上陸後 (5 以降) も 15ms ほど遅延がある。 なぜこんなに遅いのだろう? また、PPPoE なので mtu が 1454 になっている。

同じ OCN の PPPoE でも、 IPv4 だと遅くない (というか transix より若干速い) ので、 OCN の IPv6 PPPoE 接続サービスには、 なにか問題がありそう。 まあ、 追加料金無しのサービスなので、 IPv4 のオマケ的な位置づけなのかも?

fremont:~ $ tracepath 60.32.85.216
 1:  fremont.gcd.org                                       0.169ms pmtu 1500
 1:  184.105.143.85                                        1.702ms
 1:  184.105.143.85                                        0.418ms
 2:  10gigabitethernet2-3.core1.fmt1.he.net                0.665ms
 3:  10gigabitethernet1-1.core1.pao1.he.net                8.907ms
 4:  sjo-bb1-link.telia.net                                1.297ms asymm  5
 5:  verio-119529-sjo-bb1.telia.net                        4.568ms
 6:  ae-8.r20.snjsca04.us.bb.gin.ntt.net                   1.922ms
 7:  as-2.r20.tokyjp01.jp.bb.gin.ntt.net                 112.093ms asymm  8
 8:  ae-1.ocn.tokyjp01.jp.bb.gin.ntt.net                 119.692ms asymm 11
 9:  60.37.27.137                                        120.641ms asymm 10
10:  60.37.55.158                                        119.549ms asymm 11
11:  122.1.173.238                                       121.243ms
12:  118.23.5.78                                         128.853ms asymm 14
13:  no reply
14:  118.23.8.9                                          128.712ms pmtu 1454
14:  gcd.org                                             123.788ms reached
     Resume: pmtu 1454 hops 14 back 52
(続きを読む...)
Filed under: IPv6,システム構築・運用 — hiroaki_sengoku @ 18:33
2011年6月20日

フレッツ 光ネクストの IPv6 PPPoE 接続を OCN 光アクセスで使ってみた

6月1日から NTT東日本のフレッツ 光ネクストにおいて IPv6 PPPoE 接続の提供が始まった。 私は OCN光アクセスを契約しているが、 幸い OCN (NTTコミュニケーションズ) も、 NTT東日本に合わせて順次対応を開始ということなので、 さっそく OCN へ電話で問合わせてみたら、 申込書をメールで送るので記入押印の上 FAX で送り返して欲しい、とのこと。

いまどき Web で申し込めないなんて、 と思いつつ 8ページにもわたる申込書 (いつも感じるが OCN の申込書は無駄にページ数が多い *_*) を FAX で送付。 すると翌日電話がかかってきて、 開通日は 10日後などとおっしゃる。 それじゃ World IPv6 Day に間に合わないじゃんと思ったが、 どんな変更でも申込みから 7営業日は最低でもかかるらしいので仕方がない。 なお、 工事費および月額使用料は無料。

OCN には元々月額 315円の 「OCN IPv6」 というサービスがあるが、 OCN IPv6 はフレッツ網に PPPoE によるトンネルを張って IPv4 を通し (OCN フレッツ光)、 その IPv4 上に L2TP (Layer 2 Tunneling Protocol) によるトンネルを張って PPP セッションを通し、 その PPP 上に IPv6 を通すという屋上屋を架す方式だったのに対し、 6月1日から始まった 「IPv6インターネット接続」 はフレッツ網に PPPoE によるトンネルを張って IPv6 を通す方式。

つまり OCN フレッツ光の IPv4 の部分を IPv6 でそのまま置き換えたシンプルな方式。 もちろんフレッツ網に IPv6 を直接通す 「ネイティブ方式」 が一番シンプルだが、 ネイティブ方式に関しては 「平成23年7月を目途に提供を予定」 ということなので、 どのようなサービスが始まるのか今のところ不明 (もう来週には 7月が始まってしまうのだが)。 7/24追記: ネイティブ方式サービスが始まった!

開通日の 2日前に郵便で届いた 「ご利用内容のご案内」 の欄外の注釈に、

IPv6 でインターネットに接続する際、 認証ID の @ 右側を “@bizf.ocn.ne.jp” の場合は “@bizf6.ocn.ne.jp” に、 “@bizd.ocn.ne.jp” の場合は “@bizd6.ocn.ne.jp” に変更してご利用下さい。

と書いてあった。 IPv6 での接続に関する説明はこの部分だけなので、 あとは推測するしかない (サポートに接続方法を聞いても、 単に IPv6トンネル対応ルータを購入してくれと言われる)。

とりあえず、 ふだん IPv4 で接続するときに使ってる PPPoE スクリプト (RP-PPPoE) を、 認証ID を “xxxx@bizf.ocn.ne.jp” から “xxxx@bizf6.ocn.ne.jp” に変更して走らせてみる:

20:39:13 senri pppd[16587]: Plugin /etc/ppp/plugins/rp-pppoe.so loaded.
20:39:13 senri pppd[16587]: RP-PPPoE plugin version 3.10 compiled against pppd 2.4.5
20:39:13 senri pppd[16587]: pppd 2.4.5 started by root, uid 0
20:39:13 senri pppd[16587]: PPP session is 6394 (0x18fa)
20:39:13 senri pppd[16587]: Connected to 00:1e:13:c2:69:c2 via interface eth1
20:39:13 senri pppd[16587]: Using interface ppp0
20:39:13 senri pppd[16587]: Connect: ppp0 < --> eth1
20:39:13 senri pppd[16587]: Couldn't increase MTU to 1500
20:39:13 senri pppd[16587]: Couldn't increase MRU to 1500
20:39:13 senri pppd[16587]: PAP authentication succeeded
20:39:13 senri pppd[16587]: peer from calling number 00:1E:13:C2:69:C2 authorized
20:39:13 senri pppd[16587]: local  LL address fe80::fd61:ff9b:eb97:1f93
20:39:13 senri pppd[16587]: remote LL address fe80::0090:1a00:41a3:d3f3

PPP は 1 つ以上のネットワーク層プロトコルを選択できるので、 IPCP (IP Control Protocol) と IPv6CP (IPv6 Control Protocol) の両方が流れてくるのかと予想したのだが、 流れてきたのは IPv6CP のみだった。 つまり “@bizf.ocn.ne.jp” で IPv4 用、 “@bizf6.ocn.ne.jp” で IPv6 用、 計 2本の PPPoE セッションを張ることになる。

上記ログから分かる通り Link Local とはいえ IPv6 なアドレス fe80::fd61:ff9b:eb97:1f93 が割り振られたのだから、 IPv6 で通信できるはず。 試しに PPP の対向サーバへ ping を打ってみると、 ちゃんと応答が返ってきた:

senri:~ $ ping6 -c 3 fe80::0090:1a00:41a3:d3f3%ppp0
PING fe80::90:1a00:41a3:d3f3%ppp0 (fe80::90:1a00:41a3:d3f3%ppp0): 48 data bytes
56 bytes from fe80::90:1a00:41a3:d3f3%ppp0: icmp_seq=0 ttl=255 time=3.936 ms
56 bytes from fe80::90:1a00:41a3:d3f3%ppp0: icmp_seq=1 ttl=255 time=4.050 ms
56 bytes from fe80::90:1a00:41a3:d3f3%ppp0: icmp_seq=2 ttl=255 time=4.176 ms
--- fe80::0090:1a00:41a3:d3f3%ppp0 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 3.936/4.054/4.176/0.098 ms
senri:~ $ 

じゃ、 Global なアドレス (固定アドレス契約なのでプレフィックスが 「ご利用内容のご案内」 に書かれていた) を付けて routing 設定を行なえば Global に通信できそうと思い、 試してみると...

senri:/ # ifconfig eth0 add 2400:400d:100::1/56
senri:/ # ip -6 route add default via fe80::0090:1a00:41a3:d3f3 dev ppp0
senri:/ # ping6 www.kame.net
PING www.kame.net (2001:200:dff:fff1:216:3eff:feb1:44d7): 48 data bytes
^C--- www.kame.net ping statistics ---
15 packets transmitted, 0 packets received, 100% packet loss
senri:/ # 

う〜ん、ダメか。 tcpdump を使って ppp0 に届く IPv6 パケットを監視してみたが、 OCN へ送信したパケットのみが表示され、 OCN 側からは何のパケットも届かない。

しかも、 他のサイトから 2400:400d:100::1 に対して ping6 を打ってみても何も届かない。 仮にこちらの設定に何か間違いがあったとしても、 OCN 側で 2400:400d:100::1 宛のパケットを routing していれば、 パケット自体は流れてきそうな気がするのだが... 少なくとも IPv4 の場合なら、 こちらの IP アドレス設定が間違っていても、 受け取れないだけでパケット自体は流れてくる。

ひょっとして、 「ご利用内容のご案内」に書かれていた 「2400:400d:100::」 というプレフィックスが間違っているんじゃ? と、 途方に暮れる。

(続きを読む...)
Filed under: IPv6,システム構築・運用 — hiroaki_sengoku @ 09:08
2011年3月7日

Osukiniサーバを使ってみた 〜 仮想コンソールが提供されない VPS で独自OS をインストールする方法

今まで米国の VPS を使っていたが、 いつのまにか日本の VPS もリーズナブルな値段になってきたらしい。 「さくらのVPS」 がメモリ 512MB で月額 980円、 「Osukini サーバ」 ST プラン がメモリ 1GB, ディスク 100GB で月額 980円。 「さくらのVPS」 でも充分安いが、 「Osukini サーバ」 の安さは目を疑うレベル。 これはもう試してみるしかない。

実は、 最初は 「さくらのVPS」 を申し込んだのだが、 rootパスワードが見つからない事件でイカってキャンセルしてしまった。 申し込んだ直後に VPS が起動できたので、 さくら++ と思いつつ root でログインしようとしたらパスワードが分からず Web じゅう探しまわる羽目に。 万策尽きてサポートに電話したら 15分以上待たされ、 しかも担当者には root と言っても話が通じず、 別の番号にかけなおせと言われる始末。 後ほど届いた 「仮登録完了のお知らせ」 メールに root パスワードが書いてあったのだが、 メールが届く前に VPS のコントロールパネルをいじれて、 起動までできてしまう現在の仕様はいかがなものか。

Osukini サーバの場合 OS は CentOS, Ubuntu, Debian から選べるが、 「my distribution」 以外は使う気が起きないので、 どうやって私の 「独自OS」 をインストールするか、 方法を考えてみた。 もちろん、 Osukini サーバのサポートの範囲外なので、 インストールする方法だけでなく、 トラブった時の復旧方法を考えておくことが重要。

Osukini サーバでは 仮想コンソール (hvc console, リモートコンソール) が提供されない。 OS の外部から操作する手段がほとんどなく、 ブートスクリプトの設定ミスなどで OS にリモートログインできなくなってしまったら最後、 OS の再インストールしか復旧手段はない (と思う)。 しかも、 (サポートの範囲外である) 独自OS の再インストールを支援してもらえるはずはないので、 せっかくインストールした独自OS は跡形もなく消えてしまう。

再起動すれば復旧できる一時的な障害はここでは考えない。 設定ファイルなどを不適切な内容に書き換えてしまった結果、 再起動してもリモートログインできない状態が続くケースのみを考える。 実際、 サーバを運用しているとそういうトラブルはよくある。 ネットワーク設定を間違えて通信不能になるだけで、 他は全く正常でもリモートログインはできなくなるし、 あるいは sshd の設定を間違えて sshd が起動しなくなっただけでもリモートログインできなくなる。
ネットワークが全く使えなくても、 sshd が立ち上がらなくなってしまっても、 OS 自体が正常に起動していれば init から起動される getty は生きていることが多いので、 仮想コンソールさえ使えれば、 大抵はログインできて修復できる。

他の VPS, 例えば Linodeprgmr などでは、 仮想コンソールが利用できる。 さらに、 両者とも GRUB が使えるので、 複数 OS をインストールしておいて切り替えて使うこと (いわゆる 「デュアルブート」) ができる。 つまり一つの OS 上で何かトラブルが起きて OS が立ち上げ不能になっても、 別の OS (いわゆるレスキュー用 OS) を立ち上げて、 トラブった OS を修復することができる。

リアルサーバでは、 (仮想ではなく物理的な) コンソールが利用できる (当たり前)。 さらに、 トラブった時はレスキュー用の CD-ROM / USBメモリからブートして復旧作業を行なったりする。 VPS でもリアルサーバでも、 イザというときに備えて、 通常使う OS とは別に、 修復作業を行なうためのレスキュー用の OS を用意しておくことが極めて重要。

ところが、 レスキュー用 OS が一切利用できないばかりか、 仮想コンソールから操作することすらできないのが Osukiniサーバ。 トラブルに対して極めて脆弱であると言わざるを得ない。

Osukini Server Control Panel

VPS にとって仮想コンソールは必須の機能だと思っていたので、 Osukiniサーバのコントロールパネルを見て驚いた。 「再起動」 「停止」 などの操作と、 OS の初期化しかできない (値段相応? でも、 仮想コンソール機能は Xen 標準なのに...)。

OS の初期化をすれば、 全てが消えてしまう。 いわば、 クリーンインストールしかできない CD-ROM からブートするようなもの。 トラブったからといって、 即クリーンインストールしたいという人がどれだけいると言うのか?

嘆いていても仕方がないので、 どうやってレスキュー用 OS 相当のことを実現するか考えてみる。 レスキュー用 OS を立ち上げることさえできれば、 独自 OS をインストールすることも可能になる。 こんなこともあろうかと、 2年前 (2008年10月) に作っておいた ptyd (Pseudo TTY Daemon) が役に立ちそう。

(続きを読む...)
Filed under: システム構築・運用 — hiroaki_sengoku @ 08:36
2010年6月17日

Linode から prgmr.com へ乗り換えてみた

今年 2月から使っている Linode (VPS, 仮想サーバ) は、 月額 $19.95 の最小プラン Linode 360 だと RAM 360MB なので WordPress を動かす (Apache+PHP+MySQL) には若干足らない (稀に スラッシング 状態に陥って無反応になる)。 もちろん、 一つ上のプラン Linode 540 (RAM 540MB) にすればいいのであるが、 これだと月額 $29.95 でちょっと高い (私の感覚だと月額 $20 あたりに心理的な壁がある)。

prgmr.com ならば、 RAM 512MB, ディスク 12GB が月額 $12 (年一括払いなら $115.2) で済むので乗り換えてみた。 Linode のようなユーザフレンドリーな Web ユーザインタフェースは無いし、 時々新規申込みの受付を中止したりする (今も RAM 1024MB 以上のプランは受付けていない) が、 逆に言えば Web インタフェースが不要な人 (含む私) にとっては prgmr.com のシンプルさがむしろ望ましく、 レスキュー用のブートイメージなど必要最低限のものは揃っているので、 不便と感じることはない。 また、 いったん申込みが完了してしまえば受付の一時停止は関係ないわけで、 むしろサーバリソースに見合ったユーザ数しか受付けない姿勢は好感が持てる。

Web から Signup すると、 以下のようなメールが届く。

you ordered a xen vps, 512MiB ram, 12GiB Disk 80 GiB transfer Xen VPS,  $12/month username sengoku 

Before I can set you up, I need to know what distro you would like and an OpenSSH format public key (on a *NIX, run ssh-keygen  and send me either the id_dsa.pub or id_rsa.pub file in an attachment)    

on putty/windows, see

http://www.unixwiz.net/techtips/putty-openssh.html#keypair

and scroll down to the screen shot where it says 'openssh format public key for pasting into authorized_keys' - that is what I need.

thanks. 

OpenSSH の公開鍵くらい Web から登録させればいいのにと思いつつ、 「I need to know what distro you would like」 などと聞いてくるということは多少の融通は聞くのかと思い、 パーティションの切り方をついでにリクエストしてみた (6/4 15:46)。 つまり、 自分で作った Linux distro (配布していないから 「distribution」 と呼ぶのは正確性に欠けるのだが、 それなら何て呼んだらいいのだろう?) をインストールしたいから、 最小のパーティション (例えば 2GB) に ubuntu をインストールしておいてもらえるか? などとリクエストしてみた:

Hi,

At Fri, 04 Jun 2010 05:48:05 +0000,
support@prgmr.com wrote:

> Before I can set you up, I need to know what distro you would like and
> an OpenSSH format public key (on a *NIX, run ssh-keygen and send me
> either the id_dsa.pub or id_rsa.pub file in an attachment)

I'd like to use my own linux-based image that is running on
fremont.gcd.org (linode) now.  Is it possible to make two partitions
(plus swap partition) ?  One is for a normal ubuntu, and the other is
for my own image.  I'd like to switch these two OSs by pv-grub.

Please minimize the ubuntu partition in order to maximize my own image.
For example, 2GiB for ubuntu, 1GiB for swap, and 9GiB for me.

I've attached my public key id_dsa.pub at the end of this mail.


#9860.
http://www.gcd.org/sengoku/		Hiroaki Sengoku <sengoku@gcd.org>

すると、 4日後の 6/8 09:03 に返事が来た。 リクエストはあっさり無視されて、 ディスク一杯に ubuntu をインストールした状態の VPS が提供されただけ。 個別対応しないのであれば、 自動化したほうがいいような。

Your vps is setup now, your host server is whetstone.prgmr.com.
Login with your username and ssh key and follow the instructions
at http://book.xen.prgmr.com/mediawiki/index.php/Quickstart
For more information about repartitioning, see
http://book.xen.prgmr.com/mediawiki/index.php/Repartition
The default root password for the vps is password, and you
should receive a bill soon by email. If you have any questions
please email support@prgmr.com. Thanks very much.
Nick Schmalenberger

言われた通り ssh でログインしてみる:

senri:/home/sengoku % ssh whetstone.prgmr.com
The authenticity of host 'whetstone.prgmr.com (65.49.73.105)' can't be established.
RSA key fingerprint is 97:52:07:d4:52:8d:c0:cc:13:c7:fb:5d:5d:1b:ab:b4.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'whetstone.prgmr.com,65.49.73.105' (RSA) to the list
of known hosts.
Name                                        ID   Mem VCPUs      State   Time(s)
sengoku                                    xxx   512     1     -b----     11.5

Options for sengoku
1. console
2. create/start
3. shutdown
4. destroy/hard shutdown
5. reboot
6. swap i386/amd64 bootloaders (pvgrub)
7. exit
press the number> 

「6. swap i386/amd64 bootloaders (pvgrub)」 が目を引くが、 PV-GRUB は DomainU 側で実行されるので、 PV-GRUB にて 32bit/64bit の両方のカーネルを立ち上げることができず、 PV-GRUB を実行する前にどちらか一方に決めておく必要がある、 ということのようだ。 デフォルトは (残念なことに) 32bit になっていて、 セットアップしてもらった ubuntu も 32bit 版だった。

「1」 を入力して VPS のコンソールを表示させてみる:

press the number> 1

Ubuntu 10.04 LTS sengoku.xen.prgmr.com hvc0

sengoku.xen.prgmr.com login: root
Password:
Last login: Mon May 31 23:51:42 UTC 2010 on hvc0
Linux sengoku.xen.prgmr.com 2.6.32-22-generic-pae #33-Ubuntu SMP Wed Apr 28 14:57:29 UTC 2010 i686 GNU/Linux
Ubuntu 10.04 LTS

Welcome to Ubuntu!
 * Documentation:  https://help.ubuntu.com/
root@sengoku:~# df
Filesystem           1K-blocks      Used Available Use% Mounted on
/dev/xvda1            11812024   1068124  10143876  10% /
none                    249488       136    249352   1% /dev
none                    253744         0    253744   0% /dev/shm
none                    253744        28    253716   1% /var/run
none                    253744         0    253744   0% /var/lock
none                    253744         0    253744   0% /lib/init/rw
none                  11812024   1068124  10143876  10% /var/lib/ureadahead/debugfs
root@sengoku:~# 

前掲のメールに書いてあった通り、 ユーザ 「root」 パスワード 「password」 でログインできた。 初めてログインしたのに 「Last login: Mon May 31 23:51:42 UTC 2010 on hvc0」 などと出ているが、 この ubuntu イメージを作ったとき試しに root でログインしたのが残ってしまったのだと思われる。 新規ユーザのセットアップのたびに、 5/31 に作ったイメージを使いまわしているのだろう。

ディスク 12GB のプランのはずなのに 1GB ほど足りないのは swap パーティションがあるから?と思い、 fdisk で確認してみる:

root@sengoku:~# fdisk -l /dev/xvda

Disk /dev/xvda: 12.8 GB, 12884901888 bytes
255 heads, 63 sectors/track, 1566 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

    Device Boot      Start         End      Blocks   Id  System
/dev/xvda1               1        1494    12000523+  83  Linux
root@sengoku:~# 

シリンダの最大値は 1566 なので、 72 (=1566-1494) シリンダが使われずに余っている。 試しにパーティションを割当ててみたら普通に使えた。

自前 distro 10GB, swap 1GB, ubuntu 1GB といった切り分けかたにするため、 レスキュー用のカーネル 「CentOS 5.5 rescue」 をブートしてみる:

    GNU GRUB  version 0.97  (65536K lower / 0K upper memory)

 +-------------------------------------------------------------------------+
 | user bootloader configuration                                           |  
 | CentOS 5.5 rescue (2.6.18-194.3.1.el5xen)                               |
 | CentOS 5.5 installer                                                    |
 | NetBSD 5.0.2 installer                                                  |
 | Ubuntu 10.04 LTS installer                                              |
 |                                                                         |
 |                                                                         |
 |                                                                         |
 |                                                                         |
 |                                                                         |
 |                                                                         |
 |                                                                         |  
 +-------------------------------------------------------------------------+
    Use the ^ and v keys to select which entry is highlighted.
    Press enter to boot the selected OS, 'e' to edit the
    commands before booting, or 'c' for a command-line.

二行目の 「CentOS 5.5 rescue (2.6.18-194.3.1.el5xen)」 を選択してブート。 root でログイン (パスワードは無し) して fdisk を使ってパーティションを切り直した。 せっかくセットアップしてもらった Ubuntu も、 3分と使わずにお別れ。

CentOS release 5.5 (Final)
Kernel 2.6.18-194.3.1.el5xen on an x86_64

sengoku.xen.prgmr.com login: root
[root@sengoku ~]# 
	...
[root@sengoku ~]# fdisk -l /dev/xvda

Disk /dev/xvda: 12.8 GB, 12884901888 bytes
255 heads, 63 sectors/track, 1566 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

    Device Boot      Start         End      Blocks   Id  System
/dev/xvda1               1        1306    10490413+  83  Linux
/dev/xvda2            1307        1438     1060290   82  Linux swap / Solaris
/dev/xvda3            1439        1566     1028160   83  Linux
[root@sengoku ~]# mount /dev/xvda1 /mnt
[root@sengoku ~]# 

私は個人で管理している Linux マシン (自宅と職場と VPS 合わせて十数台, もちろん商用サービスを行なっているサーバではない) のディスクの内容を、 コマンド一発で同一に保てるようにしている (つまりマスタで変更/追加/削除したファイルを各マシンへコピー/各マシンで削除)。

もちろん、 各マシンの性能や 32bit/64bit などの CPU 種別、 ディスクの容量に応じて違う部分もあるわけで、 この同期コマンドは同期先の各マシンに合わせてコピーしないファイルのリストを生成した上で rsync を実行している。 なので、 新しくマシンを増やすときもマスタ上で同期コマンドを実行するだけで、 インストールが完了するはずだが...

(続きを読む...)
Filed under: システム構築・運用 — hiroaki_sengoku @ 09:44
2010年4月20日

Linux サーバ同士をシリアルケーブルでつなぐには寡黙な getty が必要

私の自宅 LAN (GCD) の対外ルータである 2台の Linux サーバ (senriasao) は、 双方のシリアルポート (つまり DSUB9ピンの RS-232C) 同士がつながっている。 いまさら RS-232C ? と思う人が大半だと思うが、 最後の命綱としての安心感は測り知れない。

つまりサーバをリモートから (ネットワーク経由で ssh ログインして) いじっていると、 ちょっとしたミスで操作不能に陥ってしまうことがある。 例えば NIC を down してしまった、 iptables で DROP する設定にしてしまった、 sshd を殺してしまった、 等々。 あるいは、 リモートから再起動を行なう場合、 起動スクリプトのほんの些細な設定ミスで、 リモートからは操作不能になってしまう。

こんなとき、 2台のサーバのシリアルポート同士を RS-232C クロスケーブル (シリアルケーブル) で接続しておけば、 生きている方のサーバからシリアルケーブル経由でログインできて、 リモートからは操作不能に陥ったサーバのネットワーク設定を修復したり、 sshd を起動し直したり、 あるいは再起動の場合であれば、 GRUB を操作することができる (ML115 などであれば BIOS 設定までいじれる)。

職場のサーバ群は、 もちろん IPMI を利用していて大抵の操作は IPMI でできてしまうのでシリアルケーブルの出番はあまりないのだが、 それでもやはり最後の命綱がある安心感は何ものにも代えがたい。 しかも数百円のケーブル一本で済むのだから、 掛けておくべき保険だろう。

シリアルケーブル経由でサーバにログインするには、 そのサーバの /dev/ttyS0 (シリアルポート COM1 の場合) で getty を動かしておく必要がある。 例えば GCD の場合 mgetty を使っているので、 /etc/inittab に

# Dialin lines
s0:25:respawn:/usr/sbin/mgetty ttyS0

などと設定している。 シリアルケーブルでつながった 2台の Linux サーバ senri と asao において、 asao から senri へシリアルケーブル経由でアクセスすると、 senri のシリアルコンソールが表示される:

asao:/home/sengoku % cu ttyS0
Connected.

Welcome to Linux 2.6.31.13-x86_64.


senri.gcd.org!login: root
otp-md5 299 se5047 ext
Response or Password:
Last login: (null) on /dev/ttyS0

senri.gcd.org:/root # tty
/dev/ttyS0
senri.gcd.org:/root # 

たとえ senri のネットワーク機能が全滅していたとしても、 mgetty は /sbin/init から直接起動されるので影響を受けない。 シリアルコンソールから root でログインして復旧作業を行なうことができる。 逆に、 asao のネットワーク機能が全滅したときは、 senri へ ssh ログインした上で asao へシリアルケーブル経由でアクセスすればよい。 ネットワーク機能が麻痺する可能性があるような危険な作業を senri と asao の両方で同時に行なったりしないようにすれば、 何が起きてもどちらかは生きていることが保証できる。

さて、 両サーバで mgetty が動いているときは以上で問題無いのだが、 どちらかのサーバを再起動するときなど、 mgetty 以外のものが動くときは状況が変わってくる。

(続きを読む...)
Filed under: システム構築・運用 — hiroaki_sengoku @ 07:48
2010年2月25日

本ブログのサーバを海外レンタルサーバ Linode を使って冗長化・地域分散してみた

前回前々回に書いたように、 このブログ 「仙石浩明の日記」 (および 「仙石浩明CTO の日記」) は、 私の自宅にある PC サーバで動かしている。 2台の PC からなる冗長構成になっていて、 10年以上の安定稼働実績がある (両サーバ共に停止したことは皆無) が、 回線が (家庭用の) フレッツ光ネクストなので、 インターネットとの接続が切れてしまう可能性は皆無ではない。 実際、 Bフレッツからフレッツ光ネクストへ切り替えたときは 40分間ほど切れてしまった。 一応バックアップ回線として ADSL 回線も契約しているが、 こちらは固定 IP アドレスを割当てていないので WWW サービス用としては使いにくい。

短時間とはいえブログが見えないようなことがあると、 たまたまそのタイミングで (検索エンジン等で見つけて) 訪れた人が、 ブログが既に閉鎖してしまったものと勘違いしてしまうかもしれない。 ここは一つ自宅以外の場所にもサーバを立てて、 回線断の影響を受けないようにしたいところ。 海外のサーバなら地域分散もできてなおよい。

道楽で運営している自宅サーバにそこまで可用性を求めるのは、 やりすぎの感もあるが、 近頃流行りの VPS (Virtual Private Server, 仮想専有サーバ) は、 月額 $20 (初期費用無し) 程度で利用できるらしい。 $20 なら試しに使ってみるのも悪くない。

サービス
プラン名
メモリ
ディスク
帯域
追加額/単位
月額
年額
データセンタの
場所
ServerAxis Xen
VS000.5G-0025.0GP
512MB
25GB
0GB
$.05/GB
$15
$180
Chicago, IL
Linode
Xen 360
360MB
16GB
200GB
$10/100GB
$19.95
$215.46
Fremont, CA
Dallas, TX ...
VPSLink
Xen VPS Link-3
256MB
10GB
300GB
$.50/GB
$19.95
$201.12
Seattle, WA
New York, NY
slicehost
Xen VPS 256 slice
256MB
10GB
150GB
$.30/GB
$20
$216
Dallas, TX
St. Louis, MO

VPSLink 以外は最安のプランで比較した。 VPSLink には、 メモリ 64MB で月額 $7.95 の Link-1 プラン、 メモリ 128MB で月額 $13.95 の Link-2 プランもあるが、 256MB 未満のメモリではできることも限られてしまうので比較対象から外した。

月額基本料だけを比較すると、 ServerAxis が格安に見えるが、 これは帯域課金が含まれていないためで、 例えば 100GB (送受信の合計バイト数) 使うと $5 加算されて $20 と他社並になる。

データセンタの場所 (というかネットワーク上での位置) によって遅延時間が変わってくるので、 どこのデータセンタを使っているかも重要。 例えば同じ Linode でもデータセンタによって、 日本からアクセスしたときの RTT (Round Trip Time, 往復にかかる時間, ミリ秒) が倍以上変わってくる。 それぞれ 10回ずつ ping を打って、 RTT の最小/平均/最大を比較してみる:

データセンタの場所ホスト名 最小平均最大
Fremont, Californiafremont1.linode.com 121.341132.299146.939
Dallas, Texasdallas1.linode.com 171.049173.152174.905
Atlanta, Georgiaatlanta1.linode.com 183.184185.938190.174
Newark, New Jerseynewark1.linode.com 200.629203.053204.755
London, Englandlondon1.linode.com 269.324273.499276.822

米国西海岸だと RTT は 130msec 程度で済むが、 東海岸は 200msec を超えてしまい、 英国はもっと遠い。 西海岸にデータセンタがあるのは Linode と VPSLink だが、 Linode がデータセンタの情報を詳細に公開しているのに対し、 VPSLink は、 Spry Hosting のシアトルのデータセンタを使用している、 ということ以上の情報を公開していない (しかもこのページは 2006年8月の内容のまま) のが少し気になる。

サポートしている Linux ディストリビューションは、 各社だいたい共通で、 CentOS, Debian, Gentoo, Ubuntu の各バージョンが利用できるが、 VPSLink Ubuntu Plan のページに Ubuntu 9.10 Karmic が書かれていない点も、 ちょっと気になった (ちゃんとポリシーを持って 9.10 をサポートしないのならいいのだが、 どうもそうではなく単に運用の手を抜いているだけのような気配がする)。 Web 界隈での評判も Linode のほうがよさげだったので、 Linode を使ってみることに決めた。

サポートしているディストリビューションのバージョンが古くてもアップグレードできるのだろうが、 帯域で課金されるので最初から新しいバージョンをインストールできる方が好ましい。

サービス CentOSDebianGentooUbuntu Fedoraその他
ServerAxis 5.45.02008.09.10- Mandriva SUSE
Linode 5.35.02008.09.1011 Arch SUSE Slack
VPSLink 55O9.0411 Arch Slack
slicehost 5.45.02008.09.1012 Arch RedHat

おそらく各社ともカーネルも入れ替えることができるのだと思うが、 Linode 以外は使っていないので未確認。 Linode の場合は pv_ops (paravirtualization) を有効にしておけば任意のカーネルを利用できる。 したがって (帯域課金さえ気にしなければ ;-) 任意のディストリビューションの利用 (あるいは完全なカスタマイズ) が可能 (私はまだそこまではやっていない)。

なお、VPS サービスは探すといろいろあるようだ。 前述した 4社と比べると公開している情報が不十分 (特に Virtuozzo/OpenVZ な VPS は実際のパフォーマンスを予想しにくく、 実地に使ってみないとなんとも...) なので私は比較検討対象から外したが、 ダメ元で使ってみるのも面白いかもしれない:

サービス
プラン名
メモリ
ディスク
帯域
追加額/単位
月額
年額
データセンタの
場所
SplitServ
Xen 1024
1024MB
15GB
200GBs
$8/400GB
$14.95
$149.50
Los Angeles, CA
Kansas City, MO
WebKeepers
Virtuozzo ライト
512MB
50GB
?1280円
11760円
?
PhotonVPS
Xen VPS WARP 1
512MB
35GB
500GB
?
$16.95
$203.40
Los Angeles, CA
KnownHost
Virtuozzo VS2
512MB
30GB
750GB
$40/100GB
$35
$400
Los Angeles, CA
Dallas, TX
BurstNet
VPS PACKAGE #1
512MB
20GB
1000GB
$25/200GB
$5.95
$71.40
Scranton, PA
VPS NOC
OpenVZ Bronze
512 MB
20 GB
400 GB
?
$12.95
$155.40
Kansas City, MO
CoreNetworks
CoreMR
512MB
12GB
500GB
?
$19.95
$239.40
?
prgmr.com
Xen VPS
512MB
12GB
80GB
?
$12
$115.20
San Jose, CA
HostCadet
VPS Micro
512MB
10GB
400GB
?
$9.99
$119.88
Colorado Springs, CO
NY NOC OpenVZ
Super VPS #1
512MB
10GB
1000GB
?
$10.00
$120
Chicago, IL
North Bergen, NJ
ARP Networks
KVM/QEMU
512MB
10GB
100GB
?
$15
$180
Los Angeles, CA
VirtuallyDedicated
Xen VX384
384MB
24GB
300GB
?
$10.99
$131.88
Chicago, IL
Scranton, PA
GrokThis.net
Xen VPS
320MB
16GB
160GB
?
$20
$200
Philadelphia, PA
DMEHosting
OpenVZ VPS1
256MB
25GB
1000GB
$10/200GB
$5.95
$71.40
Denver, CO
Chicago, IL
Quantact
OpenVZ VS1
256MB
15GB
300GB
$1/GB
$14.99
$179.88
Santa Rosa, CA
RackspaceCloud
Xen CloudServers
256MB
10GB
0GB
$.08-$.22/GB
$10.95
$131.40
Dallas, TX
XENnode
XEN 256
256MB
10GB
200GB
?
$14.99
$143.99
Dallas, TX
Datarealm Xen
PowerVPS
256MB
10GB
1Mbps
Unmetered
$19.95
$215.52
?
HostVirtual Xen
XV0 Server
256MB
10GB
250GB
?
$14.95
$179.40
San Jose, CA
RackUnlimited
Xen RVPS-Starter
256MB
10GB
100GB
?
$8
$96
Asheville, NC
QuickWeb
Xen Lite
256MB
5GB
240GB
?
$17.95
$215.40
San Jose, CA
QuickVPS
Xen プラン1
256MB
10GB
50GB
?
2300円
21600円
日本国内
QuillHost
OpenVZ Standard
256MB
8GB
150GB
?
$9.99
$119.88
Washington, DC
Charlotte, NC

と、いうわけで、 Linode 360 を契約してみた。

(続きを読む...)
Filed under: システム構築・運用 — hiroaki_sengoku @ 08:58
2010年2月10日

CTO日記も livedoorブログから WordPress へ引越しました (URL は変更なし)

「仙石浩明の日記」 に続いて、 「仙石浩明CTO の日記」 も先週末に livedoorブログから自宅サーバへ引っ越した (つまりネームサーバの設定を変更して切替。有料プランの解約はこれから)。 もともと両ブログは相互にリンクを張って密接に連係していたので、 引越を機会に両者を統合した。

統合といっても両ブログは微妙(?)に読者層が異なると思われるし、 何よりページの体裁が大きく変わってしまっては読者の方々を戸惑わせてしまうので、 CTO日記を 「仙石浩明の日記」 の一カテゴリという位置付けにして、 かつページの体裁は WordPress のテーマを切り替えることによって、 どちらのブログもあまり大きな変化がないようにしている。

「仙石浩明CTO の日記」 http://sengoku.blog.klab.org/ をアクセスすると、 次のような PHP スクリプトを走らせた上で、 WordPress を呼び出す (末尾の require 文):

<?php
$new = NULL;
if ($_SERVER['REQUEST_URI'] == "/") {
    $new = "/blog/category/cto/";
} elseif ($_SERVER['REQUEST_URI'] == "/feed/") {
    $new = "/blog/category/cto/feed/";
} elseif (preg_match('@^/\d+/\d+/\d+/@',
		     $_SERVER['REQUEST_URI'], $matches)) {
    $new = $_SERVER['REQUEST_URI'];
...(中略)...
}
if ($new) {
    ...(中略)...
    $ORIG_SERVER_NAME = $_SERVER['SERVER_NAME'];
    $host = "www.gcd.org";
    $_SERVER['SERVER_NAME'] = $host;
    $_SERVER['REQUEST_URI'] = $new;
    $_SERVER['SCRIPT_NAME'] = $new;
    $_SERVER['PHP_SELF'] = $new;
    $abspath = "/usr/local/www/wordpress/";
    $themepath = "${abspath}wp-content/themes/sengoku_cto/";
    define('WP_USE_THEMES', true);
    define('TEMPLATEPATH', $themepath);
    define('STYLESHEETPATH', $themepath);
    require("${abspath}wp-blog-header.php");
...(中略)...
}
?>

つまり http://sengoku.blog.klab.org/ へのアクセスは、 パス名に 「/category/cto/」 を追加することによって、 CTO日記カテゴリへのアクセスに変換する。

ページの体裁については、 「wp-content/themes/sengoku_cto/」 ディレクトリが、 CTO日記のテーマフォルダで、 二つの PHP 定数 TEMPLATEPATH と STYLESHEETPATH をこのディレクトリへ設定することによって、 テーマの切り替えを行なっている。

テーマフォルダの中にあるテーマ関数ファイル 「functions.php」 は、 WordPress の初期化中に読み込まれるので、 ここに PHP スクリプトを書いておくことによって WordPress の挙動を変更することができる。 例えばブログのタイトルを 「仙石浩明CTO の日記」 に変更するには、 以下のスクリプトを functions.php に追加しておけばよい:

function option_blogname_cto() {
    return '仙石浩明CTO の日記';
}
add_filter('pre_option_blogname', 'option_blogname_cto');

つまり、 pre_option_blogname フックに、 option_blogname_cto フィルタを登録する。

WordPress では、 ブログのタイトルなど各種オプションの設定値 (DB に格納している) を、 get_option($setting) 関数を呼び出すことで参照している。 例えばタイトルは get_option('blogname') を呼び出すことで得られ、 URL は get_option('home') で得られる。

get_option($setting) 関数は wp-includes/functions.php で定義されていて、 以下のようにフィルタフック pre_option_* が定義されている:

function get_option( $setting, $default = false ) {
    global $wpdb;

    // Allow plugins to short-circuit options.
    $pre = apply_filters('pre_option_' . $setting, false);
    if ( false !== $pre )
	return $pre;
    ...(中略)...
}

つまり、 「pre_option_設定名」 というフックに登録されたフィルタが値を返すなら、 get_option はオプションの設定値ではなくフィルタが返した値を返すようになる。 前述の例なら、 「pre_option_blogname」 フックに登録された 「option_blogname_cto」 フィルタが 「仙石浩明CTO の日記」 という値を返すので、 get_option('blogname') も 「仙石浩明CTO の日記」 という値を返すようになり、 結果としてブログのタイトルを変更できる、というわけ。

ただし、 前述したように CTO日記は 「仙石浩明の日記」 の一カテゴリという位置付けなので、 ブログのタイトルを変更しただけだと、 ブログ 「仙石浩明CTO の日記」 の 「仙石浩明CTO の日記」 カテゴリということで、 ページのタイトル等が 「仙石浩明CTO の日記 » 仙石浩明CTO の日記」 という冗長なものになってしまう。 そこで、 以下のようなスクリプトを 「functions.php」 に追加して、 タイトルとカテゴリ名が同じときはカテゴリ名が表示されないようにする:

function single_cat_title_cto($category_name) {
    $name = get_option('blogname');
    if ($category_name == $name) return "";
    return $category_name;
}
add_filter('single_cat_title', 'single_cat_title_cto');

single_cat_title フックは、 wp-includes/general-template.php で定義されていて、 ページのタイトルなどに表示されるカテゴリ名を変更することができる。

以上で、 「仙石浩明の日記」 の一カテゴリを 「仙石浩明CTO の日記」 の体裁で見せることができるようになる。 しかし、 元が 「仙石浩明の日記」 であるだけに、 リンク先が全て 「仙石浩明の日記」 のページになってしまう。 例えば、 「仙石浩明CTO の日記」 のトップページの一番下に、 「古い投稿 »」 というリンクがあるが、 このリンク先が http://www.gcd.org/blog/page/2/ になってしまい、 たどると 「仙石浩明の日記」 のトップページの 2ページ目へ遷移してしまう。

また、 本文中 (あるいはサイドバー) に現れるリンクも、 DB のデータは 「仙石浩明の日記」 のパーマリンクを用いているので、 たとえそれが 「仙石浩明CTO の日記」 カテゴリに含まれていても、 そのリンクをたどると 「仙石浩明の日記」 の記事として表示されてしまう。

そこで、 遷移先も 「仙石浩明CTO の日記」 として表示したいリンクを、 フィルタで書き換えることにした。 つまり DB のデータは 「仙石浩明の日記」 へのリンクのままで、 ブラウザに送信する前に都度書き換える。

対象となるリンクは、 記事本文中だけでなく、 前述したページナビ 「古い投稿」 「新しい投稿」 や、 サイドバー (「人気記事」 や 「最近の投稿」) にも現れる。 ページ丸ごと (つまり HTTP レスポンス丸ごと) HTML を書き換えられるフックがあるとよかったのだが、 残念ながらそういうフックは定義されていないようだ。 以下のフックそれぞれについてリンクを書き換えればよさげ:

フィルタフック フィルタが変更できる対象, 第2引数, ...
the_content 記事本文 HTML
the_category 記事の末尾に表示されるカテゴリーリストの HTML,
$separator, $parents
get_pagenum_link ページ末尾に表示されるページナビ 「古い投稿」 「新しい投稿」 の URL
post_link 記事の URL (パーマリンク), $post, $leavename
widget_text サイドバーに表示されるテキストウィジェットの HTML, $instance
wp_list_categories サイドバーに表示されるカテゴリーのリストの HTML
category_feed_link カテゴリーの RSSフィードの URL, $feed

書き換え対象のリンクを決めるために、 まず CTO日記カテゴリに属す記事の ID を取得する:

function setup_cto_id() {
    global $wpdb;
    global $is_cto_id;
    $result = $wpdb->get_results("SELECT object_id FROM wp_term_relationships WHERE term_taxonomy_id=17", ARRAY_N);
    foreach ($result as $row) {
	$is_cto_id[$row[0]] = 1;
    }
}

あるカテゴリに属す記事ID のデータを取得する関数など、 WordPress に含まれているんじゃないかと探してみたのだが、 見つからなかったので DB に問合わせて取得するようにしてみた。 毎回 DB アクセスが発生してしまうが、 キャッシュとかはアクセス数が増えてから考える (^^;)。 「term_taxonomy_id=17」 が CTO日記のカテゴリ (決め打ち ^^;)。 CTO日記カテゴリに属す記事は、 配列 $is_cto_id[記事ID] に 1 を代入しておく。

次に URL を書き換えるスクリプト:

function replace_URL_cto($matches) {
    global $is_cto_id;
    global $ORIG_SERVER_NAME;
    if (!is_array($is_cto_id)) {
	setup_cto_id();
    }
    if (is_null($matches[2])) {
	if ($matches[1] == "category/cto/") return "http://$ORIG_SERVER_NAME/";
	return "http://$ORIG_SERVER_NAME/$matches[1]";
    } elseif ($is_cto_id[$matches[2]]) {
	return "http://$ORIG_SERVER_NAME/$matches[1]";
    }
    return $matches[0];
}

function convert_URLs_cto($text) {
    $textarr = preg_split("/(<.*>)/U", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
    $stop = count($textarr);
    for ($i = 0; $i < $stop; $i++) {
	$content = $textarr[$i];
	if (strlen($content) > 0) {
	    $content = preg_replace_callback(
		    '@http://www.gcd.org/blog/(\d+/\d+/(\d+)|category/cto/)@',
		    'replace_URL_cto', $content);
	}
	$output .= $content;
    }
    return $output;
}

顔文字を画像に変換して表示するフィルタ wptexturize (wp-includes/formatting.php で定義されている) を参考にさせてもらった。 preg_replace_callback() を使って書き換え対象リンクを探し、 replace_URL_cto で記事ID がCTO日記カテゴリに属す ($is_cto_id[$matches[2]] が TRUE) 場合のみ書き換える。

最後に、 この書き換えフィルタ replace_URL_cto を前述したフィルタフックに追加:

add_filter('the_content', 'convert_URLs_cto');
add_filter('the_category', 'convert_URLs_cto');
add_filter('get_pagenum_link', 'convert_URLs_cto');
add_filter('post_link', 'convert_URLs_cto');
add_filter('widget_text', 'convert_URLs_cto');
add_filter('wp_list_categories', 'convert_URLs_cto', 12000);
add_filter('category_feed_link', 'convert_URLs_cto');

wp_list_categories にフィルタを追加すると Category Order プラグインと衝突するので、 優先順位を下げて Category Order プラグインの後で実行されるようにしている。

また、 the_category, post_link, widget_text, category_feed_link 各フックは、 2つ以上の引数を持つが、 第1引数 (書き換え対象の HTML あるいは URL) のみ使用するので引数の数 (add_filter の第3引数) を省略している。

Filed under: システム構築・運用,プログラミングと開発環境 — hiroaki_sengoku @ 07:46
2010年1月28日

本ブログを livedoorブログから WordPress へ引越しました (URL は変更なし)

このブログ 「仙石浩明の日記」 は、 今まで livedoorブログの有料プラン (月額 315円) を利用していた。 比較的自由にデザインをカスタマイズできること、 URL のドメインを自由に設定できることから、 2006年3月9日にこのブログを開設して以来、 今まで 4年近く使い続けてきた。 しかし自前サーバでない不便さ、隔靴掻痒感はいかんともしがたく、 乗り換えを検討したことは何度もあって、 そのたびに不便さと乗り換えの面倒くささを天秤にかけて踏み切れずにいた。

例えば、 HTML文法エラーを修正できない問題点を見つけたときや、 ブログのサイドバーにある 「livedoor Reader」 「RSS」 「livedoor Blog」などのバナーを非表示にしていたら、 ライブドアから警告メールが来て、 やむなくバナー表示を復活させたときなど。 なかでも、 昨年ブログ管理画面 (有料プラン) がリニューアルされたときは、 とても大きなフラストレーションを感じた。 ライブドアはよかれと思ってリニューアルしているのだろうが、 JavaScript を多用した新管理画面は、 私にとっては使いにくいことこのうえないし、 生ログ取得スクリプトも使えなくなった。

そして、 今年に入って 「ブログ共通ヘッダー」 (最上端に表示される livedoor Blogのロゴ, 所属カテゴリ, テキスト広告) が勝手に表示されるようになったのが最後の背中のひと押しとなった。 これを非表示にしたければ PRO プラン (月額 315円) ではなく ADVANCE プラン (月額 840円) に変更しろという趣旨 (要は値上げ) なのだと思うが、 何の連絡もなく画面の一番目立つ最上端に、 醜悪なヘッダー (ADVANCE プランへの移行を促すためにわざと醜悪にしている? *_*) を勝手に挿入するデリカシーの無さに驚き呆れ、 自前サーバへの引越を決めた。

common header

ブラウザ画面最上端 ↑ の 「ブログ共通ヘッダー」 (所属カテゴリ 「IT > プログラミング 」 と 「受験にまつわるエピソードを教えてください」 という広告? 表示) は、 ライブドアのサーバが HTML 中に自動挿入する以下の JavaScript (settings/header.js) によって body 要素へ追加 (appendChild) されている。

(function () {
    var hd = document.createElement('div');
    var str = '';
    str += '<div id="header2" style="z-index:10001"><table cellspacing="0" class="blog-common-header" id="header">';
	...(中略)...
    hd.innerHTML = str;
    document.body.appendChild(hd);
	...(後略)...

ユーザがカスタマイズ可能な範囲の外であるため、 この JavaScript の実行そのものを止めることは無理っぽいが、 追加された子要素を削除する JavaScript を実行して消すことは可能。 例えば以下のようなコードを HTML ファイルのどこかに入れておけばよい。

<script type="text/javascript" src="http://www.gcd.org/sengoku/docs/livedoor.js"></script>

livedoor.js は、 body 要素の子要素 「header2」 を取り除く (removeChild) だけの JavaScript:

(function () {
    var element = document.getElementById("header2");
    element.removeChild(element.childNodes.item(0));
})();

正月早々 「ブログ共通ヘッダー」 が勝手に表示されるようになったことに気付き、 直ちにこの対策を行なって非表示にしたのだが、 以前バナーを非表示にしたときライブドアから警告メールが来たことを考えれば、 今回も警告メールが来るのは時間の問題だろうと、 引越を急いだ次第。

私はブログを書くときも HTML で書いていて、 あまり CMS 的な機能は必要としないので、 トラックバックとコメントを受付けるだけの簡単な Web アプリを書こうと思っていたのだが、 試しにインストールしてみた WordPress が思いの外シンプルな作りで、 ちょっといじっているうちに簡単にカスタマイズできてしまったので、 これを使うことにした。

livedoorブログでエクスポートしたデータを (Movable Type / TypePad のデータとして) WordPress でインポートしたら、 (pre 要素なのに) 行頭の空白文字が削除されてしまって往生したが、 大した量でもなかったので手作業で修正してしまった。 もう少し量が多かったら真面目に PHP コードを追ったのだが...

こんなに簡単にできるなら、 もっと早く WordPress へ移行すればよかった。 既存のテーマを見よう見まねでいじっただけなのだが、 以前のデザインをほとんどそのまま踏襲することができた (むしろ余計な div 要素を排除できたので、よりシンプルになった)。

livedoorブログと WordPress では URL (パーマリンク, permalink) の形式が異なる。 例えば、 あるエントリの URL は、 livedoorブログ (旧URL) と WordPress (新URL) で次のようになる:

旧: livedoorブログ新: WordPress
ベースURL http://blog.gcd.org http://www.gcd.org/blog
エントリ /archives/51168556.html /2008/04/154/
月別表示 /archives/2008-04.html /2008/04/
カテゴリ /archives/cat_50026701.html /category/enlighten/
RSS /index.rdf /feed/rdf/

一見して、 WordPress のパーマリンクの方が分かりやすい。 正確に言えば、 WordPress のパーマリンクの形式は任意に設定できて、 livedoorブログ (Movable Type ベース) の形式に合わせることも可能だが、 エントリID (上記の例だと 51168556 と 154) が異なるので形式を合わせること自体にはあまり意味はない。

livedoorブログを利用していた時の URL がそのまま使えるように、 旧URL を新URL へ変換する以下の PHP スクリプトを書いた (switch 文で URL ごとにずらずら case を並べただけ)。

<?php
$new = NULL;
if ($_SERVER['REQUEST_URI'] == "/") {
    $new = "/blog/";
} elseif ($_SERVER['REQUEST_URI'] == "/atom.xml") {
    $new = "/blog/feed/atom/";
} elseif ($_SERVER['REQUEST_URI'] == "/index.rdf") {
    $new = "/blog/feed/rdf/";
} elseif (preg_match('/^\/archives\/(20\d\d)-(\d\d)\.html$/',
		     $_SERVER['REQUEST_URI'], $matches)) {
    $year = $matches[1];
    $mon = $matches[2];
    $new = "/blog/$year/$mon/";
} elseif (preg_match('/^\/archives\/cat_(\d+)\.html$/',
		     $_SERVER['REQUEST_URI'], $matches)) {
    $id = $matches[1];
    $new = "/blog/category/";
    switch ($id) {
    case 50026701: $new .= "enlighten/";   break;
    case 50026704: $new .= "business/";    break;
    case 50026703: $new .= "engineer/";    break;
    case 50026699: $new .= "system/";      break;
    case 50035671: $new .= "hardware/";    break;
    case 50026700: $new .= "programming/"; break;
    case 50021362: $new .= "stone/";       break;
    case 50035209: $new .= "la-fonera/";   break;
    case 50041534: $new .= "hawaii/";      break;
    case 50045637: $new .= "hongkong/";    break;
    case 50026702: $new .= "others/";      break;
    default: $new = NULL;
    }
} elseif (preg_match('/^\/archives\/(\d+)\.html$/',
		     $_SERVER['REQUEST_URI'], $matches)) {
    $id = $matches[1];
    $new = "/blog/";
    switch ($id) {
    case 50071073: $new .= "2006/03/8/"; break;
	...(中略)...
    case 51168556: $new .= "2008/04/154/"; break;
	...(中略)...
    case 51552583: $new .= "2009/12/184/"; break;
    case 51555097: $new .= "2010/01/185/"; break;
    default: $new = NULL;
    }
}
if ($new) {
    $host = "www.gcd.org";
    $_SERVER['SERVER_NAME'] = $host;
    $_SERVER['REQUEST_URI'] = $new;
    $_SERVER['SCRIPT_NAME'] = $new;
    $_SERVER['PHP_SELF'] = $new;
    define('WP_USE_THEMES', true);
    require('/usr/local/www/wordpress/wp-blog-header.php');
} else {
    header("HTTP/1.1 301 Moved Permanently");
    header("Location: http://www.gcd.org/blog/");
    exit();
}
?>

http://blog.gcd.org/* にアクセスすると、 ↑ この PHP スクリプトが実行される。

これに加え、 逆方向の変換、 つまり新URL から旧URL への変換も必要。 例えば 「はてなブックマーク」 へ頂いたコメント (例えば前述したエントリに対するコメント一覧) は旧URL に紐づいているので、 各エントリに旧URL を登録しておく必要がある。 WordPress には カスタムフィールド という機能があって、ひとつのエントリに複数の 「キー」 と、 各キーにその値を登録することができる。 そこで沢山の 「ブックマーク」 を頂いたエントリには、 「hatena_b」 というキーで旧URL を登録しておくことにした。 すると、以下のような PHP スクリプトでブックマーク数 を表示できる。

<?php
  $hatena_b = get_post_meta($post->ID, "hatena_b", true);
  if ($hatena_b)
    echo ' <a href="http://b.hatena.ne.jp/entry/' . $hatena_b
       . '"><img style="border:0;" src="http://b.hatena.ne.jp/entry/image/'
       . $hatena_b . '" alt="" /></a>';
?>

最後に、 ネームサーバの設定変更を行なって、 blog.gcd.org レコード (TTL = 3時間) の値を 「CNAME blog-01.livedoor.jp」 から 「CNAME www.gcd.org」 へ変更した。 1/26 19:35 に変更を行なったところ、 19:46 には最初のアクセスが www.gcd.org へ届き、 20時台には大半のアクセスが www.gcd.org へ届くようになった。 20時台にライブドア側へ届いた blog.gcd.org へのアクセスはわずかに 7件、 その後は 1 時間〜数時間ごとに、ぱらぱらアクセスがあるという状況が続いている。 旧レコードを保持しているキャッシュネームサーバが残っていて、 そのネームサーバを使ってるブラウザからのアクセスのみが、 ライブドア側へ届いているといった感じ。

(続きを読む...)
Filed under: システム構築・運用 — hiroaki_sengoku @ 08:26
2009年11月12日

自宅回線を 100Mbps から 200Mbps へ、帯域を倍にしてみた ~ NGN が自宅にやってきた

私の自宅がある神奈川でも、 先月 13日から 「フレッツ 光ネクスト ファミリー・ハイスピードタイプ」 の申込受付が始まったので、 早速申し込んでみた。 今まで使っていたのは 「Bフレッツ ニューファミリータイプ」 だから、 下りの帯域が 100Mbps から倍の 200Mbps に増える。 月額利用料は変わらない。 工事費が 5827円かかるが、 キャンペーン期間中につき月額料金が 3ヶ月間無料になるので悪くない。

10/16日に NTT東日本 0120-116116 に電話して申し込んだところ、 回線切替工事まで最短で 10営業日かかり、 工事の予約状況によると最速で 11/1(日) の工事になるらしい。 続いて NTT コミュニケーションズ 0120-506-506 に電話して、 200Mbps に変更したときプロバイダ (OCN) 側の変更が必要か確認。 個人向けサービス (OCN 光 with フレッツ) は元々 200Mbps に対応しているから契約変更は必要無しとのことだったが、 あいにく私が契約しているのは法人向けサービスであるところの OCN 光アクセス IP8/IP16「Bフレッツ」プラン である。 この場合、 OCN 光アクセス IP8/IP16「フレッツ 光ネクスト」プラン への契約変更が必要となる。

NTT コミュニケーションズの担当者の話によると、 認証サーバの設定を変更するらしい。 PPPoE のユーザ名とパスワードはそのまま同じものを使っていい (新しいユーザ名も付与されるので新旧どちらのユーザ名も使える) が、 認証サーバ側の設定を変更すると旧回線 (つまり Bフレッツ) からの認証は拒否されるらしい。 すなわち認証サーバの設定変更後は、 新回線 (つまり フレッツ 光ネクスト) からでないと認証が通らない。 したがって、 回線が切り替わる前に PPPoE セッションが切れると、 旧回線のままでは再接続できなくなってしまう。 さらに、 設定変更は平日の未明 0:00 ~ 5:00 の間にしか実施しないらしい。

ということはつまり、 11/1(日) に回線工事を行なうには、 10/30(金) の未明に認証サーバの設定変更を行なうことになる。 10/30 未明から 11/1 の回線工事まで丸二日間、 もし PPPoE セッションが何らかの原因によって切れてしまうと、 工事完了後まで二度と再接続できなくなる。 最長丸二日間切れたままというのはあまりに不便なので、 11/2(月) の未明に認証サーバの設定変更を行ない、 同日午前 9:00-12:00 に回線切替工事を行なうことにした。 これなら PPPoE セッションが切れても数時間の切断で済む。

(続きを読む...)
Filed under: IPv6,システム構築・運用 — hiroaki_sengoku @ 10:00
2009年10月8日

__sync_bool_compare_and_swap_4 とは何か? ~ glibc をビルドする場合は、 gcc の –with-arch=i686 configure オプションを使ってはいけない

glibc-2.10.1 をビルドしようとしたら、 「__sync_bool_compare_and_swap_4 が定義されていない」 というエラーが出た:

senri:/usr/local/src/glibc-2.10.1.i386 % ../glibc-2.10.1/configure
	...
senri:/usr/local/src/glibc-2.10.1.i386 % make
	...
/usr/local/src/glibc-2.10.1.i386/libc_pic.os: In function `__libc_fork':
/usr/local/src/glibc-2.10.1/posix/../nptl/sysdeps/unix/sysv/linux/i386/../fork.c:79: undefined reference to `__sync_bool_compare_and_swap_4'
/usr/local/src/glibc-2.10.1.i386/libc_pic.os: In function `__nscd_drop_map_ref':
/usr/local/src/glibc-2.10.1/nscd/nscd-client.h:320: undefined reference to `__sync_fetch_and_add_4'
	...
/usr/local/src/glibc-2.10.1.i386/libc_pic.os: In function `*__GI___libc_freeres':
/usr/local/src/glibc-2.10.1/malloc/set-freeres.c:39: undefined reference to `__sync_bool_compare_and_swap_4'
collect2: ld returned 1 exit status
make[1]: *** [/usr/local/src/glibc-2.10.1.i386/libc.so] Error 1
make[1]: Leaving directory `/usr/local/src/glibc-2.10.1'
make: *** [all] Error 2

__sync_bool_compare_and_swap_4 は gcc の組み込み関数なので、 関数が未定義であることを示す 「undefined reference to ...」 というエラーメッセージは、 誤解を招く不親切なメッセージだと思う。

__sync_bool_compare_and_swap_4(mem, oldval, newval) は、 mem が指し示すメモリの値 (4バイト分) が oldval であれば newval に変更する、 という操作をアトミックに行なう組み込み関数。 アトミック (不可分) 操作とは、 操作の途中が存在してはいけない操作のことで、 この例なら比較 (メモリの値が oldval か?) と代入 (newval に変更) が必ず 「いっぺん」 に行なわれ、 「比較だけ行なったけどまだ代入は行なわれていない」 という状態が存在しないことを意味する。

アトミックに行なうためには、 当然ながら CPU でその操作をサポートしている必要がある (複数個の命令の列で実現しようとすると、 命令列の半ばを実行中の状態が必ず存在してしまう) わけだが、 残念ながら Intel 386 プロセッサでは、 この compare_and_swap (CMPXCHG 命令) をサポートしておらず、 サポートするのは Intel 486 以降の CPU である。 テストプログラムを書いて試してみる:

#include <stdio.h>

int main() {
    int mem[1], oldval, newval;
    oldval=0;
    newval=1;
    mem[0] = 0;
    __sync_bool_compare_and_swap(mem, oldval, newval);
    printf("mem[0]=%d\n", mem[0]);
    return 0;
}

見ての通り、 mem[0] の値を oldval の値 (0) と比較し、 一致していたら newval の値 (1) を代入し、 mem[0] の値を表示するだけのプログラムである。

関数名が 「__sync_bool_compare_and_swap」 であって、 後ろに 「_4」 がついていないことに注意。 gcc が引数の型 (この例では int) を見て、 その型のビット長を後ろにつけてくれる (この例では int 型は 4 バイトなので 「_4」 をつけてくれる)。

gcc では 「-march=タイプ」 オプションを指定することによって CPU タイプを指定できる。 -march オプションを指定しなかったり (この場合は全 CPU でサポートされる組み込み関数のみ利用できる)、 あるいは -march=i386 を指定したりすると、 コンパイル時にエラーになる:

% gcc -Wall test.c
/tmp/cc4eNX6L.o: In function `main':
test.c:(.text+0x3b): undefined reference to `__sync_bool_compare_and_swap_4'
collect2: ld returned 1 exit status
% gcc -Wall -march=i386 test.c
/tmp/cc6chtFj.o: In function `main':
test.c:(.text+0x36): undefined reference to `__sync_bool_compare_and_swap_4'
collect2: ld returned 1 exit status
% gcc -Wall -march=i486 test.c
% ./a.out
mem[0]=1

いまさら i486 というのもアレなので、 今なら i686 を指定するのがよさげ。 私の手元にはいまだ PentiumIII マシンがあるものの、 PentiumIII より古いマシンはない (昨年 ML115 と SC440 を買ったとき PentiumII マシンを引退させた) ので、 pentium3 を指定すれば SSE (Streaming SIMD Extensions) が利用できるようになるが、 glibc をビルドするときに必要かというと、 たぶん必要ない。

というわけでエラーの原因は分かったが、 では glibc をビルドするときは、 どうすればいいだろうか?

とりあえず google で検索してみたら、 gcc の configure オプションに 「--with-arch=i686」 を指定して gcc をビルドする必要がある、 と書いてあるページが見つかった。

--with-arch オプションは、 -march のデフォルトを設定するための configure オプションである。 つまり 「--with-arch=i686」 を指定して gcc を再インストールすると、 gcc に -march オプションをつけなくてもデフォルトが i686 になる。 なるほど確かにそうすれば、 glibc 側で何も変更せずに __sync_bool_compare_and_swap_4 関数が使えるようになりそうである。

いまどき i686 以前の CPU 用のコードが必要になりそうなケースは滅多にないだろうから、 -march オプションのデフォルトを i686 にするのも悪い選択ではないように思えた。 gcc をビルドし直すのは面倒だなーと思いつつも、 ついでに gcc のバージョンを上げておこうと gcc-4.3.4 をダウンロードしてきて 「--with-arch=i686」 付でビルドしてみた。

ところが!

(続きを読む...)
Filed under: システム構築・運用,プログラミングと開発環境 — hiroaki_sengoku @ 09:39
2009年9月14日

文字化けしていなくても MySQL 内の文字コードが正しくない場合がある

MySQL 5 からテーブルごとに文字列のエンコーディングを指定できるようになった (「そんなことは知ってるYO!」という人も多いと思うので、 そういう人は「これからが本題」 の部分まで読み飛ばして欲しい)。 例えばテーブルを作るときに、

mysql> CREATE DATABASE test;
Query OK, 1 row affected (0.05 sec)

mysql> USE test
Database changed
mysql> CREATE TABLE user ( name VARCHAR(255) ) CHARSET=utf8;
Query OK, 0 rows affected (0.05 sec)

などと 「CHARSET=utf8」 を指定すれば、 文字列を UTF-8 エンコーディングで格納する。 「CHARSET」 すなわち 「文字集合」 と、 エンコーディング (文字符号化) は本来別の概念であるが、 MySQL の場合は両者をまとめて CHARSET ないし character_set と呼んでいるので、 ここではそれを踏襲してキャラクタセットと呼ぶことにする。 MySQL のシステム変数のうちキャラクタセットに関連するものは、 以下のように沢山ある。

mysql> SHOW VARIABLES LIKE 'character\_set\_%';
+--------------------------+--------+
| Variable_name            | Value  |
+--------------------------+--------+
| character_set_client     | latin1 |
| character_set_connection | latin1 |
| character_set_database   | latin1 |
| character_set_filesystem | binary |
| character_set_results    | latin1 |
| character_set_server     | latin1 |
| character_set_system     | utf8   |
+--------------------------+--------+
7 rows in set (0.00 sec)

たくさんあってややこしいが、 重要なのは 「character_set_client」 と 「character_set_connection」 「character_set_results」 で、 この3変数はクライアントがクエリを送信し、 クエリ結果を受信するときのキャラクタセットを設定する。 charset コマンド (あるいは SET NAMES) を使うと、 クライアント側のキャラクタセットに関係するこの3変数を一度に変更できるので、 特に必要がなければこの3変数は常に同じキャラクタセット、 すなわちクライアント側で送受信するキャラクタセットに一致させておくとよい (PHP スクリプトから MySQL をアクセスするときは、 mysql_set_charset() を使ってクライアント側のキャラクタセットを設定する)。

mysql> CHARSET utf8
Charset changed
mysql> SHOW VARIABLES LIKE 'character\_set\_%';
+--------------------------+--------+
| Variable_name            | Value  |
+--------------------------+--------+
| character_set_client     | utf8   |
| character_set_connection | utf8   |
| character_set_database   | latin1 |
| character_set_filesystem | binary |
| character_set_results    | utf8   |
| character_set_server     | latin1 |
| character_set_system     | utf8   |
+--------------------------+--------+
7 rows in set (0.00 sec)

mysql> INSERT INTO user VALUES ('仙石 浩明');
Query OK, 1 row affected (0.00 sec)

mysql> SELECT name, HEX(name) FROM user;
+-----------------+--------------------------------+
| name            | HEX(name)                      |
+-----------------+--------------------------------+
| 仙石 浩明      | E4BB99E79FB3E38080E6B5A9E6988E |
+-----------------+--------------------------------+
1 row in set (0.00 sec)

このように 「select HEX()」 で確認すると、 文字列が正しく UTF-8 エンコーディングで格納されていることが確認できる。

蛇足だが、mysql クライアントが GNU readline を使っている場合は、 ~/inputrc などで 「set convert-meta Off」 を設定しておく。 デフォルトの readline では convert-meta が On なので、 キャラクタの最上位ビット (MSB) を 0 にしてしまう。 つまり UTF-8 (および EUC-JP や Shift_JIS) などの 8bit キャラクタセットだと、 MSB が 1 である文字が正しく送信されない。 例えば 「あ」 (UTF-8 で E3 81 82) を入力しようとしても、 MSB が落ちた 「c^A^B」 (63 01 02) が送られてしまう。

「character_set_server」 が latin1 になっていて気持ち悪いかも知れないが、 このシステム変数は新しく database を作るときのデフォルトを設定するものなので、 (latin1 な database は金輪際作らないというのでも無い限り) 変更する必要はない。

latin1 になっているもう片方の変数 「character_set_database」 は、 デフォルト database に合わせて (つまり USE コマンドを発行するごとに) サーバがこの変数を変更するので、 これもユーザが変更する必要はない。

前置きが長くなったが、これからが本題

UTF-8 なテーブルを読み書きする際は、 「charset utf8」 コマンドを送信してクライアント側のキャラクタセットを UTF-8 に設定すればよいのであるが、 デフォルトが latin1 であるクライアントも多い。 PHP などから MySQL サーバにアクセスする場合なども、 (PHP のビルド方法にも依存するが) デフォルトは latin1 になっている (PHP の場合 mysql_client_encoding() で確認できる)。 このようなクライアントをデフォルトの latin1 のままで使うとどうなるだろうか?

(続きを読む...)
Filed under: システム構築・運用 — hiroaki_sengoku @ 08:19
2009年1月8日

wpa_supplicant のバグ: TLS拡張 (TLS Extensions) 対応の OpenSSL と使うと、WPA EAP-TLS が常に失敗する

あけましておめでとうございます。 今年もよろしくお願いします。

自宅の無線LAN を WPA2-EAP (WPA2 エンタープライズ) にして 1ヶ月になるが、 すこぶる快適。 アクセスポイントがエンタープライズモードに対応していなければいけないのと、 RADIUS サーバが必要である上、 EAP-TLS を用いる場合は SSL クライアント証明書を発行する 認証局 (CA) を作る必要まであるので、 自宅LAN での導入にはややハードルが高いが、 いったん導入してしまえば WPA/WPA2 パーソナルより手間がかからない。

WPA/WPA2 パーソナルだと、 秘密のパスワードを全ての無線LAN 端末で共有するので、 端末の台数が増えてくるとどんどん漏洩のリスクが高まる。

自宅LAN でそんなに端末があるのか? という突っ込みが入りそうだが、 格安 NetBook や、 無線LAN 機能付スマートフォンなどを買っていると、 自宅LAN とはいえ、 「エンタープライズ」 的なニーズが出てくる。

WPA2 エンタープライズで認証方式として EAP-TLS を使うと、 無線LAN 端末にはそれぞれ個別の証明書をインポートしておくだけでよく、 万一その端末を紛失しても、 その端末の証明書を無効にするだけで済む。

EAP-TLS には対応機器/OS が多いというメリットもある。 無線LAN 端末として、 Windows VISTA, Windows XP, Ubuntu 8.04 LTS などを使ってみたが、 いずれもあっけないくらい簡単に接続できてしまった。 証明書さえインポートしておけば (Windows ならダブルクリックだけ)、 あとはパスワードを入力しなくてもいいぶん WPA/WPA2 パーソナルより設定が簡単。

既存の OS (Linux の場合はディストリビューション) で無線接続できるようになったので、 次は私が独自に構築した GNU/Linux (いわば my distribution) 上で WPA2 EAP-TLS 接続を試みた。

まず wpa_supplicant 0.5.11 をダウンロードしてコンパイル。 続いて設定ファイルである wpa_supplicant.conf を書く。

network={
	ssid="XXXXXXXXXX"
	key_mgmt=WPA-EAP
	eap=TLS
	identity="olevia"
	ca_cert="/usr/local/ssl/certs/GCD_Root_CA.pem"
	client_cert="/usr/local/ssl/certs/olevia.pem"
	private_key="/usr/local/ssl/private/olevia.pem"
	private_key_passwd=""
}

「ca_cert=」 「client_cert=」 「private_key=」 にそれぞれ CA の公開鍵、端末の公開鍵、端末の秘密鍵のファイル名を指定している。 で、wpa_supplicant を実行。

# wpa_supplicant -i eth1 -c /etc/wpa_supplicant.conf -d -D wext
Initializing interface 'eth1' conf '/etc/wpa_supplicant.conf' driver 'wext' ctrl_interface 'N/A' bridge 'N/A'
Configuration file '/etc/wpa_supplicant.conf' -> '/etc/wpa_supplicant.conf'
Reading configuration file '/etc/wpa_supplicant.conf'
Priority group 0
   id=0 ssid='XXXXXXXXXX'
Initializing interface (2) 'eth1'
EAPOL: SUPP_PAE entering state DISCONNECTED
EAPOL: KEY_RX entering state NO_KEY_RECEIVE
EAPOL: SUPP_BE entering state INITIALIZE
EAP: EAP entering state DISABLED
	...(中略)...

wpa_supplicant はデバッグモード (-d オプション) にすると大量のログを出力するので動作を追いにくいが、 wpa_supplicant のプログラムは状態遷移機械 (オートマトン) として動作するよう書かれているので、 「EAP: EAP entering state」 の部分を追っていくとよい。 「DISABLED」 と出力されているのが、 その時点におけるオートマトンの状態。

状態遷移機械 (オートマトン) と言ってしまうと、 コンピュータ/プログラムは全てオートマトンなのであるが (^^;)、 プログラミングの方法として状態遷移機械 (state machine) と言うときは、 各状態に名前を付けて、 各状態ごとの動作を (switch 文や if ... else if ... 文などで) 分けて書く方法を指す。

状態には INITIALIZE, DISABLED, IDLE, RECEIVED, GET_METHOD, METHOD, SEND_RESPONSE, DISCARD, IDENTITY, NOTIFICATION, RETRANSMIT, SUCCESS, FAILURE の 13状態がある。 もちろん 「SUCCESS」 が受理状態。 SUCCESS 状態は、 アクセスポイントが接続を許可した状態を意味する。

ところが、ログを追っていくと、

EAP: EAP entering state RECEIVED
EAP: Received EAP-Success
EAP: EAP entering state FAILURE
CTRL-EVENT-EAP-FAILURE EAP authentication failed

FAILURE 状態 (非受理状態) に遷移している (*_*)。当然、接続できず。
その直前に 「Received EAP-Success」 と出ているにもかかわらず!

アクセスポイント側 (正確に言うと RADIUS サーバ) のログを調べてみると、 ちゃんと接続を許可している (EAP-Success を送っているのだから当然だが)。 一体これはどうしたことか?

私がコンパイルした wpa_supplicant に問題があるのかと思って、 この wpa_supplicant を、 既に接続できることが確認済みの Ubuntu 上にコピーして実行してみる。 「Received EAP-Success」 をログの中から探してみると、

EAP: EAP entering state RECEIVED
EAP: Received EAP-Success
EAP: EAP entering state SUCCESS
CTRL-EVENT-EAP-SUCCESS EAP authentication completed successfully

ちゃんと SUCCESS 状態に遷移しているし、接続もできた。
同一バイナリなのに、異なる環境 (ディストリビューション) だと、 どうして結果が異なるのか?

どのような可能性が考えられるだろうか? 腕に覚えがあるかたは、 この続きを見ずに (といっても、既にタイトルでネタバレしてしまっているが ^^;) 原因を推測してみてはいかがだろうか?

(続きを読む...)
Filed under: システム構築・運用 — hiroaki_sengoku @ 08:58
2008年10月27日

initramfs シェル環境 (initramfs shell environment) でジョブ制御する方法 (aka “can’t access tty; job control turned off” を消す方法)

GNU/Linux OS のブート時に、init(8) を経由せずにシェル (/bin/sh) を実行すると、 このシェル上ではジョブ制御 (job control) が行なえない。 つまりこのシェル環境は制御端末 (controlling tty) に成れない。 これがどんなに不便かというと、 自動的に止まらないプログラム (例えばオプション無しで ping を実行したときなど) を止める方法が無いわけで、 いったんそういうプログラムを動かしてしまったら最後、 CTRL-ALT-DEL で reboot させる他なくなってしまう。

そもそも、なぜ init(8) を起動する前に /bin/sh を実行したいかというと、 ミニルート (initramfs) 上で 作業を行ないたいから。 initramfs の init (これは init(8) ではなくシェル・スクリプト) の中で、 BusyBox の /bin/sh (/bin/ash) を exec する (つまり PID=1) ことによって、 initramfs 上での作業を可能にする。

init(8) は、 GNU/Linux OS の全てのプロセスの親プロセスだが、 その万物の親すら生まれていない創世記以前に作業を行なえるメリットは数多い。 例えば、ルート・ファイル・システム (root file system) すらマウントしていない段階なので、 マウント後 (つまり init(8) 起動後) には実行不可能な操作 (xfs_repair などのファイルシステム修復操作とか) を行なうことができる。 しかもこのシェルはプロセスID が 1番なので、 このシェル環境上で root file system を「/」にマウントし、 続いて init(8) を exec すれば、 そのまま GNU/Linux OS を起動することができる。

root file system のメンテナンス等は、 別の起動ディスク (CD-ROM や USB メモリ等) からブートして行なうのが一般的だが、 CD-ROM ドライブや USB メモリを準備したり、 あるいは CD-ROM や USB メモリの抜き差しが必要になったりと、 なにかと面倒である。 メンテナンス用の起動パーティションを root file system とは別に用意する、 という方法も考えられるが、 メンテナンス専用のパーティションを維持管理するのが面倒くさい (普段使わないものほど陳腐化して、いざというとき役に立たない)。
initramfs だとハードディスクすら不要 (例えば PXE ブート) でメンテナンスが可能になるし、 普段 GNU/Linux OS 起動用として使ってる initramfs が、 そのまま非常用のメンテナンス環境になるため、 陳腐化する心配がない。
実は「突然死したハードディスクを復旧させる『お手軽パック』」は、 initramfs そのものだったりする。 しかも「復旧」用として作った initramfs というわけではなく、 私が普段 GNU/Linux OS を ブートするときに使っている initramfs と 全く同じものである (だからこそ ハードディスクの突然死問題 が勃発した直後にリリースできた)。

というわけで、 いいことづくめの initramfs シェル環境 (initramfs shell environment) なのだが、 「復旧お手軽パック」実行例 にもあるように、 init(8) 以前の段階で /bin/sh を実行すると

/bin/sh: can't access tty; job control turned off
#

と表示してジョブ制御がオフになってしまう。 つまりこのシェル環境では、 プログラムを実行中に control-C (^C) を押しても止める (正確にいうと SIGINT シグナルを送る) ことができない。

ジョブ制御 (^C などでシグナルを送ること) ができない状態に陥って 初めて沸き起こる制御端末 (controlling tty) に対する感謝の念なのであるが、 initramfs が役目を終えて init(8) が起動して (GNU/Linux OS がブートして) しまうと、 喉元過ぎればなんとやらで 「can't access tty」をなんとかしようという意欲は雲散霧消し、 そのままになっていた。

制御端末になれない initramfs シェル環境に対して何十回目かの悪態をついた後、 ようやく対策を立てるべく原因を調べてみることにした。

Linuxカーネル(ドライバ)のソースを読んでみたところ、 以下の端末デバイスは制御端末に成れないのです。 興味がある人は、ソース、 drivers/char/tty_io.cのtty_open()を見てみてください。

* /dev/console -- カーネルの起動時の端末。
* /dev/tty0 -- tty1~の「Linux Virtual Terminal」のうち、現在表示している物を示す。
* /dev/tty -- 現在使っている端末を示す。
* PTYのマスター側
Linux:制御端末 から引用

initramfs シェル環境で使っている端末は /dev/console だから制御端末になれない。 だから BusyBox には /dev/console という仮想的な端末ではなく、 本物のデバイスを探すための cttyhack というプログラムが付属している。 /bin/sh を実行する代わりに cttyhack /bin/sh を実行すれば ジョブ制御ができると BusyBox のマニュアルには書いてある。

...という解説は上に引用したページをはじめ、 WWW 上のあちらこちらのページで見かけるし、 私としても当然そんなことは先刻承知で、

/bin/sh: can't access tty; job control turned off
# tty
/dev/console
# cttyhack sh
sh: can't access tty; job control turned off
# tty
/dev/tty1
#

などと、確かに cttyhack の働きにより /dev/console ではなく /dev/tty1 を使うようになったものの、 相変わらず「can't access tty」エラーが出ているので困っているわけである。 cttyhack を使っているのにジョブ制御できないわけで、 cttyhack-- と思っていた。

前置きが長くなったが、ここからが本題である。

(続きを読む...)
Filed under: システム構築・運用 — hiroaki_sengoku @ 07:38
2008年3月19日

フレッツ・ドットネットを解約したら、フレッツ網 router へ ping6 できなくなった!

昨年12月26日に、 NTT東日本から 「BフレッツにおけるIPv6映像視聴等機能の標準装備について」 というお知らせが来た。 3月3日より、 「Bフレッツ」に IPv6 映像視聴等機能を標準装備するので、 フレッツ・ドット・ネットの契約が不要になるとのこと。

現在、IPv6映像視聴等機能は「フレッツ・ドットネット」にて 提供しておりますが、 平成20年3月3日(月)以降は、 ブロードバンド映像サービスのみ をご利用の場合は、 「フレッツ・ドットネット」のご契約が不要になります。 これにより解約を希望されるお客さまにつきましては、 平成20年3月3日(月) より※3受付を開始いたします。  なお、ブロードバンド映像サービス以外で、 「FdNネーム」「FdNディスク」「FdNディスクビューセレクト」「FdNナンバー」等 「フレッツ・ドットネット」サービス※4をご利用の際には、 引き続き「フレッツ・ドットネット」のご契約が必要となりますのでご注意ください。

私は IPv6 機能のためだけに「フレッツ・ドットネット」を契約していて、 「FdNネーム」「FdNディスク」「FdNディスクビューセレクト」「FdNナンバー」等の サービスを利用したことはない (「ブロードバンド映像サービス」も利用していない) ので、 フレッツ・スクウェアトップから、 サービス申込受付を選んで、「フレッツ・ドットネット」を解約した。

ところが!

フレッツ網側の v6 ルータが ping に反応しなくなった。

senri % ping6 -n router.flets.gcd.org
PING router.flets.gcd.org(2001:c90:XXXX:XXXX:2d0:2bff:fe30:b91a) 56 data bytes
From 2001:c90:XXXX:XXXX:2d0:2bff:fe30:b91a icmp_seq=1 Destination unreachable: Administratively prohibited
From 2001:c90:XXXX:XXXX:2d0:2bff:fe30:b91a icmp_seq=2 Destination unreachable: Administratively prohibited

--- router.flets.gcd.org ping statistics ---
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1000ms

ちなみに、 この v6 ルータからの router advertisement は正常に流れてきている:

senri # tcpdump -i eth1 -vvv ip6
tcpdump: listening on eth1, link-type EN10MB (Ethernet), capture size 96 bytes
	...
13:02:02.904899 fe80::2d0:2bff:fe30:b91a > ff02::1: icmp6: router advertisement(chlim=64, pref=medium, router_ltime=1800, reachable_time=0, retrans_time=0)(src lladdr: 00:d0:2b:30:b9:1a)(mtu: mtu=1500)[ndp opt] [class 0xe0] (len 64, hlim 255)

v6 ルータ越えの通信が全て禁止されてしまったらしく、 フレッツ網に接続している他サイトとの IPv6 通信も同様に禁止されて (Administratively prohibited) しまった。 例外は、フレッツ・スクウェアv6 へのアクセス:

senri % ping6 -n flets-v6.jp
PING flets-v6.jp(2001:c90:ff:1::1) 56 data bytes
64 bytes from 2001:c90:ff:1::1: icmp_seq=1 ttl=52 time=5.05 ms
64 bytes from 2001:c90:ff:1::1: icmp_seq=2 ttl=52 time=4.55 ms

--- flets-v6.jp ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 4.554/4.803/5.053/0.258 ms

おそらく「ブロードバンド映像サービス」を提供するサーバへの IPv6 通信も 許可されているのだろう。 つまり、 「BフレッツにIPv6映像視聴等機能を標準装備」 という NTT東日本の発表のココロは、 IPv6 映像サービスへの IPv6 通信*のみ*許可するということであって、 それ以外の IPv6 通信は対象外ということのようだ。

「フレッツ・ドットネット」サービスの概要には、 「フレッツ・ドットネットサービス機能一覧」として、

・FdNネームが1つ利用できます。
・FdNディスク(100MB)で、ファイル共有が利用できます。
・FdNディスク(100MB)には、最大10のグループメンバーを登録できます。
※NTT東日本が無料で提供する専用ソフトウェア「FLET'S.Netメッセンジャーにより、 ファイル転送やビデオチャットが利用できます。

が列挙されているのみであって、 IPv6 通信の許可/不許可について言及していないのは、 とてもミスリーディングな記述だと思う。

慌てて再度フレッツ・ドットネット契約を (フレッツ・スクウェアで) 申込むと、 10分ほどで再び IPv6 通信ができるようになった:

senri % ping6 -n router.flets.gcd.org
PING router.flets.gcd.org(2001:c90:XXXX:XXXX:2d0:2bff:fe30:b91a) 56 data bytes
64 bytes from 2001:c90:XXXX:XXXX:2d0:2bff:fe30:b91a: icmp_seq=1 ttl=64 time=3.11 ms
64 bytes from 2001:c90:XXXX:XXXX:2d0:2bff:fe30:b91a: icmp_seq=2 ttl=64 time=0.920 ms

--- router.flets.gcd.org ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.920/2.019/3.118/1.099 ms

フレッツ網を経由した IPv6 通信を利用しているかたは、 たとえフレッツ・ドットネットサービスを利用していなくても、 フレッツ・ドットネットを解約すべきではないので、 ご注意のほどを!

Filed under: IPv6,システム構築・運用 — hiroaki_sengoku @ 15:32
Older Posts »