IBM Cloud Collection for Ansible を用いた IBM Cloud の構成管理(まずは手始めに)


はじめに

参考にさせていただいた記事にもありますように、IBM CloudをAnsibleで操作するためのAnsibleモジュール、IBM Cloud Ansible Collectionsがリリースされました。

数多くのモジュールがある反面、サンプルがIaaS関連のごく一部しか存在しないのが現状です。

そこで、IBM Cloudに数多くあるサービス(SaaS)の作成を通して、このツールでIBM Cloudサービスの構成管理ができないか検証してみたので、メモ書きとして残そうと思います。

環境

当方の環境は以下のとおりです。

  • VirtualBox (+ vagrant)
  • CentOS 7.7
  • ansible 2.9.6
  • Python 3.6.8 (OS同梱のPython 2.7.5はそのまま) + jinja2
  • Terraform v0.12.24
  • IBM Cloud Terraform Provider v1.3.0
  • IBM Cloud Ansible Collections 1.3.0

Ansible、Python3、jinja2は、これらのコマンドで導入します。

$ sudo yum install python3
$ sudo pip3 install "ansible>=2.9.2"
$ sudo pip3 install jinja2

Terraform と Terraform Provider は、こちらを参考に導入します。

Ansible Collectionsの導入は、このコマンドで導入します。

$ ansible-galaxy collection install ibmcloud.ibmcollection

ansible.cfgの設定

この Closed issue にあるように、ansible.cfg(例:./ansible.cfg) の [default] セクションに2行定義を追加します。

ansible.cfg
[defaults]
library         = ~/.ansible/plugins/modules:/usr/share/ansible/plugins/modules:~/.ansible/collections/ansible_collectio
ns/ibmcloud/ibmcollection/plugins/modules
module_utils    = ~/.ansible/plugins/module_utils:/usr/share/ansible/plugins/module_utils:~/.ansible/collections/ansible
_collections/ibmcloud/ibmcollection/plugins/module_utils

インベントリファイルと変数の準備

インベントリファイル

インベントリファイル(例:inventories/test/hosts)に以下のように定義し、Ansible Collection実行時はPython3が使われるようにします。(Ansible Collectionの前提がPython3とあるので、おそらくこれでよいかと・・・)

inventories/test/hosts
localhost ansible_python_interpreter=/usr/bin/python3

変数:APIキー

IBM CloudのAPIキーを記載する変数ファイルを用意します。例えば group_vars/all/id.yml などに以下のように記載します。(そして本来はAnsible Vaultを用いて、本ファイルを暗号化しておくことになります)

group_vars/all/id.yml
ic_api_key: xxxxxxxxxxxxxxxxxxxxx

変数:その他

APIキー以外を定義する変数ファイル、例えば group_vars/all/resource.yml に必要な定義を追加していきます。

まずサービスを配置するリソースグループを定義します。ここでは、resource_group_name ではなく、resource_group_id を指定する必要があります。使用するリソースグループのIDを、ibmcloudコマンド ibmcloud resource groups で確認して、以下のように変数ファイルに定義します。

group_vars/all/resource.yml
resource_group_id: xxxxxxxxxxxxx

続いて、同じファイルに Db2 lite オーダーに必要な情報を定義します。詳細はコメントを参照ください。

group_vars/all/resource.yml
db2:
  instance_name: db2-test           # 作成されるサービスの名前
  service: dashdb-for-transactions  # 作成するサービス
  plan: free                        # サービスのプラン
  location: eu-gb                   # サービスのロケーション
  tags: tag1,tag2                   # タグ (任意)
  state: available                  # いわゆる state (present は指定不可・・・absentを指定すると既存のインスタンスを削除する。省略時は available)

Playbookの作成

いよいよPlaybookです。参考にさせていただいた記事にもあるように、残念ながらIBM Cloud Ansible Collectionsにはまだ冪等性が一部考慮されていないようです。ibm_resource_instanceモジュールも、idフィールドを指定せずに何度もPlaybookを実行すると、同じ名前のサービスが幾つも幾つも作成されます。(サービス名でユニーク性を保っていないとはいえ、エラーにならないので辛いです)

そのため、ibm_resource_instance_infoモジュールで、まず同名のサービスがあるかどうかチェックし、ibm_resource_instanceモジュールで作成、更新あるいは削除を実施する流れとします。

まずPlaybook冒頭で、必要な定義を行います。詳細はコメントを参照ください。

ibm_resource_instance.yml
---
- name: IBM Cloud resource instance
  hosts: localhost       # Python3を利用するため (インベントリファイルで指定)
  connection: local      # ローカルで実行
  gather_facts: false    # 時短
  become: false          # Playbook実行ユーザーの ~/.local にある jinja2 を利用するためsudoしない
  environment:
    IC_API_KEY: '{{ ic_api_key }}'   # IBM Cloud APIキーを環境変数に設定
  collections:
    - ibmcloud.ibmcollection         # IBM Cloud Ansible Collectionsを利用

続いてタスク定義です。1つ目のタスクで同名のサービスが、指定したリソースグループID及びロケーションに存在するかどうかチェックします。ここでもし存在しないと、このタスクがfailしてしまい、処理が中断します。それでは困るので、結果failでも、指定した名前のサービスが存在しない、というエラーメッセージの時だけは通すよう、failed_whenで指定します。存在する場合は、そのままokで通りますが、その結果を次のタスクで使用するので、registerで保管します。

ibm_resource_instance.yml
  tasks:
    - name: Check resource instance {{ db2.instance_name }}
      ibm_resource_instance_info:
        name: '{{ db2.instance_name }}'
        resource_group_id: '{{ resource_group_id }}'
        location: '{{ db2.location }}'
      register: check_result
      failed_when:
        - check_result.failed
        - "'Error: No resource instance found with name' not in check_result.stderr"

次のタスクでサービスを作成、更新あるいは削除します。前タスクのチェックでもし存在していた場合は、そのサービスのidをここで指定していますので、既存のサービスを更新または削除することが出来ます。例えば、変数tagsを変更して、Playbookを再実行すると、タグの内容が変更されますし、stateをabsentにすると、サービスが削除されます。

ibm_resource_instance.yml
    - name: The resource instance {{ db2.instance_name }} is provisioned
      ibm_resource_instance:
        name: '{{ db2.instance_name }}'
        resource_group_id: '{{ resource_group_id }}'
        service: '{{ db2.service }}'
        plan: '{{ db2.plan }}'
        location: '{{ db2.location }}'
     # check_result.resource.idが無ければ、このフィールド指定そのものをなかったことにします。tags, stateも同様。
        id: '{{ check_result.resource.id | default(omit) }}'
        tags: '{{ db2.tags | default(omit) }}'
        state: '{{ db2.state | default(omit) }}'
      register: provision_result

ちなみに、このPlaybookの例でlocationを変えても、前タスクで重複無しと判断され、idがセットされないので、同じ名前のサービスがもう1つ出来ます。逆に、そのチェックをせずに、例えばus-southにすでにあるサービスのIDを使って、locationをeu-gbに変えてこのタスクを実行すると、なんとまずus-southのサービスを削除してから、eu-gbに改めてサービスを作成する動きをします...(そりゃそうかもしれませんが...ちょっと焦ります)

基本的に以上ですが、Playbook実行の結果何が起きたのか不安になるので、最後にibm_resource_instanceモジュールの実行結果を表示させます。

ibm_resource_instance.yml
    - name: Show resource instance information
      debug:
        var: provision_result

Playbook実行

上記を、例えば ibm_resource_instance.yml に記述し、実行します。

$ ansible-playbook -i inventories/test ibm_resource_instance.yml 

PLAY [IBM Cloud resource instance] *************************************************************************************

TASK [Check resource instance db2-test] ********************************************************************************
ok: [localhost]

TASK [The resource instance db2-test is provisioned] *******************************************************************
changed: [localhost]

TASK [Show resource instance information] ******************************************************************************
ok: [localhost] => {
    "provision_result": {
        "changed": true,
        "failed": false,
        "rc": 0,
        "resource": {
            "crn": "略",
            "guid": "略",
            "id": "略",
            "location": "eu-gb",
            "name": "db2-test",
            "parameters": null,
            "plan": "free",
            "resource_controller_url": "https://cloud.ibm.com/services/",
            "resource_crn": "略",
            "resource_group_id": "略",
            "resource_group_name": "",
            "resource_name": "db2-test",
            "resource_status": "active",
            "service": "dashdb-for-transactions",
            "service_endpoints": null,
            "status": "active",
            "tags": [
                "tag1",
                "tag2"
            ],
            "timeouts": null
        },
        "stderr": "",
        "stderr_lines": [],
        "stdout": "ibm_resource_instance.db2-test: Creating...\nibm_resource_instance.db2-test: Still creating... [10s elapsed]\nibm_resource_instance.db2-test: Still creating... [20s elapsed]\nibm_resource_instance.db2-test: Creation complete after 23s [id=略]\n\nApply complete! Resources: 1 added, 0 changed, 0 destroyed.\n",
        "stdout_lines": [
            "ibm_resource_instance.db2-test: Creating...",
            "ibm_resource_instance.db2-test: Still creating... [10s elapsed]",
            "ibm_resource_instance.db2-test: Still creating... [20s elapsed]",
            "ibm_resource_instance.db2-test: Creation complete after 23s [id=略]",
            "",
            "Apply complete! Resources: 1 added, 0 changed, 0 destroyed."
        ]
    }
}

PLAY RECAP *************************************************************************************************************
localhost                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

その後、IBM Cloudのコンソールでリソース一覧を見ると、作成されていることがわかります。

もう一度同じPlaybookを実行すると、changedは無しですべてokになります。(この瞬間のこの結果を得るためにPlaybookを書いているようなもの。。。)

後始末としては、変数ファイルの state: availablestate: absent に変更した後、Playbookを実行すると、作成したインスタンスを削除してくれます。

まとめ

サンプルが無い中、かなり試行錯誤しました。また、冪等性を自分で担保しなければいけないので、画面操作なら数分で出来ることに数日悩んでしまいました・・・

ただ、一度仕組みを確率させれば、あとは変数ファイルに必要なリソースをどんどん追加していけば、このPlaybookで使用しているサービス構成を一元管理・・・出来る・・・ようになるには、まだまだ改善の余地はありそうです。

  • リソースグループのIDを事前に調べるとかダサい
    • リソースグループIDを調べられるモジュールはある
      • ただ、defaultとそうでないリソースグループで動きが違ってて、また作り込み発生の匂いが・・・
  • 有償のサービスで色々試したいが、その度にチャリンチャリン課金されてはたまらない・・・もっとサンプル出して欲しいですね

という、構成管理に向けてまだまだ取っ掛かりの手始めでした。

参考にさせていただいた記事