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 。原因を追求したい気もするけれど今はちょっと放置。