Model周りの複雑さがやや増してきたWebアプリのリファクタリングメモ

ほぼ Rails の話だけど、考え方は Rails や ActiveRecord に限らず適用できると思ってる。

現在の status としては適用済みのものもそうでないものも混ざっており、絶対全部オススメ!とかそういうキャッチーなものではない。

■ その1 - Interactor

□ 追加gem

collectiveidea/interactor: Interactor provides a common interface for performing complex user interactions.

□ 効果

  • いわゆる Service Object であり、記法が決まっている
  • 複数の Interactor を organize して一つの大きな Interactor を作ることができる1
  • rollback ( ≒ transaction ) を実現できる2

□ 適用範囲

  1. 複雑な処理のカプセル化(CでもMでもないいわゆるService Object)
    • 複数の処理が依存しあっていて、かつその結果を同期的にコントロールしたい、あるいはすぐにフィードバックを返したい
  2. Callback を分解して明示的な処理の流れに書き起こす
    • まず 1 があって C や M の外にやりたいことが明示されることが大事

□ メモ

伝統的なサーバサイドWeb開発アンチパターンとその対策 - C, V編 - - あーありがち(2019-01-20) で Fat Controller への対処としてとにかく Model を作れと書いたが、例えば複数の外部 API を叩くようなことがあるような場合、シンプルな PORO で実現しようとするとエラーハンドリングが面倒くさくなってしまう。

cf. Fowler EAA の Notification

そこで Interactor を入れることで処理全体の成否や rollback などの記述しやすさを期待できる。また Interactor という名前がいかにも外部 API やユーザーのアクションとの間に立ちそうで、少なくとも単なる Service Object という捉え方よりよいと考えている。

また Callback の分解については、例えば処理A, 処理B, 処理C があって、画面1からは処理AとCを、画面2からは処理AとBを行う、

画面の種類画面1画面2画面3
処理の流れ処理A処理A処理B
処理C処理B 

みたいなものがあった時に Fat Controller を避けるために 処理 A の callback で処理を流していく、みたいなことをやると callback に条件分岐が入って変に複雑になってしまう。

これを素直に Interactor で書き起こすと分岐が減って読みやすくなるし callback が減ると暗黙の依存が減る。結果、メンテナンス性を高く維持しやすくなるはず。

□ 参考

似た gem として

もあるが、やや Flow Control の DSL 色が強すぎると感じた。

■ その2 - CQS

□ 追加gem

なし

□ 考え方

  • Command と Query を分ける
  • Command の分離は上の Inetractor を使って明示的に外にくくり出す
  • Query は Thin Read Layer の class に分離する

□ 適用範囲

  • Model

□ メモ

Fat Model は Fat Controller よりはるかにマシではあるが、Model には様々な役割があり、その役割によって異なる複雑さが一つの Model の中に収まってしまうことで、どこを変更したらどこに影響が出るかが分かりにくくなっていく。

一口に Model の複雑さと言っても read と write ( 副作用 ) で異なることが多いと考えている。

Read の複雑さ

  • よく似た微妙に異なる Query の組み立てが画面や操作ごとに分かれやすいこと
    • 結果、Query の組み立て時に条件分岐が入りまくる
  • 関連する Model の呼び出しもどんどん増え、他の Model への依存がどんどん増える

これに対して、C でも M でもない場所で目的ごとに明示的に分離された Query の組み立てを行うことで複雑怪奇な named scope を減らすことができる。

class Model < AR
  scope :items, -> {|args1, args2|
    rel = self.where(.....

    if ( args1 )
      rel = rel.merge(AnotherModel.where(...
    end
  }
end

class ItemsWhenConditionSpecified
  def call(param)
    Model.where(....).merge(AnotherModel.where(...
  end
end

こうする。

Write の複雑さ

上にもあるが

  • 関連する他の Model や外部 API の芋づる呼び出しが多くなりやすい
  • 結果 Callback など暗黙的な複雑な操作が埋め込まれやすい

これは Interactor の導入で対応できるはず。

□ 参考

上のリンク先は CQS ではなく CQRS なので「時間やイベントの概念が入ってきて現在の snapshot を取得しにくいので Read Model を導入しましょう」なんだけど、ベースとなっている CQS でも Read 用の Model のようなもの、Thin Data Layer を導入するのはアリだと思う。少なくともこれで Fat Controller も Fat Model も避けることができる。

■ その3 - PubSub

□ 追加gem

krisleech/wisper: A micro library providing Ruby objects with Publish-Subscribe capabilities

□ 効果

依存のうち、同期的に結果が分かる必要のないもの、即時に rollback する必要のないものを非同期メッセージングで分離することで結合度を下げる。要は decoupling の一つ。

□ 適用範囲

Callback

例えば Model の Callback は使いすぎると暗黙の依存が増えて目的の Model のテストが重くなる3という問題がある。これを PubSub に持っていくことで、テストをメッセージの Publish のテストと Subscriber の実際の動作のテストに分離することができ、Model をテストしやすくなるし、callback を condition で複雑にせずに済む。

その他 C, M が長くなってしまう処理のうち非同期に分けられるもの。

□ デメリット

処理の流れが書いた順番通りにならない。イベントドリブンの考え方に不慣れだと混乱が増す。

Interactor の利用が適しているところに Wisper を持ち出さないように注意したい。結合度は下がるけど複雑さが上がる可能性がある。同期、非同期をきちんと見分けることが大事。result をきっちり活用したいなら Interactor.

ただし、Backend を ActiveJob にすると GlobalID が付与されてトレーサビリティが生まれる。イベントソーシング的に扱える部分を差し込むために Interactor と Wisper を併用するという選択肢もあると思う。

□ 参考

  1. この gem については個々の Interactor を書く際に気にしなければいけないのは context だけ 

  2. この gem に hook があるだけなので中の処理をどうするかはもちろん自分で考える必要アリ 

  3. 実行時間や副作用を閉じ込めるためのセットアップなど。RSpec側の設定でbefore(:all)でskip_callback = trueするという方法はあるけど。 

CI始めました

Welcome to Jenkins CI! | Jenkins CI

なんとなく Java はメンドイという思い込みで避けていたんだけど、先週末にちょっと思い立って Jenkins のインストールを試してみた。Debian で試したところものすごく簡単だったので職場にも起こしてみることにした。

DebianにJenkinsをインストール

Debian 6 にJenkinsをインストール - cactusman日誌

を参考に。

まずインストールはとても簡単。各種パッケージシステム用にパッケージが用意されてるから。

自分は Debian を使ったんだけど、上のエントリと java のパッケージが違う以外は

apt-key add
apt-get update
apt-get install

でイケた。これだけで、

  • jenkins ユーザーの作成
  • daemon の起動スクリプトと必要な設定

が展開されるので、あとは

/etc/init.d/jenkins start

するだけ。ここら辺とてもよく Debianize されてる印象。

Debian + Jenkins の考え方

  • jenkins ユーザーでプロセスが動いて
  • jenkins ユーザーのホームディレクトリ以下に各種情報が集まっていく(そういう設定ファイルが自動でセットされる)

ので、この jenkins ユーザーの扱いがキモになる。

インストール後にやったこと

  • jenkins ユーザーのホームディレクトリ変更
    • デフォルトは /var/lib/jenkins 以下になるんだけど、ここはそんなに容量用意してなかったので、別ディスクに移動
    • jenkins の設定ファイルと /etc/passwd のホームディレクトリ設定の両方変えておく1
  • rbenv 環境を用意して jenkins ユーザーは rbenv を通して ruby を起動するように .bash_profile を設定
    • 今はアプリ一つだけど将来的に増えた際にバージョンが合ってない可能性を考慮して2

※ jenkins ユーザーでの作業は su と source で行いました。ログインできる必要ないので。

ジョブはとりあえず全部フリースタイルで

  • LLのジョブしかないのでフリースタイルで
    • rake spec を叩く sh スクリプト、自前のテストスイート実行の sh スクリプトを jenkins のジョブ設定画面から放り込んでおく。現在の jenkins 環境に特有の呼び出し方なのでこれはリポジトリには入れないでおく3

Rails アプリのビルドは以下を参考にした。

Testing Rails apps with Jenkins - komagata

rvm の gemset は使っていないのでもっとずっとシンプル。要するに bundle install, rake db:migrate, rake spec するだけ。

ちょっとハマったこと

  • ジョブ名にスペースを入れると workspace にそのまま反映されて、sh スクリプトやら make やらパスにスペースが入っている場合を考慮していないものが軒並み失敗する
    • スクリプトをいじるのは面倒なのでジョブ名を変更して対処
  • メモリ不足でスコンスコン落ちる
    • rake spec で全部一度に処理しようとするとメモリが不足して jenkins が落ちる

なんとか節約する。

省メモリ Rails アプリ CI

  • spec:* を一つずつジョブに分割
  • 一つだけ SCM をポーリング
  • こいつをトリガにして別なジョブを次々に起こす

Jenkins 用語で言う上流、下流プロジェクトで対処する。

当然ディスクも手間も食うけど、一気に実行してメモリが足りないという状況は回避できる。CI サーバ落ちたら意味ないので苦肉の策。

PHP は Debian 6 の 5.3 のまま使う

実際は 5.1 が production のバージョンなんだけど、5.3 への移行を考えたうえでの CI の導入なので、Debian 6 の標準パッケージである PHP 5.3 をそのまま使うことにした。

本当は PHP についても phpfarm でも使って複数バージョンを動かせるようにするのも手なんだけど、面倒なのでやってない。

workspaceのディレクトリレイアウトに依存したsetup

PHP のアプリが特定のディレクトリツリーを利用しており、この準備を早く確実なものにするために setup 用のスクリプトを用意している。

ビルド用 sh スクリプトの中で $WORKSPACE という変数を使うと workspace のパスを得ることができるので、例えば他のジョブの workspace にある何かを使うといったこともできる。

テストが独立していなかった

rake spec を分割したところ動かない spec がいくつか出てきた。急ぐときは直接 bundle exec rspec spec/**/*.rb で動かしつつ、全体の rake spec は落ちないように注意しながら進めていたんだけど、spec:* 単位で分割したらあちこち落ちて、かつその修正に意外に時間を取られてしまった。

最初の頃は stub すごいと思って喜んで使って、最近は fixture, fixture replacement も適切に使った方がいいよねと考えながら spec を書いてるんだけど、fixture, fixture replacement を使ったときのテストの独立性がちゃんと確保できていなかったらしい。

まだまだ精進が必要だ。

あと期待していること

これでとりあえずの目的は達成できた。今のところ全自動でちゃんとビルドできるものは多くないのでジョブも少ない。ただ今後はもう少し活用できるんじゃないかと思っている。

外部ジョブの監視

Jenkins には XML の POST でジョブの結果を受け付ける機能がある。これは Jenkins の動いているホスト上で直接何かを実行していなくてもジョブの結果だけを扱えるということだ。

例えばあるホストの cron の記録をメールではなく XML にして Jenkins に POST すれば成功、失敗を記録でき、失敗のときだけメールを飛ばすということが可能になる。以前から cron のメールが多すぎてロクに見ないことを問題に感じていたので、これで少し扱いやすくできないかなーと目論んでいる。

p.s. ユニットテスト始めました から6年半か…。ずいぶん経ったな。

  1. 実際には一通り設定し終わってから容量の問題に気づいたのでちょっと苦労した。先にちゃんと設計しよう。 

  2. やはり rbenv rehash を忘れますね… 

  3. 入れて呼ぶ方がビルドスクリプトもバージョン管理できるから良いと書かれてるけど 

特定の namespace の中で const_get()

先日 Rubyで変数から定数を得る で書いたように const_get() すると与えた文字列から定数を得ることができる。1

これ、特定の namespace の中でどうするんだろうと思って

const_get( 'Klass::Konstant' )

みたいなことして怒られてた。正解は

Klass::const_get( 'Konstant' )

でした。

なるほどな。

  1. もちろん定数の規約には合致してる必要がある。 

OpenSSH を Zebedee 代わりに使う

Zebedee は便利なソフトである。wakatonoさんただただしさんの紹介、何より、えーすいませんお名前を忘れましたが man を和訳してくださっている方によって我々日本語ネイティブにも馴染みのトンネリングツールである。

しかし今回の話はこの Zebedee をやめて ssh によるトンネルにしましたという話。

なお、お前これでほんとに運用してるの?というひどい勘違いがある可能性もあります。その場合は怒濤の勢いで突っ込んでください。

なぜ Zebedee でなく ssh なのか?

某所で使っている Zebedee トンネルの調子がずいぶん前から悪く、rsync が転送途中で何も言わなくなり、それだけなら timeout を設定して何回かトライすることで対処できるが、サーバが突然落ちたり、動いてるけど全然機能しなくなっていたりしたのに業を煮やして Zebedee をやめることにした。1

なおこれは恐らく経路とホスト環境が絡んだ複雑な問題であり、クライアント側が同じネットワークで別なサーバに向かって張っているトンネルについては支障なく利用できているなど謎な部分が多く、

Zebedee そのものがタコだからやめるわけではない

ということは一応断っておく。

Zebedee をやめて ssh を使うことについては別に ssh が大好きだからという理由ではなく、

  • ssh 越しの rsync は途中で切れないことを確認済み
  • ssh 以外に別なトンネルソフトを発掘して調べて設定するのが面倒

という理由による。特に「切れないことを確認済み」というのが大きい。これ以前から ssh は session 開始に掛かる時間や転送速度の問題はあるものの「通信が強い」2という印象を抱いていたので、「切れるのが問題ならもう ssh にしちまおう」と思った次第である。

※ しかし特にシステムアカウントを一つ用意しなければいけない ssh トンネルは正直あまり使いたくなく、他によいアプリがあるなら教えていただけると嬉しいです。あと rsync したいだけなら別にこんな面倒なことしなくてよくね?と思われるかもしれませんが、「詰まる」という現象が rsync の際によく起きるというだけで rsync しかしてないわけじゃないです。iptables などで接続元を制限するだけでなく通信路の暗号化も必須要件です。

ssh のデメリット

まず Zebedee のメリットを確認しておこう。

  • システムアカウントを用意する必要がない
  • 転送できるホスト、ポートをサーバ側で細かく制御できる
  • Zebedee C/S 間の通信は暗号化される
  • IPアドレスによる接続制限だけでなく公開鍵認証が利用できる
  • Zebedee クライアントを動かしているホストだけでなく、他のホストも Zebedee のトンネルに相乗りできる(アプリケーションレベルではあるけれど LAN 間のトンネルに使える)
  • TCP も UDP も転送できる
  • 実際にトンネルを利用するアプリが通信を始めるまではトンネルを張らない(通信の節約)

このうち ssh にないメリットは以下になる。

  1. システムアカウントを用意する必要がない
  2. 転送できるホスト、ポートをサーバ側で細かく制御できる
    • (これは OpenSSH でも 4.4 以降なら可能)
  3. TCP も UDP も転送できる
  4. 利用するときにだけトンネルを作成する

この Zebedee にあって ssh にないメリットは、逆に言うと ssh のデメリットなわけだけれど、このうちシステムアカウントについてはあれこれ対策を盛り込むことで、UDP の転送は基本的に必要ないので我慢することで、転送ホスト、ポートの制御は結局のところ信用できないユーザーに解放するわけではないので我慢、というなんだか消極的な判断を重ねたことをまずお断りしておく。これが我慢できない人にはオススメできない。

port forward 用の ssh の設定

でまぁなんとか我慢できる状態に落ち着いたので以下その設定を紹介しておく。

  • 専用のアカウントを作る(クライアント / サーバ)
  • shell を制限する(サーバ側)
  • chroot jail(サーバ側)
  • GatewayPorts yes(クライアント側)
  • ssh -N で転送以外何もしない接続(クライアント側)
  • autossh で接続や ssh クライアントアプリの不調を回復(クライアント側)

結構やることが多い。ほんと、Zebedee が安定して動くなら絶対に Zebedee の方がいい。

その前に確認事項

  • 利用した OpenSSH は 4.3
  • プロトコル 2 だけ使おう
    • 一部のオプションはプロトコル 2 でしか動きません
  • 利用したプラットフォームはクライアント/サーバともに Linux だけど、クライアント側は cygwin を使えば Windows でも実現可能
  • 認証は空パスフレーズの公開鍵認証を利用
    • 秘密鍵の管理はちゃんとすること。共用する秘密鍵にパスフレーズがあるかないかはもう気休め程度の問題だと思う。
  • これを port forward 用の機械にセットして、他の機械はこの forward 用の機械に接続にいく

例えば今回の目的の一つは rsyncd との間での通信なので

+----------------+
| USER -> PROXY -+-(暗号化)--> SERVER
+----------------+

という繋ぎ方で

rsync rsync://PROXY:PORT/MODULE LOCAL

みたいな使い方を想定。

※ 完全に余談だけど公開鍵認証を使うと authorized_keys で no-port-forwarding を指定することで port forward を禁止するなど、Match を使えないバージョンでもある程度ユーザーの権限の制御ができるし、絶対に公開鍵認証にしておいた方がいいですよ。詳しくは man sshd.

専用のユーザーを作る(クライアント / サーバ)

今回の目的は特定の人間が port forward を利用することではなく、できたトンネルはみんなで利用するものなので、それ用のアカウントを用意してしまった方が設定を分離、集約できて便利。

※ 実際にはサーバ側は後で触れる scponly の chroot_setup.sh でユーザーを作ったので今回は何もしていないんだけど。

クライアント側は普通にユーザーを作って ~/.ssh/ に config, 秘密鍵を置けるようにしておく。クライアント側でユーザーを作るのはこの鍵と設定ファイルの置き場所を作る以上の意味はないと言ってもいいかも。

port forward 用のユーザーの shell を制限する(サーバ)

ssh は Secure Shell であり、基本的には ssh アカウントはシステムの操作を行うために作成するものである。しかし今回の目的は port forward のみ。やはりそれ以外には利用できない状態にしておきたい。LAN 内にこのアカウントを悪用する人がいないとも限らないし、万が一鍵が漏れたらエラいことである3。しかし remote shell が terminal でアレコレ作業するのを許さない制限 shell であればとりあえずこのアカウントで余計な作業はできなくなる。4制限 shell として自分が知っているのは

で、どちらも chroot に対応している。

rssh は最近あまりブログなどで話題になっているのを見ないが、scponly と違ってあまり場当たり的に機能追加を行わないことでセキュリティを保つことを意識して作られており、また sshd とは別個に rssh.conf によってユーザーごとに細かく設定を調整できる柔軟性を持つ。

scponly は便利な機能を使いたいという要求に次々応えているような印象。scp, sftp だけでなく現在のバージョンは rsync も unison も svn にも対応している。(rsync は rssh でも利用できる。が、いずれにせよ今回 rsync は rsyncd への接続という形を利用するので制限 shell での対応もそのための設定も必要ないけど。)

chroot環境の構築(サーバ)

shell を制限しておいても scp や sftp でシステム全体へアクセスできてしまえば危険なのに変わりはない。そこで chroot 環境に閉じ込める。

今回自分は scponlyc5 で chroot jail を作成した。これは

付属の chroot_setup.sh が扱いやすい

ということに尽きる。rssh にも source tarball の中には chroot setup 用のスクリプトが付属しているのだが、これだけではどうにもうまくいかない。6rssh そのもののアップデートが活発でないことから、この辺りの問題をツール側の更新を待って解決に当てるというアプローチの採用は難しく、自分なりに rssh のノウハウを身につけて対処するしかない。ちょっと面倒なのでこれはパス7

ただし扱いやすいとは言え chroot_setup.sh も source tarball の中にあるものであり、configure 時に必要なパラメータをセットして作られる sh スクリプトであり、かつ完全に動作する sh スクリプトが全自動でできるとは限らないので注意は必要である。

しかし、それでも rssh のセットアップツールよりは楽。うまく動けばアカウントの作成、ホームディレクトリの作成、必要なバイナリのコピー、permission の設定まで自動で行ってくれる。Linux でも *BSD でもうまく動く。useradd にも pw にも対応しており、passwd データベースの取り回しに気が利いている。rssh はユーザーごとに設定を柔軟に変更したい場合や umask を設定したい場合には便利だが、port forward 目的に制限したいのであれば scponly の方が楽だと思う。

なお、chroot jail を作成したときに /etc/localtime をコピーしておかないとログの時間がずれるのと、FreeBSD では chroot jail の中に /dev/null を作っておかないと動かないのは注意が必要な点。Linux では /dev/null はなくても動いた。

GatewayPorts yes(クライアント)

これは Zebedee と対比させると以下のような設定の意味である。

 Zebedeessh
localhostに限定localsource trueGatewayPorts no(デフォルト)
他のホストも利用可能localsource false(デフォルト)GatewayPorts yes

ssh はもともとトンネル用のツールではないので、デフォルトでは実際に ssh の接続を行っているホスト以外からは port forward は利用できない。これを利用できるようにするのが GatewayPorts yes の設定である。

others --(ここの可否)--> ssh client --> ssh server

ssh -N で接続(クライアント)

これは

  • OpenSSH は scp, sftp での接続時に自動的に port forward の設定をクリアしてしまうので port forward 目的では ssh クライアントで接続しなければならない。
  • shell が rssh の場合は ssh で普通に接続してもすぐに切れてしまう。scponly の場合は WinSCP 互換モードというものがあり、これで session を維持してくれる。が、どちらにしろ tty を解放しないとサービス化できないので、-N は必要。

という理由による。ssh -N の意味は man を読むこと。

ssh_config

というわけでできあがった ssh_config はこんな感じ。以下のような感じのファイルを ~/.ssh/config に置く。

Host PROXY
  Hostname PROXYHOST
  User ACCOUNT FOR PROXY
  GatewayPorts yes
  LocalForward PORT HOST:PORT
  LocalForward PORT HOST:PORT
  LocalForward PORT HOST:PORT

autossh

Zebedee はトンネルを利用した通信の要求が発生したときに初めて Zebedee 自体のコネクションが結ばれる。ssh にはそんな機能がないので8、autossh を使って繋ぎっぱなしにする。autossh は ssh コマンドを叩き、かつその ssh の接続を監視し、切れたりすると自動的に再起動してくれるもので、ssh でトンネルを掘るという話で調べるとたいてい autossh がセットで出てくる。ということで恐らくこれが標準的に使われているものなのだろう。

この使い方は

autossh -M [port] -f [SSH OPTIONS]

で、例えば

  • port forward 用のアカウントが forwarder
  • ~forwarder/.ssh/config に設定がある

場合はこんな感じで叩く。

sudo -u forwarder autossh -M 0 -f -F ~forwarder/.ssh/config -N PROXY

ここで

  • -M 0 は autossh 自体の監視ポートを消費しないための設定
  • -f は autossh をバックグラウンドに回すためのオプション
  • -F は ssh の config ファイルを指定するオプション
  • -N は上で触れましたね
  • `PROXY' の意味は上の config 参照

これでうまく動くなら起動時に動くようにするまでもう一息。Linux だと /etc/init.d/ 以下に template があったりするのでそれを使ってシコシコ書いていく。システムの起動スクリプトに書く場合は

-F ~forwarder/.ssh/config

の記述はフルパスにしておいた方がいいと思う。

補足

サーバに localhost からの接続と思わせる

tunnel サーバと転送先のサーバが同じ場合、サービスを提供しているデーモンから見たときに接続元が localhost として見えるかそうでないかは意外に大事だったりする。例えば外部からの TCP/IP 接続は許していないが、localhost からのみ許可するような場合である。図にするとこういう形。

               +------------------------+
ssh client --> | ssh server --> service |
               +------------------------+

これ、service に localhost からの接続として見せるための設定が Zebedee と ssh で違う。

Zebedee の場合

serverhost xxx.example.com

tunnel III:xxx.example.com:JJ
tunnel MMMM:xxx.example.com:NNNN

という具合に serverhost と tunnel の行の host を合わせておく。

ssh の場合

Hostname xxx.example.com
LocalForward III localhost:JJ
LocalForward MMMM localhost:NNNN

と、転送先を localhost にしておく。

OpenSSH 4.4 以降を使ってさらに安全に

OpenSSH は 4.4 以降で PermitOpen という設定項目が追加されている。これは転送先の制限をするもので、sshd_config 上で

PermitOpen HOST:PORT[ HOST:PORT HOST:PORT ...]

のように指定する。複数書く場合はホワイトスペースで区切って列挙する。`any' と書けばすべての転送が許可される。デフォルトは any.

GatewayPorts のときと同じように Zebedee と比較すると以下のような形だ。

 ZebedeeOpenSSH
パラメータtargetPermitOpen
デフォルトすべて禁止すべて許可9

Zebedee では target で明示した転送先にしか転送できない。対して OpenSSH の方では AllowTCPForwarding yes で転送そのものを許可してしまえばどこへでも転送可能である。PermitOpen という設定の方が後から登場しているのだから当然なのだけど。

また authorized_keys 上で

permitopen="HOST:PORT[,HOST:PORT,...]" key

と指定しておけばその鍵で認証された接続はここに書かれた転送先にしか繋がらなくなる。つまり、全体の設定と接続ごとの設定を別々にできるのだ。

通常、クライアントはこの PermitOpen で転送先が制限されているかどうかに関係なくサーバに接続し、forward 用の port を開いて listen する。しかし実際にはその port に繋いでも何も起きない。こうした事態を防ぐにはクライアント側で

ExitOnForwardFailure yes

と設定しておく。こうしておくと tunnel が確立されなかった場合に ssh クライアントは異常終了する。


注にも書いたけど、PermitOpen と同じく 4.4 以降で登場する Match を利用すると、例えばこの転送用の鍵を特定のネットワークからの接続にしか使えないように制限できるんじゃないかと思っているんだけど、サンプルが全然見つからなくてまだ分かっていません。制限できるなら例えこの鍵は漏れても平気ってことになるんだけど、そういうのは無理なんですかね?

  1. 2.4.1 -> 2.5.3 のアップグレードをしたら良くなるかと思ったら返って悪くなった。 

  2. 切れない 

  3. Match を使いこなせれば特定のユーザーが特定のネットワーク以外から接続できないように設定を絞ることができるんじゃないかと思っている。そうなれば鍵が漏れても心配は要らない。いや管理に問題があるという事実には変わりないけど。ただ今回はサーバ側が 4.3 なのでその部分の検証は行っていない。 

  4. もちろん自由に port forward できたらかなり危険なのは間違いないので、shell を制限するだけじゃダメだけど。 

  5. scponlyc は scponly の setuid して使う chroot 用バイナリ 

  6. FreeBSD 6 と CentOS 5 で実験。 

  7. 実は今回の話とは別に試してみたことはあるんだけど、個人的にはうまくいかなかった。 

  8. ないよね? 

  9. AllowTCPForwarding yes は必要 

今さら Trac の感想

※ もうこの「今さら〜」というタイトルの付け方がマンネリだなぁ。

さて、頭の回転の速い Subversion 使いのみなさんにはもうすっかりお馴染みの Trac だが、わたしゃどうも道具を使いこなすまでが遅くてなかなかついていけない。しかしえっちらおっちら試してきた過程で思いついたことは一応書き留めてある。これから Trac を試してみようとか、ちょっと試したけどなんか思ったように動かんかった、という人に少しは役に立つかもしれないのでこのメモの抜粋を公開しようと思う。嘘、不足はツッコミよろ。

なお、以下のメモは FreeBSD 上で Trac 0.9.3 を動かしたときのものである。

まず Trac って何?

Subversion リポジトリブラウザ & Wiki & BTS(Bug Tracking System) の統合ツール。trac ロゴには Integrated SCM & Project Management って書いてあるように、Web ベースのプロジェクト管理ツール、という位置づけでそんなに外れてないと思う。(進捗管理とかできないけど)

Trac を動かすには何が必要?

  • Webサーバ1
  • Python
  • SQL系のDBMS(SQLite でも動くのでそんなに難しくない)
  • Subversion

CGI, FastCGI, mod_python で動く。らしいけど、自分で確認したのは CGI と FastCGI のみ。

Windows でも Linux でも MacOSX でも動かせそう。個人的には FreeBSD でしか試してないけど、Windows なら All-In-One-Trac というものもあるので、必要なものが少なくない割には導入は楽になってきているんじゃないかと思う。

どういう人が使うと嬉しい?

Subversion を使って何らかのデータの管理を行っている人。cvs の場合は cvstrac という C で書かれたツールがあるようだが、試してないのでどんなもんかは知らない。

何ができない?

qwikWeb みたいにメールで Wiki の更新とかできないはず。ただし BTS(Ticket) の変更をメールで通知する機能はある。ViewCVS と違って特定のディレクトリ以下の tarball を download する機能はない。

インストール記事は書きません

頑張ってくれ。

セットアップするときの注意点

必ず svn リポジトリが一つは create されてること。

[2006-09-14 追記]svn リポジトリを cvs2svn で作成した場合、どうもデフォルトでは svn:eol-style=native という属性が付加されるっぽい。こうすると例えば Windows と Linux が混在している環境では「リポジトリ上では同じだが、手元でいじっているファイルの改行コードは違う」という現象が起きる。で、この working copy のファイルを利用して何かをしようとすると改行コードの違いが思わぬ副作用をもたらす可能性があるので注意。

deploy ツールを用意しろってことかなぁ。

重いので fastcgi か mod_python がオススメ

最初 CGI で動かしたんだけど、自宅サーバではびっくりするほど重かったので、fastcgi か mod_python がオススメ。マシンパワーが余っているか、気の長い人なら CGI でも大丈夫だと思う。

あと、Apache 2 用の fastcgi のセットアップのドキュメントで

<IfModule mod_fastcgi.c>
  FastCgiIpcDir /var/run/fastcgi
  FastCgiConfig -initial-env TRAC_ENV /var/lib/trac/HOGE
</IfModule>

みたいに書いてある場合もあるんだけど、fastcgi で TRAC_ENV を設定する場合は = で結んでおかないといけないので、上のままでは動かない。

<IfModule mod_fastcgi.c>
  FastCgiIpcDir /var/run/fastcgi
  FastCgiConfig -initial-env TRAC_ENV=/var/lib/trac/HOGE
</IfModule>

にする。(個人的には fastcgi の設定は lighttpd の方が楽だと思う。)

mod_python は自分では試してねっす。fastcgi は落ちたときに自動的に再起動してくれるとか便利な機能があるので楽ちんだと思うんだな。

マルチバイト文字(含む日本語)の問題

基本的には UTF-8 で扱うので日本語についても問題なく使える。UTF-8 に対応しているブラウザで編集すれば Wiki についてはなんら問題ない。trac-ja が提供されているが、これは基本的に UI やマニュアルの日本語訳であって、日本語の処理に問題があるためではない。2

問題は Subversion で管理するテキストファイルのエンコーディングが UTF-8 じゃない場合。これの解決方法は二つある。3

  1. プロジェクト全体のエンコーディングを trac.ini の default_charset に指定する
  2. 個々のファイルについて svn の機能でエンコーディングを指定する
svn propset svn:mime-type "text/plain;charset=ENCODING" filename

基本的には 1 の方法だけで問題ないはず。というのもこういうツールを使う人はもともとプロの開発者とかその手の人が大半なので、ある単位のファイル群のエンコーディングがバラついていることはまずあり得ないから。

ただしこれが個人的なファイルをとにかく突っ込んでいる場合や Web サイトのファイルを管理したいという場合はこの限りではないと思う。特に Web の場合、サーバサイドスクリプトでは内部エンコーディングとブラウザとの入出力に使うエンコーディングが違う、それに伴って CSS やクライアントサイドスクリプトのエンコーディングが違う、ということはよくある。

この場合は地味に svn propset & svn commit していくしかない。サブディレクトリが見つかると4そこで svn propset は「止まる」ので、あまりたくさんのファイルに対して設定するのはしんどい。もうその場合はプロジェクトを分けちゃうのが正解だと思う。5あるいはコード中にはもうマルチバイト文字は書かない。そう思った理由は以下のリンクに関する記述を参照されたし。

あ、そうそうまだ試してない(まだ試し始めてから ascii の comment しか書いていない)けど commit log も UTF-8 で書かないといけないと思う。

[2006-08-02 追記]subversion 1.1 以降で gettext がリンクされていて locale がちゃんと設定されていれば、svn コマンドは locale を解釈したうえで UTF-8 に変換して commit する模様。どこかで拾った OSX 用 SVN バイナリは locale を解釈しなかったので Fink 版に変えた。

ツールを横断するリンクが便利

Wiki と BTS とリポジトリが一つのツールで扱えてとても便利だなと思うのは、それぞれを簡単にリンクさせられる点。Wiki からソースブラウザ、commit log から BTS へ簡単にリンクが張れる。これはかなり便利に使える。

ということは添付ファイルやめて svn で管理しちゃえばいいじゃん

最近の Wiki では添付ファイルの機能はそれほど珍しくない。Trac でもページに対してファイルを添付して、それをページから参照することができる。ただしこの添付ファイルはほんとにただ添付できるだけ。svn も Trac も履歴管理がバッチリできるのに添付ファイルは蚊帳の外なのであまり嬉しくないのだ。

そこで上のリンク機能。添付したいファイルを svn リポジトリに突っ込んで、Wiki からそこにリンクしてしまえばよい。画像の場合はこれに Image マクロを組み合わせるとそのままページ内に表示できる。具体的にはこんな感じ

[[Image(source:foo/bar.png)]]

で Wiki に書けばよい。なんて便利。これで Wiki のテキストも画像も全部履歴管理できるという塩梅だ。

svn リポジトリに dav アクセスできれば全部 HTTP で済むし、ファイルを編集してブラウザから添付し直して…なんて面倒な操作をしなくて済むのがすごく嬉しい。

認証は単なる BASIC 認証

インストール直後、Web サーバの認証を設定していない状態で login のリンクを辿るとエラーが出る。なんじゃこれと思ったが、

svn の dav アクセス用のユーザーDB をそのまま利用すればいいじゃん

という方針らしい。なんとスバラシイ発想。ま、逆に言えば dav アクセスを許可するつもりがなかった場合はあんまり嬉しくないんだけど。

認証を経ないと使えない機能があるのかどうかはよく知らない6。まだそこまで使い込んでないんで。編集時のユーザー名はその場で記入できるので、普通に Wiki を編集するためには認証は必要ない。ただ login していると Wiki の編集時に自分の名前を記憶しておいてくれるので、その辺は楽。BASIC 認証の設定を済ませたら、せっかくだから dav アクセスも許可しちゃおうかとか思ってきちゃうから不思議だ。(lighttpd を使っている場合は違うか。)

いろんな RSS が取れて便利

CVS で管理しているプロジェクトでコミットの様子を RSS で吐かせようと思うと一工夫いる。しかし Trac では本当に様々な情報について RSS が提供されている。特定のファイルの更新を監視するなど、楽勝でできてしまう。これはいい。

今のところのまとめ

現在は ViewCVS + PukiWiki で似たようなことをやってはいるが、Subversion の CVS に対するアドバンテージ、ツールが統合されていることによる利便性がとても気持ちよい環境である。

一方 Wiki 単体ではプラグインの豊富さや日記形式の記入が楽に行えるなど、PukiWiki のアドバンテージももちろんあるので、全面移行というのはまだちょっと難しいように思う。逆に今までそういう環境を構築していない場合はとりあえず使っとけと言って間違いないと思う。多少セットアップが面倒でもお釣りはくるはず。

あ。ViewCVS と違って tarball のダウンロードできないなぁ。まぁ要らないか。要らない要らない。要らないってことにする。

  1. 個人的には Apache2 と lighttpd で確認 

  2. 一部バグがあるという情報もあるが、これまで利用してきた範囲では特に困ったことは起きていない。あーでも日本語のファイル名とかは知らない。 

  3. もう一つ、default_charset でも svn propset でもなく、Trac を hack して文字コードの自動判別の機能を付加するという方法を採っている人もいるが、ここでは考えない。 

  4. 「”管理対象外”のリソースが見つかった場合」かもしれない。 

  5. ディレクトリの階層さえどうにかなればリポジトリが分かれていなくても Trac 上のプロジェクトは分離できる。 

  6. 添付ファイルの削除や Wiki ページの削除はデフォルトでは anonymous ユーザーでは行えない。この辺は TracPermissions というページに解説があり、設定は trac-admin で行うので目を通しておくとよい。 

CNET Japan の幅が広がってうっとうしくなったな

つーか Feed でチェックだけしといた記事の本文が飛んでなくなってるし…。トラックバックは見えるけど本文が見えないっていういやんな状態になってる。

つか障害の原因てまだ調査中なんだ。そら大変だ。とりあえずこっちはこの使いにくいデザインへの対策を考えよう。ってことで。

ステレオタイプなひとびと

ラーメンを食べに行った。たまたま前の車も同じラーメン屋に入った。その車はいかにもスポーツスポーツしているんだけど、ちょっと妙なステッカーを貼っていた。

ピン。

ときてしまった。「これはヲタク車だ。」そう思っていたところ、中から本当に無駄に脂肪を着込んだ野郎どもが出てきた。髪も無駄に伸ばしていたりしている。まだこちらは車から降りていなかったのを幸いに、思わず爆笑してしまった。シティボーイズのライブでも言っていた。「かっこいいスポーツカーに乗っている人はデブが多いね」。別にデブにもヲタにもまったく偏見はない1んだけど、本当に漫画かと思った。

そして店内では携帯片手にパソコンの話をしていた。「USB の調子がさぁ…」。そんなに分かりやすくなくたっていいじゃないか。そういえばこの手の会話って、たいていハード寄りなのはなんでだろう。やっぱ「モノ」と「所有」がオタクの基本原理なのだろうか。

それとは別にそのラーメン屋はもう二度と行くまいと思った。店の味が変わったのか自分の味覚が変わったのか分からないけど、なんかこりゃもう合わないなと。

素敵な日曜日だ。

  1. 自分だってオタクだ。 

[MS][[MyIE2|http://www.itmedia.co.jp/news/articles/0404/09/news055.html]]

だいたい、IE コンポーネントブラウザが IE を駆逐できるわけないじゃん。IE 依存なんだから。なんだこの記事は。

Web アプリのドキュメントをもっと書きやすく

ドキュメントというと、Excel を方眼紙にした1関数1ページで仕様書を書くというふざけた習慣が一部にある。これは著しく生産性が悪いし、絶対に撲滅させるべき習慣だが、こうした、まだ「システムといえば大掛かりで当然」だった頃の古い体質に学ぶことが一つある。入力と出力を常に意識している点である。

なぜこうしたことを思ったかと言うと、ある Web アプリのある画面に関するコメントで、

  • 入力(これは当然ブラウザからくる)
  • 出力(画面用)
  • 出力(他の画面に渡す用)

の3種類に変数が整理されて書かれていたものを見かけたから。

これは書くのは面倒だが実はありがたいコメントである。しかしドキュメント作成システムと相性が悪い。(ここでは PHPDoc を念頭に置いているが、)ドキュメント生成システムは原則的にクラスとメソッドに関するドキュメントを自動生成するものである。「画面」に対するドキュメントではない。これは PHPDoc のもとになった JavaDoc の性格を引き継いでいるのだろうが、Web アプリの場合は「画面1つ」に対して必ず何らかの「プログラムファイル1つ」が対応する。(もちろんモード分けによって画面数よりファイルが少なくなることはよくあるが。)ここが問題になるのだ。

何を言いたいかというと、GUI アプリと違って、Web アプリの場合は画面とドキュメントがうまくかみ合わないということである。GUI アプリはウィンドウやダイアログにクラスを割り当て、それらに対して操作を行うので、画面とドキュメントはスムーズに対応する。しかし Web アプリではそういうきれいな対応は作れない。

もちろん上の方法に倣ってすべての「画面」を「ページインスタンス」と見做し、全部オブジェクト指向で書くという方法もあるだろう。これならドキュメントシステムとの相性はよい。それが実現可能な言語ならそういう方法はアリだ。

でもそれは激しく書きにくいだろう。今度は HTML との相性が悪くなるし。

あ。でも画面を全部テンプレートに追い出して全部 OO で書くならイケそうだな。サーブレット + JSP な現場はどうなっているのかな? 調査が必要かもしんない。

最近すっかりオブジェクト脳だ。

書くスピードが上がっている

久しぶりに Dolphin のステータスを確認したら 5000位を切っていた。おおざっぱに言うと 3ヶ月で 300万弱をタイプ。なんだこりゃ。

なんで Dolphin をふと思い出したかというと、実はすでにキーボードの Enter や右 shift の辺りが「キシキシ」言い始めたから。自前の ThinkPad はまだ元気なのでこのキーボードがダメなのか、あるいは「以前よりタイプ量が増えたか」。

で、ちょっと考えたら、以前よりやっぱ打ってる気がする。もともと打つ/書くのは好きだが、Wiki の導入で以前より断然楽に書けるようになったってのが大きいのかもしんない。(最近は Wiki スタイルで書きやすさが飛躍的に向上したこの tDiary も貢献している。)

Wiki 以前は

  • 保存ファイル名と保存場所をどうするか

を悩むプロセスがあった。時系列にメモファイルをどかどか増やすのは好きじゃなかったので、書く段階で整理先を決めるようにしていたのだ。しかしこれはやはり書き始めるまでの障壁が高い。

しかし今は Wiki や tDiary でとにかくその日の記録として書き出してしまうことにしている。これは

  • ファイルの存在を気にしなくてよい
  • 検索の機能がついている

の2点がやはり大きい。また、案外

  • あとで整理しやすい

というのもある。これらが安心感となって、メモ書きのペースが上がっているってわけだ。もちろんメモだけでなく資料作成にも使っているので、なんというかライティングそのものがシームレスなのである。日常と業務が切り離されていない感覚。これはもちろん良し悪しだが、仕事モードに切り替えることなく仕事に役に立つメモが取れるし、趣味に役に立つ記録を作成できるし、仕事しながら日常生活のメモもできる。エディタや付箋ソフト、メモソフトなどあれこれ使い分ける必要もない。

以前から「起きている間ずっとキーボードに向かう」ような生活だったが、便利な道具の出現によりそれがさらに加速されているって感じだ。

この「書きやすさ」は 20年を越える自分の PC 歴の中で、スクリプト言語の習得と同じかそれ以上の革新である。革新のほとんどはここ数年で起きているってのがなんだかビミョーだが、革新を支えるスキルを地味に磨いていた(Web サーバや Unix を扱えるなど)ことの賜物だろう。と思うことにする。

ほーらあっという間にこんだけ書いちまった。ただ、以前より内容のダブりが増えているような気もする。同じ内容をくり返し書いているのだ。これは内容でページを起こさなくなってきているからなんだよな。まぁ、量から質を抽出する戦法ということにしよう。

AdobeReader SpeedUp

VB ランタイムが必要なのが残念だけど、効果はある。しかも面倒なセットアップは考える必要なし。凝るなら別だけど。設定に

  • Fast
  • Turbo

の項目があらかじめ用意されていて、それを選ぶだけで ok。Turbo にしても普段使う

  • 閲覧(ズーム)
  • 印刷
  • テキストのコピー

なんかには影響ない。(Adobe Acrobat 5 で確認。)影響出るのは PDF 以外のフォーマットへの書き出しやメール送信、注釈や署名関係の機能が。[ ツール ] メニューでは Distill 以外全部消えたので、Reader で Turbo にしたら [ ツール ] メニューはなくなるのかもしんない。 元の設定に戻すのも AdobeReader SpeedUp から Restore Original Configuration を選ぶだけなので楽だ。しかもほんとに Read するだけの人は設定を戻す必要も恐らくない。

あ、インストーラはないので自分で適当なフォルダに放り込んで起動しやすく設定しておくこと。

AppleRemoteDesktop

http://www.apple.co.jp/remotedesktop/

マックセクション@/.-j を見ていたらタイトルのものを見かけた。調べてみるとこれがかなりいい。何がいいって、AppleNetworkAssistant もコントロールできる点。これでコンソールに抵抗のある人たちにも OS X を手軽にリモートコントロールできるだけでなく、Classic 混在環境でも使える、と一粒で二度おいしい。(誤解なきように書き添えておくと、RemoteDesktop の用途はこれだけではありません。)

残念ながら Mac 依存なので Windows との混在環境で管理を楽にしてくれるわけではないけれど、VNC の動きがイマイチな Mac 環境の強い味方ですな。

久しぶりに K-Meleon なぞ

http://kmeleon.sourceforge.net/

いつの間にやら K-Meleon 0.7 (と SP1)が出ていた。Mozilla 1.2 ベース。試してみると、XUL を捨てているおかげで起動がめちゃめちゃ速いのは相変わらずだし、メモリ消費量が Mozilla の半分くらいになってていい。0.6 のときに文字化けで悩んでいたのは Web を探したら解消法が出ていたし、文字サイズなんかは Mozilla の js ファイルからパクってきた設定を書いておけばだいたいオッケー。なかなかいい具合。

だ・け・ど。

Favorite Icon が表示されないのと、URL のところでオートコンプリートが効かないのが痛い。個人的にはブックマークは使わず、Wiki と Google と記憶を頼りの手入力でネットサーフィンするので、補完してくれないと使えないし、自分でサイト作ったり、あちこちで同じ Wiki が動いていると favicon なしではつらすぎ。

あと、ブックマークとタブ上で日本語が化けます。まだかな。(一応この更新は K-Meleon から。)

About

例によって個人のなんちゃらです