linux: 2008年5月アーカイブ

B○G IPとか買うお金がなく、NIC追加したりするためのサーバ停止する機会すらなく、30分でロードバランサを構築したい場合、poundでリバースプロキシを構築すると便利です。

■■ やりたいこと

1台のリバースプロキシを、httpやhttpsアクセスの矢面に立たせる
バックエンドには、2台のhttpサーバがあり、それぞれhttpd.confベタ書きかIPベースのバーチャルホストを構築する

このコンテンツのために構築したサーバは以下の通りです。
リバースプロキシ:www.example.com 192.168.0.4 (CentOS4.6)
httpサーバ1:be1.example.com 192.168.0.120 (CentOS5.1)
httpサーバ2:be2.example.com 192.168.0.121 (CentOS5.1)

なお、www.example.comでapacheが動いている場合、192.168.0.4でhttpとhttpsをlistenしないようにします。
httpd.confで
Listen 80
となっているところを
Listen 192.168.0.3:80
のように、192.168.0.4以外のIPアドレスでlistenするようにします。
ssl.confでも、
Listen 443
となっているところを
Listen 192.168.0.3:443
とします。

■■ poundのインストール

※ ソースRPMをダウンロードして自分でビルドしてRPMパッケージを作るので、あらかじめrpm-buildをインストールし、ホームディレクトリ以下に.rpmmacrosファイルとrpm/{BUILD,RPMS,SOURCES,SPECS,SRPMS}ディレクトリを作成しておいてください。

poundの公式サイトから、パッケージをダウンロードしてきます。
ブラウザをちょっとだけスクロールすると、
 ・ RPMs for RedHat are available at http://www.invoca.ch/pub/packages/pound/
という文字列が見えるので、右クリックしてリンク先のURLをコピーします。

ターミナルから
$ wget http://www.invoca.ch/pub/packages/pound/pound-2.4.2-1.src.rpm
で、SRPMパッケージをダウンロードしてきます。なお、これは執筆時点での最新バージョンです。

次に、ダウンロードしてきたSRPMファイルを展開します。
$ rpm -ivh pound-2.4.2-1.src.rpm

SPECファイルを確認します。
$ cd rpm/SPECS/
$ less pound.spec
(中略)
BuildRequires: openssl-devel, pkgconfig, pcre-devel, google-perftools-devel
BuildRequires: sed, perl
と書いてあるので、openssl-devel、pkgconfig、pcre-devel、google-perftools-devel、sed、perlがインストールされているか確認します。

sedとperlは、恐らくほとんどの環境ではインストールされていると思います。
openssl-devel、pkgconfig、pcre-develはbaseリポジトリにあるので、何のオプションもなしにyumコマンドでインストールできるはずです。

google-perftools-devel は、RPM Searchから探して持って来ました。なお、google-perftools-develをインストールするには、google-perftoolsも必要になります。

$ wget ftp://ftp.pbone.net/mirror/download.fedora.redhat.com/pub/fedora/epel/4/i386/google-perftools-devel-0.92-1.el4.2.i386.rpm
(↑2行に見えますが実際は1行です)
$ wget ftp://ftp.pbone.net/mirror/download.fedora.redhat.com/pub/fedora/epel/4/i386/google-perftools-0.92-1.el4.2.i386.rpm
(↑2行に見えますが実際は1行です)

$ rpm --test -ivh google-perftools-0.92-1.el4.2.i386.rpm google-perftools-devel-0.92-1.el4.2.i386.rpm
$ su -
# rpm -ivh google-perftools-0.92-1.el4.2.i386.rpm google-perftools-devel-0.92-1.el4.2.i386.rpm
# exit

続いて、一般ユーザ「oresama」に戻ってからpoundのRPMをビルドします。

$ cd ~/rpm/SPECS/
$ rpmbuild -ba pound.spec

ビルドが終わったら、poundの依存関係を確認します。

$ cd ../RPMS/i386/
$ rpm --test -ivh pound-2.4.2-1.i386.rpm

何も問題なければ、インストールを行います。

$ su -
# cd /home/oresama/rpm/RPMS/i386/
# rpm -ivh pound-2.4.2-1.i386.rpm


■■ pound.cfgを編集する

# cd /etc/pound
# cp -p pound.cfg pound.cfg.orig
# vi pound.cfg
User "nobody"
Group "nobody"
RootJail "/usr/share/pound"
Control "/var/run/pound/ctl_socket"

# Main listening ports
ListenHTTP
    #Address 0.0.0.0
    #poundで待ち受けるIPアドレスを指定する。
    Address 192.168.0.4
    Port    80
    xHTTP   1
End
ListenHTTPS
    #Address 0.0.0.0
    #poundで待ち受けるIPアドレスを指定する。
    Address 192.168.0.4
    Port    443
    #秘密鍵と証明書がひとまとめになったファイルを指定する。
    Cert    "/usr/share/ssl/certs/pound.pem"
    Ciphers "ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL"
    xHTTP   1
End

# Catch-all server(s)
Service
    BackEnd
        #バックエンドで動くサーバのIPアドレスを指定する。
        Address 192.168.0.120
        Port    80
        #優先度を1から9で指定する。数字が大きいほど優先度が高くなる。
        Priority 1
    End
    BackEnd
        Address 192.168.0.121
        Port    80
        Priority 1
    End
    Session
        #セッションの保持時間を秒単位で指定する。
        #ベーシック認証のほか、クッキーなども指定できるようだ。
        Type    BASIC
        TTL     300
    End
End

設定ファイルはここまで。


■■ 証明書作成

# cd /usr/share/ssl/certs/

まずはpound.pemのバックアップを取ります。

# cp -p pound.pem pound.pem.orig
# echo -n "" > pound.pem

■ 秘密鍵の生成

# openssl genrsa -des3 -out server-key.pem 1024

■ CSRの生成

# openssl req -new -key server-key.pem -out server-csr.pem

■ 秘密鍵からパスフレーズを取り除く

# cp -p server-key.pem server-key.pem.orig
# openssl req -text -noout -in server-csr.pem

■ 証明書作成

# openssl x509 -in server-csr.pem -out server-crt.pem -req -signkey server-key.pem

■ 秘密鍵と証明書をひとつのファイルにまとめる

# cat server-key.pem > pound.pem
# cat server-crt.pem >> pound.pem


■■ 起動前の確認

poundを起動する前に、poundコマンドの引数に-cをつけて、設定の確認をします。

# pound -c
starting...
Config file /etc/pound/pound.cfg is OK

このように表示されたら、設定ファイルの書式はあっています。
設定ファイルの書式が間違っていると
# pound -c
starting...
line 7: unknown directive "ListenHTTTP " - aborted
というように、設定ファイルの行番号つきで「ここ間違ってるぞ」と教えてくれます。

また、Certファイルが壊れていても
# pound -c
starting...
line 17: SSL_CTX_use_certificate_chain_file "/usr/share/ssl/certs/pound.pem" failed - aborted
error:0906D064:PEM routines:PEM_read_bio:bad base64 decode
このようにエラーが出ます。


■■ pound起動

# /etc/init.d/pound start
Starting pound:                                            [  OK  ]

■■ pound自動起動設定

# chkconfig pound --list
pound           0:off   1:off   2:off   3:off   4:off   5:off   6:off

# chkconfig pound on

# chkconfig pound --list
pound           0:off   1:off   2:on    3:on    4:on    5:on    6:off


■■ テスト

2台あるバックエンドサーバのドキュメントルートに、ちょっとずつ内容の違うhtmlファイルを、同じファイル名(ここではindex.htmlとしましょう)でアップロードします。

http://192.168.0.4/
https://192.168.0.4/

にそれぞれアクセスし、何度かリロードして、違う表示が交互に出てきたら、正しく振り分けられています。勿論、規則正しく振り分けてくれるわけではありません。


■■ 運用

poundのログは、/var/log/messagesに出力されます。ここに、アクセスログが記録されます。
バックエンドにあるapacheのアクセスログのフォーマットはデフォルトで
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
と設定されていますが、この状態だと、poundが動いているサーバのIPアドレスが記録されてしまいます。なので、httpd.confの修正を行う必要があります。

ここを
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

こうする
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
#LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined


以上で、ごくごく簡単なロードバランサができました。
ね、簡単でしょ?

前のエントリーでping監視の1行コマンドを書きましたが、pingコマンドのオプションで気になったことをメモ。

まず、「-s 1」で1バイトのパケットを投げてるのに、どうして
9 bytes from 192.168.0.1: icmp_seq=0 ttl=255
みたいなレスポンスが帰ってくるかっていうと、「8バイトのICMPヘッダと1バイトのパケットの合計」なので、辻褄があうのです。試しに1バイトのパケットを投げたときの出力結果と8バイトのパケットを投げたときの出力結果を比較してみましょう。

■ 1バイトのパケットを投げたとき

$ ping -c 1 -s 1 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 1(29) bytes of data.
9 bytes from 192.168.0.1: icmp_seq=0 ttl=255

--- 192.168.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
, pipe 2


■ 8バイトのパケットを投げたとき

$ ping -c 1 -s 8 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 8(36) bytes of data.
16 bytes from 192.168.0.1: icmp_seq=0 ttl=255 time=0.552 ms

--- 192.168.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.552/0.552/0.552/0.000 ms, pipe 2


このように、自分が投げたパケットサイズにプラスして、8バイトのヘッダがくっつくのです。

あと、jmanのping(8)の項目を見て思ったのですが、
> -s packetsize
> 何バイトのデータが送られるかを指定する。デフォルトは 56 で、 ICMP ヘッダの 8 バイトを加えて、
> 64 バイトの ICMP データになる。 スーパーユーザーだけがこのオプションを使用できる。
ってあるんだけど、rootじゃないのにこのオプション使えてるのです。どーでもいーっちゃいーんだけど気になったので、
手元の端末でmanを見てみた。

$ man ping
(中略)
> -s packetsize
> Specifies  the number of data bytes to be sent.  The default is 56, which translates into 64 ICMP data bytes when combined with
> the 8 bytes of ICMP header data.

とだけ書いてあるので、特にスーパーユーザーでないとこのオプションが使えないってわけじゃないのかなーと。
ちなみに、(っ´∀`)っ ゃーのマシンにインストールされているpingのバージョンは

$ ping -V
ping utility, iputils-ss020927

で、pingコマンドが含まれているパッケージ「iputils」のバージョンは

$ rpm -q iputils
iputils-20020927-19.EL4.5

です。
タイトルのまんま、複数のホストにpingを打ってその結果をわかりやすく出力してみたくなったので、
一行大好きな(っ´∀`)っ ゃーは、pingを打つ対象をfor文でグルグルまわし、pingが通ったら「生きてるよー」
死んでたら「死んでるよー」と出力するようにしました。

以下の例は、192.168.0.1から192.168.0.8まで、それぞれに1発ずつ1バイトのパケットを投げて
pingによる死活監視をし、終了ステータスから判断して生きてたら「(時刻)(TAB)~~~ is alive」、
死んでたら「(時刻)(TAB)~~~ is dead」という返事をさせます。

$ for i in 192.168.0.{1..8}; do ping  -c 1 -s 1 $i | grep "bytes from" > /dev/null ; if [ 0 -eq `echo $?` ]; then echo `date`$'\t'$i' is alive'; else echo `date`$'\t'$i' is dead' ; fi; done

出力結果(例)は以下の通りです。
Fri May 2 17:21:12 JST 2008     192.168.0.1 is alive
Fri May 2 17:21:15 JST 2008     192.168.0.2 is dead
Fri May 2 17:21:15 JST 2008     192.168.0.3 is alive
Fri May 2 17:21:18 JST 2008     192.168.0.4 is dead
Fri May 2 17:21:21 JST 2008     192.168.0.5 is dead
Fri May 2 17:21:24 JST 2008     192.168.0.6 is dead
Fri May 2 17:21:27 JST 2008     192.168.0.7 is dead
Fri May 2 17:21:30 JST 2008     192.168.0.8 is dead

コマンドの途中に、pingの出力を/dev/nullに捨てていますが、もし「 > /dev/null」がないと、以下のようになります。

$ for i in 192.168.0.{1..8}; do ping  -c 1 -s 1 $i | grep "bytes from" ; if [ 0 -eq `echo $?` ]; then echo `date`$'\t'$i' is alive'; else echo `date`$'\t'$i' is dead' ; fi; done
9 bytes from 192.168.0.1: icmp_seq=0 ttl=255
Fri May 2 17:24:57 JST 2008     192.168.0.1 is alive
Fri May 2 17:25:00 JST 2008     192.168.0.2 is dead
9 bytes from 192.168.0.3: icmp_seq=0 ttl=64
Fri May 2 17:25:00 JST 2008     192.168.0.3 is alive
Fri May 2 17:25:03 JST 2008     192.168.0.4 is dead
Fri May 2 17:25:06 JST 2008     192.168.0.5 is dead
Fri May 2 17:25:09 JST 2008     192.168.0.6 is dead
Fri May 2 17:25:12 JST 2008     192.168.0.7 is dead
Fri May 2 17:25:15 JST 2008     192.168.0.8 is dead

ちょっと見づらいですね。
カスタム検索

カレンダー