新天地への拡大 - IstioにおけるスマートDNSプロキシ
VM統合、マルチクラスタなどを簡素化するワークロードローカルDNS解決。
DNS解決は、Kubernetes上のあらゆるアプリケーションインフラストラクチャにおいて重要なコンポーネントです。アプリケーションコードがKubernetesクラスタ内の別のサービス、あるいはインターネット上のサービスにアクセスしようとすると、サービスへの接続を開始する前に、サービスのホスト名に対応するIPアドレスを最初にルックアップする必要があります。この名前ルックアッププロセスは、しばしばサービスディスカバリと呼ばれます。Kubernetesでは、kube-dns
またはCoreDNSなどのクラスタDNSサーバーが、サービスの種類がclusterIP
の場合、サービスのホスト名を一意のルーティング不可能な仮想IP(VIP)に解決します。各ノード上のkube-proxy
は、このVIPを一連のサービスのPodにマッピングし、ランダムに選択されたPodの1つにトラフィックを転送します。サービスメッシュを使用する場合、サイドカーはトラフィック転送に関してはkube-proxy
と同様に動作します。
次の図は、今日のDNSの役割を示しています。
DNSが抱える問題
サービスメッシュ内でのDNSの役割は取るに足らないように見えるかもしれませんが、メッシュをVMに拡張し、シームレスなマルチクラスタアクセスを実現する上で、常に障害となってきました。
KubernetesサービスへのVMアクセス
サイドカーを持つVMの場合を考えてみましょう。以下の図に示すように、VM上のアプリケーションは、通常、クラスタのDNSサーバーにアクセスできないため、Kubernetesクラスタ内のサービスのIPアドレスをルックアップします。
dnsmasq
とNodePort
サービスを使用したkube-dns
の外部公開を含む複雑な回避策を使用すれば、VMでkube-dns
を名前サーバーとして使用することは技術的には可能ですが、クラスタ管理者にそうするように説得できるかどうかは別問題です。それでも、セキュリティ上の問題を招く可能性があります。結局のところ、これらは、組織能力とドメイン専門知識が限られている人々にとっては、通常は範囲外のポイントソリューションです。
VIPのない外部TCPサービス
メッシュ内のVMだけがDNSの問題に悩まされるわけではありません。サイドカーがメッシュ外の2つの異なるTCPサービス間のトラフィックを正確に区別するには、サービスは異なるポートにあるか、Kubernetesサービスに割り当てられたclusterIP
のように、グローバルに一意のVIPが必要です。しかし、VIPがない場合はどうでしょうか?ホスト型データベースなどのクラウドホスト型サービスには、通常、VIPがありません。代わりに、プロバイダーのDNSサーバーは、アプリケーションが直接アクセスできるインスタンスIPの1つを返します。たとえば、以下の2つのサービスエントリは、2つの異なるAWS RDSサービスを指しています。
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: db1
namespace: ns1
spec:
hosts:
- mysql-instance1.us-east-1.rds.amazonaws.com
ports:
- name: mysql
number: 3306
protocol: TCP
resolution: DNS
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: db2
namespace: ns1
spec:
hosts:
- mysql-instance2.us-east-1.rds.amazonaws.com
ports:
- name: mysql
number: 3306
protocol: TCP
resolution: DNS
サイドカーは0.0.0.0:3306
に単一のリスナーを持ち、パブリックDNSサーバーからmysql-instance1.us-east1.rds.amazonaws.com
のIPアドレスをルックアップし、トラフィックを転送します。db2
にトラフィックをルーティングすることはできません。なぜなら、0.0.0.0:3306
に到着するトラフィックがdb1
向けかdb2
向けかを区別する方法がないからです。これを達成する唯一の方法は、解決をNONE
に設定することです。これにより、サイドカーは3306
ポートのトラフィックをアプリケーションによって要求された元のIPに盲目的に転送します。これは、宛先IPに関係なく、3306ポートへのすべてのトラフィックを許可するファイアウォールに穴を開けるようなものです。トラフィックを流すためには、システムのセキュリティ体制を妥協せざるを得なくなります。
リモートクラスタ内のサービスのDNS解決
マルチクラスタメッシュのDNSの制限は周知の事実です。1つのクラスタ内のサービスは、呼び出し元名前空間にスタブサービスを作成するなどの面倒な回避策を使わずに、他のクラスタ内のサービスのIPアドレスをルックアップできません。
DNSの制御
全体的に、DNSはIstioにおいてしばらくの間厄介な問題でした。この問題を解決する時が来ました。(Istioネットワーキングチームは)エンドユーザーである皆様にとって完全に透過的な方法で、この問題に一度に完全に取り組むことにしました。最初の試みでは、EnvoyのDNSプロキシを使用しました。Envoyで使用されているc-ares DNSライブラリの洗練性の欠如のため、非常に信頼性が低く、全体的に期待外れでした。この問題を解決しようと決意し、Goで記述されたIstioサイドカーエージェントにDNSプロキシを実装することにしました。スケールと安定性を損なうことなく、取り組みたいすべてのシナリオを処理できる実装を最適化することができました。私たちが使用するGo DNSライブラリは、CoreDNS、Consul、Mesosなど、スケーラブルなDNS実装で使用されているものと同じです。これは、スケールと安定性に関して本番環境でテストされています。
Istio 1.8以降、サイドカー上のIstioエージェントには、Istiodによって動的にプログラムされたキャッシングDNSプロキシが搭載されます。Istiodは、アプリケーションがクラスタ内のKubernetesサービスとサービスエントリに基づいてアクセスする可能性のあるすべてのサービスのホスト名とIPアドレスのマッピングをプッシュします。アプリケーションからのDNSルックアップクエリは、透過的にインターセプトされ、PodまたはVM内のIstioエージェントによって提供されます。クエリがメッシュ内のサービスに対するものである場合(サービスが存在するクラスタに関係なく)、エージェントはアプリケーションに直接応答します。そうでない場合は、/etc/resolv.conf
で定義されたアップストリームネームサーバーにクエリを転送します。次の図は、アプリケーションがホスト名を使用してサービスにアクセスしようとしたときに発生する相互作用を示しています。
次のセクションでわかるように、DNSプロキシ機能は、Istioの多くの側面に大きな影響を与えています。
DNSサーバーの負荷軽減と高速な解決
ほとんどすべてのDNSクエリがIstioによってPod内で解決されるため、クラスタのKubernetes DNSサーバーの負荷は大幅に減少します。クラスタ上のメッシュのフットプリントが大きいほど、DNSサーバーの負荷は小さくなります。Istioエージェントに独自のDNSプロキシを実装することで、CoreDNSが現在抱えている正確性の問題なしに、CoreDNS auto-pathなどの優れた最適化を実装することができました。
この最適化の影響を理解するために、標準的なKubernetesクラスタ(Pod用のカスタムDNS設定なし、つまり/etc/resolv.conf
でndots:5
のデフォルト設定)での単純なDNSルックアップシナリオを考えてみましょう。アプリケーションがproductpage.ns1.svc.cluster.local
のDNSルックアップを開始すると、/etc/resolv.conf
のDNS検索名前空間(例:ns1.svc.cluster.local
)をDNSクエリの一部として追加してから、ホストをそのままクエリします。その結果、実際に送信される最初のDNSクエリはproductpage.ns1.svc.cluster.local.ns1.svc.cluster.local
のようになり、Istioが関与していない場合はDNS解決に必ず失敗します。/etc/resolv.conf
に5つの検索名前空間がある場合、アプリケーションは各検索名前空間に対して2つのDNSクエリ(IPv4のA
レコードとIPv6のAAAA
レコード)を送信し、その後、コードで使用されているホスト名を使った最終的なクエリペアを送信します。接続を確立する前に、アプリケーションはホストごとに12個のDNSルックアップクエリを実行します!
IstioによるCoreDNSスタイルのauto-pathテクニックの実装により、サイドカーエージェントは最初のクエリ内でクエリされている実際のホスト名を検出し、このDNSレスポンスの一部としてproductpage.ns1.svc.cluster.local
へのcname
レコードと、productpage.ns1.svc.cluster.local
のA/AAAA
レコードを返します。この応答を受け取ったアプリケーションは、IPアドレスをすぐに抽出し、そのIPへのTCP接続の確立に進みます。IstioエージェントのスマートDNSプロキシは、DNSクエリの数を12個からわずか2個に劇的に削減します!
VMとKubernetesの統合
Istioエージェントはメッシュ内のサービスのローカルDNS解決を実行するため、VMからのKubernetesサービスに対するDNSルックアップクエリは、クラスタの外部にkube-dns
を公開するための面倒な回避策を必要とせずに成功するようになります。クラスタ内の内部サービスをシームレスに解決できるため、VM上のモノリスはAPIゲートウェイ経由の追加のレベルの間接参照なしでKubernetes上のマイクロサービスにアクセスできるようになり、モノリスからマイクロサービスへの移行が簡素化されます。
可能な場合の自動VIP割り当て
エージェントのこのDNS機能は、同じポートにVIPのない複数の外部TCPサービスを区別するという問題をどのように解決するのでしょうか?
Kubernetesに着想を得て、Istioはワイルドカードホストを使用しない限り、そのようなサービスにルーティング不可能なVIP(Class Eサブネットから)を自動的に割り当てます。サイドカー上のIstioエージェントは、アプリケーションからのDNSルックアップクエリへの応答としてVIPを使用します。Envoyは、各外部TCPサービスにバインドされたトラフィックを明確に区別し、適切なターゲットに転送できるようになりました。DNSプロキシの導入により、非ワイルドカードTCPサービスに対してresolution: NONE
を使用する必要がなくなり、全体的なセキュリティ体制が向上します。Istioはワイルドカード外部サービス(例:*.us-east1.rds.amazonaws.com
)にはあまり役立ちません。そのようなサービスを処理するには、NONE解決モードを使用する必要があります。
マルチクラスタDNSルックアップ
リモートクラスタの名前空間内の内部サービスをアプリケーションが直接呼び出すマルチクラスタメッシュを作成しようとする冒険的な方々にとって、DNSプロキシ機能は非常に便利です。アプリケーションは、すべてのクラスタのすべての名前空間でKubernetesサービスを解決でき、各クラスタにスタブKubernetesサービスを作成する必要はありません。
DNSプロキシの利点は、今日のIstioで説明されているマルチクラスタモデル以外にも及びます。Tetrateでは、このメカニズムを顧客のマルチクラスタ展開で広く使用して、サイドカーがメッシュ内のすべてのクラスタのイングレスゲートウェイで公開されているホストのDNSを解決し、相互TLS経由でアクセスできるようにしています。
結論
複数のクラスタ、異なる環境、および外部サービスの統合において、DNS制御の欠如によって引き起こされる問題は、これまで見過ごされ、完全に無視されてきました。IstioサイドカーエージェントにキャッシングDNSプロキシを導入することで、これらの問題が解決されます。アプリケーションのDNS解決を制御することで、Istioはトラフィックがバインドされるターゲットサービスを正確に識別し、クラスタ内およびクラスタ間でのIstioにおける全体的なセキュリティ、ルーティング、およびテレメトリの姿勢を強化します。
Istio 1.8のpreview
プロファイルでは、スマートDNSプロキシが有効になっています。ぜひお試しください!