-
-
Notifications
You must be signed in to change notification settings - Fork 3
Description
概要
AppSync の WebSocket エンドポイントにカスタムドメイン graphql.api.smalruby.app を設定し、エンドポイントの URL を固定化します。これにより、WAF やネットワークフィルターの管理、将来的なバックエンドの切り替えが容易になります。
背景
現在、AppSync API は AWS が自動生成する URL(例: https://xxxxx.appsync-api.ap-northeast-1.amazonaws.com/graphql)を使用しています。この URL は以下の課題があります:
- API ID が変わるとエンドポイント URL も変わる
- WAF やファイアウォールのホワイトリスト管理が困難
- ブランディングやユーザー体験の観点で改善の余地がある
カスタムドメインを設定することで、これらの課題を解決できます。
STAGE による振る舞い
カスタムドメイン設定は、デプロイする STAGE(環境)によってドメイン名が異なりますが、設定方法は統一されています。
prod(本番環境)
- ドメイン:
graphql.api.smalruby.app - 証明書: CDK が自動作成(既存の場合は再利用)
- Route 53 ホストゾーン:
api.smalruby.app(事前に手動作成、すでに存在) - CNAME レコード: CDK が自動作成(既存の場合は再利用)
- 削除:
cdk destroy時に証明書と CNAME を自動削除
stg / stg2(ステージング環境)
- ドメイン:
stg.graphql.api.smalruby.app/stg2.graphql.api.smalruby.app - 証明書: CDK が自動作成(既存の場合は再利用)
- Route 53 ホストゾーン:
api.smalruby.app(事前に手動作成、すでに存在、prod と共通) - CNAME レコード: CDK が自動作成(既存の場合は再利用)
- 削除:
cdk destroy時に証明書と CNAME を自動削除
メリット: すべての環境で CDK による自動管理が可能。ホストゾーン api.smalruby.app のみ手動で事前作成(すでに存在)。
タスク
1. 環境変数の設定
1.1 infra/mesh-v2/.env.example
infra/mesh-v2/.env.example に以下の環境変数を追加:
# AppSync Custom Domain
# Production: graphql.api.smalruby.app
# Staging: stg.graphql.api.smalruby.app / stg2.graphql.api.smalruby.app
APPSYNC_CUSTOM_DOMAIN=graphql.api.smalruby.app
# Route 53 Parent Hosted Zone Name (required)
# This hosted zone should already exist (api.smalruby.app)
ROUTE53_PARENT_ZONE_NAME=api.smalruby.app注意:
APPSYNC_CERTIFICATE_ARNは不要(CDK が自動作成)ROUTE53_HOSTED_ZONE_IDは不要(CDK が lookup で検索)
1.2 smalruby3-develop/.env.example
smalruby3-develop/.env.example の MESH_GRAPHQL_ENDPOINT をカスタムドメインに更新:
# Mesh V2 拡張機能設定
MESH_GRAPHQL_ENDPOINT=https://graphql.api.smalruby.app/graphql
MESH_API_KEY=da2-your-api-key
MESH_AWS_REGION=ap-northeast-1
MESH_SECRET_KEY=your-secret-key-for-domain-hashing1.3 gui/scratch-vm/.env.example
gui/scratch-vm/.env.example の MESH_GRAPHQL_ENDPOINT をカスタムドメインに更新:
# Mesh V2 Extension Configuration
# AppSync GraphQL API Endpoint (Production)
MESH_GRAPHQL_ENDPOINT=https://graphql.api.smalruby.app/graphql
# AppSync API Key (Production)
# Example: da2-xxxxx
MESH_API_KEY=da2-your-api-key
# AWS Region (Production)
# Example: ap-northeast-1
MESH_AWS_REGION=ap-northeast-11.4 gui/smalruby3-gui/.env.example
gui/smalruby3-gui/.env.example の MESH_GRAPHQL_ENDPOINT をカスタムドメインに更新:
# Mesh V2 Extension Configuration
# See https://github.com/smalruby/scratch-vm/tree/develop/ for backend setup
# AppSync GraphQL API Endpoint (Production)
MESH_GRAPHQL_ENDPOINT=https://graphql.api.smalruby.app/graphql
# AppSync API Key (Production)
# Example: da2-xxxxx
MESH_API_KEY=da2-your-api-key
# AWS Region (Production)
# Example: ap-northeast-1
MESH_AWS_REGION=ap-northeast-12. CDK実装 (infra/mesh-v2/lib/mesh-v2-stack.ts)
// Add imports
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import * as route53 from 'aws-cdk-lib/aws-route53';
// Read custom domain configuration from environment variables
const customDomainBase = process.env.APPSYNC_CUSTOM_DOMAIN || '';
const parentZoneName = process.env.ROUTE53_PARENT_ZONE_NAME || 'api.smalruby.app';
// Domain name based on stage
const stageDomain = stage === 'prod' ? customDomainBase : `${stage}.${customDomainBase}`;
// Certificate and DNS configuration
let certificate: acm.ICertificate | undefined;
let hostedZone: route53.IHostedZone | undefined;
if (customDomainBase) {
// Lookup existing parent hosted zone (api.smalruby.app)
hostedZone = route53.HostedZone.fromLookup(this, 'ParentHostedZone', {
domainName: parentZoneName,
});
// Create certificate with DNS validation (reuses existing if available)
certificate = new acm.Certificate(this, 'AppSyncCertificate', {
domainName: stageDomain,
validation: acm.CertificateValidation.fromDns(hostedZone),
});
}
// AppSync GraphQL API with custom domain
this.api = new appsync.GraphqlApi(this, 'MeshV2Api', {
name: `MeshV2Api${stageSuffix}`,
definition: appsync.Definition.fromFile(path.join(__dirname, '../graphql/schema.graphql')),
authorizationConfig: {
// ... existing config ...
},
// Custom domain configuration
domainName: certificate && stageDomain
? {
certificate,
domainName: stageDomain,
}
: undefined,
// ... existing config ...
});
// Create CNAME record in parent hosted zone if custom domain is configured
if (customDomainBase && hostedZone) {
new route53.CnameRecord(this, 'AppSyncCnameRecord', {
recordName: stageDomain,
zone: hostedZone,
domainName: this.api.appSyncDomainName,
ttl: cdk.Duration.seconds(300),
});
}
// Output custom domain endpoints if configured
if (customDomainBase) {
new cdk.CfnOutput(this, 'CustomDomainName', {
value: stageDomain,
description: 'Custom domain name for AppSync API',
});
new cdk.CfnOutput(this, 'AppSyncDomainName', {
value: this.api.appSyncDomainName,
description: 'AppSync CloudFront domain (CNAME target)',
});
if (certificate) {
new cdk.CfnOutput(this, 'CertificateArn', {
value: certificate.certificateArn,
description: 'ACM Certificate ARN',
});
}
}3. Route 53 ホストゾーンの確認
ホストゾーン api.smalruby.app がすでに存在することを確認します。
3.1 AWS Console での確認
- AWS マネジメントコンソールで Route 53 を開く
- 「ホストゾーン」を選択
api.smalruby.appが存在することを確認- 存在しない場合は新規作成(以下の手順)
3.2 ホストゾーンの新規作成(必要な場合のみ)
- 「ホストゾーンの作成」をクリック
- ホストゾーン設定:
- ドメイン名:
api.smalruby.app - タイプ: パブリックホストゾーン
- ドメイン名:
- 「ホストゾーンの作成」をクリック
3.3 親ドメインへの NS レコード追加(必要な場合)
smalruby.app の DNS 設定で、api.smalruby.app のネームサーバーを委任:
smalruby.appのホストゾーンを開く- 「レコードを作成」をクリック
- レコード設定:
- レコード名:
api - レコードタイプ:
NS - 値:
api.smalruby.appのネームサーバー(4つ) - TTL:
172800(48時間)
- レコード名:
- 「レコードを作成」をクリック
4. 環境変数の設定
すべての環境で同じ環境変数を使用します。
# infra/mesh-v2/.env
APPSYNC_CUSTOM_DOMAIN=graphql.api.smalruby.app
ROUTE53_PARENT_ZONE_NAME=api.smalruby.appステージング環境の場合:
# STAGE を指定するだけで、ドメイン名が自動的に stg.graphql.api.smalruby.app になります
STAGE=stg # または stg25. CDKデプロイ
5.1 本番環境(prod)
cd infra/mesh-v2
npx cdk deploy --context stage=prod5.2 ステージング環境(stg/stg2)
cd infra/mesh-v2
npx cdk deploy --context stage=stg
# または
npx cdk deploy --context stage=stg2デプロイ完了後、以下の情報が出力されます:
CustomDomainName: 設定したカスタムドメインAppSyncDomainName: CloudFront ドメイン(CNAME ターゲット)CertificateArn: 作成された証明書の ARN
6. クライアント環境変数の更新
カスタムドメイン設定完了後、各環境の .env ファイルを更新します:
6.1 本番環境
# smalruby3-develop/.env
MESH_GRAPHQL_ENDPOINT=https://graphql.api.smalruby.app/graphql
# gui/scratch-vm/.env
MESH_GRAPHQL_ENDPOINT=https://graphql.api.smalruby.app/graphql
# gui/smalruby3-gui/.env
MESH_GRAPHQL_ENDPOINT=https://graphql.api.smalruby.app/graphql6.2 ステージング環境
# smalruby3-develop/.env
MESH_GRAPHQL_ENDPOINT=https://stg.graphql.api.smalruby.app/graphql
# gui/scratch-vm/.env
MESH_GRAPHQL_ENDPOINT=https://stg.graphql.api.smalruby.app/graphql
# gui/smalruby3-gui/.env
MESH_GRAPHQL_ENDPOINT=https://stg.graphql.api.smalruby.app/graphql注意: これらの環境変数は webpack でビルド時に埋め込まれるため、変更後は再ビルドが必要です:
# scratch-vm の再ビルド
cd gui/scratch-vm
npm run build
# smalruby3-gui の再ビルド
cd gui/smalruby3-gui
npm run build7. WAF設定(オプション)
特定のオリジンからのアクセスのみを許可するWAFルールを設定します。
7.1 WAF WebACL の作成(CDK実装)
import * as wafv2 from 'aws-cdk-lib/aws-wafv2';
// Allowed origins
const allowedOrigins = [
'http://localhost:8601',
'https://smalruby.app',
'https://smalruby.jp',
];
// Create WAF WebACL
const webAcl = new wafv2.CfnWebACL(this, 'AppSyncWebACL', {
scope: 'REGIONAL',
defaultAction: { block: {} },
rules: [
{
name: 'AllowSpecificOrigins',
priority: 1,
statement: {
orStatement: {
statements: allowedOrigins.map(origin => ({
byteMatchStatement: {
searchString: origin,
fieldToMatch: { singleHeader: { name: 'origin' } },
textTransformations: [{ priority: 0, type: 'LOWERCASE' }],
positionalConstraint: 'EXACTLY',
},
})),
},
},
action: { allow: {} },
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'AllowSpecificOrigins',
},
},
],
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'AppSyncWebACL',
},
});
// Associate WAF with AppSync API
new wafv2.CfnWebACLAssociation(this, 'AppSyncWafAssociation', {
resourceArn: this.api.arn,
webAclArn: webAcl.attrArn,
});
// Output WAF ARN
new cdk.CfnOutput(this, 'WebACLArn', {
value: webAcl.attrArn,
description: 'WAF WebACL ARN',
});7.2 WAF設定の注意点
- Origin ヘッダーの制限: ブラウザからのリクエストでは Origin ヘッダーが送信されますが、サーバー間通信やネイティブアプリからのリクエストでは送信されない場合があります
- WebSocket の制限: WebSocket 接続の場合、初期の HTTP ハンドシェイク時のみ Origin ヘッダーがチェックされます
- 開発環境:
localhost:8601からのアクセスを許可していますが、本番環境では削除を検討してください - CORS設定との関係: WAF は CORS とは独立して動作します。CORS ヘッダーは AppSync 側で自動的に処理されます
検証手順
1. DNS伝播の確認
# CNAME レコードの確認(本番環境)
dig graphql.api.smalruby.app CNAME
# CNAME レコードの確認(ステージング環境)
dig stg.graphql.api.smalruby.app CNAME
# 結果例:
# graphql.api.smalruby.app. 300 IN CNAME d123456789.cloudfront.net.DNS が世界中に浸透するまで数分から最大 1 時間程度かかる場合があります。
2. カスタムドメインでの接続テスト
# GraphQL エンドポイントへのリクエスト(本番環境)
curl -X POST https://graphql.api.smalruby.app/graphql \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{"query":"{ __typename }"}'
# GraphQL エンドポイントへのリクエスト(ステージング環境)
curl -X POST https://stg.graphql.api.smalruby.app/graphql \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{"query":"{ __typename }"}'
# 期待される応答:
# {"data":{"__typename":"Query"}}3. WebSocket接続テスト
JavaScript クライアントでの接続テスト:
import { ApolloClient, InMemoryCache, HttpLink, split } from '@apollo/client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
import { getMainDefinition } from '@apollo/client/utilities';
const httpLink = new HttpLink({
uri: 'https://graphql.api.smalruby.app/graphql', // または https://stg.graphql.api.smalruby.app/graphql
headers: {
'x-api-key': 'YOUR_API_KEY',
},
});
const wsLink = new GraphQLWsLink(createClient({
url: 'wss://graphql.api.smalruby.app/graphql/realtime', // または wss://stg.graphql.api.smalruby.app/graphql/realtime
connectionParams: {
headers: {
'x-api-key': 'YOUR_API_KEY',
},
},
}));
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
);
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});4. アプリケーションでの動作確認
環境変数を更新してビルドした後、実際のアプリケーションでMesh拡張機能が動作することを確認:
# Docker環境で開発サーバーを起動
docker compose up gui
# ブラウザで http://localhost:8601 にアクセス
# Mesh拡張機能でグループ作成・参加が正常に動作することを確認参考資料
- AWS AppSync Custom Domain Names - CDK Documentation
- Using AWS WAF to protect AWS AppSync APIs
- ACM Certificate Validation
- Route 53 CNAME Records
- Route 53 Hosted Zones
完了条件
共通(すべての環境)
- Route 53 ホストゾーン
api.smalruby.appの確認(すでに存在) - 環境変数の設定完了(infra/mesh-v2)
- CDK実装の完了
- CDKデプロイ(証明書と CNAME が自動作成される)
- DNS伝播の確認
- カスタムドメインでの GraphQL リクエスト成功
- カスタムドメインでの WebSocket 接続成功
- クライアント環境変数の更新と再ビルド(scratch-vm, smalruby3-gui, smalruby3-develop)
- アプリケーションでの動作確認
- (オプション)WAF設定の完了と動作確認
追加情報
ロールバック手順
すべての環境で同じ手順:
- クライアントの
.envファイルを AWS 自動生成 URL に戻す npx cdk destroy --context stage=<環境>を実行(証明書と CNAME が自動削除)- 再ビルド
注意: ホストゾーン api.smalruby.app は削除されません(手動作成したため)。
コスト影響
すべての環境共通
- ACM証明書: 無料
- Route 53 ホストゾーン
api.smalruby.app: 月額 $0.50(共通で使用) - Route 53 CNAME レコード: クエリ料金のみ($0.40 / 100万クエリ)
- CloudFront(AppSync背後): カスタムドメイン使用による追加コストなし
- WAF(オプション): WebACL あたり月額 $5.00 + ルールごと $1.00 + リクエスト料金
コスト削減のヒント
- ステージング環境(stg/stg2)は不要な場合
cdk destroyで削除 - ホストゾーン
api.smalruby.appは共通で使用するためコスト効率が良い
セキュリティ考慮事項
- 証明書の管理: ACM証明書は自動更新されますが、DNS検証レコードを削除しないように注意
- WAFルール: 本番環境では localhost からのアクセスを削除することを推奨
- APIキー: カスタムドメイン設定後も既存の認証設定(API キー)は継続して必要
- 環境変数の管理:
.envファイルは.gitignoreに含まれているため、各環境で個別に設定が必要 - ステージング環境の削除: 不要な stg/stg2 環境は
cdk destroyで削除してセキュリティリスクを最小化
影響範囲
このカスタムドメイン設定は以下のコンポーネントに影響します:
- infra/mesh-v2: CDK スタック(AppSync API、ACM証明書、Route53 CNAME の設定)
- gui/scratch-vm: Mesh拡張機能のGraphQLクライアント
- gui/smalruby3-gui: Webpackビルド設定(環境変数の埋め込み)
- smalruby3-develop: 全体的な開発環境設定
すべてのコンポーネントで環境変数を統一することで、一貫性のある設定を維持できます。
STAGE による動作の違い(まとめ)
| 項目 | prod | stg / stg2 |
|---|---|---|
| ドメイン | graphql.api.smalruby.app |
stg.graphql.api.smalruby.app / stg2.graphql.api.smalruby.app |
| ホストゾーン | api.smalruby.app(手動作成、共通) |
api.smalruby.app(手動作成、共通) |
| 証明書作成 | 自動(CDK) | 自動(CDK) |
| CNAME作成 | 自動(CDK) | 自動(CDK) |
| 削除方法 | cdk destroy で証明書と CNAME 削除 |
cdk destroy で証明書と CNAME 削除 |
| 環境変数 | APPSYNC_CUSTOM_DOMAINROUTE53_PARENT_ZONE_NAME |
APPSYNC_CUSTOM_DOMAINROUTE53_PARENT_ZONE_NAMESTAGE=stg または stg2 |
| 用途 | 本番運用 | 開発・検証 |
重要: すべての環境で同じ CDK コードと環境変数を使用。STAGE の値だけでドメイン名が自動的に変わります。