LXCでWebサービスを分離してみた

少し前ですが、ownCloud 7.0.2がDebian sid/Ubuntu 14.10に入りました。私はもともとさくらのVPSにインストールしたUbuntu 14.04 LTS上に、universeからowncloudやtt-rssパッケージをインストールして自分用として使っていたのですが、あわしろいくや氏がownCloud 7.0.2の14.04 LTS向けパッケージをPPAに用意したと聞き、アップデートを行いました。
ownCloud自体のアップデートは問題なく終わったのですが、tt-rssの設定画面を開くとdojo.jsのエラーで止まるという現象が起きていることに気づきました。詳しく調べたわけではありませんがエラーメッセージから察するに、libjs-dojo-{core,dijit}あたりのバージョンが上がってしまったことが原因だと思います*1。Utopicにあるtt-rssの1.13を持ってこれば解決しそうですが、14.04 LTSでは依存関係が満たせないので少し手間がかかりそうと判断し、tt-rssパッケージの利用はやめてソースを手で入れてしまいました*2
というのが、先月末の話。LTSのOSを長く使いたいけど、進化の早いアプリケーションは最新版を使いたいというジレンマはよくある話ですが、標準で提供されている組み合わせを外れて勝手にアプリを入れると、そりゃこういう問題も起こりうるわけです。そこでVPSにLXCを導入して、機能ごとに直交性を持たせてメンテナンス性を上げることにしました(ここまで前フリ)。

VPSの初期化とLXCのインストール

せっかくなのでVPSはOSからインストールし直します。といっても、現在進行形で動いているサーバーをいきなり消すのはよろしくありません。こういう場合、新しいサーバーを別途用意し、並行稼動させながらデータを移すのがセオリーでしょう。しかしそのためにVPSをもう一台契約するのはあまりにも無駄なので、現行のサーバーは一旦クラウドへ退避させることにします。さくらのクラウドには、同一アカウントで稼働中のVPSのディスクイメージをクラウド上のアーカイブにコピーし、そこからインスタンスを起動するマイグレーション機能が用意されています。稼働中のVPSのコピーが保存できる上に、コストは日割、時間割でとてもお得なわけです*3
OSのインストールや基本的な設定は先のRecipe通りなので省略。LXCは以下のコマンドでインストールします。

$ sudo apt-get install lxc

コンテナの作成と初期設定

UbuntuのLXCのコンテナには10.0.3.2から10.0.3.254までのIPアドレスが動的に割り当てられるのですが、固定IPアドレスが使いたいので、まずはLXCの設定を変更します*4

$ sudo sed -i 's/LXC_DHCP_RANGE="10.0.3.2,10.0.3.254"/LXC_DHCP_RANGE="10.0.3.2,10.0.3.127"/' /etc/default/lxc-net
$ sudo sed -i 's/LXC_DHCP_MAX="253"/LXC_DHCP_MAX="126"/' /etc/default/lxc-net
$ sudo stop lxc
$ sudo start lxc

lxc-createコマンドでコンテナを作成します。ubuntuテンプレート、バージョンは14.04 LTS、archはamd64を指定します。

$ sudo lxc-create -t ubuntu -n owncloud -- -r trusty -a amd64

作成したコンテナの設定とrootfsは、/var/lib/lxc/(コンテナ名)以下に作成されています。固定IPアドレスの付与やコンテナの自動起動を設定したいので、/var/lib/lxc/(コンテナ名)/configを編集します。

lxc.network.ipv4 = 10.0.3.200/24
lxc.network.ipv4.gateway = 10.0.3.1
lxc.start.auto = 1

コンテナをdaemonとして起動します*5。デフォルトでSSHサーバーが起動しているのでSSH接続も可能ですが、lxc-attachコマンドでrootのシェルを取ることもできます。コンテナのデフォルトユーザー名/パスワードはubuntu/ubuntuなので、lxc-attachでrootログインし、usermod/groupmod等をしておきました。

$ sudo lxc-start -n owncloud --daemon
$ sudo lxc-attach -n owncloud

genericな設定ができたら、このコンテナのバックアップを取っておきます。lxc-stopでコンテナを停止した状態で、lxc-cloneしました*6。新しいコンテナを作る際は、lxc-createではなく設定済みのバックアップからlxc-cloneする方向で。本当はChefとかを使った方がいいのかもしれないけど。

$ sudo lxc-stop -n owncloud
$ sudo lxc-clone -o owncloud -n backup

各コンテナへのtt-rssやらowncloudやらのインストールについては省略。

リバースプロキシとmod_rpafの設定

各コンテナへアクセスを振り分けるため、ホストにリバースプロキシとしてNginxをインストールします。「Nginxはcurrentこそがstable」とかいう話も聞きますが、せっかく14.04 LTSからはmain入りしているので、Ubuntuのパッケージを使うことにします。どうせリバースプロキシとしてしか使わんしね。

$ sudo apt-get install nginx

UbuntuのNginxの設定はApacheと同様に、バーチャルホストごとの設定を/etc/nginx/sites-availableに作成し、有効にしたいホストは/etc/nginx/sites-enabledにシンボリックリンクを張るという方式になっています。defaultという設定が最初から用意されているので、適宜書き換えるなどします。UbuntuのパッケージからインストールしたownCloudの場合、本体は/usr/share/owncloud以下にインストールされ、/owncloudにAliasが設定される*7という作りになっていますので、そのようなproxy_passを設定します。

location /owncloud {
    proxy_pass http://10.0.3.200/owncloud;
}

バックエンドサーバーへリクエストを転送する際、いくつかのHTTPヘッダを設定する必要があります。たとえばリバースプロキシはHostヘッダをproxy_passに指定した値、この場合はコンテナに割り当てられたローカルIPアドレスへ変更した上でバックエンドへリクエストを投げますが、ownCloudはconfig.php内のtrusted_domainsに設定されていないドメイン名でアクセスされた場合に警告を発します。アクセスされたホスト名は/usr/share/owncloud/lib/private/request.php内のserverHost関数でチェックしており、ここでX-Forwarded-Hostの値も見ているので、X-Forwarded-HostにオリジナルのHostを設定してあげる必要があるわけです。

proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Real-IP $remote_addr;

またこれだけだと、バックエンドのApacheアクセスログに、リバースプロキシのローカルIPアドレスが記録されてしまいます。これをX-Forwarded-Forに記録されたオリジナルのクライアントIPアドレスに変更するため、コンテナ内にmod_rpafをインストールします。

$ sudo apt-get install libapache2-mod-rpaf

/etc/apache2/mods-enabled/rpaf.confにリバースプロキシのIPアドレスを設定し、Apacheをリロードします。

RPAFproxy_ips 10.0.3.1

POST時のボディサイズ制限にハマる

ownCloudでは、アップロードできるファイルサイズを自由に制限することができ、デフォルトでは512MBになっています。これはownCloudのWeb画面から自由に変更することができるのですが、間にリバースプロキシを挟んでいるため、Nginx側の制限を越えることができません。具体的にはNginxのデフォルト設定でclient_max_body_sizeが1MBになっているため、1MBを越えるファイルのPOST時に413 Request Entity Too Largeが返されます。ownCloud側の設定に合わせて、大きくしておきましょう。私は2GBにしてみました*8

client_max_body_size 2G;

ownCloudクライアントのアクティビティに413が記録されていたら、Nginxの設定変更後に「同期を再実行」を実行しておきましょう。

こんな感じで、ownCloud、tt-rss、Munin、普段の雑用環境などをコンテナに分離してみました。不要になったらコンテナごと削除すればよいし、他所の環境は汚さないし、だいぶすっきりできた気がします*9。リバースプロキシを挟むことで構成が少し変化しましたが、特にトラブルも起きていないし、しばらくこのまま様子を見ようと思います。

*1:tt-rssdojoを内包しているのですが、Debianでは共有ライブラリのパッケージが提供されているので、そちらを使っています。tt-rss_1.12+dfsg-1では、それを理由にtt-rssパッケージ内からdojoが除去されているようです。

*2:makeして/usr/local以下に入れるようなアプリケーションであれば、頑張ってパッケージを作ったでしょうが、Webのコンテンツ領域に展開すればそれだけで動くPHPのアプリなので、あまりこだわっていません。なお設定はすべてDBに持っているので、インストール時に既存のDBを指定して初期化処理をスキップすればデータはそのまま引き継げます。ただし一度admin権限でログインして、DBをアップデートする必要アリ。

*3:昨年の石狩データセンターツアーで貰った2万円クーポンがまだ残っているので、実際には無料で使えてたりします。なおここでクラウド上に作成した複製サーバーのIPアドレスを付け替えたり、DNSの向き先を変えるといった話は省略。

*4:http://gihyo.jp/admin/serial/01/ubuntu-recipe/0329 参照

*5:--daemonを忘れるとコンテナの標準出力が現在のターミナルに繋がれて、そこから抜ける方法がわからなかった……。

*6:もちろんこの状態だと、バックアップしたコンテナにも同じIPアドレスが設定されている上に自動起動してしまうので、このへんは手動で変更しておきます。

*7:という設定がconf-availableに追加され、conf-enabledからリンクされる。tt-rssやMuninなども同様。

*8:0にするとサイズのチェックをしなくなるらしい。

*9:LXCははじめて使ったので、何か間違ってる可能性は否定できませんが……。