6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Try Envoy: その1 Getting Started with Envoy をやってみた

Last updated at Posted at 2020-08-18

はじめに

現在 Kubernetes を本番運用中ですが、まだサービス数が少ないのでサービスメッシュ化はしていません。今後サービス数が増えてくるとサービスメッシュ化を検討したほうがよいらしいです。
サービスメッシュ化するためのサービスとして、IstioAWS app meshなどがありますが、これらはenvoyという proxy をコントロールするための機能であり、実際に通信を制御しているのは enovy のようです。そのため、まずは素のenvoyがどのようなことができるかを勉強していきたいと思います。

Envoy のサイトに Try Envoy という学習コンテンツがあるので、これで勉強を進めていきたいと思います。

Try Envoy は、ブラウザベースで進められる Katacoda の学習コンテンツが埋め込まれているものになります。
Try Envoyでは Chome 翻訳が何故か効かなかったのですが、katacoda を直接使用すると Chome 翻訳できたのでオススメです。

では今回は Getting Started with Envoyを進めていきたいと思います。

Getting Started with Envoy

1. Create Proxy Config

Envoy はプロキシの動作を制御するために YAML 定義ファイルを使用して設定します。このステップでは、静的構成 API を使用して設定します。これはすべての設定が定義ファイルで事前定義されていることを意味します。

※Envoy は動的構成もサポートしています。これにより、外部ソースを介して設定を検出することもできます。

1-1. Resources

conf の最初の行は、使用される API 構成を定義します。今回は静的 API を設定したいので、最初の行はstatic_resourcesである必要があります。

static_resources:

1-2. Listeners

次はリスナーを定義します。リスナーは Envoy がリクエストをリッスンするIPアドレスやポートなど、ネットワークに関する設定です。Envoy は Docker コンテナー内で実行されるため、IPアドレス0.0.0.0でリッスンする必要があります。下記設定では、Envoy は port 10000でリッスンします。

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

1-3. Filter Chains and Filters

次はリクエストの処理方法を定義します。各リスナーにはフィルターのセットがあり、リスナーごとに異なるフィルターのセットを持つことができます。

この例では、すべてのトラフィックをGoogle.comにプロキシします。そのため Envoy エンドポイントをリクエストすると、URL がEnvoyエンドポイントのまま、Googleホームページが表示されるはずです。
(リダイレクトではなくリバースプロキシされている)

フィルタリングはfilter_chainsを使用して定義されます。各フィルターの目的は、リスクエスト要求に一致するものを見つけ、それをターゲット宛先に一致させることです。

    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route: { host_rewrite: www.google.com, cluster: service_google }
          http_filters:
          - name: envoy.router

フィルターは、HTTP接続用に設計された組み込みフィルターであるenvoy.http_connection_managerを使用しています。

項目名 説明
stat_prefix 接続マネージャーの統計を出力するときに使用する、人間が読める接頭辞
route_config ルートの設定。仮想ホストが一致すると、ルートがチェックされます。この例ではroute_configは、要求されたホストドメインに関係なく、すべての着信HTTPリクエストに一致します。
routes URLプレフィックスが一致した場合、一連のルートルールが次に何が起こるかを定義します。この場合/はリクエストのルートに一致することを意味します
host_rewrite HTTPリクエストのHostヘッダーを書き換えます
cluster リクエストを処理するクラスターの名前。実装は後述
http_filters フィルターにより、Envoyはリクエストの処理時にリクエストを適応および変更できます

1-4. Clusters

リクエストがフィルターに一致すると、リクエストはクラスターに渡されます。
以下に示すクラスターは、ホストがHTTPSで実行されているgoogle.comであることを定義しています。複数のホストが定義されている場合 Envoy はラウンドロビン戦略を実行します。

  clusters:
  - name: service_google
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    hosts: [{ socket_address: { address: google.com, port_value: 443 }}]
    tls_context: { sni: www.google.com }

1-5. Admin

最後に管理セクションが必要です。管理セクションについては、次の手順で詳しく説明します。

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 0.0.0.0, port_value: 9901 }

この構造は、Envoy Static Configurationのボイラープレートを定義します。リスナーは、EnvoyのポートとIPアドレスを定義します。リスナーには、着信要求に一致する一連のフィルターがあります。リクエストが一致すると、クラスターに転送されます。

2. Start Proxy

2-1. Start Envoy

1.Create Proxy Configで作成したenvoy.yamlを使用して、port 80にバインドされた Envoy を起動します。

envoyコンテナの起動
$ docker run --name=proxy -d \
  -p 80:10000 \
  -v $(pwd)/envoy/envoy.yaml:/etc/envoy/envoy.yaml \
  envoyproxy/envoy:latest

2-2. View Envoy

katacoda 上のターミナルでcurl localhostを叩くと Google.com のソースが表示されます。(HTMLなのでよくわからないですが…)

また katacoda が発行した URL を自身の PC のブラウザから叩くことができます。叩いてみると、リクエストが設定通り Google.com にプロキシされたことがわかります。

3. Admin View

Envoyには管理用ビューが用意されており、構成、統計、ログ、その他の内部Envoyデータを表示できます。
管理者は、管理者ビューのポートが定義されている追加のリソース定義を追加することによって定義できます。
ポートは他のリスナー設定と競合してはなりません。

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 0.0.0.0, port_value: 9901 }

3-1. Start Admin

このDockerコンテナは、管理ポートを外部に公開します。上記のリソース設定により、管理者ビューが一般に公開されます。デモ目的でのみ使用してください。管理者ポータルを保護する方法に関するドキュメントを参照してください。

管理ポータルを公開するには、次のコマンドを実行します。

$ docker run --name=proxy-with-admin -d \
      -p 9901:9901 \
      -p 10000:10000 \
      -v $(pwd)/envoy/envoy.yaml:/etc/envoy/envoy.yaml \
      envoyproxy/envoy:latest

現在の形式の管理インターフェイスでは、破壊的な操作(サーバーのシャットダウンなど)を実行できるだけでなく、プライベート情報(統計、クラスター名、証明書情報など)を公開する可能性もあります。アクセスすることが重要です。管理インターフェースへのアクセスは安全なネットワークを介してのみ許可されています

ブラウザから管理者ビューを開いてみると、以下のような画面が表示されました。ここからいろいろな情報が取れるみたいです。

4. Route to Docker Containers

最後の例では Envoy を使用して、リクエストされた URL パスに基づいてトラフィックをさまざまな Python サービスにプロキシします。

4-1. Configuration

アプリケーションの構成は Docker Compose ファイルとして定義されます。複数のコンテナを同時に実行したいので Docker Compose ファイルを使用します。1つはプロキシ用で、もう1つは個々のサービスごとです。

examples/front-proxy/docker-compose.yml
version: '2'
services:

  front-envoy:
    build:
      context: .
      dockerfile: Dockerfile-frontenvoy
    volumes:
      - ./front-envoy.yaml:/etc/front-envoy.yaml
    networks:
      - envoymesh
    expose:
      - "80"
      - "8001"
    ports:
      - "8000:80"
      - "8001:8001"

  service1:
    build:
      context: .
      dockerfile: Dockerfile-service
    volumes:
      - ./service-envoy.yaml:/etc/service-envoy.yaml
    networks:
      envoymesh:
        aliases:
          - service1
    environment:
      - SERVICE_NAME=1
    expose:
      - "80"

  service2:
    build:
      context: .
      dockerfile: Dockerfile-service
    volumes:
      - ./service-envoy.yaml:/etc/service-envoy.yaml
    networks:
      envoymesh:
        aliases:
          - service2
    environment:
      - SERVICE_NAME=2
    expose:
      - "80"

networks:
  envoymesh: {}

4-2. Application

このサービスは Python Web アプリケーションであり、コンテナー内で Envoy を使用して、トラフィックを Python アプリケーションに転送します。アプリケーションの前に Envoy を配置する必要はありません。

examples/front-proxy/service.py
from flask import Flask
from flask import request
import socket
import os
import sys
import requests

app = Flask(__name__)

TRACE_HEADERS_TO_PROPAGATE = [
    'X-Ot-Span-Context',
    'X-Request-Id',

    # Zipkin headers
    'X-B3-TraceId',
    'X-B3-SpanId',
    'X-B3-ParentSpanId',
    'X-B3-Sampled',
    'X-B3-Flags',

    # Jaeger header (for native client)
    "uber-trace-id"
]

@app.route('/service/<service_number>')
def hello(service_number):
    return ('Hello from behind Envoy (service {})! hostname: {} resolved'
            'hostname: {}\n'.format(os.environ['SERVICE_NAME'], 
                                    socket.gethostname(),
                                    socket.gethostbyname(socket.gethostname())))

@app.route('/trace/<service_number>')
def trace(service_number):
    headers = {}
    # call service 2 from service 1
    if int(os.environ['SERVICE_NAME']) == 1 :
        for header in TRACE_HEADERS_TO_PROPAGATE:
            if header in request.headers:
                headers[header] = request.headers[header]
        ret = requests.get("http://localhost:9000/trace/2", headers=headers)
    return ('Hello from behind Envoy (service {})! hostname: {} resolved'
            'hostname: {}\n'.format(os.environ['SERVICE_NAME'], 
                                    socket.gethostname(),
                                    socket.gethostbyname(socket.gethostname())))

if __name__ == "__main__":
    app.run(host='127.0.0.1', port=8080, debug=True)

4-3. Envoy Frontend Proxy

Envoy プロキシの設定は以下のようになります。

static_resources:
  listeners:
  - address:
      socket_address:
        address: 0.0.0.0
        port_value: 80
    filter_chains:
    - filters:
      - name: envoy.http_connection_manager
        config:
          codec_type: auto
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: backend
              domains:
              - "*"
              routes:
              - match:
                  prefix: "/service/1"
                route:
                  cluster: service1
              - match:
                  prefix: "/service/2"
                route:
                  cluster: service2
          http_filters:
          - name: envoy.router
            config: {}
  clusters:
  - name: service1
    connect_timeout: 0.25s
    type: strict_dns
    lb_policy: round_robin
    http2_protocol_options: {}
    hosts:
    - socket_address:
        address: service1
        port_value: 80
  - name: service2
    connect_timeout: 0.25s
    type: strict_dns
    lb_policy: round_robin
    http2_protocol_options: {}
    hosts:
    - socket_address:
        address: service2
        port_value: 80
admin:
  access_log_path: "/dev/null"
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 8001

routesは、リクエストの URL に基づいて一致します。

routes:
    - match:
        prefix: "/service/1"
    route:
        cluster: service1
    - match:
        prefix: "/service/2"
    route:
        cluster: service2

envoy の設定により、service1 および service2 というエンドポイントにトラフィックを転送します。これらは Docker Compose で構成された Docker Network によって提供される DNS エントリーです。

4-4. Deploy

サンプルを開始します。

$ docker-compose -f ~/envoy/examples/front-proxy/docker-compose.yml up -d

4-5. Admin View

管理ビューを見ると、色々な情報を確認できます。

URL 内容
https://管理画面用URLやポート/ 管理インターフェイス
https://管理画面用URLやポート/config_dump ルーティング構成をJSONとして表示できます。
https://管理画面用URLやポート/clusters 利用可能なクラスターやそのメトリックなどの追加情報
https://管理画面用URLやポート/stats 各種メトリクス

5-6. Application Routing

Envoy は port 8000 で リッスンさせています。よって katacoda 上のターミナルでcurl localhos:8080を叩くと、付加したURLに基づいて、さまざまなサービスが設定に従って応答します。

$ curl localhost:8000/service/1
Hello from behind Envoy (service 1)! hostname: 24c46f5120ad resolvedhostname: 172.19.0.2
$ curl localhost:8000/service/2
Hello from behind Envoy (service 2)! hostname: 8b93ae3dd237 resolvedhostname: 172.19.0.3

#おわりに
とりあえずGetting Started with Envoyが完了しました。リクエストを外部サービスにリバースプロキシできたり,複数のアプリケーションにパスベースのルーティングができたりしました。まあまだ基本の設定だけなので、「で? ApacheやNginxでもできるんですけど」という状態です。
Envoy の良さを知るために、引き続きTry Envoyを進めていこうと思います。

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?