あまり日の目を見ないPassengerにスポットを当てる
Phusion Passengerとは
非公式にはmod_railsとmod_rackとも呼ばれている。
役割としても、Apacheにインストールして、そのまま動かす当たりはやはり、mod_php
とかと被ってる。
イマドキだと、PumaやUnicornがRubyのAppサーバーとしての主流だけど、こっちは「nginx + php-fpm」に近いイメージ。
今だからというのはあるけど、PassengerはDockerが無かったりした時代ではある程度メジャーな手法だったんだろうし、何より当時としては簡単なインストール方法だったのだ。
メリット
ApacheやNginxのモジュールとして稼働する
これのメリットとしては、特別にRailsアプリケーション用のコマンドを用意する必要がない、
すなわちsudo service apache2 (nginx) restart
でだけで再起動できるし、色々できるというわけだ。
複数アプリケーションを立てたときのメモリの有効活用
これは具体的に言うと、Passengerでは一定期間リクエストがないインスタンスに関しては終了させ、 またリクエストがあったときにインスタンスを生成するという仕組みを取っているので、トラフィックが少ないサイトを沢山まとめて1つのサーバーで管理したい時に重宝する。 この終了までの期間に関してはPassengerの設定で変更することも可能である。
定期的な終了による小規模アプリケーションの安定稼働
Unicorn当たりでよくあるパターンは、ActiveRecordなどのCacheをどんどん溜めてしまってメモリが溜まってしまうというものだ。
Pumaは本番で余り触ったことがないので、よくわからないのだが、Unicornの場合は、unicorn-woker-killer
によって、
一定回数のリクエストや、プロセス単体のメモリ容量によってプロセスの再起動を行う対策を取るのが一般的ではある。
Passengerではリクエストが無いときにはインスタンスを終了させるので、小規模なアプリケーションの場合は定期的に再起動を行っていることになり、 メモリ解放について気を使うことは、小規模なアプリケーションに限って言えば他のアプリケーションサーバーに比べると少ないのかもしれない。
デメリット
Apacheに同梱されているので、静的ファイルを返すときでもApache + Passengerが動く
結構痛い。 なるべく余計な処理をさせないようにするには、 静的ファイルを返すときにはアプリケーションサーバー(Passenger)は動いてほしくないものである。 静的ファイルが多めのサイトを運用する場合は避けたほうがいいかもしれない。
一応対策もあるが、それは別途Nginxを立ててReverseProxyする方法。 静的ファイルはNginxから、動的に処理しないといけないところをApacheでという方法。 でもそれだったら、Unicornとか、Puma使えば良くないですかということになる…
ビジネス向けでないとDeploy時・再起動時にダウンタイムが発生する
コレはPhusion Passengerの機能制限。有料のビジネス版を利用しないとリスタート時にダウンタイムが発生する。 すなわちgraceful startができない。 どのくらいダウンタイムが発生して、どのくらいの時間かは未検証だけど、止まるということは頭に入れておいてほしい。
どういうときに利用するのか
止むにやまれない事情で、少メモリ環境で、複数アプリケーションを立ち上げる案件
インフラの費用がなかったり、管理上1つのVMやインスタンスで管理したいという時。あんまりないねー。
小規模なRedmineを自前で立てる必要がある場合
いや、もうDocker使えよ…という話なんだろうけど、複数のRedimineアプリケーションを1つのサーバーに立てる時に何かと重宝する。 あと、単純に資料が多いというの公式のインストールマニュアルがあるしね。
積極的に使う理由が余り見当たらない…
普通にアプリケーション作っているんだったら、やっぱり、PumaかUnicornが無難。 元々Passenger使っていたとか、複数のRailsアプリケーションを1つのサーバーに同居させて管理する場合のみ、Passengerを使うべきであるというのが自分の出した答えである。
Nginxでリクエスト毎に発番して、Railsのログに書き込むまで
いきなりまとめ
- マイクロサービスとかでいくつものアプリケーションに対してリクエストを行うような構造のアプリケーションはリクエストIDを設定しておき、異なるマイクロサービスでも同一のIDがログ上で記録されるので、ログのチェックなどがはかどって便利。
- Nginx 1.10以降で利用できる
$request_id
を使うと簡単に リクエストごとに発番できる。 - Railsは HEADER
X-Request-Id
を取得してログにタグを簡単に設定できる。
なぜ必要なのかをもっと詳しく
Railsアプリケーションの手前でNginxでTLSを終端していたり、 複数のアプリケーションをまたいだりしているマイクロサービスでアプリケーションを実装していると、もし大げさなサービスを構築していなくとも、リクエストが多いとログを確認するのが大変なので、 なにかとNginxでリクエストごとに生成したIDを振ったものをProxyしているアプリケーションに渡してやることでログの確認を補助することができる。
やりたいこと・サンプルアプリケーションの構成
- 今回は前段にTLS終端を想定した、Nginxでロードバランサを立てる。
- 1.のNginxアプリケーション(コンテナ)でNginxでUUIDを割り当て、Proxyする。
- 2のコンテナから、Proxyからのアクセスに
X-Request-Id
が設定されている場合は、それをそのままX-Request-Id
に設定する。設定されていない場合は、3のコンテナでリクエストIDを発番する。 - 更にRailsアプリケーション用にProxyして、ProxyHeaderに書き込まれた
X-Request-Id
をRailsのログにタグとして設定して書き込む
図解
サンプルリポジトリ
これをGit cloneしてきて、docker-compose up
ってすればシュシュッとサンプルのアプリケーションが動くので、よかったら動かしてみて欲しい。
TLS終端用のNginx側の設定
リクエストごとにIDを発番する機構はNginx1.10以上であれば、$request_id
を利用して、簡単に設定することができる。
http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_id
server { listen 80; server_name _; client_max_body_size 4G; keepalive_timeout 5; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For http; location / { # X-Request-IdをProxy Headerに設定 proxy_set_header X-Request-Id $request_id; proxy_pass http://nginx_rails_proxy; } }
上記は設定の抜粋なので、下記にすべて記載したものを掲載しておく。*1
nginx_proxy_uuid_rails/nginx.conf at master · webuilder240/nginx_proxy_uuid_rails · GitHub
Rails Proxy用のNginxコンテナの設定
基本的にはTLS終端用のNginxの設定と同じ。RailsアプリケーションへのProxy設定部分以外で異なるのは、
先程のTLS終端用のコンテナから受け取った X-Request-Id
をProxyするアプリケーションに対して
ProxyHeaderに設定してあげればOKということ。
もし X-Request-Id
がない場合は、このNginxでリクエストIDを生成して設定すればOK。
server { listen 80; server_name _; client_max_body_size 4G; keepalive_timeout 5; # この生成したRequestIdをProxy用のRequest IDに設定。 set $proxy_request_id $request_id; if ($http_x_request_id) { # Proxy Headerに `X-Request-Id`が設定されている場合、 Proxy用のリクエストIDに設定する set $proxy_request_id $http_x_request_id; } root /usr/src/app/public; try_files $uri/index.html $uri.html $uri @app; location @app { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; # Header, Proxy Headerに `X-Request-Id` を設定する。 add_header X-Request-Id $proxy_request_id; proxy_set_header X-Request-Id $proxy_request_id; proxy_redirect off; proxy_pass http://app_server; } error_page 500 502 503 504 /500.html; location = /500.html { root /usr/src/app/public; } }
nginx_proxy_uuid_rails/nginx.conf at master · webuilder240/nginx_proxy_uuid_rails · GitHub
Railsの設定
Rails側の設定は至って簡単。
application.rbに下記設定を追記すればOK。
なお、X-Request-Id
が設定されていない場合は、Rails側で生成したものが反映される。
require_relative 'boot' require 'rails/all' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module NginxProxyUuid class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 5.2 config.log_tags = [:request_id] # Settings in config/environments/* take precedence over those specified here. # Application configuration can go into files in config/initializers # -- all .rb files in that directory are automatically loaded after loading # the framework and any gems in your application. end end
ログのフォーマットはこんな感じ。
[43a62d59ee9f05db194539c71e113601] Started GET "/" for 172.20.0.2 at 2018-07-28 05:00:26 +0000 [43a62d59ee9f05db194539c71e113601] Cannot render console from 172.20.0.4! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255 [43a62d59ee9f05db194539c71e113601] Processing by Rails::WelcomeController#index as HTML
*1:ここではNginxのアクセスログの設定を省略しているが、ちゃんとRequestIdを記録しておこう https://github.com/webuilder240/nginx_proxy_uuid_rails/blob/master/containers/nginx_tls/nginx.conf#L15
Rails + SQLServer 航海日誌:実際に運用している編
実際にSQLServerとRailsを利用して、アプリケーションを運用した知見をまとめておく。 この記事の続き。
いきなりまとめ
- DB周りで何か問題あったとすればMySQLとの違いはユニーク制約の違いくらい.
- 逆に言えば他でハマるようなことはなかった。
- 1年近くこの構成で5〜6個近いアプリケーションを運用しているが、特にDB起因での障害とかで大きなものはいまのところはない*1
- AzureでMySQL / PostgresqlのRDSっぽいサービスがリリースされたので、AzureからSQLServer一択ということではなくなり旨味がかなり減った。
- 前回話していたSQLServer for LinuxをDockerで利用すれば開発環境のDBも困らない状態に。
- 動作スピードについては未検証・未計測だが大きな差異はないと思ってる。
- MySQLとSQLServerを両方をメンテするのは辛いので、事情がなければサポートするRDBMSは1つだけにしたほうがいい。
- 最近は特に事情がなければ、Azure Database for MySQLを基本的には使ってる。
Azure Database for MySQL / PostgreSQLについて
まずはこの一番大きなトピックからです。 みなさんご存知かと思いますが、かねてから噂されていたサービスがPreviewですがリリースを発表しました。
こちらもDB無停止でのスケールアップが可能なので、AzureでSQLServerを利用する理由が1つ減ってしまったように感じています。 まぁSQLServerに比べると割高なのは否めないですが… 現状MySQL / PostgreSQLで動かしているようなサービスの場合は、こちらに移行させるのが一番いいと思います。*2
PHPなアプリケーションサーバーをWebAppsで幾分ラクラクに運用できることを考えても、 大型WordPressを運用する環境でAzureも検討材料の1つとして候補になるのではないかなーと思っています。
ただし、AWSでいうRDS相当なので性能がAuroraくらいのものかというとそういうわけではないです。
開発DBについて
DockerでSQLServerを運用できるということは前回軽く話していたけど、 実際に試してみて問題なく使えそうなので、現在開発環境のDBとして利用しています。 あとはCIをSQLServerでも回したかったので、そちらでもMSSQL Dockerを使っています。
CircleCIでSQLServerとMySQLでテストを回している話
まだCircleCI1.0を利用している勢なのですが、現在開発中のアプリケーションでは両方のRDBMSでテストを回しています。 この辺については少し旬が過ぎてしまった感もあるのですが、また今後ブログにしたいと思います。
そもそも複数のRDBMSをサポートするのしんどい話
弊社ではまだまだプロダクトの中でそこまで分析系のクエリを書いていないので、まだまだマシだと思っているけど、 union allはどうしてもActiveRecordでは使えない(複雑なArelのコードを書くことで実現できるだろうが、どっちにしろメンテコストは高くつく)ので、 基本的には生でSQLを書かなければ行けないと思ってて、そういう箇所が数箇所だけある。
そこに関してはどうしてもSQLServerとMySQLでは使用できるSQL文に多少違い・挙動の違いが存在するので、どうしてもコードをMySQLとSQLServerで分けて書くということをしなくてはならない。 とは言え、自分は生SQLが得意なエンジニアではないのを自覚しているし、いまのところは2箇所で済んでいるけど、コレが増えるとまずいなぁとも感じている。 そもそもダッシュボードの項目部分だったり、分析クエリに関してはRDBMSではなくTresureDataなどの分析基盤などを別途利用した方が良さそうな感じはしている。
さいごに
PAY.JP Platformを試してみた
最近、弊社でもひいきにしているPAYさんから、 これまで開発中だったお待ちかねのプラットフォーマー向けのサービス、 PAY.JP Platformをローンチしたという知らせを聞いたので、早速試してみることにした。
PAY.JP Platformとは
例えばEコマースプラットフォームサービスのように、商品を販売するショップと購入を行うカスタマーのビジネスでは、ショップの登録・クレジットカード審査・管理から、ショップと購入者間の決済、そしてプラットフォーマーとショップへの入金処理までをAPIで組み込むことができます。 他にも各種シェアリングエコノミーサービス、CtoC/BtoC/BtoBサービスのプラットフォーム、クラウドファンディングといった、さまざまなマーケットプレイス型のビジネスユースの決済として活用することが可能です。
とにかくまぁ、公式でも説明しているので、詳しくはそちらを参照してほしい。
類似サービスにStirpeがあるけど、これのConnectという機能がStirpeのプラットフォーマー向けの機能になっていて、 これが似たようなプロダクトであると言える。
マーチャントという言葉について
ちょっと公式の説明で出てきた、マーチャントとプラットフォーマーという言葉だけど、 マーチャントという言葉がわかりにくい気がしたので、以下のように解釈するとしっくり来るということを補足しておく。
マーチャントとは、プラットフォームを利用して商品を販売したり、クラウドファウンディングであれば資金を得る起案者である。 つまり、プラットフォームを利用して、販売したりサービスを提供するユーザーの事を指している。
ほとんど既存のAPIの利用が可能
既存のPAY.JPのサービスとの差分だけど、支払い部分に関しては、殆どないと思ってOK。
ざっくりやることを書くと、
①は、ココの審査はPAY.JPが行うものなので、審査自体をプラットフォーマーができるようにする。 だけなので、自分たちで審査を代行したりみたいなことはやらなくてOK。多分プラットフォーム向けに対応する場合、新規に実装する部分の8割はこの部分だろう。 これらの画面に関してはAPIが現状提供されているので、APIを利用して自分で実装する必要がある。
②に関して言えばPAY.JPが絡む部分の書き換えはあまりない。 ちゃんとマーチャントのトークンを取得できるようにしておき、Charge Objectに決済手数料を設定するだけでOK。
こんな感じである。 プラットフォーム対応を行う際に決済部分のエンドポイントを切り替えたりということはなく、 既存のAPI・SDKに対してすこし拡張するだけで対応が完了できる。
他社製品との違い
基本的には他社製品である、Stripe Connectとコア機能はおなじようなものに感じる。 現状PAY.JP Platformのほうがローンチ直後というものあるのだろうが、 Stripe Connectよりもシンプルであること、安いことをウリにしているような印象を感じた。
いくつか注意点もある
現在も大絶賛開発中なので、いくつか現状にない機能もあるので導入前に確認してみることを推奨する。
通知用Webhookがマーチャント単位でも、プラットフォーマー向けにもない。
定額課金ないし、プランに対して platform_fee
を設定することができないので、
まだ定額課金に対してプラットフォームの使用料を請求することができない。*1
弊社ではPAY.JPの定額課金に頼った実装構成になっているので、PAY.JP Platformを利用するには、PAYさんが対応してくれるのを待つか、 PAY.JPの定額課金を利用するのではなく、自社で定額課金を管理する実装を書くかの2択になると思う。
今すぐに導入を急いでいるわけでもまぁないので、様子見でも良いのだろうけど、 他の事情があって、PAY.JPの定額課金を利用するのをやめて、自社で定額課金を管理する実装を書く事を検討しているので、 どっちでやっていくかは現在もお悩み中という感じ…
*1:今後対応予定だが、PAYさんはこのあたりの仕様をどうするかを現在悩んでいるとのでした。
メッセージングアプリの永続化サービス・ミドルウェアの選定について
色々有識者の方に相談に乗ってもらったのでシェア。
要件的にはまず完成を目指して、チューニングはちょっとあとからということで進んでいたのだけど、 せっかく作るのだから少ない労力である程度の規模までは戦えるようにしたいよねーくらいなレベルで、少し考えて永続化するミドルウェア・サービスを選定してみることにした。 *1
RDBMS
まずこれは一番扱いやすいしすでに資産としてある。 技術者も多いので真っ先に思い浮かぶんだけど、有識者と相談して、
- 「メッセージングで利用するのであれば、すぐに限界来るだろうし...」
- 「トランザクションが不要な要件ではもったいない気がする...」
- 「とりあえずちょっと考える時間あるし、他あたってダメそうなら…」
というまぁ当たり前の結論になって次の選択肢を検討した。
Redis
これも候補として上がってきた。 Pairy何かが最初期はRedisでチャットのメッセージを保存していたようだ。(後にDynamoDBに移行している。)
あとは、これは組み合わせで利用する用途だけど、まずアプリケーションはRedisにメッセージを書き込んで、 一定時間毎にRDBMSなんかにBulkInsertするような仕組みもいいね、という話にもなった。
ただデータの永続化が難しいこと、AzureのRedisCacheでクラスターをシュッと立てるにしてもかなりお高い値段なのと、管理が大変そうなので、現実的ではない... なので、「Redisだけ」という選択肢はすぐに外れた。 また、SidekiqあたりでRDBMSへの書き込みを非同期に行うケースも結構ありなのなとは思った。
HBase
正直、キャッチアップしている余裕もないしスモールスタートできなそうということで候補からはあっさり外した。
DynamoDB
プライシングでもスモールスタートできるし、 データベース構造でもメッセージングに最適だと思っていて、初めて登場してからしばらく立っていて実装も枯れている。*2 RubyのSDKもあり、今回の案件で自分がが求めているものに一番近いように感じた。
ただ、社内的な制約があって弊社ではメインのクラウドサービスにAzureを利用していて、 できればAzure内のサービスで完結させておきたいという気持ちがある。そこで候補に上がったのがCosmosDBだった。
CosmosDB
AzureでのDynamoDBの代替サービスだけど... 自分は100%そうじゃないと思っていて、理由としてはCosmosDBが複数のAPI(接続方法)に対応しているからである。 その中でサポートしているものから、候補を模索していくことにした。
MongoDB API
結構最終候補くらいまで残っていた。
理由はRDBMSよりもスケールアウトしやすいのと、そこまでの整合性が必要ではなかったこと。 Mongoはメインで利用している言語(Ruby)のサポートもそれなりにあって、結構知見があったことから。 ただ、CosmosDBでもいくつかの接続APIがあり、それが異なることによって、スループットの差が「多少」生じるとのことだった。 Mongoはスキーマレスだけど、大量Write,Readのあるようなチャットに向くソリューションかどうかわからないので、一旦他の方法を検討してみることに。
Cassandra API
MongoDB互換だけでなく、Cassandra互換も用意されていた。
Cassandraの構造が一番メッセージングに適している、かつマネージドなので非常に良い選択肢として模索していた。 が、Cassandraの構造からメッセージングサービスのデータ構造を設計するに妙に難しさを感じていて、 心が折れてしまったので、ほかを当たってみることにした。
あとは扱えない技術者が少ないのがネックかも。
Firebase Firestore
Firebase RealTime Databaseのクエリ周りをいい感じにしたプロダクト。
RealTime Databaseよりもクエリ周りが便利なのと、WebフロントエンドやiOS, AndroidのSDKからも書き込みが直接可能で、 Realtime Database共々、チャットアプリを作成するのによく利用されているイメージがある。
更に永続化だけではなく、 フロントエンドから読み込みを行うことでリアルタイムで追加・更新・削除処理が簡単に行えるので、 Websocketあたりのミドルウェアを自前で用意する必要もなくなった。 永続化のサービス・ミドルウェアの選定というところから少しスコープが外れているかもしれないが、これは本当に大きかった。
ただ、永続化サービスという意味ではちょっと違うかもしれない…?
いろいろ考え、有識者に相談した結果
RDBMSとFirebase(Firestore)の併用案にひとまず落ち着いた。 基本的にはメッセージの参照・読み込みをFirestoreに、それ以外のリソースに関してはAPIを経由して、RDBMSを参照するようにしている。 メッセージの書き込みをAPIを経由して書き込み、APIサーバから非同期でFirestoreにWriteを行っている。
やはり、チャットの大部分を占める読み込みをFirestoreに任せることで、 Websocketの実装や管理が不要だったのが一番大きいかなと。
別にFirestoreだけでもいいじゃんということなんだけど、Firestoreだけでの運用は、
- やっぱりベンダーロックインが怖いこと。
- フロントエンドから色々書き込むのは処理が煩雑になりがちになってしまうこと。
- 既存のアプリケーションの1機能として入れるので、サーバーサイドを通したほうが実装と管理がラク。
- チャットに投稿できるかどうかをサーバーサイドで判定してから、Firestoreに流すとかやりたい。
- RealtimeDatabaseに比べてクエリ周りで便利になったとは言え、検索周りでAPIあったほうが今後便利では
ということになりこの案を採用して現在は動いている。*3
そんな感じで、現在はメッセージングアプリケーションを作っている最中だ。 サーバーサイドの観点では結構手抜き構成にしてあったのが幸いして、 苦労しがちなフロントエンド周りに時間を掛けることができて非常に助かっている。