Terraformで構築するOCIのAPI Gatewayとロードバランサ連携

## 1. はじめに
Oracle Cloud Infrastructure（OCI）のAPI Gatewayを利用することで、ロードバランサ（Load Balancer）を通してコンピュートインスタンス（Compute Instance）のWebサーバ（Apache）を返す環境を作成したので、その内容について以下を中心に記載しています。

 - API Gatewayやロードバランサ、コンピュートインスタンスの役割と連携
 - Terraformを使用したインフラ構築コード

## 2. 全体アーキテクチャ
### 2.1 各サービスの役割
**API Gateway**: 外部からのHTTPリクエストを受け取り、適切なバックエンド（この場合はLoad Balancer）に転送します。
**Load Balance**r: API Gatewayからのリクエストを受け、指定されたバックエンド（Computeインスタンス）に負荷分散します。
**Compute Instance**: HelloWorldページを提供するシンプルなWebサーバ（Apache）として動作します。

### 2.2 アーキテクチャ図
以下の構成でリクエストが処理されます。
![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322881/72ec523a-8e79-76c5-14d9-ebe3baab68e1.png)




## 3. Terraformコード
Terraformを使用して、以下のリソースを構築します。

 - VCNとサブネット
 - Computeインスタンス
 - Load Balancer
 - API Gateway

### 3.1 VCNとサブネット
ネットワークの基盤となるVCN（仮想クラウドネットワーク）と、その内部に作成するサブネットを定義します。
```tf
resource "oci_core_vcn" "my_vcn" {
  compartment_id = var.compartment_ocid
  cidr_block     = "10.0.0.0/16"
  display_name   = "my_vcn"
  dns_label      = "myvcn"
}

resource "oci_core_subnet" "my_subnet" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.my_vcn.id
  cidr_block     = "10.0.1.0/24"
  display_name   = "my_subnet"
  prohibit_public_ip_on_vnic = false
  route_table_id = oci_core_route_table.my_route_table.id
  dns_label      = "pub1"
}

resource "oci_core_network_security_group" "my_nsg" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.my_vcn.id
  display_name   = "my_nsg"
}

resource "oci_core_network_security_group_security_rule" "my_nsg_rule" {
  network_security_group_id = oci_core_network_security_group.my_nsg.id
  direction = "INGRESS"
  protocol  = "6"
  source    = "0.0.0.0/0"

  tcp_options {
    destination_port_range {
      min = 80
      max = 80
    }
  }
}

resource "oci_core_network_security_group_security_rule" "my_nsg_https_rule" {
  network_security_group_id = oci_core_network_security_group.my_nsg.id
  direction = "INGRESS"
  protocol  = "6"
  source    = "0.0.0.0/0"

  tcp_options {
    destination_port_range {
      min = 443
      max = 443
    }
  }
}

resource "oci_core_internet_gateway" "my_igw" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.my_vcn.id
  display_name   = "my_internet_gateway"
  enabled        = true
}

resource "oci_core_route_table" "my_route_table" {
  compartment_id = var.compartment_ocid
  vcn_id         = oci_core_vcn.my_vcn.id

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

### 3.2 Load Balancer
ロードバランサは本体の作成に以下の設定を含んで作成しています。

**バックエンドセット**: Computeインスタンスを登録。
**ヘルスチェック**: HTTPプロトコルでバックエンドの正常性を監視。

```tf
resource "oci_load_balancer_load_balancer" "my_lb" {
  compartment_id = var.compartment_ocid
  display_name   = "my_lb"
  shape          = "100Mbps"
  subnet_ids     = [oci_core_subnet.my_subnet.id]
  network_security_group_ids = [oci_core_network_security_group.my_nsg.id] 
}

resource "oci_load_balancer_backend_set" "my_backend_set" {
  load_balancer_id = oci_load_balancer_load_balancer.my_lb.id
  name             = "my_backend_set"
  policy           = "ROUND_ROBIN"

  health_checker {
    protocol = "HTTP"
    url_path = "/"
    port     = 80
  }
}

resource "oci_load_balancer_backend" "my_backend" {
  load_balancer_id   = oci_load_balancer_load_balancer.my_lb.id
  backendset_name    = oci_load_balancer_backend_set.my_backend_set.name
  ip_address         = oci_core_instance.my_instance.private_ip
  port               = 80
}

resource "oci_load_balancer_listener" "my_listener" {
  load_balancer_id = oci_load_balancer_load_balancer.my_lb.id
  name             = "my_listener"
  protocol         = "HTTP"
  port             = 80
  default_backend_set_name = oci_load_balancer_backend_set.my_backend_set.name
}
```

### 3.3API Gatewayとデプロイメント
API Gatewayの作成と、その上で動作するデプロイメント（APIのルーティングとバックエンドの設定）の定義について作成しています。

※デプロイメントの役割
デプロイメントは、API Gatewayに設定される「ルートマップ」として機能します。具体的には以下を定義します：

**リクエストのルーティング**: 特定のパス（例: /helloworld）やHTTPメソッド（GET, POSTなど）に基づいてリクエストをどのバックエンドに転送するかを指定します。
**バックエンドの接続情報**: リクエストを処理するバックエンド（ここではロードバランサ）のURLを指定します。
これにより、API Gatewayが適切なバックエンドリソースにリクエストをルーティングし、クライアントに応答を返す仕組みを構築できます。

```tf
resource "oci_apigateway_gateway" "my_api_gateway" {
  compartment_id = var.compartment_ocid
  display_name   = "my_api_gateway"
  endpoint_type  = "PUBLIC"
  subnet_id      = oci_core_subnet.my_subnet.id
  network_security_group_ids = [oci_core_network_security_group.my_nsg.id] 
}

resource "oci_apigateway_deployment" "my_api_deployment" {
  compartment_id = var.compartment_ocid
  display_name   = "my_api_deployment"
  gateway_id     = oci_apigateway_gateway.my_api_gateway.id
  path_prefix    = "/helloworld"

  specification {
    routes {
      path = "/"
      methods = ["GET"]
      backend {
        type = "HTTP_BACKEND"
        url  = "http://${oci_load_balancer_load_balancer.my_lb.ip_address_details[0].ip_address}/"
      }
    }
  }
}
```
path_prefixを"/helloworld/"のように最後に/を入れたら以下のエラーが出ました。最後に/はいれたら内容です。
```
Error: 400-InvalidParameter, Invalid pathPrefix: must not end with '/'
Suggestion: Please update the parameter(s) in the Terraform config as per error message Invalid pathPrefix: must not end with '/'
```

### 3.4 Computeインスタンス
以下の設定でComputeインスタンスを作成します。

**Apacheを自動インストール**: user_data を使い、インスタンス起動時にApacheをインストールしてHelloWorldページを設定します。
**firewalldを無効化**: Oracle Linuxでデフォルト有効になっているfirewalldを停止しています。
```tf
resource "oci_core_instance" "my_instance" {
  compartment_id       = var.compartment_ocid
  availability_domain  = "Vior:AP-TOKYO-1-AD-1"
  shape                = "VM.Standard.A1.Flex"
  display_name         = "my_compute_instance"

  create_vnic_details {
    subnet_id = oci_core_subnet.my_subnet.id
    assign_public_ip  = false
    nsg_ids   = [oci_core_network_security_group.my_nsg.id] 
  }

  source_details {
    source_type = "image"
    source_id   = "ocid1.image.oc1.ap-tokyo-1.aaaaaaaa5bee4ivdv6ymoykdziexp3hvaapltwqhiklcbszv3bno4gbyncpq"
  }

  shape_config {
    ocpus         = 4
    memory_in_gbs = 8
  }

  metadata = {
    ssh_authorized_keys = var.ssh_public_key
    user_data = base64encode(<<-EOF
      #!/bin/bash
      sudo yum install -y httpd
      echo '<html><body><h1>helloworld</h1></body></html>' > /var/www/html/index.html
      sudo systemctl start httpd
      sudo systemctl enable httpd
      sudo systemctl stop firewalld  # firewalldを停止
      sudo systemctl disable firewalld  # firewalldを無効化（自動起動をオフ）
      EOF
    )
  }
}
```

### 3.5 variables
Terraformでは、変数を利用することでコードの再利用性できます(今回は変数を1回しか利用してないので変数化しなくてもよかったのですが使ってみたいので)。

変数の説明
**ssh_public_key:** コンピュートインスタンスへのSSH接続に使用する公開鍵を指定します。
もしこちらを参考にするようなことがあれば、xxxxの箇所を実際に使用する公開鍵に置き換えてください。
**compartment_ocid:** リソースが作成されるOCIのコンパートメントを指定します。

```tf
variable "ssh_public_key" {
  description = "Public SSH key to be used for the compute instance."
  type        = string
  default     = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCvWlbXXXXXXXXXXXXXX"  # デフォルトのSSH公開鍵
}

variable "compartment_ocid" {
  default = "ocid1.compartment.oc1..aaaaaaaazlydmn3is6vsmgllckofon62npunr5shgemek35zdnai36fpa2ga"
}
```

### 3.6 provider.tf
OCIプロバイダを設定しています。
```tf
provider "oci" {
}
```

## 4. 動作確認
**APIエンドポイントの確認:** Terraformが出力するAPIエンドポイントを使ってブラウザからアクセスし、HelloWorldページが正しく表示されることを確認します。
![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322881/ac017801-0f7f-e82b-98d0-b7c53ffd05d0.png)


**LoadBalancerのIP確認:** ロードバランサーのIPアドレスを使って、直接アクセスした際の応答も確認します。
![005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322881/2dd03115-3aeb-69e1-5c96-18c4b150582e.png)


## 5. 作成されたリソースの確認
Terraformで作成したリソースがOCIコンソール上に正しく表示されているかを確認します。画面で設定値は確認できるもので、ポイントとなる箇所については文章補足しています。

### 5.1 API Gateway

**API Gatewayの設定確認**
API Gateway > APIゲートウェイ > ゲートウェイ > ゲートウェイの詳細 でゲートウェイのエンドポイントや紐付けられたデプロイメントを確認します。
![0052.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322881/66b20661-9218-1621-172d-e82793a52ca8.png)


**デプロイメントの詳細**
OCIコンソール > API Gateway > APIゲートウェイ > ゲートウェイ > ゲートウェイの詳細 > デプロイメントの詳細でデプロイメントのルーティング設定やバックエンド情報が正しいことを確認します。ここに表示されているホスト名がhttpアクセスするAPI情報です
![006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322881/082c26a0-5a62-1b28-2dfc-c6d1dcd33403.png)


### 5.2 LB

**LBとヘルスチェック**
OCIコンソール > ネットワーキング > ロード・バランサ > ロード・バランサ詳細 でヘルスチェックがOKとなっていることを確認します。
![007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322881/904a1646-6bb1-5a37-1d24-9c53f854aff4.png)

**バックエンドセット**
OCIコンソール > ロード・バランサ詳細 > バックエンド・セット でComputeインスタンスがバックエンドとして登録され、正常に動作していることを確認します。
![008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322881/00a2f428-3c1b-072a-cb97-cd571ec18411.png)

### 5.3 Compputeインスタンス
**IPアドレス確認**
OCIコンソール > コンピュート > インスタンス > インスタンスの詳細 でインスタンスのプライベートIPがロードバランサに登録されたIPと一致していることを確認します。
![009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322881/e0823a69-8578-fc08-79b5-ba743a271b4f.png)


## 6. 参考資料
Terraform公式ドキュメント
TerraformでOCIリソースを構築する際の詳細な設定や例は以下の公式ドキュメントを参照してみてください。

https://registry.terraform.io/providers/oracle/oci/latest/docs