はじめに
今回は、socket.io,redis,ioredisを使用してWebSocket通信を行うシステムのインフラ作成時に、はまったポイントを備忘録を兼ねて紹介していきます
システム構成
フロントエンド
Vue.js:ユーザーインターフェースと管理インターフェース。
S3:静的ファイルのホスティング。
CloudFront:グローバルキャッシュと配信。
バックエンド
Nest.js:APIサーバー。
ECS(Elastic Container Service):コンテナオーケストレーション。
その他のAWSサービス
ALB(Application Load Balancer):リクエストをECSタスクにルーティング。
ECR(Elastic Container Registry):Dockerイメージの格納と管理。
Socket.ioを用いてWebSocket通信を行う際、以下のような構成にしました:
- ElastiCache Redisを使ったPub/Subシステムを実装し、複数のECSタスク間でリアルタイム通信を実現。
- albのSticky Sessionを有効にし、ユーザーのWebSocket接続が同じECSタスクに留まるように設定。
問題の詳細
一つ目の問題:ElastiCache Redisとの接続ができない
バックエンドのRedis設定を以下のようにしていました。
import { ConfigService } from "@nestjs/config";
import { Redis, RedisOptions } from "ioredis";
const host = this.configService.get<string>('REDIS_HOST')
const redisOptions: RedisOptions = {
host,
port: 6379,
}
const redisClient = new Redis(redisOptions);
この設定はローカル環境では動作しますが、ECSとElastiCache Redisの疎通ができませんでした。原因は、ElastiCache Redisクラスタへの接続にTLSが必要だったことです。AWSのElastiCache Redisは、セキュリティのためにTLSを使用してデータを暗号化します。ローカル環境ではTLSが必要ありませんが、本番環境ではTLSが必須です。
本番環境でのみTLS設定を追加することで、ECS環境でもElastiCache Redisに接続できるようになりました。
const host = this.configService.get<string>('REDIS_HOST')
const redisTls = process.env.NODE_ENV === 'production' ? {} : false
const redisOptions: RedisOptions = {
host,
port: 6379,
tls: redisTls
}
const redisClient = new Redis(redisOptions);
二つ目の問題:WebSocketのコネクションの確立がうまくいかない
ALBのセッション永続化(sticy session)を利用していました。ALBのセッション永続化は、クライアントが接続するサーバーを特定のCookieを通じて記憶し、同じサーバーに接続し続けることを可能にします。
しかし、Socket.ioはWebSocketの接続前にポーリングを行い、接続を確立します。このポーリングリクエストは独立しており、セッション永続化のCookie情報が含まれていません。その結果、ポーリングリクエストが異なるECSタスクに振り分けられてしまいました。
問題を解決するために以下のステップを踏みました:
-
セッション永続性の再設定: ALBでのセッション永続性を再確認し、適切に設定。
-
Socket.ioの設定見直し:
-
transportsオプションでWebSocketのみを使用するように設定。 - これにより、ポーリングを回避し、直接WebSocket接続を確立するようにしました。
-
const socket = io(url, {
transports: ['websocket'],
upgrade: false,
autoConnect: false
});
まとめ
今回はWebSocketを使用したインフラ作成時に詰まった部分を紹介しました。
この記事が、同様の事象が発生している人の解決に役立てば幸いです。
ここまで読んでくださりありがとうございました!