トラフィック管理の問題
リクエストが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}" 200
Pod 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
これは、このようにすることで、クライアントプロキシにリトライポリシーが設定され、アップストリームプロキシにフォールトインジェクションが設定されるためです。