ALB では標準で WebSocket プロトコル機能に対応しているため、HTTP/HTTPS リスナーで受けて HTTP/1.1 から Upgrade で WebSocket 通信を開始することができます。
しかしながら、CLB, NLB では、ELB の機能としては対応しておりませんが、TCP リスナーを経由してバックエンドで WebSocket の機能に対応することで実現可能です。

CLB を利用することで、TCP レイヤーではスティッキーセッションのようなセッション維持機能を利用することができないので、nginx でプロキシし、その際、接続元の IP アドレスの情報は Proxy Protocol を利用することで、WebSocket 通信を行います。
ALB では、Upgrade からそれに続く WebSocket 通信の間でスティッキーセッション機能を利用することもできますが、WebSocket が1つの TCP コネクションを利用するため、スティッキーセッション機能を利用することなく一つのバックエンドに割り振られます。

ただし、CLB ではアイドルタイムアウトもあり、60秒間アイドル状態が続くとコネクションが切断されてしまうので、サーバ側から定期的に Ping を定期的に行い、Pong の応答を得てコネクションを維持する方法があります。

設定

Node.js のインストール

$ sudo yum install -y gcc-c++ make openssl-devel
$ curl -sL https://rpm.nodesource.com/setup_8.x | sudo bash -
$ sudo yum install -y nodejs

インストールされていることの確認

$ node -v
v8.11.3

npm のインストール

$ curl -L http://npmjs.org/install.sh | sudo sh
$ vim ~/.npmrc
$ vim ~/.bashrc
$ source ~/.bashrc

.npmrc

root = ~/.npm/libraries
binroot = ~/.npm/bin
manroot = ~/.npm/man

.bashrc に以下を追記

export PATH=$HOME/.npm/bin:$PATH
export NODE_PATH=$HOME/.npm/libraries:$NODE_PATH
export MANPATH=$HOME/.npm/man:$MANPATH

インストールされていることを確認

$ npm -v
6.3.0

Socket.IO 等のインストール

$ npm install socket.io
$ npm install express

nginx のインストール

$ sudo rpm -ivh http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
$ sudo yum install -y nginx
$ sudo service nginx start
$ sudo chkconfig nginx on

プログラムの作成

$ vim index.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket){
  socket.broadcast.emit('hi');
  socket.on('chat message', function(msg){
    console.log('message: ' + msg);
    io.emit('chat message', msg);
  });
});

http.listen(3000, function(){
  console.log('listening on *:3000');
});
$ vim index.html
<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>
    <script src="/socket.io/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-1.11.1.js"></script>
    <script>
        $(function () {
            var socket = io();
            $('form').submit(function(){
            socket.emit('chat message', $('#m').val());
            $('#m').val('');
             return false;
        });
      });
    </script>
  </body>
</html>
$ vim /etc/nginx/nginx.conf
http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

:
:

    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    server {
        listen 80 proxy_protocol;
        real_ip_header proxy_protocol;
        # listen       80 default_server;
        # listen       [::]:80 default_server;
        # server_name  localhost;
        # root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
            proxy_http_version "1.1";
            proxy_pass http://localhost:3000/;
            proxy_set_header Connection $connection_upgrade;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header X-Forwarded-For $proxy_protocol_addr;
            proxy_set_header X-Forwarded-Host $http_host;
        }

ELBのProxy Protocolの設定

$ aws elb create-load-balancer-policy \
  --load-balancer-name WebSocketCLB \
  --policy-name EnableProxyProtocol \
  --policy-type-name ProxyProtocolPolicyType \
  --policy-attributes \
    AttributeName=ProxyProtocol,AttributeValue=true \
  --region us-west-2

$ aws elb set-load-balancer-policies-for-backend-server \
  --load-balancer-name WebSocketCLB \
  --instance-port 80 \
  --policy-names EnableProxyProtocol \
  --region us-west-2


$ aws elb describe-load-balancers \
  --load-balancer-name WebSocketCLB \
  --region us-west-2

動作確認

ELB の FQDN に対してブラウザからアクセス
http://.us-west-2.elb.amazonaws.com/

Socket.IOのアプリケーション起動

$ node index.js
listening on *:3000
message: abc
message: def

参考

https://qiita.com/you21979@github/items/4c9c382b9536effc590d
http://d.hatena.ne.jp/lettas0726/20110406/1302041546
http://d.hatena.ne.jp/lettas0726/20110406
https://socket.io/get-started/chat
https://qiita.com/yamamaijp/items/84da15b81ec5c16ffdd0
Elastic Load Balancerをつかってwebsocketを処理する方法

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です