Apache

(更新:2017/09)Install on Ubuntu
(更新:2013/02)deflate_module, expires_module+ETAG, アクセス負荷が酷い時の確認方法
(更新:2013/01)スクリプトサンプルをtextで表示させたい
(更新:2012/11)Apache Bench, Alternative PHP Cache, FastCGI, mod_pagespeed
(作成:2012/05)

OSSのHTTPサーバとしては有名過ぎるくらい有名なので特に紹介する事も無く。
お仕事でもよく使うので、ちょっと設定備忘録でも書いておこうかなと。

Install on Ubuntu

さくさくと。

# apt-get install apache2

関連パッケージも一応メモ。

  • 追加パッケージ
    apache2-bin apache2-data apache2-utils libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap liblua5.2-0 ssl-cert

Apache 2では *.conf の置き方が昔と変わったみたい。

#   /etc/apache2/
#   |-- apache2.conf
#   |   `--  ports.conf
#   |-- mods-enabled
#   |   |-- *.load
#   |   `-- *.conf
#   |-- conf-enabled
#   |   `-- *.conf
#   `-- sites-enabled
#       `-- *.conf
  • ports.conf
    仮想ホスト含めたリスニングポート設定を入れておくみたい。
  • mods-enabled
    Apacheモジュール設定用らしい。
  • conf-enabled
    グローバル設定の一部用だとか。
  • sites-enabled
    仮想ホスト設定用だそうだ。

3つのディレクトリ内には、それぞれ対応する mods-available/ , conf-available/ , sites-available/ フォルダ内の実体からリンクせよ、と。
まあ、何でもかんでも httpd.conf にぶっ込んでた過去のやり方は可読性低かったからなぁ。納得はできる。

で、特に何もせずとも動いたし。便利な世の中になったねぇ。

PHP 7.0

PHPも使えるようにしてみる。

# apt-get install php

関連パッケージはこちら。

  • 追加パッケージ
    libapache2-mod-php7.0 php-common php7.0 php7.0-cli php7.0-common php7.0-json php7.0-opcache php7.0-readline

インストール途中で Creating config file が連発してたんだけど、 /etc/apache2/ 配下のディレクトリに色々追加された。成程こういう事か。
取り敢えずドキュメントルートにお約束の phpinfo(); などを。

<?php

//// /var/www/html/phpinfo.php

phpinfo();
?>
見れるし。楽だなー。

userdir/home/*/public_html/ 有効にするやつ) を使う場合はこう。

# a2enmod userdir
Enabling module userdir.
To activate the new configuration, you need to run:
  systemctl restart apache2
# 

php7.0.conf 見れば解るんだけれど、ユーザディレクトリでPHP使いたいなら <IfModule ...></IfModule> をコメントアウトしろ、とあるのでそうする。

#### /etc/apache2/mods-enabled/php7.0.conf

# Running PHP scripts in user directories is disabled by default
#
# To re-enable PHP in user directories comment the following lines
# (from <IfModule ...> to </IfModule>.) Do NOT set it to On as it
# prevents .htaccess files from disabling it.
#<IfModule mod_userdir.c>
#    <Directory /home/*/public_html>
#        php_admin_flag engine Off
#    </Directory>
#</IfModule>

apache再起動。

# systemctl restart apache2
ユーザディレクトリでも phpinfo(); 。ホント楽だなー。

Install on CentOS

何ていうか深く考える必要も無いかな。

# yum install httpd

必要最低限の設定はこんなものか。

#### /etc/httpd/conf/httpd.conf

ServerAdmin root@localhost
ServerName (サーバFQDN)

## 省略 ##

# 文字コード固定
AddDefaultCharset UTF-8

## 省略 ##

ユーザディレクトリを使いたい場合はこんな設定。

<IfModule mod_userdir.c>
    UserDir public_html
</IfModule>

## 省略 ##

# http://www.hyakki.local/~riyo/ みたいなアドレスでアクセスできる。
# ユーザ毎のコンテンツ置き場は~/public_html/以下。
<Directory /home/*/public_html>
    AllowOverride FileInfo AuthConfig Limit
    Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
    <Limit GET POST OPTIONS PROPFIND>
        Order allow,deny
        Allow from all
    </Limit>
    <LimitExcept GET POST OPTIONS PROPFIND>
        Order deny,allow
        Deny from all
    </LimitExcept>
</Directory>

## 省略 ##

CGIを使いたい場合はこんな設定。

LoadModule cgi_module modules/mod_cgi.so

## 省略 ##

# これはhttp://www.hyakki.local/cgi-bin/ でCGIが使えるようになる設定。
ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"

<Directory "/var/www/cgi-bin">
    AllowOverride None
    #Options None
    Options +ExecCGI +FollowSymLinks -Indexes
    Order allow,deny
    Allow from all
</Directory>

## 省略 ##

AddHandler cgi-script .cgi

## 省略 ##

モジュール整理

設定ファイルの途中 LoadModule てな書式で沢山のモジュールが読み込まれている。このあたりも整理して不要なものをコメントアウトなりしておくと、リソース消費も軽減されるしセキュアにもなるので調べておくと良い。
取り敢えずここでは以前調べたものでも載せておこうかな。

  • auth_module
    HTTP認証を提供する。 ht* ファイルで使う。htaccess認証とかやりたければこれを有効化する。
  • access_module
    アクセス元のIP/ホスト情報によるアクセス制御をするモジュール。 Allow/Deny from でホスト毎の制限をする時とかに使う。
  • alias_module
    URLのエイリアスを作るためのモジュール。リダイレクト機能とかも提供してる。
  • userdir_module
    ユーザディレクトリを提供する。 http://www.hyakki.local/~riyo/ とかで参照するディレクトリ。
  • php4_module
    Web上でPHP4を使うためのモジュール。利用時にはファイルハンドラ設定も必要。
AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps
  • rewrite_module
    リクエストされたURLを別のURLに変更するモジュール。 Redirect に似てるけどもちっと高機能。
  • setenvif_module
    リクエスト側がよこす情報を正規表現で判断し処理を行なうためのモジュール。アクセスログの整理とかに便利。

追加モジュール

DoS攻撃対策

サーバ管理者の脅威なんてのは腐る程あるけれど、やっぱ第三者から攻撃に晒される「外部公開環境」を扱っている事実に他ならないと思う。作った人が使うだけなら脅威に晒される事なんて無いもんね。
で、その中でもDoS攻撃なんて手法はこれといった特殊な技術や特別なツールが無くても実行出来ちゃう。以前2chが某隣の半島からF5攻撃受けてたけど、つまりそういう攻撃(これは不特定多数がしDDoSかもだけど)。

取り敢えずDDoSは置いといて、同一IPからの攻撃であるDoS攻撃を検知するためのモジュール mod_dosdetector というものがある。この前導入してみたので、そちらの紹介をば。

Install on CentOS

このモジュールはパッケージ化されていないので、ソースからのインストールになる。まずはソースからRPMを作ってくれる checkinstall を導入。

# wget http://futuremix.org/downloads/checkinstall-1.6.1-1.x86_64.rpm
# rpm -ivh checkinstall-1.6.1-1.x86_64.rpm

続いて mod_dosdetector のアーカイブを取得、展開してRPMを作成。

# wget http://cloud.github.com/downloads/tkyk/mod_dosdetector-fork/mod_dosdetector-fork-1.0.0.tar.gz
# tar xvfz mod_dosdetector-fork-1.0.0.tar.gz
# cd mod_dosdetector-fork-1.0.0/
# make
# checkinstall --exclude=/selinux
# rpm -i --replacefiles --nomd5 /usr/src/redhat/RPMS/x86_64/mod_dosdetector-fork-1.0.0-1.x86_64.rpm

httpd.confLoadModules が1つ追加された事を確認。

# less /etc/httpd/conf/httpd.conf
...
LoadModule dosdetector_module /usr/lib64/httpd/modules/mod_dosdetector.so
...

設定は httpd.conf と別のファイルで作成すると分かり易いかな。
busyページがあれば ErrorDocument にて設定。無ければ文字列でもOK。
検知したログについては access_log 等とは別のログファイルに書き出すと分かり易い。

#### /etc/httpd/conf.d/dosdetector.conf

<IfModule dosdetector_module>
    DoSDetection On

    DoSPeriod 30
    DoSThreshold 100
    DoSHardThreshold 300

    DoSBanPeriod 60

    DoSShmemName dosshm
    DoSTableSize 100
    <IfModule setenvif_module>
        SetEnvIfNoCase Request_URI ".*(common|download|ajax|json|images|gif|jpe?g|icon?|js|css|png|html).*" NoCheckDoS
    </IfModule>

    ##ErrorDocument 403 "Server is busy."
    ErrorDocument 403 /error/busy.html

    # send a 403 response with mod_rewrite
    RewriteEngine On
    RewriteCond %{ENV:SuspectHardDoS} =1
    RewriteRule .* - [R=403,L]

    LogFormat "%{SuspectHardDoS}e %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" dosdetector
    CustomLog logs/dos_suspect_log dosdetector env=SuspectDoS
</IfModule>

因みにSSLサイトに仕込みたい場合は、 <VirtualHost _default_:443> タグ内に上記設定を引用すればOK。設定が同じなら Include が便利。

#### /etc/httpd/conf.d/ssl.conf

<VirtualHost _default_:443>

     ## 省略 ##

    <IfModule dosdetector_module>
        Include conf.d/dosdetector.conf
    </IfModule>
</VirtualHost>

上記設定ならば単なるブラウザアクセスでも十分引っ掛かるので、実際にブラウザでアクセス→ F5 連打してみる。
何度か目にbusyページが表示されるようになれば成功。後は通常アクセスとDoS攻撃を見極めつつ閾値を調整していく。

deflate_module

コンテンツの圧縮通信をしてくれるモジュール。サーバのCPUリソースを食う代わりに、通信量がちょこっと減る。

#### /etc/httpd/conf/httpd.conf

<IfModule deflate_module>
    DeflateCompressionLevel 5
    AddOutputFilterByType DEFLATE application/javascript application/x-javascript text/javascript text/css text/xml text/html text/plain

#    DeflateFilterNote Input instream
#    DeflateFilterNote Output outstream
#    DeflateFilterNote Ratio ratio
#    LogFormat '"%r" %{outstream}n/%{instream}n (%{ratio}n%%) %{User-agent}i' deflate
#    CustomLog logs/deflate_log deflate
</IfModule>

DeflateFilterNoteCustomLog の部分は、圧縮状況をログ出力してくれる設定。最初のうちはこれで動作確認すると良いんじゃないかな。

expires_module + ETag

所謂キャッシュを活用するための設定で、期限切れ期間を定める。

#### /etc/httpd/conf/httpd.conf

<IfModule expires_module>
    ExpiresActive On
    ExpiresByType application/javascript "access plus 3 days"
    ExpiresByType application/x-javascript "access plus 3 days"
    ExpiresByType text/javascript "access plus 3 days"
    ExpiresByType text/css "access plus 3 days"
    ExpiresByType image/jpeg "access plus 5 days"
    ExpiresByType image/png "access plus 5 days"
    ExpiresByType image/gif "access plus 5 days"
    ExpiresByType image/x-icon "access plus 5 days"
</IfModule>

これで期限切れまではブラウザのキャッシュを使うようになる。
それよりも更にファイルのETagヘッダを優先してキャッシュ更新をするらしい。不要ならETag削除すれば expires のみで判断するようになる。なおデフォルトのETagは inode+更新日時+ファイルサイズ でユニークなETag情報を作るらしい。複数台サーバで負荷分散している場合、inode を含めるとサーバ毎にETagが変わってしまうので、以下のようにしてinode情報を省くと良い。

#### /etc/httpd/conf/httpd.conf

<Files ~ "\.(css|js|html?|xml|gz)$">
    FileETag MTime Size
</Files>
<Files ~ "\.(gif|jpe?g|png|flv|mp4|ico)$">
    FileETag None
</Files>

なお負荷分散サーバ間では更新日時が合致するようにファイル同期する仕組みを使うこと。lsyncd + rsyncが良いんじゃないかな。そちらは拙サイト「lsyncd」参照。

lsyncd
(作成:2012/11) 冗長化の一環として。用途としてはMySQLレプリケーションに通じるものがあるね。 ストレージサーバなどでリアル...

Alternative PHP Cache

PHPを高速化してくれるよ! やったね!
PHPの中間コードのキャッシングや最適化をしてくれるそうで、スクリプトに直接関わるような改修が行なわれるわけでもないので、バージョンが合うなら入れておくと良いかと。

# yum install pcre-devel
# pear install pecl/APC
#### /etc/php.d/apc.ini

[apc]
extension=apc.so
# service httpd restart

ちゃんと入ったか確認。

# php -i | grep apc
...
apc.enabled => On => On
...

故あってWordPress環境に仕込んでApache Benchで比較してみた結果。総リクエスト数1000、同時リクエスト数100。わりと凄い改善されてる。

デフォルト +APC
Failed Requests 97 72
Requests per second 14.34 26.33
Time per request 69.753 37.986

FastCGI

PHP処理プロセスをメモリに常駐させて、Apacheでなくそっちで処理してもらうためのモジュール。これでも処理の高速化と負荷軽減が期待出来る、らしい。

# yum install --enablerepo=epel fcgi mod_fcgid
#### /etc/httpd/conf/httpd.conf

<VirtualHost *:80>

    ## 省略 ##

    <Directory "/var/www/html">
        Options FollowSymLinks Includes ExecCGI
        AddHandler fcgid-script .php
        FCGIWrapper /usr/bin/php-cgi .php

        ## 省略 ##

    </Directory>
</VirtualHost>
# service httpd restart

すると php-cgi てのが常駐する。

# ps -ef | grep php
apache   19643 19339  3 19:03 ?        00:01:30 /usr/bin/php-cgi

うむ苦しゅうない。さてApache Benchの結果は……。

+APC +APC+FastCGI
Failed Requests 72 0
Requests per second 26.33 13.21
Time per request 37.986 75.675

……APC導入前よりも速度が低下しているような。その代わりリクエスト失敗数は減ってる。別プロセスにすることで処理の正確さが上がった?
試験環境はVMゲストで、CPUもメモリも少なめというのが起因しているのかも。環境が環境なら、多分効果はあると思うんだけれど……。

mod_pagespeed

Google先生が一晩で頑張ってくれました。何かねjsやcssの最適化やインライン化、Extend cacheとか、高速化に繋がる諸々をよろしくやってくれるらしい。

# wget https://dl-ssl.google.com/dl/linux/direct/mod-pagespeed-stable_current_x86_64.rpm
# rpm -ivh mod-pagespeed-stable_current_x86_64.rpm
# service httpd restart

さて結果は……。

+APC+FastCGI +APC+FastCGI+mod_pagespeed
Failed Requests 0 999
Requests per second 13.21 9.8
Time per request 75.675 102.05

何が起きたのか判らないのぜ☆
えーっとこれはどう判断すれば良いのかなぁ。多分、こうしたチートは doc_length 値ほぼ無視するんだろうなぁ。だからFailed Requestsが大きい。んで、導入前より速度低下した理由だけれど……FastCGIと相性が良くないのかも知れない。
FastCGIを抜いてみるとこんな感じの結果に。

+APC +APC+mod_pagespeed
Failed Requests 72 196
Requests per second 26.33 55.86
Time per request 37.986 17.903

エラーの頻度が上昇……このために速度が上昇しているきらいもあるので、ちょっと比較にならないね。他の試験方法を検討しようかな。

Tips

Apache設定よもやま

スクリプトサンプルをtextで表示させたい

サンプルスクリプトを .pl だの .php だのの型式でそのままサーバに置くと、実行可能なサーバなら実行されちゃったりする。サンプルとして表示さ せるのにこれだとマズイが、いちいちHTMLに書き起こすのはナンセンスだしHTMLエンティティを考えた表示スクリプト書くのも馬鹿らしいしファイル名を .txt に変更するのすら面倒……なんてな人にお勧め。

#### .htaccess

AddHandler default-handler .pl

ForceType text/plain
<Files "*.tgz">
    ForceType None
</Files>

取り敢えず AddHandler なPerlスクリプトは、 AddHandler で上書きしてやれば良い。 PHPなんかの新規MIMEタイプ指定によるものは、該当ディレクトリ以下全てのMIMEタイプを強制上書(拡張子をも無視)してやると良い。 しかし例えば .tgz 等、普通にDLさせてあげたいモノに関しては Files セクションにて ForceType を無効にしてやる事も可能。

ログ解析よもやま

公開したらログ解析しながらサーバ/サービスの健康状態を維持してあげる必要がある。けれど、どう診てあげればサーバは喜んでくれるんだろう……といった所を、インフラ視点からつらつらと。
解析アプリの話とかは基本的にしない予定。どちらかと言えば、インフラ的な調査をメインに。なお、ログ形式はデフォルト combined でのお話。

アクセス負荷が酷い時の確認方法

サーバ/サービス監視なんかでは、負荷が高いよーとか帯域使い過ぎだよーとかしか判らないので、原因の特定が難しい。けれど、公開サーバに関して言えば、もし公開時の状態が非常に安定していたなら、そういった際の原因はやっぱり外部からの攻撃が殆どだったりする。特亜からのアタックとか、割と脅威。

というわけで、アクセスログからアクセス上位のIPとアクセス数を簡単に抜き出すコマンド。

# cat /var/log/httpd/access_log | grep -v -e ".gif" -e ".jpg" -e ".png" -e ".js" -e ".css" | awk '{print $1;}' | sort | uniq --count | sort -r | head -10

簡単に説明すると。
access_log に関して静的コンテンツ類(但しHTMLは除く)をgrep -vで削り、スペース区切りの1単語目(IP情報)を抜き取ってソート。
uniq でIP情報をカウントしつつ一意化、結果は「カウント数 IPアドレス」の順で表示されるので、逆順ソートして最初の10行を表示する。
といった感じ。

アタックかけられてたりすると、この際のカウント数が明らかにハネ上がってたりする。例えば上位5〜9位くらいのアクセスが300/日に対し、上位1位が10000/日だったり。

そしたら今度は、アクセス数の多いIPが何にアクセスしているのか確認したくなってくる。以下はその上位の宛先とアクセス数を簡単に抜き出すコマンド。

# cat /var/log/httpd/access_log | grep -e "192.168.1.XXX" | awk -F '"' '{print $2;}' | awk '{print $2;}' | sort | uniq --count | sort -r | head -10

簡単に説明すると。
例えばアタック元IPが 192.168.1.XXX だったとして、まずはそれで絞り込み。
ダブルクォート区切りの2単語目を抜き取ればHTTPコマンドが判るので、そこから更にスペース区切りの2単語目でアクセス先ファイルを抜き取る。
後はファイル名ソート、 uniq +カウント、カウント別ソート、最初の10行を表示といった感じの流れ。

これにより、何に対しアクセスが多いかが判る。
静的コンテンツ宛なら影響は少ないかも知れないが、それでも多量アクセスはリソース消費の元なので、E-tagなんかでブラウザキャッシュ効かせると効果的かも。凶悪なのがPHPスクリプトなど動的コンテンツへの多量アクセス。既知の脆弱性を狙った攻撃やゼロディ攻撃、はたまた純粋にDoSアタックかも知れない。まあいずれにせよ、偏ったアクセスは悪ですわ。

そんな感じで特定して、明らかに悪さしていそうなIPに関しては、FWなどでDROPするなり対策を取りましょう。

Apache Bench

Apacheチューニングのお供に。

# apt-get install apache2-utils

実行方法と結果はこんな感じ。

$ ab -n 1000 -c 100 http://www.hyakki.local/

オプションの意味は以下の通り。

  • -n: 総リクエスト数
  • -c: 同時発行リクエスト数

で、結果。

This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking www.hyakki.local (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        Apache
Server Hostname:        www.hyakki.local
Server Port:            80

Document Path:          /
Document Length:        0 bytes

Concurrency Level:      100
Time taken for tests:   69.753 seconds
Complete requests:      1000
Failed requests:        97
   (Connect: 0, Receive: 0, Length: 97, Exceptions: 0)
Write errors:           0
Non-2xx responses:      97
Total transferred:      268345 bytes
HTML transferred:       25511 bytes
Requests per second:    14.34 [#/sec] (mean)
Time per request:       6975.302 [ms] (mean)
Time per request:       69.753 [ms] (mean, across all concurrent requests)
Transfer rate:          3.76 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        4    5   1.4      5      24
Processing:   699 6863 2517.5   7141   19761
Waiting:      698 6861 2517.8   7125   19761
Total:        703 6868 2517.8   7145   19765

Percentage of the requests served within a certain time (ms)
  50%   7145
  66%   7683
  75%   8039
  80%   8262
  90%   9174
  95%  11113
  98%  12098
  99%  12669
 100%  19765 (longest request)

以下がポイントになるかな。

項目 意味
Complete requests 総リクエスト数。
Failed requests 処理に失敗したリクエスト数。直下にある括弧内の項目は、その原因を表す。※1
Non-2xx responses 200系レスポンス以外の発生した数。
Requests per second 秒間平均のリクエスト処理数。
Time per request リクエスト平均の実行時間。
※1
Connect 接続の失敗。
Receive データ受信の失敗。
Length doc_length値と実データ量が異なる。
Exceptions Apacheエラー。