ConoHa VPS API 各論（APIを使用したVPS作成）

この記事はConoHa Advent Calendar 2022カレンダーの24日目の記事です。

https://qiita.com/advent-calendar/2022/conoha

17日の記事でAPI概論をされていたので、そこに乗っかって各論として実際にVPSサーバーをAPI経由で作成してみようと思います。

https://qiita.com/skworks33/items/c4052a11fb5d970a0a71

VMの作成に必要な**トークンID**、**対象 image の UUID**、**VMプラン(flavor) の UUID**、**セキュリティグループ名**の取得[^3]から、
取得した情報を使用して**VMの作成**を行うまでを順に見ていきます。

1. [トークンIDを取得](#トークンidを取得)
2. [対象imageのUUIDを取得](#対象imageのuuidを取得)
3. [VMプラン(flavor)のUUIDを取得](#vmプランflavorのuuidを取得)
4. [セキュリティグループ名を取得](#セキュリティグループ名を取得)
5. [VM追加](#vm追加)
6. [作成されたVMを確認](#作成されたvmを確認)

### トークンIDを取得

コントロールパネルからAPIユーザーの追加を行います。

https://support.conoha.jp/v/addapiuser/

トークン発行APIへ向けてリクエストを行います。

- [Identity API v2.0 POST /v2.0/tokens](https://www.conoha.jp/docs/identity-post_tokens.php)[^1]

``` bash
curl https://identity.tyo1.conoha.io/v2.0/tokens \
  --data '{
    "auth": {
      "passwordCredentials": {
        "username": "【API情報/APIユーザー/ユーザー名】",
        "password": "【API情報/APIユーザー/パスワード】"
      },
      "tenantId": "【API情報/テナント情報/テナントID】"
    }
  }' \
  --request POST \
| jq '.access.token | {expires, id}'
```

``` bash
{
  "expires": "2022-12-20T04:26:48Z",
  "id": "【トークンID】"
}
```

`access.token.id` の値を使用します。
取得したアクセストークンは `access.token.expires` の日時（UTC）まで有効のようです。

### 対象imageのUUIDを取得

イメージ一覧取得APIへ向けてリクエストを行います。

- [Image API v2 GET /v2/images](https://www.conoha.jp/docs/image-get_images_list.php)[^1]

``` bash
curl https://image-service.tyo1.conoha.io/v2/images \
  --request GET \
  --header 'Accept: application/json' \
  --header 'X-Auth-Token: 【トークンID】' \
| jq '.images[] | select(.name | contains("centos-stream")) | [.name, .id] | @csv' \
  --raw-output
```

``` bash
"vmi-kusanagimanager9-0.5.1-centos-stream8-amd64-100gb","3259adb3-8df6-4687-978b-c25f2f6bf5c4"
"vmi-kusanagimanager9-0.5.1-centos-stream8-amd64","4de6e408-9b07-4e99-b1d1-10b29a5cbdd3"
"vmi-centos-stream9-amd64-100gb","642d1a41-5a32-490c-8fac-a4229363dd76"
"vmi-centos-stream9-amd64","d16ba42f-a1ce-4775-bace-50f159b672d9"
"vmi-centos-stream9-amd64-30gb","5005e687-d644-471a-8f34-8c6594fba487"
"vmi-centos-stream9-amd64-20gb","ce13c434-fa6f-43b0-a7e5-7ad8f600c370"
"vmi-centos-stream-8-latest-amd64-100gb","c67cfd69-b096-4903-ad8a-fe02fe7aa896"
"vmi-centos-stream-8-latest-amd64","d8af04f6-6bce-40bf-8dc5-867d0a52cdc3"
"vmi-centos-stream-8-latest-amd64-30gb","39376bda-80b2-4b7c-b6eb-3b44cbac5faa"
"vmi-centos-stream-8-latest-amd64-20gb","33b8f21a-1a2e-4609-9178-99809606bc6b"
```

`vmi-centos-stream9-amd64` を使いたいけど `-30gb` とかサフィックスがついてるやつは何、、、ストレージの容量でしょうか？
とりあえず一番数字の小さいかつ後で検証できそうな `vmi-centos-stream9-amd64-20gb` ( `ce13c434-fa6f-43b0-a7e5-7ad8f600c370` ) を使用します。

### VMプラン(flavor)のUUIDを取得

VMプラン一覧取得APIへ向けてリクエストを行います。

- [Compute API v2.0 GET /v2/{tenant_id}/flavors](https://www.conoha.jp/docs/compute-get_flavors_list.php)[^1]

``` bash
curl https://compute.tyo1.conoha.io/v2/【API情報/テナント情報/テナントID】/flavors \
  --header 'Accept: application/json' \
  --header 'X-Auth-Token: 【トークンID】' \
  --request GET \
| jq '.flavors[]| [.name, .id] | @csv' \
  --raw-output
```

``` bash
"g-2gb","294639c7-72ba-43a5-8ff2-513c8995b869"
"g-16gb","3aa001cd-95b6-46c9-a91e-e62d6f7f06a3"
"g-c4m4d100","5d3b29d1-511e-4f06-bb69-42f79b00e514"
"g-c12m32d100","5f58bcf4-cf4b-46ea-886c-de04b632c7f2"
"g-4gb","62e8fb4b-6a26-46cd-be13-e5bbf5614d15"
"g-c8m16d100","7ddaba07-b000-45f7-99ce-540fbbf8b965"
"g-1gb","7eea7469-0d85-4f82-8050-6ae742394681"
"g-c1m512d30","93b572a0-f1c7-4753-8384-47d9dcc826ee"
"g-8gb","965affd4-d9e8-4ffb-b9a9-624d63e2d83f"
"g-c6m8d100","a13e5367-b191-4d31-9df5-d196862b9a94"
"g-32gb","a20905c6-3733-46c4-81cc-458c7dca1bae"
"g-c2m1d100","ab7b9b6d-108c-4487-90a4-2da604ad6a92"
"g-64gb","c2a97b05-1b4b-4038-bbcb-343201659279"
"g-c24m64d100","c36c5701-65e1-492c-b757-af843fe25599"
"g-512mb","d92b02ce-9a4f-4544-8d7f-ae8380bc08e7"
"g-c3m2d100","ead5edd0-27af-4799-aecf-4e5633b46adc"
```

んん、、、？
ドキュメントの例だと `64gb-flavor` みたいな名前がついてるはずなんですが、、、

コントロールパネルでいうところのこの辺でしょうか？

![スクリーンショット 2022-12-19 15.01.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3047864/7d7028f3-1922-2944-d1b8-1c671d0a3a08.png)

APIからだとメモリの大きさとストレージの大きさを別で指定できるってことなんでしょうか、、、？
とりあえず1GBにしたかったので `g-1gb` （ `7eea7469-0d85-4f82-8050-6ae742394681` ） を使用します。

### セキュリティグループ名を取得

セキュリティグループ一覧取得APIへ向けてリクエストを行います。

- [Network API v2.0 GET /v2.0/security-groups](https://www.conoha.jp/docs/neutron-get_secgroups_list.php)[^1]

``` bash
curl https://networking.tyo1.conoha.io/v2.0/security-groups \
  --header 'Accept: application/json' \
  --header 'X-Auth-Token: 6e9ccdb87eb64ead84b68795536a61ac' \
  --request GET \
| jq '.security_groups[] | {n: .name} + (.security_group_rules[] | {d: .direction, pmn: .port_range_min, pmx: .port_range_max}) | [.n, .d, .pmn, .pmx] | @csv' \
  --raw-output
```

``` bash
"gncs-ipv4-all","ingress",,
"gncs-ipv4-all","egress",,
"gncs-ipv4-all","egress",,
"gncs-ipv6-mysql","egress",,
"gncs-ipv6-mysql","ingress",3306,3306
"gncs-ipv6-mysql","egress",,
"gncs-ipv4-mysql","egress",,
"gncs-ipv4-mysql","egress",,
"gncs-ipv4-mysql","ingress",3306,3306
"gncs-ipv6-ssh","ingress",22,22
"gncs-ipv6-ssh","egress",,
"gncs-ipv6-ssh","egress",,
"gncs-ipv6-web","egress",,
"gncs-ipv6-web","egress",,
"gncs-ipv6-web","ingress",21,21
"gncs-ipv6-web","ingress",443,443
"gncs-ipv6-web","ingress",80,80
"gncs-ipv6-web","ingress",20,20
"gncs-ipv4-ssh","ingress",22,22
"gncs-ipv4-ssh","egress",,
"gncs-ipv4-ssh","egress",,
"gncs-ipv6-all","egress",,
"gncs-ipv6-all","egress",,
"gncs-ipv6-all","ingress",,
"gncs-ipv4-web","egress",,
"gncs-ipv4-web","ingress",80,80
"gncs-ipv4-web","ingress",21,21
"gncs-ipv4-web","ingress",443,443
"gncs-ipv4-web","egress",,
"gncs-ipv4-web","ingress",20,20
"default","egress",,
"default","ingress",,
"default","ingress",,
"default","egress",,
```

今回はHTTPアクセスとSSHアクセスだけ受け入れてくれれば良いので、
`gncs-ipv4-web` 、 `gncs-ipv4-ssh` を使用します。

FTPで使用する20番や21番が不要な場合、 [カスタムしたセキュリティグループを作成する](https://www.conoha.jp/docs/neutron-create_secgroup.php) こともできるようです。

### VM追加

必要な情報が揃ったので、
VM追加APIへ向けてリクエストを行います。

- [Compute API v2.0 POST /v2/{tenant_id}/servers](https://www.conoha.jp/docs/compute-create_vm.php)[^1]

``` bash
curl https://compute.tyo1.conoha.io/v2/【API情報/テナント情報/テナントID】/servers \
  --data '{
    "imageRef": "ce13c434-fa6f-43b0-a7e5-7ad8f600c370",
    "flavorRef": "7eea7469-0d85-4f82-8050-6ae742394681",
    "adminPass": "【ルートユーザー用パスワード】",
    "security_groups": [
      { "name": "gncs-ipv4-web" },
      { "name": "gncs-ipv4-ssh" }
    ],
    "metadata": {
      "instance_name_tag": "20221219"
    }
  }' \
  --header 'Accept: application/json' \
  --header 'X-Auth-Token: 【トークンID】' \
  --request POST
```

``` bash
500 Internal Server Error
```

は？

どうもドキュメントの例を見ると、リクエストデータは `server` キーの下にまとめる必要があるようです。[^2]


``` bash
curl https://compute.tyo1.conoha.io/v2/【API情報/テナント情報/テナントID】/servers \
  --data '{
    "server": {
      "imageRef": "ce13c434-fa6f-43b0-a7e5-7ad8f600c370",
      "flavorRef": "7eea7469-0d85-4f82-8050-6ae742394681",
      "adminPass": "【ルートユーザー用パスワード】",
      "security_groups": [
        { "name": "gncs-ipv4-web" },
        { "name": "gncs-ipv4-ssh" }
      ],
      "metadata": {
        "instance_name_tag": "20221219"
      }
    }
  }' \
  --header 'Accept: application/json' \
  --header 'X-Auth-Token: 【トークンID】' \
  --request POST
```

``` bash
{"badRequest":{"message":"Invalid characters combination found in password. Admin password must include lower, upper case, numeric, symbolic characters.","code":400}}
```

これについてはドキュメントに記載がありますね。

> vmのrootパスワードについて
> vmのrootパスワードは“adminPass”パラメータを利用します。
> adminPassはOptionalですが、指定するimageによって動作が変わります。
>
> Publicイメージを指定した場合、パスワードが入力されなかったらランダムなパスワードが設定されます。
> Privateイメージ（自身のVPSの保存イメージ）を指定した場合、パスワードが入力されなかったら元イメージのパスワードが設定されます。(※レスポンスのadminPassには““と表示されます)
> 文字種：半角英大文字と、半角英小文字と、半角数字または記号の組み合わせ以外はエラー。文字数：9文字以上～70文字以内
> 使用可能の記号 ⇒ ! # $ % & ? ” ' = + - _ { } [ ] ^ ~ : ; ( ) . , / | \ * @

パスワード部分を修正して再度リクエストを投げると、

``` bash
{
  "server": {
    "security_groups": [
      {
        "name": "gncs-ipv4-web"
      },
      {
        "name": "gncs-ipv4-ssh"
      }
    ],
    "OS-DCF:diskConfig": "MANUAL",
    "id": "【なんらかのUUID】",
    "links": [
      {
        "href": "https://compute.tyo1.conoha.io/v2/【API情報/テナント情報/テナントID】/servers/【なんらかのUUID】",
        "rel": "self"
      },
      {
        "href": "https://compute.tyo1.conoha.io/【API情報/テナント情報/テナントID】/servers/【なんらかのUUID】",
        "rel": "bookmark"
      }
    ],
    "adminPass": "【ルートユーザー用パスワード】"
  }
}
```

無事作成されたようなレスポンスが返りました。

### 作成されたVMを確認

コントロールパネルから作成されたVMを確認してみると、下のスクリーンショットのような状態でした。

![スクリーンショット 2022-12-19 15.54.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3047864/c4d6e194-a099-c2b8-df92-a5a5c0ce78bb.png)

![スクリーンショット 2022-12-19 16.04.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3047864/580244cc-27b0-f01f-4c2a-63313ae1c8cc.png)

んん？イメージは `vmi-centos-stream9-amd64-20gb` を使用したはずですが、 `SSD 50GB` となっています。
イメージ名のサフィックス ( `-20gb` ) はストレージの大きさではないのでしょうか、、、？
メモリは期待通り `1GB` になっていて、ネームタグも指定通り、接続許可ポートも `Web` 、 `SSH` のみになっていてもんだいなさそうです。

作成したVMを削除して、イメージ名のサフィックスを　`vmi-centos-stream9-amd64-100gb` ( `642d1a41-5a32-490c-8fac-a4229363dd76` )　へ変更してみました。

``` bash
curl https://compute.tyo1.conoha.io/v2/【API情報/テナント情報/テナントID】/servers \
  --data '{
    "server": {
      "imageRef": "642d1a41-5a32-490c-8fac-a4229363dd76",
      ...
    }
  }' \
  ...
```

``` bash
{"badRequest":{"message":"Flavor's disk is too small for requested image.","code":400}}
```

んんん？どういうことでしょう、、、？？？

Flavor (`vmi-centos-stream9-amd64-100gb`) のディスクが要求されたイメージ ( `g-1gb` ) に対して小さすぎます、、、？
さっき成功した `vmi-centos-stream9-amd64-20gb` の方が小さいのでは、、、？

#### `vmi-centos-stream9-amd64-30gb` を試す

`vmi-centos-stream9-amd64-30gb` はどうでしょう？

``` bash
{
  "server": {
    ...
  }
}
```

リクエストは成功しましたが、
コントロールパネルで確認すると変わらず `SSD 50GB` となっています。

#### `vmi-centos-stream9-amd64` を試す

最後に `vmi-centos-stream9-amd64` を試してみます。

リクエストは成功、 しかしまたしても `SSD 50GB` が設定されていました。。

#### flavor を変えてみる

あれ、もしかして、Flavorのところで出てきた `g-c2m1d100` って、

- cpu: 2 core
- memory: 1 GB
- disk: 100 GB

ということでは、、、？
ということは、 `flavorRef` の部分を `g-c2m1d100` ( `ab7b9b6d-108c-4487-90a4-2da604ad6a92` ) に変更すると、

![スクリーンショット 2022-12-19 16.33.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3047864/df6a34ea-e68d-8936-e98f-5b64efba8a46.png)

`SSD 100GB` になりました。
結局イメージ名についてる ( `-20gb` ) これ何、、、？

### まとめ

初見でドキュメントをパッと見た感じではスムーズに作成できるかと思ったのですが、
意外と手間がかかったように思います。

また、謎が残ったままになってしまいました。
よく考えてみると image の指定でディスク容量まで決められるのはちょっと違うよね、って感じではありますが。
あのサフィックスは何だったんでしょうか。
`Flavor's disk is too small for requested image` のエラーはなぜ発生したのでしょうか。

最終的に flavor を `g-c2m1d100` に指定することで期待したものが出来ましたが、
ドキュメントに記載がないので「ほんまか、、、？」という疑いが晴れず、この状態で課金され続けるのは怖いです。

とはいえ[課金は1時間単位](https://support.conoha.jp/common/faq/payment-q/billing-q/)なので、
色々試してみてもそんなにコストはかからない気がします[^4]。

API経由だとコントロールパネル経由より詳細な設定が出来るようですので、
必要に応じて使っていければより便利に、効果的に運用ができそうです。
が、コントロールパネルからできる操作はコントロールパネルからやってしまう方が現状個人的には安心できそうです[^5][^6]。

ConoHa APIのSDKがあればより便利になる気がするので[このはちゃん頑張ってください！！！！！！！！！](https://conoha.mikumo.com/)[^7]

[^1]: [※エンドポイントはお客様環境ごとに異なりますのでAPIご利用の際は必ず一度ご確認ください。](https://support.conoha.jp/v/addapiuser/#02)
[^2]: `server.imageRef` みたいな書き方してほしいですね、、、
[^3]: imageやらflavorやらのリストをドキュメントのどこかに一覧作っていただければ基本API叩く必要ないのでは、、、
[^4]: そういえばメモリ 1GB ストレージ SSD 50GB で出来ちゃったVMって料金どうなるんでしょう？そんなプランないですよね？
[^5]: 本来はヒューマンエラーを避けるためにスクリプト書いてAPI経由のほうが安心なはずですが。ドキュメント、、、
[^6]: まあでもドキュメントって更新されないものですよね。OpenAPIでドキュメント作成しつつ自動テストまでできるような感じがイケてるんでしょうか？
[^7]: あと卓上カレンダーください！！！