Istio 南北向流量管理
引言
之前的文章:Istio 东西向流量管理
这次讲的是南北向流量管理
,也就是入口请求到集群服务的流量管理
。
Istio 网关
网络社区中有一个术语叫Ingress
,是指入口请求到集群内服务的流量管理。Ingress
指的是本地网络之外
的流量请求
到本地集群网络
中的端口。此流量先路由
到公开的入口点
,以便通过执行一些本地网络
的规则和策略
来确认哪些流量被允许
进入。如果流量未通过
这些入口点,则无法
与集群的任何服务连接
。如果入口点允许
流量进入,则将其代理
到本地网络中合适的节点
。Istio
对入口流量
的管理是由Istio网关
进行的。
Istio网关的工作原理
传统上,Kubernetes
使用Ingress控制器
来处理外部进入集群的流量。使用Istio
时,Istio网关
用新的Gateway
资源和VirtualService
资源来控制入口流量
,它们协同工作将流量路由到网格中。在网格内部不需要Gateway
,因为服务可以通过集群本地服务名称互相访问(DNS服务发现)
。
客户端
在特定端口上发出请求
,例如:http://test.com:38081/,这是外部请求。负载均衡器
在这个端口上进行侦听,并将请求转发到集群中
。- 在集群内部,请求被路由到
Istio IngressGateway
服务所侦听的负载均衡器转发的端口上。Istio IngressGateway
服务将请求转发到对应的pod
上。- 在
IngressGateway pod
上会配置Gateway资源
和VirtualService资源
定义。Gateway
会配置端口、协议及相关安全证书
。VirtualService
的路由配置信息
用于找到正确的服务(在东西向流量管理文章中我们已经讲过路由配置了)。Istio IngressGateway pod
会根据路由配置信息
将请求路由到对应的服务
上。应用服务
将请求路由到对应应用pod
中的实例上。
Istio 网关负载均衡器的作用
典型的服务网格
具有一个
或多个负载均衡器
,也称为网关(Gateway)
,它们从外部网络
终止TLS并允许
流量进入网格
。然后,流量通过边车网关(Sidecar Gateway)
流经内部服务。
应用程序使用外部服务
的场景也很常见,也可以直接调用外部服务,或者在某些部署中强制通过专用出口网关(Egress Gateway)
管理离开网格的所有流量。
Istio
具有入口网关
的概念,它扮演网络入口点
的角色,负责保护和控制
来自集群外部的流量对集群内的访问
。
此外,Istio网关
还扮演负载均衡
和虚拟主机路由
的角色。之前提到过的Envoy
,是一个功能强大的服务到服务
的代理
,但它也有负载均衡
和路由
的功能,可代理的流量包括从服务网格外部到其内部运行
的服务,或者从集群内部服务到外部的服务
。
入口网关(IngressGateway)服务
IngressGateway服务
必须监听所需的端口,以便能够将流量转发到IngressGateway pod上。minikube + Istio 1.7.4 部署demo项目这文章中简单的介绍过。
这里是看一眼IngressGateway
中常用的端口
,注意这里只显示了port
和外部访问用的nodePort
,白话理解
就是IngressGateway
中的端口直接访问没有什么用处,因为实际上它selector
的pod
中的container
是istio-proxy
,它需要与VirtualService
和Gateway
结合起来,让集群内知道从外部访问的流量
如何被路由到内部服务网格:
ports:
- name: http2
nodePort: 30000
port: 80
protocol: TCP
- name: https
nodePort: 30443
port: 443
protocol: TCP
- name: mysqlS
nodePort: 30306
port: 3306
protocol: TCP
入口网关部署
当我们创建或更改一个Gateway
或VirtualService
时,Istiod
会检测到这些变更,并将这些变更信息转换为Envoy配置
,然后将Envoy配置信息
发送给相关Envoy代理
,包括内部的Envoy
和IngressGateway中的Envoy
。
注意:请不要混淆IngressGateway和Gateway,Gateway资源只是用于配置IngressGateway的一种Istio的自定义资源。
网关资源
网关(Gateway)资源
用来配置Envoy端口
,前面我们看了IngressGateway
中常用的端口,这些端口是外部流量
通过这些端口
流入,我们可以让这些流量和网格内的服务
互通,记得最早的文章说过,服务网格
的Sidecar
只能是在网格内的流量
才会使用到Envoy
,所以这里需要IngressGateway
让外部流量进入服务网格。
例子:使用Gateway
这里跳过了Service和Deployment的创建,我们可知的是Service绑定了80端口,服务名是test,绑定了Deployment中的一个web。
创建一个Gateway
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: test-gateway
namespace: istio-system
spec:
selector: # 这里就是使用了istio-ingressgateway
istio: ingressgateway
servers:
- hosts: # 可以配置多个host,这里使用通配符不限制host
- "*"
port:
name: http
number: 80
protocol: HTTP
- hosts:
- "*"
port:
name: https
number: 443
protocol: HTTPS
tls:
mode: SIMPLE
# 除了使用privateKey和serverCertificate指定挂载路径也可以直接使用credentialName来指定secret名
# credentialName: istio-ingressgateway-certs
privateKey: /etc/istio/ingressgateway-certs/tls.key
serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
VirtualService使用Gateway
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: test-vs
spec:
gateways: # 可以写很多gateway,我们只使用刚才创建的即可。
- test-gateway.istio-system.svc.cluster.local # 因为命名空间的关系,我们写全了服务名,也可以简写,这里为了方便萌新理解,写全了。
# 之前的文章host我们直接写服务名,因为DNS服务发现可以帮助我们直接解析集群内部服务host,这次我们讲的是外部流量,这里的host写的是真实外网域名哦。
hosts:
- test.example.com
http:
- match:
- uri:
prefix: /
route:
- destination:
host: test # 这里的host是内部服务名,通过DNS服务发现即可解析
port:
number: 80
我们理一下过程
- 外部通过
test.example.com:NodePort
访问,流量先是进入IngressGateway
,被转发到内部80
端口。- 通过
test-gateway
中的servers
,我们知道IngressGateway
的80
端口对应到test-gateway
的http
服务,此时流量就已经算是进入网格了。然后VirtualService
根据test-gateway
和host
做匹配然后配合路由规则
,流量被转发到test
服务。
使用HTTPS协议
目前来说,有部分CDN
提供免费/收费
的HTTPS
服务,对于CDN
来说(反向代理+智能DNS解析),使用HTTPS
分为2种
,一种是Flexible
,另一种是Full
。
Flexible
模式是这样,仅是用户←→CDN→后端(注意CDN到后端的请求会变为HTTP)
。Full
模式是这样:用户←→CDN←→后端
。
如果为了方便我们可以选择Flexible
模式,这样我们后端仅使用HTTP
即可,但是对于用户
来说是HTTPS
。
这里要说的是如果使用Full
模式,我们就必须在后端也使用HTTPS
,且将证书上传到CDN
。
这里的情况就很像上面描述的方式,我们来说说Istio
中如何使用HTTPS
。
创建TLS Secret
创建的secret
必须在istio-system
的namespace
,并且命名也是固定的(istio-ingressgateway-certs
),否则无法在Istio Gateway
中使用。
关于
tls
的secret
的名称为什么是固定
的,其实在istio-ingressgateway
的pod
中就能看见,我选了关键的几段。
注意:看挂载路径,再回头看我们之前gateway中配置的私钥路径和证书路径。
containers:
- name: istio-proxy
image: "docker.io/istio/proxyv2:1.1.0"
imagePullPolicy: IfNotPresent
volumeMounts:
- name: istio-certs
mountPath: /etc/certs
readOnly: true
- name: ingressgateway-certs
mountPath: "/etc/istio/ingressgateway-certs"
readOnly: true
- name: ingressgateway-ca-certs
mountPath: "/etc/istio/ingressgateway-ca-certs"
readOnly: true
volumes:
- name: istio-certs
secret:
secretName: istio.istio-ingressgateway-service-account
optional: true
- name: ingressgateway-certs
secret:
secretName: "istio-ingressgateway-certs"
optional: true
- name: ingressgateway-ca-certs
secret:
secretName: "istio-ingressgateway-ca-certs"
optional: true
Secret用来保存证书和私钥
kubectl create -n istio-system secret tls istio-ingressgateway-certs --key httpbin.example.com/private/httpbin.example.com.key.pem --cert httpbin.example.com/certs/httpbin.example.com.cert.pem
使用HTTPS
我们之前在test-gateway
中已经配置过443
端口的HTTPS
,只是当时没有key和证书
,现在我们创建完了secret
,就可以使用它了。
创建Deployment和Service
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: httpbin
labels:
app: httpbin
spec:
replicas: 1
template:
metadata:
labels:
app: httpbin
version: v1
spec:
containers:
- image: docker.io/citizenstig/httpbin
imagePullPolicy: IfNotPresent
name: httpbin
prots:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata: httpbin
spec:
ports:
- name: http
port: 8080
selector:
app: httpbin
# ***创建VirtualService***
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin-vs
spec:
gateways: # gateway没变
- test-gateway.istio-system.svc.cluster.local
hosts: # 为了区分 我们新建了一个VirtualService并且HOST也不同了
- httpbin.example.com
http: # 网关在接收到https请求后我们会转为http转发到后端
- match:
- uri:
prefix: /
route:
- destination:
host: httpbin # 这里的host是内部服务名,通过DNS服务发现即可解析
port:
number: 8080
为多个主机配置TLS入口网关
之前的yaml注释中写了
除了使用privateKey
和serverCertificate
指定挂载路径也可以直接使用credentialName
来指定secret
名,这样我们不通过修改或添加ingress-gateway
的挂载也可以直接使用新的tls secret
。
Gateway
资源清单中通过不同的host
使用不同的证书。
但是此时VirtualService
就需要分开建立,因为需要每个host对应一个VirtualService
。
例子:
先创建一个名为nginx-certs
的tls secret
。
kubectl create -n istio-system secret tls nginx-certs --key nginx-certs/private/nginx.example.com.key.pem --cert nginx-certs/certs/nginx.example.com.cert.pem
修改gateway资源
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: test-gateway
namespace: istio-system
spec:
selector: # 这里就是使用了istio-ingressgateway
istio: ingressgateway
servers:
- hosts: # 可以配置多个host,这里使用通配符不限制host
- "*"
port:
name: http
number: 80
protocol: HTTP
- hosts:
# 这里之前是* 因为我们就一个域名,所以不需要限制
# 现在改为了固定的httpbin.example.com 使用之前的证书
- httpbin.example.com
port:
name: https
number: 443
protocol: HTTPS
tls:
mode: SIMPLE
# 除了使用privateKey和serverCertificate指定挂载路径也可以直接使用credentialName来指定secret名
# credentialName: istio-ingressgateway-certs
privateKey: /etc/istio/ingressgateway-certs/tls.key
serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
- hosts:
# 这里是使用nginx的证书 域名不同哦
- nginx.test-nginx.com
port:
name: https
number: 443
protocol: HTTPS
tls:
mode: SIMPLE
# 除了使用privateKey和serverCertificate指定挂载路径也可以直接使用credentialName来指定secret名
credentialName: nginx-certs
新建一个nginx的VirtualService
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: nginx-vs
spec:
gateways: # gateway没变
- test-gateway.istio-system.svc.cluster.local
hosts: # 注意我们区分了hosts
- nginx.test-nginx.com
http: # 网关在接收到https请求后我们会转为http转发到后端
- match:
- uri:
prefix: /
route:
- destination:
host: nginx
port:
number: 80
注意:我跳过了Nginx的Deployment和Service的创建。
关于基于SNI的HTTPS路由
SNI
的意思是Server Name Indication
,客户端发出SSL
请求中的ClientHello
阶段),就提交Host
信息,使得服务器能够切换到正确的域并返回相应的证书。
它的作用是可以在相同的IP
上使用不同的证书
,比如www.test.com和www.test2.com各使用了一个证书,如果没有SNI
支持,那么无论使用哪个域名访问,只会使用其中一个证书。
我们如果不使用
SNI路由
,那么在建立VirtualService
时就需要几个域名
就建几个VirtualService(因为大多数情况我们的match中的prefix是/,如果我们的hosts同时写了这些域名,我们只能靠match来区分了)
为了区分,我们重建一个Gateway
spec.servers[].tls.mode
我们改为了PASSTHROUGH
,意义在于 我们不在网关这里使用证书
了,表示按照原样传递入口流量而不终止TLS
,也就是说进入这个网关的HTTPS
流量,我们原样传到后端
,这就需要在容器中去配置TLS证书
,我们需要配置到nginx
里。
关于在nginx里配置证书我这里跳过了,不然yaml太多了。
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: sni-gateway
namespace: istio-system
spec:
selector: # 这里就是使用了istio-ingressgateway
istio: ingressgateway
servers:
# 之前我们是写了hosts,分别不同的域名用了不同的证书,现在我们写在一起,因为我们的tls mode变了
- hosts:
- "nginx.test1-nginx.com"
- "nginx.test2-nginx.com"
port:
name: https
number: 443
protocol: HTTPS
tls:
mode: PASSTHROUGH
VirtualService使用sni中的host转发到后端
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: nginx-sni-test
spec:
hosts:
- nginx.test1-nginx.com
- nginx.test2-nginx.com
gateways:
- sni-gateway.istio-system.svc.cluster.local
tls: # 之前我们使用http原因是我们只在网关层使用TLS协议
- match:
- sni_hosts:
- nginx.test1-nginx.com
port: 443
route:
- destination:
host: nginx1
port:
number: 443
- match:
- sni_hosts:
- nginx.test2-nginx.com
port: 443
route:
- destination:
host: nginx2
port:
number: 443
出口流量管理
出口流量网关
出口网关
使用起来其实也很简单,定义一个VirtualService
引导对目标的访问转发到我们建立的egressgateway
即可,再引导对egressgateway的访问
,我们转发到具体的目标地址
。
我们现在使用Egress Gatway
进行HTTPS流量透传(这是由应用程序发起的TLS)。
为了简单一些,我们不开启TLS双向认证(因为双向认证我会放在安全篇写),直接进行透传即可,我们配置一个出口网关。
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: istio-egressgateway
spec:
selector:
istio: egressgateway
servers:
- port:
number: 80
name: http
protocol: http
hosts:
- istio.io
- port:
number: 443
name: https
protocol: TLS
hosts:
- istio.io
tls:
mode: PASSTHROUGH
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
host: istio-egressgateway.istio-system.svc.cluster.local
subsets:
- name: test # 因为流策默认是轮训 ,我们可以不显式的写出来
定义一个VirtualService
,配置对istio.io
的访问80是HTTP协议,443为TLS协议。
如果是网格内对外发出
的流量(80和443)
,会被转发到istio-egressgateway
。
然后判断如果是istio-egressgateway
再对外发出,就会被转发到istio.io
本身的域名地址。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: direct-through-egress-gateway
spec:
hosts:
- istio.io
gateways:
- istio-egressgateway
- mesh
http:
- match:
- gateways:
- mesh
port: 80
route:
- destination:
host: istio-egressgateway.istio-system.svc.cluster.local
subset: test
port:
number: 80
weight: 100
- match:
- gateways:
- istio-egressgateway
port: 80
route:
- destination:
host: istio.io
port:
number: 80
weight: 100
tls:
- match:
- gateways:
- mesh
port: 443
sni_hosts:
- istio.io
route:
- destination:
host: istio-egressgateway.istio-system.svc.cluster.local
subset: test
port:
number: 443
- match:
- gateways:
- istio-egressgateway
port: 443
sni_hosts:
- istio.io
route:
- destination:
host: istio.io
port:
number: 443
weight: 100
Service Entry(服务入口)
在王夕宁
的书中他写的是服务条目
,可能是因为ServiceEntry
允许将其他条目
添加到Istio
的内部服务注册表
中。
字段 | 类型 | 描述 | 必须的 |
---|---|---|---|
hosts | string[] | 与ServiceEntry关联的主机。可以是带有通配符前缀的DNS名称。 主机字段用于在VirtualServices和DestinationRules中选择匹配的主机。 对于HTTP通信,HTTP Host / Authority标头将与hosts字段匹配。 对于包含服务器名称指示(SNI)的HTTP或TLS流量,SNI值将与主机字段匹配。 注意1:当resolution设置为DNS类型且未指定端点时,主机字段将用作将流量路由到的端点的DNS名称。 注意2:如果主机名与另一个服务注册表(例如Kubernetes)中的服务名称匹配,该服务注册表也提供自己的一组端点,则ServiceEntry将被视为现有Kubernetes服务的装饰器。如果适用,服务条目中的属性将添加到Kubernetes服务。当前,将仅考虑以下附加属性istiod: subjectAltNames:除了验证与服务Pod相关联的服务帐户的SAN外,还将验证此处指定的SAN。 | 是 |
addresses | string[] | 与服务关联的虚拟IP地址。可以是CIDR前缀。HTTP通信,生成的路线的配置将包括两个HTTP路由域addresses和hosts字段值和目的地将基于HTTP Host / Authority标头进行标识。如果指定了一个或多个IP地址,并且如果目标IP与地址字段中指定的IP / CIDR匹配,则传入的流量将被标识为属于该服务。如果“地址”字段为空,则仅根据目标端口识别流量。在这种情况下,网格中的任何其他服务都不得共享正在访问服务的端口。换句话说,Sidecar将充当简单的TCP代理,将指定端口上的传入流量转发到指定的目标端点IP /主机。该字段不支持Unix域套接字地址。 | 否 |
ports | Port[] | 与外部服务关联的端口。如果端点是Unix域套接字地址,则必须只有一个端口。 | 是 |
location | Location | 指定是将服务视为网格外部还是网格的一部分。 | 否 |
resolution | Resolution | 主机的服务发现模式。对于没有随附IP地址的TCP端口,将resolution模式设置为NONE时必须小心。在这种情况下,将允许到该端口上任何IP的流量(即0.0.0.0:)。 | 是 |
endpoints | WorkloadEntry[] | 与服务关联的一个或多个端点。只能指定endpoints或之一 workloadSelector。 | 否 |
workloadSelector | WorkloadSelector | 仅适用于MESH_INTERNAL服务。只能指定endpoints或之一 workloadSelector。WorkloadEntry根据标签选择一个或多个Kubernetes Pod或VM工作负载(使用指定 )。WorkloadEntry代表VM的对象应在与ServiceEntry相同的名称空间中定义。 | 否 |
exportTo | string[] | 此服务导出到的名称空间列表。导出服务允许它被其他名称空间中定义的边车,网关和虚拟服务使用。此功能为服务所有者和网格管理员提供了一种机制,可以控制跨名称空间边界的服务的可见性。 如果未指定名称空间,则默认情况下会将服务导出到所有名称空间。 价值 ”。” 保留,并定义导出到声明服务的同一名称空间。类似地,值“ *”保留并定义到所有命名空间的导出。 对于Kubernetes服务,可以通过将注释“ networking.istio.io/exportTo”设置为以逗号分隔的名称空间名称列表来实现等效效果。 注意:在当前版本中,该exportTo值限制为“.” 或“ *”(即当前名称空间或所有名称空间)。 | 否 |
subjectAltNames | string[] | 如果指定,则代理将验证服务器证书的使用者备用名称是否与指定值之一匹配。 注意:当将工作负载条目与工作负载选择器一起使用时,在工作条目中指定的服务帐户也将用于派生应验证的其他使用者备用名称。 | 否 |
Location
名称 | 描述 |
---|---|
MESH_EXTERNAL | 表示服务在网格外部。通常用于指示通过API使用的外部服务。 |
MESH_INTERNAL | 表示服务是网格的一部分。通常用于指示在扩展服务网格以包括非托管基础架构的过程中显式添加的服务(例如,添加到基于Kubernetes的服务网格的VM)。 |
Resolution
名称 | 描述 |
---|---|
NONE | 假定传入连接已经解析(到特定的目标IP地址)。通常使用诸如IP表REDIRECT / eBPF之类的机制通过代理路由此类连接。在执行与路由相关的任何转换之后,代理将把连接转发到连接绑定到的IP地址。 |
STATIC | 使用端点中指定的静态IP地址(请参见下文)作为与服务关联的后备实例。 |
DNS | 在请求处理期间,尝试通过查询环境DNS来解析IP地址。如果未指定端点,则代理将解析未使用通配符的主机字段中指定的DNS地址。如果指定了端点,则将解析端点中指定的DNS地址,以确定目标IP地址。DNS解析不能与Unix域套接字端点一起使用。 |
举个栗子:
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: external-svc-https
spec:
hosts:
- api.dropboxapi.com
- www.googleapis.com
- api.facebook.com
location: MESH_EXTERNAL
ports:
- number: 443
name: https
protocol: TLS
resolution: DNS # 如果访问上面3个host(属于外部服务),使用dns解析
再来个栗子:
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: external-svc-mongocluster
spec:
hosts:
- mymongodb.somedomain # 因为我们不是HTTP服务,实际上hosts中的dns名称会被忽略,这种情况下会使用endpoints中的address以及ports来甄别服务调用
addresses:
- 172.20.10.1/24 # 在列表范围内的IP的访问会被认定属于这一服务,因为hosts会被忽略,如果我们不定义address,那么服务甄别就只能靠下面的ports了
ports:
- number: 27018
name: mongodb
protocol: MONGO
location: MESH_INTERNAL # 表示服务属于网格内部
resolution: STATIC # 静态方式解析
endpoints: # 一个或多个关联到这个服务的endpoint
- address: 2.2.2.2
- address: 3.3.3.3
HTTP流量强制升级为TLS
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: istio-io
spec:
hosts:
- istio.io
ports:
- number: 80
name: http
protocol: HTTP
- number: 443
name: https
protocol: HTTPS
resolution: DNS
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: istio-io
spec:
hosts:
- istio.io
http:
- match:
- port: 80 # 如果对istio.io访问80端口
route:
- destination: # 将它转发到443端口,但是注意,此时协议还是HTTP
host: istio.io
port: 443
weight: 100
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: istio-io
spec:
host: istio.io
trafficPolicy:
loadBanlancer:
simple: ROUND_ROBIN
portLevelSettings:
- port:
number: 80 # 目标规则定义了80端口
tls: SIMPLE # 发起到上游端点的TLS连接,也就是说我们是通过流量策略来将对istio.io 80端口的HTTP协议升级成TLS协议。
解释:我们对http://istio.io
进行访问,VirtualService
将端口80
上的请求重定向到目标端口443
,在此之前DestinationRule
将对端口80
上的HTTP
请求执行TLS origination
,因此我们访问http://istio.io
直接就会返回200
,不会再进行重定向后跳转,只需要访问一次即可。
关于DestinationRule.trafficPolicy
的东西,会在Istio
的流量治理
中细说。