IBM Cloud Collection for Ansible を使って、IBM Cloudを操作してみよう

# はじめに

IBM Cloud用 Ansibleモジュール であるところの IBM Cloud Collection for Ansible がリリースされました。そこで、IBM Cloud Collection for Ansible を使って、Ansible の Playbookから IBM Cloud の Virtual Server for VPC を作ってみます。

# 説明

## IBM Cloud Collection for Ansible とは

IBM Cloud Collection for Ansible とは 『IBM CloudをAnsibleで操作するためのAnsibleモジュール』です。

2020年2月10日に以下のBlogで発表されました。
https://www.ibm.com/cloud/blog/announcements/ibm-cloud-collection-for-ansible

実際のコードは以下のIBM CloudのGithubで公開されており、自由に使うことができます。
https://github.com/IBM-Cloud/ansible-collection-ibm

サンプルの Ansibleコード（Playbook）が公開されており、今回はこちらを試してみたいと思います。
https://github.com/IBM-Cloud/ansible-collection-ibm/tree/master/examples/simple-vm-ssh

このサンプルコードは以下のリソースを作ってくれるようです。

* VPC (ibm_is_vpc)
* Subnet (ibm_is_subnet)
* VSI (ibm_is_instance)
* Floating IP Address (ibm_is_floating_ip)
* Security Group Rule (ibm_is_security_group_rule)

## 何がうれしいの？

IBM Cloud の構築・設定を Ansible Playbook を使って行えるようになります。
Ansible Playbook は YAML という言語で記述されており、Infrastructure as Codeな管理が可能になり、ものすごくざっくりいうと、Cloud Nativeの階段を一段登った状態になります。

Infrastructure as Code
https://ja.wikipedia.org/wiki/Infrastructure_as_Code

インフラをコードで管理できるようになると、インフラCICDという次の階段も見えてきますね。
https://www.slideshare.net/ToruMakabe/cicd-108416105
## IBM Cloud とは

Amazon Web Service や Google Cloud Platformの ような IBMのクラウドサービスです。

>IBM Cloud はオープンテクノロジーを基本に構成されたIBMのクラウドサービスです。
「アプリケーション」、「AI」、「データ」を支えるプラットフォームとして、SaaS、PaaS、IaaSなど様々なサービスが利用できます。本書ではインフラストラクチャーのサービスを中心に記載しています。

(出典：[技術よりな人が最初に読むIBM Cloud柔らか層本](https://ibm.box.com/v/ibmcloud-yawaraka))

>IBM Cloud（アイビーエムクラウド）はIBMのクラウドサービスのブランド名。2017年11月に従来のBluemixブランドが、IBM Cloudブランド名称に変更された。

(出典:[Wikipedia](https://ja.wikipedia.org/wiki/IBM_Cloud))

## Ansible とは

AnsibleはサーバやPC上で動く構成管理ツールです。

>Ansible（アンシブル）は、レッドハットが開発するオープンソースの構成管理ツールである。サーバを立ち上げる際、あらかじめ用意した設定ファイルに従って、ソフトウェアのインストールや設定を自動的に実行する事が出来る。特に大規模なコンピュータ・クラスターを構築する時に、時間の短縮やミスの削減に有用である。構成管理に加え、オーケストレーションやソフトウェアデプロイメントの機能を持つ。

（出典：[Wikipedia](https://ja.wikipedia.org/wiki/Ansible_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2)) ）

>Ansibleは、オープンソースプロジェクトで開発されているPython製の構成管理ツールです。YAML形式で書かれた設定ファイルに従って、サーバやネットワーク機器などの設定を自動的に実行できます。
>
>現在では構成管理にとどまらずその守備範囲を広げ、アプリケーションのデプロイやオーケストレーションなどにも利用できるようになりました。本稿では、自動化ツールとしてのAnsibleにフォーカスを当てて解説したいと思います。
>
>なお、AnsibleはRed Hatがサポートを行っています。2012年に誕生したAnsibleは近年早いペースで開発が進んでおり、数か月おきに新しいリリースを出すようになりました。現在のバージョンは2.7で、近いうちに2.8のリリースを予定しています。

（出典：[Ansibleでできることを中の人が教えます - インストールと実行〜EC2へのNginx投入までを学ぼう](https://employment.en-japan.com/engineerhub/entry/2019/04/12/103000))

# 環境準備

[Readme.md](https://github.com/IBM-Cloud/ansible-collection-ibm)や各モジュールの[ドキュメント](https://github.com/IBM-Cloud/ansible-collection-ibm/blob/master/docs/ibm_is_vpc.rst)を読むと、Ansible を実行する端末には、IBM Cloud Collection for Ansible のインストール以外に以下の前提が必要になることがわかります。

* Python3
* RedHat Ansible 2.8+
* IBM-Cloud terraform-provider-ibm v1.2.0
* Terraform v0.12.20

またIBM Cloudを操作するために必要なAPI KEYの取得はこちらを参考になさってください。（サンプルコードの実行には、Defaultのリソースグループに対する権限が必要になります。）
[IBM Cloud 資料/ユーザーの API キーの管理](https://cloud.ibm.com/docs/iam?topic=iam-userapikey)

## Dockerコンテナ準備

環境をいちいち準備するのは面倒なので、上記の前提が整ったDockerコンテナを作るためのDockerfileを用意しました。このDockerfileからツール入りのコンテナを生成・起動し、そのコンテナの中からAnsible Playbookを実行します。

お手元の端末に Docker と git を導入した上で、下の手順を進めてください。以降の手順は、筆者の端末であるMacを前提に進めます。WindowsやLinuxな方は適宜読み替えてください。

```shell-session
$ git clone git@github.com:kentarok/ibmcloud_collection4ansible.git
$ cd ibmcloud_collection4ansible
$ ls -l
Dockerfile      README.md       my_env_file.txt
```

`my_env_file.txt` の中に IBM CloudのAPI KEYを記載してください

```shell-session
$ cat my_env_file.txt
IC_API_KEY=xxxxxxxxxxxxxxxxxx
```

Dockerfileを元にコンテナを生成・起動し、コンテナの内部に入ります。
`docker container run` 実行時に、 `--env-file` で `my_env_file.txt` を指定しています。

```shell-session
$ docker build -t ansible . -f Dockerfile
(中略)
Successfully built 479c85388b28
Successfully tagged ansible:latest

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ansible             latest              479c85388b28        8 minutes ago       1.26GB
python              3.6                 971c66f6a27f        7 days ago          914MB

$ docker container run -d -it -v $(pwd):/ansible --env-file ./my_env_file.txt --name ansible ansible
dbb219b5e48f0b819e3ef036393045914bdd5e4a67085c5d9ea50c0092c5ad6c

$ docker container exec -it ansible /bin/bash
#
```

## Dockerコンテナ確認

コンテナの中の環境を確認します。

```shell-session
# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
root@dbb219b5e48f:/# which python
/usr/local/bin/python

# python --version
Python 3.6.10

# ansible --version
ansible 2.9.4
  config file = /ansible/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.6/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 3.6.10 (default, Feb  2 2020, 09:39:59) [GCC 8.3.0]

# terraform -v
Terraform v0.12.20
```

envファイルで設定した環境変数もちゃんと設定されています。

```shell-session
# env | grep IC
IC_API_KEY=xxxxxxxxxxxxxxxxxxxxxx
```

# Ansible Playbook 実行

コンテナ内の `/ansible-collection-ibm` 配下に `git@github.com:IBM-Cloud/ansible-collection-ibm.git` が `git clone` されています。

今回試すサンプルコードは、`/ansible-collection-ibm/examples/simple-vm-ssh/create.yml` です。内容はこちらです。

create.yml
https://github.com/IBM-Cloud/ansible-collection-ibm/blob/master/examples/simple-vm-ssh/create.yml

二箇所修正します。

## SSH鍵の生成

`create.yml` の中で以下を行っています。

* SSH公開鍵をIBM Cloudに登録
* Virtual Server for VPC に IBM Cloudに登録したSSH公開鍵を登録
* Virtual Server for VPC に 対応するSSH秘密鍵を使ってSSHログインテスト

そのため、SSH鍵を生成し、 `create.yml` に公開鍵のほうを記載します。

```shell-session
# ssh-keygen -t rsa -b 4096
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
(略)

# cat /root/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCsj7P7SmFRq0mFyY6eqgSAW8inki9hrd4SMGe5pLVJaiMdz0x3QMxX6HLWOIgDIlcziI4sbhxjfYkQIraYZm/VXbqNOHDn4GZ0B0hoe95k6xg51TDCDcgTZ6gQxHIsNMHg7zskvcxdB10zqNEzN3MVW14si6yLR4LLgkLUq5ORlHFtGPFG9wUU2GaLAjYiqrHPfz6whfXm7rsKcJKIQ//yVgTy0edyzQMCAGI6Io9EUVxcJCAEUdnyfqK2Dn40Ikfb2AsuRNYk4vJkEwAjJvwQjEw9s4DEEsWlnh/dZyA/7z1w5ebzeT/P2KL8ZNMpnBd3j0dJVGB1g8qHcrWonQLcfonUX0rjCKw7wy2dgo5PNC/2pBgps3E1jTmlCJRVD/HzHcu3A8DSkZLwFiiBv3GCsLkqfJjQtKR3dWxC3WVf4FqKb8De5yQmYVcBCW9nJRfNjQKTTWPe/1r0tGUN7J6D3TJNk9QAzARPmJaSEGVY6VfwCm1I1P3q6bjRfzO2WfCIi0hRzM141sF6vfnu3qzd1VtAAX5T1hUIkOFLZMAE+hJXPhTvvbK6K2NsMycX1FVqJ9FuvgXS7nAvRmztw4npruVrLpKnodGoNkd+wNjkEmcQjLJgypAUlki4JwQxRLhxKolc4b1jz1HNt1rSOXK02vCJXaiikLUXH67PwDFRNQ== root@dbb219b5e48f
```

`create.yml` の 8行目に記載します。

```yaml:create.yml
---
- name: Create IBM Cloud VPC VSI
  hosts: localhost
  vars:
    name_prefix: ansible-demo
    vsi_image: ibm-ubuntu-18-04-64-ppc64le-minimal-for-vsi
    vsi_profile: cp2-2x4
    ssh_public_key: xxxx  # <- here
    ipv4_cidr_block: 10.240.128.0/24
    zone: us-south-3
```

## Security Groupの設定

`create.yml` の 104行目に以下のような記載があります。
こちらは、 Virtual Server for VPC に Attachする Security Groupを作成するPlayになります。

```yaml:create.yml
    - name: Configure Security Group Rule to open SSH on the VSI
      ibm_is_security_group_rule:
        state: available
        group: "{{ vpc.default_security_group }}"
        direction: inbound
        remote: 0.0.0.0/0
        tcp:
          - port_max: 22
            port_min: 22
```

`remote: 0.0.0.0/0` とある通り、世界中から Port22 = SSH を受け付ける設定になっているので、こちらを適切なIPに変更します。自分のIPが何になるかは [確認くん](https://www.ugtop.com/spill.shtml)あたりを使って確認するとよいでしょう。

```yaml:create.yml
    - name: Configure Security Group Rule to open SSH on the VSI
      ibm_is_security_group_rule:
        state: available
        group: "{{ vpc.default_security_group }}"
        direction: inbound
        remote: 59.138.xxx.yyy # <- here
        tcp:
          - port_max: 22
            port_min: 22
```

## 実行

さていよいよ Playbookの実行です。

```shell-session
# ansible-playbook create.yml
[WARNING]: Unable to parse /ansible/inventory/defaults as an inventory source

[WARNING]: No inventory was parsed, only implicit localhost is available

[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'


PLAY [Create IBM Cloud VPC VSI] ***********************************************************************************

TASK [Gathering Facts] ********************************************************************************************
ok: [localhost]

TASK [Configure VPC] **********************************************************************************************
changed: [localhost]

TASK [Save VPC as fact] *******************************************************************************************
ok: [localhost]

TASK [Configure VPC Subnet] ***************************************************************************************
changed: [localhost]

TASK [Save VPC Subnet as fact] ************************************************************************************
ok: [localhost]

TASK [Configure SSH Key] ******************************************************************************************
changed: [localhost]

TASK [Save SSH Key as fact] ***************************************************************************************
ok: [localhost]

TASK [Retrieve image list] ****************************************************************************************
ok: [localhost]

TASK [Set VM image name/id dictionary fact] ***********************************************************************
ok: [localhost]

TASK [Configure VSI] **********************************************************************************************
changed: [localhost]

TASK [Save VSI as fact] *******************************************************************************************
ok: [localhost]

TASK [Configure Floating IP Address] ******************************************************************************
changed: [localhost]

TASK [Save Floating IP as fact] ***********************************************************************************
ok: [localhost]

TASK [Print Floating IP Address] **********************************************************************************
ok: [localhost] => {
    "msg": "IP Address: 52.117.0.45"
}

TASK [Configure Security Group Rule to open SSH on the VSI] *******************************************************
changed: [localhost]

TASK [Add VSI to Ansible inventory] *******************************************************************************
changed: [localhost]

PLAY [Check Ansible connection to new DEMO VSI] *******************************************************************

TASK [Wait for VSI to become reachable over SSH] ******************************************************************
ok: [52.117.0.45]

PLAY [Check Ansible connection to new DEMO VSI] *******************************************************************

TASK [Gathering Facts] ********************************************************************************************
ok: [52.117.0.45]

TASK [Collect OS information] *************************************************************************************
changed: [52.117.0.45]

TASK [Print OS information] ***************************************************************************************
ok: [52.117.0.45] => {
    "os_info.stdout_lines": [
        "NAME=\"Ubuntu\"",
        "VERSION=\"18.04.2 LTS (Bionic Beaver)\"",
        "ID=ubuntu",
        "ID_LIKE=debian",
        "PRETTY_NAME=\"Ubuntu 18.04.2 LTS\"",
        "VERSION_ID=\"18.04\"",
        "HOME_URL=\"https://www.ubuntu.com/\"",
        "SUPPORT_URL=\"https://help.ubuntu.com/\"",
        "BUG_REPORT_URL=\"https://bugs.launchpad.net/ubuntu/\"",
        "PRIVACY_POLICY_URL=\"https://www.ubuntu.com/legal/terms-and-policies/privacy-policy\"",
        "VERSION_CODENAME=bionic",
        "UBUNTU_CODENAME=bionic"
    ]
}

PLAY RECAP *******************************************************************************************************
52.117.0.45                : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
localhost                  : ok=16   changed=7    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

Playbookの最後に、生成したVirtual Server for VPC にSSHログインして、OS情報を引っこ抜いてきてくれています。

IBM Cloud Portal の https://cloud.ibm.com/vpc-ext/vpcLayout あたりで、今作成した VPC などのリソースを表示してくれますので、確認します。

<img width="719" alt="Capture 2020-02-13 13.2
9.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54436/3c1b7d95-85e7-9c41-1ae5-a906e246b588.png">

ちゃんと出来ていますね。とっても簡単です！


# 少し残念なところ

リリース直後ということもあるからでしょうか、若干残念なところも散見されます。

## 冪等性がない

`create.yml` を一回目実行し、VPCなどのリソースを作りおわったあと、冪等性の確認を、、、と思い、もう一度 `create.yml` を実行したところ、以下のような表示が、、、。

```shell-session
# ansible-playbook create.yml
PLAY [Create IBM Cloud VPC VSI] ***********************************************************************************

TASK [Gathering Facts] ********************************************************************************************
ok: [localhost]

TASK [Configure VPC] **********************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "", "rc": 1, 
"resource": {"_name": "ansible-demo-vpc", "_type": "ibm_is_vpc", "target": 
"ibm_is_vpc.ansible-demo-vpc"}, "stderr": "\nError: {\"errors\":
[{\"code\":\"validation_unique_failed\",\"message\":\"Provided Name 
(ansible-demo-vpc) is not unique\",\"target\":
{\"name\":\"name\",\"type\":\"field\"}}],\"trace\":\"4a01ba51-471b-49e6-
9779-354cd10398b5\"}\n\n  on ibm_is_vpc_ansible-demo-vpc.tf line 1, in 
resource \"ibm_is_vpc\" \"ansible-demo-vpc\":\n   1: resource ibm_is_vpc 
\"ansible-demo-vpc\" {\n\n\n", "stderr_lines": ["", "Error: {\"errors\":
[{\"code\":\"validation_unique_failed\",\"message\":\"Provided Name 
(ansible-demo-vpc) is not unique\",\"target\":
{\"name\":\"name\",\"type\":\"field\"}}],\"trace\":\"4a01ba51-471b-49e6-
9779-354cd10398b5\"}", "", "  on ibm_is_vpc_ansible-demo-vpc.tf line 1, in 
resource \"ibm_is_vpc\" \"ansible-demo-vpc\":", "   1: resource ibm_is_vpc 
\"ansible-demo-vpc\" {", "", ""], "stdout": "ibm_is_vpc.ansible-demo-vpc: 
Creating...\n", "stdout_lines": ["ibm_is_vpc.ansible-demo-vpc: 
Creating..."]}

（見づらいので適当に改行しています）

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

`"Provided Name (ansible-demo-vpc) is not unique"` って言われているので、同じ名前のVPCをもう一個作ろうとしていると思われて、エラーになっているようです。。。冪等性。。。

Playに対して`when`句を使って自分で実装すればある程度は冪等性を実装できますが、それじゃカスタムモジュール使う意味がほとんどありません。。。このあたりは今後に期待したいところです。

> 「This collection comes alongside the release of the IBM Cloud Provider for Terraform for 1.2.」 (https://www.ibm.com/cloud/blog/announcements/ibm-cloud-collection-for-ansible) とのことなので、IBM Cloud Provider for Terraformのほうが改善しないと冪等性の担保ってのが難しいのかもしれませんが。

## Ansible Galaxy に登録されていない

こちらの [issue](https://github.com/IBM-Cloud/ansible-collection-ibm/issues/1)にもあがっていますが、現在のところ、Githubのみでの公開です。IBMさんは、せっかく Ansible Galaxyに[公式リポジトリ](https://galaxy.ansible.com/ibm)も持ってるんですから、こちらに登録してくれればインストールが簡単になるのになぁ。。。

### [2020-02-28追記]
昨日確認したところ、IBMではなくてIBMCLOUDとしてGalaxy上に登録されていました。

https://galaxy.ansible.com/ibmcloud/ansible_collection

Ansible Collectionという比較的新しい Ansible モジュール配布の仕組みを使うようです。実際に以下のコマンドで導入可能なことを確認しました。

```shell-session
# ansible-galaxy collection install ibmcloud.ansible_collection
Process install dependency map
Starting collection install process
Installing 'ibmcloud.ansible_collection:1.2.1' to '/root/.ansible/collections/ansible_collections/ibmcloud/ansible_collection'
```

ただし、今日時点で 各モジュール内から共通モジュールを呼びだす際の記述が間違っているので、ちゃんと動きません！ たとえば、[ibm_is_images_info](https://github.com/IBM-Cloud/ansible-collection-ibm/blob/master/plugins/modules/ibm_is_images_info.py)モジュールだと、464行目が間違ってるので動きません。

```python
# 誤： import ansible.module_utils.ibmcloud as ibmcloud
↓
# 正： import ansible_collections.ibmcloud.ansible_collection.plugins.module_utils.ibmcloud as ibmcloud
``` 

早く修正されるとよいですね。

# リソースの削除

さて、最後にサンプルコード `create.yml` で作成したリソースを削除します。
予め用意されている `destroy.yml` がこのままだとうまく動かないので、以下のように改修します。

(1) destroy.yml への記載追加

```yaml:destroy.yml
- name: Destroy IBM Cloud VPC VSI
  hosts: localhost
  vars_files:   # <- 追加
   - ./vars.yml # <- 追加

  tasks:
    - name: Release Floating IP
      ibm_is_floating_ip:
```

(2) 各リソースのIDを記載した変数ファイルの追加

各リソースのIDは、IBM Cloud Portalから手作業でもってきます。だるいですが。（Portal行くなら手で消せ、とか言わない）

```yaml:vars.yml
---
fip:
  id: r006-ae80cf6f-d5cd-44d0-960b-73616b654f58
vsi:
  id: 0737_925c9f40-e0f9-480b-98a1-aee9c77a4db1
ssh_key:
  id: r006-8d77517d-74d1-4b1b-8502-7ca5756a31fb
subnet:
  id: 0737-b7cb766e-c223-4e58-aa17-0e9b21d6a4c1
vpc:
  id: r006-edc0ca50-2d20-47c9-94dc-2327be85e237
```

準備ができたら実行します。

```shell-session
# ansible-playbook destroy.yml

PLAY [Destroy IBM Cloud VPC VSI] **********************************************************************************

TASK [Gathering Facts] ********************************************************************************************
ok: [localhost]

TASK [Release Floating IP] ****************************************************************************************
ok: [localhost]

TASK [Remove VSI] *************************************************************************************************
ok: [localhost]

TASK [Remove SSH Key] *********************************************************************************************
ok: [localhost]

TASK [Remove VPC Subnet] ******************************************************************************************
ok: [localhost]

TASK [Remove VPC] ************************************************************************************************
ok: [localhost]

PLAY RECAP *******************************************************************************************************
localhost                  : ok=6    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

一応、IBM Cloud Portal上でも、ちゃんと消えたか確認するのを忘れずに！

# まとめ

冪等性が担保されていないという些かと言うには大きすぎる残念ポイントはあるものの、公開直後でこんなに簡単に使えるのはすごいと思います。

特に面倒だった、IBM Cloud Portal上の Portalユーザー発行にも使えるんじゃないかなー。IBM Cloud上での自動化が一層はかどりますね。いろいろ試してみたいと思います。

Happy Automation!