アーキテクチャ大全とは
AWSでよく見るクラウドアーキテクチャをOCIで作りながら、OCIの基本的なアーキテクチャを学習してみようという企画。
コンテナアーキテクチャ =初級編=
第二弾は、コンテナアーキテクチャです。
Container Instancesとは
OCI Container Instancesは、サーバーレスでコンテナを実行できるマネージドサービスです。
特徴
- インフラ管理不要: サーバーのプロビジョニングや管理が不要で、コンテナイメージさえあればすぐにデプロイ可能
- 迅速なデプロイ: コンテナイメージから数秒~数分でデプロイが完了
- 柔軟なスケーリング: 必要に応じてOCPUとメモリを調整可能
- コスト効率: 使用した分だけの課金で、停止時は課金されない
- セキュア: VCN内のプライベートサブネットに配置可能
制約事項
- SSH接続不可: コンテナに直接SSH接続はできません(デバッグはログやコンテナExec機能を利用)
- 永続ストレージ: デフォルトではエフェメラルストレージのみ
今回は、ステートレスなタスク管理APIを構築するため、Container Instancesが最適です。
AWSアーキテクチャ
以下のAWS構成をOCIで再現していきます。
クラウドサービス比較
AWS | OCI | 役割 |
---|---|---|
VPC | VCN | 仮想ネットワーク |
Application Load Balancer | Network Load Balancer | ロードバランサー |
Amazon ECS | Container Instance | コンテナ実行環境 |
Amazon Aurora | MySQL HeatWave | データベース |
上記がAWSの各サービスに対応するOCIのサービスです。
Container Instance にAPIコンテナを、DB層にMySQL HeatWaveを採用します。
OCIアーキテクチャ
エンドユーザーはインターネット経由でNLBにアクセスし、APIを利用します。
DatabaseはMySQL HeatWaveを利用します。
構築アプリケーション概要
今回構築するアプリケーションは、シンプルなタスク管理APIです。
前提条件
この手順を実施するにあたり、以下の前提条件があります。
管理用Computeインスタンスの準備
本手順では、アプリケーションのデプロイ、MySQL HeatWaveへの接続テスト、Container InstanceのAPI接続テスト、などを行うため、パブリックサブネット上に管理用Computeインスタンスを用意します。
OCIへ適切な接続ができるローカルPCや環境があれば、適宜そちらをお使いください。
構築手順
- VCNの構築
- アプリケーションの準備
- MySQL HeatWaveの構築
- Container Instanceの構築
- Network Load Balancer (NLB) の構築
- 動作確認
1. VCNの構築
1-1. VCNの作成
OCIのクラウドコンソールから、ネットワーキング>仮想クラウド・ネットワークを選択し、仮想クラウド・ネットワークのサービス画面に遷移します
遷移後、「VCNの作成」ボタンを押下します
項目 | 設定値 | 説明 |
---|---|---|
名前 | demo-vcn | VCNの名前 |
IPv4 CIDRブロック | 10.0.0.0/16 | VCN全体のIPアドレス範囲 |
「VCNの作成」ボタンを押下します。
1-2. ゲートウェイの作成
プライベートサブネットとパブリックサブネットで適切な通信を行うため、3つのゲートウェイを作成します。
ゲートウェイ | 用途 | 説明 |
---|---|---|
Internet Gateway | パブリックサブネット用 | Load Balancerがインターネットと通信 |
NAT Gateway | プライベートサブネット用 | Container Instancesが外部インターネットにアクセス |
Service Gateway | プライベートサブネット用 | OCIRなどOCIサービスへの高速・セキュアなアクセス |
1-2-1. インターネット・ゲートウェイの作成
VCN作成後、ゲートウェイのタブを選択し、「インターネット・ゲートウェイの作成」ボタンを押下します
名前: demo-igw
という名前を入れ、「インターネット・ゲートウェイの作成」ボタンを押下します
インターネット・ゲートウェイが作成されたことを確認します
1-2-2. NATゲートウェイの作成
名前: demo-natgw
という名前を入れ、「NATゲートウェイの作成_ボタンを押下します
NATゲートウェイが作成されたことを確認します
1-2-3. サービス・ゲートウェイの作成
以下の情報を入力します
項目 | 設定値 | 説明 |
---|---|---|
名前 | demo-sgw |
サービス・ゲートウェイの名前 |
サービス | All <region> Services In Oracle Services Network | 東京リージョンの場合: All NRT Services In Oracle Services Network |
この設定により、OCIR、Object Storage、その他のOCIサービスへのプライベートアクセスが可能になります。
「サービス・ゲートウェイの作成」ボタンを押下します。
サービス・ゲートウェイが作成されたことを確認できます。
ここまでで、3つのゲートウェイ(インターネット・ゲートウェイ、NATゲートウェイ、サービス・ゲートウェイ)が作成されました。
1-3. ルート表の作成
ルーティングのタブを選択します。"Default Route Table for demo-vcn"という名前で既にルート表が存在していることが確認できます。このルート表はプライベートサブネット用のルートテーブルとして利用します。
1-3-1. パブリックサブネット用のルート表の作成
インターネット・ゲートウェイにルートを持つパブリックサブネット用のルート表を作成します。
項目 | 設定値 | 説明 |
---|---|---|
名前 | demo-rtb-public | パブリックサブネット用のルートテーブル名 |
ルート・ルール 「+別のルート・ルール」を押下 |
ターゲット・タイプ: インターネット・ゲートウェイ 宛先CIDRブロック: 0.0.0.0/0 ターゲット・インターネット・ゲートウェイ: demo-igw |
全ての通信をインターネット・ゲートウェイにルーティング |
その後、「作成」ボタンを押下し、ルート表が追加されていることが確認します
1-3-2. プライベートサブネット用のルート表の編集
次に、プライベートサブネット用のルート表を編集します。
"Default Route Table for demo-vcn"のラベルを選択し、画面遷移します。
プライベートサブネットからは、OCIサービスと外部インターネットの両方にアクセスできるよう、2つのルート・ルールを追加します。
ルート・ルール1: サービス・ゲートウェイ経由のOCIサービスへのアクセス
項目 | 設定値 | 説明 |
---|---|---|
ターゲット・タイプ | サービス・ゲートウェイ | OCIサービスへのアクセス用 |
宛先サービス | All <region> Services In Oracle Services Network | 東京リージョンの場合: All NRT Services In Oracle Services Network |
ターゲット・サービス・ゲートウェイ | demo-sgw | 1-2-3で作成したサービス・ゲートウェイ |
「別のルート・ルールの追加」ボタンを押下します。
ルート・ルール2: NATゲートウェイ経由の外部インターネットへのアクセス
項目 | 設定値 | 説明 |
---|---|---|
ターゲット・タイプ | NATゲートウェイ | 外部インターネットへのアクセス用 |
宛先CIDRブロック | 0.0.0.0/0 | 全ての通信をNATゲートウェイにルーティング |
ターゲット・NATゲートウェイ | demo-natgw | 1-2-2で作成したNATゲートウェイ |
すべてのルール入力後、「ルート・ルールの追加」ボタンを押下します
1-4. セキュリティ・リストの作成
Load Balancer、Container Instance、MySQL HeatWaveが配置されるサブネット毎に、別々のセキュリティ・リストを用意します
セキュリティのタブを選択し、「セキュリティ・リストの作成」ボタンを押下します
1-4-1. パブリックサブネット用のセキュリティ・リストの作成
項目 | 設定値 | 説明 |
---|---|---|
名前 | demo-seclist-public | パブリックサブネット用セキュリティ・リストの名前 |
イングレス・ルール1 | ソースCIDR: 0.0.0.0/0 宛先ポート範囲: 22 |
SSH接続用(管理用Computeインスタンス) |
イングレス・ルール2 | ソースCIDR: 0.0.0.0/0 宛先ポート範囲: 80,443 |
Load Balancer用HTTP(80)とHTTPS(443)ポート |
エグレス・ルール | 宛先CIDR: 0.0.0.0/0 宛先ポート範囲: ALL |
全通信許可(デフォルト設定) |
1-4-2. Container Instance用のセキュリティ・リストの作成
項目 | 設定値 | 説明 |
---|---|---|
名前 | demo-seclist-app |
Container Instance用セキュリティ・リストの名前 |
イングレス・ルール1 | ソースCIDR: 10.0.0.0/24 宛先ポート範囲: 8000 |
パブリックサブネットからのFlaskアプリ(8000)へのアクセス許可 |
エグレス・ルール | 宛先CIDR: 0.0.0.0/0 宛先ポート範囲: ALL |
全通信許可(デフォルト設定) |
1-4-3. MySQL HeatWave用のセキュリティ・リストの作成
項目 | 設定値 | 説明 |
---|---|---|
名前 | demo-seclist-db |
MySQL HeatWave用セキュリティ・リストの名前 |
イングレス・ルール1 | ソースCIDR: 10.0.1.0/24 宛先ポート範囲: 3306 |
アプリサブネットからのMySQL(3306)へのアクセス許可 |
イングレス・ルール2 | ソースCIDR: 10.0.0.0/24 宛先ポート範囲: 3306 |
パブリックサブネット(管理用Compute)からのMySQL(3306)へのアクセス許可 |
エグレス・ルール | 宛先CIDR: 0.0.0.0/0 宛先ポート範囲: ALL |
全通信許可(デフォルト設定) |
ここまでの手順で、セキュリティ・リストが3つできたことを確認できます。
1-5. サブネットの作成
サブネットのタブを選択し、「サブネットの作成」ボタンを押下します。
1-5-1. パブリックサブネットの作成
パブリックサブネットを作成します。
項目 | 設定値 | 説明 |
---|---|---|
名前 | demo-subnet-public |
パブリックサブネットの名前 |
IPv4 CIDRブロック | 10.0.0.0/24 |
パブリックサブネットのIPアドレス範囲 |
ルート表 | demo-rtb-public |
1-3-1で作成したパブリック用ルート表 |
サブネット・アクセス | パブリック・サブネット | インターネットからアクセス可能 |
セキュリティ・リスト | demo-seclist-public |
1-4-1で作成したパブリックサブネット用セキュリティ・リスト |
「サブネットの作成」ボタンを押下します
1-5-2. プライベートサブネットの作成 -container用
Container Instance用のプライベートサブネットを作成します。
項目 | 設定値 | 説明 |
---|---|---|
名前 | demo-subnet-app |
アプリケーション用プライベートサブネットの名前 |
IPv4 CIDRブロック | 10.0.1.0/24 |
アプリケーション用プライベートサブネットのIPアドレス範囲 |
ルート表 | Default Route Table for demo-vcn |
1-3-2で編集したプライベート用ルート表 |
サブネット・アクセス | プライベート・サブネット | インターネットから直接アクセス不可 |
セキュリティ・リスト | demo-seclist-app |
1-4-2で作成したContainer Instance用セキュリティ・リスト |
「サブネットの作成」ボタンを押下します
1-5-3. プライベートサブネットの作成 -db用
MySQL HeatWave用のプライベートサブネットを作成します
項目 | 設定値 | 説明 |
---|---|---|
名前 | demo-subnet-db |
データベース用プライベートサブネットの名前 |
IPv4 CIDRブロック | 10.0.2.0/24 |
データベース用プライベートサブネットのIPアドレス範囲 |
ルート表 | Default Route Table for demo-vcn |
1-3-2で編集したプライベート用ルート表 |
サブネット・アクセス | プライベート・サブネット | インターネットから直接アクセス不可 |
セキュリティ・リスト | demo-seclist-db |
1-4-3で作成したMySQL HeatWave用セキュリティ・リスト |
「サブネットの作成」ボタンを押下します
1-6. 管理用Computeインスタンスの構築
VCN構築完了後、管理用Computeインスタンスを作成します。
1-6-1. インスタンスの作成
- OCIコンソールから「コンピュート」→「インスタンス」を選択し、「インスタンスの作成」ボタンを押下します
項目 | 設定値 | 説明 |
---|---|---|
名前 | demo-mgmt-server |
管理用Computeインスタンスの名前 |
コンパートメント | 現在のコンパートメント | デフォルト設定 |
可用性ドメイン | AD-1 | 任意の可用性ドメイン |
イメージ | Oracle Linux 9 | 最新のOracle Linux 9イメージ |
シェイプ | VM.Standard.E2 | 任意でOK。最小構成(Always Free対象) |
ネットワーキング | 既存のVCNを選択 | VCN: demo-vcn 、サブネット: demo-subnet-public
|
SSHキー | 公開キーの貼付けまたはアップロード | 接続用のSSHキーペア |
1-6-2. インスタンスの詳細確認
作成完了後、以下の情報を確認します:
項目 | 説明 |
---|---|
パブリックIPアドレス | SSH接続時に使用 |
プライベートIPアドレス | VCN内での通信に使用 |
ステータス | 「実行中」であることを確認 |
1-6-3. 必要なツールのインストール
SSH接続後、以下のコマンドで各種ツールをインストールします:
# Oracle Linux 9の場合
sudo dnf update -y
sudo dnf install mysql -y
sudo dnf install docker -y
1-6-4. 接続確認
# SSH接続例
ssh -i <秘密鍵のパス> opc@<パブリックIPアドレス>
# MySQL Clientのインストール確認
mysql --version
注意:
- 本番環境では、Bastionサービスやプライベート接続を使用したセキュアな管理方法を検討してください
2. アプリケーションの準備
Container Instanceで稼働させるTODO管理アプリケーションのソースコードを準備します。
2-1. アプリケーションの概要
本記事では、GUI対応のTODO管理アプリケーションを構築します。以下の機能を提供します:
- タスクの追加: 新しいTODOアイテムの作成
- タスクの完了: 完了/未完了の切り替え
- タスクの削除: 不要なタスクの削除
2-2. ファイル構成
アプリケーションのファイル構成は下記の通りです。
container-api/
├── Dockerfile
├── requirements.txt
└── app.py
2-3. ソースコード
2-3-1. requirements.txt
Flask==2.3.3
PyMySQL==1.1.0
gunicorn==21.2.0
cryptography==41.0.7
2-3-2. app.py
すごく長いので折りたたみ表示
from flask import Flask, request, jsonify, render_template_string
import pymysql
import os
from datetime import datetime
app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False # 日本語をエスケープしない
# データベース接続設定
DB_CONFIG = {
'host': os.getenv('DB_HOST'),
'user': os.getenv('DB_USER'),
'password': os.getenv('DB_PASSWORD'),
'database': os.getenv('DB_NAME'),
'port': 3306,
'charset': 'utf8mb4'
}
def get_db_connection():
return pymysql.connect(**DB_CONFIG)
# HTMLテンプレート
HTML_TEMPLATE = '''
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODO管理アプリ - Container Instance</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.header { background: #2c3e50; color: white; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
.todo-form { background: #ecf0f1; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
.todo-form input[type="text"] { width: 70%; padding: 10px; border: 1px solid #bdc3c7; border-radius: 3px; }
.todo-form button { padding: 10px 20px; background: #3498db; color: white; border: none; border-radius: 3px; cursor: pointer; margin-left: 10px; }
.todo-form button:hover { background: #2980b9; }
.todo-item { background: white; border: 1px solid #bdc3c7; padding: 15px; margin: 10px 0; border-radius: 5px; display: flex; justify-content: space-between; align-items: center; }
.todo-item.completed { background: #d5f4e6; border-color: #27ae60; }
.todo-item.completed .todo-title { text-decoration: line-through; color: #7f8c8d; }
.todo-title { flex-grow: 1; margin-right: 15px; }
.todo-actions button { padding: 5px 10px; margin: 0 5px; border: none; border-radius: 3px; cursor: pointer; }
.toggle-btn { background: #f39c12; color: white; }
.toggle-btn:hover { background: #e67e22; }
.delete-btn { background: #e74c3c; color: white; }
.delete-btn:hover { background: #c0392b; }
.message { padding: 10px; margin: 10px 0; border-radius: 3px; }
.success { background: #d5f4e6; color: #27ae60; border: 1px solid #27ae60; }
.error { background: #fadbd8; color: #e74c3c; border: 1px solid #e74c3c; }
</style>
</head>
<body>
<div class="header">
<h1>📝 TODO管理アプリ</h1>
<p>OCI Container Instance + MySQL HeatWave で構築</p>
</div>
<div class="todo-form">
<form id="todoForm">
<input type="text" id="todoTitle" placeholder="新しいタスクを入力してください..." required>
<button type="submit">追加</button>
</form>
</div>
<div id="message"></div>
<div id="todos">
<h2>タスク一覧</h2>
<div id="todoList">
<!-- TODOリストがここに動的に挿入されます -->
</div>
</div>
<script>
// ページ読み込み時にTODOリストを取得
loadTodos();
// フォーム送信処理
document.getElementById('todoForm').addEventListener('submit', async function(e) {
e.preventDefault();
const title = document.getElementById('todoTitle').value.trim();
if (!title) return;
try {
const response = await fetch('/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: title })
});
if (response.ok) {
document.getElementById('todoTitle').value = '';
loadTodos();
showMessage('タスクが追加されました!', 'success');
} else {
showMessage('エラーが発生しました', 'error');
}
} catch (error) {
showMessage('接続エラーが発生しました', 'error');
}
});
// TODOリストの読み込み
async function loadTodos() {
try {
const response = await fetch('/todos');
const todos = await response.json();
displayTodos(todos);
} catch (error) {
showMessage('TODOリストの読み込みに失敗しました', 'error');
}
}
// TODOリストの表示
function displayTodos(todos) {
const todoList = document.getElementById('todoList');
if (todos.length === 0) {
todoList.innerHTML = '<p>タスクがありません。新しいタスクを追加してください。</p>';
return;
}
todoList.innerHTML = todos.map(todo => `
<div class="todo-item ${todo.done ? 'completed' : ''}">
<div class="todo-title">${todo.title}</div>
<div class="todo-actions">
<button class="toggle-btn" onclick="toggleTodo(${todo.id}, ${todo.done})">
${todo.done ? '未完了に戻す' : '完了'}
</button>
<button class="delete-btn" onclick="deleteTodo(${todo.id})">削除</button>
</div>
</div>
`).join('');
}
// TODOの完了/未完了切り替え
async function toggleTodo(id, currentDone) {
try {
const response = await fetch(`/todos/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ done: currentDone ? 0 : 1 })
});
if (response.ok) {
loadTodos();
showMessage('タスクが更新されました!', 'success');
} else {
showMessage('更新に失敗しました', 'error');
}
} catch (error) {
showMessage('接続エラーが発生しました', 'error');
}
}
// TODOの削除
async function deleteTodo(id) {
if (!confirm('このタスクを削除しますか?')) return;
try {
const response = await fetch(`/todos/${id}`, {
method: 'DELETE'
});
if (response.ok) {
loadTodos();
showMessage('タスクが削除されました!', 'success');
} else {
showMessage('削除に失敗しました', 'error');
}
} catch (error) {
showMessage('接続エラーが発生しました', 'error');
}
}
// メッセージ表示
function showMessage(message, type) {
const messageDiv = document.getElementById('message');
messageDiv.innerHTML = `<div class="message ${type}">${message}</div>`;
setTimeout(() => {
messageDiv.innerHTML = '';
}, 3000);
}
</script>
</body>
</html>
'''
@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)
@app.route('/health')
def health_check():
return jsonify({'status': 'healthy', 'service': 'TODO Management App'}), 200
@app.route('/todos', methods=['GET'])
def get_todos():
try:
conn = get_db_connection()
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute("SELECT * FROM todos ORDER BY created_at DESC")
todos = cursor.fetchall()
cursor.close()
conn.close()
return jsonify(todos)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/todos', methods=['POST'])
def create_todo():
try:
data = request.get_json()
title = data.get('title')
if not title:
return jsonify({'error': 'Title is required'}), 400
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("INSERT INTO todos (title, done) VALUES (%s, %s)", (title, 0))
conn.commit()
todo_id = cursor.lastrowid
cursor.close()
conn.close()
return jsonify({'id': todo_id, 'title': title, 'done': 0}), 201
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/todos/<int:todo_id>', methods=['PUT'])
def update_todo(todo_id):
try:
data = request.get_json()
done = data.get('done')
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("UPDATE todos SET done = %s WHERE id = %s", (done, todo_id))
conn.commit()
cursor.close()
conn.close()
return jsonify({'message': 'Todo updated successfully'})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/todos/<int:todo_id>', methods=['DELETE'])
def delete_todo(todo_id):
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("DELETE FROM todos WHERE id = %s", (todo_id,))
conn.commit()
cursor.close()
conn.close()
return jsonify({'message': 'Todo deleted successfully'})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=False)
2-3-3. Dockerfile
FROM python:3.11-slim
WORKDIR /app
# 依存関係をインストール
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# アプリケーションファイルをコピー
COPY . .
# 非rootユーザーを作成
RUN adduser --disabled-password --gecos '' appuser
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["gunicorn", "-w", "2", "-b", "0.0.0.0:8000", "app:app"]
2-4. Container Registryへの登録
OCIコンソールから「開発者サービス」>「コンテナ・レジストリ」を選択し、コンテナレジストリのサービス画面に遷移します。
遷移後、「リポジトリの作成」ボタンを押下し、以下の設定でリポジトリを作成します。
項目 | 設定値 | 説明 |
---|---|---|
名前 | container-api |
コンテナイメージ用リポジトリの名前 |
アクセス設定 | プライベート | セキュリティ強化のためプライベート設定 |
2-5. イメージのビルドとプッシュ
Container RegistryにイメージをプッシュするためにAuth Tokenが必要です。
2-5-1. Auth Tokenの取得
OCIコンソールにログインし、以下の手順でAuth Tokenを生成します。
-
説明欄に「Container Registry用」など、わかりやすい説明を入力し、「トークンの生成」ボタンを押下します
2-5-2. 必要な情報の確認
docker loginコマンドで使用する以下の情報を確認します。
region-key(リージョンキー)
リージョンに応じたキーを使用します。主要なリージョンキーは以下の通りです:
リージョン | リージョンキー |
---|---|
東京リージョン | nrt |
大阪リージョン | kix |
その他のリージョンキーはOCIドキュメントを参照してください。
※本手順では ソウルリージョン: icn にて実施しています。
tenancy-namespace(テナンシ・ネームスペース)
username(ユーザー名)
OCIのユーザー名です。以下のいずれかの形式になります:
- 一般的なOCIユーザー:
user@example.com
またはusername
- SSO・IDCSなどのフェデレーション・ユーザー:
oracleidentitycloudservice/user@example.com
2-5-3. 管理用Computeでのイメージビルドとプッシュ
上記で確認した情報を使用して、管理用Computeインスタンスで以下のコマンドを実行します。
手順:
-
管理用ComputeインスタンスにSSH接続します。
-
アプリケーションファイルを準備します。
# 作業ディレクトリを作成
mkdir -p ~/container-api
cd ~/container-api
# 各ファイルを作成(2-1のコードを使用)
# 以下のいずれかの方法でファイルを作成してください:
# 方法1: viエディタを使用
vi requirements.txt
vi app.py
vi Dockerfile
# 方法2: catコマンドでヒアドキュメントを使用
cat > requirements.txt << 'EOF'
Flask==3.0.3
gunicorn==22.0.0
PyMySQL==1.1.1
python-dotenv==1.0.1
EOF
# 方法3: ローカルで作成し、アップロード
Tip: Cloud Shellのエディタ機能を使うと、ブラウザ上で快適に編集できます。Cloud Shellツールバーの「コードエディタ」アイコンをクリック
- Container Registryにログインし、イメージのビルドとPushを実行
# Container Registryにログイン
echo "<auth-token>" | docker login <region-key>.ocir.io -u <tenancy-namespace>/<username> --password-stdin
# イメージのビルド
docker build -t <region-key>.ocir.io/<tenancy-namespace>/container-api:latest .
# イメージのプッシュ
docker push <region-key>.ocir.io/<tenancy-namespace>/container-api:latest
プッシュが成功すると、OCIコンソールのContainer Registryの画面でイメージが確認できます。
3. MySQL HeatWaveの構築
DBサーバを構築していきます。
MySQL HeatWaveを利用し、マネージドなMySQL Databaseを構築します。
3-1. MySQL HeatWaveの作成
データベース→HeatWave MySQL→DBシステム を選択し、MySQL HeatWaveの画面に遷移します。
「DBシステムの作成」ボタンを押下します
項目 | 設定値 | 説明 |
---|---|---|
テンプレート | 開発またはテスト | 開発・検証用途に適した設定 |
名前 | demo-mysql |
MySQL HeatWaveインスタンスの名前 |
管理者資格証明 | ユーザー名: admin パスワード: 任意の強力なパスワード |
DB管理者アカウント |
設定 | スタンドアロン | 単一インスタンス構成 |
ネットワーキング | demo-subnet-db |
1-4-3で作成したDB用プライベートサブネット |
HeatWaveクラスタ | 無効 | 今回は使用しない |
自動バックアップ | 無効 | 検証用途のため無効化 |
運用通知連絡先 | 任意のメールアドレス | 障害通知等の受信用 |
画面左下の「作成」ボタンを押下します
※作成完了に30分ぐらいかかります
3-2. 管理用ComputeインスタンスからMySQL HeatWaveへの接続
DBシステムがアクティブになったら、管理用Computeインスタンスから接続します。
3-2-1. 接続情報の確認
MySQL HeatWaveの接続情報を確認します。
- OCIコンソールのMySQL HeatWave画面で「接続」タブを選択
- 「内部FQDNの編集」をクリック
- ホスト名に
demo-mysql
を入力
以下のFQDNが接続エンドポイントとなります:
demo-mysql.demosubnetdb.demovcn.oraclevcn.com
3-2-2. 管理用Computeインスタンスでの接続
管理用ComputeインスタンスにSSH接続して、MySQLクライアントで接続します。
# 管理用ComputeインスタンスにSSH接続
ssh opc@<管理用ComputeのパブリックIP>
# MySQLクライアントで接続
mysql -h demo-mysql.demosubnetdb.demovcn.oraclevcn.com -u admin -p
パスワードを入力すると、MySQL HeatWaveに接続できます。
Enter password: <password入力>
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 221
Server version: 8.4.6-u2-cloud MySQL Enterprise - Cloud
Copyright (c) 2000, 2025, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
接続に成功しました。
3-3. スキーマ作成
アプリ用のDBユーザーを作成し、DBとテーブルを作成します。
mysql> CREATE DATABASE sampledb;
Query OK, 1 row affected (0.01 sec)
mysql> CREATE USER 'appuser'@'%' IDENTIFIED BY 'your_secure_password_here';
Query OK, 0 rows affected (0.00 sec)
mysql> GRANT ALL PRIVILEGES ON sampledb.* TO 'appuser'@'%';
Query OK, 0 rows affected (0.00 sec)
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)
mysql> USE sampledb;
Database changed
mysql> CREATE TABLE todos (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
done TINYINT(1) NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Query OK, 0 rows affected, 1 warning (0.02 sec)
mysql> show tables;
+--------------------+
| Tables_in_sampledb |
+--------------------+
| todos |
+--------------------+
1 row in set (0.00 sec)
mysql> SHOW COLUMNS FROM todos FROM sampledb;
+------------+--------------+------+-----+-------------------+-------------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+-------------------+-------------------+
| id | int | NO | PRI | NULL | auto_increment |
| title | varchar(255) | NO | | NULL | |
| done | tinyint(1) | NO | | 0 | |
| created_at | timestamp | YES | | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
+------------+--------------+------+-----+-------------------+-------------------+
4 rows in set (0.00 sec)
4. Container Instanceの構築
プライベートサブネット demo-subnet-app
に、APIコンテナを稼働させるContainer Instanceを構築します。
4-1. Container Instanceの作成
OCIのクラウドコンソールから、開発者サービス > コンテナ・インスタンス を選択し、Container Instanceのサービス画面に遷移します
遷移後、「Container Instanceの作成」ボタンを押下します
Container Instanceの作成は単一の画面で行います。以下の項目を順番に入力・選択していきます。
基本情報の設定
項目 | 設定値 | 説明 |
---|---|---|
名前 | demo-container | Container Instanceの名前 |
コンパートメント | 任意のコンパートメントを選択 | デフォルト設定 |
可用性ドメイン | AD-1 | 任意のADを選択 |
シェイプ | CI.Standard.E4.Flex | Container Instance用のシェイプ |
OCPU数 | 1 | 最小値、必要に応じて調整可能 |
メモリ(GB) | 1 | 最小値、必要に応じて調整可能 |
ネットワーキングの設定
項目 | 設定値 | 説明 |
---|---|---|
仮想クラウド・ネットワーク | demo-vcn | 1章で作成したVCN |
サブネット | demo-subnet-app (リージョナル) | アプリケーション用プライベートサブネット |
プライベートIPアドレスの割当て | 自動割当て | デフォルト設定のまま |
コンテナの設定
「コンテナ1」として以下を設定します。
項目 | 設定値 | 説明 |
---|---|---|
名前 | api-container | コンテナの名前 |
イメージの設定:
項目 | 設定値 | 説明 |
---|---|---|
イメージ・レジストリ | OCIR | OCI Container Registry |
リポジトリ | container-api (プライベート) | 2-2で作成したリポジトリ |
イメージ | latest | プッシュしたイメージのタグ |
資格証明を手動で指定 | ✓ チェック | 手動で認証情報を入力 |
レジストリ・ユーザー名 | docker loginしたユーザー名 | |
レジストリ・パスワード | 2-3-1で取得したAuth Token | ユーザーの認証トークン |
警告ボックス
画面に表示されるオレンジの警告ボックスは、IAMポリシーを使用することが推奨されることを示していますが、本記事では初学者向けに認証トークン方式を使用します。
環境変数:
環境変数を以下の4つ追加します。「別の環境変数の追加」を押下して、それぞれ設定してください。
変数名 | 値 |
---|---|
DB_HOST | demo-mysql.demosubnetdb.demovcn.oraclevcn.com |
DB_USER | appuser |
DB_PASSWORD | ************(3-3で設定したappuserのパスワード) |
DB_NAME | sampledb |
すべて入力後、画面下部の「作成」ボタンを押下します
数分待つと、Container Instanceがアクティブ状態になります
4-2. 動作確認
Container Instanceが作成されたら、動作確認をしてみます。
今の時点では、Databaseのテーブルには何もデータは入っていないので、空文字[ ] が返ってくれば成功です。
管理用Computeインスタンスで以下のコマンドを実行して、Container InstanceのAPIにアクセスします:
# 管理用ComputeインスタンスにSSH接続
ssh -i ~/.ssh/id_rsa opc@<management-compute-public-ip>
# Container InstanceのプライベートIPアドレスを確認
# (例: 10.0.1.XXX)
# APIにアクセスしてテスト
curl -s http://10.0.1.XXX:8000/todos
# 新しいTODOの作成
curl -X POST http://10.0.1.XXX:8000/todos \
-H "Content-Type: application/json" \
-d '{"title": "Container Instance test"}'
# TODOリストの再取得(作成されたTODOが表示される)
curl -s http://10.0.1.XXX:8000/todos
TODO作成後に作成されたTODOが表示されます。
[{"created_at":"Sun, 05 Oct 2025 15:46:10 GMT","done":0,"id":1,"title":"Container Instance test"}]
接続に成功していることが確認できました
5. Network Load Balancer (NLB) の構築
プライベートサブネット上にあるContainer Instanceへインターネット経由で接続するため、パブリックサブネット上にNetwork Load Balancerを作成します。
重要: Container Instanceをバックエンドとして使用する場合は、Flexible Load BalancerではなくNetwork Load Balancer (NLB) を使用する必要があります。
5-1. Network Load Balancer (NLB) の作成
ネットワーキング→ネットワーク・ロード・バランサを選択します
遷移後、「ネットワーク・ロード・バランサの作成」ボタンを押下します
5-1-1. 詳細の追加
項目 | 設定値 | 説明 |
---|---|---|
ロード・バランサ名 | demo-lb | Network Load Balancerの名前 |
可視性タイプ | パブリック | インターネットからアクセス可能 |
パブリックIPアドレスの割当て | エフェメラルIPアドレス | 動的IPアドレス割当て |
仮想クラウド・ネットワーク | demo-vcn | 1章で作成したVCN |
サブネット | demo-subnet-public | パブリックサブネット |
「次」ボタンを押下します。
5-1-2. リスナーの構成
項目 | 設定値 | 説明 |
---|---|---|
リスナー名 | demo-listener | リスナーの名前 |
トラフィックのタイプ | TCP | TCPプロトコル(Layer 4) |
ポート | ポートを指定 80 |
HTTP標準ポート |
「次」ボタンを押下します。
5-1-2. バックエンドの選択
項目 | 設定値 | 説明 |
---|---|---|
バックエンドセット名 | demo-backend-set | バックエンドセットの名前 |
ソースIPの保持 | チェックを外す |
先にこのチェックを外さないとIPアドレスが指定できません |
バックエンドの追加 →IPアドレス |
IPアドレス: ポート:8000 重み: 1 |
ヘルスチェック・ポリシーの詳細設定:
項目 | 設定値 | 説明 |
---|---|---|
プロトコル | TCP | TCPプロトコルでヘルスチェック |
ポート | 8000 |
Flaskアプリケーションのポート |
間隔(ミリ秒) | 10000 |
10秒間隔でチェック |
タイムアウト(ミリ秒) | 3000 |
3秒でタイムアウト |
再試行回数 | 3 |
3回まで再試行 |
セキュリティ・リストの管理:
「ネットワーク・ロード・バランサの作成後にセキュリティ・リスト・ルールを手動で構成します」を選択します
ロード・バランシング・ポリシー:
項目 | 設定値 | 説明 |
---|---|---|
ロード・バランシング・ポリシーの指定 | 5タプル・ハッシュ | 均等にトラフィックを分散 |
「次」ボタンを押下します。
5-1-3. 確認および作成
入力内容を確認し、問題なければ「作成」ボタンを押下します
5-2. 動作確認
ここまでの操作で、Load balancerは作成されますが、正常にパスが通っているか確認します。
$ curl -s http://<Load balancerのパブリックIPアドレス>/todos
空の配列もしくは以前に実施した値が返ってくれば正常に動作していることが確認できます。
6. 動作確認
表示確認
Webブラウザから、動作確認してみましょう。
http://<Load balancerのパブリックIPアドレス>/todos
さいごに
OCIで、シンプルなコンテナアーキテクチャを作ってみました。
とはいえ、手順の一つ一つ丁寧に作ると長文になりますね。
Container Instanceを使用することで以下のメリットを享受できます。
- デプロイの簡素化: アプリケーションをコンテナイメージとして管理
- 環境の一貫性: ローカル開発環境と本番環境の統一
- スケーラビリティ: コンテナ単位でのリソース調整
今回の手順は、HTTPアクセスだったり、DBパスワードをベタ打ちだったり、可用性を考慮していなかったりと、本番利用するにはまだまだ考慮すべき点が残っています。
そういった点は次回以降では触れていけたらと思います。