ラベル PostgreSQL の投稿を表示しています。 すべての投稿を表示
ラベル PostgreSQL の投稿を表示しています。 すべての投稿を表示

2017/10/07

Akatsuki を OSS 化した

Akatsuki を OSS 化して GitHub で公開しました。



Akatsuki とは琉球大学情報工学科のシステムです。具体的には
  • 学生のIP申請
    • 対応するドメインを内向き DNS へと登録
    • その IP を DHCP で配れるように
  • 学生のVM新規作成/起動
  • 学生のパスワード変更
    • unix login
    • 802.11X で無線に繋ぐ際の RADIUS を提供
といったことができます。


つまり今まで記事で部分部分を解説していたものの本体です。
関連している記事は以下。


公開にあたって

もちろん大学の内部情報をそのまま GitHub に投げるわけにはいきません。
なので、こいつは OSS 化用にいじってあります。
具体的には漏れたらまずい情報を消してあります。
あとライセンスは MIT だし気にせず自分でいじって公開していく。

その手順として
  • $ git rm very-important-information
    • で公開してはいけない情報を消して
  • $ git commit
    • コミットします。
  • $ git rebase -i --root
    • その後に rebase します。
    • 全部 fixup して一つのコミットにまとめてしまいます
    • fixup しないと復元ができてしまうので、消しても意味がない。
    • ということで、逆に言えばこの手順を踏めばアカンやつを隠蔽できます。


まとめ

所謂実績の一つとして見てもらえると良いかなー、と思って OSS 化しました。
ということで、大学生/大学院生時代にしていた仕事の1つが OSS になりました。

2017/10/06

Heroku の Hobby プランの PostgreSQL が一杯になったので Standard にアップグレードした

Rails を使った Web application を heroku の hobby プラン(無料枠) で運用中、DBへの書き込みができなくなる。

  • $ heroku logs

を漁ると

ActiveRecord::StatementInvalid (PG::InsufficientPrivilege: ERROR:  permission denied

とある。何事だろう。
調べたら、レコードが多すぎる時に発生するらしい。

  • $ heroku pg:info

すると無料枠の 10k レコードを越えていました。
というかぴったり 10k 制限じゃないんだ。優しさなのかなんなのか。

参照した記事の場合、レコードを消して対応していました。
私の場合、レコードを消すわけにはいかないので、 hobby からの upgrade をすることにしました。


コマンド

正直なことを言えば公式のドキュメントが最強です。
指示通りでそのまま問題なくアップグレードできます。
なので、コメント付きで公式ドキュメントをなぞるくらいの気持ちでログを残します。
  • $ heroku maintenance:on
    • Application を止めます。メンテナンスモードですね。
    • おお、これは所謂「停止メンテ」だ。何が起こるか分からないのが良いですね。
  • $ heroku addons:create heroku-postgresql:standard-0
    • DB を新規に作成。プランは standard にしました。
  • $ heroku pg:wait
    • 新規作成したDBを使えるようにします。
  • $ heroku pg:info
    • を見ると DB が増えています。
  • $ heroku pg:copy DATABASE_URL HEROKU_POSTGRESQL_BLACK_URL
    • 古いDBから新しいDBにコピー。
    • DATABASE_URL は置換するとかではなく、この文字列で正しい。
    • なので postgresql-lively-12345 とかに書き換える必要は無いです。
    • 公式ドキュメントだと PINK とかだったかな。どうやらネーミングは色らしい。
  • $ heroku pg:promote HEROKU_POSTGRESQL_BLACK_URL
    • 新しい standard の方をメインのDBにセットします
    • これで DB の移行は終わり
  • $ heroku maintenance:off
    • メンテナンスモード終了
    • これで DB がアップグレードされた状態で Web Application が上がります。
流石 PaaS 。サクサクメンテができるのでインフラに手間がかからない。
ということで淡々とアップグレードができたのでした。ぱちぱち。



参考

2017/07/18

ActiveLDAP が Timeout Module を使っていた

かなり前の記事になりますが、ActiveLDAP が Process.fork を使っているせいで PostgreSQL との相性が悪いバグがありました。
具体的には Timeout を指定すると fork したプロセスを使って Timeout を確認しているせいで pg の connection を使いはたしてしまう、というのが原因でした。

本当に具合が悪い時は Ruby がコアダンプを吐いて死ぬ時もあったので、直してしまってRuby会議2017のLTネタ にでもしようと思って調査を続行。
最新版では再現しないのでソースを読んでみたらそもそも Process.fork をしていない。
Timeout というものに block を渡している。
なんと Timeout なる Module があるようで、それに時間の処理を一任しているようです。

Timeout Module 知らなかった。
Timeout の処理って面倒なので自分が次に Timeout の処理を書くことがあったら使おうと思いましたまる。
ソース読むのはおもしろかったけれどネタが無くなってしまったな。どうしようかな。

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/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/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 でできると楽なんだけれど何か方法無いかな