AWS ELB 越しに WebSocket を通す

## 経緯

- Rails 5 から ActionCable が使えるようになった
- AWS 上でお手軽に試したい
- せっかくなので ELB を使って冗長構成にしよう

----

# あまりお手軽じゃなかった

----

## 問題

- ELB が Websocket の接続に必要な `Connection` ヘッダと `Upgrade` ヘッダを削る
- 削らないようにプロトコルを `TCP` にすると今度は `X-Fowarded-For` ヘッダをつけてくれない
- ので発信元のIPアドレスがとれない

----

## Proxy Protocol を使うしかないらしい

----

## Proxy Protocol とは

- HAProxy が策定
- リクエストボディの1行目に発信元の情報を追加して送信する仕様

##### 例
```
PROXY TCP4 198.51.100.22 203.0.113.7 80 80\r\n
GET / HTTP/1.1\r\n
Host: example.com\r\n
\r\n
```

----

## 全体の構成

![elb_actioncable.png](https://qiita-image-store.s3.amazonaws.com/0/19816/b3008d7a-0f81-10c4-2398-999f2794a9ce.png)

----

## ELB の設定

- プロトコルを両方とも `TCP` に設定しておく

<img width="893" alt="elb1.png" src="https://qiita-image-store.s3.amazonaws.com/0/19816/e39ac341-98c6-a665-3363-04ca39c36225.png">

<img width="893" alt="elb2.png" src="https://qiita-image-store.s3.amazonaws.com/0/19816/b85d0d18-c64b-fe17-87ea-dcc0d49def0e.png">

----

## 肝心のProxy Protocolの設定はAWSコンソールからはできない

----

## AWS CLI のインストール

###### Mac

```bash
$ brew install awscli
```

###### Linux ( Python 2.6.5 以降が必要 )

```bash
$ pip install awscli
```

##### Windows

[インストーラがあるのでそれで](https://aws.amazon.com/jp/cli/)

----

## AWS CLI の設定

- IAMユーザを追加して `Access Key ID` と `Secret Access Key` を設定

```bash
$ aws configure
AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Default region name [None]: ap-northeast-1
Default output format [None]: ENTER
```

----

## ELB - ProxyProtocol の設定

```bash
$ aws elb create-load-balancer-policy \
  --load-balancer-name {ELB Name}
  --policy-name EnableProxyProtocol \
  --policy-type-name ProxyProtocolPolicyType \
  --policy-attributes \
    AttributeName=ProxyProtocol,AttributeValue=true
$ aws elb set-load-balancer-policies-for-backend-server \
  --load-balancer-name {ELB Name} \
  --instance-port 80 \
  --policy-names EnableProxyProtocol
```

----

## ELB - Proxy Protocol 設定確認

```bash
$ aws elb describe-load-balancers \
  --load-balancer-name {ELB Name}
```
```
"BackendServerDescriptions": [
    {
        "InstancePort": 80, 
        "PolicyNames": [
            "EnableProxyProtocol"
        ]
    }
], 
```

----

## Nginx の設定(1)

```nginx
server {
  listen 80 proxy_protocol;
  real_ip_header proxy_protocol;

  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;
  }
}
```

----

## Nginx の設定(2)

- `Upgrade` ヘッダが空の場合は `Connection` ヘッダを `close` にする

```nginx
http {
  ...
  map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
  }
  ...
}
```

----

## まとめ

- ELBはTCPロードバランサーとして使用する
- 発信元のIPアドレスをとるために Proxy Protocol が必要
- Proxy Protocol を捌くために間に Nginx か HAProxy が必要

----

## 結局なにが嬉しい？

- マネージドなロードバランサーは管理が楽だしEC2のインスタンス立てるよりは安い
- SSL の設定はAWSコンソール上からできるし、最近だとAWS Certificate Managerの証明書が無料で使える

----

## 参考

- [ロードバランサーの Proxy Protocol のサポートを設定する - Elastic Load Balancing](http://docs.aws.amazon.com/ja_jp/ElasticLoadBalancing/latest/DeveloperGuide/enable-proxy-protocol.html)
- [How to Configure NGINX to Accept the Proxy Protocol | NGINX](https://www.nginx.com/resources/admin-guide/proxy-protocol/)
- [PROXY protocol - HAProxy](http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt)