在上一篇文章 Istio 出口流量的 TLS 中,我演示了如何在网格内部直接通过 HTTP 协议访问外部加密服务,并揭示了其背后 Envoy 的配置逻辑。
本文将会通过 Egress Gateway
来引导 Istio 的出口流量,与 Istio 出口流量的 TLS 任务中描述的功能的相同,唯一的区别就是,这里会使用 Egress Gateway
来完成这一任务。
Istio 0.8 引入了 ingress 和 Egress gateway 的概念。 Ingress Gateway 允许定义进入服务网格的流量入口,所有入站流量都通过该入口;Egress Gateway
与之相对,它定义了网格的流量出口。 Egress Gateway 允许将 Istio 的流量治理功能(例如,监控和路由规则)应用于 Egress 流量。
设想一个具有严格安全要求的组织。根据这些要求,服务网格的所有出口流量必须流经一组专用节点。这些节点与运行其他应用的节点分开,通过策略来控制出口流量。相比其他节点而言,对这些专用节点的监控也更加详细。
另一个用例是设想一个集群,它的应用程序所在的节点没有外网 IP,因此在其上运行的网格内服务无法访问外网服务。通过定义 Egress Gateway
,并将公共 IP 分配给 Egress Gateway
节点,然后通过它引导所有出口流量,就可以控制网格内服务访问外网服务了。
如果您已启用自动注入 sidecar, 请按如下命令部署 sleep
应用程序:
$ kubectl apply -f samples/sleep/sleep.yaml
否则,您必须在部署 sleep
应用程序之前手动注入 sidecar:
$ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml)
请注意,任何可以 exec
和 curl
的 pod 都可以执行以下步骤。
$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
首先创建一个 ServiceEntry
以允许网格内服务访问外部服务。
1. 为 edition.cnn.com
定义一个 ServiceEntry
:
2. 验证 ServiceEntry
是否生效。发送 HTTPS 请求到 http://edition.cnn.com/politics。
$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics
HTTP/1.1 301 Moved Permanently
...
location: https://edition.cnn.com/politics
...
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
...
Content-Length: 151654
...
此处的返回结果应该与 Istio 出口流量的 TLS 中没有配置 TLS 发起的情况下的返回结果相同。
3. 为 edition.cnn.com
的 80 端口创建一个 Egress Gateway(假设没有启用双向 TLS 认证)。
此处 Istio 会将 Gateway
翻译成 Egress Gateway 所在的 Pod 的 Listener
。具体配置如下:
$ istioctl -n istio-system pc listeners istio-egressgateway-f8b6469db-4csb2 -o json
[
{
"name": "0.0.0.0_80",
"address": {
"socketAddress": {
"address": "0.0.0.0",
"portValue": 80
}
},
"filterChains": [
{
"filters": [
{
"name": "envoy.http_connection_manager",
"config": {
...
"rds": {
"config_source": {
"ads": {}
},
"route_config_name": "http.80"
},
...
可以看到流量经过该 Listener 之后被转交给 RDS http.80
,由于此时我们还没有创建 VirtualService
,所以 RDS http.80
中不会包含任何有意义的路由,它会直接返回 404
状态码。
$ istioctl -n istio-system pc route istio-egressgateway-f8b6469db-4csb2 -o json
[
{
"name": "http.80",
"virtualHosts": [
{
"name": "blackhole:80",
"domains": [
"*"
],
"routes": [
{
"match": {
"prefix": "/"
},
"directResponse": {
"status": 404
},
"perFilterConfig": {
"mixer": {}
}
}
]
}
],
"validateClusters": false
}
]
此处的 validateClusters
用来决定集群管理器是否对路由表中指向的 Cluster 进行验证。如果该参数设置为 true
且路由指向了不存在的集群,则不会加载该路由表;如果该参数设置为 false
且路由指向了不存在的集群,则会继续加载该路由表,最后找不到路由会返回 404。如果通过静态配置文件 route_config 定义路由表,则该选项默认值为 true
;如果通过 RDS 接口动态加载路由器,则该选项默认值为 false
。
如果你启用了双向 TLS 认证,需要加上额外的 TLS 配置,这里我不展开详述,可以参考官方文档。
4. 创建一个 DestinationRule
和 VirtualService
来引导流量通过 Egress Gateway 与外部服务通信。
这里其实创建了两条路由,我们一个一个来看:
mesh
,表示该路由创建在网格内的应用中:$ istioctl pc route sleep-5bc866558c-5nl8k --name 80 -o json|grep "edition.cnn.com" -A 11 -B 1
{
"name": "edition.cnn.com:80",
"domains": [
"edition.cnn.com",
"edition.cnn.com:80"
],
"routes": [
{
"match": {
"prefix": "/"
},
"route": {
"cluster": "outbound|80|cnn|istio-egressgateway.istio-system.svc.cluster.local",
"timeout": "0.000s",
"maxGrpcTimeout": "0.000s"
},
如果不指定 gateway,gateway 默认值就是
mesh
。
该 VirtualService 的作用就是将目的地址是 edition.cnn.com:80
的流量重定向到 Egress Gateway
。
这里我们将流量打向了 subset 为 cnn
的 Cluster,但现在不存在这个 Cluster,所以还需要通过 DestinationRule
定义一个 Cluster:
查看创建好的 Cluster:
$ istioctl pc cluster sleep-5bc866558c-5nl8k --fqdn istio-egressgateway.istio-system.svc.cluster.local --subset cnn --port 80 -o json
[
{
"name": "outbound|80|cnn|istio-egressgateway.istio-system.svc.cluster.local",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {}
},
"serviceName": "outbound|80|cnn|istio-egressgateway.istio-system.svc.cluster.local"
},
"connectTimeout": "1.000s",
"circuitBreakers": {
"thresholds": [
{}
]
},
"http2ProtocolOptions": {
"maxConcurrentStreams": 1073741824
}
}
]
istio-egressgateway
,表示该路由创建在 Egress Gateway 中:$ istioctl -n istio-system pc route istio-egressgateway-f8b6469db-fj6zr -o json
[
{
"name": "http.80",
"virtualHosts": [
{
"name": "edition.cnn.com:80",
"domains": [
"edition.cnn.com",
"edition.cnn.com:80"
],
"routes": [
{
"match": {
"prefix": "/"
},
"route": {
"cluster": "outbound|80||edition.cnn.com",
"timeout": "0.000s",
"maxGrpcTimeout": "0.000s"
},
...
该 VirtualService 的作用是通过 Egress Gateway 访问目的地址 edition.cnn.com:80
。这里 Egress Gateway 将流量路由到 Cluster outbound|80||edition.cnn.com
,最后将流量转发到服务 edition.cnn.com:80
。完整的流量转发流程如下图所示:
通过 Egress Gateway 引导 Istio 的出口 HTTP 流量
5. 重新发送 HTTP 请求到 http://edition.cnn.com/politics。
$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics
HTTP/1.1 301 Moved Permanently
...
location: https://edition.cnn.com/politics
...
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
...
Content-Length: 151654
...
输出应与步骤 2 中的输出相同。
6. 查看 istio-egressgateway
pod 中与我们的请求相对应的日志。
$ kubectl logs $(kubectl get pod -l istio=egressgateway -n istio-system -o jsonpath='{.items[0].metadata.name}') istio-proxy -n istio-system | tail
你会在输出结果中看到与请求相关的日志:
[2018-06-14T11:46:23.596Z] "GET /politics HTTP/1.1" 301 - 0 0 3 1 "172.30.146.87" "curl/7.35.0" "ab7be694-e367-94c5-83d1-086eca996dae" "edition.cnn.com" "151.101.193.67:80"
这里我们只将到 80 端口的 HTTP 流量重定向到 Egress Gateway,到 443 端口的 HTTPS 流量直接转到
edition.cnn.com
。
删除 Gateway、VirtualService、DestinationRule 和 ServiceEntry。
$ kubectl delete gateway istio-egressgateway
$ kubectl delete serviceentry cnn
$ kubectl delete virtualservice direct-cnn-through-egress-gateway
$ kubectl delete destinationrule egressgateway-for-cnn