2015/12/23

FreeRADIUS を使って複数のインターフェースで DHCP サーバを動かす

FreeRADIUS を使って DHCP サーバを提供している時に複数の interface を使って DHCP を提供する必要があったのでその方法とか。

前回の記事にも書いたのですが、FreeRADIUS で DHCPサービスを提供する場合は
/etc/raddb/sites-available にあるサービスを /etc/raddb/sites-enabled にリンクを貼ることで有効化する様子。

ということでまずは /etc/raddb/sites-available にある dhcp をコピーして listen する interface を変更。
そのリンクを /etc/raddb/sites-enabled に貼ると複数のインターフェースにも対応できます。

例えば /etc/raddb/sites-available/dhcp.private と /etc/raddb/sites-available/dhcp.global みたいな感じ。


これで個別の interface ごとに違う設定を適用した複数の DHCP サーバを提供することできます。

2015/12/06

Homebrew で debug symbols を埋め込んだ LLVM の bottle を作る

Homebrew の bottle を使って debug information が埋め込まれた LLVM を配ってみんなで読もう、ということになったのでそのログ。


環境

  • OSX Yosemite 10.10.5
  • Homebrew 0.9.5
  • LLVM 3.8
  • あと自分のマシンじゃないのでチェックしてないけれど El Capitan (10.11.1?)


Release Build の LLVM の bottle 化

私が所属している研究室では Continuation Based C (CbC) という言語を開発していて、LLVM implemented 版があるのでまずはそいつで bottle で作れないかをチェック。
formula は github の ie-developers/ie/cbc にあります。

普通に build した LLVM を bottle にするのは結構簡単で、
  • brew install --build-bottle ie-developers/ie/cbc
  • brew bottle cbc
とか。 これで build された binary を固めたやつが current directory にできます。
ついでにこうやって書いてね、って出力が sha256 と共に出てくるのでそいつを formula に追記して、 bottle を公開用サーバか何かに置けばOK.


LLVM 3.8 の build を bottle 化

いつからか LLVM は build を repository の top でやると怒るようになってます。
具体的には ./configure をすると怒ってくる。
そいつを回避するために Homebrew 公式の llvm は mktemp を使っているようです。
なので mktemp を使ってやれば最近の LLVM も bottle 化することができます。


LLVM 3.8 を debug information 付きで build して bottle 化

ひとまず LLVM 3.8 を debug information 付きで build する手順としては

  • git clone http://llvm.org/git/llvm
  • mkdir llvm_build
  • cd llvm_build
  • ../llvm/configure --enable-debug-runtime --enable-debug-symbols --disable-optimized --enable-assertions
  • make -j 3
とか。
Debug+Assets って directory に bin と lib ができるのでそいつで追えます。
具体的には
  • lldb Debug+Assets/bin/clang
とかできます。


LLVM 3.8 を debug information 付きで build

Homebrew は build する時のディレクトリは build 時に生成して install 後に捨ててしまうっぽいです。
なので build 場所を変えてバイナリを残すように。
formula の prefix ってメソッドがインストール先のディレクトリを返してくれるので、その下で build しちゃいます。(ちなみに prefix の値は "/usr/local/Cellar/llvm_original/llvm3.8/" とか。)

インストール先のディレクトリで直接 build するれば、Debug+Assets が bottle に入るんじゃないかって魂胆。
あと、 Homebrew は make する時に CFLAGS とかを変更しちゃう superenv ってのがあるらしく、そいつは勝手に -g とかを落とすっぽいです。
なので build する時に --env=std とかすると良いらしい

ってなわけで
  • brew install --env=std --build-bottle ie-developers/ie/llvm_original
  • brew bottle
とかすると OK。
ちなみに圧縮前は8.9Gで圧縮後は2.1Gほど。
あと LLVM はとりあえず make するとこで止めてます。ファイル多くなっちゃいますし。


bottle を展開して lldb で追う

あとは落として追えば良いです。
が、 brew が build 時に buildpath を /private/tmp に作っちゃうのでそのパスを解決してやれば良い。

とか。

これで
  • lldb /usr/local/Cellar/llvm_original/llvm3.8/build/Debug+Asserts/bin/clang
  • l main
とかしてソースが表示されます。やったね。
ちなみにこの path は Yosemite の場合なので El Capitan の path はまた別です。
lldb が l main して時にファイルが無いって文句言うはずなのでそれを解決できように symlink 貼れば良いはず。


よもやまばなし

以下適当に本筋と関係無い話。

Cmake で build

Brew 公式の lldb は cmake で make してるっぽいです。
formula の中では std_cmake_args ってものを持ってるらしく、そいつが  -DCMAKE_BUILD_TYPES=Release を突っこむっぽいです。
なので CMAKE_BUILD_TYPES=Debug とか入れても弾かれる。
RelWithDebugInfo とかもあるのでそいつでもいけるかも?

superenv を見つけた話

どうも -g が有効じゃないっぽいのでログを漁ってたら
  • superenv removed:  -g -Wcast-qual -m64 -Wall -W -Wwrite-strings

とかあったので調べてみました。ログつよい。
ちなみにログの場所は
  • less ~/Library/Logs/Homebrew/llvm_original/02.make.cc
とかです。
superenv は -g を落として -O2 とかを入れてくれるっぽいので brew は Debug Build は基本入れない方針っぽいです。
もちろん --env=std で回避手段があるのは流石ですが。

Mach-O

ちなみに -g が効かないのを探すために Mach-O の仕様とか dSYM とか DWARF とか symbols コマンドとか dsymutil とか調べたりしてました。
その辺の hello.c とかコンパイルすると dSYM が作られて、そいつを消すと追えなかったのでこの辺が問題なのかなー、とか思ってゴニョゴニョしてました。
結果的にはハズレか。

make install してない

どうせ Debug+Assets が残ってないと追えないので make install はしてません。
なので brew link とかは効かないです。
make install した clang も Debug+Assets の debug symbols を見にいくはずなので、二重に clang 作るのは容量的に勿体ないかな、と。


参考文献とか

2015/11/18

FreeRADIUS 3.0.4 で DHCP サーバを立てる時に気をつけること

FreeRADIUS 3.0.4 を使って DHCP を使っているとたまに SEGV することがあったのでその原因を探ってみたメモ。
なんとなーく高負荷時くらいに発生するようですが、負荷が低い時にも発生するので core dump を取ってきちんと追ってみました。


環境

  • OS : CentOS 7.1.1503
  • FreeRADIUS : 3.0.4


abrtd の設定

SEGV だけじゃ原因が分からないのでまずは coredump を取るように設定。
abrt なるものがあるらしいので使ってみる
  • # yum install -y abrt-cli
  • # vim /etc/security/limits.d/core.conf
    • *       hard        core        unlimited
    • *       soft        core        unlimited
    • くらいに設定。 core のファイルを作るように。
  • # vim /etc/sysctl.d/50-core.conf
    • kernel.core_uses_pid = 1
    • fs.suid_dumpable = 2
    • Storage = both
    • くらいで。
  • # vim /etc/sytemd/system.conf
    • DumpCore を yes,  DefaultLimitCORE を unlimited に
  • # systemctl enable abrtd
  • # reboot

これで core が取れるようになった。
coredump の場所は abrt が設定するようで /var/spool/abrt/ くらい。


coredump から原因を追う

  • # gdb /var/spool/abrt/ccpp-2015-11-04-09\:39\:18-2478/coredump
    • すると debuginfo が無いので入れろと言ってくる
  • # yum --enablerepo='*debug*' install /usr/lib/debug/.build-id/9c/9089cc84962eae11b31e65e92af30874f83b2c
    • 言われた通りに打つと入る。便利。
どうやら
  • #0  fr_packet_cmp (a=0x7f068be885a0, b=0xcb6b5c9a1101f601) at src/lib/packet.c:45
とかで SEGV している様子。

back trace は

  • #0  fr_packet_cmp (a=0x7f068be885a0, b=0xcb6b5c9a1101f601) at src/lib/packet.c:45
  • #1  0x00007f06894c9eea in rbtree_find (tree=0x7f068bed05a0, data=data@entry=0x7fff68b1cdb8) at src/lib/rbtree.c:517
  • #2  0x00007f06894d0f8f in fr_packet_list_yank (pl=0x7f068bed0fc0, request=0x7f068be885a0) at src/lib/packet.c:559
  • #3  0x00007f0689b5e1ff in request_done (request=request@entry=0x7f068be3d070, action=action@entry=2) at src/main/process.c:636
  • #4  0x00007f0689b5e6ad in request_process_timer (request=0x7f068be3d070) at src/main/process.c:908
  • #5  0x00007f0689b624bd in request_common (request=request@entry=0x7f068be3d070, action=<optimized out>) at src/main/process.c:1122
  • #6  0x00007f0689b632a5 in request_cleanup_delay (request=0x7f068be3d070, action=<optimized out>) at src/main/process.c:1176
  • #7  0x00007f06894d1a1f in fr_event_run (el=el@entry=0x7f068bbb4f00, when=when@entry=0x7fff68b1d040) at src/lib/event.c:260
  • #8  0x00007f06894d1fd9 in fr_event_loop (el=0x7f068bbb4f00) at src/lib/event.c:482
  • #9  0x00007f0689b63d61 in radius_event_process () at src/main/process.c:4983
  • #10 0x00007f0689b42758 in main (argc=3, argv=<optimized out>) at src/main/radiusd.c:584

とか。

request の管理は rbtree でやっていて、その rbtree で find や insert する時の比較ルーチンで落ちてるみたいです。
他の dump もいくつか見るとやっぱり rbtree 周り。
request_done すると request_hash から外す時に rbtree_insert とかするっぽいですね。
コードを読んでいくと nodup なるものが true ならそのルーチンは呼ばれないみたいです

git grep nodup すると読んだ時の最新の 3.0.9 では DHCP は default で nodup が true の様子
そして CentOS7 の yum で入る 3.0.4 では nodup は true では無いみたいです。

ということで
/etc/raddb/sites-enabled/dhcp
performance { skip_duplicate_checks = yes }
を追加しておしまい。しばらく運用しても SEGV することはなくなりました。


再現してみる

とりあえず運用している radiusd は落ちなくなったのですが原因が本当にコレかチェックしてみます。


再現環境

  • OS : OSX Yosemite 10.10.5
  • Docker : 1.9
  • docker-machine : 0.5.0
  • Virtualbox : 5.0.10 r104061


再現方法

前回作った FreeRADIUS のサーバで試してみます。
Docker で container を上げると '02:42:ac:11:00:??' の mac address が自動生成されるようなのでそれに対して適当にダミーのデータを投入します
  • 256.times{|n| IpAddress.create!(address:n, mac_address: format('02:42:ac:11:00:%02x', n))}
この状態で dhclient だけが入っている container を100個くらい起動。
そうすると SEGV しました。数度再現するのでこれが原因っぽいです。
そして nodup を設定してまた負荷をかけてみます。
今度は SEGV しません。原因はやっぱりこれのようですね。


まとめ

FreeRADIUS 3.0.4 で DHCP サーバを上げる場合は nodup の設定をしておきましょう。
CentOS 7 の yum で入る FreeRADIUS は 3.0.4 です。
また、FreeRADIUS 3.0.8 ではデフォルトで有効なようなので、この設定はしばらくすると必要無くなります。


参考URL

2015/11/11

CentOS7 で HACluster を組んで GFS2 を mount したりノードを追加したりする

CentOS7 のマシン4台を使って iscsi + clvmd + gfs2 の HA Cluster を組んだログ。


環境

  • CentOS :  7.1.1503
  • Kernel : 3.10.0-229.20.1.el7
  • pcs : 0.9.137
  • dlm_controld : 4.0.2
  • clvmd : 2.02.115(2)-RHEL7 (2015-01-28)
  • iscsi-initiator-utils : 6.2.0.873-28


設定

  • ホスト名 : aquamarine, heliodor, malachite, topaz
  • クラスタ名 : sim
  • iscsi のデバイス : /dev/disck/by-id/dm-name-hoge
  • gfs2のストレージ名 : base
  • マウントポイント : /mnt/base


各ノードから iscsi でディスクへと接続する

iscsi 側の設定は省略します。
ディスクのフォーマットと各ノードのIPへのアクセス制限とマルチアクセスは許可されているものとします。
全ノードで
  • # yum install -y iscsi-initiator-utils gfs2-utils lvm2-cluster
  • # iscsiadm -m discovery -t sendtargets -p <iscsi-ip>
  • # iscsiadm -m node --login
してディスクを /dev/disk/by-id/dm-name-hoge として認識させます。
あと lvm のロックの設定を変更して gfs2 を使えるようにします。
  • # lvmconf --enable-cluster
これは設定の反映に再起動が必要なので reboot します。
/etc/lvm/lvm.conf の locking_type が 3 になっていれば OK です。


2ノードで HA Cluster を組む

ディスクの準備ができたところで、ここを参考に 2ノードで HA Cluster を組みます。
組むノードは aquamarine と heliodor 。
それぞれ /etc/hostname にホスト名の設定と、 /etc/hosts で相互にアドレス解決ができるようにしておきます。

## は全ノードで実行するコマンド、 a# は aquamarine で、 h# は heliodor で実行するコマンドです。

  • ## yum install -y pcs fence-agents-all
    • クラスタ管理用の pcs と各ノードの死活監視をする fence の agent を入れます
  • ## firewall-cmd --permanent --add-service=high-availability
  • ## firewall-cmd --add-service=high-availability
    •  firewalld で HA Cluster 用の通信を許可します
  • ## passwd hacluster
    • pcs でクラスタを管理する際に使用するユーザ hacluster のパスワードを設定します
    • pcs をインストールすると自動で追加されます
    • 全ノードで同じパスワードを設定しておきます
  • ## systemctl start pcsd
  • ## systemctl enable pcsd
    • pcsd の起動と自動起動を有効化しておきます
  • ## systemctl enable corosync
    • cluster の設定を同期する corosync の自動起動を有効化しておきます
  • ## systemctl enable pacemaker
    • cluster の各ノードをハートビートで監視をする pacemaker の自動起動を有効化しておきます
  • a# pcs cluster auth aquamarine heliodor -u hacluster
    • クラスタのノード間で認証します
    • パスワードは先程設定したパスワードです
    • 認証しておくと、1ノードだけの作業でクラスタ内で共有リソースを管理できます
    • なのでここからは aquamarine で作業していきます
  • a# pcs cluster setup --start --name sim aquamarine heliodor
    • クラスタ sim を作成します
  • a# pcs cluster start --all
    • 2ノードでクラスタをスタートします
    • # pcs status で状態が確認できます
    • 2ノードがオンラインになっていればOKです


2ノードのクラスタで GFS2 をフォーマットしてマウントする

ここ と ここを参考に GFS2 を構築します。
ディスクのパーティション設定は既に終わっているものとします。

  • a# pcs stonith create scsi-shooter fence_scsi devices=/dev/disk/by-id/dm-name-hoge pcmk_host_list='aquamarine heliodor'  meta provides=unfencing
  • a# pcs property set no-quorum-policy=freeze
    • scsi 用の fence デバイスを作成します
    • fence デバイスの名前は scsi-shooter です
    • fence は各ノードの死活監視をするものです
    • stonith はノードに異常を検出した場合にそのノードを遮断する機構のようです
    • 具体的には pacemaker で行なわれるようで、 systemctl status pacemaker で確認できます
    • pcmk_host_list はこのデバイスが監視できるノードのリストです
      • 今回は aquamarine と heliodor を監視します
    • この状態で pcs status をすると stonith が追加されているはずです
  • a# pcs resource create dlm ocf:pacemaker:controld op monitor interval=30s on-fail=fence clone interleave=true ordered=true
    • 各ノードが GFS2 にアクセスした際の排他制御をする dlm を resource として追加します
  • a# pcs resource create clvmd ocf:heartbeat:clvm op monitor interval=30s on-fail=fence clone interleave=true ordered=true
    • cluster 上で同じ logical volume を扱うために clvmd を resource として追加します
  • a# pcs constraint order start dlm-clone then clvmd-clone
    • dlm が起動してから clvmd が起動するように順序を設定します
  • a# pcs constraint colocation add clvmd-clone with dlm-clone
    • dlm と clvmd が同じ時に起動することを許可します
  • a# mkfs.gfs2 -p lock_dlm -t sim:base -j 2 /dev/mapper/hoge
    • journal 2 つで /dev/mapper/hoge を gfs2 でフォーマットします
    • lock は dlm を使います
  • a# pcs resource create fs_gfs2 Filesystem device="/dev/mapper/hoge" directory="/mnt/base" fstype="gfs2" options="noatime,nodiratime" op monitor interval=10s on-fail=fence clone interleave=true
    • gfs2 を resource として追加します
    • /mnt/base に mount するようにしています
  • a# pcs constraint order start clvmd-clone then fs_gfs2-clone
  • a# pcs constraint colocation add fs_gfs2-clone with clvmd-clone
    • gfs2 が clvmd よりも先に起動しないようにします
これで各ノードから gfs2 が見えるようになりました。
aquamarine でしか作業をしていませんが heliodor でも mount されています。
pcs status で各ノードと stonith と resource の状態を確認できます。
また、 pcs resource cleanup をすると stonith/resource の状態を更新できます。


クラスタのノード数を2から3にする

クラスタに topaz を追加します。
topaz での作業は t# と書きます。
  • a# pcs stonith update scsi-shooter pcmk_host_list='aquamarine heliodor malachite topaz' devices=/dev/disk/by-id/dm-name-hoge meta provides=unfencing
    • fence を許可するノードに topaz を追加します
      • おまけで malachite も追加します
    • この時に一時的に gfs2 が umount されます
    • どうやら fence device を追加したり update したりすると依存してるものが停止するようです
  • a# pcs cluster auth aquamarine heliodor topaz -u hacluster
    • ノードを追加するために topaz も auth します
  • a# pcs cluster node add topaz
    • topaz をクラスタに追加します
    • corosync が実行されてクラスタの設定が topaz に反映されます
    • この段階では pcs で Online になりますが、クラスタを topaz で起動していないので resource などは topaz で利用できません
  • a# gfs2_jadd -j1 /mnt/base
    • gfs2 に journal を追加します
    • journal は mount するノードの台数分だけ必要なので+1します
  • t# pcs cluster start
    • topaz で cluster を起動します
    • 自動で dlm, clvmd が起動して gfs2 も mount されます


クラスタのノード数を3から4にする

malachite もクラスタに追加します。
基本的にやることは同じで
  • a# pcs cluster auth aquamarine heliodor topaz malachite -u hacluster
    • クラスタに認証する
  • a# pcs cluster node add malachite
    • クラスタに追加する
  • a# gfs2_jadd -j1 /mnt/base
    • journal を追加する
  • a# pcs cluster start --all
    • クラスタを起動する
の手順です。
今回は起動を aquamarine でやっています。
加えて、 fence の pcmk_host_list に事前に malachite を追加していたので、gfs2が停止することはありません。


クラスタのノード数を4から3にする

クラスタからノードを外してみます
  • a# pcs cluster node remove topaz
    • topaz を外します
    • この段階で resource が topaz で利用できなくなります
    • topaz には cluster の設定も無くなります
topaz を除く他のノードは gfs2 をそのまま使えます。
3から2にすることも同じように可能です。


まとめ

4ノードで HA Cluster が組めました。
fence の pcmk_host_list の変更さえ気を付ければ gfs2 を mount したままノード数を減らしたり増やしたりできます。
以下 tips とか。


TIPS: gfs2 を fsck する

node が kernel panic などをおこして正常に終了しなかった場合など、 gfs2 に整合性の取れない書き込みなどが残る場合があります。
fsck でその部分を修復します。

  • a# pcs resource disable fs_gfs2-clone
    • fsck するために gfs2 を全ノードから umount します
  • a# fsck.gfs2 -y /dev/mapper/hoge
    • fsck します。
    • だいぶ酷い時は yes/no を聞いてくる回数が多いので -y 推奨です
    • 修復する時もありますが数時間で終わらない時もあります……
    • もちろんディスクのサイズとデータのサイズによります
  • a# pcs resource enable fs_gfs2-clone
    • fsck が終わったので全ノードで mount します


TIPS: 一部ノードだけ gfs2 を利用できなくする

一部ノードだけ gfs2 を umount する場合は
  • a# pcs resource ban heliodor
とかします。もう一度 mount する時は
  • a# pcs resource clear heliodor
とかです。


TIPS: Resource を他ノードに移動させる

stonith デバイスを明示的に特定のノードに移動させる場合は
  • a# pcs resource move scsi-shooter malachite
とかでできます。


TIPS: その他ちょっとしたこと

  • auth に使用するのは hostname の方が良いようです
  • resource の dlm と、 systemctl で見える dlm は別ものみたいです。
    • systemctl status dlm して inactive でもきちんと ps にはいます
  • 同じように、 clvmd は systemctl から見えないですけれど pcs で動いてる場合にはきちんと ps にいます。
  • ちなみに各ノードを reboot とかかけてもきちんと umonut して mount してくれます。
    • たまに終了が長かったりしますが
  • あと network の restart を mount したままやると dlm が大変なことになるのでやめた方が良いです。(これのせいで fsck のお世話になりました)


参考文献

2015/10/19

FreeRADIUS を使って DHCP サーバを立てて Rails から管理してみる

デバイスごとに静的なIPを振ってそれをDHCPで配ろう、ということに。
デバイスは多々ありそうなので静的IPの登録を Rails 経由でやってみる。
FreeRADIUS を使うと DHCP の静的IPの設定を DB 側に保存できるらしいので使ってみた。

ということでサンプル的なアプリケーションを書いてみた。(https://github.com/atton-/freeradius_dhcp_with_rails)
IP の最後のオクテットと、デバイスの MacAddress を入力すると、DHCP サーバから IP を取得できます。


環境

  • OSX Yosemite 10.10.5
  • Ruby 2.2.3
  • Rails 4.2.4
  • Docker 1.8.1
  • FreeRADIUS 3.0.4
  • PostgreSQL 9.4.5


使いかた

まず Docker で PostgreSQL と FreeRADIUS 用のコンテナを起動。
FreeRADIUS 用のコンテナに直接入って、起動しているコンテナの情報に合わせて /etc/raddb/sites-enabled/dhcp.relay を書き換えてから radiusd を起動。
Rails 側でクライアントとなるデバイスの Mac Address と IP を登録。
そうすると DHCP で特定の Mac Address 宛に Static な IP が振られます。


解説とか

まず DHCP の設定は このページを参考に FreeRADIUS の設定。
基本的な設定はデフォルトのままでもOKです。
ちょっと問題なのが mods-config/sql/main/postgresql/queries.conf の設定。
元々のサンプルは /etc/raddb/mods-config/sql/main/postgresql/setup.sql や /etc/raddb/mods-config/sql/main/postgresql/schema.sql を利用しているのですが、これで使っている attribute という column name が ActiveRecord 的にアウト。
なので適当に名前を変えます。

今回使うテーブルは radcheck と radreply 。こいつの構造を保ったまま名前を変えます。
元の schema は username, attribute, value, op の 4 column + id。
username が mac address に相当して, value, op, attribute は config の key, op, value に対応してるっぽいです。
例えば attribute = 'DHCP-Your-Address', op = '=', value = '192.168.100.1' とか。
元は DHCP-Your-Address = 192.168.100.1 みたいに書いてたんだろうなー、とか想像したり。なのでいろんな情報とか突っこめそう。
それはさておき column の name を以下に変更。 op と value は変更無し。
  • username => mac_address
  • attribute => radius_attribute
対応するように queries.conf の query を書き換えます
あとは Rails 側で radcheck 相当のモデル radius_check_information を作って、  mods-available/sql で参照している radcheck を radius_check_informations に変更
同じように radreply もやってしまいます。
これで DHCP の Static IP の情報を DB に突っこむのはできました。

あとは rails 側でよしなにフォーマットに合うようにレコードを追加してあげたらOK。


引っかかったところ

Docker 側で dhcp とかを扱おうとすると arp table とかをいじろうとしてコケることが。
その場合は docker run する時に --privileged してやると良いみたいです。
あと Docker だと FreeRADIUS の実行ユーザとグループを root にしておかないと dhcp の返事ができないっぽいです。


具体的なコマンドとか

Docker で起動時の IP を使って FreeRADIUS の設定とかをしなかったので、ある程度手動で設定する必要があります。
なので具体的なコマンドとかを載せておこうと思います。

  • $ git clone https://github.com/atton-/freeradius_dhcp_with_rails
  • $ cd freeradius_dhcp_with_rails
  • $ docker build -t pg94 docker/postgres
  • $ docker run -itd --name postgres-server -p 5432:5432 -e POSTGRES_PASSWORD=hogehoge pg94
  • $ docker build -t freeradius_with_rails/freeradius-dhcp docker/freeradius-dhcp
  • $ docker run --privileged --link postgres-server:postgres-server --name dhcp-server -it freeradius_with_rails/freeradius-dhcp
    • これで bash が起動します。
    • ip a してその IP Address を確認。 (ex: 172.17.0.50)
    •  ip r してクライアントの Router を確認 (ex: 172.17.42.1)
      • 今回はクライアントも Docker で上げてみます。
      • なので Router は Docker Private Network の gateway です
    • /etc/raddb/sites-enabled/dhcp.relay を編集
      • DHCP-Domain-Name-Server に DNS を設定します (ex: 8.8.8.8)
      • DHCP-DHCP-Server-Identifier に DHCP サーバの IP を設定します (ex: 172.17.0.50)
      • DHCP-Router-Address に DHCP クライアントのゲートウェイのIP を設定します (ex: 172.17.42.1)
      • DHCP-Subnet-Mask に DHCP クライアントのネットワークのサブネットマスクを設定します (ex: 255.255.0.0)
    • radiusd -X
      • で FreeRADIUS を起動します
  • $ docker run --privileged -it centos bash
    • DHCP クライアント相当の Docker container です
    • $ yum install -y dhclient
    • $ ip addr
      • とかして mac address を確認します (ex: 02:42:ac:11:00:37)
  • $ rake db:create db:migrate
  • $ rails server
    • mac address と IP の値を入れます。 (ex: 02:42:ac:11:00:37, 100)
      • 今回は 172.17.0.0/16 のIPを振ります。
  • $ dhclient -d
    • docker run --privileged -it centos bash のコンテナで実行します
    • 172.17.0.100 が振られます。やったね。

まとめ

これでデバイスごとに静的IPを触れるDHCPサーバを Rails で管理できました。
前の記事にも書いた KVM を Ruby から触る機能登録したIPに対応するDNSとかと連携すると、IPを申請したら静的にIPが振られてドメインが登録されたり、VMを template から clone してそのIPとドメインが自動申請されるようなシステムが作れます。やったね。


参考

2015/10/14

fog-libvirt を使って Ruby から KVM を扱う

Ruby から KVM を扱ってみよう、ということで fog-libvirt を使ってみた話。

もともと fog という gem があって、OpenStack とか AWS とかを Ruby で扱うような gem みたいです。
それの libvirt 版が fog-libvirt 。
他にも ruby-libvirt とかあるのですが fog の方が扱いやすかったし、結局内部では ruby-libvirt を呼んでいるようです。


libvirt への remote access の設定

まずは KVM host 側の設定をします。
virsh の remote access の機能を使って fog-libvirt はアクセスするようなので、remote access を許可するように設定してやらないといけません。
具体的な設定方法は参考URLを参照してください。

remote access が何かと言えば、 virsh でやる時に
  • $ virsh -c 'qemu+tcp://hoge.exmaple.com/system'
とか書くやつです。

それが fog-libvirt を使うと、sasl での認証だと

  • Fog::Compute.new(provider: 'libvirt', libvirt_uri: 'qemu+tcp://user@hoge.example.com/system', libvirt_username: 'user',libvirt_password: 'password')
みたいに書きます。
public key を使った ssh だと

  • Fog::Compute.new(provider: 'libvirt',libvirt_uri: 'qemu+ssh://user@hoge.example.com/system?keyfile=/path/to/key')
みたいになります。public key の path を URI に書くような形式っぽいです。



fog-libvirt を使ったKVM操作

接続できればあとはいろいろできます。

  • connection = Fog::Compute.new(provider: 'libvirt',libvirt_uri: 'qemu+ssh://user@hoge.example.com/system?keyfile=/path/to/key')
    • KVM への接続
  • server = connection.servers.find{|s| s.name == 'hogehoge'}
    • domain name 'hogehoge' で define された VM を探してくる
  • server.active? #=> true or false
    • 電源が入ってるかチェック
  • server.start
    • VM 起動
  • server.halt
    • VM 停止
  • connection.volumes
    • イメージファイルの一覧表示とか。
    • define された domain に引っついてるディスクの一覧が手に入る。
  • volume = connection.volumes.find{|v| v.name = 'fugafuga.qcow2'}
    • 'fugafuga.qcow2' を探してくる
  • volume.clone_volume('piyopiyo.qcow2')
    • volume の clone もできます。


テンプレートVM の clone

特定の VM をテンプレートとして、そのテンプレートを clone する機能が欲しいな、ということになりました。
Fog::Libvirt::Compute::Server#create には volume_template_name を指定するオプションがあるのですが、どうも思ってたように動かない。
確認すると volume の clone には成功しているようですが、clone した後の VM の volume が template と同じ。
どうやら Fog::Libvirt::Compute::Volume#clone_volume は clone すると template の方を return する様子。
これじゃ新しいVMの volume が clone対象の方だ、ということで clone し終わった方の volume を返すように変更。
加えて、テンプレートと clone した後の volume の storage pool を分けたかったので  Fog::Libvirt::Compute::Volume#clone_volume に storage pool の指定もできるようにしてみました
ドキュメントがほとんど無かったので割とソース読んで手探り。
gem はだいたいソースが見られるので良いですね。

例えばこんな感じで使います。

  • cloned_vol = template_vol.clone_volume('new-vm-volume-name', 'new-pool')
  • kvm.servers.create(name: 'new-vm-nameloned_vm_name' , volumes: [cloned_vol])


ストレージプールの話

説明が後になっているのでよろしく無いのですが、KVM にはストレージプールというものがあるらしいです。
特定のディスクやディレクトリを virsh 経由で操作できるようにするものみたいですね。
こいつを設定しておくと特定のディレクトリの下に clone したイメージを fog-libvirt 経由で自由に作ったり消したりできるようになります。

例えば pool を作るには

  • virsh pool-define-as vm_pool dir - - - - /path/to/dir
  • virsh pool-build vm_pool
  • virsh pool-start vm_pool
  • virsh pool-autostart vm_pool
とかします。

そうすると
  • virsh vol-list --pool vm_pool
    • volume の一覧
  • virsh vol-clone --pool vm_pool tempalte.qcow2 new.qcow2
    • volume の clone
  • virsh vol-delete --pool vm_pool garbage.qcow2
    • volume の削除
とかできるようになります。もちろん fog-libvirt 側でもOK。
あと、ストレージプールにしたディレクトリの下にディレクトリがあると、 fog-libvirt 側の list が上手く動かなかったりする様子。ディレクトリ作らない方が良さそうですね。

そんなこんなで fog-libvirt を使って KVM をいじってみたよ、というお話でした。


使った環境情報

実行OS : OSX Yosemite 10.10.5, CentOS 7.1
Ruby : 2.2.3
KVM Host : CentOS 7.1 + libvirt 1.2.8,  Fedora 21 + libvirt


参考URL


2015/10/10

Rails と bind-sdb を連携してみる

DB と連携した DNS を立てることになったので、そのDBの操作を Rails でやってしまおう、というお話。

環境

  • OS : Mac OSX 10.10.5
  • Docker : 1.8.1 (docker-machine)
  • Ruby : 2.2.3
  • Rails : 4.2.4
  • PostgreSQL : 9.4
  • Bind-SDB : 9.9.4

Bind-SDB について

通常の bind だと zone ファイルとかを更新したりする必要があるのですが、 sdb インターフェースというものを使うと zone ファイルではなくてDBのレコードを使って名前解決とかできるらしいです。
具体的には CentOS とかだと bind-sdb(bind-sdb-chroot) パッケージ。
バックエンドはデフォルトでは PostgreSQL なので PostgreSQL で。 一応 MySQL のアダプタもあります。
レコードに要求されるカラムは以下の4つ。
  • name : DNS を引く時の key。 A record ならFQDNとか。
  • rdtype : レコードの種類。 A とか AAAA とか SOA とか。
  • rdata : DNS を引く時の value。 A record ならIPとか。
  • ttl : レコードのTTL
この4つがきちんと引けていれば動くようなので、この構造を持ったモデルを作ってしまえば Rails で管理できそうだな、ということで実際に作ってみました。

サンプルアプリケーション

サンプルとしてちょこっと書いてみました。(https://github.com/atton-/bind_sdb_with_rails)
*.hoge.com を解決できるようなDNSを作れます。
一応逆引きもできるようにしてるのでアドレス帯域は 10.100.200.0/24 くらい。
rails server して、 domain を入れて IP の最後のオクテットを数値で入れると名前が解決できます。
例えば、 rake db:seed では aaa.hoge.com というレコードを入れるので
$ dig aaa.hoge.com @<your-dns-ip>
とかすると 10.100.200.1 と返します。
実行コマンドは README.md に書いてますが、コンテナのIPが環境によって違うのでちょっと書き換えが必要です。
あと DOCKER_HOST が設定されていること前提。OSX で docker-machine を使っていれば設定されているはずです。

やってること

やっていることはドメインの文字列とIPの組から A レコードと PTR レコードを作ってるだけです。
ドメインとIPが入る record モデルを作って
A レコードが入る forward_record モデルと PTR レコードが入る reverse_record モデルを作って has_one で持たせる。
record モデルの after_save くらいで forward_record と reverse_record を生成してやります。
あとは bind-sdb 側がやってくれます。

bind-sdb 側の設定として、 /var/named/chroot_sdb/etc/named.conf にDBサーバのIPを指定する必要があります。
どうやら名前で書くのはダメなようで、IPで直接指定します。
今回は docker container で DB を持っているので、 docker の host か DB の container の IP を書いてから build するようにしてます。ちょっと面倒。

bind-sdb は特定の zone を特定のテーブルから引く、という処理をするようなので、正引き用の zone は forward_records テーブルを、逆引き用のzoneは reverse_records テーブルを見るようにします。
あとは forward_records/reverse_records に SOA/NS を突っこんでやれば動きます。
ただ、ゾーンを転送する際はテーブルの中に入っているもの全てを転送してしまうようなので、複数のゾーンを1つのテーブルに入れてしまわないように注意。一応転送しないのなら動きはしますが……
ちなみに select するだけのようなので、 read only の view とかを切って複数のゾーンに対応することもできます。そうすれば Rails 側の association が多くなることも無いです。
もちろん AAAA レコードも追加できるので IPv6 も対応可能。今回はゾーンを書くのが面倒だったので v4 のみで。
あとは転送とかするのなら SOA のシリアルの管理もきちんと。 record モデルの after_update くらいに SOA のシリアルを上げる処理を書いてやる形とか。

ということで、 Rails でドメインとIPを申請すると名前が解決できるようになりました。

実行コマンド例

一応置換してね、みたいな記述をしているつもりなのですけれど、具体的な値で実行してみた時の例を載せておきます。
  • $ git clone https://github.com/atton-/bind_sdb_with_rails
  • $ cd bind_sdb_with_rails
  • $  echo $DOCKER_HOST
    • tcp://192.168.99.100:2376
    • docker のホストのIP は 192.168.99.100 のようなのでこれを使います。
  • $ docker build -t pg94 docker/postgres
  • $ docker run -itd --name postgres-server -p 5432:5432 -e POSTGRES_PASSWORD=hogehoge pg94
  • $ rake db:create db:migrate db:seed
    • ここで aaa.hoge.com, 10.100.200.1 が追加される。
  • $ rails server
    • http://localhost:3000 でレコードの追加とかができます。
  • $ sed -i -e 's/postgres-server/192.168.99.100/'
  • $ docker build -t bind_sdb_with_rails/bind-sdb docker/bind-sdb
  • $ docker run -itd --name name-server -p 53:53 -p 53:53/udp bind_sdb_with_rails/bind-sdb
    • この段階でDNSが立ち上がります
  • $ dig @192.168.99.100 aaa.hoge.com
    • 正引きチェック
  • $ dig @192.168.99.100 -x 10.100.200.1
    • 逆引きチェック

参考

2015/10/09

ActiveLdap を使っている時に PG がエラーを吐いて RSpec が落ちる

RSpec を使って Rails の並列リクエスト込みのテストを書いていると、

 PG::ConnectionBad: PQsocket() can't get socket descriptor
とか言われてテストがコケる。
テストだけじゃなくて Webrick でもエラーを吐く時は吐く。
実行する環境が Mac だとどうやっても通って Linux だとどうやっても通らない。

結論から言うと、 ActiveLDAP にタイムアウトを付けていると ActiveLDAP が fork するのでなんやかんや PG のソケットが足りなくなるから PG 側がエラーを吐いて死ぬっぽい。
fork が Process.fork してるのでたぶん OS 依存なバグになってややこしかった形。
対症療法としては activeldap の config の  timeout を 0 にして無効化すれば発生しなくなる。

環境

具体的に試した環境としては

OKな環境(Mac)

  • Rails : 4.2.4
  • Ruby : 2.2.2, 2.1.5, 2.0.0-p645
  • DB
    • PostgreSQL :  9.2.13, 9.3, 9.4 on Docker (1.8.0) on VirtualBox (5.0.0) on Mac OSX Yosemite
    • MySQL : 5.7 on Docker (1.8.0) on VirtualBox (5.0.0) on Mac OSX Yosemite
  • OpenLDAP : 2.4.39 on Docker (1.8.0) on VirtualBox (5.0.0) on Mac OSX Yosemite
  • ActiveLDAP :  4.0.4
  • pg :  0.18.2, 0.18.1, 0.18.0, 0.17.1, 0.17.0
  • RSpec 実行OS Mac OSX Yosemite
OK な環境(Linux)
  • Rails 4.2.4
  • Ruby : 2.2.2
  • MySQL : 5.7 on Docker (1.8.0) on VirtualBox (5.0.0) on Mac OSX Yosemite
  • OpenLDAP : 2.4.39 on Docker (1.8.0) on VirtualBox (5.0.0) on Mac OSX Yosemite
  • ActiveLDAP :  4.0.4
  • pg :  0.18.2, 0.18.1, 0.18.0, 0.17.1, 0.17.0
  • RSpec 実行OS : CentOS 7, Fedora 20
NG な環境(Linux)
  • Rails 4.2.4
  • Ruby : 2.2.2, 2.1.5, 2.0.0-p645
  • PostgreSQL : 9.2.13, 9.3, 9.4 on Docker (1.8.0) on VirtualBox (5.0.0) on Mac OSX Yosemite
  • OpenLDAP : 2.4.39 on Docker (1.8.0) on VirtualBox (5.0.0) on Mac OSX Yosemite
  • ActiveLDAP :  4.0.4
  • pg : 0.18.2, 0.18.1, 0.18.0, 0.17.1, 0.17.0
  • RSpec 実行OS : CentOS 7, CentOS 7 and Fedora 20 on VirtualBox, boot2docker 1.8.0
まとめると
  • Mac なら DB を PostgreSQL にしても MySQL にしても何でもOK
  • Linux だと DB を PostgreSQL にするとたまに再現
  • Linux だと DB を MySQL にすると再現せず
  • postgresql-server や postgresql-devel のバージョンを変えても問題は解決せず

原因調査の流れ

まずは gem のバージョンを変える。なるべく最新へ。解決せず。
gem のバージョンを過去のバージョンにもする。解決せず。
次に PostgreSQL も最新にする。解決せず。
クライアント側の postgresql-devel も最新に。 解決せず。
とりあえず pg を読む。
 PG::ConnectionBad: PQsocket() can't get socket descriptor
は 'pg' の gem の native extension で、PostgreSQL への socket が取れない時に投げられるらしい。
socket が取れないとのことなので pool を増やしたりもしたけれど解決せず。
MySQL にしてみる。 Linux でも動く。どうやら問題はDBだとアタリを付ける(結果的にははずれ)
動かす Linux の kernel を上げたりする。(CentOS でも Fedora でも動かず。 2.x でも 3.x でもダメ)
Linux を物理マシンに乗っけるも動かず。
試しに rails new したやつで並列にリクエストをしても捌く。どうやら原因はデフォルトの gem では無いらしい。
そうこうしてると Linux 側でコアダンプ吐いて Ruby が死ぬ。 stack trace を見ると ActiveLDAP の様子。
ということで覗いて見ると Process.fork とかしてるらしい
fork した先で PG の connection を使いまくって PG が正しいソケット取れないのかな、と推測してとりあえず timeout を切る。
これで解決。再現せず。長かった……

とりあえず怪しいのは ActiveLDAP の fork 方法か Ruby の Linux 側の fork 。原因を追求したい気もするけれど今はちょっと放置。

2015/09/16

ActiveLDAP で DN が被ってないのに Distinguishedname is duplicated と言われる時

タイトルの通りに ActiveLDAP で DN が被ってないのに Distinguishedname is duplicated と言われる時にチェックするポイントとか。

DN を構成する時に使ってる attribute の末尾にスペースとかが入ってないかチェック。

どうやらスペースが入ってると validation で引っかかってしまう様子。
推測ですが、attributeが違うと DN が違う判定になって update ではなく create 扱いになって、create 時のDN重複チェックはスペースを無視しちゃうので重複扱いになる、といった感じ?(参考)。

見つけた方法としては、 ldapserarch で各種 attributes を確認。
DN自体はOKっぽかったのですが DN を構成する attiribute の value が英字 only のはずなのに :: が付いてたので怪しいなー、と思って確認してみると投入するデータの attribute の末尾にスペースが入ってました、というオチでした。
ちなみにスペースを消すと直りました。めでたしめでたし。

2015/08/01

Capybara で同時に複数のリクエストを受け取った時のテストをする

Capybara を使って Rails Application のテストを書いていた時に同時にリクエストを受け取った時のテストを書きたくなったのでそのログ。

環境


  • OSX Yosemite 10.10.4
  • rbenv 0.4.0
  • ruby-build 20150719
  • Ruby 2.2.2
  • Rails 4.2.2
  • Rspec 3.3.1
  • Capybara 2.4.4
  • Postgresql 9.2.13


状況と解説と解決策


想定としては

  • ユーザが resource を new する
  • resource に対して Rails 側が unique な値を追加する
  • unique な値は DB 内では重複してはいけない
といった感じです。

とりあえずアバウトなサンプルとして
  • 本の名前と価格をユーザが入れる
  • ISBNをRails側が順番に振っていく
  • もし本の登録が無くなったらそのISBNは再利用
みたいなサンプルを書いてみました


まず、リクエストを並列に投げるには spec 内で fork しちゃうと良いみたいです
validates_uniqueness_of をすり抜ける程度には近いタイミングで実行されるようなので、同時に受け取ったテストと考えて良いかな、と思っています。
実際、 SQLite3 + validates_uniqueness_of のみを書いてある commit では、2つのリクエストを同時に投げると unique に振らないといけない ISBN に対して同じ値でレコードが2つ追加されてしまいます。


DB内で重複を排除するには unique な index を貼ることで回避してます。
そうすることで SQLite3 でも重複された値は格納されないようになるのですが
  • /Users/atton/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/sqlite3-1.3.10/lib/sqlite3/statement.rb:108:in `step':
  •  SQLite3::BusyException: database is locked: INSERT INTO "books" ("name", "price", "created_at", "updated_at") VALUES (?, ?, ?, ?)
  •  (ActiveRecord::StatementInvalid)
と言われてしまってテストがコケちゃいます。
ActiveRecord::StatementInvalid を rescue で拾ってから再格納しようとしても BusyException が出るのでうまくいかず。


ということで DB を PostgreSQL に変えてみます
そうすると、 validates_uniqueness_of をすり抜けて重複した値が入ってきた時には ActiveRecord::RecordNotUnique が返ってきます。
こいつを rescue してきちんとした値にしてやればどうにか目的は達成できました。
ちなみにちゃんと rescue するロジックを書いた場合だとテストで同時に80リクエストくらい捌けました。fork しまくったせいでメモリを数GBとか使うので80くらいが限界。


コマンドとか

Github に上げたサンプルをとりあえず動かすには
  • git clone https://github.com/atton-/sample_of_rspec_to_concurrent_requests_using_capybara.git
  • cd sample_of_rspec_to_concurrent_requests_using_capybara
  • git checkout sqlite3_and_validates_uniqueness_of
  • RAILS_ENV=test bundle exec  rake db:drop db:create db:migrate spec
    • sqlite3 と validates_uniqueness_of のみ。
    • unique であって欲しい column に同じ値が入ってしまう
  • git checkout use_postgresql
  • RAILS_ENV=test bundle exec  rake db:drop db:create db:migrate spec
    • PostgreSQL + Unique Index
    • unique であって欲しい column に同じ値は入らない
    • 同じ値を入れようとすると ActiveRecord::RecordNotUnique
  • git checkout handle_record_not_unique
  • RAILS_ENV=test bundle exec  rake db:drop db:create db:migrate spec
    • PostgreSQL + Unique Index + ActiveRecord::RecordNotUnique を rescue
    • ActiveRecord::RecordNotUnique をきちんと処理してやれば unique にしてあげられる
とかです。
PostgreSQL の設定は環境変数に設定するか database.yml を変更してください。


まとめ

  • 特定の column の値を Unique にしたかったら unique index を貼っておくと validates_uniqueness_of をすり抜けられてもDBに入るのは止められる
  • SQLite3 だと Unique index がある table に同時に insert とかすると BusyException とか出ちゃう
  • PostgreSQL だとほぼ同時なリクエストでも捌けた
  • PostgreSQL だと返ってくる exception は ActiveRecord::RecordNotUnique に変わる


気になるところ

  • Unique Index を貼る方法で Uniqueness を確保する方法って一般的なのかな
  • 特定の column の値が [1,2,3,4, 6,7,8,9] みたいな状態で 5 を取ってくる SQL ってあったっけ(今は 1 から順に ActiveRecord#exists? で回してるので O(n) なんだよなー)
  • 並列にリクエストを投げるのを SQLite3 でできると楽なんだけれど何か方法無いかな

2015/07/03

ハッカーズチャンプルー2015に行ってきた

2015/06/27(Sat) に ハッカーズチャンプルー2015 があったので参加してきました。

今年のハッカーズチャンプルーは開発合宿もあったようです。
私はカンファレンスの部ビーチパーティーの部に行ってきました。


カンファレンス

カンファレンスの部は、うちはこんな風に仕事しているよ、という紹介が多かった印象。
例えば esa.io さん の仕事の仕方だとか、 Github さんでの仕事の仕方とか。
LT も@Amothic さんのマイ20%ルールとか @Yutaka_Kinjyo さんの一度死んだ話とか。
なんだかゆるーくやっていこうぜ、みたいな話がたくさん出ているの、沖縄っぽくて良いのかなー、と思います。
あとカーゴカルトの話は耳が痛い。何も考えずに良さそうと採用するのは良くない。
その点、esa さんは自分達が考えた結果こうしている、みたいな形だったのできちんと考えているな、と。

働き方とかの話とは違うベクトルと言えば Jupyter の話
確実に再現可能なコードがレポートについて来るのは非常にうれしいことですね。(実際過去のコードが再現できなくて本人に聞いてる風景を見たことがあるので)
あとは IoT。ガジェットが KickStarter でぽんぽん出てるのは良いことだなー、と。
アジャイルに関してはスクラムリーダーどこから連れてくるのー、って質問が結構印象的でした。
最初の一人どこから来るんだ問題はどこでもあるみたいですね。


ビーチパーティー

ビーチでバーベキューでした。
学生の数が少なくて(たぶん全体で3名?)、大学内でビーチパーティ来た人では私が一番若いという状態。
ハッカーズチャンプルーの目的とは違うかもしれませんけれど、学生こないの勿体ないなー、とか思う。
学生どうにかなりませんか、という話を @k_nishijima さんに話したら学生料金にして頂けました。ありがとうございます。
実際、学生料金とかを設定すると人数の把握が難しくて金額調整が大変なんだとか。うーむ。
後から学生に何故来なかったのか聞いたところ「高かったので。学生料金なら行ってました」と答える人が数人。
あと「枠が埋まっていたので参加を諦めました」って人もいました。
そういう意味だと学生側からは学生枠の需要はありそうですね。

バーベキューをずっと手伝ってたのでいろいろ貰ったり。
学生料金の件も含めてなんだかゴネた人みたいな感じになので罪悪感。
なんらかの形でフィードバックしないとなー、とか思う。
とりあえず安直なのは来年やる時のステマで、お金が手にはいる用になったら学生バーベキュー用スポンサーでもやろうかな、とか。
これで学生枠問題が解決すると良いんだけれど。でも今お金無いです。

バーベキューしながらの立ち話では、沖縄の学生にコード書くバイトさせようよ東京の方々、みたいな話をしていました。
沖縄の大学生はコンビニとかでバイトしてる人もいて、コード書ける奴にそんなことさせるのもったいないよ、仕事振ってみたら、と。
コード書くのはリモートでも可能だし、沖縄の学生基準だと時給1000円でも十分うれしいはずなので十分振ることでメリットあるのでは、とか話してました。
あとは人が足りないみたいな話。
スクラムリーダーがいない、って話じゃないですけれど、良い感じのプログラマ/デザイナってどこに潜んでるんだろう、とかとか。
東京でもデザイナ足りていない状態らしくて、東京でも不足しているのなら人材不足って相当なもんなのかなー、と。


まとめとしては、ゆるくやろうぜー、みたいな雰囲気が漂ってるのは良いことだな、と。
私もゆるくやっていきたい所存でございます。

2015/06/09

xv6 ソースコード読み会

2015/06/05 - 2015/06/07 にxv6のソース読み会があったので参加してきました。

読んだソースコードは swetland/xv6 で 64bit 版の xv6 です。
gdb で追えるようにした話はこちら


History of CPU Architectures

OS を読む/書くにあたって CPU の仕様を見ることは避けられないので Intel CPU の歴史から。ソースを辿ったわけでは無いので間違いがあるかもしれません。
  • 4004 (4bit cpu)
  • 8080/8080a/Z80 (8bit cpu)
    • Z80 が AH/AL, BH/BL, CH/CL, DH/DL, SP, IP で register は16bit だったとか
  • 8086 (16bit cpu)
    • 16bit に拡張するためにセグメントレジスタを使った
  • i386(32/64bit cpu)
    • eax register ができた
    • セグメントレジスタの参照先を変えることでプロセス空間を変えたりできる
    • segment register を変えることで virtual memory へもアクセスできる
    • セグメントの対応表は GDT とかに入ってる
16 から 32 への拡張は命令セットはそんなに変わらなかったそうです。
8 から 16 、 32 から 64 がだいぶ変わったとか。


Intel 64 and IA-32 Architectures Software Developer Manuals

xv6 の boot は 16 bit から始まって 32bit へ拡張、最後に64bitになります。
何故16bitからスタートしているかは CPU の仕様だったりします。
ということでマニュアルを確認することに。
読んだ当時の2015年の Intel 64bit CPU の Manual はこちら
Section 2.2 Modes of Operation によると、CPU は3つのモードがあるみたいです。
  • protected mode
    • native state of processor
    • 32/64bit mode
  • real-address mode
    • 8086 processor
    • 16bit mode
    • 起動時はこのモード
  • system management mode
    • Power Management とかの System 周りをやるモード
なので xv6 の boot process は real-address mode から protected mode への移行ももちろん入っているわけです。
モードの変更方法もマニュアルに書かれていて、Section 9.9 Mode Switching の 9.9.1 Switching to Protected Mode にあります。
Interupput を disable にして、 CR0 register の PE (Protection Enable) flag をセットするとか具体的な手順が書かれています。
あと、Register の構成や GDT については Figure 2-2. System-Level Registers and Data Structures in IA-32e Mode に載っています。
仮想記憶のアドレスの切り替えやメモリ空間の拡張はセグメントレジスタの値を切り替えることによって行なっていて、その Segment Descripter の対応は GDT(Global Descripter Table)に入ってるみたいです。
なので GDT を切り替えることでアドレス空間を切り替えるような構成になってるみたいです。


Boot phase on xv6(16bit)

さてブートから読んでいきましょう、ということで kernel/bootasm.S を読みます。
.code16 から始まっているので 16bit mode で始まっていることが分かります。
cli で clear interrupt して ds,es,ss を zero fill 。
seta20.1 でinb/outb で 0xd1 と 0xdf を書き込み。
inb/outb は CPU が持ってる IO らしいです。
0xd1 は今から命令を書くよ、ってもので 0xdf は A20 を有効化する命令みたいです。
そして gdtdesc から lgdt (load gdt) して gdt をセット。
%cr0 に PE をセットして protected modeへ。
最後にljmpして 32bit mode になります。
この手順はマニュアルにそのまま書いてあったりします(Section 9-9)。

Boot phase on xv6(32bit)

protected mode になっても基本的には初期化。
32bit なので .code32 で始まっています。
register を初期化して bootmain って関数を call。
bootmain は C で書かれていて、 kernel/bootmain.c にあります。
mbheader にいろいろとベタで書かれていて、こいつは後から main に移る時に参照します。
bootmain は readseg してその値をメモリの 0x10000 に書き込みます。
readseg する場所は entry64.S が書きこまれているところです。
entry64.S を見ると分かるのですが、マジックナンバー 0x1BADB002 が書かれていて、read segした後にこいつがあるかチェックしています。
マジックナンバーが書かれてる = multi boot の entry が入ってる、ってことで entry の中身を展開して、 entry() を呼びます。
kernel/entry64.S に entry の中身が書かれていて、 page table の初期化とかをしています。
64bit mode (IA32e) にするために CR4.PAE を立てたり、 EFER.LME を立ててから
 ljmp します。
CR4.PAE は Physical Address Extension でメモリを 4GB 以上扱えるようにするもので、 64bit mode になる時に必須です。
EFER.LME の EFER は Extended Feature Enable Register でフラグを設定するレジスタです。
  LME は IE-32e Mode Enable で命令セットを64bit mode に切り替えるフラグです。(たぶん LongModeEnable)
これを有効にして ljmp すると 64bit mode になります。

Boot phase on xv6(64bit)

64bit になったらあとは main へ。
main は kernel/main.c にあります。
main では初期化を行ないます。
  • uartearlyinit: uart (console)の初期化
  • kinit1: カーネル用にメモリ確保。 freelist を作るだけ。
  • kvmalloc: カーネルのページテーブルにメモリ割り当て。
  • acpiinit: acpi を確認してCPU数をチェック。
  • pinit: プロセステーブルの初期化。 lock を作るだけ。
とかを読みました。
main は最後に mpmain を呼びます。
mpmain は CPU ごとに呼ばれます。
mpmain は最終的に scheduler に落ちます。


syscall

次に syscall を読もう、ということに。
syscall の table は kernel/syscall.h に定義されています。
syscall は trap された後に kernel/syscall.c の syscall(void) で実行されます。
trap は alltraps から呼ばれるようになっていて、 alltraps は kernel/trapasm64.S に定義されています。
trap vector は kernel/vectors.S に定義されていて、 tools/vectors64.pl で生成されるみたいです。

sys_wait

具体的なシステムコールを読もうということで wait を読むことに。
sys_wait から wait() を呼んでます。
wait は kernel/proc.c にあります。
process table を lock して、子供を ptable から探してそれが終わるまで sleep してます。
sleep は 2つの spinlock を使って status を SLEEPING にして内部で sched() しています。

sched

sched は process の切り替えみたいです。kernel/proc.c にあります。
ちゃんとロックしてるか、interrupt が enable じゃないかなどをチェックしてから swtch() します。
proc.h に定義されている proc という名前の変数には __thread が付いていて、 thread local storage みたいです。
コメントによれば Per CPU Variable とのこと。
gcc の実装はこうなっていて、 fs や gs に CPU ごとに違う値が入ってるっぽいです。
thread local な値があるので並列にプロセスを実行できるってわけですね。

swtch

kernel/swtch64.S にあります。
レジスタの中身を退避してから stack pointer を切り替えることで process を切り替えてます。
実際 sp を切り替えたあとに gdb で bt すると別の back trace が得られました。
stack pointer が変わるのでこのCPUでこの先に実行される命令が変わるわけです。

scheduler

sched を読んだのて scheduler もきちんと読もう、ということに。
scheduler() は kernel/proc.c にあります。
mpmain から呼ばれています。
sti() を使ってinterrupt を enableに。
ptable を lock して、 RUNNABLE なものを拾ってきて switchuvm。
switchuvm はたぶん switch user virtual memory で gdt を切り替え。
これでプロセスの virutal memory を切り替えているみたいですね。
その後にまた swtch する感じです。
プロセスが終わったら switchkvm で kernel virtual memory に戻ってきます。
scheduler は ptable を無限ループで見続けてます。

sys_fork

あと1つくらい syscall を読もうよ、ということで fork。
fork の本体は kernel/proc.c にあります。
allocproc で新しいプロセスを作ってます。
copyuvm() で親の virtual memory をコピってますね。
子供側の return value は0なので eax を0にして state を RUNNING に。
親側に pid を返して終わり。


感想とか

当然といえば当然なのだけれど、OSはCPUの仕様に依存していて、特にbootとかはCPU側の手順に合わせる必要があって。
Page Table とかも IA32e では CPU 側が専用命令を持っているようで、OSが handle できない部分もあるんだなー、とか。
xv6 のコードはかなり短かくて、読みやすいとか玩具みたいとかのコメントがちらほら。
個人的にはマクロとかが無いので ctags とか gdb だけで追えて割と素直だなー、と。
PC とか SP とか register とか、CPUの機構知ってないと読むの大変な気もしますが、コンピュータアーキテクチャの講義でやってるのでハードウェアとソフトウェアの繋がりが見えて良いですね。
あと gdb が stack pointer の切り替えとか thread local な値も dump できるのがだいぶ便利で助かりました。
迷ったら x/20 とかするの便利。 x/20i とか x/20s とか x/20x とか。
これだけハードウェアに近い機構もサポートしている gdb の内部も気になるかもです。
CPU Architecture に詳しくなった気がする xv6 読み会でした。

2015/05/23

64bit 版 xv6 を gdb で追う

前回 にも xv6 を読もう、ということで Mac に環境を構築したのですが、 swetland さんによる 64bit 版の xv6 があるみたいで、そっちも読めるようにしたログ。


環境
  • Host
    • Mac OSX Yosemite 10.10.3
    • Vagrant 1.7.2
    • VirtualBox 4.3.26
  • Guest
    • Ubuntu 14.04 on vagrant (chef/ubuntu-14.04)
    • qemu 1.7.91
    • gcc 4.8.2
    • gdb-multiarch 7.7


ブートの流れと gdb で素直に追えない理由

64bit 版の boot loader は memory space を 16 -> 32 -> 64 と拡張しながら起動していくみたいです
切り替えの際に命令セットが変わるので、 16bit(i8086) で attach していた gdb で b main して continute しても main で止まらなかったり、アーキテクチャの設定を間違えると gdb が文句を言ったりします。
この問題は swetland さんの xv6 の README.64bit にも書かれていて、
* gdb pukes when qemu switches from 32bit to 64bit mode
  * this made debugging the mode change entertaining
  * for now attach gdb after the switch
とのこと。
要は切り替えると gdb がダメになるので、切り替えた後に attach しなよ、と。
この切り替えポイントと gdb の attach をどうするか格闘したログ


16bit -> 32bit

boot 部分はアセンブラで直書きされているのでその辺りを読む。

out/bootblock.asm にある
ff 53 1c                call   *0x1c(%ebx)
でどうやら 16bit -> 32bit している様子。
なのでこの命令がある 0x7dc1 に break を置く。
ここは起動直後の gdb での continue で止まる。
stepi すると 0x00100020 に飛ぶ。

ちなみに 0x00100020  は mboot_entry ってやつで、ページテーブルの初期化とかしてるみたいです。
これで 32bit になったので 64bit に飛ぶところを探す。


32bit -> 64bit

メモリが32bitになったので 0x1000a4 とかが指定できる。
64bit mode にしてる部分は out/kernel.asm の
# shift to 64bit segment
 ljmp $8,$(entry64low - mboot_header + mboot_load_addr)
のところみたいです。
私の環境のアドレスだと 0x1000a4 。
ここから stepi すると
Remote 'g' packet reply is too long: 000200000000000000000.....
と言われて gdb で操作できなくなる。
直前に set architecture i386:x86-64 とかしてもダメ。
ということで、 16 -> 32 した gdb はそのままで新しい gdb を上げる。
新しい gdb で symbol-file と set arch してから target remote すると gdb を待つ状態になるので、古い gdb で continue してエラーが出てから quit 。
古い gdb は落ちて新しいの 64bit で繋がるので、これで b main とかが効くようになります。


Ubuntu 14.04 に環境を作る

  • sudo apt-get install -y gcc git qemu gdb-multiarch vim zsh
  • git clone https://github.com/swetland/xv6.git xv6-64bit
とか。
Makefile の QEMU を qemu-system-x86_64 にして、64bit build の時は X64 って環境変数を作る。
gdb で debug する時は tool/gdbinit.tmpl の symbol-file kernel を symbol-file/kernel.elf にする。
あとは
  • make 
  • make qemu-nox-gdb
してから gdb-multiarch でいけます。
最初の gdb-multiarch の .gdbinit は
echo + target remote localhost:25900\ntarget remote localhost:25900
echo + symbol-file out/kernel.elf\nsymbol-file out/kernel.elf
break *0x7dc1continuestepibreak *0x1000a4
echo + please attach another gdb.\n
とか。32bit mode での最後の ljmp までは2つめの gdb-multiarch でいけます。
2つ目の gdb-multiarch の .gdbinit は
set architecture i386:x86-64:intel
echo + target remote localhost:25900\ntarget remote localhost:25900
echo + symbol-file out/kernel.elf\nsymbol-file out/kernel.elf

とか。 2つ目を接続してから1つ目を continue すると良い。

xv6 を fork してみる

結局やることは
  • .gdbinit の書き換え
  • 16 -> 32 bit 版 gdb 用 .gdbinit
  • 32 -> 64 bit 版 gdb 用 .gdbinit
  • QEMU の設定
  • X64 を設定
とか沢山あったので、 fork してみました
3つ shell を上げて
  • (1) make
  • (1) make qemu-nox-gdb
  • (2) gdb-multiarch -x .gdbinit64 
    • please attach another gdb と出たら
  • (3) gdb-multiarch -x .gdbinit64-2
  • (2) continue
  • (2) exit
    • すると (3) の gdb が繋がるので
  • (3) b main
  • (3) continue
    • とかができます。
(3) の gdb の attach 時に 'Bogus trace status reply from target: PacketSize=1000' と出る場合もありますが、もう一度  $ gdb-multiarch -x .gdbinit64-2 すると繋がります。

メモリがきちんと64bitになっていればok。
The target architecture is assumed to be i386:x86-64:intel+ target remote localhost:259000x00000000001000e0 in ?? ()+ symbol-file out/kernel.elf(gdb) 
とかですね。
あとは main から読むなりなんなりできます。



コマンドまとめ

vagrant 上の Ubuntu 14.04 にて 3 つ shell を上げます。それぞれが(1), (2), (3) です。

(1) $ sudo apt-get install -y gcc git qemu gdb-multiarch vim zsh
(1) $ git clone https://github.com/atton-/xv6.git xv6-64bit
(1,2,3) $ cd xv6-64bit
(1) $ make
(1) $ make qemu-nox-gdb
(2) $ gdb-multiarch -x .gdbinit64
    please attach と出たら
(3) $ gdb-multiarch -x .gdbinit64-2
(2) $ continue
    して Remote 'g' packet reply is too long: 000200000000000000000.....
    と言われると思うので(2) の gdb を落とす
(3) $ b main
(3) $ continue
    main で止まります。
(3) の attach に失敗したら (3) をもう一度  gdb-multiarch -x .gdbinit64-2  で上げると良いはずです。

2015/05/14

tmux 2.0 と Ricty と East Asian Ambiguous

先日 tmux 2.0 がリリースされましたね。

最近は Terminal.app + tmuxinator を使って作業とかしてるので、 tmux もアップデート。
そうすると U+22BD(▽) とかの East Asian Ambiguous 文字の幅が tmux 上だと single になってしまう問題が発生。
tmux の上で eskk.vim を使って変換すると文字の表示がズレてしまうのでちょっと不便。

East Asian Amniguous の幅を2にする patch が tmux 1.7 の時に作ってくれた方がいるみたいなので、それを借りて自分向けに formula を作ってみました。
homebrew-customs って名前でついでに tap も作成。

$ brew tap atton-/customs
$ brew install atton-/customs/tmux

で patch が当たった版の tmux が入ります。
一応これで eskk.vim を使う分には快適に。

環境

  • Mac OSX Yosemite
  • Terminal.app 2.5.3
  • Ricty 3.2.2 (no build option)
  • tmux 2.0

蛇足

eskk.vim を使うのには快適になりましたが、 Agda の一部の文字はやっぱりズレる。
例えば U+2248(≈)とか U+2080 - U+2089 (₀₁₂₃₄₅₆₇₈₉) とか。

U+2248(≈) はフォント上では single width なのだけれど、 Width が Ambiguous
Terminal 側で Ambiguous は2にする設定をしているので、幅が2つ取られてフォントは1つ分になって無駄な空白が入っちゃう。

U+2080 - U+2089 も同じようにフォントは single width。
でも何故か U+2081 - U+2084 だけが Ambiguous で、U+2085 とかは Neutral 。
これは Unicode 作った時に何を考えてたのか謎……。

そんな感じなので、East Asian Ambiguous の幅を 2 にすると空白が出てくる文字があって、1にすると重なってしまう文字が出てくるので消化不良……。
とりあえず空白入ってても良いや、という趣旨で基本的に幅を2にしてます。

ちなみに Ricty の生成スクリプトに -a というFull Width を切ってくれるオプションがあるのだけれど、U+22BD は Full Width になってしまってる。
fontforge とかで調べてみるとそもそも Inconsolata に U+22BD のフォントが無いので、 Migu 側が使われてちゃって Full と Half がいりまじることに。
うーん。 英語は Inconsolata, 日本語は Migu 1M, Ambiguous は全部 1 or 2 のフォントって無いかな。

2015/04/21

32bit 版 xv6 を Yosemite で動かす

諸処あって xv6 を Mac で動かしたのでそのログとかをつらつらと。
元々は Vagrant 経由で Virtual Box の VM に構築しようかと思っていたんだけれど、 nested VM できないっぽいので Mac で qemu 動かしました。

環境

  • Mac OS X Yosemite 10.10.3
  • Homebrew 0.9.5

必要なもののインストール

  • brew install homebrew/versions/gcc49
    • クロスコンパイラが必要なのでそのビルドに必要な gcc-4.9 を入れる
  • brew install qemu
    • qemu で xv6 を動かすので必要。
  • brew tap altkatz/gcc_cross_compilers
  • brew install --with-all-targets gdb
    • xv6 が .gdbinit を自動で吐くので gdb で debug するのが入れる。
    • --with-all-targets を付けないと i386 とかの symbol が読めない。
  • brew install i386-elf-gcc i386-elf-binutils
    • i386 な elf をサポートした gcc と binutils。
    • 各コマンドの prefix に i386-elf- って付く。

インストールしたものの各バージョンメモ

  • gcc 4.9.2
  • qemu 2.2.1
  • gdb 7.9
  • i386-elf-gcc 4.9.2
  • i386-elf-binutils 2.23

xv6 の起動

xv6 を落としてくる。
  • git clone git://pdos.csail.mit.edu/xv6/xv6.git
Makefile をちょっと書きかえて
TOOLPREFIX = i386-elf-QEMU = qemu-system-i386
にする。
コマンドとかは
  • make qemu
    • xv6 が起動する。
    • ls とかできる。
  • make qemu-gdb
    • xv6 が gdb で debug できる状態で起動する。
    • 同じディレクトリで gdb って打つだけで attach する。
    • どうやら .gdbinit を勝手に吐いてくれてるっぽい。便利。
っぽいです。

参考文献

2015/04/13

pry-rails がマルチバイト文字を含むヒストリを追加できない現象の対症療法

Rails Console で 'ほげ' とかのマルチバイト文字を入れると、ヒストリへの書き込みができなくて落ちる問題にぶちあたったので対症療法と調べたログ。

環境

  • OSX 10.10.3
  • rbenv 0.4.0
  • ruby-build 20150319zf
  • ruby 2.2.1
  • rails 4.2.0
  • pry 0.10.1
  • pry-rails 0.3.3

問題

日本語を含むコマンドを rails console で打つと以下のエラーで rails console が落ちる。

たぶんこれを踏んでる。

history.rb の save_to_file でどうやら保存できない模様

$ Pry.history.instance_eval {def hoge line; binding.pry end; @saver = method :hoge}
$ にほんご
$ Pry.history.instance_eval {@saver = method :save_to_file}
とかして無理矢理に pry で pry に割り込む。
そしたら line が ASCII-8bit になってるっぽい。
ので正しいエンコーディングに変換してしまえば保存できた。
.pryrc に書くとすると


とか。

これでヒストリへの保存時に落ちなくなったけれど、プロンプトはやっぱりおかしいまま。
どこか根幹の原因があるっぽい。
pry と pry-rails と rails の console 回りを軽く読んだけれど、 encoding 回りを触ってるのは見付けられなかったのでとりあえずの対症療法としてブログにまとめる。

読んでる時にちょっとおもしろかったものとか

  • Rails::Configuration に attr_accesor :x とかがあった。このパッと見 x が何か分からない感。
  • Pry.REPL#start ってメソッドがあって、 repl の loop を始めるメソッドらしいんだけれど
    • prologue
    • Input の handling
    • ensure epilogue
  • とかしていてなんかネーミングセンス良いなー、とか

根幹の原因

対症療法は分かったんだけれど、根幹の原因が分からないなー、ブログに書くかー、と思って再現する最小限の Gemfile を書いてたら、どうやら pry-rails そのものが問題じゃなくて rb-readline と組み合せるとダメみたい。
ちなみに再現する Gemfile は以下。

そのうちまた調べるかなー。

2015/03/01

Agda のソースコードを listings で LaTeX に埋め込む

LaTeX で Agda のソースコードを含む pdf を作ることに。
他のソースコードは listings で埋め込んでいたけれど、Agdaで使っている特定の文字が化けて表示されない。
verbatim でも表示されないので、Agda のソースを LaTeX 用にエスケープして、記号処理は LaTeX に任せることで listings できるようになったお話。

環境

  • Mac OS X 10.9.5
  • MacTeX 20140525

埋め込み方法

listings の escapechar を設定して、表示されない記号を LaTeX 用に置換してあげる。
escapechar が @ で、≡ を置換したかったら @$\equiv$@ みたいな。
escapechar の中は LaTeX 環境になるので、$ で数式を直接書いてしまえばok。
スクリプトとかは以下。
.agda から置換した .eagda を生成してくれます。
LaTeX からの参照は .eagda を指定してます。




周り道とか

Literate Agda ってのもあるみたいです。
lagda で書いておけば LaTeX としても認識してくれるっぽい?
でも今回は使わない方向でいきました。
本来Agdaのソースを埋め込むのならこっちなのかな。

あと大きなソース(500行くらい)のやつを LaTeX に食わせるとメモリが足りなくて落ちる時が。
! TeX capacity exceeded, sorry [main memory size=5000000].
とか言われる。
/usr/local/texlive/2014/texmf.cnf に
main_memory=8000000
extra_mem_bot=8000000
font_mem_size=8000000
pool_size=8000000
buf_size=8000000 
とか書いたら動いた。
メモリの制限とか自前で持ってるのね TeX。

あと、utf8周りでもメモリ周りでも LuaTeX 良いよって指摘があった。
LuaTeX 使うのも良いのかもしれない。
ちなみに今回は platex 使いました。

あとは listings 側のオプションに escapeinside とか literate とか使えそうなのもあったけれど動かず。
これはどうしてなのだろう。
ウムラウト付きの文字なら literate できます、みたいなのは記事にあったんだけれど ≡ は置換されず。

参考文献

ギー沖解散パーティーに行ってきました

2014/02/21 にギークハウス沖縄解散パーティーがあったので参加してきました。

ギー沖の3周年+解散パーティーでした。

人がかなり来ていて、多い時には30名以上いたみたいです。
ギー沖に関わっている人って多かったのだなー、とつくづく。
お隣りさんも来たりして、ネットは広いのだか狭いのだか。

メインディッシュは豚の丸焼き。
始めて食べたのですけれど部位によって硬さとか油とか変わるので若干探検っぽい。
しばらく販売が止まってたカーリーフライを久々に見たりもしました。

ギー沖LTがあって、これまでのギー沖の思い出とか突然の brain fu*k の話とか。
brain fu*k の話があるならー、ということで飛び入りでAgdaの話をしてきました。

LT ではあまりギー沖の話をしなかったのでブログに書こうかと。
ギー沖、何かの動機で1週間くらい泊まったことが(2012/02 くらいだっけ?)。
そんなこんなでギー沖に行くようになって、 Okinawa.rb の weekly meetup にも参加。
私が Ruby を勧める時の「はじめてのプログラミング」 -> 「たのしいRuby」 -> 「メタプログラミングRuby」の流れは Okinawa.rb から来てたり。
初心者組がメソッド分からんって言ってる時に横で libkinjo さんがメタプロの話とかしていたよーな。
Okinawa.rb で hanachin さんと関数型の話して、結果的に Agda 書いてたりするので、Ruby に触れるのも Haskell やら触っているのも元はギー沖から来てるかも。
思えば libkinjo さんのみゅーたんとか tompng さんの Ruby で lisp interpreter できるよねー、とかぱかなさんのギー飯とかこたにんさん飯とか、思い返すといろいろとありますね。

イベントもたくさんあったし、人もたくさんいた場所でした。
ありがとうギー沖。