AWS東京リージョンのVPC内に閉域網プライベートRAG(Qdrant + Llama-3)をTerraformで構築する完全手順
はじめに
こんにちは、NKKTech Global 技術チームです。
社内規定やコンプライアンス上の制約から、「機密文書を一切インターネットに触れさせずにLLMで検索・回答させたい」という相談を数多くいただきます。
これを満たすアーキテクチャが、**「完全閉域網(Isolated VPC)構成のプライベートRAG」**です。
インターネットゲートウェイ(IGW)やNATゲートウェイを配置せず、社内閉域網(Direct Connect / VPN)またはAWS Systems Manager(SSM)経由でのみアクセス可能なVPC内に、以下のコンポーネントを構築します。
-
LLM推論基盤: Llama-3-8B-Instruct on NVIDIA A10G (EC2
g5.2xlarge) + vLLM - ベクトルデータベース: Qdrant(オンプレ同等の完全ローカルインスタンス)
- プライベート接続: VPCエンドポイント(S3 / ECR / SSM)による完全アウトバウンド遮断
本記事では、この強固なインフラを再現可能にするTerraformコード一式と、閉域網特有のプロビジョニングの勘所を公開します。
1. アーキテクチャ設計図
2. Terraformによるインフラ定義
① 閉域VPCとVPCエンドポイント (vpc.tf)
外部インターネットへの出口(IGW/NAT)を一切作成せず、AWS内部サービスへの通信のみをVPCエンドポイントで開通させます。
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
# 1. VPCの作成
resource "aws_vpc" "rag_vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "private-rag-vpc"
}
}
# 2. 完全プライベートサブネット (IGWルートなし)
resource "aws_subnet" "private_subnet" {
vpc_id = aws_vpc.rag_vpc.id
cidr_block = "10.0.10.0/24"
availability_zone = "ap-northeast-1a"
tags = {
Name = "private-rag-subnet"
}
}
# 3. ルートテーブル
resource "aws_route_table" "private_rt" {
vpc_id = aws_vpc.rag_vpc.id
tags = { Name = "private-rag-rt" }
}
resource "aws_route_table_association" "private_assoc" {
subnet_id = aws_subnet.private_subnet.id
route_table_id = aws_route_table.private_rt.id
}
# 4. S3 ゲートウェイエンドポイント (モデル重み配置用)
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.rag_vpc.id
service_name = "com.amazonaws.ap-northeast-1.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = [aws_route_table.private_rt.id]
}
# 5. SSM接続用インターフェースエンドポイント (SSM, EC2Messages, SSMMessages)
locals {
ssm_services = ["ssm", "ec2messages", "ssmmessages"]
}
resource "aws_vpc_endpoint" "ssm_endpoints" {
for_each = toset(locals.ssm_services)
vpc_id = aws_vpc.rag_vpc.id
service_name = "com.amazonaws.ap-northeast-1.${each.value}"
vpc_endpoint_type = "Interface"
subnet_ids = [aws_subnet.private_subnet.id]
security_group_ids = [aws_security_group.endpoint_sg.id]
private_dns_enabled = true
}
② セキュリティグループとIAMロール (security.tf)
パブリックIPを持たないインスタンスに対し、SSM接続のみを許可する最小権限IAMロールと通信制御です。
# VPCエンドポイント用セキュリティグループ
resource "aws_security_group" "endpoint_sg" {
name = "vpce-sg"
vpc_id = aws_vpc.rag_vpc.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [aws_vpc.rag_vpc.cidr_block]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# RAGクラスタ内部通信用セキュリティグループ
resource "aws_security_group" "rag_cluster_sg" {
name = "rag-cluster-sg"
vpc_id = aws_vpc.rag_vpc.id
# 同一セキュリティグループ内の全通信を許可 (Qdrant 6333, vLLM 8000)
ingress {
from_port = 0
to_port = 0
protocol = "-1"
self = true
}
# SSMエンドポイント(443)へのアウトバウンドを許可
egress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [aws_vpc.rag_vpc.cidr_block]
}
# S3エンドポイントへのアクセス
egress {
from_port = 443
to_port = 443
protocol = "tcp"
prefix_list_ids = [aws_vpc_endpoint.s3.prefix_list_id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
self = true
}
}
# SSM接続用IAMロール
resource "aws_iam_role" "instance_role" {
name = "private-rag-instance-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
}]
})
}
resource "aws_iam_role_policy_attachment" "ssm_attach" {
role = aws_iam_role.instance_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_role_policy_attachment" "s3_read_attach" {
role = aws_iam_role.instance_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}
resource "aws_iam_instance_profile" "instance_profile" {
name = "private-rag-profile"
role = aws_iam_role.instance_role.name
}
③ Qdrant & GPU推論インスタンスの構築 (compute.tf)
# 1. ベクトルDB: Qdrant インスタンス (Ubuntu / t3.large)
resource "aws_instance" "qdrant_server" {
ami = "ami-0b20f552f63953f0e" # Ubuntu 22.04 LTS (東京)
instance_type = "t3.large"
subnet_id = aws_subnet.private_subnet.id
vpc_security_group_ids = [aws_security_group.rag_cluster_sg.id]
iam_instance_profile = aws_iam_instance_profile.instance_profile.name
root_block_device {
volume_size = 100
volume_type = "gp3"
encrypted = true
}
tags = { Name = "qdrant-vector-db" }
}
# 2. LLM推論: Llama-3用 GPUインスタンス (g5.2xlarge / A10G 24GB)
resource "aws_instance" "llm_server" {
ami = "ami-0a56114eb37ad4e3e" # Deep Learning OSS AMI with GPU (Ubuntu 22.04)
instance_type = "g5.2xlarge"
subnet_id = aws_subnet.private_subnet.id
vpc_security_group_ids = [aws_security_group.rag_cluster_sg.id]
iam_instance_profile = aws_iam_instance_profile.instance_profile.name
root_block_device {
volume_size = 200 # モデル重み + キャッシュ領域
volume_type = "gp3"
encrypted = true
}
tags = { Name = "llama3-vllm-inference" }
}
3. 閉域網プロビジョニングの勘所:モデル重みの搬入
インターネットが存在しないVPCでは、huggingface-cli download や docker pull が外部から直接行えません。以下の手順でモデルとコンテナを搬入します。
-
S3バケットへの事前ステージング:
管理端末側から、社内許可済みのLlama-3モデル重み(SafeTensors形式)をプライベートS3バケットへアップロードしておきます。 -
S3 Gateway Endpoint経由の高速転送:
閉域網内EC2から、通信料無料のS3エンドポイント経由で直接ローカルSSDへ高速同期します。aws s3 sync s3://my-private-model-bucket/Meta-Llama-3-8B-Instruct/ /opt/models/llama3/ -
vLLM推論エンジンのオフライン起動:
ローカルに展開した重みを指定し、OpenAI互換サーバーとして起動します。python3 -m vllm.entrypoints.openai.api_server \ --model /opt/models/llama3/ \ --host 0.0.0.0 \ --port 8000 \ --gpu-memory-utilization 0.90 \ --max-model-len 8192
4. 閉域網内でのRAG実行検証(Python)
同一サブネット内のアプリケーションサーバーから、プライベートIPを用いてQdrantとLlama-3を連携させる検証スクリプトです。
import requests
from qdrant_client import QdrantClient
from openai import OpenAI
QDRANT_PRIVATE_IP = "10.0.10.XX" # Qdrant EC2のプライベートIP
LLM_PRIVATE_IP = "10.0.10.YY" # GPU EC2のプライベートIP
# 1. Qdrantクライアント接続 (閉域内通信)
qdrant = QdrantClient(host=QDRANT_PRIVATE_IP, port=6333)
# 2. vLLM (OpenAI互換) クライアント接続
client = OpenAI(
base_url=f"http://{LLM_PRIVATE_IP}:8000/v1",
api_key="none-needed-in-private-vpc"
)
def run_private_rag(query: str):
# ① ベクトル検索 (Qdrant)
# ※ 埋め込み表現は閉域内ローカルモデル(bge-m3等)で生成
search_results = qdrant.search(
collection_name="internal_docs",
query_vector=[...], # 埋め込みベクトル
limit=3
)
contexts = "\n".join([hit.payload["text"] for hit in search_results])
# ② Llama-3へのプロンプト投入
response = client.chat.completions.create(
model="/opt/models/llama3/",
messages=[
{"role": "system", "content": "以下の社内機密資料のみに基づいて回答してください。"},
{"role": "user", "content": f"資料:\n{contexts}\n\n質問: {query}"}
],
temperature=0.1
)
return response.choices[0].message.content
print(run_private_rag("当社の2026年度セキュリティ規程におけるパスワード要件は?"))
5. 運用とコスト最適化のベストプラクティス
-
EC2 Instance Connect Endpoint (EIC) / SSMの併用:
パブリック踏み台サーバー(Bastion Host)を完全撤廃し、IAMロールベースで一時的なポートフォワーディングを行うことで、セキュリティ監査(SOC2 / ISO27001)を容易にクリアします。 -
夜間・休日のインスタンス自動停止:
g5.2xlargeは常時稼働で月額約100,000円〜150,000円程度発生します。社内利用時間が平日の日中に限定される場合、AWS EventBridgeとLambdaで夜間に停止・朝に起動するスケジュールを組むことで、インフラコストを約60%削減できます。 -
EBS最適化と暗号化:
モデル保存用のEBSボリュームは必ずKMS(カスタマー管理キー: CMK)で暗号化し、物理的なデータ漏洩リスクをゼロにします。
まとめ:真のエンタープライズAIは「閉域網」に宿る
LLMを業務システムへ深く組み込むほど、「データの主権(Data Sovereignty)」と「物理的な通信隔離」の要求は高まります。
AWS東京リージョン内の閉域VPCに、オープンソース最高峰の推論エコシステム(Qdrant + Llama-3 + vLLM)をIaC(Terraform)で展開することで、「オンプレミスの安全性」と「クラウドの俊敏性」を両立した最高強度のAI基盤が完成します。
NKKTech Globalでは、金融・公共・製造業に向けた閉域網AI基盤の設計・構築から、セキュアなMLOpsパイプラインの運用までを一貫してご支援しています。
お問い合わせ先
自社専用プライベートLLM/RAG基盤の構築、Terraformによるインフラ自動化、クラウドセキュリティ設計に関するご相談は、下記よりお気軽にお問い合わせください。
- Webサイト: https://nkktech.com/
- メール: contact@nkk.com.vn
- LinkedIn: https://www.linkedin.com/company/nkktech
著者:NKKTech Global 技術チーム
私たちは、強固なクラウドセキュリティと最先端のAIエンジニアリングで、エンタープライズのDXをグローバルに支えます。
