我们知道在服务网格集群中的每个工作负载实例上都会透明地注入一个 Istio sidecar 代理,这个代理拦截负载的出入流量,并根据配置完成相应的流量管理,包括流量、安全、可观测性等等。为了更加细粒度的控制代理的行为,从 1.1 版本开始 Istio 便引入了和服务网格数据面 Sidecar 同名的 Sidecar CRD 资源对象,控制负载上的出入流量以及课访问的目标服务等。
Sidecar 对象描述了 sidecar 代理的配置,sidecar 代理管理与其连接的工作负载的 inbound 和 outbound 流量。默认情况下,Istio 将为网格中的所有 sidecar 代理服务,使其具有到达网格中每个工作负载所需的必要配置,并在与工作负载关联的所有端口上接收流量。Sidecar 资源提供了一种的方法,在向工作负载转发流量或从工作负载转发流量时,微调端口集合和代理将接收的协议,此外,可以限制代理在从工作负载转发 outbound 流量时可以达到的服务集合。
比如我们可以创建一个如下所示的 Sidecar 对象:
apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
name: test-sc
spec:
egress:
- hosts:
- "istio-system/*"
- "default/*"
在上面的 Sidecar 对象中我们指定了 egress 字段,这个字段用于指定 sidecar 代理的出口流量,其中 hosts 字段用于指定 sidecar 代理可以访问的目标服务,这里我们指定了 istio-system/* 和 default/*,意思是我们可以控制 default 命名空间下的 sidecar 代理只可以访问 istio-system 和 default 命名空间下的服务,其他命名空间下的服务则无法访问。
整体上 Sidecar 对象的核心包括四个字段:workloadSelector、ingress 与 egress、outboundTrafficPolicy。
- workloadSelector:这个字段用来指定 sidecar 代理所属的工作负载,可以通过标签来指定,如果没有指定则会应用到当前命名空间下所有的工作负载上(每个命名空间下只能定义一个全局的 Sidecar 对象),如果定义在根命名空间 istio-system 下则会应用到所有命名空间下的工作负载上。需要注意的是如果一个命名空间下存在多个 workloadSelector 的 Sidecar 选中同样的负载,则也会出现问题,所有要注意避免这种情况。
- egress:这个字段用来配置 sidecar 代理对服务网格内部其他服务的访问,如果没有配置则默认命名空间下的所有服务都可以访问,如果配置了则只能访问配置的服务。该字段下面可以配置如下几个参数:
- hosts:这是一个必选的字段,表示监听器对应的服务地址,格式为 /,可以对 namespace、FQDN 进行通配符匹配,比如 default/* 表示 default 命名空间下的所有服务,*/foo 表示所有命名空间下的 foo 服务,*/* 表示允许目标是任意命名空间下的任意服务,~/* 表示禁止目标是任意命名空间下的任意服务。
- port:监听器关联的端口。
- bind:监听器绑定的地址。
- captureMode:配置流量捕获模式,可以是 DEFAULT、IPTABLES、NONE 三种模式,默认是 DEFAULT,DEFAULT 模式表示使用环境默认的流量捕获规则;IPTABLES 模式表示基于 iptables 规则转发的流量,NONE 模式表示没有流量拦截。
- ingress:这个字段用来配置 sidecar 代理对应工作负载的入流量控制。该字段下面可以配置如下几个参数:
-
port:这是一个必选的字段,表示监听器对应的端口。
-
bind:监听器绑定的地址。
-
captureMode:配置流量捕获模式,与 egress 中的 captureMode 字段一样。
-
defaultEndpoint:也是必选字段,表示流量的转发目标地址,比如 127.0.0.1:port 或者 0.0.0.0:port。
-
outboundTrafficPolicy:这个字段用来配置 sidecar 代理对应工作负载的出流量控制,该字段有两种访问配置:
-
ALLOW_ANY:表示允许访问任意服务,sidecar 代理在拦截到这个出流量后,会直接透传。
-
REGISTRY_ONLY:sidecar 代理会拦截所有的出口流量,只允许服务网格内部服务可以被访问,对于外部服务需要使用 ServiceEntry 注册才可以被访问。
Sidecar 对象可以定义在根命名空间 istio-system 下,这样就会应用到所有命名空间下的工作负载上,比如我们可以创建一个如下所示的 Sidecar 对象:
# global-sidecar.yaml
apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
name: default
namespace: istio-system
spec:
egress:
- hosts:
- "./*"
上面的这个 Sidecar 对象定义在 istio-system 命名空间下,这样就会应用到所有命名空间下的工作负载上,其中 egress 字段中的 hosts 字段指定了可以访问的服务,这里我们指定了 "./*",表示限制整个服务网格中的服务只能访问本命名空间的服务。在实践中我们推荐使用这种方式在全局范围定义一个统一的 Sidecar 规则,然后在特定的命名空间下再定义一个 Sidecar 对象来覆盖全局的 Sidecar 规则。
比如我们可以在 default 命名空间下创建一个如下所示的 Sidecar 对象来覆盖上面全局的这个对象:
# default-sidecar.yaml
apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
name: default
namespace: default
spec:
egress:
- hosts:
- "foo/*"
这个对象就允许 default 命名空间的服务可以访问 foo 命名空间的服务。
同样我们还可以使用 workloadSelector 字段来指定 sidecar 代理所属的工作负载,比如我们可以创建一个如下所示的 Sidecar 对象:
# default-sidecar.yaml
apiVersion: networking.istio.io/v1beta1
kind: Sidecar
metadata:
name: default
namespace: default
spec:
workloadSelector:
labels:
app: bar
egress:
- hosts:
- "bar/foo-api"
上面的这个对象只会应用到 app: bar 标签的工作负载上,并覆盖以上命名空间级别的规则,使得 default 命名空间下面的 app: bar 标签的工作负载只能访问 bar 命名空间下面的 foo-api 服务。
接下来我们使用 sleep 和 httpbin 应用来进行测试说明,将这两个应用部署到 default 和 other 两个命名空间下面:
kubectl create ns other
kubectl label ns other istio-injectinotallow=enabled
kubectl apply -f samples/sleep/sleep.yaml -n default
kubectl apply -f samples/sleep/sleep.yaml -n other
kubectl apply -f samples/httpbin/httpbin.yaml -n default
kubectl apply -f samples/httpbin/httpbin.yaml -n other
默认情况下,注入了 Istio 的工作负载会进行全网格的传播,假设 default 和 other 两个不相干的命名空间,other 中有大量的服务,而 default 中只有几个,因为路由传播的关系,default 命名空间中的工作负载,其 sidecar 代理中也会带上 other 命名空间中的路由信息。例如:
$ istioctl proxy-config clusters sleep-9454cc476-jfw97 |grep other
httpbin.other.svc.cluster.local 8000 - outbound EDS
sleep.other.svc.cluster.local 80 - outbound EDS
可以看到,在 default 命名空间中的 Pod,保存了其它命名空间中的路由信息。这不管是对内存消耗还是路由控制来说,都会造成一定浪费,这个时候我们就可以定义一个 Sidecar 资源,限制 sleep 服务只访问同一命名空间的其他服务,如下所示:
# sleep-sidecar.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
name: sleep
spec:
workloadSelector:
labels:
app: sleep
egress:
- hosts:
- "default/*"
直接应用上面的资源对象即可:
$ kubectl apply -f sleep-sidecar.yaml
$ kubectl get sidecar
NAME AGE
sleep 16s
这个时候可以看到在 sleep 应用中只剩下了本命名空间之内的服务了:
$ istioctl proxy-config clusters sleep-9454cc476-jfw97
SERVICE FQDN PORT SUBSET DIRECTION TYPE DESTINATION RULE
80 - inbound ORIGINAL_DST
BlackHoleCluster - - - STATIC
InboundPassthroughClusterIpv4 - - - ORIGINAL_DST
PassthroughCluster - - - ORIGINAL_DST
agent - - - STATIC
httpbin.default.svc.cluster.local 8000 - outbound EDS
kubernetes.default.svc.cluster.local 443 - outbound EDS
prometheus_stats - - - STATIC
sds-grpc - - - STATIC
sleep.default.svc.cluster.local 80 - outbound EDS
xds-grpc - - - STATIC
zipkin - - - STRICT_DNS
现在我们可以在 sleep 应用中去访问下 httpbin 的应用:
$ kubectl exec -it sleep-9454cc476-jfw97 -- curl http://httpbin.default:8000/ip
{
"origin": "127.0.0.6"
}
可以看到 default 命名空间下面的应用可以正常访问,那么对于 other 命名空间下面的服务正常就不能访问了。
$ kubectl exec -it sleep-9454cc476-jfw97 -- curl http://httpbin.other.svc.cluster.local:8000/ip
可以看到 default 命名空间下面的应用无法访问 other 命名空间下面的服务了。
Istio 默认情况下,服务网格内部的所有数据面代理都通过 xDS 从控制面获取全量的配置,这种方式在数据面代理数量较少的情况下是没有问题的,但是当数据面代理数量较多的大规模服务网格的场景下,这种方式显然会造成性能问题,全量的配置会引起数据面代理的内存暴涨,所以 Sidecar 对象是非常有必要的,通过 Sidecar 对象只维护少量依赖服务的配置,可以大大减少无用的内存消耗,所以在生产环境中我们推荐大家使用 Sidecar 对象来控制数据面代理的配置。