3 年ほど前、 「えもにゅ」というサービスを Amazon EC2 でリリースしました。残念ながら「えもにゅ」はサービスを終了しましたが、今回、別プロダクトで再び AWS を使う機会がありました。「えもにゅ」当時と比べて、AWS も大きな進化を遂げました。
- 「えもにゅ」当時にはなかった東京リージョンがある。
- EC2 / S3 以外にも VPC, ELB や RDS といったサービスが展開されている。
久々の FFTT では、 AWS をどのように利用しているかについて、公開できる範囲でご紹介したいと思います。
AWS 選定までの流れ
要求諸元
今回サービスを立ち上げるに当たって、インフラ面では以下の要求がありました。
- Rails 3.2 アプリケーションを動作させたい。
- IaaS またはデータセンタの場合は、一般的なロードバランサ → Web appサーバ → MySQL という構成にしたい。機密情報も扱うのでプライベートネットワークの構成は必須。
- 時間がかなり限られているので、調達・セットアップにかかるコストを最小限にしたい。
- 負荷が増えたときのスケールアップ・スケールアウトを迅速にしたい。
- Web上に事例や blog 記事が潤沢にあること。
何かと余裕があれば通常のデータセンタでもホスティングは可能でしたが、調達も含めたインフラ構築にかけることのできる時間が 2 週間程度ということもあり、今回は最近流行のクラウドサービスを利用することとしました。
Heroku
弊社では以前より Heroku を試験的に利用しており、今回も Rails アプリケーションということで Heroku も検討に入りました。何よりも Heroku であれば、インフラ構築にかかる時間をかなり圧縮することができます。ですが、以下の理由により Heroku を選択することはありませんでした。
- Heroku 自体が AWS にあることから、インフラ面での障害発生箇所は Heroku に加えて AWS の 2 箇所になる。
- Heroku 自体の障害が 思った以上に多い。
- ミドルウェアに問題があった場合、解決までのプロセスを第三者に依存することになる。
- 復旧までの時間が読めない。
- 弊社側にスキル蓄積が全くない。
また、3. から PaaS の選択肢はなくなりました。実際問題として Rails のプロダクトを運用できるレベルの IaaS が選定当時になかったというもあります。
最終的に…
こうして IaaS にするわけですが、選択肢は他にもいくつかあります。ですが、前述の条件を満たすものは事実上 AWS しかなかったため、AWS を選択することとしました。
構成
AWS
今回は、AWS の機能のうち、主に以下を使っています。
- VPC Virtual Private Cloud。AWS 内に仮想的なプライベートネットワークを作成できる。
- EC2
- ELB Elastic Load Balancer。負荷に応じて自動的にスケールするロードバランサ。
- RDS AWS が提供するリレーショナルデータベースサービス。MySQL や Oracle, MS SQL を選択可能。今回はMySQL 5.5で利用。Multi-AZ。
- Route 53 Amazon が提供している DNS サービス。一部ドメインをホストしている。
ネットワーク構成は、若干簡略化している箇所はありますが、大まかに以下のような感じです。
EC2 構成
各 EC2 の OS は CentOS 6.2 としました。個人的には社内で Gentoo Linux を布教して回っているのですが、一向にユーザが増えない現状、EC2 の OS を Gentoo にすることはテロ行為に等しいと判断せざるを得ず、泣く泣く断念しました。CentOS を自前でインストールするのではなく、「えもにゅ」と同じく RightScale 社の AMI (Amazon Machine Image) をベースにカスタマイズしています。
Rails アプリケーションをホストする Web サーバには nginx + Passenger を利用することにしました。Apache ではなく nginx にしたのはやはり高負荷時のことを考えたことと、後は 個人的に nginx を試していたりしたのもあります。
セキュリティ
こういったネットワークを構成する際、セキュリティ面は何かと頭の痛いポイントですが、AWS では「Security Group」と呼ばれる設定でネットワークの出入りについて制限することができます。今回、セキュリティ面の設定は SSH など一部を除いて全て Security Group の設定のみとしました。
可用性
AWS 自体、可用性を考慮した構成にはなっています。ですが、アベイラビリティゾーン全体が落ちる可能性も考慮し、今回は極力 2 つのアベイラビリティゾーン (ap-northeast-1a, ap-northeast-1b) に各機能が分散するように配置しています。また、RDS については Multi-AZ と呼ばれる アベイラビリティゾーンをまたぐ形でレプリケーションする機能 がありますので、こちらを採用しています。
memcached
最近の Web アプリケーションでは、各キャッシュやセッションストアなどに memcached を使うことが多くなってきました。ですが、上記の 2 つのアベイラビリティゾーンにまたぐ配置を考えた場合、各アベイラビリティゾーンに最低 1 台ずつ memcached を配置し、レプリケーションさせる必要があります。
こういった用途用に repcached というレプリケーション可能な memcached の実装がありますが、memcached 1.2.8 ベースであること、そもそも memcached の仕様として 30 日以上の有効期限を設定できないという問題がありますが、今回の要件ではここが少し問題となりました。
そこで、今回は KyotoTycoon + memcached プラグインを memcached として運用しています。
NAT インスタンス
Amazon VPC で運用する場合、各 EC2 に SSH などでログインするには踏み台となるインスタンスに Elastic IP と呼ばれる固定 IP を振る必要があります。ですが、 ELB 経由のアプリケーションへのアクセスであれば、Amazon VPC の設定で「Internet Gateway」の設定をしておくことでは問題なく行えます。ここで「Internet Gateway」の名前に惑わされがちですが、これはいわゆるゲートウェイサーバではなく、あくまで ELB や RDS など、VPC 外の AWS 提供機能にアクセスするためのゲートウェイに過ぎません。そのため、これを設定しても EC2 からいわゆる「インターネット」にアクセスできるわけではありません。
アプリケーション内で例えば Google API などの外部アクセスが必要だったりミドルウェア更新を行うには、各 EC2 に Elastic IP を振る必要があります。また、そもそも Amazon VPC 内の EC2 にアクセスするには、何かしら踏み台となるインスタンスに Elastic IP を振らないことには始まりません。ただ、実際問題として 1 アカウントに対して発行される Elastic IP には上限がありますし、Elastic IP を割り振ることでセキュリティ上のリスクも発生します。
そこで、Elastic IP を振った EC2 インスタンスを NAT インスタンスとして用意し、Amazon VPC 側のサブネット設定で 0.0.0.0/0 向けの通信を NAT インスタンスに向けることで、全ての EC2 で外部にアクセスできるようになります。
ELB + Route 53
ELB を独自のドメインで運用する場合、各 ELB に対して発行される FQDN をホストするドメインの CNAME として登録することで運用します。A レコードでは運用できないため、いわゆる Zone Apex と呼ばれる feedforce.jp といったサブドメインがない場合は bind などで運用できません。
こういった場合は、ドメインを Amazon Route 53 で運用します。Route 53 では Zone Apex でもエイリアスとして A レコードで登録できます。
実際に運用して躓いた点
ELB + Sticky Session + Multi-AZ
ELB には、他のロードバランサと同じく、cookie などに含まれるセッション ID とバックエンドを紐づけてセッションを維持させる Sticky Session 機能があります。ただ、これの運用には若干制限があります。
高可用性のために複数のアベイラビリティゾーンにバックエンドを配置しているケースでは、Sticky Session が有効の状態で特定のバックエンドが落ちた場合、ELB は同一のアベイラビリティゾーンにある別のバックエンドにアクセスを振り替えようとします。この場合、
- アベイラビリティゾーンそのものが落ちてしまう
- 特定のアベイラビリティゾーンにあるバックエンド全てが落ちてしまう(最小構成でアベイラビリティゾーンに1台しかバックエンドがない場合など)
などで特定のアベイラビリティゾーン全てのバックエンドにアクセスできなくなった場合、そのアベイラビリティゾーンに紐づけられたリクエストに対しては 502 を返してしまいます。ですので、ELB 側の Sticky Session は使わないほうが無難です。
t1.micro は重すぎる
最初に種となるインスタンスを構築し、それをコピーすることで全体を構成したのですが、種となるインスタンスは一番安い t1.micro で構築していました。これが予想以上に重く、Ruby のビルドに 15 分以上かかったりします。(ちなみに手元の Core-i7 2600 + Intel 510 のマシンで make -j9
してみたところ、GNOME Shell が盛大にリソースを食っているにも関わらず、わずか 2 分 2 秒で終了しました。)
検索してみたところ、「 t1.microは高い負荷が連続すると、一定時間経過後、極端に性能が低下する」そうです。個人で色々試すならともかく、業務、しかも時間が限られているのならビルド環境は下手にケチらないほうがいいかと思います。
m1.small も重い
t1.micro は重すぎると書きましたが、どんな条件でも有料である m1.small が軽いかというとそんなことはありません。全体的に重く、Rails の初回起動だけで 20 秒ほどかかることを覚悟しておく必要があります。
Web サービスは Unicorn なり Passenger なりで動作させるので初回起動にかかる時間はあまり考慮しなくてもいいかとは思いますが、当然ながら再起動時は重くなります。その結果、ELB のバックエンド死活監視で引っかかり、インスタンスが切り離される可能性があります。capistrano や chef などで同時に全てのバックエンドの Passenger を再起動した場合、全てのバックエンドが ELB から切り離されて 502 が返る悲劇が起こり得ます。
午後〜翌日1時辺りは非常に重い
東京リージョンの場合、業務時間や個人の趣味の時間であろう午後〜翌日 1 時はかなり重く、EBS のスナップショットを取るだけでも数分かかります。これが深夜になってくると一瞬で終わります。若干ブラック気味にはなりますが、AMI の作成は深夜帯がお勧めです。
アベイラビリティゾーンをまたいだ冗長化が困難
現状、Amazon VPC ではアベイラビリティゾーンをまたぐ形でサブネットの設定ができません。また、Amazon VPC で各 EC2 に割り振ったプライベート IP 以外のプライベート IP は RDS や ELB で使われる可能性があります。そのため、NAT インスタンスなど、一部の冗長化が困難になっています。
インフラ知識は必要
「クラウド」が話題になって久しい昨今ですが、AWS を扱うにはそれなりにネットワークに関する知識、運用する OS の知識が必要です。また、Xen や KVM など、仮想環境の運用経験があったほうがいいと痛感しました。
まとめ
躓いた点で色々書きましたが、とは言え、1.5 人・実質 1 週未満という限られたリソースでアプリケーションのためのインフラ面を整えられたのは AWS のお陰です。 ハードウェア調達からインストール、ラックマウント、ファイアウォールの設定といった通常のデータセンタへの環境構築の場合は早くても 2 〜 3 週間かかりますし、初期費用もハードウェアだけで数百万円単位となります。
最近流行のリーンスタートアップの場合、そもそも顧客がつくかどうかから始まることもあって莫大な初期費用はかけられません。そういう点では、AWS は初期費用を抑えつつ、それなりの構成を作ることができるほぼ唯一の選択肢としておすすめできるものだと思います。