2014年1月31日金曜日

slapadd のログを syslogd でとる設定 ※slapd でも none を忘れずに

昨日に引き続き OpenLDAP ネタ。slapadd を実行するシェルスクリプトを書いていて、ログ出力がどうもうまくいかないので調べていた。

slapadd の man ページを参照すると -o オプションで syslogd に関する設定を色々指定できるみたいだ。該当部分はこんな記述になっている。

-o option[=value]
     Specify an option  with  a(n  optional)  value.   Possible  generic
     options/values are:

        syslog=<subsystems>  (see `-s' in slapd(8))
        syslog-level=<level> (see `-S' in slapd(8))
        syslog-user=<user>   (see `-l' in slapd(8))

        schema-check={yes|no}
        value-check={yes|no}

     The schema-check option toggles schema checking (default on);
     the value-check option toggles value checking (default off).
     The latter is incompatible with -q.
 

今回 syslogd 側 では daemon ファシリティでプライオリティが info 以上のログを出力するよう設定していたので、まずは

slapadd -o "syslog-level=INFO" -o "syslog-user=DAEMON" ・・・(以下略)
 

みたいな感じにやってみたけどログが出ない。いろいろ試行錯誤する中で、試しにそれまで付けていた -F オプションを外してみるとエラーがログに出た。どうやら -F オプションを指定するとデフォルトの slapd から出すログレベルの設定が消えて、何も syslog に出なくなるようだ。
そこで -o "syslog=<subsystems>" で改めて指定する。<subsystems> に指定できるログレベルについては昨日の記事を参照。

slapadd -o "syslog=stats,stats2,shell,parse,sync,none" -o "syslog-level=INFO" -o "syslog-user=DAEMON" ・・・(以下略)
 

これでログ出力できた。

なお、ログレベルについて一点気をつけたほうがいいことがある。それは none だ。各種サイトで調べてみても none ってなんか一見不要そうな雰囲気だから最初は付けてなかった。けれど、これは指定しておくことを強くおすすめする。

たとえば、slapd をシェルからコマンドラインで打つとシェルには出るエラーメッセージが syslogd に記録されないんでおかしいと思ってたら、none を含めたら記録されるようになった。読んで意味がわかるメッセージがログに記録されるのは重要。こんな有用なログをなぜ none って名前のログレベルにしたんだよ・・・。

ちなみに余談だけれど、上に引用した slapadd の man ページの -o オプションの部分には、syslogd へ出力するログのプライオリティを変更するための slapd の -S オプションについて言及があるのに、slapd の man ページの方には -S オプションについての説明がない(笑)。それでも -S オプションは slapd の方でもきちんと機能する。単に man ページヘの書き忘れかもしれないから、syslogd への出力プライオリティを変更するオプションとして覚えておくと便利だろう。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年1月30日木曜日

slapd の syslogd 向けログ出力とデバッグレベル

OpenLDAPでLDAPサーバを構築している途中で、slapdがうまく起動しないことがあった。その際、slapdがログを吐かないことに気づいた。

そもそも、OpenLDAPの場合、デフォルト設定のままではログが出力されない設定になっているらしい。変更すべき設定は syslogd のファシリティとデバッグレベル。

・syslogd のファシリティ
ファシリティとは、ログの分類のこと。syslogd で出力されるログには、アプリケーションが指定したファシリティとプライオリティが付いている。
ファシリティには次のようなものがある。

ファシリティ 対象のログ
auth(security) 認証サービスのメッセージ(現在はauthprivが推奨されている)
authpriv 認証サービス(カテゴリはauthと同じ。authとは出力結果が異なる)
cron cronのメッセージ
daemon デーモンのメッセージ
kern カーネルのメッセージ
lpr プリンタサービスのメッセージ
mail メールサービスのメッセージ
news ニュースサービスのメッセージ
syslog syslogのメッセージ
user ユーザープロセスのメッセージ
uucp uucp転送を行うプログラムのメッセージ
local0~7 アプリケーションに依存する

プライオリティは、ログの重要度を表す。次のようなものがある。

プライオリティ 説明
emerg 認証サービスのメッセージ(現在はauthprivが推奨されている)
alert 認証サービス(カテゴリはauthと同じ。authとは出力結果が異なる)
crit cronのメッセージ
err デーモンのメッセージ
warning カーネルのメッセージ
notice プリンタサービスのメッセージ
info メールサービスのメッセージ
debug ニュースサービスのメッセージ

どのファシリティのどのプライオリティのログを出力するかについては、/etc/syslog.conf ファイルに設定する。なお最近の Linux では syslogd ではなくその改良版である rsyslog が使われているかもしれないので、その場合 /etc/rsyslog.conf が設定ファイルになる。

設定は

ファシリティ.プライオリティ    ログ出力先

のように書く。例えばメールに関する全てのログを /var/log/maillog に出力する設定は

mail.*    /var/log/maillog

という感じになる。

で、話をOpenLDAPに戻すと、実はOpenLDAPのデフォルトのファシリティは local4 になっている。この local4 に対するログ出力設定がデフォルトでは存在しないため、デフォルト設定のままではログが出力されないのだ。

これに対処するには、syslogd側の設定を変更するlogd側の設定を変更する場合、ファシリティlocal4 に対する設定を追加すればいい。次のような感じになる。

local4.*    /var/log/ldap.log 

これを /etc/syslog.conf (または /etc/rsyslog.conf)に追加し、syslogデーモンを再起動すればよい。

なお、slapd側がsyslogdへログを出力する際のファシリティを変更することもできる。これは slapd 起動時の -l オプションで指定できる。LOCAL0〜LOCAL7, USER, DAEMON が指定可能。

もう一つ注意すべき点は、slapd が syslog へ出力する際のプライオリティは DEBUG になる点だ。このプライオリティを変更する方法は色々調べたが見つからなかった。(僕の環境では -S INFO を指定することでプライオリティを INFO に変更できたが、誰か正式な方法知ってたら教えてください。)

slapd が出力するログのレベルは、Configuration Backend を使っている場合 cn=config エントリの olcLogLevel に格納する。ログレベルは、次のようなものがある。

ログレベル
(10進数)
ログレベル
(16進数)
ログレベル
(文字列)
ログ出力内容
-1 - any すべてのログを出力
0 - - まったくログを出力しない
1 0x1 trace 内部の関数呼び出し
2 0x2 packets パケット操作
4 0x4 args 処理の詳細なトレース
8 0x8 cons コネクション管理
16 0x10 BER パケット送受信
32 0x20 filter 検索フィルタ処理
64 0x40 config 設定ファイルの処理
128 0x80 ACL アクセス制御リストの処理
256 0x100 stats ステータスログ(デフォルト)
512 0x200 stats2 ステータスログ2
1024 0x400 shell シェルバックエンドとの通信
2048 0x800 parse エントリの解析
16384 0x4000 sync syncrepl コンシューマ処理
32768 0x8000 none 設定に依存しない最低限の出力のみ

出力したいログレベルの数値の合計、またはログレベルの文字列をカンマ区切りで列挙する。デフォルトは 256 (stats) 。設定例としては

dn: cn=config
objectClass: olcGlobal
cn: config
olcLogLevel: trace,ACL

こんな感じ。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

参考ページ:
OpenLDAP ソフトウェア 2.3 管理者ガイド: slapd の実行
OpenLDAPによるディレクトリサーバ運用(3):ものいわぬOpenLDAPサーバのログ管理 (1/3) - @IT
Linux管理者への道(3):システム管理の基礎 syslogdの設定をマスターしよう (1/3) - @IT

2014年1月29日水曜日

UNIX/Linux系OSにおけるシグナル

昨日プロセスとシェルについて調べていく途中で、プロセス間で情報をやりとりする仕組みの一種のシグナルとはそもそも何なのかが気になって調べてみたのでまとめてみる。

↓こちらのサイトが素晴らしい解説なので一読をお勧めする。
http://www.nurs.or.jp/~sug/soft/super/signal.htm
下の文章はほぼ上記サイトの要約みたいなもんです。

・ハードウェア割り込み
キーボードからの入力など、予期せぬタイミングで処理をしなければならないイベントに対応するため、CPUには「ハードウェア割り込み」の機能がある。これはCPUのINTRピンがOFFになった時に、現在の処理を一時中断して別の処理へと飛び、完了後に元に戻ってくる機能である。ハードウェア割り込みがあるおかげで、プログラムは例えば一定間隔でキーボードの入力があったかチェックするようなことをせずに済む。

・ハードウェア割り込みとシグナル
このハードウェア割り込みの仕組みと同様の仕組みをOSにおいて実現したのがシグナルである。シグナルには対応する処理(シグナルハンドラ)を決めておくことができ、シグナルを受け取るとその処理が呼び出される。なお、UNIX系OSでは全てのシグナルにデフォルト処理が定めれらている。デフォルト処理を再定義して任意の処理に変更できる。

・シグナルのリエントラント問題とPOSIXシグナルの動作
システムコールで何かを待機中にシグナルを受け取ったり、シグナルハンドラの処理中に再度同じシグナルを受け取ることが考えられる。これらのパターンは予期しない結果を生む可能性がある。これをシグナルのリエントラント(再入)問題と呼ぶ。

そのためPOSIXの定めたシグナルの動作では、シグナルハンドラの処理中に別のシグナルを受け取ると、新しく受け取ったシグナルを保留状態にし、前のシグナルハンドラの処理が終わった段階で新しい方のシグナルを処理するようになっている。また、システムコールを実行中にシグナルを受け取った場合はデフォルトではシステムコールはエラーで終了するが、シグナルハンドラの処理完了後にシステムコールを継続するようにすることもできる。

・Linuxにおけるシグナルの一部とデフォルトの動作及び送信方法の例
SIGINT:プロセスの終了。シェルでCtrl+Cが押されると送信。
SIGTSTP:プロセスを一旦停止。シェルでCtrl+Zが押されると送信。
SIGCONT:プロセスを再開。シェルで「fg」や「bg」と叩いた時送信。
SIGQUIT:プロセスを終了してコアダンプ。シェルでCtrl+\が押されると送信。
SIGTERM:プロセスの終了。子プロセスがある場合、そのプロセスが子プロセスも終了させる。killコマンド実行時にデフォルトで送信。
SIGHUP:プロセスの終了(デーモンの場合再起動)。ログインシェルからログアウト時に送信。
SIGKILL:プロセスの強制終了。killコマンドで明示的に指定すれば送信できる。
上記の他にも多数ある。下の参考URLを参照。

こんなもんかな。シグナルについてまとめてみたけれど、よく考えられてると思う。シェルからの操作とか、killコマンドとか、なんとなくの理解のまま使ってたのでスッキリした。

2014年1月28日火曜日

シェルとプロセスとプログラムの呼び出し

結構長いことLinuxを使っている自分だけれども、アプリ開発の仕事をずっとやってきたために、OSの機能については疎いところがあったりする。

先日、プログラミング言語から別のプログラムを呼ぶときに「シェル経由で呼び出す」方法と「シェルを経由せずに呼び出す」方法がある、という話を聞いた時によくわからなかったので、調べてみた。

・プロセスの概念
まず、プロセスの概念について。言葉だけは何となく使っちゃてるけど。プロセスとは、プログラムを実行するときにメモリ上にロードしてIDを割り当てたもの。たとえ同じプログラムでも、別々のメモリ領域を与えてアドレス空間を分離することによって、互いに干渉せず同時に実行することが可能になる。

・プロセスの複製と親子関係
UNIX系OSにはfork()システムコールがあり、これはプロセスを環境変数や実行位置なども含め現在の状態をまるごとコピーしてIDを付け替えたプロセスを生成する。fork()を呼び出した側が親プロセスで、fork()によって生成されたプロセスが子プロセスとなる。fork()の戻り値として親プロセス側には子プロセスのプロセスIDが渡され、子プロセス側には0が渡されるので自分が親か子かを判別できる。

・子プロセスの置き換え
あるプログラムから別のプログラムを呼び出す場合、まずfork()システムコールで子プロセスを生成し、子プロセス側でexec()システムコールを呼び出す。exec()システムコールは、自分自身を別のプログラムに置き換える(一旦メモリ上のアドレス空間を破棄して作りなおす)。プロセスIDは維持されるので、親子関係は保たれる。

・プロセスの終了
プロセス内でexit()システムコールを呼び出すと、そのプロセスは終了するが、メモリ上に管理情報は残っている。親プロセスからwait()システムコールを呼ぶことによって親は子プロセスの終了ステータスを取得した上で子プロセスの管理情報を削除する。親プロセスがwait()を呼ぶタイミングは、子プロセスを生成してすぐか、あるいは子プロセスから終了シグナルを受け取った時点になる。

・プロセスツリー
UNIX系OSでは、プログラムの実行は fork() → exec() を利用して、次々と子プロセスを生成していくことで行われる。一番最初のプロセスは、カーネル自身が init というプロセスを生成し、全てのプロセスの親となる。init を頂点に全てのプロセスがツリー状に親子関係でつながっている。ちなみに、子プロセスの終了前に親プロセスでが終了した場合、子プロセスの親は自動的に init になる。この場合の子プロセス終了時にwait()を呼ぶ処理は init が担当することになる。

・シェルはプログラム実行時に何をしているのか?
シェルは、ユーザからプログラム実行のコマンドを受け付けると、自分自身をfork()によって複製し、子プロセス側でexec()を呼び出して指定されたプログラムを実行する。あとは、wait() を呼んで子プロセスの終了を待つ(フォアグラウンドジョブ)。あるいは、wait()をすぐには呼ばず、にユーザからのコマンド受付状態に戻るようにも指定できる(バックグラウンドジョブ)。この場合終了した子プロセスに対して wait() を呼ぶのはシェルが適宜行ってくれると思う(詳しい方いたら教えてください)。

・シェルを経由するかしないかの違いとは
要するに fork() → exec() (+ wait()の待機) の一連の流れができれば、あるプログラムから別のプログラムを呼び出すことができる。この処理をシェルに任せるのが「シェル経由で呼び出す」方法であり、独自に行うのが「シェルを経由せずに呼び出す」方法だということ。

だいたいこんなもんかな。上に書いていない知識も色々整理できたが(親プロセスが異常終了した場合の子プロセス=ゾンビプロセスについてとか)、その辺は下の参考URLを参照。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

※参考URL
http://fumiyas.github.io/2013/12/21/dont-use-shell.sh-advent-calendar.html
http://itpro.nikkeibp.co.jp/article/COLUMN/20080502/300565/?ST=oss&P=1
http://www.gadgety.net/shin/tips/unix/ipc/fork.html
http://www.fireproject.jp/feature/c-language/process/fork-wait.html

2014年1月27日月曜日

統一性へのこだわり

Linuxディストリビューションのパッケージに付いてくる設定ファイルなんかでも、たまにインデントでスペースとタブが入り交じっているのを見かけたりする。

でもこれは僕としては相当気になる(笑)。だから自分でカスタマイズするときは直してしまう。タブをスペースに全部置き換えるとか。

あるいは、人から引き継いだプロジェクトでタブとスペースがごちゃまぜになってる時などは一括で置換してしまう。Git でソースコードを管理しているなら、空白を除いて比較するコマンド

git diff -w

とか使えるわけだから、間違って空白以外の部分をいじってしまった時はすぐ分かるし、リスクも特に高くない。

さて、上記はインデントの話だけれど、例えば if 文の後のカッコの置き方とか変数の名前の付け方とか、そういったものを統一させようがさせまいがプログラムとしては正しく動作するだろう。しかし細かい部分での統一性を維持しようとすることが、結局バグの発生率も下げるし、メンテナンス性のいいコードになると思うんだ。

統一性へのこだわりっていうのは一種の美意識って側面もあるから、プログラミングを扱うITエンジニアには美的センスも求められると僕は勝手に思っている。一つ経験則として言えるのは、ITエンジニアとして仕事がよくできる人は、殆ど例外なく美的センスを持っている人だったってことだ。

いかにもガチガチに論理的そうなイメージのITエンジニアだけど、実は美的センスも仕事の成果に結びつく。これだからITエンジニアは面白いんだ。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年1月26日日曜日

面倒さを乗り越える能力はエンジニアの強み

他の人が面倒で諦めてしまうことでも、一歩一歩進めていける能力があるエンジニアは非常に強いと思う。

これは最近仕事をしていてよく感じることなんだけれども、結局のところIT系のエンジニアの仕事というのは、細かな作業の大量集合体みたいな部分があり、普通に客観的に考えれば大変面倒だと思ってしまう要素を多分に含んでいる。

その面倒さをいかに乗り越えていけるか、という能力がエンジニアの強みの1つなんじゃないかと思うわけだ。

面倒さを乗り越える能力というのは、例えば論理的な解決の道筋を考えて頭の中で構成し、その頭の中の解決策にそって一歩一歩地道な作業を進めていける能力、という風に表現できる。

ではどうやったらその能力が身につくかというと、そういう面倒な状況から逃げずに正面からガッツリ戦うしかないんではないかと思う。もちろん最初は上手くいかず、人の助けを借りる必要もあるかもしれない。でもその時に人に頼り切るのではなくて、自分の可能な限り自分のできることを、面倒でも億劫でもやっていくしか、能力を身につける方法はないだろう。

だから、僕は非常に面倒そうな問題に直面した時に、自分を鍛える絶好の機会だと思って喜ぶことにしている。そう考えれば結構楽しめるものだ。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年1月25日土曜日

シェルスクリプトの全引数リスト $*, "$*", $@, "$@" の違い

シェルスクリプトで全引数リストをまとめて扱う変数は「$*」と「$@」がある。色々なサイトで解説があるが、自分の理解のため実際にスクリプトを書いて実験してみた。

用意したスクリプトはこちら:

test.sh
#!/bin/sh

print_arguments() {
  echo "\$1: $1"
  echo "\$2: $2"
  echo "\$3: $3"
  echo ""
}

test_pattern() {
  echo '$*'
  print_arguments $*

  echo '"$*"'
  print_arguments "$*"

  echo '$@'
  print_arguments $@

  echo '"$@"'
  print_arguments "$@"
}

echo 'arguments: "arg1 arg2" "arg3"'
echo ''
test_pattern "arg1 arg2" "arg3"

これを実行すると、次の結果が得られる。

[user@localhost]~% sh ./test.sh
arguments: "arg1 arg2" "arg3"

$*
$1: arg1
$2: arg2
$3: arg3

"$*"
$1: arg1 arg2 arg3
$2: 
$3: 

$@
$1: arg1
$2: arg2
$3: arg3

"$@"
$1: arg1 arg2
$2: arg3
$3: 

ダブルクオーテーションで囲わない場合、$*と$@は同じで、元のダブルクオーテーションは無視される。

ダブルクオーテーションで囲った場合、"$*"は元のダブルクオーテーションを無視して全部を一括りに囲ったように動作するけど、"$@"はもとのダブルクオーテーションと同じように各引数をダブルクオーテーションで括ったものとして動作する。

言葉に書いて文章にしてみれば他のサイトに書いてあることと結局同じだけれど、こういうのは自分で実験してみると理解が深まるね。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。