このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

nginx(エンジンエックス)をWindows上で動かして、SSLとリバースプロキシーの実験をした

我々のWebフレームワークCatyでは、Pythonで実装したサーバーによりWebサイト/Webアプリケーションを動かしています。このCatyサーバーは、次のような点で十分とは言えません。

  1. 仮想ホストの実現
  2. HTTPSSSL)通信
  3. キャッシュによるWebアクセラレーション
  4. 詳細かつ柔軟なロギング
  5. 複数プロセスを使ったパフォーマンス向上

とはいえ、Webサーバーとしての機能を充実させることはCatyの目的ではないので、不足な部分は既存のサーバーソフトウェアの力を借りることにします。Apache + (mod_python or mod_wsgi)、lighttpd + FastCGI といったところが候補でしょうが、結局、nginx(エンジンエックス)を使うことにしました。現状のCatyサーバー(caty-server.py)には特に手を加えないで、nginxを手前に置くという構成です。

先週末に、さくらインターネットのサーバーにリモートログインしながらゴチャゴチャと調べたり考えたりしていました。そのときのメモ(と愚痴)は次のエントリーに記録してあります。

リモートログインが面倒になったので、Windows版のnginxを使って実験をすることにしました。以下は、その実験の記録です。僕は、サーバーのセットアップとか管理の知識がろくにないので、解説よりは手順と結果が中心。最後に感想が書いてあります。

内容:

  1. nginxに何をさせたいのか
  2. Windows版nginxのインストール
  3. プロセスに関する注意
  4. Windows版OpenSSLのインストール
  5. SSL使うための鍵だの証明書だのを作る
  6. nginxのSSLを設定する
  7. FirefoxSSLページにアクセスしてみると
  8. 設定ファイルを変えては試す
  9. Catyサーバーのフロントにnginxを使う
  10. Catyとnginx

nginxに何をさせたいのか

nginxは静的コンテンツを高速に配信できるWebサーバーですが、リバースプロキシーとしての機能を持っています。今回使いたいのは、リバースプロキシーの機能です。リバースプロキシー機能を使うと、サーバーマシンのポート80番にやってきたHTTPリクエストを、nginxが、背後にいるCatyサーバーに渡してくれます。単にポート80番でサービスするだけならCatyサーバーにもできますが、複数のCatyサーバー達をうまく使い回したり、Catyサーバーが苦手な作業を肩代わりしてくれる役割をnginxに期待しているのです。

とりあえず、今のCatyサーバーはHTTPSSSL)に対応してないので、HTTPS通信のラップ/アンラップをnginxにやらせてみることにします。Catyサーバー自体でもHTTPSを扱えるようにする予定(そうしないとHTTPSのテストに不便)ですが、実際の運用ではnginxにHTTPSハンドリングを任せたほうがいいでしょう。

そんなわけで、nginxによりHTTPSをほどいてCatyサーバーに渡すことを実験の目標にします。

Windows版nginxのインストール

http://www.nginx.org/en/download.html からzipファイルをダウンロドできます。http://www.nginx.org/en/docs/windows.html によると、最新版の0.8.32を使うのがいいと書いてあるのですが、なんかうまく動かないので、僕は安定版の0.7.64を使いました(僕がなんか勘違いしている可能性もあります)。使ったファイルは http://www.nginx.org/download/nginx-0.7.64.zip です。

インストールはこのzipを展開するだけです。Windowsインストーラーにはなってないし、GUIも付いてないので取っつきにくいかも知れませんが、使い方も設定も簡単です。ドキュメンテーションhttp://www.nginx.org/en/docs/http://wiki.nginx.org/ にあります。

以下では、zipを展開してできたディレクトリをNGINX_HOMEと記します。僕の場合は C:\Installed\nginx\nginx-0.7.64\ がNGNX_HOMEです。パス名はときに c:/Installed/nginx/nginx-0.7.64/ のようにも書きます。

さて、試しに起動と終了。これは非常に簡単です。

  • NGINX_HOME>start nginx

この後、ブラウザで http://localhost/ にアクセスすると、Welcome to nginx! というページが表示されるはずです。終了は以下のコマンドを使います。

  • NGINX_HOME>nginx -s stop

なお、nginx -h でヘルプが出ます。


C:\Installed\nginx\nginx-0.7.64>nginx -h
nginx version: nginx/0.7.64
Usage: nginx [-?hvVt] [-s signal] [-c filename] [-p prefix] [-g directives]

Options:
-?,-h : this help
-v : show version and exit
-V : show version and configure options then exit
-t : test configuration and exit
-s signal : send signal to a master process: stop, quit, reopen, reload
-p prefix : set prefix path (default: NONE)
-c filename : set configuration file (default: conf/nginx.conf)
-g directives : set global directives out of configuration file


C:\Installed\nginx\nginx-0.7.64>

プロセスに関する注意

nginxを起動すると、マスタープロセスといくつかのワーカープロセスが生成されます。Windowsでは1つのワーカープロセスしか有効でないようです。マスタープロセスのPID(プロセスID)は、NGINX_HOME/logs/nginx.pid に記録されます。ここに記録されたPIDに基づきマスタープロセスを殺すと、配下のワーカープロセスも全部消滅するとのこと。

しかし、なんかのハズミでプロセスがうまく消えずゾンビになってしまうことがあります。タスクマネージャからゾンビプロセスを殺せないこともあります(しばらく時間をおくと殺せるみたいですが)。念のため、タスクマネージャを起動してプロセスの状況を監視しましょう。コマンドラインを使うなら、Windows付属のtasklistコマンドか、Cygwin/MSYSなどのpsコマンドが使えます。

  • >tasklist /fi "imagename eq nginx.exe"
  • >ps -W | grep nginx

Windows版OpenSSLのインストール

SSLを使うには、やれ鍵だの証明書だのと、(僕には)ワケわからんファイルを作らねばなりません。この厄介な部分を省略することはできないので、いたしかたない; Windows版のOpenSSLを使ってやることにします。

http://www.slproweb.com/products/Win32OpenSSL.html から Win32OpenSSL_Light-0_9_8l.exeをダウンロード。マイクロソフトのサイト(Win32OpenSSLのページにリンクあり)から Microsoft Visual C++ 2008 Redistributable Package (x86) もダウンロード。vcredist_x86.exe を実行して先にVC++のランタイム環境をインストールしておきます(もちろん、既にインストール済みなら不要)。Win32OpenSSLのインストール時には、コマンドプロンプトが動いているとダメだというので、開いているコマンドプロンプトを全部閉じます。

あとは、インストーラーである Win32OpenSSL_Light-0_9_8l.exe を実行すればオシマイ。のはずですが、僕の場合ちょっとしたトラブルがありました。

OpenSSLのDLLを入れる場所として、Windowsのシステムディレクトリを選択(デフォルト)したら、DLLのコピーは失敗しました。既に、system32\libeay32.dll が入っててアプリケーションが使っていたようです。DLLの置き場所を、OpenSLLのbinに選択し直してインスールをやり直しました。ほんとは、全てのアプリケーションを閉じてやり直したほうがいいと思います。Windowsシステムディレクトリに新しいDLLを入れれば、他のプログラムにも恩恵があるでしょうから*1

SSL使うための鍵だの証明書だのを作る

ここらへんのことは、僕は事情を理解してないので、単に手順だけ。たまたまMSYSのbashを使ってますが、当然cmd.exeでもかまいません。'$'に続く太字のところがキー入力したコマンドラインです。


$ openssl genrsa -out server.key 1024
Loading 'screen' into random state - done
Generating RSA private key, 1024 bit long modulus
.............++++++
................................................++++++
e is 65537 (0x10001)

$ openssl req -new -key server.key -out server.csr
Loading 'screen' into random state - done
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.

        • -

Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Tokyo
Locality Name (eg, city) :meguro-ku
Organization Name (eg, company) [Internet Widgits Pty Ltd]:HMO
Organizational Unit Name (eg, section)
:ProjectCaty
Common Name (eg, YOUR name) :m-hiyama
Email Address
:hiyama@chimaira.org

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password :
An optional company name
:

$ ls
server.csr server.key

$ cp server.key server.key.orig

$ openssl rsa -in server.key.orig -out server.key
writing RSA key

$ openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt
Loading 'screen' into random state - done
Signature ok
subject=/C=JP/ST=Tokyo/L=meguro-ku/O=HMO/OU=ProjectCaty/CN=m-hiyama/emailAddress=hiyama@chimaira.org
Getting Private key

$ ls
server.crt server.csr server.key server.key.orig

$

A challenge password を入れたとき、エコーバックが丸見えでした。いいんかな?*2

nginxのSSLを設定する

前節の作業で、server.keyとserver.crtというファイルが出来ました。これがないとSSLの設定ができません。

準備ができたので、nginxのSSLを設定します。以下がSSLを使えるようにしたnginxの設定ファイルです。説明はすぐ後にあります。


worker_processes 1;

events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;

# HTTP server
#
server {
listen 80;
server_name localhost;
location / {
root myhtml;
index index.html index.htm;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

# HTTPS server
#
server {
listen 443;
server_name localhost;

ssl on;
ssl_certificate c:/Installed/nginx/nginx-0.7.64/SSL/server.crt;
ssl_certificate_key c:/Installed/nginx/nginx-0.7.64/SSL/server.key;

ssl_session_timeout 5m;

ssl_protocols SSLv2 SSLv3 TLSv1;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers on;

location / {
root myhtml;
index index.html index.htm;
}
}
}

これは、デフォルトの設定ファイルである NGINX_HOME/conf/nginx.conf を変更したものです。変更点は以下のとおり。

  1. コメントアウトされている部分は邪魔なので消しました。
  2. ドキュメントルートをhtmlからmyhtmlに。そして、NGINX_HOME/myhtml/index.htmlを僕好み(?)にしました。
  3. コメントアウトされていたHTTPS serverの記述を生かして、ssl_certificateとssl_certificate_keyを設定。http://wiki.nginx.org/NginxHttpSslModuleによると、設定値はconfディレクトリからの相対パスとのことですが、ドライブレター付き絶対パスでも大丈夫なようです。可搬性の観点からは相対パスが望ましいでしょう。

HTTPS設定に関するより詳しい情報は http://www.nginx.org/en/docs/http/configuring_https_servers.html にもあります。

FirefoxSSLページにアクセスしてみると

Firefoxhttps://localhost/ にアクセスしてみると、次のような警告が出ます。


接続の安全性を確認できません

localhost に安全に接続するように求められましたが、接続の安全性が確認で
きませんでした。

安全に接続する場合は通常、あなたが適切な相手と通信することを確認できる
ように、信頼できる証明書を提供してきます。しかし、このサイトの証明書は
信頼性を検証できません。


どうすればよいのか?

これまでこのサイトに問題なく接続できていた場合、このエラーが表示される
のは誰かがこのサイトになりすましている可能性があるということであり、接
続すべきではありません。

技術的詳細を表示

localhost は不正なセキュリティ証明書を使用しています。

自己署名をしているためこの証明書は信頼されません。
この証明書は m-hiyama にだけ有効なものです。

(エラーコード: sec_error_untrusted_issuer)

危険性を理解した上で接続するには

何が起きていて何が問題なのか理解できているのであれば、このサイトの証明
書を信頼するよう Firefox にセキュリティ例外を追加することもできます。た
だし、たとえこのサイトが信頼できるサイトであっても、誰かが通信を改ざん
しているからこのエラーが表示されている可能性があるので十分に注意してく
ださい。

信頼できる証明書をこのサイトが使用しない正当な理由がない限り、例外とし
て追加しないでください。

確かに正当な証明書じゃないので、これは仕方ないですね。でも、こんな警告が出たってことは、SSLでの接続がうまくいっているってことです。localhostを例外として指定すると、index.html が表示されます。なお、Firefoxの[オプション]-[セキュリティ]-[警告メッセージ]の設定を全部ONにしておくとテストには便利です。

設定ファイルを変えては試す

http://wiki.nginx.org/NginxConfiguration にさまざまな設定のサンプルがあります。これらを参考に、設定ファイルをいじっては試すことになりますが、設定ファイルの構文チェックには -t オプションが使えます。


$ nginx.exe -t -c conf/test04.conf
the configuration file c:\Installed\nginx\nginx-0.7.64/conf/test04.conf syntax is ok
configuration file c:\Installed\nginx\nginx-0.7.64/conf/test04.conf test is successful

$

設定を変更したときは、いきなり起動しないで構文チェックをすることをお勧めします。

Catyサーバーのフロントにnginxを使う

いよいよ、nginxに、バックエンドのCatyサーバーにHTTPとHTTPSを中継してもらいます。まずはCatyサーバーを起動しておきます。


$ python caty-server.py
Serving on port 8000...

$

http://localhost:8000/ にブラウザでにアクセスすると、Catyのホームページが表示されます。これはCatyサーバーへの直接アクセスです。

次に、nginxを起動してみます。


$ start nginx -c conf/test05.conf

$

すると、http://localhost/(ポート80番)としても https://localhost/(ポート443番)としてもCatyサーバーに接続できます。nginxが中継してくれています。ブラウザからのHTTPS通信も、CatyサーバーにはHTTP通信として流されます。ただし、HTTPヘッダ X-Forwarded-Protoにより、もともとはHTTPSであった旨は伝えています。このとき使った設定ファイルは以下のとおり。


# -*- coding: utf-8 -*-
#
# ポート80番へのリクエストをそのままポート8000番で待機しているCatyサーバーに流す。
# ポート443番(HTTPS)へのリクエストは、HTTPに直してポート8000番で待機しているCatyサーバーに流す。
#

worker_processes 1;

events {
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;

sendfile on;
keepalive_timeout 65;

server {
listen 80;
server_name localhost;

location / {
# HTTPリクエストをそのままCatyサーバーに流す
proxy_pass http://localhost:8000/;

# Catyサーバーにヘッダーを通じて情報を提供
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
# HTTPS server
#
server {
listen 443;
server_name localhost;

ssl on;
ssl_certificate c:/Installed/nginx/nginx-0.7.64/SSL/server.crt;
ssl_certificate_key c:/Installed/nginx/nginx-0.7.64/SSL/server.key;

ssl_session_timeout 5m;

ssl_protocols SSLv2 SSLv3 TLSv1;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers on;

location / {
# HTTPリクエストに直してCatyサーバーに流す
proxy_pass http://localhost:8000/;

# Catyサーバーにヘッダーを通じて情報を提供
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https; # もとが https だと知らせる
}
}
}

Catyとnginx

Catyは、とにかくお手軽安直にWebサイト/Webアプリケーションを作れることを目標にしています。基本はシンプルな構成です。しかし、なんかのハズミでトラフィックが激増したり、どうしても複雑な構成を取らざるをえなくなった場合への対処に目星くらいは付けておいたほうがいいでしょう。

nginxはレイヤー7・ロードバランシングもできるので、複数のCatyサーバーをクラスター構成にして、トラフィックを振り分けることができます。このとき、複数のCatyサーバー群でストレージの共有が必要になります。Catyが使用するストレージは、mafs(最小抽象ファイルシステム)とJSONストレージです。mafsはもともと、アマゾンS3のように、HTTPと相性がいいように設計しているので、HTTP RESTインターフェースでアクセスできるmafsサーバーを作るのはそんなに難しくはないと思います。あるいは、NFSでいいかも知れません。JSONストレージはApache CouchDBが使えそうです。nginxのキャッシュ機能で不足があればSquidやVarnishで補強できるでしょう*3

フロントにnginx、その後ろにCatyクラスター、さらにストレージバックエンドとしてHTTPでアクセスできるKey-Valueストレージサーバー群という配置にすれば、通信はすべてHTTPなので管理が楽になるのではないかと予想してます。ほんとのところはやってみないと分からないですけどね(当面、やってみる機会もなさそうです)。

このテのことを考えるのは、僕は得意でも好きでもない*4ので、なんか勘違いをしていたり、机上の空論になっている可能性もあります。ですがともかく、Catyのように“Webサーバー機能が本筋ではない”ソフトウェアにとって、nginxと組み合わせるのが良い方法なのは間違いなさそうです。nginx、いいじゃん。

*1:DLLが更新されて、他のプログラムが動かなることもありますが。

*2:後ろからのぞいている人のリスクより、意図通りにパスワードが入った事の確認のほうが重要という判断なんでしょう。僕みたいに、画面を貼り付けるときにも丸見えになりますが、まーこれは、極端なレアケースですね。

*3:確信はないのですが、nginxだけで間に合いそうです。

*4:有り体に言えば、とても嫌い。