負け組アーキテクトの憂鬱

メモしておきたいことや読書の記録を淡々と書く。

linux

CBQ.initを使ってLinuxでお手軽QoS

ネットワーク帯域上で必要な帯域をあらかじめ確保したり、必要以上のトラフィックを制限することを引っくるめて一般にQoS(Quality of Service)などと言うが、LinuxでIPのトラフィックコントロール(帯域制御)をするには、iprouteとtcを使う。

しかしこの設定は柔軟ではあるものの、直感的ではなく分かりづらい。こういうモノが必要な場合の大半は、アドレス帯毎に制限したいとかポート毎に制限したいとか、比較的目的がはっきりしているため、出来ればザックリと簡単に設定したいものだ。

そこで、CBQ.initというtcのwrapperとして使いやすいscriptがある。CBQというのはClass-Based Queuingと言って、トラフィック(パケット)をクラスとして定義し、それぞれに帯域上限値を設定しておく事で、上限値を超えたパケットを破棄するという方式だ。単純なqueueの優先順位付けの帯域制限方式と異なり、いつまで経ってもパケットがスケジューリングされず、事実上の通信断になってしまう事態を防ぐ事が出来る。

このCBQ.initをCentOS4で試してみた(たぶんCentOS5でもredhatでも同じでしょう)。必要なモジュールは最小構成でも用意されていたので、単純にscriptを設置するだけで良い。

まずはscriptをダウンロードし、適当はdirectoryに置いておく。redhat系のchkconfig形式のscriptのため、/etc/init.d/に置くなりlinkするなりしておくと、サービスとして管理することができる。分かりやすくするため、/etc/init.d/cbqというファイル名で設置してみた。

# chkconfig --add cbq
# chkconfig --list | grep cbq
cbq             0:off   1:off   2:off   3:off   4:off   5:off   6:off

こんな感じで見えれば正常。

あとは、script中のコメントに従って設定ファイルを用意する。設定ファイルもredhat系のdistributionならばおなじみの形式だ。設定ファイルの置き場はデフォルトでは/etc/sysconfig/cbq/なので、特に問題無ければここにすると良い。当然CentOSの標準構成には無いので作っておく。

# mkdir /etc/sysconfig/cbq/

設定ファイルは帯域制御したいクラス毎に、このdirectoryにファイルを置く。ファイル名の命名規則は以下のとおり。

cbq-lt;clsidgt;.lt;namegt;
  • clsidは帯域制御のクラスIDで、他のクラス(要は他の設定ファイル名)と重複してはいけない。0002からFFFFまでの値を指定する。
  • nameは任意の文字列。分かりやすい名前にでもしておく。

たとえば/etc/sysconfig/cbq/cbq-5963.for_httpsなどというファイルを作る。内容の例はこんな感じ。

DEVICE=eth0,1000Mbit,100Mbit
RATE=5Mbit
WEIGHT=500kbit
RULE=:443,
  • DEVICEには対象とするinterface名と、対応する物理NICのリンク速度、ウェイト(通常はリンク速度の1/10)を指定する。
  • RATEにはこのクラスへの割り当て帯域を指定する。
  • WEIGHTはウェイトを指定する。通常はRATEの1/10。
  • RULEは、[[saddr[/prefix]][:port[/mask]],][daddr[/prefix]][:port[/mask]]この形式で指定する。カンマ区切りで前半がソースアドレスとポート、後半がディスティネーションだ。特に指定しない(anyを指定する)場合は省略可能。

これがもっとも基本的で単純な使い方だ。上記の例だと、1Gbpsのインターフェイスeth0を通るパケットに対し、ソースポートが443であれば何れのソースアドレスでも、何れのディスティネーションでも5Mbpsの帯域とする、と言う意味。Webサーバなどに設定して、httpsの下り帯域を制限しているわけだ。

単なるwrapperとはいえ、設定出来る内容は多岐にわたり、かなりきめ細かい設定も出来る。scriptのコメントがとてもよく纏まっているので、いろいろ試してみると良い(CBQ.initという名前だけど、CBQ以外の制御もできる)。

chkconfigでサービスに登録してあれば、動作させるには

# service cbq start

だけで良い。動作状況の確認は以下のコマンドでできる。

# service cbq stats(帯域制御の状況を表示する)
# service cbq list(制御対象のリストを表示する)
# tc -s class ls dev eth0(tcで見る場合にはこんな感じ watchコマンドの引数にすると便利)

動作に問題が無ければ、chkconfigコマンドでonにしておけば、毎回OSのboot時に設定される。chkconfigを使わない場合は、そのままscriptの実行し、引数にstart,stop,stats,list等を指定すれば同じ結果になる。

NPTLが標準の2.6系kernelでLinuxThreadsを使う

Linux kernelには、FreeBSDのKSEや、NetBSDやSolarisのLWPのような、プロセスよりも細かいコンテキストスイッチの単位がない。あくまでプロセスがコンテキストスイッチの最小の単位で、スレッドはfork()のスーパーセットであるclone()というLinux独自のシステムコールで作成されたプロセスとして動作する。

要はメモリリソースを親プロセスと共有したまま(子プロセス用のメモリ空間をコピーせずに)作られたプロセスなのだが、実際のコンテキストスイッチの際には、メモリリソースの切り替えが発生しないので、通常のプロセスコンテキストの切り替えよりも理屈の上では速いはずだ。結果的にLWPと似たような位置付けになっている。

kernelのプロセスコンテキストに対して、どのようにスレッドを割り当てるかはglibc(libc,libthread)がやっていて、kernel2.4系の頃はLinuxThreadsという方式で行っていた。スレッドにはそれぞれ別のpid(プロセスid)が払い出され、それぞれのプロセス(スレッド)は別のマネージャスレッド(プロセス)が管理する。この方式だとスレッド間の(実際にはプロセス間の)シグナル配送などに問題が出ることがある(ややこしいなあ、プロセスとスレッドが一緒だと)。また、pidも大量に消費する傾向があり、大量のスレッドを作るプロセスが大量に起動したりするとpidを使い果たしてしまう可能性もある。一方で、kernel側に特に修正は必要なくて、kernelはプロセスとして見えているものだけをコンテキストスイッチの対象にすればよいので動作はシンプルだ。FreeBSDでもrfork()をclone()の代わりに使ったLinuxThreads互換の実装があったりなんかして、kseやuleスケジューラが安定する前にはよく使われたりした。

2.6系のkernelではLinuxThreadsに変わってNPTL(Native POSIX Thread Library)が実装されている。kernel、glibc両方に変更が加わっていて、LinuxThreadsよりもスレッドの生成がやたら速いらしい。スレッドの生成には相変わらずclone()を使うことや、kernelから見た際のコンテキストスイッチの単位はプロセスであることなどは変わらないが、見た目の大きな変化として、一つのプロセスには一つのpidがひも尽くこと(スレッド毎にpidが変わらない)、スレッドidが巨大な数字を扱えることなどの違いがある。またPOSIX Thread(標準的なスレッド実装)への準拠度合いも高い。逆に言うとLinuxThreadsはPOSIX Threadと非互換な部分が多いと言える。

不幸にもLinuxThreadsのPOSIX非互換部分に依存したプログラムがあった場合、2.4系では正常だったのに2.6系で予期しない動作になるという、悲しい結果になる場合がある。そんなときのために、2.6系kernelでもLinuxThreadsを実行すればよい(LinuxThreadsにはkernelに特殊な仕掛けが必要ないので、glibcさえ用意すれば問題ない)。

ライブラリの切り替えは一見、再コンパイルが必要なように感じるが、libpthreadとlibcが動的リンクされていれば、ldがLinuxThrads版か、標準のNPTL版を選択してリンクしてくれる仕組みなので、コンパイル済みバイナリでも起動時に切り替えることができる。

具体的には環境変数 LD_ASSUME_KERNEL にkernelバージョンを入れると良い。環境変数が定義されていなければデフォルトのNPTL版glibcがリンクされ、2.4.19などとすればLinuxThreads版のglibcがリンクされる。

この仕組み自体は、shared libraryが必要とするkernelバージョンに対して、現在の実kernelバージョンを上書きするためのものだ。環境変数 LD_ASSUME_KERNEL に対象のkernelバージョンを入れると、ldが実行時にそのkernelバージョンに応じたshared libraryをリンクするという寸法(そのバージョンより大きいバージョンを必要とするshared libraryをリンクしない)。NPTL版glibcはkernelバージョン2.4.20に依存しているため、LD_ASSUME_KERNELにそれ未満の数字が入っていると、そのバージョンでリンク可能なglibc(LinuxThreads版)がリンクされるためこんな挙動になる。

ちなみに動作はCentOS4系で確認した。CentOS5系の最小構成インストールだと、LinuxThreads互換のglibcはインストールされないようだ。

スレッド実装の確認のために以下のコマンドが用意されている。

% getconf GNU_LIBPTHREAD_VERSION

以下、実際の動作。動作確認のプログラムは小俣光之さんの所からいただきました。

% getconf GNU_LIBPTHREAD_VERSION
NPTL 2.3.4
% ldd ./mt
        libpthread.so.0 => /lib/tls/libpthread.so.0 (0x00504000)
        libc.so.6 => /lib/tls/libc.so.6 (0x00353000)
        /lib/ld-linux.so.2 (0x00339000)
% ./mt
[2758]start
[2758]thread_id1=-1208775776
[2758]thread_id2=-1219265632
[2758][-1208775776]0
[2758][-1219265632]0
[2758][-1208775776]1
[2758][-1219265632]1
[2758][-1208775776]2
[2758][-1219265632]2
[2758][-1208775776]3
[2758][-1219265632]3
[2758][-1208775776]4
[2758][-1219265632]4
[2758][-1208775776]5
[2758][-1219265632]5
[2758][-1208775776]6
[2758][-1219265632]6
[2758][-1208775776]7
[2758][-1219265632]7
[2758][-1208775776]8
[2758][-1219265632]8
[2758][-1208775776]9
[2758][-1219265632]9
[2758]thread_id1 = -1208775776 end
[2758]thread_id2 = -1219265632 end
[2758]end
zsh: exit 10    ./mt
%

これが通常のNPTLの動作結果。それぞれ別々のスレッドidが払い出されているが、pidはどちらも2758で共通。

一方LinuxThreadsだと、

% export LD_ASSUME_KERNEL=2.4.19
% getconf GNU_LIBPTHREAD_VERSION
linuxthreads-0.10
※LinuxThreadsになった
% ldd ./mt
        libpthread.so.0 => /lib/i686/libpthread.so.0 (0x0069d000)
        libc.so.6 => /lib/i686/libc.so.6 (0x00ae9000)
        /lib/ld-linux.so.2 (0x00339000)
※/lib/tls/以下のshared libraryが/lib/i686/以下のものに置き換わってリンクされる
% ./mt
[2854]start
[2854]thread_id1=16386
[2854]thread_id2=32771
[2856][16386]0
[2857][32771]0
[2856][16386]1
[2857][32771]1
[2856][16386]2
[2857][32771]2
[2856][16386]3
[2857][32771]3
[2856][16386]4
[2857][32771]4
[2856][16386]5
[2857][32771]5
[2856][16386]6
[2857][32771]6
[2856][16386]7
[2857][32771]7
[2856][16386]8
[2857][32771]8
[2856][16386]9
[2857][32771]9
[2854]thread_id1 = 16386 end
[2854]thread_id2 = 32771 end
[2854]end
zsh: exit 10    ./mt
%
※スレッドidも別ならば、それぞれのスレッドのpidも別。スレッドidの採番ルールも明らかに違う。

というわけで、2.6系kernel上でもLinuxThreadsを使うことはできた。2.6系kernelではスケジューラなども違うので、細かい挙動そのものが完全に2.4系と一緒になるわけではないが、LinuxThreadsに依存したバイナリを実行するには十分だろう。今回は性能のベンチマークは取っていないけれども、LinuxThreadsがNPTLより速い理由もないので、性能上の理由でLinuxThreaadsを使うこともないと思う。

詳しくはpthreads(7)を。

scsiバスのリスキャン方法メモ

※ベンダ提供の専用のコマンドが用意されてるときにはそっち使った方が確実

一般的なLinuxの一般的なscsiデバイスの場合
# echo "scsi add-single-device 0 0 1 0" > /proc/scsi/scsi
※rescanというか、targetの認識のためにやる。引数の数字はそれぞれ、host bus target(id) lun

Solarisの場合
# drvconfig

SPARC機のokプロンプトの場合
ok probe-scsi または
ok probe-scsi-all

VMware ESXのservice consoleの場合
# esxcfg-rescan [vmhbaのデバイス名]

Debian GNU/Linuxのアップデートをproxy経由で行いたい

これからお仕事でDebian GNU/Linuxをたくさん使うことになりそう。まずはアップデートの方法くらい知らないとね、ってことでちょっと調べてみた。

自分自身がインターネットに到達できるなら問題ないけど、直接外に出れないセグメントに繋がってる場合、とりあえず安直にproxyで何とかできないかと模索。apt-getコマンドでアップデートする場合のproxyの指定方法はだいたい以下の2種類のようだ。

  • 環境変数http_proxy(必要によってftp_proxyも)を指定
  • /etc/apt/apt.confで設定
  Acquire {
     http {
        Proxy "http://proxy:8080";;
     };
     ftp {
        Proxy "http://proxy:8080";;
     };
  }

いろいろ例外サイトとか指定できるみたい。

あとはsquidなりapacheなりでproxyのキャッシュサーバでも作ってあげれば大丈夫。

今日も眠いのでこんだけ。無念。