gRPCサーバーでAWS ELBのPROXY ProtocolのリモートIPを取得する

grpc-logo

ども、大瀧です。
前回の記事(gRPCアプリをAWS ELBで負荷分散してみた)では、ELBのL4転送による負荷分散をご紹介しました。 この構成では手軽に負荷分散ができる一方で、ELBを介すためにサーバ側でクライアントのリモートIPを取得することができません。そこで今回は、ELBが サポートするPROXY ProtocolによってリクエストにリモートIPを付与し、それをgRPCサーバーで取得するサンプルコードをご紹介します。

PROXY Protocolの有効化

ELBのClassic Load BalancerはHAProxyのPROXY Protocol version 1をサポートしますが、既定では無効になっているため有効化します。aws elb create-load-balancer-policyコマンドでPROXY Protocolサポートが有効なポリシーを作成、aws elb set-load-balancer-policies-for-backend-serverコマンドでポリシーをELBのリスナに設定します。

$ aws elb create-load-balancer-policy \
  --load-balancer-name <ELB名> \
  --policy-name ProxyProtocol-policy \
  --policy-type-name ProxyProtocolPolicyType \
  --policy-attributes AttributeName=ProxyProtocol,AttributeValue=true
$ aws elb set-load-balancer-policies-for-backend-server \
  --load-balancer-name <ELB名> \
  --instance-port <リスナのポート番号> \
  --policy-names ProxyProtocol-policy
$

これでOKです。PROXY Protocolサポートが有効なリスナからはリモートIPが先頭に付与されたトラフィックが転送されるため、PROXY Protocolをサポートしないターゲット(gRPCサーバー)ではリクエストを正常に処理できないことに注意しましょう。

gRPCサーバーのPROXY Protocol対応

今回は、Go版gRPCサーバーで対応させるため、Go標準のnetパッケージのラッパーとして動作するgo-proxyprotoを利用しました。あらかじめgo getでライブラリを取得しておきます。

$ go get github.com/armon/go-proxyproto

gRPCサーバーのコードは、おなじみのサンプルhelloworldのgreeter_server/main.goです。

main関数ではTCPのリスナをproxyproto.Listenerメソッドでラップすることで、トラフィックからのリモートアドレスの除去とgRPCピアのリモートアドレスの置換が行われます。

func main() {
  lis, err := net.Listen("tcp", port)
  if err != nil {
    log.Fatalf("failed to listen: %v", err)
  }
  s := grpc.NewServer()
  pb.RegisterGreeterServer(s, &server{})
  proxyLis := &proxyproto.Listener{Listener: lis}
  s.Serve(proxyLis)
  //s.Serve(lis)
}

リクエストに対応するSayHello関数では、置換されたピアのリモートアドレスを取り出してコンソールに表示します。

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
  var addr string
  if pr, ok := peer.FromContext(ctx); ok {
    if tcpAddr, ok := pr.Addr.(*net.TCPAddr); ok {
      addr = tcpAddr.IP.String()
    } else {
      addr = pr.Addr.String()
    }
  }
  fmt.Printf("Remote Addr: %s\n", addr)
  return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

実行し、クライアントから接続すると、以下のようにELBのIPアドレスの代わりにクライアントのリモートアドレスが表示されます。

Remote Addr: 54.250.XXX.XXX
Remote Addr: 54.250.XXX.XXX

ちゃんと動いていますね!

まとめ

gRPCサーバーでAWS ELBのPROXY Protocolに対応する例をご紹介しました。↓の参考URLにあるメルカリさんのスライドに書いてあることそのままなのですが、コードに書こうとして躓いてしまったので誰かの参考にあれば幸いです。

参考URL