さくらのクラウド APIをRubyから操作する

 さくらのクラウドでは、公開されているAPIを利用することでサーバーの作成や各種設定などを自動処理できる。今回はこのAPIをRubyを使って操作する例を紹介する。

プログラミング言語からさくらのクラウドAPIを利用するメリット

 前回はNode.jsベースのコマンドラインツール「sacloud」を使い、さくらのクラウドAPIを使ってサーバーの作成や設定などを行う流れを紹介した。sacloudはシェルやバッチファイルなどから利用するには便利だが、条件分岐を行ったり、複雑な操作を行うにはやや不向きだ。そういった目的では、Rubyなどのスクリプト言語からさくらのクラウドAPIを操作すると良い。

 Rubyなどのスクリプト言語は、シェルスクリプトよりも複雑な処理を記述しやすいほか、ほかの既存ライブラリと組み合わせることで容易にさまざまな処理を実現できるというメリットがある。たとえばサービスの負荷を監視して負荷が一定値を超えたら自動的に新たなサーバーを作成したり、Webブラウザからサーバーの管理を行う独自のコンソールを実装する、といったことも可能になるわけだ。

 さくらのクラウドAPIはHTTPベースのシンプルなリクエストで利用できるため、これらを利用するプログラムも比較的簡単に実装が可能だ。そこで今回は、RubyスクリプトからさくらのクラウドAPIを利用する方法について紹介しよう。

さくらのクラウドAPIの基礎知識

 まず、さくらのクラウドAPIを利用するために必要な前提知識について簡単に説明しておこう。なお、より詳しい情報に付いては「さくらのクラウド API v1.1 ドキュメント」を参照してほしい(図1)。

図1 さくらのクラウド API v1.1 ドキュメント
図1 さくらのクラウド API v1.1 ドキュメント

 さくらのクラウドAPIではHTTPSを使ってリクエストおよびその処理結果(レスポンス)のやり取りを実行する。このとき、認証はBasic認証もしくはDigest認証が使用され、またリクエストおよびレスポンスのデータはJSON形式で送受信される。文字コードはUTF-8で、日付のフォーマットはISO 860120(「2014-01-02T03:04:05+0900」のような形式)となっている。

 Basic認証およびDigest認証の際は、ユーザー名としてAPIキーを、パスワードとしてシークレットトークンを利用する。これらはさくらのクラウドのコントロールパネルで参照が可能だ。これらについては前回記事で解説しているので、そちらも参照していただきたい。

 Basic認証やDigest認証といった認証方法自体についてはここでは言及しないが、多くのHTTPライブラリには標準でこれらを使った認証機構が実装されているので、それらをそのまま利用すれば良いだろう。

 また、さくらのクラウドAPIでは以下のようなURLに対しGETやPOST、PUT、DELETEなどのHTTPリクエストを送信することで処理を実行する。

https://secure.sakura.ad.jp/cloud/zone/<ゾーンID>/api/cloud/1.1/<操作するAPIのパス>

 ここで「ゾーンID」は対象とする処理を行いたいゾーンを指定するもので、石狩第1ゾーンの場合「is1a」、石狩第2ゾーンの場合「is1b」となる。また、「操作するAPIのパス」は処理ごとに決められているパスだ。どのようなパスが用意されているかはAPIドキュメントで確認できるが、たとえばサーバーの検索や一覧を取得するには「/server」というパスを使用する(ドキュメントのサーバー関連APIページ)。

 同じパスに対するリクエストでもGETやPOSTといったリクエストメソッドが異なると、処理も異なるものになる。たとえば石狩第1ゾーンで稼動しているサーバー一覧を取得するには、以下のURLに対しGETリクエストを送信すれば良い。

https://secure.sakura.ad.jp/cloud/zone/is1a/api/cloud/1.1/server

 いっぽう、このURLに対しPOSTリクエストを送信すると、サーバーを作成する処理が実行される。

 以下では、APIを「<メソッド名> <パス名>」という形で表記することとする。たとえば/serverというパスに対しGETリクエストを送って実行するAPIは「GET /server」と表記する。

 パラメータを含むパスを利用するAPIもある。たとえばIDで指定したサーバの情報を取得するAPIのパスは「/server/:serverid」になる。ここで、「:serverid」部分は実際には対象とするサーバーのIDを指定することになる。たとえば石狩第1ゾーンで稼動している「123456789012」というIDを持つサーバーの情報を取得するには、以下のURLに対しGETリクエストを送信すれば良い。

https://secure.sakura.ad.jp/cloud/zone/is1a/api/cloud/1.1/server/123456789012

RubyでさくらのクラウドAPIを使用する

 RubyではHTTPを扱うための標準ライブラリとして「net/http」が用意されているが、さくらのクラウドAPIではHTTPSを使った暗号化通信が必須となっているので、net/httpにSSL/TSL拡張を実装する「net/https」を利用することになる。

 net/httpsライブラリでは、まずhttpオブジェクトを作成し、そこでSSL関連の設定を行った後、リクエストの種類に応じたリクエストオブジェクトを生成してリクエスト情報を格納し、startメソッドやrequestメソッドを使ってリクエストを送信する、という流れになる。

GETリクエストでAPIを実行する

 以下のサンプルコードは、net/httpsを使ってサーバー一覧を取得する「/server」APIを利用するものだ。ここではまずNet::HTTP.newメソッドを使ってNet::HTTPオブジェクトを作成し、続いてNet::HTTP::Get.newメソッドでGETリクエストに対応したリクエストオブジェクトを生成、最後Net::HTTPクラスのstartメソッドおよびrequestメソッドを使ってHTTPセッションの開始やリクエストの送信を行っている。

 実際に自分の環境でテストする場合、「<アクセストークン>」や「<アクセストークンシークレット>」の部分は適宜利用するさくらのクラウドアカウントに対応するものに置き換えて欲しい。また、ここではゾーンとして石狩第一(「is1a」)を指定しているが、石狩第二ゾーンを利用するする場合は代わりに「is1b」を指定する。

# -*- coding: utf-8 -*-

require 'net/https'
require 'uri'

# アクセストークンおよびゾーン、APIバージョンを指定
token = '<アクセストークン>'
secret = '<アクセストークンシークレット>'
zone_id = 'is1a'
api_version = '1.1'

# アクセスするためのURLを生成
base_url = "https://secure.sakura.ad.jp/cloud/zone/#{zone_id}/api/cloud/#{api_version}"

# 「/servers」にGETリクエストを送信する
uri = URI.parse(base_url + '/server')

# HTTPSを使うための設定
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_NONE

# BASIC認証のための設定
req = Net::HTTP::Get.new(uri.path)
req.basic_auth(token, secret)

# リクエスト送信
res = https.start do |x|
  x.request(req)
end

# 取得したレスポンスを表示
puts res.body

 このコードを実行すると、次のようにJSON形式で指定したゾーン上で稼動している仮想サーバー一覧が表示される。

{"From":0,"Count":11,"Total":11,"Servers":[{"Index":0,"ID":"************","Name":"centos 77","HostName":"localhost","Description":"","ServiceClass":"cloud\/plan\/1core-1gb","CreatedAt":"2014-04-14T18:50:27+09:00","Icon":{"ID":"112300511981","URL":"https:\/\/secure.sakura.ad.jp\/cloud\/zone\/is1a\/api\/cloud\/1.1\/icon\/112300511981.png","Name":"CentOS","Scope":"shared"},"ServerPlan":{"ID":1001,"Name":"\u30d7\u30e9\u30f3\/1Core-1GB","CPU":1,"MemoryMB":1024,"ServiceClass":"cloud\/plan\/1core-1gb","Availability":"available"},"Zone":{"ID":31001,"Name":"is1a","Description":"\u77f3\u72e9\u7b2c1\u30be\u30fc\u30f3","VNCProxy":{"HostName":"sac-is1a-ssl.sakura.ad.jp","IPAddress":"133.242.31.244"},"FTPServer":{"HostName":"sac-is1a-ssl.sakura.ad.jp","IPAddress":"133.242.31.244"},"Region":{"ID":310,"Name":"\u77f3\u72e9","Description":"\u77f3\u72e9","NameServers":["133.242.0.3","133.242.0.4"]}},"Instance":{"Server":{"ID":"************"},"Status":"up","BeforeStatus":"down",
 
 

 さて、さくらのクラウドAPIではこの例のようにリソースの検索や一覧を行うAPIを実行した場合、その結果は次のような形式で返される。

{
  Total: 検索条件にマッチする全レコード数: int,
  From: 取得された最初のレコードのオフセット: int,
  Count: 取得されたレコード数: int,
  リソース名: [
      リソース情報: object,
      リソース情報: object,
      ...
  ]
}

 たとえば取得できたレコード数を知りたければ、返されたJSONデータの「Total」要素を参照すれば良いわけだ。これをプログラムで実現するには、返されたJSONデータをプログラム内で処理しやすいHash型に変換し、目的の要素を参照すれば良い。たとえば取得されたレコード数を調べるには、以下のような処理を行えば良い。

# 取得したJSON形式のレスポンスをオブジェクトに変換
result = JSON.parse(res.body)

# Total要素の値を出力する
puts "Total: " + result['Total'].to_s

 また、人間が読みやすいように整形された形で取得したJSONデータを表示するには、次のようにしてHash型に変換した出力データをJSON.pretty_generateメソッドで整形して出力すれば良い。

puts JSON.pretty_generate(result)

 なお、どのAPIがどのような情報をどういった構造で返すかはド各キュメントの「オブジェクト構造」に記載されている。

POSTリクエストでAPIを実行する

 続いてはPOSTリクエストで実行するAPIの利用法について説明しよう。POSTリクエストの場合、GETリクエストとは異なりリクエストに適切なパラメータを与える必要がある。これには、リクエストに関する情報を格納するNet::HTTP:Postオブジェクトを作り、そこに送信するパラメータをJSON形式で格納すれば良い。具体的には以下のようなコードになる。

# BASIC認証のための設定
req = Net::HTTP::Post.new(uri.path)
req.basic_auth(token, secret)

# 送信するパラメータ
params = {
  ハッシュ形式で送信するパラメータを指定
  
  
}

# リクエストボディにJSON形式に変換したパラメータを格納
req.body=(JSON.generate(params))

 たとえば、サーバーを作成するAPIは「POST /server」となっているが、この処理を実行するスクリプトは以下のようになる。

# -*- coding: utf-8 -*-

require 'net/https'
require 'uri'
require 'json'

# アクセストークンおよびゾーン、APIバージョンを指定
token = '<アクセストークン>'
secret = '<アクセストークンシークレット>'
zone_id = 'is1a'
api_version = '1.1'

# アクセスするためのURLを生成
base_url = "https://secure.sakura.ad.jp/cloud/zone/#{zone_id}/api/cloud/#{api_version}"

# 「/servers」にPOSTリクエストを送信する
uri = URI.parse(base_url + '/server')

# HTTPSを使うための設定
https = Net::HTTP.new(uri.host, uri.port)
https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_NONE

# BASIC認証のための設定
req = Net::HTTP::Post.new(uri.path)
req.basic_auth(token, secret)

# 送信するパラメータ
params = {
  'Server' => {
    'Name' => 'foobar', # サーバー名
    'Zone' => { 'ID' => 31001 },  # ゾーンID
    'ServerPlan' => { 'ID' => 1001 },  # サーバープランID
  }
}

req.body=(JSON.generate(params))

# リクエスト送信
res = https.start do |x|
  x.request(req)
end

# 201以外のHTTPステータスコードが返ってきたら終了する
unless res.code == '201'
  puts res.code + ': ' + res.message
  exit
end

# 取得したJSON形式のレスポンスをオブジェクトに変換
result = JSON.parse(res.body)

# 作成したサーバーのサーバーIDなどを出力
printf("Server %s created. ID: %d\n", result["Server"]["Name"], result["Server"]["ID"])

puts result

 送信するパラメータについて詳しくはAPIドキュメントを参照してほしいが、ここではサーバー名およびゾーンID、サーバープランIDを送信している。

 なお、ゾーンIDやサーバープランIDはドキュメントには記載されていないので、APIを使って取得する必要がある。ゾーンIDを取得するAPIについてはAPIドキュメントの設備関連APIページに、サーバープランIDについては商品関連APIに記載があるが、たとえばゾーンIDを取得するAPI(GET /zone)を実行した場合、次のような結果が得られる。ここから、石狩第1ゾーンのIDは「31001」、第2ゾーンのIDは「31002」であることが分かる。

{
  "From": 0,
  "Count": 2,
  "Total": 2,
  "Zones": [
    {
      "Index": 0,
      "ID": 31001,
      "Name": "is1a",
      "Description": "石狩第1ゾーン",
      "VNCProxy": {
        "HostName": "sac-is1a-ssl.sakura.ad.jp",
        "IPAddress": "133.242.31.244"
      },
      "FTPServer": {
        "HostName": "sac-is1a-ssl.sakura.ad.jp",
        "IPAddress": "133.242.31.244"
      },
      "CreatedAt": "2011-11-03T22:41:11+09:00",
      "Region": {
        "ID": 310,
        "Name": "石狩",
        "Description": "石狩",
        "NameServers": [
          "133.242.0.3",
          "133.242.0.4"
        ]
      }
    },
    {
      "Index": 1,
      "ID": 31002,
      "Name": "is1b",
      "Description": "石狩第2ゾーン",
      "VNCProxy": {
        "HostName": "sac-is1b-ssl.sakura.ad.jp",
        "IPAddress": "133.242.239.244"
      },
      "FTPServer": {
        "HostName": "sac-is1b-ssl.sakura.ad.jp",
        "IPAddress": "133.242.239.244"
      },
      "CreatedAt": "2013-10-01T07:01:04+09:00",
      "Region": {
        "ID": 310,
        "Name": "石狩",
        "Description": "石狩",
        "NameServers": [
          "133.242.0.3",
          "133.242.0.4"
        ]
      }
    }
  ],
  "is_ok": true
}

 また、サーバーIDを取得するAPI(GET /product/server)を実行した場合、次のような結果が得られる。出力が長くなるので割愛しているが、プランIDは「<メモリ容量><コア数(3桁)>」という数字となり、たとえば1コア、メモリ1GBの場合「1001」、2コア、メモリ1GBの場合は「2001」、12コア、メモリ128GBの場合は「128012」となっている。


{
  "From": 0,
  "Count": 42,
  "Total": 42,
  "ServerPlans": [
    {
      "Index": 0,
      "ID": 1001,
      "Name": "プラン/1Core-1GB",
      "Description": "",
      "CPU": 1,
      "MemoryMB": 1024,
      "ServiceClass": "cloud/plan/1core-1gb",
      "Availability": "available"
    },
    {
      "Index": 1,
      "ID": 2001,
      "Name": "プラン/1Core-2GB",
      "Description": "",
      "CPU": 1,
      "MemoryMB": 2048,
      "ServiceClass": "cloud/plan/1core-2gb",
      "Availability": "available"
    },
    {
      "Index": 2,
      "ID": 2002,
      "Name": "プラン/2Core-2GB",
      "Description": "",
      "CPU": 2,
      "MemoryMB": 2048,
      "ServiceClass": "cloud/plan/2core-2gb",
      "Availability": "available"
    },
  
  」
    {
      "Index": 40,
      "ID": 96012,
      "Name": "プラン/12Core-96GB",
      "Description": "",
      "CPU": 12,
      "MemoryMB": 98304,
      "ServiceClass": "cloud/plan/12core-96gb",
      "Availability": "available"
    },
    {
      "Index": 41,
      "ID": 128012,
      "Name": "プラン/12Core-128GB",
      "Description": "",
      "CPU": 12,
      "MemoryMB": 131072,
      "ServiceClass": "cloud/plan/12core-128gb",
      "Availability": "available"
    }
  ],
  "is_ok": true

PUT/DELETEリクエストでAPIを実行する

 さくらのクラウドAPIではGETおよびPOST以外に、PUTやDELETEといったリクエストも利用される。PUTはリソースの設定を行う際に、DELETEはリソースの開放を行う際に使用するリクエストだ。Rubyのnet/httpライブラリでは、これらは「Net::HTTP::Put」および「Net::HTTP::Delete」クラスで実装されており、それぞれのインスタンスを作成してrequestメソッドの引数として与えることでリクエストを送信できる。なお、さくらのクラウドAPIでは、PUTリクエストを行う際はリクエストボディが空の場合でもContent-Lengthヘッダが必須となっているようだ。この場合、以下のように空のデータをリクエストボディに設定してやれば良い。

    # BASIC認証のための設定
    req = Net::HTTP::Put.new(uri.path)
    req.basic_auth(token, secret)

    # 送信するパラメータの設定
    req.body=(JSON.generate({}))

さくらのクラウドAPI経由でサーバーのセットアップを行う

 さて、さくらのクラウドAPIを使ってサーバーの作成を行う例は「POSTリクエストでAPIを実行する」で紹介したが、これだけでは機能するサーバーを作成できない。実際には作成したサーバーにディスクを接続したり、ネットワークの設定を行う作業が必要となる。以下ではこれを行う作業手順を説明しておこう。

 この作業手順については前回も説明しているが、具体的には以下のようになる。

  1. サーバーを作成する
  2. ディスクを作成する
  3. サーバーにディスクを接続する
  4. サーバーにネットワークインターフェイスを作成する
  5. 作成したネットワークインターフェイスをルーターに接続する

 また、ディスク作成時には既存のディスクをコピーしたり、アーカイブからコピーするといったことも可能だ。ここではあらかじめさくらインターネットが用意しているアーカイブをコピーしてディスクを作成し、続いてディスクの内容(ホスト名およびパスワード)を変更する、という手順でディスクの作成を行うことにする。

リクエストを処理するためのクラスを作成する

 今回の例のように複数のリクエストを送信する場合、あらかじめリクエストを処理するためのクラスを作成しておくと便利だ。そこで今回は「sakura-cloud-request.rb」というファイル内に、以下のような「SakuraCloudRequest」というクラスを用意した。

# -*- coding: utf-8 -*-

require 'net/https'
require 'uri'

class SakuraCloudRequest
  def initialize(token, secret, zone='is1a', api_version='1.1')
    @token = token
    @secret = secret
    @zone = zone
    @api_version = api_version
    @base_url = "https://secure.sakura.ad.jp/cloud/zone/#{zone}/api/cloud/#{api_version}"
  end

  # リクエストの送信
  def send(method, path, params={})
    uri = URI.parse(@base_url + path)

    # HTTPSを使うための設定
    https = Net::HTTP.new(uri.host, uri.port)
    https.use_ssl = true
    https.verify_mode = OpenSSL::SSL::VERIFY_NONE

    # リクエストオブジェクトの生成
    case method
    when 'GET'
      request_class = Net::HTTP::Get
    when 'POST'
      request_class = Net::HTTP::Post
    when 'DELETE'
      request_class = Net::HTTP::Delete
    when 'PUT'
      request_class = Net::HTTP::Put
    else
      raise ArgumentError, 'invalid method'
    end

    # BASIC認証のための設定
    req = request_class.new(uri.path)
    req.basic_auth(@token, @secret)

    # 送信するパラメータの設定
    if method == 'POST' || method == 'PUT'
      req.body=(JSON.generate(params))
    end

    # リクエスト送信
    res = https.start do |x|
      x.request(req)
    end

    return res
  end
end

 SakuraCloudRequestクラスでは、アクセストークンおよびアクセストークンシークレット、ゾーン、APIバージョンを指定してインスタンスを作成し、sendメソッドで指定したパスに対し指定したリクエストを送信できる。

 たとえば「GET /server」というリクエストを送信する場合、以下のようにすれば良い。

# アクセストークンおよびゾーン、APIバージョンを指定
token = '<アクセストークン>'
secret = '<アクセストークンシークレット>'
zone_id = 'is1a'
api_version = '1.1'

# リクエストオブジェクトを作成
req = SakuraCloudRequest.new(token, secret, zone_id, api_version)

# 「/server」にGETリクエストを送信する
  res = req.send('GET', '/server')

サーバーの作成

 さて、これらクラスを使ってサーバーおよびディスクの作成、ネットワーク関連の設定などを実行する手順は以下のようになる。

 まず、必要なパラメータを定義してSakuraCloudRequestクラスのインスタンスを作成する。

# -*- coding: utf-8 -*-

require 'json'
require './sakura-cloud-request'

# アクセストークンおよびゾーン、APIバージョンを指定
token = '<アクセストークン>'
secret = '<アクセストークンシークレット>'
zone_id = 'is1a'
api_version = '1.1'

new_password = 'FooBarHogeHoge' # 作成するサーバーに設定するパスワード
host_name = 'Test01' # 作成するサーバーのホスト名
server_name = 'Test01' # 作成するサーバーのサーバー名
disk_name = server_name + '_disk' # 作成するディスクのディスク名

## リクエストオブジェクトを作成
req = SakuraCloudRequest.new(token, secret, zone_id, api_version)

 続いて、サーバーの作成を行う。これは先に説明したとおり、/serverにPOSTリクエストを送信することで実行できる。

## サーバーを作成する
# 送信するパラメータ
params = {
  'Server' => { 
    'Name' => server_name,
    'Zone' => { 'ID' => 31001 }, # 石狩第1ゾーン
    'ServerPlan' => { 'ID' => 1001 }, # 1GB/1コアプラン
  }
}

# /server にPOSTリクエストを送信
puts 'create server...'
res = req.send('POST', '/server', params)

# 201以外のHTTPステータスコードが返ってきたら終了する
unless res.code == '201'
  puts res.code + ': ' + res.message
  exit
end

# 取得したJSON形式のレスポンスをオブジェクトに変換
result = JSON.parse(res.body)

# 作成したサーバーのサーバーIDを取得
server_id = result["Server"]["ID"]
printf("server created. ID: %d\n", server_id)

ディスクの作成

 次に、ディスクの作成を行う。ディスクの作成は、/diskにPOSTリクエストを送信することで実行できる。プランIDについては「2」がHDD、「4」がSSDとなっている。これもドキュメントに明示はされていないが、「GET /product/disk」APIで確認できる。また、「SourceArchive」パラメータを指定することで指定したアーカイブからのコピーが可能となる。この場合、APIは成功時にはステータスコードとして201(Created)ではなく202(Accepted)が返ってくる点に注意したい。ディスクの複製には時間がかかるためだ。

## ディスクを作成
# 送信するパラメータ
params = {
  'Disk' => { 
    'Name' => disk_name,
    'Zone' => { 'ID' => 31001 },
    'Plan' => { 'ID' => 4 },
    'SourceArchive' => { 'ID' => 112500570421 }
  }
}

# 「/disk」にPOSTリクエストを送信
puts 'create disk...'
res = req.send('POST', '/disk', params)

# 202以外のHTTPステータスコードが返ってきたら終了する
unless res.code == '202'
  puts res.code + ': ' + res.message
  exit
end

# 取得したJSON形式のレスポンスをオブジェクトに変換
result = JSON.parse(res.body)

# 作成したディスクのIDを取得
disk_id = result['Disk']['ID']
disk_status = result['Disk']['Availability']
printf("disk created. ID: %d\n", disk_id)

 ちなみに、アーカイブの一覧は「GET /archive」APIで取得できる。このAPIの出力結果を抜粋して以下に示すが、ここでは「CentOS 6.5 64bit (基本セット)」(ID:112500570421)を指定している。

{
  "From": 0,
  "Count": 29,
  "Total": 29,
  "Archives": [
    {
      "Index": 0,
      "ID": "112500513643",
      "DisplayOrder": "100100510011",
      "Name": "CentOS 5.10 64bit (基本セット)",
      "Description": "以下で入力したパスワードはrootユーザに設定されます。\nサーバ作成後、rootユーザでログインしてください。",
      "Scope": "shared",
      "Availability": "available",
      "SizeMB": 20480,
      "MigratedMB": 20480,
      "WaitingJobCount": null,
      "JobStatus": null,
      "OriginalArchive": {
        "ID": "112500513643"
      },
  
  
    {
      "Index": 1,
      "ID": "112500570421",
      "DisplayOrder": "100100605011",
      "Name": "CentOS 6.5 64bit (基本セット)",
      "Description": "以下で入力したパスワードはrootユーザに設定されます。\nサーバ作成後、rootユーザでログインしてください。",
  
  

 また、このようにしてアーカイブからのコピーを行った場合、コピーが完了するまでディスクに関する一部の操作が制限される。コピーの完了を検知するには、以下のように「GET /disk/:diskid」APIを実行してそのステータスをチェックすれば良い。

## ディスクの作成完了まで待機する
puts 'check disk status...'
until disk_status == 'available'
  # 10秒待機する
  sleep(10)
  # 「/disk/:diskid」にGETリクエストを送信してその情報を取得
  res = req.send('GET', '/disk/' + disk_id)
  # 202以外のHTTPステータスコードが返ってきたら終了する
  unless res.code == '200'
    puts res.code + ': ' + res.message
    exit
  end

  result = JSON.parse(res.body)
  disk_status = result['Disk']['Availability']
end
puts 'disk available.'

 ディスクのコピーが完了して利用可能になると、APIの実行結果のうち「Disk.Availability」要素が「’available’」となる。ここでは10秒おきにリクエストを送信し、ディスクが利用可能になるまで待機するようループ処理を行っている。

パスワードやホスト名などを設定する

 続いて、ディスクの内容を書き換えてパスワードやホスト名などの設定を行う。これはアーカイブからディスクを複製した場合にのみ利用できる機能で、「PUT /disk/:diskid/config」APIで実行できる。「:diskid」には対象とするディスクのディスクIDを指定する。

## ディスクの内容を書き換える
# 送信するパラメータ
params = {
  'Password' => new_password,
  'HostName' => host_name
}

# 「/disk/:diskid/config」にPUTリクエストを送信
puts 'modify disk...'
res = req.send('PUT', '/disk/' + disk_id + '/config', params)

# 200以外のHTTPステータスコードが返ってきたら終了する
unless res.code == '200'
  puts res.code + ': ' + res.message
  exit
end

puts 'disk modified.'

サーバーにディスクを接続する

 作成したディスクは、サーバーへの接続処理を実行することでサーバーからアクセスが可能になる。ディスクの接続は「PUT /disk/:diskid/to/server/:serverid」APIで行える。「:diskid」および「:serverid」には、対象とするディスクのディスクIDと接続先のサーバーIDを指定する。

## サーバーにディスクを接続
# 「/disk/:diskid/to/server/:serverid」にPUTリクエストを送信する
puts 'connect disk to server...'
res = req.send('PUT', '/disk/' + disk_id + '/to/server/' + server_id)

# 200以外のHTTPステータスコードが返ってきたら終了する
unless res.code == '200'
  puts res.code + ': ' + res.message
  exit
end

puts 'connected.'

ネットワークインターフェイスの作成

 ネットワークインターフェイスの作成は、「POST /interface」APIで実行できる。

## サーバーにネットワークインターフェイスを追加
# 送信するパラメータ
params = {
  'Interface' => {
    'Server' => {
      'ID' => server_id
    }
  }
}

# 「/interface」にPOSTリクエストを送信する
puts 'add interface...'
res = req.send('POST', '/interface', params)

# 201以外のHTTPステータスコードが返ってきたら終了する
unless res.code == '201'
  puts res.code + ': ' + res.message
  exit
end

# 取得したJSON形式のレスポンスをオブジェクトに変換
result = JSON.parse(res.body)

# 作成したインターフェイスのIDを取得
interface_id = result["Interface"]["ID"]
printf("interface created. ID: %d\n", interface_id)

ネットワークインターフェイスをスイッチに接続

 作成したインターフェイスは、スイッチもしくは共有セグメントに接続して利用する。今回は共有セグメントに接続してインターネットに接続するよう設定した。「PUT /interface/:interfaceid/to/switch/shared」APIでこの処理が実行できる。

## インターフェイスを共有セグメントに接続
# 「/interface/:interfaceid/to/switch/shared」にPUTリクエストを送信する
puts 'connect interface to internet...'
res = req.send('PUT', '/interface/' + interface_id + '/to/switch/shared')

# 200以外のHTTPステータスコードが返ってきたら終了する
unless res.code == '200'
  puts res.code + ': ' + res.message
  exit
end

puts 'connected.'

サーバーを起動する

 以上でサーバーの作成とセットアップはすべて完了だ。最後に「PUT/server/:serverid/power」APIを実行することで、サーバーを起動できる。

## サーバーを起動する
# 「/server/:serverid/power」にPUTリクエストを送信する
puts 'starting server...'
res = req.send('PUT', '/server/' + server_id + '/power')

# 200以外のHTTPステータスコードが返ってきたら終了する
unless res.code == '200'
  puts res.code + ': ' + res.message
  exit
end

puts 'server started.'

さくらのクラウドAPIを利用する際の注意点

 さくらのクラウドAPIを利用する際、注意したいのがAPIによって返ってくるHTTPステータスコードが異なる点だ。APIドキュメントには各コードの意味が解説されているが、どのAPIがどのコードを返すかは明示されていない。そのため、ステータスコードを見て成功か失敗かを判断する場合には十分に注意する必要がある。

クラウドサービスライブラリ「fog」を利用してさくらのクラウドを操作する

 さくらのクラウド以外にもAPI経由で操作できるクラウドサービスを提供している事業者は多くあるが、それらAPIには互換性がない。そこで、さまざまなクラウドサービスのAPIをラッピングし、統一的な手法でアクセスできるようにする「fog」というRuby向けクラウドサービスライブラリがある。fogはさまざまなクラウドサービスに対応しており、さくらのクラウドにも対応している。そこで以下ではfogを使ってさくらのクラウドを操作する例を紹介しておこう。

fogにおけるさくらのクラウド対応

 fogはRubyのモジュール機能を使って実装されており、さくらのクラウド対応は「Fog::Volume::SakuraCloud」および「Fog::Compute::SakuraCloud」というモジュールで実装されている。前者がディスク関連の処理を実装したモジュールで、後者がサーバー関連の処理を実装したモジュールだ。これらに関するドキュメントは、https://github.com/fog/fog/blob/master/lib/fog/sakuracloud/docs/getting_started.mdにて閲覧できる。

 ただし、現時点ではさくらのクラウドのすべての機能がfogから利用できるわけではない。現時点で利用できるのはサーバーの作成/削除、サーバー一覧の取得、サーバーの起動/停止、ディスクの作成/削除、ディスク一覧の取得、SSH鍵の管理、といった機能のみだ。

fogのインストール

 fogはgem形式で提供されているため、次のようにgemコマンドでのインストールが可能だ。

# gem install fog

 インストール後は、スクリプト内で「require ‘fog’」文を実行することでfogの機能を利用できるようになる。

fogを使ってさくらのクラウドを操作する

 次のスクリプトは、fogを使ってさくらのクラウドでサーバーを作成するものだ。

# -*- coding: utf-8 -*-

# ゾーンを指定
SAKURACLOUD_API_ZONE = 'is1a'

require 'fog'

# APIトークンを指定
token = '<アクセストークン>'
secret = '<アクセストークンシークレット>'

compute = Fog::Compute.new({
                             :provider           => 'SakuraCloud',
                             :sakuracloud_api_token => token,
                             :sakuracloud_api_token_secret => secret
                           })

server = compute.servers.create({
                                  :serverplan => '1001', # 1コア/1GBメモリ
                                  :name => 'FogTest',
                                  :volume => {
                                    :diskplan => 4, # SSD
                                    :sourcearchive => '112500570421'
                                    # ↑「CentOS 6.5 64bit (基本セット)」からコピー
                                  }
                                })

p server # 作成されたサーバーの情報を出力

 まず、「SAKURACLOUD_API_ZONE = ‘is1a’」で使用するゾーンを指定している。fogのさくらのクラウドモジュールではコード内の定数で使用するゾーンを決めているので、このような形でゾーンを指定する形になる。

 次に、Fog::Compute.newメソッドでさくらのクラウドのサーバーを操作するためのFog::Computeクラスのインスタンスを作成する。ここで、引数として与えるハッシュの:provider要素に「SakuraCloud」を指定することで、さくらのクラウド用モジュールを使ったインスタンスが作成される。また、アクセストークンやアクセストークンシークレットは:sakuracloud_api_tokenおよび:sakuracloud_api_token_secret要素で指定する。

 サーバーの作成は、Fog::Computeクラスに対しservers.createメソッドを実行することで行える。引数としてはサーバープランID、サーバー名、サーバーに接続するディスクなどのパラメータが指定可能だ。ここでは「CentOS 6.5 64bit (基本セット)」アーカイブからのコピーを行うよう指定している。

 このメソッドはFog::Compute::SakuraCloud::Serverクラスのインスタンスを返し、その出力結果は以下のようになる。

  <Fog::Compute::SakuraCloud::Server
    id="112600360342",
    name="482c35b3-5b57-473c-81c7-9cb6e9bbb3c2",
    server_plan={"ID"=>1001, "Name"=>"プラン/1Core-1GB", "CPU"=>1, "MemoryMB"=>1024, "ServiceClass"=>"cloud/plan/1core-1gb", "Availability"=>"available"},
    instance={"Server"=>{"ID"=>"112600360342"}, "Status"=>"down", "BeforeStatus"=>nil, "StatusChangedAt"=>nil, "MigrationProgress"=>nil, "MigrationSchedule"=>nil, "IsMigrating"=>nil, "MigrationAllowed"=>nil, "ModifiedAt"=>"2014-05-19T19:49:36+09:00", "Host"=>nil, "CDROM"=>nil, "CDROMStorage"=>nil},
    disks=[{"ID"=>"112600360344", "Name"=>"c78c627b-7b88-429f-a92d-e0cd08d28f8f", "Connection"=>"virtio", "ConnectionOrder"=>1, "ReinstallCount"=>0, "Availability"=>"available", "SizeMB"=>20480, "Storage"=>{"ID"=>"3100194012", "MountIndex"=>"3100194012", "Class"=>"iscsi1204"}, "BundleInfo"=>nil}],
    interfaces=[{"ID"=>"112600360343", "MACAddress"=>"9C:A3:BA:23:83:D9", "IPAddress"=>"153.120.83.203", "UserIPAddress"=>nil, "HostName"=>nil, "Switch"=>{"ID"=>"112600296406", "Name"=>"スイッチ", "Scope"=>"shared", "Subnet"=>{"ID"=>nil, "NetworkAddress"=>"153.120.83.0", "NetworkMaskLen"=>24, "DefaultRoute"=>"153.120.83.1", "Internet"=>{"BandWidthMbps"=>100}}, "UserSubnet"=>nil}, "PacketFilter"=>nil}]
  >

 また、サーバー一覧はcompute.serversプロパティに格納されており、これを参照することで作成されているサーバー情報を確認できる。

 なお、現状ではfogのさくらのクラウドサポートは前述のように非常に限られており、新規ディスクの作成やスイッチへの接続、サーバーやディスクの属性変更と言った昨日は実装されていない。fogのさくらのクラウドサポート自体、今年になってから実装されたばかりなので、今後の機能強化に期待したい。

APIを活用した自動化を

 さくらのクラウドAPIは、さくらのクラウドを単純にVPS代わりに利用するのであれば不要かもしれないが、頻繁にサーバーやディスクの作成/削除を繰り返したり、状況に応じて構成を変更するような用途では非常に有用だ。今回はRubyを使ったリクエスト送信について紹介したが、認証はシンプルなBASIC認証で、リクエストの送受信にはJSON形式を利用することから、ほかの言語でも容易に利用できるだろう。ドキュメントや関連ライブラリのサポート不足などやや取っつきにくい点もあるが、比較的簡単にその使い方は習得できるので、一度試してみてはいかがだろうか。

>>参考記事:さくらのクラウドAPIを使ってみよう(1)–コマンドラインツール「sacloud」から使う

おしらせ

banner_cloud
banner_writer