ここから本文です

みなさま、こんにちは!

この度、バックログブログを弊社の新しいブログヌーラボブログに移行して続けることになりました。(こちらのブログは削除せずに残しておきます。)

引き続きそしてパワーアップ!して技術情報や弊社のイベントやその他ツールの情報などもあわせてお届けします!

今後ともヌーラボブログの方でよろしくお願いします!

この記事は Ansible Advent Calendar 2013 の記事です。

Backlog ブログとしては「 Ansible モジュール作成のイロハ 」の後編でもあります。Ansible モジュールの入出力などの基本、デバッグの仕方、シェルスクリプトでのモジュール作成などは前回の記事をご参考ください。

Advent Calendar には「 AnsibleでOSのディストリビューションによって挙動を変えるモジュールの実装例 」という濃いお話もありましたが、本エントリでは Python で Ansible を作成する方法の基礎を紹介します。前回に引き続き、今回の内容も GitHub に サンプルコード をあげておりますので、是非試しながらご覧ください。

Python で記述するメリット

Python を知らなくても簡単に使いはじめられる、言語を問わずモジュールを書けるといった辺りは Ansible の魅力なのですが、いざモジュールを書くとなると以下のようなメリットがあるので Python で記述することをおススメします。

  • 引数のハンドリングや、返り値の制御、チェックモードのサポートなどの汎用的なものは Ansible 側で用意されているユーティリティを利用できる
  • Ansible の標準ライブラリは全て Python で記述されており、実戦投入されているサンプルが豊富にある

Ansible 提供のユーティリティの実行の流れ

Ansible が提供するユーティリティは

以下で提供されてます。ここにある basic.py はまず利用するものになります。これを利用するには、作成する Ansible のモジュール内で

from ansible.module_utils.basic import *

のように import します。ただし実行時に Python の標準の import は行われず、module_common.py にある置換処理によって、リモートサーバに送信する前にモジュールファイルに直接 basic.py の内容が展開されます。ごっそり basic.py の内容が展開されるので import に細かい指定をしても実行時には関係なくなります。尚、このときモジュールへの引数もあわせてそのファイルに展開されます。

前回紹介したように ANSIBLE_KEEP_REMOTE_FILES=1 を指定すればサーバ側で実行された、置換処理が行われた後のモジュールを確認することが出来ます。なお、古いモジュールでは同様の置換処理のために以下を使っていました。

#<<INCLUDE_ANSIBLE_MODULE_COMMON>>

まだ標準モジュールでもこの記述は数多く見受けられますし、Ansible のドキュメントも現在はこの形式の記述がありますが、import 形式にすると IDE などでも解決出来るといったメリットもありますので、今後作成するモジュールでは import の形式にしておくとよいでしょう。

モジュールの構造

標準のユーティリティを利用したモジュールは大体以下のような構造になります。

#!/usr/bin/python
DOCUMENTATION = '''
'''

EXAMPLES = '''
'''

def main():

    module = AnsibleModule()
    module.exit_json(changed=False)

from ansible.module_utils.basic import *

main()

まず、最初にモジュールのドキュメントや例を記述します。自分たちで作成して使う分には必須ではありませんが、もし本体の標準モジュールへの取り込みをリクエストする場合には、記述することが義務づけられています。続いて

  • 処理の本体となる main 関数の定義
  • 標準のユーティリティの import
  • 最後に main 関数の呼び出し

という流れになっています。main 関数は特に "main" という名前でなくても良いですが、標準モジュール内でも慣例的に利用されていますので、特に理由がない限りそれにならうと良いでしょう。

さて、その main 関数の内部で利用されている AnsibleModule というクラスがいわば肝のクラスです。次にその代表的な機能を幾つかみてみましょう。

AnsibleModule クラスの提供する機能

引数の処理

まず引数の処理ですが、以下のように argument_spec に各引数の定義を行います。デフォルト値、必須等のほか、force パラメータで指定しているような、引数の型についても指定を行うことで型のチェックを行うことが出来ます。( str, list, dict, bool, int が利用可能 )

module = AnsibleModule(
    argument_spec=dict(
        version=dict(default=None, required=True),
        prefix=dict(default='/usr/local/bin', required=False),
        force=dict(default=False, required=False, type='bool'),
    ),
)

必須の引数がない場合などのチェックは AnsibleModule 側で行ってくれます。引数の値は module のプロパティである params から以下のように取得することが可能です。

version = module.params['version']
prefix = module.params['prefix']
force = module.params['force']

この他にも、引数同士の許可されない組み合わせを指定する mutually_exclusive 、どれか一つが必須であることを指定する required_one_of 、ファイル処理の基本的な引数 ( src, mode, owner など ) をワンセットで追加する add_file_common_args などなど、引数の処理に関しては概ね事足りるでしょう。

返り値の制御

成功した場合は以下のように changed プロパティを指定して exit_json を実行します。これ以外の任意のプロパティも返す事ができ、その値を出力で確認することができます。

module.exit_json(changed=True)

対して、エラーが発生した場合は

 module.fail_json(msg='error occuered. specified version %s is not found' % version)

のように、msg というプロパティ付きで fail_json を呼び出します。どちらも実行すると exit するのでこの後に処理を継続することは出来ません。

チェックモードのサポート

ansible コマンドや ansible-playbook コマンドでは -C を引数につけることで、実際の実行は行わないチェックモードにて処理を走らせる事が出来ます。こちらに対応する場合は、コンストラクタで supports_check_mode に True を設定します。

module = AnsibleModule(
    argument_spec=dict(
        version=dict(default=None, required=True),
        prefix=dict(default='/usr/local/bin', required=False),
        force=dict(default=False, required=False, type='bool'),
    ),
    supports_check_mode=True,
)

そしてチェックモードかどうかは module.check_mode にて取得できます。

※ 現在 pip でインストールされる 1.4.1 については ここ の修正がまだ入っておらず、先述の import 方式だと古いタイプのモジュールと誤判定され、チェックモードが利用できない不具合があります。1.4.1 でチェックモードを利用したい場合には、上で紹介したコメントによる置換を行ってください。

コマンドの実行

システムのコマンドを実行するには、run_command メソッドを利用します。またその前準備として、特にシステム標準ではないようなコマンドを利用する場合などは 、get_bin_path メソッドでコマンドのパスを取得しておくと良いでしょう。opt_dir という引数にてコマンドのサーチパスを追加することもできます。

wget_path = module.get_bin_path('wget', required=True)
(rc, out, err) = module.run_command('%s -P %s %s' % (wget_path, temp_dir, download_url), check_rc=True)

コマンドの出力結果は out にて確認が可能です。

Python モジュールのサンプル

さて、これらを踏まえたサンプルとして、システムの環境にあわせて serf をインストールするようなモジュールを以下に作成しました。

実行するには、必要なツールをインストールの上、以下のようにしてください。ANSIBLE_REMOTE_PORT は、vagrant の起動時に決まるので変更する必要がある場合もあります。生成された ssh.config 内の Port を確認して、必要であればその値にあわせてください。

$ git clone https://github.com/nulab/ansible-sample.git
$ cd ansible-sample
$ vagrant up
$ vagrant ssh-config > ssh.config
$ ANSIBLE_REMOTE_PORT=2222 ANSIBLE_SSH_ARGS="-F ssh.config" ansible default -i hosts -m serf -s -a "version=0.3.0"

上で説明した機能に加え、以下のような機能も活用しています。

    def do_copy():
        module.atomic_move(serf_bin, target_bin)
        module.set_mode_if_different(target_bin, 0755, changed)

atomic_move はアトミックにファイルをコピーする機能です。set_mode_if_different は名前の通り、もし指定したモードと異なる場合は更新する、という関数です。グループやオーナーの変更などにも同様のメソッドがありますし、一括で変更する set_file_attributes_if_different なども提供されています。この辺りに興味があれば是非 basic.py の中をのぞいてみてください。

今回のケースについては、通常は get_url と shell モジュールの組み合わせで十分事足りるとは思いますが、今回は AnsibleModule クラスの様々な機能を紹介するという観点でモジュールにしてみました。

二回に渡ってお送りしましたが、いかがでしたでしょうか?タスクの定義が複雑になってきた場合には独自のモジュール作成をすれば、スッキリとまとまり見通しがよくなる事もありますので、是非お試しください。

明日は ando-masaki さん です!

ベトナムのホーチミンとオフショア開発を行っている東京は六本木にオフィスを構えるエバーライズさん。
今回はベトナムと東京でどのようにしてシステム開発において情報共有しているかエバーライズの中川さんと古賀さんにお話をお伺いしました。

  • 会社名:エバーライズ
  • チーム構成:六本木20数名、ベトナム(ホーチミン)40名。
  • 日本での受託開発案件や自社製品のプロジェクトにおける開発をベトナムで行っている。日本は営業や設計を担っている。

どのような経緯でベトナムで開発するようになったんでしょうか?

ベトナム視察を行く事があり、その際にベトナムの活気を受け日本とベトナムでオフショア開発をしたらのびるんじゃないかと直感で思い始める事にしました。現在はベトナムとの開発を始めて1年ぐらいになります。日本の会社は8年くらいです。

ベトナムのチームとの仕事を行うにあたり、問題点などありましたら教えてください。

ベトナムの開発チームが抱える課題を当初日本のマネージャが把握することができないことがありました。マネージャを敬う気持ちが強い特性で、問題を自分で解決しようとしてしまうためです。

work-1.jpg

どのようにしてこの問題を解決していきましたか?

当初ベトナムの開発チーム構成は、プログラマ5名+日本の通訳者、ベトナム現地のチームリーダーで構成しており、BacklogやSkypeでコミュニケーションをとっていましたが通訳者がITがよくわからない事もありうまくきませんでした。理解度がすこし低かったのです。そこでBacklogのWikiを使い用語集を作成しました。また毎朝日本とベトナム間でミーティングを開始する事で、わからない事があれば解決してからすすめるようにしました。すると少しずつ問題が解決されていきました。教育やナレッジを伝える時間を作る事が重要だと感じました。

Backlogの他にどんなツールを複数拠点での仕事のために使っていますか?

主にSkypeとBacklogを中心に仕事をしています。Backlogでは、親課題を日本側で作成し、ベトナム側で子課題を作成し、その課題の進み具合を確認しています。小課題はベトナム側におまかせして自由に登録するようにすすめています。これは仕様の理解度を確認するにも役立っています。理解していなければ課題をついかできませんので。開発の仕様書についてはWikiを作ってそこの上で共有するようにしています。

everriseVN2013.jpg

海外や国内含め複数拠点で開発している会社にアドバイスがありましたらおしえてください。

タスクの見える化するルールを作って複数拠点での仕事をスタートする事をおすすめします。ツールだけでは解決できない事もありますので問題がある場合は何が問題か丁寧にコミュニケーションして把握するようにしましょう。またそのためにコミュニケーションしやすい雰囲気作りを心がけるといいと思います。

こんにちは、染田です。すっかり寒くなってきましたがいかがお過ごしでしょうか。

いわゆるサーバ構成管理ツールとしては Chef が不動の人気を博していますが、最近では Python 製の Ansible の情報も多く見かけるようになってきています。現在 Backlog をはじめとするヌーラボの各サービスでは、この Ansible を利用しています。

私たちにとって採用の決め手となったのはサーバ側にインストールの必要がなく ssh 出来さえすればいい点や、YAML を書くだけで大抵の事は出来るといったシンプルさです。ただし、

  • 標準の shell モジュールや command モジュールの組み合わせがツラくなった
  • よくある処理を複数のプロジェクトで共有したくなった

といった場合には独自のモジュールを書くのがおススメです。例えば Backlog では cpanm や plenv また pyenv、AWS の特定の設定を行う用のモジュールを作成して利用しています。

さて、その Ansible でのモジュール作成について二回に渡りご紹介します。本エントリでは、Ansible モジュールの概要と shell でのモジュール作成方法、次回は Python でのモジュール作成方法を取り上げます。エントリ中で紹介するモジュールは以下の GitHub のリポジトリにアップロードしてありますので、是非動かしながら確認してください。Ansible のバージョンは 1.4 です。

それでは早速はじめましょう。

モジュールとは

Ansible におけるモジュールは、処理の最小単位で ansible コマンドや Playbook のタスクで指定されたモジュールが処理対象のサーバに転送されて、サーバ上で実行されます。

ですので、標準のモジュールを利用していても Ansible のバージョンが異なれば、仮に同じモジュールであっても中身が違うという事がありえます。作業者が複数いる場合には Ansible そのものや、自作モジュールのバージョンの統一といった点に注意が必要です。

Ansible が標準的に提供している モジュール群 (モジュールライブラリ) は Python で記載されていますが、モジュールを作る観点では、サーバで実行可能であればどのような言語で記述されていても構いません。

Ansible のモジュールは

  • ansible.cfg の library で指定されたディレクトリ
  • 環境変数 ANSIBLE_LIBRARY で指定されたディレクトリ
  • コマンドラインで --module-path で指定されたディレクトリ
  • 実行ディレクトリ直下にある library という名前のディレクトリ

から検索されます。プロジェクト固有のものは playbook を実行するディレクトリの直下に library を作るという最後の方法がバージョン管理的にも実行のしやすさからいっても便利でしょう。 逆に例えば複数のサービスで共有のモジュールは個別バージョン管理を行い、ansible.cfg や ANSIBLE_LIBRARY で追加する形が良いでしょう。

モジュールの入出力

実行時に渡される引数は、モジュールには引数が格納されたファイル名として渡され、その中身は以下のようになります。

引数名1=値1 引数名2=値2 ...

大抵のモジュールは引数を取る事になると思いますので、まず最初にこの引数を正しく受け取る処理を行う必要があります。Python の場合には Ansible が提供しているクラスを利用することで、より簡単に引数を取得することが出来ます。

次に出力ですが、原則 JSON フォーマットにて出力することが推奨されています。処理が成功した場合には、changed というプロパティに boolean の値を設定することで、変更が行われたかどうかを Ansible 側に返すことができます。また、処理に失敗した場合は failed というプロパティに真を設定し、あわせて msg というプロパティに失敗した理由を返すことが推奨されています。

尚、主に shell でのモジュールに向けて、以下のような簡易フォーマットも許可されています。

key=value rc=0 changed=true favcolor=green

これ以外のフォーマットを出力として返すとエラーになってしまいます。

モジュールのデバッグ

Ansible を直接 Git のリポジトリから clone して利用している場合、test-module というユーティリティを使って、開発中のモジュールをローカル環境で実行しながらデバッグする事ができます。

$ git clone https://github.com/ansible/ansible.git
$ source ansible/hacking/env-setup
$ chmod 755 ansible/hacking/test-module

この clone した ansible の場所を ANSIBLE_HOME として、現在開発中のモジュールが library/clipboard とすると、以下のような形でデバッグ実行します。尚、テストとはいえローカルでは実際に処理が走りますのでその点は注意してください。

$ $ANSIBLE_HOME/hacking/test-module -m library/clipboard -a "message='hello world'"
* module boilerplate substitution not requested in module, line numbers will be unaltered
***********************************
RAW OUTPUT
rc=0 changed=false msg='no message found'

***********************************
PARSED OUTPUT
{
    "changed": false, 
    "msg": "no message found", 
    "rc": 0
}

また、最終的にサーバ側で実行されたモジュールファイルの中身を確認したい場合は、

ANSIBLE_KEEP_REMOTE_FILES=1

を指定して実行を行う事でサーバ側に転送されたファイルを残しておくことが出来ます。モジュールは、ssh でログインしたユーザの

  • $HOME/.ansible/tmp/ansible-<タイムスタンプ>-<乱数>

の中に保存されています。(ANSIBLE_REMOTE_TEMP を指定している場合はそのディレクトリ配下) こちらはモジュールの開発時だけでなく、運用時に問題が起こった場合にもデバッグに活用できるでしょう。

シェルスクリプトでモジュールを作る

シェルスクリプトのサンプルとして、ローカルで稼働させて指定したメッセージをそっとクリップボードにコピーするようなモジュールを作ってみましょう。実行のイメージは以下のような形です。

 $ ansible -m clipboard -i hosts local -a "message='hello ansible module'"

これを実行した後にペーストを実行すると message という引数に渡された「hello ansible module」がペーストされるはずです。スクリプトの全体は以下の通りとなります。

#!/bin/bash
eval $(perl -lne 'while(/\s?([^=\s]+)\s?=\s?(\"(?:\\\"|[^\"])+\"|\x27(?:\x27\\\x27\x27|[^\x27])+\x27|\S+)\s?/g){print "$1=$2"}' $1)

is_exist()
{
    (type $1 > /dev/null 2>&1) && echo "yes"
}

setup_command()
{
    if [ "$(is_exist pbcopy)" == "yes" ]; then 
        cmd="pbcopy"
    elif [ "$(is_exist xsel)" == "yes" ]; then
        cmd="xsel --input --clipboard"
    elif [ "$(is_exist putclip)" == "yes" ]; then
        cmd="putclip"
    fi

    if [ -z "${cmd}" ]; then
        printf "failed=true msg='clipboard copy command not found'\n"
        exit 1
    fi    
}

setup_command
if [ -z "${message}" ]; then
    printf "rc=0 changed=false msg='no message found'"
    exit 0
fi

echo -n "${message}" | ${cmd}
printf "rc=$? changed=true cmd=\'${cmd}\' message=\'$message\'\n"

shell でモジュールを書くときのポイントは、まずファイルで渡された引数を評価するところです。clipboard スクリプトでは、

eval $(perl -lne 'while(/\s?([^=\s]+)\s?=\s?(\"(?:\\\"|[^\"])+\"|\x27(?:\x27\\\x27\x27|[^\x27])+\x27|\S+)\s?/g){print "$1=$2"}' $1)

というようにして、各々の「変数名=値」を eval にて評価することで shell 内で変数として扱えるようにしています。この際、もし何らかの ansible の不具合があり、

a=b rm -fr /*

といった引数のファイルが来た場合にそのまま評価してしまうと大変な事になってしまいます。これは極端な例ですが、不慮のコマンド実行を避けるため、シェルで変数宣言として評価出来る以下の文字のみを受け取るための処理をしています。= の前後の空白は省くようにしてます。

  • a=bcd # 空白を含まない文字列
  • a="b \"c=d" # ダブルクォートで囲まれた文字列 ( 空白やエスケープしたダブルクォートは許可 )
  • a='b '\''c=d' # シングルクォートで囲まれた文字列 ( 空白や '\'' によるシングルクォートは許可 )

また、この処理はスクリプト内の変数の値を上書きしてしまう可能性があるため、一番最初に呼び出すようにしています。

それ以外の部分はごくごく普通の shell スクリプトです。最終的な返り値だけ上で記述したような、フォーマットとルールに従って出力を行います。

クリップボードコピーのコマンドが見つからなかったとき

printf "failed=true msg='clipboard copy command not found'\n"

引数が与えられなかったとき ( changed=false で正常終了 )

printf "rc=0 changed=false msg='no message found'"

正しく実行されたとき ( changed=true で正常終了 )

printf "rc=$? changed=true cmd=\'${cmd}\' message=\'$message\'\n"

次回の Python で記述するほうが便利なユーティリティなどもあり基本はそちらがおススメではありますが、お決まりのビルド処理などを記述する場合には shell でも十分な事もあると思います。

Ansible モジュール作成の基本についてご紹介しました。次回の Python でのモジュール作成についてもお楽しみに!

2013年12月上旬に予定しているリリースから、セキュリティ強化のためにパスワードを設定する時の制約を強化します。

  • パスワードの最小文字数が 4 文字から 8 文字に変わります。
  • ユーザ ID と同じパスワードを設定できなくなります。
  • 前回と同じパスワードを設定できなくなります。

現在ご利用中のパスワードについては、特に変更しなければ引き続きご利用いただけます。

また、設定する方法によって適用される制約が変わります。

個人設定 > パスワードの変更 スペース設定 > ユーザの編集 (管理者のみ) API からの登録変更 (管理者のみ)
最小文字数は 8 文字-
ユーザ ID をパスワードに設定できない
前回と同じパスワードを設定できない--

ご利用の皆様にはお手数をお掛けしますが、よろしくお願いいたします。

追記(12/5): この変更は 2013年12月4日より適用されています。

ソーシャルボタン
  • お問い合わせ
  • RSS
検索
カテゴリー
アーカイブ
Backlog開発者よりメッセージ
backlog orignal icons