TerraformでIKSのクラスターを作成する

最近諸々あって投稿を休んでましたが、久しぶりの投稿です。

仕事でIKS (IBM Cloud Kubernetes Service)とGKE(Google Kubernetes Engine)をよく触っています。基盤構築はどちらも簡単ですが、上に乗っかるシステムと合わせて全てTerraformで構成できないかなと思い調べていました。

とりあえずGKEは簡単でしたが、IBM Cloudの場合は以下のチュートリアルではIKSのクラスターのデプロイ部分が古かったので、上手く動きませんでした。
[1] https://console.bluemix.net/docs/tutorials/plan-create-update-deployments.html#plan-create-and-update-deployment-environments

とはいえ、できないままにしておくのも微妙なので、備忘録としてIKSのクラスターをデプロイするまでの手順を以下にまとめました。

#作りたい環境
IKSでもGKEと同じくマルチゾーン(Multiple Zone)対応が追加されました。なので、Terraformでマルチゾーンなクラスターを設定してみます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/101771/8ff0d23c-ca1f-a196-32a5-6fe968c41885.png)

上記の図のように、東京(tok02)とシンガポール(sng01)を使った構成とします。

#準備
実施するためには以下のコマンドラインを導入しておく必要があります。

- ibmcloudコマンドライン
    - IKS操作のためにcontainer serviceも追加で導入
- terraform
    - v0.11.7で今回は動作確認
    - IBM Cloud Provider v0.11.1以上も合わせて導入

導入手順は上記リンク[1]に記載があるので割愛します。
terraformからIBM Cloudを操作するためのIBM Cloud Providerの導入が必須ですが、アベイラビリティーゾーンとして日本を選択するためにはv0.11.1以上が必須です。([2]に記載される修正が含まれているのがv0.11.1からのため)

[2] https://github.com/IBM-Cloud/terraform-provider-ibm/issues/284

# 必要なファイルの作成
以下のファイルを作成します。

- main.tf (メインで実行したいファイル)
- providers.tf (プロバイダーの定義ファイル)
- variables.tf (変数の定義ファイル)
- myvalues.tfvars (デプロイで使用する設定を記載する変数ファイル)

##main.tf
main.tfには「クラスター作成 → ワーカープール作成 → ワーカープールにゾーンをアタッチ」の順で設定を記載します。

```
resource "ibm_container_cluster" "cluster" {
  account_guid    = "${var.account_id}"
  name            = "${var.cluster_name}"
  billing         = "${var.cluster_billing}"
  kube_version    = "${var.kube_version}"
  datacenter      = "${var.cluster_default_pool_zone}"
  machine_type    = "${var.cluster_default_pool_machine_type}"
  hardware        = "${var.cluster_default_pool_hardware}"
  disk_encryption = "${var.disk_encryption}"
  worker_num      = "${var.cluster_default_pool_worker_num}"
  public_vlan_id  = "${var.cluster_default_pool_public_vlan_id}"
  private_vlan_id = "${var.cluster_default_pool_private_vlan_id}"
}

resource "ibm_container_worker_pool" "additional_workerpool" {
  worker_pool_name = "${var.cluster_additional_pool_name}"
  cluster          = "${ibm_container_cluster.cluster.id}"
  machine_type     = "${var.cluster_additional_pool_machine_type}"
  hardware         = "${var.cluster_additional_pool_hardware}"
  disk_encryption  = "${var.disk_encryption}"
  size_per_zone    = "${var.cluster_additional_pool_worker_num}"

  labels = {
    "pool_name" = "${var.cluster_additional_pool_name}"
  }

  //User can increase timeouts 
  timeouts {
    update = "180m"
  }
}

resource "ibm_container_worker_pool_zone_attachment" "additional_zone" {
  cluster         = "${ibm_container_cluster.cluster.id}"
  //worker_pool     = "${ibm_container_worker_pool.additional_workerpool.id}"
  worker_pool     = "${var.cluster_additional_pool_name}"
  zone            = "${var.cluster_additional_pool_zone}"
  private_vlan_id = "${var.cluster_additional_pool_private_vlan_id}"
  public_vlan_id  = "${var.cluster_additional_pool_public_vlan_id}"

  //User can increase timeouts
  timeouts {
    create = "90m"
    update = "3h"
    delete = "30m"
  }
}
```

***注意***
細かい話ですが、ibm_container_worker_pool_zone_attachmentリソースのworker_poolの設定に関しては、[3]より名前かid指定で良いと記載があります。なので、`${ibm_container_worker_pool.additional_workerpool.id}`では動的に値を設定すれば良いはずなのですが、上手くいかないので`${var.cluster_additional_pool_name}`で設定しています。

[3] https://ibm-cloud.github.io/tf-ibm-docs/v0.11.1/r/container_worker_pool_zone_attachment.html

##providers.tf
以下の内容のファイルを作成します。

```
provider "ibm" {
  bluemix_api_key = "${var.ibmcloud_api_key}"
  region = "${var.cluster_region}"
}
```

##variables.tf
変数の定義ファイル(variables.tf)として以下の内容のファイルを作成します。

```
# Account Settings of IBM Cloud
variable "ibmcloud_api_key" {}
variable "cluster_region" {}
variable "account_id" {}

# Information for IKS Cluster (Global)
variable "cluster_name" {}
variable "cluster_billing" {}
variable "kube_version" {}
variable "disk_encryption" {}

# Information for first (default) Worker Pool
variable "cluster_default_pool_zone" {}
variable "cluster_default_pool_machine_type" {}
variable "cluster_default_pool_hardware" {}
variable "cluster_default_pool_worker_num" {}
variable "cluster_default_pool_public_vlan_id" {}
variable "cluster_default_pool_private_vlan_id" {}

# Information for Additional Worker Pool(s)
variable "cluster_additional_pool_name" {}
variable "cluster_additional_pool_zone" {}
variable "cluster_additional_pool_machine_type" {}
variable "cluster_additional_pool_hardware" {}
variable "cluster_additional_pool_worker_num" {}
variable "cluster_additional_pool_public_vlan_id" {}
variable "cluster_additional_pool_private_vlan_id" {}
```

##myvalues.tfvals
変数ファイル(myvalues.tfvals)は以下のように作成します。ユーザー固有の情報に関して、事前にibmcloudコマンドで値を確認しておき、指定します。

```
ibmcloud_api_key = "<事前にibmcloud iam api-key-createで生成したPlatform API Keyを指定>"
account_id = "<ibmcloud account listコマンドで確認できるアカウントの固有IDを指定>"

cluster_name = "qiita"
cluster_region = "jp-tok"
cluster_billing = "hourly"
kube_version = "1.10.3"
disk_encryption = "true"

cluster_default_pool_zone = "tok02"
cluster_default_pool_machine_type = "u2c.2x4"
cluster_default_pool_hardware = "shared"
cluster_default_pool_worker_num = 3
cluster_default_pool_public_vlan_id = "<ibmcloud cs vlans --zone tok02で確認した値を設定 or 新規作成なら何も指定しない>"
cluster_default_pool_private_vlan_id = "<ibmcloud cs vlans --zone tok02で確認した値を設定 or 新規作成なら何も指定しない>"

cluster_additional_pool_name = "default2"
cluster_additional_pool_zone = "sng01"
cluster_additional_pool_machine_type = "u2c.2x4"
cluster_additional_pool_hardware = "shared"
cluster_additional_pool_worker_num = 1
cluster_additional_pool_public_vlan_id = "<ibmcloud cs vlans --zone sng01で確認した値を設定 or 新規作成なら何も指定しない>"
cluster_additional_pool_private_vlan_id = "<ibmcloud cs vlans --zone sng01で確認した値を設定 or 新規作成なら何も指定しない>"
```

**注意**
cluster_regionで指定しているリージョン名は`ibmcloud cs regions`から確認したものを使用するのですが、7/11のv0.11.1の時点ではRegion NameではなくRegion Aliasを指定する必要があります。

```
$ibmcloud cs regions
Region Name   Region Alias   
ap-north      jp-tok   
ap-south      au-syd   
eu-central    eu-de   
uk-south      eu-gb   
us-east       us-east   
us-south      us-south 
```

# 設定のチェック
まず`terraform init`コマンドで初期化します。

```
$terraform init

Initializing provider plugins...

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
```


`terraform plan`コマンドで作成したファイルをチェックします。

```
$terraform plan --var-file=myvalues.tfvars 
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + ibm_container_cluster.cluster
      id:                <computed>
      account_guid:      "xxxxxxxxxxxxxxxxxxxxxxxxx"
      billing:           "hourly"
      datacenter:        "tok02"
      disk_encryption:   "true"
      hardware:          "shared"
      ingress_hostname:  <computed>
      ingress_secret:    <computed>
      is_trusted:        "false"
      isolation:         <computed>
      kube_version:      "1.10.3"
      machine_type:      "u2c.2x4"
      name:              "qiita"
      no_subnet:         "false"
      server_url:        <computed>
      wait_time_minutes: "90"
      worker_num:        "3"
      worker_pools.#:    <computed>
      workers_info.#:    <computed>

  + ibm_container_worker_pool.additional_workerpool
      id:                <computed>
      cluster:           "${ibm_container_cluster.cluster.id}"
      disk_encryption:   "true"
      hardware:          "shared"
      kube_version:      <computed>
      labels.%:          "1"
      labels.pool_name:  "default2"
      machine_type:      "u2c.2x4"
      size_per_zone:     "1"
      state:             <computed>
      worker_pool_name:  "default2"
      zones.#:           <computed>

  + ibm_container_worker_pool_zone_attachment.additional_zone
      id:                <computed>
      cluster:           "${ibm_container_cluster.cluster.id}"
      worker_count:      <computed>
      worker_pool:       "default2"
      zone:              "sng01"


Plan: 3 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
```

# デプロイ
実際にデプロイしてみます。

```
$terraform apply --var-file=myvalues.tfvars 

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + ibm_container_cluster.cluster
      id:                <computed>
      account_guid:      "xxxxxxxxxxxxxxxxxx"
      billing:           "hourly"
      datacenter:        "tok02"
      disk_encryption:   "true"
      hardware:          "shared"
      ingress_hostname:  <computed>
      ingress_secret:    <computed>
      is_trusted:        "false"
      isolation:         <computed>
      kube_version:      "1.10.3"
      machine_type:      "u2c.2x4"
      name:              "qiita"
      no_subnet:         "false"
      private_vlan_id:   ""
      public_vlan_id:    ""
      server_url:        <computed>
      wait_time_minutes: "90"
      worker_num:        "3"
      worker_pools.#:    <computed>
      workers_info.#:    <computed>

  + ibm_container_worker_pool.additional_workerpool
      id:                <computed>
      cluster:           "${ibm_container_cluster.cluster.id}"
      disk_encryption:   "true"
      hardware:          "shared"
      kube_version:      <computed>
      labels.%:          "1"
      labels.pool_name:  "default2"
      machine_type:      "u2c.2x4"
      size_per_zone:     "1"
      state:             <computed>
      worker_pool_name:  "default2"
      zones.#:           <computed>

  + ibm_container_worker_pool_zone_attachment.additional_zone
      id:                <computed>
      cluster:           "${ibm_container_cluster.cluster.id}"
      private_vlan_id:   ""
      public_vlan_id:    ""
      worker_count:      <computed>
      worker_pool:       "default2"
      zone:              "sng01"


Plan: 3 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

ibm_container_cluster.cluster: Creating...
:
(省略)
:
ibm_container_cluster.cluster: Creation complete after 32m27s (ID: xxxxxxxx)
ibm_container_worker_pool_zone_attachment.additional_zone: Creating...
  cluster:         "" => "xxxxxxx"
  private_vlan_id: "" => "xxxxxxx"
  public_vlan_id:  "" => "xxxxxxx"
  worker_count:    "" => "<computed>"
  worker_pool:     "" => "default2"
  zone:            "" => "sng01"
ibm_container_worker_pool.additional_workerpool: Creating...
  cluster:          "" => "xxxxxxx"
  disk_encryption:  "" => "true"
  hardware:         "" => "shared"
  kube_version:     "" => "<computed>"
  labels.%:         "" => "1"
  labels.pool_name: "" => "default2"
  machine_type:     "" => "u2c.2x4"
  size_per_zone:    "" => "1"
  state:            "" => "<computed>"
  worker_pool_name: "" => "default2"
  zones.#:          "" => "<computed>"
ibm_container_worker_pool.additional_workerpool: Still creating... (10s elapsed)
ibm_container_worker_pool.additional_workerpool: Creation complete after 15s (ID: xxxxxxxx/xxxxxxxx-xxxxxxxx)
:
(省略)
:
ibm_container_worker_pool_zone_attachment.additional_zone: Creating...
  cluster:         "" => "xxxxxxxx"
  private_vlan_id: "" => "xxxxxxxx"
  public_vlan_id:  "" => "xxxxxxxx"
  worker_count:    "" => "<computed>"
  worker_pool:     "" => "default2"
  zone:            "" => "sng01"
ibm_container_worker_pool_zone_attachment.additional_zone: Still creating... (10s elapsed)
ibm_container_worker_pool_zone_attachment.additional_zone: Still creating... (20s elapsed)
ibm_container_worker_pool_zone_attachment.additional_zone: Still creating... (30s elapsed)
ibm_container_worker_pool_zone_attachment.additional_zone: Creation complete after 31s (ID: xxxxxxxx/default2/sng01)

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
```

***注意***
たまに３番目の`ibm_container_worker_pool_zone_attachment.additional_zone`の実行に失敗してしまいます。そのときは再度`terraform apply`を実行してください。。
この問題はそのうち修正しようと思います。 

# 結果こうなった
できあがった環境をIBM Cloudコンソール画面から見ると、確かにワーカー・プールが設定されていることが確認できます。

![スクリーンショット 2018-07-12 18.41.10.png](https://qiita-image-store.s3.amazonaws.com/0/101771/d02bda07-2892-fda9-0056-fcd46c18a661.png)

ただ、`ibm_container_worker_pool_zone_attachment.additional_zone`はワーカー・プールを指定したゾーンに紐づけるところまでの設定のようで、実際のワーカー・ノードのプロビジョニング完了までは待ってくれないようでした。。ちょっと残念。。。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/101771/1e2f7c7f-291b-a834-fce8-c13ad756698f.png)

とはいえ、待っていれば使用可能になりますので問題なしかなと。

#終わりに
最近Infrastructure as Codeから逃げ回ってGUIでやってましたが、Terraformを使ってIKSのクラスターを簡単に設定できることが確認できました。
案件で結構IKSなりGKEなりでクラスターを立てまくっているので、今後はTerraformを活用していきたいです。