🐡

Oracle Cloud Infrastructure を Terraform 管理できるようにするまで

に公開

schnell と申します. 現在 SaaS 系企業で MLOps を推進するエンジニアとして勤務しています.
本記事では、自主学習の一環として取り組んでいる Oracle Cloud Infrastructure(以下、OCI)の環境を Terraform で管理するための手順について、備忘録として共有いたします.

OCI は AWS や GCP といった有名 IaaS サービスと比べると利用社が少なく地味ですが無料枠が非常に充実しており
例えば、AWSのEC2に相当するコンピューティングリソースでは Arm 版を選択することで RAM 24GB、4 OPCU のインスタンスが無料で利用できます.

このリソースは最大 4 つの VM に分割可能で、無料枠の範疇で RAM 6GB、1 OCPU のインスタンスを4台作成することができ, 簡易的な kubernetes クラスタでさえも運用することが十分に可能なスペックとなっています.

尚、本記事ではおそらく最難関であろう OCI にサインアップするというプロセスについては省略します。

実行環境について

macOS Sequoia で実行確認をしております.

❯ sw_vers
ProductName:		macOS
ProductVersion:		15.3
BuildVersion:		24D2059

terraform は 1.12.0 を利用します.

❯ terraform -v
Terraform v1.12.0
on darwin_arm64

API キーの追加

プログラム (terraform) から OCI にアクセスするために API キーの登録を行います.
このページにとんで「APIキーの追加」を選択してください。

スクショのような画面が出たら「秘密キーをダウンロード」を選択して秘密キーをダウンロードしてください。
ダウンロードしたファイルは ~/.oci/oci.pem として保存してください。

ダウンロードが完了したらページ下部の「追加」ボタンを押してください。
追加すると、構成ファイルのプレビューというポップアップが表示され tenancyregion が書かれた認証用の設定ファイルが表示されるのでコピーして ~/.oci/config に保存してください。
この時、key_file というフィールドには先ほど保存した ~/.oci/oci.pem を設定してください。

key_file=<path to your private keyfile> # TODO

以上で API キー関連の設定は完了です。

terraform のプロバイダー設定

次に以下のような hcl コードを作成してください.

provider.tf
terraform {
  required_providers {
    oci = {
      source = "oracle/oci"
    }
  }
}

provider "oci" {
  config_file_profile = "DEFAULT"
}

terraform init コマンドを実行して以下のように表示されたら設定完了です.

❯ terraform init
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of oracle/oci from the dependency lock file
- Using previously-installed oracle/oci v4.123.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 のステートファイル管理を OCI のバケットで行う

terraform=1.12.0 からステートファイル管理のバックエンドとして OCI バケットが標準としてサポートされました。
コンソールからバケットを作成して以下のように backend の設定を追加してください。

provider.tf
terraform {
  required_providers {
    oci = {
      source = "oracle/oci"
    }
  }
  backend "oci" {
    bucket    = <バケット名>
    namespace = <namespace>
    region    = <リージョン>
    key       = "terraform.tfstate"
  }
}

可用性ドメインを出力してみる

可用性ドメインを取得するためのシンプルな hcl を作成しました.

terraform {
  required_providers {
    oci = {
      source = "oracle/oci"
    }
  }
  backend "oci" {
    bucket    = <バケット名>
    namespace = <namespace>
    region    = <リージョン>
    key       = "terraform.tfstate"
  }
}

provider "oci" {
  config_file_profile = "DEFAULT"
}

locals {
  oci_config_content = file("~/.oci/config")
  tenancy_ocid       = regex("(?m)^tenancy\\s*=\\s*(.*)$", local.oci_config_content)[0]
}

data "oci_identity_availability_domains" "ads" {
  compartment_id = local.tenancy_ocid
}

output "names" {
  value = [for ad in data.oci_identity_availability_domains.ads.availability_domains : ad.name]
}
実行結果
❯ terraform apply
data.oci_identity_availability_domains.ads: Reading...
data.oci_identity_availability_domains.ads: Read complete after 0s [id=IdentityAvailabilityDomainsDataSource-1575237231]

Changes to Outputs:
  + names = [
      + "ewah:AP-OSAKA-1-AD-1",
    ]

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

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


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

Outputs:

names = [
  "ewah:AP-OSAKA-1-AD-1",
]

(おまけ) nginx サーバーをシンプルな構成でデプロイしてみる

最小限のシンプルな構成で nginx サーバーをデプロイしてみます.
iptables -I INPUT -p TCP --dport 80 -j ACCEPT を実行している点がポイントで、OCI ではデフォルトで OS のファイアウォールが設定されているため明示的に許可する必要があります.
terraform apply すると出力される URL にアクセスすると nignx サーバーにアクセスできることが確認できます.

また、ssh の設定もしているので ssh ubuntu@<ip addr> -i ~/.ssh/<key name> でインスタンスに ssh で接続することもできます.

terraform ファイル
terraform {
  required_providers {
    oci = {
      source = "oracle/oci"
    }
  }
}

provider "oci" {
  config_file_profile = "DEFAULT"
}

locals {
  oci_config_content = file("~/.oci/config")
  tenancy_ocid       = regex("(?m)^tenancy\\s*=\\s*(.*)$", local.oci_config_content)[0]
}

# コンパートメントを作成
resource "oci_identity_compartment" "this" {
  compartment_id = local.tenancy_ocid
  name           = "terraform"
  description    = "Terraform compartment"
}

# 可用性ドメインを取得
data "oci_identity_availability_domains" "this" {
  compartment_id = local.tenancy_ocid
}

# VCN を作成
resource "oci_core_vcn" "this" {
  cidr_block     = "10.0.0.0/16"
  compartment_id = oci_identity_compartment.this.id
  display_name   = "nginx-vcn"
}

# IGW を作成
resource "oci_core_internet_gateway" "this" {
  compartment_id = oci_identity_compartment.this.id
  display_name   = "nginx-igw"
  vcn_id         = oci_core_vcn.this.id
}

# ルートテーブルを作成
resource "oci_core_route_table" "this" {
  compartment_id = oci_identity_compartment.this.id
  vcn_id         = oci_core_vcn.this.id
  display_name   = "nginx-rt"

  route_rules {
    destination       = "0.0.0.0/0"
    network_entity_id = oci_core_internet_gateway.this.id
  }
}

# サブネットを作成
resource "oci_core_subnet" "this" {
  cidr_block        = "10.0.1.0/24"
  compartment_id    = oci_identity_compartment.this.id
  vcn_id            = oci_core_vcn.this.id
  route_table_id    = oci_core_route_table.this.id
  security_list_ids = [oci_core_security_list.nginx_security_list.id]
  display_name      = "nginx-subnet"
}

# セキュリティリストの更新
resource "oci_core_security_list" "nginx_security_list" {
  compartment_id = oci_identity_compartment.this.id
  vcn_id         = oci_core_vcn.this.id
  display_name   = "nginx-security-list"

  egress_security_rules {
    destination = "0.0.0.0/0"
    protocol    = "all"
  }

  ingress_security_rules {
    protocol = "6" # TCP
    source   = "0.0.0.0/0"

    tcp_options {
      max = "22"
      min = "22"
    }
  }

  ingress_security_rules {
    protocol = "6" # TCP
    source   = "0.0.0.0/0"
    tcp_options {
      max = "80"
      min = "80"
    }
  }
}

# Compute インスタンスを作成
resource "oci_core_instance" "nginx_instance" {
  availability_domain = data.oci_identity_availability_domains.this.availability_domains[0].name
  compartment_id      = oci_identity_compartment.this.id
  shape               = "VM.Standard.A1.Flex"

  shape_config {
    ocpus         = 1
    memory_in_gbs = 6
  }

  create_vnic_details {
    subnet_id        = oci_core_subnet.this.id
    assign_public_ip = true
  }

  source_details {
    source_type = "image"
    source_id   = data.oci_core_images.ubuntu_image.images[0].id
  }

  metadata = {
    ssh_authorized_keys = file("~/.ssh/oci-nginx.pub") # 公開鍵は事前に作成しておく
    user_data = base64encode(<<-EOF
    #!/bin/bash
    apt-get update
    apt-get install -y nginx
    systemctl start nginx
    systemctl enable nginx
    iptables -I INPUT -p TCP --dport 80 -j ACCEPT
    EOF
    )
  }
}

data "oci_core_images" "ubuntu_image" {
  compartment_id           = oci_identity_compartment.this.id
  operating_system         = "Canonical Ubuntu"
  operating_system_version = "24.04"
  shape                    = "VM.Standard.A1.Flex"
}

output "public_ip" {
  value = "http://${oci_core_instance.nginx_instance.public_ip}"
}

参考記事

Discussion