仮想待合室 SaaS "NetFUNNEL" に入門してみた ~CloudFront連携がとても簡単だった件~
中山です。
この記事では、韓国産の仮想待合室サービスであるNetFUNNELを紹介します。
今回はCloudFrontと連携する方法を試してみましたが非常に簡単でした。
仮想待合室とは
そもそも、仮想待合室とは何かを簡単に説明します。
仮想待合室とは、Webサービスへのトラフィックを制御するソリューションです。
予め設定した流量を超えたトラフィックは待合室にリダイレクトし、アクセスが可能になったら順次オリジンにルーティングする仕組みです。
スケールアウトが難しい(オンプレミス環境)/ Auto Scalingでは対応が難しいレベルのスパイクアクセスが発生するような状況でも、サーバーをダウンさせずにサービスを継続させるのに有効です。
代表的なサービスとしては Cloudflare Waiting Room 等があります。
活用事例に関しては DevelopersIO でも紹介しています。
NetFUNNELとは
NetFUNNEL は、韓国の STCLab 社が提供する仮想待合室サービスです。
韓国国内におけるシェアは97%とのことで、とても支持されているサービスのようです。
仕組みとしては、クライアント・エッジ・サーバーのいずれかでエージェントが動作し、それによってトラフィックが制御されます。
詳細な機能に関しては、このあとの「やってみた」の中で適宜説明していきます。
やってみた
それでは、実際に使ってみたいと思います。
前提として、ライセンスの発行が完了しており管理画面にログインできる状態であるとします。
また、今回はエッジ(CloudFront)にエージェントを導入する方式を試してみます。
理由は、一番簡単かなーと思ったからですw
流れは以下の通りです。
- 保護対象の環境を構築
- プロジェクトの作成
- セグメントの作成
- CloudFront Distributionへの組み込み
- 動作確認
保護対象の環境を構築
まずは、NetFUNNELを設定する対象のサイトを構築します。
今回は、CloudFrontとS3を利用した簡単な静的Webサイトを用意します。
細かい解説は省きますが、以下の通りTerraformで作成してみました。
CloudFront + S3
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
resource "aws_s3_bucket" "origin" {
bucket = "my-cloudfront-origin-bucket-${data.aws_caller_identity.current.account_id}-${data.aws_region.current.region}"
}
resource "aws_cloudfront_origin_access_control" "oac" {
name = "s3-oac"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
resource "aws_cloudfront_distribution" "distribution" {
enabled = true
origin {
domain_name = aws_s3_bucket.origin.bucket_regional_domain_name
origin_id = aws_s3_bucket.origin.id
origin_access_control_id = aws_cloudfront_origin_access_control.oac.id
}
default_cache_behavior {
target_origin_id = aws_s3_bucket.origin.id
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
# AWS managed cache policy: CachingOptimized
# https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html#managed-cache-caching-optimized
cache_policy_id = "658327ea-f89d-4fab-a63d-7e88639e58f6"
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
data "aws_iam_policy_document" "origin" {
statement {
sid = "AllowCloudFrontServicePrincipal"
effect = "Allow"
principals {
type = "Service"
identifiers = ["cloudfront.amazonaws.com"]
}
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.origin.arn}/*"]
condition {
test = "StringEquals"
variable = "AWS:SourceArn"
values = [aws_cloudfront_distribution.distribution.arn]
}
}
}
resource "aws_s3_bucket_policy" "origin" {
bucket = aws_s3_bucket.origin.id
policy = data.aws_iam_policy_document.origin.json
}
resource "aws_s3_object" "index" {
bucket = aws_s3_bucket.origin.id
key = "index.html"
content_type = "text/html"
content = <<-HTML
<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8"><title>Hello</title></head>
<body><h1>Hello from S3 + CloudFront</h1></body>
</html>
HTML
}
output "cloudfront_domain_name" {
value = aws_cloudfront_distribution.distribution.domain_name
}
output "s3_bucket_name" {
value = aws_s3_bucket.origin.bucket
}
https://d1xxxxxxxxxxxx.cloudfront.net/index.html にアクセスして画面が表示できることを確認しました。
プロジェクトの作成
ここからがNetFUNNELの設定になります。
まずは、プロジェクトを作成します。
プロジェクトとは何か、については公式ドキュメントをご確認ください。
プロジェクトは NetFUNNEL をインストール・稼働させる作業の単位です。通常、ドメインごとに 1 つのプロジェクトを適用します。
NetFUNNELのホーム画面からプロジェクトを追加できます。

プロジェクトの名前とメインドメインを指定します。
今回は独自ドメイン無しのCloudFrontで検証するので、 メインドメインには cloudfront.net を設定しました。
セグメントの作成
プロジェクト内にセグメントを作成します。
セグメントとは、NetFUNNELにおいて、アクセス制御を行う単位(区間)のことです。
ウェブサイト上の特定のページやURLパスごとにセグメントを設定し、セグメント単位でユーザーの流入量(同時接続数)を制御・管理できます。
NetFUNNELにおいては以下の2種類のセグメントを作成できます。
- 基本制御
- 特定のアクションや単一URLに対してトラフィックを制御
- トラフィックの急増地点が明確な場合に推奨
- 区間制御
- 特定の区間に対するトラフィックを制御
- トラフィックの急増地点が不明確な場合やサイト内の特定区間で高いトラフィックの発生が予想される場合に推奨
前提条件や要件によってどちらを利用するべきか変わってくるため、詳細な違いは公式ドキュメントを確認してください。
今回はエッジに組み込みますが、その場合には基本制御のみサポートしているので基本制御を選びます。


以降はセグメント作成の詳細を見ていきます。
進入制御モデル設定
進入制御モデルには2種類あります。
- 変動進入型
- 固定進入型
変動進入型はリアルタイムのユーザー流入量に合わせて待機列を流動的に管理したいケース等で推奨されます。
固定進入型はオリジンに一定量のトラフィックをルーティングすることでユーザーに精度の高い待機順番と予想時間を案内できます。
それぞれにメリット・デメリットがあるので要件に応じて選択してください。
今回は「変動進入型」を選択します。

基本設定
セグメントの名前とセグメントキーを指定します。
セグメントキーは自動で設定されますが、プロジェクト内でセグメントを一意に識別できればOKです。
エッジにエージェントをインストールする場合には利用しません。

進入状態設定
セグメントの有効化・無効化を行います。
また、指定した流量を超えたユーザーを待機させるかブロックするかを選択します。

待合室適用
表示する待合室・ブロックルームを指定します。

今回は事前定義済のサンプルページを利用しますが、独自にカスタマイズしたものを利用する事も可能です。
カスタマイズについては本記事では割愛します。
詳細はドキュメントを確認してください。
進入許容数の設定
進入許容数を指定します。
新規作成するにあたり、「固定型:0」を指定しておきます。
これにより、全てのユーザーが一旦待合室にルーティングされる状態となります。

待機ルール設定
「どのリクエストに対してトラフィック制御を適用するか」を指定します。
また、今回は指定しませんが待機完了後にリダイレクトするページを指定する事もできます。
今回は最初に作成したS3オブジェクトへのPathを指定します。

待機通知設定
NetFUNNELの管理者向けおよび利用者向けの通知設定を行います。
今回はいずれもデフォルト(いずれも無効)の状態で次へ進みます。

高度な設定
そのほかの設定を行います。
今回はデフォルトのままとします。

詳細はドキュメントをご確認ください。
CloudFront Distributionへの組み込み
管理画面上での設定が完了しました。
続いてエッジにエージェントをインストールします。
CloudFrontの場合、Lambda@Edgeを利用します。
開発者ガイドに詳細な手順が記載されており、記載の通り設定することで実装できました。
なお、Client IDは管理コンソールの「統合資格証明」から確認できます。

必要なリソースを追加します。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
+ archive = {
+ source = "hashicorp/archive"
+ version = "~> 2.0"
+ }
+ local = {
+ source = "hashicorp/local"
+ version = "~> 2.0"
+ }
}
}
variable "client_id" {
description = "NetFUNNEL Client ID"
type = string
}
locals {
lambda_src_dir = "${path.root}/lambda_src"
}
data "aws_iam_policy_document" "lambda_assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = [
"lambda.amazonaws.com",
"edgelambda.amazonaws.com",
]
}
actions = ["sts:AssumeRole"]
}
}
resource "aws_iam_role" "netfunnel_agent" {
name = "netfunnel-cloudfront-agent"
assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json
}
resource "aws_iam_role_policy_attachment" "netfunnel_agent_logs" {
role = aws_iam_role.netfunnel_agent.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "terraform_data" "download_agent" {
triggers_replace = timestamp() # Plan / Apply時に毎回ダウンロードされるので、必要に応じて改善してください。
provisioner "local-exec" {
command = "mkdir -p ${local.lambda_src_dir} && curl -fsSL -o ${local.lambda_src_dir}/netfunnel-cloudfront-agent.js https://agent-lib.stclab.com/agents/cdn/cloudfront/netfunnel-cloudfront-agent-latest.js"
}
}
resource "local_file" "index_mjs" {
filename = "${local.lambda_src_dir}/index.mjs"
depends_on = [terraform_data.download_agent]
content = <<-JS
import handleEvent from './netfunnel-cloudfront-agent.js';
const config = {
clientID: "${var.client_id}",
};
export const handler = async (event) => {
return await handleEvent(event, config);
};
JS
}
resource "local_file" "package_json" {
filename = "${local.lambda_src_dir}/package.json"
depends_on = [terraform_data.download_agent]
content = <<-JSON
{
"name": "cloudfront-agent",
"type": "module",
"dependencies": {}
}
JSON
}
data "archive_file" "lambda" {
type = "zip"
source_dir = local.lambda_src_dir
output_path = "${path.root}/lambda.zip"
depends_on = [terraform_data.download_agent, local_file.index_mjs, local_file.package_json]
}
resource "aws_lambda_function" "netfunnel_agent" {
function_name = "netfunnel-cloudfront-agent"
role = aws_iam_role.netfunnel_agent.arn
filename = data.archive_file.lambda.output_path
source_code_hash = data.archive_file.lambda.output_base64sha256
handler = "index.handler"
runtime = "nodejs24.x"
publish = true
}
ちなみに、Lambda@Edge では環境変数がサポートされていませんのでご注意ください。
CloudFront DistributionにLambda関数を関連付けます。
resource "aws_cloudfront_distribution" "distribution" {
enabled = true
origin {
domain_name = aws_s3_bucket.origin.bucket_regional_domain_name
origin_id = aws_s3_bucket.origin.id
origin_access_control_id = aws_cloudfront_origin_access_control.oac.id
}
default_cache_behavior {
target_origin_id = aws_s3_bucket.origin.id
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
# AWSマネージドキャッシュポリシー: CachingOptimized
cache_policy_id = "658327ea-f89d-4fab-a63d-7e88639e58f6"
+ lambda_function_association {
+ event_type = "viewer-request"
+ lambda_arn = aws_lambda_function.netfunnel_agent.qualified_arn
+ include_body = false
+ }
+
+ lambda_function_association {
+ event_type = "viewer-response"
+ lambda_arn = aws_lambda_function.netfunnel_agent.qualified_arn
+ include_body = false
+ }
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
Apply時には Client IDを指定してください。
terraform apply -var="client_id=xxxxxxxxxxxx-xxxx"
リソースの作成が完了したらキャッシュをクリアします。
※ Lambda@Edge を Viewer request / Viewer response に設定しているため、キャッシュヒット有無に関わらず関数は実行されます。ただし、NetFUNNEL の開発者ガイドではキャッシュクリアが手順に含まれているため、ここではそれに従います。
To execute the function for every request that CloudFront receives for the distribution, use the viewer request or viewer response events.
動作確認
待合室
それでは、ターゲットにアクセスしてみます。
進入許容数を0にしているので、想定通り待合室を表示できました。

それでは、進入許容数を1に変更してみます。


すると、待合室を抜けて用意したページに自動で遷移しました。
ロードテスター
NetFUNNELにはセグメントに対して仮想的なトラフィックを発生させるロードテスター機能があります。
これにより、待合室での待機が正しく行えていそうかを確認できます。


ロードテスターを起動後にサイトにアクセスすると、待機列に並んでいることを確認できます。

また、管理者は待機列の状態を管理画面でモニタリングできます。

まとめ
本記事では、仮想待合室サービス「NetFUNNEL」をCloudFrontと連携する方法を紹介しました。
実際に試してみた所感としては、管理画面でのプロジェクト・セグメントの作成から、Lambda@Edgeを利用したCloudFrontへの組み込みまで、開発者ガイドに沿って進めるだけで非常に簡単に導入できました。
今回はエッジ(CloudFront)への組み込みで基本制御のみを試しましたが、クライアントやサーバーへのエージェント導入による区間制御や、待合室のカスタマイズなど、まだまだ試せていない機能も多くあります。
スパイクアクセスへの対策として仮想待合室の導入を検討されている方は、選択肢のひとつとしてNetFUNNELを検討してみてはいかがでしょうか。
現場からは以上です。






