ApacheとTomcatを連携するにはどのモジュールを使うのがよいか???

案1)mod_proxy_http
案2)mod_proxy_ajp
案3)mod_jk?

Apache-Tomcat連携モジュールmod_jk/mod_proxy_ajp/mod_proxy_httpそれぞれについて接続が切れた時の挙動を比較・調査してみた

また、IISとTomcatを連携するには、ajp通信する場合には、ISAPIフィルタしかないみたい。

ところで、WebサーバとAPサーバを別マシンで稼働させる場合の優位性は何だろうか?

1.APサーバは重要なデータを保持しているため、APサーバとの通信は、Webサーバにのみ行うことで、セキュリティを担保しやすい。一方Webサーバはフロントエンドになるため、不特定多数と通信する。

2.Webサーバをロードバランサ(SSL通信を行う場合には、SSLオフロード)の役割を担わせることによって、APサーバをスケールアウトできる。

3.APサーバで処理する必要のない静的コンテンツをWebサーバ側で処理させることによって、オフロードできる。

ban4ipインストールしてみました

オープンソースアンカンファレンス金沢2016でT.Kabuさんからのご紹介のあった
fail2banをやめてBan4ipにしませんか?
を早速試してみました。
ApacheやSSH、postfix、dovecot等のログを解析して失敗を検知した接続元のIPアドレスをiptablesに書き込みしてフィルタリングするデーモンになります。
IPv6にも対応しているところが優れています。
CentOS7の環境でしたが、問題なく導入でき1日間で17回BANできています。
インターネット環境にサーバを公開するということは常に攻撃にさらされていると感じました。
自動BANツールを導入していない方はぜひ導入してみるとよいと思います。
なお、サーバを再起動する際には、banした設定が消えてしまうので /sbin/service iptables save を忘れずに!

Let’s Encrypt でのSSL証明書作成

無償でSSL証明書を発行できるLet’s Encrypt。HTTPS対応は必須になりつつある。
ちなみにこのブログもLet’s Encrypt でSSL証明書を取得している。
Let’s Encrypt の使い方を基に実際にやってみた。
バージョンが変わると一部実装が変わるので、最新はコマンド一覧を参考にしてほしい。
証明書を取得したいFQDNをwww.hogehoge.com、そのDocumentRootが/var/www/www.hogehoge.com/ として記載する。
証明書を発行する場合
Webサーバを停止した状態で、証明書を取得する場合
$ sudo ./certbot-auto certonly –standalone -d www.hogehoge.com
Webサーバを停止せずに証明書を発行する場合
$ sudo ./certbot-auto certonly –webroot -w /var/www/www.hogehoge.com/ -d www.hogehoge.com
x Saving debug log to /var/log/letsencrypt/letsencrypt.log x
x Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org x
x Obtaining a new certificate x
x Performing the following challenges: x
x http-01 challenge for zaiteku.jp x
x Using the webroot path /var/www/www.hogehoge.com/ for x
x all unmatched domains. x
x Waiting for verification… x
x Cleaning up challenges x
x Generating key (2048 bits): x
x /etc/letsencrypt/keys/****_key-certbot.pem x
x Creating CSR: /etc/letsencrypt/csr/****_csr-certbot.pem
IMPORTANT NOTES:
– Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/www.hogehoge.com/fullchain.pem. Your cert will
expire on YYYY-MM-DD(有効期限). To obtain a new or tweaked version of this
certificate in the future, simply run certbot-auto again. To
non-interactively renew *all* of your certificates, run
“certbot-auto renew”
– If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let’s Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
証明書を更新する場合
$ sudo ./certbot-auto renew
ただ、この場合にはWebサーバを停止させる必要がある。
Saving debug log to /var/log/letsencrypt/letsencrypt.log
——————————————————————————-
Processing /etc/letsencrypt/renewal/www.hogehoge.com.conf
——————————————————————————-
Cert is due for renewal, auto-renewing…
Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org
Renewing an existing certificate
Performing the following challenges:
tls-sni-01 challenge for www.hogehoge.com
——————————————————————————-
Port 443 is already in use by another process. This will prevent us from binding
to that port. Please stop the process that is populating the port in question
and try again. For automated renewal, you may want to use a script that stops
and starts your webserver. You can find an example at
https://certbot.eff.org/docs/using.html#renewal . Alternatively you can use the
webroot plugin to renew without needing to stop and start your webserver.
——————————————————————————-
Cleaning up challenges
Attempting to renew cert from /etc/letsencrypt/renewal/www.hogehoge.com.conf produced an unexpected error: At least one of the required ports is already taken.. Skipping.
All renewal attempts failed. The following certs could not be renewed:
/etc/letsencrypt/live/www.hogehoge.com/fullchain.pem (failure)
1 renew failure(s), 0 parse failure(s)
無停止による証明書の更新方法について
下記コマンドで無停止で実行しようと思ったがうまくいかない
$ sudo ./certbot-auto renew –webroot -w /var/www/www.hogehoge.com/ -d www.hogehoge.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Currently, the renew verb is only capable of renewing all installed certificates that are due to be renewed; individual domains cannot be specified with this action. If you would like to renew specific certificates, use the certonly command. The renew verb may provide other options for selecting certificates to renew in the future.
下記コマンドでWebサーバの停止→証明書の更新→Webサーバの開始が可能(証明書の更新対象がない場合には再起動しない)
$ sudo ./certbot-auto renew –pre-hook “systemctl stop httpd” –post-hook “systemctl start httpd”
1.更新対象がある場合は次のようになる。
——————————————————————————-
Processing /etc/letsencrypt/renewal/www.hogehoge.com.conf
——————————————————————————-
Cert is due for renewal, auto-renewing…
Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org
Running pre-hook command: systemctl stop httpd
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for www.hogehoge.com
Waiting for verification…
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/0004_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0004_csr-certbot.pem
——————————————————————————-
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/www.hogehoge.com/fullchain.pem
——————————————————————————-
2.更新対象がない場合には下記のようになる。
Saving debug log to /var/log/letsencrypt/letsencrypt.log
——————————————————————————-
Processing /etc/letsencrypt/renewal/www.hogehoge.com.conf
——————————————————————————-
Cert not yet due for renewal
The following certs are not due for renewal yet:
/etc/letsencrypt/live/www.hogehoge.com/fullchain.pem (skipped)
No renewals were attempted.
No renewals attempted, so not running post-hook

Segmentation faultが発生するとHTTPレスポンスが返らない

Linux+Apache2 + PHPの環境において、特定のページだけがブラウザでアクセスしても「ページが表示できません」となってしまう。
VirtualHostディレクティブで設定しているaccess_logをtail -f で監視していても対象のページだけリクエストがログに残らない。ページが表示されるときには正しくログに残っているので、書き出し先が間違っているわけでもない。
次にVirtualHostディレクティブで設定しているerror_logを見てみたが考えられるエラーは記録されていない。
その次にWiresharkでTCP通信を監視してみた。すると
(正常系)
クライアント –(SYN)–> サーバー
クライアント <--(SYN+ACK)-- サーバー クライアント --(ACK)--> サーバー
クライアント –(ACK)–> サーバー HTTPリクエスト
クライアント <--(ACK)--> サーバー HTTPレスポンス
と続くはずが
(異常系)
クライアント –(SYN)–> サーバー
クライアント <--(SYN+ACK)-- サーバー クライアント --(ACK)--> サーバー
クライアント –(ACK)–> サーバー HTTPリクエスト
クライアント <--(ACK)-- サーバー クライアント <--(ACK+FIN)-- サーバー クライアント --(ACK)--> サーバー
クライアント –(ACK+FIN)–> サーバー
で通信が終了してしまっている。
つまりHTTPレスポンスが返っていないのだ。
(このときはなぜにFINを返してしまうのだーと正直思った)
Linuxでもtcpdumpを使って解析したが、同じパケットがやり取りされており、通信経路上の問題点ではないことがわかった。
そうなるとTCPレイヤーが上位アプリケーション層にパケットを渡さなかったか、アプリケーション層が受け取ったパケットに対する応答をTCPレイヤーに返せないかのどちらかである。
/var/log/messageにはなにも記録されていなかったので、前者は考えにくく、またこの現象はPHPプログラムを入れ替えた機能から発生していることをみると、プログラムが怪しそうだったので、前のプログラムに入れ替えるとやはり問題が改善していた。
プログラムで問題のあると思われるところを修正した結果、正しくページが表示できるようになった。
これでめでたしめでたしだったが、ステータスコードが返せない理由がよくわからなかったので、ログをいろいろ調べてみた。
すると、httpd.confで設定しているerror_logをみると
[notice] child pid 21450 exit signal Segmentation fault (11)
という記録があった。
今回勉強になったのは次の3点
・Segmentation faultなどの致命的な問題が起動プログラム上で発生した場合、ApacheはログをVirtualHostディレクティブで指定したErrorLogのパスに書き出してくれない。(子プロセスが落っこちるから仕方ない?)
・また、Apacheの子プロセスが落っこちた場合にHTTPレスポンスを返せない。
・access_logを記録するのはHTTPレスポンスを返したあとなので、リクエストを受け取れたとしてもaccess_logには記録が残らない。
初めて遭遇した不可思議なこのトラブルには解決に2時間くらい要してしまった。。。

リバースプロキシを利用する上での問題点

Apache2.0から利用可能となったリバースプロキシ機能で ProxyPassとProxyPassReverseを利用することで本格的なロードバランサーを作成できる。
ただ、このロードバランサーを利用する上での問題点もあることは事実。その問題点について説明したい。
1.ProxyPassの指定先が異なるディレクトリだった場合においてコンテンツ内に絶対パスが含まれているとリンクがうまくいかない
たとえば、
ProxyPass / http://192.168.0.1/
などであれば問題はないが、
ProxyPass /share http://192.168.0.1/
などとしてしまうと絶対パスが指定されたリンクではうまく行かないことがある。また、baseタグが利用されているコンテンツがある場合にも注意が必要。
2.ProxyPassの指定先のホストが外部からは接続できないようなセグメントに配置されている場合において、HTTP_HOSTというサーバー環境変数をリンクに利用している場合にうまくいかない
たとえば、
ProxyPass / http://192.168.0.1/
と設定してリバースプロキシサーバーにグローバルIPアドレスが振られているケースでインターネットからリバースプロキシサーバーにアクセスするとはHTTP_HOSTの返答値が192.168.0.1になってしまい、それがリンクに使われているようなことがあると、そのリンク先をクリックすることでプライベートIPアドレスに接続に行ってしまうことになる。これはSERVER_NAMEという環境変数を使った上で、httpd.confのServerNameの設定値が外部DNSにおいて正引き可能なホスト名でかつ外部から到達可能なIPアドレスで解決できなければならない。なお、UseCanonicalNameがOffになっているとhttpd.confのServerNameは利用されないので、Onにする必要がある。(ApacheのデフォルトはOn)
もしリバースプロキシによるロードバランシングを検討されている場合には、上記のようなコンテンツ構成になっていないかを注意する必要があるだろう。

1サーバーに格納していたコンテンツを分離する方法

もともと1サーバーにコンテンツを丸ごと格納していたものを別々のサーバーに分離するようなことがあった(CGIプログラムなどは暴走するとコンテンツがすべて見れなくなるので、それを別サーバーに分離して影響度を減らすようにした)ので、そのための方法を考えてみた。
○やりたいこと
HTML,CGI,PHPファイルおよびその他画像ファイルがあり、いままではAサーバーに格納されていた
それをCGI,PHPコンテンツのみをBサーバーで実行させ、残りをAサーバーで実行させる
○実現方法
1.CGI,PHPファイルをBサーバーへ移動させ、CGI,PHPファイルへリンクしているHTMLファイルのパスを変更する。またCGI,PHPからそのたファイルへリンクしているパスを変更する。
→修正範囲が多すぎるのであっさり却下
2.RedirectMatchディレクティブをAサーバーに設定し、CGI,PHPファイルへのリクエストがあった場合Bサーバーへ転送する
→RedirectMatchを使うとGETリクエストは問題ないが、POSTリクエストされた場合、クライアントに対しては、301,302などのステータスコードが返され、クライアントはBサーバーへGETリクエストで再度リクエストする。そのため、POSTデータがBサーバーに対しては転送されないので、正しくコンテンツを遷移できなくなるのでやはりあきらめた。
3.リバースプロキシの仕組みを利用し、CGI,PHPファイルへのリクエストがあった場合、AサーバーはBサーバーへまったく同じ問い合わせを行って、その結果をクライアントへ返す
→これであればまったく問題ないことが分かった。
リバースプロキシ(通常のプロキシサーバーはフォワードプロキシといわれている)の仕組みはApacheを使って安価にロードバランサーを実現する際にも使われるので、設定を熟知できるようにしておきたい。

1.3系のWindows用Apacheのインストーラが見つからない

もうすでにApacheは2.2がリリースされており、1.3系の古いApacheはもう使われないのかもしれない。
だけど、何らかの理由によって1.3系のApacheを使いたいとしても、apache.orgには1.3系のWindows用のバイナリパッケージを探すことはできない。tgzファイルはリンクが貼ってあるが・・・。
アーカイブ用のページが実は存在していて、1.3系のWindows用バイナリパッケージがあるので、もし必要なときには覗いてみると良いだろう。

ホスト制限が効かない

<Directory (pathname)>
order allow,deny
allow from all
deny from XXX.XXX.XXX.XXX
</Directory>
Apacheでホスト制限が効かない場合には、以下が考えられる。
1.pathnameがそもそも勘違いしている
pathnameはDocumentRoot以下の絶対パスではなく、システム上のパスである。Windows版のApacheならば、c:\などから始まることになるだろう。
2.同一のディレクティブが存在しないかをチェックする
httpd.confファイルとそのファイルがincludeしている全てのファイルにおいて、同じ
<Directory XXX>
が存在していないかを確認する。経験では、同一のpathnameを持つDirectoryディレクティブは後に出てくるものが無視されているように思える。どのような使用になっているかはもう少し確認してみる必要がありそうだが、同一のpathnameを持つDirectoryディレクティブが存在していないかを確認してみると良いだろう。
もし同じものが見つかったならば、見つかった方に追加するかもしくはpathnameをもっと詳細なパス(たとえば、/var/wwwなど)を設定するようにすれば良いだろう。

Webサーバーの環境を破壊した?

友人のサーバーにPHPアプリケーションを組み込んでいたら恐ろしいことが起こった。
コンテンツ組み込みにあたりApacheの設定変更が必要になり、設定ファイルを変更して/etc/init.d/httpd configtest
を実行したらなんと大量にエラーが発生するじゃないですか。
そこでやめておけば良かったのにtry&errorで何とかできるレベルだと思ってエラー箇所をコメントアウトしてエラーが出なくなったことを確認して、強引に再起動したわけです。
もちろんPHPは動かず。たしかPHPは動作する環境だって聞いてたはずだし、どうやらPHPのマッピングがうまくいってないんだなと勝手に判断してガリガリ設定ファイルに書くもうまくいかず。
落ち着いて考えてみたらLoadModuleも入ってないんじゃねーのと気づくが、どうやらmoduleディレクトリにPHPのライブラリらしい物は見あたらず。。。
ただphp -vは通るわけで、変だなぁと思ってrpmの一覧を調べたらなんと入ってないわけです。えっ?ソースコンパイル?でもさっきの一覧にApacheはあったしなぁ・・・?
まさかと思ってPHPのライブラリをルートからfindしてみたら愕然としました。なんとApacheもソースコンパイルされてるし。
どうやら再起動コマンドに
/etc/init.d/httpd
ではなく /usr/local/apache2/bin/apachectl
を使わなきゃいけないってオチだったらしい。
念のため、
/sbin/chkconfig
してみたらやっぱりhttpdはrunlevel=4以外がすべてoffになってましたとさ。(えっ?でも4だけonなの?と突っ込みたくなりましたが)
もーまぎらわしいからrpmはアンインストールしといてよと言いたくなりました。