OpenStack管理下にあるVMのNIC間でip_forwardを行う

構成

以下のような構成を想定して、vm0eth1eth2の間でルータのようにIPフォワードをさせたい場合に必要となる設定メモ。

ちょっと事情があってserverXDefaultGateway10.0.1.1serverYDefaultGateway10.0.2.1に向けて、相互に通信させたかったのです。

-----------+---------------------------------------------------------- ext-net
           |
           |
      +----+----+
      | router0 |
      +----+----+
           |
           |.129
-----------+------+--------------------------------------------------- demo-net0 (10.0.0.0/24)
                  |.135
                  |
------------------|-----+-------------------+------------------------- demo-net1 (10.0.1.0/24)
                  |     |.1                 |.100
                  |     |                   |
------------------|-----|-----+-------------|---------------+--------- demo-net2 (10.0.2.0/24)
                  |     |     |.1           |               |.101
                  |     |     |             |               |
              +---+-----+-----+---+   +-----+-----+   +-----+-----+
              |  eth0  eth1  eth2 |   |    eth0   |   |    eth0   |
              |                   |   |           |   |           |
              |        vm0        |   |  serverX  |   |  serverY  |
              |                   |   |           |   |           |
              +-------------------+   +-----------+   +-----------+

できない...

実は...デフォルトの設定ではできないんです。SecurityGroupでごにょごにょすれば行けるのかとも思いましたが、残念ならができません。

そこで、調べてみました。

nova boot仮想マシンを起動させると、computeノード上で起動しているneutronのagentが、computeノードのiptablesに以下のような設定を投入します。

Chain neutron-linuxbri-s7780e5de-2 (1 references)
num  target     prot opt source               destination
1    RETURN     all  --  10.0.1.1             anywhere             MAC FA:16:3E:69:AD:2E /* Allow traffic from defined IP/MAC pairs. */
2    DROP       all  --  anywhere             anywhere             /* Drop traffic without an IP/MAC allow rule. */

Chain neutron-linuxbri-sbcd24cc0-d (1 references)
num  target     prot opt source               destination
1    RETURN     all  --  10.0.2.1             anywhere             MAC FA:16:3E:92:84:F1 /* Allow traffic from defined IP/MAC pairs. */
2    DROP       all  --  anywhere             anywhere             /* Drop traffic without an IP/MAC allow rule. */

Neutronでは、IP Spoofing対策の1つとして、仮想マシン自身に割り当てられているIPアドレス以外は、FORWARD時に落とすという仕組みを、上記の設定を投入することで実現しているようです。

そして、このチェーンは、以下のようにChain FORWARDに登録されます。 ちなみに、チェーン名の末尾に付与されているIDは、それぞれのtapデバイス名に由来しているので、環境により異なります。

Chain FORWARD (policy ACCEPT)
  |
  +- neutron-filter-top
  |     |
  |     +- neutron-linuxbri-local
  |
  +- neutron-linuxbri-FORWARD
        |
        +- neutron-linuxbri-sg-chain
              |
              +- neutron-linuxbri-ibcd24cc0-d
              |
              +- neutron-linuxbri-obcd24cc0-d
              |     |
              |     +- neutron-linuxbri-sbcd24cc0-d   <-- これ
              |
              +- neutron-linuxbri-i7780e5de-2
              |
              +- neutron-linuxbri-o7780e5de-2
              |     |
              |     +- neutron-linuxbri-s7780e5de-2   <-- これ
              |
              +- neutron-linuxbri-i8a0e0062-b
              |
              +- neutron-linuxbri-o8a0e0062-b
                    |
                    +- neutron-linuxbri-s8a0e0062-b

考えた対策

computeノード上で...

  1. iptables -F を実行してポリシー全消し -> まあだめだよね
  2. neutron-linuxbri-s* のルールを削除する -> まぁだめだよね
  3. iptables -I FORWARD -m physdev --physdev-is-bridged -j ACCEPTを突っ込む -> まぁだめだよね
  4. sysctl -w net.bridge.bridge-nf-call-iptables=0 でブリッジにiptablesを見せない -> 本末転倒

どれも、まぁだめだよね...

手詰まり...かと思いきや、実はちゃんと設定できました。

この機能をNeutronは提供していた

IPフォワードしてもらうために向けられてくるネットワークアドレスレンジを、neutron-linuxbri-s*チェーンに追加してやる方法ないかな...いろいろ調べてみると、ネットワークレンジを--allowed-address-pairsでポートに追加してやれば良さそうという結論に至りました。

$ neutron port-update 7780e5de-222a-4805-accc-77a380732cf3 \
--allowed-address-pairs \
type=dict \
list=true \
ip_address=10.0.2.0/24

$ neutron port-update bcd24cc0-d1d9-4016-a512-f0b12075f1c0 \
--allowed-address-pairs \
type=dict \
list=true \
ip_address=10.0.1.0/24

これを実行すると、neutron-linuxbri-s*チェーンの設定に、以下のようなポリシーが追加となります。

Chain neutron-linuxbri-s7780e5de-2 (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1      112  9408 RETURN     all  --  any    any     10.0.2.0/24          anywhere             MAC FA:16:3E:69:AD:2E /* Allow traffic from defined IP/MAC pairs.     */
2        0     0 RETURN     all  --  any    any     10.0.1.1             anywhere             MAC FA:16:3E:69:AD:2E /* Allow traffic from defined IP/MAC pairs.     */
3        0     0 DROP       all  --  any    any     anywhere             anywhere             /* Drop traffic without an IP/MAC allow rule. */

Chain neutron-linuxbri-sbcd24cc0-d (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1      230 19320 RETURN     all  --  any    any     10.0.1.0/24          anywhere             MAC FA:16:3E:92:84:F1 /* Allow traffic from defined IP/MAC pairs.     */
2        0     0 RETURN     all  --  any    any     10.0.2.1             anywhere             MAC FA:16:3E:92:84:F1 /* Allow traffic from defined IP/MAC pairs.     */
3        0     0 DROP       all  --  any    any     anywhere             anywhere             /* Drop traffic without an IP/MAC allow rule. */

ということで、これでcomputeノード上のVMで、ip_fowardを有効化してあげれば、ちゃんとIPフォワードしてくれました。

この機能、Havanaから実装されてるのね。知らんかった。

Ansibleの Developing Modules を和訳してみた

ちょっと訳があって、Ansibleのモジュール開発方法を解説している Developing Modules を和訳してみた。だいたいあってると思うんだけど、意図を計りかねるところもあったので、想像しながら意訳してみた。それほど外れてないと思う。

Developing Modules

Ansibleのモジュールは再利用可能な機能単位で、ansibleコマンドやansible-playbookコマンド、そしてAnsibleのAPI経由で呼び出して利用することができます。

  • 参考情報: About Modules にモジュール開発に役立つ情報がリストアップされています

モジュールは、さまざまなプログラム言語で記述できます。モジュールのサーチパスは、環境変数ANSIBLE_LIBRARYか、コマンドラインオプションの--module-pathで指定します。

ansible-modules-core, ansible-modules-extrasは、ansible本体と一緒にデフォルトのサーチパスにあるライブラリパスにインストールされますが、独自のパスを利用シたい場合は、上記のような方法で追加する必要があります。 あなたのPlaybookのトップレベルディレクトリにある libraryディレクトリは、デフォルトでモジュールのサーチパスに入っています。

モジュールを作成したら、modules-extras projectにPull Requestを出してみてください。Ansibleのモジュールプロジェクトには、より一般的に利用されているモジュール群が登録されているmodules-core projectがあります。Extrasモジュールは、定期的にCoreモジュールに昇格する場合がありますが、これらのモジュールに決定的な違いはありません。どちらもAnsible本体と一緒にパッケージングされます。

Tutorial

まず最初に、現在のシステムの時刻を取得するモジュールを作成してみましょう。 ここではPythonを利用しますが、ファイルに対するI/O制御と、標準出力への出力機能を持っている他の言語(例えば、bashなどのシェルスクリプト,C++,clojure,Rubyなどなど)で、モジュールを記述することが可能です。

Pythonでモジュールを記述する場合、Coreモジュールが共通で利用しているモジュールとして最低限必要となる基本的な機能(さまざまな関数やクラスなど)を、同じようにimportして利用することができますが、Python以外の言語では、このようなショートカットは提供されません。そこで、まずは、このような便利な利用せずにモジュールを記述してみましょう。ショートカットを利用する方法は、のちほど紹介します。

本来、このような場合だとcommandモジュールを利用すれば、この例のようにシステム時刻を取得するようなモジュールは必要ありませんが、ここでは、あえて作成してみることにします。

CoreやExtrasなどのAnsibleのモジュールのコードは、モジュールを書くための良い教材です。コードを読みましょう。目的にもよりますが、async_wrapperのように、ユーザが直接利用しないモジュールは参考にしないほうが良いでしょう。参考にするなら、serviceモジュールやyumモジュールあたりがオススメです。

それでは、Pythonで書かれたサンプルコード(timetest.py)を見てみましょう。

#!/usr/bin/python

import datetime
import json

date = str(datetime.datetime.now())
print json.dumps({
    "time" : date
})

Testing Modules

Ansibleと一緒に提供される、テストスクリプトをチェックアウトして利用します。

(注1) --recursiveオプションを付与してgit cloneを実行すると、Ansible本体と同時にCoreとExtraモジュールもあわせてチェックアウトしてくれます。

(注2)ここではv1系のAnsibleでテストするため、オリジナルのドキュメントのパスから変更しています。

$ git clone git@github.com:ansible/ansible.git --recursive
$ source ansible/v1/hacking/env-setup

test-moduleスクリプトで、先ほど作成したtimetest.pyモジュールを実行してみましょう。

$ ansible/v1/hacking/test-module -m ./timetest.py

以下のような結果が出力されます。

(注3) 2015-07-14時点でのテストスクリプト実行結果に差し替えています

* including generated source, if any, saving to: /Users/saitou/.ansible_module_generated
* this may offset any line numbers in tracebacks/debuggers!
***********************************
RAW OUTPUT
{"time": "2015-07-14 16:52:13.819876"}


***********************************
PARSED OUTPUT
{
    "time": "2015-07-14 16:52:13.819876"
}

モジュールが動作しなかった場合は、コードのtypoなどを再確認してくださいね。

Reading Input

次に、現在時刻を変更してみましょう。変更する時刻は、キーになるパラメータ名と、その値となる時刻のペアを time=<string> 形式でモジュールに渡されます。

Ansibleは、モジュールに渡されたパラメータを引数ファイルに保存します。モジュールは、この引数ファイルを読み込んで適切にパースしてやる必要があります。引数ファイルは、ただのテキストファイルですから、パーサ次第でさまざまな形式に対応できますが、ここでは基本的な key=value 形式の引数をパースすることにします。

時刻を設定するためのサンプルは、以下の通りです。

time time="March 14 22:10"

もしも、timeパラメータが指定されなかった場合は、現在時刻を取得して返すことにしましょう。

  • メモ: 時刻の設定や取得をするなら、shellモジュールを使うべきですが、ここではチュートリアルのために、あえて新たにモジュールを作成することにしました。

以下のサンプルモジュール(time)を見てください。このモジュールの動きを、コード内のコメントとして記載しています。また、このモジュールのコードは、若干冗長に感じるかもしれませんが、これは、モジュール作成のチュートリアルに利用するためです。このサンプルコードは、実際には、もう少し短くシンプルに書くことができます。

(注4) MacOS Xの場合は、rc = os.system("date -s \"%s\"" % value) を rc = os.system("date \"%s\"" % value)としてください。

#!/usr/bin/python

# import some python modules that we'll use.  These are all
# available in Python's core

import datetime
import sys
import json
import os
import shlex

# read the argument string from the arguments file
args_file = sys.argv[1]
args_data = file(args_file).read()

# for this module, we're going to do key=value style arguments
# this is up to each module to decide what it wants, but all
# core modules besides 'command' and 'shell' take key=value
# so this is highly recommended

arguments = shlex.split(args_data)
for arg in arguments:

    # ignore any arguments without an equals in it
    if "=" in arg:

        (key, value) = arg.split("=")

        # if setting the time, the key 'time'
        # will contain the value we want to set the time to

        if key == "time":

            # now we'll affect the change.  Many modules
            # will strive to be 'idempotent', meaning they
            # will only make changes when the desired state
            # expressed to the module does not match
            # the current state.  Look at 'service'
            # or 'yum' in the main git tree for an example
            # of how that might look.

            rc = os.system("date -s \"%s\"" % value)

            # always handle all possible errors
            #
            # when returning a failure, include 'failed'
            # in the return data, and explain the failure
            # in 'msg'.  Both of these conventions are
            # required however additional keys and values
            # can be added.

            if rc != 0:
                print json.dumps({
                    "failed" : True,
                    "msg"    : "failed setting the time"
                })
                sys.exit(1)

            # when things do not fail, we do not
            # have any restrictions on what kinds of
            # data are returned, but it's always a
            # good idea to include whether or not
            # a change was made, as that will allow
            # notifiers to be used in playbooks.

            date = str(datetime.datetime.now())
            print json.dumps({
                "time" : date,
                "changed" : True
            })
            sys.exit(0)

# if no parameters are sent, the module may or
# may not error out, this one will just
# return the time

date = str(datetime.datetime.now())
print json.dumps({
    "time" : date
})

それでは実行してみましょう。システムの時刻を修正します。

(注5) sudo権限が必要です。影響範囲が大きいので、テストであっても、あまり大幅なシステム時刻の変更はしないようにしましょう。

sudo ansible/v1/hacking/test-module -m ./time -a time=\"0714195315\"

実行結果は以下の通りです

(注6) 2015-07-14時点でのテストスクリプト実行結果に差し替えています

(注7) -a オプションで、time引数を与えなかった場合は、現在時刻を出力して正常終了します。

* including generated source, if any, saving to: /Users/saitou/.ansible_module_generated
* this may offset any line numbers in tracebacks/debuggers!
***********************************
RAW OUTPUT
2015年 7月14日 火曜日 19時53分00秒 JST
{"changed": true, "time": "2015-07-14 19:53:00.002979"}


***********************************
PARSED OUTPUT
{
    "changed": true,
    "time": "2015-07-14 19:53:00.002979"
}


$ sudo ansible/v1/hacking/test-module -m ./time
* including generated source, if any, saving to: /Users/saitou/.ansible_module_generated
* this may offset any line numbers in tracebacks/debuggers!
***********************************
RAW OUTPUT
{"time": "2015-07-15 11:23:53.311061"}


***********************************
PARSED OUTPUT
{
    "time": "2015-07-15 11:23:53.311061"
}

Module Provided 'Facts'

setupモジュールを利用すると、Ansibleが動作するシステムのさまざまなパラメータを取得することができます。この情報は、Playbookとtemplateモジュールで利用するtemplateの中で再利用することが可能です。さらに、Ansibleのシステムに手を加えることなしに、独自のパラメータを追加することができます。この場合、モジュールの戻り値として ansible_factsキーと、その値として、再利用したい独自パラメータを辞書型のデータを設定してやります。

{
    "changed" : True,
    "rc" : 5,
    "ansible_facts" : {
        "leptons" : 5000,
        "colors" : {
            "red"   : "FF0000",
            "white" : "FFFFFF"
        }
    }
}

これらのパラメータは、setupモジュールの実行直後から、Playbook内で利用可能となります。site_factsと呼ばれるモジュールを作成して、Playbookの先頭で実行して独自のパラメータを設定し、以降のセクションで利用するのは良い考えです。そして、われわれは、Ansibleのシステム情報を収集するセクションの改善に関して、常に門戸を開いています。

Common Module Boilerplate

最初に書いたとおり、Pythonでモジュールを記述するときは、非常に強力な共通モジュールを利用することができます。依然として、モジュールは1つのファイルとしてターゲットノードに転送されますが、パラメータファイルは不要となります。これにより、モジュールコードを簡単に記述できるだけでなく、モジュールの実行時間短縮という面でも効果があります。尚、ここでは、上記に関して詳細には説明しません。最良の学習方法は、Coreモジュールのソースコードを読むことです。

groupモジュールやuserモジュールを例として、説明が必要となる点を紹介します。重要な箇所は、以下のとおりで、モジュールの最後の部分に記述されています。

from ansible.module_utils.basic import *
if __name__ == '__main__':
    main()

さらに、興味深いのはAnsibleModuleクラスのインスタンスであるmoduleです。

module = AnsibleModule(
    argument_spec = dict(
        state     = dict(default='present', choices=['present', 'absent']),
        name      = dict(required=True),
        enabled   = dict(required=True, choices=BOOLEANS),
        something = dict(aliases=['whatever'])
    )
)

AnsibleModuleクラスは、モジュールに渡される引数や、実行結果を返す際の戻り値をハンドリングするための多くの機能を提供しており、引数のチェックなどを簡単に行うことができます。

モジュールの実行が成功した場合の戻り値を生成するコードは、以下のとおりです。

module.exit_json(changed=True, something_else=12345)

モジュールの実行が失敗した場合の戻り値を生成するコードは以下のようにシンプルなものです。このとき、必須のパラメータとして、msgにエラーの概要を設定します。

module.fail_json(msg="Something fatal happened")

その他にも、便利な機能としてmodule.sha1(path)のようなクラスががあります。詳しくは lib/ansible/module_common.py をチェックアウトして実装を確認してみてください。

構築したモジュールをテストするための最も良い方法は、hacking/test-moduleスクリプトをチェックアウトして利用することです。そして、このスクリプトが、Ansible本体以外から独立してモジュールをテスト実行するための唯一の手段です。

独自に作成したモジュールを、Ansibleのソースツリーにマージしてほしいなら、AnsibleModuleクラスを利用していることが必須要件となっています。

Check Mode

モジュールでは、オプションとしてチェックモードをサポートしています。あなたのモジュールで、チェックモードをサポートしたければ、AnsibleModuleオブジェクトのインスタンス生成時に、引数として supports_check_mode=True を渡す必要があります。チェックモードが指定されているか否かは、AnsibleModule.check_modeがTrueになっていることで判定できます。以下がその例です。

module = AnsibleModule(
    argument_spec = dict(...),
    supports_check_mode=True
)

if module.check_mode:
    # Check if any changes would be made but don't actually make those changes
    module.exit_json(changed=check_if_system_state_would_be_changed())

モジュールの開発者には、チェックモードが有効化されているときに、システム状態が変更されないことを保証する責任があります。

あなたのモジュールで、チェックモードをサポートしていない場合に、利用者がAnsibleでチェックモードを指定すると、Ansibleは単純にモジュールの実行をスキップします。

Common Pitfalls

モジュール内に、以下のようなコードを書いてはいけません。なぜなら、モジュールの出力はJSON形式でしか認められていないからです。

print "some status message"

モジュールは、いかなるメッセージも標準エラー出力に出力すべきではありません。なぜなら、最終的に標準出力と標準エラー出力はマージされてしまうので、JSON形式でのメッセージパースを妨げてしまう原因となるからです。もしも、stderrや、その他の出力が有効なJSON形式で返された場合、その出力はAnsibleの実行結果として表示されるでしょうが、実際のコマンド実行は失敗に終わります。

このスクリプトは、さまざまな問題を警告してくれます。開発中のモジュールをテストする時は、必ずtest-moduleスクリプトを利用しましょう。

Conventions/Recommendations

これまで紹介したサンプルコードを思い出しつつ、モジュール開発の際の、基本的な規則とガイドラインを以下にまとめました。

  • Playbookに配置したモジュールは、実行時に、name:パラメータに設定した文字列で呼び出されます(たとえばhandlerセクションに書くhandlerなど)。これはモジュールの別名のようなものです。
  • もしも、あなたが独自のパラメータをfactsとして返すモジュールを配置するのであれば、モジュール名は site_facts とするのが良いでしょう。
  • モジュールのbolean型の値は、デフォルトの'yes','no','on','off','true','false','1','0',1,0 の他、AnsibleModuleクラスで定義されている、choicesパラメータとboolean関数で定義をオーバーライドすることができます。
  • 依存関係を少なくするために、インクルードするモジュールは最小限にしましょう。依存関係がある場合は、モジュールの先頭にコメントとして記述します。また、インポートに失敗した場合は、ImportErrorをキャッチして、はJSONでエラーメッセージを返してください(module.fail_json(msg))。
  • モジュールは、Ansibleが自動転送できるように、必要なコードをすべて含んだ、1つのファイルで構成しなければなりません。
  • 作成したAnsibleのモジュールを、RPMでパッケージ化して配布するのもあなたの自由です。その場合は、Ansibleが動作するホストの/usr/share/ansible/以下に配置してください。
  • モジュールからの出力は、JSON形式のみが許可されています。すべての戻り値は辞書型のデータ構造でなければなりません。なお、この辞書型データはネストさせることができます。配列はスカラはサポートされませんが、たとえば配列を辞書型データの値として利用することはできます。
  • 実行時の失敗通知には、msgパラメータに設定する概要説明と、'failed'キーを一緒に含まなければなりません。トレースバックを発生させるモジュールは、一般的に見て行儀の悪いモジュールです。しかし、AnsibleModuleクラスを利用していれば、トレースバックのような解析できないメッセージを、自動的に実行失敗時の戻り値として変換し、AnsibleModuleクラスで定義されているfail_json()を呼び出して返してくれます。
  • モジュールのリターンコードは、実際には重要ではありませんが、0が成功、それ以外を失敗として利用するのは、将来も継続して保証される可能性が高いです。
  • 多くの実行対象ホストからの出力が集約できるように、モジュールは関連のある情報のみを出力ようにするべきです。あらゆる情報を含んでいるログのような出力を返すのは、お行儀が良いとはいえません。

Documenting Your Module

Coreモジュールは、DOCUMENTATIONにモジュールの概要説明がYAML形式で定義されている必要があります。YAMLの編集に対応したテキストエディタを使えば、キーワードがハイライトされるので便利です。モジュール内のDOCUMENTATION変数に文字列として代入する前に、あらかじめYAML形式に沿っているかどうかをチェックしておきましょう。

Example

概要説明のサンプルが examples/DOCUMENTATION.yml <https://github.com/ansible/ansible/blob/devel/examples/DOCUMENTATION.yml>_ にあるので、checkoutして見てみましょう。

あなたのモジュール内のDOCUMENTATION変数に、以下のような概要説明を設定します。

#!/usr/bin/python
# Copyright header....

DOCUMENTATION = '''
---
module: modulename
short_description: This is a sentence describing the module
# ... snip ...
'''

descriptionnotes フィールドの記述には、いくつかの特別なマクロを利用することができます。

U()はURL、M()はモジュール、I()はイタリック、そしてC()は等幅フォーマット表記に置換されるマクロです。C()はファイル名やオプション名に利用し、I()にはパラメータを、モジュールの名前の指定にはM(module)を利用しましょう。

コロンや'を含む、利用方法のサンプルをYAML形式で記述するのは難しいですが、これらは以下の通り、モジュール内のEXAMPLES変数にプレーンテキストで設定します。

EXAMPLES = '''
- action: modulename opt1=arg1 opt2=arg2
'''

モジュールをPull Requestするためには、EXAMPLES変数とDOCUMENTATION変数に、そのモジュールの適切な概要説明や利用方法が設定されている必要があります。

Building & Testing

作成したモジュールを'library'ディレクトリ以下に配置して、make webdocsコマンドを実行すると、'docsite/'ディレクトリ以下に'modules.html'ファイルが作成されます。

tip

もしも、あなたがYAMLの構文に問題を抱えているなら、 YAML Lint <http://www.yamllint.com/>_ を利用して、構文をチェックすることができます。

tip

シェルの環境変数ANSIBLE_KEEP_REMOTE_FILESに1を設定しておくと、Ansibleが実行後のモジュールを削除するのを防ぐことができ、モジュールのデバッグに役立ちます。

Module Paths

あなたのモジュールを、Ansibleが見つけられない問題が発生しているなら、そのモジュールが、シェルの環境変数ANSIBLE_LIBRARY_PATHのパスに含まれていることを確認してください。

あなたがモジュールプロジェクトをforkしている場合は、以下のように設定します。

ANSIBLE_LIBRARY=~/ansible-modules-core:~/ansible-modules-extras

このようにすれば、forkしたモジュールプロジェクトがAnsibleの標準のモジュールよりも先にロードされます。このような状況では、forkしたバージョンのバグを報告しないように注意が必要です。

あなたが開発しているプロジェクトに対して、Ansibleの正規のバージョンとは別のバージョン名をつけるのは悪くない考えです。どのバージョンをPullすれば良いか理解できているはずなので、間違いを犯すことはないでしょう。これは安全性の観点では悪くない考えです。

Getting Your Module Into Ansible

他のモジュールとの依存関係を最小限にした高品質なモジュールは、AnsibleのCore,Extrasリリースに含めることもできます。ただし、このようなモジュールは、Pythonで記述されAnsibleModuleクラスが提供している機能を適切に利用しており、その他の部分も首尾一貫した記述になっていなければなりません。必要ならメーリングリストで要件を確認して、github上にある Extras <https://github.com/ansible/ansible-modules-extras>_ プロジェクトにPull Requestを投げてください。Extrasプロジェクトに含まれるモジュールは、Coreモジュールに昇格するチャンスがあります。CoreとExtrasは同じ品質ですが、開発の優先度はCoreモジュールの方が、わずかに高く設定されています。

Module checklist

  • スクリプトの1行目は、つねに #!/usr/bin/python であるべきです。この場合、常に ansible_python_interpreter に設定されたPythonインタプリタが利用されます。

  • 以下のポイントを確認してください。

    • モジュールのパラメータが必須の場合は、requiredに true を、必須でない場合は false のどちらかを必ず設定します。
    • required を false に設定した場合は、default に 'null' であってもデフォルト値を設定する必要があります。
    • required を 'true' に設定した場合は、default にデフォルト値を設定する必要はありません。
    • 何も設定していないのにもかかわらず、 aliases: []choices: [] のようなパラメータを記述する必要はないので削除しましょう。
    • バージョンは浮動小数点ではなく、カレントの開発バージョン番号です。
    • モジュールの実際の引数が、ドキュメントと同じ仕様であることを確認しましょう。
    • パスワードのような機密情報を引数に指定する場合は、 no_log=True に設定しましょう。
    • 依存しているライブラリなどは、requirements=[] パラメータに記述しましょう。
    • Authorには、あなたのgithubのIDを設定しましょう。
    • U(),C(),I(),M()マクロをちゃんと使っていますよね?
    • モジュールの先頭部分のコメントで、GPLライセンスであることを記載しましょう。
    • そのモジュールはチェックモードを利用していますか? チェックモードを利用するように修正されていますか? それをドキュメントに書いておきましょう。
    • Examples: に記述されている実行例は、ちゃんと動きますか?
    • Returns: にモジュールの実行結果の戻り値の概要説明を書きましょう。
  • モジュール内では適切な例外処理を行わなければなりません。
    • 例外発生時に、どのような状態だったかというような有用なメッセージを msg に設定して返しましょう。
    • 全ての例外を一括処理するfinallyのように、有用なエラーメッセージを含んでいない例外処理方法は避けましょう。
  • モジュールには、sys.exit()を使ってはいけません。module.fail_json()を使いましょう。
  • main()内で、カスタムモジュールをインポートした場合の例外のハンドリング例は以下の通りです。HAS_LIBをmodule.fail_json()で返します。

.

try:
    import foo
    HAS_LIB=True
except:
    HAS_LIB=False
  • 戻り値のキーとして、NAやNoneが使われるような場合は、通常は他の値として定義するなど、データ構造には一貫性をもたせる必要があります。

  • そのモジュールは冪等性を担保していますか?もしも担保していなければ、ドキュメントに明記しましょう。

  • コードの末尾付近にある from ansible.module_utils.basic import * には、さまざまな関数が含まれています。これを利用すれば、デバッグのためのコード記述量を少なくできます。

  • 以下のように、以下のようなif文による条件でmain()関数を呼び出すことが可能です。

.

if __name__ == '__main__':
    main()
  • 他のモジュールとの整合させたいなら、あなたのモジュールのパラメータに aliases オプションで別名を設定することができます。

  • pep8の規約に準拠していることは、良いことですが必須ではありません。具体的に1行の長さを80文字までとする条件は、可読性の向上という観点では邪魔となります。

  • action/commandモジュールを使うは避けましょう。それと同じような機能を提供する他の方法があります。

  • あるモジュールのために利用する固有の情報を取得するような場合は、そのモジュールとは別に、モジュール名_factsモジュールを作成して、情報を収集させましょう。

  • モジュールの中から別のモジュールを実行したい...そんな時は、Playbookのroleセクションを書きましょう。

Deprecating and making module aliases

Ansible v1.8以降では、モジュール名の先頭に_を付与することで、そのモジュールを非推奨扱いにすることができます。たとえば、 old_cloud.py_old_cloud.py に変更すると、モジュールとしては有効で利用可能な状態ですが、主要なドキュメントやモジュールリストからは除外されます。

また、モジュールの別名として、_で始まるシンボリックリンクを利用して、古い名前を維持することもできます。以下のサンプルは、statモジュール呼び出す際に、別名のfileinfoを利用することができます。

EXAMPLES = '''
ln -s stat.py _fileinfo.py
ansible -m stat -a "path=/tmp" localhost
ansible -m fileinfo -a "path=/tmp" localhost
'''

参考となるサイトへのリンク

modules

モジュールに関する情報

developing_plugins

プラグインを開発するのに必要な情報

developing_api

PythonのコードからAnsibleのAPIを利用するための情報

GitHub Core modules directory

Coreモジュールのソースコードリポジトリ

Github Extras modules directory

Extrasモジュールのソースコードリポジトリ

Mailing List

開発者向けのメーリングリスト

irc.freenode.net

AnsibleのIRCチャンネル

Vagrantで作成した仮想マシンのディスクを後から拡張する方法メモ - CentOS7編

参考にした手順

こちら https://gist.github.com/christopher-hopper/9755310 に記載されている手順が参考になった。ただし、今回利用したCentOS7のvagrant boxのイメージでは、ファイルシステムがCentOS7のデフォルトであるxfsとなっているため、最後のファイル拡張時には xfs_growf コマンドを利用する。

Vagrant使いなら、おそらくVagrantfile内でかっこよく処理して、最初から任意のサイズのディスクで仮想マシンを構成するんだろうが、ぼくはその方法を探せなかった。

vagrant boxをダウンロードする

vagrant boxは、A list of base boxes for Vagrant - Vagrantbox.es から CentOS-7.1.1503-x86_64-netboot.boxをダウンロードして利用した。

$ vagrant box add centos7 https://github.com/holms/vagrant-centos7-box/releases/download/7.1.1503.001/CentOS-7.1.1503-x86_64-netboot.box
$ mkdir ~/vagrant && cd ~/vagrant
$ vagrant box list

vagrant boxから仮想マシンインスタンスを起動させる。

$ vagrant init centos7
$ vagrant up

vagrant boxのディスク構成

今どきだとディスク容量が約10GBというのは小さい。これは、後から変更することを想定しているんだろうが、最初から変更できる方法は、あるのかもしれないが見つけられなかった。

やりたいこと

仮想マシンのディスクを、任意のサイズに変更したい。デフォルトの構成は以下の通り。

  • controller: SATA
  • filename: box-disk1.vmdk
  • size: 9.90GB

起動したvagrant boxにログインして、実際に確認してみる。

[vagrant@localhost ~]$ sudo fdisk -l

Disk /dev/sda: 10.6 GB, 10632560640 bytes, 20766720 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
Disk label type: dos
ディスク識別子: 0x000a56e0

デバイス ブート      始点        終点     ブロック   Id  システム
/dev/sda1   *        2048     1026047      512000   83  Linux
/dev/sda2         1026048    20766719     9870336   8e  Linux LVM

Disk /dev/mapper/centos-root: 8996 MB, 8996782080 bytes, 17571840 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト


Disk /dev/mapper/centos-swap: 1065 MB, 1065353216 bytes, 2080768 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト

これを拡張する。

ディスク拡張の流れ

仮想マシンのディスクを拡張する作業の流れは以下の通り。

  1. 仮想マシンをシャットダウンする
  2. ディスクのクローン機能を利用してディスクフォーマットをVMDK(box-disk1.vmdk)からVDI形式(box-disk1.vdi)に変更したディスクを作成する
  3. クローンで作成したVDI形式のディスクをリサイズする
  4. リサイズしたディスク(box-disk1.vdi)に切り替える
  5. 仮想マシンを起動する
  6. 拡張されたディスクにパーティションを追加する
  7. 仮想マシンを再起動する
  8. LVMを利用して、VolumeGroupに追加したパーティションを追加する
  9. ルートファイルシステム用の論理ボリューム領域を拡張する
  10. xfsファイルシステムを拡張する

では、実際に拡張してみる。まずは仮想マシンを停止する。

$ vagrant halt
==> default: Attempting graceful shutdown of VM...

続いて、仮想マシンの実体があるディレクトリに移動する。

$ cd ~/VirtualBox\ VMs/test_default_1433612417803_41795
$ ll
total 2759840
drwx------  3 foo  staff   102B  6  7 02:40 Logs
-rw-------  1 foo  staff   1.3G  6  7 02:49 box-disk1.vmdk
-rw-------  1 foo  staff   7.9K  6  7 02:40 test_default_1433612417803_41795.vbox
-rw-------  1 foo  staff   7.9K  6  7 02:40 test_default_1433612417803_41795.vbox-prev

続いて、VBoxManageコマンドを利用して、box-disk1.vmdkをVDI形式でクローンする。

$ VBoxManage clonehd box-disk1.vmdk box-disk1.vdi --format VDI
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Clone hard disk created in format 'VDI'. UUID: 89464f0b-0fa1-49bd-8fd5-ec8105ab6d26

クローンしたファイルを確認する。まだ拡張していないので、サイズは約10GBのまま。

$ VBoxManage showhdinfo box-disk1.vdi
UUID:           89464f0b-0fa1-49bd-8fd5-ec8105ab6d26
Parent UUID:    base
State:          created
Type:           normal (base)
Location:       /Users/foo/VirtualBox VMs/test_default_1433612417803_41795/box-disk1.vdi
Storage format: VDI
Format variant: dynamic default
Capacity:       10140 MBytes
Size on disk:   1369 MBytes

VDIファイル(box-disk1.vdi)をリサイズする。サイズの単位はMB。ここでは30GBに変更する。

$ VBoxManage modifyhd box-disk1.vdi --resize 30720
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%

サイズが変更されたことを確認する。

$ VBoxManage showhdinfo box-disk1.vdi
UUID:           89464f0b-0fa1-49bd-8fd5-ec8105ab6d26
Parent UUID:    base
State:          created
Type:           normal (base)
Location:       /Users/foo/VirtualBox VMs/test_default_1433612417803_41795/box-disk1.vdi
Storage format: VDI
Format variant: dynamic default
Capacity:       30720 MBytes
Size on disk:   1369 MBytes

ディスクをbox-disk1.vmdkからbox-disk1.vdiに切り替える。VBoxManageコマンドを利用して、コントローラ削除->コントローラ追加->ディスク接続という流れで作業を実行する。

$ VBoxManage list vms
"test_default_1433612417803_41795" {dce4105d-4864-48d7-a18e-d60988617beb}
$ VBoxManage storagectl dce4105d-4864-48d7-a18e-d60988617beb --name "SATA Controller" --remove
$ VBoxManage storagectl dce4105d-4864-48d7-a18e-d60988617beb --name "SATA Controller" --add sata
$ VBoxManage storageattach dce4105d-4864-48d7-a18e-d60988617beb --storagectl "SATA Controller" --type hdd --medium box-disk1.vdi --port 0

30GBになっている。続いて仮想マシンを起動する。

$ cd ~/vagrant
$ vagrant up

仮想マシンにログインして、ディスクサイズが30GBになっていることを確認する。

$ vagrant ssh
[vagrant@localhost ~]$ sudo -i
[root@localhost ~]# fdisk -l

Disk /dev/sda: 32.2 GB, 32212254720 bytes, 62914560 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
Disk label type: dos
ディスク識別子: 0x000a56e0

デバイス ブート      始点        終点     ブロック   Id  システム
/dev/sda1   *        2048     1026047      512000   83  Linux
/dev/sda2         1026048    20766719     9870336   8e  Linux LVM

Disk /dev/mapper/centos-root: 8996 MB, 8996782080 bytes, 17571840 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト


Disk /dev/mapper/centos-swap: 1065 MB, 1065353216 bytes, 2080768 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト

基本パーティション3(TypeはLVM)を作成する。

[root@localhost ~]# fdisk /dev/sda
Welcome to fdisk (util-linux 2.23.2).

Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


コマンド (m でヘルプ): n
Partition type:
   p   primary (2 primary, 0 extended, 2 free)
   e   extended
Select (default p): p
パーティション番号 (3,4, default 3): 3
最初 sector (20766720-62914559, 初期値 20766720):
初期値 20766720 を使います
Last sector, +sectors or +size{K,M,G} (20766720-62914559, 初期値 62914559):
初期値 62914559 を使います
Partition 3 of type Linux and of size 20.1 GiB is set

コマンド (m でヘルプ): t
パーティション番号 (1-3, default 3): 3
Hex code (type L to list all codes): 8e
Changed type of partition 'Linux' to 'Linux LVM'

コマンド (m でヘルプ): p

Disk /dev/sda: 32.2 GB, 32212254720 bytes, 62914560 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
Disk label type: dos
ディスク識別子: 0x000a56e0

デバイス ブート      始点        終点     ブロック   Id  システム
/dev/sda1   *        2048     1026047      512000   83  Linux
/dev/sda2         1026048    20766719     9870336   8e  Linux LVM
/dev/sda3        20766720    62914559    21073920   8e  Linux LVM

コマンド (m でヘルプ): w
パーティションテーブルは変更されました!

ioctl() を呼び出してパーティションテーブルを再読込みします。

WARNING: Re-reading the partition table failed with error 16: デバイスもしくはリソースがビジー状態です.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
ディスクを同期しています。

作成したパーティションを利用するには再起動が必要であるため、仮想マシンを再起動する。

[root@localhost ~]# reboot
Connection to 127.0.0.1 closed by remote host.
Connection to 127.0.0.1 closed.


$ vagrant ssh
Last login: Sat Jun  6 18:23:14 2015 from 10.0.2.2
Welcome to your Vagrant-built virtual machine.
[vagrant@localhost ~]$ sudo -i
[root@localhost ~]# pvdisplay
  --- Physical volume ---
  PV Name               /dev/sda2
  VG Name               centos
  PV Size               9.41 GiB / not usable 3.00 MiB
  Allocatable           yes
  PE Size               4.00 MiB
  Total PE              2409
  Free PE               10
  Allocated PE          2399
  PV UUID               5tuLzr-3IXr-pecY-i6yv-JNgy-OeRa-7WrIML

追加した基本パーティション(/dev/sda3)をVolumeGroup(centos)に加える。

[root@localhost ~]# pvcreate /dev/sda3
[root@localhost ~]# pvdisplay
  --- Physical volume ---
  PV Name               /dev/sda2
  VG Name               centos
  PV Size               9.41 GiB / not usable 3.00 MiB
  Allocatable           yes
  PE Size               4.00 MiB
  Total PE              2409
  Free PE               10
  Allocated PE          2399
  PV UUID               5tuLzr-3IXr-pecY-i6yv-JNgy-OeRa-7WrIML

  "/dev/sda3" is a new physical volume of "20.10 GiB"
  --- NEW Physical volume ---
  PV Name               /dev/sda3
  VG Name
  PV Size               20.10 GiB
  Allocatable           NO
  PE Size               0
  Total PE              0
  Free PE               0
  Allocated PE          0
  PV UUID               QotznM-3nYG-nCyj-UUEs-rowi-VFNl-liv55I

[root@localhost ~]# vgextend centos /dev/sda3
  Volume group "centos" successfully extended

ルートファイルシステムを提供している論理ボリュームのデバイス名を確認する。

[root@localhost ~]# df
ファイルシス            1K-ブロック    使用  使用可 使用% マウント位置
/dev/mapper/centos-root     8775680 1063956 7711724   13% /
devtmpfs                     227912       0  227912    0% /dev
tmpfs                        234316       0  234316    0% /dev/shm
tmpfs                        234316    4376  229940    2% /run
tmpfs                        234316       0  234316    0% /sys/fs/cgroup
/dev/sda1                    508588   84776  423812   17% /boot

ルートファイルシステム用の論理ボリュームデバイス( /dev/mapper/centos-root)のサイズを拡張する。

[root@localhost ~]# lvextend -l +100%FREE /dev/mapper/centos-root
  Size of logical volume centos/root changed from 8.38 GiB (2145 extents) to 28.51 GiB (7299 extents).
  Logical volume root successfully resized

この時点では、まだxfsファイルシステムとしては拡張されていない。

[root@localhost ~]# df -h
ファイルシス            サイズ  使用  残り 使用% マウント位置
/dev/mapper/centos-root   8.4G  1.1G  7.4G   13% /
devtmpfs                  223M     0  223M    0% /dev
tmpfs                     229M     0  229M    0% /dev/shm
tmpfs                     229M  4.3M  225M    2% /run
tmpfs                     229M     0  229M    0% /sys/fs/cgroup
/dev/sda1                 497M   83M  414M   17% /boot

最後にxfsファイルシステムを拡張する。

[root@localhost ~]# xfs_growfs /
meta-data=/dev/mapper/centos-root isize=256    agcount=4, agsize=549120 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=0        finobt=0
data     =                       bsize=4096   blocks=2196480, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0 ftype=0
log      =internal               bsize=4096   blocks=2560, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0
data blocks changed from 2196480 to 7474176
[root@localhost ~]# df -h
ファイルシス            サイズ  使用  残り 使用% マウント位置
/dev/mapper/centos-root    29G  1.1G   28G    4% /
devtmpfs                  223M     0  223M    0% /dev
tmpfs                     229M     0  229M    0% /dev/shm
tmpfs                     229M  4.3M  225M    2% /run
tmpfs                     229M     0  229M    0% /sys/fs/cgroup
/dev/sda1                 497M   83M  414M   17% /boot

これで完了。 VMDKファイル(box-disk1.vmdk)は、もう使わないと思うので、削除しておいてもいいかも。

devstackを利用したOpenStackの構築 - CentOS7版

CentOS 7にdevstackでOpenStackをインストールしてみたので、手順をメモしておく。

構成

VMware Fusionの上に以下のスペックの仮想マシンを作成する。

  • VMware Fusion Professional 7.1.1 (2498930)
  • cpu: 1
  • mem: 4096MB
  • hdd: 30GB
  • nic0: vmnet10,192.168.100.240/24
  • nic1: vmnet11,172.16.100.240/24

インストール対象コンポーネント

  • cinder
  • heat
  • horizon
  • keystone
  • neutron
  • nova

事前準備

ホスト名の変更や/etc/hostsへの自身のIPアドレスの登録など、基本的な設定を行う。また、VMware Fusion上の仮想マシンNIC名は長すぎて、VLAN & LinuxBridgeの組み合わせのようなケースでは、ipコマンドがインターフェイスを作成するときにエラーとなるので、昔ながらのethNとして利用できるよう、grubの設定を変更しておく。

ホスト名設定(/etc/hostname)

devstack

hostsファイルへの登録(/etc/hosts)

192.168.100.240 devstack
172.16.100.240  devstack

/etc/sysconfig/selinux

「いしかわさんごめんなさい いしかわさんごめんなさい いしかわさんごめんなさい」3回詠唱してから無効化する。

SELINUX=disabled

NetworkManagerを停止する

NetworkManagerを停止して、無効化しておく。

# systemctl stop NetworkManager
# systemctl disable NetworkManager

/etc/sysconfig/network-scripts/ifcfg-eth[01]を作成する

構成にあわせてIPアドレスを設定する

NIC名を従来のethNに戻す(/etc/sysconfig/grub)

  1. /etc/sysconfig/grubGRUB_CMDLINE_LINUX に biosdevname=0 net.ifnames=0 を追記する
  2. grub2-mkconfig -o /boot/grub2/grub コマンドで変更を反映する
  3. システムを再起動する

必要となるパッケージを追加する

個別に必要となるパッケージ群をインストールしておく。Development Toolsはパッケージグループであるため、yum groupinstallでインストールする。

iptablesのクリア

iptablesをクリアした状態で保存しておく。(いらないかも)

# iptables -F
# service iptables save

devstackを利用したOpenStackのインストール

stackユーザを作成してsudoを許可する

ユーザを作成する

# adduser stack
# passwd stack
New password: ********
Retype new password: ********

sudoを許可する

# echo "stack ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers

devstackを取得する

公式サイトのリポジトリから、devstackをチェックアウトする。以降の作業はstackユーザで実施する

$ git clone https://git.openstack.org/openstack-dev/devstack
$ cd devstack

local.confを作成する

$HOME/devstack/local.confを作成する

##
## local.conf for devstack
##
[[local|localrc]]
ADMIN_PASSWORD=changeme
MYSQL_PASSWORD=changeme
RABBIT_PASSWORD=changeme
SERVICE_PASSWORD=changeme
SERVICE_TOKEN=changeme

## Log settings
LOGDIR=$DEST/logs
SCREEN_LOGDIR=$LOGDIR
SCREEN_HARDSTATUS="%{= rw} %H %{= wk} %L=%-w%{= bw}%30L> %n%f %t*%{=    wk}%+Lw%-17< %-=%{= gk} %y/%m    /%d %c"
LOGFILE=$LOGDIR/devstack.log

## Milestone of each components
VERSION=master
CINDER_BRANCH=$VERSION
GLANCE_BRANCH=$VERSION
HORIZON_BRANCH=$VERSION
KEYSTONE_BRANCH=$VERSION
NOVA_BRANCH=$VERSION
NEUTRON_BRANCH=$VERSION
SWIFT_BRANCH=$VERSION

## Keystone
KEYSTONE_TOKEN_FORMAT=UUID

## Glance
IMAGE_URLS=http://download.cirros-cloud.net/0.3.2/cirros-0.3.2-x86_64-uec.tar.gz

## Cinder
VOLUME_BACKING_FILE_SIZE=8192M

## Neutron
## To use nova-network, comment out the following
## for VM on VMware Fusion
PRIVATE_NETWORK_NAME=net1
PUBLIC_NETWORK_NAME=ext_net
PUBLIC_NETWORK_GATEWAY=192.168.100.2
FLOATING_RANGE=192.168.100.0/24

Q_PLUGIN=ml2
Q_FLOATING_ALLOCATION_POOL="start=192.168.100.129,end=192.168.100.190"
disable_service n-net
enable_service neutron
enable_service q-svc
enable_service q-agt
enable_service q-dhcp
enable_service q-l3
enable_service q-meta
enable_service q-lbaas
enable_service q-fwaas
enable_service q-vpn


## Heat
enable_service heat h-api h-api-cfn h-api-cw h-eng
#disable_service heat h-api h-api-cfn h-api-cw h-eng

## Ceilometer
#enable_service ceilometer-acompute ceilometer-acentral ceilometer-collector ceilometer-api
disable_service ceilometer-acompute ceilometer-acentral ceilometer-collector ceilometer-api
CEILOMETER_BACKEND=mongo

## Swift
SWIFT_HASH=e2ce2896-d961-4eba-b649-62ae2835f137
#enable_service s-proxy s-object s-container s-account
disable_service s-proxy s-object s-container s-account

## Database backend
enable_service mysql
#enable_service postgresql

##
## [EOF]
##

OpenStackをインストールする

iptablesで母艦からのhorizon及び各APIへのアクセスを許可するスクリプト(open_filter.sh)を作成して実行しておく。

#!/bin/sh
export DEVSTACK_SERVER=192.168.100.240
sudo iptables -A INPUT -p tcp --dport http -j ACCEPT
sudo iptables -I FORWARD 4 -i eth0 -m state --state NEW -m tcp -p tcp -d $DEVSTACK_SERVER --dport http -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 5000 -j ACCEPT
sudo iptables -I FORWARD 4 -i eth0 -m state --state NEW -m tcp -p tcp -d $DEVSTACK_SERVER --dport 5000 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 6080 -j ACCEPT
sudo iptables -I FORWARD 4 -i eth0 -m state --state NEW -m tcp -p tcp -d $DEVSTACK_SERVER --dport 6080 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8000 -j ACCEPT
sudo iptables -I FORWARD 4 -i eth0 -m state --state NEW -m tcp -p tcp -d $DEVSTACK_SERVER --dport 8000 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8003 -j ACCEPT
sudo iptables -I FORWARD 4 -i eth0 -m state --state NEW -m tcp -p tcp -d $DEVSTACK_SERVER --dport 8003 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8004 -j ACCEPT
sudo iptables -I FORWARD 4 -i eth0 -m state --state NEW -m tcp -p tcp -d $DEVSTACK_SERVER --dport 8004 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8773 -j ACCEPT
sudo iptables -I FORWARD 4 -i eth0 -m state --state NEW -m tcp -p tcp -d $DEVSTACK_SERVER --dport 8773 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8774 -j ACCEPT
sudo iptables -I FORWARD 4 -i eth0 -m state --state NEW -m tcp -p tcp -d $DEVSTACK_SERVER --dport 8774 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8775 -j ACCEPT
sudo iptables -I FORWARD 4 -i eth0 -m state --state NEW -m tcp -p tcp -d $DEVSTACK_SERVER --dport 8775 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8776 -j ACCEPT
sudo iptables -I FORWARD 4 -i eth0 -m state --state NEW -m tcp -p tcp -d $DEVSTACK_SERVER --dport 8776 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 9191 -j ACCEPT
sudo iptables -I FORWARD 4 -i eth0 -m state --state NEW -m tcp -p tcp -d $DEVSTACK_SERVER --dport 9191 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 9292 -j ACCEPT
sudo iptables -I FORWARD 4 -i eth0 -m state --state NEW -m tcp -p tcp -d $DEVSTACK_SERVER --dport 9292 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 9696 -j ACCEPT
sudo iptables -I FORWARD 4 -i eth0 -m state --state NEW -m tcp -p tcp -d $DEVSTACK_SERVER --dport 9696 -j ACCEPT
#
# [EOF]
#

stack.shを実行して、OpenStackをインストールする。stack.sh実行後にscreenでOpenStackの各コンポーネントのサービスが起動するのはubuntu版と同様。

$ cd $HOME/devstack
$ ./stack.sh

再起動時に備えてmariadbhttpdサービスを有効化しておく。

$ sudo systemctl enable mariadb
$ sudo systemctl enable rabbitmq-server
$ sudo systemctl enable httpd
$ sudo systemctl enable openvswitch

リブート後のサービス再開

再起動後にOpenStackの各コンポーネントを起動する方法は以下の通り、cinder-volume用のボリュームバックエンドをlosetupしてからrejoin-stack.shを実行する。あらかじめ、open_filer.shを実行しておくのを忘れずに。

$ sh $HOME/open_filter.sh
$ sudo losetup -f /opt/stack/data/stack-volumes-lvmdriver-1-backing-file
$ cd $HOME/devstack
$ ./rejoin-stack.sh

JenkinsとGerritの連携させてCIっぽい仕組みを作る

CI環境

OpenStackのようなCI/CD環境を自前で作りたいと思っていろいろ調べてみたので備忘録として残しておく。使うプロダクトはGerritとJenkinsと、そのバックエンドのMariaDBの3つ。GerritとJenkinsは、ともにJavaVMの上で稼働させる。

  1. Gerrit - ソースコードレビューに利用
  2. Jenkins - ソースコードの自動ビルド&自動チェックに利用

母艦

仮想マシン(CIサーバ)

  • CPU x 1 / Memory 2048MB
  • CentOS Linux release 7.1.1503
  • IPAddress: 10.0.2.10/24
  • java-1.7.0-openjdk-1.7.0.79-2.5.5.1.el7_1.x86_64
  • jenkins-1.596.2-1.1.noarch
  • gerrit-2.10.3.1.war
  • mariadb-server-5.5.41-2.el7_0.x86_64

Jenkins

事前準備

インストールに利用するwgetと、Jenkinsを起動させるためのjdkを、あらかじめインストールしておく。

---
$ sudo yum install wget
$ sudo yum install java-1.7.0-openjdk
---

インストール

JenkinsのRPMパッケージをインストールする。パッケージはCentOS7としては標準提供されていないが、公式サイトが提供しているJenkinsのリポジトリを利用することにより、Jenkins公式RPMパッケージをインストールすることができる。

---
$ sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat-stable/jenkins.repo
$ sudo rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.key
$ sudo yum install jenkins
---

起動

systemctlコマンドでJenkinsの起動と、OSリブート時の自動起動設定を行う。chkconfigではsystemdのサービス適用外というメッセージが出るが、ここではsystemdのサービスに載せないので気にせず進める。

---
$ chkconfig --list jenkins

Note: This output shows SysV services only and does not include native
      systemd services. SysV configuration data might be overridden by native
      systemd configuration.

      If you want to list systemd services use 'systemctl list-unit-files'.
      To see services enabled on particular target use
      'systemctl list-dependencies [target]'.

jenkins         0:off   1:off   2:off   3:on    4:off   5:on    6:off
---

RPMパッケージをインストールすると、RunLevel:3で自動起動onとなっているので、特になにもしない。続いてサービスを手動で起動する。

---
$ sudo service jenkins start
---

確認

Jenkinsはデフォルト状態で起動させると、ダッシュボードとして0.0.0.0:8080にアクセスすることにより利用できる。

http://10.0.2.0:8080

これで、ひとまずJenkinsが正常にインストールされたことが確認できた。

Gerrit

事前準備

Gerritが利用するデータベースをインストールする

Gerritが利用できるデータベースは、公式サイトのドキュメントによると以下の通り。

ここではMySQL(CentOS7なのでMariaDB)を利用する。

---
$ sudo yum install mariadb
$ sudo yum install mariadb-server
$ sudo systemctl enable mariadb
$ sudo systemctl start mariadb
$ sudo mysqladmin -u root password 'changeme'
---

MariaDBのインストールが完了したら、続いてGerritが利用するデータベースとユーザを作成する。

---
MariaDB [(none)]> CREATE USER 'gerrit2'@'localhost' IDENTIFIED BY 'changeme';
MariaDB [(none)]> CREATE DATABASE reviewdb;
MariaDB [(none)]> GRANT ALL ON reviewdb.* TO 'gerrit2'@'localhost';
MariaDB [(none)]> FLUSH PRIVILEGES;
---

※Gerritを起動させるためにはjdkが必要だが、これはJenkinsのインストール時に既にインストール済みなので割愛。

Gerritユーザ(gerrit2)を作成する

Gerritが利用するユーザアカウント(gerrit2)を作成する。

---
$ sudo adduser gerrit2
---

インストール

Gerritをダウンロードする。

---
$ sudo su - gerrit2
$ wget https://gerrit-releases.storage.googleapis.com/gerrit-2.10.3.1.war
---

起動

ここでは サイト名:review_site のGerritサイトを初期化する。

---
$ java -jar gerrit-2.10.3.1.war init -d /home/gerrit2/review_site

*** Gerrit Code Review 2.10.3.1
***

Create '/home/gerrit2/review_site' [Y/n]? Y

*** Git Repositories
***

Location of Git repositories   [git]:

*** SQL Database
***

Database server type           [h2]: mysql

Gerrit Code Review is not shipped with MySQL Connector/J 5.1.21
**  This library is required for your configuration. **
Download and install it now [Y/n]? Y
Downloading http://repo2.maven.org/maven2/mysql/mysql-connector-java/5.1.21/mysql-connector-java-5.1.21.jar ... OK
Checksum mysql-connector-java-5.1.21.jar OK
Server hostname                [localhost]:10.0.2.10
Server port                    [(mysql default)]:
Database name                  [reviewdb]:
Database username              [gerrit2]:
gerrit2's password             :
              confirm password :

*** Index
***

Type                           [LUCENE/?]:

*** User Authentication
***

Authentication method          [OPENID/?]:

*** Review Labels
***

Install Verified label         [y/N]? y

*** Email Delivery
***

SMTP server hostname           [localhost]:
SMTP server port               [(default)]:
SMTP encryption                [NONE/?]:
SMTP username                  :

*** Container Process
***

Run as                         [gerrit2]:
Java runtime                   [/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.45-30.b13.el7_1.x86_64/jre]:
Copy gerrit-2.10.3.1.war to /home/gerrit2/review_site/bin/gerrit.war [Y/n]? Y
Copying gerrit-2.10.3.1.war to /home/gerrit2/review_site/bin/gerrit.war

*** SSH Daemon
***

Listen on address              [*]:
Listen on port                 [29418]:

Gerrit Code Review is not shipped with Bouncy Castle Crypto SSL v151
  If available, Gerrit can take advantage of features
  in the library, but will also function without it.
Download and install it now [Y/n]? Y
Downloading http://www.bouncycastle.org/download/bcpkix-jdk15on-151.jar ... OK
Checksum bcpkix-jdk15on-151.jar OK

Gerrit Code Review is not shipped with Bouncy Castle Crypto Provider v151
** This library is required by Bouncy Castle Crypto SSL v151. **
Download and install it now [Y/n]? Y
Downloading http://www.bouncycastle.org/download/bcprov-jdk15on-151.jar ... OK
Checksum bcprov-jdk15on-151.jar OK
Generating SSH host key ... rsa... dsa... done

*** HTTP Daemon
***

Behind reverse proxy           [y/N]? N
Use SSL (https://)             [y/N]? N
Listen on address              [*]:
Listen on port                 [8080]: 8081
Canonical URL                  http://localhost:8081/: http://10.0.2.10:8081

*** Plugins
***

Install plugin commit-message-length-validator version v2.10.3.1 [y/N]? y
Install plugin download-commands version v2.10.3.1 [y/N]? y
Install plugin replication version v2.10.3.1 [y/N]? y
Install plugin reviewnotes version v2.10.3.1 [y/N]? y
Install plugin singleusergroup version v2.10.3.1 [y/N]? y

Initialized /home/gerrit2/review_site
...
---

Gerritがデフォルトで開くポートは8080で、Jenkinsが利用しているポートとバッティングする。そこで、GerritをPort:8081で起動するよう明示的に指定してGerritを起動させることにした。

ちなみに、サービスの起動・停止・再起動はサイト(review_site)以下にインストールされている bin/gerrit.sh スクリプトを利用して行う。

---
/home/gerrit2/review_site/bin/gerrit.sh stop|start|restart
---

最後に自動起動設定を行う。gerrit.shはchkconfigコマンドに対応した記述となっている。

---
$ exit    # gerrit2ユーザからexitする
$ ln -snf /home/gerrit2/review_site/bin/gerrit.sh /etc/init.d/gerrit
# sudo -i
# chkconfig --add gerrit
# echo "GERRIT_SITE=/home/gerrit2/review_site" > /etc/default/gerritcodereview
---

確認

Webプラウザで、GerritサーバのPort:8081にアクセスすると、Gerritのダッシュボードを利用することができる。ログインアカウントはLaunchpadアカウント(母艦上で利用しているユーザの公開鍵登録済み)を利用した。

http://10.0.2.0:8081

これで、Gerritも正常にインストールされたことが確認できた。

プロジェクト登録

Gerritのインストールが完了したところで、テスト用のプロジェクト「sandbox」を作成して、Gerritが正しく機能提供できているかをチェックする。

  1. Gerritダッシュボード -> Sign In -> Sign in with a Launchpad ID -> Launchpadアカウントでログインする
  2. settings -> profile -> Usernameを設定する -> SSH Public Keys -> 公開鍵を登録する -> HTTP Password -> Generate Passwordでパスワードを生成する

これでGerritにログインしてプロジェクトを作成することが可能となったので、実際にsandboxプロジェクトを作成してみる

---
$ ssh -p 29418 foo@10.0.2.10 gerrit create-project --empty-commit --name sandbox
---

エラーが出なければ作成成功。Gerritのダッシュボードから確認すると、sandboxプロジェクトが作成されているのがわかる。

続いて、母艦(osx)上で、gitコマンドでGerritのサイトからソースコードをチェックアウトしてみる。

---
$ git clone http://10.0.2.10:8081/sandbox
$ cd sandbox
$ git config --local user.name foo
$ git config --local user.email foo@example.com

$ git remote add gerrit http://10.0.2.10:8081/sandbox
$ git-review -s

$ git branch test/0
$ git checkout test/0
$ date > testfile.txt
$ git add testfile.txt
$ git commit
$ git-review
Username for 'http://10.0.2.10:8081': プロジェクト登録で指定したUsername
Password for 'http://foo@10.0.2.10:8081': プロジェクト登録で生成したHTTP Password
---

おまけ

デフォルトのプロジェクト設定ファイル(project.config)を変更したい場合

---
$ git init cfg ; cd cfg
$ git remote add origin ssh://foo@10.0.2.10:29418/All-Projects
$ git pull origin refs/meta/config
$ vi project.config
$ git push origin HEAD:refs/meta/config
---

いろいろあってパッチセットをDBから削除したい

---
MariaDB [(none)]> use reviewdb
MariaDB [reviewdb]> delete from patch_sets;
---

JenkinsとGerritの連携

事前準備

JenkinsがGerritを利用するために必要になる公開鍵を作成する。パスフレーズありだとテストが成功しないため、ここではパスフレーズ無しの公開鍵を生成した。生成した公開鍵は、あらかじめクライアントに登録しておく。

---
$ sudo -u jenkins ssh-keygen -t rsa -C foo@example.com
---

続いて、母艦(osx)から、GerritにJenkinsのユーザアカウントを登録する。ここでは、アカウント名:jenkinsとした。生成した公開鍵の登録も忘れずに。

---
$ ssh -p 29418 foo@10.0.2.10 gerrit create-account jenkins --email foo@example.com --full-name jenkins
$ cat /tmp/jenkins.pub | ssh -p 29418 foo@10.0.2.10 gerrit set-account jenkins --add-ssh-key -
---

続いて、CIサーバからjenkinsユーザでGerritにログインできることを確認する。Welcomeメッセージが表示されれば、公開鍵の登録は無事に完了している。

---
sudo -u jenkins ssh jenkins@localhost -p 29418
---

Gerrit上でJenkinsのユーザアカウントに対して、コードレビューに必要な権限を設定する。

  1. AdministratorグループのアカウントでGerritにログイン
  2. People -> List Groups -> Non-Interactive Users -> Members を選択する
  3. jenkins ユーザをAddする
  4. Projects -> List -> sandbox -> Access -> Edit -> Add Reference を押す
  5. Reference: refs/* を設定する
  6. Reference: refs/* で Add Permissions -> Read -> ALLOW: Non-Interactive Users を選択する
  7. Reference: refs/heads/* で Label Code-Review に Non-Interactive Users を(-1,+1)で追加する
  8. Reference: refs/heads/* で Label Verified に Non-Interactive Users を(-1,+1)で追加する
  9. People -> Create New Group : "Event Streaming Users" を作成して jenkins ユーザを追加する
  10. Projects -> List -> All-Projects -> Access -> Edit -> Global Capabilities -> Stream Events に ALLOW: Event Streaming Users を追加する

Jenkinsプラグインのインストール

Jenkinsのダッシュボードのプラグインの管理から、Gerrit Trigger PluginとGit Pluginをインストールする。

Gerrit Triggerプラグインの設定

プラグイン管理->Gerritトリガー を選択して、以下の3つのセクションの設定を行う。設定を保存するのを忘れずに。

  1. 新しいサーバを追加
  2. Gerrit接続設定
  3. Gerritレポートスコア

新しいサーバを追加

---
- サーバを追加: develop
- ◉ Gerrit Server with Default Configurations
---

Gerrit接続設定

---
- 名前: develop
- 起動時に接続しない: □
- ホスト名: 10.0.2.10
- フロントエンドURL: http://10.0.2.10:8081/
- SSHポート: 29418
- プロキシ: 空
- ユーザー名: jenkins
- メールアドレス: foo@example.com
- SSHキーファイル: /var/lib/jenkins/.ssh/id_rsa
- SSHキーファイルパスワード: 空
- 最新のパッチのみビルド: □
---

Gerritレポートスコア

---
Verified
  ===
  - 開始: 0
  - 成功: 1
  - 失敗: -1
  - 不安定: 0
  - ビルドせず: 0
  ===

Code-Review
  ===
  - 開始: 0
  - 成功: 1
  - 失敗: -1
  - 不安定: 0
  - ビルドせず: 0
  ===
---

JenkinsとGerritを連携させるジョブの作成

Jenkinsのダッシュボードから、新規ジョブの作成を選択する。

---
プロジェクト名: sandbox_gerrit
説明: Gerritのサンプルプロジェクト用のテストジョブ
フリースタイルジョブ
□ 古いビルドの破棄    Help for feature: 古いビルドの破棄
□ ビルドのパラメータ化  Help for feature: ビルドのパラメータ化
□ ビルド無効化 (プロジェクトが再び有効化されるまで新しいビルドは行われなくなります)
□ ビルドを並行実行
---

ソースコード管理

---
○ なし 
○ CVSCVS Projectset 
◉ Git
   ===
   Repositories
   - Repository URL: git+ssh://jenkins@10.0.2.10:29418/sandbox.git
   - Credentials: なし
   - 高度な設定:
     - Name: 空
     - Refspec: $GERRIT_REFSPEC

   Branches to build
   - Branch Specifier (blank for 'any'): 空   .....空文字にしても、設定を反映すると結局は"**"が入る

   リポジトリ・ブラウザ: 自動

   Additional Behaviours: Strategy for choosing what to build
   - Choosing strategy: Gerritトリガー
   ===
○ Subversion
---

ビルド・トリガ

---
□ Build after other projects are built
■ Gerritイベント
---

Gerritトリガー

トリガーするGerritイベントに、必要なイベントを追加していくが、Patchset Createdだけで十分かな。

---
サーバー: develop
サイレントモード: □
レビューされていないパッチセットをチェックする: ■
トリガーするGerritイベント
  ===
  - Patchset Created
    - Draftsを除外する: □
    - Trivial Rebaseを除外する: □
    - No Code Changeを除外する: □
  ===
動的トリガー設定: □

プロジェクト追加
  ===
  Gerritプロジェクト
  - タイプ: Plain
  - パターン: sandbox
  - ブランチ追加
    ===
    タイプ: Path
    パターン: **
    ===

□ SCMをポーリング
□ 定期的に実行
---

ビルド

ビルド手順を追加する。テスト用なので、成功させたいときは exit 0 で、失敗させたいときは exit 0以外 とする。

---
シェルの実行
  - シェルスクリプト
    ===
    #!/bin/sh
    echo "Jenkins review is done"
    exit 0
    ===
---

おわり

これで、ソースコードをcloneしてソースコードを修正後にcommitし、git-reviewにかけるとパッチセットが作成されて、Gerrit上でレビューが実施できる状態となり、そのタイミングでパッチセットの自動ビルドとチェックをJeninsを利用して実施できる環境を構築できた。

ビルド後の処理

特に何もしない

参考URL

クリスマスの夜はOpenStackをPythonワンライナーでキメる(第1夜)

みなさんめりーくりすます!

この記事は、OpenStack Advent Calendar 2014の12/25の記事です。

日本OpenStackユーザ会のAdvent Calendarでは、第1回からクリスマス(またはイヴ)の夜はOpenStackをワンライナーで操作するという伝統芸をお送りしています。 2014年は4部くらいの構成でお送りする予定で、今日は、その第1夜です(今日は、まだワンライナーじゃないよ)

※決して2013年のワンライナーを解読するのに時間がかかって、締切に間に合わなそうだから、苦しまぎれに4回構成にしたってわけではありません

目標

今回の目標は、ワンライナー仮想マシンのリストを取得するまでです。毎年こんなんですが、、、

PythonのプログラムからOpenStackを操作する方法

ぼくも、正直普段は1の方法で利用しています。2の方法をとるのは、毎年このAdvent Calendarを書くときだけです(^^;

方法1. OpenStackのPythonクライアントライブラリを利用する

方法2. 直接REST APIを叩く

REST APIを利用するためのドキュメント

以下のドキュメント(本当によく書いてあるなぁ)を参考に実装していきます。

普通に書いてみる

python-novaclientを利用しない時点で、すでにあまり普通ではないのですが、まぁ普通に書いてみます。

novaが管理する仮想マシンのリストを取得する手順

OpenStackのREST APIを利用します。基本的にPythonが標準で提供しているライブラリのみで乗り切ります。

1. keystoneに認証してもらってトークンIDとサービスカタログを取得する

サービスカタログには、コンポーネントのエンドポイントURLが - POSTメソッド - URL: $OS_AUTH_URL/tokens - ヘッダ

Content-Type: application/json
Accept: application/json
  • リクエスト(JSON形式)
{
    "auth": {
        "tenantName": $OS_TENANT_NAME,
        "passwordCredentials": {
            "username": $OS_USERNAME,
            "password": $OS_PASSWORD
        }
    }
}
  • レスポンス
{
    "access": {
        "token": {
            ... 省略 ...
            "id": "aaaaa-bbbbb-ccccc-dddd",  <-取得したトークンのID
            "tenant": {
                ... 省略 ...
                "id": "fc394f2ab2df4114bde39905f800dc57", <-取得したテナントID
                "name": $OS_TENANT_NAME
            }
        },
    ... 省略 ...
   }
}

2. keystoneからテナントIDを取得する

Content-Type: application/json
Accept: application/json
X-Auth-Token: トークンID  <-1.で取得したトークンID
  • レスポンス(JSON形式)
{
    "tenants": [
        {
            "id": テナントID,
            "name": "tenant-A",
            ...省略...
        },
        {
            "id": テナントID,
            "name": "tenant-B",
            ...省略...
        }
    ],
    "tenants_links": []
}

3. リージョン名とテナントIDからエンドポイントURLを特定する

$OS_REGION_NAMEと2.で取得したテナントIDを利用して1.で取得したサービスカタログから、nova APIのエンドポイントURLを特定します。

4. novaから仮想マシンのリストを取得する

  • GETメソッド
  • URL: novaAPIのエンドポイントURL/servers
  • ヘッダ
Content-Type: application/json
Accept: application/json
X-Auth-Token: トークンID  <-1.で取得したトークンID
  • レスポンス(JSON形式)
だーーーーっとJSON形式で構造化された仮想マシン情報

ソースコード(nova_list.py)

python-novaclientを使わないと、おそろしくめんどくさい...

#!/usr/bin/env python

import json
import re
import urllib
import urllib2
from os import environ as env


def throw_post(url, body, header):
    request = urllib2.Request(url=url,
                              data=json.dumps(body),
                              headers=header)
    return json.loads(urllib2.urlopen(request).read())


def throw_get(url, header):
    request = urllib2.Request(url=url, headers=header)
    return json.loads(urllib2.urlopen(request).read())


def main():
    os_auth_url = env.get('OS_AUTH_URL').rstrip('/')
    os_user = env.get('OS_USERNAME')
    os_passwd = env.get('OS_PASSWORD')
    os_region = env.get('OS_REGION_NAME')
    os_tenant = env.get('OS_TENANT_NAME')

    header = {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
    }

    #
    # get auth-token and catalog info
    #
    auth_header = header.copy()
    api_tokens_url = os_auth_url + '/tokens'
    auth_body = dict(auth=dict(tenantName=os_tenant,
                               passwordCredentials=dict(username=os_user,
                                                        password=os_passwd)))
    auth_response = throw_post(api_tokens_url, auth_body, auth_header)
    token_info = auth_response['access']['token']
    catalog_info = auth_response['access']['serviceCatalog']

    #
    # get tenant-id
    #
    tenant_header = header.copy()
    api_tenants_url = os_auth_url + '/tenants'
    tenant_header['X-Auth-Token'] = token_info['id']
    tenant_response = throw_get(api_tenants_url, tenant_header)
    for i in tenant_response['tenants']:
        if i['name'] == os_tenant:
            tenant_id = i['id']

    #
    # get endpoint-url
    #
    for i in catalog_info:
        if i['type'] == 'compute':
            for j in i['endpoints']:
                if j['region'] == os_region and j['tenantId'] == tenant_id:
                    os_nova_url = j['publicURL']

    #
    # get instance list
    #
    servers_header = header.copy()
    servers_header['X-Auth-Token'] = token_info['id']
    api_servers_url = os_nova_url + '/servers'
    servers_response = throw_get(api_servers_url, servers_header)
    for i in servers_response['servers']:
        print '%s %s' % (i['id'], i['name'])


if __name__ == '__main__':
    # usage: python nova_list.py
    # description: get instance-list such as "nova list"
    main()


#
# [EOF]
#

実行してみる

ちゃんとリストは取得できますね。

$ python nova_list.py
4f0a63bb-f79e-4850-ad5f-3156d677940b test-vm00
a4ca06c9-aff5-4363-b5aa-7a85eb18500c test-vm01
6e7c2192-253c-4b6d-94b4-f1ada8a993d8 test-vm02
213299ca-1c13-4b5e-8ce6-bbb30758f340 test-vm03

明日からが本番だぜ!

明日からは、この普通のコードをワンライナーに直して行く予定です。 あんまり期待しないでまっててね。今年のOpenStackのアドカレは、まだまだおわらないぜ!

OpenStack & Ansible Advent Calendar 2014 12-24

OpenStack & Ansible Advent Calendar 2014 12-24

これは、OpenStack (2枚目) Advent Calendar 2014 と Ansible Advent Calendar 2014 の24日目(クリスマスイヴ!!)の記事です。

OpenStackとAnsibleの、12/24という割と重要な日を同時に担当してしまったので、この2つのキーワードを連携させる内容で書いてみました。

AnsibleからOpenStack上に仮想マシンを作成する

AnsibleではOpenStackを操作するためのモジュール群がいくつか提供されています。 しかしながら、積極的にメンテナンスされているのは、 nova_computeモジュール のみという状態に見えます。 そこで、この記事ではnova_computeモジュールの利用方法を紹介します。

モジュール名 機能
glance_image 仮想マシンイメージの登録・削除
keystone_user プロジェクト/ユーザー/ロールの作成・削除・紐付け
nova_compute 仮想マシンの作成・削除
nova_keypair キーペアの登録・削除
quantum_floating_ip フローティングIPの払い出しと仮想マシンインスタンスへの割り当て
quantum_floating_ip_associate 特定フローティングIPの割り当てと割り当て解除
quantum_network 仮想ネットワークの作成・削除
quantum_router 仮想ルータの作成・削除
quantum_router_gateway 仮想ルータと外部ネットワークセグメントの接続・切断
quantum_router_interface 仮想ルータと仮想ネットワークセグメントの接続・切断
quantum_subnet 仮想ネットワーク内のサブネットの作成・削除

しかしながら、このモジュールは問題含みで本当に限られた環境でしか安全に動作しません。 利用方法を紹介する前に、標準提供されるOpenStack操作関連のモジュールが抱える問題(結構致命傷)を2つ書いておきます。OpenStackの仮想マシン管理にAnsibleを利用したい場合(あるのか?)は、対応しておくことをお勧めします。

<問題1> quantumモジュールとglanceモジュールはマルチリージョン未対応

neutronを操作する全てのモジュール(quantum_*)と、glanceを操作するモジュール(glance_image)はマルチリージョンに対応してません。 Optionでは、Region_nameを指定できますが、実際には利用されておらず、OpenStackのクライアントオブジェクト生成時に渡されていない....

<問題2> nova_computeモジュールがfloating_ipの割り当て時に使用済みアドレスの除外に失敗しています

nova_computeモジュールで仮想マシン新規作成時にfloating_ipを割り当てる際、以下のような動きをします。

本来の動作
  1. floating_ip_poolsを指定すると、払い出し済みの全てのfloating_ipの中から未使用のアドレスを1つを選んで仮想マシンに割り当てる
  2. 未使用のアドレスがなければ、新たに1つfloating_ipを払いだして割り当てる
実際の動作

しかし、実際には、払い出し済みかどうかに関わらず、プール内のすべてのfloating_ipの中から1つを選択して割り当ててしまうのですこれはひどい...

背景

nova_computeモジュールでは、floating_ipが割り当て済みかどうかを、APIから返されるfloating_ipに紐付けられているinstance_idから判断しているのですが、現状のnova apiは、instance_idを空で返してきます。nova_computeモジュールはinstance_idが存在していないため、未使用と判断して、これを仮想マシンへの割り当て候補リストに加えてしまうのです。 ちなみに、HP Public Cloudでは、このnovaの不具合が修正されているので問題ありません。

nova_computeモジュールが持つ致命的な不具合

自前で構築したOpenStack環境だとJuno(2014.2)でもこの不具合を引き当てます。 引き当てるとどうなるかというと、既に他の仮想マシンに割り当てられているfloating_ipが強制的に取り上げられて、新たに作成した仮想マシンに付与してしまいます(novaの仕様)。これは相当やばい。

対策

v1.8系のnova_computeモジュールを使うときは要注意!

対策をちゃんと施したほうが良いです。 Pull Requestしてるんだけど、早くマージしてくれないかなぁ...

実際にnova_computeモジュールを利用してみる

前節の対策はおわすれなく。

必須要件

AnsibleからOpenStackを操作するためには、大きく3つの要件を満たしている必要があります。

  1. OpenStack APIのURL/アカウント/パスワード/テナント名/リージョン名を持っている
  2. OpenStackのPythonクライアントライブラリがインストールされている
  3. nova_computeの不具合に対処済みである

下準備

pythonのvirtualenvを利用して仮想実行環境にAnsibleとOpenStackのクライアントライブラリを閉じ込めます。

1. virtualenv環境構築
$ virtualenv $HOME/advc
New python executable in advc/bin/python
Installing Setuptools..............................................................................................................................................................................................................................done.
Installing Pip.....................................................................................................................................................................................................................................................................................................................................done.
$ source advc/bin/activate
(advc)$
2. Ansibleのインストール

pipを利用してAnsibleをインストールする

(advc)$ pip install jinja2 passlib pycrypto pyyaml
(advc)$ pip install ansible
3. python-novaclientとpython-neutronclientのインストールと環境変数設定

nova_computeモジュールは、python-novaclientが提供するライブラリを利用しているため、利用前にインストールしておく必要があります。

依存関係があるのでpython-keystoneclientもインストールされます。

(advc)$ pip install python-novaclient python-neutronclient
(advc)$ vi openrc
export OS_AUTH_URL=https://認証用URL:ポート/v2.0/
export OS_REGION_NAME=リージョン名
export OS_TENANT_NAME=テナント名
export OS_USERNAME=ユーザ名
export OS_PASSWORD=パスワード
4. nova_computeモジュールの配置
(advc)$ cd $HOME/advc/lib/python2.7/site-packages/ansible/modules/core/cloud/openstack
(advc)$ mv nova_compute.py nova_compute.py.org
(advc)$ wget http://goo.gl/8Vr3QT -O nova_compute.py
5. Ansibleの動作確認
(advc)$ cd $HOME
(advc)$ echo "localhost ansible_connection=local" > ansible_hosts
(advc)$ ansible all -i ansible_hosts -m ping

仮想マシンを作成する

作成に必要な仮想マシンやネットワークの情報は nova コマンドや neutron コマンドで取得しておきましょう!

  • create_vm.yml
---
- hosts: localhost
  tasks:
  - name: ansible_python_interpreter setup
    set_fact: ansible_python_interpreter="{{ lookup('pipe', 'which python') }}"
  - name: create virtual machine instance
    nova_compute:
      state: present
      auth_url: "https://認証URL:ポート/v2.0/"
      region_name: "リージョン名"
      login_tenant_name: "テナント名"
      login_username: "ユーザID"
      login_password: "パスワード"
      availability_zone: "アベイラビリティゾーン名"
      flavor_id: フレーバID
      floating_ip_pools:
      - "パブリックネッーワーク名"
      image_id: "GuestOSイメージID"
      key_name: "キーペア名"
      name: "仮想マシンのホスト名"
      nics:
      - net-id: "内部ネットワークID"
      security_groups: "セキュリティグループ名"

#
# [EOF]
#
  • 実行
(advc)$ ansible-playbook -i ansible_hosts create_vm.yml

PLAY [localhost] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [ansible_python_interpreter setup] **************************************
ok: [localhost]

TASK: [create virtual machine instance] ***************************************
changed: [localhost]

PLAY RECAP ********************************************************************
localhost                  : ok=3    changed=1    unreachable=0    failed=0
  • 作成されたかを確認してみる
(advc)$ nova show test-vm00
+-----------------------------+----------------------------------------------------------+
| Property                    | Value                                                    |
+-----------------------------+----------------------------------------------------------+
| OS-EXT-AZ:availability_zone | アベイラビリティゾーン                                   |
| OS-EXT-STS:power_state      | 1                                                        |
| OS-EXT-STS:task_state       | -                                                        |
| OS-EXT-STS:vm_state         | active                                                   |
| accessIPv4                  |                                                          |
| accessIPv6                  |                                                          |
| config_drive                |                                                          |
| created                     | 2014-12-24T08:52:24Z                                     |
| flavor                      | フレーバ名(フレーバID)                                   |
| hostId                      | 4bd0942b81bcc260be67911e43a43fc91217d78a18e59d32f2bd275b |
| id                          | 4f0a63bb-f79e-4850-ad5f-3156d677940b                     |
| image                       | OSイメージ名 (OSイメージID)                              |
| key_name                    | キーペア名                                               |
| metadata                    | {}                                                       |
| name                        | ホスト名                                                 |
| progress                    | 0                                                        |
| security_groups             | セキュリティグループ名                                   |
| status                      | ACTIVE                                                   |
| tenant_id                   | テナントID                                               |
| updated                     | 2014-12-24T08:52:54Z                                     |
| user_id                     | ユーザID                                                 |
| work-net network            | 10.0.0.23, 15.126.228.53                                 |
+-----------------------------+----------------------------------------------------------+

よし!できた! これを基本にしてvarsなどをうまく利用すれば、novaコマンドを素で叩くよりは楽に仮想マシンが作れそうです!

それではみなさん素敵なクリスマスを!