Oasys Nodeを本番環境に立てるとき、SGとNLBで悩んだ話

Epic Quest CTOの森本です。

Oasysのノードを構築する機会があり、セキュリティグループ(SG)の設定でけっこう悩んだので共有します。結論から言うと、NLB(Network Load Balancer)を立てることで解決しました。

Oasysとは

軽く説明すると、Oasysはゲームに特化したブロックチェーン。2層構造になっていて:

  • Hub層:メインのチェーン。バリデータが動いてる
  • Verse層:ゲームごとのL2チェーン。Homeverseとか

今回構築したのはVerse層のレプリカノード。チェーンのデータを同期して、APIリクエストに応答できるようにするやつ。

基本構成

EC2 + Docker Composeでノードを動かす。構成はシンプル。

EC2 (Ubuntu)
├── geth (実行クライアント)
└── (必要に応じて) Blockscout

Terraformで構築を自動化してる。インスタンスタイプはm5.xlargeくらいあれば十分。ディスクは同期するチェーンの長さによるけど、500GB〜1TBあると安心。

P2P通信に必要なポート

ブロックチェーンノードはP2Pで他のノードと通信する。gethの場合、以下のポートを使う:

ポート プロトコル 用途
30303 TCP/UDP P2P通信(ノード間同期)
8545 TCP JSON-RPC(HTTP)
8546 TCP JSON-RPC(WebSocket)

30303は他ノードとブロックデータをやり取りするのに必須。8545/8546はAPIエンドポイント。

SGの罠:全開放したくなる誘惑

開発環境でサクッと動かすなら、SGはこうなる:

# 開発環境:雑に全開放
ingress {
  from_port   = 30303
  to_port     = 30303
  protocol    = "tcp"
  cidr_blocks = ["0.0.0.0/0"]
}

ingress {
  from_port   = 30303
  to_port     = 30303
  protocol    = "udp"
  cidr_blocks = ["0.0.0.0/0"]
}

これで動く。他ノードからの接続も来るし、同期も始まる。

問題は、本番環境でこれが許されないこと。

セキュリティ要件的に「0.0.0.0/0でインバウンド全開放」は突っ込まれる。当然といえば当然。

でもP2P通信の相手は世界中のノード。IPアドレスを事前に特定して許可リストに入れるのは現実的じゃない。

解決策:NLBを前段に立てる

NLB(Network Load Balancer)を前段に置くことで解決した。

インターネット
    ↓
  NLB (固定IP)
    ↓
  EC2 (プライベートサブネット)

NLBを使う理由

  • 固定IPが使える:Elastic IPをNLBに紐づけられる
  • L4ロードバランサー:TCP/UDPをそのまま通せる(ALBはHTTP/HTTPSのみ)
  • セキュリティグループの分離:EC2のSGはNLBからのみ許可すればいい

構成のポイント

# NLB
resource "aws_lb" "node" {
  name               = "oasys-node-nlb"
  internal           = false
  load_balancer_type = "network"
  subnets            = var.public_subnet_ids

  enable_cross_zone_load_balancing = true
}

# ターゲットグループ(P2P用)
resource "aws_lb_target_group" "p2p_tcp" {
  name     = "oasys-p2p-tcp"
  port     = 30303
  protocol = "TCP"
  vpc_id   = var.vpc_id

  health_check {
    protocol = "TCP"
    port     = 30303
  }
}

resource "aws_lb_target_group" "p2p_udp" {
  name     = "oasys-p2p-udp"
  port     = 30303
  protocol = "UDP"
  vpc_id   = var.vpc_id

  health_check {
    protocol = "TCP"
    port     = 30303
  }
}

# リスナー
resource "aws_lb_listener" "p2p_tcp" {
  load_balancer_arn = aws_lb.node.arn
  port              = 30303
  protocol          = "TCP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.p2p_tcp.arn
  }
}

resource "aws_lb_listener" "p2p_udp" {
  load_balancer_arn = aws_lb.node.arn
  port              = 30303
  protocol          = "UDP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.p2p_udp.arn
  }
}

EC2側のSG

EC2はプライベートサブネットに置いて、SGはNLBからのトラフィックのみ許可。

# EC2のSG:NLBからのみ許可
ingress {
  from_port   = 30303
  to_port     = 30303
  protocol    = "tcp"
  cidr_blocks = var.nlb_subnet_cidrs  # NLBがいるサブネットのCIDR
}

ingress {
  from_port   = 30303
  to_port     = 30303
  protocol    = "udp"
  cidr_blocks = var.nlb_subnet_cidrs
}

これで「EC2に直接0.0.0.0/0を開けてない」状態になる。セキュリティ監査的にも説明しやすい。

gethの設定

NLBを使う場合、gethに外部IPを教えてあげる必要がある。

# docker-compose.yml
services:
  geth:
    image: ghcr.io/oasysgames/oasys-validator:v1.x.x
    command:
      - --nat=extip:${NLB_PUBLIC_IP}
      - --port=30303
      - --http
      - --http.addr=0.0.0.0
      - --http.port=8545
      - --http.api=eth,net,web3
      - --ws
      - --ws.addr=0.0.0.0
      - --ws.port=8546

--nat=extip:${NLB_PUBLIC_IP} がポイント。これを指定しないと、他ノードに自分のプライベートIPを広告してしまい、接続が確立できない。

Blockscoutも立てた

ノードだけだとAPIは使えるけど、トランザクションを人間が見るのは辛い。Blockscout(ブロックエクスプローラー)も一緒に立てた。

# docker-compose.yml に追加
services:
  blockscout:
    image: blockscout/blockscout:latest
    environment:
      - ETHEREUM_JSONRPC_HTTP_URL=http://geth:8545
      - DATABASE_URL=postgresql://...
      - NETWORK=Homeverse
    ports:
      - "4000:4000"

これでトランザクションやアドレスの残高をブラウザから確認できるようになる。開発時のデバッグにも便利。

ハマったポイント

1. UDPリスナーのヘルスチェック

NLBのUDPリスナーはヘルスチェックにUDPを使えない。TCPでやる必要がある。同じポートでTCP/UDP両方リッスンしてるgethだから問題なかったけど、UDP専用のサービスだと工夫が必要。

2. 同期に時間がかかる

チェーンの長さによるけど、フル同期には数時間〜数日かかる。最初は「動いてないのでは?」と不安になるけど、ログを見て着実にブロックが増えてれば大丈夫。

# 同期状況の確認
docker compose logs -f geth | grep "Imported new"

3. ディスクI/O

同期中はディスクI/Oがボトルネックになりやすい。gp3でIOPS上げるか、io1/io2を検討。

まとめ

Oasys Nodeを本番環境に立てるとき、SGの全開放は避けたい。NLBを前段に立てることで:

  • EC2のSGは限定的な許可で済む
  • 固定IPが使える
  • セキュリティ監査で説明しやすい

P2P通信を扱うサービス全般に使えるパターンなので、参考になれば。


質問や感想があれば、お問い合わせからどうぞ。