Istio シリーズです。
前回の予告通り今回は「外部サービスでも Fault Injection したいぞ」編です。
「Fault Injection 編」でその便利さを取り上げましたが、外部の API を使用している時にもそこに Inject したいですよね?依存している外部サービスでエラーが発生したらどうなるのかとか、レスポンスが遅かった場合どうなるかとか、それを Istio の設定で調整することが可能になります。
前回設定した ServiceEntry の確認
前回は最終的に次の設定を行いました。これで httpbin.org と www.google.com 宛ては通信が許可されました。(outboundTrafficPolicy は REGISTRY_ONLY でした)
$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: name: external-svc spec: hosts: - httpbin.org - www.google.com location: MESH_EXTERNAL ports: - number: 80 name: http protocol: HTTP - number: 443 name: tls protocol: TLS resolution: DNS EOF
しかし、これでは直接各サイトに出ていくだけです。Fault Injection は VirtualService の機能でした。「VirtualService 編」を読み返しましょう。VirtualService にはそのサービスの転送先として DestinationRule しました。
VirtualService の作成
httpbin.org
httpbin.org 用の VirtualService を作成します。httpbin.org は HTTPS には対応していないため port 80 だけです。全リクエストに3秒の delay を入れ、30% のリクエストは 500 Internal Server Error を返すようにしてみます。DestinationRule は登録せずとも ServiceEntry があるため接続できます。ServiceEntry がない場合は "response_flags":"NR,DI"
で Injection の後でエラーになります。これは outboundTrafficPolicy が ALLOW_ANY でも同じです。ServiceEntry が必要です。
$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: httpbin-org spec: hosts: - httpbin.org http: - match: - port: 80 fault: delay: fixedDelay: 3s percentage: value: 100 abort: httpStatus: 500 percentage: value: 30 route: - destination: host: httpbin.org EOF
Delay だけが適用された時のログです。
{ "authority": "httpbin.org", "bytes_received": "0", "bytes_sent": "34", "downstream_local_address": "35.170.216.115:80", "downstream_remote_address": "172.17.0.8:43426", "duration": "3366", "istio_policy_status": "-", "method": "GET", "path": "/ip", "protocol": "HTTP/1.1", "request_id": "fa22dc61-1cc5-45c4-bc97-4f02d8a7c468", "requested_server_name": "-", "response_code": "200", "response_flags": "DI", "route_name": "-", "start_time": "2020-03-11T15:24:31.272Z", "upstream_cluster": "outbound|80||httpbin.org", "upstream_host": "34.230.193.231:80", "upstream_local_address": "172.17.0.8:54090", "upstream_service_time": "361", "upstream_transport_failure_reason": "-", "user_agent": "curl/7.58.0", "x_forwarded_for": "-" }
こちらは Delay と Fault が両方適用された時のログです。
{ "authority": "httpbin.org", "bytes_received": "0", "bytes_sent": "18", "downstream_local_address": "3.232.168.170:80", "downstream_remote_address": "172.17.0.8:50018", "duration": "3001", "istio_policy_status": "-", "method": "GET", "path": "/ip", "protocol": "HTTP/1.1", "request_id": "98a3a4c5-4a0d-4325-a800-da7c3b6db3e3", "requested_server_name": "-", "response_code": "500", "response_flags": "DI,FI", "route_name": "-", "start_time": "2020-03-11T15:24:47.524Z", "upstream_cluster": "-", "upstream_host": "-", "upstream_local_address": "-", "upstream_service_time": "-", "upstream_transport_failure_reason": "-", "user_agent": "curl/7.58.0", "x_forwarded_for": "-" }
www.google.com
それでは次に www.google.com の VirtualService を作成します。こちらは https にも対応しています。で、ここがキモですが、そのままでは Envoy は https の中身には関与しませんでした。でも Fault Injection をするためにはそれではいけませんから、http で受けたリクエストを Envoy が proxy する際に https にして接続するようにします。そうすることで元のリクエストは http なので中身をいじることができます。さっきとの違いがわかるように今回は delay を1秒に、abort を 400 Bad Request にしてみます。
今回は destination に tls-origination という subset を指定してあります。これは下に続く DestinationRule で定義してあります。tls.mode を SIMPLE にして sni で www.google.com を指定しています。接続先が SNI 必須の場合はこの sni 指定が必要です。SIMPLE って何?って思いますが、他の選択肢は MUTUAL がクライアント証明書を必要とするやつです。あとは PASSTHROUGH とか。
httpbin.org の時と違って DestinationRule はありますが、こちらも ServiceEntry を消すとアクセスできなくなります。
$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: www-google-com spec: hosts: - www.google.com http: - match: - port: 80 fault: delay: fixedDelay: 1s percentage: value: 100 abort: httpStatus: 400 percentage: value: 30 route: - destination: host: www.google.com subset: tls-origination port: number: 443 --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: www-google-com spec: host: www.google.com subsets: - name: tls-origination trafficPolicy: portLevelSettings: - port: number: 443 tls: mode: SIMPLE sni: www.google.com EOF
これで http://www.google.com/ にアクセスすれば Envoy が Injection したうえで https で www.google.com に proxy してくれます。
ログです。"downstream_local_address": "216.58.197.196:80"
で curl は www.google.com の port 80 にアクセスしようとしたことがわかります。"upstream_cluster": "outbound|443|tls-origination|www.google.com"
で Envoy 内の経路がわかります。DestinationRule の tls-origination が使われています。"upstream_host": "216.58.197.196:443"
で proxy 先が port 443 (https) であることがわかります。
{ "authority": "www.google.com", "bytes_received": "0", "bytes_sent": "13062", "downstream_local_address": "216.58.197.196:80", "downstream_remote_address": "172.17.0.8:41130", "duration": "1189", "istio_policy_status": "-", "method": "GET", "path": "/", "protocol": "HTTP/1.1", "request_id": "069baa9b-7d0a-4e84-9b4b-a11e34226fce", "requested_server_name": "-", "response_code": "200", "response_flags": "DI", "route_name": "-", "start_time": "2020-03-11T15:38:11.379Z", "upstream_cluster": "outbound|443|tls-origination|www.google.com", "upstream_host": "216.58.197.196:443", "upstream_local_address": "172.17.0.8:43430", "upstream_service_time": "186", "upstream_transport_failure_reason": "-", "user_agent": "curl/7.58.0", "x_forwarded_for": "-" }
次は Fault が Inject された時のログです。これは proxy せずに 400 を返しているため proxy 先の情報はありません。
{ "authority": "www.google.com", "bytes_received": "0", "bytes_sent": "18", "downstream_local_address": "216.58.197.196:80", "downstream_remote_address": "172.17.0.8:41304", "duration": "1001", "istio_policy_status": "-", "method": "GET", "path": "/", "protocol": "HTTP/1.1", "request_id": "78dabd1b-fe57-41fb-8eaf-44ccc6517e3b", "requested_server_name": "-", "response_code": "400", "response_flags": "DI,FI", "route_name": "-", "start_time": "2020-03-11T15:38:21.648Z", "upstream_cluster": "-", "upstream_host": "-", "upstream_local_address": "-", "upstream_service_time": "-", "upstream_transport_failure_reason": "-", "user_agent": "curl/7.58.0", "x_forwarded_for": "-" }
www.google.com に https でアクセスしようとするとどうなるか
本来、https でアクセスするべきところに http でアクセスするようにしなければならないわけですが、https のままアクセスしようとするとどうなるでしょうか。
冒頭で前回までの設定を確認しましたが、そこで ServiceEntry に https の設定が残っているため、https の場合はその設定が有効でそのままアクセスできます。もちろん outboundTrafficPolicy が ALLOW_ANY であればその ServiceEntry も不要ですね。
コンテナ化されたアプリケーションでは外部リソースの定義は ConfigMap などを使って容易に変更可能なはずですよね。Fault Injection 使っていきましょう。
まとめ
クラスタ外へのアクセスでも VirtualService を使うことができ、Fault Injection することができることを確認しました。https の termination だけじゃなくて orinination も Envoy に任せられることも確認できました。
Accessing External Services とか Egress TLS Origination に書いてあります。
は〜〜、まだまだいろいろあるなあ、先は長い。続く
Istio 導入への道シリーズ
- Istio 導入への道 (1) – インストール編
- Istio 導入への道 (2) – サービス間通信編
- Istio 導入への道 (3) – VirtualService 編
- Istio 導入への道 (4) – Fault Injection 編
- Istio 導入への道 (5) – OutlierDetection と Retry 編
- Istio 導入への道 (6) – Ingress Gatway 編
- Istio 導入への道 (7) – 外部へのアクセス / ServiceEntry 編
- Istio 導入への道 (8) – 外部へのアクセスでも Fault Injection 編
- Istio 導入への道 (9) – gRPC でも Fault Injection 編
- Istio 導入への道 (10) – 図解
- Istio 導入への道 (11) – Ingress Gateway で TLS Termination 編