通过 Istio 可以劫持出方向的 HTTP/HTTPS 流量到特定的 Egress Gateway。通过固定 Egress Gateway 的节点可以实现线路优化,固定出口 IP,安全审计等等功能。
但是如果需要代理 TCP 的流量,会麻烦一点,由于 TCP 不像 HTTP 那样可以通过 Host header 拿到目的服务的地址。转发 TCP 流量会丢失目的地址信息。
我们需要结合 Istio 的 DNS Proxying 功能,给 TCP 连接的域名固定一个 IP 地址。Istio 劫持到这个 IP 地址的出连接后,转发时通过 TLS 带上 SNI 信息转发给一个 SNI Proxy,SNI Proxy 会根据 server name 去代理连接目标地址。
配置方式
示例假设我们希望劫持 example.com:9444 端口的流量,到我们指定的 SNI Proxy 去。
为了便于验证,9444 端口的协议还是用 HTTP 来进行测试。
启用 DNS Proxying 功能
通过给 Pod 加上 annotation 启用 DNS 代理功能,以及启用自动分配 IP 地址的能力。
template:
metadata:
annotations:
proxy.istio.io/config: |
proxyMetadata:
ISTIO_META_DNS_CAPTURE: "true"
ISTIO_META_DNS_AUTO_ALLOCATE: "true"
创建 ServiceEntry 声明要劫持的域名和端口
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: external-auto
spec:
hosts:
- example.com
ports:
- name: tcp
number: 9444
protocol: TCP
resolution: DNS
给 SNI Proxy 配置 DestinationRule,启用 TLS
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: tls-sni-proxy
spec:
host: sni-proxy
subsets:
- name: example-com-9444
trafficPolicy:
tls:
mode: SIMPLE
sni: example.com:9443
创建 VirtualService 将 example.com:9444
的流量路由到上面创建的 DestinationRule
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: auto-tls
spec:
hosts:
- "example.com"
gateways:
- mesh
tcp:
- match:
- port: 9444
route:
- destination:
host: sni-proxy
subset: example-com-9444
port:
number: 443
测试
curl example.com:9444
会被路由到 SNI Proxy 代理请求出去。
问题
同一域名未定义的其他端口无法正常访问
HTTP request failed after ISTIO_META_DNS_AUTO_ALLOCATE enabled
当前如果开启了 ISTIO_META_DNS_AUTO_ALLOCATE
,由 Istio 给 ServiceEntry 的域名分配了 IP,会导致所有没有在 ServiceEntry 中定义的端口都无法访问。
因为这些连接会被路由到 PassthroughCluster,然后 upstream 会是 Istio 分配的虚拟 IP,无法正常连接。
目前只能通过在 ServiceEntry 中给该域名所有需要访问的端口都预先定义好才能避免。
但是如果是严格限制对外访问的环境,要求所有外部请求的端口都事先定义也很合理。
注: 可以不使用 ISTIO_META_DNS_AUTO_ALLOCATE
自动分配 IP,而是通过 ServiceEntry.addresses 手动给需要的 ServiceEntry 分配 IP,这样可以尽量减小对其他 ServiceEntry 中定义域名的影响。
SNI Proxy 的端口问题
由于我们通过 SNI 设置的 server name 格式为 example.com:9443
,SNI Proxy 需要能够解析出 host 和端口号然后去访问。