Apache libcloudでAmazon EC2, OpenNebulaクラウドにVM作成
はじめに
正月の休みを利用して、以前から気になっていたApache libcloudを使ってみたときのメモ。libcloudは複数のクラウドAPIを統一的に扱えるPythonライブラリ。AWS EC2, S3やRackspace、OpenStack、OpenNebulaなどに対応してる。対応プロバイダ一覧はこちら。v0.6.2からCloudStackにも対応したとのことなので、研究用にも使えないかなと思っていたんだけれども、時間が取れずに正月の自由課題になってしまった。以下、大晦日から1/3まで38度前後の熱にうなされながら、初めてのPythonプログラミングに四苦八苦しつつ、調査した結果です。
環境
libcloud実行環境 | CentOS 6.0, Python 2.6.5 |
libcloudバージョン | v0.7.1 |
Target Cloud 1 | Amazon EC2, US East (Virginia) |
Target Cloud 2 | OpenNebula v3.1.90 (v.3.2RC) |
libcloudはまだあまりドキュメント化されてなく、OpenNebulaに関してはOCCIサービスの有無について書かれていないようだったけれども、libcloudはOCCIのInterfaceを叩いてOpenNebulaにクエリを送るので、OCCIの設定・サーバの起動は必須です。
インストール
公式サイトの説明通り、pipでインストールできます。CentOS 6.0だとこんな感じ。
$ sudo yum install python-setuptools $ sudo easy_install pip $ sudo pip install apache-libcloud
Amazon EC2
VMを起動
以下のようなスクリプトをcreate_node_ec2.pyの名前で作成。
from libcloud.compute.types import Provider from libcloud.compute.providers import get_driver EC2_ACCESS_ID = 'AWSのACCESS ID' EC2_SECRET_KEY = '上記ACCESS IDのペアとなるSECRET KEY' Driver = get_driver(Provider.EC2_US_EAST) conn = Driver(EC2_ACCESS_ID, EC2_SECRET_KEY) # 1 vm_name = 'test' # 2 image_id = 'ami-1b814f72' # 3 size_id = 't1.micro' # 4 sshkey_name = 'USE_V_key' # 5 image = [i for i in conn.list_images() if i.id == image_id][0] # 6 # print image print('Image file') print(' id: ' + str(image.id)) print(' name: ' + str(image.name)) for key in image.extra: print(" {0}: {1}".format(key, image.extra[key])) size = [s for s in conn.list_sizes() if s.id == size_id][0] # 7 # print size print('Machine Type') print(' id: ' + str(size.id)) print(' name: ' + str(size.name)) print(' ram: ' + str(size.ram)) print(' disk: ' + str(size.disk)) print(' bandwidth: ' + str(size.bandwidth)) print(' price: ' + str(size.price)) node = conn.create_node(name=vm_name, image=image, size=size, ex_keyname=sshkey_name) # 8 print('New node') print(' id: ' + str(node.id)) print(' name: ' + str(node.name))
Amazon EC2でVMを起動する方法は公式サイトにあるサンプルほぼそのまま。EC2のドライバインスタンスを取得して(1), 利用するOSイメージファイル(6)、VMインスタンスタイプ(7)を取得して、VMを起動する(8)だけ。OSイメージはIDで指定している(3)けれども、これは「Basic 64-bit Amazon Linux AMI 2011.09」に該当するイメージ。インスタンスタイプには、今回はマイクロを指定している(4)。ssh公開鍵として「USE_V_key」を指定している(5)けれども、今回は予めAWSコンソールにて、この名前で事前に作成して登録しておいた。なお、conn. ex_create_keypairメソッドを呼び出すことで、keyペアの作成にも対応しているとのこと。
ちなみにlist_*はEC2のサーバに問い合わせを行っているためか、特にlist_imagesでは、大きく時間がかかる。
このファイルを実行すると以下の様に表示される。
$ python create_node_ec2.py Image file id: ami-1b814f72 name: amazon/amzn-ami-2011.09.2.x86_64-ebs owneralias: amazon state: available architecture: x86_64 hypervisor: None platform: None rootdevicetype: ebs ownerid: 137112412989 ispublic: true imagetype: machine virtualizationtype: paravirtual Machine Type id: t1.micro name: Micro Instance ram: 613 disk: 15 bandwidth: None price: 0.02 New node id: i-ae6e02cc name: test
それぞれ、libcloud.compute.base.NodeImage、libcloud.compute.base.NodeSize、libcloud.compute.base.Nodeクラスのインスタンスなんだけれど、どのようなフィールドがあるか、きちんとドキュメント化されていないのでソースコードをあたる必要があった。
なお、既に同じ名前(この例の場合'test' (2))のVMインスタンスがEC2上に存在する場合、VMの起動には成功するけれどcreate_nodeの戻り値として返ってくるNodeインスタンスのnameフィールドはidフィールドの値となった(この場合は'i-ae6e02cc')。
VMの状態表示
以下のようなスクリプトをshow_node_ec2.pyの名前で作成。
from libcloud.compute.types import Provider from libcloud.compute.providers import get_driver EC2_ACCESS_ID = 'AWSのACCESS ID' EC2_SECRET_KEY = '上記ACCESS IDのペアとなるSECRET KEY' Driver = get_driver(Provider.EC2_US_EAST) conn = Driver(EC2_ACCESS_ID, EC2_SECRET_KEY) node = [i for i in conn.list_nodes() if i.name == 'test'][0] # 1 print('Instance') print(' id: ' + node.id) print(' name: ' + node.name) print(' state: ' + str(node.state)) print(' public ips: ' + str(node.public_ips)) print(' private ips: ' + str(node.private_ips)) print(' size: ' + str(node.size)) print(' image: ' + str(node.image)) for key in inst.extra: print(" {0}: {1}".format(key, node.extra[key]))
EC2のドライバインスタンスを取得して、情報を得たいVMインスタンスのインスタンスを得ている点は上と同じ。VM起動時にVM名として'test'を指定したので、それをキーとしてインスタンスリストを探索している(1)。
このファイルを実行すると以下の様に表示される。
$ python show_node_ec2.py Instance id: i-ae6e02cc name: test state: 0 public ips: ['x.x.x.x'] private ips: ['y.y.y.y'] size: None image: None status: running productcode: [] groups: ['default'] tags: {'Name': 'test'} instanceId: i-ae6e02cc dns_name: ec2-z-z-z-z.compute-1.amazonaws.com launchdatetime: 2012-01-04T12:03:47.000Z imageId: ami-1b814f72 kernelid: aki-825ea7eb keyname: USE_V_key availability: us-east-1b clienttoken: launchindex: 0 ramdiskid: None private_dns: ip-w-w-w-w.ec2.internal instancetype: t1.micro
VMの停止
以下のようなスクリプトをdestroy_node_ec2.pyの名前で作成。
from libcloud.compute.types import Provider from libcloud.compute.providers import get_driver EC2_ACCESS_ID = 'AWSのACCESS ID' EC2_SECRET_KEY = '上記ACCESS IDのペアとなるSECRET KEY' Driver = get_driver(Provider.EC2_US_EAST) conn = Driver(EC2_ACCESS_ID, EC2_SECRET_KEY) node = [i for i in conn.list_nodes() if i.name == 'test'][0] # 1 val = conn.destroy_node(node) if val: print("Successfully delete Node[{0}]".format(node.name)) else: print("Failed to delete Node[{0}])".format(node.name))
1までは上と同じで、取得したVMインスタンスのインスタンスを引数にdestroy_nodeを実行しているだけ。
このファイルを実行すると以下の様に表示される。
$ python destroy_node_ec2.py Successfully delete Node[test]
OpenNebula
前提
OpenNebulaのクラウド環境は構築者により変わってくる。今回、僕が構築した環境では以下の様に設定してある。
- OpenNebula利用者: cldadmin
- OSイメージ: このページからダウンロードできるttylinuxイメージを使用
- 次の定義ファイルを作成して、OpenNebulaに登録した。
NAME = ttylinux PATH = "PATH_TO_IMAGE" TYPE = OS
- VMインスタンスタイプ: small
- OpenNebula標準付属のocci_templateに定義されているsmallを使用
- VM用ネットワーク: 'Test Network'として定義したネットワークを使用
- 次の定義ファイルを作成して、OpenNebulaに登録した。
NAME = "Test Network" TYPE = FIXED BRIDGE = br0 LEASES = [ IP="x.x.x.a"] LEASES = [ IP="x.x.x.b"]
VMを起動
以下のようなスクリプトをcreate_node_one.pyの名前で作成。
from libcloud.compute.types import Provider from libcloud.compute.providers import get_driver ONE_USER_ID = 'cldadmin' ONE_PASSWD = 'PASSWORD' ONE_HOST = 'URL_OF_OCCI_SERVER' ONE_PORT = 4567 Driver = get_driver(Provider.OPENNEBULA) conn = Driver(ONE_USER_ID, secret=ONE_PASSWD, secure=False, host=ONE_HOST, port=ONE_PORT, api_version='3.2') # 1 vm_name = 'test' image_name = 'ttylinux' size_name = 'small' net_name = 'Test Network' # 2 context = {'hostname':'$NAME', 'files':'/data/cloud/one/ttylinux/init.sh /home/cldadmin/.ssh/id_rsa.pub', 'target':'hdc', 'ip_public':'$NIC[ IP, NETWORK=\\"Test Network\\" ]', 'root_pubkey':'id_rsa.pub', 'username':'shin', 'user_pubkey':'id_rsa.pub'} # 3 image = [i for i in conn.list_images() if i.name == image_name][0] # print image print('Image file') print(' id: ' + str(image.id)) print(' name: ' + str(image.name)) for key in image.extra: print(" {0}: {1}".format(key, image.extra[key])) size = [s for s in conn.list_sizes() if s.name == size_name][0] # print size print('Machine Type') print(' id: ' + str(size.id)) print(' name: ' + str(size.name)) print(' ram: ' + str(size.ram)) print(' disk: ' + str(size.disk)) print(' bandwidth: ' + str(size.bandwidth)) print(' price: ' + str(size.price)) print(' cpu: ' + str(size.cpu)) print(' vcpu: ' + str(size.vcpu)) net = [i for i in conn.ex_list_networks() if i.name == net_name][0] # 4 # print network print('Network') print(' id: ' + str(net.id)) print(' name: ' + str(net.name)) print(' address: ' + str(net.address)) print(' size: ' + str(net.size)) for key in net.extra: print(" {0}: {1}".format(key, net.extra[key])) node = conn.create_node(name=vm_name, image=image, size=size, networks=[net], context=context) # 5 print('New node') print(' id: ' + str(node.id)) print(' name: ' + str(node.name))
OpenNebula用のドライバインスタンスを取得しているが(1)、EC2の場合と比較して引数の種類が異なる。また、VMを接続するネットワークも明示的に指定する必要がある(2, 4)。さらに、OpenNebulaではCONTEXTと言う仕組みで、VMインスタンスの初期設定を行うが、このためのパラメータも数多く指定する必要があり(3)、これらを引数にcreate_nodeする必要がある(5)。
これは、以下のファイルを指定してonevm createコマンドを実行してVMを起動することに相当する。
NAME = test CPU = 1 MEMORY = 1024 DISK = [ IMAGE = "ttylinux" ] NIC = [ NETWORK = "Test Network" ] CONTEXT = [ hostname = "$NAME", ip_public = "$NIC[ IP, NETWORK=\"Test Network\" ]", files = "/data/cloud/one/ttylinux/init.sh /home/cldadmin/.ssh/id_rsa.pub", target = "hdc", root_pubkey = "id_rsa.pub", username = "shin", user_pubkey = "id_rsa.pub" ]
このファイルを実行すると以下の様に表示される。
$ python create_node_one.py Image file id: 1 name: ttylinux fstype: None type: 0 description: None size: 40 Machine Type id: 1 name: small ram: 1024 disk: None bandwidth: None price: None cpu: 1 vcpu: None Network id: 0 name: Test Network address: None size: None New node id: 13 name: test
VMの状態表示
以下のようなスクリプトをshow_node_one.pyの名前で作成。
from libcloud.compute.types import Provider from libcloud.compute.providers import get_driver ONE_USER_ID = 'cldadmin' ONE_PASSWD = 'PASSWORD' ONE_HOST = 'URL_OF_OCCI_SERVER' ONE_PORT = 4567 Driver = get_driver(Provider.OPENNEBULA) conn = Driver(ONE_USER_ID, secret=ONE_PASSWD, secure=False, host=ONE_HOST, port=ONE_PORT, api_version='3.2') node = [i for i in conn.list_nodes() if i.name == 'test'][0] print('Instance') print(' id: ' + node.id) print(' name: ' + node.name) print(' state: ' + str(node.state)) print(' public ips: ' + str(node.public_ips)) print(' private ips: ' + str(node.private_ips)) print(' size: ' + str(node.size)) print(' image: ' + str(node.image)) for key in node.extra: print(" {0}: {1}".format(key, node.extra[key]))
ドライバインスタンスの取得のみEC2と異なる。それ以外は同じ。
このファイルを実行すると以下の様に表示される。
$ python show_node_one.py Instance id: 13 name: test state: 4 public ips: [<OpenNebulaNetwork: uuid=7c3c633a7d389051291c394f534e07ca22023642, name=Test Network, address=x.x.x.a, size=1, provider=OpenNebula ...>] private ips: [] size: <OpenNebulaNodeSize: id=1, name=small, ram=1024, disk=None, bandwidth=None, price=None, driver=OpenNebula, cpu=1, vcpu=None ...> image: <NodeImage: id=1, name=ttylinux, driver=OpenNebula ...> context: {'files': '/data/cloud/one/ttylinux/init.sh /home/cldadmin/.ssh/id_rsa.pub', 'username': 'shin', 'target': 'hdc', 'hostname': 'test', 'ip_public': 'x.x.x.a', 'root_pubkey': 'id_rsa.pub', 'user_pubkey': 'id_rsa.pub'}
OpenNebula専用のネットワークオブジェクトが使われていたり、sizeやimageフィールドの型がEC2と異なるため、ドライバ毎に専用にパーズする必要がある。
VMの停止
以下のようなスクリプトをdestroy_node_one.pyの名前で作成。
from libcloud.compute.types import Provider from libcloud.compute.providers import get_driver ONE_USER_ID = 'cldadmin' ONE_PASSWD = 'PASSWORD' ONE_HOST = 'URL_OF_OCCI_SERVER' ONE_PORT = 4567 Driver = get_driver(Provider.OPENNEBULA) conn = Driver(ONE_USER_ID, secret=ONE_PASSWD, secure=False, host=ONE_HOST, port=ONE_PORT, api_version='3.2') node = [i for i in conn.list_nodes() if i.name == 'test'][0] val = conn.destroy_node(node) if val: print("Successfully delete Node[{0}]".format(node.name)) else: print("Failed to delete Node[{0}])".format(node.name))
こちらも、ドライバインスタンスの取得のみEC2と異なる。
このファイルを実行すると以下の様に表示される。
$ python destroy_node_one.py Successfully delete Node[test]
まとめ
今回は単純なVMの起動・状態表示・停止しかしていないため詳細まで理解はできていないが、クラウドプロバイダ毎に個別の処理(ドライバインスタンスの取得や、VMの起動メソッドの引数)が必要になったり、戻り値の型が異なるなど、結局クラウドプロバイダを意識して実装しなければならない。また、標準で定義・実装されている機能は各種クラウドプロバイダの最大公約数であり、一部のクラウドプロバイダでしか提供されていない機能はex_XXXと言った名前で特別実装されているので、libcloud自体がきれいにカプセル化された実装になっていない。なので、今後API周りは大きく変化するのではないかと思っている。
ただ、各種クラウドAPIライブラリを導入しなくても、標準的なクラウド操作をlibcloudをインストールするだけでできるようになるのはうれしい。libcloudが無ければ、OpenNebulaのAPI、EC2 API、CloudStack APIなどを別途インストールしなければならないけれど、libcloud自体のインストールはすごく簡単に済むので。
今後、CloudStackのテスト環境も利用可能になる予定なので、そこでも試してみるつもり。