orangain flavor

じっくりコトコト煮込んだみかん2。知らないことを知りたい。

さくらVPSでLXCを使って安価に複数台構成を実現する

2013年6月7日 22:04更新

はじめに

Chefを使っていると、役割やサービスごとに環境を分離したくなります。

しかし、個人レベルで大してトラフィックがない段階で、サービスごとに仮想サーバーを借りていてはお金が足りません。

そこで、安価なVPS上でLinux Container (LXC) を使うことで、複数のサーバーを作ります。 スケールしたくなったときは、コンテナを潰して、新しく仮想サーバーを借りてChefで同様の設定をすれば手軽にスケールできると考えています。

Heroku使えば?と言われるかもしれませんが、色々なミドルウェアを利用したり、バックグラウンドで処理をしようとすると、たちまちお金がかかるので、VPSをやりくりして遊びます。 *1

 LXCとは

LXCはchrootと完全仮想化のいいとこ取りと言われ、以下の特徴があります。

  • 各コンテナのリソースは分離されていて、例えばポートが被っていても大丈夫
  • 完全仮想化に比べてオーバーヘッドが少ない

ただし、各コンテナはホストと同じカーネルで動くので、同じアーキテクチャLinuxしか動かせません。

ホストのリソースは、基本的にホストと各コンテナで共有されますが、コンテナごとに制限することも可能です。

環境

今回は、LXCの導入の簡単さと、サポート期間の長さを考えて、Ubuntu 12.04 amd64を使います。

VPSは、さくらのVPS 2Gを利用します。以下のスペックで月額1,480円です。

  • メモリ:2GB
  • CPU:仮想3コア
  • HDD:200GB

完成イメージ

f:id:mi_kattun:20130605222349p:plain

データベースを複数プロセス立ち上げるのはリソースがもったいないので、DBサーバーとして1つのコンテナを割り当てます。

さくらVPSではグローバルIPは1つしか割り当てられません。 そこで、コンテナ内からインターネットへのアクセスはNATを使います。インターネットからコンテナへは直接接続できませんので、以下のようにします。

HTTP

ホストでnginxをリバースプロキシとして動作させ、名前ベースのバーチャルホストで、各コンテナのアプリケーションサーバーに振り分けます。

SSH

SSHは、まずホストにログインしてからコンテナにログインする多段接続を利用します。 設定でコマンド1つでログインできるようにします。

0. 前提

  • UFWが有効な状態で、SSHで接続できていること

例えば、SSHに10022番ポートを使う場合は、以下のようにしておきます。

$ sudo ufw allow 10022/tcp
$ sudo ufw enable

以降の手順でUFWの設定を変更するので、SSHで繋がらなくなった!ということがないように気をつけてください。

1. LXCのインストール

$ sudo apt-get install lxc

これだけでLXCがインストールされ、以下の設定も行われます。

  • ブリッジlxcbr0が作られ、IPアドレス10.0.3.1、ネットマスク255.255.255.0が割り当てられる。
  • ネットワーク10.0.3.0/24から外部への通信用にNATが構成される。
  • Dnsmasqを使ったDHCPで、10.0.3.2から10.0.3.254IPアドレスが割り当てられる。

これらの設定は/etc/lxc/lxc.conf/etc/default/lxcで変更できます。 具体的な挙動は/etc/init/lxc-net.confを見ればわかります。

ifconfigの実行結果は次のようになり、lxcbr0ができていることがわかります。

$ ifconfig
eth0      Link encap:Ethernet  HWaddr 52:54:0a:00:92:27
          inet addr:133.***.***.***  Bcast:133.***.***.255  Mask:255.255.254.0
          inet6 addr: fe80::****:****:****:****/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:492875 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6954 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:32296394 (32.2 MB)  TX bytes:2810653 (2.8 MB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:36 errors:0 dropped:0 overruns:0 frame:0
          TX packets:36 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:3240 (3.2 KB)  TX bytes:3240 (3.2 KB)

lxcbr0    Link encap:Ethernet  HWaddr 22:2a:4d:97:df:ae
          inet addr:10.0.3.1  Bcast:10.0.3.255  Mask:255.255.255.0
          inet6 addr: fe80::140b:58ff:fe1c:5ac5/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:387 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1591 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:33834 (33.8 KB)  TX bytes:1559349 (1.5 MB)

2. DNSの設定

DHCPによってコンテナの作成時にIPアドレスを気にしなくていいのは楽ですが、IPアドレスが適当なものになってしまいます。そこで、ホスト名でアクセスできるようにDNSの設定を行います。

具体的には、このネットワークにlocal.example.comというドメインを割り当て、dbという名前のコンテナにdb.local.example.comdbというホスト名でアクセスできるようにします。

これには、/etc/init/lxc-net.confの中にあるdnsmasqの起動オプションを変更する必要があります。*2

しかし、パッケージに含まれるUpstartのジョブを直接書き換えると、アップデートされたときに手作業で変更する必要があるため、あまりよろしくありません。

そこでlxc-netをコピーしたlxc-net-with-domainというサービスを作り、元のlxc-netは自動起動しないようにします。

$ sudo cp /etc/init/lxc-net.conf /etc/init/lxc-net-with-domain.conf
$ sudo sh -c 'echo "manual" >> /etc/init/lxc-net.override' # lxc-netを自動起動させないように

コピーした/etc/init/lxc-net-with-domain.confを書き換えて、dnsmasqの起動オプションを変更します。

$ sudo vim /etc/init/lxc-net-with-domain.conf

dnsmasq -u lxc-dnsmasq --strict-order --bind-interfaces --pid-file=${varrun}/dnsmasq.pid --conf-file= --listen-address ${LXC_ADDR} --dhcp-range ${LXC_DHCP_RANGE} --dhcp-lease-max=${LXC_DHCP_MAX} --dhcp-no-override --except-interface=lo --interface=${LXC_BRIDGE} || cleanup
↓
dnsmasq --bogus-priv --domain-needed --local=/local.example.com/ --domain=local.example.com -u lxc-dnsmasq --strict-order --bind-interfaces --pid-file=${varrun}/dnsmasq.pid --conf-file= --listen-address ${LXC_ADDR} --dhcp-range ${LXC_DHCP_RANGE} --dhcp-lease-max=${LXC_DHCP_MAX} --dhcp-no-override --except-interface=lo --interface=${LXC_BRIDGE} || cleanup

やたら長いですが、dnsmasqの引数に以下の引数を追加しているだけです。

--bogus-priv --domain-needed --local=/local.example.com/ --domain=local.example.com

lxc-netを止めて、lxc-net-with-domainを起動します。

$ sudo stop lxc-net
$ sudo start lxc-net-with-domain

ちなみにlxc-netをrestartすると正常に再起動されない問題があったので、今後lxc-net-with-domainを再起動したいときは、stop & start を使うのが吉です。

ホストでもこのDNSサーバーを参照し、コンテナを名前解決できるようにします。

$ sudo vim /etc/network/interfaces

iface eth0 inet static
    (略)
    # dns-* options are implemented by the resolvconf package, if installed
    dns-nameservers 133.xxx.0.3
    dns-search sakura.ne.jp
    ↓
    dns-nameservers 10.0.3.1 133.xxx.0.3
    dns-search local.example.com sakura.ne.jp

マシンを再起動して変更を反映します。

$ sudo reboot

3. ファイアウォール(UFW)の設定

デフォルトではブリッジによるフォワーディングは無効になっているので、これを許可します。

$ sudo vim /etc/default/ufw
DEFAULT_FORWARD_POLICY="DROP"DEFAULT_FORWARD_POLICY="ACCEPT"

リロードして変更を反映します。

$ sudo ufw reload

また、ブリッジへの接続を許可します。

$ sudo ufw allow in on lxcbr0

4. 初回起動時に実行するスクリプトの用意

コンテナを作る前に、コンテナ作成直後の設定を行うスクリプトをホームディレクトリに用意しておきます。 コンテナは最小限の状態で作られるので、多少の処理が必要になります。

$ wget -P ~ https://gist.github.com/orangain/5692643/raw/lxc_ubuntu_bootstrap.sh
$ chmod +x ~/lxc_ubuntu_bootstrap.sh

このスクリプトの内容は、以下のとおりです。

もちろんコンテナを大量に作るようであれば、テンプレートを変更しても構いません。 しかし、初期インストールパッケージを変更した場合は、キャッシュを更新しないと反映されないのでやや面倒です。

5. コンテナの作成

コンテナのテンプレートは/usr/lib/lxc/templates/に存在し、実体はシェルスクリプトです。 lxc-****というファイルが存在するので、****の部分をテンプレート名として指定します。

ここでは、以下のようにubuntuテンプレートを利用してdbという名前のコンテナを作成します。

$ sudo MIRROR=http://jp.archive.ubuntu.com/ubuntu lxc-create -t ubuntu -n db -- --bindhome $USER

--以降はテンプレートごとのオプションのため、テンプレートごとに異なります。ubuntuテンプレートでは、デフォルトだとubuntuユーザーが作成されますが、--bindhome ユーザー名とすると指定したユーザーが作成されます。さらに、該当ユーザーのホスト側のホームディレクトリがコンテナ側のホームディレクトリとしてマウントされるため、とても便利です。

MIRROR=http://jp.archive.ubuntu.com/ubuntuは、aptのミラーを指定しています。この指定は初回のダウンロード時のみ有効です。

初回はOSのダウンロードが行われるため時間がかかりますが、2回目以降は/var/cache/lxc/以下のキャッシュが使われるため数十秒で完了します。

コンテナは/var/lib/lxc/dbに作成されます。

6. コンテナの起動

作成したコンテナを起動して、用意したスクリプトを実行します。

$ sudo lxc-start -n db -d    # 起動
$ sudo lxc-console -n db    # コンソールに接続

# コンテナ側でブートストラップスクリプトを実行
(container) $ sudo ./lxc_ubuntu_bootstrap.sh

# コンソールはCtrl+a, qで抜けられます

起動は一瞬ですが、ネットワークやファイアウォールの設定が間違っていてIPアドレスが取得できない場合は、コンソールに接続してから操作できるようになるまで1分ほどかかります。その場合は、Ctrl+a, qで抜けてホストの/var/log/syslogなどを確認しましょう。

以下のように、コンテナのconfigファイルへのシンボリックリンク/etc/lxc/auto/に作成することで、ホストのブート時に自動でコンテナを起動させることができます。

$ sudo ln -s /var/lib/lxc/db/config /etc/lxc/auto/db.conf

7. コンテナへのSSH接続

作業用マシンの.ssh/configに以下のような記述を加えて、コマンド1つでログインできるようにします。

(workstation) $ vim .ssh/config

# ホスト
Host lxchost
  HostName wwwXXX.sakura.ne.jp
  Port 10022
# コンテナ
Host db # スペース区切りで増やせます
  ProxyCommand ssh lxchost nc %h 22 2> /dev/null 

作業用マシンから接続してみます。

(workstation) $ ssh db

まとめ

このようにしてコンテナを作れば後はChefで自由に設定できます。コンテナであることをあまり意識することなく複数台構成を組めると思います。

このように簡単に使えて便利なLXCですが、注意点もあります。

完全仮想化された環境や物理環境とはブートプロセスが異なるので、想定通りに動かないこともあります。 例えば、foremanでexportしたUpstartのジョブがブート時に起動しない問題にハマったことがあります。

このような点には注意しつつ、楽しいLXCライフを送りましょう。

なお、今回利用したlxcなどのバージョンは次のとおりです。

$ uname -a
Linux wwwXXXX 3.2.0-45-generic #70-Ubuntu SMP Wed May 29 20:12:06 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
$ lsb_release -a
No LSB modules are available.
Distributor ID:     Ubuntu
Description:     Ubuntu 12.04.2 LTS
Release:     12.04
Codename:     precise
$ lxc-version
lxc version: 0.7.5

参考

*1:自宅にはKVMの環境がありますが、インターネットに公開するのはいろいろと面倒なのでやめてしまいました。

*2:次のようにする方法もあります。 (1) /etc/default/lxcのLXC_USE_BRIDGEをfalseにする。(2) 新しいブリッジを定義する。 (3) NATの設定をする。 (4) dnsmasqパッケージをインストールし、DNSDHCPの設定をする。