Service Mesh · Istio · 以实践入门

Photo @ Jez Timms
文 | 三辰
前言
本文源自笔者在学习官方文档、阅读相关博客文章以及参与实践的过程中,系统归纳了相关的知识要点以及个人的思考与体会。重点探讨了 lstio 在实际应用中的各种场景,并从"Sidecar 原理"入手分析"Service Mesh"的技术背景及其发展脉络。旨在为理解微服务技术架构的升级与落地提供可行性的分析框架。
本文不是 Istio 的全部,但是希望入门仅此一篇就够。
概念
聚焦于云原生(CN)的概念时会有一种身临其境的感觉仿佛置身于一个知识的爆炸中心但一旦深入探究每一个概念的核心你会发现这些技术原理其实就在你的日常工作中不断被应用。

图片来源:https://landscape.cncf.io/
关键词:Service Mesh、Istio、Sidecar、Envoy 等。
服务网格
Service Grid(Service Mesh)通常被视为一种过时的概念。其发展与微服务的兴起相伴而来,并非在Kubernetes之前出现。然而,Kubernetes和Istio的出现,则导致了这一概念被认定为更为流行且标准化。
Sidecar 是服务网格技术中的主流设计架构之一,在 Kubernetes 环境中允许多个容器在同一 Pod 运行(相当于同一 cgroup 环境),这一环境为 Sidecar 模式提供了良好的发展基础。
首先看看 Sidecar 的设计:

图片来源于网络
为什么是新瓶旧酒?任何技术的发展都不是凭空地跳跃式发展的。
历史

原始的应用程序--图片来源于网络

独立的网络层--图片来源于网络

出现网络层(4层协议)控制的需求--图片来源于网络

控制逻辑下移到网络层--图片来源于网络
在早期阶段的应用程序伴随着功能的不断升级发展。特别是一个大型项目。各功能模块之间高度关联,并且逐渐增多。这种高度关联导致维护难度显著增加。由于模块间的耦合程度较高。不再愿意修改原有的核心代码。开发周期持续延长。系统整体复杂性呈指数级提升。
于是乎,在程序设计中提出了一种方法:将不同功能独立分配至各自独立的程序(进程)中,并通过降低各模块之间的耦合度以及实现敏捷迭代的方式来提升系统整体效能。

出现新的应用层(7层协议)需求(服务发现、熔断、超时重试等)--图片来源于网络

封装成三方库(服务发现:Dubbo/HSF)--图片来源于网络
困难:
服务被拆分成众多的微服务,最困难的问题就是——调用它自己:
原来认为简单的事情,在程序层面都会被提升到七层以上的远程操作了。那些看似简单相互调用的事情,在网络层面都演变成远程操作的过程了。
原来用于公共工具类的功能现在已经转换成了第三方库SDK等系统,在每个进程中都得以应用时
原本是在内部透明调用时无需进行额外的安全防护措施,在进行代码分离后则需要增加相应的安全防护措施及隔离工作
4、不再是代码即文档,需要维护大量的 API 定义和版本管理。

封装到隔离的进程中代理--图片来源于网络
到这里,独立进程的方式基本成型,即为Sidecar模式。
Sidecar 解决什么问题?
在服务网格架构中存在一个关键概念:微服务间的调用。这种交互关系通常以横向布局呈现,并被技术界称为东西向流量。当这些服务通过公网接口对外提供功能时,则形成了另一种交互模式——这种模式则被称为南北向流量。
Sidecar 的设计就是为了解决微服务互相调用(东西流量)的问题。
先来一张我们比较熟悉的图:

图片来源于网络
Consumer 与 Provider 就是微服务互相调用的一种解决方案。
毫 无疑问, 我们都知道一套中间件解决方案解决了流量问题, 其中图示为 Dubbo 架构
然而,Dubbo中间件的一整套组件是基于SPI机制深入嵌入到运行时内,并且是以一种高度隔离的方式完成这一过程。此外,这种设计也只能限于像Java这样经官方认证的语言环境才能实现服务应用的开发。
小结
归纳一下与东西流量有关的问题:
流量管理涉及多个关键领域包括服务发现机制负载均衡策略路由优化限流措施熔断机制以及容错能力等核心功能;可观测性方面主要涵盖监控系统日志集成分析工具以及性能测量方法等;安全体系则集中于身份验证认证授权策略访问控制以及多因素认证机制等基础保障措施;此外还包括更为复杂的高级功能如动态配置方案故障注入技术和镜像流量管理以实现系统的智能化运维与扩展
相较于现有的其他模式而言,Sidecar的设计思路更加巧妙且推进了一步。基于容器机制实现进程间的隔离性,并采用L7代理协议进行通信的方式下,支持使用任意编程语言构建微服务架构。

图片来源于网络
以下是微服务集群基于Sidecar互相通讯的简化场景:

图片来源于网络
所以回到服务网格的概念上来吧。尽管这个概念与之前有所不同,在逻辑上我们可以将其理解为:所有使用中间件的服务共同构成了一个庞大的服务网格系统。这有助于我们更好地理解整个架构的工作原理。此外,在这种架构下(基于Kubernetes这样的容器技术),我们将复杂的问题处理得更加透明无感。
服务网格(Service Mesh)是应对微服务之间网络问题和可观测性问题的核心标准,并且逐渐趋于统一化。
Service Mesh 是 Kubernetes 支撑微服务能力拼图的最后一块
Istio 和 Envoy
Istio,第一个字母是(ai)。
Istio 实现的服务网格分为数据平面和控制平面。核心能力包括4大块:
1、流量控制(Traffic Management)。
2、安全(Security)。
3、动态规则(Policy)。
4、可观测能力(Observability)。
Envoy 面向数据平面,也就是服务之间调用的代理。
Envoy 是 Istio Service Mesh 中默认的 Sidecar 方案。
Istio 在 Enovy 的基础上按照 Envoy 的 xDS 协议扩展了其控制平面。


Istio基于Envoy实现Service Mesh数据平面--图片来源于网络

Envoy角色--图片来源于网络
Envoy 是一种基于 C++ 开发的高效率代理服务器。与之相当的还包括 Nginx 和 Traefik 服务器。因此可以看出这些替代方案确实存在一定的相似性。
也就是下图中的 Proxy :

图片来源于Istio官网
该系统在控制层主要致力于解决流量管理、安全以及可观察性等关键问题。这与之前讨论过的内容中涉及的数据流量管理相关的问题相吻合。类似于基于配置中心的微服务集群架构设计。具体的实现细节在此从略。
Sidecar注入
之前在讲述服务网格时,仅仅提到了Sidecar设计所扮演的功能及其特点,并未深入探讨其工作原理。
首先是一些预备概念:
1、Sidecar 模式:容器应用模式之一,Service Mesh 架构的一种实现方式
Init 容器:一种初始化容器,在应用程序容器启动前作为基础运行,并用于包含一些应用镜像中不包含的实用工具或安装脚本。
3、iptables:流量劫持是通过 iptables 转发实现的。
Sidecar模式负责处理微服务间的网络通讯(远程调用)。通常用于通信层次的具体实现方案。综上所述,该方法具备以下优势:
1、在微服务应用程序中导入 SDK 类库。
2、网络代理节点(主要通过垂直方向上的API网关或采用本地智能体进行配置),用于执行路径决策逻辑以确定数据传输的具体方向,并将数据发送至指定的目标服务器。
Sidecar容器以形式运行,并与应用容器协同运行;系统性地控制所有应用容器的入站与出站流量。
采用SDK库的方式是一种非常直接的方法,并且其调用机制完全融入了程序运行环境。然而随着编程语言的发展,许多新兴的语言针对特定场景进行了优化设计,而采用SDK库的方式则限于支持其功能的语言,因此无法适应这些新兴技术带来的需求变化。
节点代理的方法是采用某个特定的服务来充当微服务之间的交互中介,在系统中扮演着一个代表角色。然而这种代理者的安全性要求极其高因为其必须能够处理来自各个来源的请求并能够识别每个请求的具体身份信息以确保系统的正常运行
Sidecar模型处于SDK库与节点代理之间的中间形态。它相当于在每个微服务上安装一套独特的代理机制。这样一来每个微服务的Sidecar标识了其特定的身份信息从而确保调用的安全审计需求得以满足。由此看来Sidecar及其关联的应用程序拥有相似的权限设置以保障系统安全

图片来源:https://toutiao.io/posts/uva4uy/preview
以 Istio 为例:
在Istio平台中,在采用Sidecar模式时,在初始化阶段首先运行的是一个名为istio-init的内核容器。该容器专注于单一功能,并通过iptables指令设置Pod的网络路由规则。从而实现了对所有进出该Pod流量的转发拦截。
随后通过Pod中的共享namespace(即网络命名空间)中的loopback(localhost)与Sidecar交互;外部流量经过Sidecar处理后进入微服务
由于它们共用一个Pod,并对其他Pod以及节点代理均保持不可见性;可被视为两容器间共享存储与网络等资源;从广义上说,则认为注入了Sidecar容器的Pod相当于一台独立的主机;两容器则得以共享该主机的各项资源。
下图是具体 iptables 与 Sidecar 之间互作用原理,来源:
_https://jimmysong.io/posts/envoy-sidecar-injection-in-istio-service-mesh-deep-dive/_

具体原理上的细节,我们可以通过实践,慢慢挖掘。
小结
最后给概念章节有个阶段性的总结:

图片来源于网络
所以我们打算卖什么?
实践
铺垫这么多概念,我们可以实操起来了。具体怎么做?从安装 Istio 开始。
准备工作
首先,预备一个Kubernates集群,这里不赘述。
如果是本地测试,Docker-Desktop也可以启动一个单机的k8s集群
装 Istio 的命令行工具 istioctl :
下载 istio-release(包括 istioctl 、示例文件和安装配置)。
curl -sL "https://github.com/istio/istio/releases/download/1.4.2/istio-1.4.2-osx.tar.gz" | tar xz
安装 helm (可选):
从 1.4.0 版本开始,不再使用 helm 来安装 Istio
# helm工具
$ brew install kubernetes-helm
**
**
安装Istio
进入到安装文件的目录,开始将 Istio 安装到 k8s 上。
首先确认 kubectl 连接的正确的 k8s 集群。
选择以下其中一种方式:
方式1 、使用 istioctl 安装
cd istio-1.4.2
# 安装istioctl
cp bin/istioctl /usr/local/bin/ # 也可以加一下PATH
# (可选)先查看配置文件
istioctl manifest generate --set profile=demo > istio.demo.yaml
# 安装istio
istioctl manifest apply --set profile=demo
## 以下是旧版本istio的helm安装方式 ##
# 创建istio专属的namespace
kubectl create namespace istio-system
# 通过helm初始化istio
helm template install/kubernetes/helm/istio-init --name istio-init --namespace istio-system | kubectl apply -f -
# 通过helm安装istio的所有组件
helm template install/kubernetes/helm/istio --name istio --namespace istio-system | kubectl apply -f -
方式2 、使用 helm 安装
## 以下是旧版本istio的helm安装方式 ##
# 创建istio专属的namespace
kubectl create namespace istio-system
# 通过helm初始化istio
helm template install/kubernetes/helm/istio-init --name istio-init --namespace istio-system | kubectl apply -f -
# 通过helm安装istio的所有组件
helm template install/kubernetes/helm/istio --name istio --namespace istio-system | kubectl apply -f -
等待所有的 Istio 组件的容器启动,直到:
$ kubectl get crds | grep 'istio.io' | wc -l
23
如果是阿里云(ACs)集群,在完成Istio服务部署后,在网络层面会自动生成一个特定的SLB节点,并将其配置为转发至Istio提供的虚拟化服务器集合。
**示例:**Hello World
示例代码在源码的 samples 目录中
cd samples/hello-world
注入
Istio Sidecar 的注入有两种方式:自动、手动。
这里先通过 istioctl 命令直接手工inject:
istioctl kube-inject -f helloworld.yaml -o helloworld-istio.yaml
实际上就是通过脚本修改了原文件,增加了:
1、sidecar init容器。
2、istio proxy sidecar容器。
分析
我们可以简单对比一下注入的配置,原文件:
apiVersion: v1
kind: Service
metadata:
name: helloworld
labels:
app: helloworld
spec:
ports:
- port: 5000
name: http
selector:
app: helloworld
---
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
version: v1
name: helloworld-v1
spec:
replicas: 1
selector:
matchLabels:
app: helloworld
version: v1
strategy: {}
template:
metadata:
labels:
app: helloworld
version: v1
spec:
containers:
- image: docker.io/istio/examples-helloworld-v1
imagePullPolicy: IfNotPresent
name: helloworld
ports:
- containerPort: 5000
resources:
requests:
cpu: 100m
---
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
version: v2
name: helloworld-v2
spec:
replicas: 1
selector:
matchLabels:
app: helloworld
version: v2
strategy: {}
template:
metadata:
labels:
app: helloworld
version: v2
spec:
containers:
- image: docker.io/istio/examples-helloworld-v2
imagePullPolicy: IfNotPresent
name: helloworld
ports:
- containerPort: 5000
resources:
requests:
cpu: 100m
可以看到,需要部署两个版本 helloworld-v1/v2 的容器,挂载在同一个服务下。
该方案属于典型的蓝绿部署模式,在实际应用中,在配置Istio后以调节流量分配比例的方式进行处理,在真实生产环境中模拟这一过程。
再来看增加的部分:

这里增加了一部分 Istio 的配置,是 K8s 中的标准做法 annotations 。

此部分可观察到现有服务架构未作任何修改,在原有基础上新增了一个sidecar容器,并包含启动参数与环境变量设置(由于配置顺序的影响因素,在此方案中将args参数排在首位)。具体定义如下:
- args:
- proxy
- sidecar
- ...
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- ...
image: docker.io/istio/proxyv2:1.3.2
imagePullPolicy: IfNotPresent
name: istio-proxy
ports:
- containerPort: 15090
name: http-envoy-prom
protocol: TCP
readinessProbe:
failureThreshold: 30
httpGet:
path: /healthz/ready
port: 15020
initialDelaySeconds: 1
periodSeconds: 2
resources:
limits:
cpu: "2"
memory: 1Gi
requests:
cpu: 100m
memory: 128Mi
securityContext:
readOnlyRootFilesystem: true
runAsUser: 1337
volumeMounts:
- mountPath: /etc/istio/proxy
name: istio-envoy
- mountPath: /etc/certs/
name: istio-certs
readOnly: true
镜像名 docker.io/istio/proxyv2:1.3.2 。
另外一部分,就是 initContainer :
initContainers:
- args:
- -p
- "15001"
- -z
- "15006"
- -u
- "1337"
- -m
- REDIRECT
- -i
- '*'
- -x
- ""
- -b
- '*'
- -d
- "15020"
image: docker.io/istio/proxy_init:1.3.2
imagePullPolicy: IfNotPresent
name: istio-init
resources:
limits:
cpu: 100m
memory: 50Mi
requests:
cpu: 10m
memory: 10Mi
securityContext:
capabilities:
add:
- NET_ADMIN
runAsNonRoot: false
runAsUser: 0
volumes:
- emptyDir:
medium: Memory
name: istio-envoy
- name: istio-certs
secret:
optional: true
secretName: istio.default
部署
$ kubectl apply -f helloworld-istio.yaml
service/helloworld created
deployment.apps/helloworld-v1 created
deployment.apps/helloworld-v2 created
$ kubectl get deployments.apps -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
helloworld-v1 1/1 1 1 20m helloworld,istio-proxy docker.io/istio/examples-helloworld-v1,docker.io/istio/proxyv2:1.3.2 app=helloworld,version=v1
helloworld-v2 1/1 1 1 20m helloworld,istio-proxy docker.io/istio/examples-helloworld-v2,docker.io/istio/proxyv2:1.3.2 app=helloworld,version=v2
并启用一个简单的gateway来监听,便于我们访问测试页面$ kubectl apply -f helloworld-gateway.yaml
gateway.networking.istio.io/helloworld-gateway created
virtualservice.networking.istio.io/helloworld created
部署完成之后,我们就可以通过gateway访问hello服务了:$ curl "localhost/hello"
Hello version: v2, instance: helloworld-v2-7768c66796-hlsl5
$ curl "localhost/hello"
Hello version: v2, instance: helloworld-v2-7768c66796-hlsl5
$ curl "localhost/hello"
Hello version: v1, instance: helloworld-v1-57bdc65497-js7cm
两个版本的 Deployment 都可以随机被访问到
深入探索
接着刚才我们部署好的 hello-world ,我们随着Istio的feature进行探索。
流量控制 - 切流
首先,我们尝试控制一下流量,比如只走v2。参考Traffic Shifting:
_https://istio.io/docs/tasks/traffic-management/traffic-shifting/_
我们可以通过 VirtualService 配置控制版本流量,详情参考:
http://istia作为istio公司的网站域名提供配置文件的参考资料
先查看一下当前 Gateway 和 VirtualService 的配置:
$ kubectl get gw helloworld-gateway -o yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: helloworld-gateway
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
$ kubectl get vs helloworld -o yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: helloworld
spec:
hosts:
- "*"
gateways:
- helloworld-gateway
http:
- match:
- uri:
exact: /hello
route:
- destination:
host: helloworld # short for helloworld.${namespace}.svc.cluster.local
port:
number: 5000
可以看到,在VS平台中转发/Hello路径的请求会被发送至helloworld:5000端口;然而这种简短的写法并不推荐使用;建议采用helloworld.${namespace}.svc.cluster.local这一更为规范的方式进行配置
我们将其中 VirtualService 的配置修改为:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: helloworld
spec:
hosts:
- "*"
gateways:
- helloworld-gateway
http:
- match:
- uri:
exact: /hello
route:
- destination:
host: helloworld.default.svc.cluster.local
subset: v1
weight: 0
- destination:
host: helloworld.default.svc.cluster.local
subset: v2
weight: 100
在 http.route 里增加一个 destination ,并将 v2 的 weight 权重配置到100 。
并增加一个 DestinationRule 对 subset 进行定义。
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: helloworld-destination
spec:
host: helloworld.default.svc.cluster.local
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
然后应用更新:
$ kubectl apply -f helloworld-gateway.yaml
gateway.networking.istio.io/helloworld-gateway unchanged
virtualservice.networking.istio.io/helloworld configured
destinationrule.networking.istio.io/helloworld-destination created
测试一下效果:
$ while true;do sleep 0.05 ;curl localhost/hello;done
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
在讨论流量优化策略时发现一个高效的解决方案:成功割裂流量。值得注意的是,在当前阶段我们主要关注的重点领域主要包括Gateway 、VirtualService和DestinationRule这三个关键组件。让我们回头梳理一下:
Gateway
Gateway 负责处理服务网格边缘,并制定了进出网络负载时使用的域名、端口及相应的通信协议参数。
VirtualService
VirtualService 支持网络路径的控制(涵盖子网/版本权重参数、匹配逻辑以及转发策略等)、能够执行故障注入操作、提供TLS协议。
DestinationRule
DestinationRule 明确路由细节规则包括子集定义、负载均衡策略,并且还可以针对特定端口重新设定详细规则。
**示例:**Bookinfo
前面的例子,通过控制流量权重达到版本切流的目的。
在这一节中, 我们将通过另一个 Bookinfo 实例进一步深入研究其他功能特性。

图片来源于 Istio 官网
本例是一个多实例微服务架构,并且由不同语言开发。
开始
$ cd samples/bookinfo
注入
这次Pod定义比较多,我们打开auto sidecar-injection
$ kubectl label namespace default istio-injection=enabled
开启后,在创建过程中,每个Pod会被自动配置上istio-proxy服务,并根据相应的initContainer设置进行配置。
部署
$ kubectl apply -f platform/kube/bookinfo.yaml
service/details created
serviceaccount/bookinfo-details created
deployment.apps/details-v1 created
service/ratings created
serviceaccount/bookinfo-ratings created
deployment.apps/ratings-v1 created
service/reviews created
serviceaccount/bookinfo-reviews created
deployment.apps/reviews-v1 created
deployment.apps/reviews-v2 created
deployment.apps/reviews-v3 created
service/productpage created
serviceaccount/bookinfo-productpage created
deployment.apps/productpage-v1 created
创建一个Gateway用于查看页面:
$ kubectl apply -f networking/bookinfo-gateway.yaml
gateway.networking.istio.io/bookinfo-gateway created
virtualservice.networking.istio.io/bookinfo created
访问 http://localhost/productpage 页面:

不断刷新可以看到右侧Reviews有三个版本:



流量控制 - 网络可见性
基于前面安装好的 Bookinfo 应用,起一个 Pod 探索一下网络可见性:
$ kubectl run --image centos:7 -it probe
# 请求productpage服务上的接口
[root@probe-5577ddd7b9-rbmh7 /]# curl -sL http://productpage:9080 | grep -o "<title>.*</title>"
<title>Simple Bookstore App</title>
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl www.baidu.com | grep -o "<title>.*</title>"
<title>百度一下,你就知道</title>
在默认情况下,在所有微服务(容器)之间都实现了开放且公共可用的状态,并且每个微服务都能实现与外部网络的连接
接下来,介绍 Sidecar 配置对可见性进行控制。
Sidecar
由于每个容器都内置了Sidecar容器,并运行所有的出入请求。因此,我们利用这个 Sidecar 容器能够方便地对其进行配置。
Sidecar 配置就是 Istio 中专门用于配置 sidecar 之间的网络可见性。
首先,修改全局配置,使用 blocked-by-default 的方式。
$ kubectl get configmap istio -n istio-system -o yaml | sed 's/mode: ALLOW_ANY/mode: REGISTRY_ONLY/g' | kubectl replace -n istio-system -f -
configmap "istio" replaced
$ kubectl get configmap istio -n istio-system -o yaml | grep -n1 -m1 "mode: REGISTRY_ONLY"
67- outboundTrafficPolicy:
68: mode: REGISTRY_ONLY
该配置参数被设置为REGISTRY_ONLY模式,并且这表示容器在默认情况下禁止外部网络的访问,并仅允许与预先注册的ServiceEntry建立连接。
然后,我们设置一个全局性的 Sidecar 配置:
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
name: default
namespace: istio-system
spec:
egress:
- hosts:
- "./*"
- "istio-system/*"
EOF
sidecar.networking.istio.io/default configured
每个 namespace 最多只能有一个没有 workloadSelector 的配置
在 root namespace 中不带 workloadSelector 的配置被视为全局设置,在这种情况下会作用于所有 namespace,默认情况下 root namespace 被设置为 istio-system
这个配置的含义是:
每个container在特定namespace中的outflow(egress)仅能被其所属的namespace或namespace=istio-system中的服务所限制。
egress
我们先测试一下外网连通性, Sidecar 的出流量被称作 egress 流量。
这里需要等待一会生效,或者直接销毁重新部署一个测试容器
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl -v www.baidu.com
* About to connect() to www.baidu.com port 80 (#0)
* Trying 220.181.38.150...
* Connected to www.baidu.com (220.181.38.150) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: www.baidu.com
> Accept: */*
>
* Recv failure: Connection reset by peer
* Closing connection 0
curl: (56) Recv failure: Connection reset by peer
command terminated with exit code 56
效果是:外网已经访问不通。
部署:在这种情况中,我们将需要访问的域名注册到 ServiceEntry 中,并且为每个需要访问的域名添加侧载流量规约规则。
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: baidu
spec:
hosts:
- www.baidu.com
ports:
- number: 80
name: http
protocol: HTTP
resolution: DNS
location: MESH_EXTERNAL
---
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
name: default
spec:
egress:
- hosts:
- "./www.baidu.com"
port:
number: 80
protocol: HTTP
name: http
重新请求,确认恢复了。
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl -v www.baidu.com
* About to connect() to www.baidu.com port 80 (#0)
* Trying 220.181.38.150...
* Connected to www.baidu.com (220.181.38.150) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: www.baidu.com
> Accept: */*
>
< HTTP/1.1 200 OK
< accept-ranges: bytes
< cache-control: private, no-cache, no-store, proxy-revalidate, no-transform
< content-length: 2381
< content-type: text/html
< date: Tue, 15 Oct 2019 07:45:33 GMT
< etag: "588604c8-94d"
< last-modified: Mon, 23 Jan 2017 13:27:36 GMT
< pragma: no-cache
< server: envoy
< set-cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
< x-envoy-upstream-service-time: 21
同样地,容器之间的流量同理:
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl productpage:9080
curl: (56) Recv failure: Connection reset by peer
command terminated with exit code 56
配置上ServiceEntryapiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
name: default
spec:
egress:
- hosts:
- "./www.baidu.com"
- "./productpage.default.svc.cluster.local" # 这里必须用长名称
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: baidu
spec:
hosts:
- www.baidu.com
resolution: DNS
location: MESH_EXTERNAL
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: productpage
spec:
hosts:
- productpage
resolution: DNS
location: MESH_EXTERNAL
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl productpage:9080 | grep -o "<title>.*</title>"
<title>Simple Bookstore App</title>
Notice that the Sidecar configuration without workloadSelector (i.e., not specifying container-specific configurations) can only have one rule, so all rules must be written together.
ingress
下面我们探究容器入流量的配置:
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
name: productpage-sidecar
spec:
workloadSelector:
labels:
app: productpage
ingress:
- port:
number: 9080
protocol: HTTP
defaultEndpoint: 127.0.0.1:10080
egress:
- hosts:
- "*/*"
这个配置的效果是让 productpage 应用的容器收到 9080 端口的 HTTP 请求时,转发到容器内的10080端口。
由于容器内没有监听 10080 ,所以会访问失败。
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl -s productpage:9080
upstream connect error or disconnect/reset before headers. reset reason: connection failure
小结
Sidecar 的示例说明就到这里,这只是一个示例。
egress 配置设置域内流量访问规则时,并非必需在 ingress 处重复;因此 ingressive 配置主要用于设定代理流量的相关规则。比如,在实际操作中我们可以将所有入口流量导入到 sidecar中的监听进程(实施定制开发的安全权限验证等操作),之后再将这些数据传递给下游的服务。
egress 配置设置域内流量访问规则时,并非必需在 ingress 处重复;因此 ingressive 配置主要用于设定代理流量的相关规则。比如,在实际操作中我们可以将所有入口流量导入到 sidecar中的监听进程(实施定制开发的安全权限验证等操作),之后再将这些数据传递给下游的服务。
egress 配置主要侧重于保障服务网格对外部访问的能力,在这种情况下,在设置相关限制时会发现导致应用自身的流量注册大量ServiceEntry的问题。因此,在处理微服务之间的流量信任问题时必须依靠相应的安全机制来加以应对
安全机制
概述

图片来源于Istio官网
Istio 提供包含了南北流量和东西流量两部分的防御机制:
Security by default:采用微服务架构无需编写任何代码,并且不依赖于第三方库来实现自身的安全性
2、Defense in depth:能够与已有的安全体系融合,深度定制安全能力。
Zero-trust Network旨在实现一种安全方案,在其服务网格内部和外部都假设为非安全状态。
下图是 Istio 中每个组件的角色:

图片来源于Istio官网
1、Citadel,证书(CA)管理
2、Sidecar等Envoy Proxy,提供TLS保障
3、Pilot,策略(Policy)和身份信息下发
4、Mixer,认证和审计
策略(Policy)
Istio 支持在运行时实时地配置策略(Policy),支持:
1、服务入流量速率控制。
2、服务访问控制,黑白名单规则等。
3、Header重写,重定向。
也可以自己定制 Policy Adapter 来定义业务逻辑。
TLS
在介绍安全机制之前,有必要先科普一下 TLS 。
SSL(Security Socket Layer, 安全 Socket 层),是介于4层TCP和7层HTTPS之间的协议层面, 其主要任务是确保数据传输的安全性
TLS(Transport Layer Security,传输层安全协议),是在SSL v3的基础上进行了升级的协议,在实际应用中等价于SSL v3.1版本
主要提供:
认证(Transport Authentication),用户与服务器的身份信息,保证数据被正确地发送至目标位置。
2、加密数据,防止数据明文泄露。
3、数据一致性,传输过程不被串改。
Istio 中的安全传输机制都是建立在 TLS 之上的。
更多信息可以查看官方概念,详情参考:
_https://istio.io/docs/concepts/security_
认证(Authentication)与鉴权(Authorization)
这两个词很相近,甚至缩写 auth 都是相同的,所以有时候很容混淆。
在 istioctl 中有两个命令 authn 和 authz ,这样就可以区分它们。
认证与鉴权各自承担什么功能,在后续两节中将详细阐述它们之间的关联。这里先进行说明。
认证 实际上是 鉴权 的必要条件
认证 实际上是 鉴权 的必要条件
认证 实际上是 鉴权 的必要条件
为什么?
1、认证是识别身份(Identification)。
2、鉴权是检查特定身份(Identity)的权限。
这样就很好理解了。二者时常相随,我们常说的比如登录,就是:
1、基于登录机制的cookie来识别访问来源的身份——认证。
然后识别用户身份是否拥有登录系统的权限(或访问特定页面的权限)——鉴权。
那么在Istio体系中,采用mTLS机制实现Authentication功能.那么开启启用后,可以配置相应的AuthorizationPolicy进行访问控制.细节请参考下文.
认证(Authentication)
Istio 中的认证包含两种:
1、Transport Authentication ,传输层认证方式。该方法遵循 mTLS ( Mutual TLS ) 协议,在验证数据流量方向的基础上实现两端数据流的合法交换。
2、原生认证 ,移动端认证。采用 JWT 以及其他技术对南北流量进行校验以确保登录身份的有效性。
**示例:**配置Policy
在本次阶段中参考Task: Authentication Policy这一案例的学习过程。不进行全程复制操作仅关注核心要素。
准备环境:
在这个示例中,默认生成了三个 namespace 包括两个配置为Sidecar的foo和bar类以及一个 legacy类未做特殊配置以作对照
#!/bin/bash
kubectl create ns foo
kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n foo
kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo
kubectl create ns bar
kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n bar
kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n bar
kubectl create ns legacy
kubectl apply -f samples/httpbin/httpbin.yaml -n legacy
kubectl apply -f samples/sleep/sleep.yaml -n legacy
默认情况下,容器之间是互通的(mTLS运行在PRESSIVE_MODE)。
这里通过一个 check.sh 脚本检查连通性:
#!/bin/bash
for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done$ ./check.sh
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 200
sleep.legacy to httpbin.bar: 200
sleep.legacy to httpbin.legacy: 200
打开TLS:
通过全局的 MeshPolicy 配置打开mTLS:
$ kubectl apply -f - <<EOF
apiVersion: "authentication.istio.io/v1alpha1"
kind: "MeshPolicy"
metadata:
name: "default"
spec:
peers:
- mtls: {}
EOF
这时,原本互通的容器访问不通了
执行:
$ ./check.sh
sleep.foo to httpbin.foo: 503
sleep.foo to httpbin.bar: 503
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 503
sleep.bar to httpbin.bar: 503
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 000
command terminated with exit code 56
sleep.legacy to httpbin.legacy: 200
Sidecar 配置成功后将返回 503 错误码。未进行注释的 namespace 中将导致连接被重置(Connection Reset)。
配置托管的 mTLS 能力
接着,通过 DestinationRule ,重新对注入Sidecar的微服务增加 mTLS 能力:
kubectl apply -f - <<EOF
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "default"
namespace: "istio-system"
spec:
host: "*.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
EOF
*.local 配置的作用是适用于所有K8s集群内的跨namespace的数据流量的正常运行。
2、tls.mode=ISTIO_MUTUAL :参考官方文档, 表示完全基于 Istio 管理 mTLS 实现, 其他配置选项无效. 具体配置安排将在稍后讨论.
重新运行 check.sh :
$ ./check.sh
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 503
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 503
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 000
command terminated with exit code 56
sleep.legacy to httpbin.legacy: 200
注意,在遵循前面示例的情况下,默认的 Sidecar Egress 配置会限制仅允许在同一 namespace 内的服务被访问;因此,在这种情况下进行跨 namespace 调用将导致 503 错误:
sleep.foo to httpbin.bar: 503
可以自己试验一下,回顾一下配置:
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
name: default
namespace: istio-system
spec:
egress:
- hosts:
- ./* # <--
- istio-system/*
分析
对比之前的结果,有两点变化:
1、同样注入Sidecar的微服务互相可以访问了(200)。
未配置ns=legacy Sidecar参数的微服务将无法从已配置该参数的服务访问
ns=legacy中的行为仍然不变
变化1:详细阐述了微服务之间采用TLS加密通信的方式,并通过Istio实现了统一管理;在这一过程中我们未曾更改任何服务的代码
变化2:说明服务网格对外部容器也要求具备 TLS 能力;具体而言,在 legacy 环境中未配置侧车(Sidecar)会导致无法成功建立连接。
鉴权(Authorization)
Istio 的鉴权机制的主要依据是启用 mTLS 认证方案,并通过该机制实现所有相关微服务 sidecar 之间的相互访问。
不是必要前提
有一部分鉴权规则是不依赖mTLS的,但是很少。
鉴权基于 istio CRD :AuthorizationPolicy
例如,默认拒绝所有微服务互相访问:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: foo
spec:
需要留意的是,默认全部设置为拒绝将会导致istio-system中的istio-ingressgateway流量访问foo这个namespace的服务也被拒绝。这就意味着我们无法从外部成功地访问到所有的foo服务。因此我们可以考虑修改配置如下:将每个foo资源实例对应的ingress service name字段设置为对应的namespace下的service name。例如将ingress service name字段设置为foo-namespace下的service name
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: foo
spec:
rules:
- from:
- source:
namespaces:
- "istio-system"
AuthorizationPolicy 的规则文档里都已经很详细了,这里就不再赘述。
配置应用后,在任意一个微服务访问另一个微服务时都会触发HTTP 403错误,并且错误信息显示RBAC权限被拒绝
其它
本文仅涵盖Istio流量控制(Traffic Management)与安全机制(Security)中较为基础的内容,在高级功能方面包括限流配置、路由干预等Policy设置以及服务观测(Telemetry)等功能未进行深入介绍。
除此之外,在生产实践中还需要关注的问题包括但不限于:制定并实施高效的运维管理措施(例如升级Istio版本以及配置不同集群的参数设置),在默认情况下完成无信任基础下的安全访问策略配置工作,并基于Istio的安全访问策略开展二次开发延伸工作等其他相关问题均属于生产实践中的关注重点
参考文档
- Istio官方文档
- Istio Handbook
The document introduces the fundamental concepts and principles of Service Mes
h, offering a comprehensive overview of its role in modern distributed systems.
- Pattern Service Mesh
_https://philcalcado.com/2017/08/03/pattern_service_mesh.html_
作者信息:
袁赓拓(本名三辰),现为阿里云智能-计算平台事业部技术专家,在该部门负责Numerical Plus Platform的微服务生态建设项目负责人及DataWorks项目的相关技术负责人。目前专注于_microservices_技术和_Service Mesh_等前沿领域的发展与研究
Tips:
点下“在看”❤️
然后,公众号对话框内发送“CNCF ”,试试手气?????
本期奖品是正版CNCF指尖陀螺 。
#受疫情影响,大量快递服务中断,请您耐心等待。本期抽奖礼品将在所有快递全面恢复后进行派送。感谢您的理解与支持!
