はじめに
現在 Kubernetes を本番運用中ですが、まだサービス数が少ないのでサービスメッシュ化はしていません。今後サービス数が増えてくるとサービスメッシュ化を検討したほうがよいらしいです。
サービスメッシュ化するためのサービスとして、Istio
、AWS 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 を起動します。
$ 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つは個々のサービスごとです。
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 を配置する必要はありません。
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
を進めていこうと思います。