[macOS] AWS CLIのインストール

AWS CLImacOS 12.1にインストールする手順を備忘録として残しておく。

環境

AWS CLIのインストール

AWS公式サイトのインストール手順にしたがってAWS CLIをインストールする。

AWSCLIV2.pkgをダウンロード:

$ mkdir awscli && cd awscli
$ curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"

AWSCLIV2.pkgをインストール:

$ sudo installer -pkg ./AWSCLIV2.pkg -target /
Password: ********
installer: Package name is AWS Command Line Interface
installer: Installing at base path /
installer: The install was successful.
$ hash -r
$ aws --version
aws-cli/2.4.7 Python/3.8.8 Darwin/21.2.0 exe/x86_64 prompt/off
$ which aws
/usr/local/bin/aws

認証情報の登録

公式サイトのクイック設定ガイドにしたがって、awx configureコマンドで設定する:

※あらかじめACCESS_KEYとSECRET_KEY情報を取得しておく

$ aws configure
AWS Access Key ID [None]: <AWS_ACCESS_KEY>
AWS Secret Access Key [None]: <AWS_SECRET_KEY>
Default region name [None]: ap-northeast-1
Default output format [None]: json

AWS接続に利用するdefaultプロファイルが$HOME/.aws/ディレクトリ配下に作成される:

$ cd $HOME
$ tree .aws
.aws
├── config
└── credentials

$HOME/.aws/config:

[default]
region = ap-northeast-1
output = json

$HOME/.aws/credentials:

[default]
aws_access_key_id = <AWS_ACCESS_KEY>
aws_secret_access_key = <AWS_SECRET_KEY>

AWSへのアクセス確認

作成したプロファイルを利用して、awsコマンドでAWSのリソースを操作できることを確認する:

$ aws ec2 describe-instances --output=table --query='Reservations[].Instances[].{InstanceId: InstanceId, Name: Tags[?Key==`Name`].Value|[0]}'
-------------------------------------------------------------
|                     DescribeInstances                     |
+----------------------+------------------------------------+
|      InstanceId      |               Name                 |
+----------------------+------------------------------------+
|  i-***************** |  hsaito-aap125                     |
|  i-***************** |  hsaito-aap210                     |
+----------------------+------------------------------------+

[macOS] Terraformのインストール

TerraformをmacOS 12.1にインストールする手順を備忘録として書き留めておく。

環境

Terraformのインストール

Download Terraformにしたがって、Homebrewのtapを登録してインストールする。

$ brew tap hashicorp/tap
$ brew install hashicorp/tap/terraform
$ hash -r
$ terraform -v
Terraform v1.1.2
on darwin_arm64

動作確認

Quick start tutorialにしたがって動作確認を実施。

Docker Desktopの起動と作業ディレクトリの作成:

$ open -a Docker
$ mkdir test && cd test

テスト用のマニフェストファイル(main.tf)の作成:

terraform {
    required_providers {
        docker = {
            source  = "kreuzwerker/docker"
            version = "~> 2.15.0"
        }
    }
}

provider "docker" {}

resource "docker_image" "nginx" {
    name = "nginx:latest"
    keep_locally = false
}

resource "docker_container" "nginx" {
    image = docker_image.nginx.latest
    name  = "tutorial"
    ports {
        internal = 80
        external = 8000
    }
}

main.tfでTerraformのInitialize:

$ terraform init

terraform initを実行すると、Terraformのワーキングディレクトリが作成される:

$ tree -a .
.
├── .terraform
│   └── providers
│       └── registry.terraform.io
│           └── kreuzwerker
│               └── docker
│                   └── 2.15.0
│                       └── darwin_arm64
│                           ├── CHANGELOG.md
│                           ├── LICENSE
│                           ├── README.md
│                           └── terraform-provider-docker_v2.15.0
├── .terraform.lock.hcl
└── main.tf

Docker Desktopを起動してmain.tfをApplyすると、テスト用のNginxコンテナが起動する:

$ open -a Docker
$ terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are
indicated with the following symbols:
  + create
...
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

Nginxコンテナが起動していることを確認:

$ docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED              STATUS              PORTS                  NAMES
74241ca394eb   eeb9db34b331   "/docker-entrypoint.…"   About a minute ago   Up About a minute   0.0.0.0:8000->80/tcp   tutorial
$ curl -o /dev/null -s -w '%{http_code}\n' https://www.google.co.jp
200

テスト環境の削除

main.tfを配置したディレクトリでterraform destroyコマンドで環境を削除:

$ terraform destroy
docker_image.nginx: Refreshing state... [id=sha256:eeb9db34b33190cac170b27d857e9b23511d396a2609c1a54e93025634afed0anginx:latest]
docker_container.nginx: Refreshing state... [id=74241ca394eb095063b2cd2565725b0e72108cc4c6c0f5985b507506a3fc12ed]

Terraform used the selected providers to generate the following execution plan. Resource actions are
indicated with the following symbols:
  - destroy
...
Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes
...

はじめてのAnsibleのモジュール開発

はじめに

Ansibleのモジュールは、ansibleコマンドやansible-playbookコマンドから利用されるスクリプトです。

モジュールは、引数を受け取り、何らかの処理を実行した後に、標準出力にJSON形式で実行結果を出力します。

+---------+                               +----------------+
| ansible +---{引数を格納したファイル}--->| module program |
+----^----+                               +--------+-------+
     |                                             |
     +----------{戻り値:stdout(JSON形式}-----------+

引数

引数は以下のようにハンドリングされます。(※v2系の場合)

  • モジュールには、引数としてkey=value形式のパラメータを渡すことができます
  • 引数は、パラメータファイル(ファイル名:args)に書き込まれます
  • Ansibleは、モジュールスクリプト実行時の引数(sys.argv[1])として、このファイル名を指定することで、パラメータファイル名をモジュールに渡します
  • モジュールは、このファイルを実行時に読み込んで、パラメータを利用します

戻り値

JSON形式のデータ構造を標準出力(stdout)に出力することで、モジュールの実行結果をansibleコマンドやansible-playbookコマンドに渡すことができます。

はじめてのAnsibleモジュール

本家のチュートリアルのサンプルモジュール(timetest.py)は、以下のとおりです。このモジュールは、実行環境に対して変更を加えないので、changedパラメータはFalseとして設定しています。

※本家のコードに対してchangedパラメータを追加しています

#!/usr/bin/python

import datetime
import json

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

このサンプルモジュールは、引数は受け取らず、現在時刻を取得して戻り値として返します。この戻り値は、前述の通りJSON形式である必要があるため、json.dumps()を利用して、データ構造をJSON形式に変換して標準出力に出力しています。

このサンプルモジュール(timetest.py)をカレントディレクトリに配置して、ansibleコマンドを実行してみましょう。

% echo "127.0.0.1 ansible_connection=local" | tee hosts
127.0.0.1 ansible_connection=local
% ansible localhost -i hosts -M . -m timetest.py
localhost | SUCCESS => {
    "changed": false,
    "time": "2017-01-19 14:01:30.328960"
}

想定通りの実行結果(現在時刻)が得られました。

モジュールにパラメータを指定する

モジュールに渡されたパラメータは、モジュールと同じディレクトリに“args”というファイル名で配置されます。Ansibleは、モジュール実行時に、このファイル名を最初の引数に指定して実行します。(※v2系の場合)

#!/usr/bin/python
"""
Description:
  Sample module for checking how to handle command-line options.
"""
from __future__ import print_function

import json
import shlex
import sys


def main():
    result = dict()
    result['changed'] = False
    result['message'] = 'Hello, World!'

    with open(sys.argv[1], 'rb') as args_file:
        args_buffer = args_file.read()

    for arg in shlex.split(args_buffer):
        (key, value) = arg.split('=')
        if key == 'name':
            result['message'] = 'Hello, {name:s}!'.format(name=value)

    print(json.dumps(result))


if __name__ == '__main__':
    main()

それでは、以下のサンプルモジュール(paramtest.py)をカレントディレクトリに配置して、ansibleコマンドを実行してみましょう。

% ansible localhost -i hosts -M . -m paramtest.py -a name=Hideki
localhost | SUCCESS => {
    "changed": false,
    "message": "Hello, Hideki!"
}

argsファイルの実際の内容は、以下のようにスペースで区切られた key=value形式となっています。このサンプルモジュールも、実行環境に変更を加えないため、changedパラメータはFalseとしています。

_ansible_version=2.2.1.0 _ansible_selinux_special_fs='['"'"'fuse'"'"', '"'"'nfs'"'"', '"'"'vboxsf'"'"', '"'"'ramfs'"'"']' name=Hideki _ansible_module_name=paramtest.py _ansible_verbosity=0 _ansible_syslog_facility=LOG_USER _ansible_diff=False _ansible_debug=False _ansible_check_mode=False _ansible_no_log=False

ここから、キーであるnameの値を取り出して戻り値にセットしています。

実行環境の状態を変化させる

次に、実行環境を変更するモジュールを書いていましょう。このサンプルモジュール(touchtest.py)は、以下のような仕様とします。

  • nameパラメータに指定したファイル名で空ファイルを作成する(changed: True)
  • nameパラメータに指定したファイル名と同名のファイルが存在していたら何もしない(changed: False)
#!/usr/bin/python
"""
Description:
  Sample module for checking how to change your system.
"""
from __future__ import print_function

import json
import shlex
import os
import sys


def main():
    result = dict()
    result['changed'] = False

    filename = None
    with open(sys.argv[1], 'rb') as args_file:
        args_buffer = args_file.read()

    for arg in shlex.split(args_buffer):
        (key, value) = arg.split('=')
        if key == 'name':
            filename = value

    if not os.path.exists(filename):
        result['changed'] = True
        target_fd = open(filename, 'w')
        target_fd.close()

    result['filename'] = filename
    print(json.dumps(result))


if __name__ == '__main__':
    main()

それでは、以下のサンプルモジュール(touchtest.py)をカレントディレクトリに配置して、ansibleコマンドを実行してみましょう。

まずはカレントディレクトリに存在していないファイル名(hello)を指定します。

% ansible localhost -i hosts -M . -m touchtest.py -a name=hello
localhost | SUCCESS => {
    "changed": true,
    "filename": "hello"
}

作成されたファイル(hello)のステータスを確認してみましょう。

% stat hello
16777220 44124007 -rw-r--r-- 1 saitou staff 0 0 "Jan 19 16:11:25 2017" "Jan 19 16:11:25 2017" "Jan 19 16:11:25 2017" "Jan 19 16:11:25 2017" 4096 0 0 hello

新たに空ファイルが作成されました。この処理では、実行環境に変更を加える(空ファイルが作成される)ため、changedパラメータをTrueに設定しています。

次に、全く同じ作業を実施してみます。

% ansible localhost -i hosts -M . -m touchtest.py -a name=hello
localhost | SUCCESS => {
    "changed": false,
    "filename": "hello"
}

ファイル(hello)のステータスを確認してみましょう。

% stat hello
16777220 44124007 -rw-r--r-- 1 saitou staff 0 0 "Jan 19 16:11:25 2017" "Jan 19 16:11:25 2017" "Jan 19 16:11:25 2017" "Jan 19 16:11:25 2017" 4096 0 0 hello

すでにファイルが存在しているため、os.path.exists(filename)の評価結果がTrueとなり、ファイルに関する操作がスキップされます。この場合は、実行環境が変化しませんから、changedパラメータにFalseをセットしてやります。

このようにして、1つ1つ丁寧にモジュールの中の処理で冪等性を担保してやるわけです。

Ansibleモジュールのお作法にしたがう

Ansibleが推奨しているモジュールの書式にしたがって、touchtest.pyを書き直してみましょう。

モジュールの基本構造

Ansibleでは、モジュールの作成が楽になるよう、さまざまな機能がライブラリとして提供されています。これらを利用すれば、引数のハンドリングや、戻り値のフォーマットなど、モジュールに共通する処理を自身で書く必要がなくなります。

このようなライブラリを利用する場合のモジュール基本構造が、本家のDeveloping Modulesというドキュメントで紹介されています。

from ansible.module_utils.basic import AnsibleModule

def main():
    module = AnsibleModule(
        argument_spec=dict(
            state=dict(default='present', choices=['present', 'absent']),
            name=dict(required=True),
            enabled=dict(required=True, type='bool'),
            something=dict(aliases=['whatever'])
        )
    )

if __name__ == '__main__':
    main()

お作法にしたがってモジュールを書いてみる

それでは、上記の基本構造を利用して、さきほどのtouchtest.pyを書き直してみましょう。ただ書き直すだけではなく、Ansibleのモジュールらしく、いくつか仕様を追加しています。

  • stateパラメータがpresentの場合
    • nameパラメータに指定したファイル名で空ファイルを作成する(changed: True)
    • nameパラメータに指定したファイル名と同名のファイルが存在していたら何もしない(changed: False)
  • stateパラメータがabsentの場合
    • nameパラメータに指定したファイルが存在していれば削除する(changed: True)
    • nameパラメータに指定したファイルだ存在しなければ何もしない(changed: False)

この仕様にしたがって、書き直したサンプルモジュール(myfile.py)は、以下の通りです。

#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Description:
  Sample module for checking how to change your system.
"""

import os

from ansible.module_utils.basic import AnsibleModule


def main():
    module = AnsibleModule(
        argument_spec=dict(
            state=dict(default='present', choices=['present', 'absent']),
            name=dict(required=True)
        )
    )

    state = module.params['state']
    filename = module.params['name']

    changed = False
    if state == 'absent':
        if os.path.exists(filename):
            changed = True
            try:
                os.unlink(filename)
            except OSError as err:
                module.fail_json(msg='{err}'.format(err=err))
        module.exit_json(changed=changed, filename=filename)

    if not os.path.exists(filename):
        changed = True
        try:
            target_fd = open(filename, 'w')
        except IOError as err:
            module.fail_json(msg='{err}'.format(err=err))
        target_fd.close()
    module.exit_json(changed=changed, filename=filename)


if __name__ == '__main__':
    main()

AnsibleModuleクラスのオブジェクトインスタンスを利用することで、このクラスが提供している、引数やJSON形式での戻り値のハンドリングを行ってくれるメソッドなどが利用できるようになります。

まずは、state==presentから。1回目の実行ではファイルが作成されるので、changed: Trueとなり、2回目の実行では、すでにファイルが存在していて新たに作成されることはありませんから、changed: Falseになっています。

% ansible localhost -i hosts -M . -m myfile.py -a "name=hello state=present"
localhost | SUCCESS => {
    "changed": true,
    "filename": "hello"
}
% ansible localhost -i hosts -M . -m myfile.py -a "name=hello state=present"
localhost | SUCCESS => {
    "changed": false,
    "filename": "hello"

続いてstate==absentで実行してみましょう。1回目の実行ではファイルが削除されてchanged: True、2回目の実行ではファイルが存在していませんから、changed: Falseになっています。

% ansible localhost -i hosts -M . -m myfile.py -a "name=hello state=absent"
localhost | SUCCESS => {
    "changed": true,
    "filename": "hello"
}
% ansible localhost -i hosts -M . -m myfile.py -a "name=hello state=absent"
localhost | SUCCESS => {
    "changed": false,
    "filename": "hello"
}

作成や削除に失敗した場合の処理についても、サンプルとして記述してあります。例えばディレクトリに対してstate=absentで削除処理を適用しようとすると、削除処理に失敗して、以下のようなエラーがfail_json()で戻されます。

% file hello
hello: directory
% ansible localhost -i hosts -M . -m myfile.py -a "name=hello state=absent"
localhost | FAILED! => {
    "changed": false,
    "failed": true,
    "msg": "[Errno 1] Operation not permitted: 'hello'"
}

AnsibleModuleクラスの機能(ほんの一部)

サンプルモジュール(myfile.py)では、ほんの一部しか利用していませんが、AnsibleModuleクラスは、モジュール開発を支援するために、さまざまな共通機能を提供してくれます。

以下では、myfile.pyで利用している機能のみを紹介します。

モジュールパラメータ処理: AnsibleModule().params
    module = AnsibleModule(
        argument_spec=dict(
            state=dict(default='present', choices=['present', 'absent']),
            name=dict(required=True)
        )
    )

AnsibleModuleクラスのオブジェクトインスタンス生成時に、argument_specにモジュールパラメータを渡すことで、argsファイルに記述されているモジュールのオプションパラメータを読み込んで辞書型の変数(params)に格納してくれます。

ここでは、statenameという2つのパラメータを取得するように指定していあす。辞書型で指定するオプションパラメータの要件には、以下のようなキーワードを指定できます。

  • aliased: 互換性を確保するのに必要なパラメータの別名を記述したリスト
  • choices: stateパラメータのように"present"や"absent"などのキーワード指定うためのキーワードリスト
  • default: 無指定だった場合のデフォルト値
  • required: 指定必須のパラメータかどうか(True or False)
  • type: int、bool、listなどのような渡されるパラメータ値の型

モジュール内では、以下のように渡されたオプションパラメータを利用することができます。

    state = module.params['state']
    filename = module.params['name']
正常終了: AnsibleModule.exit_json(changed, result)

モジュールが正常終了した場合に、JSON形式でメッセージを標準出力して終了します。

    module.exit_json(changed=changed, result=filename)

モジュールの動作結果として、実行環境に何らかの変更が加わった場合は、changed_にTrueを、変更がなかった場合はFalseを設定してやります。

resultには、必要に応じて利用者に知らせたい情報を設定します。

異常終了: AnsibleModule.fail_json(msg)

モジュールが異常終了した場合に、JSON形式でメッセージを標準出力して終了します。

    module.fail_json(msg='{err}'.format(err=err))

異常終了時に利用者に知らせたい情報を、msgに設定してやります。

冪等性問題

Ansibleは、冪等性が担保されていると良く言われていますが、これもモジュールの作り次第です。

冪等性を実現するために、モジュールの作成者は、AnsibleModuleクラスが提供している共通機能を上手に活用して、冪等性の担保するコードを実装してやる必要があります。

参考情報

OpenStackとAnsible Towerでメリークリスマス

OpenStackとAnsible Towerでメリークリスマス

みなさんメリークリスマス。 この記事は、OpenStack Advent Calendar 2016の25日目の記事です。

OpenStack Advent Calendar 2016 最終日の今日は、OpenStack管理下にインスタンスを作成する際に、Ansible Towerを利用してPlaybookを適用する方法を紹介します。

OpenStackとAnsibleとAnsible Tower

OpenStack

OpenStackは、言わずと知れたクラウド基盤管理システムです。オープンな開発コミュニティ、そして全ての機能を外部から管理可能な、オープンなAPIを持っています。

このAPIを利用することで、従来のオンプレミスな仮想化基盤では難しかった、CI/CDに代表されるツールチェーンのような仕組みをインフラ分野にも適用することが可能となます。いわゆるInfrastructure as Codeを実現するのに、OpenStackが一役買ってくれるという訳ですね。

Ansible

Ansibleは、現代を代表するITオートメーションツールです。コア部分の実体は、単なるコマンドラインツールなのですが、この単純でシンプルな構造が非常に使い勝手が良く、運用の現場での利用が急速に進んでいます。

世の中では利点ばかりが強調されがちですが、シンプルであるがゆえに、Ansibleのコア部分が削ぎ落とした機能がいくつかあります。 実際に運用していると、サーバのオートスケール時の自動構築や、ツールチェーンに組み込みたい場合、そしてシステム監査のような場面で、以下の3点は、なにか対策があるといいのになぁと思ってりしています。

  1. 外部APIを持っていない
  2. コマンドの実行権限管理はOS任せ
  3. Playbookやログなどが分散してしまう

それぞれ、いろいろと考えて対処しているのですが、これを公式に解決してくれるのがAnsible Towerです。

Ansible Tower

Ansible Towerは、Playbookによるジョブ管理システムです。具体的には以下の機能を提供してくれます。現時点では有償ですが、10ノードまで管理できる無償のトライアル版が提供されいますので、今回は、このトライアル版を利用してAnsible TowerとOpenStackの連携方法についてご紹介します。

  1. コールバック機能も含めた外部API機能
  2. Gitなどのソフトウェア構成管理システム上に置いたPlaybookを利用したジョブ管理機能
  3. ジョブ実行ログの集中管理機能
  4. ユーザ・グループレベルでの実行権限管理

インストール手順は、特に難しということはありません。公式ドキュメントや、@ITコチラの記事を参照してみてください。

Tower起動させると、こんな感じのリッチなWebダッシュボードから操作できるようになります。

f:id:pyde:20161225211100p:plain

紹介する構成

testserverをデプロイする際に、Ansible Towerの管理下にあるPlaybookをジョブとして実行する方法を紹介します。

                             172.16.165.0/24
+------+--------------+---------------------+
       |.130          |.66
       |              |
       |              |
 +-----+----+   +-----+-----+
 |  router  |   | OpenStack |
 +-----+----+   +-----------+
       |
       |
       |.1                    192.168.0.0/24
+-----++---------------+--------------------+
      |.15             |
      |                |
      |                |
 +----+----+    +------+------+
 |  tower  |    | testserver  |
 +---------+    +-------------+

ジョブとして実行するPlaybook

playbookは、以下の構成でGitHubに置いて、これをAnsible Towerから利用します。

playbook
├── site.yml
└── web
    ├── files
    │   ├── index.html
    │   └── josug2016.png
    └── tasks
         └── main.yml

playbookで実行するタスク(playbook/web/tasks/main.yml)は、以下の通りです。パッケージ更新後にnginxをインストールし、コンテンツを配置した後で、最後にnginxをリスタートしています。

---
# tasks file for web

- block:
  - name: update all packages
    apt:
      upgrade: yes
      update_cache: yes
  - name: install nginx
    apt:
      name: nginx
      update_cache: no
  - name: copy contents
    copy:
      src: "{{ item }}"
      dest: "/usr/share/nginx/html/{{ item }}"
    with_items:
      - index.html
      - josug2016.png
  - name: restart nginx
    service:
      name: nginx
      state: restarted
  become: yes
  become_user: root

Ansible TowerでのOpenStack連携設定

それでは、Ansible Towerを操作して、実際にOpenStackとの連携をみてみましょう。

Ansible TowerからOpenStack環境を利用するための設定項目は4つあります。

  1. プロジェクトの作成
  2. Credentialの作成
  3. Dynamic Inventoryを利用するInventory Groupの作成
  4. Playbookを実行するJob Templateの作成

1. プロジェクトの作成

今回のAdvent Calendar用に1つプロジェクトを作成します。

SCM URLには、GitHub上に配置してあるPlaybookのパスを指定しています。

f:id:pyde:20161225211052p:plain

2. Credentialの作成

利用するCredentialは2つあります。

1つは、Dynamic InventoryからOpenStackのAPIを利用するためのopenstackと、

f:id:pyde:20161225211102p:plain

そしてもう1つは、起動したインスタンスにログインしてPlaybookを適用するためのubuntuです。

f:id:pyde:20161225211104p:plain

3. Inventory Groupの作成

OpenStack環境で管理されている仮想マシン群を、Ansibleから操作するためには Dynamic Inventory を利用するのが常套手段です。当然ながらAnsible Towerからも Dynamic Inventory が利用できます。

ここでは、すでに作成済みのCredential(openstack)を利用して、Dynamic Inventory(webservice)を作成しています。

4. Job Templateの作成

ubuntuとopenstackという2つのCredentialとInventoryを指定して、Job Templateを作成します。更に、管理対象ホストからジョブ起動させるトリガーを引けるように、Callbackを許可します。

f:id:pyde:20161225211057p:plain

このCallback URLに、操作対象ホストからcurlなどでアクセスすることにより、ジョブをキックすることができます。

curl --data "host_config_key=設定キー" https://192.168.0.241:443/api/v1/job_templates/15/callback/ --insecure

OpenStack管理下に仮想マシンインスタンスを起動

openstackコマンドで、インスタンスを起動させる際に、user-dataとして実行するスクリプト(userdata.sh)は、以下の通りです。

#!/bin/bash -v

curl --data "host_config_key=08e4d419bebc422a2bfacbd4bfd469fb" https://192.168.0.15:443/api/v1/job_templates/15/callback/ --insecure

#
# [EOF]
#

実際に、このuser-dataを利用してインスタンスを起動します。

openstack --os-cloud=josug server create \
--flavor m1.small \
--image trusty-server-cloudimg-amd64 \
--key-name ぼくの鍵 \
--nic net-id=ぼくのネットワークID \
--security-group ぼくのSecGroup \
--user-data ./userdata.sh \
testserver

起動時のTowerとの連携は以下の通りです。

  1. boot => OpenStack APIを利用して、Computeノード上にインスタンス(testserver)を起動する
  2. callback => 起動プロセスの最後に実行されるスクリプト(userdata.sh)からAnsible TowerのCallback URLにアクセスしてジョブをキックする
  3. playbook => Ansible Towerにジョブとして登録されているPlaybookを実行する
client               OpenStack               Tower             testserver(compute)
  +                      +                     +                      +
  |      1.boot          |                     |                      |
  +---------------------->                     |                      |
  |                      |                     |                      |
  |                      |                     |                      |
  |                      |                     |        1.boot        |
  |                      +-------------------------------------------->
  |                      |                     |                      |
  |                      |                     |                      |
  |                      |                     |       2.callback     |
  |                      |                     <----------------------+
  |                      |                     |                      |
  |                      |                     |                      |
  |                      |                     |       3.playbook     |
  |                      |                     +---------------------->
  |                      |                     |                      |
  |                      |                     |                      |
  +                      +                     +                      +

さらに進んだ利用方法

今回は、openstackコマンドのuser-dataオプションから、Callback URLキックするスクリプトを利用しましたが、こちらの記事のように、Heatのテンプレートからも同様にuser-dataに指定することで、操作対象ホストからCallbackでPlaybookをキックできます。オートスケール時の初期設定にも利用できて、さらに便利に使えますね。

クリスマスプレゼント間に合わず

Ansible Towerは、現在のところオープンソースではありません。しかし、Ansible WorksをRed Hatが買収されて以降、Red Hatのポリシーに従って、オープンソース化に向けた努力を続けているそうです。

きっと来年のクリスマスまでには、Ansible Towerがオープンソースソフトウェアとして広く利用できるに違いない!と期待を込めて、このカレンダー記事を書きました。

それでは皆さん、メリークリスマス:-)

cryptographyをpipインストールする際の fatal error: 'openssl/opensslv.h' file not found 対策メモ

OSX環境(10.11.6 Beta(15G12a))で、cryptographyをpipでインストールしようとした際に遭遇したエラーに対する対策メモ。

いまおもえばpastebinにでも貼っておいたほうがよかった。

問題

brewでインストールしたOpenSSLのヘッダファイル(opensslv.h)を見つけてくれず、コンパイル時にエラーとなる。

% pip install cryptography
Collecting cryptography
  Using cached cryptography-1.4.tar.gz
<...>
  building '_openssl' extension
  creating build/temp.macosx-10.11-intel-2.7/build
  creating build/temp.macosx-10.11-intel-2.7/build/temp.macosx-10.11-intel-2.7
  cc -fno-strict-aliasing -fno-common -dynamic -arch i386 -arch x86_64 -g -Os -pipe -fno-common -fno-strict-aliasing -fwrapv -DENABLE_DTRACE -DMACOSX -DNDEBUG -Wall -Wstrict-prototypes -Wshorten-64-to-32 -DNDEBUG -g -fwrapv -Os -Wall -Wstrict-prototypes -DENABLE_DTRACE -arch i386 -arch x86_64 -pipe -I/System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7 -c build/temp.macosx-10.11-intel-2.7/_openssl.c -o build/temp.macosx-10.11-intel-2.7/build/temp.macosx-10.11-intel-2.7/_openssl.o
  build/temp.macosx-10.11-intel-2.7/_openssl.c:423:10: fatal error: 'openssl/opensslv.h' file not found
  #include <openssl/opensslv.h>
           ^
  1 error generated.
  error: command 'cc' failed with exit status 1

  ----------------------------------------
  Failed building wheel for cryptography
Failed to build cryptography
Installing collected packages: cryptography
  Running setup.py install for cryptography
    Complete output from command /Users/saitou/venv/2.7.10/ansible/bin/python -c "import setuptools, tokenize;__file__='/private/var/folders/kk/ftd09wdd0sz27nbm8zwzjsdh0000gn/T/pip-build-u8oOQQ/cryptography/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /var/folders/kk/ftd09wdd0sz27nbm8zwzjsdh0000gn/T/pip-d3jDDM-record/install-record.txt --single-version-externally-managed --compile --install-headers /Users/saitou/venv/2.7.10/ansible/include/site/python2.7/cryptography:

<...>
    building '_openssl' extension
    cc -fno-strict-aliasing -fno-common -dynamic -arch i386 -arch x86_64 -g -Os -pipe -fno-common -fno-strict-aliasing -fwrapv -DENABLE_DTRACE -DMACOSX -DNDEBUG -Wall -Wstrict-prototypes -Wshorten-64-to-32 -DNDEBUG -g -fwrapv -Os -Wall -Wstrict-prototypes -DENABLE_DTRACE -arch i386 -arch x86_64 -pipe -I/System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7 -c build/temp.macosx-10.11-intel-2.7/_openssl.c -o build/temp.macosx-10.11-intel-2.7/build/temp.macosx-10.11-intel-2.7/_openssl.o
    build/temp.macosx-10.11-intel-2.7/_openssl.c:423:10: fatal error: 'openssl/opensslv.h' file not found
    #include <openssl/opensslv.h>
             ^
    1 error generated.
    error: command 'cc' failed with exit status 1

    ----------------------------------------

<...>

対策

本家のHomebrewの項に書いてあった...

% brew install openssl
% env CRYPTOGRAPHY_OSX_NO_LINK_FLAGS=1 LDFLAGS="$(brew --prefix openssl)/lib/libssl.a $(brew --prefix openssl)/lib/libcrypto.a" CFLAGS="-I$(brew --prefix openssl)/include" pip install cryptography

恐らく一週間後には覚えてなさそうなのでメモしておく。

きょうのdevstack用local.conf - 2016.5.13時点でのmaster向け

Environment

VMware FusionのGuestOSにUbuntu16.04をインストールし、その上にdevstackでOpenStack開発環境を構築する。

  • GuestOS: ubuntu 16.04lts
  • VMware Fusion 8.1.1
  • SPEC: vCPU x 2 / MEM 8GB / HDD 100GB / Network VMNET12,13

注意)GuestOSのメモリは少なくとも4GBは確保しておく。それ以下のサイズだとOOM Killerが発動し、mysqldあたりからkillされていく...

インストールされるコンポーネント

コアコンポーネント

  • cinder
  • glance
  • keystone
  • neutron
  • nova
  • swift

その他のBigTentコンポーネント

  • barbican
  • ceilometer
  • designate
  • heat
  • magnum
  • murano

local.conf

  • 管理用NIC: ens33
  • サービス用NIC: ens34
  • PASSWORD,VERSION,PUBLIC_INTERFACEは環境に応じて変更する
  • 2回目以降はOFFLINE=True,RECLONE=Falseで実行すれば時間短縮!
[[local|localrc]]
#OFFLINE=True
RECLONE=True

HOST_IP=10.0.12.3
SERVICE_HOST=$HOST_IP
MYSQL_HOST=$HOST_IP
RABBIT_HOST=$HOST_IP
GLANCE_HOSTPORT=$HOST_IP:9292

PASSWORD=changeme
ADMIN_PASSWORD=$PASSWORD
DATABASE_PASSWORD=$PASSWORD
RABBIT_PASSWORD=$PASSWORD
SERVICE_PASSWORD=$PASSWORD
SWIFT_HASH=JRBj7ukxMG4tckek

VERSION=master
CEILOMETER_BRANCH=$VERSION
CINDER_BRANCH=$VERSION
GLANCE_BRANCH=$VERSION
KEYSTONE_BRANCH=$VERSION
HEAT_BRANCH=$VERSION
HEAT_CFNTOOLS_BRANCH=$VERSION
HEAT_TEMPLATES_BRANCH=$VERSION
HORIZON_BRANCH=$VERSION
NEUTRON_BRANCH=$VERSION
NOVA_BRANCH=$VERSION
SWIFT_BRANCH=$VERSION

# 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
LOGDAYS=1
LOG_COLOR=False

# Neutron settings
disable_service n-net
ENABLED_SERVICES+=,q-svc,q-dhcp,q-meta,q-agt,q-l3,q-lbaasv2,q-fwaas,q-vpn,q-qos,q-flavors,octavia,o-cw,o-hk,o-hm,o-api
# Ceilometer settings
ENABLED_SERVICES+=,ceilometer-acompute,ceilometer-acentral,ceilometer-collector,ceilometer-api
# Swift settings
ENABLED_SERVICES+=,s-proxy,s-object,s-container,s-account
# Heat settings
ENABLED_SERVICES+=,heat,h-api,h-api-cfn,h-api-cw,h-eng
# Ceilometer settings
enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer
# Magnum settings
enable_plugin magnum https://git.openstack.org/openstack/magnum $VERSION
enable_plugin magnum-ui https://git.openstack.org/openstack/magnum-ui $VERSION
# Barbican settings
enable_plugin barbican https://git.openstack.org/openstack/barbican $VERSION
# Designate settings
enable_plugin designate https://git.openstack.org/openstack/designate $VERSION
ENABLED_SERVICES+=,designate,designate-central,designate-api,designate-pool-manager,designate-zone-manager,designate-mdns
# Murano settings
MURANO_APPS=io.murano.apps.apache.Tomcat,io.murano.apps.Guacamole
enable_plugin murano https://git.openstack.org/openstack/murano $VERSION
ENABLED_SERVICES+=,murano-cfapi,g-glare

## Neutron options
Q_USE_SECGROUP=True
FLOATING_RANGE="10.0.13.0/24"
FIXED_RANGE="192.168.0.0/24"
NETWORK_GATEWAY="192.168.0.1"
Q_FLOATING_ALLOCATION_POOL=start=10.0.13.129,end=10.0.13.254
PUBLIC_NETWORK_GATEWAY="10.0.13.2"
Q_L3_ENABLED=True
PUBLIC_INTERFACE=ens34

# Open vSwitch provider networking configuration
Q_USE_PROVIDERNET_FOR_PUBLIC=True
OVS_PHYSICAL_BRIDGE=br-ex
PUBLIC_BRIDGE=br-ex
OVS_BRIDGE_MAPPINGS=public:br-ex

#
# [EOF]
#

OpenStackとAnsibleと私

OpenStack Advent Calendar 2015とAnsible Advent Calendar 2015の12月25日の記事です。

沖縄でハンズオンをやってきました

12/14-18で開催されたOkinawa Open Days 2015で、OpenStackとAnsibleとDockerを組み合わせたハンズオンセミナーをやってきました。

OpenStackとAnsibleのAdvent Calendarの最終日としてふさわしいかどうかわかりませんが、ハンズオンに向けての準備で得られた知見を、ここにメモしておきたいと思います。

"OpenStack"と"Ansible v2"

Ansibleは標準でOpenStackを操作するためのモジュールを持っています。 このモジュールを利用すれば、OpenStackが提供するAPIを利用して、仮想マシンインスタンス、ネットワーク、ボリュームなどの仮想リソースの管理をAnsibleのPlaybookを利用して実施することができます。 また、OpenStack管理下にある仮想マシンインスタンス群の管理を容易にするために、ダイナミックインベントリも提供しています。 このOpenStack向けに提供される標準モジュールとダイナミックインベントリは、v2で大きく進化しており、v1向けのモジュールよりも簡単便利に使えるようになりました。

Ansible v2向けモジュールの進化

v2向けのOpenStack用モジュールは、github上にある公式リポジトリから入手可能です。

従来のv1向けのモジュールやダイナミックインベントリは、OpenStackの各コンポーネントが提供するクライアントライブラリ(python-novaclientやpython-neutronclientなど)を直接利用していましたが、v2向けのモジュールは、shadeライブラリを利用するよう全面的に刷新されました。

v1向けOpenStack用モジュールの特徴

例えば、nova-computeモジュールでは、python-novaclientを直接利用していました。 OpenStackの各コンポーネントが提供するクライアントモジュール(pyhon-*client)を直接利用しており、OpenStack環境へのログイン情報をパラメータとして指定する必要がありました。 さらに、OpenStackのクライアントモジュールはコンポーネント毎に認証方式が微妙に違っており、この差異はAnsibleのモジュール側の実装で吸収していました。

例) nova_computeモジュール

+--------------+    +-------------------+         +-----------+
| nova_compute +----+ python-novaclient +==(API)=>+ openstack |
+--------------+    +-------------------+         +-----------+

例) quantum_networkモジュール

+-----------------+    +----------------------+         +-----------+
| neutron_network +----+ python-neutronclient +==(API)=>+ openstack |
+-----------------+    +----------------------+         +-----------+

v2向けOpenStack用モジュールで改善された点

v2向けのモジュールでは、shadeライブラリを利用しています。shadeはOpenStackクライアントライブラリ(python-*client)の上に一枚被さる形で各クライアントライブラリの認証部分など共通に利用する部分の差異を吸収して抽象化したレイヤーを、上位のプログラムに対して提供してくれます。 さらに、v1時代のモジュールのように、OpenStackへの接続情報をPlaybookに書く必要はなく、外部の設定ファイルに複数のOpenStack環境の認証情報を記述して実行時に選択することで、接続先を切り替えるも可能です。

+--------------+    +-------+    +----------------+         +-----------+
| os_* modules +----+ shade +----+ python-*client +==(API)=>+ openstack |
+--------------+    +---+---+    +----------------+         +-----------+
                        |
               +--------+---------+    +-------------------------------------+
               | os-client-config +----+ $HOME/.config/openstack/clouds.yaml |
               +------------------+    +-------------------------------------+

shadeが利用しているos-client-configについては、OSS とクラウドの狭間で: 環境変数1つで OpenStack 環境を切り替える os-client-config で、もときさんが詳しく紹介してくれています。必読です。

v1向けモジュールのClientクラスインスタンスの初期化

nova_computeモジュールと、quantum_networkモジュールの例を以下に紹介します。ログイン情報をモジュールパラメータとして指定する必要があります。 さらにpython-novaclientとpython-neutronclientのClientクラスインスタンスは、生成する際の引数が微妙に違います。

nova_compute.py

...
 41: options:
 42:    login_username:
 43:     description:
 44:        - login username to authenticate to keystone
 45:     required: true
 45:     default: admin
 46:   login_password:
 47:     description:
 48:        - Password of login user
 49:     required: true
 50:     default: 'yes'
 51:   login_tenant_name:
 52:     description:
 53:        - The tenant name of the login user
 54:     required: true
 55:     default: 'yes'
...
534: def main():
...
572:     nova = nova_client.Client(module.params['login_username'],
573:                               module.params['login_password'],
574:                               module.params['login_tenant_name'],
575:                               module.params['auth_url'],
576:                               region_name=module.params['region_name'],
577:                               service_type='compute')
...

quantum_network.py

...
 37: options:
 38:   login_username:
 39:     description:
 40:        - login username to authenticate to keystone
 41:     required: true
 42:     default: admin
 43:   login_password:
 44:     description:
 45:        - Password of login user
 46:     required: true
 47:     default: 'yes'
 48:   login_tenant_name:
 49:     description:
 50:        - The tenant name of the login user
 51:     required: true
 52:     default: 'yes'
...
130: def _get_ksclient(module, kwargs):
131:    try:
132:         kclient = ksclient.Client(username=kwargs.get('login_username'),
133:                                  password=kwargs.get('login_password'),
134:                                  tenant_name=kwargs.   get('login_tenant_name'),
135:                                  auth_url=kwargs.get('auth_url'))
...

v2向けモジュールのClientクラスインスタンスの初期化

v2向けモジュールでは、認証情報をパラメータとして直接モジュールに渡す必要はありません。shadeがos-client-configを利用して設定ファイルを取得して利用してくれるからです。 ちなみに、何もしなくてもopenrcなどによって反映された環境変数(OS_USERNAMEなど)を接続情報として利用してくれます。なにこれ便利すぎる!

v2向けのOpenStack操作用モジュール(os_*モジュール)は、認証情報をパラメータとしてセットする必要がありません。さらに、内部的にはnovaを操作する場合(例:os_nova_flavorモジュール)も、neutronを操作する場合(例:os_portモジュール)も同一の方法で利用することができます。

os_nova_flavor.py

...
201:         cloud = shade.operator_cloud(**module.params)
...

os_port.py

...
336:         cloud = shade.openstack_cloud(**module.params)
...

設定ファイル($HOME/.config/openstack/clouds.yaml)

以下の例では、libertyとdevstackの2つの環境を定義しており、os_*モジュールのcloudパラメータに環境名(ここではlibertyまたはdevstack)を指定することで、接続先となるOpenStack環境を切り替えることが可能です。 また、cloudパラメータに何もしていしなかった場合は、openrcなどで反映されている環境変数(OS_USERNAMEなど)が利用されます。

clouds:
  liberty:
    auth:
      auth_url: http://192.168.0.1:5000
      username: saitou
      password: changeme
      project_domain_id: default
      user_domain_id: default
      project_name: SYSTEM00
    identity_api_version: '3'
  devstack:
    auth:
      auth_url: http://172.16.31.1:35357
      username: admin
      password: changeme
      project_domain_id: default
      user_domain_id: default
      project_name: admin
    identity_api_version: '3'
    region_name: RegionOne

利用方法

この例では、os_*モジュールにはcloudパラメータを指定していません。 この状態だと、ansible-playbookコマンドの実行環境で反映されている環境変数(OS_USERNAMEなど)が接続情報として利用されます。

  1. 公開鍵を登録する(os_keypairモジュール)
  2. セキュリティグループを作成する(os_security_groupモジュール)
  3. セキュリティグループにルールを登録する(os_security_group_ruleモジュール)
  4. 仮想マシンインスタンスを起動する(os_serverモジュール)
  5. 仮想マシンインスタンスにフローティングIPアドレスを割り当てる(quantum_floating_ipモジュール)

比較のために利用しているquantum_floating_ipモジュールはv1向けのモジュールです。v1向けのモジュールは、実行時にOpenStack環境への接続情報がパラメータとして必要となるため、わざわざ環境変数から取得するようvarsセクションでlookup()しています。

Playbook: create_instance.yml
---
- hosts: localhost

  vars:
    ansible_python_interpreter: /home/centos/handson/bin/python
    os_auth_url: "{{ lookup('env','OS_AUTH_URL') }}"
    os_username: "{{ lookup('env','OS_USERNAME') }}"
    os_password: "{{ lookup('env','OS_PASSWORD') }}"
    os_project_name: "{{ lookup('env','OS_TENANT_NAME') }}"
    os_region_name: "{{ lookup('env','OS_REGION_NAME') }}"
    keypairs:
      - name: "step-server"
        public_key_file: "/home/centos/.ssh/id_rsa.pub"
    secgroups:
      - name: "eplite"
        desc: "secgroup for eplite"
        rules:
          - protocol: "icmp"
            port_range_min: -1
            port_range_max: -1
            remote_ip_prefix: "0.0.0.0/0"
          - protocol: "tcp"
            port_range_min: 22
            port_range_max: 22
            remote_ip_prefix: "0.0.0.0/0"
          - protocol: "tcp"
            port_range_min: 80
            port_range_max: 80
            remote_ip_prefix: "0.0.0.0/0"
          - protocol: "tcp"
            port_range_min: 3306
            port_range_max: 3306
            remote_ip_prefix: "0.0.0.0/0"
    servers:
      - name: "{{ hostname }}"
        key_name: "step-server"
        flavor: "m1.small"
        image: "Docker01"
        secgroups:
          - "eplite"
        nics:
          - net-name: "work-net"
        auto_ip: no
        ext_net: "ext-net_1214"
        int_net: "work-net"

  tasks:
    - name: import keypairs
      os_keypair:
        state=present
        name="{{ item.name }}"
        public_key_file="{{ item.public_key_file }}"
      with_items: keypairs
    - name: create security group
      os_security_group:
        state=present
        name="{{ item.name }}"
        description="{{ item.desc}}"
      with_items: secgroups
    - name: add rules to secgroup
      os_security_group_rule:
        state=present
        security_group="{{ item[0].name }}"
        protocol="{{ item[1].protocol }}"
        port_range_min="{{ item[1].port_range_min }}"
        port_range_max="{{ item[1].port_range_max }}"
        remote_ip_prefix="{{ item[1].remote_ip_prefix }}"
      with_subelements:
        - secgroups
        - rules
    - name: create servers
      os_server:
        state: present
        timeout: 200
        name: "{{ item.name }}"
        key_name: "{{ item.key_name }}"
        flavor: "{{ item.flavor }}"
        image: "{{ item.image }}"
        security_groups: "{{ item.secgroups }}"
        nics: "{{ item.nics }}"
        auto_ip: "{{ item.auto_ip }}"
      with_items: servers
    - name: create and assign floating_ip to server
      quantum_floating_ip:
        state=present
        login_username="{{ os_username }}"
        login_password="{{ os_password }}"
        login_tenant_name="{{ os_project_name }}"
        network_name="{{ item.ext_net }}"
        instance_name="{{ item.name }}"
        internal_network_name="{{ item.int_net }}"
      with_items: servers
Playbookを実行してみる

OpenStackモジュールを利用する前に、まずはshadeのインストールをしなければなりません。ここではpipを利用してvirtualenv環境にインストールしていますが、みなさんの環境にあわせてインストールしておいてください。

$ cd ~ && virtualenv develop
(develop)$ pip install shade functools32

-eオプションで指定したホスト名で、仮想マシンインスタンスを作成します。

$ source openrc
$ ansible-playbook -i ansible_hosts -e "hostname=web00" create_instance.yml

OpenStack管理機能を提供するv2向けモジュール

仮想マシンインスタンスを起動するという目的を達成するために、必要最低限の機能を提供していたv1向けモジュールに対して、v2向けのモジュールは大幅に機能追加されています。 これらv2向けモジュールのほとんど(すべて検証したわけではないけれど)は、Ansible v1.9.4でも正常動作するため、みなさんのv1環境でも恩恵を受けることができます。

是非試してみてください。

v1向けOpenStack用モジュールリスト

  • glance_image.py
  • keystone_user.py
  • nova_compute.py
  • nova_keypair.py
  • quantum_floating_ip_associate.py
  • quantum_floating_ip.py
  • quantum_network.py
  • quantum_router_gateway.py
  • quantum_router_interface.py
  • quantum_router.py
  • quantum_subnet.py

v2向けOpenStack用モジュールリスト

  • os_auth.py
  • os_client_config.py
  • os_floating_ip.py
  • os_image_facts.py
  • os_image.py
  • os_ironic_node.py
  • os_ironic.py
  • os_keypair.py
  • os_network.py
  • os_networks_facts.py
  • os_nova_flavor.py
  • os_object.py
  • os_port.py
  • os_router.py
  • os_security_group.py
  • os_security_group_rule.py
  • os_server_actions.py
  • os_server_facts.py
  • os_server.py
  • os_server_volume.py
  • os_subnet.py
  • os_subnets_facts.py
  • os_user_group.py
  • os_user.py
  • os_volume.py

刷新されたダイナミックインベントリプログラム

Ansibleのソースコードには、ダイナミックインベントリのサンプルプログラムが含まれています。v2のソースコードにも、もちろんOpenStack環境用のダイナミックインベントリプログラムが含まれているのですが、今回はこのプログラムも全面刷新され、shadeを利用するようになりました。

v1時代のOpenStack環境用ダイナミックインベントリプログラムは、あくまでもサンプル的な内容でしたが、v2は違います。一度取得した情報を一定時間キャッシュして再利用することで、問い合わせ回数を削減するなどの工夫がされており、そのまま特に手を加えることなく利用できるレベルの仕上がりで、足りない機能を自身で追加するにしても、コードがシンプルで改修もしやすくなっています。

実際に利用してみる

まずは、上記サイトからダイナミックインベントリプログラムと設定ファイルを入手します。

※以降、すでにshadeはインストール済みであるものと仮定します。

テスト環境

ここでは以下のような環境を例に、ダイナミックインベントリプログラムの利用例を紹介します。

  • OpenStackコントローラ: 10.0.0.2
  • ユーザ: saitou
  • パスワード: changeme
  • ドメイン: default
  • プロジェクト名: OOD2015
  • 環境名: liberty

設定ファイルの取得と編集

(develop)$ sudo mkdir /etc/ansible/ (develop)$ cd /etc/ansible (develop)$ sudo wget https://raw.githubusercontent.com/ansible/ansible/devel/contrib/inventory/openstack.yml

/etc/ansible/openstack.yml
clouds:
  liberty:
    auth:
      auth_url: http://10.0.0.2:5000
      username: saitou
      password: changeme
      project_domain_id: default
      user_domain_id: default
      project_name: OOD2015
    identity_api_version: '3'

ダイナミックインベントリプログラムの取得とテスト実行

公式サイトからダイナミックインベントリプログラムを取得して、実行権を付与しておきます。

(develop)$ cd ~
(develop)$ wget https://raw.githubusercontent.com/ansible/ansible/devel/contrib /inventory/openstack.py
(develop)$ chmod +x openstack.py

早速テストしてみます。

(develop)$ ./openstack.py --list

実行結果は以下の通り。ちゃんと _meta も使ってくれています。素敵!

{
  "": [
    "9da2a418-61bd-4118-8eac-1e3d4d639c73", 
    "9da2a418-61bd-4118-8eac-1e3d4d639c73", 
    "ea5bd227-3b9c-4a43-80ea-8feb34eb6eb5", 
    "ea5bd227-3b9c-4a43-80ea-8feb34eb6eb5", 
    "f40e9f60-2f9c-4405-99dc-50f37b988ef6", 
    "f40e9f60-2f9c-4405-99dc-50f37b988ef6"
  ], 
  "_meta": {
    "hostvars": {
      "9da2a418-61bd-4118-8eac-1e3d4d639c73": {
        "ansible_ssh_host": "10.0.1.143", 
        "openstack": {
          "HUMAN_ID": true, 
          "NAME_ATTR": "name", 
          "OS-DCF:diskConfig": "AUTO", 
          "OS-EXT-AZ:availability_zone": "nova", 
          "OS-EXT-STS:power_state": 4, 
          "OS-EXT-STS:task_state": null, 
          "OS-EXT-STS:vm_state": "stopped", 
          "OS-SRV-USG:launched_at": "2015-12-06T07:17:22.000000", 
          "OS-SRV-USG:terminated_at": null, 
          "accessIPv4": "10.0.1.143", 
          "accessIPv6": ""
...以下略...

これをansibleから利用してみます。まずはallで全インスタンスpingモジュールを適用してみる。

(develop)$ ansible all -i ./openstack.py -m ping -u centos
ea5bd227-3b9c-4a43-80ea-8feb34eb6eb5 | success >> {
    "changed": false,
    "ping": "pong"
}

9da2a418-61bd-4118-8eac-1e3d4d639c73 | success >> {
    "changed": false,
    "ping": "pong"
}

f40e9f60-2f9c-4405-99dc-50f37b988ef6 | success >> {
    "changed": false,
    "ping": "pong"
}

Ansibleのダイナミックインベントリは、ホスト名やフレーバなどの情報を元に自動でグループを作ってくれるようなので、これもテストしてみます。

(develop)$ ansible step -i ./openstack.py -m ping -u centos
9da2a418-61bd-4118-8eac-1e3d4d639c73 | success >> {
    "changed": false,
    "ping": "pong"
}

(develop)$ ansible docker7 -i ./openstack.py -m ping -u centos
ea5bd227-3b9c-4a43-80ea-8feb34eb6eb5 | success >> {
    "changed": false,
    "ping": "pong"
}

ホスト名でのグループ指定もできてる!便利! 実はshadeはAnsibleのダイナミックインベントリを処理するため(としか思えない)に利用できるOpenStackInventoryクラスを持っています。 Ansible v2に含まれるダイナミックインベントリは、このクラスを利用しているため、v1用と比較して、コード自体が非常に簡潔になっておりカスタマイズもしやすくなっていますので、カスタマイズベースとしても使う価値ありです!

まとめ

Ansible v2では、OpenStack環境との親和性が飛躍的に高まりました。 OpenStackモジュールは刷新されており、shadeのおかげでシンプルでわかりやすい構造となっています。 OpenStack環境でAnsibleを利用しているひとは、切り替える価値が十分ありますので、是非試してみてください。

とは言っても

肝心のv2ってまだリリースされてないですよねぇ サンタはリリース情報をもって来てくれないかもしれないですが、みなさん素敵なクリスマスを:-)