OCI で手動構築したリソースを Terraform 管理下に取り込む

# はじめに

Oracle Cloud Infrastructure（以下、OCI） には、Terraform による自動構築に対応しています。また、Web コンソールから Terraform を実行するためのサービスである [Resource Manager](https://docs.oracle.com/ja-jp/iaas/Content/ResourceManager/Concepts/resourcemanager.htm) が用意されています。

AWS における Cloud Formation のような立ち位置のサービスといえますが、Cloud Formation とは異なり Terraform 自体が様々なクラウドに対応したツールであるため、OCI とそれ以外の垣根を下げるメリットがあります [^2]。

Resource Manager による自動構築はとても便利ですが、構築のためのコードを準備してレビューを受けるまでにはどうしてもリードタイムが発生します。ときには「急ぎだから手作業で構築したい」ということもあるでしょう。すると、**手作業で構築したものを後から Terraform に取り込む仕組みがあると便利**です。今回はその方法について検証してまとめました。

# 前提知識

## スタックとジョブ

OCI の Resource Manager には、スタックとジョブの概念があります。

* スタック：Terraform のコードを管理する
* ジョブ：Terraform の実行（plan と apply）を管理する

:::note info
この記事ではこの 2 つを前提知識とし、それより進んだ内容を扱うため、詳しくは説明しません。不明の場合は [リソース・マネージャの概要](https://docs.oracle.com/ja-jp/iaas/Content/ResourceManager/Concepts/resourcemanager.htm) を参照してください。
:::

## state ファイルについて

Terraform では、[VCN](https://docs.oracle.com/ja-jp/iaas/Content/Network/Tasks/Overview_of_VCNs_and_Subnets.htm) や [Compute](https://docs.oracle.com/ja-jp/iaas/Content/Compute/Concepts/computeoverview.htm) などの構築したリソースに関する情報を、terraform.tfstate というファイルに保管します。このファイルは通称「state ファイル」と呼ばれます。

Terraform で構築済みのリソースの変更や削除を行うときには、その時点でどのようなリソースがあるのかを知らなければなりません[^1]。すべてのリソースを毎回クラウドにアクセスして調べてしまうと、システム負荷が高く時間もかかります。そこで、作ったリソースの情報を一元管理するためのテキストファイルを作っておきます。それが「state ファイル」です。このファイルがあることで、実際のリソースをすべて検索しなくてよくなるのです。

:::note warn
このファイルは JSON 形式で記述されており、とても複雑な書式となっています。このため、**手作業で編集することは非推奨**とされています。
:::

# OCI で手動構築したリソースを Terraform 管理下に取り込む方法

おすすめ順に記載しています。

## 方法1: import ブロック

Terraform 1.5 から、import ブロックが利用可能です。方法3 の terraform import に代わるもので、より安全に利用できます[^3]。

https://developer.hashicorp.com/terraform/language/import

### 使い方

まず、以下のような VCN を手作業で構築済みであったとします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734734/ff5e5a25-f9dd-4e48-069b-6023d1203e89.png)

OCI における一意なリソース識別子である OCID は `ocid1.vcn.oc1.ap-tokyo-1.amaaaaaaicukjoqawr3vxtz4cboil7kfm4csgtkfmm4wcl3ug77qz5ttmmua` です。

これに対応して、以下のような Terraform コードを書きます。import ブロックが登場していることがわかります。

```tf:main.tf:
variable "tenancy_ocid" {}
variable "region" {}

provider "oci" {
  tenancy_ocid     = var.tenancy_ocid
  region           = var.region
}

import {
  to = oci_core_vcn.vcn
  id = "ocid1.vcn.oc1.ap-tokyo-1.amaaaaaaicukjoqawr3vxtz4cboil7kfm4csgtkfmm4wcl3ug77qz5ttmmua"
}

resource "oci_core_vcn" "vcn" {
}
```

`to` には 「インポート先となるTerraformコード上のリソース識別子」を、`id` には「インポート対象のOCIのリソース識別子」を指定しましょう。 

:::note info
ほとんどのリソースでは `id` に OCID を指定しますが、例外もあります。Terraform のリソースごとのドキュメントの「Import」という項目に記載されているので、あらかじめ確認してからご利用ください（参考：[oci_core_vcn リソースの Import](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/core_vcn#import)）
:::

Resource Manager では、Web コンソールへのサインインによって認証が担保されています。ユーザーや API キーなどの認証情報を指定する必要がなく、お手軽ですね。また、`tenancy_ocid` や `region` などは、Resource Manager によって値が自動的に設定されます[^4]。

このコードを Resource Manager のスタックに登録して、plan ジョブを実行しましょう。

<details><summary>planジョブの結果</summary>

```:planジョブの結果
Getting providers from registry and/or custom terraform providers

Initializing provider plugins...
- Reusing previous version of hashicorp/oci from the dependency lock file
- Installing hashicorp/oci v6.11.0...
- Installed hashicorp/oci v6.11.0 (unauthenticated)

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.
oci_core_vcn.vcn: Preparing import... [id=ocid1.vcn.oc1.ap-tokyo-1.amaaaaaaicukjoqawr3vxtz4cboil7kfm4csgtkfmm4wcl3ug77qz5ttmmua]
oci_core_vcn.vcn: Refreshing state... [id=ocid1.vcn.oc1.ap-tokyo-1.amaaaaaaicukjoqawr3vxtz4cboil7kfm4csgtkfmm4wcl3ug77qz5ttmmua]

Terraform will perform the following actions:

  # oci_core_vcn.vcn will be imported
    resource "oci_core_vcn" "vcn" {
        byoipv6cidr_blocks       = []
        cidr_block               = "10.0.0.0/16"
        cidr_blocks              = [
            "10.0.0.0/16",
        ]
        compartment_id           = "ocid1.compartment.oc1..aaaaaaaalk6ocpvdh5vg5ftqougunpjsyejgjruzjpel23qnv5frblvcc4tq"
        default_dhcp_options_id  = "ocid1.dhcpoptions.oc1.ap-tokyo-1.aaaaaaaawelaningxeipvftrgjpuzd5nvvis2xv4ihhpichkjii367rxrpea"
        default_route_table_id   = "ocid1.routetable.oc1.ap-tokyo-1.aaaaaaaacah5yf52nwrwqsmllcdh4owd7jrin2e36ocentado7djaeay6lvq"
        default_security_list_id = "ocid1.securitylist.oc1.ap-tokyo-1.aaaaaaaatv4hmgi6dccrea4qy4h7cuggkut6lz5zcfvatepv7mt3rlknuu4a"
        defined_tags             = {
            "Oracle-Tags.CreatedBy" = "xxxxxxxxxxx"
            "Oracle-Tags.CreatedOn" = "2024-09-29T12:19:31.707Z"
        }
        display_name             = "sample-vcn"
        dns_label                = "samplevcn"
        freeform_tags            = {}
        id                       = "ocid1.vcn.oc1.ap-tokyo-1.amaaaaaaicukjoqawr3vxtz4cboil7kfm4csgtkfmm4wcl3ug77qz5ttmmua"
        ipv6cidr_blocks          = []
        ipv6private_cidr_blocks  = []
        is_ipv6enabled           = false
        state                    = "AVAILABLE"
        time_created             = "2024-09-29 12:19:31.785 +0000 UTC"
        vcn_domain_name          = "samplevcn.oraclevcn.com"
    }

Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + vcn_id = "ocid1.vcn.oc1.ap-tokyo-1.amaaaaaaicukjoqawr3vxtz4cboil7kfm4csgtkfmm4wcl3ug77qz5ttmmua"

You can apply this plan to save these new output values to the Terraform
state, without changing any real infrastructure.

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: <path hidden>

To perform exactly these actions, run the following command to apply:
    terraform apply "<path hidden>"
```

</details>

うまくいったので、続けて apply ジョブも実行します。

<details><summary>applyジョブの結果</summary>

```:applyジョブの結果
Getting providers from registry and/or custom terraform providers

Initializing provider plugins...
- Reusing previous version of hashicorp/oci from the dependency lock file
- Installing hashicorp/oci v6.11.0...
- Installed hashicorp/oci v6.11.0 (unauthenticated)

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.
oci_core_vcn.vcn: Importing... [id=ocid1.vcn.oc1.ap-tokyo-1.amaaaaaaicukjoqawr3vxtz4cboil7kfm4csgtkfmm4wcl3ug77qz5ttmmua]
oci_core_vcn.vcn: Import complete [id=ocid1.vcn.oc1.ap-tokyo-1.amaaaaaaicukjoqawr3vxtz4cboil7kfm4csgtkfmm4wcl3ug77qz5ttmmua]

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

</details>

その後、import ブロックは消してしまっても構いません（残しておいても問題ありません）。

### 利点と欠点

* Resource Manager だけでお手軽 state ファイルの取り込みが実施できます。
* Terraform バージョン 1.5 以上に対応。それ未満のバージョンでは実施できません。

## 方法2: Resource Manager のリソース検出機能

Resource Manager には [リソース検出](https://docs.oracle.com/ja-jp/iaas/Content/ResourceManager/Concepts/resource-discovery.htm) 機能があります。これは、手作業で構築済みのリソースをまるごと Resource Manager に取り込む機能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734734/42db1ec8-4151-1dd4-f34b-aec8860a4cdd.png)

### 使い方

Resource Manager のスタックを新規作成する際に「Existing Compartment」を選択して、指定のサービスを選択してください。そのサービスに含まれるリソースをコード化して、さらに「state ファイル」も自動生成してくれます。

ただし、あまりきれいなコードにはなりません。たとえば、Terraform を使って 100 台の Compute を作成するとき、普通は [for_each](https://developer.hashicorp.com/terraform/language/meta-arguments/for_each) などを使い、同じような実装を何度も繰り返さないように工夫します。しかし、この方法で自動作成されたコードは、単に resource 句を 100 回繰り返し記述するようなコードとなります。

### 利点と欠点

* Resource Manager 側で対応していないサービスがわりと多いです
* Terraform コードが自動生成されてしまうため、「state ファイル」**だけ**を取り込むことはできません。
* Terraform コードの実装方法がわからないときに非常に便利です（この記事の趣旨とずれますが…）。

## 方法3: Terraform Import

Terraform には、`terraform import` という、そのためのコマンドがあります。

https://developer.hashicorp.com/terraform/cli/commands/import

### 使い方

方法1と同じく、OCID が `ocid1.vcn.oc1.ap-tokyo-1.amaaaaaaicukjoqawr3vxtz4cboil7kfm4csgtkfmm4wcl3ug77qz5ttmmua` である VCN を構築済みであるものとします。

これに対応して、以下のような Terraform コードを書きます。resource ブロックは空で構いません。

```tf:main.tf:
terraform {
  required_providers {
    oci = {
      source = "oracle/oci"
    }
  }
}

provider "oci" {
  region           = "ap-tokyo-1"
  tenancy_ocid     = "ocid1.tenancy.oc1..xxxxxxx"
  user_ocid        = "ocid1.user.oc1..xxxxxxx"
  private_key_path = "/path/to/terraform-key.pem"
  fingerprint      = "xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx"
}

resource "oci_core_vcn" "vcn" {
}
```

terraform import は以下のシンタックスで入力します。

```
terraform import <インポート先となるTerraformコード上のリソース識別子> <インポート対象のOCIのリソース識別子>
```

Terraformコード上のリソース識別子は、`oci_core_vcn.vcn` で、OCIのリソース識別子は OCID の `ocid1.vcn.oc1.ap-tokyo-1.amaaaaaaicukjoqawr3vxtz4cboil7kfm4csgtkfmm4wcl3ug77qz5ttmmua` ということになります[^5]。

:::note info
ほとんどのリソースでは `id` に OCID を指定しますが、例外もあります。Terraform のリソースごとのドキュメントの「Import」という項目に記載されているので、あらかじめ確認してからご利用ください（参考：[oci_core_vcn リソースの Import](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/core_vcn#import)）
:::

### 実行サンプル

<details><summary>コマンド実行結果を開く</summary>

```shell-session
$ terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of oracle/oci from the dependency lock file
- Using previously-installed oracle/oci v6.11.0

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 import oci_core_vcn.vcn ocid1.vcn.oc1.ap-tokyo-1.amaaaaaaicukjoqawr3vxtz4cboil7kfm4csgtkfmm4wcl3ug77qz5ttmmua
oci_core_vcn.vcn: Importing from ID "ocid1.vcn.oc1.ap-tokyo-1.amaaaaaaicukjoqawr3vxtz4cboil7kfm4csgtkfmm4wcl3ug77qz5ttmmua"...
oci_core_vcn.vcn: Import prepared!
  Prepared oci_core_vcn for import
oci_core_vcn.vcn: Refreshing state... [id=ocid1.vcn.oc1.ap-tokyo-1.amaaaaaaicukjoqawr3vxtz4cboil7kfm4csgtkfmm4wcl3ug77qz5ttmmua]      

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
```

</details>

### 利点と欠点

* Terraform だけで完結する方法であり、クラウドに関係なく利用できます。
* Terraform バージョン 1.5 未満でも実施できます
* Resource Manager では terraform import コマンドに対応していないため、利用できません。



# 最後に

あなたも楽しい import ライフを！

[^1]: Infrastructure as Code といいます。本題と逸れる上に長くなるので割愛しますが、詳しく知りたい方は [What is Infrastructure as Code with Terraform?](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code) をご参照ください。
[^2]: この考え方を Cloud Agnostic といいます。
[^3]: import コマンドとは異なり、plan コマンドによるドライランを行うことで事前の挙動確認ができます。
[^4]: [リソース・マネージャーの変数](https://docs.oracle.com/ja-jp/iaas/Content/ResourceManager/Concepts/terraformconfigresourcemanager.htm#configvar) を参照