【初心者向け】OCIでWebアプリのインフラ構築

## はじめに
本記事は、 **OCI(Oracle Cloud Infrastructure)** の練習として、簡単なタスク管理webアプリを作成した備忘録です。特にインフラの構築をメインに基礎的な内容を解説します。  対象読者は、**クラウド初心者** の方を想定しています。  
もし、以下のような悩みや興味があれば、この記事の内容が参考になるかもしれません。

- 「AWSやAzureを少し触ったことがあるけれど、OCIは初めて」
- 「クラウドのインフラ設計って、具体的にどう進めたらいいの？」
- 「OCIで何ができるのか、実例を通して学びたい！」  

本記事では、専門用語を解説しながら進めていくので、クラウド初心者の方でも無理なく読み進められると思います。初めてのOCI構築の参考にしていただければ幸いです。

## 目次
[1.OCIとは](#1-ociとは)  
[2.使用したサービス一覧](#2-使用したサービス一覧)  
[3.インフラ設計の概要](#3-インフラ設計の概要)  
[4.ネットワークとセキュリティ](#4-ネットワークとセキュリティ)  
[5.Oracle NoSQL Database](#5-oracle-nosql-database)  
[6.OCI Compute](#6-oci-compute)  
[7.ロード・バランサ](#7-ロードバランサ)  
[8.NoSQL Databaseへの接続](#8-nosql-databaseへの接続)  
[9.まとめ](#9-まとめ)  
[10.おわりに](#10-おわりに)

## 1. OCIとは
[OCI(Oracle Cloud Infrastructure)](https://www.oracle.com/jp/cloud/)は、Oracleが提供するパブリッククラウドプラットフォームで、AWSやAzureと並ぶクラウドサービスの一つです。コンピューティングリソースやデータベースサービスを提供し、エンタープライズ向けの高可用性、セキュリティ、コスト効率に優れた環境が特徴です。

## 2. 使用したサービス一覧
今回の構築には以下のOCIサービスを利用しました：

- **Virtual Cloud Network (VCN)**：OCI上で構築するプライベートな仮想ネットワーク
- **Compute**：OCI上で仮想サーバを提供するサービス
- **ロード・バランサ**：トラフィックを分散
- **NoSQL Database**：キーバリュー型のデータベース
- **サービス・ゲートウェイ**：[Oracle Service Network(OSN)](https://docs.oracle.com/ja-jp/iaas/Content/Network/Tasks/servicegateway.htm#oracle-services)への接続
- **NATゲートウェイ**：プライベートインスタンスからインターネットへの通信を提供
- **インターネット・ゲートウェイ**：インターネットからVCNへの通信を提供

*OSNはOCIの一部として提供される、クラウドサービスを使用するために接続するためのネットワークサービスです。OCIで提供されているNoSQL Databaseサービスは、OSNを通じてアクセスできます。

## 3. インフラ設計の概要
### 3.1 アーキテクチャ全体像  
以下のような構成でインフラを構築しました。
![構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3941898/190c69bc-f12f-ff3b-02d7-93d268cf8888.png)
- **Compute インスタンス(Web/APサーバ)**：Webアプリケーションの処理やAPIを実行します。高可用性のために同様のサーバを２つ作ってLoad Balancerで振り分ける構成にしてみました。
- **Load Balancer**：パブリックIPを持ち、インターネットからのリクエストを受け付け、2つのプライベートインスタンスに振り分けます。
- **NoSQL Database**：ユーザーデータとタスクデータを管理します。NoSQL Databaseはスキーマレスであり、データ構造を変更してもアプリケーションの動作に与える影響が少なく、柔軟にデータを扱うことができます。

### 3.2 設計上の注意点
- NoSQL Databaseに接続するインスタンスはプライベートサブネットに置く  
    - **OCIでは、OSN宛てのルートルールと、Internet Gateway宛てのルートルールを１つのルート表に書くことができません。** そのため、Service Gateway経由でOSNに接続する必要があるインスタンスはInternet Gateway宛てのルートルールが必要であるパブリックサブネットではなく、プライベートサブネットに置く必要があります。
(この仕様を知らずに、はじめはパブリックサブネットにインスタンスを作成したためつまずきました、、、)

## 4. ネットワークとセキュリティ  
はじめに、OCI上でネットワークとセキュリティの設計・構築を行います。この章では次のものを作成します。

- VCN(Virtual Cloud Network)
- インターネット・ゲートウェイ、NATゲートウェイ、サービス・ゲートウェイ
- ルート表
- セキュリティ・リスト
- パブリックサブネット、プライベートサブネット

### **4.1 VCNの作成**
[VCN(Virtual Cloud Network)](https://docs.oracle.com/ja-jp/iaas/Content/Network/Tasks/Overview_of_VCNs_and_Subnets.htm#Overview)はOCI上で作成する仮想ネットワークで、クラウド環境内のネットワークを細かく制御できます。  
OCIのコンソールにログインし、左上のメニューバーから`ネットワーキング`→`仮想クラウドネットワーク`を選択します。`VCNの作成`ボタンからVCNを作成できます。  
今回はVCNのCIDRブロックを **`10.0.0.0/16`** に設定しました。

### **4.2 インターネット・ゲートウェイ、NATゲートウェイ、サービス・ゲートウェイの作成**
作成したVCN上で、[インターネット・ゲートウェイ](https://docs.oracle.com/ja-jp/iaas/Content/Network/Tasks/managingIGs.htm#Internet_Gateway)、[NATゲートウェイ](https://docs.oracle.com/ja-jp/iaas/Content/Network/Tasks/NATgateway.htm#NAT_Gateway)、[サービス・ゲートウェイ](https://docs.oracle.com/ja-jp/iaas/Content/Network/Tasks/servicegateway.htm#overview)を作成します。
#### **インターネット・ゲートウェイ**
- パブリックサブネットに配置されたロードバランサは、インターネット・ゲートウェイを通じてインターネットに接続します。

#### **NATゲートウェイ**
- プライベートサブネット内のインスタンス（Web/APサーバ）は直接インターネットに接続できません。必要に応じてインターネットへのアウトバウンド通信を行うため、NATゲートウェイを利用します。
- この構成により、インターネットからプライベートインスタンスへの直接アクセスを防ぎ、セキュリティを確保しながら外部サービスへの通信が可能になります。

#### **サービス・ゲートウェイ**
- OCI内のリソースがインターネットを経由せず、直接Oracle Cloudの各種サービスにアクセスするためのゲートウェイです。
- サービス・ゲートウェイ作成時、宛先を`OCI xxx Object Storage`または`All xxx Services In Oracle Services Network`を選択できます。（xxxにはリージョン名が入ります。東京リージョンはNRT。）  
Object Storageは、AWS S3と同様のスケーラブルなストレージサービスです。
**今回はNoSQL Databaseに接続したいので、`All NRT Services In Oracle Services Network`を選択します。**

### 4.3 ルート表の作成
[ルート表](https://docs.oracle.com/ja-jp/iaas/Content/Network/Tasks/managingroutetables.htm#Route2)はネットワーク内の通信をどのゲートウェイやネットワークデバイスにルーティングするかを定義するための設定です。  
今回は、パブリックサブネット用とプライベートサブネット用にそれぞれ以下のルートルールを作成しました。

- **パブリックサブネットのルート表**

|ターゲット・タイプ|宛先CIDR|
|---|---|
|インターネット・ゲートウェイ| 0.0.0.0/0|

- **プライベートサブネットのルート表**

|ターゲット・タイプ|宛先CIDR/サービス|
|---|---|
|NATゲートウェイ| 0.0.0.0/0  |
|サービス・ゲートウェイ|All NRT Services In Oracle Services Network|

### 4.4 セキュリティ・リストの作成
[セキュリティ・リスト](https://docs.oracle.com/ja-jp/iaas/Content/Network/Concepts/securitylists.htm#Security_Lists)はVCN内でネットワークトラフィックを制御するためのファイアウォールルールのセットです。  
パブリックサブネット用とプライベートサブネット用にそれぞれ以下のセキュリティ・リストを作成しました。

- **パブリックサブネットのセキュリティ・リスト**
  - **イングレス・ルール**

  |ステートレス|ソース|IPプロトコル|ソースポート範囲|宛先ポート範囲|説明|
  |---|---|---|---|---|---|
  |いいえ|0.0.0.0/0|TCP|All|80|インターネットからのHTTPアクセスを許可|

  パブリックサブネットに配置されたロードバランサがHTTP（ポート80）でインターネットからのアクセスを受け入れるために設定されています。`0.0.0.0/0`はインターネット全体を指します。通信できるIPアドレスの範囲を制限したい場合は、この値を変えてください。

  - **エグレス・ルール**

  |ステートレス|ソース|IPプロトコル|ソースポート範囲|宛先ポート範囲|説明|
  |---|---|---|---|---|---|
  |いいえ|0.0.0.0/0|すべてのプロトコル|||全ての通信を許可|

  ロードバランサがバックエンドのインスタンスと通信するためのルールです。
  
- **プライベートサブネットのセキュリティ・リスト**
  - **イングレス・ルール**

  |ステートレス|ソース|IPプロトコル|ソースポート範囲|宛先ポート範囲|説明|
  |---|---|---|---|---|---|
  |いいえ|10.0.1.0/24(パブリックサブネットのCIDR)|TCP|All|80|パブリックサブネットからのHTTPアクセスを許可|
  |いいえ|0.0.0.0/0|TCP|All|22|インスタンスへのSSH接続用|

  1つ目のルールは、パブリックサブネット（後ほど10.0.1.0/24で作成）からのHTTPアクセス（ポート80）を許可しています。これにより、プライベートサブネット内のインスタンスがHTTPリクエストを受け取れるようになります。  
  2つ目のルールは、インターネットからのSSH接続を許可しています。ポート22はSSH通信に使用されるポートです。

  - **エグレス・ルール**

  |ステートレス|ソース|IPプロトコル|ソースポート範囲|宛先ポート範囲|説明|
  |---|---|---|---|---|---|
  |いいえ|0.0.0.0/0|すべてのプロトコル|||インターネットへの全ての通信を許可|

  プライベートサブネット内のインスタンスがインターネットに向けて通信できるようにするために設定されています。すべてのプロトコルが許可されており、インターネットへの通信を制限なく行える設定です。

### 4.5 パブリックサブネット、プライベートサブネットの作成
最後にパブリックサブネットとプライベートサブネットを作成します。CIDRブロックは次のように設定しました。
- パブリックサブネット: `10.0.1.0/24`
- プライベートサブネット: `10.0.11.0/24`、`10.0.12.0/24`  

ルート表とセキュリティ・リストは先ほど作成したものをそれぞれ選択します。

## 5. Oracle NoSQL Database
### 5.1 データベースの概要
本アプリケーションでは、ユーザー情報やタスク情報の管理に[Oracle NoSQL Database](https://docs.oracle.com/ja-jp/iaas/nosql-database/index.html)を使用しました。選定した理由は以下の通りです。

- **スキーマレス設計**: データの構造を事前に決める必要がないため、データの追加や変更が簡単にできる。例えば、ユーザー情報やタスクのフィールドが変わった場合でも、柔軟に対応可能。
  
- **OCIのネイティブサービス**: Oracle Cloud上で提供されるサービスのため、他のOCIサービスとの統合がスムーズ。

- **高いパフォーマンス**: 読み取りと書き込みの性能を容易にチューニングでき、大量のデータに対する高速なアクセスが求められるアプリケーションでも高いパフォーマンスを維持できる。

### 5.2 テーブル設計
左上のメニューバーから`データベース`→`Oracle NoSQL Database`→`表の作成`ボタンからNoSQL Dababaseを作成できます。今回は、以下の2つのテーブルを作成しました。  
読取り容量、書込み容量については[容量の見積もり](https://docs.oracle.com/ja/cloud/paas/nosql-cloud/fkdyw/index.html#FKDYW-GUID-96D27EE8-A8D6-464C-B244-22570FB7DC0C)を参照してください。


- **ユーザー管理テーブル**

ユーザー情報を管理するテーブルです。各ユーザーのログイン認証に必要なメールアドレスとパスワードを格納します。

|容量モード|読取り容量|書込み容量|ディスク・ストレージ(GB)|
|---|---|---|---|
|プロビジョニング容量|10|10|10|

|列名|タイプ|説明|
|---|---|---|
|email (主キー)|String|ユーザーのメールアドレス（ユニークな識別子）|
|password|String|ユーザーのログインパスワード|

- **タスク管理テーブル**

ユーザーが作成するタスク情報を管理するテーブルです。各タスクに一意のID（task_id）が割り当てられ、タスクのステータスや期限も追跡します。

|容量モード|読取り容量|書込み容量|ディスク・ストレージ(GB)|
|---|---|---|---|
|プロビジョニング容量|10|10|10|

|列名|タイプ|説明|
|---|---|---|
|task_id (主キー)|String|タスクを一意に識別するID|
|user_email|String|タスクの所有者であるユーザーのメールアドレス|
|task_title|String|タスクのタイトルや名前|
|task_status|String|タスクの現在のステータス（例: "未完了", "完了"）|
|due_date|Timestamp|タスクの期限日|

## 6. OCI Compute
### 6.1 Computeインスタンスの作成
左上のメニューバーから`コンピュート`→`インスタンス`→`インスタンスの作成`ボタンからComputeインスタンスを作成できます。  
以下のような設定で作成しました。（明記していない部分はデフォルトのままです。）
- **配置**  
  - 可用性ドメイン：`AD1`
  - 容量タイプ：`オンデマンド容量`
- **イメージとシェイプ**
  - イメージ：`Oracle Linux 8`
  - Shape：`VM.Standard.E5.Flex`
- **プライマリVNIC情報**
  - プライマリ・ネットワーク：`既存の仮想クラウド・ネットワークを選択`→作成したVCNを選択
  - サブネット：`既存のサブネットを選択`→作成したプライベートサブネットを選択
  - パブリックIPv4アドレス：自動割り当てのチェックを外す
- **SSHキーの追加**
  - 既存のキーペアを利用する場合：`公開キー・ファイルのアップロード`
  - キーペアを生成する場合：`キー・ペアを自動で生成`を選択し、`秘密キーの保存`をクリックしてダウンロード(再表示されないため注意してください)

必要な設定を終えたら`作成`をクリックしてインスタンスを立ち上げます。 

### 6.2 Cloud ShellでインスタンスにSSHで接続
コンソール画面右上の`開発者ツール`→`Cloud Shell`を選択します。  
Cloud Shell起動後、`ネットワーク`→`エフェメラル・プライベート・ネットワーク設定`を選択し、インスタンス作成時に設定したVCN、プライベートサブネットを選択します。  
右上の`Cloud Shellのメニュー(歯車アイコン)`→`アップロード`を選択し、先ほど保存した秘密キーをアップロードします。

- アップロードされていることを確認

```bash:bash
# ファイル一覧を表示
ll
　
# 出力例
-rw-r--r-- 1 oci_user oci 1675 Dec 1 12:34 ssh-key-2024-12-01.key
```

- 秘密キーのパーミッション変更
```bash:bash
# パーミッションを400に変更
chmod 400 ssh-key-2024-12-01.key

# パーミッション確認
ll

# 出力例
-r-------- 1 oci_user oci 1675 Dec 1 12:34 ssh-key-2024-12-01.key

```

- インスタンスにSSHで接続
```bash:bash
ssh -i ssh-key-2024-12-01.key opc@<インスタンスのプライベートIP>
```

### 6.3 ミドルウェアのインストール
ComputeインスタンスにApache、Python、Flaskをインストールします。  
以下、Oracle Linuxの場合のコマンド例です。
- Apacheのインストール  
Apache HTTP Serverをインストールし、サービスを起動します。
```bash:bash
# Apacheをインストール
sudo yum install httpd
# Apacheサービスを起動
sudo systemctl start httpd
# サーバ再起動後もApacheが自動的に起動するよう設定(任意)
sudo systemctl enable httpd
```

- Pythonのインストール  
今回Webアプリケーションフレームワークとして使用する[Flask](https://msiz07-flask-docs-ja.readthedocs.io/)はPyhton3.7以降をサポートしています。必要に応じてPythonをインストールします。(自分の環境ではデフォルトでPython3.6がインストールされていました)
```bash:bash
# Python3がインストール済みか確認
python3 --version

# インストールされていない場合
sudo yum install python3.12
# Python3.12をデフォルトのPython3とするシンボリックリンクの作成
sudo ln -sf /usr/bin/python3.12 /usr/bin/python3
# Python3のバージョン確認
python3 --version
```

- Flaskのインストール  
[Flask](https://msiz07-flask-docs-ja.readthedocs.io/)はPythonフレームワークの一つです。開発プロセスで外部ライブラリを使用しないため、軽量であるのが特徴です。
```bash:bash
# Python3.12にpipがインストールされていない場合、または古い場合に最新のpipをインストール
sudo python3.12 -m ensurepip --upgrade
# pipを最新バージョンにアップグレード
sudo python3.12 -m pip install --upgrade pip
# シンボリックリンクを作成して pip3 を使えるようにする
sudo ln -sf /usr/local/bin/pip3.12 /usr/bin/pip3
# Flaskのインストール
sudo pip3 install Flask
```

## 7. ロード・バランサ
[ロード・バランサ](https://docs.oracle.com/ja-jp/iaas/Content/Balance/Concepts/balanceoverview.htm#Overview_of_Load_Balancing)はバックエンドインスタンスの健全性を監視します。今回は`/health`エンドポイント作成して、インスタンスが正常であることを確認する方法を記載します。

### 7.1 Flaskアプリケーションでの/healthエンドポイント作成
以下のコードは、簡単な健康状態を返すFlaskアプリケーションの例です。
```python:app.py
from flask import Flask

app = Flask(__name__)

# /health エンドポイントを追加
@app.route('/health')
def health_check():
    return "OK", 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80)
```

### 7.2 ロード・バランサの作成
左上のメニューバーから、`ネットワーキング`→`ロード・バランサ`→`ロードバランサの作成`ボタンをクリックします。  
以下のような設定で作成しました。（明記していない部分はデフォルトのままです。）

- **詳細の追加**
  - VCN：作成したVCNを選択
  - サブネット：作成したパブリックサブネットを選択
- **バックエンドの選択**
  - インスタンスの選択：作成したComputeインスタンスを追加
  - ヘルス・チェック・ポリシーの指定
    - プロトコル：`HTTP`
    - 間隔(ミリ秒)：`5000`
    - タイムアウト(ミリ秒)：`3000`
    - 再試行回数：`3`
    - ステータス・コード：`200`
    - URLパス：`/health`
- **リスナーの構成**
  - リスナーで処理するトラフィックのタイプを指定します：`HTTP`

必要な設定を終えたら`作成`をクリックしてロード・バランサを作成します。  

### 7.3 動作確認
次のコマンドでapp.pyを実行します
```bash:bash
sudo python3 app.py
```
正常に動作していれば、作成したロード・バランサのヘルスが`OK`になるはずです。(OKになるまで30秒程度かかりました)

## 8. NoSQL Databaseへの接続
### 8.1 Computeインスタンスにポリシーを付与
 NoSQL Databaseにアクセスするためには、OCIの**Identity and Access Management (IAM) ポリシー**を使用して、Computeインスタンスに適切なアクセス権限を付与する必要があります。これにより、ComputeインスタンスからNoSQL Databaseへのアクセスが許可されます。  
 IAMポリシーを付与するには [動的グループ](https://docs.oracle.com/ja-jp/iaas/Content/Identity/Tasks/managingdynamicgroups.htm#About)を作成する必要があります。動的グループは、特定の条件に合致するインスタンスをグループ化する機能です。これにより、個別のインスタンスを指定せずにIAMポリシーを適用できます。

- **動的グループの作成**  
左上のメニューバーから`アイデンティティとセキュリティ`→`ドメイン`→`Defaultドメイン`→`動的グループ`→`動的グループの作成`ボタンをクリックします。  
次のような一致ルールを設定し、動的グループを作成しました。
```
Any {instance.id = '<インスタンスのOCID>', instance.compartment.id = '<インスタンスのOCID>'}
```

- **IAMポリシーを付与**  
左上のメニューバーから`アイデンティティとセキュリティ`→`アイデンティティ`→`ポリシー`→`ポリシーの作成`ボタンをクリックします。  
ポリシー・ビルダーで手動エディタを表示し、以下のポリシー構文を入力してポリシーを作成しました。
```
Allow dynamic-group <動的グループ名> to use nosql-family in compartment <コンパートメント名> where target.nosql-database.name='<NoSQL Database名>'
```

### 8.2 接続設定の記述
動的グループとポリシーの設定が完了したら、ComputeインスタンスからNoSQL Databaseに接続するためのコードを記述します。

- **必要なライブラリをインストール**  
NoSQL Databaseに接続するために、OCIのSDKを使用します。まず、OCI Python SDKをインストールします。
```bash:bash
sudo pip3 install oci
```
- **接続コードの例**
```python:app.py
import oci

# OCIの設定を読み込む
config = oci.config.from_file(file_location='/home/opc/.oci/config')

# NoSQL DBクライアントを作成
nosql_client = oci.nosql.NosqlClient(config)

# テーブル名やキーを指定してデータを読み書きしていく
```

## 9. まとめ
長くなりましたが、OCIでwebアプリ開発をする際のインフラ構築部分を紹介させていただきました。ここからはごりごりコードを書いていくフェーズですね。webアプリ開発編も後日時間があるときに投稿したいと思っています。  
本記事に関するご意見や改善点があれば、ぜひコメント欄で教えていただけると嬉しいです。

## 10. おわりに
本記事は、[NSSOL Advent Calendar 2024](https://qiita.com/advent-calendar/2024/nssol)の18日目の記事です。  
人前に出るのが苦手&文章を書くのが苦手な自分にとって記事を書くのはとても大きな挑戦でしたが、逃げなかった自分を褒めたいです。[5日目の610さんの記事](https://note.com/610_mto/n/nee83866b10eb)にもあるように、【やらない言い訳よりも、「やる言い訳」をつくる】ことを意識して、来年は数多くの挑戦に取り組む1年にしたいと思います。