Terraformを使ってROSA(Red Hat OpenShift Service)をデプロイしてみる

# はじめに
OpenShiftのマネージドサービスであるROSA(Red Hat OpenShift Service on AWS)をデプロイするにあたり、他のAWSリソースと同様にTerraformを使ってIaC化できると嬉しいなーと思い調べたところ、Custom Providerとして[terraform-provider-ocm](https://github.com/openshift-online/terraform-provider-ocm)が公開されていたので、こちらを使ってROSAをデプロイしてみようと思います。

:::note warn
注意
* [リポジトリのREADME](https://github.com/openshift-online/terraform-provider-ocm)にも記載がありますが、`terraform-provider-ocm`は現在 0.1 であり、機能は完全ではなく、下位互換性の保証はないことをご理解のうえ利用ください。
:::


# 前提条件
* MacOS
* 各種CLIをインストール済み
    * aws
    * terraform
    * oc
    * rosa
    * go
* terraform-provider-ocm : Release 0.1.6
* `Administrator Access`権限を持つIAMユーザーでaws-cliのconfig設定が完了している


# 手順

## ROSAデプロイに必要なRoleとPolicyを作成
残念ながら、Terraform実行だけではROSAの構築はできません。まずはAWS環境にROSAのデプロイに必要なIAM RoleとIAM Policyを作成します。
今回は[公式ドキュメント](https://access.redhat.com/documentation/ja-jp/red_hat_openshift_service_on_aws/4/html-single/installing_accessing_and_deleting_rosa_clusters/index#rosa-sts-creating-a-cluster-quickly-cli_rosa-sts-creating-a-cluster-quickly)を参考に、デフォルト設定でRole/Policyを準備します。

以下のコマンドによって必要なリソースを一気に作成します。
```
rosa create account-roles --prefix ManagedOpenShift --mode auto --yes
```


## Providerの準備
次に対象のProviderを準備します。
```
git clone https://github.com/openshift-online/terraform-provider-ocm.git
cd terraform-provider-ocm
```

ProviderをBuildします。

```
make install
```

するとディレクトリ配下に`.terraform.d`が作成されています。中身を見るとProviderが格納されていることがわかります。
```
tree .terraform.d
.terraform.d
└── plugins
    └── localhost
        └── openshift-online
            └── ocm
                └── 0.1.6
                    └── darwin_arm64
                        └── terraform-provider-ocm
```

## tfファイルの準備
ROSAクラスターを作成するためのtfファイルを準備していきます。

### `.terraformrc`
今回はローカルのProviderを使うため、[TerraformのConfigファイル](https://www.terraform.io/cli/config/config-file)に設定を追加します。
設定ファイルはホームディレクトリ配下に作成します。
```
vi ~/.terraformrc
```
```.terraformrc
provider_installation {
  fileystem_mirror {
    path    = "<work-directory>/terraform-provider-ocm/.terraform.d/plugins"
    include = ["localhost/*/*"]
  }
  direct {
    exclude = ["localhost/*/*"]
  }
}
```
少し記載内容を補足します。
* `filesystem_mirror`
    * オリジンのレジストリ（defaultでは`registry.terraform.io`）とは異なる場所からProviderをインストールしたい時に使用します。今回は`localhost`を指定したProviderは`<work-directory>/terraform-provider-ocm/.terraform.d/plugins`配下のProviderを参照するよう設定します。`<work-directory>`には今回の作業ディレクトリを設定します。

* `direct`
  * `direct`はオリジンのレジストリにアクセスするProviderの条件を指定します。ここでは`localhost/*/*`以外を指定したProviderは全て`registry.terraform.io`を参照しにいくように設定します。

---

### `provider.tf`
次にProviderを指定します。
[GitHubのサンプルコード](https://github.com/openshift-online/terraform-provider-ocm/blob/main/examples/create_rosa_cluster/create_rosa_sts_cluster/main.tf)ではsourceが`openshift-online/ocm`となっていますが、`localhost`の指定を追記する必要があります。
OCMのトークンやAWS接続情報はセキュリティの観点から別ファイルに切り出しています。

```provider.tf
terraform {
  required_providers {
    ocm = {
      version = ">= 0.1"
      source  = "localhost/openshift-online/ocm"
    }
    aws = {
      source  = "hashicorp/aws"
      version = ">= 4.20.0"
    }
  }
}

provider "ocm" {
  token = local.token
  url   = local.url
}

provider "aws" {
  access_key = local.access_key
  secret_key = local.secret_key
  region     = local.region
}
```

---

### `credentials.tf`
シークレット情報をこのファイルで定義します。
ocmのtokenについては[OpenShift Cluster Manager API Token](https://console.redhat.com/openshift/token)にて「Load Token」をクリックすると取得できます。
```
vi credentials.tf
```
```credentials.tf
locals {
  access_key = "<AWSのアクセスキー>"
  secret_key = "<AWSのシークレットキー>"
  region     = "us-east-1"
  token      = "<OCMのトークン>"
```

---

### `ocm_cluster.tf`

あとはサンプルコードを参照に、多少手を加えつつクラスター構築用のファイルを作成します。
今回は`us-eas-1`に3AZ構成のROSAクラスターを構築します。
まずは`ocm_cluster.tf`です。

```ocm_cluster.tf
locals {
  sts_roles = {
    role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/ManagedOpenShift-Installer-Role",
    support_role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/ManagedOpenShift-Support-Role",
    operator_iam_roles = [
      {
        name =  "cloud-credential-operator-iam-ro-creds",
        namespace = "openshift-cloud-credential-operator",
        role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.operator_role_prefix}-openshift-cloud-credential-operator-cloud-c",
      },
      {
        name =  "installer-cloud-credentials",
        namespace = "openshift-image-registry",
        role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.operator_role_prefix}-openshift-image-registry-installer-cloud-cr",
      },
      {
        name =  "cloud-credentials",
        namespace = "openshift-ingress-operator",
        role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.operator_role_prefix}-openshift-ingress-operator-cloud-credential",
      },
      {
        name =  "ebs-cloud-credentials",
        namespace = "openshift-cluster-csi-drivers",
        role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.operator_role_prefix}-openshift-cluster-csi-drivers-ebs-cloud-cre",
      },
      {
        name =  "cloud-credentials",
        namespace = "openshift-cloud-network-config-controller",
        role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.operator_role_prefix}-openshift-cloud-network-config-controller-c",
      },
      {
        name =  "aws-cloud-credentials",
        namespace = "openshift-machine-api",
        role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.operator_role_prefix}-openshift-machine-api-aws-cloud-credentials",
      },
    ]
    instance_iam_roles = {
      master_role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/ManagedOpenShift-ControlPlane-Role",
      worker_role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/ManagedOpenShift-Worker-Role"
    },
  }
}

data "aws_caller_identity" "current" {
}

resource "ocm_cluster_rosa_classic" "rosa_sts_cluster" {
  name                 = local.ocm_cluster.name
  cloud_region         = local.region
  aws_account_id       = data.aws_caller_identity.current.account_id
  multi_az             = local.ocm_cluster.multi_az
  compute_nodes        = local.ocm_cluster.compute_nodes
  compute_machine_type = local.ocm_cluster.compute_machine_type
  availability_zones   = local.ocm_cluster.availability_zones
  properties = {
    rosa_creator_arn = data.aws_caller_identity.current.arn
  }
  sts  = local.sts_roles
}

module operator_roles {
  source  = "./terraform-provider-ocm/modules/operator_roles"

  cluster_id = ocm_cluster_rosa_classic.rosa_sts_cluster.id
  operator_role_prefix = local.operator_role_prefix
  account_role_prefix = local.account_role_prefix
  rh_oidc_provider_thumbprint = ocm_cluster_rosa_classic.rosa_sts_cluster.sts.thumbprint
  rh_oidc_provider_url = ocm_cluster_rosa_classic.rosa_sts_cluster.sts.oidc_endpoint_url
}
```

いくつか記載内容を補足します。

#### `locals`
`locals`では、ROSAに必要な operator roleの情報が記載されています。こちらはサンプルコードをベースに、変数情報を`var`でなく`local`から取得するように変更しています。ここはユーザー側で変更する箇所ではないので実行用のtfファイルに含めています。

#### `data.aws_caller_identity`
現在`aws cli`でログインしているIAMユーザーの情報を取得しています。ROSAクラスターを構築する`ocm_cluster_rosa_classic`resourceで使います。そのため、terraform applyの前に`aws configure`でログイン処理を実行しておく必要があります。

#### `ocm_cluster_rosa_classic`
ROSAクラスターの構築を実行します。ただ実際に行なっているのは「OpenShift Cluster Manager」に対してROSAの構築をAPI実行しているだけなので、Terraformの実行処理はすぐに終わります。クラスターの構築状況はOpenShift Cluster Manager側で確認する形になります。

---

### `local.tf`
このファイルではユーザー側で変更する可能性のある値を管理します。
```local.tf
locals {
  cluster_name = "rosa-demo-01"

  operator_role_prefix = "ManagedOpenShift"
  account_role_prefix  = "ManagedOpenShift"

  ocm_cluster = {
    name                 = local.cluster_name
    multi_az             = true
    compute_nodes        = 3
    compute_machine_type = "m5.xlarge"
    availability_zones = [
      "us-east-1a",
      "us-east-1b",
      "us-east-1c",
    ]
  }
}
```
その他にもNW周りで様々な値を設定可能ですが、今回は割愛します。設定可能なパラメータは[providerのソースコード](https://github.com/openshift-online/terraform-provider-ocm/blob/main/provider/cluster_rosa_classic_resource.go)を読み解くと見えてくるかと思います。

`operator_role_prefix`と`account_role_prefix`は手順の最初に実行した`rosa create account-roles`でオプション指定した `--prefix`の値と合わせてください。
また、`multi_az`をtrueにした場合は、`compute_nodes`の値を3の倍数で指定する必要がありますのでご注意ください。

## 実行
これで実行準備が完了しました。ディレクトリ階層は以下のようになっています。

```
tree -L 1
.
├── credentials.tf
├── local.tf
├── ocm_cluster.tf
├── provider.tf
└── terraform-provider-ocm
```

`terraform apply`を実行します。
```
terraform init
terraform apply
```


## デプロイ確認

OpenShift Cluster Managerでクラスター情報を確認できます。
![deploy_rosa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/e06b9915-65dc-a1f8-a10c-36203a61870b.png)



# おわりに
今回はTerraformを使ったROSAクラスターのデプロイの自動化を試してみました。
rosaは専用のコマンドが用意されているためシェルスクリプトでも自動化は可能ですが、複数のクラスターをパラメータを変更しながら繰り返しデプロイする運用を想定すると、TerraformやAnsibleなど、設定パラメータを切り出して管理できるIaCツールを活用した方が運用面でのメリットは大きいかと思います。
クラスター削除の際もTerraformでは`terraform destroy`をたたくことで実行可能ですので、削除用のスクリプトを作る必要もありません。
[What's new in Red Hat OpenShift 4.11](https://speakerdeck.com/redhatlivestreaming/whats-new-in-openshift-4-dot-11?slide=3)を見ると、Mid TermにてROSAのterraform providerがリリースされるとのことなので、まもなくこのProviderが公式に使えるようになるのではと思います。
v1.0が出た際はまた手順を記事にしてみますので乞うご期待ください！