2011/11/04

Logrotateはいつログを圧縮するか、あるいは、postrotateはいつ実行されるか

日々刻々、サーバにログはたまり続け、ディスク容量を圧迫する場合がある。これを回避するため、ログファイルをrename (mv)、圧縮(gzip)し、保存期間を過ぎたものを削除する、ローテートと呼ばれる操作を行う必要がある。RHELやCentOSでは、logrotateというコマンドでこれを行う。

このlogrotateの設定ファイルは、/etc/logrotate.confおよび、その中から読み出される/etc/logrotate.d/*である。詳しくは、logrotate(8)を参照のこと。

さて、このlogrotateを使って、複数のサーバのログを、NFSなどの共有ディスク(仮に/var/oldlogsとする)に集約する場合を考えてみる。まず、複数のサーバのログが同ファイル名のだと一箇所に集約したときに上書きされてしまう可能性があるので、例えば、/var/log/some/log-ホスト名の様にしなければならない。

設定ファイル中で、ディレクティブolddir directoryでその共有ディスクに移動させる方法を思いつくかもしれない。
/var/log/some/log-ホスト名 {
      olddir /var/oldlogs
}
しかし、これは上手くいかない。というのは、logrotateでは、ローテート後のファイルは、元ファイルと同じデバイス上にしか置けない、という制限があるからだ。
# cat /etc/logrotate.d/test
/var/log/some/log-ホスト名 {
        olddir /var/oldlog
}
# ls /var/log/some
log-ホスト名
# logrotate -f /etc/logrotate.d/test
error: /etc/logrotate.d/test:3 olddir /var/oldlog and log file /var/log/some/log-ホスト名 are on different devices
# 
これを回避するために、ローテート後に共有ディスクへのコピーを実行することを思いつくだろう。設定ファイル中でpostrotate/endscript ディレクティブを使えば実現できる。このディレクティブを用いると、mv直後にシェルスクリプトを実行させることができるためだ。
# cat /etc/logrotate.d/test
/var/log/some/log-ホスト名 {
        rotate 4
        postrotate
                cp -p /var/log/some/log-ホスト名.1 /var/oldlog
        endscript
}
# logrotate -f /etc/logrotate.d/test
# ls /var/log/some
log-ホスト名.1
# ls /var/oldlog
log-ホスト名.1  lost+found
#
しかし、これにもまだ問題が出る場合がある。ディレクティブcompressを使って、ログファイルを圧縮する場合だ。次の様にして上手くいくだろうか?
# cat /etc/logrotate.d/test
/var/log/some/log-ホスト名 {
        rotate 4
        compress
        postrotate
                cp -p /var/log/some/log-ホスト名.1.gz /var/oldlog
        endscript
}
#
これは上手くいかない。
# ls /var/log/some/log-ホスト名*
/var/log/some/log-ホスト名
# ls /var/oldlog/log-ホスト名*
ls: /var/oldlog/log-ホスト名*: No such file or directory
# logrotate -f /etc/logrotate.d/test
cp: cannot stat `/var/log/some/log-ホスト名.1.gz': No such file or directory
error: error running postrotate script for /var/log/some/log-ホスト名
# ls /var/log/some/log-ホスト名*
/var/log/some/log-ホスト名.1.gz
# ls /var/oldlog/log-ホスト名.*
ls: /var/oldlog/log-ホスト名*: No such file or directory
#
なぜか?それは、実行タイミングにある。logrotateは、まずmvし、postrotate/endscript ディレクティブのシェルスクリプトを実行した後、gzipで圧縮するため、シェルスクリプト実行時には、まだ*.1.gzというファイルは存在せず、圧縮前の*.1のままである。

これは、postrotate/endscript ディレクティブの代わりにlastaction/endscript ディレクティブを使えば解決できる。
# cat /etc/logrotate.d/test
/var/log/some/log-ホスト名 {
        rotate 4
        compress
        lastaction
                cp -p /var/log/some/log-ホスト名.1.gz /var/oldlog
        endscript
}
# ls /var/log/some/log-ホスト名*
/var/log/some/log-ホスト名
# ls /var/oldlog/log-ホスト名*
ls: /var/oldlog/log-ホスト名*: No such file or directory
# logrotate -f /etc/logrotate.d/test
# ls /var/log/some/log-ホスト名*
/var/log/some/log-ホスト名.1.gz
# ls /var/oldlog/log-ホスト名*
/var/oldlog/log-ホスト名.1.gz
#
このままでは、共有ディスクには一世代しか保存されない。また、一度コピーに失敗すると、復旧できない。これらを解決するには、dateextディレクティブとrsyncコマンドを用いる。
# cat /etc/logrotate.d/test
/var/log/some/log-ホスト名 {
        rotate 4
        compress
        dateext
        lastaction
                rsync -a /var/log/some/log-ホスト名-*.gz /var/oldlog
        endscript
}
# ls /var/log/some/log-ホスト名*
/var/log/some/log-ホスト名
# ls /var/oldlog/log-ホスト名*
ls: /var/oldlog/log-ホスト名*: No such file or directory
# logrotate -f /etc/logrotate.d/test
# ls /var/log/some/log-ホスト名*
/var/log/some/log-ホスト名-20111104.gz
# ls /var/oldlog/log-ホスト名*
/var/oldlog/log-ホスト名-20111104.gz
#

2011/07/31

PowerShellでActive Directoryのグループのメンバを大量に取得

Active DirectoryをPowerShellで管理』で、PowerShell を使って、Active Directory のグループのメンバを取得する方法について書いた。
しかし、これには実は問題が二つある。一つ目は、グループのメンバ数が大量だったときにエラーが起こるという問題だ。二つ目は、所属関係の問い合わせを直接解決することができないという問題だ。どちらの問題も、PowerShell のActive Directory モジュールの問題で、代わりにADSIを利用することで解決できる。

まず一つ目の問題。例えば、次のようなエラーが発生する場合がある。
PS C:\> Import-Module ActiveDirectory
PS C:\> Get-ADGroupMember LargeGroup
Get-ADGroupMember : この要求のサイズ制限を超過しました。
発生場所 行:1 文字:18
+ Get-ADGroupMember <<<< LargeGroup
+ CategoryInfo : NotSpecified: (LargeGroup:ADGroup) [Get-ADGroupMember]、ADException
+ FullyQualifiedErrorId : この要求のサイズ制限を超過しました。,Microsoft.ActiveDirectory.Management.Commands.GetADGroupMember

PS C:\>
調査した範囲では、ActiveDirectory モジュールではこれを解決する方法は無い様だ。

そこで、この問題をADSIを使って解決する。
PS C:\> [DirectoryServices.DirectoryEntry]$ADSIGroup = [ADSI]("LDAP://CN=LargeGroup,CN=Some Groups,DC=domain,DC=local")
PS C:\> $members = $ADSIGroup.Members() | foreach { ([DirectoryServices.DirectoryEntry]$_).distinguishedName }
PS C:\>


二つ目の問題は、次のように一般化できる。
あるグループGとオブジェクトUに対して、UがGのメンバであるかどうかを返す。
間接的には、Get-ADPrincipalGroupMembershipを使えば解決できるが、このコマンドレットでは、G以外のグループに関しても所属関係を調べてしまう分無駄だ。
これもADSIで解決する。
PS C:\> [DirectoryServices.DirectoryEntry]$ADSIGroup = [ADSI]("LDAP://CN=LargeGroup,CN=Some Groups,DC=domain,DC=local")
PS C:\> $ADSIGroup.IsMember("LDAP://CN=user01,OU=Users,DC=domain,DC=local")
True
PS C:\> $ADSIGroup.IsMember("LDAP://CN=user02,OU=Users,DC=domain,DC=local")
False
PS C:\>

2011/06/11

Active DirectoryをPowerShellで管理

作業メモ。

今までできるだけWindowsには近づかない様に仕事をしてきが、その甲斐空しく、ついにActive Directory(AD)を触らなければならなくなってしまった。
毒を食らわば皿まで、ということで、ついでにPowreShellにも本格的に挑戦することに。ちょこちょこPowerShellでは遊んでたのだが。
参考: 『Active Directory Cmdlets in Windows PowerShell

今回のシステムでは、ユーザ・グループの情報は、別システムで管理されていて、それをADへ反映する必要がある。別システム側で更新があれば、即ADへ通知してADへ反映、というのが理想だ。ADにはLDAPインターフェースも準備されている。しかし、それはできないらしい。別システム側の担当者の勉強不足が原因。
であれば、毎日別システム側で前日通知からの変更差分を作成して、それをADへ通知、それを元にADへ反映するのが効率がよい。しかし、この差分更新方式にしても、前の即時更新方式にしても、通知が届かない場合あるいは、反映処理が異常停止する場合があることを考えると、いつかそのズレを正す棚卸処理を行わなければならない。
実行時間が許すならば、棚卸処理と差分更新処理を二つ準備するよりは、棚卸処理のみ書き、毎日棚卸処理を実行するのも一つの手だ。別システム側から見ても、差分データを送るのと、全データを送るのとでは、むしろ前者の方がコストが高い場合もある。
ということで、棚卸処理のみで行くことにした。

棚卸処理で難しいのは、グループに関する所属関係の変更だ。ユーザに対して所属グループが列挙されているようなデータを元にすると、所属関係が変更になったとき、新データからは、前に所属していたグループが判らない。ユーザから所属しているグループを得るには、次の様に実行すればよい。
C:\Users\Administrator>powershell
Windows PowerShell
Copyright (C) 2009 Microsoft Corporation. All rights reserved.

PS C:\Users\Administrator> Import-Module ActiveDirectory
PS C:\Users\Administrator> $u = Get-ADUser -Identity U0000000
PS C:\Users\Administrator> Get-ADPrincipalGroupMembership -Identity $u | select Name

Name
----
Domain Users
G0000001
G0000003


PS C:\Users\Administrator>

実際に実行していると、Get-ADPrincipalGroupMembershipの実行に時間が かかっているように感じられる。計測してみよう。
C:\Users\Administrator>powershell
Windows PowerShell
Copyright (C) 2009 Microsoft Corporation. All rights reserved.

PS C:\Users\Administrator> (Measure-Command {Import-Module ActiveDirectory}).TotalSeconds
0.8516534
PS C:\Users\Administrator> (Measure-Command {$u = Get-ADUser -Identity U0000000}).TotalSeconds
0.0352089
PS C:\Users\Administrator> (Measure-Command {Get-ADPrincipalGroupMembership -Identity $u}).TotalSeconds
0.6945307
PS C:\Users\Administrator>
実際、Get-ADUserの20倍程時間がかかっている。これはおそらく、グループからユーザへのリンクは用意されているが、逆のグループからのユーザのリンクは存在しておらず、ユーザから所属するグループを調べるには、グループを総なめしてるからだと考えられる。←これは間違いでした。

逆に、グループから所属メンバを得るには、次の様に実行する。
PS C:\Users\Administrator> $g=Get-ADGroup -Identity G0000003
PS C:\Users\Administrator> Get-ADGroupMember -Identity $g | select Name

Name
----
U0000000
U0000001
U0000002
U0000006
U0000444


PS C:\Users\Administrator>

実行時間を計測してみよう。
PS C:\Users\Administrator> (Measure-Command {$g=Get-ADGroup -Identity G0000003}).TotalSeconds
0.0057593
PS C:\Users\Administrator> (Measure-Command {Get-ADGroupMember -Identity $g}).TotalSeconds
0.2843582
PS C:\Users\Administrator>
意外な事に、Get-ADGroupMember の実行に時間がかかっている。これは、グループのメンバをリンクからたどってオブジェクトを生成しているからかもしれない?確かに時間はかかっているが、前のGet-ADPrincipalGroupMembershipに比べると、返されるオブジェクト数に大差ないのに 、実行時間は半分以下でしかない。

以上の推測が正しいとすると、グループ数に比例してGet-ADPrincipalGroupMembershipの実行時間が増加すると考えられる。

もしそうでなかったとしても、一般に、ユーザ数は、グループ数より多いので、ユーザ数分の繰り返しを実行するより、ユーザ数分のループより、グループ数分のループの方が実行時間は短いはずだ。

結論として、ユーザに対してループして、その所属関係を変更するのではなく、グループに対してループして、その所属関係を変更する方が実行時間が短くなるはずだ。

2011/04/13

Xen DomUでRedmine + Phusion Passenger + Ruby Enterprise Edition

二年ぶりのご無沙汰でした。

Xen DomUで、Redmineを運用する。物理マシンの場合の手順は、『Redmine 1.1をCentOS5.5にインストールする手順』に詳しいが、Xen DomUにこれと同じ手順を適用すると、コンソールや/var/log/messagesあたりに次のようなメッセージが表示されることがある
4gb seg fixup, process ruby (pid 1270), cs:ip 73:00867ba6
4gb seg fixup, process ruby (pid 1270), cs:ip 73:001c30f1
4gb seg fixup, process ruby (pid 1270), cs:ip 73:00867ba6
4gb seg fixup, process ruby (pid 1270), cs:ip 73:001c30f1
printk: 96 messages suppressed.
4gb seg fixup, process ruby (pid 1270), cs:ip 73:00867ba6
これは、『4gb seg fixup REE XEN』で報告されている問題と同じで、ここで挙げられている通り、環境変数を設定してからRuby Enterprise Edition(以下、REE)をインストールすればよい。以下作業ログ。
REEをダウンロードし、展開する。
[fujino@DomU Ruby]$ ls ruby-enterprise-1.8.7-2011.03.tar.gz
ruby-enterprise-1.8.7-2011.03.tar.gz
[fujino@DomU Ruby]$ tar xzf ruby-enterprise-1.8.7-2011.03.tar.gz
[fujino@DomU Ruby]$
以下、rootで作業する。まず、環境変数を設定する。
[root@DomU Ruby]# export CFLAGS="-mno-tls-direct-seg-refs"
[root@DomU Ruby]# export CXXFLAGS="-mno-tls-direct-seg-refs"
[root@DomU Ruby]#
REEをインストールする。このとき、REEに同梱されているgemや開発者用ドキュメントをインストールしないよう指定する。
[root@DomU Ruby]# ./ruby-enterprise-1.8.7-2011.03/installer --dont-install-useful-gems --no-dev-docs
Welcome to the Ruby Enterprise Edition installer
This installer will help you install Ruby Enterprise Edition 1.8.7-2011.03.
Don't worry, none of your system files will be touched if you don't want them
to, so there is no risk that things will screw up.

You can expect this from the installation process:

1. Ruby Enterprise Edition will be compiled and optimized for speed for this
system.
2. Ruby on Rails will be installed for Ruby Enterprise Edition.
3. You will learn how to tell Phusion Passenger to use Ruby Enterprise
Edition instead of regular Ruby.

Press Enter to continue, or Ctrl-C to abort.[Enter]を入力

Checking for required software...

* C compiler... found at /usr/bin/gcc
* C++ compiler... found at /usr/bin/g++
* The 'make' tool... found at /usr/bin/make
* The 'patch' tool... found at /usr/bin/patch
* Zlib development headers... found
* OpenSSL development headers... found
* GNU Readline development headers... found
--------------------------------------------
Target directory

Where would you like to install Ruby Enterprise Edition to?
(All Ruby Enterprise Edition files will be put inside that directory.)

[/opt/ruby-enterprise-1.8.7-2011.03] :[Enter]を入力
--------------------------------------------
Compiling and optimizing the memory allocator for Ruby Enterprise Edition
In the mean time, feel free to grab a cup of coffee.

./configure --prefix=/opt/ruby-enterprise-1.8.7-2011.03 --disable-dependency-tracking
checking build system type... i686-pc-linux-gnu
checking host system type... i686-pc-linux-gnu
<<略>>
--------------------------------------------
Ruby Enterprise Edition is successfully installed!
If want to use Phusion Passenger (http://www.modrails.com) in combination
with Ruby Enterprise Edition, then you must reinstall Phusion Passenger against
Ruby Enterprise Edition, as follows:

/opt/ruby-enterprise-1.8.7-2011.03/bin/passenger-install-apache2-module

Make sure you don't forget to paste the Apache configuration directives that
the installer gives you.


If you ever want to uninstall Ruby Enterprise Edition, simply remove this
directory:

/opt/ruby-enterprise-1.8.7-2011.03

If you have any questions, feel free to visit our website:

http://www.rubyenterpriseedition.com

Enjoy Ruby Enterprise Edition, a product of Phusion (www.phusion.nl) :-)
[root@DomU Ruby]#
Gemを1.4.2へダウングレードする。
[root@DomU Ruby]# /opt/ruby-enterprise-1.8.7-2011.03/bin/ruby -S gem update --system 1.4.2
Updating rubygems-update
Fetching: rubygems-update-1.4.2.gem (100%)
Successfully installed rubygems-update-1.4.2
Installing RubyGems 1.4.2
RubyGems 1.4.2 installed
File not found: README
[root@DomU Ruby]# /opt/ruby-enterprise-1.8.7-2011.03/bin/ruby -S gem --version
1.4.2
[root@DomU Ruby]#
Rack 1.0.1およびi18n 0.4.2をインストールする。開発者用ドキュメントは不要なのでインストールしない。
[root@DomU Ruby]# /opt/ruby-enterprise-1.8.7-2011.03/bin/ruby -S gem install rack --version=1.0.1 --no-rdoc --no-ri
Fetching: rack-1.0.1.gem (100%)
Successfully installed rack-1.0.1
1 gem installed
[root@DomU Ruby]# /opt/ruby-enterprise-1.8.7-2011.03/bin/ruby -S gem install i18n --version=0.4.2 --no-rdoc --no-ri
Fetching: i18n-0.4.2.gem (100%)
Successfully installed i18n-0.4.2
1 gem installed
[root@DomU Ruby]#
づつけて、MySQL gemをインストールする。やはり開発者用ドキュメントは不要なのでインストールしない。
[root@DomU Ruby]# /opt/ruby-enterprise-1.8.7-2011.03/bin/ruby -S gem install mysql --no-rdoc --no-ri
Fetching: mysql-2.8.1.gem (100%)
Building native extensions. This could take a while...
Successfully installed mysql-2.8.1
1 gem installed
[root@DomU Ruby]#
Apache HTTP Server用モジュールをインストールする。
[root@DomU Ruby]# /opt/ruby-enterprise-1.8.7-2011.03/bin/passenger-install-apache2-module
Welcome to the Phusion Passenger Apache 2 module installer, v3.0.6.

This installer will guide you through the entire installation process. It
shouldn't take more than 3 minutes in total.

Here's what you can expect from the installation process:

1. The Apache 2 module will be installed for you.
2. You'll learn how to configure Apache.
3. You'll learn how to deploy a Ruby on Rails application.

Don't worry if anything goes wrong. This installer will advise you on how to
solve any problems.

Press Enter to continue, or Ctrl-C to abort.[Enter]を入力


--------------------------------------------

Checking for required software...

* GNU C++ compiler... found at /usr/bin/g++
* Curl development headers with SSL support... found
* OpenSSL development headers... found
<<略>>

--------------------------------------------
The Apache 2 module was successfully installed.

Please edit your Apache configuration file, and add these lines:

LoadModule passenger_module /opt/ruby-enterprise-1.8.7-2011.03/lib/ruby/gems/1.8/gems/passenger-3.0.6/ext/apache2/mod_passenger.so
PassengerRoot /opt/ruby-enterprise-1.8.7-2011.03/lib/ruby/gems/1.8/gems/passenger-3.0.6
PassengerRuby /opt/ruby-enterprise-1.8.7-2011.03/bin/ruby

After you restart Apache, you are ready to deploy any number of Ruby on Rails
applications on Apache, without any further Ruby on Rails-specific
configuration!

Press ENTER to continue.[Enter]を入力


--------------------------------------------
Deploying a Ruby on Rails application: an example

Suppose you have a Rails application in /somewhere. Add a virtual host to your
Apache configuration file and set its DocumentRoot to /somewhere/public:


ServerName www.yourhost.com
DocumentRoot /somewhere/public # <-- be sure to point to 'public'!

AllowOverride all # <-- relax Apache security settings
Options -MultiViews # <-- MultiViews must be turned off



And that's it! You may also want to check the Users Guide for security and
optimization tips, troubleshooting and other useful information:

/opt/ruby-enterprise-1.8.7-2011.03/lib/ruby/gems/1.8/gems/passenger-3.0.6/doc/Users guide Apache.html

Enjoy Phusion Passenger, a product of Phusion (www.phusion.nl) :-)
http://www.modrails.com/

Phusion Passenger is a trademark of Hongli Lai & Ninh Bui.
[root@DomU Ruby]#