Python経験ゼロからEIP自動割り当てプログラムを作成するまでの道程【21日目】

2012.12.21

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

オープニング

冒険者くろの(=筆者)の命の灯火は消えようとしていた。

HPバーが赤い
※HPバーが100%赤くなっている

もうクラスメソッドブログワールドでは生きていくことはできないのか。そうくろのが思った瞬間、どこからともなく女神セレシアの声が聞こた。

「くろのよ立ち上がりなさい。今はまだあなたが光の世界に来る時ではありません」

次の瞬間、くろのをまばゆい光が包み込み、くろのが蘇った。

しかし、くろののジョブは以前ブログを書いていた時代のもの(ジョブ:スマートフォン)とは異なっていた。そのジョブは「クラウド」というジョブであった。

この物語は、女神により新しい生命(いのち)を授かった冒険者くろのが、インフラからアプリケーションの世界までに広がる広大なAWSクラウドの世界を旅する物語である。♪本気(マジ)か!? 本気(マジ(ry

EIP自動割り当てプログラム

AWSにおいては、AutoScalingで起動したEC2インスタンスにEIPを自動で割り当てたい場合があります。

EIPを割り当てる方法はいくつかあります:

  • EC2インスタンスが自分で自分に割り当てに行く
  • EC2インスタンスを監視している別のEC2インスタンスがインスタンスのEIP割り当て状況を確認し、割り当てる
  • AutoScalingしたことをCloudWatch経由でメール通知し、メールが飛んできたら人間がAWS管理コンソールでEIPを手付けする

今回は2番めの方法を試してみます。AWSでこのような「cronから呼び出される小型のプログラム」を作成する方法はいくつかあります:

  • シェル(Bash)で作成する
  • AWS SDKを用いて作成する(Java、.NET、Ruby、PHP)
  • botoを用いて作成する(Python)

Javaは小型のプログラムを開発する場合、プログラム実行時にJVMの起動処理が走ってしまうため、cronから頻繁に呼び出されるような処理の実装にはあまり向いていません。またシェルで小型のプログラムを開発する場合、そもそもプログラムの開発生産性・メンテナンス性で他の技術に比べ劣る面があります。

そこで今回は、Python未経験の筆者がPythonを用いてEIP自動割り当てプログラムを作成するまでをお伝えしていきたいと思います。

開発環境

  • クライアントOS:Mac OS X 10.8
  • サーバーOS:Amazon Linux 2012.09/64bit

プログラミング言語「Python」

Pythonはスプリクト言語、軽量言語(LL)の一つです。シンプル覚えやすい言語と言われていますが、複雑なアプリケーション開発においても用いられており、DropBoxやPinterest、YouTubeなどの開発にもPythonが用いられています。

本記事ではMac OS Xを利用して開発を行いますが、Mac OS XにはデフォルトでPythonがインストールされています。Pythonのバージョンの確認を確認します。

$ python --version 
Python 2.7.2

また、Amazon LinuxにもデフォルトでPythonがインストールされています。Amazon Linux 2012.09ではデフォルトのPythonのバージョンは2.6(2.6.8)となっています。

$ python --version 
Python 2.6.8

現在、Pythonは2系では2.7.3で、さらに3系がリリースされており、最新バージョンは3.3.0です。まだ、3系は出て間もないこともあり、多くのプロジェクトにおいては2系が利用されているようです。ただし、2系は2.7が最後になります

Python:Hello World

▼Hello Worldの作成

Hello Worldを作成します。テキストエディットかコンソールのviで以下のコードを作成します。ファイル名は「hello.py」とします。

print "Hello World"

※他の言語をやったことのある開発者から見た簡単なポイント:

  • 処理の文末にセミコロンがありません。
  • メソッドの引数を半角スペース後に指定しています。

▼Hello Worldの実行

ターミナルで下記のようにpythonコマンドを実行します。

$ python hello.py
Hello World

その他の基本的な文法に関しては、後ほど作成するプログラムの解説の所で扱います。

▼対話モード

ターミナルでpythonとだけ打つと対話モードになります。

$ python
Python 2.7.2 (default, Jun 20 2012, 16:23:33) 
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

>>> print "Hello World!"
Hello World!
>>> a = 1
>>> b = 2
>>> print a + b
3
  • 終了はControl+Dです。
  • あんまり使わない???

▼Pythonの「インデントでブロックを指定する」スタイルを学ぶ

下記のファイルを作成します。

#! /usr/bin/env python
import sys

argvs = sys.argv
if len(argvs) > 1:
  print 'Hello World! ' + sys.argv[1]
else:
  print 'Hello World'

プログラムの内容としては、コマンドライン引数が指定されていたら、それをHello Worldに続けて画面に表示しています。

Pythonの文法の特徴(上記プログラムを通じてのもの)

  • ifブロックの内容が「インデント(タブ)」によって表現されている。
  • import文でパッケージを読み込む。他言語だとライブラリの読み込みの事。sysはシステムパッケージ。
  • ifとelseの最後のコロン(:)に注意!
  • argvはコマンドライン引数の配列。配列要素には[]でアクセスしている。
  • ※変数に型やvar、スコープ(privateやpublic)の指定を行いません(スコープの概念はあります)

以上でPythonの感触がざっくりですがつかめたので、早速AWS向けプログラム作成の準備に入ります!

botoのインストール

Python向けのAWS SDKはAWSからは提供されていません。ただし、Python用のAWS開発ツールとしては、デファクトスタンダードなもので「boto(ボト)」というライブラリがあります。

・boto/boto · GitHub
https://github.com/boto/boto

ライブラリのインストールは下記になります。

$ git clone git://github.com/boto/boto.git
$ cd boto
$ sudo python setup.py install

※botoの語源(と思われるもの)

南アメリカのイルカ(Inia geoffrensis)。Amazon川とOrinoco川に生息し、年齢や日照、水温、ストレスなどの環境要素に応じて体色が明るいピンクから灰色に変化する。また、botoとも呼ばれている。botoはポルトガル語。

・Amazon dolphin - definition of Amazon dolphin by the Free Online Dictionary, Thesaurus and Encyclopedia.
http://www.thefreedictionary.com/Amazon+dolphin

・Boto - Wikipedia, the free encyclopedia
http://en.wikipedia.org/wiki/Boto

botoを用いたEIP自動割り当てプログラムの作成

それでは早速botoを用いたEIP自動割り当てプログラムを作成したいと思います。プログラムの要件定義は以下のようになります:

  • 概要:AutoScalingでインスタンスが起動すると、ELB配下のEC2インスタンスを監視している別のEC2インスタンスがインスタンスのEIP割り当て状況を確認し、割り当てる
  • 詳細:
    • ELB一覧を取得する。
    • ELB配下のEC2インスタンス一覧を取得する。
    • 各インスタンスにEIPが割り当てられているか確認する。割り当てられていない場合は、EIP無しインスタンス一覧にインスタンス追加する。
    • EIP一覧を取得する。
    • 各EIPがインスタンスに割り当てられているかを確認する。割り当てられていない場合は、未割り当てEIP一覧にEIPを追加する。
    • EIP無しインスタンス一覧の各インスタンスにEIPを割り当てる。

Python w/z botoで記述したプログラムは以下のようになります。ファイル名は「associate_eip.sh」としています:

#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys,boto.ec2.elb

# 基本設定
AWS_ACCESS_KEY = "アクセスキー"
AWS_SECRET_ACCESS_KEY = "シークレットアクセスキー"

# コマンドライン引数の取得
argvs = sys.argv

# ELBとEC2を表す参照の取得
# ※下記では改行していますが、改行なしで記述します
elb_conn=boto.ec2.elb.connect_to_region(aws_access_key_id=AWS_ACCESS_KEY ,
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,region_name="ap-northeast-1")
ec2_conn=boto.ec2.connect_to_region(aws_access_key_id=AWS_ACCESS_KEY ,
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,region_name="ap-northeast-1")

# ELBインスタンス一覧の取得
elbs=elb_conn.get_all_load_balancers(load_balancer_names=[argvs[1]])

noips = [] # EIP無しインスタンス一覧
freeaddresses = [] # 未割り当てEIP一覧

# EIP無しインスタンス一覧の取得
for lb in elbs:
  for i in lb.instances:
    instance_id = i.id
    reservations = ec2_conn.get_all_instances(instance_ids=[instance_id])
    if len(reservations) > 0:
      instance = reservations[0].instances[0]
      print(instance)
      if instance.ip_address is None:
        noips.append(instance)

# EIP一覧の取得
addresses=ec2_conn.get_all_addresses(filters={"domain":"vpc"})

# 未割り当てEIP一覧の取得
for address in addresses:
  if address.instance_id is None:
    freeaddresses.append(address)

# EC2インスタンスへのEIPの割り当て
for instance in noips:
  if freeaddresses:
    address = freeaddresses.pop();
  else:
    address = ec2_conn.allocate_address(domain="vpc")

  ec2_conn.associate_address(instance_id=instance.id, allocation_id=address.allocation_id)
  print "attached eip to instance under elb : instance-id:" + instance.id + ", ip:" + address.public_ip

上記プログラムを実行してみます。EC2インスタンスが2つあって、AutoScalingで3つ目のEC2インスタンスが立ち上がったケースでの実行イメージになります。実行の際のコマンドライン引数としてELBのLoad Balancer Name(下記例ではcm-lb)を指定します。

$ python associate_eip.sh cm-lb
Instance:i-aaaaaaaa
Instance:i-bbbbbbbb
Instance:i-cccccccc
attached an eip to the instance under elb. instance-id: i-cccccccc, ip:176.12.34.56

プログラムは他の言語をやったことのある人であれば、Python初見でも非常に読みやすい感じになっていると思います。要件定義のプログラムの流れをそのまま実装したイメージになります。reservationオブジェクトの配列がAWS経験者の方でも見慣れないものですが、いったんあまりきにしなくて良いです。

また、Python文法的なところを見てみると:

  • 最初にcodingで文字コードを指定している。
  • メソッドは全部括弧で呼び出している!ということはprintだけ特別?
    • print txtはPython 2系までの書き方(文)で、3系からはメソッドの呼び出しprint(txt)に変更されています。
    • 2012/12/25修正:printを「文」と修正しました。メソッド等のcallableなオブジェクトとは明確に区別されています。(3.x系ではビルトイン関数に変更されていますが、2.x系では飽くまで文です。)
  • メソッドの引数の指定は引数の名前=値で指定する
  • 配列の初期化は[]で行なっている。
  • 何にもないはNone(他の言語だとnullのようなもの?)
"""
※今回は都合により未割り当てEIPを予めAWS管理コンソールにて取得しておく仕様にしてあります。
この場合、未割り当てEIPは課金対象になるため、コストを抑える場合は動的にEIPを取得すべきです。
その場合、AutoScalingでのスケールアウト時は動的にEIPを取得(Allocate Address)し、
インスタンスへ割り当て(Associate Address)。
スケールイン時は、EC2インスタンスから外れた(Disassociate  Addressされた)EIPを、
破棄(Release Address)する必要があります。

ちなみにこのPythonプログラムは複数行コメントの例になっています。
クォーテーション3つまたはダブルクォーテーション3つで囲むと複数行コメントになります。
"""

あとはこのプログラムを「ELB配下のインスタンスを監視する」EC2インスタンスに配置し、cronから呼び出すだけです!

$ sudo crontab -e
# 1分毎にELB配下のインスタンスにEIPが付与されていないかを確認する
*/1 * * * * /home/ec-user/scripts/associate_eip.sh cm-lb

最後に

AWSを操作するプログラムを記述するために今回はPythonを初めて用いてみました。JavaやC#に加えて、Pythonやシェルなどが使えると、プログラムの要件に応じてより適切なプログラムを適切なコストで開発できるようになるので、Python未経験のAmazon-erな皆さんもPythonを触ってみてはいかがでしょうか!