aoishiの備忘録

備忘録

ApacheのScoreboardをモニタリングする

はじめに

私事ですが、昨年末に運用しているWEBサービスにて、Apacheの同時接続数が上限に達し一時的にサービス提供できなくなる経験をしました。 その際に、エラーログにScoreboardという文字列を含む下記のエラーログが頻発していました。

AH00286: server reached MaxRequestWorkers setting, consider raising the MaxRequestWorkers setting
AH00287: server is within MinSpareThreads of MaxRequestWorkers, consider raising the MaxRequestWorkers setting
AH00288: scoreboard is full, not at MaxRequestWorkers

対応の一環で、Scoreboadのモニタリング設定などをしたので、その際の内容をまとめておきたいと思います。 また、私の理解不足で不正確な内容を記載している箇所もあるかと思いますが、自分の外部記憶装置代わり的な意味でも記載していますのでご了承下さい。

検証環境

用語

Slot

ApacheがHTTPリクエストを処理する実体のことで、MPMがPreforkであればプロセスのことであり、Worker, Eventであればスレッドのこと。

Scoreboard

Apacheが親プロセスと子プロセスとの間で共有するメモリ領域のこと。各Slot毎にさまざまな情報を保持しているらしい。

下記あたりのコードを見れば、Scoreboardについて詳細にわかりそう。

Scoreboardの状態確認方法

調査すると、mod_statusモジュールでステータスページを通じて確認する方法と、共有メモリ上のScoreboardに直接アクセスして情報を取得する方法があるようです。 今回は、前者のmod_statusモジュールを使った方法を記載します。

後者の共有メモリに直接アクセスする方法は情報があまり多くなく、Scoreboardについて実装レベルで把握していないと難しいようだったので、今回は断念しました。 ただ、mod_statusモジュールを使う場合と比較して、モニタリングのためにApacheにアクセスする必要がないため、接続数が飽和している状態でもScoreboardの状態が確認できるという点でメリットがありそうです。

参考までに、下記に記事を紹介しておきます。

mod_statusの有効化とステータスページからのデータ取得

下記の内容をApacheの設定ファイルに追記します。これにより、Apacheの状態がステータスページで確認できるようになります。

LoadModule status_module modules/mod_status.so
ExtendedStatus On

Listen 81
<VirtualHost *:81>
    <Location /server-status>
        SetHandler server-status
        Require all granted
    </Location>
</VirtualHost>

ここでは、ステータスページのURIがサービスに干渉することを防ぐために、VirtualHostで未使用のポートに対してステータスページを表示させるよう設定しています。 モニタリングや通信制限の運用次第ではありますが、接続元をlocalhostなどに絞るなどのセキュリティ対策は実際の環境に応じて検討したほうが良いと思います。

また、.htaccessなどの設定ファイル内でもステータスページの表示設定ができるようになるため、.htaccessを有効している環境では意図せずステータスページを外部に公開してしまう恐れもあるため注意が必要です。回避策としては、AllowOverrideやAllowOverrideListなどでSetHandlerの使用を許可しないよう設定すれば良さそうです。

https://httpd.apache.org/docs/2.4/mod/core.html#allowoverride

設定ファイルを編集後、Apacheを再起動します。

$ apachectl -t
Syntax OK

$ sudo systemctl restart httpd

ローカルからステータスページにアクセスしてみます。先のVirtualHostで定義したURLに対して?autoパラメタをつけてアクセスすることで、下記のようなkey-value形式でデータを取得できます。

$ curl localhost:81/server-status?auto
localhost
ServerVersion: Apache/2.4.28 (CentOS) OpenSSL/1.0.2k-fips
ServerMPM: worker
Server Built: Oct  9 2017 12:34:18
CurrentTime: Monday, 01-Jan-2018 16:11:40 JST
RestartTime: Monday, 01-Jan-2018 16:08:56 JST
ParentServerConfigGeneration: 1
ParentServerMPMGeneration: 0
ServerUptimeSeconds: 163
ServerUptime: 2 minutes 43 seconds
Load1: 0.04
Load5: 0.12
Load15: 0.22
Total Accesses: 305
Total kBytes: 361
CPUUser: .39
CPUSystem: .18
CPUChildrenUser: 0
CPUChildrenSystem: 0
CPULoad: .349693
Uptime: 163
ReqPerSec: 1.87117
BytesPerSec: 2267.88
BytesPerReq: 1212.01
BusyWorkers: 1
IdleWorkers: 49
Scoreboard: __________________W_______________________________
TLSSessionCacheStatus
CacheType: SHMCB
CacheSharedMemory: 512000
CacheCurrentEntries: 76
CacheSubcaches: 32
CacheIndexesPerSubcaches: 88
CacheTimeLeftOldestAvg: 183
CacheTimeLeftOldestMin: 136
CacheTimeLeftOldestMax: 288
CacheIndexUsage: 2%
CacheUsage: 3%
CacheStoreCount: 76
CacheReplaceCount: 0
CacheExpireCount: 0
CacheDiscardCount: 0
CacheRetrieveHitCount: 0
CacheRetrieveMissCount: 0
CacheRemoveHitCount: 0
CacheRemoveMissCount: 0

ここで、目的のScoreboardは下記のような文字列として表示されており、1文字が1つのSlotの状態を表現しています。

Scoreboard: __________________W_______________________________

各文字とSlotの状態の意味は下記の通りです。

文字 状態名 意味
_ Waiting for Connection プロセス/スレッド起動し、アクセス待ちの状態
S Starting up プロセス/スレッドが起動中の状態
R Reading Request クライアントからのアクセスを受け付けて、リクエストを読み込んでいる状態
W Sending Reply クライアントにレスポンスを返している状態
K Keepalive (read) KeepAliveによりリクエストを待機している状態
D DNS Lookup クライアントのIPの名前解決している状態
C Closing connection 接続を終了している状態
L Logging ログ出力している状態
G Gracefully finishing スレッドのgracefulな停止処理中またはそこからの起動中 ?
I Idle cleanup of worker スレッドが終了中な状態
. Open slot with no current process 空きSlot状態

何度かステータスページにアクセスしていると気づきますが、アクセスが全くない状態でもScoreboardには必ず1つWが表示されます。これはステータスページへのアクセスを処理しているSlotの状態を示しており、そのレスポンス中のScoreboardの状態が取得されステータスページが生成されているためだと思います。

Scorboardの数値化

ステータスページに表示されていたScoreboardは1文字が1つのSlotの状態を表現した文字列として表示されるため、その意味に従って状態毎にカウントして数値化することでモニタリングできるようになります。

ここでは、下記のPythonスクリプトでステータスページのScoreboardの文字列を数値化してみます。

#!/bin/env python

import requests
import argparse

scoreboard_lookup = {
    '_': 'WaitingForConnection',
    'S': 'StartingUp',
    'R': 'ReadingRequest',
    'W': 'SendingReply',
    'K': 'KeepAlive',
    'D': 'DNSLookup',
    'C': 'ClosingConnection',
    'L': 'Logging',
    'G': 'GracefullyFinishing',
    'I': 'IdleCleanupOfWorker',
    '.': 'OpenSlotWithNoCurrentProcess',
}

def main(url):
    r = requests.get(url)

    for key, value in [ tuple(x) for x in [ line.split(': ') for line in r.text.splitlines() ] if len(x) == 2]:
        key = key.replace(' ', '')
        if key == 'Scoreboard':
            print('apache.scoreboard.{},{}'.format('TotalSlot', len(value)))
            for scoreboard_char,scoreboard_label in scoreboard_lookup.items():
                print('apache.scoreboard.{},{}'.format(scoreboard_label, value.count(scoreboard_char)))
        else:
            print('apache.{},{}'.format(key, value))

if __name__ == '__main__':

    argparser = argparse.ArgumentParser()
    argparser.add_argument('-u', '--url', type=str, required=True)

    args = argparser.parse_args()
    main(args.url)

上記スクリプトは、ステータスページの内容を取得して下記の処理をしています。

  • csv形式のkey-value(key,value)形式に整形
  • keyのprefixにapache.を追加
  • keyに含まれていた半角スペースを削除
  • ScoreboardをSlotの状態毎にカウントし、apache.scoreboard.状態名というkeyのvalueに追加
  • Scoreboardの総Slot数(apache.scoreboard.TotalSlot)を追加

適当にファイル名でスクリプトを作成し、実行権限を付与して実行します。

$ vi apache.py
$ chmod 755 apache.py
$ apache.py -u http://pxy01.private:81/server-status?auto
apache.ServerVersion,Apache/2.4.28 (CentOS) OpenSSL/1.0.2k-fips                                                                
apache.ServerMPM,worker                                        
apache.ServerBuilt,Oct  9 2017 12:34:18                        
apache.CurrentTime,Monday, 01-Jan-2018 17:53:38 JST            
apache.RestartTime,Monday, 01-Jan-2018 16:08:56 JST            
apache.ParentServerConfigGeneration,1                          
apache.ParentServerMPMGeneration,0                             
apache.ServerUptimeSeconds,6282                                
apache.ServerUptime,1 hour 44 minutes 42 seconds               
apache.Load1,0.00                                              
apache.Load5,0.01                                              
apache.Load15,0.05                                             
apache.TotalAccesses,11589                                     
apache.TotalkBytes,13357                                       
apache.CPUUser,16.41                                           
apache.CPUSystem,6.45                                          
apache.CPUChildrenUser,0                                       
apache.CPUChildrenSystem,0                                     
apache.CPULoad,.363897                                         
apache.Uptime,6282                                             
apache.ReqPerSec,1.84479                                       
apache.BytesPerSec,2177.26                                     
apache.BytesPerReq,1180.22                                     
apache.BusyWorkers,1                                           
apache.IdleWorkers,49                                          
apache.scoreboard.TotalSlot,50                                 
apache.scoreboard.ClosingConnection,0                          
apache.scoreboard.DNSLookup,0                                  
apache.scoreboard.GracefullyFinishing,0                        
apache.scoreboard.IdleCleanupOfWorker,0                        
apache.scoreboard.KeepAlive,0                                  
apache.scoreboard.Logging,0                                    
apache.scoreboard.StartingUp,0                                 
apache.scoreboard.ReadingRequest,0                             
apache.scoreboard.SendingReply,1                               
apache.scoreboard.WaitingForConnection,49                      
apache.scoreboard.OpenSlotWithNoCurrentProcess,0               
apache.CacheType,SHMCB                                         
apache.CacheSharedMemory,512000                                
apache.CacheCurrentEntries,137                                 
apache.CacheSubcaches,32                                       
apache.CacheIndexesPerSubcaches,88                             
apache.CacheTimeLeftOldestAvg,65                               
apache.CacheTimeLeftOldestMin,0                                
apache.CacheTimeLeftOldestMax,222                              
apache.CacheIndexUsage,4%                                      
apache.CacheUsage,5%                                           
apache.CacheStoreCount,2883                                    
apache.CacheReplaceCount,0                                     
apache.CacheExpireCount,2746                                   
apache.CacheDiscardCount,0                                     
apache.CacheRetrieveHitCount,0                                 
apache.CacheRetrieveMissCount,0                                
apache.CacheRemoveHitCount,0                                   
apache.CacheRemoveMissCount,0

上記apache.scoreboard.あたりがScoreboardの状態が数値化したものになります。 私の場合は、上記のkey-valueのデータをZabbix SenderでZabbixサーバに送ることでグラフ化したりしています。

モニタリングツールでScoreboardをモニタリングする参考リンク

私自身ですべて試したことがあるわけではありませんが、有名どころのモニタリングツールを使ってScoreboardをモニタリングする方法の関連リンクを紹介しておきます。

Zabbix

Datadog

mackerel

Prometheus