DNSプロキシ
Istioはアプリケーショントラフィックのキャプチャに加え、メッシュのパフォーマンスと可用性を向上させるためにDNSリクエストもキャプチャできます。DNSプロキシを使用すると、アプリケーションからのすべてのDNSリクエストはサイドカーにリダイレクトされ、そこでドメイン名とIPアドレスのローカルマッピングが保存されます。リクエストがサイドカーで処理できる場合、アップストリームのDNSサーバーへのラウンドトリップを回避し、アプリケーションに直接レスポンスを返します。そうでない場合は、標準の/etc/resolv.conf
DNS設定に従って、リクエストはアップストリームに転送されます。
KubernetesはKubernetes Service
に対するDNS解決を標準で提供しますが、カスタムServiceEntry
は認識されません。この機能により、DNSサーバーのカスタム設定を必要とせずに、ServiceEntry
アドレスを解決できます。Kubernetes Service
の場合、DNSレスポンスは同じですが、kube-dns
の負荷が軽減され、パフォーマンスが向上します。
この機能は、Kubernetes外で実行されているサービスでも利用できます。つまり、クラスタ外のKubernetes DNSエントリを公開するための面倒な回避策なしに、すべての内部サービスを解決できます。
はじめに
この機能は現在、デフォルトでは有効になっていません。有効にするには、次の設定でIstioをインストールします。
$ cat <<EOF | istioctl install -y -f -
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
meshConfig:
defaultConfig:
proxyMetadata:
# Enable basic DNS proxying
ISTIO_META_DNS_CAPTURE: "true"
# Enable automatic address allocation, optional
ISTIO_META_DNS_AUTO_ALLOCATE: "true"
EOF
proxy.istio.io/config
アノテーションを使用して、ポッドごとに有効にすることもできます。
kind: Deployment
metadata:
name: curl
spec:
...
template:
metadata:
annotations:
proxy.istio.io/config: |
proxyMetadata:
ISTIO_META_DNS_CAPTURE: "true"
ISTIO_META_DNS_AUTO_ALLOCATE: "true"
...
動作中のDNSキャプチャ
DNSキャプチャを試すには、最初にいくつかの外部サービスのServiceEntry
を設定します。
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: external-address
spec:
addresses:
- 198.51.100.1
hosts:
- address.internal
ports:
- name: http
number: 80
protocol: HTTP
EOF
DNSリクエストを開始するクライアントアプリケーションを起動します。
$ kubectl label namespace default istio-injection=enabled --overwrite
$ kubectl apply -f @samples/curl/curl.yaml@
DNSキャプチャがない場合、address.internal
へのリクエストは解決に失敗する可能性があります。これが有効になると、代わりに設定されたaddress
に基づいてレスポンスが返されるようになります。
$ kubectl exec deploy/curl -- curl -sS -v address.internal
* Trying 198.51.100.1:80...
アドレス自動割り当て
上記の例では、リクエストを送信したサービスに対して事前に定義されたIPアドレスがありました。しかし、安定したアドレスを持たず、DNSに依存する外部サービスにアクセスすることが一般的です。この場合、DNSプロキシはレスポンスを返すための十分な情報を持たず、DNSリクエストをアップストリームに転送する必要があります。
これは特にTCPトラフィックで問題になります。Host
ヘッダーに基づいてルーティングされるHTTPリクエストとは異なり、TCPははるかに少ない情報しか運びません。宛先IPアドレスとポート番号でのみルーティングできます。バックエンドに安定したIPアドレスがないため、それにも基づいてルーティングすることもできず、ポート番号しか残らず、複数のTCPサービスのServiceEntry
が同じポートを共有する場合に競合が発生します。詳細については、次のセクションを参照してください。
これらの問題を回避するために、DNSプロキシは、明示的に定義されていないServiceEntry
にアドレスを自動的に割り当てる機能もサポートしています。これは、ISTIO_META_DNS_AUTO_ALLOCATE
オプションで設定されます。
この機能が有効になっている場合、DNSレスポンスには、各ServiceEntry
に固有の自動的に割り当てられたアドレスが含まれます。次に、プロキシは、このIPアドレスへのリクエストを一致させ、対応するServiceEntry
にリクエストを転送するように構成されます。ISTIO_META_DNS_AUTO_ALLOCATE
を使用する場合、Istioは、ワイルドカードホストを使用していない限り、そのようなサービスにルーティング不可能なVIP(Class Eサブネットから)を自動的に割り当てます。サイドカー上のIstioエージェントは、アプリケーションからのDNSルックアップクエリに対するレスポンスとしてVIPを使用します。Envoyは、各外部TCPサービスにバインドされたトラフィックを明確に区別し、適切なターゲットに転送できるようになりました。詳細については、スマートDNSプロキシに関するIstioブログを参照してください。
これを試すには、別のServiceEntry
を構成します。
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
name: external-auto
spec:
hosts:
- auto.internal
ports:
- name: http
number: 80
protocol: HTTP
resolution: DNS
EOF
次に、リクエストを送信します。
$ kubectl exec deploy/curl -- curl -sS -v auto.internal
* Trying 240.240.0.1:80...
ご覧のとおり、リクエストは自動的に割り当てられたアドレス240.240.0.1
に送信されます。これらのアドレスは、実際のサービスとの競合を避けるために、240.240.0.0/16
の予約済みIPアドレス範囲から選択されます。
VIPなしの外部TCPサービス
デフォルトでは、Istioは、複数のTCPサービスを同じポートで区別できないため、外部TCPトラフィックをルーティングする際に制限があります。この制限は、AWS Relational Database Serviceなどのサードパーティデータベース、または地理的冗長性を持つデータベース設定を使用する場合に特に顕著です。類似しているが異なる外部TCPサービスは、デフォルトでは個別に処理できません。サイドカーがメッシュ外の2つの異なるTCPサービス間のトラフィックを区別するには、サービスが異なるポートにあるか、グローバルに一意のVIPを持つ必要があります。
たとえば、2つの外部データベースサービスmysql-instance1
とmysql-instance2
があり、両方のサービスエントリを作成した場合、クライアントサイドカーは依然として0.0.0.0:{port}
で単一のリスナーを持ち、パブリックDNSサーバーからmysql-instance1
のIPアドレスのみをルックアップし、トラフィックをそこに転送します。0.0.0.0:{port}
に到着したトラフィックがmysql-instance1
とmysql-instance2
のどちらにバインドされているかを区別する方法がないため、mysql-instance2
へのトラフィックをルーティングできません。
次の例は、DNSプロキシを使用してこの問題を解決する方法を示しています。仮想IPアドレスが各サービスエントリに割り当てられ、クライアントサイドカーは各外部TCPサービスにバインドされたトラフィックを明確に区別できるようになります。
はじめにセクションで指定されたIstio構成を更新して、
istio-injection
が有効になっている名前空間をメッシュに制限するdiscoverySelectors
も構成します。これにより、クラスタ内の他の名前空間を使用して、メッシュ外のTCPサービスを実行できます。$ cat <<EOF | istioctl install -y -f - apiVersion: install.istio.io/v1alpha1 kind: IstioOperator spec: meshConfig: defaultConfig: proxyMetadata: # Enable basic DNS proxying ISTIO_META_DNS_CAPTURE: "true" # Enable automatic address allocation, optional ISTIO_META_DNS_AUTO_ALLOCATE: "true" # discoverySelectors configuration below is just used for simulating the external service TCP scenario, # so that we do not have to use an external site for testing. discoverySelectors: - matchLabels: istio-injection: enabled EOF
最初の外部サンプルTCPアプリケーションをデプロイします。
$ kubectl create ns external-1 $ kubectl -n external-1 apply -f samples/tcp-echo/tcp-echo.yaml
2番目の外部サンプルTCPアプリケーションをデプロイします。
$ kubectl create ns external-2 $ kubectl -n external-2 apply -f samples/tcp-echo/tcp-echo.yaml
外部サービスに到達するための
ServiceEntry
を構成します。$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1 kind: ServiceEntry metadata: name: external-svc-1 spec: hosts: - tcp-echo.external-1.svc.cluster.local ports: - name: external-svc-1 number: 9000 protocol: TCP resolution: DNS --- apiVersion: networking.istio.io/v1 kind: ServiceEntry metadata: name: external-svc-2 spec: hosts: - tcp-echo.external-2.svc.cluster.local ports: - name: external-svc-2 number: 9000 protocol: TCP resolution: DNS EOF
クライアント側で各サービスごとにリスナーが個別に構成されていることを確認します。
$ istioctl pc listener deploy/curl | grep tcp-echo | awk '{printf "ADDRESS=%s, DESTINATION=%s %s\n", $1, $4, $5}' ADDRESS=240.240.105.94, DESTINATION=Cluster: outbound|9000||tcp-echo.external-2.svc.cluster.local ADDRESS=240.240.69.138, DESTINATION=Cluster: outbound|9000||tcp-echo.external-1.svc.cluster.local
クリーンアップ
$ kubectl -n external-1 delete -f @samples/tcp-echo/tcp-echo.yaml@
$ kubectl -n external-2 delete -f @samples/tcp-echo/tcp-echo.yaml@
$ kubectl delete -f @samples/curl/curl.yaml@
$ istioctl uninstall --purge -y
$ kubectl delete ns istio-system external-1 external-2
$ kubectl label namespace default istio-injection-