ClamAVをCGroupで管理してCPUを節約する

2015.07.30

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、せーのです。今日は最近触っているClamAVについてのちょっとした工夫をご紹介します。ClamAVに関わらずサービス実行するプログラムであれば応用がきくかと思いますので参考になさってください。今までのシリーズをご覧になった上でこちらの記事を読むとより理解が深まります。

サーバーはウイルススキャンのために立てているわけではない

ご存じの通りClamAVはアンチウイルスソフトウェアです。サーバーがウイルスに侵されないように監視、検知するためのソフトなわけですが、ウイルススキャン中にはサーバーをくまなくチェックするため、CPU使用率がぐんっと上がります。チェックするファイル数によってはスキャン中にはCPUが100%まで上がってしまい、別の監視ソフトで異常として検知されたりする場合もあります。そもそもサーバーはサービスを実行するために立てているわけで、ウイルススキャンというサーバーの健康を保つための操作でCPUをやたらと使ってしまい、もしそれが原因でサービスが重くなったりしてしまっては本末転倒です。

"CGroup"とは

そこで今回はコントロールグループ(通称CGroup)というLinuxカーネルの機能を使ってCPUリソースを抑制してみたいと思います。CGroupとはCPUやメモリ等のシステムリソースの割り当て、優先度設定、拒否、管理、モニタリングに対する粒度の細かいコントロールを可能にする機能です。CGroupを使うことでシステムリソースをどのそれぞれのプロセスにどのくらい割り当てるか、というコントロールができるようになります。

やってみた

では早速やってみましょう。ClamAVは大きく分けて「ウイルス定義DBを更新する(freshclam)」と「ウイルススキャンをする(clamscan)」の2つの動作に分かれるのでそれぞれをCGroupにてコントロールしてみます。Amazon Linux(t2.micro)のEC2を立て、ClamAVをインストールした状態まで、上のシリーズ記事を参考に準備して下さい。でははじめます。

CGroupの設定

まずCGroupを使えるようにインストールします。隅々までスキャンする想定のためrootユーザーで行っていますが、本番環境ではスキャン用のユーザーを作成して行ってもよいかと思います。

[root@ip-172-31-28-136 ~]# yum install libcgroup -y
読み込んだプラグイン:priorities, update-motd, upgrade-helper
amzn-main/latest                                         | 2.1 kB     00:00
amzn-updates/latest                                      | 2.3 kB     00:00
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ libcgroup.x86_64 0:0.40.rc1-5.11.amzn1 を インストール
--> 依存性解決を終了しました。

依存性を解決しました

================================================================================
 Package         アーキテクチャー
                              バージョン                  リポジトリー     容量
================================================================================
インストール中:
 libcgroup       x86_64       0.40.rc1-5.11.amzn1         amzn-main       146 k

トランザクションの要約
================================================================================
インストール  1 パッケージ

総ダウンロード容量: 146 k
インストール容量: 320 k
Downloading packages:
libcgroup-0.40.rc1-5.11.amzn1.x86_64.rpm                 | 146 kB     00:00
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  インストール中          : libcgroup-0.40.rc1-5.11.amzn1.x86_64            1/1
  検証中                  : libcgroup-0.40.rc1-5.11.amzn1.x86_64            1/1

インストール:
  libcgroup.x86_64 0:0.40.rc1-5.11.amzn1

完了しました!
[root@ip-172-31-28-136 ~]#

次にCGroupの設定ファイルを修正します。設定ファイルは/etc/cgconfig.confにあります。

/etc/cgconfig

[root@ip-172-31-28-136 ~]# cat /etc/cgconfig.conf
#
#  Copyright IBM Corporation. 2007
#
#  Authors:	Balbir Singh <balbir@linux.vnet.ibm.com>
#  This program is free software; you can redistribute it and/or modify it
#  under the terms of version 2.1 of the GNU Lesser General Public License
#  as published by the Free Software Foundation.
#
#  This program is distributed in the hope that it would be useful, but
#  WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See man cgconfig.conf for further details.
#
# By default, mount all controllers to /cgroup/<controller>

mount {
    blkio      = /cgroup/blkio;
    cpu        = /cgroup/cpu;
    cpuacct    = /cgroup/cpuacct;
    cpuset     = /cgroup/cpuset;
    devices    = /cgroup/devices;
    freezer    = /cgroup/freezer;
    hugetlb    = /cgroup/hugetlb;
    memory     = /cgroup/memory;
    perf_event = /cgroup/perf_event;
}

group test {
 cpu {
   cpu.cfs_quota_us = 250000;
   cpu.cfs_period_us = 1000000;
 }
 cpuacct {

 }
}

新たに「test」というCGroupを追記しました。コマンドでも追加できるのですが再起動すると消えてしまうので設定ファイルに書き込んだほうが確実です。ここではCPUリソースについて抑制する設定を書いています。 少し解説します。cfs_period_usというのはCGroupで管理する実行時間を表しcfs_quota_usというのはそのうちこのグループに所属しているプロセスがアクセスできる時間を表します。単位はそれぞれマイクロ秒(μs)です。つまり上の例で行くとこのグループに所属しているプロセスは1秒間(1000000μs)に250msのみCPUにアクセスできることになります。常にCPUにアクセスするとCPUは100%になるので、この設定だと最大25%にCPUが抑制されることになります。

ただ一つ押さえておいて欲しいのはcfs_period_usは「単一CPUで」ということです。つまり上の設定でCPUが2つある場合は1つで1秒間、つまり2つで2秒間という割り当てになるので、そのうち250msしか使えないと全体でのCPU使用率は12.5%になります。今回検証に使用しているt2.microは単一CPUなのでCPU1つとして計算していますが、実際の環境で設定する場合はそのコア数も注意して下さい。

それでは設定が終わりましたのでサービスを起動します。

[root@ip-172-31-28-136 ~]# service cgconfig start
Starting cgconfig service:                                 [  OK  ]

サービスを永続化するためにchkconfigをonにします。

[root@ip-172-31-28-136 ~]# chkconfig cgconfig on

これでCGroupの設定は完了です。後はClamAVのプロセスを作動し、作成したCGroupに移行する作業に入ります。

freshclamをデーモン化する

まずはfreshClam, つまりウイルス検知のデータベースの更新をデーモン化します。freshclamはデーモン化のオプションがあるのでそれを追加するだけでOKです。また後で上にて作成したCGroupに移行する際にプロセスIDが必要になるので、それをファイルに吐き出すpオプションをつけておきます。

[root@ip-172-31-28-136 ~]# freshclam -d -c 24 -p /root/freshclampid.log -u root

こんな感じです。オプションは

  • d: デーモン化する
  • c: 一日に何回作動させるか
  • p: プロセスIDをファイルに吐き出す
  • u: 指定ユーザーにて作動させる

となります。このコマンドを実行後/root/freshclampid.logを見てみると

[root@ip-172-31-28-136 ~]# cat freshclampid.log
29721

とプロセスIDが吐き出されています。

clamscanをデーモン化する

次にclamscanをデーモン化します。ですがclamscanはfreshclamのようにデーモン化するコマンドがありません。代わりに「clamd」というサーバーをプロセス稼働させ、それを使います。 まずclamdをインストールします。

[root@ip-172-31-28-136 ~]# yum install clamd
読み込んだプラグイン:priorities, update-motd, upgrade-helper
amzn-main/latest                                         | 2.1 kB     00:00
amzn-updates/latest                                      | 2.3 kB     00:00
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ clamd.x86_64 0:0.98.7-1.12.amzn1 を インストール
--> 依存性の処理をしています: clamav-scanner-sysvinit = 0.98.7-1.12.amzn1 のパッケージ: clamd-0.98.7-1.12.amzn1.x86_64
--> トランザクションの確認を実行しています。
---> パッケージ clamav-scanner-sysvinit.noarch 0:0.98.7-1.12.amzn1 を インストール
--> 依存性の処理をしています: clamav-scanner = 0.98.7-1.12.amzn1 のパッケージ: clamav-scanner-sysvinit-0.98.7-1.12.amzn1.noarch
--> 依存性の処理をしています: clamav-server-sysvinit = 0.98.7-1.12.amzn1 のパッケージ: clamav-scanner-sysvinit-0.98.7-1.12.amzn1.noarch
--> トランザクションの確認を実行しています。
---> パッケージ clamav-scanner.noarch 0:0.98.7-1.12.amzn1 を インストール
--> 依存性の処理をしています: clamav-server = 0.98.7-1.12.amzn1 のパッケージ: clamav-scanner-0.98.7-1.12.amzn1.noarch
---> パッケージ clamav-server-sysvinit.noarch 0:0.98.7-1.12.amzn1 を インストール
--> トランザクションの確認を実行しています。
---> パッケージ clamav-server.x86_64 0:0.98.7-1.12.amzn1 を インストール
--> 依存性解決を終了しました。

依存性を解決しました

================================================================================
 Package                   アーキテクチャー
                                    バージョン             リポジトリー    容量
================================================================================
インストール中:
 clamd                     x86_64   0.98.7-1.12.amzn1      amzn-updates    18 k
依存性関連でのインストールをします:
 clamav-scanner            noarch   0.98.7-1.12.amzn1      amzn-updates    25 k
 clamav-scanner-sysvinit   noarch   0.98.7-1.12.amzn1      amzn-updates    19 k
 clamav-server             x86_64   0.98.7-1.12.amzn1      amzn-updates    99 k
 clamav-server-sysvinit    noarch   0.98.7-1.12.amzn1      amzn-updates    20 k

トランザクションの要約
================================================================================
インストール  1 パッケージ (+4 個の依存関係のパッケージ)

総ダウンロード容量: 182 k
インストール容量: 205 k
Is this ok [y/d/N]: y
Downloading packages:
(1/5): clamav-scanner-0.98.7-1.12.amzn1.noarch.rpm       |  25 kB     00:00
(2/5): clamav-scanner-sysvinit-0.98.7-1.12.amzn1.noarch. |  19 kB     00:00
(3/5): clamav-server-0.98.7-1.12.amzn1.x86_64.rpm        |  99 kB     00:00
(4/5): clamav-server-sysvinit-0.98.7-1.12.amzn1.noarch.r |  20 kB     00:00
(5/5): clamd-0.98.7-1.12.amzn1.x86_64.rpm                |  18 kB     00:00
--------------------------------------------------------------------------------
合計                                               821 kB/s | 182 kB  00:00
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  インストール中          : clamav-server-0.98.7-1.12.amzn1.x86_64          1/5
  インストール中          : clamav-server-sysvinit-0.98.7-1.12.amzn1.noar   2/5
  インストール中          : clamav-scanner-0.98.7-1.12.amzn1.noarch         3/5
  インストール中          : clamav-scanner-sysvinit-0.98.7-1.12.amzn1.noa   4/5
  インストール中          : clamd-0.98.7-1.12.amzn1.x86_64                  5/5
  検証中                  : clamav-scanner-sysvinit-0.98.7-1.12.amzn1.noa   1/5
  検証中                  : clamav-server-sysvinit-0.98.7-1.12.amzn1.noar   2/5
  検証中                  : clamav-server-0.98.7-1.12.amzn1.x86_64          3/5
  検証中                  : clamd-0.98.7-1.12.amzn1.x86_64                  4/5
  検証中                  : clamav-scanner-0.98.7-1.12.amzn1.noarch         5/5

インストール:
  clamd.x86_64 0:0.98.7-1.12.amzn1

依存性関連をインストールしました:
  clamav-scanner.noarch 0:0.98.7-1.12.amzn1
  clamav-scanner-sysvinit.noarch 0:0.98.7-1.12.amzn1
  clamav-server.x86_64 0:0.98.7-1.12.amzn1
  clamav-server-sysvinit.noarch 0:0.98.7-1.12.amzn1

完了しました!

次にclamdの設定を行います。Amazon Linuxでのclamdの設定ファイルはドキュメントにある[/etc/clamd.conf]ではなく[etc/clamd.d/scan.conf]になりますので注意してください。

ファイルを開いたら次の点を修正していきます。

Excampleをコメントアウト

/etc/clamd.d/scan.conf

# Comment or remove the line below.
#Example

ログファイルを指定

/etc/clamd.d/scan.conf

# Uncomment this option to enable logging.
# LogFile must be writable for the user running daemon.
# A full path is required.
# Default: disabled
LogFile /var/log/clamd.scan

ログファイルのロックを外しておく

/etc/clamd.d/scan.conf

# By default the log file is locked for writing - the lock protects against
# running clamd multiple times (if want to run another clamd, please
# copy the configuration file, change the LogFile variable, and run
# the daemon with --config-file option).
# This option disables log file locking.
# Default: no
LogFileUnlock yes

ログに日時のスタンプをつける

/etc/clamd.d/scan.conf

# Log time with each message.
# Default: no
LogTime yes

Syslogにログを吐く設定をコメントアウト

/etc/clamd.d/scan.conf

# Use system logger (can work together with LogFile).
# Default: no
#LogSyslog yes

ローカルのソケットを開け、ローカルで作動するようにする

/etc/clamd.d/scan.conf

# Path to a local socket file the daemon will listen on.
# Default: disabled (must be specified by a user)
LocalSocket /var/run/clamd.scan/clamd.sock

rootユーザで動くようにする(clamAV用のユーザを作成している方はそのユーザを指定する)

/etc/clamd.d/scan.conf

# Run as another user (clamd must be started by root for this option to work)
# Default: don't drop privileges
User root

以上で設定は終了です。ではclamdを作動させます。

[root@ip-172-31-28-136 ~]# service clamd.scan start
clamd.scan を起動中:                                       [  OK  ]
[root@ip-172-31-28-136 ~]# chkconfig clamd.scan on

プロセスIDを返すようなオプションが無いため、psコマンドでプロセスIDを確認します。

[root@ip-172-31-28-136 ~]# ps ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:00 /sbin/init
...
...
 3025 ?        Ssl    0:00 clamd.scan -c /etc/clamd.d/scan.conf --pid /var/run/c
 3028 pts/0    R+     0:00 ps ax
...
...

clamdの動いているプロセスIDは3025のようです。

freshclamとclamdのプロセスを設定したCGroup配下に移動する

次に上の2つのプロセスをCGroup配下に移動します。移動させるには[cgclassify]コマンドを使います。

[root@ip-172-31-28-136 ~]# cgclassify -g cpu:test --sticky 3025
[root@ip-172-31-28-136 ~]# cat /proc/3025/cgroup
10:perf_event:/
9:memory:/
8:hugetlb:/
7:freezer:/
6:devices:/
5:cpuset:/
4:cpuacct:/
3:cpu:/test
2:blkio:/

使用しているオプションについて軽く触れておきます。gオプションはグループの指定を指します。今回はCPUリソースについてtestグループに移動させるのでこのように書きます。stickyオプションはこのプロセスの子プロセスも同じグループにキープさせるというオプションです。実際のスキャン時にはこのプロセスから子プロセスが作られる場合もあるのでこのようなオプションをつけておきました。 cgclassifyコマンド実行後プロセスIDのcgroupを確認したところcpuリソースのみtestグループに移動していることが確認できました。これで設定完了です。freshclamのプロセスIDである29721も同様に移動させます。

まとめ

いかがでしょうか。CGroupの設定はなれるまではやることが多くて大変ですが、慣れてしまえばルーティーン化出来るかと思います。CGroupはClamAVだけではなく他のプロセスにも活用できるので是非応用してみてください。

参考リンク