トラフィック管理の問題
リクエストがEnvoyによって拒否される
リクエストは様々な理由で拒否される可能性があります。リクエストが拒否される理由を理解する最善の方法は、Envoyのアクセスログを検査することです。デフォルトでは、アクセスログはコンテナの標準出力に出力されます。次のコマンドを実行してログを確認してください。
$ kubectl logs PODNAME -c istio-proxy -n NAMESPACE
デフォルトのアクセスログフォーマットでは、Envoyレスポンスフラグはレスポンスコードの後に配置されます。カスタムログフォーマットを使用している場合は、%RESPONSE_FLAGS%を含めるようにしてください。
レスポンスフラグの詳細については、Envoyレスポンスフラグを参照してください。
一般的なレスポンスフラグは次のとおりです。
NR: ルートが構成されていません。DestinationRuleまたはVirtualServiceを確認してください。UO: サーキットブレーキングによるアップストリームオーバーフロー。DestinationRuleのサーキットブレーカー構成を確認してください。UF: 上流への接続に失敗しました。Istio認証を使用している場合は、相互TLS設定の競合を確認してください。
ルーティングルールがトラフィックフローに影響しないように見える
現在のEnvoyサイドカー実装では、重み付けされたバージョン配布が適用されるまでに、最大100個の要求が必要になる場合があります。
Bookinfoサンプルでルートルールが完全に機能しているのに、同様のバージョンルーティングルールが独自のアプリケーションに影響を与えない場合は、Kubernetesサービスを少し変更する必要がある可能性があります。Kubernetesサービスは、IstioのL7ルーティング機能を利用するために、特定の制限に従う必要があります。詳細については、Podとサービスの要件を参照してください。
もう1つの潜在的な問題は、ルートルールが単に適用されるのが遅い可能性があることです。Kubernetes上のIstio実装は、最終的に整合性のあるアルゴリズムを使用して、すべてのEnvoyサイドカーがすべてのルートルールを含む正しい構成を持っていることを保証します。構成の変更は、すべてのサイドカーに伝播するまでに時間がかかります。大規模なデプロイメントでは、伝播に時間がかかり、数秒程度の遅延が発生する可能性があります。
デスティネーションルールの設定後の503エラー
DestinationRuleを適用した後、サービスへの要求がすぐにHTTP 503エラーを生成し始め、DestinationRuleを削除または元に戻すまでエラーが続く場合は、DestinationRuleがサービスのTLS競合を引き起こしている可能性があります。
たとえば、クラスタ全体で相互TLSを設定する場合は、DestinationRuleに次のtrafficPolicyを含める必要があります。
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
それ以外の場合は、モードはDISABLEにデフォルトになり、クライアントプロキシサイドカーはTLS暗号化されたリクエストではなく、プレーンHTTPリクエストを行うようになります。そのため、サーバープロキシが暗号化されたリクエストを期待しているため、リクエストはサーバープロキシと競合します。
DestinationRuleを適用する際は、常にtrafficPolicyのTLSモードがグローバルサーバー構成と一致していることを確認してください。
ルーティングルールがイングレスゲートウェイのリクエストに影響しない
内部サービスにアクセスするために、イングレスGatewayと対応するVirtualServiceを使用していると仮定しましょう。たとえば、あなたのVirtualServiceは次のようになります。
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: myapp
spec:
hosts:
- "myapp.com" # or maybe "*" if you are testing without DNS using the ingress-gateway IP (e.g., http://1.2.3.4/hello)
gateways:
- myapp-gateway
http:
- match:
- uri:
prefix: /hello
route:
- destination:
host: helloworld.default.svc.cluster.local
- match:
...
また、helloworldサービスのトラフィックを特定のサブセットにルーティングするVirtualServiceもあります。
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: helloworld
spec:
hosts:
- helloworld.default.svc.cluster.local
http:
- route:
- destination:
host: helloworld.default.svc.cluster.local
subset: v1
この状況では、イングレスゲートウェイを介したhelloworldサービスへの要求はサブセットv1に送信されず、代わりにデフォルトのラウンドロビンルーティングを引き続き使用することに気付くでしょう。
イングレスリクエストはゲートウェイホスト(例:myapp.com)を使用しており、helloworldサービスの任意のエンドポイントにルーティングするmyappVirtualService内のルールをアクティブ化します。ホストhelloworld.default.svc.cluster.localを持つ内部リクエストのみが、トラフィックをサブセットv1のみに送信するhelloworldVirtualServiceを使用します。
ゲートウェイからのトラフィックを制御するには、myappVirtualServiceにもサブセットルールを含める必要があります。
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: myapp
spec:
hosts:
- "myapp.com" # or maybe "*" if you are testing without DNS using the ingress-gateway IP (e.g., http://1.2.3.4/hello)
gateways:
- myapp-gateway
http:
- match:
- uri:
prefix: /hello
route:
- destination:
host: helloworld.default.svc.cluster.local
subset: v1
- match:
...
あるいは、可能であれば、2つのVirtualServiceを1つのユニットに結合することもできます。
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: myapp
spec:
hosts:
- myapp.com # cannot use "*" here since this is being combined with the mesh services
- helloworld.default.svc.cluster.local
gateways:
- mesh # applies internally as well as externally
- myapp-gateway
http:
- match:
- uri:
prefix: /hello
gateways:
- myapp-gateway #restricts this rule to apply only to ingress gateway
route:
- destination:
host: helloworld.default.svc.cluster.local
subset: v1
- match:
- gateways:
- mesh # applies to all services inside the mesh
route:
- destination:
host: helloworld.default.svc.cluster.local
subset: v1
Envoyが負荷下でクラッシュする
ulimit -aを確認してください。多くのシステムでは、デフォルトで1024のファイル記述子上限があり、これによりEnvoyがアサートしてクラッシュします。
[2017-05-17 03:00:52.735][14236][critical][assert] assert failure: fd_ != -1: external/envoy/source/common/network/connection_impl.cc:58
ulimitを上げるようにしてください。例:ulimit -n 16384
Envoyが私のHTTP/1.0サービスに接続しない
Envoyは、上流サービスにHTTP/1.1またはHTTP/2トラフィックを必要とします。たとえば、NGINXをEnvoyの背後でトラフィックを提供するために使用する場合、NGINXのデフォルトは1.0であるため、NGINX構成でproxy_http_versionディレクティブを「1.1」に設定する必要があります。
構成例
upstream http_backend {
server 127.0.0.1:8080;
keepalive 16;
}
server {
...
location /http/ {
proxy_pass http://http_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
...
}
}
ヘッドレスサービスへのアクセス時の503エラー
Istioが次の構成でインストールされていると仮定します。
- メッシュ内で
mTLSモードがSTRICTに設定されています。 meshConfig.outboundTrafficPolicy.modeがALLOW_ANYに設定されています。
nginxがデフォルト名前空間にStatefulSetとしてデプロイされ、以下に示すように対応するHeadless Serviceが定義されているとします。
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: http-web # Explicitly defining an http port
clusterIP: None # Creates a Headless Service
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: registry.k8s.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
Service定義のポート名http-webは、そのポートのプロトコルとしてhttpを明示的に指定しています。
デフォルト名前空間にcurl pod Deploymentもあると仮定します。このcurl podからPod IPを使用してnginxにアクセスする場合(これはheadlessサービスにアクセスする一般的な方法の1つです)、リクエストはPassthroughClusterを介してサーバーサイドに渡されますが、サーバーサイドのサイドカープロキシはnginxへのルートエントリが見つからず、HTTP 503 UCで失敗します。
$ export SOURCE_POD=$(kubectl get pod -l app=curl -o jsonpath='{.items..metadata.name}')
$ kubectl exec -it $SOURCE_POD -c curl -- curl 10.1.1.171 -s -o /dev/null -w "%{http_code}"
503
10.1.1.171はnginxのレプリカの1つのPod IPであり、サービスはcontainerPort 80でアクセスされます。
この503エラーを回避する方法はいくつかあります。
正しいHostヘッダーを指定する
上記のcurlリクエストのHostヘッダーは、デフォルトでPod IPになります。
nginxへのリクエストでHostヘッダーをnginx.defaultとして指定すると、HTTP 200 OKが正常に返されます。$ export SOURCE_POD=$(kubectl get pod -l app=curl -o jsonpath='{.items..metadata.name}') $ kubectl exec -it $SOURCE_POD -c curl -- curl -H "Host: nginx.default" 10.1.1.171 -s -o /dev/null -w "%{http_code}" 200ポート名を
tcpまたはtcp-webまたはtcp-<custom_name>に設定するここでは、プロトコルが
tcpとして明示的に指定されています。この場合、クライアント側とサーバー側の両方で、サイドカープロキシのTCP Proxyネットワークフィルターのみが使用されます。HTTP Connection Managerはまったく使用されないため、リクエストにはヘッダーは必要ありません。Hostヘッダーを明示的に設定する場合としない場合でも、
nginxへのリクエストはHTTP 200 OKを正常に返します。これは、クライアントがリクエストにヘッダー情報を含めることができない特定のシナリオで役立ちます。
$ export SOURCE_POD=$(kubectl get pod -l app=curl -o jsonpath='{.items..metadata.name}') $ kubectl exec -it $SOURCE_POD -c curl -- curl 10.1.1.171 -s -o /dev/null -w "%{http_code}" 200$ kubectl exec -it $SOURCE_POD -c curl -- curl -H "Host: nginx.default" 10.1.1.171 -s -o /dev/null -w "%{http_code}" 200Pod IPの代わりにドメイン名を使用する
headlessサービスの特定のインスタンスには、ドメイン名だけでアクセスすることもできます。
$ export SOURCE_POD=$(kubectl get pod -l app=curl -o jsonpath='{.items..metadata.name}') $ kubectl exec -it $SOURCE_POD -c curl -- curl web-0.nginx.default -s -o /dev/null -w "%{http_code}" 200ここでは、
web-0はnginxの3つのレプリカの1つのpod名です。
headlessサービスとさまざまなプロトコルのトラフィックルーティング動作に関する追加情報については、このトラフィックルーティングページを参照してください。
TLS構成ミス
多くのトラフィック管理の問題は、不適切なTLS設定によって発生します。次のセクションでは、最も一般的な誤設定の一部について説明します。
HTTPポートへのHTTPSの送信
アプリケーションがHTTPとして宣言されたサービスにHTTPSリクエストを送信すると、Envoyサイドカーはリクエストを転送中にHTTPとして解析しようとしますが、HTTPが予期せず暗号化されているため失敗します。
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: httpbin
spec:
hosts:
- httpbin.org
ports:
- number: 443
name: http
protocol: HTTP
resolution: DNS
ポート443で意図的にプレーンテキストを送信する場合(例:curl http://httpbin.org:443)、上記の構成は正しい場合がありますが、一般的にポート443はHTTPSトラフィック専用です。
デフォルトでポート443を使用するcurl https://httpbin.orgのようなHTTPSリクエストを送信すると、curl: (35) error:1408F10B:SSL routines:ssl3_get_record:wrong version numberのようなエラーが発生します。アクセスログにも400 DPEのようなエラーが表示される場合があります。
これを修正するには、ポートプロトコルをHTTPSに変更する必要があります。
spec:
ports:
- number: 443
name: https
protocol: HTTPS
ゲートウェイから仮想サービスへのTLSの不一致
仮想サービスをゲートウェイにバインドする際に発生する可能性のある2つの一般的なTLSミスマッチがあります。
- ゲートウェイがTLSを終了し、仮想サービスがTLSルーティングを設定します。
- ゲートウェイがTLSパススルーを実行し、仮想サービスがHTTPルーティングを設定します。
TLS終端付きゲートウェイ
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
hosts:
- "*"
tls:
mode: SIMPLE
credentialName: sds-credential
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: httpbin
spec:
hosts:
- "*.example.com"
gateways:
- istio-system/gateway
tls:
- match:
- sniHosts:
- "*.example.com"
route:
- destination:
host: httpbin.org
この例では、ゲートウェイはTLSを終了しています(ゲートウェイのtls.mode構成はSIMPLEであり、PASSTHROUGHではありません)。仮想サービスはTLSベースのルーティングを使用しています。ルーティングルールの評価は、ゲートウェイがTLSを終了した後に発生するため、リクエストはHTTPSではなくHTTPになるため、TLSルールは効果がありません。
この誤設定では、リクエストはHTTPルーティングに送信されますが、HTTPルートが構成されていないため、404応答が返されます。これは、istioctl proxy-config routesコマンドを使用して確認できます。
この問題を解決するには、仮想サービスを切り替えてtlsではなくhttpルーティングを指定する必要があります。
spec:
...
http:
- match:
- headers:
":authority":
regex: "*.example.com"
TLSパススルー付きゲートウェイ
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: gateway
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- "*"
port:
name: https
number: 443
protocol: HTTPS
tls:
mode: PASSTHROUGH
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: virtual-service
spec:
gateways:
- gateway
hosts:
- httpbin.example.com
http:
- route:
- destination:
host: httpbin.org
この構成では、仮想サービスはゲートウェイを介して渡されたTLSトラフィックに対してHTTPトラフィックを照合しようとします。これにより、仮想サービス構成は効果がなくなります。istioctl proxy-config listenerおよびistioctl proxy-config routeコマンドを使用して、HTTPルートが適用されていないことを確認できます。
これを修正するには、仮想サービスを切り替えてtlsルーティングを設定する必要があります。
spec:
tls:
- match:
- sniHosts: ["httpbin.example.com"]
route:
- destination:
host: httpbin.org
あるいは、ゲートウェイのtls構成を切り替えることで、TLSをパススルーするのではなく終了させることもできます。
spec:
...
tls:
credentialName: sds-credential
mode: SIMPLE
ダブルTLS(TLSリクエストに対するTLS発信)
TLSオリジネーションを実行するようにIstioを構成する場合は、アプリケーションがプレーンテキストのリクエストをサイドカーに送信し、サイドカーがTLSを開始することを確認する必要があります。
次のDestinationRuleは、httpbin.orgサービスへのリクエストに対してTLSを生成しますが、対応するServiceEntryはポート443のプロトコルをHTTPSとして定義しています。
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: httpbin
spec:
hosts:
- httpbin.org
ports:
- number: 443
name: https
protocol: HTTPS
resolution: DNS
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: originate-tls
spec:
host: httpbin.org
trafficPolicy:
tls:
mode: SIMPLE
この構成では、サイドカーはアプリケーションがポート443でTLSトラフィックを送信することを期待しています(例:curl https://httpbin.org)。しかし、リクエストを転送する前にTLSオリジネーションも行います。これにより、リクエストが二重に暗号化されます。
たとえば、curl https://httpbin.orgのようなリクエストを送信すると、エラーが発生します:(35) error:1408F10B:SSL routines:ssl3_get_record:wrong version number。
この例を修正するには、ServiceEntryでポートプロトコルをHTTPに変更します。
spec:
hosts:
- httpbin.org
ports:
- number: 443
name: http
protocol: HTTP
この構成では、アプリケーションはポート443にプレーンテキストのリクエストを送信する必要があります(例:curl http://httpbin.org:443)。なぜなら、TLSオリジネーションはポートを変更しないからです。ただし、Istio 1.8以降では、アプリケーションにHTTPポート80を公開し(例:curl http://httpbin.org)、TLSオリジネーションのためにリクエストをtargetPort 443にリダイレクトできます。
spec:
hosts:
- httpbin.org
ports:
- number: 80
name: http
protocol: HTTP
targetPort: 443
同じTLS証明書で構成された複数のゲートウェイが構成されている場合に404エラーが発生する
HTTP/2接続の再利用を利用するブラウザ(つまり、ほとんどのブラウザ)では、別のホストへの接続が既に確立された後に2番目のホストにアクセスすると、同じTLS証明書を使用して複数のゲートウェイを構成すると、404エラーが発生します。
たとえば、次のように同じTLS証明書を共有する2つのホストがあるとします。
- ワイルドカード証明書
*.test.comがistio-ingressgatewayにインストールされています。 - ホスト
service1.test.com、セレクターistio: ingressgateway、およびゲートウェイのマウントされた(ワイルドカード)証明書を使用したTLSを持つGateway構成gw1 - ホスト
service2.test.com、セレクターistio: ingressgateway、およびゲートウェイのマウントされた(ワイルドカード)証明書を使用したTLSを持つGateway構成gw2 - ホスト名
service1.test.com、ゲートウェイgw1を持つVirtualService設定vs1 - ホスト名
service2.test.com、ゲートウェイgw2を持つVirtualService設定vs2
両方のゲートウェイが同じワークロード(セレクタistio: ingressgateway)によって提供されているため、両方のサービス(service1.test.comとservice2.test.com)へのリクエストは同じIPアドレスに解決されます。最初にservice1.test.comにアクセスすると、ワイルドカード証明書(*.test.com)が返され、service2.test.comへの接続にも同じ証明書を使用できることが示されます。ChromeやFirefoxなどのブラウザは、その結果、service2.test.comへのリクエストに対して既存の接続を再利用します。ゲートウェイ(gw1)にservice2.test.comのルートがないため、404(Not Found)応答が返されます。
この問題を回避するには、2つのゲートウェイ(gw1とgw2)ではなく、単一のワイルドカードGatewayを設定します。その後、次のように両方のVirtualServicesをそれにバインドします。
- ホスト名
*.test.com、セレクタistio: ingressgateway、ゲートウェイにマウントされた(ワイルドカード)証明書を使用したTLSを持つGateway設定gw - ホスト名
service1.test.com、ゲートウェイgwを持つVirtualService設定vs1 - ホスト名
service2.test.com、ゲートウェイgwを持つVirtualService設定vs2
SNIを送信していない場合のSNIルーティングの構成
hostsフィールドを指定したHTTPS Gatewayは、受信リクエストに対してSNIマッチを実行します。例えば、次の設定では、SNIに*.example.comが一致するリクエストのみが許可されます。
servers:
- port:
number: 443
name: https
protocol: HTTPS
hosts:
- "*.example.com"
これにより、一部のリクエストが失敗することがあります。
例えば、DNSを設定しておらず、代わりにホストヘッダーを直接設定している場合(例:curl 1.2.3.4 -H "Host: app.example.com")、SNIは設定されず、リクエストは失敗します。代わりに、DNSを設定するか、curlの--resolveフラグを使用してください。詳細については、「セキュアなゲートウェイ」のタスクを参照してください。
もう1つのよくある問題は、Istioの前にロードバランサーがある場合です。ほとんどのクラウドロードバランサーはSNIを転送しないため、クラウドロードバランサーでTLSを終了している場合は、次のいずれかを行う必要があります。
- クラウドロードバランサーを設定して、TLS接続をパススルーするようにします。
hostsフィールドを*に設定して、GatewayでのSNIマッチングを無効にします。
この一般的な症状としては、実際のトラフィックが失敗する一方で、ロードバランサーのヘルスチェックは成功することです。
変更されていないEnvoyフィルター構成が突然動作しなくなる
他のフィルタに対する挿入位置を指定するEnvoyFilter設定は、デフォルトでは評価順序がフィルタの作成時間に基づいているため、非常に脆弱になる可能性があります。次の仕様のフィルタを考えてみましょう。
spec:
configPatches:
- applyTo: NETWORK_FILTER
match:
context: SIDECAR_OUTBOUND
listener:
portNumber: 443
filterChain:
filter:
name: istio.stats
patch:
operation: INSERT_BEFORE
value:
...
このフィルタ設定が正しく動作するには、istio.statsフィルタの作成時間がそれよりも古い必要があります。そうでない場合、INSERT_BEFORE操作はサイレントに無視されます。このフィルタがチェーンに追加されていないことを示すエラーログは何もありません。
これは、istio.statsのような、バージョン固有の(つまり、マッチング基準にproxyVersionフィールドが含まれている)フィルタをマッチングする場合に特に問題になります。このようなフィルタは、Istioをアップグレードすると、新しいフィルタによって削除または置き換えられる可能性があります。その結果、上記のようなEnvoyFilterは最初は完璧に動作しているかもしれませんが、Istioを新しいバージョンにアップグレードした後、サイドカーのネットワークフィルタチェーンに含まれなくなります。
この問題を回避するには、他のフィルタの存在に依存しない操作(例:INSERT_FIRST)に変更するか、EnvoyFilterに明示的な優先順位を設定して、デフォルトの作成時間に基づく順序付けを上書きします。例えば、上記のフィルタにpriority: 10を追加すると、デフォルトの優先順位が0であるistio.statsフィルタの後で処理されることが保証されます。
フォールトインジェクションとリトライ/タイムアウトポリシーを持つ仮想サービスが期待通りに動作しない
現在、Istioでは、同じVirtualServiceにフォールトインジェクションとリトライまたはタイムアウトポリシーを設定することはサポートされていません。次の設定を考えてみましょう。
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: helloworld
spec:
hosts:
- "*"
gateways:
- helloworld-gateway
http:
- match:
- uri:
exact: /hello
fault:
abort:
httpStatus: 500
percentage:
value: 50
retries:
attempts: 5
retryOn: 5xx
route:
- destination:
host: helloworld
port:
number: 5000
5回の再試行が設定されているため、ユーザーはhelloworldサービスを呼び出すときにエラーをほとんど見ないことが予想されます。しかし、フォールトと再試行の両方が同じVirtualServiceに設定されているため、再試行設定は有効にならず、失敗率は50%になります。この問題を回避するために、VirtualServiceからフォールト設定を削除し、代わりにEnvoyFilterを使用してアップストリームEnvoyプロキシにフォールトを注入することができます。
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: hello-world-filter
spec:
workloadSelector:
labels:
app: helloworld
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND # will match outbound listeners in all sidecars
listener:
filterChain:
filter:
name: "envoy.filters.network.http_connection_manager"
patch:
operation: INSERT_BEFORE
value:
name: envoy.fault
typed_config:
"@type": "type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault"
abort:
http_status: 500
percentage:
numerator: 50
denominator: HUNDRED
これは、このようにすることで、クライアントプロキシにリトライポリシーが設定され、アップストリームプロキシにフォールトインジェクションが設定されるためです。