# ELB(CLB) で WebSocket 通信

## ELB(CLB) で WebSocket 通信

ALB では標準で WebSocket プロトコル機能に対応しているため、HTTP/HTTPS リスナーで受けて HTTP/1.1 から Upgrade で WebSocket 通信を開始することができます。 しかしながら、CLB, NLB では、ELB の機能としては対応しておりませんが、TCP リスナーを経由してバックエンドで WebSocket の機能に対応することで実現可能です。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(&#039;express&#039;)();
var http = require(&#039;http&#039;).Server(app);
var io = require(&#039;socket.io&#039;)(http);

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

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

http.listen(3000, function(){
  console.log(&#039;listening on *:3000&#039;);
});
```

```
$ vim index.html
```

```
&lt;!doctype html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Socket.IO chat&lt;/title&gt;
    &lt;style&gt;
      * { 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; }
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;ul id=&quot;messages&quot;&gt;&lt;/ul&gt;
    &lt;form action=&quot;&quot;&gt;
      &lt;input id=&quot;m&quot; autocomplete=&quot;off&quot; /&gt;&lt;button&gt;Send&lt;/button&gt;
    &lt;/form&gt;
    &lt;script src=&quot;/socket.io/socket.io.js&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;https://code.jquery.com/jquery-1.11.1.js&quot;&gt;&lt;/script&gt;
    &lt;script&gt;
        $(function () {
            var socket = io();
            $(&#039;form&#039;).submit(function(){
            socket.emit(&#039;chat message&#039;, $(&#039;#m&#039;).val());
            $(&#039;#m&#039;).val(&#039;&#039;);
             return false;
        });
      });
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
```

```
$ vim /etc/nginx/nginx.conf
```

```
http {
    log_format  main  &#039;$remote_addr - $remote_user [$time_local] &quot;$request&quot; &#039;
                      &#039;$status $body_bytes_sent &quot;$http_referer&quot; &#039;
                      &#039;&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;&#039;;

:
:

    map $http_upgrade $connection_upgrade {
        default upgrade;
        &#039;&#039;      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 &quot;1.1&quot;;
            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>
* <https://debiancdn.wordpress.com/2012/03/03/elb-loadbalancer-websocket/>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://hayashier.gitbook.io/article/load-balancer/clb-websocket-get-started.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
