Functions（サーバーレス）で時刻をトリガーとしてVSIを自動起動／停止する（Classic VSI編）

[この記事](https://qiita.com/takeyan/items/49da6b10661d12bce601)のClassic VSI編です。IBM CloudのFunctions（サーバーレス）を使って、Classic InfrastructureのVSIを起動・停止する仕組みを作ってみます。
Gen1 VSI編は主にダッシュボードのGUI操作で作業を進めましたが、今回はibmcloud CLIの操作も織り交ぜながら進めます。プログラムはzipファイル形式でパッケージングして、ibmcloud CLIでデプロイします。

作業の流れは次のとおりです。言語はPythonを使用します。

1. VSIを起動／停止するプログラムの作成
2. プログラム（アクション）をパッケージングしてサーバーレス環境にデプロイ
3. クラシック・インフラストラクチャー API呼び出し用ユーザー作成＆APIキー作成
4. クラシック・インフラストラクチャーのアクセス権限の設定
5. パラメータの設定とアクションの動作確認
6. 時刻トリガーの登録と、アクションとの関連付け
7. トリガーの動作の確認

参照：
https://cloud.ibm.com/docs/openwhisk?topic=cloud-functions-prep#prep_python
https://cloud.ibm.com/docs/openwhisk?topic=cloud-functions-namespaces
https://cloud.ibm.com/docs/openwhisk?topic=cloud-functions-actions
https://cloud.ibm.com/docs/openwhisk?topic=cloud-functions-triggers

---

## 1. VSIを起動／停止するプログラムの作成
[SoftLayer API Python Client](https://github.com/softlayer/softlayer-python)を使ってクラシックインフラストラクチャーのVSIを起動／停止するプログラムを、[こちら](https://sldn.softlayer.com/python/power_on_guest.py/)も参考にしつつ作成しました。
Functionsにデプロイする前提として、ファイル名は\_\_main\_\_.pyでなければなりません。その中でFunctionsのアクションとして実行するロジックをmain関数として実装しました。

```python:__main__.py
import SoftLayer

def main(dict):

    API_USERNAME = dict['user']
    API_KEY = dict['apikey']
    POWER = dict['power']
    VSI = dict['vsi']
    client = SoftLayer.create_client_from_env(username=API_USERNAME, api_key=API_KEY)

    try:
        virtualGuests = client['SoftLayer_Account'].getVirtualGuests()

    except SoftLayer.SoftLayerAPIError as e:
        print("Unable to retrieve virtual guest. "
              % (e.faultCode, e.faultString))

    vsiFound = False
    for virtualGuest in virtualGuests:
        if virtualGuest['hostname'] == VSI:
            vsiFound = True
            try:
               if POWER == 'OFF':
                    virtualMachines = client['SoftLayer_Virtual_Guest'].powerOff(id=virtualGuest['id'])
               else:
                    virtualMachines = client['SoftLayer_Virtual_Guest'].powerOn(id=virtualGuest['id'])

            except SoftLayer.SoftLayerAPIError as e:
                 vsiFound = e

    return { 'VSI' : VSI, 'Action' : POWER,  'Action_Performed' : vsiFound, 'result' : virtualMachines }
```


## 2. プログラム（アクション）をパッケージングしてサーバーレス環境にデプロイ
プログラムではSoftLayerというパッケージをimportしていますが、これはFunctionsのPython実行環境には組み込まれていません。プログラム独自の依存パッケージ（ここではSoftLayer）は、プログラムと一緒にデプロイする必要があります。
SoftLayerパッケージの入手も含め、手順は次のようになります。

2-1. ibmcloudにログインし、環境を準備しておきます。

```bash
$ ibmcloud login
$ ibmcloud target -g default -r us-south                  # Functionsが属するリソースグループとリージョンを指定
$ ibmcloud fn namespace create takeyang-fn-dal            # アクションのデプロイ先となるネームスペースの作成
$ ibmcloud fn property set --namespace takeyang-fn-dal    # 上で作成したネームスペースをアクションのデプロイ先として指定
```


2-2. ダウンロードするパッケージを記述するファイル（pipのrequirements.txtファイル）を作成します。ダウンロードするパッケージはsoftlayerひとつだけです。

```text:requirements.txt
softlayer
```

2-3. Functions提供のDockerイメージをダウンロードしておきます。

```bash
$ docker pull ibmfunctions/action-python-v3.7
```

2-4. requirements.txtに指定したパッケージ（softlayer）をダウンロードして、virtualenvディレクトリ以下に配置します。

```bash
$ docker run --rm -v "$PWD:/tmp" ibmfunctions/action-python-v3.7 bash -c "cd /tmp && virtualenv virtualenv && source virtualenv/bin/activate && pip install -r requirements.txt"
```

2-5. virtualenvと\_\_main\_\_.pyをzipファイルにパッケージングします。

```bash
$ zip -r takeyan-vsi-classic-power.zip virtualenv __main__.py
```

2-6. Functionsのアクションを作成します。同時にパッケージ（zipファイル）をデプロイします。

```bash
$ ibmcloud fn action create takeyan-vsi-classic-power-on takeyan-vsi-classic-power.zip --kind python:3.7
$ ibmcloud fn action create takeyan-vsi-classic-power-off takeyan-vsi-classic-power.zip --kind python:3.7
```

ここではひとつのパッケージ（zipファイル）をVSI起動用のアクション（takeyan-vsi-classic-power-on）と停止用のアクション（takeyan-vsi-classic-power-off）の両方にデプロイしています。起動と停止はアクションに渡すパラメータで区別することにしました。

## 3. クラシック・インフラストラクチャー API呼び出し用ユーザー作成＆APIキー作成
クラシックインフラストラクチャーはIAMのサービスIDが利用できません。クラシックインフラストラクチャーAPIキーは、必ずユーザーIDに紐づけて作成することになります。またクラシックインフラストラクチャーのAPIキーは、そのユーザーしか作成・照会・削除することができません。これは管理者にとっては扱いが少し面倒です。
この制約の回避策として、VPNユーザーを作成してそこにクラシックインフラストラクチャーAPIキーを紐付けるという方法が考えられます。VPNユーザーはクラシックインフラストラクチャーにSSL VPNでログインするためのユーザーIDですが、次の特徴があるのでIAMのサービスIDに相当するAPI呼び出し専用IDとして好都合です。

* 管理者は任意のVPNユーザーのユーザーIDを作成できる。パスワードを秘匿しておけば、このユーザーIDがAPI呼び出し以外の用途（ダッシュボード操作等）に利用されることはない。
* 管理者は任意のVPNユーザーのクラシックインフラストラクチャーAPIキーを作成することができる

以下にVPNユーザー作成からAPIキー作成までの流れを紹介します。

3-1. まずクラシックインフラストラクチャーの管理者権限を持つユーザーのダッシュボードで、「管理」→「アクセス（IAM）」→「ユーザー」と遷移して、「VPN専用ユーザーの追加」をクリックします。
![VPNユーザー追加開始.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/2ea41ad4-cb07-5799-dd1d-677455f5e277.png)

3-2. VPNユーザーを定義します。ここでVPNユーザー名がクラシックインフラストラクチャーAPIキーのユーザーIDになります。
![VPNユーザー定義.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/d46f8947-b55d-daff-715a-88c7ae0070ec.png)

3-3. 作成したVPNユーザーの「ユーザーの詳細」の画面の一番下に「クラシックインフラストラクチャー APIキーの作成」ボタンがあるので、これをクリックします。
![VPNユーザー詳細１.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/ca51d264-c494-a01f-6c2c-2874aa46a7d4.png)
![VPNユーザー詳細２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/1df2ac6b-bd98-94b6-10b4-16272703c2fb.png)

3-4. 作成されたAPIキーをメモしておきます。
![APIキー作製.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/def6293b-9982-f7ed-8e77-887bd52da6f0.png)

クラシックインフラストラクチャーのAPIキーは、他のIAMのAPIキーと違って作成後に何度でも照会できます。
![APIキー照会.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/0ca37ce5-6132-52c7-0701-9a590fd5f4f5.png)
![APIキー表示.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/a99e80ce-b847-7cdf-c45d-43805f598c1a.png)

VPNユーザーではなく、通常のユーザーIDに紐づけてクラシックインフラストラクチャーAPIキーを作成する場合には、そのユーザーでダッシュボードにログインして、「IBM Cloud APIキー」の画面で「クラシックインフラストラクチャー APIキーの作成」をクリックします。
既にAPIキー作成済の場合はこのボタンは表示されません。APIキー一覧の中にクラシックインフラストラクチャーAPIキーがあるはずなので、詳細を表示してユーザーIDとAPIキーを確認します。
一般のユーザーIDの場合は、クラシックインフラストラクチャーAPIのAPIユーザー名がユーザーIDと異なるので、APIキーだけでなくAPIユーザー名も必ず確認しておきます。


## 4. クラシック・インフラストラクチャーのアクセス権限の設定
管理者のダッシュボードで「管理」→「アクセス（IAM）」→「ユーザー」→「｛ユーザーID｝の管理」→「クラシック・インフラストラクチャー」と遷移して、「許可」「デバイス」「VPNサブネット」のうちの「許可」の画面でアクセス権限を設定します。

許可セットで「基本ユーザー」を選択して「設定」をクリックすると、「アカウント」「サービス」「デバイス」「ネットワーク」の４つのカテゴリーでそれぞれ相応のアクションが選択されます。
クラシックインフラストラクチャーAPIでVSIを起動・停止するだけなら「デバイス」以外の権限は不要なので解除しておきます。
![アクセス権限許可.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/72ae0c79-6bc0-4f20-a516-9d09ad97f862.png)

次に「デバイス」の画面に移って操作対象のサーバーを選択します。「すべての仮想サーバー」「仮想サーバーへの自動アクセス」を選択しておくと、将来作成されるVSIも含めてすべてのVSIを操作できます。
![デバイス選択.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/612c1a4b-c5cc-9d61-9310-85247b8ba808.png)


## 5. パラメータの設定とアクションの動作確認
左側のハンバーガーメニューから「Functions」→「アクション」と遷移して、2章でデプロイしたアクションの一覧を表示します。
![アクション一覧.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/67dd04f0-7a29-9361-d9b6-94e3337cd697.png)

それぞれのアクションを選択し「パラメータ」の画面でパラメータを設定します。「追加」で入力フィールドを追加して値を入れる作業を繰り返し、最後に「保存」で確定します。今回のプログラムの引数として必要な値はそれぞれ次のとおりです。

* vsi: 操作対象のVSI名
* power: OFFまたはON
* apikey: クラシックインフラストラクチャーAPIキー
* user: 上記APIキーに紐付いたユーザーID（ここでは3章で作成したVPNユーザー名）

![パラメータ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/91fc686c-feaa-4fac-31c0-02192a271993.png)

次に「コード」の画面の「起動」ボタンでアクションを起動して、動作を確認します。VSIが起動済だったら停止用のアクション（takeyan-vsi-classic-power-off）、停止済だったら起動用のアクション（takeyan-vsi-classic-power-on）を起動してみます。以下は停止成功時の実行例です。
![アクション動作確認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/0b8aa5f4-9d00-ce17-3fc7-ff34a74b457c.png)


## 6. 時刻トリガーの登録と、アクションとの関連付け
アクションが完成したら、最後に時刻トリガーを作成してアクションを関連付けます。トリガーは[VPC Gen1 VSI編](https://qiita.com/takeyan/items/49da6b10661d12bce601)と同様にダッシュボードのGUIでも作成できますが、ここではibmcloud CLIで登録します。

月ー金の8:00に発報するトリガーと、18:00に発報するトリガーを作成するコマンドの例を以下に示します。

```bash
$ ibmcloud fn trigger create 0800weekday --feed /whisk.system/alarms/alarm -p cron "0 8 * * 1-5"
$ ibmcloud fn trigger create 1800weekday --feed /whisk.system/alarms/alarm -p cron "0 18 * * 1-5"
```

デフォルトでは発報時刻のタイムゾーンがUTCになっているので、これを日本時間に変換します。

```bash
$ ibmcloud fn trigger update 0800weekday -p timezone "Japan"
$ ibmcloud fn trigger update 1800weekday -p timezone "Japan"
```

作成したトリガーをアクションに紐付けます。8時発報トリガーを起動用アクション、18時発報トリガーを停止用アクションにそれぞれ関連付けます。

```bash
$ ibmcloud fn rule create takeyan-vsi-classic-power-on_0800weekday 0800weekday takeyan-vsi-classic-power-on
$ ibmcloud fn rule create takeyan-vsi-classic-power-off_1800weekday 1800weekday takeyan-vsi-classic-power-off
```

## 7. トリガーの動作の確認
トリガーの実行結果は「モニター」の画面で確認することができます。
アクティビティー・サマリーのトリガー名（ここでは0800weekdayと1800weekday）と、その右にある棒グラフで成功と失敗の回数がわかります。またアクティビティー・ログで実行履歴とログの詳細が確認できます。
![Functionsモニター.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32083/b161e6e7-ba5a-f3ba-28d9-e074010a6e46.png)


以上です。