首先我们来看一下Istio大概是个什么东西呢?
我们经常听到的,和它绑定的词汇就是service mesh,但是如果又要追溯service mesh,然后看的越来越多,搞得自己都迷糊了。所以,我们就看Istio。
Istio是架构与Kubernetes之上的一个服务治理架构,我们可以看一下它在官网上的架构图。
Istio在逻辑上分为数据平面和控制平面。可以在图上看到控制平面,它负责了路由,策略配置,收集信息,身份认证等多方面的功能;而数据平面就是下方的,pod集合,每个pod中除了我们部署的服务容器外,还包含一个proxy容器,它是由Envoy实现的,控制层的那些功能的实现,都要依托于它。
接下来,我们不扯那些概念,理念,开始的时候,看的越多越懵。我们从实操开始,一点一点讲起。在进行实操之前,首先你需要有一定的Kubernetes的知识储备,不然后面的内容仍然是云里雾里。这里只讲Istio,所以如果没有Kubernetes的相关知识,自己去补。
首先,我们需要做什么呢?当然是启动服务啊,连服务都启动不了,要它何用?
所以我们就从一个简单服务启动开始。我们需要一个YAML文件,用于定义k8s中的资源。
我们先写一个最简单的,部署单一服务。
apiVersion: v1
kind: Service
metadata:
name: route-guide
labels:
app: route-guide
spec:
ports:
- name: http
port: 8848
selector:
app: route-guide
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: route-guide-v1
spec:
replicas: 1
template:
metadata:
labels:
app: route-guide
version: v1
spec:
containers:
- image: yubotao/route-guide:v1
imagePullPolicy: IfNotPresent
name: route-guide
ports:
- containerPort: 8848
---
上面便是一个简单的部署模板,其中的Deployment是k8s中的概念,用于控制pod;Service也是k8s的概念,用于代表后端的pod,是一个抽象,方便服务间的调用。需要注意的是,Deployment中的containerPort和Service中的port需要一致。
Deployment中的replicas就是用于控制pod的数量的。
有了部署模板后,我们需要使用k8s命令及istio命令进行手动部署,如下:
kubectl apply -f <(istioctl kube-inject -f <filename>) [-n <namespace>]
上面的命令含义如下,<>
中的内容是替换内容,[]
中的内容是可选内容,第一个 -f
后的 <
是文件传输的命令,-f
参数表示使用的是文件;-n
参数表示命名空间;kubectl
为k8s的控制命令,istioctl
为istio的控制命令;kube-inject
参数是istio对我们的k8s部署模板文件加入了istio需要的相当部分配置,可以通过 istioctl kube-inject -f
命令,来对比
及
中的内容,来观察istio添加了什么。
新建完成后,我们需要看一下pod的运行状态,保证它们是Running,正常运作。
使用命令 kubectl get pod [-n
来查看。
到此,我们就部署完pod和服务了,服务之间的互相调用,在同一命名空间下,使用serviceName:port的形式即可访问,不同命名空间下,通过serviceName.namespace:port来访问服务。
可以说,到这里,我们就完成了最基本的功能实现了,因为istio自带的服务注册和发现功能让我们可以实现上面的功能。
首先是流量管理,这也是service mesh,或者说istio最值得称道的地方了。
首先需要介绍的就是外部如何访问istio内部的服务。因为istio的内部与外部是隔离的,所以说我们需要一个门,让流量进来,这个门就是gateway。
在讨论gateway之前,我们需要说一下,istio有关路由管理的四种类型模板:VirtualService,DestinationRule,Gateway,ServiceEntry。
这里我分别简单的介绍一下。
VirtualService:定义了在istio服务网格中控制如何路由到一个服务的请求的规则。
DestinationRule:配置了适用于VirtualService路由发生后的请求策略,由服务所有者撰写。
Gateway:描述了一个运行在网格边缘的负载均衡器,用于接受传入或传出的HTTP/TCP连接。
ServiceEntry:允许向Istio的内部服务注册表中添加其他条目,以便网格中自动发现的服务可以访问/路由到这些手动指定的服务。
可能你看了上面的说明还是看不懂,我们还是先来看例子,边看边说。
首先我们回到上文,看一个gateway的模板定义。
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: route-gateway
spec:
selector:
istio: ingressgateway # use Istio default gateway implementation
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: route-virtualservice
spec:
hosts:
- "*"
gateways:
- route-gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
port:
number: 8848
host: route-guide
---
我们看到这里gateway和VirtualService搭配使用。首先,在VirtualService中的gateways对应了Gateway的name;其次两者的hosts也对应(目前来看,直接写”*”是最简单方便地);最后,VirtualService中地http定义了匹配前缀,及路由到的后端服务。这个匹配前缀需要多提一嘴,就是改前缀必须要在后端服务中,比如prefix值为/has/something/,那么,后端服务中就必须要有相应的url何其匹配,且该gateway只能路由到这个路径上;另外,gateway目前只能存在一个,即使不同命名空间,也只能在存在一个,如果出现多个,则最早声明的生效,其余不生效,不知道后续是否会修复这个bug。
讲到这里,应该对VirtualService有个直观的认识了,就是之前的那句话,它控制如何路由到一个服务。
现在我们看到外部访问集群内部,那内部互相访问怎么搞呢?
这个我不说,你可以翻到前面去看看。
接下来我们看看如何从集群内部访问外部。比如我们的服务启动的时候都要连接外部的rds,如果不能从内部访问外部服务,我们的服务就无法启动。
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: rds-entry
spec:
hosts:
- rds地址
ports:
- number: 3306
name: rds
protocol: TCP
之前我们提到,使用ServiceEntry可以访问外部服务,正如我们上面定义的。其中需要注意的地方是hosts填写rds的外部访问地址,域名或者ip(ip没有测试,猜测可以),number对应port端口,其中的protocol需要注意,因为mysql传输时TCP连接,所以协议用的时tcp,如果连接的外部服务用的时HTTP,需要注意修改。
接下来,我们看一下如何路由流量,以及关于服务请求的一些管理。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: istio-route
spec:
hosts:
- istio-route
http:
- route:
- destination:
host: istio-route
subset: v1
weight: 50
- destination:
host: istio-route
subset: v2
weight: 50
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: istio-route
spec:
host: istio-route
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
---
我们可以看到这是一个版本管理的路由选择,可以将请求平均分配给两个版本服务。
这里我们可以看到DestinationRule的作用,正如它的名字,表示目的地。也就是说VirtualService中的
destination:subset 和 DestinationRule 中 subsets 关联,而DestinationRule 中 subsets的version,就和Deployment建立联系了。
此外,我们可以模拟网络延迟
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: istio-route
spec:
hosts:
- istio-route
http:
- fault:
delay:
percent: 100
fixedDelay: 2s
route:
- destination:
host: istio-route
subset: v1
这表示,对于istio-route的请求,100%延迟2s,可以用于模拟网络波动。
当然我们还可以设置熔断,不过在这里就不展开了。
讲到安全,我们需要说的内容就有点多了。首先你需要了解RBAC,然后还要知道k8s中的相关概念。这里还是去繁就简。
我们只看两个方面,一个是服务间开启互相TLS认证,另一个就是RBAC策略。
首先说TLS
,Transport Layer Security(安全传输层协议)。
当我们开启相互TLS认证的时候,服务到服务之间的通信都经过了加密。而对应的密钥/证书是放在Envoy构成的Istio proxy中,由架构中的Citadel完成加解密等。
我们在安装istio的时候,可以以两种方式进行安装,一种是默认没有TLS认证的;另一种是默认带有TLS认证的。
首先我们看一下,如果使用没有TLS认证的安装方式,我们需要开启服务间互相TLS访问应该怎么做:
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "example-1"
namespace: "foo"
spec:
peers:
- mtls:
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "example-1"
namespace: "foo"
spec:
host: "*.foo.svc.cluster.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
我们稍微解释一下,上面的策略表示的是,我们在foo命名空间定义了一个命名空间级的策略,但是这个策略是干什么用的,就需要我们定义一个相应的目标规则(注意该命名空间下如果由其他目标规则,则需要进行相应的调整,尽量保证只有一个);下面的目标规则表示,我们在命名空间foo中的所有服务,都开启了互相TLS认证机制,也即其他命名空间的服务向foo中的服务发送请求时,需要进行TLS认证。
那如果我们想要取消互相TLS认证时(针对默认为开启TLS的安装模式,否则直接删除对应规则和策略即可;另一种情况是,使用服务级的策略覆盖命名空间级的策略),该怎么做呢?
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
name: "example-3"
spec:
targets:
- name: two
这次我们可以看到,我们是直接针对某一服务进行策略的配置。
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "example-3"
spec:
host: two.test-istio.svc.cluster.local
trafficPolicy:
tls:
mode: DISABLE
上述策略很明显,针对的是test-istio命名空间中的two服务,取消了它的互相TLS认证。
那么我们把TLS就简单的介绍到这里吧。
接下来介绍RBAC。
RBAC:基于角色的权限访问控制(Role-Based Access Control)。这个概念我们应该听烂了,有很多框架就是用来做这个的,比如shiro,spring security都可以做。
但是!istio中的RBAC是针对服务及服务管理者而言的,并不是我们应用层面的,但是功能性是一致的。
我们通过定义相应的服务角色(类似于服务的管理者),指定它的访问权限,然后为每个服务绑定相应的服务角色,最后达到通过不同的服务角色来限制对不同服务的访问。
当然,这里面也同样包含了相当多的概念,这里我简单的阐述一下。
apiVersion: v1
kind: ServiceAccount
metadata:
name: test-one
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: test-two
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: one
spec:
replicas: 1
template:
metadata:
labels:
app: one
version: v1
spec:
serviceAccountName: test-one
containers:
- image: yubotao/istio-one:test
imagePullPolicy: IfNotPresent
name: one
ports:
- containerPort: 8180
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: two
spec:
replicas: 1
template:
metadata:
labels:
app: two
version: v1
spec:
serviceAccountName: test-two
containers:
- image: yubotao/istio-two:test
imagePullPolicy: IfNotPresent
name: two
ports:
- containerPort: 8080
如上分别定义了两个ServiceAccounts,并分别部署了服务。
apiVersion: "config.istio.io/v1alpha2"
kind: authorization
metadata:
name: requestcontext
namespace: istio-system
spec:
subject:
user: source.user | ""
groups: ""
properties:
app: source.labels["app"] | ""
version: source.labels["version"] | ""
namespace: source.namespace | ""
action:
namespace: destination.namespace | ""
service: destination.service | ""
method: request.method | ""
path: request.path | ""
properties:
app: destination.labels["app"] | ""
version: destination.labels["version"] | ""
---
apiVersion: "config.istio.io/v1alpha2"
kind: rbac
metadata:
name: handler
namespace: istio-system
spec:
config_store_url: "k8s://"
---
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
name: rbaccheck
namespace: istio-system
spec:
match: destination.namespace == "test-istio"
actions:
- handler: handler.rbac
instances:
- requestcontext.authorization
我们可以看到,上述的定义都是针对命名空间istio-system的,而需要修改的部分就在rule中的match部分,我们需要在哪个命名空间开启RBAC,就在这里的destination.namespace字段填上相应内容。
此时,由于存在RBAC策略,我们可以看到服务之间的访问是不通的,会出现错误。
开启RBAC后,我们看一下它的效果,首先我们测试命名空间级的RBAC控制访问策略;
apiVersion: "config.istio.io/v1alpha2"
kind: ServiceRole
metadata:
name: service-viewer
namespace: test-istio
spec:
rules:
- services: ["*"]
methods: ["*"]
constraints:
- key: "app"
values: ["one","two"]
---
apiVersion: "config.istio.io/v1alpha2"
kind: ServiceRoleBinding
metadata:
name: bind-service-viewer
namespace: test-istio
spec:
subjects:
- properties:
namespace: "istio-system"
- properties:
namespace: "test-istio"
roleRef:
kind: ServiceRole
name: "service-viewer"
上述ServiceRole定义了一个service-viewer服务角色,它可以对两个应用one/two(在constraints字段体现)的所有服务(rules中对应字段)有所有的访问权限方法,方法包含GET,POST,HEAD等,不明确的话,就推荐使用“*”。
而下面的ServiceRoleBinding就是将刚才定义的ServiceRole绑定到test-istio命名空间上,这样就可以保证该命名空间的服务是互通的。
我们看到,上面出现三个名词,ServiceAccount、ServiceRole、ServiceRoleBinding;那么它们之间是什么关系呢?
首先Istio RBAC定义了ServiceRole和ServiceRoleBinding。ServiceRole中的constraints的字段和“authorization”模板中的action部分的properties一一对应。也就是说action中properties有什么,constraints中才能写什么。而ServiceRoleBinding和ServiceRole一一绑定,在roleRef中可以体现出来。而其subjects中的属性必须和“authorization”模板中的subject部分的各属性匹配(“user” or “groups” or one of the “properties”)。所以说,ServiceRole和ServiceRoleBinding离不开启动RBAC的“authorization”模板。
ServiceAccount是k8s中的身份与权限控制,它为pod中的进程提供身份信息,当pod中的进程与apiserver联系时,就带有特定的ServiceAccount。换句话说,如果pod和ServiceAccount做了绑定,那么只有在该ServiceAccount下,才能够访问pod中的资源,否则无法访问。这也就是为什么开启RBAC后,会出现权限问题,拒绝访问服务;而没开启RBAC时,就不存在这些影响。
接下来,我们看一下服务级的访问控制。服务级的访问控制就需要针对每个服务做控制了,以我们上面的例子,需要对one和two分别做访问控制,如果有其他服务在调用链路上,还需要新加策略。
apiVersion: "config.istio.io/v1alpha2"
kind: ServiceRole
metadata:
name: one-viewer
namespace: test-istio
spec:
rules:
- services: ["one.test-istio.svc.cluster.local"]
methods: ["GET"]
---
apiVersion: "config.istio.io/v1alpha2"
kind: ServiceRoleBinding
metadata:
name: bind-one-viewer
namespace: test-istio
spec:
subjects:
- user: "*"
roleRef:
kind: ServiceRole
name: "one-viewer"
这里和命名空间级策略有所不同,ServiceRole没有constraints,ServiceRoleBinding使用的是user,代表每个服务(和服务对应的pod绑定的ServiceAccount)。这里是保证gateway能够访问到one服务。
apiVersion: "config.istio.io/v1alpha2"
kind: ServiceRole
metadata:
name: two-viewer
namespace: test-istio
spec:
rules:
- services: ["two.test-istio.svc.cluster.local"]
methods: ["GET"]
---
apiVersion: "config.istio.io/v1alpha2"
kind: ServiceRoleBinding
metadata:
name: bind-two-viewer
namespace: test-istio
spec:
subjects:
- user: "cluster.local/ns/test-istio/sa/test-one"
roleRef:
kind: ServiceRole
name: "two-viewer"
和上面类似,不多说了。不过,这里在ServiceRoleBinding处做了更精细的设置,在subject中的user里,我们指定了特定的ServiceAccount(它和对应服务的pod进行了绑定。)。在Istio中的服务账户形式为: “spiffe://
;domain是当前的cluster.local
。
到此,我们就基本把istio的常用安全策略等内容讲完了。
这一部分是比较简单的,不需要在应用代码中插入对接代码了,直接可以通过istio完成对应中间件的部署,并且都是现成模板,唯一一点就是,你需要会用相应的软件。
目前提供的有Jaeger、Zipkin(追踪);Prometheus(metric);Grafana(istio监控的图形界面,这个挺不错的);Service Graph(服务间调用关系)等。其中绝大部分中间件都是以Prometheus为基的,所以目前0.8版本默认就安装好Prometheus了。
相关的安装之类的可以看翻译的文档,没什么好说的。
终于,目前关于Istio所暴露出来的常用功能就介绍完了,还有相当的待开发功能,能力有限,暂时没有相关资料,有一点是我比较在意的,就是Istio的Mixer这个概念以及它所提供的功能,这个应该是一个功能比较强大的可插拔组件的功能模块,如果有余力可以研究研究这个。