サーバサイド担当のくまくら (@kumatch) です。
7/1 から 7/7 まで社内で行われた「超聖域」 の成果報告のブログ記事となります。
私はこれまで SYNCNEL のサーバサイドの実装と構築を担当し続けているのですが、今回自由に使っていい一週間あるということで、その SYNCNEL サービスの運用のなか Issue として挙げられている事柄のうち個人的に関心あるものをピックアップして、ある程度の方針決定、あるいは解決に至るまでできるかに時間を充てることにしました。
1. サブドメインベースアプリケーション
SYNCNEL では、サービス契約されたお客様ごとに固有の名前を持った識別子を発行して運用しているのですが、この識別子をサービスのサブドメイン部分に据えてより良いサービス提供ができないかという機能案がありましたので、その可能性について調査と仮実装を行ってみました。
ドメインをワイルドカードで運用する
何はともあれ、任意のサブドメインを持つドメインアドレスで特定のエントリサーバへリクエストしてもらう必要がありますので、ドメインはワイルドカードで運用することになります。
たとえば Amazon Route53 では 全てのレコードタイプによるワイルドカードエントリがサポートされている ので、これを利用すれば簡単に実現可能です。
www.example.com という A レコードと *.example.com というワイルドカードな CNAME レコードが存在している場合でも、www.example.com の解決はきちんと A レコードの方になりますので、公式サイトサーバとアプリケーションサーバとを区別して備えるといったことも容易ではないでしょうか。
ただ、利用している DNS サービスによってはワイルドカードエントリが定義不可能なものもあるようです。
「アクセスコントロール」と「サービスコントロール」
リクエストを受け付けるサーバ側はどんな構成になるべきかと考えた際に、結論として「サブドメイン部分にあたる会社識別子を用いて会社を特定した上で、どのような問題を解決したいのか」という点でやるべきことが異なるということに至りました。
たとえば、特定した会社によってシャーディングしたり、リクエスト元の制限や認証方式の切り替えなどを統括して行いたいというのであれば、エントリサーバでそれらの処理を行った上で後ろに控えるアプリケーションサーバへ Proxy するといった体制が考えられます。この体制のポイントとしてアプリケーションサーバで備えているサービス固有の振る舞いとは切り離されていて、アプリケーションレイヤには直接関係のないインフラストラクチャやセキュリティの解決が行えているというところです。アクセスコントロールが主たる目的となります。
一方、アプリケーション内で特定した会社ごとに固有な内容に基づく振る舞いを行いたい場合は、アプリケーションサーバ上で直接サブドメイン名による会社の特定と処理を行う必要があります。たとえば簡単な例だとページトップのロゴを変更したりですとか、特定した会社による名前空間内でサービスのための処理を施すなどが考えられます。こういったケースだと、この振る舞いそのものはアプリケーションレイヤに組み込まれるべき実装になっていくことでしょう。こちらは実際のサービス内容を決定する、サービスコントロールが主の目的になります。
以上で目的がはっきりとしました。
今回私は、前者のアクセスコントロールのパターンのためのサーバを Node を使って実装してみました。HTTP アクセスしてきたクライアントのリクエストソースからサブドメイン部を抽出して、サーバプロセス内で保持しているルーティング情報に基づいて転送を行うというものです。転送には http-proxy や bouncy などを利用して、同じことを行ういくつかのサーバプログラムコードを書きました。
以下のコードは http-proxy を使った例です。ここでは静的に持った情報を元に転送先を決定する処理のみを行っていますが、前述したとおり転送前に個別に設けられたアクセス制限処理を合わせて行うこともできます。またここでは会社情報自体がコードに埋め込まれていますが、別途任意のデータベースに保持しておいてそこから参照すれば情報更新後もサーバの再起動なしに反映、利用することができることでしょう。
<br />var http = require("http");
var httpProxy = require("http-proxy");
var headerHostname = require("header-hostname");
var routes = {
foo: {
host: "172.16.0.1", port: 8000
},
bar: {
host: "172.16.10.1", port: 8000
}
};
var proxy = httpProxy.createProxyServer({ xfwd: true });
var server = http.createServer(function(req, res) {
var hostname = headerHostname(req.headers);
var company = hostname.match(/^([a-z0-9-]+).example.com$/);
var route = routes[company] || undefined;
if (route) {
proxy.web(req, res, route);
} else {
res.writeHead(404, {'Content-Type': 'text/plain'});
res.end();
}
});
server.listen(80);
後者のサービスコントロールパターンの方も実際のアプリケーションサーバ側のコードとして実装して、初アクセス時に会社名を表示したりなどの会社固有の振る舞いを実装してみました。こちらはコード例は割愛します。
2. PHP 5.3 から PHP 5.6 への環境移行テスト
SYNCNEL のアプリケーションサーバの一部は PHP で書かれているのですが、その箇所は古い実装体系から継続的に進めてきたため、内部の一部ではより古い PHP コードが残っています。その都合上 PHP 5.3 へのアップグレードを最後に、それ以上のアップグレードを行うことをこれまで行うことができていませんでした。
今回まとまった時間が用意されたということで、現行のコードを PHP 5.4 以降のものへアップグレードするための考慮点の調査と、実際にアップグレードしてみての移行テストを行いました。やはり古いコードがベースになっているということもあり、PHP の設定値および直接コード側の改変が若干必要でしたが、ひとまずは最新バージョンである PHP 5.6 でアプリケーションサーバの稼働やテストコードによる動作(green)に成功しました。
PHP 5.4 の新機能でコードが比較的楽に書けるようになる(配列は言わずもがなですが、個人的にはクロージャ内での $this 参照が嬉しい)恩恵もありますが、セキュリティアップデートの考慮や、最近では Composer で管理している各 module が requirement している PHP バージョンが 5.4 以降になりはじめていたりなどもあり、すみやかにバージョンアップを行いたいところです。
表面上動いているように見えるだけという可能性はまだまだ捨てきれないので、今後継続して動作確認を行った上で是非とも本番系への反映を済ませたいと考えています。