Istio シリーズです。
今回はクラスタ内から外部のサービスへのアクセスについてです。ServiceEntry ってやつが登場です。(これを書く中でだいぶ自分の理解の誤りが訂正されました、良かった良かった)
クラスタ内から外部へのアクセスモードについて
Istio のデフォルト設定では istio-system namespace の istio という ConfigMap で次のように outboundTrafficPolicy の mode が ALLOW_ANY
となっており、中から外は自由に通信できます。
$ kubectl get cm -n istio-system istio -o yaml | grep "^ outboundTrafficPolicy:" -A 1 outboundTrafficPolicy: mode: ALLOW_ANY
これを REGISTRY_ONLY
に変更すると登録された宛先にしかアクセスできなくなります。
次の様にして変更することができます。インストール時に指定しておくには --set values.global.outboundTrafficPolicy.mode=REGISTRY_ONLY
とします。
$ kubectl get configmap istio -n istio-system -o yaml \ | sed 's/mode: ALLOW_ANY/mode: REGISTRY_ONLY/g' \ | kubectl replace -n istio-system -f -
変更されました。
$ kubectl get cm -n istio-system istio -o yaml | grep "^ outboundTrafficPolicy:" -A 1 outboundTrafficPolicy: mode: REGISTRY_ONLY
戻す場合はこれで。
$ kubectl get configmap istio -n istio-system -o yaml \ | sed 's/mode: REGISTRY_ONLY/mode: ALLOW_ANY/g' \ | kubectl replace -n istio-system -f -
HTTP の外部サービスを登録する
REGISTRY_ONLY になったら未登録の外部サービス(アドレス)にはアクセス出来ません。
502 Bad Gateway となりました。
root@ubuntu-deployment-54bbd6f4ff-q9sdj:/# curl -sv http://httpbin.org/ip * Trying 52.202.2.199... * TCP_NODELAY set * Connected to httpbin.org (52.202.2.199) port 80 (#0) > GET /ip HTTP/1.1 > Host: httpbin.org > User-Agent: curl/7.58.0 > Accept: */* > < HTTP/1.1 502 Bad Gateway < date: Mon, 09 Mar 2020 16:11:01 GMT < server: envoy < content-length: 0 < * Connection #0 to host httpbin.org left intact
アプリを Kubernetes でコンテナとして動かしていても、データベースなどはクラウドのマネージドサービスを使うことが多いと思います。クラスタ外にアクセス出来ないということはそういったサービスへもアクセス出来ないことを意味します。それでは困るので ServiceEntry というもので宛先を登録します。
登録は簡単です。次の様にします。hosts に許可したい宛先 FQDN を指定します。
$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: name: external-svc spec: hosts: - httpbin.org location: MESH_EXTERNAL ports: - number: 80 name: http protocol: HTTP resolution: DNS EOF
アクセスできるようになりました。
root@ubuntu-deployment-54bbd6f4ff-q9sdj:/# curl -sv http://httpbin.org/headers * Trying 3.232.168.170... * TCP_NODELAY set * Connected to httpbin.org (3.232.168.170) port 80 (#0) > GET /headers HTTP/1.1 > Host: httpbin.org > User-Agent: curl/7.58.0 > Accept: */* > < HTTP/1.1 200 OK < date: Mon, 09 Mar 2020 16:18:21 GMT < content-type: application/json < content-length: 1186 < server: envoy < access-control-allow-origin: * < access-control-allow-credentials: true < x-envoy-upstream-service-time: 347 < { "headers": { "Accept": "*/*", "Content-Length": "0", "Host": "httpbin.org", "User-Agent": "curl/7.58.0", "X-Amzn-Trace-Id": "Root=1-5e666c4d-f5fff240a5be87147f2cc6a4", "X-B3-Sampled": "0", "X-B3-Spanid": "db1df3e34fb690a9", "X-B3-Traceid": "e12ef759daff1ddcdb1df3e34fb690a9", "X-Envoy-Decorator-Operation": "httpbin.org:80/*", "X-Envoy-Peer-Metadata": "ChwKDElOU1RBTkNFX0lQUxIMGgoxNzIuMTcuMC44CsYBCgZMQUJFTFMSuwEquAEKDwoDYXBwEggaBnVidW50dQohChFwb2QtdGVtcGxhdGUtaGFzaBIMGgo1NGJiZDZmNGZmCiQKGXNlY3VyaXR5LmlzdGlvLmlvL3Rsc01vZGUSBxoFaXN0aW8KKwofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIIGgZ1YnVudHUKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAosCgROQU1FEiQaInVidW50dS1kZXBsb3ltZW50LTU0YmJkNmY0ZmYtcTlzZGoKFgoJTkFNRVNQQUNFEgkaB2RlZmF1bHQKVQoFT1dORVISTBpKa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2RlZmF1bHQvZGVwbG95bWVudHMvdWJ1bnR1LWRlcGxveW1lbnQKHAoPU0VSVklDRV9BQ0NPVU5UEgkaB2RlZmF1bHQKJAoNV09SS0xPQURfTkFNRRITGhF1YnVudHUtZGVwbG95bWVudA==", "X-Envoy-Peer-Metadata-Id": "sidecar~172.17.0.8~ubuntu-deployment-54bbd6f4ff-q9sdj.default~default.svc.cluster.local" } } * Connection #0 to host httpbin.org left intact
しかし、なんだか余計なデータを header に詰めて送ってますね。X-Envoy-Peer-Metadata には送信元 Pod の metadata が Base64 encode されて入っています。何に使うのだろう?
istioctl コマンドで ENDPOINT に登録されていることがわかります。
$ istioctl proxy-config endpoint ubuntu-deployment-54bbd6f4ff-q9sdj | egrep 'ENDPOINT|httpbin.org' ENDPOINT STATUS OUTLIER CHECK CLUSTER 3.232.168.170:80 HEALTHY OK outbound|80||httpbin.org 52.202.2.199:80 HEALTHY OK outbound|80||httpbin.org
resolution: DNS と指定しているため、この宛先のIPアドレスは DNS から取得した値となっており、エラーが増えたり、接続できなかったら状態が変わるのでしょう。Envoy は自分で名前解決して接続するようです。curl の –resolve で全然関係の無い IP アドレスを指定していても Envoy は Host ヘッダーのサーバーへ自分で名前解決を行って接続するようです。
HTTP や HTTPS の場合は Host ヘッダーや SNI に接続先ホスト情報がありますが、他の protocol ではそうはいきませんから、接続先 IP アドレスが ServiceEntry の addresses にマッチしているかどうかがチェックされます。 resolution: STATIC の場合は endpoints に指定した IP アドレスに接続を試みます。
resolution: NONE とした場合は元の接続先 IP アドレスがそのまま使われます。つまり、curl で –resolve で指定した場合、Envoy もそこに接続します。
HTTPS の外部サービスを登録する
次に HTTPS でも接続できるようにします。www.google.com でテストしてみます。port 443 を追加するだけですね。resolution が DNS や NONE であれば先の HTTP のやつとまとめてしまうことが可能です。
$ 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
name: tls
, protocol: TLS
は name: https
, protol: HTTPS
でも機能します。が、明確な違いがわかりません。GitHub に issue (ServiceEntry protocol HTTPS vs TLS documentation + Virtual Services requirements #19188) を見つけました。全く同感ですね。 name の方はルールがあるっぽいけど https と tls の違いはわからない。http2 もあるのか…
resolution: DNS
よりも resolution: NONE
の方が良さそうですね。
次回は「外部サービスでも Fault Injection したいぞ」です。
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 編