連載の第 1 回では SmallPayroll.ca アプリケーションを Amazon クラウドにマイグレーションし、第 2 回で、このアプリケーションがより堅牢になるように強化しました。さらに第 3 回の作業によって、アプリケーションは負荷に応じて自動的にサーバーを追加、除去できるようになりました。これはつまり、ある特定の時点でアクティブなサーバーの数も、それぞれの IP アドレスも予測できないため、サーバーへの接続が課題となるということです。結局のところ、クラウド環境は従来のデータ・センターとは異なります。
クラウドが持つ動的な性質は、アプリケーションをデプロイするのも困難にします。デプロイするたびにサーバーのリストが異なるとしたら、どのようにアプリケーションを更新すればよいのでしょうか。さらに言えば、サーバーを監視して障害を発見するにはどうすればよいのでしょうか。
「普通」のデータ・センターでは、コンピューターに任意の名前を付けて、都合の良い IP アドレスを指定することができます。そして (お望みであれば) データ・センターに出掛けて行って、サーバーがそこにあることを目で確認することができます。サーバーの情報を記録しておくには、スプレッドシートやソフトウェアを使用するという場合もあれば、あるいは単に情報を頭で記憶するか、テキスト・ファイルに記録するという場合もあるでしょう。また、構成の一貫性を維持するために、構成管理を行うことになります。
クラウド環境は、従来のデータ・センターとはまったく異なります。その理由は、クラウド環境では多くの機能の制御を任せることになるためです。IP アドレスを予測することもできなければ、2 台のサーバーが同じサブネットに配置されることさえも保証されません。マイグレーション作業がリソースの自動スケーリングの段階まで進むと、新しいノードが起動された途端、これまで手動で苦労して行った構成作業はすべて水の泡と消えてしまいます。20 台の Web サーバーがあることがわかっていて、それぞれの名前が予測できるという前提で作成したスクリプトは、クラウドでは役に立ちません。
幸い、簡単な規則を定めることによって、このような問題を回避できるだけでなく、物理データ・センターでのアップタイム時間を向上させることもできます。
人々は、サーバーに付ける名前を考えたり、賢明な IP アドレス設計を行ったりすることに相当な時間を費やしがちです。Amazon EC2 (Amazon Elastic Compute Cloud) インスタンスには、かなりランダムな IP アドレスと、そのアドレスに基づいた名前が付けられます。もちろんサーバーの名前を変更することもできますが、それには多くの場合、環境の他の部分に関する知識も必要になってきます。例えば、サーバーに webprd42 という名前を付けるには、最後に起動したサーバーが webprd41 であることがわかっていなければなりません。
それよりも望ましいソリューションは、名前や IP アドレスに頼らないこと、そしてサーバーの名前が問題にはならないようなソフトウェアを作成することです。
物理環境では、通常はサーバーの構成を手動で変更すれば、問題を切り抜けることができます。けれどもサーバーが自動的に起動される場合、手動による変更は適用されません。変更するたびに AMI (Amazon Machine Image) を再バンドルするという方法もありますが、変更をどうやって実行中の他のサーバーに適用するかという問題は依然として残ります。有難いことに、Puppet および Cfengine などの優れたソフトウェア・パッケージは、サーバーの構成を自動的に変更してくれます (「参考文献」を参照)。
アプリケーションの変更をデプロイするという点に関しては、構成管理の 1 つの側面として検討するだけの価値があります。汎用の構成管理ツールでもアプリケーションの変更をデプロイすることはできますが、アプリケーションをデプロイしてマイグレーションおよび構成のロールバックを管理する際の特定のステップを再現するとなると、汎用の構成管理ツールで対応するのは困難です。そのため、Rails コミュニティーではアプリケーションのデプロイ作業に対処するツールを提供しています。その一例が、Capistrano です (「参考文献」を参照)。
構成管理については、これを 2 つの異なる問題として捉えると役に立ちます。問題の 1 つは、ソフトウェア・パッケージのインストールから各種デーモンの構成に至るまで、サーバーをどのように管理するかです。そしてもう 1 つの問題は、ソフトウェアの新しいバージョンをデプロイする方法をどのように制御するかです。
重要なのは、サーバーが何を行っているかを知ることです。CPU、ディスク・リソース、メモリー、そしてネットワークはすべて不可欠なコンポーネントであり、監視する必要があります。また、アプリケーション自体を含め、システムで実行中のデーモンには、監視すべき他のメトリックがある場合もあります。例えば、アプリケーションの応答時間、そして Web サーバーおよびアプリケーション・サーバーに対する接続数を監視することで、問題が発生する前にその問題を見つけられる可能性があります。
サーバーを監視して結果をグラフ化するには、多くのツールを使用することができます。けれども問題は、オンライン状態になった新しいサーバーの監視を開始する方法、そしてサーバーがオフライン状態になると同時に監視を停止する方法です。
Amazon EC2 のような動的環境の管理方法を調べてみると、3 つの一般的な管理パターンが浮かび上がってきます。
- クライアント・サイド (各サーバー) からのポーリング: 各サーバーが中央サーバーにリソースに関する問い合わせを行います。このパターンを使用すれば、すべてのサーバーのアドレスを知っておく必要はなくなります。その一方、各サーバーはそれぞれ独自のスケジュールで稼働するため、クライアント・サイド (各サーバー) からのポーリングのタイミングを制御することはできません。
- サーバー・サイド (中央サーバー) からのプッシュ: このパターンでは、クラウド・プロバイダーのアプリケーション・プログラミング・インターフェース (API) を使用して、現在のサーバーのリストを取得するための問い合わせをしておくと、中央サーバーの方から各サーバーへのアクセスが行われ、サーバーのリストが提供されます。このパターンには時間がかかるとともに、管理ツールが環境の動的性質を把握していなければなりませんが、更新を同期できるという利点があります。
- クライアント・サイド (各サーバー) からの登録: 各サーバーはオンライン状態になると中央サーバーに自ら登録し、オフライン状態になる前にその登録を解除します。この方法は複雑さが増しますが、クラウド環境内でクラウド対応でないツールを使用することができます。
このパターンを実装するのは簡単です。クライアント・サイドでは単純に、事前定義されたスケジュールに従って既知のサーバー (中央サーバー) に対してポーリングを行って、クライアント・サイドへの命令の有無を確かめるだけに過ぎません。クライアント・サイドへの命令がなければ、サーバー・サイドはクライアント・サイドにそのことを通知します。このパターンの欠点は、クライアント・サイドがサーバー・サイドに対してポーリングを行わなければ、サーバー・サイドから命令を発行できないことです。急を要する変更でも、次のポーリングが行われるまで待たなければなりません。
ポーリングの最適な使い道は、サーバーの構成管理です。構成管理ツールとしてよく使われている Reductive Labs の Puppet パッケージでは、Puppetmaster と呼ばれるプロセスが中央サーバーで実行されます。クライアント・サイドで実行される Puppet デーモンはこの Puppetmaster をポーリングして、そのクライアント・サイドに適した構成マニフェストの有無を調べます。構成マニフェストには、例えば「NTP デーモンがインストールされて実行中であることを確認すること」など、それぞれのコンポーネントに必要とされる最終状態が指定されています。Puppet はこれらのマニフェストを読み取って問題を修正するというわけです。
お使いのディストリビューションに Puppet が含まれていなければ、gem install puppet facter を実行して簡単にインストールすることができます。Puppet はセキュリティー・システムを実装しますが、これが事態を複雑にしています。Puppetmaster とやりとりするには、クライアント・サイドに署名付きの鍵がなければならないからです。クライアント・サイドからの接続に対して自動的に鍵に署名するよう Puppetmaster に指示することもできますが、そうなると誰もが構成ファイルをダウンロードできることになってしまいます。これに代わるソリューションは、Puppetmaster を無視し、マニフェストを独自に配布して Puppet ツールをローカルで実行することです。
クライアント・サイドに Puppet マニフェストを実行させるためのイベント・シーケンスは以下のとおりです。
- 更新されたマニフェストのコピーと、関連するすべてのファイルをサーバー・サイドからダウンロードします。
- マニフェストに対して Puppet を実行します。
ステップ 1 に最適なツールは、変更されているファイルだけをダウンロードする rsync です。ステップ 2 では、puppet コマンド (Puppet インストールに含まれています) でマニフェストを実行します。この手法には、注意しなければならない点が 2 つあります。
- サーバー・サイドではクライアントのSSH (Secure Shell) 公開鍵を受け入れる必要があります。この鍵は、AMI で配布することができます。
- マニフェストに指定する構成ファイルはすべて、マニフェストと一緒にコピーする必要があります。組み込み Puppet ファイル・サーバーの場合は、証明書も必要となるため、このファイル転送手法を使用することはできません。
サンプル・マニフェストでは、クライアント・サイドにネットワーク・タイム・プトロコル (NTP) が正しく構成されていることを確認します。これには、ソフトウェアがインストールされていること、構成ファイルが変更されていること、そしてデーモンが実行中であることの確認も含まれます。リスト 1 に、最上位レベルのマニフェストを記載します。
リスト 1. 最上位レベルのマニフェスト
import "classes/*"
node default {
include ntpclient
}
|
リスト 1 は最初に classes ディレクトリー内のすべてのファイルをインポートします (ファイルごとに、1 つのコンポーネントに関する情報が含まれています)。次に、すべてのノードにリスト 2 に定義された ntpclient クラスを組み込みます。
リスト 2. ntpclient クラス
class ntpclient {
package {
ntp:
ensure => installed
}
service {
ntpd:
ensure => true,
enable => true,
subscribe => [ Package [ntp], File["ntp.conf"] ],
}
file {
"ntp.conf":
mode => 644,
owner => root,
group => root,
path => "/etc/ntp.conf",
source => "/var/puppetstage/files/etc/ntp.conf",
before => Service["ntpd"]
}
}
|
Puppet 言語の詳細についての説明はこの記事ではしませんが、リスト 2 の内容を大まかに説明すると、ここで定義しているのは、ntp パッケージに含まれる ntpclient というクラス、ntpd というサービス、/etc ディレクトリー内の ntp.conf というファイルです。ntp パッケージがインストールされていない場合、Puppet は yum または apt-get などの適切なツールを使用してパッケージをインストールします。サービスが実行中ではなく、起動スクリプトに含まれていなければ、それを修正します。ntp.conf ファイルが /var/puppetstage/files/etc に置かれたコピーと異なる場合には、ファイルを更新します。before および subscribe の行は、構成が変更された場合に、デーモンが再起動されることを確実にするためにあります。
サーバー・サイドではマニフェストおよびファイルを /var/puppetdist に保管し、クライアント・サイドではこのディレクトリーのツリーを /var/puppetstage にコピーします。ディレクトリー・ツリーの概要は、リスト 3 のとおりです。
リスト 3. /var/puppetdist の内容
/var/puppetdist/
|-- files
| `-- etc
| `-- ntp.conf
`-- manifests
|-- classes
| `-- ntp.conf
`-- site.pp
|
最後に、リスト 4 によってファイルが同期され、マニフェストがクライアント・サイドで実行されます。
リスト 4. マニフェストを同期して実行するためのクライアント・サイドのコード
#!/bin/bash /usr/bin/rsync -avz puppetserver:/var/puppetdist/ /var/puppetstage/ --delete /usr/bin/puppet /var/puppetstage/manifests/site.pp |
このコードが cron から定期的に実行されると、マニフェストでのすべての変更が拾い出され、クラウド・サーバーに適用されます。サーバーの構成が多少でも変更されている場合、Puppet はサーバーを新しい構成に適合させるための手段を講じます。
サーバー上での構成の更新に、サーバー間での同期が必要になることはめったにありません。パッケージを更新する必要があるとしても、通常は 30 分の時間枠があれば十分です。けれどもアプリケーションの更新となると、変更を一度にロールアウトする必要があり、しかもそのタイミングを制御しなければなりません。そのためによく使われているツールが、Capistrano です。Capistrano では、Capistrano のドメイン特化言語を使用してスクリプトを作成し、さまざまなタスクを実行します。リスト 5 は、アプリケーションを一連の既知のサーバーにプッシュするための必要最小限の Capistrano スクリプトです。
リスト 5. 単純な Capistrano スクリプト
set :application, "payroll"
set :repository, "https://svn.smallpayroll.ca/svn/app/trunk/"
set :user, 'payroll'
set :home, '/home/payroll'
set :deploy_to, "#{home}"
set :rails_env, "production"
role :db, "174.129.174.213", :primary => true
role :web, "174.129.174.213", "184.73.3.169"
|
リスト 5 に記載されている行のほとんどは、Capistrano のデフォルトの振る舞いを変更するための変数を設定するためのものです。デフォルトの振る舞いでは、Capistrano はすべてのサーバーに SSH でアクセスし、ソース・コード管理ツールを使ってアプリケーションのコピーをチェックアウトします。最後の 2 行で定義しているのは使用中のサーバーです。具体的に言うと、データベース・サーバーと Web サーバーを定義しています。これで、これらのサーバーの役割が Capistrano に既知となります (したがって、目的に合わせて役割を拡張することができます)。
リスト 5 では、サーバーを事前に定義しなければならないことが問題となります。けれども AWS (Amazon Web Services) API を使用すれば、実行時に Capistrano にサーバーのリストを確定させることができます。それにはまず、以下のコードを実行します。
gem install amazon-ec2 |
上記のコードで AWS API を実装するライブラリーをインストールした後、Capistrano のレシピ (deploy.rb) をリスト 6 のように変更します。
リスト 6. 実行時に Capistrano にサーバーのリストを動的にロードさせるための変更
# Put this at the beginning of your deploy.rb
require 'AWS'
# Change your role :web definition to this
role(:web) { my_instances }
# This goes at the bottom of the recipe
def my_instances
@ec2 = AWS::EC2::Base.new( :access_key_id => ENV['AWS_ACCESS_KEY_ID'],
:secret_access_key => ENV['AWS_SECRET_ACCESS_KEY'])
servers = @ec2.describe_instances.reservationSet.item.collect do |itemgroup|
itemgroup.instancesSet.item.collect {|item| item.ipAddress}
end
servers.flatten
end
|
リスト 6 では Web の役割を、静的な定義から、my_instances 関数によって返されるサーバーの動的なリストに変更します。関数がサーバーのリストを返すために使用するのは、Amazon EC2 API の DescribeInstances 呼び出しです。この API は、同じ予約 ID で一緒に起動された複数のインスタンスをグループ化したフォーマットでデータを返します。外側の collect ループでこれらの予約グループを繰り返し処理し、内側の collect ループで各グループに含まれるサーバーを繰り返し処理します。この処理による結果は、配列からなる配列です。サーバーの IP アドレスから成る 1 次元配列に平坦化されたこの配列が、呼び出し側に渡されます。
Capistrano がサーバーの動的なリストを処理する手段を提供しているのは幸いです。Capistrano がこのようなフックを提供していなかったとしたら、別の手段が必要となっていたことでしょう。
サーバーの動的なリストを簡単に使用できないアプリケーションでは、クラウド・サーバーを使って他のアプリケーションに登録させるという方法で問題に対処することができます。このプロセスは通常、以下の 2 つの形のいずれかを取ります。
- クラウド・サーバーが別のサーバーに接続してスクリプトを実行し、それによって管理アプリケーションを直接更新します。
- クラウド・サーバーがメタデータを設定したファイルを共通の場所 (Amazon S3 (Simple Storage Service) など) にドロップします。他のスクリプトは、そのファイルを使用してそれぞれの構成ファイルを再構成します。
パフォーマンス管理ツールとしてよく使われている Cacti では、SNMP (Simple Network Management Protocol) によって各種のメトリックまたはスクリプトをグラフ化したり、これらのグラフをダッシュボードやメタグラフに統合したりすることができます (「参考文献」を参照)。Cacti の使用に伴う制約は、Cacti Web インターフェースまたはコマンドライン・スクリプトで管理対象のサーバーを構成しなければならないことです。この例では、クラウド・サーバーが Cacti サーバーに接続することによって自己構成を行います。
Cacti はテンプレート・システムをベースとするため、グラフに大量の変更を行う場合でも至って簡単に対処することができます。ただし、すべてのコマンドライン・ツールはテンプレート ID で動作することから、まずはどの ID を使用するかを調べなければなりません。リスト 7 に、ホスト・テンプレートを見つける方法を示します。作業を楽にするために、ホスト・テンプレートにはいくつかのデータ要素があらかじめ入力されています。
リスト 7. ホスト・テンプレートのリスト
$ php -q /var/lib/cacti/cli/add_device.php --list-host-templates Valid Host Templates: (id, name) 0 None 1 Generic SNMP-enabled Host 3 ucd/net SNMP Host 4 Karlnet Wireless Bridge 5 Cisco Router 6 Netware 4/5 Server 7 Windows 2000/XP Host 8 Local Linux Machine |
id が 3 のテンプレートが対象とするホストは、巷に出回っている Linux® ディストリビューションのほとんどで使用できる Net-SNMP デーモンを実行します。汎用バージョンのデーモンではなく、この特定のデーモンを使用することで、Linux 固有のコンポーネントを簡単に監視することができます。
リスト 8 に記載するのは、id が 3 のホスト・テンプレートを使用しているという前提で使用可能なグラフのリストです。
リスト 8. グラフ・テンプレートのリスト
$ php -q /var/lib/cacti/cli/add_graphs.php --list-graph-templates --host-template-id=3 Known Graph Templates:(id, name) 4 ucd/net - CPU Usage 11 ucd/net - Load Average 13 ucd/net - Memory Usage |
リスト 8 の 3 つのグラフは、デフォルトの Cacti ディストリビューションに用意されているものです。他にも多くのグラフを追加して、--host-template-id オプションをオフにして表示することも、インターネット上のソースからグラフをインポートすることもできます。
リスト 9 に、新しいデバイスを追加し、続いて CPU グラフを追加する方法を示します。
リスト 9. 新規デバイスとグラフの追加
$ php -q /var/lib/cacti/cli/add_device.php --description="EC2-1.2.3.4" \ --ip=1.2.3.4 --template=3 Adding EC2-1.2.3.4 (1.2.3.4) as "ucd/net SNMP Host" using SNMP v1 with community "public" Success - new device-id: (5) php -q /var/lib/cacti/cli/add_graphs.php --host-id=5 --graph-type=cg \ --graph-template-id=4 Graph Added - graph-id: (6) - data-source-ids: (11, 12, 13) |
リスト 9 ではまず、IP アドレスが 1.2.3.4 に設定されたホストを追加しています。次に、デバイス ID として返された 5 を使用して、CPU 使用率のグラフ (グラフ・タイプ cg のテンプレート 4) を追加します。その結果、このグラフの ID と、監視対象となった各種データ・ソースの ID が出力されます。
これで、リスト 9 の手順を簡単にスクリプトにすることができます。リスト 10 に、スクリプトの一例を記載します。
リスト 10. add_to_cacti.sh
#!/bin/bash
IP=$1
# Add a new device and parse the output to only return the id
DEVICEID=`php -q /var/lib/cacti/cli/add_device.php --description="EC2-$IP" \
--ip=$IP --template=3 | grep device-id | sed 's/[^0-9]//g'`
# CPU graph
php -q /var/lib/cacti/cli/add_graphs.php --host-id=$DEVICEID --graph-type=cg \
--graph-template-id=4
|
スクリプトに渡す最初のパラメーターは、$IP という変数に保存します。この IP アドレスを使って add_device.php スクリプトを実行し、その出力を grep コマンドでフィルタリングすることによって、ID が含まれる行だけに絞り込みます。こうしてフィルタリングした出力を、数値だけを出力する sed スクリプトに取り込み、このスクリプトによって出力された値を $DEVICEID という変数に保存します。
デバイス ID を保存した後は、add_graphs.php スクリプトを呼び出すだけでグラフを追加することができます。ただし、この CPU グラフは最も単純な例であることに注意してください。グラフのタイプによっては、これよりも多くのパラメーターが必要になります。
add_to_cacti.sh スクリプトを Cacti サーバーに配置すれば、あとはクラウド・サーバーがこれを実行するだけのことです。リスト 11 に、このスクリプトを呼び出す方法を示します。
リスト 11. クラウド・サーバーからの cacti スクリプトの呼び出し
#!/bin/bash MYIP=`/usr/bin/curl -s http://169.254.169.254/2007-01-19/meta-data/public-ipv4` ssh cacti@cacti.example.com "/usr/local/bin/add_to_cacti.sh $MYIP" |
リスト 11 では Amazon EC2 メタデータ・サーバーを呼び出してパブリック IP アドレスを取得した後、リモートにある Cacti サーバーでコマンドを実行しています。
この連載では、単一のサーバーから AWS クラウドへのアプリケーションのマイグレーション方法について説明してきました。新しいサーバーの起動から、ロード・バランサーまで、Amazon EC2 オファリングを利用するための改善を段階的に加え、最後にこの記事で動的クラウド環境の管理について取り上げ、構成管理に使用できるパターンをいくつか提案しましました。
クラウド・リソースを使用する際の導入コストの低さを考えると、クラウドへのマイグレーションを検討して演習を行ってみるべきです。本番環境のアプリケーションの実行にはクラウドを使用しないことにしたとしても、マイグレーションを検討するなかで、クラウドで実行できる内容について多くを学べるだけでなく、システムの管理スキルを磨く結果にもなると思います。
学ぶために
- 「LPI exam 301 prep, Topic 306: Capacity planning」(developerWorks、2008年4月) で、システムをモニターし、その結果を測定する方法について詳しく説明しています。
- Scott Patten が Amazon S3 を Ruby で使用する方法を説明する『The S3 Cookbook』は、Leanpub から PDF で出版されています。この本では 60 の問題を取り上げ、そのそれぞれを解決する方法をコードと併せて説明しています。
- developerWorks の Cloud Computing ゾーンで、クラウド内でのアプリケーションを開発およびデプロイするために必要なリソースを入手して、クラウド開発の最先端に立ってください。
- 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を実行してください。 - Capistrano はよく使用されているデプロイメント・パッケージで、Rake と同じように動作します。
- Cfengine は、最もよく使われている UNIX® 用の構成管理ツールです。この軽量のツールは、多数のマシンで動作します。
- Cacti は、RRDTool をベースに作成されたネットワーク・グラフ作成ツールで、想像し得るほとんどすべてのものをグラフにすることができます。このツールをデータ・センターで使用している場合、これをグラフ化するためのプラグインを誰かがすでに作成している可能性は大いにあります。
- Puppet は、Cfengine の制約を克服するために Ruby で作成された構成管理ツールです。このツールを使い始めるには、この記事の著者にとっても大いに参考になった『Pulling Strings with Puppet』(James Turnbull 著、Apress、2008年) が最適な入門編となります。
- ご自分に最適な方法で IBM 製品を評価してください。評価の方法としては、製品の試用版をダウンロードすることも、オンラインで製品を試してみることも、クラウド環境で製品を使用することもできます。また、SOA Sandbox では、数時間でサービス指向アーキテクチャーの実装方法を効率的に学ぶことができます。
議論するために
- My developerWorks コミュニティーに加わってください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者が主導するブログ、フォーラム、グループ、ウィキを調べることができます。
