envoyを用いたHTTP2対応


SREチーム所属の大嶋です。Advent Calendarは2年目になります。

この記事はZOZOテクノロジーズ #2 Advent Calendar 2019 21日目の記事になります。

弊社では全部で5つのAdvent Calendarが公開しています。

ZOZOテクノロジーズ #1 Advent Calendar 2019
ZOZOテクノロジーズ #2 Advent Calendar 2019
ZOZOテクノロジーズ #3 Advent Calendar 2019
ZOZOテクノロジーズ #4 Advent Calendar 2019
ZOZOテクノロジーズ #5 Advent Calendar 2019

概要

ZOZOテクノロジーズ #5 Advent Calendar 2019 10日目の記事においてNLB配下でgRPC通信するときに考えるALPN対応状況という内容が公開されています。
上記記事の振り返りで述べられている以下内容に我々のチームで直面しました。

ALPN対応のgrpcライブラリが用意されているJavaやNode.jsなどの言語で、かつ外部通信用にgRPCのAPIを公開(NLB使う)する方式だと苦しいなと思いました。
この場合は、EnvoyやNginxにTLS終端させて、ACMは諦めるという方向になるでしょうか。。。

この内容について我々のチームがどのような対処をしたのか書かせて頂きます。

課題

NLBでのALPNが未対応のため、gRPCでconnection errorが発生していました。そのため一時的にSSL暗号化をせず検証していました。

上記状態を解消するために今回はenvoyにてSSL終端を実施しました。
普段であればELBでのSSL終端を実施していたので、初めての検証となりました。
こちらが構築イメージになります。

Client-envoy間は暗号化し、envoyにて複合するように設計しました。

実施作業事項

  1. SSL証明書準備、配置
  2. envoyにてTLS設定を追加
  3. envoyの受信Portを443に変更
  4. NLBのListenerPortを443に変更
  5. 動作確認

SSL証明書準備、配置

今回は検証のため自己署名証明書を利用します。

$ openssl req -nodes -x509 -newkey rsa:4096 -keyout example-com.key -out example-com.crt -days 365

そして/etc/配下に配置します。

ADD ./example-com.crt /etc/server.crt
ADD ./example-com.key /etc/server.key

envoyにてTLS設定を追加

         tls_context:
            common_tls_context:
              alpn_protocols: 
                - "h2,http/1.1"
              tls_certificates:
                - certificate_chain:
                    filename: "/tmp/server.crt"
                  private_key:
                    filename: "/tmp/server.key"

インデントに注意してください。僕はズレててハマったので、以下を参考にどうぞ。
参考:Example configuration

envoyのPortを443に変更

         ports:
            - name: https
              containerPort: 443

上記に加え、ヘルスチェックなどのPortも443に変更してください。

NLBのListenerPortを443に変更

apiVersion: v1
kind: Service
metadata:
  name: envoy
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
  type: LoadBalancer
  selector:
    app: envoy
  ports:
    - name: https
      protocol: TCP
      port: 443
      targetPort: 443

動作確認

$ curl -k -vvv --http2-prior-knowledge https://test.com:443
* Rebuilt URL to: https://test.com:443/
*   Trying XX.XX.XX.XX...
* TCP_NODELAY set
* Connected to test.com (XX.XX.XX.XX) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=AU; ST=Some-State; O=Internet Widgits Pty Ltd; CN=test.com
*  start date: Dec 20 06:44:26 2019 GMT
*  expire date: Dec 19 06:44:26 2020 GMT
*  issuer: C=AU; ST=Some-State; O=Internet Widgits Pty Ltd; CN=test.com
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fc69d004400)
> GET / HTTP/2
> Host: test.com
> User-Agent: curl/7.54.0
> Accept: */*
> 
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
< HTTP/2 404 
< content-length: 0
< date: Mon, 23 Dec 2019 03:00:57 GMT
< server: envoy
< x-envoy-upstream-service-time: 27
< 
* Connection #0 to host test.com left intact

無事TLSでのハンドシェイクを通過し、HTTP2で通信することができました。
適当なパスをcurlしているので、404が出てますが一旦検証としては完了とします。

まとめ

現在は検証段階なので、追加で気づいたことがあれば別記事で更新させて頂きます。
最後までお付き合い頂きありがとうございました!

参考
SETTING UP SSL IN ENVOY