Linux アプリケーションを Amazon クラウドにマイグレーションする: 第 3 回 スケーラビリティーの確立

トラフィックの増加に容易に対応する方法

この連載ではこれまで、サンプル Linux® アプリケーションをクラウドにマイグレーションし、信頼性に関する基本的な機能を構成してきました。アプリケーションを Amazon クラウドにマイグレーションする方法について説明する連載の第 3 回目となる今回は、いよいよクラウドが持つ動的な性質を利用します。その方法は、負荷に応じてインフラストラクチャーを拡大/縮小することです。さらに、静的アセットの一部をクラウドのエッジに保管するようにアプリケーションを更新します。

Sean A. Walberg, Senior Network Engineer

Author photoSean Walberg は 1994年以来、学界、企業、およびインターネット・サービス・プロバイダー環境で Linux および UNIX システムに取り組んできました。この数年の間は、システム管理に関する広範な著作活動を行っています。



2010年 10月 06日

この連載の第 1 回では、SmallPayroll.ca という Software as a Service のオファリングについて説明し、このサンプル・アプリケーションを Amazon EC2 (Amazon Elastic Compute Cloud) にマイグレーションする方法を説明しました。続く第 2 回では、アプリケーションをより堅牢なものにするため、冗長性を持たせ、バックアップをするためのソリューションと、より信頼性の高いディスクを追加しました。

安定して動作するための構成ができたら、今度はスケーラビリティーについて検討します。サーバーは時間単位でレンタルするので、必要なときにだけサーバーを追加して稼働させたほうが賢明です。また、ジョブを非同期で処理するというのはどうでしょうか。

これまで、静的コンテンツをネットワークのエッジにキャッシュするコンテンツ配信ネットワーク (CDN) は高価な手段でしたが、クラウド・コンピューティングによって小規模なサイトでさえも CDN を気軽に利用できるようになっています。CDN を利用すれば、パフォーマンスは大幅に向上するはずです。そこで、今回は CDN もセットアップすることにします。

自動デプロイメント

インフラストラクチャーを動的に拡大する上で鍵となる要素は、外部から介入することなく自動的に、新しいインスタンスが本番環境に追加されるようにすることです。変更 (コードのデプロイメントやデータベースの変更など) が行われるたびに AMI (Amazon Machine Instance) を再バンドルすることもできますが、簡単なスクリプトを作成すれば、大掛かりな作業を行うことなく、サーバーを自動的にデプロイすることができます。

SmallPayroll.ca には、大抵のアプリケーションと同じようなデプロイメント・プロセスがあります。

  1. リポジトリー (Subversion や git など) からコードを取得します。
  2. アプリケーション・サーバーを起動します。
  3. アプリケーションが正常に開始されたことを確認します。
  4. 新しいサーバーをロード・バランサー・プールに追加します。

この新しいインスタンスも、/etc/hosts でデータベース・サーバーを構成する必要があります。

コードをリポジトリーから取得する

SmallPayroll.ca のコード・ベースは Subversion リポジトリーに保管されています。Subversion とは、コード・ベースの変更を追跡するソース・コード管理システムのことです。開発者はSubversion を使用してコードを分けたり、マージしたりすることで、別の環境で機能の開発に取り組むことができます。

最も単純なレベルでは、Rails アプリケーションはチェックアウトされたソース・コードのコピーをそのまま実行することができます。コードに変更が行われると、本番サーバーは更新を実行してから再起動が行われます。Capistrano という Ruby Gem は、複数のサーバーを一元管理し、問題が発生した場合に簡単にロールバックできるように、コードのデプロイメントを管理します。

コードを手動でチェックアウトするのではなく、新しいサーバーが Capistrano プロセスをブートするようにするには、事前の作業が多くなります。しかしこの事前作業によって、Capistrano が簡単にサーバーを管理できるようになります。リスト 1 に、Capistrano に必要な初期 Capfile の内容を記載します。このファイルは、給与計算アプリケーションのユーザーのホーム・ディレクトリーに格納されることになります。

リスト 1. Capistrano のデプロイメント・プロセスをブートするための Capfile
load 'deploy' 

# Where is the code? This will need to be customized!
set :repository,  "http://svn.smallpayroll.ca/svn/payroll/trunk/"
set :scm_username, "DEPLOY_USERNAME"
set :scm_password, "DEPLOY_PASSWORD"

# Deploy to the local server
server "localhost", :app, :web, :db, :primary => true

# By default Capistrano tries to do some things as root through sudo - disable this
set :use_sudo, false

# Define where everything goes
set :home, '/home/payroll'
set :deploy_to, "#{home}"
set :rails_env, "production"

Capfile は、cap コマンドによって実行されます。Capistrano はサーバーとの接続にセキュア・シェル (SSH) を使用することを前提とします。この前提は、サーバーがローカル・ホストであっても変わりません。アプリケーション・ユーザーとして実行されるリスト 2 のコードは、Capistrano タスクを実行可能にするために、アプリケーション・ユーザーが SSH を使用してローカル・サーバーに接続できるようにサーバーを準備します。

リスト 2. 環境の準備
# Create the SSH directory and generate a secure key pair
mkdir .ssh
chmod 700 .ssh
ssh-keygen -b 1024 -t dsa -q -f .ssh/id_dsa -N ""

# Allow the user to SSH in to the server without a password
cat .ssh/id_dsa.pub >> .ssh/authorized_keys
# SSH is very fussy about permissions! Nothing should be readable
# by other users
chmod 600 .ssh/*

# SSH in once, ignoring host keys, so that the server will save the host key
ssh 127.0.0.1 -o StrictHostKeyChecking=false /bin/false

リスト 2 は、以下の 3 つのセクションで構成されています。

  • 最初の 2 つのコマンドで、アプリケーション・ユーザーが読み取りのみ可能な SSH ディレクトリーを作成します。3 番目のコマンドでは、1024 ビットの DSA (Digital Signature Algorithm) 鍵を作成し (-b 1024 -t dsa)、出力を無効に設定し (-q)、鍵の名前を指定して、パスワードを使用しないことを指定します。
  • ユーザーの公開鍵を authorized_keys ファイルにコピーします。これにより、この公開鍵に対応する秘密鍵を持つユーザーは、パスワードなしでログインできるようになります。このディレクトリー内のファイルは必ず他のユーザーが読み取れることのないようにしてください。
  • 保存されたホスト鍵がない場合、SSH および Capistrano は鍵を確認するようにプロンプトを出します。このプロンプトにより、自動デプロイメントは中断されます。最後のコマンドで、ホスト鍵を無視してローカル・サーバーにログインし、/bin/false を実行します。ホスト鍵を保存するには、これで十分です。

デプロイメント・プロセスの最後のステップでは、環境を作成して現行バージョンのアプリケーションをデプロイします。リスト 3 に、このタスクを Capistrano を使用して行う方法を示します。

リスト 3. Capistrano によるアプリケーションのデプロイ
cap deploy:setup
chmod 700 /home/payroll
cap deploy

リスト 3 が実行する deploy:setup タスクは、アプリケーションのホーム・ディレクトリーの下に、shared と releases という名前のディレクトリーからなるディレクトリー構造を作成します。デプロイメントされたアプリケーションは、releases フォルダー内にある独自のディレクトリーに格納されます。shared ディレクトリーは個々のデプロイメント間で共有できるログやその他の要素を格納するために使用されます。

アプリケーションをデプロイする前に、2 番目のコマンドでホーム・ディレクトリーのパーミッションを 700 に変更します。このように設定する理由は、デプロイメント先がサブディレクトリーではなくホーム・ディレクトリーであることによるもので、このセットアップ・タスクでは、デプロイメント・ディレクトリーへのグループ書き込み操作が可能になるようにデプロイメント・ディレクトリーのパーミッションを設定するからです。というのも、所有者以外のユーザーにホーム・ディレクトリーへの書き込み操作が許可されている場合には、SSH デーモンは鍵ベースの認証を許可しないのです。

deploy タスクが最終的に実行されると、Subversion からコードがチェックアウトされてリリース用のディレクトリー内に置かれ、current という名前のシンボリック・リンク (symlink) が作成されます。この symlink が設定され、各デプロイメントが個別のディレクトリーに配置されていれば、アプリケーションを前のバージョンに簡単にロールバックすることができます。


アプリケーション・サーバーの自動起動

負荷が増加すると自動的に Web サーバーを追加するシステムを構築することは可能ですが、そうすることが必ずしも名案であるとは限りません。Web トラフィックは変動が激しいため、別のサーバーを立ち上げる前に、トラフィックが増加した状態がしばらく続くことを確かめなければなりません。現行の負荷が新しいサーバーを立ち上げるのに値するかどうかを判断するには、約 10 分間は必要です。その後、5 分から 10 分間でインスタンスを起動し、新しい Web サーバーをプールに追加します。このプロセスは Web サーバーには効果的ではないかもしれませんが、ジョブ・サーバーには有効に機能するはずです。

アプリケーション・サーバーの数をスケジュールに従って増減させることもできます。例えば、SmallPayroll.ca アプリケーションのほとんどのユーザーは、平日の午前 8 時から午後 9 時くらいまでの間、このアプリケーションを使用します。したがって、登録ユーザーに対する応答時間を短縮し、サーバーのコストを節約するためには、午前 8 時から午後 9 時までは 3 台のサーバーを稼働させ、それ以外の時間は 2 台のサーバーを稼働させることになります。デプロイメント・スクリプトはすでに作成されているので、あとはサーバーを起動して、ロード・バランサーに追加するだけでよいのです。

cron からサーバーを起動する

事前に決められたスケジュールで追加インスタンスを起動/終了するのにふさわしいと思われる場所は、cron プロセスです。理想的な crontab は、リスト 4 のような内容になります。

リスト 4. サーバーを自動的に起動/停止する crontab
0  8 * * 1-5 $BASEDIR/servercontrol launch app
0 21 * * 1-5 $BASEDIR/servercontrol terminate app

リスト 4 のコードは、仮想環境外部のマシン上でユーザーの crontab に入力します。第 1 日 から第 5 日 (月曜日から金曜日) の午前 8 時に、パラメーター launch app を設定した servercontrol スクリプトが実行されます。午後 9 時になると、同じコマンドが、今度はパラメーターとして terminate app を指定して実行されます。このスクリプトはこれらのパラメーターをコマンドおよびサーバー・ロールと解釈します。今のところ、サーバー・ロールは常に app です。

制御スクリプトを作成する

次のステップは、サーバーをオンデマンドで起動するために cron から実行する servercontrol スクリプトを作成することです。リスト 5 に、このスクリプトを記載します。

リスト 5. servercontrol スクリプト
#!/bin/bash

AMI=ami-ad7e95c4
OP=$1
ROLE=$2

if [ "$OP" == "" -o "$ROLE" == "" ]; then
  echo You\'re doing it wrong.
  echo $0 operation number role
  exit
fi

case "$OP" in
  "launch")

    # Launch the instance and parse the data to get the instance ID
    DATA=`ec2-run-instances -k main -d "DB=10.126.17.1;ROLE=$ROLE" $AMI`
    INSTANCE=`echo $DATA | grep INSTANCE  | cut -f 6 -d ' '`
    echo $INSTANCE is your instance

    # Keep on checking until the state is "running"
    STATE=""
    while [ "$STATE" != "running" ]; do
      STATE=`ec2-describe-instances  $INSTANCE |awk '/^INSTANCE/ {print $6}'`
      echo the state is $STATE
      sleep 10
    done
    # Keep track of which instances were started by this method
    echo $INSTANCE >> $HOME/.ec2-$ROLE

    # Now that the instance is running, grab the IP address
    IP=`ec2-describe-instances $INSTANCE |awk '/^INSTANCE/ {print $13}'`

    # If this is to be an app server...
    if [ "$ROLE" == "app" ]; then
      # Check the web server to make sure it returns our content
      UP=0
      while [ $UP -eq 0 ]; do
        OUTPUT=`curl -s -m 5 http://$IP`
        if [ $? -eq 0 ]; then # curl was successful
          echo $OUTPUT | grep -qi 'welcome'
          if [ $? -eq 0 ]; then
            UP=1
          else
            sleep 5
          fi
        fi
      done

      # Register with the load balancer
      elb-register-instances-with-lb smallpayroll-http --instances $INSTANCE
    fi

  ;;

  "terminate")
     # Grab the instance ID. It's the last line of the file
     FILE=.ec2-$ROLE

     # Assuming the file exists, of course
     if [ ! -f $FILE ]; then
        echo No dynamic instances have been started for the $ROLE role
        exit
     fi

     # The last instance started is the last line of the file
     INSTANCE=`tail -1 $HOME/.ec2-$ROLE`

     # Assuming there's something in that file, that is
     if [ "$INSTANCE" == "" ]; then
         echo No dynamic instances have been started for the $ROLE role
         exit
     fi

     # Terminate the instance
     ec2-terminate-instances $INSTANCE

     # Take the instance out of the load balancer
     elb-deregister-instances-from-lb smallpayroll-http --instances $INSTANCE

     # Delete the last line of the file
     sed -i '$d' $FILE
  ;;

  *)
      echo "You may only launch or terminate"
      exit
  ;;
esac

長いスクリプトに見えますが、その大部分はエラーのチェックに費やされています。このスクリプトはまず、パラメーターを構文解析し、launch 関数と terminate 関数を分割して case 文に組み込んでいます。起動の部分では、スクリプトはインスタンスを起動し、インスタンスの状態が “running” になるまで待機します。インスタンスが “running” になると、インスタンスの IP アドレスを取得し、サーバーへの Web リクエストに対して welcome という単語が含まれるレスポンスが正常に返されるまで待機します。

インスタンスを停止する場合は、起動する場合よりも遥かに簡単です。インスタンス ID はユーザーのホーム・ディレクトリーにある dotfile に書き込まれ、新しいインスタンスがファイルの終わりに次々と追加されていきます。インスタンスを停止する場合、スクリプトはファイルの最終行を読み取り、最後に起動されたインスタンスのインスタンス ID を取得します。そして終了コマンドを実行してインスタンスをロード・バランサーから除去し、続いてインスタンスが含まれるファイルの最終行を削除すればよいのです。

ロールごとに固有のファイルがあることに注意してください。今のところは Web ロールしかありませんが、後でロールが追加されます。

リスト 5 のなかで興味深い要素は、ec2-run-instances に渡されている -d パラメーターです。このパラメーターに含まれる情報をインスタンスが読み取るには、そのインスタンスだけがアクセスできる特殊な URI にアクセスします。この情報はストリングの形を取ります。リスト 5 では、このパラメーターによってサーバーのロールおよびデータベース・サーバーがインスタンスに渡されています。

初期化スクリプトを作成する

システム・ブート時に実行される初期化スクリプトは、現行バージョンのアプリケーションでインスタンスを構成するとともに、適切なアプリケーション構成を開始します。リスト 6 のコードは、制御スクリプトから渡された情報を使用して、インスタンスおよびアプリケーションの構成を決定します。このコードは SYSV 起動スクリプトの一部にすることも、ブート時に一度実行される rc.local ファイルに組み込むこともできます。

リスト 6. アプリケーションの起動スクリプト
USER=payroll
HOME=/home/payroll
SRC=/etc/smallpayroll

# If this is a fresh AMI, set up the application directories
if [ ! -d $HOME/releases ]; then
  echo "Setting up environment"
  cp $SRC/Capfile $HOME
  # Listing 2
  su - $USER -c "cd $HOME && sh $SRC/setup_environment"
fi

echo "Deploying the application"
su - $USER -c "cd $HOME && /opt/ree/bin/cap deploy"

# Grab the user supplied data. 169.254.169.254 returns data unique to the instance.
USER_DATA=`/usr/bin/curl -s http://169.254.169.254/2007-01-19/user-data`
DBHOST=`echo $USER_DATA | sed 's/.*DB=\([0-9\.]*\).*/\1/'`
ROLE=`echo $USER_DATA | sed 's/.*ROLE=\([a-zA-Z]*\).*/\1/'`
logger "Starting application with DBHOST=$DBHOST and ROLE=$ROLE"

# If available, put the dbhost in /etc/hosts
if [ "$DBHOST" != "" ]; then
  sed -i '/dbhost/d' /etc/hosts
  echo "$DBHOST dbhost" >> /etc/hosts
fi

# Depending on the role...
case "$ROLE" in
  'app')
    # Web server... start up mongrel
    su - $USER -c "mongrel_rails cluster::start \
      -C $HOME/current/config/mongrel_cluster.yml"
  ;;
  *)
    logger "$ROLE doesn't make sense to me"
  ;;
esac

リスト 6 は、いくつかの変数を初期化するところから始まっています。次にアプリケーション・ユーザーのホーム・ディレクトリーをチェックして、cap deploy:setup タスクが以前に実行されたかどうかを調べ、まだ実行されていない場合にはこのタスクを実行します。その後、デプロイメントが実行されて、最新のコードが使用可能になります。

最新のコードが使用可能になると、スクリプトはインスタンスに渡されたメタデータをチェックし、sed コマンドによってコンポーネントを変数に抽出します。DBHOST 変数が設定されている場合は、アプリケーションがデータベースの検索場所を把握できるように、この変数の値が /etc/hosts に取り込まれます。次にロールがチェックされ、サーバーがアプリケーション・サーバーとして指定されている場合には、Mongrel サーバーが起動されます。

リスト 5 とリスト 6 を合わせると、かなりの量のコードになります。けれどもこの 2 つのコードが、あらゆる種類のサーバーの起動および停止を自動化するための基盤となります。リスト 4 の crontab が用意されていれば、トラフィックのピーク期間中に追加サーバーがオンラインになり、サイトの負荷が下がってくると、追加サーバーが停止されることになります。次は、このフレームワークを拡張して、種類の異なる複数のサーバーを起動できるようにします。


ジョブの非同期処理

動的 Web サイトを効率化するためによく使われる 1 つの手法は、長期間実行されるリクエストをバックグラウンド・プロセスに移すことです。長期間実行されるジョブは、通常、実際のリクエストほど時間的な制約がありません。そこで、アプリケーションがレポート実行のリクエストをジョブの処理システムに送信し、Ajax (Asynchronous JavaScript + Extensible Markup Language) を使用してバックグラウンドでジョブが完了したかどうかをポーリングするという手法を使えます。ユーザーにはアプリケーションが動作中であることを示すある種のスピナーが表示されますが、よりインタラクティブなリクエストに対応する Mongrel プロセスが妨害されることにはなりません。

この手法を使うことによって、ジョブを処理するために十分なリソースを確保する必要がなくなるわけではありません。ジョブ・キューが長くなり過ぎると、ユーザーはレポートを待つのにうんざりしてしまいます。こうした状況は、動的サーバーを起動するには理想的です。何らかの方法でジョブのバックログをモニターし、バックログが一定のしきい値を超えた時点で、新しいサーバーを起動してジョブの処理を支援させます。そしてしばらくしたら、そのサーバーを停止するという仕組みです。

ノー・フリー・ランチ

新しいサーバーは無料で実行できるわけではありません。したがって、新しいサーバーを起動するかどうかを決定するには、経済的な事情も考慮に入れる必要があります。

この連載で用いている m1.small インスタンスの時間単価は 8.5 セント (US) です。料金はこのように時間単位で設定されているため、インスタンスを起動すると、たとえ 1 分間しかサーバーを実行しないとしても 1 時間分の料金を支払わなければなりません。処理量を増やすためにインスタンスのサイズを大きくすると、それだけ時間単価も高くなります。

新しいサーバーを起動する場合のコスト、そして新しいサーバーを起動しないことにした場合のコストを確実に把握してください。サービスに時間がかかると、顧客は満足しません。サービスの速度が上がれば、顧客の満足度は上がりますが、コストも同じく高くなります。

バックグラウンド処理は、collectiveidea フォークという優れた delayed_job gem によって行われます (「参考文献」を参照)。この gem では、たった 1 行のコードでジョブを開始することができます。しかも、優先キューが実装されているため、重要なジョブがルーチン・ジョブの後回しになることがありません。ジョブの処理デーモンは Rails アプリケーション・ディレクトリーから実行され、データベースを使用して作業をリクエストします。これはつまり、現行のスクリプトを拡張して delayed_job デーモンを処理できるということです。

ジョブの処理サーバーをサポートするように初期化スクリプトを更新する

リスト 6 をもう一度見てみると、このスクリプトではインスタンス・メタデータをチェックして、servercontrol スクリプトから渡された内容を調べています。ROLE パラメーターはサーバーのジョブとして、アプリケーション・サーバーを意味する app を指定しています。各サーバー・タイプに対する指示は、case 文にラップされるので、リスト 7 ではこの case 文を拡張して delayed_job ロールを処理するようにしています。

リスト 7. delayed_job サーバーの起動操作
case "$ROLE" in
  'app')
    # For an application server, start the mongrels
    su - payroll -c "/opt/ree/bin/mongrel_rails cluster::start \
      -C /home/payroll/current/config/mongrel_cluster.yml"
  ;;
  'job')
    # For a job server, figure out what kind of machine this is, and run
    # an appropriate number of job processing daemons
    TYPE=`curl -s http://169.254.169.254/2007-08-29/meta-data/instance-type`
    case "$TYPE" in
      'm1.small') NUM=2 ;; # 2 per ECU * 1 ECU
      'm1.large') NUM=8 ;; # 2 per ECU * 4 ECUs
      *) NUM=2 ;;
    esac
    su - payroll -c "RAILS_ENV=production $HOME/current/script/delayed_job -n $NUM start"
  ;;
  *)
  logger "$ROLE doesn't make sense to me"
  ;;
esac

上記のスクリプトはサーバーのロールをチェックし、サーバーがアプリケーション・サーバーであれば、Mongrel サーバーを起動します。サーバーがジョブ・サーバーの場合には、どんな種類のインスタンス上でそのサーバーが実行されているのかをスクリプトは調べます。この情報は、169.254.169.254 仮想ホスト上にある別の URL から入手することができます。おおよその推定として、スクリプトは ECU (Elastic Compute Unit) ごとに 2 つの delayed_job ワーカーを起動します。作業負荷は、それぞれの場合に応じてこの例とは異なると思います。

これで、コマンドライン上で launch job を渡すことによって、servercontrol スクリプトが新しいジョブ・サーバーを起動できるようになりました。

キューをモニターする

キューのバックログをモニターする方法はいくつかあります。その 1 つは、ジョブとそのジョブの処理に要した時間を追加することです。そしてジョブの処理時間がしきい値を超えた時点で、新しいサーバーを起動するようにします。しかしこの方法には、実際にキューに未処理のジョブが溜まってくると、処理が遅れているかどうかを判断するまでに相当な時間がかかるという欠点があります。そこで最も単純なソリューションとなるのは、データベースに対して未処理のリクエスト数を問い合わせ、コントローラーでこの情報に対処することです。リスト 8 に、そのようなコントローラーの一例を記載します。

リスト 8. キューの長さを示すコントローラー
class QueueController < ApplicationController
  def length
    render :text => Delayed::Job.count(
      :conditions => "priority > 0 AND failed_at IS NULL").to_s
  end
end

リスト 8 の実行内容は単純なもので、キューの長さ (具体的には、0 より大きい優先度を持ち、まだ処理されていない (つまり、まだ失敗していない) ジョブの数) を示し、この数をテンプレートに渡す代わりに直接レンダリングしているだけです。/queue/length までブラウズすると、現行のキューのバックログが表示されます。

必要に応じて新しいジョブ・サーバーを起動する

キューの長さは容易に判断できるようになったので、今度はこのデータを処理するスクリプトが必要です。リスト 9 に、そのためのスクリプトを記載します。

リスト 9. 必要に応じて追加ジョブ・サーバーを起動するスクリプト
#!/bin/bash

# What's the length of the queue
QUEUE=`curl -s http://app.smallpayroll.ca/queue/length`
# How many servers are running now? (zero out if file doesn't exist)
SERVERS=`wc -l $HOME/.ec2-job`
if [ "$SERVERS" == "" ]; then SERVERS=0; fi

# launch up to two servers while the queue is over 20
if [ $SERVERS -le 2 -a $QUEUE -gt 20 ]; then
  servercontrol launch job
fi

# Terminate one instance if the queue is under 5
if [ $SERVERS -gt 0 -a $QUEUE -lt 5 ]; then
  export TZ=/usr/share/zoneinfo/UTC 
  LAST=`tail -1 $HOME/.ec2-job`
  # But only if the server has run for at least 45 minutes
  UPTIME=`ec2-describe-instances $LAST | \
    awk '/INSTANCE/ {t=$10; gsub(/[\-:T\+]/, " ", t); print systime() - mktime(t) }'`
  if [ $UPTIME -gt 2700 ]; then
    servercontrol terminate job
  fi
fi

ビジネス・ロジック

リスト 9 では、単純なアルゴリズムを実装しています。サーバーを動的に起動するという考えの背後にある原理を説明するにはこれで十分ですが、さらに賢いスクリプトにすることも可能です。例えば、現時点での単純なスクリプトではなく、キューの長さを監視し続け、一定の安定期間が過ぎるとサーバーを停止するというスクリプトにすることもできます。

リスト 9 のコードは、cron から 5 分間隔で実行される必要があります。このコードが最初に取得するのは、キューの長さと現在実行中のジョブ・サーバーの数です。ジョブ・サーバーの数は、動的に実行されるサーバーのインスタンス ID が含まれる .ec2-job ファイルの長さから取得されます。キューの長さが 20 より大きく、追加で実行されているジョブ・サーバーの数が 2 に満たない場合には、サーバーが起動されます。複数のサーバーが実行中で、キューの長さが 5 を下回る場合、スクリプトはインスタンスを終了するかどうかを調べるために、さらに別のチェックを行います。

このスクリプトではTZ 環境変数を設定してタイムゾーンを UTC (Coordinated Universal Time) に設定してから、最後に実行されたジョブ・サーバーのインスタンス ID を取得し、クエリーで起動時刻を取得します。取得した起動時刻は awk による置換に渡されて、サーバーがこれまで稼働していた時間 (秒単位) が割り出されます。サーバーの稼働時間が 45 分を超えた場合、インスタンスを停止することができます。稼働時間が 45 分以下の場合、サーバーはそのまま稼働し続けます。

45 分間という基準は、時期尚早にサーバーを停止しないために設定されています。このように設定されていれば、キューに未処理のジョブがなくなってきた後、再び未処理のジョブが溜まってきた場合でも、サーバーはまだ稼働しています。


コンテンツ配信ネットワークを使用する

Web サイトにアクセスするということは、画像、CSS (Cascading Style Sheets)、および JavaScript コードをロードするということでもあります。CDN と呼ばれるサービスは、静的アセットをキャッシュしてインターネット上の多数のサーバーに静的アセットを配布します。その結果、ユーザーは静的アセットに高速にアクセスし、複数のファイルを並行してダウンロードできるようになります。Amazon で提供している Amazon CloudFront という名前の CDN サービスは、Amazon の他のサービスと同様、使用した分だけ支払うという方式の従量課金制オファリングです。

CDN から提供されるアセットは、異なるサーバーからリクエストされるため、その URL が変わります。一例として、http://app.smallpayroll.ca/stylesheets/style.css とすると、アプリケーション・サーバーからスタイル・シートがロードされますが、http://cdn0.smallpayroll.ca/stylesheets/style.css とすると CDN からロードされます。CDN に該当するアセットがない場合、CDN はそのアセットをオリジン・サーバーから取得し、それをキャッシュに入れてからユーザーに渡します。

CloudFront は、オリジン・サーバーが Amazon S3 (Amazon Simple Storage Service) バケットであるという点で、その他の CDN とはやや異なります。CloudFront を使用するには、まず Amazon S3 バケットに静的アセットを収容してから、URL を変更して CloudFront ホストを使用するようにします。

CloudFront をセットアップする

CloudFront のメイン・ページ (「参考文献」にリンクを記載) から、サインアップ用のリンクをクリックします。作業を続けるには、起動を通知する E メールを受信するまで待たなければなりません。

起動の通知を受け取ったら、Amazon Web Services コンソール・ページに進んで「Amazon CloudFront」タブをクリックします。すると、図 1 に示すページが表示されます。

図 1. CloudFront Distributions ダッシュボード
CloudFront Distributions ダッシュボード

作成済みのディストリビューションは 1 つもないことに注意してください。ディストリビューションとは、ファイルを収容する単なるバケットにすぎません。このバケットが、Amazon S3 バケットに関連付けられます。次に、「Create Distribution (ディストリビューションの作成)」をクリックして、図 2 のページを表示してください。

図 2. ディストリビューションの作成
Creating a distribution

このページで、以下の作業を行います。

  1. Delivery Method (配信方式)」では、「Download (ダウンロード)」を選択します。
  2. Origin (オリジン)」ドロップダウン・リストでは、Amazon S3 バケットの名前を選択するか、任意のAmazon S3 ツールで作成するバケットの名前を入力します。
  3. Logging (ロギング)」では、ログを使用する予定でない限り、「Off (オフ)」を選択します。
  4. CNAMEs」フィールドに、ドメインの下に含まれる 4 つの名前を入力します (例えば、cdn0.smallpayroll.ca から cdn3.smallpayroll.ca)。
  5. Comments (コメント)」には任意のテキストを入力します。
  6. Distribution Status (ディストリビューションのステータス)」では、「Enabled (使用可能)」を選択します。

Create (作成)」をクリックすると、「CloudFront」タブが再び表示され、そこに作成したディストリビューションが表示されます (図 3 を参照)。

図 3. 構成されたディストリビューション
構成されたディストリビューション

このページ上には、djdzxdmb99068.cloudfront.net というようなドメイン名が表示されます。あとは、DNS サーバーにアクセスして 4 つの CNAME を構成し、これらの CNAME がディストリビューションのドメイン名を指すようにすればよいのです。Amazon Elastic Load Balancing のセットアップ方法も、4 つの番号付き名前を作成するという点を除けば、これと同様です。

ディストリビューションに関連付けた Amazon S3 バケットにテスト用ファイルを配置します。すると、ディストリビューションのドメイン名 (http://djdzxdmb99068.cloudfront.net/test.htm など) にブラウズすれば、この文書を確認できるはずです。この文書を確認できたら、バケット内のtest.htm ファイルを表示してみてください。アクセス拒否のエラーが表示された場合は、ファイルに対するパブリック・アクセスが有効に設定されていることを確認してください (そのための方法は、Amazon S3 バケットの管理に使用しているツールによって異なります)。このテストが上手く行ったら、今度は上記の手順で作成した CNAME (http://cdn1.smallpayroll.ca/test.htm) を使用して試してください。

ファイルを同期させる

パブリック・ディレクトリーのコンテンツは、Amazon S3 のオリジン・バケットにコピーする必要があります。それには以下のコードを実行するのが最も簡単な方法です。

s3sync.rb -r -p public/ smallpayroll-cdn:

このコードは、Rails アプリケーションのルート・ディレクトリーから実行します。-r オプションは、このコピーを再帰的コピーとして指定します。-p オプションは、すべてのファイルを公開して読み取り可能にします。

この手順を自動化するには、「参考文献」に記載されている、ファイル同期用 gem のリンクにアクセスしてください。

アプリケーションを更新する

単純な方法としては、画像、JavaScript、CSS のすべてのリンクが Web サーバーではなく、CDN リンクのいずれかを指すように変更することによって、アプリケーションを更新することができますが、image_tag などの Rails URL ヘルパーを使用すれば、Rails が自動的に更新作業を行ってくれます。config/environments/production.rb に以下の行を追加してください。

ActionController::Base.asset_host = "cdn%d.smallpayroll.ca"

このコードは、本番構成に 1 つの行、すなわち asset_hosts として定義されたホストから静的アセットが提供されるようにするための行を追加します。%d はデフォルトで 0 から 3 までの数値に置き換えられます。したがって、Rails に対し、cdn0.smallpayroll.ca、cdn1.smallpayroll.ca、cdn2.smallpayroll.ca、および cdn3.smallpayroll.ca を循環するように指示していることになります。この 4 つは、CloudFront の応答先として構成したホストと同じです。ホストは 4 つあり、一般にブラウザーの接続はホストごとに 2 つに制限されることから、ブラウザーは一度に最大 8 つのアセットをダウンロードできることになります。

これでアプリケーションは、可能な場合には CDN を使用するようになりました。改善された処理速度をお楽しみください!


まとめ

アプリケーションは、CDN として Amazon CloudFront を使用するようになりました。これによって、ダウンロード時間は短縮され、クライアントが並列にダウンロードできるようになったため、ページのロードが高速になりました。また、アプリケーションを更新して、コンピューティング・リソースが動的に増減されるようにもなりました。スケジュールに従って起動、停止されるコンピューティング・リソースの他に、負荷に応じて起動、停止されるコンピューティング・リソースもあります。これまで作成したスクリプトを拡張すれば、他の事態にも対処することができます。

このアプリケーションにまだ足りないのは、管理です。今の状態では、ある特定の時点で稼働しているコンピューターの正確な数がわかりません。デプロイメントがまだ自動化されていないわけは、サーバーのリストが常に流動的であるためです。このままでは、サーバー自体のパフォーマンスを正確に把握することはできません。連載の最終回では、この問題に対処するのでお見逃しなく。

参考文献

学ぶために

  • AssetTagHelper: Ruby の資料で AssetTagHelper モジュールについて調べてください。このモジュールの関数を使用してリンクおよび画像を生成すると、CDN に簡単に移行できることがわかります。
  • Preparing a Rails Application for SVN」では、あらゆる Rails アプリケーションを Subversion によって管理する方法を説明しています。さまざまなログ・ファイルがあることから、バージョンの管理は思っているより易しくありません。
  • Performance tuning considerations in your application server environment」(Sean Walberg 著、developerWorks、2009年1月) では、ジョブ・サーバーの使用を含め、アプリケーションを高速化する方法について説明しています。
  • Amazon EC2 インスタンスには、インスタンスが環境について学習するのに役立つインスタンス・メタデータがいくつか含まれています。Amazon EC2 マニュアルのこの章とメタデータ・カテゴリーのリストに目を通して、Amazon EC2 では何ができるのかを把握してください。
  • developerWorks の Cloud Computing エリアで、クラウド内でのアプリケーションを開発およびデプロイするために必要なリソースを入手して、クラウド開発の最先端に立ってください。
  • Alfa Jango のブログ: CloudFront をさらに踏み込んで理解するために、圧縮アセットの操作方法、そしてアプリケーションまたは CDN のいずれかから選択的にアセットを提供する方法を学んでください。
  • developerWorks Linux ゾーンで、Linux 開発者および管理者向けのハウツー記事とチュートリアル、そしてダウンロード、ディスカッション、フォーラムなど、豊富に揃った資料を探してください。
  • さまざまな IBM 製品および IT 業界についての話題に絞った developerWorks の Technical events and webcasts で時代の流れをキャッチしてください。
  • 無料の developerWorks Live! briefing に参加して、IBM 製品およびツール、そして IT 業界の傾向を素早く学んでください。
  • developerWorks の on-demand demos で、初心者向けの製品のインストールおよびセットアップから熟練開発者向けの高度な機能に至るまで、さまざまに揃ったデモを見てください。
  • Twitter で developerWorks をフォローするか、developerWorks で Linux に関するツイートのフィードに登録してください。

製品や技術を入手するために

  • Amazon S3 内に複数の AMI を保管している場合、古い AMI は削除してください。Amazon S3 File Manager は、数多くのスタンドアロンのアプリケーションやブラウザー・プラグインの機能に匹敵する Web ベースのファイル・マネージャーです。AMI を削除したら、忘れずに ec2-deregister を実行してください。
  • S3Sync は、Amazon S3 との間でのファイルのコピー、そしてバケットの操作に役立つツールです。
  • Capistrano はよく使用されているデプロイメント・パッケージで、Rake と同じように動作します。
  • delayed_job は、Rails および ActiveRecord と統合するのに最適なバックグラウンド・ジョブ・サーバーです。文頭のリンクをクリックすると、このプロジェクトで現在主流となっている collectiveidea フォークにアクセスすることができます。
  • synch_s3_asset_host は、Amazon S3 の配布元バケットとアプリケーションの静的ファイルとの同期をいとも簡単な作業にしてくれる gem です。
  • ご自分に最適な方法で IBM 製品を評価してください。評価の方法としては、製品の試用版をダウンロードすることも、オンラインで製品を試してみることも、クラウド環境で製品を使用することもできます。また、SOA Sandbox では、数時間でサービス指向アーキテクチャーの実装方法を効率的に学ぶことができます。

議論するために

  • My developerWorks コミュニティーに加わってください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者が主導するブログ、フォーラム、グループ、ウィキを調べることができます。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux, Cloud computing, Open source
ArticleID=559897
ArticleTitle=Linux アプリケーションを Amazon クラウドにマイグレーションする: 第 3 回 スケーラビリティーの確立
publish-date=10062010