Roswell時代のCommon LispのWebアプリケーション運用
最近Quickdocs.orgをフルスクラッチしました (2回目)。
今回のメインは裏側の月次バッチ処理でしたが、Webアプリ部分も多少変更をしました。特に運用方法が、Roswellを全面的に使うことで大きく変わりました。
Common LispのWebアプリ開発では、未だにこのブログの以下のエントリが参照されてるっぽいですが内容もかなり古くなっています。なのでこの機会に現代に合わせてアップデートしておこうと思います。
Roswell
何はともあれRoswellが必要です。
Roswellの紹介記事は以前書きました。
RoswellはCommon Lispスクリプトを簡単に実行できる機能があると共に、その配布も簡単にしています。
ros install <ライブラリ名>
を実行すると、ライブラリのroswell/
ディレクトリ以下のスクリプトを~/.roswell/bin
以下にコピーして利用可能にしてくれます。
使うライブラリ
QuickdocsではWebフレームワークにCaveman2を使っています。
Caveman2はClackに対応していてDBライブラリとの連携もある軽量なWebフレームワークです。
合わせてDBアクセスはdatafly、テンプレートエンジンはDjulaを使います。どちらもCaveman2のデフォルトです。
アプリケーション起動
開発時はREPLで (quickdocs-server:start)
を実行すればHTTPサーバが起動しますが、デプロイすることを考えるとシェルから実行できる必要があります。
Clackはシェルコマンドの「clackup」を提供しているのでRoswell経由でインストールします。
$ ros install clack
Webアプリのプロジェクトルートにapp.lispというファイルがあるので、それをclackupコマンドに渡せばHTTPサーバが起動します。
$ clackup app.lisp $ clackup --server :woo --port 38080 --debug nil app.lisp
ちなみに以前のQuickdocsはFastCGIを使っていましたが、今はWooで動いています。
ライブラリのバージョン固定
Webアプリケーションを長く運用していると、最新のライブラリではいつの間にか動かなくなっている、というようなことがあります。うまく動き続けている間は問題がありませんが、サーバの追加や移転をしようとしたりするときに困ります。
また、他の誰かがアプリケーションを動かすときに、自分と同じライブラリバージョンを使わないと挙動に齟齬が生じます。
Qlotを使えば、使うライブラリのバージョンを固定できるのでこれらの問題は解決します。clackupと同様、Roswell経由でqlotコマンドをインストールします。
$ ros install qlot
プロジェクトルートにqlfile を作って、qlot installをするとプロジェクトローカルにQuicklispがインストールされ、qlfile.lockができます。バージョンの固定にはこのqlfile.lockが必要なので、qlfileと合わせてコミットしておきます。
プロジェクトローカルのQuicklispを使うにはqlot execします。
$ qlot exec clackup app.lisp --debug nil --server :woo --port 38080
ホットデプロイ
デプロイ時に既存のプロセスを殺して新しいプロセスを立ち上げると、どうしてもサービスのダウンタイムが発生します。
これを防ぐためにServer::Starterを使ってclackupを実行します。これは新しいプロセスをforkしてHTTPサーバを立ち上げるのでportの競合なくファイルディスクリプタを新旧プロセスで共有し、新プロセスが正常に立ち上がったら旧プロセスを殺します。
clackupはServer::Starterに対応しているので使うのに特に難しいことはありません。
$ start_server --port 38080 -- qlot exec clackup app.lisp --debug nil --server :woo
死活監視
アプリケーションプロセスが、何らかの要因でいつの間にか落ちてサービス停止するのを防ぐためにプロセスの死活監視を行います。これにはSupervisorを使います。
リバースプロキシ
アプリケーションサーバの前段にNginxをリバースプロキシとして置きます。ここではアクセスログの出力や、エラー発生時にエラーページを返したり、その他静的ファイルの配信を行います。
デプロイ
デプロイ時に毎回SSHしてgit pullして再起動するのは面倒なので、ローカルから簡単にデプロイできるようにします。これにはFabricを使います。
こんな感じのfabfile.pyを書いておけば以下のようにデプロイができます。
$ fab server deploy
デプロイ時には以下のことをしています。
- git pullする
- qlot installする
- supervisord からプロセスを再起動する
supervisorctl restartするとSupervisorがServer::StarterにSIGTERMを送って、Server::Starterが新しいプロセスを立ち上げます。立ち上がったら、Server::Starterがclackupにシグナルを送り、最終的にWooがリクエストを捌いてから終了します。
ただし、このfabfile.pyには実は問題があります。
git pullした時点でHTMLテンプレートや静的ファイルは更新されて再起動なしで配信されてしまうので、走っているプロセスとの不整合が起きます。
これを防ぐにはデプロイ時に毎回別ディレクトリにgit cloneして、サーバ起動前にすり替えるみたいなことが必要なんですが、Fabricはそういう気の利いたことをしてくれないので自分でPythonを書く必要があって面倒なので今のところ放置しています。
サービス監視
サービス監視にMackerelを使っています。
ただ、Quickdocsはまだそれほどトラフィックもないので、DBもアプリもバッチもリバースプロキシも1台のサーバに共存しているので、全く活かせている気がしませんが…。
まとめ
全体的にCommon Lispもこなれてきたなーと思います (毎回言ってる)。Roswellのおかげで簡単にスクリプトが書けるようになったのでServer::StarterやSupervisorなど、他のツールとの組み合わせが簡単になっています。
心残りはFabricかなー。もうちょっと何とかなりそうだけどPythonは書きたくないし。Common LispのデプロイツールがあればリモートのSwankサーバとの連携とかもできそう。
今回紹介したQuickdocsのWebアプリ部分はGitHubに公開されています。
Ansibleのplaybookはこちらです。