Uzabase Tech Blog

SPEEDA, NewsPicks, FORCASなどを開発するユーザベースの技術チームブログです。

EnvoyをFront Proxyとして利用する

こんにちは、ユーザベースのProductチームでSREをやっています阿南です。弊社ではKubebrnetes + Istioを利用してサービスメッシュの構築、マイクロサービスの運用を行っています。Istioでは sidecar proxyとしてEnvoyが利用されていますが、このEnvoyをFront Proxyとしても利用できないかと思い、よく使われる設定について調べてみました。下記目次です。

少し長いので、気になる部分だけでも読んで頂ければ幸いです。 今回作成した全体の設定はこちらにありますので、適宜参照してください。

envoy proxy for frontend · GitHub

また、実際にEnvoyを起動して確認してみたい方は、下記に環境構築について記載しています。

GitHub - tanan/migrate-from-apache-to-envoy

EnvoyをFront Proxyとして利用するメリット

(Front Proxyに限らずですが...) Envoyを利用することで下記のようなメリットがあります。

  • 柔軟なLoadBalancing機能
    • Blue/GreenやWeightを利用して簡単にBalancingの設定を変更することができます。また、rate limitやcircuit breakerの機能も有しているため、適切に設定を行うことでエラーが全体に波及するのを防ぐことができます。
  • control-planeやファイルベースのdynamic configurationが利用できる
    • Apache等では明示的に各プロセスに対してreload/restart等の動作を実行する必要がありますが、Envoyではcontrol-planeを利用して設定をDynamicに反映することができます。Apacheでもupstream先を有効/無効にする等は動的に設定できますが、プロセスがrestartすると状態が消えてしまいます。
  • Observabilityを高めるための他ツールとの連携のしやすさ
    • prometheus / grafana / jaegerとの連携が簡単に実現できるエコシステムが整っています。

Envoyのバージョン

envoyバージョンはv1.16.0-devで、v3 APIを利用しました。

Front ProxyのためのEnvoy Configuration

現在の弊社環境はざっくり下記のような構成になっています。WebサーバとしてApacheを利用し、元々開発されてきたtomcatのモノリスアプリケーションと新しく開発しているマイクロサービス群にPathベースでルーティングをしています。(Apacheとtomcatはon-premiseに構築されています。)

f:id:tanan55:20200927132457p:plain
architecture

今回は、このApacheをEnvoy Proxyに代替するために、基本的な設定を行いましたので、以降でそれぞれの設定を見ていきたいと思います。

80(HTTP),443(HTTPS)でアクセスを受け付ける

listenerを定義することで特定のポートでアクセスを受け付ける事ができます。

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 0.0.0.0, port_value: 80 }

TLSの設定をする場合は、filter_chainsにtls_contextを設定します。

tls_context:
  common_tls_context:
    tls_certificates:
      - certificate_chain:
          filename: "/etc/envoy/certs/example-com.crt"
        private_key:
          filename: "/etc/envoy/certs/example-com.key"

ここではfilenameを指定していますが、証明書の内容を直接記載することも可能です。

Pathベースのルーティングを設定する

Routingの設定はlistenerのfilter部分に記載します。

route_config:
  name: backend_route
  virtual_hosts:
  - name: backend
    domains:
    - "www.example.com"
    - "example.com"
    routes:
    - match:
        prefix: "/service/2"
      route:
        prefix_rewrite: "/"
        cluster: service2
        hash_policy:
          - cookie:
              name: balanceid
              ttl: 0s
    - match:
        prefix: "/"
      route:
        cluster: service1

上記の設定は、

  • ドメインはwww.example.com,example.comのみ許可
  • pathが/service/2にマッチした場合、service2のclusterを参照する
  • pathが/にマッチした場合、service1のclusterを参照する

という設定になります。ちなみにpathのルールは上から順に判定されるようで、matchの順番を逆にするとすべてservice1にリクエストが送られるので注意が必要です。

clusterではupstreamのendpointやbalancing ruleを定義します。 lb_policyには、ROUND_ROBIN の他に、 LEAST_REQUEST や後述の RING_HASH などが指定できます。

clusters:
  - name: service1
    connect_timeout: 0.25s
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: service1
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: service1-1
                port_value: 8080
        - endpoint:
            address:
              socket_address:
                address: service1-2
                port_value: 8080

また、 https_redirect をtrueにすることで、HTTPSへのリダイレクトが可能です

routes:
- match:
    prefix: "/"
  redirect:
    https_redirect: true

レスポンスに特定のヘッダーを付与する

response_headers_to_add に追加したいヘッダーを記載することができます。 注意点としては、v1.8でRouteActionレベルでは本パラメータがdeprecatedになりましたので、Routeレベルで設定する必要があります。 その他、リクエストヘッダーに特定の値を付与できる request_headers_to_addrequest_headers_to_remove もここで指定できます。

response_headers_to_add:
  - header:
      key: "x-frame-options"
      value: "sameorigin"
  - header:
      key: "x-xss-protection"
      value: "1; mode=block"

Healthcheckに失敗した場合、upstreamの対象から除外する

普段の運用ではupstreamに複数のエンドポイントを設定して、正しく応答が返せない場合はリクエストが割り振られないようにしたいケースがよくあると思います。Envoyではhealth_checks をclusterの設定に追加することでヘルスチェックを実施することができます。

health_checks:
  - timeout: 1s
    interval: 10s
    interval_jitter: 1s
    unhealthy_threshold: 6
    healthy_threshold: 2
    http_health_check:
      path: "/healthz"

jitterはinitial_jitterinterval_jitterが指定でき、ヘルスチェックがすべて同時にリクエストされないように、ばらつきを与えたい場合に設定します。 ヘルスチェックに失敗したupstreamはendpointのリストから除外され、healthy_thresholdの数だけ成功するとhealthyとみなされ再度リストに追加されます。 ちなみに初回起動時はhealthy_thresholdに関係なく1回の成功でhealthyとみなされます。

Cookieをもとに、upstreamを固定(sticky session)する

アプリケーション内にセッション情報を持っているようなシステムでは、純粋にROUND ROBINでリクエストが振られるとアプリが正常に動作しなくなってしまいます。そのため、Session Cookie(sessionが続く間だけ有効なcookie)を使って、ユーザーのリクエストを同じupstreamにルーティングするようにするsticky sessionが利用されます。 Envoyではclusterでlb_policy: RING_HASHを指定した上で、下記を設定することにより、sticky sessionが実現できます。

hash_policy:
  - cookie:
      name: balanceid   ## nameは何でもよい
      ttl: 0s

この設定でのポイントはttlです。ttlの有無により下記のように動作が異なります。

  • ttlが指定されない場合、cookieはつくられません。既にあればその値が利用されます。
  • ttlが指定されている場合、かつ、cookieがない場合はcookieを生成します。また、ttlが0sのときはsession cookieになります。

詳細はhashpolicy-cookieのDocumentを参照して下さい。

まとめ

EnvoyをFront Proxyとして利用するための設定を見ていきました。EnvoyのDocumentの読み方を覚えるまでに少し時間がかかりましたが、無事意図通り動作するよう設定できました。 実際に利用する場合は、control-planeを導入してdynamic configurationで設定反映をすることが多いかと思いますが、staticな設定を一度書いてみると理解が早まると思いますので、興味のある方はぜひやってみて下さい。

仲間募集!!

ユーザベースでは、「経済情報で、世界をかえる」 というミッションの実現に向けて、日々邁進しており、SREのメンバーを募集しています。 技術的にも様々なことにチャレンジしてますので少しでも興味を持ってくださった方はこちらまで!