Istio 導入への道 - 外部へのアクセス / ServiceEntry 編

Istio シリーズです。

今回はクラスタ内から外部のサービスへのアクセスについてです。ServiceEntry ってやつが登場です。(これを書く中でだいぶ自分の理解の誤りが訂正されました、良かった良かった)

クラスタ内から外部へのアクセスモードについて

Istio のデフォルト設定では istio-system namespace の istio という ConfigMap で次のように outboundTrafficPolicymodeALLOW_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 を追加するだけですね。resolutionDNSNONE であれば先の 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: TLSname: 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 導入への道シリーズ

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