各種スクリプト言語でさくらのクラウドを操作できるライブラリ「saklient」入門
さくらのクラウドで提供されているAPIをPHPやC#、Python、Rubyといった言語で利用するためのライブラリが「saklient」だ。今回はこのsaklientの構造や基本的な使い方と、これを使ってクラスタを自動スケーリングされるやり方を紹介する。
APIをより手軽に扱うためのAPI「saklient」
さくらインターネットが提供しているIaaS(Infrastracture as a Service)型クラウドサービス「さくらのクラウド」では、仮想サーバーやネットワークなどを操作するためのコントロールパネルが用意されている。このコントロールパネルはWebブラウザ経由で操作でき、サーバーやネットワークの作成や削除、起動/停止といった操作が行えるようになっている。
いっぽう、さくらのクラウドではRESTベースのAPIも提供されており、HTTPクライアント経由でこれらを利用することで、コントロールパネルを経由せずにさくらのクラウドに対する各種操作を実行できる。そして、さまざまな言語からこのAPIを簡単に呼び出せるようにしたクライアントライブラリが今回紹介する「saklient」だ。
saklientでできること
saklientでは、PHPおよびC#、Python、Ruby、Perl向けのライブラリが公開されている。PHPおよびC#、Python、Ruby版については現在Beta版、Perl版についてはAlpha版という段階であるが、さくらのクラウドAPIで可能な操作は現時点でおおむねサポートされている。具体的には、下記のような操作が可能だ。
- 各種リソースの作成、設定変更、削除
- 各種リソースの一覧取得
- 個々のリソースの情報取得
- 各種リソースに対する操作
扱えるリソースはサーバーのほか、ディスクやスイッチ、アーカイブ、ISOイメージ、ブリッジ、ルーター、インターフェイス、アプライアンスなど、さくらのクラウドで扱われているものすべてが対象だ。
saklientは、どのプラットフォームでも同じようなコードで各種操作を実行できるように設計・実装されている。そのため、それぞれの言語で標準的なライブラリ構造や利用方法とはやや異なる形でのコード記述が必要となる場合もあり、最初は全体像が分かりにくいかもしれない。そこで今回は、まずsaklientの全体構造について解説し、続いて例としてサーバーのリソースを監視し、状況に応じて各種操作を行うようなスクリプトの例を紹介する。
saklientのインストール
saklientはGitHubで配布されている(PHP、C#、Python、Ruby)ほか、各言語で一般的に使われているパッケージマネージャからの入手が可能だ。
まずPHPでは、依存性管理ツールComposerを使ってのインストールが可能だ。Composerが利用できる環境で次のような内容を記述したcomposer.jsonファイルを作成し、「composer install」コマンドを実行することで自動的にダウンロードとインストールを行える。
{ "require": { "sakura-internet/saklient": "dev-master" } }
$ composer install
なお、ComposerはRed Hat Enterprise Linnuxの標準パッケージとしては用意されていないが、拡張パッケージ集であるEPELからそのパッケージを入手できる。
C#では、NuGetでのインストールが可能だ。Visual Studioを利用している場合、NuGetのパッケージ管理画面から「Saklient」を検索してインストールすれば良い。
Pythonでは、easy_installもしくはpipでのインストールが行える。
# easy_install saklient もしくは # pip install saklient
Rubyではgemコマンドでインストールが可能だ。
# gem install saklient
saklientの全体構造
それでは、さっそくsaklientの全体構造と基本的な使い方について紹介していこう。
なお、saklientでは各言語で推奨される命名ルールに従ってメソッド名やプロパティ名、クラス名、名前空間名などが付けられている。たとえばPHPではメソッド名としてlowerCamelCase(2つめ以降の単語の最初を大文字にする)が、C#ではUpperCamelCase(すべての単語の最初を大文字にする)が、PythonやRubyではsnake_case(すべて小文字で単語間はアンダースコアでつなぐ)が採用されている。しかし、各言語間でクラス構成やメソッド構成、単語の組み合わせ自体はほぼ同じとなっている。
下記ではクラス名やメソッド名としてPythonのものを例として挙げている。それ以外の言語の場合はそれぞれの名称に置き換えて読んで欲しい。
saklientで用意されているクラス
まず、saklientでは多数のクラスが用意されているが、それらはリソース(resource)クラスとモデル(model)クラス、APIクラスの3つに大別できる(図1)。
まずリソースクラスだが、これはその名のとおりサーバーやディスク、インターフェイスといったさくらのクラウドが扱うリソースをクラス化したものだ。各リソースに対応するクラスが用意されており、saklient.cloud.resourcesという名前空間内で定義されている。
各リソースクラスでは、そのリソースに対する操作を行うメソッドが実装されている。たとえばサーバーを示すServerクラスでは、サーバーを起動するbootメソッドやシャットダウンするshutdownメソッド、サーバープランを変更するchange_planメソッドなどが用意されている。また、そのリソースに関連するリソースにはプロパティ経由でアクセスできる。たとえばServerクラスの場合、サーバー名はnameプロパティで、サーバープランはplanプロパティでアクセスできる。また、ifaceプロパティではサーバーに接続されているネットワークインターフェイスのリストを取得できる。
モデルクラスは主にリソースの取得や新規作成といった操作を行うためのクラスで、saklient.cloud.models名前空間内で定義され、「Model_」という接頭辞が付いている。各リソースに対応するモデルが用意されており、たとえばServerリソースの取得や新規作成を行うモデルとしてModel_Serverクラスが用意されている。
APIクラスはさくらのクラウドAPIへの認証や各種モデルへのアクセスを行うためのクラスだ。名前空間的にはsaklient.cloud.api内に含まれており、saklientを利用するには以下のようにしてまずこのクラスをロードしておく必要がある。
PHPの場合 use Saklient\Cloud\API; C#の場合 using Saklient.Cloud; Pythonの場合 from saklient.cloud.api import API Rubyの場合 require 'saklient/cloud/api'
saklientを使ってさくらのクラウドを操作する場合、まずAPIクラスから目的のモデルを取得し、モデルのcreateメソッドを使ってリソースを新規作成したり、findメソッドを使って既存のリソースを検索し、これらの操作で得られた各種リソースクラスのメソッドやプロパティを使って目的の処理を行う、という流れとなる。
なお、saklient.cloud名前空間内には「facility」や「product」といった名前空間もある。facilityは設備情報(リージョン情報)にアクセスするためのモデルを取得するためのクラスが、productは商品情報(サーバー/ディスク/ルータープランやライセンス情報)などにアクセスするためのモデルを取得するためのクラスが含まれている。設備情報や商品情報についてもリソースクラスとモデルクラスがそれぞれ用意されており、これらのモデルクラスについてもAPIクラス経由でアクセスできるようになっている。
saklientでのユーザー認証
saklientではさくらのクラウドAPIの場合と同様、認証にAPIキー(Access Token)とシークレットトークン(Access Token Secret)を利用する。具体的には、引数としてAPIキーとシークレットトークン、対象のゾーンを指定してAPIモジュールのauthorizeメソッドを呼び出すと認証が行われ、APIクラスのインスタンスが返される。
PHPの場合 $api = API::authorize($token,$secret,$zone); C#の場合 API api = API.Authorize(token,secret,zone); Pythonの場合 api = API.authorize(token,secret,zone) Rubyの場合 api = Saklient::Cloud::API::authorize(token, secret, zone)
なお、ここでtoken変数はAPIキーを、secret変数はシークレットトークンを文字列として格納した変数となる。これらはさくらのクラウドコントロールパネルの「設定」画面にある「APIキー」画面で入手できる(図2)。
ここで「追加」をクリックし、適当な名前を指定することでAPIキーを作成できる。それぞれのAPIキーのAPIキー(Access Token)とシークレットトークン(Access Token Secret)は、APIキーの詳細表示画面で確認できる(図3)。
また、zone変数はゾーン名を格納する変数で、東京第1なら「tk1a」、石狩第1は「is1a」、石狩第2は「is1b」、sandboxは「tk1v」となる。
モデルの取得
前述のとおり、saklientでは各リソースに対応するモデル経由でリソースにアクセスする。たとえばサーバーへの操作を行うにはまずModel_Serverクラスのインスタンスを取得し、createメソッドやfindメソッドを呼び出して実際のリソースに対応するServerクラスのインスタンスを取得する。
リソースの取得に必要となる各種モデルは、APIクラス経由で取得できる。たとえばMode_Serverクラスには次のようにしてアクセスできる。
PHPの場合 $model_server = $api->server; C#の場合 Model_Server model_server = api.Server; Pythonの場合 model_server = api.server Rubyの場合 model_server = api.server
リソースの取得/作成
各モデルクラスには、リソースを検索するためのfindメソッドや指定したIDに対応したリソースを取得するget_by_idメソッド、リソースを新規作成するためのcreateメソッドが用意されている。たとえば作成済みのすべてのサーバーを取得するコードは次のようになる。
PHPの場合 $servers = $model_server->find(); C#の場合 List<Server> servers = model_server.Find(); Pythonの場合 servers = model_server.find() Rubyの場合 servers = model_server.find
また、特定の条件に合致するサーバーのみを取得するには、findメソッドを実行する前に絞り込みを行うためのメソッドを呼び出しておく。たとえば名前に「test」という文字列を含むサーバーを取得するには、この文字列を引数として与えてwith_name_likeメソッドを実行した後、findメソッドを実行すれば良い。
PHPの場合 $model_server->withNameLike('test'); $servers = $model_server->find(); C#の場合 model_server.WithNameLike("test"); List<Server> servers = model_server.Find(); Pythonの場合 model_server.with_name_like('test') servers = model_server.find() Rubyの場合 model_server.with_name_like('test') servers = model_server.find
なお、絞り込みのためのメソッド(with_*メソッド)は戻り値としてそのモデルクラスのインスタンスを返すので、次のように記述することも可能だ。
PHPの場合 $servers = $model_server->withNameLike('test')->find(); C#の場合 List<Server> servers = model_server.WithNameLike("test").Find(); Pythonの場合 servers = model_server.with_name_like('test').find() Rubyの場合 servers = model_server.with_name_like('test').find
いっぽう、リソースを新規に作成する場合はまずcreateメソッドを実行してリソースクラスのインスタンスを取得し、続けて各種プロパティを設定し、最後にsaveメソッドを実行してその内容を反映させる、といった手順となる。
PHPの場合 $server = $api->server->create(); $server->name = $name; $server->description = $description; $server->tags = [$tag]; $server->plan = $api->product->server->getBySpec($cpu, $mem); $server->save(); C#の場合 Server server = api.Server.Create(); server.Name = name; server.Description = description; server.Tags = new List<string> {tag}; server.Plan = api.Product.Server.GetBySpec(cpu, mem); server.Save(); Pythonの場合 server = api.server.create() server.name = name server.description = description server.tags = [tag] server.plan = api.product.server.get_by_spec(cpu, mem) server.save() Rubyの場合 server = api.server.create server.name = name server.description = description server.tags = [tag] server.plan = api.product.server.get_by_spec(cpu, mem) server.save
なお、planプロパティはServerPlanクラスのオブジェクトで指定するようになっている。ここではModel_ServerPlanクラスのインスタンスであるapi.product.serverオブジェクトに対しget_by_specメソッドを呼び出し、引数で指定したCPUコア数およびメモリサイズに対応するServerPlan型オブジェクトを取得している。
リソースの操作
サーバーの起動や停止など、リソースに対する操作はリソースクラスのメソッドを呼び出すことで行える。たとえばサーバーを起動するにはbootメソッドを使用する。
PHPの場合 $server->boot(); C#の場合 server.Boot(); Pythonの場合 server.boot() Rubyの場合 server.boot
ここまではServerクラスについて解説してきたが、これ以外のリソースに対しても基本的な処理の流れは同じだ。たとえばディスクに対する操作を行う場合はModel_Diskクラスのオブジェクトであるapi.diskを用いてDiskクラスのオブジェクトを取得し、各種操作を実行することになる。
サーバーのリソースを監視するスクリプトを作成する
続いてはsaklientを使った運用の例として、サーバーのリソースを監視し、状況に応じてサーバーを追加したり、削除するという管理用スクリプトを作る例を紹介しよう。
サーバーのCPU時間を監視する
さくらのクラウドの管理コンソールでは、各サーバーの「アクティビティ」画面でCPU時間およびネットワークトラフィック、ストレージのWrite/Read状況を確認できるようになっている。これらの情報はさくらのクラウドAPIやsaklientでも取得可能で、これらを使ってサーバーを監視するようなスクリプトを作成できる。
まずCPU時間だが、これはServerクラスのactivityプロパティで確認できる。このプロパティはServerActivityクラスのオブジェクトとなっており、そのfetchメソッドを実行することで最新のアクティビティ情報(CPU時間情報)を取得できる。取得した情報はsamplesプロパティで参照可能だ。このsamplesプロパティは日時とその時点でのCPU時間を含むServerActivitySample型オブジェクトの配列となっている。
また、ネットワークトラフィックについてはIfaceクラスのactivityプロパティを、ストレージのI/O状況についてはDiskクラスのactivityプロパティを使って同様にして取得できる。
たとえば次のコードは、IDで指定したサーバーのCPU時間を取得するものだ。
def get_activity(api, server_id): '''get target server's activity''' server = api.server.get_by_id(server_id) server.reload() activity = server.get_activity() activity.fetch() samples = activity.samples return samples
この関数で取得したデータは、次のようなコードで出力できる。
for sample in samples: if sample.is_available: sys.stdout.write("{}: {}\n".format(sample.at, sample.cpu_time))
このコードを実行すると、次のような出力が得られる。ここから分かるように、得られるのは5分おきのCPU時間(単位は秒)となっており、これらのデータを参照することでサーバーの負荷状態を確認できる。
2015-09-13 19:05:00+09:00: 0.0033333333333 2015-09-13 19:10:00+09:00: 0.0033333333333 2015-09-13 19:15:00+09:00: 0.0033333333333 2015-09-13 19:20:00+09:00: 0.0066666666667 2015-09-13 19:25:00+09:00: 0.0033333333333 2015-09-13 19:30:00+09:00: 0.0033333333333 2015-09-13 19:35:00+09:00: 0.0066666666667 2015-09-13 19:40:00+09:00: 0.0033333333333 : :
次のcheck_server_status関数は、取得したデータのうちtime_range引数で指定した数だけの最新データを対象に、CPU負荷がthreshold引数で指定された秒数を超える状況がrepeat_times引数で指定した回数以上連続で続いた場合Falseを、そうでない場合はTrueを返すものだ。
def check_server_status(api, server_id, threshold, repeat_times, time_range): samples = get_activity(api, server_id) counter = 0 for sample in samples[-time_range:]: if sample.is_available: if sample.cpu_time > threshold: counter += 1 if counter >= repeat_times: return False else: counter = 0 return True
たとえば過去1時間のデータを対象に、CPU時間が500ミリ秒以上の状態が5回連続したら高負荷状態と判断する場合、time_range引数に12、threshold引数に0.5、repeat_times引数に5を指定すればよい。
サーバーを新規作成する
サーバーを新規に作成するには、Model_Serverクラスのcreateメソッドを使ってServerクラスのオブジェクトを作成し、サーバー名や説明、タグ、プランなどをプロパティで指定してからsaveメソッドを実行する。その後、作成したサーバーに対してサーバーに接続するストレージやネットワークインターフェイスを指定する形になる。
たとえば、あらかじめ指定された条件でサーバーを新規作成する関数は次のようになる。server_name引数では作成するサーバーの名前を指定し、またこのサーバーのディスクはarchive_id変数で指定したIDを持つアーカイブを元に作成される。さらに、このサーバーのネットワークインターフェイスはswytch_id変数で指定したスイッチに接続される。
def add_server(api, server_name): cpu = 1 #CPU数 memory = 2 #メモリ容量(GB) tags = ['test servers'] #付加するタグ server_desc = 'auto created server' #サーバーの説明 archive_id = '************' #使用するアーカイブID swytch_id = '************' #接続するスイッチのID # サーバーを作成 server = api.server.create() server.name = server_name server.description = server_desc server.tags = tags server.plan = api.product.server.get_by_spec(cpu, memory) server.save() # ネットワークインターフェイスを作成して # 指定したネットワークに接続 iface = server.add_iface() swytch = api.swytch.get_by_id(swytch_id) iface.connect_to_swytch(swytch) # ディスクを作成 disk = api.disk.create() disk.name = server_name disk.description = server_desc disk.tags = tags disk.source = api.archive.get_by_id(archive_id) disk.plan = api.product.disk.ssd disk.save() # ディスクの作成が完了するのを待機 if not disk.sleep_while_copying(): raise Exception('failed') # ディスクを接続しサーバーをブートする disk.connect_to(server) server.boot()
複数のサーバーを対象に負荷チェックを行う
サーバー負荷に応じてサーバー台数を増減させる運用の場合、すべてのサーバーが重い、といった状況になった際にサーバーを増やして負荷分散を行うことが多いだろう。複数のサーバーを対象に負荷チェックを行う場合、次のコードのようにModel_Serverクラスのwith系メソッドで条件を指定して対象とするServerクラスのオブジェクトを取得し、それらに対して順にチェックを行えば良い。
def servers_status(api, server_name, threshold, repeat_times): servers = api.server.with_name_like(server_name).find() for server in servers: if check_server_status(api, server.id, threshold, repeat_times, time_range): return True return False
あとはこのチェック結果を参照し、全サーバーの負荷が一定以上となっていればサーバーを新規作成すれば良い。このコードは次のようになる。
if not servers_status(api, server_name, threshold, repeat_times): servers = api.server.with_name_like(server_name).find() new_server_name = server_name + str(len(server)) add_server(api, new_server_name)
実際の運用時における注意点
今回例示したコードはサーバーの負荷が一定以上の場合にサーバーを追加するものだが、実際の運用時にはもう少し複雑な条件設定が必要となる場合もあるだろう。また、サーバーの負荷が一定以下となった際にサーバーを削除する、という処理も必要だ。
また、今回はsaklientの使用方法に絞って解説しているため、サーバーやネットワーク側の設定については触れていない。さくらのクラウドでは柔軟なネットワーク構成が可能であるため、一概にどういったやり方が良いかを示すのは難しいが、たとえば新たに作成するサーバーでは起動と同時に各種サービスが稼動できるよう、あらかじめ各種設定が完了したディスクアーカイブを用意しておき、それをコピーしてサーバーのディスクとして使用する、という方法が考えられる。
ネットワークについてはDHCPで自動的にIPアドレスを取得できるようにしておき、またサーバーの起動時にはトラフィックがそこに自動的に振り分けられるよう、ロードバランサへの通知などを行う必要もある。
こういった細かい設定は利用者側で行う必要があり、若干の手間もあるが、逆に言えば環境や必要な要件にに応じて監視やスケーリング方法などを柔軟にカスタマイズできるということでもある。
スクリプトによるクラウドの操作は、特にサーバーの台数が増えてきた場合に非常に効果的だ。作業を自動化することで、繰り返し行うような作業で人的ミスを減らせるというメリットもある。さくらのクラウドを利用しているユーザーは、ぜひ試して見てはいかがだろうか。