Istio 導入への道 - 外部へのアクセスでも Fault Injection 編

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 InjectionVirtualService の機能でした。「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.modeSIMPLE にして sniwww.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 導入への道シリーズ

Built with Hugo
テーマ StackJimmy によって設計されています。