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

## はじめに

参考にさせていただいた[記事](https://qiita.com/kentarok/items/f303d0cb3bf156b4833d)にもありますように、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 は、[こちら](https://github.com/IBM-Cloud/terraform-provider-ibm)を参考に導入します。

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

```
$ ansible-galaxy collection install ibmcloud.ibmcollection
```

## ansible.cfgの設定

[この Closed issue](https://github.com/IBM-Cloud/ansible-collection-ibm/issues/5) にあるように、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とあるので、おそらくこれでよいかと・・・）

```text:inventories/test/hosts
localhost ansible_python_interpreter=/usr/bin/python3
```

### 変数：APIキー

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

```yaml: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` で確認して、以下のように変数ファイルに定義します。

```yaml:group_vars/all/resource.yml
resource_group_id: xxxxxxxxxxxxx
```

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

```yaml: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冒頭で、必要な定義を行います。詳細はコメントを参照ください。

```yaml: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で保管します。

```yaml: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にすると、サービスが削除されます。

```yaml: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モジュールの実行結果を表示させます。

```yaml: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のコンソールでリソース一覧を見ると、作成されていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307720/460f027f-c675-116d-acaa-6909d237cbbc.png)

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

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

## まとめ

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

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

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

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

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

+ [IBM Cloud Collection for Ansible を使って、IBM Cloudを操作してみよう](https://qiita.com/kentarok/items/f303d0cb3bf156b4833d)
+ [IBM Cloud Ansible Collections](https://github.com/IBM-Cloud/ansible-collection-ibm)
+ [IBM Cloud Terraform Provider](https://github.com/IBM-Cloud/terraform-provider-ibm)